Browse code
initial commit
fiddlerwoaroof authored on 15/08/2016 08:26:25
Showing 9 changed files
Showing 9 changed files
- .gitignore
- README.md
- alimenta-feed-archive.asd
- archive-root/index.html
- archive-root/purify.js
- archive-root/run.js
- archive-root/style.css
- archive-root/vue.js
- feed-archive.lisp
0 | 2 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,16 @@ |
1 |
+Dump a collection of feeds to a directory. |
|
2 |
+ |
|
3 |
+To use, clone alimenta ( https://github.com/fiddlerwoaroof/alimenta ), my fork of chronicity ( |
|
4 |
+https://github.com/fiddlerwoaroof/chronicity ) and my utility library ( |
|
5 |
+https://github.com/fiddlerwoaroof/fwoar.lisputils ) to your local-projects directory and then use quicklisp to |
|
6 |
+load alimenta-feed-archive. Then, execute something like: |
|
7 |
+ |
|
8 |
+``` |
|
9 |
+(alimenta.feed-archive::init-feeds '("http://example.com/feed") "/home/me/my_feeds") |
|
10 |
+``` |
|
11 |
+ |
|
12 |
+Next create the directory specified in the last argument and copy the contents of the archive-root directory |
|
13 |
+to it. Then run (in lisp) `(alimenta.feed-archive::archive-feeds)` and it should populate that directory with |
|
14 |
+your feeds. Then, if that directory is accessible via a web server, you should be able to navigate to it and |
|
15 |
+browse through your feeds. |
|
16 |
+ |
0 | 17 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,15 @@ |
1 |
+(asdf:defsystem #:alimenta-feed-archive |
|
2 |
+ :description "Dump a collection of feeds to a directory" |
|
3 |
+ :author "Fiddlerwoaroof <fiddlerwoaroof@howit.is>" |
|
4 |
+ :license "MIT" |
|
5 |
+ :depends-on (#:alexandria |
|
6 |
+ #:alimenta |
|
7 |
+ #:fwoar.lisputils |
|
8 |
+ #:ironclad |
|
9 |
+ #:local-time |
|
10 |
+ #:serapeum |
|
11 |
+ #:ubiquitous |
|
12 |
+ #:yason) |
|
13 |
+ :serial t |
|
14 |
+ :components ((:file "feed-archive"))) |
|
15 |
+ |
0 | 16 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,51 @@ |
1 |
+<!DOCTYPE html> |
|
2 |
+<html lang="en"> |
|
3 |
+<head> |
|
4 |
+ <meta charset="UTF-8"> |
|
5 |
+ <title></title> |
|
6 |
+ <base href="<XXX: YOUR BASE URL HERE>" /> |
|
7 |
+ <link rel="stylesheet" href="style.css" /> |
|
8 |
+</head> |
|
9 |
+<body> |
|
10 |
+ <div id="container" v-cloak> |
|
11 |
+ <div class="left"> |
|
12 |
+ <span class="pull-time">{{pull_time}}</span> |
|
13 |
+ <!--<div v-for="url in feed_urls">{{url}}</div>--> |
|
14 |
+ <div v-for="feed in feeds"> |
|
15 |
+ <a v-on:click="get_feed(feed.path)"> |
|
16 |
+ <span class="feed_url">{{feed.title}} <{{feed.url}}></span> |
|
17 |
+ </a> |
|
18 |
+ </div> |
|
19 |
+ |
|
20 |
+ <div v-if="current_feed.title !== null" class="current_feed"> |
|
21 |
+ <h2>{{current_feed.title}}</h2> |
|
22 |
+ <h3>{{current_feed.link}}</h3> |
|
23 |
+ <div>{{current_feed.description}}</div> |
|
24 |
+ <ul> |
|
25 |
+ <li v-for="item in current_feed.items | orderBy 'path' -1"> |
|
26 |
+ <a v-on:click="get_item(item.path)"> |
|
27 |
+ <span class="feed_url">{{item.title}}</span> |
|
28 |
+ </a> |
|
29 |
+ </li> |
|
30 |
+ </ul> |
|
31 |
+ </div> |
|
32 |
+ </div> |
|
33 |
+ <div class="right"> |
|
34 |
+ <div v-if="current_item.title !== null"> |
|
35 |
+ <h2> |
|
36 |
+ <a href="{{current_item.link}}">{{current_item.title}}</a> |
|
37 |
+ </h2> |
|
38 |
+ <h3>{{current_item.author}}</h3> |
|
39 |
+ <div>{{{ item_content }}}</div> |
|
40 |
+ </div> |
|
41 |
+ </div> |
|
42 |
+ </div> |
|
43 |
+ |
|
44 |
+ <script> |
|
45 |
+ baseUrl = 'current' |
|
46 |
+ </script> |
|
47 |
+ <script src="vue.js"></script> |
|
48 |
+ <script src="purify.js"></script> |
|
49 |
+ <script src="run.js"></script> |
|
50 |
+</body> |
|
51 |
+</html> |
0 | 52 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,863 @@ |
1 |
+;(function(factory) { |
|
2 |
+ 'use strict'; |
|
3 |
+ /* global window: false, define: false, module: false */ |
|
4 |
+ var root = typeof window === 'undefined' ? null : window; |
|
5 |
+ |
|
6 |
+ if (typeof define === 'function' && define.amd) { |
|
7 |
+ define(function(){ return factory(root); }); |
|
8 |
+ } else if (typeof module !== 'undefined') { |
|
9 |
+ module.exports = factory(root); |
|
10 |
+ } else { |
|
11 |
+ root.DOMPurify = factory(root); |
|
12 |
+ } |
|
13 |
+}(function factory(window) { |
|
14 |
+ 'use strict'; |
|
15 |
+ |
|
16 |
+ var DOMPurify = function(window) { |
|
17 |
+ return factory(window); |
|
18 |
+ }; |
|
19 |
+ |
|
20 |
+ /** |
|
21 |
+ * Version label, exposed for easier checks |
|
22 |
+ * if DOMPurify is up to date or not |
|
23 |
+ */ |
|
24 |
+ DOMPurify.version = '0.8.2'; |
|
25 |
+ |
|
26 |
+ /** |
|
27 |
+ * Array of elements that DOMPurify removed during sanitation. |
|
28 |
+ * Empty if nothing was removed. |
|
29 |
+ */ |
|
30 |
+ DOMPurify.removed = []; |
|
31 |
+ |
|
32 |
+ if (!window || !window.document || window.document.nodeType !== 9) { |
|
33 |
+ // not running in a browser, provide a factory function |
|
34 |
+ // so that you can pass your own Window |
|
35 |
+ DOMPurify.isSupported = false; |
|
36 |
+ return DOMPurify; |
|
37 |
+ } |
|
38 |
+ |
|
39 |
+ var document = window.document; |
|
40 |
+ var originalDocument = document; |
|
41 |
+ var DocumentFragment = window.DocumentFragment; |
|
42 |
+ var HTMLTemplateElement = window.HTMLTemplateElement; |
|
43 |
+ var NodeFilter = window.NodeFilter; |
|
44 |
+ var NamedNodeMap = window.NamedNodeMap || window.MozNamedAttrMap; |
|
45 |
+ var Text = window.Text; |
|
46 |
+ var Comment = window.Comment; |
|
47 |
+ var DOMParser = window.DOMParser; |
|
48 |
+ |
|
49 |
+ // As per issue #47, the web-components registry is inherited by a |
|
50 |
+ // new document created via createHTMLDocument. As per the spec |
|
51 |
+ // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries) |
|
52 |
+ // a new empty registry is used when creating a template contents owner |
|
53 |
+ // document, so we use that as our parent document to ensure nothing |
|
54 |
+ // is inherited. |
|
55 |
+ if (typeof HTMLTemplateElement === 'function') { |
|
56 |
+ var template = document.createElement('template'); |
|
57 |
+ if (template.content && template.content.ownerDocument) { |
|
58 |
+ document = template.content.ownerDocument; |
|
59 |
+ } |
|
60 |
+ } |
|
61 |
+ var implementation = document.implementation; |
|
62 |
+ var createNodeIterator = document.createNodeIterator; |
|
63 |
+ var getElementsByTagName = document.getElementsByTagName; |
|
64 |
+ var createDocumentFragment = document.createDocumentFragment; |
|
65 |
+ var importNode = originalDocument.importNode; |
|
66 |
+ |
|
67 |
+ var hooks = {}; |
|
68 |
+ |
|
69 |
+ /** |
|
70 |
+ * Expose whether this browser supports running the full DOMPurify. |
|
71 |
+ */ |
|
72 |
+ DOMPurify.isSupported = |
|
73 |
+ typeof implementation.createHTMLDocument !== 'undefined' && |
|
74 |
+ document.documentMode !== 9; |
|
75 |
+ |
|
76 |
+ /* Add properties to a lookup table */ |
|
77 |
+ var _addToSet = function(set, array) { |
|
78 |
+ var l = array.length; |
|
79 |
+ while (l--) { |
|
80 |
+ if (typeof array[l] === 'string') { |
|
81 |
+ array[l] = array[l].toLowerCase(); |
|
82 |
+ } |
|
83 |
+ set[array[l]] = true; |
|
84 |
+ } |
|
85 |
+ return set; |
|
86 |
+ }; |
|
87 |
+ |
|
88 |
+ /* Shallow clone an object */ |
|
89 |
+ var _cloneObj = function(object) { |
|
90 |
+ var newObject = {}; |
|
91 |
+ var property; |
|
92 |
+ for (property in object) { |
|
93 |
+ if (object.hasOwnProperty(property)) { |
|
94 |
+ newObject[property] = object[property]; |
|
95 |
+ } |
|
96 |
+ } |
|
97 |
+ return newObject; |
|
98 |
+ }; |
|
99 |
+ |
|
100 |
+ /** |
|
101 |
+ * We consider the elements and attributes below to be safe. Ideally |
|
102 |
+ * don't add any new ones but feel free to remove unwanted ones. |
|
103 |
+ */ |
|
104 |
+ |
|
105 |
+ /* allowed element names */ |
|
106 |
+ var ALLOWED_TAGS = null; |
|
107 |
+ var DEFAULT_ALLOWED_TAGS = _addToSet({}, [ |
|
108 |
+ |
|
109 |
+ // HTML |
|
110 |
+ 'a','abbr','acronym','address','area','article','aside','audio','b', |
|
111 |
+ 'bdi','bdo','big','blink','blockquote','body','br','button','canvas', |
|
112 |
+ 'caption','center','cite','code','col','colgroup','content','data', |
|
113 |
+ 'datalist','dd','decorator','del','details','dfn','dir','div','dl','dt', |
|
114 |
+ 'element','em','fieldset','figcaption','figure','font','footer','form', |
|
115 |
+ 'h1','h2','h3','h4','h5','h6','head','header','hgroup','hr','html','i', |
|
116 |
+ 'img','input','ins','kbd','label','legend','li','main','map','mark', |
|
117 |
+ 'marquee','menu','menuitem','meter','nav','nobr','ol','optgroup', |
|
118 |
+ 'option','output','p','pre','progress','q','rp','rt','ruby','s','samp', |
|
119 |
+ 'section','select','shadow','small','source','spacer','span','strike', |
|
120 |
+ 'strong','style','sub','summary','sup','table','tbody','td','template', |
|
121 |
+ 'textarea','tfoot','th','thead','time','tr','track','tt','u','ul','var', |
|
122 |
+ 'video','wbr', |
|
123 |
+ |
|
124 |
+ // SVG |
|
125 |
+ 'svg','altglyph','altglyphdef','altglyphitem','animatecolor', |
|
126 |
+ 'animatemotion','animatetransform','circle','clippath','defs','desc', |
|
127 |
+ 'ellipse','filter','font','g','glyph','glyphref','hkern','image','line', |
|
128 |
+ 'lineargradient','marker','mask','metadata','mpath','path','pattern', |
|
129 |
+ 'polygon','polyline','radialgradient','rect','stop','switch','symbol', |
|
130 |
+ 'text','textpath','title','tref','tspan','view','vkern', |
|
131 |
+ |
|
132 |
+ // SVG Filters |
|
133 |
+ 'feBlend','feColorMatrix','feComponentTransfer','feComposite', |
|
134 |
+ 'feConvolveMatrix','feDiffuseLighting','feDisplacementMap', |
|
135 |
+ 'feFlood','feFuncA','feFuncB','feFuncG','feFuncR','feGaussianBlur', |
|
136 |
+ 'feMerge','feMergeNode','feMorphology','feOffset', |
|
137 |
+ 'feSpecularLighting','feTile','feTurbulence', |
|
138 |
+ |
|
139 |
+ //MathML |
|
140 |
+ 'math','menclose','merror','mfenced','mfrac','mglyph','mi','mlabeledtr', |
|
141 |
+ 'mmuliscripts','mn','mo','mover','mpadded','mphantom','mroot','mrow', |
|
142 |
+ 'ms','mpspace','msqrt','mystyle','msub','msup','msubsup','mtable','mtd', |
|
143 |
+ 'mtext','mtr','munder','munderover', |
|
144 |
+ |
|
145 |
+ //Text |
|
146 |
+ '#text' |
|
147 |
+ ]); |
|
148 |
+ |
|
149 |
+ /* Allowed attribute names */ |
|
150 |
+ var ALLOWED_ATTR = null; |
|
151 |
+ var DEFAULT_ALLOWED_ATTR = _addToSet({}, [ |
|
152 |
+ |
|
153 |
+ // HTML |
|
154 |
+ 'accept','action','align','alt','autocomplete','background','bgcolor', |
|
155 |
+ 'border','cellpadding','cellspacing','checked','cite','class','clear','color', |
|
156 |
+ 'cols','colspan','coords','datetime','default','dir','disabled', |
|
157 |
+ 'download','enctype','face','for','headers','height','hidden','high','href', |
|
158 |
+ 'hreflang','id','ismap','label','lang','list','loop', 'low','max', |
|
159 |
+ 'maxlength','media','method','min','multiple','name','noshade','novalidate', |
|
160 |
+ 'nowrap','open','optimum','pattern','placeholder','poster','preload','pubdate', |
|
161 |
+ 'radiogroup','readonly','rel','required','rev','reversed','rows', |
|
162 |
+ 'rowspan','spellcheck','scope','selected','shape','size','span', |
|
163 |
+ 'srclang','start','src','step','style','summary','tabindex','title', |
|
164 |
+ 'type','usemap','valign','value','width','xmlns', |
|
165 |
+ |
|
166 |
+ // SVG |
|
167 |
+ 'accent-height','accumulate','additivive','alignment-baseline', |
|
168 |
+ 'ascent','attributename','attributetype','azimuth','basefrequency', |
|
169 |
+ 'baseline-shift','begin','bias','by','clip','clip-path','clip-rule', |
|
170 |
+ 'color','color-interpolation','color-interpolation-filters','color-profile', |
|
171 |
+ 'color-rendering','cx','cy','d','dx','dy','diffuseconstant','direction', |
|
172 |
+ 'display','divisor','dur','edgemode','elevation','end','fill','fill-opacity', |
|
173 |
+ 'fill-rule','filter','flood-color','flood-opacity','font-family','font-size', |
|
174 |
+ 'font-size-adjust','font-stretch','font-style','font-variant','font-weight', |
|
175 |
+ 'fx', 'fy','g1','g2','glyph-name','glyphref','gradientunits','gradienttransform', |
|
176 |
+ 'image-rendering','in','in2','k','k1','k2','k3','k4','kerning','keypoints', |
|
177 |
+ 'keysplines','keytimes','lengthadjust','letter-spacing','kernelmatrix', |
|
178 |
+ 'kernelunitlength','lighting-color','local','marker-end','marker-mid', |
|
179 |
+ 'marker-start','markerheight','markerunits','markerwidth','maskcontentunits', |
|
180 |
+ 'maskunits','max','mask','mode','min','numoctaves','offset','operator', |
|
181 |
+ 'opacity','order','orient','orientation','origin','overflow','paint-order', |
|
182 |
+ 'path','pathlength','patterncontentunits','patterntransform','patternunits', |
|
183 |
+ 'points','preservealpha','r','rx','ry','radius','refx','refy','repeatcount', |
|
184 |
+ 'repeatdur','restart','result','rotate','scale','seed','shape-rendering', |
|
185 |
+ 'specularconstant','specularexponent','spreadmethod','stddeviation','stitchtiles', |
|
186 |
+ 'stop-color','stop-opacity','stroke-dasharray','stroke-dashoffset','stroke-linecap', |
|
187 |
+ 'stroke-linejoin','stroke-miterlimit','stroke-opacity','stroke','stroke-width', |
|
188 |
+ 'surfacescale','targetx','targety','transform','text-anchor','text-decoration', |
|
189 |
+ 'text-rendering','textlength','u1','u2','unicode','values','viewbox', |
|
190 |
+ 'visibility','vert-adv-y','vert-origin-x','vert-origin-y','word-spacing', |
|
191 |
+ 'wrap','writing-mode','xchannelselector','ychannelselector','x','x1','x2', |
|
192 |
+ 'y','y1','y2','z','zoomandpan', |
|
193 |
+ |
|
194 |
+ // MathML |
|
195 |
+ 'accent','accentunder','bevelled','close','columnsalign','columnlines', |
|
196 |
+ 'columnspan','denomalign','depth','display','displaystyle','fence', |
|
197 |
+ 'frame','largeop','length','linethickness','lspace','lquote', |
|
198 |
+ 'mathbackground','mathcolor','mathsize','mathvariant','maxsize', |
|
199 |
+ 'minsize','movablelimits','notation','numalign','open','rowalign', |
|
200 |
+ 'rowlines','rowspacing','rowspan','rspace','rquote','scriptlevel', |
|
201 |
+ 'scriptminsize','scriptsizemultiplier','selection','separator', |
|
202 |
+ 'separators','stretchy','subscriptshift','supscriptshift','symmetric', |
|
203 |
+ 'voffset', |
|
204 |
+ |
|
205 |
+ // XML |
|
206 |
+ 'xlink:href','xml:id','xlink:title','xml:space','xmlns:xlink' |
|
207 |
+ ]); |
|
208 |
+ |
|
209 |
+ /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */ |
|
210 |
+ var FORBID_TAGS = null; |
|
211 |
+ |
|
212 |
+ /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */ |
|
213 |
+ var FORBID_ATTR = null; |
|
214 |
+ |
|
215 |
+ /* Decide if custom data attributes are okay */ |
|
216 |
+ var ALLOW_DATA_ATTR = true; |
|
217 |
+ |
|
218 |
+ /* Decide if unknown protocols are okay */ |
|
219 |
+ var ALLOW_UNKNOWN_PROTOCOLS = false; |
|
220 |
+ |
|
221 |
+ /* Output should be safe for jQuery's $() factory? */ |
|
222 |
+ var SAFE_FOR_JQUERY = false; |
|
223 |
+ |
|
224 |
+ /* Output should be safe for common template engines. |
|
225 |
+ * This means, DOMPurify removes data attributes, mustaches and ERB |
|
226 |
+ */ |
|
227 |
+ var SAFE_FOR_TEMPLATES = false; |
|
228 |
+ |
|
229 |
+ /* Specify template detection regex for SAFE_FOR_TEMPLATES mode */ |
|
230 |
+ var MUSTACHE_EXPR = /\{\{[\s\S]*|[\s\S]*\}\}/gm; |
|
231 |
+ var ERB_EXPR = /<%[\s\S]*|[\s\S]*%>/gm; |
|
232 |
+ |
|
233 |
+ /* Decide if document with <html>... should be returned */ |
|
234 |
+ var WHOLE_DOCUMENT = false; |
|
235 |
+ |
|
236 |
+ /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html string. |
|
237 |
+ * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead |
|
238 |
+ */ |
|
239 |
+ var RETURN_DOM = false; |
|
240 |
+ |
|
241 |
+ /* Decide if a DOM `DocumentFragment` should be returned, instead of a html string */ |
|
242 |
+ var RETURN_DOM_FRAGMENT = false; |
|
243 |
+ |
|
244 |
+ /* If `RETURN_DOM` or `RETURN_DOM_FRAGMENT` is enabled, decide if the returned DOM |
|
245 |
+ * `Node` is imported into the current `Document`. If this flag is not enabled the |
|
246 |
+ * `Node` will belong (its ownerDocument) to a fresh `HTMLDocument`, created by |
|
247 |
+ * DOMPurify. */ |
|
248 |
+ var RETURN_DOM_IMPORT = false; |
|
249 |
+ |
|
250 |
+ /* Output should be free from DOM clobbering attacks? */ |
|
251 |
+ var SANITIZE_DOM = true; |
|
252 |
+ |
|
253 |
+ /* Keep element content when removing element? */ |
|
254 |
+ var KEEP_CONTENT = true; |
|
255 |
+ |
|
256 |
+ /* Tags to ignore content of when KEEP_CONTENT is true */ |
|
257 |
+ var FORBID_CONTENTS = _addToSet({}, [ |
|
258 |
+ 'audio', 'head', 'math', 'script', 'style', 'svg', 'video' |
|
259 |
+ ]); |
|
260 |
+ |
|
261 |
+ /* Tags that are safe for data: URIs */ |
|
262 |
+ var DATA_URI_TAGS = _addToSet({}, [ |
|
263 |
+ 'audio', 'video', 'img', 'source' |
|
264 |
+ ]); |
|
265 |
+ |
|
266 |
+ /* Attributes safe for values like "javascript:" */ |
|
267 |
+ var URI_SAFE_ATTRIBUTES = _addToSet({}, [ |
|
268 |
+ 'alt','class','for','id','label','name','pattern','placeholder', |
|
269 |
+ 'summary','title','value','style','xmlns' |
|
270 |
+ ]); |
|
271 |
+ |
|
272 |
+ /* Keep a reference to config to pass to hooks */ |
|
273 |
+ var CONFIG = null; |
|
274 |
+ |
|
275 |
+ /* Ideally, do not touch anything below this line */ |
|
276 |
+ /* ______________________________________________ */ |
|
277 |
+ |
|
278 |
+ var formElement = document.createElement('form'); |
|
279 |
+ |
|
280 |
+ /** |
|
281 |
+ * _parseConfig |
|
282 |
+ * |
|
283 |
+ * @param optional config literal |
|
284 |
+ */ |
|
285 |
+ var _parseConfig = function(cfg) { |
|
286 |
+ /* Shield configuration object from tampering */ |
|
287 |
+ if (typeof cfg !== 'object') { |
|
288 |
+ cfg = {}; |
|
289 |
+ } |
|
290 |
+ |
|
291 |
+ /* Set configuration parameters */ |
|
292 |
+ ALLOWED_TAGS = 'ALLOWED_TAGS' in cfg ? |
|
293 |
+ _addToSet({}, cfg.ALLOWED_TAGS) : DEFAULT_ALLOWED_TAGS; |
|
294 |
+ ALLOWED_ATTR = 'ALLOWED_ATTR' in cfg ? |
|
295 |
+ _addToSet({}, cfg.ALLOWED_ATTR) : DEFAULT_ALLOWED_ATTR; |
|
296 |
+ FORBID_TAGS = 'FORBID_TAGS' in cfg ? |
|
297 |
+ _addToSet({}, cfg.FORBID_TAGS) : {}; |
|
298 |
+ FORBID_ATTR = 'FORBID_ATTR' in cfg ? |
|
299 |
+ _addToSet({}, cfg.FORBID_ATTR) : {}; |
|
300 |
+ ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true |
|
301 |
+ ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false |
|
302 |
+ SAFE_FOR_JQUERY = cfg.SAFE_FOR_JQUERY || false; // Default false |
|
303 |
+ SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false |
|
304 |
+ WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false |
|
305 |
+ RETURN_DOM = cfg.RETURN_DOM || false; // Default false |
|
306 |
+ RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false |
|
307 |
+ RETURN_DOM_IMPORT = cfg.RETURN_DOM_IMPORT || false; // Default false |
|
308 |
+ SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true |
|
309 |
+ KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true |
|
310 |
+ |
|
311 |
+ if (SAFE_FOR_TEMPLATES) { |
|
312 |
+ ALLOW_DATA_ATTR = false; |
|
313 |
+ } |
|
314 |
+ |
|
315 |
+ if (RETURN_DOM_FRAGMENT) { |
|
316 |
+ RETURN_DOM = true; |
|
317 |
+ } |
|
318 |
+ |
|
319 |
+ /* Merge configuration parameters */ |
|
320 |
+ if (cfg.ADD_TAGS) { |
|
321 |
+ if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) { |
|
322 |
+ ALLOWED_TAGS = _cloneObj(ALLOWED_TAGS); |
|
323 |
+ } |
|
324 |
+ _addToSet(ALLOWED_TAGS, cfg.ADD_TAGS); |
|
325 |
+ } |
|
326 |
+ if (cfg.ADD_ATTR) { |
|
327 |
+ if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) { |
|
328 |
+ ALLOWED_ATTR = _cloneObj(ALLOWED_ATTR); |
|
329 |
+ } |
|
330 |
+ _addToSet(ALLOWED_ATTR, cfg.ADD_ATTR); |
|
331 |
+ } |
|
332 |
+ |
|
333 |
+ /* Add #text in case KEEP_CONTENT is set to true */ |
|
334 |
+ if (KEEP_CONTENT) { ALLOWED_TAGS['#text'] = true; } |
|
335 |
+ |
|
336 |
+ // Prevent further manipulation of configuration. |
|
337 |
+ // Not available in IE8, Safari 5, etc. |
|
338 |
+ if (Object && 'freeze' in Object) { Object.freeze(cfg); } |
|
339 |
+ |
|
340 |
+ CONFIG = cfg; |
|
341 |
+ }; |
|
342 |
+ |
|
343 |
+ /** |
|
344 |
+ * _forceRemove |
|
345 |
+ * |
|
346 |
+ * @param a DOM node |
|
347 |
+ */ |
|
348 |
+ var _forceRemove = function(node) { |
|
349 |
+ DOMPurify.removed.push({element: node}); |
|
350 |
+ try { |
|
351 |
+ node.parentNode.removeChild(node); |
|
352 |
+ } catch (e) { |
|
353 |
+ node.outerHTML = ''; |
|
354 |
+ } |
|
355 |
+ }; |
|
356 |
+ |
|
357 |
+ /** |
|
358 |
+ * _removeAttribute |
|
359 |
+ * |
|
360 |
+ * @param an Attribute name |
|
361 |
+ * @param a DOM node |
|
362 |
+ */ |
|
363 |
+ var _removeAttribute = function(name, node) { |
|
364 |
+ DOMPurify.removed.push({ |
|
365 |
+ attribute: node.getAttributeNode(name), |
|
366 |
+ from: node |
|
367 |
+ }); |
|
368 |
+ node.removeAttribute(name); |
|
369 |
+ }; |
|
370 |
+ |
|
371 |
+ /** |
|
372 |
+ * _initDocument |
|
373 |
+ * |
|
374 |
+ * @param a string of dirty markup |
|
375 |
+ * @return a DOM, filled with the dirty markup |
|
376 |
+ */ |
|
377 |
+ var _initDocument = function(dirty) { |
|
378 |
+ /* Create a HTML document using DOMParser */ |
|
379 |
+ var doc, body; |
|
380 |
+ try { |
|
381 |
+ doc = new DOMParser().parseFromString(dirty, 'text/html'); |
|
382 |
+ } catch (e) {} |
|
383 |
+ |
|
384 |
+ /* Some browsers throw, some browsers return null for the code above |
|
385 |
+ DOMParser with text/html support is only in very recent browsers. |
|
386 |
+ See #159 why the check here is extra-thorough */ |
|
387 |
+ if (!doc || !doc.documentElement) { |
|
388 |
+ doc = implementation.createHTMLDocument(''); |
|
389 |
+ body = doc.body; |
|
390 |
+ body.parentNode.removeChild(body.parentNode.firstElementChild); |
|
391 |
+ body.outerHTML = dirty; |
|
392 |
+ } |
|
393 |
+ |
|
394 |
+ /* Work on whole document or just its body */ |
|
395 |
+ if (typeof doc.getElementsByTagName === 'function') { |
|
396 |
+ return doc.getElementsByTagName( |
|
397 |
+ WHOLE_DOCUMENT ? 'html' : 'body')[0]; |
|
398 |
+ } |
|
399 |
+ return getElementsByTagName.call(doc, |
|
400 |
+ WHOLE_DOCUMENT ? 'html' : 'body')[0]; |
|
401 |
+ }; |
|
402 |
+ |
|
403 |
+ /** |
|
404 |
+ * _createIterator |
|
405 |
+ * |
|
406 |
+ * @param document/fragment to create iterator for |
|
407 |
+ * @return iterator instance |
|
408 |
+ */ |
|
409 |
+ var _createIterator = function(root) { |
|
410 |
+ return createNodeIterator.call(root.ownerDocument || root, |
|
411 |
+ root, |
|
412 |
+ NodeFilter.SHOW_ELEMENT |
|
413 |
+ | NodeFilter.SHOW_COMMENT |
|
414 |
+ | NodeFilter.SHOW_TEXT, |
|
415 |
+ function() { return NodeFilter.FILTER_ACCEPT; }, |
|
416 |
+ false |
|
417 |
+ ); |
|
418 |
+ }; |
|
419 |
+ |
|
420 |
+ /** |
|
421 |
+ * _isClobbered |
|
422 |
+ * |
|
423 |
+ * @param element to check for clobbering attacks |
|
424 |
+ * @return true if clobbered, false if safe |
|
425 |
+ */ |
|
426 |
+ var _isClobbered = function(elm) { |
|
427 |
+ if (elm instanceof Text || elm instanceof Comment) { |
|
428 |
+ return false; |
|
429 |
+ } |
|
430 |
+ if ( typeof elm.nodeName !== 'string' |
|
431 |
+ || typeof elm.textContent !== 'string' |
|
432 |
+ || typeof elm.removeChild !== 'function' |
|
433 |
+ || !(elm.attributes instanceof NamedNodeMap) |
|
434 |
+ || typeof elm.removeAttribute !== 'function' |
|
435 |
+ || typeof elm.setAttribute !== 'function' |
|
436 |
+ ) { |
|
437 |
+ return true; |
|
438 |
+ } |
|
439 |
+ return false; |
|
440 |
+ }; |
|
441 |
+ |
|
442 |
+ /** |
|
443 |
+ * _sanitizeElements |
|
444 |
+ * |
|
445 |
+ * @protect nodeName |
|
446 |
+ * @protect textContent |
|
447 |
+ * @protect removeChild |
|
448 |
+ * |
|
449 |
+ * @param node to check for permission to exist |
|
450 |
+ * @return true if node was killed, false if left alive |
|
451 |
+ */ |
|
452 |
+ var _sanitizeElements = function(currentNode) { |
|
453 |
+ var tagName, content; |
|
454 |
+ /* Execute a hook if present */ |
|
455 |
+ _executeHook('beforeSanitizeElements', currentNode, null); |
|
456 |
+ |
|
457 |
+ /* Check if element is clobbered or can clobber */ |
|
458 |
+ if (_isClobbered(currentNode)) { |
|
459 |
+ _forceRemove(currentNode); |
|
460 |
+ return true; |
|
461 |
+ } |
|
462 |
+ |
|
463 |
+ /* Now let's check the element's type and name */ |
|
464 |
+ tagName = currentNode.nodeName.toLowerCase(); |
|
465 |
+ |
|
466 |
+ /* Execute a hook if present */ |
|
467 |
+ _executeHook('uponSanitizeElement', currentNode, { |
|
468 |
+ tagName: tagName |
|
469 |
+ }); |
|
470 |
+ |
|
471 |
+ /* Remove element if anything forbids its presence */ |
|
472 |
+ if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) { |
|
473 |
+ /* Keep content except for black-listed elements */ |
|
474 |
+ if (KEEP_CONTENT && !FORBID_CONTENTS[tagName] |
|
475 |
+ && typeof currentNode.insertAdjacentHTML === 'function') { |
|
476 |
+ try { |
|
477 |
+ currentNode.insertAdjacentHTML('AfterEnd', currentNode.innerHTML); |
|
478 |
+ } catch (e) {} |
|
479 |
+ } |
|
480 |
+ _forceRemove(currentNode); |
|
481 |
+ return true; |
|
482 |
+ } |
|
483 |
+ |
|
484 |
+ /* Convert markup to cover jQuery behavior */ |
|
485 |
+ if (SAFE_FOR_JQUERY && !currentNode.firstElementChild && |
|
486 |
+ (!currentNode.content || !currentNode.content.firstElementChild) && |
|
487 |
+ /</g.test(currentNode.textContent)) { |
|
488 |
+ DOMPurify.removed.push({element: currentNode.cloneNode()}); |
|
489 |
+ currentNode.innerHTML = currentNode.textContent.replace(/</g, '<'); |
|
490 |
+ } |
|
491 |
+ |
|
492 |
+ /* Sanitize element content to be template-safe */ |
|
493 |
+ if (SAFE_FOR_TEMPLATES && currentNode.nodeType === 3) { |
|
494 |
+ /* Get the element's text content */ |
|
495 |
+ content = currentNode.textContent; |
|
496 |
+ content = content.replace(MUSTACHE_EXPR, ' '); |
|
497 |
+ content = content.replace(ERB_EXPR, ' '); |
|
498 |
+ if (currentNode.textContent !== content) { |
|
499 |
+ DOMPurify.removed.push({element: currentNode.cloneNode()}); |
|
500 |
+ currentNode.textContent = content; |
|
501 |
+ } |
|
502 |
+ } |
|
503 |
+ |
|
504 |
+ /* Execute a hook if present */ |
|
505 |
+ _executeHook('afterSanitizeElements', currentNode, null); |
|
506 |
+ |
|
507 |
+ return false; |
|
508 |
+ }; |
|
509 |
+ |
|
510 |
+ var DATA_ATTR = /^data-[\-\w.\u00B7-\uFFFF]/; |
|
511 |
+ var IS_ALLOWED_URI = /^(?:(?:(?:f|ht)tps?|mailto|tel):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i; |
|
512 |
+ var IS_SCRIPT_OR_DATA = /^(?:\w+script|data):/i; |
|
513 |
+ /* This needs to be extensive thanks to Webkit/Blink's behavior */ |
|
514 |
+ var ATTR_WHITESPACE = /[\x00-\x20\xA0\u1680\u180E\u2000-\u2029\u205f\u3000]/g; |
|
515 |
+ |
|
516 |
+ /** |
|
517 |
+ * _sanitizeAttributes |
|
518 |
+ * |
|
519 |
+ * @protect attributes |
|
520 |
+ * @protect nodeName |
|
521 |
+ * @protect removeAttribute |
|
522 |
+ * @protect setAttribute |
|
523 |
+ * |
|
524 |
+ * @param node to sanitize |
|
525 |
+ * @return void |
|
526 |
+ */ |
|
527 |
+ var _sanitizeAttributes = function(currentNode) { |
|
528 |
+ var attr, name, value, lcName, idAttr, attributes, hookEvent, l; |
|
529 |
+ /* Execute a hook if present */ |
|
530 |
+ _executeHook('beforeSanitizeAttributes', currentNode, null); |
|
531 |
+ |
|
532 |
+ attributes = currentNode.attributes; |
|
533 |
+ |
|
534 |
+ /* Check if we have attributes; if not we might have a text node */ |
|
535 |
+ if (!attributes) { return; } |
|
536 |
+ |
|
537 |
+ hookEvent = { |
|
538 |
+ attrName: '', |
|
539 |
+ attrValue: '', |
|
540 |
+ keepAttr: true |
|
541 |
+ }; |
|
542 |
+ l = attributes.length; |
|
543 |
+ |
|
544 |
+ /* Go backwards over all attributes; safely remove bad ones */ |
|
545 |
+ while (l--) { |
|
546 |
+ attr = attributes[l]; |
|
547 |
+ name = attr.name; |
|
548 |
+ value = attr.value; |
|
549 |
+ lcName = name.toLowerCase(); |
|
550 |
+ |
|
551 |
+ /* Execute a hook if present */ |
|
552 |
+ hookEvent.attrName = lcName; |
|
553 |
+ hookEvent.attrValue = value; |
|
554 |
+ hookEvent.keepAttr = true; |
|
555 |
+ _executeHook('uponSanitizeAttribute', currentNode, hookEvent ); |
|
556 |
+ value = hookEvent.attrValue; |
|
557 |
+ |
|
558 |
+ /* Remove attribute */ |
|
559 |
+ // Safari (iOS + Mac), last tested v8.0.5, crashes if you try to |
|
560 |
+ // remove a "name" attribute from an <img> tag that has an "id" |
|
561 |
+ // attribute at the time. |
|
562 |
+ if (lcName === 'name' && |
|
563 |
+ currentNode.nodeName === 'IMG' && attributes.id) { |
|
564 |
+ idAttr = attributes.id; |
|
565 |
+ attributes = Array.prototype.slice.apply(attributes); |
|
566 |
+ _removeAttribute('id', currentNode); |
|
567 |
+ _removeAttribute(name, currentNode); |
|
568 |
+ if (attributes.indexOf(idAttr) > l) { |
|
569 |
+ currentNode.setAttribute('id', idAttr.value); |
|
570 |
+ } |
|
571 |
+ } else { |
|
572 |
+ // This avoids a crash in Safari v9.0 with double-ids. |
|
573 |
+ // The trick is to first set the id to be empty and then to |
|
574 |
+ // remove the attriubute |
|
575 |
+ if (name === 'id') { |
|
576 |
+ currentNode.setAttribute(name, ''); |
|
577 |
+ } |
|
578 |
+ _removeAttribute(name, currentNode); |
|
579 |
+ } |
|
580 |
+ |
|
581 |
+ /* Did the hooks approve of the attribute? */ |
|
582 |
+ if (!hookEvent.keepAttr) { |
|
583 |
+ continue; |
|
584 |
+ } |
|
585 |
+ |
|
586 |
+ /* Make sure attribute cannot clobber */ |
|
587 |
+ if (SANITIZE_DOM && |
|
588 |
+ (lcName === 'id' || lcName === 'name') && |
|
589 |
+ (value in window || value in document || value in formElement)) { |
|
590 |
+ continue; |
|
591 |
+ } |
|
592 |
+ |
|
593 |
+ /* Sanitize attribute content to be template-safe */ |
|
594 |
+ if (SAFE_FOR_TEMPLATES) { |
|
595 |
+ value = value.replace(MUSTACHE_EXPR, ' '); |
|
596 |
+ value = value.replace(ERB_EXPR, ' '); |
|
597 |
+ } |
|
598 |
+ |
|
599 |
+ /* Allow valid data-* attributes: At least one character after "-" |
|
600 |
+ (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes) |
|
601 |
+ XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804) |
|
602 |
+ We don't need to check the value; it's always URI safe. */ |
|
603 |
+ if (ALLOW_DATA_ATTR && DATA_ATTR.test(lcName)) { |
|
604 |
+ // This attribute is safe |
|
605 |
+ } |
|
606 |
+ /* Otherwise, check the name is permitted */ |
|
607 |
+ else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) { |
|
608 |
+ continue; |
|
609 |
+ } |
|
610 |
+ /* Check value is safe. First, is attr inert? If so, is safe */ |
|
611 |
+ else if (URI_SAFE_ATTRIBUTES[lcName]) { |
|
612 |
+ // This attribute is safe |
|
613 |
+ } |
|
614 |
+ /* Check no script, data or unknown possibly unsafe URI |
|
615 |
+ unless we know URI values are safe for that attribute */ |
|
616 |
+ else if (IS_ALLOWED_URI.test(value.replace(ATTR_WHITESPACE,''))) { |
|
617 |
+ // This attribute is safe |
|
618 |
+ } |
|
619 |
+ /* Keep image data URIs alive if src is allowed */ |
|
620 |
+ else if ( |
|
621 |
+ lcName === 'src' && |
|
622 |
+ value.indexOf('data:') === 0 && |
|
623 |
+ DATA_URI_TAGS[currentNode.nodeName.toLowerCase()]) { |
|
624 |
+ // This attribute is safe |
|
625 |
+ } |
|
626 |
+ /* Allow unknown protocols: This provides support for links that |
|
627 |
+ are handled by protocol handlers which may be unknown ahead of |
|
628 |
+ time, e.g. fb:, spotify: */ |
|
629 |
+ else if ( |
|
630 |
+ ALLOW_UNKNOWN_PROTOCOLS && |
|
631 |
+ !IS_SCRIPT_OR_DATA.test(value.replace(ATTR_WHITESPACE,''))) { |
|
632 |
+ // This attribute is safe |
|
633 |
+ } |
|
634 |
+ /* Check for binary attributes */ |
|
635 |
+ else if (!value) { |
|
636 |
+ // binary attributes are safe at this point |
|
637 |
+ } |
|
638 |
+ /* Anything else, presume unsafe, do not add it back */ |
|
639 |
+ else { |
|
640 |
+ continue; |
|
641 |
+ } |
|
642 |
+ |
|
643 |
+ /* Handle invalid data-* attribute set by try-catching it */ |
|
644 |
+ try { |
|
645 |
+ currentNode.setAttribute(name, value); |
|
646 |
+ DOMPurify.removed.pop(); |
|
647 |
+ } catch (e) {} |
|
648 |
+ } |
|
649 |
+ |
|
650 |
+ /* Execute a hook if present */ |
|
651 |
+ _executeHook('afterSanitizeAttributes', currentNode, null); |
|
652 |
+ }; |
|
653 |
+ |
|
654 |
+ /** |
|
655 |
+ * _sanitizeShadowDOM |
|
656 |
+ * |
|
657 |
+ * @param fragment to iterate over recursively |
|
658 |
+ * @return void |
|
659 |
+ */ |
|
660 |
+ var _sanitizeShadowDOM = function(fragment) { |
|
661 |
+ var shadowNode; |
|
662 |
+ var shadowIterator = _createIterator(fragment); |
|
663 |
+ |
|
664 |
+ /* Execute a hook if present */ |
|
665 |
+ _executeHook('beforeSanitizeShadowDOM', fragment, null); |
|
666 |
+ |
|
667 |
+ while ( (shadowNode = shadowIterator.nextNode()) ) { |
|
668 |
+ /* Execute a hook if present */ |
|
669 |
+ _executeHook('uponSanitizeShadowNode', shadowNode, null); |
|
670 |
+ |
|
671 |
+ /* Sanitize tags and elements */ |
|
672 |
+ if (_sanitizeElements(shadowNode)) { |
|
673 |
+ continue; |
|
674 |
+ } |
|
675 |
+ |
|
676 |
+ /* Deep shadow DOM detected */ |
|
677 |
+ if (shadowNode.content instanceof DocumentFragment) { |
|
678 |
+ _sanitizeShadowDOM(shadowNode.content); |
|
679 |
+ } |
|
680 |
+ |
|
681 |
+ /* Check attributes, sanitize if necessary */ |
|
682 |
+ _sanitizeAttributes(shadowNode); |
|
683 |
+ } |
|
684 |
+ |
|
685 |
+ /* Execute a hook if present */ |
|
686 |
+ _executeHook('afterSanitizeShadowDOM', fragment, null); |
|
687 |
+ }; |
|
688 |
+ |
|
689 |
+ /** |
|
690 |
+ * _executeHook |
|
691 |
+ * Execute user configurable hooks |
|
692 |
+ * |
|
693 |
+ * @param {String} entryPoint Name of the hook's entry point |
|
694 |
+ * @param {Node} currentNode |
|
695 |
+ */ |
|
696 |
+ var _executeHook = function(entryPoint, currentNode, data) { |
|
697 |
+ if (!hooks[entryPoint]) { return; } |
|
698 |
+ |
|
699 |
+ hooks[entryPoint].forEach(function(hook) { |
|
700 |
+ hook.call(DOMPurify, currentNode, data, CONFIG); |
|
701 |
+ }); |
|
702 |
+ }; |
|
703 |
+ |
|
704 |
+ /** |
|
705 |
+ * sanitize |
|
706 |
+ * Public method providing core sanitation functionality |
|
707 |
+ * |
|
708 |
+ * @param {String} dirty string |
|
709 |
+ * @param {Object} configuration object |
|
710 |
+ */ |
|
711 |
+ DOMPurify.sanitize = function(dirty, cfg) { |
|
712 |
+ var body, currentNode, oldNode, nodeIterator, returnNode; |
|
713 |
+ /* Make sure we have a string to sanitize. |
|
714 |
+ DO NOT return early, as this will return the wrong type if |
|
715 |
+ the user has requested a DOM object rather than a string */ |
|
716 |
+ if (!dirty) { |
|
717 |
+ dirty = ''; |
|
718 |
+ } |
|
719 |
+ |
|
720 |
+ /* Stringify, in case dirty is an object */ |
|
721 |
+ if (typeof dirty !== 'string') { |
|
722 |
+ if (typeof dirty.toString !== 'function') { |
|
723 |
+ throw new TypeError('toString is not a function'); |
|
724 |
+ } else { |
|
725 |
+ dirty = dirty.toString(); |
|
726 |
+ } |
|
727 |
+ } |
|
728 |
+ |
|
729 |
+ /* Check we can run. Otherwise fall back or ignore */ |
|
730 |
+ if (!DOMPurify.isSupported) { |
|
731 |
+ if (typeof window.toStaticHTML === 'object' |
|
732 |
+ || typeof window.toStaticHTML === 'function') { |
|
733 |
+ return window.toStaticHTML(dirty); |
|
734 |
+ } |
|
735 |
+ return dirty; |
|
736 |
+ } |
|
737 |
+ |
|
738 |
+ /* Assign config vars */ |
|
739 |
+ _parseConfig(cfg); |
|
740 |
+ |
|
741 |
+ /* Clean up removed elements */ |
|
742 |
+ DOMPurify.removed = []; |
|
743 |
+ |
|
744 |
+ /* Exit directly if we have nothing to do */ |
|
745 |
+ if (!RETURN_DOM && !WHOLE_DOCUMENT && dirty.indexOf('<') === -1) { |
|
746 |
+ return dirty; |
|
747 |
+ } |
|
748 |
+ |
|
749 |
+ /* Initialize the document to work on */ |
|
750 |
+ body = _initDocument(dirty); |
|
751 |
+ |
|
752 |
+ /* Check we have a DOM node from the data */ |
|
753 |
+ if (!body) { |
|
754 |
+ return RETURN_DOM ? null : ''; |
|
755 |
+ } |
|
756 |
+ |
|
757 |
+ /* Get node iterator */ |
|
758 |
+ nodeIterator = _createIterator(body); |
|
759 |
+ |
|
760 |
+ /* Now start iterating over the created document */ |
|
761 |
+ while ( (currentNode = nodeIterator.nextNode()) ) { |
|
762 |
+ |
|
763 |
+ /* Fix IE's strange behavior with manipulated textNodes #89 */ |
|
764 |
+ if (currentNode.nodeType === 3 && currentNode === oldNode) { |
|
765 |
+ continue; |
|
766 |
+ } |
|
767 |
+ |
|
768 |
+ /* Sanitize tags and elements */ |
|
769 |
+ if (_sanitizeElements(currentNode)) { |
|
770 |
+ continue; |
|
771 |
+ } |
|
772 |
+ |
|
773 |
+ /* Shadow DOM detected, sanitize it */ |
|
774 |
+ if (currentNode.content instanceof DocumentFragment) { |
|
775 |
+ _sanitizeShadowDOM(currentNode.content); |
|
776 |
+ } |
|
777 |
+ |
|
778 |
+ /* Check attributes, sanitize if necessary */ |
|
779 |
+ _sanitizeAttributes(currentNode); |
|
780 |
+ |
|
781 |
+ oldNode = currentNode; |
|
782 |
+ } |
|
783 |
+ |
|
784 |
+ /* Return sanitized string or DOM */ |
|
785 |
+ if (RETURN_DOM) { |
|
786 |
+ |
|
787 |
+ if (RETURN_DOM_FRAGMENT) { |
|
788 |
+ returnNode = createDocumentFragment.call(body.ownerDocument); |
|
789 |
+ |
|
790 |
+ while (body.firstChild) { |
|
791 |
+ returnNode.appendChild(body.firstChild); |
|
792 |
+ } |
|
793 |
+ } else { |
|
794 |
+ returnNode = body; |
|
795 |
+ } |
|
796 |
+ |
|
797 |
+ if (RETURN_DOM_IMPORT) { |
|
798 |
+ /* adoptNode() is not used because internal state is not reset |
|
799 |
+ (e.g. the past names map of a HTMLFormElement), this is safe |
|
800 |
+ in theory but we would rather not risk another attack vector. |
|
801 |
+ The state that is cloned by importNode() is explicitly defined |
|
802 |
+ by the specs. */ |
|
803 |
+ returnNode = importNode.call(originalDocument, returnNode, true); |
|
804 |
+ } |
|
805 |
+ |
|
806 |
+ return returnNode; |
|
807 |
+ } |
|
808 |
+ |
|
809 |
+ return WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML; |
|
810 |
+ }; |
|
811 |
+ |
|
812 |
+ /** |
|
813 |
+ * addHook |
|
814 |
+ * Public method to add DOMPurify hooks |
|
815 |
+ * |
|
816 |
+ * @param {String} entryPoint |
|
817 |
+ * @param {Function} hookFunction |
|
818 |
+ */ |
|
819 |
+ DOMPurify.addHook = function(entryPoint, hookFunction) { |
|
820 |
+ if (typeof hookFunction !== 'function') { return; } |
|
821 |
+ hooks[entryPoint] = hooks[entryPoint] || []; |
|
822 |
+ hooks[entryPoint].push(hookFunction); |
|
823 |
+ }; |
|
824 |
+ |
|
825 |
+ /** |
|
826 |
+ * removeHook |
|
827 |
+ * Public method to remove a DOMPurify hook at a given entryPoint |
|
828 |
+ * (pops it from the stack of hooks if more are present) |
|
829 |
+ * |
|
830 |
+ * @param {String} entryPoint |
|
831 |
+ * @return void |
|
832 |
+ */ |
|
833 |
+ DOMPurify.removeHook = function(entryPoint) { |
|
834 |
+ if (hooks[entryPoint]) { |
|
835 |
+ hooks[entryPoint].pop(); |
|
836 |
+ } |
|
837 |
+ }; |
|
838 |
+ |
|
839 |
+ /** |
|
840 |
+ * removeHooks |
|
841 |
+ * Public method to remove all DOMPurify hooks at a given entryPoint |
|
842 |
+ * |
|
843 |
+ * @param {String} entryPoint |
|
844 |
+ * @return void |
|
845 |
+ */ |
|
846 |
+ DOMPurify.removeHooks = function(entryPoint) { |
|
847 |
+ if (hooks[entryPoint]) { |
|
848 |
+ hooks[entryPoint] = []; |
|
849 |
+ } |
|
850 |
+ }; |
|
851 |
+ |
|
852 |
+ /** |
|
853 |
+ * removeAllHooks |
|
854 |
+ * Public method to remove all DOMPurify hooks |
|
855 |
+ * |
|
856 |
+ * @return void |
|
857 |
+ */ |
|
858 |
+ DOMPurify.removeAllHooks = function() { |
|
859 |
+ hooks = {}; |
|
860 |
+ }; |
|
861 |
+ |
|
862 |
+ return DOMPurify; |
|
863 |
+})); |
0 | 864 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,97 @@ |
1 |
+root = new Vue({ |
|
2 |
+ el: '#container', |
|
3 |
+ data: { |
|
4 |
+ "pull_time": null, |
|
5 |
+ "feed_urls": [], |
|
6 |
+ feeds: { |
|
7 |
+ feeds: [] |
|
8 |
+ }, |
|
9 |
+ |
|
10 |
+ current_feed: { |
|
11 |
+ title: null, |
|
12 |
+ fetch_url: null, |
|
13 |
+ link: null, |
|
14 |
+ description: null, |
|
15 |
+ items: [], |
|
16 |
+ base_path: null, |
|
17 |
+ }, |
|
18 |
+ |
|
19 |
+ current_item: { |
|
20 |
+ title: null, |
|
21 |
+ date: null, |
|
22 |
+ author: null, |
|
23 |
+ id: null, |
|
24 |
+ link: null, |
|
25 |
+ content: null, |
|
26 |
+ }, |
|
27 |
+ }, |
|
28 |
+ |
|
29 |
+ computed: { |
|
30 |
+ item_content() { |
|
31 |
+ if (this.current_item.content !== null) { |
|
32 |
+ return DOMPurify.sanitize(this.current_item.content); |
|
33 |
+ } |
|
34 |
+ }, |
|
35 |
+ }, |
|
36 |
+ |
|
37 |
+ methods: { |
|
38 |
+ sanitize(html) { |
|
39 |
+ return DOMPurify.sanitize(html, { |
|
40 |
+ FORBID_TAG: ['style'], |
|
41 |
+ FORBID_ATTR: ['style'], |
|
42 |
+ }); |
|
43 |
+ }, |
|
44 |
+ |
|
45 |
+ get_feed: function (path) { |
|
46 |
+ window.fetch(path+'index.json').then((resp) => resp.json()) |
|
47 |
+ .then((data) => { |
|
48 |
+ var result = Object.assign({}, data); |
|
49 |
+ result.fetch_url = data['fetch-url']; |
|
50 |
+ result.base_path = path; |
|
51 |
+ window.history.pushState({ |
|
52 |
+ 'current_feed': result |
|
53 |
+ }, "", window.location.pathname); |
|
54 |
+ Object.assign(root.current_feed, result); |
|
55 |
+ }); |
|
56 |
+ }, |
|
57 |
+ |
|
58 |
+ get_item(path) { |
|
59 |
+ window.fetch(this.current_feed.base_path + path).then((resp) => resp.json()) |
|
60 |
+ .then((data) => { |
|
61 |
+ window.history.pushState({ |
|
62 |
+ 'current_feed': root.current_feed, |
|
63 |
+ 'current_item': data |
|
64 |
+ }, "", window.location.pathname); |
|
65 |
+ Object.assign(this.current_item, data); |
|
66 |
+ }); |
|
67 |
+ } |
|
68 |
+ |
|
69 |
+ } |
|
70 |
+}); |
|
71 |
+ |
|
72 |
+window.fetch(baseUrl+'/index.json').then((resp) => resp.json()) |
|
73 |
+.then(function (data) { |
|
74 |
+ root.pull_time = data['pull-time']; |
|
75 |
+ root.feed_urls = data['feed-urls']; |
|
76 |
+ root.feeds = data.feeds; |
|
77 |
+}); |
|
78 |
+ |
|
79 |
+window.onpopstate = function (ev) { |
|
80 |
+ console.log(ev); |
|
81 |
+ var current_feed = ev.state.current_feed, current_item = ev.state.current_item; |
|
82 |
+ |
|
83 |
+ Object.assign(root.current_feed, current_feed); |
|
84 |
+ |
|
85 |
+ if (current_item !== undefined) { |
|
86 |
+ Object.assign(root.current_item, current_item); |
|
87 |
+ } |
|
88 |
+}; |
|
89 |
+ |
|
90 |
+document.addEventListener('DOMContentLoaded', function (ev) { |
|
91 |
+ if (window.history.state !== null) { |
|
92 |
+ Object.assign(root.current_feed, window.history.state.current_feed); |
|
93 |
+ if (window.history.state.current_item !== undefined) { |
|
94 |
+ Object.assign(root.current_item, window.history.state.current_item); |
|
95 |
+ } |
|
96 |
+ } |
|
97 |
+}); |
0 | 98 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,65 @@ |
1 |
+body { |
|
2 |
+ margin: 0px; |
|
3 |
+ font-family: 'Lato', 'Alegreya Sans', sans-serif; |
|
4 |
+ background: #002834; |
|
5 |
+ color: #cce5dd; |
|
6 |
+} |
|
7 |
+ |
|
8 |
+ |
|
9 |
+#container { |
|
10 |
+ opacity: 1; |
|
11 |
+ transition: 0.1s ease opacity; |
|
12 |
+} |
|
13 |
+ |
|
14 |
+#container[v-cloak] { |
|
15 |
+ opacity: 0; |
|
16 |
+} |
|
17 |
+ |
|
18 |
+a { |
|
19 |
+ color: #28b0b1; |
|
20 |
+ /*text-shadow: 0em 0em 0.1em #000*/ |
|
21 |
+ /*, 0em 0em 0.2em #888;*/ |
|
22 |
+} |
|
23 |
+ |
|
24 |
+a:hover, a:focus { |
|
25 |
+ text-decoration: underline; |
|
26 |
+} |
|
27 |
+ |
|
28 |
+.left, .right { |
|
29 |
+ overflow-y: auto; |
|
30 |
+} |
|
31 |
+ |
|
32 |
+.right { |
|
33 |
+ width: 66vw; |
|
34 |
+ height: 100vh; |
|
35 |
+ box-sizing: border-box; |
|
36 |
+ position: absolute; |
|
37 |
+ padding: 1em; |
|
38 |
+ display: block; |
|
39 |
+ right: 0; |
|
40 |
+ font-size: 16px; |
|
41 |
+ line-height: 1.25; |
|
42 |
+ /*text-shadow: 0 0 0.1em #333, 0 0 0.2em #cce5dd;*/ |
|
43 |
+} |
|
44 |
+ |
|
45 |
+h1, h2, h3, h4, h5, h6 { |
|
46 |
+ border-bottom: 3px #28b0b1 double; |
|
47 |
+} |
|
48 |
+ |
|
49 |
+.left { |
|
50 |
+ border-right: 4px #28b0b1 double; |
|
51 |
+ width: 33vw; |
|
52 |
+ height: 100vh; |
|
53 |
+ box-sizing: border-box; |
|
54 |
+ position: absolute; |
|
55 |
+ padding: 1em; |
|
56 |
+ display: block; |
|
57 |
+ left: 0; |
|
58 |
+} |
|
59 |
+ |
|
60 |
+ |
|
61 |
+.right img { |
|
62 |
+ max-width: 50vw; |
|
63 |
+ display: block; |
|
64 |
+ margin: auto; |
|
65 |
+} |
0 | 66 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,10073 @@ |
1 |
+/*! |
|
2 |
+ * Vue.js v1.0.26 |
|
3 |
+ * (c) 2016 Evan You |
|
4 |
+ * Released under the MIT License. |
|
5 |
+ */ |
|
6 |
+(function (global, factory) { |
|
7 |
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : |
|
8 |
+ typeof define === 'function' && define.amd ? define(factory) : |
|
9 |
+ (global.Vue = factory()); |
|
10 |
+}(this, function () { 'use strict'; |
|
11 |
+ |
|
12 |
+ function set(obj, key, val) { |
|
13 |
+ if (hasOwn(obj, key)) { |
|
14 |
+ obj[key] = val; |
|
15 |
+ return; |
|
16 |
+ } |
|
17 |
+ if (obj._isVue) { |
|
18 |
+ set(obj._data, key, val); |
|
19 |
+ return; |
|
20 |
+ } |
|
21 |
+ var ob = obj.__ob__; |
|
22 |
+ if (!ob) { |
|
23 |
+ obj[key] = val; |
|
24 |
+ return; |
|
25 |
+ } |
|
26 |
+ ob.convert(key, val); |
|
27 |
+ ob.dep.notify(); |
|
28 |
+ if (ob.vms) { |
|
29 |
+ var i = ob.vms.length; |
|
30 |
+ while (i--) { |
|
31 |
+ var vm = ob.vms[i]; |
|
32 |
+ vm._proxy(key); |
|
33 |
+ vm._digest(); |
|
34 |
+ } |
|
35 |
+ } |
|
36 |
+ return val; |
|
37 |
+ } |
|
38 |
+ |
|
39 |
+ /** |
|
40 |
+ * Delete a property and trigger change if necessary. |
|
41 |
+ * |
|
42 |
+ * @param {Object} obj |
|
43 |
+ * @param {String} key |
|
44 |
+ */ |
|
45 |
+ |
|
46 |
+ function del(obj, key) { |
|
47 |
+ if (!hasOwn(obj, key)) { |
|
48 |
+ return; |
|
49 |
+ } |
|
50 |
+ delete obj[key]; |
|
51 |
+ var ob = obj.__ob__; |
|
52 |
+ if (!ob) { |
|
53 |
+ if (obj._isVue) { |
|
54 |
+ delete obj._data[key]; |
|
55 |
+ obj._digest(); |
|
56 |
+ } |
|
57 |
+ return; |
|
58 |
+ } |
|
59 |
+ ob.dep.notify(); |
|
60 |
+ if (ob.vms) { |
|
61 |
+ var i = ob.vms.length; |
|
62 |
+ while (i--) { |
|
63 |
+ var vm = ob.vms[i]; |
|
64 |
+ vm._unproxy(key); |
|
65 |
+ vm._digest(); |
|
66 |
+ } |
|
67 |
+ } |
|
68 |
+ } |
|
69 |
+ |
|
70 |
+ var hasOwnProperty = Object.prototype.hasOwnProperty; |
|
71 |
+ /** |
|
72 |
+ * Check whether the object has the property. |
|
73 |
+ * |
|
74 |
+ * @param {Object} obj |
|
75 |
+ * @param {String} key |
|
76 |
+ * @return {Boolean} |
|
77 |
+ */ |
|
78 |
+ |
|
79 |
+ function hasOwn(obj, key) { |
|
80 |
+ return hasOwnProperty.call(obj, key); |
|
81 |
+ } |
|
82 |
+ |
|
83 |
+ /** |
|
84 |
+ * Check if an expression is a literal value. |
|
85 |
+ * |
|
86 |
+ * @param {String} exp |
|
87 |
+ * @return {Boolean} |
|
88 |
+ */ |
|
89 |
+ |
|
90 |
+ var literalValueRE = /^\s?(true|false|-?[\d\.]+|'[^']*'|"[^"]*")\s?$/; |
|
91 |
+ |
|
92 |
+ function isLiteral(exp) { |
|
93 |
+ return literalValueRE.test(exp); |
|
94 |
+ } |
|
95 |
+ |
|
96 |
+ /** |
|
97 |
+ * Check if a string starts with $ or _ |
|
98 |
+ * |
|
99 |
+ * @param {String} str |
|
100 |
+ * @return {Boolean} |
|
101 |
+ */ |
|
102 |
+ |
|
103 |
+ function isReserved(str) { |
|
104 |
+ var c = (str + '').charCodeAt(0); |
|
105 |
+ return c === 0x24 || c === 0x5F; |
|
106 |
+ } |
|
107 |
+ |
|
108 |
+ /** |
|
109 |
+ * Guard text output, make sure undefined outputs |
|
110 |
+ * empty string |
|
111 |
+ * |
|
112 |
+ * @param {*} value |
|
113 |
+ * @return {String} |
|
114 |
+ */ |
|
115 |
+ |
|
116 |
+ function _toString(value) { |
|
117 |
+ return value == null ? '' : value.toString(); |
|
118 |
+ } |
|
119 |
+ |
|
120 |
+ /** |
|
121 |
+ * Check and convert possible numeric strings to numbers |
|
122 |
+ * before setting back to data |
|
123 |
+ * |
|
124 |
+ * @param {*} value |
|
125 |
+ * @return {*|Number} |
|
126 |
+ */ |
|
127 |
+ |
|
128 |
+ function toNumber(value) { |
|
129 |
+ if (typeof value !== 'string') { |
|
130 |
+ return value; |
|
131 |
+ } else { |
|
132 |
+ var parsed = Number(value); |
|
133 |
+ return isNaN(parsed) ? value : parsed; |
|
134 |
+ } |
|
135 |
+ } |
|
136 |
+ |
|
137 |
+ /** |
|
138 |
+ * Convert string boolean literals into real booleans. |
|
139 |
+ * |
|
140 |
+ * @param {*} value |
|
141 |
+ * @return {*|Boolean} |
|
142 |
+ */ |
|
143 |
+ |
|
144 |
+ function toBoolean(value) { |
|
145 |
+ return value === 'true' ? true : value === 'false' ? false : value; |
|
146 |
+ } |
|
147 |
+ |
|
148 |
+ /** |
|
149 |
+ * Strip quotes from a string |
|
150 |
+ * |
|
151 |
+ * @param {String} str |
|
152 |
+ * @return {String | false} |
|
153 |
+ */ |
|
154 |
+ |
|
155 |
+ function stripQuotes(str) { |
|
156 |
+ var a = str.charCodeAt(0); |
|
157 |
+ var b = str.charCodeAt(str.length - 1); |
|
158 |
+ return a === b && (a === 0x22 || a === 0x27) ? str.slice(1, -1) : str; |
|
159 |
+ } |
|
160 |
+ |
|
161 |
+ /** |
|
162 |
+ * Camelize a hyphen-delmited string. |
|
163 |
+ * |
|
164 |
+ * @param {String} str |
|
165 |
+ * @return {String} |
|
166 |
+ */ |
|
167 |
+ |
|
168 |
+ var camelizeRE = /-(\w)/g; |
|
169 |
+ |
|
170 |
+ function camelize(str) { |
|
171 |
+ return str.replace(camelizeRE, toUpper); |
|
172 |
+ } |
|
173 |
+ |
|
174 |
+ function toUpper(_, c) { |
|
175 |
+ return c ? c.toUpperCase() : ''; |
|
176 |
+ } |
|
177 |
+ |
|
178 |
+ /** |
|
179 |
+ * Hyphenate a camelCase string. |
|
180 |
+ * |
|
181 |
+ * @param {String} str |
|
182 |
+ * @return {String} |
|
183 |
+ */ |
|
184 |
+ |
|
185 |
+ var hyphenateRE = /([a-z\d])([A-Z])/g; |
|
186 |
+ |
|
187 |
+ function hyphenate(str) { |
|
188 |
+ return str.replace(hyphenateRE, '$1-$2').toLowerCase(); |
|
189 |
+ } |
|
190 |
+ |
|
191 |
+ /** |
|
192 |
+ * Converts hyphen/underscore/slash delimitered names into |
|
193 |
+ * camelized classNames. |
|
194 |
+ * |
|
195 |
+ * e.g. my-component => MyComponent |
|
196 |
+ * some_else => SomeElse |
|
197 |
+ * some/comp => SomeComp |
|
198 |
+ * |
|
199 |
+ * @param {String} str |
|
200 |
+ * @return {String} |
|
201 |
+ */ |
|
202 |
+ |
|
203 |
+ var classifyRE = /(?:^|[-_\/])(\w)/g; |
|
204 |
+ |
|
205 |
+ function classify(str) { |
|
206 |
+ return str.replace(classifyRE, toUpper); |
|
207 |
+ } |
|
208 |
+ |
|
209 |
+ /** |
|
210 |
+ * Simple bind, faster than native |
|
211 |
+ * |
|
212 |
+ * @param {Function} fn |
|
213 |
+ * @param {Object} ctx |
|
214 |
+ * @return {Function} |
|
215 |
+ */ |
|
216 |
+ |
|
217 |
+ function bind(fn, ctx) { |
|
218 |
+ return function (a) { |
|
219 |
+ var l = arguments.length; |
|
220 |
+ return l ? l > 1 ? fn.apply(ctx, arguments) : fn.call(ctx, a) : fn.call(ctx); |
|
221 |
+ }; |
|
222 |
+ } |
|
223 |
+ |
|
224 |
+ /** |
|
225 |
+ * Convert an Array-like object to a real Array. |
|
226 |
+ * |
|
227 |
+ * @param {Array-like} list |
|
228 |
+ * @param {Number} [start] - start index |
|
229 |
+ * @return {Array} |
|
230 |
+ */ |
|
231 |
+ |
|
232 |
+ function toArray(list, start) { |
|
233 |
+ start = start || 0; |
|
234 |
+ var i = list.length - start; |
|
235 |
+ var ret = new Array(i); |
|
236 |
+ while (i--) { |
|
237 |
+ ret[i] = list[i + start]; |
|
238 |
+ } |
|
239 |
+ return ret; |
|
240 |
+ } |
|
241 |
+ |
|
242 |
+ /** |
|
243 |
+ * Mix properties into target object. |
|
244 |
+ * |
|
245 |
+ * @param {Object} to |
|
246 |
+ * @param {Object} from |
|
247 |
+ */ |
|
248 |
+ |
|
249 |
+ function extend(to, from) { |
|
250 |
+ var keys = Object.keys(from); |
|
251 |
+ var i = keys.length; |
|
252 |
+ while (i--) { |
|
253 |
+ to[keys[i]] = from[keys[i]]; |
|
254 |
+ } |
|
255 |
+ return to; |
|
256 |
+ } |
|
257 |
+ |
|
258 |
+ /** |
|
259 |
+ * Quick object check - this is primarily used to tell |
|
260 |
+ * Objects from primitive values when we know the value |
|
261 |
+ * is a JSON-compliant type. |
|
262 |
+ * |
|
263 |
+ * @param {*} obj |
|
264 |
+ * @return {Boolean} |
|
265 |
+ */ |
|
266 |
+ |
|
267 |
+ function isObject(obj) { |
|
268 |
+ return obj !== null && typeof obj === 'object'; |
|
269 |
+ } |
|
270 |
+ |
|
271 |
+ /** |
|
272 |
+ * Strict object type check. Only returns true |
|
273 |
+ * for plain JavaScript objects. |
|
274 |
+ * |
|
275 |
+ * @param {*} obj |
|
276 |
+ * @return {Boolean} |
|
277 |
+ */ |
|
278 |
+ |
|
279 |
+ var toString = Object.prototype.toString; |
|
280 |
+ var OBJECT_STRING = '[object Object]'; |
|
281 |
+ |
|
282 |
+ function isPlainObject(obj) { |
|
283 |
+ return toString.call(obj) === OBJECT_STRING; |
|
284 |
+ } |
|
285 |
+ |
|
286 |
+ /** |
|
287 |
+ * Array type check. |
|
288 |
+ * |
|
289 |
+ * @param {*} obj |
|
290 |
+ * @return {Boolean} |
|
291 |
+ */ |
|
292 |
+ |
|
293 |
+ var isArray = Array.isArray; |
|
294 |
+ |
|
295 |
+ /** |
|
296 |
+ * Define a property. |
|
297 |
+ * |
|
298 |
+ * @param {Object} obj |
|
299 |
+ * @param {String} key |
|
300 |
+ * @param {*} val |
|
301 |
+ * @param {Boolean} [enumerable] |
|
302 |
+ */ |
|
303 |
+ |
|
304 |
+ function def(obj, key, val, enumerable) { |
|
305 |
+ Object.defineProperty(obj, key, { |
|
306 |
+ value: val, |
|
307 |
+ enumerable: !!enumerable, |
|
308 |
+ writable: true, |
|
309 |
+ configurable: true |
|
310 |
+ }); |
|
311 |
+ } |
|
312 |
+ |
|
313 |
+ /** |
|
314 |
+ * Debounce a function so it only gets called after the |
|
315 |
+ * input stops arriving after the given wait period. |
|
316 |
+ * |
|
317 |
+ * @param {Function} func |
|
318 |
+ * @param {Number} wait |
|
319 |
+ * @return {Function} - the debounced function |
|
320 |
+ */ |
|
321 |
+ |
|
322 |
+ function _debounce(func, wait) { |
|
323 |
+ var timeout, args, context, timestamp, result; |
|
324 |
+ var later = function later() { |
|
325 |
+ var last = Date.now() - timestamp; |
|
326 |
+ if (last < wait && last >= 0) { |
|
327 |
+ timeout = setTimeout(later, wait - last); |
|
328 |
+ } else { |
|
329 |
+ timeout = null; |
|
330 |
+ result = func.apply(context, args); |
|
331 |
+ if (!timeout) context = args = null; |
|
332 |
+ } |
|
333 |
+ }; |
|
334 |
+ return function () { |
|
335 |
+ context = this; |
|
336 |
+ args = arguments; |
|
337 |
+ timestamp = Date.now(); |
|
338 |
+ if (!timeout) { |
|
339 |
+ timeout = setTimeout(later, wait); |
|
340 |
+ } |
|
341 |
+ return result; |
|
342 |
+ }; |
|
343 |
+ } |
|
344 |
+ |
|
345 |
+ /** |
|
346 |
+ * Manual indexOf because it's slightly faster than |
|
347 |
+ * native. |
|
348 |
+ * |
|
349 |
+ * @param {Array} arr |
|
350 |
+ * @param {*} obj |
|
351 |
+ */ |
|
352 |
+ |
|
353 |
+ function indexOf(arr, obj) { |
|
354 |
+ var i = arr.length; |
|
355 |
+ while (i--) { |
|
356 |
+ if (arr[i] === obj) return i; |
|
357 |
+ } |
|
358 |
+ return -1; |
|
359 |
+ } |
|
360 |
+ |
|
361 |
+ /** |
|
362 |
+ * Make a cancellable version of an async callback. |
|
363 |
+ * |
|
364 |
+ * @param {Function} fn |
|
365 |
+ * @return {Function} |
|
366 |
+ */ |
|
367 |
+ |
|
368 |
+ function cancellable(fn) { |
|
369 |
+ var cb = function cb() { |
|
370 |
+ if (!cb.cancelled) { |
|
371 |
+ return fn.apply(this, arguments); |
|
372 |
+ } |
|
373 |
+ }; |
|
374 |
+ cb.cancel = function () { |
|
375 |
+ cb.cancelled = true; |
|
376 |
+ }; |
|
377 |
+ return cb; |
|
378 |
+ } |
|
379 |
+ |
|
380 |
+ /** |
|
381 |
+ * Check if two values are loosely equal - that is, |
|
382 |
+ * if they are plain objects, do they have the same shape? |
|
383 |
+ * |
|
384 |
+ * @param {*} a |
|
385 |
+ * @param {*} b |
|
386 |
+ * @return {Boolean} |
|
387 |
+ */ |
|
388 |
+ |
|
389 |
+ function looseEqual(a, b) { |
|
390 |
+ /* eslint-disable eqeqeq */ |
|
391 |
+ return a == b || (isObject(a) && isObject(b) ? JSON.stringify(a) === JSON.stringify(b) : false); |
|
392 |
+ /* eslint-enable eqeqeq */ |
|
393 |
+ } |
|
394 |
+ |
|
395 |
+ var hasProto = ('__proto__' in {}); |
|
396 |
+ |
|
397 |
+ // Browser environment sniffing |
|
398 |
+ var inBrowser = typeof window !== 'undefined' && Object.prototype.toString.call(window) !== '[object Object]'; |
|
399 |
+ |
|
400 |
+ // detect devtools |
|
401 |
+ var devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__; |
|
402 |
+ |
|
403 |
+ // UA sniffing for working around browser-specific quirks |
|
404 |
+ var UA = inBrowser && window.navigator.userAgent.toLowerCase(); |
|
405 |
+ var isIE = UA && UA.indexOf('trident') > 0; |
|
406 |
+ var isIE9 = UA && UA.indexOf('msie 9.0') > 0; |
|
407 |
+ var isAndroid = UA && UA.indexOf('android') > 0; |
|
408 |
+ var isIos = UA && /(iphone|ipad|ipod|ios)/i.test(UA); |
|
409 |
+ var iosVersionMatch = isIos && UA.match(/os ([\d_]+)/); |
|
410 |
+ var iosVersion = iosVersionMatch && iosVersionMatch[1].split('_'); |
|
411 |
+ |
|
412 |
+ // detecting iOS UIWebView by indexedDB |
|
413 |
+ var hasMutationObserverBug = iosVersion && Number(iosVersion[0]) >= 9 && Number(iosVersion[1]) >= 3 && !window.indexedDB; |
|
414 |
+ |
|
415 |
+ var transitionProp = undefined; |
|
416 |
+ var transitionEndEvent = undefined; |
|
417 |
+ var animationProp = undefined; |
|
418 |
+ var animationEndEvent = undefined; |
|
419 |
+ |
|
420 |
+ // Transition property/event sniffing |
|
421 |
+ if (inBrowser && !isIE9) { |
|
422 |
+ var isWebkitTrans = window.ontransitionend === undefined && window.onwebkittransitionend !== undefined; |
|
423 |
+ var isWebkitAnim = window.onanimationend === undefined && window.onwebkitanimationend !== undefined; |
|
424 |
+ transitionProp = isWebkitTrans ? 'WebkitTransition' : 'transition'; |
|
425 |
+ transitionEndEvent = isWebkitTrans ? 'webkitTransitionEnd' : 'transitionend'; |
|
426 |
+ animationProp = isWebkitAnim ? 'WebkitAnimation' : 'animation'; |
|
427 |
+ animationEndEvent = isWebkitAnim ? 'webkitAnimationEnd' : 'animationend'; |
|
428 |
+ } |
|
429 |
+ |
|
430 |
+ /** |
|
431 |
+ * Defer a task to execute it asynchronously. Ideally this |
|
432 |
+ * should be executed as a microtask, so we leverage |
|
433 |
+ * MutationObserver if it's available, and fallback to |
|
434 |
+ * setTimeout(0). |
|
435 |
+ * |
|
436 |
+ * @param {Function} cb |
|
437 |
+ * @param {Object} ctx |
|
438 |
+ */ |
|
439 |
+ |
|
440 |
+ var nextTick = (function () { |
|
441 |
+ var callbacks = []; |
|
442 |
+ var pending = false; |
|
443 |
+ var timerFunc; |
|
444 |
+ function nextTickHandler() { |
|
445 |
+ pending = false; |
|
446 |
+ var copies = callbacks.slice(0); |
|
447 |
+ callbacks = []; |
|
448 |
+ for (var i = 0; i < copies.length; i++) { |
|
449 |
+ copies[i](); |
|
450 |
+ } |
|
451 |
+ } |
|
452 |
+ |
|
453 |
+ /* istanbul ignore if */ |
|
454 |
+ if (typeof MutationObserver !== 'undefined' && !hasMutationObserverBug) { |
|
455 |
+ var counter = 1; |
|
456 |
+ var observer = new MutationObserver(nextTickHandler); |
|
457 |
+ var textNode = document.createTextNode(counter); |
|
458 |
+ observer.observe(textNode, { |
|
459 |
+ characterData: true |
|
460 |
+ }); |
|
461 |
+ timerFunc = function () { |
|
462 |
+ counter = (counter + 1) % 2; |
|
463 |
+ textNode.data = counter; |
|
464 |
+ }; |
|
465 |
+ } else { |
|
466 |
+ // webpack attempts to inject a shim for setImmediate |
|
467 |
+ // if it is used as a global, so we have to work around that to |
|
468 |
+ // avoid bundling unnecessary code. |
|
469 |
+ var context = inBrowser ? window : typeof global !== 'undefined' ? global : {}; |
|
470 |
+ timerFunc = context.setImmediate || setTimeout; |
|
471 |
+ } |
|
472 |
+ return function (cb, ctx) { |
|
473 |
+ var func = ctx ? function () { |
|
474 |
+ cb.call(ctx); |
|
475 |
+ } : cb; |
|
476 |
+ callbacks.push(func); |
|
477 |
+ if (pending) return; |
|
478 |
+ pending = true; |
|
479 |
+ timerFunc(nextTickHandler, 0); |
|
480 |
+ }; |
|
481 |
+ })(); |
|
482 |
+ |
|
483 |
+ var _Set = undefined; |
|
484 |
+ /* istanbul ignore if */ |
|
485 |
+ if (typeof Set !== 'undefined' && Set.toString().match(/native code/)) { |
|
486 |
+ // use native Set when available. |
|
487 |
+ _Set = Set; |
|
488 |
+ } else { |
|
489 |
+ // a non-standard Set polyfill that only works with primitive keys. |
|
490 |
+ _Set = function () { |
|
491 |
+ this.set = Object.create(null); |
|
492 |
+ }; |
|
493 |
+ _Set.prototype.has = function (key) { |
|
494 |
+ return this.set[key] !== undefined; |
|
495 |
+ }; |
|
496 |
+ _Set.prototype.add = function (key) { |
|
497 |
+ this.set[key] = 1; |
|
498 |
+ }; |
|
499 |
+ _Set.prototype.clear = function () { |
|
500 |
+ this.set = Object.create(null); |
|
501 |
+ }; |
|
502 |
+ } |
|
503 |
+ |
|
504 |
+ function Cache(limit) { |
|
505 |
+ this.size = 0; |
|
506 |
+ this.limit = limit; |
|
507 |
+ this.head = this.tail = undefined; |
|
508 |
+ this._keymap = Object.create(null); |
|
509 |
+ } |
|
510 |
+ |
|
511 |
+ var p = Cache.prototype; |
|
512 |
+ |
|
513 |
+ /** |
|
514 |
+ * Put <value> into the cache associated with <key>. |
|
515 |
+ * Returns the entry which was removed to make room for |
|
516 |
+ * the new entry. Otherwise undefined is returned. |
|
517 |
+ * (i.e. if there was enough room already). |
|
518 |
+ * |
|
519 |
+ * @param {String} key |
|
520 |
+ * @param {*} value |
|
521 |
+ * @return {Entry|undefined} |
|
522 |
+ */ |
|
523 |
+ |
|
524 |
+ p.put = function (key, value) { |
|
525 |
+ var removed; |
|
526 |
+ |
|
527 |
+ var entry = this.get(key, true); |
|
528 |
+ if (!entry) { |
|
529 |
+ if (this.size === this.limit) { |
|
530 |
+ removed = this.shift(); |
|
531 |
+ } |
|
532 |
+ entry = { |
|
533 |
+ key: key |
|
534 |
+ }; |
|
535 |
+ this._keymap[key] = entry; |
|
536 |
+ if (this.tail) { |
|
537 |
+ this.tail.newer = entry; |
|
538 |
+ entry.older = this.tail; |
|
539 |
+ } else { |
|
540 |
+ this.head = entry; |
|
541 |
+ } |
|
542 |
+ this.tail = entry; |
|
543 |
+ this.size++; |
|
544 |
+ } |
|
545 |
+ entry.value = value; |
|
546 |
+ |
|
547 |
+ return removed; |
|
548 |
+ }; |
|
549 |
+ |
|
550 |
+ /** |
|
551 |
+ * Purge the least recently used (oldest) entry from the |
|
552 |
+ * cache. Returns the removed entry or undefined if the |
|
553 |
+ * cache was empty. |
|
554 |
+ */ |
|
555 |
+ |
|
556 |
+ p.shift = function () { |
|
557 |
+ var entry = this.head; |
|
558 |
+ if (entry) { |
|
559 |
+ this.head = this.head.newer; |
|
560 |
+ this.head.older = undefined; |
|
561 |
+ entry.newer = entry.older = undefined; |
|
562 |
+ this._keymap[entry.key] = undefined; |
|
563 |
+ this.size--; |
|
564 |
+ } |
|
565 |
+ return entry; |
|
566 |
+ }; |
|
567 |
+ |
|
568 |
+ /** |
|
569 |
+ * Get and register recent use of <key>. Returns the value |
|
570 |
+ * associated with <key> or undefined if not in cache. |
|
571 |
+ * |
|
572 |
+ * @param {String} key |
|
573 |
+ * @param {Boolean} returnEntry |
|
574 |
+ * @return {Entry|*} |
|
575 |
+ */ |
|
576 |
+ |
|
577 |
+ p.get = function (key, returnEntry) { |
|
578 |
+ var entry = this._keymap[key]; |
|
579 |
+ if (entry === undefined) return; |
|
580 |
+ if (entry === this.tail) { |
|
581 |
+ return returnEntry ? entry : entry.value; |
|
582 |
+ } |
|
583 |
+ // HEAD--------------TAIL |
|
584 |
+ // <.older .newer> |
|
585 |
+ // <--- add direction -- |
|
586 |
+ // A B C <D> E |
|
587 |
+ if (entry.newer) { |
|
588 |
+ if (entry === this.head) { |
|
589 |
+ this.head = entry.newer; |
|
590 |
+ } |
|
591 |
+ entry.newer.older = entry.older; // C <-- E. |
|
592 |
+ } |
|
593 |
+ if (entry.older) { |
|
594 |
+ entry.older.newer = entry.newer; // C. --> E |
|
595 |
+ } |
|
596 |
+ entry.newer = undefined; // D --x |
|
597 |
+ entry.older = this.tail; // D. --> E |
|
598 |
+ if (this.tail) { |
|
599 |
+ this.tail.newer = entry; // E. <-- D |
|
600 |
+ } |
|
601 |
+ this.tail = entry; |
|
602 |
+ return returnEntry ? entry : entry.value; |
|
603 |
+ }; |
|
604 |
+ |
|
605 |
+ var cache$1 = new Cache(1000); |
|
606 |
+ var filterTokenRE = /[^\s'"]+|'[^']*'|"[^"]*"/g; |
|
607 |
+ var reservedArgRE = /^in$|^-?\d+/; |
|
608 |
+ |
|
609 |
+ /** |
|
610 |
+ * Parser state |
|
611 |
+ */ |
|
612 |
+ |
|
613 |
+ var str; |
|
614 |
+ var dir; |
|
615 |
+ var c; |
|
616 |
+ var prev; |
|
617 |
+ var i; |
|
618 |
+ var l; |
|
619 |
+ var lastFilterIndex; |
|
620 |
+ var inSingle; |
|
621 |
+ var inDouble; |
|
622 |
+ var curly; |
|
623 |
+ var square; |
|
624 |
+ var paren; |
|
625 |
+ /** |
|
626 |
+ * Push a filter to the current directive object |
|
627 |
+ */ |
|
628 |
+ |
|
629 |
+ function pushFilter() { |
|
630 |
+ var exp = str.slice(lastFilterIndex, i).trim(); |
|
631 |
+ var filter; |
|
632 |
+ if (exp) { |
|
633 |
+ filter = {}; |
|
634 |
+ var tokens = exp.match(filterTokenRE); |
|
635 |
+ filter.name = tokens[0]; |
|
636 |
+ if (tokens.length > 1) { |
|
637 |
+ filter.args = tokens.slice(1).map(processFilterArg); |
|
638 |
+ } |
|
639 |
+ } |
|
640 |
+ if (filter) { |
|
641 |
+ (dir.filters = dir.filters || []).push(filter); |
|
642 |
+ } |
|
643 |
+ lastFilterIndex = i + 1; |
|
644 |
+ } |
|
645 |
+ |
|
646 |
+ /** |
|
647 |
+ * Check if an argument is dynamic and strip quotes. |
|
648 |
+ * |
|
649 |
+ * @param {String} arg |
|
650 |
+ * @return {Object} |
|
651 |
+ */ |
|
652 |
+ |
|
653 |
+ function processFilterArg(arg) { |
|
654 |
+ if (reservedArgRE.test(arg)) { |
|
655 |
+ return { |
|
656 |
+ value: toNumber(arg), |
|
657 |
+ dynamic: false |
|
658 |
+ }; |
|
659 |
+ } else { |
|
660 |
+ var stripped = stripQuotes(arg); |
|
661 |
+ var dynamic = stripped === arg; |
|
662 |
+ return { |
|
663 |
+ value: dynamic ? arg : stripped, |
|
664 |
+ dynamic: dynamic |
|
665 |
+ }; |
|
666 |
+ } |
|
667 |
+ } |
|
668 |
+ |
|
669 |
+ /** |
|
670 |
+ * Parse a directive value and extract the expression |
|
671 |
+ * and its filters into a descriptor. |
|
672 |
+ * |
|
673 |
+ * Example: |
|
674 |
+ * |
|
675 |
+ * "a + 1 | uppercase" will yield: |
|
676 |
+ * { |
|
677 |
+ * expression: 'a + 1', |
|
678 |
+ * filters: [ |
|
679 |
+ * { name: 'uppercase', args: null } |
|
680 |
+ * ] |
|
681 |
+ * } |
|
682 |
+ * |
|
683 |
+ * @param {String} s |
|
684 |
+ * @return {Object} |
|
685 |
+ */ |
|
686 |
+ |
|
687 |
+ function parseDirective(s) { |
|
688 |
+ var hit = cache$1.get(s); |
|
689 |
+ if (hit) { |
|
690 |
+ return hit; |
|
691 |
+ } |
|
692 |
+ |
|
693 |
+ // reset parser state |
|
694 |
+ str = s; |
|
695 |
+ inSingle = inDouble = false; |
|
696 |
+ curly = square = paren = 0; |
|
697 |
+ lastFilterIndex = 0; |
|
698 |
+ dir = {}; |
|
699 |
+ |
|
700 |
+ for (i = 0, l = str.length; i < l; i++) { |
|
701 |
+ prev = c; |
|
702 |
+ c = str.charCodeAt(i); |
|
703 |
+ if (inSingle) { |
|
704 |
+ // check single quote |
|
705 |
+ if (c === 0x27 && prev !== 0x5C) inSingle = !inSingle; |
|
706 |
+ } else if (inDouble) { |
|
707 |
+ // check double quote |
|
708 |
+ if (c === 0x22 && prev !== 0x5C) inDouble = !inDouble; |
|
709 |
+ } else if (c === 0x7C && // pipe |
|
710 |
+ str.charCodeAt(i + 1) !== 0x7C && str.charCodeAt(i - 1) !== 0x7C) { |
|
711 |
+ if (dir.expression == null) { |
|
712 |
+ // first filter, end of expression |
|
713 |
+ lastFilterIndex = i + 1; |
|
714 |
+ dir.expression = str.slice(0, i).trim(); |
|
715 |
+ } else { |
|
716 |
+ // already has filter |
|
717 |
+ pushFilter(); |
|
718 |
+ } |
|
719 |
+ } else { |
|
720 |
+ switch (c) { |
|
721 |
+ case 0x22: |
|
722 |
+ inDouble = true;break; // " |
|
723 |
+ case 0x27: |
|
724 |
+ inSingle = true;break; // ' |
|
725 |
+ case 0x28: |
|
726 |
+ paren++;break; // ( |
|
727 |
+ case 0x29: |
|
728 |
+ paren--;break; // ) |
|
729 |
+ case 0x5B: |
|
730 |
+ square++;break; // [ |
|
731 |
+ case 0x5D: |
|
732 |
+ square--;break; // ] |
|
733 |
+ case 0x7B: |
|
734 |
+ curly++;break; // { |
|
735 |
+ case 0x7D: |
|
736 |
+ curly--;break; // } |
|
737 |
+ } |
|
738 |
+ } |
|
739 |
+ } |
|
740 |
+ |
|
741 |
+ if (dir.expression == null) { |
|
742 |
+ dir.expression = str.slice(0, i).trim(); |
|
743 |
+ } else if (lastFilterIndex !== 0) { |
|
744 |
+ pushFilter(); |
|
745 |
+ } |
|
746 |
+ |
|
747 |
+ cache$1.put(s, dir); |
|
748 |
+ return dir; |
|
749 |
+ } |
|
750 |
+ |
|
751 |
+var directive = Object.freeze({ |
|
752 |
+ parseDirective: parseDirective |
|
753 |
+ }); |
|
754 |
+ |
|
755 |
+ var regexEscapeRE = /[-.*+?^${}()|[\]\/\\]/g; |
|
756 |
+ var cache = undefined; |
|
757 |
+ var tagRE = undefined; |
|
758 |
+ var htmlRE = undefined; |
|
759 |
+ /** |
|
760 |
+ * Escape a string so it can be used in a RegExp |
|
761 |
+ * constructor. |
|
762 |
+ * |
|
763 |
+ * @param {String} str |
|
764 |
+ */ |
|
765 |
+ |
|
766 |
+ function escapeRegex(str) { |
|
767 |
+ return str.replace(regexEscapeRE, '\\$&'); |
|
768 |
+ } |
|
769 |
+ |
|
770 |
+ function compileRegex() { |
|
771 |
+ var open = escapeRegex(config.delimiters[0]); |
|
772 |
+ var close = escapeRegex(config.delimiters[1]); |
|
773 |
+ var unsafeOpen = escapeRegex(config.unsafeDelimiters[0]); |
|
774 |
+ var unsafeClose = escapeRegex(config.unsafeDelimiters[1]); |
|
775 |
+ tagRE = new RegExp(unsafeOpen + '((?:.|\\n)+?)' + unsafeClose + '|' + open + '((?:.|\\n)+?)' + close, 'g'); |
|
776 |
+ htmlRE = new RegExp('^' + unsafeOpen + '((?:.|\\n)+?)' + unsafeClose + '$'); |
|
777 |
+ // reset cache |
|
778 |
+ cache = new Cache(1000); |
|
779 |
+ } |
|
780 |
+ |
|
781 |
+ /** |
|
782 |
+ * Parse a template text string into an array of tokens. |
|
783 |
+ * |
|
784 |
+ * @param {String} text |
|
785 |
+ * @return {Array<Object> | null} |
|
786 |
+ * - {String} type |
|
787 |
+ * - {String} value |
|
788 |
+ * - {Boolean} [html] |
|
789 |
+ * - {Boolean} [oneTime] |
|
790 |
+ */ |
|
791 |
+ |
|
792 |
+ function parseText(text) { |
|
793 |
+ if (!cache) { |
|
794 |
+ compileRegex(); |
|
795 |
+ } |
|
796 |
+ var hit = cache.get(text); |
|
797 |
+ if (hit) { |
|
798 |
+ return hit; |
|
799 |
+ } |
|
800 |
+ if (!tagRE.test(text)) { |
|
801 |
+ return null; |
|
802 |
+ } |
|
803 |
+ var tokens = []; |
|
804 |
+ var lastIndex = tagRE.lastIndex = 0; |
|
805 |
+ var match, index, html, value, first, oneTime; |
|
806 |
+ /* eslint-disable no-cond-assign */ |
|
807 |
+ while (match = tagRE.exec(text)) { |
|
808 |
+ /* eslint-enable no-cond-assign */ |
|
809 |
+ index = match.index; |
|
810 |
+ // push text token |
|
811 |
+ if (index > lastIndex) { |
|
812 |
+ tokens.push({ |
|
813 |
+ value: text.slice(lastIndex, index) |
|
814 |
+ }); |
|
815 |
+ } |
|
816 |
+ // tag token |
|
817 |
+ html = htmlRE.test(match[0]); |
|
818 |
+ value = html ? match[1] : match[2]; |
|
819 |
+ first = value.charCodeAt(0); |
|
820 |
+ oneTime = first === 42; // * |
|
821 |
+ value = oneTime ? value.slice(1) : value; |
|
822 |
+ tokens.push({ |
|
823 |
+ tag: true, |
|
824 |
+ value: value.trim(), |
|
825 |
+ html: html, |
|
826 |
+ oneTime: oneTime |
|
827 |
+ }); |
|
828 |
+ lastIndex = index + match[0].length; |
|
829 |
+ } |
|
830 |
+ if (lastIndex < text.length) { |
|
831 |
+ tokens.push({ |
|
832 |
+ value: text.slice(lastIndex) |
|
833 |
+ }); |
|
834 |
+ } |
|
835 |
+ cache.put(text, tokens); |
|
836 |
+ return tokens; |
|
837 |
+ } |
|
838 |
+ |
|
839 |
+ /** |
|
840 |
+ * Format a list of tokens into an expression. |
|
841 |
+ * e.g. tokens parsed from 'a {{b}} c' can be serialized |
|
842 |
+ * into one single expression as '"a " + b + " c"'. |
|
843 |
+ * |
|
844 |
+ * @param {Array} tokens |
|
845 |
+ * @param {Vue} [vm] |
|
846 |
+ * @return {String} |
|
847 |
+ */ |
|
848 |
+ |
|
849 |
+ function tokensToExp(tokens, vm) { |
|
850 |
+ if (tokens.length > 1) { |
|
851 |
+ return tokens.map(function (token) { |
|
852 |
+ return formatToken(token, vm); |
|
853 |
+ }).join('+'); |
|
854 |
+ } else { |
|
855 |
+ return formatToken(tokens[0], vm, true); |
|
856 |
+ } |
|
857 |
+ } |
|
858 |
+ |
|
859 |
+ /** |
|
860 |
+ * Format a single token. |
|
861 |
+ * |
|
862 |
+ * @param {Object} token |
|
863 |
+ * @param {Vue} [vm] |
|
864 |
+ * @param {Boolean} [single] |
|
865 |
+ * @return {String} |
|
866 |
+ */ |
|
867 |
+ |
|
868 |
+ function formatToken(token, vm, single) { |
|
869 |
+ return token.tag ? token.oneTime && vm ? '"' + vm.$eval(token.value) + '"' : inlineFilters(token.value, single) : '"' + token.value + '"'; |
|
870 |
+ } |
|
871 |
+ |
|
872 |
+ /** |
|
873 |
+ * For an attribute with multiple interpolation tags, |
|
874 |
+ * e.g. attr="some-{{thing | filter}}", in order to combine |
|
875 |
+ * the whole thing into a single watchable expression, we |
|
876 |
+ * have to inline those filters. This function does exactly |
|
877 |
+ * that. This is a bit hacky but it avoids heavy changes |
|
878 |
+ * to directive parser and watcher mechanism. |
|
879 |
+ * |
|
880 |
+ * @param {String} exp |
|
881 |
+ * @param {Boolean} single |
|
882 |
+ * @return {String} |
|
883 |
+ */ |
|
884 |
+ |
|
885 |
+ var filterRE = /[^|]\|[^|]/; |
|
886 |
+ function inlineFilters(exp, single) { |
|
887 |
+ if (!filterRE.test(exp)) { |
|
888 |
+ return single ? exp : '(' + exp + ')'; |
|
889 |
+ } else { |
|
890 |
+ var dir = parseDirective(exp); |
|
891 |
+ if (!dir.filters) { |
|
892 |
+ return '(' + exp + ')'; |
|
893 |
+ } else { |
|
894 |
+ return 'this._applyFilters(' + dir.expression + // value |
|
895 |
+ ',null,' + // oldValue (null for read) |
|
896 |
+ JSON.stringify(dir.filters) + // filter descriptors |
|
897 |
+ ',false)'; // write? |
|
898 |
+ } |
|
899 |
+ } |
|
900 |
+ } |
|
901 |
+ |
|
902 |
+var text = Object.freeze({ |
|
903 |
+ compileRegex: compileRegex, |
|
904 |
+ parseText: parseText, |
|
905 |
+ tokensToExp: tokensToExp |
|
906 |
+ }); |
|
907 |
+ |
|
908 |
+ var delimiters = ['{{', '}}']; |
|
909 |
+ var unsafeDelimiters = ['{{{', '}}}']; |
|
910 |
+ |
|
911 |
+ var config = Object.defineProperties({ |
|
912 |
+ |
|
913 |
+ /** |
|
914 |
+ * Whether to print debug messages. |
|
915 |
+ * Also enables stack trace for warnings. |
|
916 |
+ * |
|
917 |
+ * @type {Boolean} |
|
918 |
+ */ |
|
919 |
+ |
|
920 |
+ debug: false, |
|
921 |
+ |
|
922 |
+ /** |
|
923 |
+ * Whether to suppress warnings. |
|
924 |
+ * |
|
925 |
+ * @type {Boolean} |
|
926 |
+ */ |
|
927 |
+ |
|
928 |
+ silent: false, |
|
929 |
+ |
|
930 |
+ /** |
|
931 |
+ * Whether to use async rendering. |
|
932 |
+ */ |
|
933 |
+ |
|
934 |
+ async: true, |
|
935 |
+ |
|
936 |
+ /** |
|
937 |
+ * Whether to warn against errors caught when evaluating |
|
938 |
+ * expressions. |
|
939 |
+ */ |
|
940 |
+ |
|
941 |
+ warnExpressionErrors: true, |
|
942 |
+ |
|
943 |
+ /** |
|
944 |
+ * Whether to allow devtools inspection. |
|
945 |
+ * Disabled by default in production builds. |
|
946 |
+ */ |
|
947 |
+ |
|
948 |
+ devtools: 'development' !== 'production', |
|
949 |
+ |
|
950 |
+ /** |
|
951 |
+ * Internal flag to indicate the delimiters have been |
|
952 |
+ * changed. |
|
953 |
+ * |
|
954 |
+ * @type {Boolean} |
|
955 |
+ */ |
|
956 |
+ |
|
957 |
+ _delimitersChanged: true, |
|
958 |
+ |
|
959 |
+ /** |
|
960 |
+ * List of asset types that a component can own. |
|
961 |
+ * |
|
962 |
+ * @type {Array} |
|
963 |
+ */ |
|
964 |
+ |
|
965 |
+ _assetTypes: ['component', 'directive', 'elementDirective', 'filter', 'transition', 'partial'], |
|
966 |
+ |
|
967 |
+ /** |
|
968 |
+ * prop binding modes |
|
969 |
+ */ |
|
970 |
+ |
|
971 |
+ _propBindingModes: { |
|
972 |
+ ONE_WAY: 0, |
|
973 |
+ TWO_WAY: 1, |
|
974 |
+ ONE_TIME: 2 |
|
975 |
+ }, |
|
976 |
+ |
|
977 |
+ /** |
|
978 |
+ * Max circular updates allowed in a batcher flush cycle. |
|
979 |
+ */ |
|
980 |
+ |
|
981 |
+ _maxUpdateCount: 100 |
|
982 |
+ |
|
983 |
+ }, { |
|
984 |
+ delimiters: { /** |
|
985 |
+ * Interpolation delimiters. Changing these would trigger |
|
986 |
+ * the text parser to re-compile the regular expressions. |
|
987 |
+ * |
|
988 |
+ * @type {Array<String>} |
|
989 |
+ */ |
|
990 |
+ |
|
991 |
+ get: function get() { |
|
992 |
+ return delimiters; |
|
993 |
+ }, |
|
994 |
+ set: function set(val) { |
|
995 |
+ delimiters = val; |
|
996 |
+ compileRegex(); |
|
997 |
+ }, |
|
998 |
+ configurable: true, |
|
999 |
+ enumerable: true |
|
1000 |
+ }, |
|
1001 |
+ unsafeDelimiters: { |
|
1002 |
+ get: function get() { |
|
1003 |
+ return unsafeDelimiters; |
|
1004 |
+ }, |
|
1005 |
+ set: function set(val) { |
|
1006 |
+ unsafeDelimiters = val; |
|
1007 |
+ compileRegex(); |
|
1008 |
+ }, |
|
1009 |
+ configurable: true, |
|
1010 |
+ enumerable: true |
|
1011 |
+ } |
|
1012 |
+ }); |
|
1013 |
+ |
|
1014 |
+ var warn = undefined; |
|
1015 |
+ var formatComponentName = undefined; |
|
1016 |
+ |
|
1017 |
+ if ('development' !== 'production') { |
|
1018 |
+ (function () { |
|
1019 |
+ var hasConsole = typeof console !== 'undefined'; |
|
1020 |
+ |
|
1021 |
+ warn = function (msg, vm) { |
|
1022 |
+ if (hasConsole && !config.silent) { |
|
1023 |
+ console.error('[Vue warn]: ' + msg + (vm ? formatComponentName(vm) : '')); |
|
1024 |
+ } |
|
1025 |
+ }; |
|
1026 |
+ |
|
1027 |
+ formatComponentName = function (vm) { |
|
1028 |
+ var name = vm._isVue ? vm.$options.name : vm.name; |
|
1029 |
+ return name ? ' (found in component: <' + hyphenate(name) + '>)' : ''; |
|
1030 |
+ }; |
|
1031 |
+ })(); |
|
1032 |
+ } |
|
1033 |
+ |
|
1034 |
+ /** |
|
1035 |
+ * Append with transition. |
|
1036 |
+ * |
|
1037 |
+ * @param {Element} el |
|
1038 |
+ * @param {Element} target |
|
1039 |
+ * @param {Vue} vm |
|
1040 |
+ * @param {Function} [cb] |
|
1041 |
+ */ |
|
1042 |
+ |
|
1043 |
+ function appendWithTransition(el, target, vm, cb) { |
|
1044 |
+ applyTransition(el, 1, function () { |
|
1045 |
+ target.appendChild(el); |
|
1046 |
+ }, vm, cb); |
|
1047 |
+ } |
|
1048 |
+ |
|
1049 |
+ /** |
|
1050 |
+ * InsertBefore with transition. |
|
1051 |
+ * |
|
1052 |
+ * @param {Element} el |
|
1053 |
+ * @param {Element} target |
|
1054 |
+ * @param {Vue} vm |
|
1055 |
+ * @param {Function} [cb] |
|
1056 |
+ */ |
|
1057 |
+ |
|
1058 |
+ function beforeWithTransition(el, target, vm, cb) { |
|
1059 |
+ applyTransition(el, 1, function () { |
|
1060 |
+ before(el, target); |
|
1061 |
+ }, vm, cb); |
|
1062 |
+ } |
|
1063 |
+ |
|
1064 |
+ /** |
|
1065 |
+ * Remove with transition. |
|
1066 |
+ * |
|
1067 |
+ * @param {Element} el |
|
1068 |
+ * @param {Vue} vm |
|
1069 |
+ * @param {Function} [cb] |
|
1070 |
+ */ |
|
1071 |
+ |
|
1072 |
+ function removeWithTransition(el, vm, cb) { |
|
1073 |
+ applyTransition(el, -1, function () { |
|
1074 |
+ remove(el); |
|
1075 |
+ }, vm, cb); |
|
1076 |
+ } |
|
1077 |
+ |
|
1078 |
+ /** |
|
1079 |
+ * Apply transitions with an operation callback. |
|
1080 |
+ * |
|
1081 |
+ * @param {Element} el |
|
1082 |
+ * @param {Number} direction |
|
1083 |
+ * 1: enter |
|
1084 |
+ * -1: leave |
|
1085 |
+ * @param {Function} op - the actual DOM operation |
|
1086 |
+ * @param {Vue} vm |
|
1087 |
+ * @param {Function} [cb] |
|
1088 |
+ */ |
|
1089 |
+ |
|
1090 |
+ function applyTransition(el, direction, op, vm, cb) { |
|
1091 |
+ var transition = el.__v_trans; |
|
1092 |
+ if (!transition || |
|
1093 |
+ // skip if there are no js hooks and CSS transition is |
|
1094 |
+ // not supported |
|
1095 |
+ !transition.hooks && !transitionEndEvent || |
|
1096 |
+ // skip transitions for initial compile |
|
1097 |
+ !vm._isCompiled || |
|
1098 |
+ // if the vm is being manipulated by a parent directive |
|
1099 |
+ // during the parent's compilation phase, skip the |
|
1100 |
+ // animation. |
|
1101 |
+ vm.$parent && !vm.$parent._isCompiled) { |
|
1102 |
+ op(); |
|
1103 |
+ if (cb) cb(); |
|
1104 |
+ return; |
|
1105 |
+ } |
|
1106 |
+ var action = direction > 0 ? 'enter' : 'leave'; |
|
1107 |
+ transition[action](op, cb); |
|
1108 |
+ } |
|
1109 |
+ |
|
1110 |
+var transition = Object.freeze({ |
|
1111 |
+ appendWithTransition: appendWithTransition, |
|
1112 |
+ beforeWithTransition: beforeWithTransition, |
|
1113 |
+ removeWithTransition: removeWithTransition, |
|
1114 |
+ applyTransition: applyTransition |
|
1115 |
+ }); |
|
1116 |
+ |
|
1117 |
+ /** |
|
1118 |
+ * Query an element selector if it's not an element already. |
|
1119 |
+ * |
|
1120 |
+ * @param {String|Element} el |
|
1121 |
+ * @return {Element} |
|
1122 |
+ */ |
|
1123 |
+ |
|
1124 |
+ function query(el) { |
|
1125 |
+ if (typeof el === 'string') { |
|
1126 |
+ var selector = el; |
|
1127 |
+ el = document.querySelector(el); |
|
1128 |
+ if (!el) { |
|
1129 |
+ 'development' !== 'production' && warn('Cannot find element: ' + selector); |
|
1130 |
+ } |
|
1131 |
+ } |
|
1132 |
+ return el; |
|
1133 |
+ } |
|
1134 |
+ |
|
1135 |
+ /** |
|
1136 |
+ * Check if a node is in the document. |
|
1137 |
+ * Note: document.documentElement.contains should work here |
|
1138 |
+ * but always returns false for comment nodes in phantomjs, |
|
1139 |
+ * making unit tests difficult. This is fixed by doing the |
|
1140 |
+ * contains() check on the node's parentNode instead of |
|
1141 |
+ * the node itself. |
|
1142 |
+ * |
|
1143 |
+ * @param {Node} node |
|
1144 |
+ * @return {Boolean} |
|
1145 |
+ */ |
|
1146 |
+ |
|
1147 |
+ function inDoc(node) { |
|
1148 |
+ if (!node) return false; |
|
1149 |
+ var doc = node.ownerDocument.documentElement; |
|
1150 |
+ var parent = node.parentNode; |
|
1151 |
+ return doc === node || doc === parent || !!(parent && parent.nodeType === 1 && doc.contains(parent)); |
|
1152 |
+ } |
|
1153 |
+ |
|
1154 |
+ /** |
|
1155 |
+ * Get and remove an attribute from a node. |
|
1156 |
+ * |
|
1157 |
+ * @param {Node} node |
|
1158 |
+ * @param {String} _attr |
|
1159 |
+ */ |
|
1160 |
+ |
|
1161 |
+ function getAttr(node, _attr) { |
|
1162 |
+ var val = node.getAttribute(_attr); |
|
1163 |
+ if (val !== null) { |
|
1164 |
+ node.removeAttribute(_attr); |
|
1165 |
+ } |
|
1166 |
+ return val; |
|
1167 |
+ } |
|
1168 |
+ |
|
1169 |
+ /** |
|
1170 |
+ * Get an attribute with colon or v-bind: prefix. |
|
1171 |
+ * |
|
1172 |
+ * @param {Node} node |
|
1173 |
+ * @param {String} name |
|
1174 |
+ * @return {String|null} |
|
1175 |
+ */ |
|
1176 |
+ |
|
1177 |
+ function getBindAttr(node, name) { |
|
1178 |
+ var val = getAttr(node, ':' + name); |
|
1179 |
+ if (val === null) { |
|
1180 |
+ val = getAttr(node, 'v-bind:' + name); |
|
1181 |
+ } |
|
1182 |
+ return val; |
|
1183 |
+ } |
|
1184 |
+ |
|
1185 |
+ /** |
|
1186 |
+ * Check the presence of a bind attribute. |
|
1187 |
+ * |
|
1188 |
+ * @param {Node} node |
|
1189 |
+ * @param {String} name |
|
1190 |
+ * @return {Boolean} |
|
1191 |
+ */ |
|
1192 |
+ |
|
1193 |
+ function hasBindAttr(node, name) { |
|
1194 |
+ return node.hasAttribute(name) || node.hasAttribute(':' + name) || node.hasAttribute('v-bind:' + name); |
|
1195 |
+ } |
|
1196 |
+ |
|
1197 |
+ /** |
|
1198 |
+ * Insert el before target |
|
1199 |
+ * |
|
1200 |
+ * @param {Element} el |
|
1201 |
+ * @param {Element} target |
|
1202 |
+ */ |
|
1203 |
+ |
|
1204 |
+ function before(el, target) { |
|
1205 |
+ target.parentNode.insertBefore(el, target); |
|
1206 |
+ } |
|
1207 |
+ |
|
1208 |
+ /** |
|
1209 |
+ * Insert el after target |
|
1210 |
+ * |
|
1211 |
+ * @param {Element} el |
|
1212 |
+ * @param {Element} target |
|
1213 |
+ */ |
|
1214 |
+ |
|
1215 |
+ function after(el, target) { |
|
1216 |
+ if (target.nextSibling) { |
|
1217 |
+ before(el, target.nextSibling); |
|
1218 |
+ } else { |
|
1219 |
+ target.parentNode.appendChild(el); |
|
1220 |
+ } |
|
1221 |
+ } |
|
1222 |
+ |
|
1223 |
+ /** |
|
1224 |
+ * Remove el from DOM |
|
1225 |
+ * |
|
1226 |
+ * @param {Element} el |
|
1227 |
+ */ |
|
1228 |
+ |
|
1229 |
+ function remove(el) { |
|
1230 |
+ el.parentNode.removeChild(el); |
|
1231 |
+ } |
|
1232 |
+ |
|
1233 |
+ /** |
|
1234 |
+ * Prepend el to target |
|
1235 |
+ * |
|
1236 |
+ * @param {Element} el |
|
1237 |
+ * @param {Element} target |
|
1238 |
+ */ |
|
1239 |
+ |
|
1240 |
+ function prepend(el, target) { |
|
1241 |
+ if (target.firstChild) { |
|
1242 |
+ before(el, target.firstChild); |
|
1243 |
+ } else { |
|
1244 |
+ target.appendChild(el); |
|
1245 |
+ } |
|
1246 |
+ } |
|
1247 |
+ |
|
1248 |
+ /** |
|
1249 |
+ * Replace target with el |
|
1250 |
+ * |
|
1251 |
+ * @param {Element} target |
|
1252 |
+ * @param {Element} el |
|
1253 |
+ */ |
|
1254 |
+ |
|
1255 |
+ function replace(target, el) { |
|
1256 |
+ var parent = target.parentNode; |
|
1257 |
+ if (parent) { |
|
1258 |
+ parent.replaceChild(el, target); |
|
1259 |
+ } |
|
1260 |
+ } |
|
1261 |
+ |
|
1262 |
+ /** |
|
1263 |
+ * Add event listener shorthand. |
|
1264 |
+ * |
|
1265 |
+ * @param {Element} el |
|
1266 |
+ * @param {String} event |
|
1267 |
+ * @param {Function} cb |
|
1268 |
+ * @param {Boolean} [useCapture] |
|
1269 |
+ */ |
|
1270 |
+ |
|
1271 |
+ function on(el, event, cb, useCapture) { |
|
1272 |
+ el.addEventListener(event, cb, useCapture); |
|
1273 |
+ } |
|
1274 |
+ |
|
1275 |
+ /** |
|
1276 |
+ * Remove event listener shorthand. |
|
1277 |
+ * |
|
1278 |
+ * @param {Element} el |
|
1279 |
+ * @param {String} event |
|
1280 |
+ * @param {Function} cb |
|
1281 |
+ */ |
|
1282 |
+ |
|
1283 |
+ function off(el, event, cb) { |
|
1284 |
+ el.removeEventListener(event, cb); |
|
1285 |
+ } |
|
1286 |
+ |
|
1287 |
+ /** |
|
1288 |
+ * For IE9 compat: when both class and :class are present |
|
1289 |
+ * getAttribute('class') returns wrong value... |
|
1290 |
+ * |
|
1291 |
+ * @param {Element} el |
|
1292 |
+ * @return {String} |
|
1293 |
+ */ |
|
1294 |
+ |
|
1295 |
+ function getClass(el) { |
|
1296 |
+ var classname = el.className; |
|
1297 |
+ if (typeof classname === 'object') { |
|
1298 |
+ classname = classname.baseVal || ''; |
|
1299 |
+ } |
|
1300 |
+ return classname; |
|
1301 |
+ } |
|
1302 |
+ |
|
1303 |
+ /** |
|
1304 |
+ * In IE9, setAttribute('class') will result in empty class |
|
1305 |
+ * if the element also has the :class attribute; However in |
|
1306 |
+ * PhantomJS, setting `className` does not work on SVG elements... |
|
1307 |
+ * So we have to do a conditional check here. |
|
1308 |
+ * |
|
1309 |
+ * @param {Element} el |
|
1310 |
+ * @param {String} cls |
|
1311 |
+ */ |
|
1312 |
+ |
|
1313 |
+ function setClass(el, cls) { |
|
1314 |
+ /* istanbul ignore if */ |
|
1315 |
+ if (isIE9 && !/svg$/.test(el.namespaceURI)) { |
|
1316 |
+ el.className = cls; |
|
1317 |
+ } else { |
|
1318 |
+ el.setAttribute('class', cls); |
|
1319 |
+ } |
|
1320 |
+ } |
|
1321 |
+ |
|
1322 |
+ /** |
|
1323 |
+ * Add class with compatibility for IE & SVG |
|
1324 |
+ * |
|
1325 |
+ * @param {Element} el |
|
1326 |
+ * @param {String} cls |
|
1327 |
+ */ |
|
1328 |
+ |
|
1329 |
+ function addClass(el, cls) { |
|
1330 |
+ if (el.classList) { |
|
1331 |
+ el.classList.add(cls); |
|
1332 |
+ } else { |
|
1333 |
+ var cur = ' ' + getClass(el) + ' '; |
|
1334 |
+ if (cur.indexOf(' ' + cls + ' ') < 0) { |
|
1335 |
+ setClass(el, (cur + cls).trim()); |
|
1336 |
+ } |
|
1337 |
+ } |
|
1338 |
+ } |
|
1339 |
+ |
|
1340 |
+ /** |
|
1341 |
+ * Remove class with compatibility for IE & SVG |
|
1342 |
+ * |
|
1343 |
+ * @param {Element} el |
|
1344 |
+ * @param {String} cls |
|
1345 |
+ */ |
|
1346 |
+ |
|
1347 |
+ function removeClass(el, cls) { |
|
1348 |
+ if (el.classList) { |
|
1349 |
+ el.classList.remove(cls); |
|
1350 |
+ } else { |
|
1351 |
+ var cur = ' ' + getClass(el) + ' '; |
|
1352 |
+ var tar = ' ' + cls + ' '; |
|
1353 |
+ while (cur.indexOf(tar) >= 0) { |
|
1354 |
+ cur = cur.replace(tar, ' '); |
|
1355 |
+ } |
|
1356 |
+ setClass(el, cur.trim()); |
|
1357 |
+ } |
|
1358 |
+ if (!el.className) { |
|
1359 |
+ el.removeAttribute('class'); |
|
1360 |
+ } |
|
1361 |
+ } |
|
1362 |
+ |
|
1363 |
+ /** |
|
1364 |
+ * Extract raw content inside an element into a temporary |
|
1365 |
+ * container div |
|
1366 |
+ * |
|
1367 |
+ * @param {Element} el |
|
1368 |
+ * @param {Boolean} asFragment |
|
1369 |
+ * @return {Element|DocumentFragment} |
|
1370 |
+ */ |
|
1371 |
+ |
|
1372 |
+ function extractContent(el, asFragment) { |
|
1373 |
+ var child; |
|
1374 |
+ var rawContent; |
|
1375 |
+ /* istanbul ignore if */ |
|
1376 |
+ if (isTemplate(el) && isFragment(el.content)) { |
|
1377 |
+ el = el.content; |
|
1378 |
+ } |
|
1379 |
+ if (el.hasChildNodes()) { |
|
1380 |
+ trimNode(el); |
|
1381 |
+ rawContent = asFragment ? document.createDocumentFragment() : document.createElement('div'); |
|
1382 |
+ /* eslint-disable no-cond-assign */ |
|
1383 |
+ while (child = el.firstChild) { |
|
1384 |
+ /* eslint-enable no-cond-assign */ |
|
1385 |
+ rawContent.appendChild(child); |
|
1386 |
+ } |
|
1387 |
+ } |
|
1388 |
+ return rawContent; |
|
1389 |
+ } |
|
1390 |
+ |
|
1391 |
+ /** |
|
1392 |
+ * Trim possible empty head/tail text and comment |
|
1393 |
+ * nodes inside a parent. |
|
1394 |
+ * |
|
1395 |
+ * @param {Node} node |
|
1396 |
+ */ |
|
1397 |
+ |
|
1398 |
+ function trimNode(node) { |
|
1399 |
+ var child; |
|
1400 |
+ /* eslint-disable no-sequences */ |
|
1401 |
+ while ((child = node.firstChild, isTrimmable(child))) { |
|
1402 |
+ node.removeChild(child); |
|
1403 |
+ } |
|
1404 |
+ while ((child = node.lastChild, isTrimmable(child))) { |
|
1405 |
+ node.removeChild(child); |
|
1406 |
+ } |
|
1407 |
+ /* eslint-enable no-sequences */ |
|
1408 |
+ } |
|
1409 |
+ |
|
1410 |
+ function isTrimmable(node) { |
|
1411 |
+ return node && (node.nodeType === 3 && !node.data.trim() || node.nodeType === 8); |
|
1412 |
+ } |
|
1413 |
+ |
|
1414 |
+ /** |
|
1415 |
+ * Check if an element is a template tag. |
|
1416 |
+ * Note if the template appears inside an SVG its tagName |
|
1417 |
+ * will be in lowercase. |
|
1418 |
+ * |
|
1419 |
+ * @param {Element} el |
|
1420 |
+ */ |
|
1421 |
+ |
|
1422 |
+ function isTemplate(el) { |
|
1423 |
+ return el.tagName && el.tagName.toLowerCase() === 'template'; |
|
1424 |
+ } |
|
1425 |
+ |
|
1426 |
+ /** |
|
1427 |
+ * Create an "anchor" for performing dom insertion/removals. |
|
1428 |
+ * This is used in a number of scenarios: |
|
1429 |
+ * - fragment instance |
|
1430 |
+ * - v-html |
|
1431 |
+ * - v-if |
|
1432 |
+ * - v-for |
|
1433 |
+ * - component |
|
1434 |
+ * |
|
1435 |
+ * @param {String} content |
|
1436 |
+ * @param {Boolean} persist - IE trashes empty textNodes on |
|
1437 |
+ * cloneNode(true), so in certain |
|
1438 |
+ * cases the anchor needs to be |
|
1439 |
+ * non-empty to be persisted in |
|
1440 |
+ * templates. |
|
1441 |
+ * @return {Comment|Text} |
|
1442 |
+ */ |
|
1443 |
+ |
|
1444 |
+ function createAnchor(content, persist) { |
|
1445 |
+ var anchor = config.debug ? document.createComment(content) : document.createTextNode(persist ? ' ' : ''); |
|
1446 |
+ anchor.__v_anchor = true; |
|
1447 |
+ return anchor; |
|
1448 |
+ } |
|
1449 |
+ |
|
1450 |
+ /** |
|
1451 |
+ * Find a component ref attribute that starts with $. |
|
1452 |
+ * |
|
1453 |
+ * @param {Element} node |
|
1454 |
+ * @return {String|undefined} |
|
1455 |
+ */ |
|
1456 |
+ |
|
1457 |
+ var refRE = /^v-ref:/; |
|
1458 |
+ |
|
1459 |
+ function findRef(node) { |
|
1460 |
+ if (node.hasAttributes()) { |
|
1461 |
+ var attrs = node.attributes; |
|
1462 |
+ for (var i = 0, l = attrs.length; i < l; i++) { |
|
1463 |
+ var name = attrs[i].name; |
|
1464 |
+ if (refRE.test(name)) { |
|
1465 |
+ return camelize(name.replace(refRE, '')); |
|
1466 |
+ } |
|
1467 |
+ } |
|
1468 |
+ } |
|
1469 |
+ } |
|
1470 |
+ |
|
1471 |
+ /** |
|
1472 |
+ * Map a function to a range of nodes . |
|
1473 |
+ * |
|
1474 |
+ * @param {Node} node |
|
1475 |
+ * @param {Node} end |
|
1476 |
+ * @param {Function} op |
|
1477 |
+ */ |
|
1478 |
+ |
|
1479 |
+ function mapNodeRange(node, end, op) { |
|
1480 |
+ var next; |
|
1481 |
+ while (node !== end) { |
|
1482 |
+ next = node.nextSibling; |
|
1483 |
+ op(node); |
|
1484 |
+ node = next; |
|
1485 |
+ } |
|
1486 |
+ op(end); |
|
1487 |
+ } |
|
1488 |
+ |
|
1489 |
+ /** |
|
1490 |
+ * Remove a range of nodes with transition, store |
|
1491 |
+ * the nodes in a fragment with correct ordering, |
|
1492 |
+ * and call callback when done. |
|
1493 |
+ * |
|
1494 |
+ * @param {Node} start |
|
1495 |
+ * @param {Node} end |
|
1496 |
+ * @param {Vue} vm |
|
1497 |
+ * @param {DocumentFragment} frag |
|
1498 |
+ * @param {Function} cb |
|
1499 |
+ */ |
|
1500 |
+ |
|
1501 |
+ function removeNodeRange(start, end, vm, frag, cb) { |
|
1502 |
+ var done = false; |
|
1503 |
+ var removed = 0; |
|
1504 |
+ var nodes = []; |
|
1505 |
+ mapNodeRange(start, end, function (node) { |
|
1506 |
+ if (node === end) done = true; |
|
1507 |
+ nodes.push(node); |
|
1508 |
+ removeWithTransition(node, vm, onRemoved); |
|
1509 |
+ }); |
|
1510 |
+ function onRemoved() { |
|
1511 |
+ removed++; |
|
1512 |
+ if (done && removed >= nodes.length) { |
|
1513 |
+ for (var i = 0; i < nodes.length; i++) { |
|
1514 |
+ frag.appendChild(nodes[i]); |
|
1515 |
+ } |
|
1516 |
+ cb && cb(); |
|
1517 |
+ } |
|
1518 |
+ } |
|
1519 |
+ } |
|
1520 |
+ |
|
1521 |
+ /** |
|
1522 |
+ * Check if a node is a DocumentFragment. |
|
1523 |
+ * |
|
1524 |
+ * @param {Node} node |
|
1525 |
+ * @return {Boolean} |
|
1526 |
+ */ |
|
1527 |
+ |
|
1528 |
+ function isFragment(node) { |
|
1529 |
+ return node && node.nodeType === 11; |
|
1530 |
+ } |
|
1531 |
+ |
|
1532 |
+ /** |
|
1533 |
+ * Get outerHTML of elements, taking care |
|
1534 |
+ * of SVG elements in IE as well. |
|
1535 |
+ * |
|
1536 |
+ * @param {Element} el |
|
1537 |
+ * @return {String} |
|
1538 |
+ */ |
|
1539 |
+ |
|
1540 |
+ function getOuterHTML(el) { |
|
1541 |
+ if (el.outerHTML) { |
|
1542 |
+ return el.outerHTML; |
|
1543 |
+ } else { |
|
1544 |
+ var container = document.createElement('div'); |
|
1545 |
+ container.appendChild(el.cloneNode(true)); |
|
1546 |
+ return container.innerHTML; |
|
1547 |
+ } |
|
1548 |
+ } |
|
1549 |
+ |
|
1550 |
+ var commonTagRE = /^(div|p|span|img|a|b|i|br|ul|ol|li|h1|h2|h3|h4|h5|h6|code|pre|table|th|td|tr|form|label|input|select|option|nav|article|section|header|footer)$/i; |
|
1551 |
+ var reservedTagRE = /^(slot|partial|component)$/i; |
|
1552 |
+ |
|
1553 |
+ var isUnknownElement = undefined; |
|
1554 |
+ if ('development' !== 'production') { |
|
1555 |
+ isUnknownElement = function (el, tag) { |
|
1556 |
+ if (tag.indexOf('-') > -1) { |
|
1557 |
+ // http://stackoverflow.com/a/28210364/1070244 |
|
1558 |
+ return el.constructor === window.HTMLUnknownElement || el.constructor === window.HTMLElement; |
|
1559 |
+ } else { |
|
1560 |
+ return (/HTMLUnknownElement/.test(el.toString()) && |
|
1561 |
+ // Chrome returns unknown for several HTML5 elements. |
|
1562 |
+ // https://code.google.com/p/chromium/issues/detail?id=540526 |
|
1563 |
+ // Firefox returns unknown for some "Interactive elements." |
|
1564 |
+ !/^(data|time|rtc|rb|details|dialog|summary)$/.test(tag) |
|
1565 |
+ ); |
|
1566 |
+ } |
|
1567 |
+ }; |
|
1568 |
+ } |
|
1569 |
+ |
|
1570 |
+ /** |
|
1571 |
+ * Check if an element is a component, if yes return its |
|
1572 |
+ * component id. |
|
1573 |
+ * |
|
1574 |
+ * @param {Element} el |
|
1575 |
+ * @param {Object} options |
|
1576 |
+ * @return {Object|undefined} |
|
1577 |
+ */ |
|
1578 |
+ |
|
1579 |
+ function checkComponentAttr(el, options) { |
|
1580 |
+ var tag = el.tagName.toLowerCase(); |
|
1581 |
+ var hasAttrs = el.hasAttributes(); |
|
1582 |
+ if (!commonTagRE.test(tag) && !reservedTagRE.test(tag)) { |
|
1583 |
+ if (resolveAsset(options, 'components', tag)) { |
|
1584 |
+ return { id: tag }; |
|
1585 |
+ } else { |
|
1586 |
+ var is = hasAttrs && getIsBinding(el, options); |
|
1587 |
+ if (is) { |
|
1588 |
+ return is; |
|
1589 |
+ } else if ('development' !== 'production') { |
|
1590 |
+ var expectedTag = options._componentNameMap && options._componentNameMap[tag]; |
|
1591 |
+ if (expectedTag) { |
|
1592 |
+ warn('Unknown custom element: <' + tag + '> - ' + 'did you mean <' + expectedTag + '>? ' + 'HTML is case-insensitive, remember to use kebab-case in templates.'); |
|
1593 |
+ } else if (isUnknownElement(el, tag)) { |
|
1594 |
+ warn('Unknown custom element: <' + tag + '> - did you ' + 'register the component correctly? For recursive components, ' + 'make sure to provide the "name" option.'); |
|
1595 |
+ } |
|
1596 |
+ } |
|
1597 |
+ } |
|
1598 |
+ } else if (hasAttrs) { |
|
1599 |
+ return getIsBinding(el, options); |
|
1600 |
+ } |
|
1601 |
+ } |
|
1602 |
+ |
|
1603 |
+ /** |
|
1604 |
+ * Get "is" binding from an element. |
|
1605 |
+ * |
|
1606 |
+ * @param {Element} el |
|
1607 |
+ * @param {Object} options |
|
1608 |
+ * @return {Object|undefined} |
|
1609 |
+ */ |
|
1610 |
+ |
|
1611 |
+ function getIsBinding(el, options) { |
|
1612 |
+ // dynamic syntax |
|
1613 |
+ var exp = el.getAttribute('is'); |
|
1614 |
+ if (exp != null) { |
|
1615 |
+ if (resolveAsset(options, 'components', exp)) { |
|
1616 |
+ el.removeAttribute('is'); |
|
1617 |
+ return { id: exp }; |
|
1618 |
+ } |
|
1619 |
+ } else { |
|
1620 |
+ exp = getBindAttr(el, 'is'); |
|
1621 |
+ if (exp != null) { |
|
1622 |
+ return { id: exp, dynamic: true }; |
|
1623 |
+ } |
|
1624 |
+ } |
|
1625 |
+ } |
|
1626 |
+ |
|
1627 |
+ /** |
|
1628 |
+ * Option overwriting strategies are functions that handle |
|
1629 |
+ * how to merge a parent option value and a child option |
|
1630 |
+ * value into the final value. |
|
1631 |
+ * |
|
1632 |
+ * All strategy functions follow the same signature: |
|
1633 |
+ * |
|
1634 |
+ * @param {*} parentVal |
|
1635 |
+ * @param {*} childVal |
|
1636 |
+ * @param {Vue} [vm] |
|
1637 |
+ */ |
|
1638 |
+ |
|
1639 |
+ var strats = config.optionMergeStrategies = Object.create(null); |
|
1640 |
+ |
|
1641 |
+ /** |
|
1642 |
+ * Helper that recursively merges two data objects together. |
|
1643 |
+ */ |
|
1644 |
+ |
|
1645 |
+ function mergeData(to, from) { |
|
1646 |
+ var key, toVal, fromVal; |
|
1647 |
+ for (key in from) { |
|
1648 |
+ toVal = to[key]; |
|
1649 |
+ fromVal = from[key]; |
|
1650 |
+ if (!hasOwn(to, key)) { |
|
1651 |
+ set(to, key, fromVal); |
|
1652 |
+ } else if (isObject(toVal) && isObject(fromVal)) { |
|
1653 |
+ mergeData(toVal, fromVal); |
|
1654 |
+ } |
|
1655 |
+ } |
|
1656 |
+ return to; |
|
1657 |
+ } |
|
1658 |
+ |
|
1659 |
+ /** |
|
1660 |
+ * Data |
|
1661 |
+ */ |
|
1662 |
+ |
|
1663 |
+ strats.data = function (parentVal, childVal, vm) { |
|
1664 |
+ if (!vm) { |
|
1665 |
+ // in a Vue.extend merge, both should be functions |
|
1666 |
+ if (!childVal) { |
|
1667 |
+ return parentVal; |
|
1668 |
+ } |
|
1669 |
+ if (typeof childVal !== 'function') { |
|
1670 |
+ 'development' !== 'production' && warn('The "data" option should be a function ' + 'that returns a per-instance value in component ' + 'definitions.', vm); |
|
1671 |
+ return parentVal; |
|
1672 |
+ } |
|
1673 |
+ if (!parentVal) { |
|
1674 |
+ return childVal; |
|
1675 |
+ } |
|
1676 |
+ // when parentVal & childVal are both present, |
|
1677 |
+ // we need to return a function that returns the |
|
1678 |
+ // merged result of both functions... no need to |
|
1679 |
+ // check if parentVal is a function here because |
|
1680 |
+ // it has to be a function to pass previous merges. |
|
1681 |
+ return function mergedDataFn() { |
|
1682 |
+ return mergeData(childVal.call(this), parentVal.call(this)); |
|
1683 |
+ }; |
|
1684 |
+ } else if (parentVal || childVal) { |
|
1685 |
+ return function mergedInstanceDataFn() { |
|
1686 |
+ // instance merge |
|
1687 |
+ var instanceData = typeof childVal === 'function' ? childVal.call(vm) : childVal; |
|
1688 |
+ var defaultData = typeof parentVal === 'function' ? parentVal.call(vm) : undefined; |
|
1689 |
+ if (instanceData) { |
|
1690 |
+ return mergeData(instanceData, defaultData); |
|
1691 |
+ } else { |
|
1692 |
+ return defaultData; |
|
1693 |
+ } |
|
1694 |
+ }; |
|
1695 |
+ } |
|
1696 |
+ }; |
|
1697 |
+ |
|
1698 |
+ /** |
|
1699 |
+ * El |
|
1700 |
+ */ |
|
1701 |
+ |
|
1702 |
+ strats.el = function (parentVal, childVal, vm) { |
|
1703 |
+ if (!vm && childVal && typeof childVal !== 'function') { |
|
1704 |
+ 'development' !== 'production' && warn('The "el" option should be a function ' + 'that returns a per-instance value in component ' + 'definitions.', vm); |
|
1705 |
+ return; |
|
1706 |
+ } |
|
1707 |
+ var ret = childVal || parentVal; |
|
1708 |
+ // invoke the element factory if this is instance merge |
|
1709 |
+ return vm && typeof ret === 'function' ? ret.call(vm) : ret; |
|
1710 |
+ }; |
|
1711 |
+ |
|
1712 |
+ /** |
|
1713 |
+ * Hooks and param attributes are merged as arrays. |
|
1714 |
+ */ |
|
1715 |
+ |
|
1716 |
+ strats.init = strats.created = strats.ready = strats.attached = strats.detached = strats.beforeCompile = strats.compiled = strats.beforeDestroy = strats.destroyed = strats.activate = function (parentVal, childVal) { |
|
1717 |
+ return childVal ? parentVal ? parentVal.concat(childVal) : isArray(childVal) ? childVal : [childVal] : parentVal; |
|
1718 |
+ }; |
|
1719 |
+ |
|
1720 |
+ /** |
|
1721 |
+ * Assets |
|
1722 |
+ * |
|
1723 |
+ * When a vm is present (instance creation), we need to do |
|
1724 |
+ * a three-way merge between constructor options, instance |
|
1725 |
+ * options and parent options. |
|
1726 |
+ */ |
|
1727 |
+ |
|
1728 |
+ function mergeAssets(parentVal, childVal) { |
|
1729 |
+ var res = Object.create(parentVal || null); |
|
1730 |
+ return childVal ? extend(res, guardArrayAssets(childVal)) : res; |
|
1731 |
+ } |
|
1732 |
+ |
|
1733 |
+ config._assetTypes.forEach(function (type) { |
|
1734 |
+ strats[type + 's'] = mergeAssets; |
|
1735 |
+ }); |
|
1736 |
+ |
|
1737 |
+ /** |
|
1738 |
+ * Events & Watchers. |
|
1739 |
+ * |
|
1740 |
+ * Events & watchers hashes should not overwrite one |
|
1741 |
+ * another, so we merge them as arrays. |
|
1742 |
+ */ |
|
1743 |
+ |
|
1744 |
+ strats.watch = strats.events = function (parentVal, childVal) { |
|
1745 |
+ if (!childVal) return parentVal; |
|
1746 |
+ if (!parentVal) return childVal; |
|
1747 |
+ var ret = {}; |
|
1748 |
+ extend(ret, parentVal); |
|
1749 |
+ for (var key in childVal) { |
|
1750 |
+ var parent = ret[key]; |
|
1751 |
+ var child = childVal[key]; |
|
1752 |
+ if (parent && !isArray(parent)) { |
|
1753 |
+ parent = [parent]; |
|
1754 |
+ } |
|
1755 |
+ ret[key] = parent ? parent.concat(child) : [child]; |
|
1756 |
+ } |
|
1757 |
+ return ret; |
|
1758 |
+ }; |
|
1759 |
+ |
|
1760 |
+ /** |
|
1761 |
+ * Other object hashes. |
|
1762 |
+ */ |
|
1763 |
+ |
|
1764 |
+ strats.props = strats.methods = strats.computed = function (parentVal, childVal) { |
|
1765 |
+ if (!childVal) return parentVal; |
|
1766 |
+ if (!parentVal) return childVal; |
|
1767 |
+ var ret = Object.create(null); |
|
1768 |
+ extend(ret, parentVal); |
|
1769 |
+ extend(ret, childVal); |
|
1770 |
+ return ret; |
|
1771 |
+ }; |
|
1772 |
+ |
|
1773 |
+ /** |
|
1774 |
+ * Default strategy. |
|
1775 |
+ */ |
|
1776 |
+ |
|
1777 |
+ var defaultStrat = function defaultStrat(parentVal, childVal) { |
|
1778 |
+ return childVal === undefined ? parentVal : childVal; |
|
1779 |
+ }; |
|
1780 |
+ |
|
1781 |
+ /** |
|
1782 |
+ * Make sure component options get converted to actual |
|
1783 |
+ * constructors. |
|
1784 |
+ * |
|
1785 |
+ * @param {Object} options |
|
1786 |
+ */ |
|
1787 |
+ |
|
1788 |
+ function guardComponents(options) { |
|
1789 |
+ if (options.components) { |
|
1790 |
+ var components = options.components = guardArrayAssets(options.components); |
|
1791 |
+ var ids = Object.keys(components); |
|
1792 |
+ var def; |
|
1793 |
+ if ('development' !== 'production') { |
|
1794 |
+ var map = options._componentNameMap = {}; |
|
1795 |
+ } |
|
1796 |
+ for (var i = 0, l = ids.length; i < l; i++) { |
|
1797 |
+ var key = ids[i]; |
|
1798 |
+ if (commonTagRE.test(key) || reservedTagRE.test(key)) { |
|
1799 |
+ 'development' !== 'production' && warn('Do not use built-in or reserved HTML elements as component ' + 'id: ' + key); |
|
1800 |
+ continue; |
|
1801 |
+ } |
|
1802 |
+ // record a all lowercase <-> kebab-case mapping for |
|
1803 |
+ // possible custom element case error warning |
|
1804 |
+ if ('development' !== 'production') { |
|
1805 |
+ map[key.replace(/-/g, '').toLowerCase()] = hyphenate(key); |
|
1806 |
+ } |
|
1807 |
+ def = components[key]; |
|
1808 |
+ if (isPlainObject(def)) { |
|
1809 |
+ components[key] = Vue.extend(def); |
|
1810 |
+ } |
|
1811 |
+ } |
|
1812 |
+ } |
|
1813 |
+ } |
|
1814 |
+ |
|
1815 |
+ /** |
|
1816 |
+ * Ensure all props option syntax are normalized into the |
|
1817 |
+ * Object-based format. |
|
1818 |
+ * |
|
1819 |
+ * @param {Object} options |
|
1820 |
+ */ |
|
1821 |
+ |
|
1822 |
+ function guardProps(options) { |
|
1823 |
+ var props = options.props; |
|
1824 |
+ var i, val; |
|
1825 |
+ if (isArray(props)) { |
|
1826 |
+ options.props = {}; |
|
1827 |
+ i = props.length; |
|
1828 |
+ while (i--) { |
|
1829 |
+ val = props[i]; |
|
1830 |
+ if (typeof val === 'string') { |
|
1831 |
+ options.props[val] = null; |
|
1832 |
+ } else if (val.name) { |
|
1833 |
+ options.props[val.name] = val; |
|
1834 |
+ } |
|
1835 |
+ } |
|
1836 |
+ } else if (isPlainObject(props)) { |
|
1837 |
+ var keys = Object.keys(props); |
|
1838 |
+ i = keys.length; |
|
1839 |
+ while (i--) { |
|
1840 |
+ val = props[keys[i]]; |
|
1841 |
+ if (typeof val === 'function') { |
|
1842 |
+ props[keys[i]] = { type: val }; |
|
1843 |
+ } |
|
1844 |
+ } |
|
1845 |
+ } |
|
1846 |
+ } |
|
1847 |
+ |
|
1848 |
+ /** |
|
1849 |
+ * Guard an Array-format assets option and converted it |
|
1850 |
+ * into the key-value Object format. |
|
1851 |
+ * |
|
1852 |
+ * @param {Object|Array} assets |
|
1853 |
+ * @return {Object} |
|
1854 |
+ */ |
|
1855 |
+ |
|
1856 |
+ function guardArrayAssets(assets) { |
|
1857 |
+ if (isArray(assets)) { |
|
1858 |
+ var res = {}; |
|
1859 |
+ var i = assets.length; |
|
1860 |
+ var asset; |
|
1861 |
+ while (i--) { |
|
1862 |
+ asset = assets[i]; |
|
1863 |
+ var id = typeof asset === 'function' ? asset.options && asset.options.name || asset.id : asset.name || asset.id; |
|
1864 |
+ if (!id) { |
|
1865 |
+ 'development' !== 'production' && warn('Array-syntax assets must provide a "name" or "id" field.'); |
|
1866 |
+ } else { |
|
1867 |
+ res[id] = asset; |
|
1868 |
+ } |
|
1869 |
+ } |
|
1870 |
+ return res; |
|
1871 |
+ } |
|
1872 |
+ return assets; |
|
1873 |
+ } |
|
1874 |
+ |
|
1875 |
+ /** |
|
1876 |
+ * Merge two option objects into a new one. |
|
1877 |
+ * Core utility used in both instantiation and inheritance. |
|
1878 |
+ * |
|
1879 |
+ * @param {Object} parent |
|
1880 |
+ * @param {Object} child |
|
1881 |
+ * @param {Vue} [vm] - if vm is present, indicates this is |
|
1882 |
+ * an instantiation merge. |
|
1883 |
+ */ |
|
1884 |
+ |
|
1885 |
+ function mergeOptions(parent, child, vm) { |
|
1886 |
+ guardComponents(child); |
|
1887 |
+ guardProps(child); |
|
1888 |
+ if ('development' !== 'production') { |
|
1889 |
+ if (child.propsData && !vm) { |
|
1890 |
+ warn('propsData can only be used as an instantiation option.'); |
|
1891 |
+ } |
|
1892 |
+ } |
|
1893 |
+ var options = {}; |
|
1894 |
+ var key; |
|
1895 |
+ if (child['extends']) { |
|
1896 |
+ parent = typeof child['extends'] === 'function' ? mergeOptions(parent, child['extends'].options, vm) : mergeOptions(parent, child['extends'], vm); |
|
1897 |
+ } |
|
1898 |
+ if (child.mixins) { |
|
1899 |
+ for (var i = 0, l = child.mixins.length; i < l; i++) { |
|
1900 |
+ var mixin = child.mixins[i]; |
|
1901 |
+ var mixinOptions = mixin.prototype instanceof Vue ? mixin.options : mixin; |
|
1902 |
+ parent = mergeOptions(parent, mixinOptions, vm); |
|
1903 |
+ } |
|
1904 |
+ } |
|
1905 |
+ for (key in parent) { |
|
1906 |
+ mergeField(key); |
|
1907 |
+ } |
|
1908 |
+ for (key in child) { |
|
1909 |
+ if (!hasOwn(parent, key)) { |
|
1910 |
+ mergeField(key); |
|
1911 |
+ } |
|
1912 |
+ } |
|
1913 |
+ function mergeField(key) { |
|
1914 |
+ var strat = strats[key] || defaultStrat; |
|
1915 |
+ options[key] = strat(parent[key], child[key], vm, key); |
|
1916 |
+ } |
|
1917 |
+ return options; |
|
1918 |
+ } |
|
1919 |
+ |
|
1920 |
+ /** |
|
1921 |
+ * Resolve an asset. |
|
1922 |
+ * This function is used because child instances need access |
|
1923 |
+ * to assets defined in its ancestor chain. |
|
1924 |
+ * |
|
1925 |
+ * @param {Object} options |
|
1926 |
+ * @param {String} type |
|
1927 |
+ * @param {String} id |
|
1928 |
+ * @param {Boolean} warnMissing |
|
1929 |
+ * @return {Object|Function} |
|
1930 |
+ */ |
|
1931 |
+ |
|
1932 |
+ function resolveAsset(options, type, id, warnMissing) { |
|
1933 |
+ /* istanbul ignore if */ |
|
1934 |
+ if (typeof id !== 'string') { |
|
1935 |
+ return; |
|
1936 |
+ } |
|
1937 |
+ var assets = options[type]; |
|
1938 |
+ var camelizedId; |
|
1939 |
+ var res = assets[id] || |
|
1940 |
+ // camelCase ID |
|
1941 |
+ assets[camelizedId = camelize(id)] || |
|
1942 |
+ // Pascal Case ID |
|
1943 |
+ assets[camelizedId.charAt(0).toUpperCase() + camelizedId.slice(1)]; |
|
1944 |
+ if ('development' !== 'production' && warnMissing && !res) { |
|
1945 |
+ warn('Failed to resolve ' + type.slice(0, -1) + ': ' + id, options); |
|
1946 |
+ } |
|
1947 |
+ return res; |
|
1948 |
+ } |
|
1949 |
+ |
|
1950 |
+ var uid$1 = 0; |
|
1951 |
+ |
|
1952 |
+ /** |
|
1953 |
+ * A dep is an observable that can have multiple |
|
1954 |
+ * directives subscribing to it. |
|
1955 |
+ * |
|
1956 |
+ * @constructor |
|
1957 |
+ */ |
|
1958 |
+ function Dep() { |
|
1959 |
+ this.id = uid$1++; |
|
1960 |
+ this.subs = []; |
|
1961 |
+ } |
|
1962 |
+ |
|
1963 |
+ // the current target watcher being evaluated. |
|
1964 |
+ // this is globally unique because there could be only one |
|
1965 |
+ // watcher being evaluated at any time. |
|
1966 |
+ Dep.target = null; |
|
1967 |
+ |
|
1968 |
+ /** |
|
1969 |
+ * Add a directive subscriber. |
|
1970 |
+ * |
|
1971 |
+ * @param {Directive} sub |
|
1972 |
+ */ |
|
1973 |
+ |
|
1974 |
+ Dep.prototype.addSub = function (sub) { |
|
1975 |
+ this.subs.push(sub); |
|
1976 |
+ }; |
|
1977 |
+ |
|
1978 |
+ /** |
|
1979 |
+ * Remove a directive subscriber. |
|
1980 |
+ * |
|
1981 |
+ * @param {Directive} sub |
|
1982 |
+ */ |
|
1983 |
+ |
|
1984 |
+ Dep.prototype.removeSub = function (sub) { |
|
1985 |
+ this.subs.$remove(sub); |
|
1986 |
+ }; |
|
1987 |
+ |
|
1988 |
+ /** |
|
1989 |
+ * Add self as a dependency to the target watcher. |
|
1990 |
+ */ |
|
1991 |
+ |
|
1992 |
+ Dep.prototype.depend = function () { |
|
1993 |
+ Dep.target.addDep(this); |
|
1994 |
+ }; |
|
1995 |
+ |
|
1996 |
+ /** |
|
1997 |
+ * Notify all subscribers of a new value. |
|
1998 |
+ */ |
|
1999 |
+ |
|
2000 |
+ Dep.prototype.notify = function () { |
|
2001 |
+ // stablize the subscriber list first |
|
2002 |
+ var subs = toArray(this.subs); |
|
2003 |
+ for (var i = 0, l = subs.length; i < l; i++) { |
|
2004 |
+ subs[i].update(); |
|
2005 |
+ } |
|
2006 |
+ }; |
|
2007 |
+ |
|
2008 |
+ var arrayProto = Array.prototype; |
|
2009 |
+ var arrayMethods = Object.create(arrayProto) |
|
2010 |
+ |
|
2011 |
+ /** |
|
2012 |
+ * Intercept mutating methods and emit events |
|
2013 |
+ */ |
|
2014 |
+ |
|
2015 |
+ ;['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(function (method) { |
|
2016 |
+ // cache original method |
|
2017 |
+ var original = arrayProto[method]; |
|
2018 |
+ def(arrayMethods, method, function mutator() { |
|
2019 |
+ // avoid leaking arguments: |
|
2020 |
+ // http://jsperf.com/closure-with-arguments |
|
2021 |
+ var i = arguments.length; |
|
2022 |
+ var args = new Array(i); |
|
2023 |
+ while (i--) { |
|
2024 |
+ args[i] = arguments[i]; |
|
2025 |
+ } |
|
2026 |
+ var result = original.apply(this, args); |
|
2027 |
+ var ob = this.__ob__; |
|
2028 |
+ var inserted; |
|
2029 |
+ switch (method) { |
|
2030 |
+ case 'push': |
|
2031 |
+ inserted = args; |
|
2032 |
+ break; |
|
2033 |
+ case 'unshift': |
|
2034 |
+ inserted = args; |
|
2035 |
+ break; |
|
2036 |
+ case 'splice': |
|
2037 |
+ inserted = args.slice(2); |
|
2038 |
+ break; |
|
2039 |
+ } |
|
2040 |
+ if (inserted) ob.observeArray(inserted); |
|
2041 |
+ // notify change |
|
2042 |
+ ob.dep.notify(); |
|
2043 |
+ return result; |
|
2044 |
+ }); |
|
2045 |
+ }); |
|
2046 |
+ |
|
2047 |
+ /** |
|
2048 |
+ * Swap the element at the given index with a new value |
|
2049 |
+ * and emits corresponding event. |
|
2050 |
+ * |
|
2051 |
+ * @param {Number} index |
|
2052 |
+ * @param {*} val |
|
2053 |
+ * @return {*} - replaced element |
|
2054 |
+ */ |
|
2055 |
+ |
|
2056 |
+ def(arrayProto, '$set', function $set(index, val) { |
|
2057 |
+ if (index >= this.length) { |
|
2058 |
+ this.length = Number(index) + 1; |
|
2059 |
+ } |
|
2060 |
+ return this.splice(index, 1, val)[0]; |
|
2061 |
+ }); |
|
2062 |
+ |
|
2063 |
+ /** |
|
2064 |
+ * Convenience method to remove the element at given index or target element reference. |
|
2065 |
+ * |
|
2066 |
+ * @param {*} item |
|
2067 |
+ */ |
|
2068 |
+ |
|
2069 |
+ def(arrayProto, '$remove', function $remove(item) { |
|
2070 |
+ /* istanbul ignore if */ |
|
2071 |
+ if (!this.length) return; |
|
2072 |
+ var index = indexOf(this, item); |
|
2073 |
+ if (index > -1) { |
|
2074 |
+ return this.splice(index, 1); |
|
2075 |
+ } |
|
2076 |
+ }); |
|
2077 |
+ |
|
2078 |
+ var arrayKeys = Object.getOwnPropertyNames(arrayMethods); |
|
2079 |
+ |
|
2080 |
+ /** |
|
2081 |
+ * By default, when a reactive property is set, the new value is |
|
2082 |
+ * also converted to become reactive. However in certain cases, e.g. |
|
2083 |
+ * v-for scope alias and props, we don't want to force conversion |
|
2084 |
+ * because the value may be a nested value under a frozen data structure. |
|
2085 |
+ * |
|
2086 |
+ * So whenever we want to set a reactive property without forcing |
|
2087 |
+ * conversion on the new value, we wrap that call inside this function. |
|
2088 |
+ */ |
|
2089 |
+ |
|
2090 |
+ var shouldConvert = true; |
|
2091 |
+ |
|
2092 |
+ function withoutConversion(fn) { |
|
2093 |
+ shouldConvert = false; |
|
2094 |
+ fn(); |
|
2095 |
+ shouldConvert = true; |
|
2096 |
+ } |
|
2097 |
+ |
|
2098 |
+ /** |
|
2099 |
+ * Observer class that are attached to each observed |
|
2100 |
+ * object. Once attached, the observer converts target |
|
2101 |
+ * object's property keys into getter/setters that |
|
2102 |
+ * collect dependencies and dispatches updates. |
|
2103 |
+ * |
|
2104 |
+ * @param {Array|Object} value |
|
2105 |
+ * @constructor |
|
2106 |
+ */ |
|
2107 |
+ |
|
2108 |
+ function Observer(value) { |
|
2109 |
+ this.value = value; |
|
2110 |
+ this.dep = new Dep(); |
|
2111 |
+ def(value, '__ob__', this); |
|
2112 |
+ if (isArray(value)) { |
|
2113 |
+ var augment = hasProto ? protoAugment : copyAugment; |
|
2114 |
+ augment(value, arrayMethods, arrayKeys); |
|
2115 |
+ this.observeArray(value); |
|
2116 |
+ } else { |
|
2117 |
+ this.walk(value); |
|
2118 |
+ } |
|
2119 |
+ } |
|
2120 |
+ |
|
2121 |
+ // Instance methods |
|
2122 |
+ |
|
2123 |
+ /** |
|
2124 |
+ * Walk through each property and convert them into |
|
2125 |
+ * getter/setters. This method should only be called when |
|
2126 |
+ * value type is Object. |
|
2127 |
+ * |
|
2128 |
+ * @param {Object} obj |
|
2129 |
+ */ |
|
2130 |
+ |
|
2131 |
+ Observer.prototype.walk = function (obj) { |
|
2132 |
+ var keys = Object.keys(obj); |
|
2133 |
+ for (var i = 0, l = keys.length; i < l; i++) { |
|
2134 |
+ this.convert(keys[i], obj[keys[i]]); |
|
2135 |
+ } |
|
2136 |
+ }; |
|
2137 |
+ |
|
2138 |
+ /** |
|
2139 |
+ * Observe a list of Array items. |
|
2140 |
+ * |
|
2141 |
+ * @param {Array} items |
|
2142 |
+ */ |
|
2143 |
+ |
|
2144 |
+ Observer.prototype.observeArray = function (items) { |
|
2145 |
+ for (var i = 0, l = items.length; i < l; i++) { |
|
2146 |
+ observe(items[i]); |
|
2147 |
+ } |
|
2148 |
+ }; |
|
2149 |
+ |
|
2150 |
+ /** |
|
2151 |
+ * Convert a property into getter/setter so we can emit |
|
2152 |
+ * the events when the property is accessed/changed. |
|
2153 |
+ * |
|
2154 |
+ * @param {String} key |
|
2155 |
+ * @param {*} val |
|
2156 |
+ */ |
|
2157 |
+ |
|
2158 |
+ Observer.prototype.convert = function (key, val) { |
|
2159 |
+ defineReactive(this.value, key, val); |
|
2160 |
+ }; |
|
2161 |
+ |
|
2162 |
+ /** |
|
2163 |
+ * Add an owner vm, so that when $set/$delete mutations |
|
2164 |
+ * happen we can notify owner vms to proxy the keys and |
|
2165 |
+ * digest the watchers. This is only called when the object |
|
2166 |
+ * is observed as an instance's root $data. |
|
2167 |
+ * |
|
2168 |
+ * @param {Vue} vm |
|
2169 |
+ */ |
|
2170 |
+ |
|
2171 |
+ Observer.prototype.addVm = function (vm) { |
|
2172 |
+ (this.vms || (this.vms = [])).push(vm); |
|
2173 |
+ }; |
|
2174 |
+ |
|
2175 |
+ /** |
|
2176 |
+ * Remove an owner vm. This is called when the object is |
|
2177 |
+ * swapped out as an instance's $data object. |
|
2178 |
+ * |
|
2179 |
+ * @param {Vue} vm |
|
2180 |
+ */ |
|
2181 |
+ |
|
2182 |
+ Observer.prototype.removeVm = function (vm) { |
|
2183 |
+ this.vms.$remove(vm); |
|
2184 |
+ }; |
|
2185 |
+ |
|
2186 |
+ // helpers |
|
2187 |
+ |
|
2188 |
+ /** |
|
2189 |
+ * Augment an target Object or Array by intercepting |
|
2190 |
+ * the prototype chain using __proto__ |
|
2191 |
+ * |
|
2192 |
+ * @param {Object|Array} target |
|
2193 |
+ * @param {Object} src |
|
2194 |
+ */ |
|
2195 |
+ |
|
2196 |
+ function protoAugment(target, src) { |
|
2197 |
+ /* eslint-disable no-proto */ |
|
2198 |
+ target.__proto__ = src; |
|
2199 |
+ /* eslint-enable no-proto */ |
|
2200 |
+ } |
|
2201 |
+ |
|
2202 |
+ /** |
|
2203 |
+ * Augment an target Object or Array by defining |
|
2204 |
+ * hidden properties. |
|
2205 |
+ * |
|
2206 |
+ * @param {Object|Array} target |
|
2207 |
+ * @param {Object} proto |
|
2208 |
+ */ |
|
2209 |
+ |
|
2210 |
+ function copyAugment(target, src, keys) { |
|
2211 |
+ for (var i = 0, l = keys.length; i < l; i++) { |
|
2212 |
+ var key = keys[i]; |
|
2213 |
+ def(target, key, src[key]); |
|
2214 |
+ } |
|
2215 |
+ } |
|
2216 |
+ |
|
2217 |
+ /** |
|
2218 |
+ * Attempt to create an observer instance for a value, |
|
2219 |
+ * returns the new observer if successfully observed, |
|
2220 |
+ * or the existing observer if the value already has one. |
|
2221 |
+ * |
|
2222 |
+ * @param {*} value |
|
2223 |
+ * @param {Vue} [vm] |
|
2224 |
+ * @return {Observer|undefined} |
|
2225 |
+ * @static |
|
2226 |
+ */ |
|
2227 |
+ |
|
2228 |
+ function observe(value, vm) { |
|
2229 |
+ if (!value || typeof value !== 'object') { |
|
2230 |
+ return; |
|
2231 |
+ } |
|
2232 |
+ var ob; |
|
2233 |
+ if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { |
|
2234 |
+ ob = value.__ob__; |
|
2235 |
+ } else if (shouldConvert && (isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue) { |
|
2236 |
+ ob = new Observer(value); |
|
2237 |
+ } |
|
2238 |
+ if (ob && vm) { |
|
2239 |
+ ob.addVm(vm); |
|
2240 |
+ } |
|
2241 |
+ return ob; |
|
2242 |
+ } |
|
2243 |
+ |
|
2244 |
+ /** |
|
2245 |
+ * Define a reactive property on an Object. |
|
2246 |
+ * |
|
2247 |
+ * @param {Object} obj |
|
2248 |
+ * @param {String} key |
|
2249 |
+ * @param {*} val |
|
2250 |
+ */ |
|
2251 |
+ |
|
2252 |
+ function defineReactive(obj, key, val) { |
|
2253 |
+ var dep = new Dep(); |
|
2254 |
+ |
|
2255 |
+ var property = Object.getOwnPropertyDescriptor(obj, key); |
|
2256 |
+ if (property && property.configurable === false) { |
|
2257 |
+ return; |
|
2258 |
+ } |
|
2259 |
+ |
|
2260 |
+ // cater for pre-defined getter/setters |
|
2261 |
+ var getter = property && property.get; |
|
2262 |
+ var setter = property && property.set; |
|
2263 |
+ |
|
2264 |
+ var childOb = observe(val); |
|
2265 |
+ Object.defineProperty(obj, key, { |
|
2266 |
+ enumerable: true, |
|
2267 |
+ configurable: true, |
|
2268 |
+ get: function reactiveGetter() { |
|
2269 |
+ var value = getter ? getter.call(obj) : val; |
|
2270 |
+ if (Dep.target) { |
|
2271 |
+ dep.depend(); |
|
2272 |
+ if (childOb) { |
|
2273 |
+ childOb.dep.depend(); |
|
2274 |
+ } |
|
2275 |
+ if (isArray(value)) { |
|
2276 |
+ for (var e, i = 0, l = value.length; i < l; i++) { |
|
2277 |
+ e = value[i]; |
|
2278 |
+ e && e.__ob__ && e.__ob__.dep.depend(); |
|
2279 |
+ } |
|
2280 |
+ } |
|
2281 |
+ } |
|
2282 |
+ return value; |
|
2283 |
+ }, |
|
2284 |
+ set: function reactiveSetter(newVal) { |
|
2285 |
+ var value = getter ? getter.call(obj) : val; |
|
2286 |
+ if (newVal === value) { |
|
2287 |
+ return; |
|
2288 |
+ } |
|
2289 |
+ if (setter) { |
|
2290 |
+ setter.call(obj, newVal); |
|
2291 |
+ } else { |
|
2292 |
+ val = newVal; |
|
2293 |
+ } |
|
2294 |
+ childOb = observe(newVal); |
|
2295 |
+ dep.notify(); |
|
2296 |
+ } |
|
2297 |
+ }); |
|
2298 |
+ } |
|
2299 |
+ |
|
2300 |
+ |
|
2301 |
+ |
|
2302 |
+ var util = Object.freeze({ |
|
2303 |
+ defineReactive: defineReactive, |
|
2304 |
+ set: set, |
|
2305 |
+ del: del, |
|
2306 |
+ hasOwn: hasOwn, |
|
2307 |
+ isLiteral: isLiteral, |
|
2308 |
+ isReserved: isReserved, |
|
2309 |
+ _toString: _toString, |
|
2310 |
+ toNumber: toNumber, |
|
2311 |
+ toBoolean: toBoolean, |
|
2312 |
+ stripQuotes: stripQuotes, |
|
2313 |
+ camelize: camelize, |
|
2314 |
+ hyphenate: hyphenate, |
|
2315 |
+ classify: classify, |
|
2316 |
+ bind: bind, |
|
2317 |
+ toArray: toArray, |
|
2318 |
+ extend: extend, |
|
2319 |
+ isObject: isObject, |
|
2320 |
+ isPlainObject: isPlainObject, |
|
2321 |
+ def: def, |
|
2322 |
+ debounce: _debounce, |
|
2323 |
+ indexOf: indexOf, |
|
2324 |
+ cancellable: cancellable, |
|
2325 |
+ looseEqual: looseEqual, |
|
2326 |
+ isArray: isArray, |
|
2327 |
+ hasProto: hasProto, |
|
2328 |
+ inBrowser: inBrowser, |
|
2329 |
+ devtools: devtools, |
|
2330 |
+ isIE: isIE, |
|
2331 |
+ isIE9: isIE9, |
|
2332 |
+ isAndroid: isAndroid, |
|
2333 |
+ isIos: isIos, |
|
2334 |
+ iosVersionMatch: iosVersionMatch, |
|
2335 |
+ iosVersion: iosVersion, |
|
2336 |
+ hasMutationObserverBug: hasMutationObserverBug, |
|
2337 |
+ get transitionProp () { return transitionProp; }, |
|
2338 |
+ get transitionEndEvent () { return transitionEndEvent; }, |
|
2339 |
+ get animationProp () { return animationProp; }, |
|
2340 |
+ get animationEndEvent () { return animationEndEvent; }, |
|
2341 |
+ nextTick: nextTick, |
|
2342 |
+ get _Set () { return _Set; }, |
|
2343 |
+ query: query, |
|
2344 |
+ inDoc: inDoc, |
|
2345 |
+ getAttr: getAttr, |
|
2346 |
+ getBindAttr: getBindAttr, |
|
2347 |
+ hasBindAttr: hasBindAttr, |
|
2348 |
+ before: before, |
|
2349 |
+ after: after, |
|
2350 |
+ remove: remove, |
|
2351 |
+ prepend: prepend, |
|
2352 |
+ replace: replace, |
|
2353 |
+ on: on, |
|
2354 |
+ off: off, |
|
2355 |
+ setClass: setClass, |
|
2356 |
+ addClass: addClass, |
|
2357 |
+ removeClass: removeClass, |
|
2358 |
+ extractContent: extractContent, |
|
2359 |
+ trimNode: trimNode, |
|
2360 |
+ isTemplate: isTemplate, |
|
2361 |
+ createAnchor: createAnchor, |
|
2362 |
+ findRef: findRef, |
|
2363 |
+ mapNodeRange: mapNodeRange, |
|
2364 |
+ removeNodeRange: removeNodeRange, |
|
2365 |
+ isFragment: isFragment, |
|
2366 |
+ getOuterHTML: getOuterHTML, |
|
2367 |
+ mergeOptions: mergeOptions, |
|
2368 |
+ resolveAsset: resolveAsset, |
|
2369 |
+ checkComponentAttr: checkComponentAttr, |
|
2370 |
+ commonTagRE: commonTagRE, |
|
2371 |
+ reservedTagRE: reservedTagRE, |
|
2372 |
+ get warn () { return warn; } |
|
2373 |
+ }); |
|
2374 |
+ |
|
2375 |
+ var uid = 0; |
|
2376 |
+ |
|
2377 |
+ function initMixin (Vue) { |
|
2378 |
+ /** |
|
2379 |
+ * The main init sequence. This is called for every |
|
2380 |
+ * instance, including ones that are created from extended |
|
2381 |
+ * constructors. |
|
2382 |
+ * |
|
2383 |
+ * @param {Object} options - this options object should be |
|
2384 |
+ * the result of merging class |
|
2385 |
+ * options and the options passed |
|
2386 |
+ * in to the constructor. |
|
2387 |
+ */ |
|
2388 |
+ |
|
2389 |
+ Vue.prototype._init = function (options) { |
|
2390 |
+ options = options || {}; |
|
2391 |
+ |
|
2392 |
+ this.$el = null; |
|
2393 |
+ this.$parent = options.parent; |
|
2394 |
+ this.$root = this.$parent ? this.$parent.$root : this; |
|
2395 |
+ this.$children = []; |
|
2396 |
+ this.$refs = {}; // child vm references |
|
2397 |
+ this.$els = {}; // element references |
|
2398 |
+ this._watchers = []; // all watchers as an array |
|
2399 |
+ this._directives = []; // all directives |
|
2400 |
+ |
|
2401 |
+ // a uid |
|
2402 |
+ this._uid = uid++; |
|
2403 |
+ |
|
2404 |
+ // a flag to avoid this being observed |
|
2405 |
+ this._isVue = true; |
|
2406 |
+ |
|
2407 |
+ // events bookkeeping |
|
2408 |
+ this._events = {}; // registered callbacks |
|
2409 |
+ this._eventsCount = {}; // for $broadcast optimization |
|
2410 |
+ |
|
2411 |
+ // fragment instance properties |
|
2412 |
+ this._isFragment = false; |
|
2413 |
+ this._fragment = // @type {DocumentFragment} |
|
2414 |
+ this._fragmentStart = // @type {Text|Comment} |
|
2415 |
+ this._fragmentEnd = null; // @type {Text|Comment} |
|
2416 |
+ |
|
2417 |
+ // lifecycle state |
|
2418 |
+ this._isCompiled = this._isDestroyed = this._isReady = this._isAttached = this._isBeingDestroyed = this._vForRemoving = false; |
|
2419 |
+ this._unlinkFn = null; |
|
2420 |
+ |
|
2421 |
+ // context: |
|
2422 |
+ // if this is a transcluded component, context |
|
2423 |
+ // will be the common parent vm of this instance |
|
2424 |
+ // and its host. |
|
2425 |
+ this._context = options._context || this.$parent; |
|
2426 |
+ |
|
2427 |
+ // scope: |
|
2428 |
+ // if this is inside an inline v-for, the scope |
|
2429 |
+ // will be the intermediate scope created for this |
|
2430 |
+ // repeat fragment. this is used for linking props |
|
2431 |
+ // and container directives. |
|
2432 |
+ this._scope = options._scope; |
|
2433 |
+ |
|
2434 |
+ // fragment: |
|
2435 |
+ // if this instance is compiled inside a Fragment, it |
|
2436 |
+ // needs to reigster itself as a child of that fragment |
|
2437 |
+ // for attach/detach to work properly. |
|
2438 |
+ this._frag = options._frag; |
|
2439 |
+ if (this._frag) { |
|
2440 |
+ this._frag.children.push(this); |
|
2441 |
+ } |
|
2442 |
+ |
|
2443 |
+ // push self into parent / transclusion host |
|
2444 |
+ if (this.$parent) { |
|
2445 |
+ this.$parent.$children.push(this); |
|
2446 |
+ } |
|
2447 |
+ |
|
2448 |
+ // merge options. |
|
2449 |
+ options = this.$options = mergeOptions(this.constructor.options, options, this); |
|
2450 |
+ |
|
2451 |
+ // set ref |
|
2452 |
+ this._updateRef(); |
|
2453 |
+ |
|
2454 |
+ // initialize data as empty object. |
|
2455 |
+ // it will be filled up in _initData(). |
|
2456 |
+ this._data = {}; |
|
2457 |
+ |
|
2458 |
+ // call init hook |
|
2459 |
+ this._callHook('init'); |
|
2460 |
+ |
|
2461 |
+ // initialize data observation and scope inheritance. |
|
2462 |
+ this._initState(); |
|
2463 |
+ |
|
2464 |
+ // setup event system and option events. |
|
2465 |
+ this._initEvents(); |
|
2466 |
+ |
|
2467 |
+ // call created hook |
|
2468 |
+ this._callHook('created'); |
|
2469 |
+ |
|
2470 |
+ // if `el` option is passed, start compilation. |
|
2471 |
+ if (options.el) { |
|
2472 |
+ this.$mount(options.el); |
|
2473 |
+ } |
|
2474 |
+ }; |
|
2475 |
+ } |
|
2476 |
+ |
|
2477 |
+ var pathCache = new Cache(1000); |
|
2478 |
+ |
|
2479 |
+ // actions |
|
2480 |
+ var APPEND = 0; |
|
2481 |
+ var PUSH = 1; |
|
2482 |
+ var INC_SUB_PATH_DEPTH = 2; |
|
2483 |
+ var PUSH_SUB_PATH = 3; |
|
2484 |
+ |
|
2485 |
+ // states |
|
2486 |
+ var BEFORE_PATH = 0; |
|
2487 |
+ var IN_PATH = 1; |
|
2488 |
+ var BEFORE_IDENT = 2; |
|
2489 |
+ var IN_IDENT = 3; |
|
2490 |
+ var IN_SUB_PATH = 4; |
|
2491 |
+ var IN_SINGLE_QUOTE = 5; |
|
2492 |
+ var IN_DOUBLE_QUOTE = 6; |
|
2493 |
+ var AFTER_PATH = 7; |
|
2494 |
+ var ERROR = 8; |
|
2495 |
+ |
|
2496 |
+ var pathStateMachine = []; |
|
2497 |
+ |
|
2498 |
+ pathStateMachine[BEFORE_PATH] = { |
|
2499 |
+ 'ws': [BEFORE_PATH], |
|
2500 |
+ 'ident': [IN_IDENT, APPEND], |
|
2501 |
+ '[': [IN_SUB_PATH], |
|
2502 |
+ 'eof': [AFTER_PATH] |
|
2503 |
+ }; |
|
2504 |
+ |
|
2505 |
+ pathStateMachine[IN_PATH] = { |
|
2506 |
+ 'ws': [IN_PATH], |
|
2507 |
+ '.': [BEFORE_IDENT], |
|
2508 |
+ '[': [IN_SUB_PATH], |
|
2509 |
+ 'eof': [AFTER_PATH] |
|
2510 |
+ }; |
|
2511 |
+ |
|
2512 |
+ pathStateMachine[BEFORE_IDENT] = { |
|
2513 |
+ 'ws': [BEFORE_IDENT], |
|
2514 |
+ 'ident': [IN_IDENT, APPEND] |
|
2515 |
+ }; |
|
2516 |
+ |
|
2517 |
+ pathStateMachine[IN_IDENT] = { |
|
2518 |
+ 'ident': [IN_IDENT, APPEND], |
|
2519 |
+ '0': [IN_IDENT, APPEND], |
|
2520 |
+ 'number': [IN_IDENT, APPEND], |
|
2521 |
+ 'ws': [IN_PATH, PUSH], |
|
2522 |
+ '.': [BEFORE_IDENT, PUSH], |
|
2523 |
+ '[': [IN_SUB_PATH, PUSH], |
|
2524 |
+ 'eof': [AFTER_PATH, PUSH] |
|
2525 |
+ }; |
|
2526 |
+ |
|
2527 |
+ pathStateMachine[IN_SUB_PATH] = { |
|
2528 |
+ "'": [IN_SINGLE_QUOTE, APPEND], |
|
2529 |
+ '"': [IN_DOUBLE_QUOTE, APPEND], |
|
2530 |
+ '[': [IN_SUB_PATH, INC_SUB_PATH_DEPTH], |
|
2531 |
+ ']': [IN_PATH, PUSH_SUB_PATH], |
|
2532 |
+ 'eof': ERROR, |
|
2533 |
+ 'else': [IN_SUB_PATH, APPEND] |
|
2534 |
+ }; |
|
2535 |
+ |
|
2536 |
+ pathStateMachine[IN_SINGLE_QUOTE] = { |
|
2537 |
+ "'": [IN_SUB_PATH, APPEND], |
|
2538 |
+ 'eof': ERROR, |
|
2539 |
+ 'else': [IN_SINGLE_QUOTE, APPEND] |
|
2540 |
+ }; |
|
2541 |
+ |
|
2542 |
+ pathStateMachine[IN_DOUBLE_QUOTE] = { |
|
2543 |
+ '"': [IN_SUB_PATH, APPEND], |
|
2544 |
+ 'eof': ERROR, |
|
2545 |
+ 'else': [IN_DOUBLE_QUOTE, APPEND] |
|
2546 |
+ }; |
|
2547 |
+ |
|
2548 |
+ /** |
|
2549 |
+ * Determine the type of a character in a keypath. |
|
2550 |
+ * |
|
2551 |
+ * @param {Char} ch |
|
2552 |
+ * @return {String} type |
|
2553 |
+ */ |
|
2554 |
+ |
|
2555 |
+ function getPathCharType(ch) { |
|
2556 |
+ if (ch === undefined) { |
|
2557 |
+ return 'eof'; |
|
2558 |
+ } |
|
2559 |
+ |
|
2560 |
+ var code = ch.charCodeAt(0); |
|
2561 |
+ |
|
2562 |
+ switch (code) { |
|
2563 |
+ case 0x5B: // [ |
|
2564 |
+ case 0x5D: // ] |
|
2565 |
+ case 0x2E: // . |
|
2566 |
+ case 0x22: // " |
|
2567 |
+ case 0x27: // ' |
|
2568 |
+ case 0x30: |
|
2569 |
+ // 0 |
|
2570 |
+ return ch; |
|
2571 |
+ |
|
2572 |
+ case 0x5F: // _ |
|
2573 |
+ case 0x24: |
|
2574 |
+ // $ |
|
2575 |
+ return 'ident'; |
|
2576 |
+ |
|
2577 |
+ case 0x20: // Space |
|
2578 |
+ case 0x09: // Tab |
|
2579 |
+ case 0x0A: // Newline |
|
2580 |
+ case 0x0D: // Return |
|
2581 |
+ case 0xA0: // No-break space |
|
2582 |
+ case 0xFEFF: // Byte Order Mark |
|
2583 |
+ case 0x2028: // Line Separator |
|
2584 |
+ case 0x2029: |
|
2585 |
+ // Paragraph Separator |
|
2586 |
+ return 'ws'; |
|
2587 |
+ } |
|
2588 |
+ |
|
2589 |
+ // a-z, A-Z |
|
2590 |
+ if (code >= 0x61 && code <= 0x7A || code >= 0x41 && code <= 0x5A) { |
|
2591 |
+ return 'ident'; |
|
2592 |
+ } |
|
2593 |
+ |
|
2594 |
+ // 1-9 |
|
2595 |
+ if (code >= 0x31 && code <= 0x39) { |
|
2596 |
+ return 'number'; |
|
2597 |
+ } |
|
2598 |
+ |
|
2599 |
+ return 'else'; |
|
2600 |
+ } |
|
2601 |
+ |
|
2602 |
+ /** |
|
2603 |
+ * Format a subPath, return its plain form if it is |
|
2604 |
+ * a literal string or number. Otherwise prepend the |
|
2605 |
+ * dynamic indicator (*). |
|
2606 |
+ * |
|
2607 |
+ * @param {String} path |
|
2608 |
+ * @return {String} |
|
2609 |
+ */ |
|
2610 |
+ |
|
2611 |
+ function formatSubPath(path) { |
|
2612 |
+ var trimmed = path.trim(); |
|
2613 |
+ // invalid leading 0 |
|
2614 |
+ if (path.charAt(0) === '0' && isNaN(path)) { |
|
2615 |
+ return false; |
|
2616 |
+ } |
|
2617 |
+ return isLiteral(trimmed) ? stripQuotes(trimmed) : '*' + trimmed; |
|
2618 |
+ } |
|
2619 |
+ |
|
2620 |
+ /** |
|
2621 |
+ * Parse a string path into an array of segments |
|
2622 |
+ * |
|
2623 |
+ * @param {String} path |
|
2624 |
+ * @return {Array|undefined} |
|
2625 |
+ */ |
|
2626 |
+ |
|
2627 |
+ function parse(path) { |
|
2628 |
+ var keys = []; |
|
2629 |
+ var index = -1; |
|
2630 |
+ var mode = BEFORE_PATH; |
|
2631 |
+ var subPathDepth = 0; |
|
2632 |
+ var c, newChar, key, type, transition, action, typeMap; |
|
2633 |
+ |
|
2634 |
+ var actions = []; |
|
2635 |
+ |
|
2636 |
+ actions[PUSH] = function () { |
|
2637 |
+ if (key !== undefined) { |
|
2638 |
+ keys.push(key); |
|
2639 |
+ key = undefined; |
|
2640 |
+ } |
|
2641 |
+ }; |
|
2642 |
+ |
|
2643 |
+ actions[APPEND] = function () { |
|
2644 |
+ if (key === undefined) { |
|
2645 |
+ key = newChar; |
|
2646 |
+ } else { |
|
2647 |
+ key += newChar; |
|
2648 |
+ } |
|
2649 |
+ }; |
|
2650 |
+ |
|
2651 |
+ actions[INC_SUB_PATH_DEPTH] = function () { |
|
2652 |
+ actions[APPEND](); |
|
2653 |
+ subPathDepth++; |
|
2654 |
+ }; |
|
2655 |
+ |
|
2656 |
+ actions[PUSH_SUB_PATH] = function () { |
|
2657 |
+ if (subPathDepth > 0) { |
|
2658 |
+ subPathDepth--; |
|
2659 |
+ mode = IN_SUB_PATH; |
|
2660 |
+ actions[APPEND](); |
|
2661 |
+ } else { |
|
2662 |
+ subPathDepth = 0; |
|
2663 |
+ key = formatSubPath(key); |
|
2664 |
+ if (key === false) { |
|
2665 |
+ return false; |
|
2666 |
+ } else { |
|
2667 |
+ actions[PUSH](); |
|
2668 |
+ } |
|
2669 |
+ } |
|
2670 |
+ }; |
|
2671 |
+ |
|
2672 |
+ function maybeUnescapeQuote() { |
|
2673 |
+ var nextChar = path[index + 1]; |
|
2674 |
+ if (mode === IN_SINGLE_QUOTE && nextChar === "'" || mode === IN_DOUBLE_QUOTE && nextChar === '"') { |
|
2675 |
+ index++; |
|
2676 |
+ newChar = '\\' + nextChar; |
|
2677 |
+ actions[APPEND](); |
|
2678 |
+ return true; |
|
2679 |
+ } |
|
2680 |
+ } |
|
2681 |
+ |
|
2682 |
+ while (mode != null) { |
|
2683 |
+ index++; |
|
2684 |
+ c = path[index]; |
|
2685 |
+ |
|
2686 |
+ if (c === '\\' && maybeUnescapeQuote()) { |
|
2687 |
+ continue; |
|
2688 |
+ } |
|
2689 |
+ |
|
2690 |
+ type = getPathCharType(c); |
|
2691 |
+ typeMap = pathStateMachine[mode]; |
|
2692 |
+ transition = typeMap[type] || typeMap['else'] || ERROR; |
|
2693 |
+ |
|
2694 |
+ if (transition === ERROR) { |
|
2695 |
+ return; // parse error |
|
2696 |
+ } |
|
2697 |
+ |
|
2698 |
+ mode = transition[0]; |
|
2699 |
+ action = actions[transition[1]]; |
|
2700 |
+ if (action) { |
|
2701 |
+ newChar = transition[2]; |
|
2702 |
+ newChar = newChar === undefined ? c : newChar; |
|
2703 |
+ if (action() === false) { |
|
2704 |
+ return; |
|
2705 |
+ } |
|
2706 |
+ } |
|
2707 |
+ |
|
2708 |
+ if (mode === AFTER_PATH) { |
|
2709 |
+ keys.raw = path; |
|
2710 |
+ return keys; |
|
2711 |
+ } |
|
2712 |
+ } |
|
2713 |
+ } |
|
2714 |
+ |
|
2715 |
+ /** |
|
2716 |
+ * External parse that check for a cache hit first |
|
2717 |
+ * |
|
2718 |
+ * @param {String} path |
|
2719 |
+ * @return {Array|undefined} |
|
2720 |
+ */ |
|
2721 |
+ |
|
2722 |
+ function parsePath(path) { |
|
2723 |
+ var hit = pathCache.get(path); |
|
2724 |
+ if (!hit) { |
|
2725 |
+ hit = parse(path); |
|
2726 |
+ if (hit) { |
|
2727 |
+ pathCache.put(path, hit); |
|
2728 |
+ } |
|
2729 |
+ } |
|
2730 |
+ return hit; |
|
2731 |
+ } |
|
2732 |
+ |
|
2733 |
+ /** |
|
2734 |
+ * Get from an object from a path string |
|
2735 |
+ * |
|
2736 |
+ * @param {Object} obj |
|
2737 |
+ * @param {String} path |
|
2738 |
+ */ |
|
2739 |
+ |
|
2740 |
+ function getPath(obj, path) { |
|
2741 |
+ return parseExpression(path).get(obj); |
|
2742 |
+ } |
|
2743 |
+ |
|
2744 |
+ /** |
|
2745 |
+ * Warn against setting non-existent root path on a vm. |
|
2746 |
+ */ |
|
2747 |
+ |
|
2748 |
+ var warnNonExistent; |
|
2749 |
+ if ('development' !== 'production') { |
|
2750 |
+ warnNonExistent = function (path, vm) { |
|
2751 |
+ warn('You are setting a non-existent path "' + path.raw + '" ' + 'on a vm instance. Consider pre-initializing the property ' + 'with the "data" option for more reliable reactivity ' + 'and better performance.', vm); |
|
2752 |
+ }; |
|
2753 |
+ } |
|
2754 |
+ |
|
2755 |
+ /** |
|
2756 |
+ * Set on an object from a path |
|
2757 |
+ * |
|
2758 |
+ * @param {Object} obj |
|
2759 |
+ * @param {String | Array} path |
|
2760 |
+ * @param {*} val |
|
2761 |
+ */ |
|
2762 |
+ |
|
2763 |
+ function setPath(obj, path, val) { |
|
2764 |
+ var original = obj; |
|
2765 |
+ if (typeof path === 'string') { |
|
2766 |
+ path = parse(path); |
|
2767 |
+ } |
|
2768 |
+ if (!path || !isObject(obj)) { |
|
2769 |
+ return false; |
|
2770 |
+ } |
|
2771 |
+ var last, key; |
|
2772 |
+ for (var i = 0, l = path.length; i < l; i++) { |
|
2773 |
+ last = obj; |
|
2774 |
+ key = path[i]; |
|
2775 |
+ if (key.charAt(0) === '*') { |
|
2776 |
+ key = parseExpression(key.slice(1)).get.call(original, original); |
|
2777 |
+ } |
|
2778 |
+ if (i < l - 1) { |
|
2779 |
+ obj = obj[key]; |
|
2780 |
+ if (!isObject(obj)) { |
|
2781 |
+ obj = {}; |
|
2782 |
+ if ('development' !== 'production' && last._isVue) { |
|
2783 |
+ warnNonExistent(path, last); |
|
2784 |
+ } |
|
2785 |
+ set(last, key, obj); |
|
2786 |
+ } |
|
2787 |
+ } else { |
|
2788 |
+ if (isArray(obj)) { |
|
2789 |
+ obj.$set(key, val); |
|
2790 |
+ } else if (key in obj) { |
|
2791 |
+ obj[key] = val; |
|
2792 |
+ } else { |
|
2793 |
+ if ('development' !== 'production' && obj._isVue) { |
|
2794 |
+ warnNonExistent(path, obj); |
|
2795 |
+ } |
|
2796 |
+ set(obj, key, val); |
|
2797 |
+ } |
|
2798 |
+ } |
|
2799 |
+ } |
|
2800 |
+ return true; |
|
2801 |
+ } |
|
2802 |
+ |
|
2803 |
+var path = Object.freeze({ |
|
2804 |
+ parsePath: parsePath, |
|
2805 |
+ getPath: getPath, |
|
2806 |
+ setPath: setPath |
|
2807 |
+ }); |
|
2808 |
+ |
|
2809 |
+ var expressionCache = new Cache(1000); |
|
2810 |
+ |
|
2811 |
+ var allowedKeywords = 'Math,Date,this,true,false,null,undefined,Infinity,NaN,' + 'isNaN,isFinite,decodeURI,decodeURIComponent,encodeURI,' + 'encodeURIComponent,parseInt,parseFloat'; |
|
2812 |
+ var allowedKeywordsRE = new RegExp('^(' + allowedKeywords.replace(/,/g, '\\b|') + '\\b)'); |
|
2813 |
+ |
|
2814 |
+ // keywords that don't make sense inside expressions |
|
2815 |
+ var improperKeywords = 'break,case,class,catch,const,continue,debugger,default,' + 'delete,do,else,export,extends,finally,for,function,if,' + 'import,in,instanceof,let,return,super,switch,throw,try,' + 'var,while,with,yield,enum,await,implements,package,' + 'protected,static,interface,private,public'; |
|
2816 |
+ var improperKeywordsRE = new RegExp('^(' + improperKeywords.replace(/,/g, '\\b|') + '\\b)'); |
|
2817 |
+ |
|
2818 |
+ var wsRE = /\s/g; |
|
2819 |
+ var newlineRE = /\n/g; |
|
2820 |
+ var saveRE = /[\{,]\s*[\w\$_]+\s*:|('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*\$\{|\}(?:[^`\\]|\\.)*`|`(?:[^`\\]|\\.)*`)|new |typeof |void /g; |
|
2821 |
+ var restoreRE = /"(\d+)"/g; |
|
2822 |
+ var pathTestRE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['.*?'\]|\[".*?"\]|\[\d+\]|\[[A-Za-z_$][\w$]*\])*$/; |
|
2823 |
+ var identRE = /[^\w$\.](?:[A-Za-z_$][\w$]*)/g; |
|
2824 |
+ var literalValueRE$1 = /^(?:true|false|null|undefined|Infinity|NaN)$/; |
|
2825 |
+ |
|
2826 |
+ function noop() {} |
|
2827 |
+ |
|
2828 |
+ /** |
|
2829 |
+ * Save / Rewrite / Restore |
|
2830 |
+ * |
|
2831 |
+ * When rewriting paths found in an expression, it is |
|
2832 |
+ * possible for the same letter sequences to be found in |
|
2833 |
+ * strings and Object literal property keys. Therefore we |
|
2834 |
+ * remove and store these parts in a temporary array, and |
|
2835 |
+ * restore them after the path rewrite. |
|
2836 |
+ */ |
|
2837 |
+ |
|
2838 |
+ var saved = []; |
|
2839 |
+ |
|
2840 |
+ /** |
|
2841 |
+ * Save replacer |
|
2842 |
+ * |
|
2843 |
+ * The save regex can match two possible cases: |
|
2844 |
+ * 1. An opening object literal |
|
2845 |
+ * 2. A string |
|
2846 |
+ * If matched as a plain string, we need to escape its |
|
2847 |
+ * newlines, since the string needs to be preserved when |
|
2848 |
+ * generating the function body. |
|
2849 |
+ * |
|
2850 |
+ * @param {String} str |
|
2851 |
+ * @param {String} isString - str if matched as a string |
|
2852 |
+ * @return {String} - placeholder with index |
|
2853 |
+ */ |
|
2854 |
+ |
|
2855 |
+ function save(str, isString) { |
|
2856 |
+ var i = saved.length; |
|
2857 |
+ saved[i] = isString ? str.replace(newlineRE, '\\n') : str; |
|
2858 |
+ return '"' + i + '"'; |
|
2859 |
+ } |
|
2860 |
+ |
|
2861 |
+ /** |
|
2862 |
+ * Path rewrite replacer |
|
2863 |
+ * |
|
2864 |
+ * @param {String} raw |
|
2865 |
+ * @return {String} |
|
2866 |
+ */ |
|
2867 |
+ |
|
2868 |
+ function rewrite(raw) { |
|
2869 |
+ var c = raw.charAt(0); |
|
2870 |
+ var path = raw.slice(1); |
|
2871 |
+ if (allowedKeywordsRE.test(path)) { |
|
2872 |
+ return raw; |
|
2873 |
+ } else { |
|
2874 |
+ path = path.indexOf('"') > -1 ? path.replace(restoreRE, restore) : path; |
|
2875 |
+ return c + 'scope.' + path; |
|
2876 |
+ } |
|
2877 |
+ } |
|
2878 |
+ |
|
2879 |
+ /** |
|
2880 |
+ * Restore replacer |
|
2881 |
+ * |
|
2882 |
+ * @param {String} str |
|
2883 |
+ * @param {String} i - matched save index |
|
2884 |
+ * @return {String} |
|
2885 |
+ */ |
|
2886 |
+ |
|
2887 |
+ function restore(str, i) { |
|
2888 |
+ return saved[i]; |
|
2889 |
+ } |
|
2890 |
+ |
|
2891 |
+ /** |
|
2892 |
+ * Rewrite an expression, prefixing all path accessors with |
|
2893 |
+ * `scope.` and generate getter/setter functions. |
|
2894 |
+ * |
|
2895 |
+ * @param {String} exp |
|
2896 |
+ * @return {Function} |
|
2897 |
+ */ |
|
2898 |
+ |
|
2899 |
+ function compileGetter(exp) { |
|
2900 |
+ if (improperKeywordsRE.test(exp)) { |
|
2901 |
+ 'development' !== 'production' && warn('Avoid using reserved keywords in expression: ' + exp); |
|
2902 |
+ } |
|
2903 |
+ // reset state |
|
2904 |
+ saved.length = 0; |
|
2905 |
+ // save strings and object literal keys |
|
2906 |
+ var body = exp.replace(saveRE, save).replace(wsRE, ''); |
|
2907 |
+ // rewrite all paths |
|
2908 |
+ // pad 1 space here because the regex matches 1 extra char |
|
2909 |
+ body = (' ' + body).replace(identRE, rewrite).replace(restoreRE, restore); |
|
2910 |
+ return makeGetterFn(body); |
|
2911 |
+ } |
|
2912 |
+ |
|
2913 |
+ /** |
|
2914 |
+ * Build a getter function. Requires eval. |
|
2915 |
+ * |
|
2916 |
+ * We isolate the try/catch so it doesn't affect the |
|
2917 |
+ * optimization of the parse function when it is not called. |
|
2918 |
+ * |
|
2919 |
+ * @param {String} body |
|
2920 |
+ * @return {Function|undefined} |
|
2921 |
+ */ |
|
2922 |
+ |
|
2923 |
+ function makeGetterFn(body) { |
|
2924 |
+ try { |
|
2925 |
+ /* eslint-disable no-new-func */ |
|
2926 |
+ return new Function('scope', 'return ' + body + ';'); |
|
2927 |
+ /* eslint-enable no-new-func */ |
|
2928 |
+ } catch (e) { |
|
2929 |
+ if ('development' !== 'production') { |
|
2930 |
+ /* istanbul ignore if */ |
|
2931 |
+ if (e.toString().match(/unsafe-eval|CSP/)) { |
|
2932 |
+ warn('It seems you are using the default build of Vue.js in an environment ' + 'with Content Security Policy that prohibits unsafe-eval. ' + 'Use the CSP-compliant build instead: ' + 'http://vuejs.org/guide/installation.html#CSP-compliant-build'); |
|
2933 |
+ } else { |
|
2934 |
+ warn('Invalid expression. ' + 'Generated function body: ' + body); |
|
2935 |
+ } |
|
2936 |
+ } |
|
2937 |
+ return noop; |
|
2938 |
+ } |
|
2939 |
+ } |
|
2940 |
+ |
|
2941 |
+ /** |
|
2942 |
+ * Compile a setter function for the expression. |
|
2943 |
+ * |
|
2944 |
+ * @param {String} exp |
|
2945 |
+ * @return {Function|undefined} |
|
2946 |
+ */ |
|
2947 |
+ |
|
2948 |
+ function compileSetter(exp) { |
|
2949 |
+ var path = parsePath(exp); |
|
2950 |
+ if (path) { |
|
2951 |
+ return function (scope, val) { |
|
2952 |
+ setPath(scope, path, val); |
|
2953 |
+ }; |
|
2954 |
+ } else { |
|
2955 |
+ 'development' !== 'production' && warn('Invalid setter expression: ' + exp); |
|
2956 |
+ } |
|
2957 |
+ } |
|
2958 |
+ |
|
2959 |
+ /** |
|
2960 |
+ * Parse an expression into re-written getter/setters. |
|
2961 |
+ * |
|
2962 |
+ * @param {String} exp |
|
2963 |
+ * @param {Boolean} needSet |
|
2964 |
+ * @return {Function} |
|
2965 |
+ */ |
|
2966 |
+ |
|
2967 |
+ function parseExpression(exp, needSet) { |
|
2968 |
+ exp = exp.trim(); |
|
2969 |
+ // try cache |
|
2970 |
+ var hit = expressionCache.get(exp); |
|
2971 |
+ if (hit) { |
|
2972 |
+ if (needSet && !hit.set) { |
|
2973 |
+ hit.set = compileSetter(hit.exp); |
|
2974 |
+ } |
|
2975 |
+ return hit; |
|
2976 |
+ } |
|
2977 |
+ var res = { exp: exp }; |
|
2978 |
+ res.get = isSimplePath(exp) && exp.indexOf('[') < 0 |
|
2979 |
+ // optimized super simple getter |
|
2980 |
+ ? makeGetterFn('scope.' + exp) |
|
2981 |
+ // dynamic getter |
|
2982 |
+ : compileGetter(exp); |
|
2983 |
+ if (needSet) { |
|
2984 |
+ res.set = compileSetter(exp); |
|
2985 |
+ } |
|
2986 |
+ expressionCache.put(exp, res); |
|
2987 |
+ return res; |
|
2988 |
+ } |
|
2989 |
+ |
|
2990 |
+ /** |
|
2991 |
+ * Check if an expression is a simple path. |
|
2992 |
+ * |
|
2993 |
+ * @param {String} exp |
|
2994 |
+ * @return {Boolean} |
|
2995 |
+ */ |
|
2996 |
+ |
|
2997 |
+ function isSimplePath(exp) { |
|
2998 |
+ return pathTestRE.test(exp) && |
|
2999 |
+ // don't treat literal values as paths |
|
3000 |
+ !literalValueRE$1.test(exp) && |
|
3001 |
+ // Math constants e.g. Math.PI, Math.E etc. |
|
3002 |
+ exp.slice(0, 5) !== 'Math.'; |
|
3003 |
+ } |
|
3004 |
+ |
|
3005 |
+var expression = Object.freeze({ |
|
3006 |
+ parseExpression: parseExpression, |
|
3007 |
+ isSimplePath: isSimplePath |
|
3008 |
+ }); |
|
3009 |
+ |
|
3010 |
+ // we have two separate queues: one for directive updates |
|
3011 |
+ // and one for user watcher registered via $watch(). |
|
3012 |
+ // we want to guarantee directive updates to be called |
|
3013 |
+ // before user watchers so that when user watchers are |
|
3014 |
+ // triggered, the DOM would have already been in updated |
|
3015 |
+ // state. |
|
3016 |
+ |
|
3017 |
+ var queue = []; |
|
3018 |
+ var userQueue = []; |
|
3019 |
+ var has = {}; |
|
3020 |
+ var circular = {}; |
|
3021 |
+ var waiting = false; |
|
3022 |
+ |
|
3023 |
+ /** |
|
3024 |
+ * Reset the batcher's state. |
|
3025 |
+ */ |
|
3026 |
+ |
|
3027 |
+ function resetBatcherState() { |
|
3028 |
+ queue.length = 0; |
|
3029 |
+ userQueue.length = 0; |
|
3030 |
+ has = {}; |
|
3031 |
+ circular = {}; |
|
3032 |
+ waiting = false; |
|
3033 |
+ } |
|
3034 |
+ |
|
3035 |
+ /** |
|
3036 |
+ * Flush both queues and run the watchers. |
|
3037 |
+ */ |
|
3038 |
+ |
|
3039 |
+ function flushBatcherQueue() { |
|
3040 |
+ var _again = true; |
|
3041 |
+ |
|
3042 |
+ _function: while (_again) { |
|
3043 |
+ _again = false; |
|
3044 |
+ |
|
3045 |
+ runBatcherQueue(queue); |
|
3046 |
+ runBatcherQueue(userQueue); |
|
3047 |
+ // user watchers triggered more watchers, |
|
3048 |
+ // keep flushing until it depletes |
|
3049 |
+ if (queue.length) { |
|
3050 |
+ _again = true; |
|
3051 |
+ continue _function; |
|
3052 |
+ } |
|
3053 |
+ // dev tool hook |
|
3054 |
+ /* istanbul ignore if */ |
|
3055 |
+ if (devtools && config.devtools) { |
|
3056 |
+ devtools.emit('flush'); |
|
3057 |
+ } |
|
3058 |
+ resetBatcherState(); |
|
3059 |
+ } |
|
3060 |
+ } |
|
3061 |
+ |
|
3062 |
+ /** |
|
3063 |
+ * Run the watchers in a single queue. |
|
3064 |
+ * |
|
3065 |
+ * @param {Array} queue |
|
3066 |
+ */ |
|
3067 |
+ |
|
3068 |
+ function runBatcherQueue(queue) { |
|
3069 |
+ // do not cache length because more watchers might be pushed |
|
3070 |
+ // as we run existing watchers |
|
3071 |
+ for (var i = 0; i < queue.length; i++) { |
|
3072 |
+ var watcher = queue[i]; |
|
3073 |
+ var id = watcher.id; |
|
3074 |
+ has[id] = null; |
|
3075 |
+ watcher.run(); |
|
3076 |
+ // in dev build, check and stop circular updates. |
|
3077 |
+ if ('development' !== 'production' && has[id] != null) { |
|
3078 |
+ circular[id] = (circular[id] || 0) + 1; |
|
3079 |
+ if (circular[id] > config._maxUpdateCount) { |
|
3080 |
+ warn('You may have an infinite update loop for watcher ' + 'with expression "' + watcher.expression + '"', watcher.vm); |
|
3081 |
+ break; |
|
3082 |
+ } |
|
3083 |
+ } |
|
3084 |
+ } |
|
3085 |
+ queue.length = 0; |
|
3086 |
+ } |
|
3087 |
+ |
|
3088 |
+ /** |
|
3089 |
+ * Push a watcher into the watcher queue. |
|
3090 |
+ * Jobs with duplicate IDs will be skipped unless it's |
|
3091 |
+ * pushed when the queue is being flushed. |
|
3092 |
+ * |
|
3093 |
+ * @param {Watcher} watcher |
|
3094 |
+ * properties: |
|
3095 |
+ * - {Number} id |
|
3096 |
+ * - {Function} run |
|
3097 |
+ */ |
|
3098 |
+ |
|
3099 |
+ function pushWatcher(watcher) { |
|
3100 |
+ var id = watcher.id; |
|
3101 |
+ if (has[id] == null) { |
|
3102 |
+ // push watcher into appropriate queue |
|
3103 |
+ var q = watcher.user ? userQueue : queue; |
|
3104 |
+ has[id] = q.length; |
|
3105 |
+ q.push(watcher); |
|
3106 |
+ // queue the flush |
|
3107 |
+ if (!waiting) { |
|
3108 |
+ waiting = true; |
|
3109 |
+ nextTick(flushBatcherQueue); |
|
3110 |
+ } |
|
3111 |
+ } |
|
3112 |
+ } |
|
3113 |
+ |
|
3114 |
+ var uid$2 = 0; |
|
3115 |
+ |
|
3116 |
+ /** |
|
3117 |
+ * A watcher parses an expression, collects dependencies, |
|
3118 |
+ * and fires callback when the expression value changes. |
|
3119 |
+ * This is used for both the $watch() api and directives. |
|
3120 |
+ * |
|
3121 |
+ * @param {Vue} vm |
|
3122 |
+ * @param {String|Function} expOrFn |
|
3123 |
+ * @param {Function} cb |
|
3124 |
+ * @param {Object} options |
|
3125 |
+ * - {Array} filters |
|
3126 |
+ * - {Boolean} twoWay |
|
3127 |
+ * - {Boolean} deep |
|
3128 |
+ * - {Boolean} user |
|
3129 |
+ * - {Boolean} sync |
|
3130 |
+ * - {Boolean} lazy |
|
3131 |
+ * - {Function} [preProcess] |
|
3132 |
+ * - {Function} [postProcess] |
|
3133 |
+ * @constructor |
|
3134 |
+ */ |
|
3135 |
+ function Watcher(vm, expOrFn, cb, options) { |
|
3136 |
+ // mix in options |
|
3137 |
+ if (options) { |
|
3138 |
+ extend(this, options); |
|
3139 |
+ } |
|
3140 |
+ var isFn = typeof expOrFn === 'function'; |
|
3141 |
+ this.vm = vm; |
|
3142 |
+ vm._watchers.push(this); |
|
3143 |
+ this.expression = expOrFn; |
|
3144 |
+ this.cb = cb; |
|
3145 |
+ this.id = ++uid$2; // uid for batching |
|
3146 |
+ this.active = true; |
|
3147 |
+ this.dirty = this.lazy; // for lazy watchers |
|
3148 |
+ this.deps = []; |
|
3149 |
+ this.newDeps = []; |
|
3150 |
+ this.depIds = new _Set(); |
|
3151 |
+ this.newDepIds = new _Set(); |
|
3152 |
+ this.prevError = null; // for async error stacks |
|
3153 |
+ // parse expression for getter/setter |
|
3154 |
+ if (isFn) { |
|
3155 |
+ this.getter = expOrFn; |
|
3156 |
+ this.setter = undefined; |
|
3157 |
+ } else { |
|
3158 |
+ var res = parseExpression(expOrFn, this.twoWay); |
|
3159 |
+ this.getter = res.get; |
|
3160 |
+ this.setter = res.set; |
|
3161 |
+ } |
|
3162 |
+ this.value = this.lazy ? undefined : this.get(); |
|
3163 |
+ // state for avoiding false triggers for deep and Array |
|
3164 |
+ // watchers during vm._digest() |
|
3165 |
+ this.queued = this.shallow = false; |
|
3166 |
+ } |
|
3167 |
+ |
|
3168 |
+ /** |
|
3169 |
+ * Evaluate the getter, and re-collect dependencies. |
|
3170 |
+ */ |
|
3171 |
+ |
|
3172 |
+ Watcher.prototype.get = function () { |
|
3173 |
+ this.beforeGet(); |
|
3174 |
+ var scope = this.scope || this.vm; |
|
3175 |
+ var value; |
|
3176 |
+ try { |
|
3177 |
+ value = this.getter.call(scope, scope); |
|
3178 |
+ } catch (e) { |
|
3179 |
+ if ('development' !== 'production' && config.warnExpressionErrors) { |
|
3180 |
+ warn('Error when evaluating expression ' + '"' + this.expression + '": ' + e.toString(), this.vm); |
|
3181 |
+ } |
|
3182 |
+ } |
|
3183 |
+ // "touch" every property so they are all tracked as |
|
3184 |
+ // dependencies for deep watching |
|
3185 |
+ if (this.deep) { |
|
3186 |
+ traverse(value); |
|
3187 |
+ } |
|
3188 |
+ if (this.preProcess) { |
|
3189 |
+ value = this.preProcess(value); |
|
3190 |
+ } |
|
3191 |
+ if (this.filters) { |
|
3192 |
+ value = scope._applyFilters(value, null, this.filters, false); |
|
3193 |
+ } |
|
3194 |
+ if (this.postProcess) { |
|
3195 |
+ value = this.postProcess(value); |
|
3196 |
+ } |
|
3197 |
+ this.afterGet(); |
|
3198 |
+ return value; |
|
3199 |
+ }; |
|
3200 |
+ |
|
3201 |
+ /** |
|
3202 |
+ * Set the corresponding value with the setter. |
|
3203 |
+ * |
|
3204 |
+ * @param {*} value |
|
3205 |
+ */ |
|
3206 |
+ |
|
3207 |
+ Watcher.prototype.set = function (value) { |
|
3208 |
+ var scope = this.scope || this.vm; |
|
3209 |
+ if (this.filters) { |
|
3210 |
+ value = scope._applyFilters(value, this.value, this.filters, true); |
|
3211 |
+ } |
|
3212 |
+ try { |
|
3213 |
+ this.setter.call(scope, scope, value); |
|
3214 |
+ } catch (e) { |
|
3215 |
+ if ('development' !== 'production' && config.warnExpressionErrors) { |
|
3216 |
+ warn('Error when evaluating setter ' + '"' + this.expression + '": ' + e.toString(), this.vm); |
|
3217 |
+ } |
|
3218 |
+ } |
|
3219 |
+ // two-way sync for v-for alias |
|
3220 |
+ var forContext = scope.$forContext; |
|
3221 |
+ if (forContext && forContext.alias === this.expression) { |
|
3222 |
+ if (forContext.filters) { |
|
3223 |
+ 'development' !== 'production' && warn('It seems you are using two-way binding on ' + 'a v-for alias (' + this.expression + '), and the ' + 'v-for has filters. This will not work properly. ' + 'Either remove the filters or use an array of ' + 'objects and bind to object properties instead.', this.vm); |
|
3224 |
+ return; |
|
3225 |
+ } |
|
3226 |
+ forContext._withLock(function () { |
|
3227 |
+ if (scope.$key) { |
|
3228 |
+ // original is an object |
|
3229 |
+ forContext.rawValue[scope.$key] = value; |
|
3230 |
+ } else { |
|
3231 |
+ forContext.rawValue.$set(scope.$index, value); |
|
3232 |
+ } |
|
3233 |
+ }); |
|
3234 |
+ } |
|
3235 |
+ }; |
|
3236 |
+ |
|
3237 |
+ /** |
|
3238 |
+ * Prepare for dependency collection. |
|
3239 |
+ */ |
|
3240 |
+ |
|
3241 |
+ Watcher.prototype.beforeGet = function () { |
|
3242 |
+ Dep.target = this; |
|
3243 |
+ }; |
|
3244 |
+ |
|
3245 |
+ /** |
|
3246 |
+ * Add a dependency to this directive. |
|
3247 |
+ * |
|
3248 |
+ * @param {Dep} dep |
|
3249 |
+ */ |
|
3250 |
+ |
|
3251 |
+ Watcher.prototype.addDep = function (dep) { |
|
3252 |
+ var id = dep.id; |
|
3253 |
+ if (!this.newDepIds.has(id)) { |
|
3254 |
+ this.newDepIds.add(id); |
|
3255 |
+ this.newDeps.push(dep); |
|
3256 |
+ if (!this.depIds.has(id)) { |
|
3257 |
+ dep.addSub(this); |
|
3258 |
+ } |
|
3259 |
+ } |
|
3260 |
+ }; |
|
3261 |
+ |
|
3262 |
+ /** |
|
3263 |
+ * Clean up for dependency collection. |
|
3264 |
+ */ |
|
3265 |
+ |
|
3266 |
+ Watcher.prototype.afterGet = function () { |
|
3267 |
+ Dep.target = null; |
|
3268 |
+ var i = this.deps.length; |
|
3269 |
+ while (i--) { |
|
3270 |
+ var dep = this.deps[i]; |
|
3271 |
+ if (!this.newDepIds.has(dep.id)) { |
|
3272 |
+ dep.removeSub(this); |
|
3273 |
+ } |
|
3274 |
+ } |
|
3275 |
+ var tmp = this.depIds; |
|
3276 |
+ this.depIds = this.newDepIds; |
|
3277 |
+ this.newDepIds = tmp; |
|
3278 |
+ this.newDepIds.clear(); |
|
3279 |
+ tmp = this.deps; |
|
3280 |
+ this.deps = this.newDeps; |
|
3281 |
+ this.newDeps = tmp; |
|
3282 |
+ this.newDeps.length = 0; |
|
3283 |
+ }; |
|
3284 |
+ |
|
3285 |
+ /** |
|
3286 |
+ * Subscriber interface. |
|
3287 |
+ * Will be called when a dependency changes. |
|
3288 |
+ * |
|
3289 |
+ * @param {Boolean} shallow |
|
3290 |
+ */ |
|
3291 |
+ |
|
3292 |
+ Watcher.prototype.update = function (shallow) { |
|
3293 |
+ if (this.lazy) { |
|
3294 |
+ this.dirty = true; |
|
3295 |
+ } else if (this.sync || !config.async) { |
|
3296 |
+ this.run(); |
|
3297 |
+ } else { |
|
3298 |
+ // if queued, only overwrite shallow with non-shallow, |
|
3299 |
+ // but not the other way around. |
|
3300 |
+ this.shallow = this.queued ? shallow ? this.shallow : false : !!shallow; |
|
3301 |
+ this.queued = true; |
|
3302 |
+ // record before-push error stack in debug mode |
|
3303 |
+ /* istanbul ignore if */ |
|
3304 |
+ if ('development' !== 'production' && config.debug) { |
|
3305 |
+ this.prevError = new Error('[vue] async stack trace'); |
|
3306 |
+ } |
|
3307 |
+ pushWatcher(this); |
|
3308 |
+ } |
|
3309 |
+ }; |
|
3310 |
+ |
|
3311 |
+ /** |
|
3312 |
+ * Batcher job interface. |
|
3313 |
+ * Will be called by the batcher. |
|
3314 |
+ */ |
|
3315 |
+ |
|
3316 |
+ Watcher.prototype.run = function () { |
|
3317 |
+ if (this.active) { |
|
3318 |
+ var value = this.get(); |
|
3319 |
+ if (value !== this.value || |
|
3320 |
+ // Deep watchers and watchers on Object/Arrays should fire even |
|
3321 |
+ // when the value is the same, because the value may |
|
3322 |
+ // have mutated; but only do so if this is a |
|
3323 |
+ // non-shallow update (caused by a vm digest). |
|
3324 |
+ (isObject(value) || this.deep) && !this.shallow) { |
|
3325 |
+ // set new value |
|
3326 |
+ var oldValue = this.value; |
|
3327 |
+ this.value = value; |
|
3328 |
+ // in debug + async mode, when a watcher callbacks |
|
3329 |
+ // throws, we also throw the saved before-push error |
|
3330 |
+ // so the full cross-tick stack trace is available. |
|
3331 |
+ var prevError = this.prevError; |
|
3332 |
+ /* istanbul ignore if */ |
|
3333 |
+ if ('development' !== 'production' && config.debug && prevError) { |
|
3334 |
+ this.prevError = null; |
|
3335 |
+ try { |
|
3336 |
+ this.cb.call(this.vm, value, oldValue); |
|
3337 |
+ } catch (e) { |
|
3338 |
+ nextTick(function () { |
|
3339 |
+ throw prevError; |
|
3340 |
+ }, 0); |
|
3341 |
+ throw e; |
|
3342 |
+ } |
|
3343 |
+ } else { |
|
3344 |
+ this.cb.call(this.vm, value, oldValue); |
|
3345 |
+ } |
|
3346 |
+ } |
|
3347 |
+ this.queued = this.shallow = false; |
|
3348 |
+ } |
|
3349 |
+ }; |
|
3350 |
+ |
|
3351 |
+ /** |
|
3352 |
+ * Evaluate the value of the watcher. |
|
3353 |
+ * This only gets called for lazy watchers. |
|
3354 |
+ */ |
|
3355 |
+ |
|
3356 |
+ Watcher.prototype.evaluate = function () { |
|
3357 |
+ // avoid overwriting another watcher that is being |
|
3358 |
+ // collected. |
|
3359 |
+ var current = Dep.target; |
|
3360 |
+ this.value = this.get(); |
|
3361 |
+ this.dirty = false; |
|
3362 |
+ Dep.target = current; |
|
3363 |
+ }; |
|
3364 |
+ |
|
3365 |
+ /** |
|
3366 |
+ * Depend on all deps collected by this watcher. |
|
3367 |
+ */ |
|
3368 |
+ |
|
3369 |
+ Watcher.prototype.depend = function () { |
|
3370 |
+ var i = this.deps.length; |
|
3371 |
+ while (i--) { |
|
3372 |
+ this.deps[i].depend(); |
|
3373 |
+ } |
|
3374 |
+ }; |
|
3375 |
+ |
|
3376 |
+ /** |
|
3377 |
+ * Remove self from all dependencies' subcriber list. |
|
3378 |
+ */ |
|
3379 |
+ |
|
3380 |
+ Watcher.prototype.teardown = function () { |
|
3381 |
+ if (this.active) { |
|
3382 |
+ // remove self from vm's watcher list |
|
3383 |
+ // this is a somewhat expensive operation so we skip it |
|
3384 |
+ // if the vm is being destroyed or is performing a v-for |
|
3385 |
+ // re-render (the watcher list is then filtered by v-for). |
|
3386 |
+ if (!this.vm._isBeingDestroyed && !this.vm._vForRemoving) { |
|
3387 |
+ this.vm._watchers.$remove(this); |
|
3388 |
+ } |
|
3389 |
+ var i = this.deps.length; |
|
3390 |
+ while (i--) { |
|
3391 |
+ this.deps[i].removeSub(this); |
|
3392 |
+ } |
|
3393 |
+ this.active = false; |
|
3394 |
+ this.vm = this.cb = this.value = null; |
|
3395 |
+ } |
|
3396 |
+ }; |
|
3397 |
+ |
|
3398 |
+ /** |
|
3399 |
+ * Recrusively traverse an object to evoke all converted |
|
3400 |
+ * getters, so that every nested property inside the object |
|
3401 |
+ * is collected as a "deep" dependency. |
|
3402 |
+ * |
|
3403 |
+ * @param {*} val |
|
3404 |
+ */ |
|
3405 |
+ |
|
3406 |
+ var seenObjects = new _Set(); |
|
3407 |
+ function traverse(val, seen) { |
|
3408 |
+ var i = undefined, |
|
3409 |
+ keys = undefined; |
|
3410 |
+ if (!seen) { |
|
3411 |
+ seen = seenObjects; |
|
3412 |
+ seen.clear(); |
|
3413 |
+ } |
|
3414 |
+ var isA = isArray(val); |
|
3415 |
+ var isO = isObject(val); |
|
3416 |
+ if ((isA || isO) && Object.isExtensible(val)) { |
|
3417 |
+ if (val.__ob__) { |
|
3418 |
+ var depId = val.__ob__.dep.id; |
|
3419 |
+ if (seen.has(depId)) { |
|
3420 |
+ return; |
|
3421 |
+ } else { |
|
3422 |
+ seen.add(depId); |
|
3423 |
+ } |
|
3424 |
+ } |
|
3425 |
+ if (isA) { |
|
3426 |
+ i = val.length; |
|
3427 |
+ while (i--) traverse(val[i], seen); |
|
3428 |
+ } else if (isO) { |
|
3429 |
+ keys = Object.keys(val); |
|
3430 |
+ i = keys.length; |
|
3431 |
+ while (i--) traverse(val[keys[i]], seen); |
|
3432 |
+ } |
|
3433 |
+ } |
|
3434 |
+ } |
|
3435 |
+ |
|
3436 |
+ var text$1 = { |
|
3437 |
+ |
|
3438 |
+ bind: function bind() { |
|
3439 |
+ this.attr = this.el.nodeType === 3 ? 'data' : 'textContent'; |
|
3440 |
+ }, |
|
3441 |
+ |
|
3442 |
+ update: function update(value) { |
|
3443 |
+ this.el[this.attr] = _toString(value); |
|
3444 |
+ } |
|
3445 |
+ }; |
|
3446 |
+ |
|
3447 |
+ var templateCache = new Cache(1000); |
|
3448 |
+ var idSelectorCache = new Cache(1000); |
|
3449 |
+ |
|
3450 |
+ var map = { |
|
3451 |
+ efault: [0, '', ''], |
|
3452 |
+ legend: [1, '<fieldset>', '</fieldset>'], |
|
3453 |
+ tr: [2, '<table><tbody>', '</tbody></table>'], |
|
3454 |
+ col: [2, '<table><tbody></tbody><colgroup>', '</colgroup></table>'] |
|
3455 |
+ }; |
|
3456 |
+ |
|
3457 |
+ map.td = map.th = [3, '<table><tbody><tr>', '</tr></tbody></table>']; |
|
3458 |
+ |
|
3459 |
+ map.option = map.optgroup = [1, '<select multiple="multiple">', '</select>']; |
|
3460 |
+ |
|
3461 |
+ map.thead = map.tbody = map.colgroup = map.caption = map.tfoot = [1, '<table>', '</table>']; |
|
3462 |
+ |
|
3463 |
+ map.g = map.defs = map.symbol = map.use = map.image = map.text = map.circle = map.ellipse = map.line = map.path = map.polygon = map.polyline = map.rect = [1, '<svg ' + 'xmlns="http://www.w3.org/2000/svg" ' + 'xmlns:xlink="http://www.w3.org/1999/xlink" ' + 'xmlns:ev="http://www.w3.org/2001/xml-events"' + 'version="1.1">', '</svg>']; |
|
3464 |
+ |
|
3465 |
+ /** |
|
3466 |
+ * Check if a node is a supported template node with a |
|
3467 |
+ * DocumentFragment content. |
|
3468 |
+ * |
|
3469 |
+ * @param {Node} node |
|
3470 |
+ * @return {Boolean} |
|
3471 |
+ */ |
|
3472 |
+ |
|
3473 |
+ function isRealTemplate(node) { |
|
3474 |
+ return isTemplate(node) && isFragment(node.content); |
|
3475 |
+ } |
|
3476 |
+ |
|
3477 |
+ var tagRE$1 = /<([\w:-]+)/; |
|
3478 |
+ var entityRE = /&#?\w+?;/; |
|
3479 |
+ var commentRE = /<!--/; |
|
3480 |
+ |
|
3481 |
+ /** |
|
3482 |
+ * Convert a string template to a DocumentFragment. |
|
3483 |
+ * Determines correct wrapping by tag types. Wrapping |
|
3484 |
+ * strategy found in jQuery & component/domify. |
|
3485 |
+ * |
|
3486 |
+ * @param {String} templateString |
|
3487 |
+ * @param {Boolean} raw |
|
3488 |
+ * @return {DocumentFragment} |
|
3489 |
+ */ |
|
3490 |
+ |
|
3491 |
+ function stringToFragment(templateString, raw) { |
|
3492 |
+ // try a cache hit first |
|
3493 |
+ var cacheKey = raw ? templateString : templateString.trim(); |
|
3494 |
+ var hit = templateCache.get(cacheKey); |
|
3495 |
+ if (hit) { |
|
3496 |
+ return hit; |
|
3497 |
+ } |
|
3498 |
+ |
|
3499 |
+ var frag = document.createDocumentFragment(); |
|
3500 |
+ var tagMatch = templateString.match(tagRE$1); |
|
3501 |
+ var entityMatch = entityRE.test(templateString); |
|
3502 |
+ var commentMatch = commentRE.test(templateString); |
|
3503 |
+ |
|
3504 |
+ if (!tagMatch && !entityMatch && !commentMatch) { |
|
3505 |
+ // text only, return a single text node. |
|
3506 |
+ frag.appendChild(document.createTextNode(templateString)); |
|
3507 |
+ } else { |
|
3508 |
+ var tag = tagMatch && tagMatch[1]; |
|
3509 |
+ var wrap = map[tag] || map.efault; |
|
3510 |
+ var depth = wrap[0]; |
|
3511 |
+ var prefix = wrap[1]; |
|
3512 |
+ var suffix = wrap[2]; |
|
3513 |
+ var node = document.createElement('div'); |
|
3514 |
+ |
|
3515 |
+ node.innerHTML = prefix + templateString + suffix; |
|
3516 |
+ while (depth--) { |
|
3517 |
+ node = node.lastChild; |
|
3518 |
+ } |
|
3519 |
+ |
|
3520 |
+ var child; |
|
3521 |
+ /* eslint-disable no-cond-assign */ |
|
3522 |
+ while (child = node.firstChild) { |
|
3523 |
+ /* eslint-enable no-cond-assign */ |
|
3524 |
+ frag.appendChild(child); |
|
3525 |
+ } |
|
3526 |
+ } |
|
3527 |
+ if (!raw) { |
|
3528 |
+ trimNode(frag); |
|
3529 |
+ } |
|
3530 |
+ templateCache.put(cacheKey, frag); |
|
3531 |
+ return frag; |
|
3532 |
+ } |
|
3533 |
+ |
|
3534 |
+ /** |
|
3535 |
+ * Convert a template node to a DocumentFragment. |
|
3536 |
+ * |
|
3537 |
+ * @param {Node} node |
|
3538 |
+ * @return {DocumentFragment} |
|
3539 |
+ */ |
|
3540 |
+ |
|
3541 |
+ function nodeToFragment(node) { |
|
3542 |
+ // if its a template tag and the browser supports it, |
|
3543 |
+ // its content is already a document fragment. However, iOS Safari has |
|
3544 |
+ // bug when using directly cloned template content with touch |
|
3545 |
+ // events and can cause crashes when the nodes are removed from DOM, so we |
|
3546 |
+ // have to treat template elements as string templates. (#2805) |
|
3547 |
+ /* istanbul ignore if */ |
|
3548 |
+ if (isRealTemplate(node)) { |
|
3549 |
+ return stringToFragment(node.innerHTML); |
|
3550 |
+ } |
|
3551 |
+ // script template |
|
3552 |
+ if (node.tagName === 'SCRIPT') { |
|
3553 |
+ return stringToFragment(node.textContent); |
|
3554 |
+ } |
|
3555 |
+ // normal node, clone it to avoid mutating the original |
|
3556 |
+ var clonedNode = cloneNode(node); |
|
3557 |
+ var frag = document.createDocumentFragment(); |
|
3558 |
+ var child; |
|
3559 |
+ /* eslint-disable no-cond-assign */ |
|
3560 |
+ while (child = clonedNode.firstChild) { |
|
3561 |
+ /* eslint-enable no-cond-assign */ |
|
3562 |
+ frag.appendChild(child); |
|
3563 |
+ } |
|
3564 |
+ trimNode(frag); |
|
3565 |
+ return frag; |
|
3566 |
+ } |
|
3567 |
+ |
|
3568 |
+ // Test for the presence of the Safari template cloning bug |
|
3569 |
+ // https://bugs.webkit.org/showug.cgi?id=137755 |
|
3570 |
+ var hasBrokenTemplate = (function () { |
|
3571 |
+ /* istanbul ignore else */ |
|
3572 |
+ if (inBrowser) { |
|
3573 |
+ var a = document.createElement('div'); |
|
3574 |
+ a.innerHTML = '<template>1</template>'; |
|
3575 |
+ return !a.cloneNode(true).firstChild.innerHTML; |
|
3576 |
+ } else { |
|
3577 |
+ return false; |
|
3578 |
+ } |
|
3579 |
+ })(); |
|
3580 |
+ |
|
3581 |
+ // Test for IE10/11 textarea placeholder clone bug |
|
3582 |
+ var hasTextareaCloneBug = (function () { |
|
3583 |
+ /* istanbul ignore else */ |
|
3584 |
+ if (inBrowser) { |
|
3585 |
+ var t = document.createElement('textarea'); |
|
3586 |
+ t.placeholder = 't'; |
|
3587 |
+ return t.cloneNode(true).value === 't'; |
|
3588 |
+ } else { |
|
3589 |
+ return false; |
|
3590 |
+ } |
|
3591 |
+ })(); |
|
3592 |
+ |
|
3593 |
+ /** |
|
3594 |
+ * 1. Deal with Safari cloning nested <template> bug by |
|
3595 |
+ * manually cloning all template instances. |
|
3596 |
+ * 2. Deal with IE10/11 textarea placeholder bug by setting |
|
3597 |
+ * the correct value after cloning. |
|
3598 |
+ * |
|
3599 |
+ * @param {Element|DocumentFragment} node |
|
3600 |
+ * @return {Element|DocumentFragment} |
|
3601 |
+ */ |
|
3602 |
+ |
|
3603 |
+ function cloneNode(node) { |
|
3604 |
+ /* istanbul ignore if */ |
|
3605 |
+ if (!node.querySelectorAll) { |
|
3606 |
+ return node.cloneNode(); |
|
3607 |
+ } |
|
3608 |
+ var res = node.cloneNode(true); |
|
3609 |
+ var i, original, cloned; |
|
3610 |
+ /* istanbul ignore if */ |
|
3611 |
+ if (hasBrokenTemplate) { |
|
3612 |
+ var tempClone = res; |
|
3613 |
+ if (isRealTemplate(node)) { |
|
3614 |
+ node = node.content; |
|
3615 |
+ tempClone = res.content; |
|
3616 |
+ } |
|
3617 |
+ original = node.querySelectorAll('template'); |
|
3618 |
+ if (original.length) { |
|
3619 |
+ cloned = tempClone.querySelectorAll('template'); |
|
3620 |
+ i = cloned.length; |
|
3621 |
+ while (i--) { |
|
3622 |
+ cloned[i].parentNode.replaceChild(cloneNode(original[i]), cloned[i]); |
|
3623 |
+ } |
|
3624 |
+ } |
|
3625 |
+ } |
|
3626 |
+ /* istanbul ignore if */ |
|
3627 |
+ if (hasTextareaCloneBug) { |
|
3628 |
+ if (node.tagName === 'TEXTAREA') { |
|
3629 |
+ res.value = node.value; |
|
3630 |
+ } else { |
|
3631 |
+ original = node.querySelectorAll('textarea'); |
|
3632 |
+ if (original.length) { |
|
3633 |
+ cloned = res.querySelectorAll('textarea'); |
|
3634 |
+ i = cloned.length; |
|
3635 |
+ while (i--) { |
|
3636 |
+ cloned[i].value = original[i].value; |
|
3637 |
+ } |
|
3638 |
+ } |
|
3639 |
+ } |
|
3640 |
+ } |
|
3641 |
+ return res; |
|
3642 |
+ } |
|
3643 |
+ |
|
3644 |
+ /** |
|
3645 |
+ * Process the template option and normalizes it into a |
|
3646 |
+ * a DocumentFragment that can be used as a partial or a |
|
3647 |
+ * instance template. |
|
3648 |
+ * |
|
3649 |
+ * @param {*} template |
|
3650 |
+ * Possible values include: |
|
3651 |
+ * - DocumentFragment object |
|
3652 |
+ * - Node object of type Template |
|
3653 |
+ * - id selector: '#some-template-id' |
|
3654 |
+ * - template string: '<div><span>{{msg}}</span></div>' |
|
3655 |
+ * @param {Boolean} shouldClone |
|
3656 |
+ * @param {Boolean} raw |
|
3657 |
+ * inline HTML interpolation. Do not check for id |
|
3658 |
+ * selector and keep whitespace in the string. |
|
3659 |
+ * @return {DocumentFragment|undefined} |
|
3660 |
+ */ |
|
3661 |
+ |
|
3662 |
+ function parseTemplate(template, shouldClone, raw) { |
|
3663 |
+ var node, frag; |
|
3664 |
+ |
|
3665 |
+ // if the template is already a document fragment, |
|
3666 |
+ // do nothing |
|
3667 |
+ if (isFragment(template)) { |
|
3668 |
+ trimNode(template); |
|
3669 |
+ return shouldClone ? cloneNode(template) : template; |
|
3670 |
+ } |
|
3671 |
+ |
|
3672 |
+ if (typeof template === 'string') { |
|
3673 |
+ // id selector |
|
3674 |
+ if (!raw && template.charAt(0) === '#') { |
|
3675 |
+ // id selector can be cached too |
|
3676 |
+ frag = idSelectorCache.get(template); |
|
3677 |
+ if (!frag) { |
|
3678 |
+ node = document.getElementById(template.slice(1)); |
|
3679 |
+ if (node) { |
|
3680 |
+ frag = nodeToFragment(node); |
|
3681 |
+ // save selector to cache |
|
3682 |
+ idSelectorCache.put(template, frag); |
|
3683 |
+ } |
|
3684 |
+ } |
|
3685 |
+ } else { |
|
3686 |
+ // normal string template |
|
3687 |
+ frag = stringToFragment(template, raw); |
|
3688 |
+ } |
|
3689 |
+ } else if (template.nodeType) { |
|
3690 |
+ // a direct node |
|
3691 |
+ frag = nodeToFragment(template); |
|
3692 |
+ } |
|
3693 |
+ |
|
3694 |
+ return frag && shouldClone ? cloneNode(frag) : frag; |
|
3695 |
+ } |
|
3696 |
+ |
|
3697 |
+var template = Object.freeze({ |
|
3698 |
+ cloneNode: cloneNode, |
|
3699 |
+ parseTemplate: parseTemplate |
|
3700 |
+ }); |
|
3701 |
+ |
|
3702 |
+ var html = { |
|
3703 |
+ |
|
3704 |
+ bind: function bind() { |
|
3705 |
+ // a comment node means this is a binding for |
|
3706 |
+ // {{{ inline unescaped html }}} |
|
3707 |
+ if (this.el.nodeType === 8) { |
|
3708 |
+ // hold nodes |
|
3709 |
+ this.nodes = []; |
|
3710 |
+ // replace the placeholder with proper anchor |
|
3711 |
+ this.anchor = createAnchor('v-html'); |
|
3712 |
+ replace(this.el, this.anchor); |
|
3713 |
+ } |
|
3714 |
+ }, |
|
3715 |
+ |
|
3716 |
+ update: function update(value) { |
|
3717 |
+ value = _toString(value); |
|
3718 |
+ if (this.nodes) { |
|
3719 |
+ this.swap(value); |
|
3720 |
+ } else { |
|
3721 |
+ this.el.innerHTML = value; |
|
3722 |
+ } |
|
3723 |
+ }, |
|
3724 |
+ |
|
3725 |
+ swap: function swap(value) { |
|
3726 |
+ // remove old nodes |
|
3727 |
+ var i = this.nodes.length; |
|
3728 |
+ while (i--) { |
|
3729 |
+ remove(this.nodes[i]); |
|
3730 |
+ } |
|
3731 |
+ // convert new value to a fragment |
|
3732 |
+ // do not attempt to retrieve from id selector |
|
3733 |
+ var frag = parseTemplate(value, true, true); |
|
3734 |
+ // save a reference to these nodes so we can remove later |
|
3735 |
+ this.nodes = toArray(frag.childNodes); |
|
3736 |
+ before(frag, this.anchor); |
|
3737 |
+ } |
|
3738 |
+ }; |
|
3739 |
+ |
|
3740 |
+ /** |
|
3741 |
+ * Abstraction for a partially-compiled fragment. |
|
3742 |
+ * Can optionally compile content with a child scope. |
|
3743 |
+ * |
|
3744 |
+ * @param {Function} linker |
|
3745 |
+ * @param {Vue} vm |
|
3746 |
+ * @param {DocumentFragment} frag |
|
3747 |
+ * @param {Vue} [host] |
|
3748 |
+ * @param {Object} [scope] |
|
3749 |
+ * @param {Fragment} [parentFrag] |
|
3750 |
+ */ |
|
3751 |
+ function Fragment(linker, vm, frag, host, scope, parentFrag) { |
|
3752 |
+ this.children = []; |
|
3753 |
+ this.childFrags = []; |
|
3754 |
+ this.vm = vm; |
|
3755 |
+ this.scope = scope; |
|
3756 |
+ this.inserted = false; |
|
3757 |
+ this.parentFrag = parentFrag; |
|
3758 |
+ if (parentFrag) { |
|
3759 |
+ parentFrag.childFrags.push(this); |
|
3760 |
+ } |
|
3761 |
+ this.unlink = linker(vm, frag, host, scope, this); |
|
3762 |
+ var single = this.single = frag.childNodes.length === 1 && |
|
3763 |
+ // do not go single mode if the only node is an anchor |
|
3764 |
+ !frag.childNodes[0].__v_anchor; |
|
3765 |
+ if (single) { |
|
3766 |
+ this.node = frag.childNodes[0]; |
|
3767 |
+ this.before = singleBefore; |
|
3768 |
+ this.remove = singleRemove; |
|
3769 |
+ } else { |
|
3770 |
+ this.node = createAnchor('fragment-start'); |
|
3771 |
+ this.end = createAnchor('fragment-end'); |
|
3772 |
+ this.frag = frag; |
|
3773 |
+ prepend(this.node, frag); |
|
3774 |
+ frag.appendChild(this.end); |
|
3775 |
+ this.before = multiBefore; |
|
3776 |
+ this.remove = multiRemove; |
|
3777 |
+ } |
|
3778 |
+ this.node.__v_frag = this; |
|
3779 |
+ } |
|
3780 |
+ |
|
3781 |
+ /** |
|
3782 |
+ * Call attach/detach for all components contained within |
|
3783 |
+ * this fragment. Also do so recursively for all child |
|
3784 |
+ * fragments. |
|
3785 |
+ * |
|
3786 |
+ * @param {Function} hook |
|
3787 |
+ */ |
|
3788 |
+ |
|
3789 |
+ Fragment.prototype.callHook = function (hook) { |
|
3790 |
+ var i, l; |
|
3791 |
+ for (i = 0, l = this.childFrags.length; i < l; i++) { |
|
3792 |
+ this.childFrags[i].callHook(hook); |
|
3793 |
+ } |
|
3794 |
+ for (i = 0, l = this.children.length; i < l; i++) { |
|
3795 |
+ hook(this.children[i]); |
|
3796 |
+ } |
|
3797 |
+ }; |
|
3798 |
+ |
|
3799 |
+ /** |
|
3800 |
+ * Insert fragment before target, single node version |
|
3801 |
+ * |
|
3802 |
+ * @param {Node} target |
|
3803 |
+ * @param {Boolean} withTransition |
|
3804 |
+ */ |
|
3805 |
+ |
|
3806 |
+ function singleBefore(target, withTransition) { |
|
3807 |
+ this.inserted = true; |
|
3808 |
+ var method = withTransition !== false ? beforeWithTransition : before; |
|
3809 |
+ method(this.node, target, this.vm); |
|
3810 |
+ if (inDoc(this.node)) { |
|
3811 |
+ this.callHook(attach); |
|
3812 |
+ } |
|
3813 |
+ } |
|
3814 |
+ |
|
3815 |
+ /** |
|
3816 |
+ * Remove fragment, single node version |
|
3817 |
+ */ |
|
3818 |
+ |
|
3819 |
+ function singleRemove() { |
|
3820 |
+ this.inserted = false; |
|
3821 |
+ var shouldCallRemove = inDoc(this.node); |
|
3822 |
+ var self = this; |
|
3823 |
+ this.beforeRemove(); |
|
3824 |
+ removeWithTransition(this.node, this.vm, function () { |
|
3825 |
+ if (shouldCallRemove) { |
|
3826 |
+ self.callHook(detach); |
|
3827 |
+ } |
|
3828 |
+ self.destroy(); |
|
3829 |
+ }); |
|
3830 |
+ } |
|
3831 |
+ |
|
3832 |
+ /** |
|
3833 |
+ * Insert fragment before target, multi-nodes version |
|
3834 |
+ * |
|
3835 |
+ * @param {Node} target |
|
3836 |
+ * @param {Boolean} withTransition |
|
3837 |
+ */ |
|
3838 |
+ |
|
3839 |
+ function multiBefore(target, withTransition) { |
|
3840 |
+ this.inserted = true; |
|
3841 |
+ var vm = this.vm; |
|
3842 |
+ var method = withTransition !== false ? beforeWithTransition : before; |
|
3843 |
+ mapNodeRange(this.node, this.end, function (node) { |
|
3844 |
+ method(node, target, vm); |
|
3845 |
+ }); |
|
3846 |
+ if (inDoc(this.node)) { |
|
3847 |
+ this.callHook(attach); |
|
3848 |
+ } |
|
3849 |
+ } |
|
3850 |
+ |
|
3851 |
+ /** |
|
3852 |
+ * Remove fragment, multi-nodes version |
|
3853 |
+ */ |
|
3854 |
+ |
|
3855 |
+ function multiRemove() { |
|
3856 |
+ this.inserted = false; |
|
3857 |
+ var self = this; |
|
3858 |
+ var shouldCallRemove = inDoc(this.node); |
|
3859 |
+ this.beforeRemove(); |
|
3860 |
+ removeNodeRange(this.node, this.end, this.vm, this.frag, function () { |
|
3861 |
+ if (shouldCallRemove) { |
|
3862 |
+ self.callHook(detach); |
|
3863 |
+ } |
|
3864 |
+ self.destroy(); |
|
3865 |
+ }); |
|
3866 |
+ } |
|
3867 |
+ |
|
3868 |
+ /** |
|
3869 |
+ * Prepare the fragment for removal. |
|
3870 |
+ */ |
|
3871 |
+ |
|
3872 |
+ Fragment.prototype.beforeRemove = function () { |
|
3873 |
+ var i, l; |
|
3874 |
+ for (i = 0, l = this.childFrags.length; i < l; i++) { |
|
3875 |
+ // call the same method recursively on child |
|
3876 |
+ // fragments, depth-first |
|
3877 |
+ this.childFrags[i].beforeRemove(false); |
|
3878 |
+ } |
|
3879 |
+ for (i = 0, l = this.children.length; i < l; i++) { |
|
3880 |
+ // Call destroy for all contained instances, |
|
3881 |
+ // with remove:false and defer:true. |
|
3882 |
+ // Defer is necessary because we need to |
|
3883 |
+ // keep the children to call detach hooks |
|
3884 |
+ // on them. |
|
3885 |
+ this.children[i].$destroy(false, true); |
|
3886 |
+ } |
|
3887 |
+ var dirs = this.unlink.dirs; |
|
3888 |
+ for (i = 0, l = dirs.length; i < l; i++) { |
|
3889 |
+ // disable the watchers on all the directives |
|
3890 |
+ // so that the rendered content stays the same |
|
3891 |
+ // during removal. |
|
3892 |
+ dirs[i]._watcher && dirs[i]._watcher.teardown(); |
|
3893 |
+ } |
|
3894 |
+ }; |
|
3895 |
+ |
|
3896 |
+ /** |
|
3897 |
+ * Destroy the fragment. |
|
3898 |
+ */ |
|
3899 |
+ |
|
3900 |
+ Fragment.prototype.destroy = function () { |
|
3901 |
+ if (this.parentFrag) { |
|
3902 |
+ this.parentFrag.childFrags.$remove(this); |
|
3903 |
+ } |
|
3904 |
+ this.node.__v_frag = null; |
|
3905 |
+ this.unlink(); |
|
3906 |
+ }; |
|
3907 |
+ |
|
3908 |
+ /** |
|
3909 |
+ * Call attach hook for a Vue instance. |
|
3910 |
+ * |
|
3911 |
+ * @param {Vue} child |
|
3912 |
+ */ |
|
3913 |
+ |
|
3914 |
+ function attach(child) { |
|
3915 |
+ if (!child._isAttached && inDoc(child.$el)) { |
|
3916 |
+ child._callHook('attached'); |
|
3917 |
+ } |
|
3918 |
+ } |
|
3919 |
+ |
|
3920 |
+ /** |
|
3921 |
+ * Call detach hook for a Vue instance. |
|
3922 |
+ * |
|
3923 |
+ * @param {Vue} child |
|
3924 |
+ */ |
|
3925 |
+ |
|
3926 |
+ function detach(child) { |
|
3927 |
+ if (child._isAttached && !inDoc(child.$el)) { |
|
3928 |
+ child._callHook('detached'); |
|
3929 |
+ } |
|
3930 |
+ } |
|
3931 |
+ |
|
3932 |
+ var linkerCache = new Cache(5000); |
|
3933 |
+ |
|
3934 |
+ /** |
|
3935 |
+ * A factory that can be used to create instances of a |
|
3936 |
+ * fragment. Caches the compiled linker if possible. |
|
3937 |
+ * |
|
3938 |
+ * @param {Vue} vm |
|
3939 |
+ * @param {Element|String} el |
|
3940 |
+ */ |
|
3941 |
+ function FragmentFactory(vm, el) { |
|
3942 |
+ this.vm = vm; |
|
3943 |
+ var template; |
|
3944 |
+ var isString = typeof el === 'string'; |
|
3945 |
+ if (isString || isTemplate(el) && !el.hasAttribute('v-if')) { |
|
3946 |
+ template = parseTemplate(el, true); |
|
3947 |
+ } else { |
|
3948 |
+ template = document.createDocumentFragment(); |
|
3949 |
+ template.appendChild(el); |
|
3950 |
+ } |
|
3951 |
+ this.template = template; |
|
3952 |
+ // linker can be cached, but only for components |
|
3953 |
+ var linker; |
|
3954 |
+ var cid = vm.constructor.cid; |
|
3955 |
+ if (cid > 0) { |
|
3956 |
+ var cacheId = cid + (isString ? el : getOuterHTML(el)); |
|
3957 |
+ linker = linkerCache.get(cacheId); |
|
3958 |
+ if (!linker) { |
|
3959 |
+ linker = compile(template, vm.$options, true); |
|
3960 |
+ linkerCache.put(cacheId, linker); |
|
3961 |
+ } |
|
3962 |
+ } else { |
|
3963 |
+ linker = compile(template, vm.$options, true); |
|
3964 |
+ } |
|
3965 |
+ this.linker = linker; |
|
3966 |
+ } |
|
3967 |
+ |
|
3968 |
+ /** |
|
3969 |
+ * Create a fragment instance with given host and scope. |
|
3970 |
+ * |
|
3971 |
+ * @param {Vue} host |
|
3972 |
+ * @param {Object} scope |
|
3973 |
+ * @param {Fragment} parentFrag |
|
3974 |
+ */ |
|
3975 |
+ |
|
3976 |
+ FragmentFactory.prototype.create = function (host, scope, parentFrag) { |
|
3977 |
+ var frag = cloneNode(this.template); |
|
3978 |
+ return new Fragment(this.linker, this.vm, frag, host, scope, parentFrag); |
|
3979 |
+ }; |
|
3980 |
+ |
|
3981 |
+ var ON = 700; |
|
3982 |
+ var MODEL = 800; |
|
3983 |
+ var BIND = 850; |
|
3984 |
+ var TRANSITION = 1100; |
|
3985 |
+ var EL = 1500; |
|
3986 |
+ var COMPONENT = 1500; |
|
3987 |
+ var PARTIAL = 1750; |
|
3988 |
+ var IF = 2100; |
|
3989 |
+ var FOR = 2200; |
|
3990 |
+ var SLOT = 2300; |
|
3991 |
+ |
|
3992 |
+ var uid$3 = 0; |
|
3993 |
+ |
|
3994 |
+ var vFor = { |
|
3995 |
+ |
|
3996 |
+ priority: FOR, |
|
3997 |
+ terminal: true, |
|
3998 |
+ |
|
3999 |
+ params: ['track-by', 'stagger', 'enter-stagger', 'leave-stagger'], |
|
4000 |
+ |
|
4001 |
+ bind: function bind() { |
|
4002 |
+ // support "item in/of items" syntax |
|
4003 |
+ var inMatch = this.expression.match(/(.*) (?:in|of) (.*)/); |
|
4004 |
+ if (inMatch) { |
|
4005 |
+ var itMatch = inMatch[1].match(/\((.*),(.*)\)/); |
|
4006 |
+ if (itMatch) { |
|
4007 |
+ this.iterator = itMatch[1].trim(); |
|
4008 |
+ this.alias = itMatch[2].trim(); |
|
4009 |
+ } else { |
|
4010 |
+ this.alias = inMatch[1].trim(); |
|
4011 |
+ } |
|
4012 |
+ this.expression = inMatch[2]; |
|
4013 |
+ } |
|
4014 |
+ |
|
4015 |
+ if (!this.alias) { |
|
4016 |
+ 'development' !== 'production' && warn('Invalid v-for expression "' + this.descriptor.raw + '": ' + 'alias is required.', this.vm); |
|
4017 |
+ return; |
|
4018 |
+ } |
|
4019 |
+ |
|
4020 |
+ // uid as a cache identifier |
|
4021 |
+ this.id = '__v-for__' + ++uid$3; |
|
4022 |
+ |
|
4023 |
+ // check if this is an option list, |
|
4024 |
+ // so that we know if we need to update the <select>'s |
|
4025 |
+ // v-model when the option list has changed. |
|
4026 |
+ // because v-model has a lower priority than v-for, |
|
4027 |
+ // the v-model is not bound here yet, so we have to |
|
4028 |
+ // retrive it in the actual updateModel() function. |
|
4029 |
+ var tag = this.el.tagName; |
|
4030 |
+ this.isOption = (tag === 'OPTION' || tag === 'OPTGROUP') && this.el.parentNode.tagName === 'SELECT'; |
|
4031 |
+ |
|
4032 |
+ // setup anchor nodes |
|
4033 |
+ this.start = createAnchor('v-for-start'); |
|
4034 |
+ this.end = createAnchor('v-for-end'); |
|
4035 |
+ replace(this.el, this.end); |
|
4036 |
+ before(this.start, this.end); |
|
4037 |
+ |
|
4038 |
+ // cache |
|
4039 |
+ this.cache = Object.create(null); |
|
4040 |
+ |
|
4041 |
+ // fragment factory |
|
4042 |
+ this.factory = new FragmentFactory(this.vm, this.el); |
|
4043 |
+ }, |
|
4044 |
+ |
|
4045 |
+ update: function update(data) { |
|
4046 |
+ this.diff(data); |
|
4047 |
+ this.updateRef(); |
|
4048 |
+ this.updateModel(); |
|
4049 |
+ }, |
|
4050 |
+ |
|
4051 |
+ /** |
|
4052 |
+ * Diff, based on new data and old data, determine the |
|
4053 |
+ * minimum amount of DOM manipulations needed to make the |
|
4054 |
+ * DOM reflect the new data Array. |
|
4055 |
+ * |
|
4056 |
+ * The algorithm diffs the new data Array by storing a |
|
4057 |
+ * hidden reference to an owner vm instance on previously |
|
4058 |
+ * seen data. This allows us to achieve O(n) which is |
|
4059 |
+ * better than a levenshtein distance based algorithm, |
|
4060 |
+ * which is O(m * n). |
|
4061 |
+ * |
|
4062 |
+ * @param {Array} data |
|
4063 |
+ */ |
|
4064 |
+ |
|
4065 |
+ diff: function diff(data) { |
|
4066 |
+ // check if the Array was converted from an Object |
|
4067 |
+ var item = data[0]; |
|
4068 |
+ var convertedFromObject = this.fromObject = isObject(item) && hasOwn(item, '$key') && hasOwn(item, '$value'); |
|
4069 |
+ |
|
4070 |
+ var trackByKey = this.params.trackBy; |
|
4071 |
+ var oldFrags = this.frags; |
|
4072 |
+ var frags = this.frags = new Array(data.length); |
|
4073 |
+ var alias = this.alias; |
|
4074 |
+ var iterator = this.iterator; |
|
4075 |
+ var start = this.start; |
|
4076 |
+ var end = this.end; |
|
4077 |
+ var inDocument = inDoc(start); |
|
4078 |
+ var init = !oldFrags; |
|
4079 |
+ var i, l, frag, key, value, primitive; |
|
4080 |
+ |
|
4081 |
+ // First pass, go through the new Array and fill up |
|
4082 |
+ // the new frags array. If a piece of data has a cached |
|
4083 |
+ // instance for it, we reuse it. Otherwise build a new |
|
4084 |
+ // instance. |
|
4085 |
+ for (i = 0, l = data.length; i < l; i++) { |
|
4086 |
+ item = data[i]; |
|
4087 |
+ key = convertedFromObject ? item.$key : null; |
|
4088 |
+ value = convertedFromObject ? item.$value : item; |
|
4089 |
+ primitive = !isObject(value); |
|
4090 |
+ frag = !init && this.getCachedFrag(value, i, key); |
|
4091 |
+ if (frag) { |
|
4092 |
+ // reusable fragment |
|
4093 |
+ frag.reused = true; |
|
4094 |
+ // update $index |
|
4095 |
+ frag.scope.$index = i; |
|
4096 |
+ // update $key |
|
4097 |
+ if (key) { |
|
4098 |
+ frag.scope.$key = key; |
|
4099 |
+ } |
|
4100 |
+ // update iterator |
|
4101 |
+ if (iterator) { |
|
4102 |
+ frag.scope[iterator] = key !== null ? key : i; |
|
4103 |
+ } |
|
4104 |
+ // update data for track-by, object repeat & |
|
4105 |
+ // primitive values. |
|
4106 |
+ if (trackByKey || convertedFromObject || primitive) { |
|
4107 |
+ withoutConversion(function () { |
|
4108 |
+ frag.scope[alias] = value; |
|
4109 |
+ }); |
|
4110 |
+ } |
|
4111 |
+ } else { |
|
4112 |
+ // new isntance |
|
4113 |
+ frag = this.create(value, alias, i, key); |
|
4114 |
+ frag.fresh = !init; |
|
4115 |
+ } |
|
4116 |
+ frags[i] = frag; |
|
4117 |
+ if (init) { |
|
4118 |
+ frag.before(end); |
|
4119 |
+ } |
|
4120 |
+ } |
|
4121 |
+ |
|
4122 |
+ // we're done for the initial render. |
|
4123 |
+ if (init) { |
|
4124 |
+ return; |
|
4125 |
+ } |
|
4126 |
+ |
|
4127 |
+ // Second pass, go through the old fragments and |
|
4128 |
+ // destroy those who are not reused (and remove them |
|
4129 |
+ // from cache) |
|
4130 |
+ var removalIndex = 0; |
|
4131 |
+ var totalRemoved = oldFrags.length - frags.length; |
|
4132 |
+ // when removing a large number of fragments, watcher removal |
|
4133 |
+ // turns out to be a perf bottleneck, so we batch the watcher |
|
4134 |
+ // removals into a single filter call! |
|
4135 |
+ this.vm._vForRemoving = true; |
|
4136 |
+ for (i = 0, l = oldFrags.length; i < l; i++) { |
|
4137 |
+ frag = oldFrags[i]; |
|
4138 |
+ if (!frag.reused) { |
|
4139 |
+ this.deleteCachedFrag(frag); |
|
4140 |
+ this.remove(frag, removalIndex++, totalRemoved, inDocument); |
|
4141 |
+ } |
|
4142 |
+ } |
|
4143 |
+ this.vm._vForRemoving = false; |
|
4144 |
+ if (removalIndex) { |
|
4145 |
+ this.vm._watchers = this.vm._watchers.filter(function (w) { |
|
4146 |
+ return w.active; |
|
4147 |
+ }); |
|
4148 |
+ } |
|
4149 |
+ |
|
4150 |
+ // Final pass, move/insert new fragments into the |
|
4151 |
+ // right place. |
|
4152 |
+ var targetPrev, prevEl, currentPrev; |
|
4153 |
+ var insertionIndex = 0; |
|
4154 |
+ for (i = 0, l = frags.length; i < l; i++) { |
|
4155 |
+ frag = frags[i]; |
|
4156 |
+ // this is the frag that we should be after |
|
4157 |
+ targetPrev = frags[i - 1]; |
|
4158 |
+ prevEl = targetPrev ? targetPrev.staggerCb ? targetPrev.staggerAnchor : targetPrev.end || targetPrev.node : start; |
|
4159 |
+ if (frag.reused && !frag.staggerCb) { |
|
4160 |
+ currentPrev = findPrevFrag(frag, start, this.id); |
|
4161 |
+ if (currentPrev !== targetPrev && (!currentPrev || |
|
4162 |
+ // optimization for moving a single item. |
|
4163 |
+ // thanks to suggestions by @livoras in #1807 |
|
4164 |
+ findPrevFrag(currentPrev, start, this.id) !== targetPrev)) { |
|
4165 |
+ this.move(frag, prevEl); |
|
4166 |
+ } |
|
4167 |
+ } else { |
|
4168 |
+ // new instance, or still in stagger. |
|
4169 |
+ // insert with updated stagger index. |
|
4170 |
+ this.insert(frag, insertionIndex++, prevEl, inDocument); |
|
4171 |
+ } |
|
4172 |
+ frag.reused = frag.fresh = false; |
|
4173 |
+ } |
|
4174 |
+ }, |
|
4175 |
+ |
|
4176 |
+ /** |
|
4177 |
+ * Create a new fragment instance. |
|
4178 |
+ * |
|
4179 |
+ * @param {*} value |
|
4180 |
+ * @param {String} alias |
|
4181 |
+ * @param {Number} index |
|
4182 |
+ * @param {String} [key] |
|
4183 |
+ * @return {Fragment} |
|
4184 |
+ */ |
|
4185 |
+ |
|
4186 |
+ create: function create(value, alias, index, key) { |
|
4187 |
+ var host = this._host; |
|
4188 |
+ // create iteration scope |
|
4189 |
+ var parentScope = this._scope || this.vm; |
|
4190 |
+ var scope = Object.create(parentScope); |
|
4191 |
+ // ref holder for the scope |
|
4192 |
+ scope.$refs = Object.create(parentScope.$refs); |
|
4193 |
+ scope.$els = Object.create(parentScope.$els); |
|
4194 |
+ // make sure point $parent to parent scope |
|
4195 |
+ scope.$parent = parentScope; |
|
4196 |
+ // for two-way binding on alias |
|
4197 |
+ scope.$forContext = this; |
|
4198 |
+ // define scope properties |
|
4199 |
+ // important: define the scope alias without forced conversion |
|
4200 |
+ // so that frozen data structures remain non-reactive. |
|
4201 |
+ withoutConversion(function () { |
|
4202 |
+ defineReactive(scope, alias, value); |
|
4203 |
+ }); |
|
4204 |
+ defineReactive(scope, '$index', index); |
|
4205 |
+ if (key) { |
|
4206 |
+ defineReactive(scope, '$key', key); |
|
4207 |
+ } else if (scope.$key) { |
|
4208 |
+ // avoid accidental fallback |
|
4209 |
+ def(scope, '$key', null); |
|
4210 |
+ } |
|
4211 |
+ if (this.iterator) { |
|
4212 |
+ defineReactive(scope, this.iterator, key !== null ? key : index); |
|
4213 |
+ } |
|
4214 |
+ var frag = this.factory.create(host, scope, this._frag); |
|
4215 |
+ frag.forId = this.id; |
|
4216 |
+ this.cacheFrag(value, frag, index, key); |
|
4217 |
+ return frag; |
|
4218 |
+ }, |
|
4219 |
+ |
|
4220 |
+ /** |
|
4221 |
+ * Update the v-ref on owner vm. |
|
4222 |
+ */ |
|
4223 |
+ |
|
4224 |
+ updateRef: function updateRef() { |
|
4225 |
+ var ref = this.descriptor.ref; |
|
4226 |
+ if (!ref) return; |
|
4227 |
+ var hash = (this._scope || this.vm).$refs; |
|
4228 |
+ var refs; |
|
4229 |
+ if (!this.fromObject) { |
|
4230 |
+ refs = this.frags.map(findVmFromFrag); |
|
4231 |
+ } else { |
|
4232 |
+ refs = {}; |
|
4233 |
+ this.frags.forEach(function (frag) { |
|
4234 |
+ refs[frag.scope.$key] = findVmFromFrag(frag); |
|
4235 |
+ }); |
|
4236 |
+ } |
|
4237 |
+ hash[ref] = refs; |
|
4238 |
+ }, |
|
4239 |
+ |
|
4240 |
+ /** |
|
4241 |
+ * For option lists, update the containing v-model on |
|
4242 |
+ * parent <select>. |
|
4243 |
+ */ |
|
4244 |
+ |
|
4245 |
+ updateModel: function updateModel() { |
|
4246 |
+ if (this.isOption) { |
|
4247 |
+ var parent = this.start.parentNode; |
|
4248 |
+ var model = parent && parent.__v_model; |
|
4249 |
+ if (model) { |
|
4250 |
+ model.forceUpdate(); |
|
4251 |
+ } |
|
4252 |
+ } |
|
4253 |
+ }, |
|
4254 |
+ |
|
4255 |
+ /** |
|
4256 |
+ * Insert a fragment. Handles staggering. |
|
4257 |
+ * |
|
4258 |
+ * @param {Fragment} frag |
|
4259 |
+ * @param {Number} index |
|
4260 |
+ * @param {Node} prevEl |
|
4261 |
+ * @param {Boolean} inDocument |
|
4262 |
+ */ |
|
4263 |
+ |
|
4264 |
+ insert: function insert(frag, index, prevEl, inDocument) { |
|
4265 |
+ if (frag.staggerCb) { |
|
4266 |
+ frag.staggerCb.cancel(); |
|
4267 |
+ frag.staggerCb = null; |
|
4268 |
+ } |
|
4269 |
+ var staggerAmount = this.getStagger(frag, index, null, 'enter'); |
|
4270 |
+ if (inDocument && staggerAmount) { |
|
4271 |
+ // create an anchor and insert it synchronously, |
|
4272 |
+ // so that we can resolve the correct order without |
|
4273 |
+ // worrying about some elements not inserted yet |
|
4274 |
+ var anchor = frag.staggerAnchor; |
|
4275 |
+ if (!anchor) { |
|
4276 |
+ anchor = frag.staggerAnchor = createAnchor('stagger-anchor'); |
|
4277 |
+ anchor.__v_frag = frag; |
|
4278 |
+ } |
|
4279 |
+ after(anchor, prevEl); |
|
4280 |
+ var op = frag.staggerCb = cancellable(function () { |
|
4281 |
+ frag.staggerCb = null; |
|
4282 |
+ frag.before(anchor); |
|
4283 |
+ remove(anchor); |
|
4284 |
+ }); |
|
4285 |
+ setTimeout(op, staggerAmount); |
|
4286 |
+ } else { |
|
4287 |
+ var target = prevEl.nextSibling; |
|
4288 |
+ /* istanbul ignore if */ |
|
4289 |
+ if (!target) { |
|
4290 |
+ // reset end anchor position in case the position was messed up |
|
4291 |
+ // by an external drag-n-drop library. |
|
4292 |
+ after(this.end, prevEl); |
|
4293 |
+ target = this.end; |
|
4294 |
+ } |
|
4295 |
+ frag.before(target); |
|
4296 |
+ } |
|
4297 |
+ }, |
|
4298 |
+ |
|
4299 |
+ /** |
|
4300 |
+ * Remove a fragment. Handles staggering. |
|
4301 |
+ * |
|
4302 |
+ * @param {Fragment} frag |
|
4303 |
+ * @param {Number} index |
|
4304 |
+ * @param {Number} total |
|
4305 |
+ * @param {Boolean} inDocument |
|
4306 |
+ */ |
|
4307 |
+ |
|
4308 |
+ remove: function remove(frag, index, total, inDocument) { |
|
4309 |
+ if (frag.staggerCb) { |
|
4310 |
+ frag.staggerCb.cancel(); |
|
4311 |
+ frag.staggerCb = null; |
|
4312 |
+ // it's not possible for the same frag to be removed |
|
4313 |
+ // twice, so if we have a pending stagger callback, |
|
4314 |
+ // it means this frag is queued for enter but removed |
|
4315 |
+ // before its transition started. Since it is already |
|
4316 |
+ // destroyed, we can just leave it in detached state. |
|
4317 |
+ return; |
|
4318 |
+ } |
|
4319 |
+ var staggerAmount = this.getStagger(frag, index, total, 'leave'); |
|
4320 |
+ if (inDocument && staggerAmount) { |
|
4321 |
+ var op = frag.staggerCb = cancellable(function () { |
|
4322 |
+ frag.staggerCb = null; |
|
4323 |
+ frag.remove(); |
|
4324 |
+ }); |
|
4325 |
+ setTimeout(op, staggerAmount); |
|
4326 |
+ } else { |
|
4327 |
+ frag.remove(); |
|
4328 |
+ } |
|
4329 |
+ }, |
|
4330 |
+ |
|
4331 |
+ /** |
|
4332 |
+ * Move a fragment to a new position. |
|
4333 |
+ * Force no transition. |
|
4334 |
+ * |
|
4335 |
+ * @param {Fragment} frag |
|
4336 |
+ * @param {Node} prevEl |
|
4337 |
+ */ |
|
4338 |
+ |
|
4339 |
+ move: function move(frag, prevEl) { |
|
4340 |
+ // fix a common issue with Sortable: |
|
4341 |
+ // if prevEl doesn't have nextSibling, this means it's |
|
4342 |
+ // been dragged after the end anchor. Just re-position |
|
4343 |
+ // the end anchor to the end of the container. |
|
4344 |
+ /* istanbul ignore if */ |
|
4345 |
+ if (!prevEl.nextSibling) { |
|
4346 |
+ this.end.parentNode.appendChild(this.end); |
|
4347 |
+ } |
|
4348 |
+ frag.before(prevEl.nextSibling, false); |
|
4349 |
+ }, |
|
4350 |
+ |
|
4351 |
+ /** |
|
4352 |
+ * Cache a fragment using track-by or the object key. |
|
4353 |
+ * |
|
4354 |
+ * @param {*} value |
|
4355 |
+ * @param {Fragment} frag |
|
4356 |
+ * @param {Number} index |
|
4357 |
+ * @param {String} [key] |
|
4358 |
+ */ |
|
4359 |
+ |
|
4360 |
+ cacheFrag: function cacheFrag(value, frag, index, key) { |
|
4361 |
+ var trackByKey = this.params.trackBy; |
|
4362 |
+ var cache = this.cache; |
|
4363 |
+ var primitive = !isObject(value); |
|
4364 |
+ var id; |
|
4365 |
+ if (key || trackByKey || primitive) { |
|
4366 |
+ id = getTrackByKey(index, key, value, trackByKey); |
|
4367 |
+ if (!cache[id]) { |
|
4368 |
+ cache[id] = frag; |
|
4369 |
+ } else if (trackByKey !== '$index') { |
|
4370 |
+ 'development' !== 'production' && this.warnDuplicate(value); |
|
4371 |
+ } |
|
4372 |
+ } else { |
|
4373 |
+ id = this.id; |
|
4374 |
+ if (hasOwn(value, id)) { |
|
4375 |
+ if (value[id] === null) { |
|
4376 |
+ value[id] = frag; |
|
4377 |
+ } else { |
|
4378 |
+ 'development' !== 'production' && this.warnDuplicate(value); |
|
4379 |
+ } |
|
4380 |
+ } else if (Object.isExtensible(value)) { |
|
4381 |
+ def(value, id, frag); |
|
4382 |
+ } else if ('development' !== 'production') { |
|
4383 |
+ warn('Frozen v-for objects cannot be automatically tracked, make sure to ' + 'provide a track-by key.'); |
|
4384 |
+ } |
|
4385 |
+ } |
|
4386 |
+ frag.raw = value; |
|
4387 |
+ }, |
|
4388 |
+ |
|
4389 |
+ /** |
|
4390 |
+ * Get a cached fragment from the value/index/key |
|
4391 |
+ * |
|
4392 |
+ * @param {*} value |
|
4393 |
+ * @param {Number} index |
|
4394 |
+ * @param {String} key |
|
4395 |
+ * @return {Fragment} |
|
4396 |
+ */ |
|
4397 |
+ |
|
4398 |
+ getCachedFrag: function getCachedFrag(value, index, key) { |
|
4399 |
+ var trackByKey = this.params.trackBy; |
|
4400 |
+ var primitive = !isObject(value); |
|
4401 |
+ var frag; |
|
4402 |
+ if (key || trackByKey || primitive) { |
|
4403 |
+ var id = getTrackByKey(index, key, value, trackByKey); |
|
4404 |
+ frag = this.cache[id]; |
|
4405 |
+ } else { |
|
4406 |
+ frag = value[this.id]; |
|
4407 |
+ } |
|
4408 |
+ if (frag && (frag.reused || frag.fresh)) { |
|
4409 |
+ 'development' !== 'production' && this.warnDuplicate(value); |
|
4410 |
+ } |
|
4411 |
+ return frag; |
|
4412 |
+ }, |
|
4413 |
+ |
|
4414 |
+ /** |
|
4415 |
+ * Delete a fragment from cache. |
|
4416 |
+ * |
|
4417 |
+ * @param {Fragment} frag |
|
4418 |
+ */ |
|
4419 |
+ |
|
4420 |
+ deleteCachedFrag: function deleteCachedFrag(frag) { |
|
4421 |
+ var value = frag.raw; |
|
4422 |
+ var trackByKey = this.params.trackBy; |
|
4423 |
+ var scope = frag.scope; |
|
4424 |
+ var index = scope.$index; |
|
4425 |
+ // fix #948: avoid accidentally fall through to |
|
4426 |
+ // a parent repeater which happens to have $key. |
|
4427 |
+ var key = hasOwn(scope, '$key') && scope.$key; |
|
4428 |
+ var primitive = !isObject(value); |
|
4429 |
+ if (trackByKey || key || primitive) { |
|
4430 |
+ var id = getTrackByKey(index, key, value, trackByKey); |
|
4431 |
+ this.cache[id] = null; |
|
4432 |
+ } else { |
|
4433 |
+ value[this.id] = null; |
|
4434 |
+ frag.raw = null; |
|
4435 |
+ } |
|
4436 |
+ }, |
|
4437 |
+ |
|
4438 |
+ /** |
|
4439 |
+ * Get the stagger amount for an insertion/removal. |
|
4440 |
+ * |
|
4441 |
+ * @param {Fragment} frag |
|
4442 |
+ * @param {Number} index |
|
4443 |
+ * @param {Number} total |
|
4444 |
+ * @param {String} type |
|
4445 |
+ */ |
|
4446 |
+ |
|
4447 |
+ getStagger: function getStagger(frag, index, total, type) { |
|
4448 |
+ type = type + 'Stagger'; |
|
4449 |
+ var trans = frag.node.__v_trans; |
|
4450 |
+ var hooks = trans && trans.hooks; |
|
4451 |
+ var hook = hooks && (hooks[type] || hooks.stagger); |
|
4452 |
+ return hook ? hook.call(frag, index, total) : index * parseInt(this.params[type] || this.params.stagger, 10); |
|
4453 |
+ }, |
|
4454 |
+ |
|
4455 |
+ /** |
|
4456 |
+ * Pre-process the value before piping it through the |
|
4457 |
+ * filters. This is passed to and called by the watcher. |
|
4458 |
+ */ |
|
4459 |
+ |
|
4460 |
+ _preProcess: function _preProcess(value) { |
|
4461 |
+ // regardless of type, store the un-filtered raw value. |
|
4462 |
+ this.rawValue = value; |
|
4463 |
+ return value; |
|
4464 |
+ }, |
|
4465 |
+ |
|
4466 |
+ /** |
|
4467 |
+ * Post-process the value after it has been piped through |
|
4468 |
+ * the filters. This is passed to and called by the watcher. |
|
4469 |
+ * |
|
4470 |
+ * It is necessary for this to be called during the |
|
4471 |
+ * watcher's dependency collection phase because we want |
|
4472 |
+ * the v-for to update when the source Object is mutated. |
|
4473 |
+ */ |
|
4474 |
+ |
|
4475 |
+ _postProcess: function _postProcess(value) { |
|
4476 |
+ if (isArray(value)) { |
|
4477 |
+ return value; |
|
4478 |
+ } else if (isPlainObject(value)) { |
|
4479 |
+ // convert plain object to array. |
|
4480 |
+ var keys = Object.keys(value); |
|
4481 |
+ var i = keys.length; |
|
4482 |
+ var res = new Array(i); |
|
4483 |
+ var key; |
|
4484 |
+ while (i--) { |
|
4485 |
+ key = keys[i]; |
|
4486 |
+ res[i] = { |
|
4487 |
+ $key: key, |
|
4488 |
+ $value: value[key] |
|
4489 |
+ }; |
|
4490 |
+ } |
|
4491 |
+ return res; |
|
4492 |
+ } else { |
|
4493 |
+ if (typeof value === 'number' && !isNaN(value)) { |
|
4494 |
+ value = range(value); |
|
4495 |
+ } |
|
4496 |
+ return value || []; |
|
4497 |
+ } |
|
4498 |
+ }, |
|
4499 |
+ |
|
4500 |
+ unbind: function unbind() { |
|
4501 |
+ if (this.descriptor.ref) { |
|
4502 |
+ (this._scope || this.vm).$refs[this.descriptor.ref] = null; |
|
4503 |
+ } |
|
4504 |
+ if (this.frags) { |
|
4505 |
+ var i = this.frags.length; |
|
4506 |
+ var frag; |
|
4507 |
+ while (i--) { |
|
4508 |
+ frag = this.frags[i]; |
|
4509 |
+ this.deleteCachedFrag(frag); |
|
4510 |
+ frag.destroy(); |
|
4511 |
+ } |
|
4512 |
+ } |
|
4513 |
+ } |
|
4514 |
+ }; |
|
4515 |
+ |
|
4516 |
+ /** |
|
4517 |
+ * Helper to find the previous element that is a fragment |
|
4518 |
+ * anchor. This is necessary because a destroyed frag's |
|
4519 |
+ * element could still be lingering in the DOM before its |
|
4520 |
+ * leaving transition finishes, but its inserted flag |
|
4521 |
+ * should have been set to false so we can skip them. |
|
4522 |
+ * |
|
4523 |
+ * If this is a block repeat, we want to make sure we only |
|
4524 |
+ * return frag that is bound to this v-for. (see #929) |
|
4525 |
+ * |
|
4526 |
+ * @param {Fragment} frag |
|
4527 |
+ * @param {Comment|Text} anchor |
|
4528 |
+ * @param {String} id |
|
4529 |
+ * @return {Fragment} |
|
4530 |
+ */ |
|
4531 |
+ |
|
4532 |
+ function findPrevFrag(frag, anchor, id) { |
|
4533 |
+ var el = frag.node.previousSibling; |
|
4534 |
+ /* istanbul ignore if */ |
|
4535 |
+ if (!el) return; |
|
4536 |
+ frag = el.__v_frag; |
|
4537 |
+ while ((!frag || frag.forId !== id || !frag.inserted) && el !== anchor) { |
|
4538 |
+ el = el.previousSibling; |
|
4539 |
+ /* istanbul ignore if */ |
|
4540 |
+ if (!el) return; |
|
4541 |
+ frag = el.__v_frag; |
|
4542 |
+ } |
|
4543 |
+ return frag; |
|
4544 |
+ } |
|
4545 |
+ |
|
4546 |
+ /** |
|
4547 |
+ * Find a vm from a fragment. |
|
4548 |
+ * |
|
4549 |
+ * @param {Fragment} frag |
|
4550 |
+ * @return {Vue|undefined} |
|
4551 |
+ */ |
|
4552 |
+ |
|
4553 |
+ function findVmFromFrag(frag) { |
|
4554 |
+ var node = frag.node; |
|
4555 |
+ // handle multi-node frag |
|
4556 |
+ if (frag.end) { |
|
4557 |
+ while (!node.__vue__ && node !== frag.end && node.nextSibling) { |
|
4558 |
+ node = node.nextSibling; |
|
4559 |
+ } |
|
4560 |
+ } |
|
4561 |
+ return node.__vue__; |
|
4562 |
+ } |
|
4563 |
+ |
|
4564 |
+ /** |
|
4565 |
+ * Create a range array from given number. |
|
4566 |
+ * |
|
4567 |
+ * @param {Number} n |
|
4568 |
+ * @return {Array} |
|
4569 |
+ */ |
|
4570 |
+ |
|
4571 |
+ function range(n) { |
|
4572 |
+ var i = -1; |
|
4573 |
+ var ret = new Array(Math.floor(n)); |
|
4574 |
+ while (++i < n) { |
|
4575 |
+ ret[i] = i; |
|
4576 |
+ } |
|
4577 |
+ return ret; |
|
4578 |
+ } |
|
4579 |
+ |
|
4580 |
+ /** |
|
4581 |
+ * Get the track by key for an item. |
|
4582 |
+ * |
|
4583 |
+ * @param {Number} index |
|
4584 |
+ * @param {String} key |
|
4585 |
+ * @param {*} value |
|
4586 |
+ * @param {String} [trackByKey] |
|
4587 |
+ */ |
|
4588 |
+ |
|
4589 |
+ function getTrackByKey(index, key, value, trackByKey) { |
|
4590 |
+ return trackByKey ? trackByKey === '$index' ? index : trackByKey.charAt(0).match(/\w/) ? getPath(value, trackByKey) : value[trackByKey] : key || value; |
|
4591 |
+ } |
|
4592 |
+ |
|
4593 |
+ if ('development' !== 'production') { |
|
4594 |
+ vFor.warnDuplicate = function (value) { |
|
4595 |
+ warn('Duplicate value found in v-for="' + this.descriptor.raw + '": ' + JSON.stringify(value) + '. Use track-by="$index" if ' + 'you are expecting duplicate values.', this.vm); |
|
4596 |
+ }; |
|
4597 |
+ } |
|
4598 |
+ |
|
4599 |
+ var vIf = { |
|
4600 |
+ |
|
4601 |
+ priority: IF, |
|
4602 |
+ terminal: true, |
|
4603 |
+ |
|
4604 |
+ bind: function bind() { |
|
4605 |
+ var el = this.el; |
|
4606 |
+ if (!el.__vue__) { |
|
4607 |
+ // check else block |
|
4608 |
+ var next = el.nextElementSibling; |
|
4609 |
+ if (next && getAttr(next, 'v-else') !== null) { |
|
4610 |
+ remove(next); |
|
4611 |
+ this.elseEl = next; |
|
4612 |
+ } |
|
4613 |
+ // check main block |
|
4614 |
+ this.anchor = createAnchor('v-if'); |
|
4615 |
+ replace(el, this.anchor); |
|
4616 |
+ } else { |
|
4617 |
+ 'development' !== 'production' && warn('v-if="' + this.expression + '" cannot be ' + 'used on an instance root element.', this.vm); |
|
4618 |
+ this.invalid = true; |
|
4619 |
+ } |
|
4620 |
+ }, |
|
4621 |
+ |
|
4622 |
+ update: function update(value) { |
|
4623 |
+ if (this.invalid) return; |
|
4624 |
+ if (value) { |
|
4625 |
+ if (!this.frag) { |
|
4626 |
+ this.insert(); |
|
4627 |
+ } |
|
4628 |
+ } else { |
|
4629 |
+ this.remove(); |
|
4630 |
+ } |
|
4631 |
+ }, |
|
4632 |
+ |
|
4633 |
+ insert: function insert() { |
|
4634 |
+ if (this.elseFrag) { |
|
4635 |
+ this.elseFrag.remove(); |
|
4636 |
+ this.elseFrag = null; |
|
4637 |
+ } |
|
4638 |
+ // lazy init factory |
|
4639 |
+ if (!this.factory) { |
|
4640 |
+ this.factory = new FragmentFactory(this.vm, this.el); |
|
4641 |
+ } |
|
4642 |
+ this.frag = this.factory.create(this._host, this._scope, this._frag); |
|
4643 |
+ this.frag.before(this.anchor); |
|
4644 |
+ }, |
|
4645 |
+ |
|
4646 |
+ remove: function remove() { |
|
4647 |
+ if (this.frag) { |
|
4648 |
+ this.frag.remove(); |
|
4649 |
+ this.frag = null; |
|
4650 |
+ } |
|
4651 |
+ if (this.elseEl && !this.elseFrag) { |
|
4652 |
+ if (!this.elseFactory) { |
|
4653 |
+ this.elseFactory = new FragmentFactory(this.elseEl._context || this.vm, this.elseEl); |
|
4654 |
+ } |
|
4655 |
+ this.elseFrag = this.elseFactory.create(this._host, this._scope, this._frag); |
|
4656 |
+ this.elseFrag.before(this.anchor); |
|
4657 |
+ } |
|
4658 |
+ }, |
|
4659 |
+ |
|
4660 |
+ unbind: function unbind() { |
|
4661 |
+ if (this.frag) { |
|
4662 |
+ this.frag.destroy(); |
|
4663 |
+ } |
|
4664 |
+ if (this.elseFrag) { |
|
4665 |
+ this.elseFrag.destroy(); |
|
4666 |
+ } |
|
4667 |
+ } |
|
4668 |
+ }; |
|
4669 |
+ |
|
4670 |
+ var show = { |
|
4671 |
+ |
|
4672 |
+ bind: function bind() { |
|
4673 |
+ // check else block |
|
4674 |
+ var next = this.el.nextElementSibling; |
|
4675 |
+ if (next && getAttr(next, 'v-else') !== null) { |
|
4676 |
+ this.elseEl = next; |
|
4677 |
+ } |
|
4678 |
+ }, |
|
4679 |
+ |
|
4680 |
+ update: function update(value) { |
|
4681 |
+ this.apply(this.el, value); |
|
4682 |
+ if (this.elseEl) { |
|
4683 |
+ this.apply(this.elseEl, !value); |
|
4684 |
+ } |
|
4685 |
+ }, |
|
4686 |
+ |
|
4687 |
+ apply: function apply(el, value) { |
|
4688 |
+ if (inDoc(el)) { |
|
4689 |
+ applyTransition(el, value ? 1 : -1, toggle, this.vm); |
|
4690 |
+ } else { |
|
4691 |
+ toggle(); |
|
4692 |
+ } |
|
4693 |
+ function toggle() { |
|
4694 |
+ el.style.display = value ? '' : 'none'; |
|
4695 |
+ } |
|
4696 |
+ } |
|
4697 |
+ }; |
|
4698 |
+ |
|
4699 |
+ var text$2 = { |
|
4700 |
+ |
|
4701 |
+ bind: function bind() { |
|
4702 |
+ var self = this; |
|
4703 |
+ var el = this.el; |
|
4704 |
+ var isRange = el.type === 'range'; |
|
4705 |
+ var lazy = this.params.lazy; |
|
4706 |
+ var number = this.params.number; |
|
4707 |
+ var debounce = this.params.debounce; |
|
4708 |
+ |
|
4709 |
+ // handle composition events. |
|
4710 |
+ // http://blog.evanyou.me/2014/01/03/composition-event/ |
|
4711 |
+ // skip this for Android because it handles composition |
|
4712 |
+ // events quite differently. Android doesn't trigger |
|
4713 |
+ // composition events for language input methods e.g. |
|
4714 |
+ // Chinese, but instead triggers them for spelling |
|
4715 |
+ // suggestions... (see Discussion/#162) |
|
4716 |
+ var composing = false; |
|
4717 |
+ if (!isAndroid && !isRange) { |
|
4718 |
+ this.on('compositionstart', function () { |
|
4719 |
+ composing = true; |
|
4720 |
+ }); |
|
4721 |
+ this.on('compositionend', function () { |
|
4722 |
+ composing = false; |
|
4723 |
+ // in IE11 the "compositionend" event fires AFTER |
|
4724 |
+ // the "input" event, so the input handler is blocked |
|
4725 |
+ // at the end... have to call it here. |
|
4726 |
+ // |
|
4727 |
+ // #1327: in lazy mode this is unecessary. |
|
4728 |
+ if (!lazy) { |
|
4729 |
+ self.listener(); |
|
4730 |
+ } |
|
4731 |
+ }); |
|
4732 |
+ } |
|
4733 |
+ |
|
4734 |
+ // prevent messing with the input when user is typing, |
|
4735 |
+ // and force update on blur. |
|
4736 |
+ this.focused = false; |
|
4737 |
+ if (!isRange && !lazy) { |
|
4738 |
+ this.on('focus', function () { |
|
4739 |
+ self.focused = true; |
|
4740 |
+ }); |
|
4741 |
+ this.on('blur', function () { |
|
4742 |
+ self.focused = false; |
|
4743 |
+ // do not sync value after fragment removal (#2017) |
|
4744 |
+ if (!self._frag || self._frag.inserted) { |
|
4745 |
+ self.rawListener(); |
|
4746 |
+ } |
|
4747 |
+ }); |
|
4748 |
+ } |
|
4749 |
+ |
|
4750 |
+ // Now attach the main listener |
|
4751 |
+ this.listener = this.rawListener = function () { |
|
4752 |
+ if (composing || !self._bound) { |
|
4753 |
+ return; |
|
4754 |
+ } |
|
4755 |
+ var val = number || isRange ? toNumber(el.value) : el.value; |
|
4756 |
+ self.set(val); |
|
4757 |
+ // force update on next tick to avoid lock & same value |
|
4758 |
+ // also only update when user is not typing |
|
4759 |
+ nextTick(function () { |
|
4760 |
+ if (self._bound && !self.focused) { |
|
4761 |
+ self.update(self._watcher.value); |
|
4762 |
+ } |
|
4763 |
+ }); |
|
4764 |
+ }; |
|
4765 |
+ |
|
4766 |
+ // apply debounce |
|
4767 |
+ if (debounce) { |
|
4768 |
+ this.listener = _debounce(this.listener, debounce); |
|
4769 |
+ } |
|
4770 |
+ |
|
4771 |
+ // Support jQuery events, since jQuery.trigger() doesn't |
|
4772 |
+ // trigger native events in some cases and some plugins |
|
4773 |
+ // rely on $.trigger() |
|
4774 |
+ // |
|
4775 |
+ // We want to make sure if a listener is attached using |
|
4776 |
+ // jQuery, it is also removed with jQuery, that's why |
|
4777 |
+ // we do the check for each directive instance and |
|
4778 |
+ // store that check result on itself. This also allows |
|
4779 |
+ // easier test coverage control by unsetting the global |
|
4780 |
+ // jQuery variable in tests. |
|
4781 |
+ this.hasjQuery = typeof jQuery === 'function'; |
|
4782 |
+ if (this.hasjQuery) { |
|
4783 |
+ var method = jQuery.fn.on ? 'on' : 'bind'; |
|
4784 |
+ jQuery(el)[method]('change', this.rawListener); |
|
4785 |
+ if (!lazy) { |
|
4786 |
+ jQuery(el)[method]('input', this.listener); |
|
4787 |
+ } |
|
4788 |
+ } else { |
|
4789 |
+ this.on('change', this.rawListener); |
|
4790 |
+ if (!lazy) { |
|
4791 |
+ this.on('input', this.listener); |
|
4792 |
+ } |
|
4793 |
+ } |
|
4794 |
+ |
|
4795 |
+ // IE9 doesn't fire input event on backspace/del/cut |
|
4796 |
+ if (!lazy && isIE9) { |
|
4797 |
+ this.on('cut', function () { |
|
4798 |
+ nextTick(self.listener); |
|
4799 |
+ }); |
|
4800 |
+ this.on('keyup', function (e) { |
|
4801 |
+ if (e.keyCode === 46 || e.keyCode === 8) { |
|
4802 |
+ self.listener(); |
|
4803 |
+ } |
|
4804 |
+ }); |
|
4805 |
+ } |
|
4806 |
+ |
|
4807 |
+ // set initial value if present |
|
4808 |
+ if (el.hasAttribute('value') || el.tagName === 'TEXTAREA' && el.value.trim()) { |
|
4809 |
+ this.afterBind = this.listener; |
|
4810 |
+ } |
|
4811 |
+ }, |
|
4812 |
+ |
|
4813 |
+ update: function update(value) { |
|
4814 |
+ // #3029 only update when the value changes. This prevent |
|
4815 |
+ // browsers from overwriting values like selectionStart |
|
4816 |
+ value = _toString(value); |
|
4817 |
+ if (value !== this.el.value) this.el.value = value; |
|
4818 |
+ }, |
|
4819 |
+ |
|
4820 |
+ unbind: function unbind() { |
|
4821 |
+ var el = this.el; |
|
4822 |
+ if (this.hasjQuery) { |
|
4823 |
+ var method = jQuery.fn.off ? 'off' : 'unbind'; |
|
4824 |
+ jQuery(el)[method]('change', this.listener); |
|
4825 |
+ jQuery(el)[method]('input', this.listener); |
|
4826 |
+ } |
|
4827 |
+ } |
|
4828 |
+ }; |
|
4829 |
+ |
|
4830 |
+ var radio = { |
|
4831 |
+ |
|
4832 |
+ bind: function bind() { |
|
4833 |
+ var self = this; |
|
4834 |
+ var el = this.el; |
|
4835 |
+ |
|
4836 |
+ this.getValue = function () { |
|
4837 |
+ // value overwrite via v-bind:value |
|
4838 |
+ if (el.hasOwnProperty('_value')) { |
|
4839 |
+ return el._value; |
|
4840 |
+ } |
|
4841 |
+ var val = el.value; |
|
4842 |
+ if (self.params.number) { |
|
4843 |
+ val = toNumber(val); |
|
4844 |
+ } |
|
4845 |
+ return val; |
|
4846 |
+ }; |
|
4847 |
+ |
|
4848 |
+ this.listener = function () { |
|
4849 |
+ self.set(self.getValue()); |
|
4850 |
+ }; |
|
4851 |
+ this.on('change', this.listener); |
|
4852 |
+ |
|
4853 |
+ if (el.hasAttribute('checked')) { |
|
4854 |
+ this.afterBind = this.listener; |
|
4855 |
+ } |
|
4856 |
+ }, |
|
4857 |
+ |
|
4858 |
+ update: function update(value) { |
|
4859 |
+ this.el.checked = looseEqual(value, this.getValue()); |
|
4860 |
+ } |
|
4861 |
+ }; |
|
4862 |
+ |
|
4863 |
+ var select = { |
|
4864 |
+ |
|
4865 |
+ bind: function bind() { |
|
4866 |
+ var _this = this; |
|
4867 |
+ |
|
4868 |
+ var self = this; |
|
4869 |
+ var el = this.el; |
|
4870 |
+ |
|
4871 |
+ // method to force update DOM using latest value. |
|
4872 |
+ this.forceUpdate = function () { |
|
4873 |
+ if (self._watcher) { |
|
4874 |
+ self.update(self._watcher.get()); |
|
4875 |
+ } |
|
4876 |
+ }; |
|
4877 |
+ |
|
4878 |
+ // check if this is a multiple select |
|
4879 |
+ var multiple = this.multiple = el.hasAttribute('multiple'); |
|
4880 |
+ |
|
4881 |
+ // attach listener |
|
4882 |
+ this.listener = function () { |
|
4883 |
+ var value = getValue(el, multiple); |
|
4884 |
+ value = self.params.number ? isArray(value) ? value.map(toNumber) : toNumber(value) : value; |
|
4885 |
+ self.set(value); |
|
4886 |
+ }; |
|
4887 |
+ this.on('change', this.listener); |
|
4888 |
+ |
|
4889 |
+ // if has initial value, set afterBind |
|
4890 |
+ var initValue = getValue(el, multiple, true); |
|
4891 |
+ if (multiple && initValue.length || !multiple && initValue !== null) { |
|
4892 |
+ this.afterBind = this.listener; |
|
4893 |
+ } |
|
4894 |
+ |
|
4895 |
+ // All major browsers except Firefox resets |
|
4896 |
+ // selectedIndex with value -1 to 0 when the element |
|
4897 |
+ // is appended to a new parent, therefore we have to |
|
4898 |
+ // force a DOM update whenever that happens... |
|
4899 |
+ this.vm.$on('hook:attached', function () { |
|
4900 |
+ nextTick(_this.forceUpdate); |
|
4901 |
+ }); |
|
4902 |
+ if (!inDoc(el)) { |
|
4903 |
+ nextTick(this.forceUpdate); |
|
4904 |
+ } |
|
4905 |
+ }, |
|
4906 |
+ |
|
4907 |
+ update: function update(value) { |
|
4908 |
+ var el = this.el; |
|
4909 |
+ el.selectedIndex = -1; |
|
4910 |
+ var multi = this.multiple && isArray(value); |
|
4911 |
+ var options = el.options; |
|
4912 |
+ var i = options.length; |
|
4913 |
+ var op, val; |
|
4914 |
+ while (i--) { |
|
4915 |
+ op = options[i]; |
|
4916 |
+ val = op.hasOwnProperty('_value') ? op._value : op.value; |
|
4917 |
+ /* eslint-disable eqeqeq */ |
|
4918 |
+ op.selected = multi ? indexOf$1(value, val) > -1 : looseEqual(value, val); |
|
4919 |
+ /* eslint-enable eqeqeq */ |
|
4920 |
+ } |
|
4921 |
+ }, |
|
4922 |
+ |
|
4923 |
+ unbind: function unbind() { |
|
4924 |
+ /* istanbul ignore next */ |
|
4925 |
+ this.vm.$off('hook:attached', this.forceUpdate); |
|
4926 |
+ } |
|
4927 |
+ }; |
|
4928 |
+ |
|
4929 |
+ /** |
|
4930 |
+ * Get select value |
|
4931 |
+ * |
|
4932 |
+ * @param {SelectElement} el |
|
4933 |
+ * @param {Boolean} multi |
|
4934 |
+ * @param {Boolean} init |
|
4935 |
+ * @return {Array|*} |
|
4936 |
+ */ |
|
4937 |
+ |
|
4938 |
+ function getValue(el, multi, init) { |
|
4939 |
+ var res = multi ? [] : null; |
|
4940 |
+ var op, val, selected; |
|
4941 |
+ for (var i = 0, l = el.options.length; i < l; i++) { |
|
4942 |
+ op = el.options[i]; |
|
4943 |
+ selected = init ? op.hasAttribute('selected') : op.selected; |
|
4944 |
+ if (selected) { |
|
4945 |
+ val = op.hasOwnProperty('_value') ? op._value : op.value; |
|
4946 |
+ if (multi) { |
|
4947 |
+ res.push(val); |
|
4948 |
+ } else { |
|
4949 |
+ return val; |
|
4950 |
+ } |
|
4951 |
+ } |
|
4952 |
+ } |
|
4953 |
+ return res; |
|
4954 |
+ } |
|
4955 |
+ |
|
4956 |
+ /** |
|
4957 |
+ * Native Array.indexOf uses strict equal, but in this |
|
4958 |
+ * case we need to match string/numbers with custom equal. |
|
4959 |
+ * |
|
4960 |
+ * @param {Array} arr |
|
4961 |
+ * @param {*} val |
|
4962 |
+ */ |
|
4963 |
+ |
|
4964 |
+ function indexOf$1(arr, val) { |
|
4965 |
+ var i = arr.length; |
|
4966 |
+ while (i--) { |
|
4967 |
+ if (looseEqual(arr[i], val)) { |
|
4968 |
+ return i; |
|
4969 |
+ } |
|
4970 |
+ } |
|
4971 |
+ return -1; |
|
4972 |
+ } |
|
4973 |
+ |
|
4974 |
+ var checkbox = { |
|
4975 |
+ |
|
4976 |
+ bind: function bind() { |
|
4977 |
+ var self = this; |
|
4978 |
+ var el = this.el; |
|
4979 |
+ |
|
4980 |
+ this.getValue = function () { |
|
4981 |
+ return el.hasOwnProperty('_value') ? el._value : self.params.number ? toNumber(el.value) : el.value; |
|
4982 |
+ }; |
|
4983 |
+ |
|
4984 |
+ function getBooleanValue() { |
|
4985 |
+ var val = el.checked; |
|
4986 |
+ if (val && el.hasOwnProperty('_trueValue')) { |
|
4987 |
+ return el._trueValue; |
|
4988 |
+ } |
|
4989 |
+ if (!val && el.hasOwnProperty('_falseValue')) { |
|
4990 |
+ return el._falseValue; |
|
4991 |
+ } |
|
4992 |
+ return val; |
|
4993 |
+ } |
|
4994 |
+ |
|
4995 |
+ this.listener = function () { |
|
4996 |
+ var model = self._watcher.value; |
|
4997 |
+ if (isArray(model)) { |
|
4998 |
+ var val = self.getValue(); |
|
4999 |
+ if (el.checked) { |
|
5000 |
+ if (indexOf(model, val) < 0) { |
|
5001 |
+ model.push(val); |
|
5002 |
+ } |
|
5003 |
+ } else { |
|
5004 |
+ model.$remove(val); |
|
5005 |
+ } |
|
5006 |
+ } else { |
|
5007 |
+ self.set(getBooleanValue()); |
|
5008 |
+ } |
|
5009 |
+ }; |
|
5010 |
+ |
|
5011 |
+ this.on('change', this.listener); |
|
5012 |
+ if (el.hasAttribute('checked')) { |
|
5013 |
+ this.afterBind = this.listener; |
|
5014 |
+ } |
|
5015 |
+ }, |
|
5016 |
+ |
|
5017 |
+ update: function update(value) { |
|
5018 |
+ var el = this.el; |
|
5019 |
+ if (isArray(value)) { |
|
5020 |
+ el.checked = indexOf(value, this.getValue()) > -1; |
|
5021 |
+ } else { |
|
5022 |
+ if (el.hasOwnProperty('_trueValue')) { |
|
5023 |
+ el.checked = looseEqual(value, el._trueValue); |
|
5024 |
+ } else { |
|
5025 |
+ el.checked = !!value; |
|
5026 |
+ } |
|
5027 |
+ } |
|
5028 |
+ } |
|
5029 |
+ }; |
|
5030 |
+ |
|
5031 |
+ var handlers = { |
|
5032 |
+ text: text$2, |
|
5033 |
+ radio: radio, |
|
5034 |
+ select: select, |
|
5035 |
+ checkbox: checkbox |
|
5036 |
+ }; |
|
5037 |
+ |
|
5038 |
+ var model = { |
|
5039 |
+ |
|
5040 |
+ priority: MODEL, |
|
5041 |
+ twoWay: true, |
|
5042 |
+ handlers: handlers, |
|
5043 |
+ params: ['lazy', 'number', 'debounce'], |
|
5044 |
+ |
|
5045 |
+ /** |
|
5046 |
+ * Possible elements: |
|
5047 |
+ * <select> |
|
5048 |
+ * <textarea> |
|
5049 |
+ * <input type="*"> |
|
5050 |
+ * - text |
|
5051 |
+ * - checkbox |
|
5052 |
+ * - radio |
|
5053 |
+ * - number |
|
5054 |
+ */ |
|
5055 |
+ |
|
5056 |
+ bind: function bind() { |
|
5057 |
+ // friendly warning... |
|
5058 |
+ this.checkFilters(); |
|
5059 |
+ if (this.hasRead && !this.hasWrite) { |
|
5060 |
+ 'development' !== 'production' && warn('It seems you are using a read-only filter with ' + 'v-model="' + this.descriptor.raw + '". ' + 'You might want to use a two-way filter to ensure correct behavior.', this.vm); |
|
5061 |
+ } |
|
5062 |
+ var el = this.el; |
|
5063 |
+ var tag = el.tagName; |
|
5064 |
+ var handler; |
|
5065 |
+ if (tag === 'INPUT') { |
|
5066 |
+ handler = handlers[el.type] || handlers.text; |
|
5067 |
+ } else if (tag === 'SELECT') { |
|
5068 |
+ handler = handlers.select; |
|
5069 |
+ } else if (tag === 'TEXTAREA') { |
|
5070 |
+ handler = handlers.text; |
|
5071 |
+ } else { |
|
5072 |
+ 'development' !== 'production' && warn('v-model does not support element type: ' + tag, this.vm); |
|
5073 |
+ return; |
|
5074 |
+ } |
|
5075 |
+ el.__v_model = this; |
|
5076 |
+ handler.bind.call(this); |
|
5077 |
+ this.update = handler.update; |
|
5078 |
+ this._unbind = handler.unbind; |
|
5079 |
+ }, |
|
5080 |
+ |
|
5081 |
+ /** |
|
5082 |
+ * Check read/write filter stats. |
|
5083 |
+ */ |
|
5084 |
+ |
|
5085 |
+ checkFilters: function checkFilters() { |
|
5086 |
+ var filters = this.filters; |
|
5087 |
+ if (!filters) return; |
|
5088 |
+ var i = filters.length; |
|
5089 |
+ while (i--) { |
|
5090 |
+ var filter = resolveAsset(this.vm.$options, 'filters', filters[i].name); |
|
5091 |
+ if (typeof filter === 'function' || filter.read) { |
|
5092 |
+ this.hasRead = true; |
|
5093 |
+ } |
|
5094 |
+ if (filter.write) { |
|
5095 |
+ this.hasWrite = true; |
|
5096 |
+ } |
|
5097 |
+ } |
|
5098 |
+ }, |
|
5099 |
+ |
|
5100 |
+ unbind: function unbind() { |
|
5101 |
+ this.el.__v_model = null; |
|
5102 |
+ this._unbind && this._unbind(); |
|
5103 |
+ } |
|
5104 |
+ }; |
|
5105 |
+ |
|
5106 |
+ // keyCode aliases |
|
5107 |
+ var keyCodes = { |
|
5108 |
+ esc: 27, |
|
5109 |
+ tab: 9, |
|
5110 |
+ enter: 13, |
|
5111 |
+ space: 32, |
|
5112 |
+ 'delete': [8, 46], |
|
5113 |
+ up: 38, |
|
5114 |
+ left: 37, |
|
5115 |
+ right: 39, |
|
5116 |
+ down: 40 |
|
5117 |
+ }; |
|
5118 |
+ |
|
5119 |
+ function keyFilter(handler, keys) { |
|
5120 |
+ var codes = keys.map(function (key) { |
|
5121 |
+ var charCode = key.charCodeAt(0); |
|
5122 |
+ if (charCode > 47 && charCode < 58) { |
|
5123 |
+ return parseInt(key, 10); |
|
5124 |
+ } |
|
5125 |
+ if (key.length === 1) { |
|
5126 |
+ charCode = key.toUpperCase().charCodeAt(0); |
|
5127 |
+ if (charCode > 64 && charCode < 91) { |
|
5128 |
+ return charCode; |
|
5129 |
+ } |
|
5130 |
+ } |
|
5131 |
+ return keyCodes[key]; |
|
5132 |
+ }); |
|
5133 |
+ codes = [].concat.apply([], codes); |
|
5134 |
+ return function keyHandler(e) { |
|
5135 |
+ if (codes.indexOf(e.keyCode) > -1) { |
|
5136 |
+ return handler.call(this, e); |
|
5137 |
+ } |
|
5138 |
+ }; |
|
5139 |
+ } |
|
5140 |
+ |
|
5141 |
+ function stopFilter(handler) { |
|
5142 |
+ return function stopHandler(e) { |
|
5143 |
+ e.stopPropagation(); |
|
5144 |
+ return handler.call(this, e); |
|
5145 |
+ }; |
|
5146 |
+ } |
|
5147 |
+ |
|
5148 |
+ function preventFilter(handler) { |
|
5149 |
+ return function preventHandler(e) { |
|
5150 |
+ e.preventDefault(); |
|
5151 |
+ return handler.call(this, e); |
|
5152 |
+ }; |
|
5153 |
+ } |
|
5154 |
+ |
|
5155 |
+ function selfFilter(handler) { |
|
5156 |
+ return function selfHandler(e) { |
|
5157 |
+ if (e.target === e.currentTarget) { |
|
5158 |
+ return handler.call(this, e); |
|
5159 |
+ } |
|
5160 |
+ }; |
|
5161 |
+ } |
|
5162 |
+ |
|
5163 |
+ var on$1 = { |
|
5164 |
+ |
|
5165 |
+ priority: ON, |
|
5166 |
+ acceptStatement: true, |
|
5167 |
+ keyCodes: keyCodes, |
|
5168 |
+ |
|
5169 |
+ bind: function bind() { |
|
5170 |
+ // deal with iframes |
|
5171 |
+ if (this.el.tagName === 'IFRAME' && this.arg !== 'load') { |
|
5172 |
+ var self = this; |
|
5173 |
+ this.iframeBind = function () { |
|
5174 |
+ on(self.el.contentWindow, self.arg, self.handler, self.modifiers.capture); |
|
5175 |
+ }; |
|
5176 |
+ this.on('load', this.iframeBind); |
|
5177 |
+ } |
|
5178 |
+ }, |
|
5179 |
+ |
|
5180 |
+ update: function update(handler) { |
|
5181 |
+ // stub a noop for v-on with no value, |
|
5182 |
+ // e.g. @mousedown.prevent |
|
5183 |
+ if (!this.descriptor.raw) { |
|
5184 |
+ handler = function () {}; |
|
5185 |
+ } |
|
5186 |
+ |
|
5187 |
+ if (typeof handler !== 'function') { |
|
5188 |
+ 'development' !== 'production' && warn('v-on:' + this.arg + '="' + this.expression + '" expects a function value, ' + 'got ' + handler, this.vm); |
|
5189 |
+ return; |
|
5190 |
+ } |
|
5191 |
+ |
|
5192 |
+ // apply modifiers |
|
5193 |
+ if (this.modifiers.stop) { |
|
5194 |
+ handler = stopFilter(handler); |
|
5195 |
+ } |
|
5196 |
+ if (this.modifiers.prevent) { |
|
5197 |
+ handler = preventFilter(handler); |
|
5198 |
+ } |
|
5199 |
+ if (this.modifiers.self) { |
|
5200 |
+ handler = selfFilter(handler); |
|
5201 |
+ } |
|
5202 |
+ // key filter |
|
5203 |
+ var keys = Object.keys(this.modifiers).filter(function (key) { |
|
5204 |
+ return key !== 'stop' && key !== 'prevent' && key !== 'self' && key !== 'capture'; |
|
5205 |
+ }); |
|
5206 |
+ if (keys.length) { |
|
5207 |
+ handler = keyFilter(handler, keys); |
|
5208 |
+ } |
|
5209 |
+ |
|
5210 |
+ this.reset(); |
|
5211 |
+ this.handler = handler; |
|
5212 |
+ |
|
5213 |
+ if (this.iframeBind) { |
|
5214 |
+ this.iframeBind(); |
|
5215 |
+ } else { |
|
5216 |
+ on(this.el, this.arg, this.handler, this.modifiers.capture); |
|
5217 |
+ } |
|
5218 |
+ }, |
|
5219 |
+ |
|
5220 |
+ reset: function reset() { |
|
5221 |
+ var el = this.iframeBind ? this.el.contentWindow : this.el; |
|
5222 |
+ if (this.handler) { |
|
5223 |
+ off(el, this.arg, this.handler); |
|
5224 |
+ } |
|
5225 |
+ }, |
|
5226 |
+ |
|
5227 |
+ unbind: function unbind() { |
|
5228 |
+ this.reset(); |
|
5229 |
+ } |
|
5230 |
+ }; |
|
5231 |
+ |
|
5232 |
+ var prefixes = ['-webkit-', '-moz-', '-ms-']; |
|
5233 |
+ var camelPrefixes = ['Webkit', 'Moz', 'ms']; |
|
5234 |
+ var importantRE = /!important;?$/; |
|
5235 |
+ var propCache = Object.create(null); |
|
5236 |
+ |
|
5237 |
+ var testEl = null; |
|
5238 |
+ |
|
5239 |
+ var style = { |
|
5240 |
+ |
|
5241 |
+ deep: true, |
|
5242 |
+ |
|
5243 |
+ update: function update(value) { |
|
5244 |
+ if (typeof value === 'string') { |
|
5245 |
+ this.el.style.cssText = value; |
|
5246 |
+ } else if (isArray(value)) { |
|
5247 |
+ this.handleObject(value.reduce(extend, {})); |
|
5248 |
+ } else { |
|
5249 |
+ this.handleObject(value || {}); |
|
5250 |
+ } |
|
5251 |
+ }, |
|
5252 |
+ |
|
5253 |
+ handleObject: function handleObject(value) { |
|
5254 |
+ // cache object styles so that only changed props |
|
5255 |
+ // are actually updated. |
|
5256 |
+ var cache = this.cache || (this.cache = {}); |
|
5257 |
+ var name, val; |
|
5258 |
+ for (name in cache) { |
|
5259 |
+ if (!(name in value)) { |
|
5260 |
+ this.handleSingle(name, null); |
|
5261 |
+ delete cache[name]; |
|
5262 |
+ } |
|
5263 |
+ } |
|
5264 |
+ for (name in value) { |
|
5265 |
+ val = value[name]; |
|
5266 |
+ if (val !== cache[name]) { |
|
5267 |
+ cache[name] = val; |
|
5268 |
+ this.handleSingle(name, val); |
|
5269 |
+ } |
|
5270 |
+ } |
|
5271 |
+ }, |
|
5272 |
+ |
|
5273 |
+ handleSingle: function handleSingle(prop, value) { |
|
5274 |
+ prop = normalize(prop); |
|
5275 |
+ if (!prop) return; // unsupported prop |
|
5276 |
+ // cast possible numbers/booleans into strings |
|
5277 |
+ if (value != null) value += ''; |
|
5278 |
+ if (value) { |
|
5279 |
+ var isImportant = importantRE.test(value) ? 'important' : ''; |
|
5280 |
+ if (isImportant) { |
|
5281 |
+ /* istanbul ignore if */ |
|
5282 |
+ if ('development' !== 'production') { |
|
5283 |
+ warn('It\'s probably a bad idea to use !important with inline rules. ' + 'This feature will be deprecated in a future version of Vue.'); |
|
5284 |
+ } |
|
5285 |
+ value = value.replace(importantRE, '').trim(); |
|
5286 |
+ this.el.style.setProperty(prop.kebab, value, isImportant); |
|
5287 |
+ } else { |
|
5288 |
+ this.el.style[prop.camel] = value; |
|
5289 |
+ } |
|
5290 |
+ } else { |
|
5291 |
+ this.el.style[prop.camel] = ''; |
|
5292 |
+ } |
|
5293 |
+ } |
|
5294 |
+ |
|
5295 |
+ }; |
|
5296 |
+ |
|
5297 |
+ /** |
|
5298 |
+ * Normalize a CSS property name. |
|
5299 |
+ * - cache result |
|
5300 |
+ * - auto prefix |
|
5301 |
+ * - camelCase -> dash-case |
|
5302 |
+ * |
|
5303 |
+ * @param {String} prop |
|
5304 |
+ * @return {String} |
|
5305 |
+ */ |
|
5306 |
+ |
|
5307 |
+ function normalize(prop) { |
|
5308 |
+ if (propCache[prop]) { |
|
5309 |
+ return propCache[prop]; |
|
5310 |
+ } |
|
5311 |
+ var res = prefix(prop); |
|
5312 |
+ propCache[prop] = propCache[res] = res; |
|
5313 |
+ return res; |
|
5314 |
+ } |
|
5315 |
+ |
|
5316 |
+ /** |
|
5317 |
+ * Auto detect the appropriate prefix for a CSS property. |
|
5318 |
+ * https://gist.github.com/paulirish/523692 |
|
5319 |
+ * |
|
5320 |
+ * @param {String} prop |
|
5321 |
+ * @return {String} |
|
5322 |
+ */ |
|
5323 |
+ |
|
5324 |
+ function prefix(prop) { |
|
5325 |
+ prop = hyphenate(prop); |
|
5326 |
+ var camel = camelize(prop); |
|
5327 |
+ var upper = camel.charAt(0).toUpperCase() + camel.slice(1); |
|
5328 |
+ if (!testEl) { |
|
5329 |
+ testEl = document.createElement('div'); |
|
5330 |
+ } |
|
5331 |
+ var i = prefixes.length; |
|
5332 |
+ var prefixed; |
|
5333 |
+ if (camel !== 'filter' && camel in testEl.style) { |
|
5334 |
+ return { |
|
5335 |
+ kebab: prop, |
|
5336 |
+ camel: camel |
|
5337 |
+ }; |
|
5338 |
+ } |
|
5339 |
+ while (i--) { |
|
5340 |
+ prefixed = camelPrefixes[i] + upper; |
|
5341 |
+ if (prefixed in testEl.style) { |
|
5342 |
+ return { |
|
5343 |
+ kebab: prefixes[i] + prop, |
|
5344 |
+ camel: prefixed |
|
5345 |
+ }; |
|
5346 |
+ } |
|
5347 |
+ } |
|
5348 |
+ } |
|
5349 |
+ |
|
5350 |
+ // xlink |
|
5351 |
+ var xlinkNS = 'http://www.w3.org/1999/xlink'; |
|
5352 |
+ var xlinkRE = /^xlink:/; |
|
5353 |
+ |
|
5354 |
+ // check for attributes that prohibit interpolations |
|
5355 |
+ var disallowedInterpAttrRE = /^v-|^:|^@|^(?:is|transition|transition-mode|debounce|track-by|stagger|enter-stagger|leave-stagger)$/; |
|
5356 |
+ // these attributes should also set their corresponding properties |
|
5357 |
+ // because they only affect the initial state of the element |
|
5358 |
+ var attrWithPropsRE = /^(?:value|checked|selected|muted)$/; |
|
5359 |
+ // these attributes expect enumrated values of "true" or "false" |
|
5360 |
+ // but are not boolean attributes |
|
5361 |
+ var enumeratedAttrRE = /^(?:draggable|contenteditable|spellcheck)$/; |
|
5362 |
+ |
|
5363 |
+ // these attributes should set a hidden property for |
|
5364 |
+ // binding v-model to object values |
|
5365 |
+ var modelProps = { |
|
5366 |
+ value: '_value', |
|
5367 |
+ 'true-value': '_trueValue', |
|
5368 |
+ 'false-value': '_falseValue' |
|
5369 |
+ }; |
|
5370 |
+ |
|
5371 |
+ var bind$1 = { |
|
5372 |
+ |
|
5373 |
+ priority: BIND, |
|
5374 |
+ |
|
5375 |
+ bind: function bind() { |
|
5376 |
+ var attr = this.arg; |
|
5377 |
+ var tag = this.el.tagName; |
|
5378 |
+ // should be deep watch on object mode |
|
5379 |
+ if (!attr) { |
|
5380 |
+ this.deep = true; |
|
5381 |
+ } |
|
5382 |
+ // handle interpolation bindings |
|
5383 |
+ var descriptor = this.descriptor; |
|
5384 |
+ var tokens = descriptor.interp; |
|
5385 |
+ if (tokens) { |
|
5386 |
+ // handle interpolations with one-time tokens |
|
5387 |
+ if (descriptor.hasOneTime) { |
|
5388 |
+ this.expression = tokensToExp(tokens, this._scope || this.vm); |
|
5389 |
+ } |
|
5390 |
+ |
|
5391 |
+ // only allow binding on native attributes |
|
5392 |
+ if (disallowedInterpAttrRE.test(attr) || attr === 'name' && (tag === 'PARTIAL' || tag === 'SLOT')) { |
|
5393 |
+ 'development' !== 'production' && warn(attr + '="' + descriptor.raw + '": ' + 'attribute interpolation is not allowed in Vue.js ' + 'directives and special attributes.', this.vm); |
|
5394 |
+ this.el.removeAttribute(attr); |
|
5395 |
+ this.invalid = true; |
|
5396 |
+ } |
|
5397 |
+ |
|
5398 |
+ /* istanbul ignore if */ |
|
5399 |
+ if ('development' !== 'production') { |
|
5400 |
+ var raw = attr + '="' + descriptor.raw + '": '; |
|
5401 |
+ // warn src |
|
5402 |
+ if (attr === 'src') { |
|
5403 |
+ warn(raw + 'interpolation in "src" attribute will cause ' + 'a 404 request. Use v-bind:src instead.', this.vm); |
|
5404 |
+ } |
|
5405 |
+ |
|
5406 |
+ // warn style |
|
5407 |
+ if (attr === 'style') { |
|
5408 |
+ warn(raw + 'interpolation in "style" attribute will cause ' + 'the attribute to be discarded in Internet Explorer. ' + 'Use v-bind:style instead.', this.vm); |
|
5409 |
+ } |
|
5410 |
+ } |
|
5411 |
+ } |
|
5412 |
+ }, |
|
5413 |
+ |
|
5414 |
+ update: function update(value) { |
|
5415 |
+ if (this.invalid) { |
|
5416 |
+ return; |
|
5417 |
+ } |
|
5418 |
+ var attr = this.arg; |
|
5419 |
+ if (this.arg) { |
|
5420 |
+ this.handleSingle(attr, value); |
|
5421 |
+ } else { |
|
5422 |
+ this.handleObject(value || {}); |
|
5423 |
+ } |
|
5424 |
+ }, |
|
5425 |
+ |
|
5426 |
+ // share object handler with v-bind:class |
|
5427 |
+ handleObject: style.handleObject, |
|
5428 |
+ |
|
5429 |
+ handleSingle: function handleSingle(attr, value) { |
|
5430 |
+ var el = this.el; |
|
5431 |
+ var interp = this.descriptor.interp; |
|
5432 |
+ if (this.modifiers.camel) { |
|
5433 |
+ attr = camelize(attr); |
|
5434 |
+ } |
|
5435 |
+ if (!interp && attrWithPropsRE.test(attr) && attr in el) { |
|
5436 |
+ var attrValue = attr === 'value' ? value == null // IE9 will set input.value to "null" for null... |
|
5437 |
+ ? '' : value : value; |
|
5438 |
+ |
|
5439 |
+ if (el[attr] !== attrValue) { |
|
5440 |
+ el[attr] = attrValue; |
|
5441 |
+ } |
|
5442 |
+ } |
|
5443 |
+ // set model props |
|
5444 |
+ var modelProp = modelProps[attr]; |
|
5445 |
+ if (!interp && modelProp) { |
|
5446 |
+ el[modelProp] = value; |
|
5447 |
+ // update v-model if present |
|
5448 |
+ var model = el.__v_model; |
|
5449 |
+ if (model) { |
|
5450 |
+ model.listener(); |
|
5451 |
+ } |
|
5452 |
+ } |
|
5453 |
+ // do not set value attribute for textarea |
|
5454 |
+ if (attr === 'value' && el.tagName === 'TEXTAREA') { |
|
5455 |
+ el.removeAttribute(attr); |
|
5456 |
+ return; |
|
5457 |
+ } |
|
5458 |
+ // update attribute |
|
5459 |
+ if (enumeratedAttrRE.test(attr)) { |
|
5460 |
+ el.setAttribute(attr, value ? 'true' : 'false'); |
|
5461 |
+ } else if (value != null && value !== false) { |
|
5462 |
+ if (attr === 'class') { |
|
5463 |
+ // handle edge case #1960: |
|
5464 |
+ // class interpolation should not overwrite Vue transition class |
|
5465 |
+ if (el.__v_trans) { |
|
5466 |
+ value += ' ' + el.__v_trans.id + '-transition'; |
|
5467 |
+ } |
|
5468 |
+ setClass(el, value); |
|
5469 |
+ } else if (xlinkRE.test(attr)) { |
|
5470 |
+ el.setAttributeNS(xlinkNS, attr, value === true ? '' : value); |
|
5471 |
+ } else { |
|
5472 |
+ el.setAttribute(attr, value === true ? '' : value); |
|
5473 |
+ } |
|
5474 |
+ } else { |
|
5475 |
+ el.removeAttribute(attr); |
|
5476 |
+ } |
|
5477 |
+ } |
|
5478 |
+ }; |
|
5479 |
+ |
|
5480 |
+ var el = { |
|
5481 |
+ |
|
5482 |
+ priority: EL, |
|
5483 |
+ |
|
5484 |
+ bind: function bind() { |
|
5485 |
+ /* istanbul ignore if */ |
|
5486 |
+ if (!this.arg) { |
|
5487 |
+ return; |
|
5488 |
+ } |
|
5489 |
+ var id = this.id = camelize(this.arg); |
|
5490 |
+ var refs = (this._scope || this.vm).$els; |
|
5491 |
+ if (hasOwn(refs, id)) { |
|
5492 |
+ refs[id] = this.el; |
|
5493 |
+ } else { |
|
5494 |
+ defineReactive(refs, id, this.el); |
|
5495 |
+ } |
|
5496 |
+ }, |
|
5497 |
+ |
|
5498 |
+ unbind: function unbind() { |
|
5499 |
+ var refs = (this._scope || this.vm).$els; |
|
5500 |
+ if (refs[this.id] === this.el) { |
|
5501 |
+ refs[this.id] = null; |
|
5502 |
+ } |
|
5503 |
+ } |
|
5504 |
+ }; |
|
5505 |
+ |
|
5506 |
+ var ref = { |
|
5507 |
+ bind: function bind() { |
|
5508 |
+ 'development' !== 'production' && warn('v-ref:' + this.arg + ' must be used on a child ' + 'component. Found on <' + this.el.tagName.toLowerCase() + '>.', this.vm); |
|
5509 |
+ } |
|
5510 |
+ }; |
|
5511 |
+ |
|
5512 |
+ var cloak = { |
|
5513 |
+ bind: function bind() { |
|
5514 |
+ var el = this.el; |
|
5515 |
+ this.vm.$once('pre-hook:compiled', function () { |
|
5516 |
+ el.removeAttribute('v-cloak'); |
|
5517 |
+ }); |
|
5518 |
+ } |
|
5519 |
+ }; |
|
5520 |
+ |
|
5521 |
+ // must export plain object |
|
5522 |
+ var directives = { |
|
5523 |
+ text: text$1, |
|
5524 |
+ html: html, |
|
5525 |
+ 'for': vFor, |
|
5526 |
+ 'if': vIf, |
|
5527 |
+ show: show, |
|
5528 |
+ model: model, |
|
5529 |
+ on: on$1, |
|
5530 |
+ bind: bind$1, |
|
5531 |
+ el: el, |
|
5532 |
+ ref: ref, |
|
5533 |
+ cloak: cloak |
|
5534 |
+ }; |
|
5535 |
+ |
|
5536 |
+ var vClass = { |
|
5537 |
+ |
|
5538 |
+ deep: true, |
|
5539 |
+ |
|
5540 |
+ update: function update(value) { |
|
5541 |
+ if (!value) { |
|
5542 |
+ this.cleanup(); |
|
5543 |
+ } else if (typeof value === 'string') { |
|
5544 |
+ this.setClass(value.trim().split(/\s+/)); |
|
5545 |
+ } else { |
|
5546 |
+ this.setClass(normalize$1(value)); |
|
5547 |
+ } |
|
5548 |
+ }, |
|
5549 |
+ |
|
5550 |
+ setClass: function setClass(value) { |
|
5551 |
+ this.cleanup(value); |
|
5552 |
+ for (var i = 0, l = value.length; i < l; i++) { |
|
5553 |
+ var val = value[i]; |
|
5554 |
+ if (val) { |
|
5555 |
+ apply(this.el, val, addClass); |
|
5556 |
+ } |
|
5557 |
+ } |
|
5558 |
+ this.prevKeys = value; |
|
5559 |
+ }, |
|
5560 |
+ |
|
5561 |
+ cleanup: function cleanup(value) { |
|
5562 |
+ var prevKeys = this.prevKeys; |
|
5563 |
+ if (!prevKeys) return; |
|
5564 |
+ var i = prevKeys.length; |
|
5565 |
+ while (i--) { |
|
5566 |
+ var key = prevKeys[i]; |
|
5567 |
+ if (!value || value.indexOf(key) < 0) { |
|
5568 |
+ apply(this.el, key, removeClass); |
|
5569 |
+ } |
|
5570 |
+ } |
|
5571 |
+ } |
|
5572 |
+ }; |
|
5573 |
+ |
|
5574 |
+ /** |
|
5575 |
+ * Normalize objects and arrays (potentially containing objects) |
|
5576 |
+ * into array of strings. |
|
5577 |
+ * |
|
5578 |
+ * @param {Object|Array<String|Object>} value |
|
5579 |
+ * @return {Array<String>} |
|
5580 |
+ */ |
|
5581 |
+ |
|
5582 |
+ function normalize$1(value) { |
|
5583 |
+ var res = []; |
|
5584 |
+ if (isArray(value)) { |
|
5585 |
+ for (var i = 0, l = value.length; i < l; i++) { |
|
5586 |
+ var _key = value[i]; |
|
5587 |
+ if (_key) { |
|
5588 |
+ if (typeof _key === 'string') { |
|
5589 |
+ res.push(_key); |
|
5590 |
+ } else { |
|
5591 |
+ for (var k in _key) { |
|
5592 |
+ if (_key[k]) res.push(k); |
|
5593 |
+ } |
|
5594 |
+ } |
|
5595 |
+ } |
|
5596 |
+ } |
|
5597 |
+ } else if (isObject(value)) { |
|
5598 |
+ for (var key in value) { |
|
5599 |
+ if (value[key]) res.push(key); |
|
5600 |
+ } |
|
5601 |
+ } |
|
5602 |
+ return res; |
|
5603 |
+ } |
|
5604 |
+ |
|
5605 |
+ /** |
|
5606 |
+ * Add or remove a class/classes on an element |
|
5607 |
+ * |
|
5608 |
+ * @param {Element} el |
|
5609 |
+ * @param {String} key The class name. This may or may not |
|
5610 |
+ * contain a space character, in such a |
|
5611 |
+ * case we'll deal with multiple class |
|
5612 |
+ * names at once. |
|
5613 |
+ * @param {Function} fn |
|
5614 |
+ */ |
|
5615 |
+ |
|
5616 |
+ function apply(el, key, fn) { |
|
5617 |
+ key = key.trim(); |
|
5618 |
+ if (key.indexOf(' ') === -1) { |
|
5619 |
+ fn(el, key); |
|
5620 |
+ return; |
|
5621 |
+ } |
|
5622 |
+ // The key contains one or more space characters. |
|
5623 |
+ // Since a class name doesn't accept such characters, we |
|
5624 |
+ // treat it as multiple classes. |
|
5625 |
+ var keys = key.split(/\s+/); |
|
5626 |
+ for (var i = 0, l = keys.length; i < l; i++) { |
|
5627 |
+ fn(el, keys[i]); |
|
5628 |
+ } |
|
5629 |
+ } |
|
5630 |
+ |
|
5631 |
+ var component = { |
|
5632 |
+ |
|
5633 |
+ priority: COMPONENT, |
|
5634 |
+ |
|
5635 |
+ params: ['keep-alive', 'transition-mode', 'inline-template'], |
|
5636 |
+ |
|
5637 |
+ /** |
|
5638 |
+ * Setup. Two possible usages: |
|
5639 |
+ * |
|
5640 |
+ * - static: |
|
5641 |
+ * <comp> or <div v-component="comp"> |
|
5642 |
+ * |
|
5643 |
+ * - dynamic: |
|
5644 |
+ * <component :is="view"> |
|
5645 |
+ */ |
|
5646 |
+ |
|
5647 |
+ bind: function bind() { |
|
5648 |
+ if (!this.el.__vue__) { |
|
5649 |
+ // keep-alive cache |
|
5650 |
+ this.keepAlive = this.params.keepAlive; |
|
5651 |
+ if (this.keepAlive) { |
|
5652 |
+ this.cache = {}; |
|
5653 |
+ } |
|
5654 |
+ // check inline-template |
|
5655 |
+ if (this.params.inlineTemplate) { |
|
5656 |
+ // extract inline template as a DocumentFragment |
|
5657 |
+ this.inlineTemplate = extractContent(this.el, true); |
|
5658 |
+ } |
|
5659 |
+ // component resolution related state |
|
5660 |
+ this.pendingComponentCb = this.Component = null; |
|
5661 |
+ // transition related state |
|
5662 |
+ this.pendingRemovals = 0; |
|
5663 |
+ this.pendingRemovalCb = null; |
|
5664 |
+ // create a ref anchor |
|
5665 |
+ this.anchor = createAnchor('v-component'); |
|
5666 |
+ replace(this.el, this.anchor); |
|
5667 |
+ // remove is attribute. |
|
5668 |
+ // this is removed during compilation, but because compilation is |
|
5669 |
+ // cached, when the component is used elsewhere this attribute |
|
5670 |
+ // will remain at link time. |
|
5671 |
+ this.el.removeAttribute('is'); |
|
5672 |
+ this.el.removeAttribute(':is'); |
|
5673 |
+ // remove ref, same as above |
|
5674 |
+ if (this.descriptor.ref) { |
|
5675 |
+ this.el.removeAttribute('v-ref:' + hyphenate(this.descriptor.ref)); |
|
5676 |
+ } |
|
5677 |
+ // if static, build right now. |
|
5678 |
+ if (this.literal) { |
|
5679 |
+ this.setComponent(this.expression); |
|
5680 |
+ } |
|
5681 |
+ } else { |
|
5682 |
+ 'development' !== 'production' && warn('cannot mount component "' + this.expression + '" ' + 'on already mounted element: ' + this.el); |
|
5683 |
+ } |
|
5684 |
+ }, |
|
5685 |
+ |
|
5686 |
+ /** |
|
5687 |
+ * Public update, called by the watcher in the dynamic |
|
5688 |
+ * literal scenario, e.g. <component :is="view"> |
|
5689 |
+ */ |
|
5690 |
+ |
|
5691 |
+ update: function update(value) { |
|
5692 |
+ if (!this.literal) { |
|
5693 |
+ this.setComponent(value); |
|
5694 |
+ } |
|
5695 |
+ }, |
|
5696 |
+ |
|
5697 |
+ /** |
|
5698 |
+ * Switch dynamic components. May resolve the component |
|
5699 |
+ * asynchronously, and perform transition based on |
|
5700 |
+ * specified transition mode. Accepts a few additional |
|
5701 |
+ * arguments specifically for vue-router. |
|
5702 |
+ * |
|
5703 |
+ * The callback is called when the full transition is |
|
5704 |
+ * finished. |
|
5705 |
+ * |
|
5706 |
+ * @param {String} value |
|
5707 |
+ * @param {Function} [cb] |
|
5708 |
+ */ |
|
5709 |
+ |
|
5710 |
+ setComponent: function setComponent(value, cb) { |
|
5711 |
+ this.invalidatePending(); |
|
5712 |
+ if (!value) { |
|
5713 |
+ // just remove current |
|
5714 |
+ this.unbuild(true); |
|
5715 |
+ this.remove(this.childVM, cb); |
|
5716 |
+ this.childVM = null; |
|
5717 |
+ } else { |
|
5718 |
+ var self = this; |
|
5719 |
+ this.resolveComponent(value, function () { |
|
5720 |
+ self.mountComponent(cb); |
|
5721 |
+ }); |
|
5722 |
+ } |
|
5723 |
+ }, |
|
5724 |
+ |
|
5725 |
+ /** |
|
5726 |
+ * Resolve the component constructor to use when creating |
|
5727 |
+ * the child vm. |
|
5728 |
+ * |
|
5729 |
+ * @param {String|Function} value |
|
5730 |
+ * @param {Function} cb |
|
5731 |
+ */ |
|
5732 |
+ |
|
5733 |
+ resolveComponent: function resolveComponent(value, cb) { |
|
5734 |
+ var self = this; |
|
5735 |
+ this.pendingComponentCb = cancellable(function (Component) { |
|
5736 |
+ self.ComponentName = Component.options.name || (typeof value === 'string' ? value : null); |
|
5737 |
+ self.Component = Component; |
|
5738 |
+ cb(); |
|
5739 |
+ }); |
|
5740 |
+ this.vm._resolveComponent(value, this.pendingComponentCb); |
|
5741 |
+ }, |
|
5742 |
+ |
|
5743 |
+ /** |
|
5744 |
+ * Create a new instance using the current constructor and |
|
5745 |
+ * replace the existing instance. This method doesn't care |
|
5746 |
+ * whether the new component and the old one are actually |
|
5747 |
+ * the same. |
|
5748 |
+ * |
|
5749 |
+ * @param {Function} [cb] |
|
5750 |
+ */ |
|
5751 |
+ |
|
5752 |
+ mountComponent: function mountComponent(cb) { |
|
5753 |
+ // actual mount |
|
5754 |
+ this.unbuild(true); |
|
5755 |
+ var self = this; |
|
5756 |
+ var activateHooks = this.Component.options.activate; |
|
5757 |
+ var cached = this.getCached(); |
|
5758 |
+ var newComponent = this.build(); |
|
5759 |
+ if (activateHooks && !cached) { |
|
5760 |
+ this.waitingFor = newComponent; |
|
5761 |
+ callActivateHooks(activateHooks, newComponent, function () { |
|
5762 |
+ if (self.waitingFor !== newComponent) { |
|
5763 |
+ return; |
|
5764 |
+ } |
|
5765 |
+ self.waitingFor = null; |
|
5766 |
+ self.transition(newComponent, cb); |
|
5767 |
+ }); |
|
5768 |
+ } else { |
|
5769 |
+ // update ref for kept-alive component |
|
5770 |
+ if (cached) { |
|
5771 |
+ newComponent._updateRef(); |
|
5772 |
+ } |
|
5773 |
+ this.transition(newComponent, cb); |
|
5774 |
+ } |
|
5775 |
+ }, |
|
5776 |
+ |
|
5777 |
+ /** |
|
5778 |
+ * When the component changes or unbinds before an async |
|
5779 |
+ * constructor is resolved, we need to invalidate its |
|
5780 |
+ * pending callback. |
|
5781 |
+ */ |
|
5782 |
+ |
|
5783 |
+ invalidatePending: function invalidatePending() { |
|
5784 |
+ if (this.pendingComponentCb) { |
|
5785 |
+ this.pendingComponentCb.cancel(); |
|
5786 |
+ this.pendingComponentCb = null; |
|
5787 |
+ } |
|
5788 |
+ }, |
|
5789 |
+ |
|
5790 |
+ /** |
|
5791 |
+ * Instantiate/insert a new child vm. |
|
5792 |
+ * If keep alive and has cached instance, insert that |
|
5793 |
+ * instance; otherwise build a new one and cache it. |
|
5794 |
+ * |
|
5795 |
+ * @param {Object} [extraOptions] |
|
5796 |
+ * @return {Vue} - the created instance |
|
5797 |
+ */ |
|
5798 |
+ |
|
5799 |
+ build: function build(extraOptions) { |
|
5800 |
+ var cached = this.getCached(); |
|
5801 |
+ if (cached) { |
|
5802 |
+ return cached; |
|
5803 |
+ } |
|
5804 |
+ if (this.Component) { |
|
5805 |
+ // default options |
|
5806 |
+ var options = { |
|
5807 |
+ name: this.ComponentName, |
|
5808 |
+ el: cloneNode(this.el), |
|
5809 |
+ template: this.inlineTemplate, |
|
5810 |
+ // make sure to add the child with correct parent |
|
5811 |
+ // if this is a transcluded component, its parent |
|
5812 |
+ // should be the transclusion host. |
|
5813 |
+ parent: this._host || this.vm, |
|
5814 |
+ // if no inline-template, then the compiled |
|
5815 |
+ // linker can be cached for better performance. |
|
5816 |
+ _linkerCachable: !this.inlineTemplate, |
|
5817 |
+ _ref: this.descriptor.ref, |
|
5818 |
+ _asComponent: true, |
|
5819 |
+ _isRouterView: this._isRouterView, |
|
5820 |
+ // if this is a transcluded component, context |
|
5821 |
+ // will be the common parent vm of this instance |
|
5822 |
+ // and its host. |
|
5823 |
+ _context: this.vm, |
|
5824 |
+ // if this is inside an inline v-for, the scope |
|
5825 |
+ // will be the intermediate scope created for this |
|
5826 |
+ // repeat fragment. this is used for linking props |
|
5827 |
+ // and container directives. |
|
5828 |
+ _scope: this._scope, |
|
5829 |
+ // pass in the owner fragment of this component. |
|
5830 |
+ // this is necessary so that the fragment can keep |
|
5831 |
+ // track of its contained components in order to |
|
5832 |
+ // call attach/detach hooks for them. |
|
5833 |
+ _frag: this._frag |
|
5834 |
+ }; |
|
5835 |
+ // extra options |
|
5836 |
+ // in 1.0.0 this is used by vue-router only |
|
5837 |
+ /* istanbul ignore if */ |
|
5838 |
+ if (extraOptions) { |
|
5839 |
+ extend(options, extraOptions); |
|
5840 |
+ } |
|
5841 |
+ var child = new this.Component(options); |
|
5842 |
+ if (this.keepAlive) { |
|
5843 |
+ this.cache[this.Component.cid] = child; |
|
5844 |
+ } |
|
5845 |
+ /* istanbul ignore if */ |
|
5846 |
+ if ('development' !== 'production' && this.el.hasAttribute('transition') && child._isFragment) { |
|
5847 |
+ warn('Transitions will not work on a fragment instance. ' + 'Template: ' + child.$options.template, child); |
|
5848 |
+ } |
|
5849 |
+ return child; |
|
5850 |
+ } |
|
5851 |
+ }, |
|
5852 |
+ |
|
5853 |
+ /** |
|
5854 |
+ * Try to get a cached instance of the current component. |
|
5855 |
+ * |
|
5856 |
+ * @return {Vue|undefined} |
|
5857 |
+ */ |
|
5858 |
+ |
|
5859 |
+ getCached: function getCached() { |
|
5860 |
+ return this.keepAlive && this.cache[this.Component.cid]; |
|
5861 |
+ }, |
|
5862 |
+ |
|
5863 |
+ /** |
|
5864 |
+ * Teardown the current child, but defers cleanup so |
|
5865 |
+ * that we can separate the destroy and removal steps. |
|
5866 |
+ * |
|
5867 |
+ * @param {Boolean} defer |
|
5868 |
+ */ |
|
5869 |
+ |
|
5870 |
+ unbuild: function unbuild(defer) { |
|
5871 |
+ if (this.waitingFor) { |
|
5872 |
+ if (!this.keepAlive) { |
|
5873 |
+ this.waitingFor.$destroy(); |
|
5874 |
+ } |
|
5875 |
+ this.waitingFor = null; |
|
5876 |
+ } |
|
5877 |
+ var child = this.childVM; |
|
5878 |
+ if (!child || this.keepAlive) { |
|
5879 |
+ if (child) { |
|
5880 |
+ // remove ref |
|
5881 |
+ child._inactive = true; |
|
5882 |
+ child._updateRef(true); |
|
5883 |
+ } |
|
5884 |
+ return; |
|
5885 |
+ } |
|
5886 |
+ // the sole purpose of `deferCleanup` is so that we can |
|
5887 |
+ // "deactivate" the vm right now and perform DOM removal |
|
5888 |
+ // later. |
|
5889 |
+ child.$destroy(false, defer); |
|
5890 |
+ }, |
|
5891 |
+ |
|
5892 |
+ /** |
|
5893 |
+ * Remove current destroyed child and manually do |
|
5894 |
+ * the cleanup after removal. |
|
5895 |
+ * |
|
5896 |
+ * @param {Function} cb |
|
5897 |
+ */ |
|
5898 |
+ |
|
5899 |
+ remove: function remove(child, cb) { |
|
5900 |
+ var keepAlive = this.keepAlive; |
|
5901 |
+ if (child) { |
|
5902 |
+ // we may have a component switch when a previous |
|
5903 |
+ // component is still being transitioned out. |
|
5904 |
+ // we want to trigger only one lastest insertion cb |
|
5905 |
+ // when the existing transition finishes. (#1119) |
|
5906 |
+ this.pendingRemovals++; |
|
5907 |
+ this.pendingRemovalCb = cb; |
|
5908 |
+ var self = this; |
|
5909 |
+ child.$remove(function () { |
|
5910 |
+ self.pendingRemovals--; |
|
5911 |
+ if (!keepAlive) child._cleanup(); |
|
5912 |
+ if (!self.pendingRemovals && self.pendingRemovalCb) { |
|
5913 |
+ self.pendingRemovalCb(); |
|
5914 |
+ self.pendingRemovalCb = null; |
|
5915 |
+ } |
|
5916 |
+ }); |
|
5917 |
+ } else if (cb) { |
|
5918 |
+ cb(); |
|
5919 |
+ } |
|
5920 |
+ }, |
|
5921 |
+ |
|
5922 |
+ /** |
|
5923 |
+ * Actually swap the components, depending on the |
|
5924 |
+ * transition mode. Defaults to simultaneous. |
|
5925 |
+ * |
|
5926 |
+ * @param {Vue} target |
|
5927 |
+ * @param {Function} [cb] |
|
5928 |
+ */ |
|
5929 |
+ |
|
5930 |
+ transition: function transition(target, cb) { |
|
5931 |
+ var self = this; |
|
5932 |
+ var current = this.childVM; |
|
5933 |
+ // for devtool inspection |
|
5934 |
+ if (current) current._inactive = true; |
|
5935 |
+ target._inactive = false; |
|
5936 |
+ this.childVM = target; |
|
5937 |
+ switch (self.params.transitionMode) { |
|
5938 |
+ case 'in-out': |
|
5939 |
+ target.$before(self.anchor, function () { |
|
5940 |
+ self.remove(current, cb); |
|
5941 |
+ }); |
|
5942 |
+ break; |
|
5943 |
+ case 'out-in': |
|
5944 |
+ self.remove(current, function () { |
|
5945 |
+ target.$before(self.anchor, cb); |
|
5946 |
+ }); |
|
5947 |
+ break; |
|
5948 |
+ default: |
|
5949 |
+ self.remove(current); |
|
5950 |
+ target.$before(self.anchor, cb); |
|
5951 |
+ } |
|
5952 |
+ }, |
|
5953 |
+ |
|
5954 |
+ /** |
|
5955 |
+ * Unbind. |
|
5956 |
+ */ |
|
5957 |
+ |
|
5958 |
+ unbind: function unbind() { |
|
5959 |
+ this.invalidatePending(); |
|
5960 |
+ // Do not defer cleanup when unbinding |
|
5961 |
+ this.unbuild(); |
|
5962 |
+ // destroy all keep-alive cached instances |
|
5963 |
+ if (this.cache) { |
|
5964 |
+ for (var key in this.cache) { |
|
5965 |
+ this.cache[key].$destroy(); |
|
5966 |
+ } |
|
5967 |
+ this.cache = null; |
|
5968 |
+ } |
|
5969 |
+ } |
|
5970 |
+ }; |
|
5971 |
+ |
|
5972 |
+ /** |
|
5973 |
+ * Call activate hooks in order (asynchronous) |
|
5974 |
+ * |
|
5975 |
+ * @param {Array} hooks |
|
5976 |
+ * @param {Vue} vm |
|
5977 |
+ * @param {Function} cb |
|
5978 |
+ */ |
|
5979 |
+ |
|
5980 |
+ function callActivateHooks(hooks, vm, cb) { |
|
5981 |
+ var total = hooks.length; |
|
5982 |
+ var called = 0; |
|
5983 |
+ hooks[0].call(vm, next); |
|
5984 |
+ function next() { |
|
5985 |
+ if (++called >= total) { |
|
5986 |
+ cb(); |
|
5987 |
+ } else { |
|
5988 |
+ hooks[called].call(vm, next); |
|
5989 |
+ } |
|
5990 |
+ } |
|
5991 |
+ } |
|
5992 |
+ |
|
5993 |
+ var propBindingModes = config._propBindingModes; |
|
5994 |
+ var empty = {}; |
|
5995 |
+ |
|
5996 |
+ // regexes |
|
5997 |
+ var identRE$1 = /^[$_a-zA-Z]+[\w$]*$/; |
|
5998 |
+ var settablePathRE = /^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*|\[[^\[\]]+\])*$/; |
|
5999 |
+ |
|
6000 |
+ /** |
|
6001 |
+ * Compile props on a root element and return |
|
6002 |
+ * a props link function. |
|
6003 |
+ * |
|
6004 |
+ * @param {Element|DocumentFragment} el |
|
6005 |
+ * @param {Array} propOptions |
|
6006 |
+ * @param {Vue} vm |
|
6007 |
+ * @return {Function} propsLinkFn |
|
6008 |
+ */ |
|
6009 |
+ |
|
6010 |
+ function compileProps(el, propOptions, vm) { |
|
6011 |
+ var props = []; |
|
6012 |
+ var names = Object.keys(propOptions); |
|
6013 |
+ var i = names.length; |
|
6014 |
+ var options, name, attr, value, path, parsed, prop; |
|
6015 |
+ while (i--) { |
|
6016 |
+ name = names[i]; |
|
6017 |
+ options = propOptions[name] || empty; |
|
6018 |
+ |
|
6019 |
+ if ('development' !== 'production' && name === '$data') { |
|
6020 |
+ warn('Do not use $data as prop.', vm); |
|
6021 |
+ continue; |
|
6022 |
+ } |
|
6023 |
+ |
|
6024 |
+ // props could contain dashes, which will be |
|
6025 |
+ // interpreted as minus calculations by the parser |
|
6026 |
+ // so we need to camelize the path here |
|
6027 |
+ path = camelize(name); |
|
6028 |
+ if (!identRE$1.test(path)) { |
|
6029 |
+ 'development' !== 'production' && warn('Invalid prop key: "' + name + '". Prop keys ' + 'must be valid identifiers.', vm); |
|
6030 |
+ continue; |
|
6031 |
+ } |
|
6032 |
+ |
|
6033 |
+ prop = { |
|
6034 |
+ name: name, |
|
6035 |
+ path: path, |
|
6036 |
+ options: options, |
|
6037 |
+ mode: propBindingModes.ONE_WAY, |
|
6038 |
+ raw: null |
|
6039 |
+ }; |
|
6040 |
+ |
|
6041 |
+ attr = hyphenate(name); |
|
6042 |
+ // first check dynamic version |
|
6043 |
+ if ((value = getBindAttr(el, attr)) === null) { |
|
6044 |
+ if ((value = getBindAttr(el, attr + '.sync')) !== null) { |
|
6045 |
+ prop.mode = propBindingModes.TWO_WAY; |
|
6046 |
+ } else if ((value = getBindAttr(el, attr + '.once')) !== null) { |
|
6047 |
+ prop.mode = propBindingModes.ONE_TIME; |
|
6048 |
+ } |
|
6049 |
+ } |
|
6050 |
+ if (value !== null) { |
|
6051 |
+ // has dynamic binding! |
|
6052 |
+ prop.raw = value; |
|
6053 |
+ parsed = parseDirective(value); |
|
6054 |
+ value = parsed.expression; |
|
6055 |
+ prop.filters = parsed.filters; |
|
6056 |
+ // check binding type |
|
6057 |
+ if (isLiteral(value) && !parsed.filters) { |
|
6058 |
+ // for expressions containing literal numbers and |
|
6059 |
+ // booleans, there's no need to setup a prop binding, |
|
6060 |
+ // so we can optimize them as a one-time set. |
|
6061 |
+ prop.optimizedLiteral = true; |
|
6062 |
+ } else { |
|
6063 |
+ prop.dynamic = true; |
|
6064 |
+ // check non-settable path for two-way bindings |
|
6065 |
+ if ('development' !== 'production' && prop.mode === propBindingModes.TWO_WAY && !settablePathRE.test(value)) { |
|
6066 |
+ prop.mode = propBindingModes.ONE_WAY; |
|
6067 |
+ warn('Cannot bind two-way prop with non-settable ' + 'parent path: ' + value, vm); |
|
6068 |
+ } |
|
6069 |
+ } |
|
6070 |
+ prop.parentPath = value; |
|
6071 |
+ |
|
6072 |
+ // warn required two-way |
|
6073 |
+ if ('development' !== 'production' && options.twoWay && prop.mode !== propBindingModes.TWO_WAY) { |
|
6074 |
+ warn('Prop "' + name + '" expects a two-way binding type.', vm); |
|
6075 |
+ } |
|
6076 |
+ } else if ((value = getAttr(el, attr)) !== null) { |
|
6077 |
+ // has literal binding! |
|
6078 |
+ prop.raw = value; |
|
6079 |
+ } else if ('development' !== 'production') { |
|
6080 |
+ // check possible camelCase prop usage |
|
6081 |
+ var lowerCaseName = path.toLowerCase(); |
|
6082 |
+ value = /[A-Z\-]/.test(name) && (el.getAttribute(lowerCaseName) || el.getAttribute(':' + lowerCaseName) || el.getAttribute('v-bind:' + lowerCaseName) || el.getAttribute(':' + lowerCaseName + '.once') || el.getAttribute('v-bind:' + lowerCaseName + '.once') || el.getAttribute(':' + lowerCaseName + '.sync') || el.getAttribute('v-bind:' + lowerCaseName + '.sync')); |
|
6083 |
+ if (value) { |
|
6084 |
+ warn('Possible usage error for prop `' + lowerCaseName + '` - ' + 'did you mean `' + attr + '`? HTML is case-insensitive, remember to use ' + 'kebab-case for props in templates.', vm); |
|
6085 |
+ } else if (options.required) { |
|
6086 |
+ // warn missing required |
|
6087 |
+ warn('Missing required prop: ' + name, vm); |
|
6088 |
+ } |
|
6089 |
+ } |
|
6090 |
+ // push prop |
|
6091 |
+ props.push(prop); |
|
6092 |
+ } |
|
6093 |
+ return makePropsLinkFn(props); |
|
6094 |
+ } |
|
6095 |
+ |
|
6096 |
+ /** |
|
6097 |
+ * Build a function that applies props to a vm. |
|
6098 |
+ * |
|
6099 |
+ * @param {Array} props |
|
6100 |
+ * @return {Function} propsLinkFn |
|
6101 |
+ */ |
|
6102 |
+ |
|
6103 |
+ function makePropsLinkFn(props) { |
|
6104 |
+ return function propsLinkFn(vm, scope) { |
|
6105 |
+ // store resolved props info |
|
6106 |
+ vm._props = {}; |
|
6107 |
+ var inlineProps = vm.$options.propsData; |
|
6108 |
+ var i = props.length; |
|
6109 |
+ var prop, path, options, value, raw; |
|
6110 |
+ while (i--) { |
|
6111 |
+ prop = props[i]; |
|
6112 |
+ raw = prop.raw; |
|
6113 |
+ path = prop.path; |
|
6114 |
+ options = prop.options; |
|
6115 |
+ vm._props[path] = prop; |
|
6116 |
+ if (inlineProps && hasOwn(inlineProps, path)) { |
|
6117 |
+ initProp(vm, prop, inlineProps[path]); |
|
6118 |
+ }if (raw === null) { |
|
6119 |
+ // initialize absent prop |
|
6120 |
+ initProp(vm, prop, undefined); |
|
6121 |
+ } else if (prop.dynamic) { |
|
6122 |
+ // dynamic prop |
|
6123 |
+ if (prop.mode === propBindingModes.ONE_TIME) { |
|
6124 |
+ // one time binding |
|
6125 |
+ value = (scope || vm._context || vm).$get(prop.parentPath); |
|
6126 |
+ initProp(vm, prop, value); |
|
6127 |
+ } else { |
|
6128 |
+ if (vm._context) { |
|
6129 |
+ // dynamic binding |
|
6130 |
+ vm._bindDir({ |
|
6131 |
+ name: 'prop', |
|
6132 |
+ def: propDef, |
|
6133 |
+ prop: prop |
|
6134 |
+ }, null, null, scope); // el, host, scope |
|
6135 |
+ } else { |
|
6136 |
+ // root instance |
|
6137 |
+ initProp(vm, prop, vm.$get(prop.parentPath)); |
|
6138 |
+ } |
|
6139 |
+ } |
|
6140 |
+ } else if (prop.optimizedLiteral) { |
|
6141 |
+ // optimized literal, cast it and just set once |
|
6142 |
+ var stripped = stripQuotes(raw); |
|
6143 |
+ value = stripped === raw ? toBoolean(toNumber(raw)) : stripped; |
|
6144 |
+ initProp(vm, prop, value); |
|
6145 |
+ } else { |
|
6146 |
+ // string literal, but we need to cater for |
|
6147 |
+ // Boolean props with no value, or with same |
|
6148 |
+ // literal value (e.g. disabled="disabled") |
|
6149 |
+ // see https://github.com/vuejs/vue-loader/issues/182 |
|
6150 |
+ value = options.type === Boolean && (raw === '' || raw === hyphenate(prop.name)) ? true : raw; |
|
6151 |
+ initProp(vm, prop, value); |
|
6152 |
+ } |
|
6153 |
+ } |
|
6154 |
+ }; |
|
6155 |
+ } |
|
6156 |
+ |
|
6157 |
+ /** |
|
6158 |
+ * Process a prop with a rawValue, applying necessary coersions, |
|
6159 |
+ * default values & assertions and call the given callback with |
|
6160 |
+ * processed value. |
|
6161 |
+ * |
|
6162 |
+ * @param {Vue} vm |
|
6163 |
+ * @param {Object} prop |
|
6164 |
+ * @param {*} rawValue |
|
6165 |
+ * @param {Function} fn |
|
6166 |
+ */ |
|
6167 |
+ |
|
6168 |
+ function processPropValue(vm, prop, rawValue, fn) { |
|
6169 |
+ var isSimple = prop.dynamic && isSimplePath(prop.parentPath); |
|
6170 |
+ var value = rawValue; |
|
6171 |
+ if (value === undefined) { |
|
6172 |
+ value = getPropDefaultValue(vm, prop); |
|
6173 |
+ } |
|
6174 |
+ value = coerceProp(prop, value, vm); |
|
6175 |
+ var coerced = value !== rawValue; |
|
6176 |
+ if (!assertProp(prop, value, vm)) { |
|
6177 |
+ value = undefined; |
|
6178 |
+ } |
|
6179 |
+ if (isSimple && !coerced) { |
|
6180 |
+ withoutConversion(function () { |
|
6181 |
+ fn(value); |
|
6182 |
+ }); |
|
6183 |
+ } else { |
|
6184 |
+ fn(value); |
|
6185 |
+ } |
|
6186 |
+ } |
|
6187 |
+ |
|
6188 |
+ /** |
|
6189 |
+ * Set a prop's initial value on a vm and its data object. |
|
6190 |
+ * |
|
6191 |
+ * @param {Vue} vm |
|
6192 |
+ * @param {Object} prop |
|
6193 |
+ * @param {*} value |
|
6194 |
+ */ |
|
6195 |
+ |
|
6196 |
+ function initProp(vm, prop, value) { |
|
6197 |
+ processPropValue(vm, prop, value, function (value) { |
|
6198 |
+ defineReactive(vm, prop.path, value); |
|
6199 |
+ }); |
|
6200 |
+ } |
|
6201 |
+ |
|
6202 |
+ /** |
|
6203 |
+ * Update a prop's value on a vm. |
|
6204 |
+ * |
|
6205 |
+ * @param {Vue} vm |
|
6206 |
+ * @param {Object} prop |
|
6207 |
+ * @param {*} value |
|
6208 |
+ */ |
|
6209 |
+ |
|
6210 |
+ function updateProp(vm, prop, value) { |
|
6211 |
+ processPropValue(vm, prop, value, function (value) { |
|
6212 |
+ vm[prop.path] = value; |
|
6213 |
+ }); |
|
6214 |
+ } |
|
6215 |
+ |
|
6216 |
+ /** |
|
6217 |
+ * Get the default value of a prop. |
|
6218 |
+ * |
|
6219 |
+ * @param {Vue} vm |
|
6220 |
+ * @param {Object} prop |
|
6221 |
+ * @return {*} |
|
6222 |
+ */ |
|
6223 |
+ |
|
6224 |
+ function getPropDefaultValue(vm, prop) { |
|
6225 |
+ // no default, return undefined |
|
6226 |
+ var options = prop.options; |
|
6227 |
+ if (!hasOwn(options, 'default')) { |
|
6228 |
+ // absent boolean value defaults to false |
|
6229 |
+ return options.type === Boolean ? false : undefined; |
|
6230 |
+ } |
|
6231 |
+ var def = options['default']; |
|
6232 |
+ // warn against non-factory defaults for Object & Array |
|
6233 |
+ if (isObject(def)) { |
|
6234 |
+ 'development' !== 'production' && warn('Invalid default value for prop "' + prop.name + '": ' + 'Props with type Object/Array must use a factory function ' + 'to return the default value.', vm); |
|
6235 |
+ } |
|
6236 |
+ // call factory function for non-Function types |
|
6237 |
+ return typeof def === 'function' && options.type !== Function ? def.call(vm) : def; |
|
6238 |
+ } |
|
6239 |
+ |
|
6240 |
+ /** |
|
6241 |
+ * Assert whether a prop is valid. |
|
6242 |
+ * |
|
6243 |
+ * @param {Object} prop |
|
6244 |
+ * @param {*} value |
|
6245 |
+ * @param {Vue} vm |
|
6246 |
+ */ |
|
6247 |
+ |
|
6248 |
+ function assertProp(prop, value, vm) { |
|
6249 |
+ if (!prop.options.required && ( // non-required |
|
6250 |
+ prop.raw === null || // abscent |
|
6251 |
+ value == null) // null or undefined |
|
6252 |
+ ) { |
|
6253 |
+ return true; |
|
6254 |
+ } |
|
6255 |
+ var options = prop.options; |
|
6256 |
+ var type = options.type; |
|
6257 |
+ var valid = !type; |
|
6258 |
+ var expectedTypes = []; |
|
6259 |
+ if (type) { |
|
6260 |
+ if (!isArray(type)) { |
|
6261 |
+ type = [type]; |
|
6262 |
+ } |
|
6263 |
+ for (var i = 0; i < type.length && !valid; i++) { |
|
6264 |
+ var assertedType = assertType(value, type[i]); |
|
6265 |
+ expectedTypes.push(assertedType.expectedType); |
|
6266 |
+ valid = assertedType.valid; |
|
6267 |
+ } |
|
6268 |
+ } |
|
6269 |
+ if (!valid) { |
|
6270 |
+ if ('development' !== 'production') { |
|
6271 |
+ warn('Invalid prop: type check failed for prop "' + prop.name + '".' + ' Expected ' + expectedTypes.map(formatType).join(', ') + ', got ' + formatValue(value) + '.', vm); |
|
6272 |
+ } |
|
6273 |
+ return false; |
|
6274 |
+ } |
|
6275 |
+ var validator = options.validator; |
|
6276 |
+ if (validator) { |
|
6277 |
+ if (!validator(value)) { |
|
6278 |
+ 'development' !== 'production' && warn('Invalid prop: custom validator check failed for prop "' + prop.name + '".', vm); |
|
6279 |
+ return false; |
|
6280 |
+ } |
|
6281 |
+ } |
|
6282 |
+ return true; |
|
6283 |
+ } |
|
6284 |
+ |
|
6285 |
+ /** |
|
6286 |
+ * Force parsing value with coerce option. |
|
6287 |
+ * |
|
6288 |
+ * @param {*} value |
|
6289 |
+ * @param {Object} options |
|
6290 |
+ * @return {*} |
|
6291 |
+ */ |
|
6292 |
+ |
|
6293 |
+ function coerceProp(prop, value, vm) { |
|
6294 |
+ var coerce = prop.options.coerce; |
|
6295 |
+ if (!coerce) { |
|
6296 |
+ return value; |
|
6297 |
+ } |
|
6298 |
+ if (typeof coerce === 'function') { |
|
6299 |
+ return coerce(value); |
|
6300 |
+ } else { |
|
6301 |
+ 'development' !== 'production' && warn('Invalid coerce for prop "' + prop.name + '": expected function, got ' + typeof coerce + '.', vm); |
|
6302 |
+ return value; |
|
6303 |
+ } |
|
6304 |
+ } |
|
6305 |
+ |
|
6306 |
+ /** |
|
6307 |
+ * Assert the type of a value |
|
6308 |
+ * |
|
6309 |
+ * @param {*} value |
|
6310 |
+ * @param {Function} type |
|
6311 |
+ * @return {Object} |
|
6312 |
+ */ |
|
6313 |
+ |
|
6314 |
+ function assertType(value, type) { |
|
6315 |
+ var valid; |
|
6316 |
+ var expectedType; |
|
6317 |
+ if (type === String) { |
|
6318 |
+ expectedType = 'string'; |
|
6319 |
+ valid = typeof value === expectedType; |
|
6320 |
+ } else if (type === Number) { |
|
6321 |
+ expectedType = 'number'; |
|
6322 |
+ valid = typeof value === expectedType; |
|
6323 |
+ } else if (type === Boolean) { |
|
6324 |
+ expectedType = 'boolean'; |
|
6325 |
+ valid = typeof value === expectedType; |
|
6326 |
+ } else if (type === Function) { |
|
6327 |
+ expectedType = 'function'; |
|
6328 |
+ valid = typeof value === expectedType; |
|
6329 |
+ } else if (type === Object) { |
|
6330 |
+ expectedType = 'object'; |
|
6331 |
+ valid = isPlainObject(value); |
|
6332 |
+ } else if (type === Array) { |
|
6333 |
+ expectedType = 'array'; |
|
6334 |
+ valid = isArray(value); |
|
6335 |
+ } else { |
|
6336 |
+ valid = value instanceof type; |
|
6337 |
+ } |
|
6338 |
+ return { |
|
6339 |
+ valid: valid, |
|
6340 |
+ expectedType: expectedType |
|
6341 |
+ }; |
|
6342 |
+ } |
|
6343 |
+ |
|
6344 |
+ /** |
|
6345 |
+ * Format type for output |
|
6346 |
+ * |
|
6347 |
+ * @param {String} type |
|
6348 |
+ * @return {String} |
|
6349 |
+ */ |
|
6350 |
+ |
|
6351 |
+ function formatType(type) { |
|
6352 |
+ return type ? type.charAt(0).toUpperCase() + type.slice(1) : 'custom type'; |
|
6353 |
+ } |
|
6354 |
+ |
|
6355 |
+ /** |
|
6356 |
+ * Format value |
|
6357 |
+ * |
|
6358 |
+ * @param {*} value |
|
6359 |
+ * @return {String} |
|
6360 |
+ */ |
|
6361 |
+ |
|
6362 |
+ function formatValue(val) { |
|
6363 |
+ return Object.prototype.toString.call(val).slice(8, -1); |
|
6364 |
+ } |
|
6365 |
+ |
|
6366 |
+ var bindingModes = config._propBindingModes; |
|
6367 |
+ |
|
6368 |
+ var propDef = { |
|
6369 |
+ |
|
6370 |
+ bind: function bind() { |
|
6371 |
+ var child = this.vm; |
|
6372 |
+ var parent = child._context; |
|
6373 |
+ // passed in from compiler directly |
|
6374 |
+ var prop = this.descriptor.prop; |
|
6375 |
+ var childKey = prop.path; |
|
6376 |
+ var parentKey = prop.parentPath; |
|
6377 |
+ var twoWay = prop.mode === bindingModes.TWO_WAY; |
|
6378 |
+ |
|
6379 |
+ var parentWatcher = this.parentWatcher = new Watcher(parent, parentKey, function (val) { |
|
6380 |
+ updateProp(child, prop, val); |
|
6381 |
+ }, { |
|
6382 |
+ twoWay: twoWay, |
|
6383 |
+ filters: prop.filters, |
|
6384 |
+ // important: props need to be observed on the |
|
6385 |
+ // v-for scope if present |
|
6386 |
+ scope: this._scope |
|
6387 |
+ }); |
|
6388 |
+ |
|
6389 |
+ // set the child initial value. |
|
6390 |
+ initProp(child, prop, parentWatcher.value); |
|
6391 |
+ |
|
6392 |
+ // setup two-way binding |
|
6393 |
+ if (twoWay) { |
|
6394 |
+ // important: defer the child watcher creation until |
|
6395 |
+ // the created hook (after data observation) |
|
6396 |
+ var self = this; |
|
6397 |
+ child.$once('pre-hook:created', function () { |
|
6398 |
+ self.childWatcher = new Watcher(child, childKey, function (val) { |
|
6399 |
+ parentWatcher.set(val); |
|
6400 |
+ }, { |
|
6401 |
+ // ensure sync upward before parent sync down. |
|
6402 |
+ // this is necessary in cases e.g. the child |
|
6403 |
+ // mutates a prop array, then replaces it. (#1683) |
|
6404 |
+ sync: true |
|
6405 |
+ }); |
|
6406 |
+ }); |
|
6407 |
+ } |
|
6408 |
+ }, |
|
6409 |
+ |
|
6410 |
+ unbind: function unbind() { |
|
6411 |
+ this.parentWatcher.teardown(); |
|
6412 |
+ if (this.childWatcher) { |
|
6413 |
+ this.childWatcher.teardown(); |
|
6414 |
+ } |
|
6415 |
+ } |
|
6416 |
+ }; |
|
6417 |
+ |
|
6418 |
+ var queue$1 = []; |
|
6419 |
+ var queued = false; |
|
6420 |
+ |
|
6421 |
+ /** |
|
6422 |
+ * Push a job into the queue. |
|
6423 |
+ * |
|
6424 |
+ * @param {Function} job |
|
6425 |
+ */ |
|
6426 |
+ |
|
6427 |
+ function pushJob(job) { |
|
6428 |
+ queue$1.push(job); |
|
6429 |
+ if (!queued) { |
|
6430 |
+ queued = true; |
|
6431 |
+ nextTick(flush); |
|
6432 |
+ } |
|
6433 |
+ } |
|
6434 |
+ |
|
6435 |
+ /** |
|
6436 |
+ * Flush the queue, and do one forced reflow before |
|
6437 |
+ * triggering transitions. |
|
6438 |
+ */ |
|
6439 |
+ |
|
6440 |
+ function flush() { |
|
6441 |
+ // Force layout |
|
6442 |
+ var f = document.documentElement.offsetHeight; |
|
6443 |
+ for (var i = 0; i < queue$1.length; i++) { |
|
6444 |
+ queue$1[i](); |
|
6445 |
+ } |
|
6446 |
+ queue$1 = []; |
|
6447 |
+ queued = false; |
|
6448 |
+ // dummy return, so js linters don't complain about |
|
6449 |
+ // unused variable f |
|
6450 |
+ return f; |
|
6451 |
+ } |
|
6452 |
+ |
|
6453 |
+ var TYPE_TRANSITION = 'transition'; |
|
6454 |
+ var TYPE_ANIMATION = 'animation'; |
|
6455 |
+ var transDurationProp = transitionProp + 'Duration'; |
|
6456 |
+ var animDurationProp = animationProp + 'Duration'; |
|
6457 |
+ |
|
6458 |
+ /** |
|
6459 |
+ * If a just-entered element is applied the |
|
6460 |
+ * leave class while its enter transition hasn't started yet, |
|
6461 |
+ * and the transitioned property has the same value for both |
|
6462 |
+ * enter/leave, then the leave transition will be skipped and |
|
6463 |
+ * the transitionend event never fires. This function ensures |
|
6464 |
+ * its callback to be called after a transition has started |
|
6465 |
+ * by waiting for double raf. |
|
6466 |
+ * |
|
6467 |
+ * It falls back to setTimeout on devices that support CSS |
|
6468 |
+ * transitions but not raf (e.g. Android 4.2 browser) - since |
|
6469 |
+ * these environments are usually slow, we are giving it a |
|
6470 |
+ * relatively large timeout. |
|
6471 |
+ */ |
|
6472 |
+ |
|
6473 |
+ var raf = inBrowser && window.requestAnimationFrame; |
|
6474 |
+ var waitForTransitionStart = raf |
|
6475 |
+ /* istanbul ignore next */ |
|
6476 |
+ ? function (fn) { |
|
6477 |
+ raf(function () { |
|
6478 |
+ raf(fn); |
|
6479 |
+ }); |
|
6480 |
+ } : function (fn) { |
|
6481 |
+ setTimeout(fn, 50); |
|
6482 |
+ }; |
|
6483 |
+ |
|
6484 |
+ /** |
|
6485 |
+ * A Transition object that encapsulates the state and logic |
|
6486 |
+ * of the transition. |
|
6487 |
+ * |
|
6488 |
+ * @param {Element} el |
|
6489 |
+ * @param {String} id |
|
6490 |
+ * @param {Object} hooks |
|
6491 |
+ * @param {Vue} vm |
|
6492 |
+ */ |
|
6493 |
+ function Transition(el, id, hooks, vm) { |
|
6494 |
+ this.id = id; |
|
6495 |
+ this.el = el; |
|
6496 |
+ this.enterClass = hooks && hooks.enterClass || id + '-enter'; |
|
6497 |
+ this.leaveClass = hooks && hooks.leaveClass || id + '-leave'; |
|
6498 |
+ this.hooks = hooks; |
|
6499 |
+ this.vm = vm; |
|
6500 |
+ // async state |
|
6501 |
+ this.pendingCssEvent = this.pendingCssCb = this.cancel = this.pendingJsCb = this.op = this.cb = null; |
|
6502 |
+ this.justEntered = false; |
|
6503 |
+ this.entered = this.left = false; |
|
6504 |
+ this.typeCache = {}; |
|
6505 |
+ // check css transition type |
|
6506 |
+ this.type = hooks && hooks.type; |
|
6507 |
+ /* istanbul ignore if */ |
|
6508 |
+ if ('development' !== 'production') { |
|
6509 |
+ if (this.type && this.type !== TYPE_TRANSITION && this.type !== TYPE_ANIMATION) { |
|
6510 |
+ warn('invalid CSS transition type for transition="' + this.id + '": ' + this.type, vm); |
|
6511 |
+ } |
|
6512 |
+ } |
|
6513 |
+ // bind |
|
6514 |
+ var self = this;['enterNextTick', 'enterDone', 'leaveNextTick', 'leaveDone'].forEach(function (m) { |
|
6515 |
+ self[m] = bind(self[m], self); |
|
6516 |
+ }); |
|
6517 |
+ } |
|
6518 |
+ |
|
6519 |
+ var p$1 = Transition.prototype; |
|
6520 |
+ |
|
6521 |
+ /** |
|
6522 |
+ * Start an entering transition. |
|
6523 |
+ * |
|
6524 |
+ * 1. enter transition triggered |
|
6525 |
+ * 2. call beforeEnter hook |
|
6526 |
+ * 3. add enter class |
|
6527 |
+ * 4. insert/show element |
|
6528 |
+ * 5. call enter hook (with possible explicit js callback) |
|
6529 |
+ * 6. reflow |
|
6530 |
+ * 7. based on transition type: |
|
6531 |
+ * - transition: |
|
6532 |
+ * remove class now, wait for transitionend, |
|
6533 |
+ * then done if there's no explicit js callback. |
|
6534 |
+ * - animation: |
|
6535 |
+ * wait for animationend, remove class, |
|
6536 |
+ * then done if there's no explicit js callback. |
|
6537 |
+ * - no css transition: |
|
6538 |
+ * done now if there's no explicit js callback. |
|
6539 |
+ * 8. wait for either done or js callback, then call |
|
6540 |
+ * afterEnter hook. |
|
6541 |
+ * |
|
6542 |
+ * @param {Function} op - insert/show the element |
|
6543 |
+ * @param {Function} [cb] |
|
6544 |
+ */ |
|
6545 |
+ |
|
6546 |
+ p$1.enter = function (op, cb) { |
|
6547 |
+ this.cancelPending(); |
|
6548 |
+ this.callHook('beforeEnter'); |
|
6549 |
+ this.cb = cb; |
|
6550 |
+ addClass(this.el, this.enterClass); |
|
6551 |
+ op(); |
|
6552 |
+ this.entered = false; |
|
6553 |
+ this.callHookWithCb('enter'); |
|
6554 |
+ if (this.entered) { |
|
6555 |
+ return; // user called done synchronously. |
|
6556 |
+ } |
|
6557 |
+ this.cancel = this.hooks && this.hooks.enterCancelled; |
|
6558 |
+ pushJob(this.enterNextTick); |
|
6559 |
+ }; |
|
6560 |
+ |
|
6561 |
+ /** |
|
6562 |
+ * The "nextTick" phase of an entering transition, which is |
|
6563 |
+ * to be pushed into a queue and executed after a reflow so |
|
6564 |
+ * that removing the class can trigger a CSS transition. |
|
6565 |
+ */ |
|
6566 |
+ |
|
6567 |
+ p$1.enterNextTick = function () { |
|
6568 |
+ var _this = this; |
|
6569 |
+ |
|
6570 |
+ // prevent transition skipping |
|
6571 |
+ this.justEntered = true; |
|
6572 |
+ waitForTransitionStart(function () { |
|
6573 |
+ _this.justEntered = false; |
|
6574 |
+ }); |
|
6575 |
+ var enterDone = this.enterDone; |
|
6576 |
+ var type = this.getCssTransitionType(this.enterClass); |
|
6577 |
+ if (!this.pendingJsCb) { |
|
6578 |
+ if (type === TYPE_TRANSITION) { |
|
6579 |
+ // trigger transition by removing enter class now |
|
6580 |
+ removeClass(this.el, this.enterClass); |
|
6581 |
+ this.setupCssCb(transitionEndEvent, enterDone); |
|
6582 |
+ } else if (type === TYPE_ANIMATION) { |
|
6583 |
+ this.setupCssCb(animationEndEvent, enterDone); |
|
6584 |
+ } else { |
|
6585 |
+ enterDone(); |
|
6586 |
+ } |
|
6587 |
+ } else if (type === TYPE_TRANSITION) { |
|
6588 |
+ removeClass(this.el, this.enterClass); |
|
6589 |
+ } |
|
6590 |
+ }; |
|
6591 |
+ |
|
6592 |
+ /** |
|
6593 |
+ * The "cleanup" phase of an entering transition. |
|
6594 |
+ */ |
|
6595 |
+ |
|
6596 |
+ p$1.enterDone = function () { |
|
6597 |
+ this.entered = true; |
|
6598 |
+ this.cancel = this.pendingJsCb = null; |
|
6599 |
+ removeClass(this.el, this.enterClass); |
|
6600 |
+ this.callHook('afterEnter'); |
|
6601 |
+ if (this.cb) this.cb(); |
|
6602 |
+ }; |
|
6603 |
+ |
|
6604 |
+ /** |
|
6605 |
+ * Start a leaving transition. |
|
6606 |
+ * |
|
6607 |
+ * 1. leave transition triggered. |
|
6608 |
+ * 2. call beforeLeave hook |
|
6609 |
+ * 3. add leave class (trigger css transition) |
|
6610 |
+ * 4. call leave hook (with possible explicit js callback) |
|
6611 |
+ * 5. reflow if no explicit js callback is provided |
|
6612 |
+ * 6. based on transition type: |
|
6613 |
+ * - transition or animation: |
|
6614 |
+ * wait for end event, remove class, then done if |
|
6615 |
+ * there's no explicit js callback. |
|
6616 |
+ * - no css transition: |
|
6617 |
+ * done if there's no explicit js callback. |
|
6618 |
+ * 7. wait for either done or js callback, then call |
|
6619 |
+ * afterLeave hook. |
|
6620 |
+ * |
|
6621 |
+ * @param {Function} op - remove/hide the element |
|
6622 |
+ * @param {Function} [cb] |
|
6623 |
+ */ |
|
6624 |
+ |
|
6625 |
+ p$1.leave = function (op, cb) { |
|
6626 |
+ this.cancelPending(); |
|
6627 |
+ this.callHook('beforeLeave'); |
|
6628 |
+ this.op = op; |
|
6629 |
+ this.cb = cb; |
|
6630 |
+ addClass(this.el, this.leaveClass); |
|
6631 |
+ this.left = false; |
|
6632 |
+ this.callHookWithCb('leave'); |
|
6633 |
+ if (this.left) { |
|
6634 |
+ return; // user called done synchronously. |
|
6635 |
+ } |
|
6636 |
+ this.cancel = this.hooks && this.hooks.leaveCancelled; |
|
6637 |
+ // only need to handle leaveDone if |
|
6638 |
+ // 1. the transition is already done (synchronously called |
|
6639 |
+ // by the user, which causes this.op set to null) |
|
6640 |
+ // 2. there's no explicit js callback |
|
6641 |
+ if (this.op && !this.pendingJsCb) { |
|
6642 |
+ // if a CSS transition leaves immediately after enter, |
|
6643 |
+ // the transitionend event never fires. therefore we |
|
6644 |
+ // detect such cases and end the leave immediately. |
|
6645 |
+ if (this.justEntered) { |
|
6646 |
+ this.leaveDone(); |
|
6647 |
+ } else { |
|
6648 |
+ pushJob(this.leaveNextTick); |
|
6649 |
+ } |
|
6650 |
+ } |
|
6651 |
+ }; |
|
6652 |
+ |
|
6653 |
+ /** |
|
6654 |
+ * The "nextTick" phase of a leaving transition. |
|
6655 |
+ */ |
|
6656 |
+ |
|
6657 |
+ p$1.leaveNextTick = function () { |
|
6658 |
+ var type = this.getCssTransitionType(this.leaveClass); |
|
6659 |
+ if (type) { |
|
6660 |
+ var event = type === TYPE_TRANSITION ? transitionEndEvent : animationEndEvent; |
|
6661 |
+ this.setupCssCb(event, this.leaveDone); |
|
6662 |
+ } else { |
|
6663 |
+ this.leaveDone(); |
|
6664 |
+ } |
|
6665 |
+ }; |
|
6666 |
+ |
|
6667 |
+ /** |
|
6668 |
+ * The "cleanup" phase of a leaving transition. |
|
6669 |
+ */ |
|
6670 |
+ |
|
6671 |
+ p$1.leaveDone = function () { |
|
6672 |
+ this.left = true; |
|
6673 |
+ this.cancel = this.pendingJsCb = null; |
|
6674 |
+ this.op(); |
|
6675 |
+ removeClass(this.el, this.leaveClass); |
|
6676 |
+ this.callHook('afterLeave'); |
|
6677 |
+ if (this.cb) this.cb(); |
|
6678 |
+ this.op = null; |
|
6679 |
+ }; |
|
6680 |
+ |
|
6681 |
+ /** |
|
6682 |
+ * Cancel any pending callbacks from a previously running |
|
6683 |
+ * but not finished transition. |
|
6684 |
+ */ |
|
6685 |
+ |
|
6686 |
+ p$1.cancelPending = function () { |
|
6687 |
+ this.op = this.cb = null; |
|
6688 |
+ var hasPending = false; |
|
6689 |
+ if (this.pendingCssCb) { |
|
6690 |
+ hasPending = true; |
|
6691 |
+ off(this.el, this.pendingCssEvent, this.pendingCssCb); |
|
6692 |
+ this.pendingCssEvent = this.pendingCssCb = null; |
|
6693 |
+ } |
|
6694 |
+ if (this.pendingJsCb) { |
|
6695 |
+ hasPending = true; |
|
6696 |
+ this.pendingJsCb.cancel(); |
|
6697 |
+ this.pendingJsCb = null; |
|
6698 |
+ } |
|
6699 |
+ if (hasPending) { |
|
6700 |
+ removeClass(this.el, this.enterClass); |
|
6701 |
+ removeClass(this.el, this.leaveClass); |
|
6702 |
+ } |
|
6703 |
+ if (this.cancel) { |
|
6704 |
+ this.cancel.call(this.vm, this.el); |
|
6705 |
+ this.cancel = null; |
|
6706 |
+ } |
|
6707 |
+ }; |
|
6708 |
+ |
|
6709 |
+ /** |
|
6710 |
+ * Call a user-provided synchronous hook function. |
|
6711 |
+ * |
|
6712 |
+ * @param {String} type |
|
6713 |
+ */ |
|
6714 |
+ |
|
6715 |
+ p$1.callHook = function (type) { |
|
6716 |
+ if (this.hooks && this.hooks[type]) { |
|
6717 |
+ this.hooks[type].call(this.vm, this.el); |
|
6718 |
+ } |
|
6719 |
+ }; |
|
6720 |
+ |
|
6721 |
+ /** |
|
6722 |
+ * Call a user-provided, potentially-async hook function. |
|
6723 |
+ * We check for the length of arguments to see if the hook |
|
6724 |
+ * expects a `done` callback. If true, the transition's end |
|
6725 |
+ * will be determined by when the user calls that callback; |
|
6726 |
+ * otherwise, the end is determined by the CSS transition or |
|
6727 |
+ * animation. |
|
6728 |
+ * |
|
6729 |
+ * @param {String} type |
|
6730 |
+ */ |
|
6731 |
+ |
|
6732 |
+ p$1.callHookWithCb = function (type) { |
|
6733 |
+ var hook = this.hooks && this.hooks[type]; |
|
6734 |
+ if (hook) { |
|
6735 |
+ if (hook.length > 1) { |
|
6736 |
+ this.pendingJsCb = cancellable(this[type + 'Done']); |
|
6737 |
+ } |
|
6738 |
+ hook.call(this.vm, this.el, this.pendingJsCb); |
|
6739 |
+ } |
|
6740 |
+ }; |
|
6741 |
+ |
|
6742 |
+ /** |
|
6743 |
+ * Get an element's transition type based on the |
|
6744 |
+ * calculated styles. |
|
6745 |
+ * |
|
6746 |
+ * @param {String} className |
|
6747 |
+ * @return {Number} |
|
6748 |
+ */ |
|
6749 |
+ |
|
6750 |
+ p$1.getCssTransitionType = function (className) { |
|
6751 |
+ /* istanbul ignore if */ |
|
6752 |
+ if (!transitionEndEvent || |
|
6753 |
+ // skip CSS transitions if page is not visible - |
|
6754 |
+ // this solves the issue of transitionend events not |
|
6755 |
+ // firing until the page is visible again. |
|
6756 |
+ // pageVisibility API is supported in IE10+, same as |
|
6757 |
+ // CSS transitions. |
|
6758 |
+ document.hidden || |
|
6759 |
+ // explicit js-only transition |
|
6760 |
+ this.hooks && this.hooks.css === false || |
|
6761 |
+ // element is hidden |
|
6762 |
+ isHidden(this.el)) { |
|
6763 |
+ return; |
|
6764 |
+ } |
|
6765 |
+ var type = this.type || this.typeCache[className]; |
|
6766 |
+ if (type) return type; |
|
6767 |
+ var inlineStyles = this.el.style; |
|
6768 |
+ var computedStyles = window.getComputedStyle(this.el); |
|
6769 |
+ var transDuration = inlineStyles[transDurationProp] || computedStyles[transDurationProp]; |
|
6770 |
+ if (transDuration && transDuration !== '0s') { |
|
6771 |
+ type = TYPE_TRANSITION; |
|
6772 |
+ } else { |
|
6773 |
+ var animDuration = inlineStyles[animDurationProp] || computedStyles[animDurationProp]; |
|
6774 |
+ if (animDuration && animDuration !== '0s') { |
|
6775 |
+ type = TYPE_ANIMATION; |
|
6776 |
+ } |
|
6777 |
+ } |
|
6778 |
+ if (type) { |
|
6779 |
+ this.typeCache[className] = type; |
|
6780 |
+ } |
|
6781 |
+ return type; |
|
6782 |
+ }; |
|
6783 |
+ |
|
6784 |
+ /** |
|
6785 |
+ * Setup a CSS transitionend/animationend callback. |
|
6786 |
+ * |
|
6787 |
+ * @param {String} event |
|
6788 |
+ * @param {Function} cb |
|
6789 |
+ */ |
|
6790 |
+ |
|
6791 |
+ p$1.setupCssCb = function (event, cb) { |
|
6792 |
+ this.pendingCssEvent = event; |
|
6793 |
+ var self = this; |
|
6794 |
+ var el = this.el; |
|
6795 |
+ var onEnd = this.pendingCssCb = function (e) { |
|
6796 |
+ if (e.target === el) { |
|
6797 |
+ off(el, event, onEnd); |
|
6798 |
+ self.pendingCssEvent = self.pendingCssCb = null; |
|
6799 |
+ if (!self.pendingJsCb && cb) { |
|
6800 |
+ cb(); |
|
6801 |
+ } |
|
6802 |
+ } |
|
6803 |
+ }; |
|
6804 |
+ on(el, event, onEnd); |
|
6805 |
+ }; |
|
6806 |
+ |
|
6807 |
+ /** |
|
6808 |
+ * Check if an element is hidden - in that case we can just |
|
6809 |
+ * skip the transition alltogether. |
|
6810 |
+ * |
|
6811 |
+ * @param {Element} el |
|
6812 |
+ * @return {Boolean} |
|
6813 |
+ */ |
|
6814 |
+ |
|
6815 |
+ function isHidden(el) { |
|
6816 |
+ if (/svg$/.test(el.namespaceURI)) { |
|
6817 |
+ // SVG elements do not have offset(Width|Height) |
|
6818 |
+ // so we need to check the client rect |
|
6819 |
+ var rect = el.getBoundingClientRect(); |
|
6820 |
+ return !(rect.width || rect.height); |
|
6821 |
+ } else { |
|
6822 |
+ return !(el.offsetWidth || el.offsetHeight || el.getClientRects().length); |
|
6823 |
+ } |
|
6824 |
+ } |
|
6825 |
+ |
|
6826 |
+ var transition$1 = { |
|
6827 |
+ |
|
6828 |
+ priority: TRANSITION, |
|
6829 |
+ |
|
6830 |
+ update: function update(id, oldId) { |
|
6831 |
+ var el = this.el; |
|
6832 |
+ // resolve on owner vm |
|
6833 |
+ var hooks = resolveAsset(this.vm.$options, 'transitions', id); |
|
6834 |
+ id = id || 'v'; |
|
6835 |
+ oldId = oldId || 'v'; |
|
6836 |
+ el.__v_trans = new Transition(el, id, hooks, this.vm); |
|
6837 |
+ removeClass(el, oldId + '-transition'); |
|
6838 |
+ addClass(el, id + '-transition'); |
|
6839 |
+ } |
|
6840 |
+ }; |
|
6841 |
+ |
|
6842 |
+ var internalDirectives = { |
|
6843 |
+ style: style, |
|
6844 |
+ 'class': vClass, |
|
6845 |
+ component: component, |
|
6846 |
+ prop: propDef, |
|
6847 |
+ transition: transition$1 |
|
6848 |
+ }; |
|
6849 |
+ |
|
6850 |
+ // special binding prefixes |
|
6851 |
+ var bindRE = /^v-bind:|^:/; |
|
6852 |
+ var onRE = /^v-on:|^@/; |
|
6853 |
+ var dirAttrRE = /^v-([^:]+)(?:$|:(.*)$)/; |
|
6854 |
+ var modifierRE = /\.[^\.]+/g; |
|
6855 |
+ var transitionRE = /^(v-bind:|:)?transition$/; |
|
6856 |
+ |
|
6857 |
+ // default directive priority |
|
6858 |
+ var DEFAULT_PRIORITY = 1000; |
|
6859 |
+ var DEFAULT_TERMINAL_PRIORITY = 2000; |
|
6860 |
+ |
|
6861 |
+ /** |
|
6862 |
+ * Compile a template and return a reusable composite link |
|
6863 |
+ * function, which recursively contains more link functions |
|
6864 |
+ * inside. This top level compile function would normally |
|
6865 |
+ * be called on instance root nodes, but can also be used |
|
6866 |
+ * for partial compilation if the partial argument is true. |
|
6867 |
+ * |
|
6868 |
+ * The returned composite link function, when called, will |
|
6869 |
+ * return an unlink function that tearsdown all directives |
|
6870 |
+ * created during the linking phase. |
|
6871 |
+ * |
|
6872 |
+ * @param {Element|DocumentFragment} el |
|
6873 |
+ * @param {Object} options |
|
6874 |
+ * @param {Boolean} partial |
|
6875 |
+ * @return {Function} |
|
6876 |
+ */ |
|
6877 |
+ |
|
6878 |
+ function compile(el, options, partial) { |
|
6879 |
+ // link function for the node itself. |
|
6880 |
+ var nodeLinkFn = partial || !options._asComponent ? compileNode(el, options) : null; |
|
6881 |
+ // link function for the childNodes |
|
6882 |
+ var childLinkFn = !(nodeLinkFn && nodeLinkFn.terminal) && !isScript(el) && el.hasChildNodes() ? compileNodeList(el.childNodes, options) : null; |
|
6883 |
+ |
|
6884 |
+ /** |
|
6885 |
+ * A composite linker function to be called on a already |
|
6886 |
+ * compiled piece of DOM, which instantiates all directive |
|
6887 |
+ * instances. |
|
6888 |
+ * |
|
6889 |
+ * @param {Vue} vm |
|
6890 |
+ * @param {Element|DocumentFragment} el |
|
6891 |
+ * @param {Vue} [host] - host vm of transcluded content |
|
6892 |
+ * @param {Object} [scope] - v-for scope |
|
6893 |
+ * @param {Fragment} [frag] - link context fragment |
|
6894 |
+ * @return {Function|undefined} |
|
6895 |
+ */ |
|
6896 |
+ |
|
6897 |
+ return function compositeLinkFn(vm, el, host, scope, frag) { |
|
6898 |
+ // cache childNodes before linking parent, fix #657 |
|
6899 |
+ var childNodes = toArray(el.childNodes); |
|
6900 |
+ // link |
|
6901 |
+ var dirs = linkAndCapture(function compositeLinkCapturer() { |
|
6902 |
+ if (nodeLinkFn) nodeLinkFn(vm, el, host, scope, frag); |
|
6903 |
+ if (childLinkFn) childLinkFn(vm, childNodes, host, scope, frag); |
|
6904 |
+ }, vm); |
|
6905 |
+ return makeUnlinkFn(vm, dirs); |
|
6906 |
+ }; |
|
6907 |
+ } |
|
6908 |
+ |
|
6909 |
+ /** |
|
6910 |
+ * Apply a linker to a vm/element pair and capture the |
|
6911 |
+ * directives created during the process. |
|
6912 |
+ * |
|
6913 |
+ * @param {Function} linker |
|
6914 |
+ * @param {Vue} vm |
|
6915 |
+ */ |
|
6916 |
+ |
|
6917 |
+ function linkAndCapture(linker, vm) { |
|
6918 |
+ /* istanbul ignore if */ |
|
6919 |
+ if ('development' === 'production') {} |
|
6920 |
+ var originalDirCount = vm._directives.length; |
|
6921 |
+ linker(); |
|
6922 |
+ var dirs = vm._directives.slice(originalDirCount); |
|
6923 |
+ dirs.sort(directiveComparator); |
|
6924 |
+ for (var i = 0, l = dirs.length; i < l; i++) { |
|
6925 |
+ dirs[i]._bind(); |
|
6926 |
+ } |
|
6927 |
+ return dirs; |
|
6928 |
+ } |
|
6929 |
+ |
|
6930 |
+ /** |
|
6931 |
+ * Directive priority sort comparator |
|
6932 |
+ * |
|
6933 |
+ * @param {Object} a |
|
6934 |
+ * @param {Object} b |
|
6935 |
+ */ |
|
6936 |
+ |
|
6937 |
+ function directiveComparator(a, b) { |
|
6938 |
+ a = a.descriptor.def.priority || DEFAULT_PRIORITY; |
|
6939 |
+ b = b.descriptor.def.priority || DEFAULT_PRIORITY; |
|
6940 |
+ return a > b ? -1 : a === b ? 0 : 1; |
|
6941 |
+ } |
|
6942 |
+ |
|
6943 |
+ /** |
|
6944 |
+ * Linker functions return an unlink function that |
|
6945 |
+ * tearsdown all directives instances generated during |
|
6946 |
+ * the process. |
|
6947 |
+ * |
|
6948 |
+ * We create unlink functions with only the necessary |
|
6949 |
+ * information to avoid retaining additional closures. |
|
6950 |
+ * |
|
6951 |
+ * @param {Vue} vm |
|
6952 |
+ * @param {Array} dirs |
|
6953 |
+ * @param {Vue} [context] |
|
6954 |
+ * @param {Array} [contextDirs] |
|
6955 |
+ * @return {Function} |
|
6956 |
+ */ |
|
6957 |
+ |
|
6958 |
+ function makeUnlinkFn(vm, dirs, context, contextDirs) { |
|
6959 |
+ function unlink(destroying) { |
|
6960 |
+ teardownDirs(vm, dirs, destroying); |
|
6961 |
+ if (context && contextDirs) { |
|
6962 |
+ teardownDirs(context, contextDirs); |
|
6963 |
+ } |
|
6964 |
+ } |
|
6965 |
+ // expose linked directives |
|
6966 |
+ unlink.dirs = dirs; |
|
6967 |
+ return unlink; |
|
6968 |
+ } |
|
6969 |
+ |
|
6970 |
+ /** |
|
6971 |
+ * Teardown partial linked directives. |
|
6972 |
+ * |
|
6973 |
+ * @param {Vue} vm |
|
6974 |
+ * @param {Array} dirs |
|
6975 |
+ * @param {Boolean} destroying |
|
6976 |
+ */ |
|
6977 |
+ |
|
6978 |
+ function teardownDirs(vm, dirs, destroying) { |
|
6979 |
+ var i = dirs.length; |
|
6980 |
+ while (i--) { |
|
6981 |
+ dirs[i]._teardown(); |
|
6982 |
+ if ('development' !== 'production' && !destroying) { |
|
6983 |
+ vm._directives.$remove(dirs[i]); |
|
6984 |
+ } |
|
6985 |
+ } |
|
6986 |
+ } |
|
6987 |
+ |
|
6988 |
+ /** |
|
6989 |
+ * Compile link props on an instance. |
|
6990 |
+ * |
|
6991 |
+ * @param {Vue} vm |
|
6992 |
+ * @param {Element} el |
|
6993 |
+ * @param {Object} props |
|
6994 |
+ * @param {Object} [scope] |
|
6995 |
+ * @return {Function} |
|
6996 |
+ */ |
|
6997 |
+ |
|
6998 |
+ function compileAndLinkProps(vm, el, props, scope) { |
|
6999 |
+ var propsLinkFn = compileProps(el, props, vm); |
|
7000 |
+ var propDirs = linkAndCapture(function () { |
|
7001 |
+ propsLinkFn(vm, scope); |
|
7002 |
+ }, vm); |
|
7003 |
+ return makeUnlinkFn(vm, propDirs); |
|
7004 |
+ } |
|
7005 |
+ |
|
7006 |
+ /** |
|
7007 |
+ * Compile the root element of an instance. |
|
7008 |
+ * |
|
7009 |
+ * 1. attrs on context container (context scope) |
|
7010 |
+ * 2. attrs on the component template root node, if |
|
7011 |
+ * replace:true (child scope) |
|
7012 |
+ * |
|
7013 |
+ * If this is a fragment instance, we only need to compile 1. |
|
7014 |
+ * |
|
7015 |
+ * @param {Element} el |
|
7016 |
+ * @param {Object} options |
|
7017 |
+ * @param {Object} contextOptions |
|
7018 |
+ * @return {Function} |
|
7019 |
+ */ |
|
7020 |
+ |
|
7021 |
+ function compileRoot(el, options, contextOptions) { |
|
7022 |
+ var containerAttrs = options._containerAttrs; |
|
7023 |
+ var replacerAttrs = options._replacerAttrs; |
|
7024 |
+ var contextLinkFn, replacerLinkFn; |
|
7025 |
+ |
|
7026 |
+ // only need to compile other attributes for |
|
7027 |
+ // non-fragment instances |
|
7028 |
+ if (el.nodeType !== 11) { |
|
7029 |
+ // for components, container and replacer need to be |
|
7030 |
+ // compiled separately and linked in different scopes. |
|
7031 |
+ if (options._asComponent) { |
|
7032 |
+ // 2. container attributes |
|
7033 |
+ if (containerAttrs && contextOptions) { |
|
7034 |
+ contextLinkFn = compileDirectives(containerAttrs, contextOptions); |
|
7035 |
+ } |
|
7036 |
+ if (replacerAttrs) { |
|
7037 |
+ // 3. replacer attributes |
|
7038 |
+ replacerLinkFn = compileDirectives(replacerAttrs, options); |
|
7039 |
+ } |
|
7040 |
+ } else { |
|
7041 |
+ // non-component, just compile as a normal element. |
|
7042 |
+ replacerLinkFn = compileDirectives(el.attributes, options); |
|
7043 |
+ } |
|
7044 |
+ } else if ('development' !== 'production' && containerAttrs) { |
|
7045 |
+ // warn container directives for fragment instances |
|
7046 |
+ var names = containerAttrs.filter(function (attr) { |
|
7047 |
+ // allow vue-loader/vueify scoped css attributes |
|
7048 |
+ return attr.name.indexOf('_v-') < 0 && |
|
7049 |
+ // allow event listeners |
|
7050 |
+ !onRE.test(attr.name) && |
|
7051 |
+ // allow slots |
|
7052 |
+ attr.name !== 'slot'; |
|
7053 |
+ }).map(function (attr) { |
|
7054 |
+ return '"' + attr.name + '"'; |
|
7055 |
+ }); |
|
7056 |
+ if (names.length) { |
|
7057 |
+ var plural = names.length > 1; |
|
7058 |
+ warn('Attribute' + (plural ? 's ' : ' ') + names.join(', ') + (plural ? ' are' : ' is') + ' ignored on component ' + '<' + options.el.tagName.toLowerCase() + '> because ' + 'the component is a fragment instance: ' + 'http://vuejs.org/guide/components.html#Fragment-Instance'); |
|
7059 |
+ } |
|
7060 |
+ } |
|
7061 |
+ |
|
7062 |
+ options._containerAttrs = options._replacerAttrs = null; |
|
7063 |
+ return function rootLinkFn(vm, el, scope) { |
|
7064 |
+ // link context scope dirs |
|
7065 |
+ var context = vm._context; |
|
7066 |
+ var contextDirs; |
|
7067 |
+ if (context && contextLinkFn) { |
|
7068 |
+ contextDirs = linkAndCapture(function () { |
|
7069 |
+ contextLinkFn(context, el, null, scope); |
|
7070 |
+ }, context); |
|
7071 |
+ } |
|
7072 |
+ |
|
7073 |
+ // link self |
|
7074 |
+ var selfDirs = linkAndCapture(function () { |
|
7075 |
+ if (replacerLinkFn) replacerLinkFn(vm, el); |
|
7076 |
+ }, vm); |
|
7077 |
+ |
|
7078 |
+ // return the unlink function that tearsdown context |
|
7079 |
+ // container directives. |
|
7080 |
+ return makeUnlinkFn(vm, selfDirs, context, contextDirs); |
|
7081 |
+ }; |
|
7082 |
+ } |
|
7083 |
+ |
|
7084 |
+ /** |
|
7085 |
+ * Compile a node and return a nodeLinkFn based on the |
|
7086 |
+ * node type. |
|
7087 |
+ * |
|
7088 |
+ * @param {Node} node |
|
7089 |
+ * @param {Object} options |
|
7090 |
+ * @return {Function|null} |
|
7091 |
+ */ |
|
7092 |
+ |
|
7093 |
+ function compileNode(node, options) { |
|
7094 |
+ var type = node.nodeType; |
|
7095 |
+ if (type === 1 && !isScript(node)) { |
|
7096 |
+ return compileElement(node, options); |
|
7097 |
+ } else if (type === 3 && node.data.trim()) { |
|
7098 |
+ return compileTextNode(node, options); |
|
7099 |
+ } else { |
|
7100 |
+ return null; |
|
7101 |
+ } |
|
7102 |
+ } |
|
7103 |
+ |
|
7104 |
+ /** |
|
7105 |
+ * Compile an element and return a nodeLinkFn. |
|
7106 |
+ * |
|
7107 |
+ * @param {Element} el |
|
7108 |
+ * @param {Object} options |
|
7109 |
+ * @return {Function|null} |
|
7110 |
+ */ |
|
7111 |
+ |
|
7112 |
+ function compileElement(el, options) { |
|
7113 |
+ // preprocess textareas. |
|
7114 |
+ // textarea treats its text content as the initial value. |
|
7115 |
+ // just bind it as an attr directive for value. |
|
7116 |
+ if (el.tagName === 'TEXTAREA') { |
|
7117 |
+ var tokens = parseText(el.value); |
|
7118 |
+ if (tokens) { |
|
7119 |
+ el.setAttribute(':value', tokensToExp(tokens)); |
|
7120 |
+ el.value = ''; |
|
7121 |
+ } |
|
7122 |
+ } |
|
7123 |
+ var linkFn; |
|
7124 |
+ var hasAttrs = el.hasAttributes(); |
|
7125 |
+ var attrs = hasAttrs && toArray(el.attributes); |
|
7126 |
+ // check terminal directives (for & if) |
|
7127 |
+ if (hasAttrs) { |
|
7128 |
+ linkFn = checkTerminalDirectives(el, attrs, options); |
|
7129 |
+ } |
|
7130 |
+ // check element directives |
|
7131 |
+ if (!linkFn) { |
|
7132 |
+ linkFn = checkElementDirectives(el, options); |
|
7133 |
+ } |
|
7134 |
+ // check component |
|
7135 |
+ if (!linkFn) { |
|
7136 |
+ linkFn = checkComponent(el, options); |
|
7137 |
+ } |
|
7138 |
+ // normal directives |
|
7139 |
+ if (!linkFn && hasAttrs) { |
|
7140 |
+ linkFn = compileDirectives(attrs, options); |
|
7141 |
+ } |
|
7142 |
+ return linkFn; |
|
7143 |
+ } |
|
7144 |
+ |
|
7145 |
+ /** |
|
7146 |
+ * Compile a textNode and return a nodeLinkFn. |
|
7147 |
+ * |
|
7148 |
+ * @param {TextNode} node |
|
7149 |
+ * @param {Object} options |
|
7150 |
+ * @return {Function|null} textNodeLinkFn |
|
7151 |
+ */ |
|
7152 |
+ |
|
7153 |
+ function compileTextNode(node, options) { |
|
7154 |
+ // skip marked text nodes |
|
7155 |
+ if (node._skip) { |
|
7156 |
+ return removeText; |
|
7157 |
+ } |
|
7158 |
+ |
|
7159 |
+ var tokens = parseText(node.wholeText); |
|
7160 |
+ if (!tokens) { |
|
7161 |
+ return null; |
|
7162 |
+ } |
|
7163 |
+ |
|
7164 |
+ // mark adjacent text nodes as skipped, |
|
7165 |
+ // because we are using node.wholeText to compile |
|
7166 |
+ // all adjacent text nodes together. This fixes |
|
7167 |
+ // issues in IE where sometimes it splits up a single |
|
7168 |
+ // text node into multiple ones. |
|
7169 |
+ var next = node.nextSibling; |
|
7170 |
+ while (next && next.nodeType === 3) { |
|
7171 |
+ next._skip = true; |
|
7172 |
+ next = next.nextSibling; |
|
7173 |
+ } |
|
7174 |
+ |
|
7175 |
+ var frag = document.createDocumentFragment(); |
|
7176 |
+ var el, token; |
|
7177 |
+ for (var i = 0, l = tokens.length; i < l; i++) { |
|
7178 |
+ token = tokens[i]; |
|
7179 |
+ el = token.tag ? processTextToken(token, options) : document.createTextNode(token.value); |
|
7180 |
+ frag.appendChild(el); |
|
7181 |
+ } |
|
7182 |
+ return makeTextNodeLinkFn(tokens, frag, options); |
|
7183 |
+ } |
|
7184 |
+ |
|
7185 |
+ /** |
|
7186 |
+ * Linker for an skipped text node. |
|
7187 |
+ * |
|
7188 |
+ * @param {Vue} vm |
|
7189 |
+ * @param {Text} node |
|
7190 |
+ */ |
|
7191 |
+ |
|
7192 |
+ function removeText(vm, node) { |
|
7193 |
+ remove(node); |
|
7194 |
+ } |
|
7195 |
+ |
|
7196 |
+ /** |
|
7197 |
+ * Process a single text token. |
|
7198 |
+ * |
|
7199 |
+ * @param {Object} token |
|
7200 |
+ * @param {Object} options |
|
7201 |
+ * @return {Node} |
|
7202 |
+ */ |
|
7203 |
+ |
|
7204 |
+ function processTextToken(token, options) { |
|
7205 |
+ var el; |
|
7206 |
+ if (token.oneTime) { |
|
7207 |
+ el = document.createTextNode(token.value); |
|
7208 |
+ } else { |
|
7209 |
+ if (token.html) { |
|
7210 |
+ el = document.createComment('v-html'); |
|
7211 |
+ setTokenType('html'); |
|
7212 |
+ } else { |
|
7213 |
+ // IE will clean up empty textNodes during |
|
7214 |
+ // frag.cloneNode(true), so we have to give it |
|
7215 |
+ // something here... |
|
7216 |
+ el = document.createTextNode(' '); |
|
7217 |
+ setTokenType('text'); |
|
7218 |
+ } |
|
7219 |
+ } |
|
7220 |
+ function setTokenType(type) { |
|
7221 |
+ if (token.descriptor) return; |
|
7222 |
+ var parsed = parseDirective(token.value); |
|
7223 |
+ token.descriptor = { |
|
7224 |
+ name: type, |
|
7225 |
+ def: directives[type], |
|
7226 |
+ expression: parsed.expression, |
|
7227 |
+ filters: parsed.filters |
|
7228 |
+ }; |
|
7229 |
+ } |
|
7230 |
+ return el; |
|
7231 |
+ } |
|
7232 |
+ |
|
7233 |
+ /** |
|
7234 |
+ * Build a function that processes a textNode. |
|
7235 |
+ * |
|
7236 |
+ * @param {Array<Object>} tokens |
|
7237 |
+ * @param {DocumentFragment} frag |
|
7238 |
+ */ |
|
7239 |
+ |
|
7240 |
+ function makeTextNodeLinkFn(tokens, frag) { |
|
7241 |
+ return function textNodeLinkFn(vm, el, host, scope) { |
|
7242 |
+ var fragClone = frag.cloneNode(true); |
|
7243 |
+ var childNodes = toArray(fragClone.childNodes); |
|
7244 |
+ var token, value, node; |
|
7245 |
+ for (var i = 0, l = tokens.length; i < l; i++) { |
|
7246 |
+ token = tokens[i]; |
|
7247 |
+ value = token.value; |
|
7248 |
+ if (token.tag) { |
|
7249 |
+ node = childNodes[i]; |
|
7250 |
+ if (token.oneTime) { |
|
7251 |
+ value = (scope || vm).$eval(value); |
|
7252 |
+ if (token.html) { |
|
7253 |
+ replace(node, parseTemplate(value, true)); |
|
7254 |
+ } else { |
|
7255 |
+ node.data = _toString(value); |
|
7256 |
+ } |
|
7257 |
+ } else { |
|
7258 |
+ vm._bindDir(token.descriptor, node, host, scope); |
|
7259 |
+ } |
|
7260 |
+ } |
|
7261 |
+ } |
|
7262 |
+ replace(el, fragClone); |
|
7263 |
+ }; |
|
7264 |
+ } |
|
7265 |
+ |
|
7266 |
+ /** |
|
7267 |
+ * Compile a node list and return a childLinkFn. |
|
7268 |
+ * |
|
7269 |
+ * @param {NodeList} nodeList |
|
7270 |
+ * @param {Object} options |
|
7271 |
+ * @return {Function|undefined} |
|
7272 |
+ */ |
|
7273 |
+ |
|
7274 |
+ function compileNodeList(nodeList, options) { |
|
7275 |
+ var linkFns = []; |
|
7276 |
+ var nodeLinkFn, childLinkFn, node; |
|
7277 |
+ for (var i = 0, l = nodeList.length; i < l; i++) { |
|
7278 |
+ node = nodeList[i]; |
|
7279 |
+ nodeLinkFn = compileNode(node, options); |
|
7280 |
+ childLinkFn = !(nodeLinkFn && nodeLinkFn.terminal) && node.tagName !== 'SCRIPT' && node.hasChildNodes() ? compileNodeList(node.childNodes, options) : null; |
|
7281 |
+ linkFns.push(nodeLinkFn, childLinkFn); |
|
7282 |
+ } |
|
7283 |
+ return linkFns.length ? makeChildLinkFn(linkFns) : null; |
|
7284 |
+ } |
|
7285 |
+ |
|
7286 |
+ /** |
|
7287 |
+ * Make a child link function for a node's childNodes. |
|
7288 |
+ * |
|
7289 |
+ * @param {Array<Function>} linkFns |
|
7290 |
+ * @return {Function} childLinkFn |
|
7291 |
+ */ |
|
7292 |
+ |
|
7293 |
+ function makeChildLinkFn(linkFns) { |
|
7294 |
+ return function childLinkFn(vm, nodes, host, scope, frag) { |
|
7295 |
+ var node, nodeLinkFn, childrenLinkFn; |
|
7296 |
+ for (var i = 0, n = 0, l = linkFns.length; i < l; n++) { |
|
7297 |
+ node = nodes[n]; |
|
7298 |
+ nodeLinkFn = linkFns[i++]; |
|
7299 |
+ childrenLinkFn = linkFns[i++]; |
|
7300 |
+ // cache childNodes before linking parent, fix #657 |
|
7301 |
+ var childNodes = toArray(node.childNodes); |
|
7302 |
+ if (nodeLinkFn) { |
|
7303 |
+ nodeLinkFn(vm, node, host, scope, frag); |
|
7304 |
+ } |
|
7305 |
+ if (childrenLinkFn) { |
|
7306 |
+ childrenLinkFn(vm, childNodes, host, scope, frag); |
|
7307 |
+ } |
|
7308 |
+ } |
|
7309 |
+ }; |
|
7310 |
+ } |
|
7311 |
+ |
|
7312 |
+ /** |
|
7313 |
+ * Check for element directives (custom elements that should |
|
7314 |
+ * be resovled as terminal directives). |
|
7315 |
+ * |
|
7316 |
+ * @param {Element} el |
|
7317 |
+ * @param {Object} options |
|
7318 |
+ */ |
|
7319 |
+ |
|
7320 |
+ function checkElementDirectives(el, options) { |
|
7321 |
+ var tag = el.tagName.toLowerCase(); |
|
7322 |
+ if (commonTagRE.test(tag)) { |
|
7323 |
+ return; |
|
7324 |
+ } |
|
7325 |
+ var def = resolveAsset(options, 'elementDirectives', tag); |
|
7326 |
+ if (def) { |
|
7327 |
+ return makeTerminalNodeLinkFn(el, tag, '', options, def); |
|
7328 |
+ } |
|
7329 |
+ } |
|
7330 |
+ |
|
7331 |
+ /** |
|
7332 |
+ * Check if an element is a component. If yes, return |
|
7333 |
+ * a component link function. |
|
7334 |
+ * |
|
7335 |
+ * @param {Element} el |
|
7336 |
+ * @param {Object} options |
|
7337 |
+ * @return {Function|undefined} |
|
7338 |
+ */ |
|
7339 |
+ |
|
7340 |
+ function checkComponent(el, options) { |
|
7341 |
+ var component = checkComponentAttr(el, options); |
|
7342 |
+ if (component) { |
|
7343 |
+ var ref = findRef(el); |
|
7344 |
+ var descriptor = { |
|
7345 |
+ name: 'component', |
|
7346 |
+ ref: ref, |
|
7347 |
+ expression: component.id, |
|
7348 |
+ def: internalDirectives.component, |
|
7349 |
+ modifiers: { |
|
7350 |
+ literal: !component.dynamic |
|
7351 |
+ } |
|
7352 |
+ }; |
|
7353 |
+ var componentLinkFn = function componentLinkFn(vm, el, host, scope, frag) { |
|
7354 |
+ if (ref) { |
|
7355 |
+ defineReactive((scope || vm).$refs, ref, null); |
|
7356 |
+ } |
|
7357 |
+ vm._bindDir(descriptor, el, host, scope, frag); |
|
7358 |
+ }; |
|
7359 |
+ componentLinkFn.terminal = true; |
|
7360 |
+ return componentLinkFn; |
|
7361 |
+ } |
|
7362 |
+ } |
|
7363 |
+ |
|
7364 |
+ /** |
|
7365 |
+ * Check an element for terminal directives in fixed order. |
|
7366 |
+ * If it finds one, return a terminal link function. |
|
7367 |
+ * |
|
7368 |
+ * @param {Element} el |
|
7369 |
+ * @param {Array} attrs |
|
7370 |
+ * @param {Object} options |
|
7371 |
+ * @return {Function} terminalLinkFn |
|
7372 |
+ */ |
|
7373 |
+ |
|
7374 |
+ function checkTerminalDirectives(el, attrs, options) { |
|
7375 |
+ // skip v-pre |
|
7376 |
+ if (getAttr(el, 'v-pre') !== null) { |
|
7377 |
+ return skip; |
|
7378 |
+ } |
|
7379 |
+ // skip v-else block, but only if following v-if |
|
7380 |
+ if (el.hasAttribute('v-else')) { |
|
7381 |
+ var prev = el.previousElementSibling; |
|
7382 |
+ if (prev && prev.hasAttribute('v-if')) { |
|
7383 |
+ return skip; |
|
7384 |
+ } |
|
7385 |
+ } |
|
7386 |
+ |
|
7387 |
+ var attr, name, value, modifiers, matched, dirName, rawName, arg, def, termDef; |
|
7388 |
+ for (var i = 0, j = attrs.length; i < j; i++) { |
|
7389 |
+ attr = attrs[i]; |
|
7390 |
+ name = attr.name.replace(modifierRE, ''); |
|
7391 |
+ if (matched = name.match(dirAttrRE)) { |
|
7392 |
+ def = resolveAsset(options, 'directives', matched[1]); |
|
7393 |
+ if (def && def.terminal) { |
|
7394 |
+ if (!termDef || (def.priority || DEFAULT_TERMINAL_PRIORITY) > termDef.priority) { |
|
7395 |
+ termDef = def; |
|
7396 |
+ rawName = attr.name; |
|
7397 |
+ modifiers = parseModifiers(attr.name); |
|
7398 |
+ value = attr.value; |
|
7399 |
+ dirName = matched[1]; |
|
7400 |
+ arg = matched[2]; |
|
7401 |
+ } |
|
7402 |
+ } |
|
7403 |
+ } |
|
7404 |
+ } |
|
7405 |
+ |
|
7406 |
+ if (termDef) { |
|
7407 |
+ return makeTerminalNodeLinkFn(el, dirName, value, options, termDef, rawName, arg, modifiers); |
|
7408 |
+ } |
|
7409 |
+ } |
|
7410 |
+ |
|
7411 |
+ function skip() {} |
|
7412 |
+ skip.terminal = true; |
|
7413 |
+ |
|
7414 |
+ /** |
|
7415 |
+ * Build a node link function for a terminal directive. |
|
7416 |
+ * A terminal link function terminates the current |
|
7417 |
+ * compilation recursion and handles compilation of the |
|
7418 |
+ * subtree in the directive. |
|
7419 |
+ * |
|
7420 |
+ * @param {Element} el |
|
7421 |
+ * @param {String} dirName |
|
7422 |
+ * @param {String} value |
|
7423 |
+ * @param {Object} options |
|
7424 |
+ * @param {Object} def |
|
7425 |
+ * @param {String} [rawName] |
|
7426 |
+ * @param {String} [arg] |
|
7427 |
+ * @param {Object} [modifiers] |
|
7428 |
+ * @return {Function} terminalLinkFn |
|
7429 |
+ */ |
|
7430 |
+ |
|
7431 |
+ function makeTerminalNodeLinkFn(el, dirName, value, options, def, rawName, arg, modifiers) { |
|
7432 |
+ var parsed = parseDirective(value); |
|
7433 |
+ var descriptor = { |
|
7434 |
+ name: dirName, |
|
7435 |
+ arg: arg, |
|
7436 |
+ expression: parsed.expression, |
|
7437 |
+ filters: parsed.filters, |
|
7438 |
+ raw: value, |
|
7439 |
+ attr: rawName, |
|
7440 |
+ modifiers: modifiers, |
|
7441 |
+ def: def |
|
7442 |
+ }; |
|
7443 |
+ // check ref for v-for and router-view |
|
7444 |
+ if (dirName === 'for' || dirName === 'router-view') { |
|
7445 |
+ descriptor.ref = findRef(el); |
|
7446 |
+ } |
|
7447 |
+ var fn = function terminalNodeLinkFn(vm, el, host, scope, frag) { |
|
7448 |
+ if (descriptor.ref) { |
|
7449 |
+ defineReactive((scope || vm).$refs, descriptor.ref, null); |
|
7450 |
+ } |
|
7451 |
+ vm._bindDir(descriptor, el, host, scope, frag); |
|
7452 |
+ }; |
|
7453 |
+ fn.terminal = true; |
|
7454 |
+ return fn; |
|
7455 |
+ } |
|
7456 |
+ |
|
7457 |
+ /** |
|
7458 |
+ * Compile the directives on an element and return a linker. |
|
7459 |
+ * |
|
7460 |
+ * @param {Array|NamedNodeMap} attrs |
|
7461 |
+ * @param {Object} options |
|
7462 |
+ * @return {Function} |
|
7463 |
+ */ |
|
7464 |
+ |
|
7465 |
+ function compileDirectives(attrs, options) { |
|
7466 |
+ var i = attrs.length; |
|
7467 |
+ var dirs = []; |
|
7468 |
+ var attr, name, value, rawName, rawValue, dirName, arg, modifiers, dirDef, tokens, matched; |
|
7469 |
+ while (i--) { |
|
7470 |
+ attr = attrs[i]; |
|
7471 |
+ name = rawName = attr.name; |
|
7472 |
+ value = rawValue = attr.value; |
|
7473 |
+ tokens = parseText(value); |
|
7474 |
+ // reset arg |
|
7475 |
+ arg = null; |
|
7476 |
+ // check modifiers |
|
7477 |
+ modifiers = parseModifiers(name); |
|
7478 |
+ name = name.replace(modifierRE, ''); |
|
7479 |
+ |
|
7480 |
+ // attribute interpolations |
|
7481 |
+ if (tokens) { |
|
7482 |
+ value = tokensToExp(tokens); |
|
7483 |
+ arg = name; |
|
7484 |
+ pushDir('bind', directives.bind, tokens); |
|
7485 |
+ // warn against mixing mustaches with v-bind |
|
7486 |
+ if ('development' !== 'production') { |
|
7487 |
+ if (name === 'class' && Array.prototype.some.call(attrs, function (attr) { |
|
7488 |
+ return attr.name === ':class' || attr.name === 'v-bind:class'; |
|
7489 |
+ })) { |
|
7490 |
+ warn('class="' + rawValue + '": Do not mix mustache interpolation ' + 'and v-bind for "class" on the same element. Use one or the other.', options); |
|
7491 |
+ } |
|
7492 |
+ } |
|
7493 |
+ } else |
|
7494 |
+ |
|
7495 |
+ // special attribute: transition |
|
7496 |
+ if (transitionRE.test(name)) { |
|
7497 |
+ modifiers.literal = !bindRE.test(name); |
|
7498 |
+ pushDir('transition', internalDirectives.transition); |
|
7499 |
+ } else |
|
7500 |
+ |
|
7501 |
+ // event handlers |
|
7502 |
+ if (onRE.test(name)) { |
|
7503 |
+ arg = name.replace(onRE, ''); |
|
7504 |
+ pushDir('on', directives.on); |
|
7505 |
+ } else |
|
7506 |
+ |
|
7507 |
+ // attribute bindings |
|
7508 |
+ if (bindRE.test(name)) { |
|
7509 |
+ dirName = name.replace(bindRE, ''); |
|
7510 |
+ if (dirName === 'style' || dirName === 'class') { |
|
7511 |
+ pushDir(dirName, internalDirectives[dirName]); |
|
7512 |
+ } else { |
|
7513 |
+ arg = dirName; |
|
7514 |
+ pushDir('bind', directives.bind); |
|
7515 |
+ } |
|
7516 |
+ } else |
|
7517 |
+ |
|
7518 |
+ // normal directives |
|
7519 |
+ if (matched = name.match(dirAttrRE)) { |
|
7520 |
+ dirName = matched[1]; |
|
7521 |
+ arg = matched[2]; |
|
7522 |
+ |
|
7523 |
+ // skip v-else (when used with v-show) |
|
7524 |
+ if (dirName === 'else') { |
|
7525 |
+ continue; |
|
7526 |
+ } |
|
7527 |
+ |
|
7528 |
+ dirDef = resolveAsset(options, 'directives', dirName, true); |
|
7529 |
+ if (dirDef) { |
|
7530 |
+ pushDir(dirName, dirDef); |
|
7531 |
+ } |
|
7532 |
+ } |
|
7533 |
+ } |
|
7534 |
+ |
|
7535 |
+ /** |
|
7536 |
+ * Push a directive. |
|
7537 |
+ * |
|
7538 |
+ * @param {String} dirName |
|
7539 |
+ * @param {Object|Function} def |
|
7540 |
+ * @param {Array} [interpTokens] |
|
7541 |
+ */ |
|
7542 |
+ |
|
7543 |
+ function pushDir(dirName, def, interpTokens) { |
|
7544 |
+ var hasOneTimeToken = interpTokens && hasOneTime(interpTokens); |
|
7545 |
+ var parsed = !hasOneTimeToken && parseDirective(value); |
|
7546 |
+ dirs.push({ |
|
7547 |
+ name: dirName, |
|
7548 |
+ attr: rawName, |
|
7549 |
+ raw: rawValue, |
|
7550 |
+ def: def, |
|
7551 |
+ arg: arg, |
|
7552 |
+ modifiers: modifiers, |
|
7553 |
+ // conversion from interpolation strings with one-time token |
|
7554 |
+ // to expression is differed until directive bind time so that we |
|
7555 |
+ // have access to the actual vm context for one-time bindings. |
|
7556 |
+ expression: parsed && parsed.expression, |
|
7557 |
+ filters: parsed && parsed.filters, |
|
7558 |
+ interp: interpTokens, |
|
7559 |
+ hasOneTime: hasOneTimeToken |
|
7560 |
+ }); |
|
7561 |
+ } |
|
7562 |
+ |
|
7563 |
+ if (dirs.length) { |
|
7564 |
+ return makeNodeLinkFn(dirs); |
|
7565 |
+ } |
|
7566 |
+ } |
|
7567 |
+ |
|
7568 |
+ /** |
|
7569 |
+ * Parse modifiers from directive attribute name. |
|
7570 |
+ * |
|
7571 |
+ * @param {String} name |
|
7572 |
+ * @return {Object} |
|
7573 |
+ */ |
|
7574 |
+ |
|
7575 |
+ function parseModifiers(name) { |
|
7576 |
+ var res = Object.create(null); |
|
7577 |
+ var match = name.match(modifierRE); |
|
7578 |
+ if (match) { |
|
7579 |
+ var i = match.length; |
|
7580 |
+ while (i--) { |
|
7581 |
+ res[match[i].slice(1)] = true; |
|
7582 |
+ } |
|
7583 |
+ } |
|
7584 |
+ return res; |
|
7585 |
+ } |
|
7586 |
+ |
|
7587 |
+ /** |
|
7588 |
+ * Build a link function for all directives on a single node. |
|
7589 |
+ * |
|
7590 |
+ * @param {Array} directives |
|
7591 |
+ * @return {Function} directivesLinkFn |
|
7592 |
+ */ |
|
7593 |
+ |
|
7594 |
+ function makeNodeLinkFn(directives) { |
|
7595 |
+ return function nodeLinkFn(vm, el, host, scope, frag) { |
|
7596 |
+ // reverse apply because it's sorted low to high |
|
7597 |
+ var i = directives.length; |
|
7598 |
+ while (i--) { |
|
7599 |
+ vm._bindDir(directives[i], el, host, scope, frag); |
|
7600 |
+ } |
|
7601 |
+ }; |
|
7602 |
+ } |
|
7603 |
+ |
|
7604 |
+ /** |
|
7605 |
+ * Check if an interpolation string contains one-time tokens. |
|
7606 |
+ * |
|
7607 |
+ * @param {Array} tokens |
|
7608 |
+ * @return {Boolean} |
|
7609 |
+ */ |
|
7610 |
+ |
|
7611 |
+ function hasOneTime(tokens) { |
|
7612 |
+ var i = tokens.length; |
|
7613 |
+ while (i--) { |
|
7614 |
+ if (tokens[i].oneTime) return true; |
|
7615 |
+ } |
|
7616 |
+ } |
|
7617 |
+ |
|
7618 |
+ function isScript(el) { |
|
7619 |
+ return el.tagName === 'SCRIPT' && (!el.hasAttribute('type') || el.getAttribute('type') === 'text/javascript'); |
|
7620 |
+ } |
|
7621 |
+ |
|
7622 |
+ var specialCharRE = /[^\w\-:\.]/; |
|
7623 |
+ |
|
7624 |
+ /** |
|
7625 |
+ * Process an element or a DocumentFragment based on a |
|
7626 |
+ * instance option object. This allows us to transclude |
|
7627 |
+ * a template node/fragment before the instance is created, |
|
7628 |
+ * so the processed fragment can then be cloned and reused |
|
7629 |
+ * in v-for. |
|
7630 |
+ * |
|
7631 |
+ * @param {Element} el |
|
7632 |
+ * @param {Object} options |
|
7633 |
+ * @return {Element|DocumentFragment} |
|
7634 |
+ */ |
|
7635 |
+ |
|
7636 |
+ function transclude(el, options) { |
|
7637 |
+ // extract container attributes to pass them down |
|
7638 |
+ // to compiler, because they need to be compiled in |
|
7639 |
+ // parent scope. we are mutating the options object here |
|
7640 |
+ // assuming the same object will be used for compile |
|
7641 |
+ // right after this. |
|
7642 |
+ if (options) { |
|
7643 |
+ options._containerAttrs = extractAttrs(el); |
|
7644 |
+ } |
|
7645 |
+ // for template tags, what we want is its content as |
|
7646 |
+ // a documentFragment (for fragment instances) |
|
7647 |
+ if (isTemplate(el)) { |
|
7648 |
+ el = parseTemplate(el); |
|
7649 |
+ } |
|
7650 |
+ if (options) { |
|
7651 |
+ if (options._asComponent && !options.template) { |
|
7652 |
+ options.template = '<slot></slot>'; |
|
7653 |
+ } |
|
7654 |
+ if (options.template) { |
|
7655 |
+ options._content = extractContent(el); |
|
7656 |
+ el = transcludeTemplate(el, options); |
|
7657 |
+ } |
|
7658 |
+ } |
|
7659 |
+ if (isFragment(el)) { |
|
7660 |
+ // anchors for fragment instance |
|
7661 |
+ // passing in `persist: true` to avoid them being |
|
7662 |
+ // discarded by IE during template cloning |
|
7663 |
+ prepend(createAnchor('v-start', true), el); |
|
7664 |
+ el.appendChild(createAnchor('v-end', true)); |
|
7665 |
+ } |
|
7666 |
+ return el; |
|
7667 |
+ } |
|
7668 |
+ |
|
7669 |
+ /** |
|
7670 |
+ * Process the template option. |
|
7671 |
+ * If the replace option is true this will swap the $el. |
|
7672 |
+ * |
|
7673 |
+ * @param {Element} el |
|
7674 |
+ * @param {Object} options |
|
7675 |
+ * @return {Element|DocumentFragment} |
|
7676 |
+ */ |
|
7677 |
+ |
|
7678 |
+ function transcludeTemplate(el, options) { |
|
7679 |
+ var template = options.template; |
|
7680 |
+ var frag = parseTemplate(template, true); |
|
7681 |
+ if (frag) { |
|
7682 |
+ var replacer = frag.firstChild; |
|
7683 |
+ var tag = replacer.tagName && replacer.tagName.toLowerCase(); |
|
7684 |
+ if (options.replace) { |
|
7685 |
+ /* istanbul ignore if */ |
|
7686 |
+ if (el === document.body) { |
|
7687 |
+ 'development' !== 'production' && warn('You are mounting an instance with a template to ' + '<body>. This will replace <body> entirely. You ' + 'should probably use `replace: false` here.'); |
|
7688 |
+ } |
|
7689 |
+ // there are many cases where the instance must |
|
7690 |
+ // become a fragment instance: basically anything that |
|
7691 |
+ // can create more than 1 root nodes. |
|
7692 |
+ if ( |
|
7693 |
+ // multi-children template |
|
7694 |
+ frag.childNodes.length > 1 || |
|
7695 |
+ // non-element template |
|
7696 |
+ replacer.nodeType !== 1 || |
|
7697 |
+ // single nested component |
|
7698 |
+ tag === 'component' || resolveAsset(options, 'components', tag) || hasBindAttr(replacer, 'is') || |
|
7699 |
+ // element directive |
|
7700 |
+ resolveAsset(options, 'elementDirectives', tag) || |
|
7701 |
+ // for block |
|
7702 |
+ replacer.hasAttribute('v-for') || |
|
7703 |
+ // if block |
|
7704 |
+ replacer.hasAttribute('v-if')) { |
|
7705 |
+ return frag; |
|
7706 |
+ } else { |
|
7707 |
+ options._replacerAttrs = extractAttrs(replacer); |
|
7708 |
+ mergeAttrs(el, replacer); |
|
7709 |
+ return replacer; |
|
7710 |
+ } |
|
7711 |
+ } else { |
|
7712 |
+ el.appendChild(frag); |
|
7713 |
+ return el; |
|
7714 |
+ } |
|
7715 |
+ } else { |
|
7716 |
+ 'development' !== 'production' && warn('Invalid template option: ' + template); |
|
7717 |
+ } |
|
7718 |
+ } |
|
7719 |
+ |
|
7720 |
+ /** |
|
7721 |
+ * Helper to extract a component container's attributes |
|
7722 |
+ * into a plain object array. |
|
7723 |
+ * |
|
7724 |
+ * @param {Element} el |
|
7725 |
+ * @return {Array} |
|
7726 |
+ */ |
|
7727 |
+ |
|
7728 |
+ function extractAttrs(el) { |
|
7729 |
+ if (el.nodeType === 1 && el.hasAttributes()) { |
|
7730 |
+ return toArray(el.attributes); |
|
7731 |
+ } |
|
7732 |
+ } |
|
7733 |
+ |
|
7734 |
+ /** |
|
7735 |
+ * Merge the attributes of two elements, and make sure |
|
7736 |
+ * the class names are merged properly. |
|
7737 |
+ * |
|
7738 |
+ * @param {Element} from |
|
7739 |
+ * @param {Element} to |
|
7740 |
+ */ |
|
7741 |
+ |
|
7742 |
+ function mergeAttrs(from, to) { |
|
7743 |
+ var attrs = from.attributes; |
|
7744 |
+ var i = attrs.length; |
|
7745 |
+ var name, value; |
|
7746 |
+ while (i--) { |
|
7747 |
+ name = attrs[i].name; |
|
7748 |
+ value = attrs[i].value; |
|
7749 |
+ if (!to.hasAttribute(name) && !specialCharRE.test(name)) { |
|
7750 |
+ to.setAttribute(name, value); |
|
7751 |
+ } else if (name === 'class' && !parseText(value) && (value = value.trim())) { |
|
7752 |
+ value.split(/\s+/).forEach(function (cls) { |
|
7753 |
+ addClass(to, cls); |
|
7754 |
+ }); |
|
7755 |
+ } |
|
7756 |
+ } |
|
7757 |
+ } |
|
7758 |
+ |
|
7759 |
+ /** |
|
7760 |
+ * Scan and determine slot content distribution. |
|
7761 |
+ * We do this during transclusion instead at compile time so that |
|
7762 |
+ * the distribution is decoupled from the compilation order of |
|
7763 |
+ * the slots. |
|
7764 |
+ * |
|
7765 |
+ * @param {Element|DocumentFragment} template |
|
7766 |
+ * @param {Element} content |
|
7767 |
+ * @param {Vue} vm |
|
7768 |
+ */ |
|
7769 |
+ |
|
7770 |
+ function resolveSlots(vm, content) { |
|
7771 |
+ if (!content) { |
|
7772 |
+ return; |
|
7773 |
+ } |
|
7774 |
+ var contents = vm._slotContents = Object.create(null); |
|
7775 |
+ var el, name; |
|
7776 |
+ for (var i = 0, l = content.children.length; i < l; i++) { |
|
7777 |
+ el = content.children[i]; |
|
7778 |
+ /* eslint-disable no-cond-assign */ |
|
7779 |
+ if (name = el.getAttribute('slot')) { |
|
7780 |
+ (contents[name] || (contents[name] = [])).push(el); |
|
7781 |
+ } |
|
7782 |
+ /* eslint-enable no-cond-assign */ |
|
7783 |
+ if ('development' !== 'production' && getBindAttr(el, 'slot')) { |
|
7784 |
+ warn('The "slot" attribute must be static.', vm.$parent); |
|
7785 |
+ } |
|
7786 |
+ } |
|
7787 |
+ for (name in contents) { |
|
7788 |
+ contents[name] = extractFragment(contents[name], content); |
|
7789 |
+ } |
|
7790 |
+ if (content.hasChildNodes()) { |
|
7791 |
+ var nodes = content.childNodes; |
|
7792 |
+ if (nodes.length === 1 && nodes[0].nodeType === 3 && !nodes[0].data.trim()) { |
|
7793 |
+ return; |
|
7794 |
+ } |
|
7795 |
+ contents['default'] = extractFragment(content.childNodes, content); |
|
7796 |
+ } |
|
7797 |
+ } |
|
7798 |
+ |
|
7799 |
+ /** |
|
7800 |
+ * Extract qualified content nodes from a node list. |
|
7801 |
+ * |
|
7802 |
+ * @param {NodeList} nodes |
|
7803 |
+ * @return {DocumentFragment} |
|
7804 |
+ */ |
|
7805 |
+ |
|
7806 |
+ function extractFragment(nodes, parent) { |
|
7807 |
+ var frag = document.createDocumentFragment(); |
|
7808 |
+ nodes = toArray(nodes); |
|
7809 |
+ for (var i = 0, l = nodes.length; i < l; i++) { |
|
7810 |
+ var node = nodes[i]; |
|
7811 |
+ if (isTemplate(node) && !node.hasAttribute('v-if') && !node.hasAttribute('v-for')) { |
|
7812 |
+ parent.removeChild(node); |
|
7813 |
+ node = parseTemplate(node, true); |
|
7814 |
+ } |
|
7815 |
+ frag.appendChild(node); |
|
7816 |
+ } |
|
7817 |
+ return frag; |
|
7818 |
+ } |
|
7819 |
+ |
|
7820 |
+ |
|
7821 |
+ |
|
7822 |
+ var compiler = Object.freeze({ |
|
7823 |
+ compile: compile, |
|
7824 |
+ compileAndLinkProps: compileAndLinkProps, |
|
7825 |
+ compileRoot: compileRoot, |
|
7826 |
+ transclude: transclude, |
|
7827 |
+ resolveSlots: resolveSlots |
|
7828 |
+ }); |
|
7829 |
+ |
|
7830 |
+ function stateMixin (Vue) { |
|
7831 |
+ /** |
|
7832 |
+ * Accessor for `$data` property, since setting $data |
|
7833 |
+ * requires observing the new object and updating |
|
7834 |
+ * proxied properties. |
|
7835 |
+ */ |
|
7836 |
+ |
|
7837 |
+ Object.defineProperty(Vue.prototype, '$data', { |
|
7838 |
+ get: function get() { |
|
7839 |
+ return this._data; |
|
7840 |
+ }, |
|
7841 |
+ set: function set(newData) { |
|
7842 |
+ if (newData !== this._data) { |
|
7843 |
+ this._setData(newData); |
|
7844 |
+ } |
|
7845 |
+ } |
|
7846 |
+ }); |
|
7847 |
+ |
|
7848 |
+ /** |
|
7849 |
+ * Setup the scope of an instance, which contains: |
|
7850 |
+ * - observed data |
|
7851 |
+ * - computed properties |
|
7852 |
+ * - user methods |
|
7853 |
+ * - meta properties |
|
7854 |
+ */ |
|
7855 |
+ |
|
7856 |
+ Vue.prototype._initState = function () { |
|
7857 |
+ this._initProps(); |
|
7858 |
+ this._initMeta(); |
|
7859 |
+ this._initMethods(); |
|
7860 |
+ this._initData(); |
|
7861 |
+ this._initComputed(); |
|
7862 |
+ }; |
|
7863 |
+ |
|
7864 |
+ /** |
|
7865 |
+ * Initialize props. |
|
7866 |
+ */ |
|
7867 |
+ |
|
7868 |
+ Vue.prototype._initProps = function () { |
|
7869 |
+ var options = this.$options; |
|
7870 |
+ var el = options.el; |
|
7871 |
+ var props = options.props; |
|
7872 |
+ if (props && !el) { |
|
7873 |
+ 'development' !== 'production' && warn('Props will not be compiled if no `el` option is ' + 'provided at instantiation.', this); |
|
7874 |
+ } |
|
7875 |
+ // make sure to convert string selectors into element now |
|
7876 |
+ el = options.el = query(el); |
|
7877 |
+ this._propsUnlinkFn = el && el.nodeType === 1 && props |
|
7878 |
+ // props must be linked in proper scope if inside v-for |
|
7879 |
+ ? compileAndLinkProps(this, el, props, this._scope) : null; |
|
7880 |
+ }; |
|
7881 |
+ |
|
7882 |
+ /** |
|
7883 |
+ * Initialize the data. |
|
7884 |
+ */ |
|
7885 |
+ |
|
7886 |
+ Vue.prototype._initData = function () { |
|
7887 |
+ var dataFn = this.$options.data; |
|
7888 |
+ var data = this._data = dataFn ? dataFn() : {}; |
|
7889 |
+ if (!isPlainObject(data)) { |
|
7890 |
+ data = {}; |
|
7891 |
+ 'development' !== 'production' && warn('data functions should return an object.', this); |
|
7892 |
+ } |
|
7893 |
+ var props = this._props; |
|
7894 |
+ // proxy data on instance |
|
7895 |
+ var keys = Object.keys(data); |
|
7896 |
+ var i, key; |
|
7897 |
+ i = keys.length; |
|
7898 |
+ while (i--) { |
|
7899 |
+ key = keys[i]; |
|
7900 |
+ // there are two scenarios where we can proxy a data key: |
|
7901 |
+ // 1. it's not already defined as a prop |
|
7902 |
+ // 2. it's provided via a instantiation option AND there are no |
|
7903 |
+ // template prop present |
|
7904 |
+ if (!props || !hasOwn(props, key)) { |
|
7905 |
+ this._proxy(key); |
|
7906 |
+ } else if ('development' !== 'production') { |
|
7907 |
+ warn('Data field "' + key + '" is already defined ' + 'as a prop. To provide default value for a prop, use the "default" ' + 'prop option; if you want to pass prop values to an instantiation ' + 'call, use the "propsData" option.', this); |
|
7908 |
+ } |
|
7909 |
+ } |
|
7910 |
+ // observe data |
|
7911 |
+ observe(data, this); |
|
7912 |
+ }; |
|
7913 |
+ |
|
7914 |
+ /** |
|
7915 |
+ * Swap the instance's $data. Called in $data's setter. |
|
7916 |
+ * |
|
7917 |
+ * @param {Object} newData |
|
7918 |
+ */ |
|
7919 |
+ |
|
7920 |
+ Vue.prototype._setData = function (newData) { |
|
7921 |
+ newData = newData || {}; |
|
7922 |
+ var oldData = this._data; |
|
7923 |
+ this._data = newData; |
|
7924 |
+ var keys, key, i; |
|
7925 |
+ // unproxy keys not present in new data |
|
7926 |
+ keys = Object.keys(oldData); |
|
7927 |
+ i = keys.length; |
|
7928 |
+ while (i--) { |
|
7929 |
+ key = keys[i]; |
|
7930 |
+ if (!(key in newData)) { |
|
7931 |
+ this._unproxy(key); |
|
7932 |
+ } |
|
7933 |
+ } |
|
7934 |
+ // proxy keys not already proxied, |
|
7935 |
+ // and trigger change for changed values |
|
7936 |
+ keys = Object.keys(newData); |
|
7937 |
+ i = keys.length; |
|
7938 |
+ while (i--) { |
|
7939 |
+ key = keys[i]; |
|
7940 |
+ if (!hasOwn(this, key)) { |
|
7941 |
+ // new property |
|
7942 |
+ this._proxy(key); |
|
7943 |
+ } |
|
7944 |
+ } |
|
7945 |
+ oldData.__ob__.removeVm(this); |
|
7946 |
+ observe(newData, this); |
|
7947 |
+ this._digest(); |
|
7948 |
+ }; |
|
7949 |
+ |
|
7950 |
+ /** |
|
7951 |
+ * Proxy a property, so that |
|
7952 |
+ * vm.prop === vm._data.prop |
|
7953 |
+ * |
|
7954 |
+ * @param {String} key |
|
7955 |
+ */ |
|
7956 |
+ |
|
7957 |
+ Vue.prototype._proxy = function (key) { |
|
7958 |
+ if (!isReserved(key)) { |
|
7959 |
+ // need to store ref to self here |
|
7960 |
+ // because these getter/setters might |
|
7961 |
+ // be called by child scopes via |
|
7962 |
+ // prototype inheritance. |
|
7963 |
+ var self = this; |
|
7964 |
+ Object.defineProperty(self, key, { |
|
7965 |
+ configurable: true, |
|
7966 |
+ enumerable: true, |
|
7967 |
+ get: function proxyGetter() { |
|
7968 |
+ return self._data[key]; |
|
7969 |
+ }, |
|
7970 |
+ set: function proxySetter(val) { |
|
7971 |
+ self._data[key] = val; |
|
7972 |
+ } |
|
7973 |
+ }); |
|
7974 |
+ } |
|
7975 |
+ }; |
|
7976 |
+ |
|
7977 |
+ /** |
|
7978 |
+ * Unproxy a property. |
|
7979 |
+ * |
|
7980 |
+ * @param {String} key |
|
7981 |
+ */ |
|
7982 |
+ |
|
7983 |
+ Vue.prototype._unproxy = function (key) { |
|
7984 |
+ if (!isReserved(key)) { |
|
7985 |
+ delete this[key]; |
|
7986 |
+ } |
|
7987 |
+ }; |
|
7988 |
+ |
|
7989 |
+ /** |
|
7990 |
+ * Force update on every watcher in scope. |
|
7991 |
+ */ |
|
7992 |
+ |
|
7993 |
+ Vue.prototype._digest = function () { |
|
7994 |
+ for (var i = 0, l = this._watchers.length; i < l; i++) { |
|
7995 |
+ this._watchers[i].update(true); // shallow updates |
|
7996 |
+ } |
|
7997 |
+ }; |
|
7998 |
+ |
|
7999 |
+ /** |
|
8000 |
+ * Setup computed properties. They are essentially |
|
8001 |
+ * special getter/setters |
|
8002 |
+ */ |
|
8003 |
+ |
|
8004 |
+ function noop() {} |
|
8005 |
+ Vue.prototype._initComputed = function () { |
|
8006 |
+ var computed = this.$options.computed; |
|
8007 |
+ if (computed) { |
|
8008 |
+ for (var key in computed) { |
|
8009 |
+ var userDef = computed[key]; |
|
8010 |
+ var def = { |
|
8011 |
+ enumerable: true, |
|
8012 |
+ configurable: true |
|
8013 |
+ }; |
|
8014 |
+ if (typeof userDef === 'function') { |
|
8015 |
+ def.get = makeComputedGetter(userDef, this); |
|
8016 |
+ def.set = noop; |
|
8017 |
+ } else { |
|
8018 |
+ def.get = userDef.get ? userDef.cache !== false ? makeComputedGetter(userDef.get, this) : bind(userDef.get, this) : noop; |
|
8019 |
+ def.set = userDef.set ? bind(userDef.set, this) : noop; |
|
8020 |
+ } |
|
8021 |
+ Object.defineProperty(this, key, def); |
|
8022 |
+ } |
|
8023 |
+ } |
|
8024 |
+ }; |
|
8025 |
+ |
|
8026 |
+ function makeComputedGetter(getter, owner) { |
|
8027 |
+ var watcher = new Watcher(owner, getter, null, { |
|
8028 |
+ lazy: true |
|
8029 |
+ }); |
|
8030 |
+ return function computedGetter() { |
|
8031 |
+ if (watcher.dirty) { |
|
8032 |
+ watcher.evaluate(); |
|
8033 |
+ } |
|
8034 |
+ if (Dep.target) { |
|
8035 |
+ watcher.depend(); |
|
8036 |
+ } |
|
8037 |
+ return watcher.value; |
|
8038 |
+ }; |
|
8039 |
+ } |
|
8040 |
+ |
|
8041 |
+ /** |
|
8042 |
+ * Setup instance methods. Methods must be bound to the |
|
8043 |
+ * instance since they might be passed down as a prop to |
|
8044 |
+ * child components. |
|
8045 |
+ */ |
|
8046 |
+ |
|
8047 |
+ Vue.prototype._initMethods = function () { |
|
8048 |
+ var methods = this.$options.methods; |
|
8049 |
+ if (methods) { |
|
8050 |
+ for (var key in methods) { |
|
8051 |
+ this[key] = bind(methods[key], this); |
|
8052 |
+ } |
|
8053 |
+ } |
|
8054 |
+ }; |
|
8055 |
+ |
|
8056 |
+ /** |
|
8057 |
+ * Initialize meta information like $index, $key & $value. |
|
8058 |
+ */ |
|
8059 |
+ |
|
8060 |
+ Vue.prototype._initMeta = function () { |
|
8061 |
+ var metas = this.$options._meta; |
|
8062 |
+ if (metas) { |
|
8063 |
+ for (var key in metas) { |
|
8064 |
+ defineReactive(this, key, metas[key]); |
|
8065 |
+ } |
|
8066 |
+ } |
|
8067 |
+ }; |
|
8068 |
+ } |
|
8069 |
+ |
|
8070 |
+ var eventRE = /^v-on:|^@/; |
|
8071 |
+ |
|
8072 |
+ function eventsMixin (Vue) { |
|
8073 |
+ /** |
|
8074 |
+ * Setup the instance's option events & watchers. |
|
8075 |
+ * If the value is a string, we pull it from the |
|
8076 |
+ * instance's methods by name. |
|
8077 |
+ */ |
|
8078 |
+ |
|
8079 |
+ Vue.prototype._initEvents = function () { |
|
8080 |
+ var options = this.$options; |
|
8081 |
+ if (options._asComponent) { |
|
8082 |
+ registerComponentEvents(this, options.el); |
|
8083 |
+ } |
|
8084 |
+ registerCallbacks(this, '$on', options.events); |
|
8085 |
+ registerCallbacks(this, '$watch', options.watch); |
|
8086 |
+ }; |
|
8087 |
+ |
|
8088 |
+ /** |
|
8089 |
+ * Register v-on events on a child component |
|
8090 |
+ * |
|
8091 |
+ * @param {Vue} vm |
|
8092 |
+ * @param {Element} el |
|
8093 |
+ */ |
|
8094 |
+ |
|
8095 |
+ function registerComponentEvents(vm, el) { |
|
8096 |
+ var attrs = el.attributes; |
|
8097 |
+ var name, value, handler; |
|
8098 |
+ for (var i = 0, l = attrs.length; i < l; i++) { |
|
8099 |
+ name = attrs[i].name; |
|
8100 |
+ if (eventRE.test(name)) { |
|
8101 |
+ name = name.replace(eventRE, ''); |
|
8102 |
+ // force the expression into a statement so that |
|
8103 |
+ // it always dynamically resolves the method to call (#2670) |
|
8104 |
+ // kinda ugly hack, but does the job. |
|
8105 |
+ value = attrs[i].value; |
|
8106 |
+ if (isSimplePath(value)) { |
|
8107 |
+ value += '.apply(this, $arguments)'; |
|
8108 |
+ } |
|
8109 |
+ handler = (vm._scope || vm._context).$eval(value, true); |
|
8110 |
+ handler._fromParent = true; |
|
8111 |
+ vm.$on(name.replace(eventRE), handler); |
|
8112 |
+ } |
|
8113 |
+ } |
|
8114 |
+ } |
|
8115 |
+ |
|
8116 |
+ /** |
|
8117 |
+ * Register callbacks for option events and watchers. |
|
8118 |
+ * |
|
8119 |
+ * @param {Vue} vm |
|
8120 |
+ * @param {String} action |
|
8121 |
+ * @param {Object} hash |
|
8122 |
+ */ |
|
8123 |
+ |
|
8124 |
+ function registerCallbacks(vm, action, hash) { |
|
8125 |
+ if (!hash) return; |
|
8126 |
+ var handlers, key, i, j; |
|
8127 |
+ for (key in hash) { |
|
8128 |
+ handlers = hash[key]; |
|
8129 |
+ if (isArray(handlers)) { |
|
8130 |
+ for (i = 0, j = handlers.length; i < j; i++) { |
|
8131 |
+ register(vm, action, key, handlers[i]); |
|
8132 |
+ } |
|
8133 |
+ } else { |
|
8134 |
+ register(vm, action, key, handlers); |
|
8135 |
+ } |
|
8136 |
+ } |
|
8137 |
+ } |
|
8138 |
+ |
|
8139 |
+ /** |
|
8140 |
+ * Helper to register an event/watch callback. |
|
8141 |
+ * |
|
8142 |
+ * @param {Vue} vm |
|
8143 |
+ * @param {String} action |
|
8144 |
+ * @param {String} key |
|
8145 |
+ * @param {Function|String|Object} handler |
|
8146 |
+ * @param {Object} [options] |
|
8147 |
+ */ |
|
8148 |
+ |
|
8149 |
+ function register(vm, action, key, handler, options) { |
|
8150 |
+ var type = typeof handler; |
|
8151 |
+ if (type === 'function') { |
|
8152 |
+ vm[action](key, handler, options); |
|
8153 |
+ } else if (type === 'string') { |
|
8154 |
+ var methods = vm.$options.methods; |
|
8155 |
+ var method = methods && methods[handler]; |
|
8156 |
+ if (method) { |
|
8157 |
+ vm[action](key, method, options); |
|
8158 |
+ } else { |
|
8159 |
+ 'development' !== 'production' && warn('Unknown method: "' + handler + '" when ' + 'registering callback for ' + action + ': "' + key + '".', vm); |
|
8160 |
+ } |
|
8161 |
+ } else if (handler && type === 'object') { |
|
8162 |
+ register(vm, action, key, handler.handler, handler); |
|
8163 |
+ } |
|
8164 |
+ } |
|
8165 |
+ |
|
8166 |
+ /** |
|
8167 |
+ * Setup recursive attached/detached calls |
|
8168 |
+ */ |
|
8169 |
+ |
|
8170 |
+ Vue.prototype._initDOMHooks = function () { |
|
8171 |
+ this.$on('hook:attached', onAttached); |
|
8172 |
+ this.$on('hook:detached', onDetached); |
|
8173 |
+ }; |
|
8174 |
+ |
|
8175 |
+ /** |
|
8176 |
+ * Callback to recursively call attached hook on children |
|
8177 |
+ */ |
|
8178 |
+ |
|
8179 |
+ function onAttached() { |
|
8180 |
+ if (!this._isAttached) { |
|
8181 |
+ this._isAttached = true; |
|
8182 |
+ this.$children.forEach(callAttach); |
|
8183 |
+ } |
|
8184 |
+ } |
|
8185 |
+ |
|
8186 |
+ /** |
|
8187 |
+ * Iterator to call attached hook |
|
8188 |
+ * |
|
8189 |
+ * @param {Vue} child |
|
8190 |
+ */ |
|
8191 |
+ |
|
8192 |
+ function callAttach(child) { |
|
8193 |
+ if (!child._isAttached && inDoc(child.$el)) { |
|
8194 |
+ child._callHook('attached'); |
|
8195 |
+ } |
|
8196 |
+ } |
|
8197 |
+ |
|
8198 |
+ /** |
|
8199 |
+ * Callback to recursively call detached hook on children |
|
8200 |
+ */ |
|
8201 |
+ |
|
8202 |
+ function onDetached() { |
|
8203 |
+ if (this._isAttached) { |
|
8204 |
+ this._isAttached = false; |
|
8205 |
+ this.$children.forEach(callDetach); |
|
8206 |
+ } |
|
8207 |
+ } |
|
8208 |
+ |
|
8209 |
+ /** |
|
8210 |
+ * Iterator to call detached hook |
|
8211 |
+ * |
|
8212 |
+ * @param {Vue} child |
|
8213 |
+ */ |
|
8214 |
+ |
|
8215 |
+ function callDetach(child) { |
|
8216 |
+ if (child._isAttached && !inDoc(child.$el)) { |
|
8217 |
+ child._callHook('detached'); |
|
8218 |
+ } |
|
8219 |
+ } |
|
8220 |
+ |
|
8221 |
+ /** |
|
8222 |
+ * Trigger all handlers for a hook |
|
8223 |
+ * |
|
8224 |
+ * @param {String} hook |
|
8225 |
+ */ |
|
8226 |
+ |
|
8227 |
+ Vue.prototype._callHook = function (hook) { |
|
8228 |
+ this.$emit('pre-hook:' + hook); |
|
8229 |
+ var handlers = this.$options[hook]; |
|
8230 |
+ if (handlers) { |
|
8231 |
+ for (var i = 0, j = handlers.length; i < j; i++) { |
|
8232 |
+ handlers[i].call(this); |
|
8233 |
+ } |
|
8234 |
+ } |
|
8235 |
+ this.$emit('hook:' + hook); |
|
8236 |
+ }; |
|
8237 |
+ } |
|
8238 |
+ |
|
8239 |
+ function noop$1() {} |
|
8240 |
+ |
|
8241 |
+ /** |
|
8242 |
+ * A directive links a DOM element with a piece of data, |
|
8243 |
+ * which is the result of evaluating an expression. |
|
8244 |
+ * It registers a watcher with the expression and calls |
|
8245 |
+ * the DOM update function when a change is triggered. |
|
8246 |
+ * |
|
8247 |
+ * @param {Object} descriptor |
|
8248 |
+ * - {String} name |
|
8249 |
+ * - {Object} def |
|
8250 |
+ * - {String} expression |
|
8251 |
+ * - {Array<Object>} [filters] |
|
8252 |
+ * - {Object} [modifiers] |
|
8253 |
+ * - {Boolean} literal |
|
8254 |
+ * - {String} attr |
|
8255 |
+ * - {String} arg |
|
8256 |
+ * - {String} raw |
|
8257 |
+ * - {String} [ref] |
|
8258 |
+ * - {Array<Object>} [interp] |
|
8259 |
+ * - {Boolean} [hasOneTime] |
|
8260 |
+ * @param {Vue} vm |
|
8261 |
+ * @param {Node} el |
|
8262 |
+ * @param {Vue} [host] - transclusion host component |
|
8263 |
+ * @param {Object} [scope] - v-for scope |
|
8264 |
+ * @param {Fragment} [frag] - owner fragment |
|
8265 |
+ * @constructor |
|
8266 |
+ */ |
|
8267 |
+ function Directive(descriptor, vm, el, host, scope, frag) { |
|
8268 |
+ this.vm = vm; |
|
8269 |
+ this.el = el; |
|
8270 |
+ // copy descriptor properties |
|
8271 |
+ this.descriptor = descriptor; |
|
8272 |
+ this.name = descriptor.name; |
|
8273 |
+ this.expression = descriptor.expression; |
|
8274 |
+ this.arg = descriptor.arg; |
|
8275 |
+ this.modifiers = descriptor.modifiers; |
|
8276 |
+ this.filters = descriptor.filters; |
|
8277 |
+ this.literal = this.modifiers && this.modifiers.literal; |
|
8278 |
+ // private |
|
8279 |
+ this._locked = false; |
|
8280 |
+ this._bound = false; |
|
8281 |
+ this._listeners = null; |
|
8282 |
+ // link context |
|
8283 |
+ this._host = host; |
|
8284 |
+ this._scope = scope; |
|
8285 |
+ this._frag = frag; |
|
8286 |
+ // store directives on node in dev mode |
|
8287 |
+ if ('development' !== 'production' && this.el) { |
|
8288 |
+ this.el._vue_directives = this.el._vue_directives || []; |
|
8289 |
+ this.el._vue_directives.push(this); |
|
8290 |
+ } |
|
8291 |
+ } |
|
8292 |
+ |
|
8293 |
+ /** |
|
8294 |
+ * Initialize the directive, mixin definition properties, |
|
8295 |
+ * setup the watcher, call definition bind() and update() |
|
8296 |
+ * if present. |
|
8297 |
+ */ |
|
8298 |
+ |
|
8299 |
+ Directive.prototype._bind = function () { |
|
8300 |
+ var name = this.name; |
|
8301 |
+ var descriptor = this.descriptor; |
|
8302 |
+ |
|
8303 |
+ // remove attribute |
|
8304 |
+ if ((name !== 'cloak' || this.vm._isCompiled) && this.el && this.el.removeAttribute) { |
|
8305 |
+ var attr = descriptor.attr || 'v-' + name; |
|
8306 |
+ this.el.removeAttribute(attr); |
|
8307 |
+ } |
|
8308 |
+ |
|
8309 |
+ // copy def properties |
|
8310 |
+ var def = descriptor.def; |
|
8311 |
+ if (typeof def === 'function') { |
|
8312 |
+ this.update = def; |
|
8313 |
+ } else { |
|
8314 |
+ extend(this, def); |
|
8315 |
+ } |
|
8316 |
+ |
|
8317 |
+ // setup directive params |
|
8318 |
+ this._setupParams(); |
|
8319 |
+ |
|
8320 |
+ // initial bind |
|
8321 |
+ if (this.bind) { |
|
8322 |
+ this.bind(); |
|
8323 |
+ } |
|
8324 |
+ this._bound = true; |
|
8325 |
+ |
|
8326 |
+ if (this.literal) { |
|
8327 |
+ this.update && this.update(descriptor.raw); |
|
8328 |
+ } else if ((this.expression || this.modifiers) && (this.update || this.twoWay) && !this._checkStatement()) { |
|
8329 |
+ // wrapped updater for context |
|
8330 |
+ var dir = this; |
|
8331 |
+ if (this.update) { |
|
8332 |
+ this._update = function (val, oldVal) { |
|
8333 |
+ if (!dir._locked) { |
|
8334 |
+ dir.update(val, oldVal); |
|
8335 |
+ } |
|
8336 |
+ }; |
|
8337 |
+ } else { |
|
8338 |
+ this._update = noop$1; |
|
8339 |
+ } |
|
8340 |
+ var preProcess = this._preProcess ? bind(this._preProcess, this) : null; |
|
8341 |
+ var postProcess = this._postProcess ? bind(this._postProcess, this) : null; |
|
8342 |
+ var watcher = this._watcher = new Watcher(this.vm, this.expression, this._update, // callback |
|
8343 |
+ { |
|
8344 |
+ filters: this.filters, |
|
8345 |
+ twoWay: this.twoWay, |
|
8346 |
+ deep: this.deep, |
|
8347 |
+ preProcess: preProcess, |
|
8348 |
+ postProcess: postProcess, |
|
8349 |
+ scope: this._scope |
|
8350 |
+ }); |
|
8351 |
+ // v-model with inital inline value need to sync back to |
|
8352 |
+ // model instead of update to DOM on init. They would |
|
8353 |
+ // set the afterBind hook to indicate that. |
|
8354 |
+ if (this.afterBind) { |
|
8355 |
+ this.afterBind(); |
|
8356 |
+ } else if (this.update) { |
|
8357 |
+ this.update(watcher.value); |
|
8358 |
+ } |
|
8359 |
+ } |
|
8360 |
+ }; |
|
8361 |
+ |
|
8362 |
+ /** |
|
8363 |
+ * Setup all param attributes, e.g. track-by, |
|
8364 |
+ * transition-mode, etc... |
|
8365 |
+ */ |
|
8366 |
+ |
|
8367 |
+ Directive.prototype._setupParams = function () { |
|
8368 |
+ if (!this.params) { |
|
8369 |
+ return; |
|
8370 |
+ } |
|
8371 |
+ var params = this.params; |
|
8372 |
+ // swap the params array with a fresh object. |
|
8373 |
+ this.params = Object.create(null); |
|
8374 |
+ var i = params.length; |
|
8375 |
+ var key, val, mappedKey; |
|
8376 |
+ while (i--) { |
|
8377 |
+ key = hyphenate(params[i]); |
|
8378 |
+ mappedKey = camelize(key); |
|
8379 |
+ val = getBindAttr(this.el, key); |
|
8380 |
+ if (val != null) { |
|
8381 |
+ // dynamic |
|
8382 |
+ this._setupParamWatcher(mappedKey, val); |
|
8383 |
+ } else { |
|
8384 |
+ // static |
|
8385 |
+ val = getAttr(this.el, key); |
|
8386 |
+ if (val != null) { |
|
8387 |
+ this.params[mappedKey] = val === '' ? true : val; |
|
8388 |
+ } |
|
8389 |
+ } |
|
8390 |
+ } |
|
8391 |
+ }; |
|
8392 |
+ |
|
8393 |
+ /** |
|
8394 |
+ * Setup a watcher for a dynamic param. |
|
8395 |
+ * |
|
8396 |
+ * @param {String} key |
|
8397 |
+ * @param {String} expression |
|
8398 |
+ */ |
|
8399 |
+ |
|
8400 |
+ Directive.prototype._setupParamWatcher = function (key, expression) { |
|
8401 |
+ var self = this; |
|
8402 |
+ var called = false; |
|
8403 |
+ var unwatch = (this._scope || this.vm).$watch(expression, function (val, oldVal) { |
|
8404 |
+ self.params[key] = val; |
|
8405 |
+ // since we are in immediate mode, |
|
8406 |
+ // only call the param change callbacks if this is not the first update. |
|
8407 |
+ if (called) { |
|
8408 |
+ var cb = self.paramWatchers && self.paramWatchers[key]; |
|
8409 |
+ if (cb) { |
|
8410 |
+ cb.call(self, val, oldVal); |
|
8411 |
+ } |
|
8412 |
+ } else { |
|
8413 |
+ called = true; |
|
8414 |
+ } |
|
8415 |
+ }, { |
|
8416 |
+ immediate: true, |
|
8417 |
+ user: false |
|
8418 |
+ });(this._paramUnwatchFns || (this._paramUnwatchFns = [])).push(unwatch); |
|
8419 |
+ }; |
|
8420 |
+ |
|
8421 |
+ /** |
|
8422 |
+ * Check if the directive is a function caller |
|
8423 |
+ * and if the expression is a callable one. If both true, |
|
8424 |
+ * we wrap up the expression and use it as the event |
|
8425 |
+ * handler. |
|
8426 |
+ * |
|
8427 |
+ * e.g. on-click="a++" |
|
8428 |
+ * |
|
8429 |
+ * @return {Boolean} |
|
8430 |
+ */ |
|
8431 |
+ |
|
8432 |
+ Directive.prototype._checkStatement = function () { |
|
8433 |
+ var expression = this.expression; |
|
8434 |
+ if (expression && this.acceptStatement && !isSimplePath(expression)) { |
|
8435 |
+ var fn = parseExpression(expression).get; |
|
8436 |
+ var scope = this._scope || this.vm; |
|
8437 |
+ var handler = function handler(e) { |
|
8438 |
+ scope.$event = e; |
|
8439 |
+ fn.call(scope, scope); |
|
8440 |
+ scope.$event = null; |
|
8441 |
+ }; |
|
8442 |
+ if (this.filters) { |
|
8443 |
+ handler = scope._applyFilters(handler, null, this.filters); |
|
8444 |
+ } |
|
8445 |
+ this.update(handler); |
|
8446 |
+ return true; |
|
8447 |
+ } |
|
8448 |
+ }; |
|
8449 |
+ |
|
8450 |
+ /** |
|
8451 |
+ * Set the corresponding value with the setter. |
|
8452 |
+ * This should only be used in two-way directives |
|
8453 |
+ * e.g. v-model. |
|
8454 |
+ * |
|
8455 |
+ * @param {*} value |
|
8456 |
+ * @public |
|
8457 |
+ */ |
|
8458 |
+ |
|
8459 |
+ Directive.prototype.set = function (value) { |
|
8460 |
+ /* istanbul ignore else */ |
|
8461 |
+ if (this.twoWay) { |
|
8462 |
+ this._withLock(function () { |
|
8463 |
+ this._watcher.set(value); |
|
8464 |
+ }); |
|
8465 |
+ } else if ('development' !== 'production') { |
|
8466 |
+ warn('Directive.set() can only be used inside twoWay' + 'directives.'); |
|
8467 |
+ } |
|
8468 |
+ }; |
|
8469 |
+ |
|
8470 |
+ /** |
|
8471 |
+ * Execute a function while preventing that function from |
|
8472 |
+ * triggering updates on this directive instance. |
|
8473 |
+ * |
|
8474 |
+ * @param {Function} fn |
|
8475 |
+ */ |
|
8476 |
+ |
|
8477 |
+ Directive.prototype._withLock = function (fn) { |
|
8478 |
+ var self = this; |
|
8479 |
+ self._locked = true; |
|
8480 |
+ fn.call(self); |
|
8481 |
+ nextTick(function () { |
|
8482 |
+ self._locked = false; |
|
8483 |
+ }); |
|
8484 |
+ }; |
|
8485 |
+ |
|
8486 |
+ /** |
|
8487 |
+ * Convenience method that attaches a DOM event listener |
|
8488 |
+ * to the directive element and autometically tears it down |
|
8489 |
+ * during unbind. |
|
8490 |
+ * |
|
8491 |
+ * @param {String} event |
|
8492 |
+ * @param {Function} handler |
|
8493 |
+ * @param {Boolean} [useCapture] |
|
8494 |
+ */ |
|
8495 |
+ |
|
8496 |
+ Directive.prototype.on = function (event, handler, useCapture) { |
|
8497 |
+ on(this.el, event, handler, useCapture);(this._listeners || (this._listeners = [])).push([event, handler]); |
|
8498 |
+ }; |
|
8499 |
+ |
|
8500 |
+ /** |
|
8501 |
+ * Teardown the watcher and call unbind. |
|
8502 |
+ */ |
|
8503 |
+ |
|
8504 |
+ Directive.prototype._teardown = function () { |
|
8505 |
+ if (this._bound) { |
|
8506 |
+ this._bound = false; |
|
8507 |
+ if (this.unbind) { |
|
8508 |
+ this.unbind(); |
|
8509 |
+ } |
|
8510 |
+ if (this._watcher) { |
|
8511 |
+ this._watcher.teardown(); |
|
8512 |
+ } |
|
8513 |
+ var listeners = this._listeners; |
|
8514 |
+ var i; |
|
8515 |
+ if (listeners) { |
|
8516 |
+ i = listeners.length; |
|
8517 |
+ while (i--) { |
|
8518 |
+ off(this.el, listeners[i][0], listeners[i][1]); |
|
8519 |
+ } |
|
8520 |
+ } |
|
8521 |
+ var unwatchFns = this._paramUnwatchFns; |
|
8522 |
+ if (unwatchFns) { |
|
8523 |
+ i = unwatchFns.length; |
|
8524 |
+ while (i--) { |
|
8525 |
+ unwatchFns[i](); |
|
8526 |
+ } |
|
8527 |
+ } |
|
8528 |
+ if ('development' !== 'production' && this.el) { |
|
8529 |
+ this.el._vue_directives.$remove(this); |
|
8530 |
+ } |
|
8531 |
+ this.vm = this.el = this._watcher = this._listeners = null; |
|
8532 |
+ } |
|
8533 |
+ }; |
|
8534 |
+ |
|
8535 |
+ function lifecycleMixin (Vue) { |
|
8536 |
+ /** |
|
8537 |
+ * Update v-ref for component. |
|
8538 |
+ * |
|
8539 |
+ * @param {Boolean} remove |
|
8540 |
+ */ |
|
8541 |
+ |
|
8542 |
+ Vue.prototype._updateRef = function (remove) { |
|
8543 |
+ var ref = this.$options._ref; |
|
8544 |
+ if (ref) { |
|
8545 |
+ var refs = (this._scope || this._context).$refs; |
|
8546 |
+ if (remove) { |
|
8547 |
+ if (refs[ref] === this) { |
|
8548 |
+ refs[ref] = null; |
|
8549 |
+ } |
|
8550 |
+ } else { |
|
8551 |
+ refs[ref] = this; |
|
8552 |
+ } |
|
8553 |
+ } |
|
8554 |
+ }; |
|
8555 |
+ |
|
8556 |
+ /** |
|
8557 |
+ * Transclude, compile and link element. |
|
8558 |
+ * |
|
8559 |
+ * If a pre-compiled linker is available, that means the |
|
8560 |
+ * passed in element will be pre-transcluded and compiled |
|
8561 |
+ * as well - all we need to do is to call the linker. |
|
8562 |
+ * |
|
8563 |
+ * Otherwise we need to call transclude/compile/link here. |
|
8564 |
+ * |
|
8565 |
+ * @param {Element} el |
|
8566 |
+ */ |
|
8567 |
+ |
|
8568 |
+ Vue.prototype._compile = function (el) { |
|
8569 |
+ var options = this.$options; |
|
8570 |
+ |
|
8571 |
+ // transclude and init element |
|
8572 |
+ // transclude can potentially replace original |
|
8573 |
+ // so we need to keep reference; this step also injects |
|
8574 |
+ // the template and caches the original attributes |
|
8575 |
+ // on the container node and replacer node. |
|
8576 |
+ var original = el; |
|
8577 |
+ el = transclude(el, options); |
|
8578 |
+ this._initElement(el); |
|
8579 |
+ |
|
8580 |
+ // handle v-pre on root node (#2026) |
|
8581 |
+ if (el.nodeType === 1 && getAttr(el, 'v-pre') !== null) { |
|
8582 |
+ return; |
|
8583 |
+ } |
|
8584 |
+ |
|
8585 |
+ // root is always compiled per-instance, because |
|
8586 |
+ // container attrs and props can be different every time. |
|
8587 |
+ var contextOptions = this._context && this._context.$options; |
|
8588 |
+ var rootLinker = compileRoot(el, options, contextOptions); |
|
8589 |
+ |
|
8590 |
+ // resolve slot distribution |
|
8591 |
+ resolveSlots(this, options._content); |
|
8592 |
+ |
|
8593 |
+ // compile and link the rest |
|
8594 |
+ var contentLinkFn; |
|
8595 |
+ var ctor = this.constructor; |
|
8596 |
+ // component compilation can be cached |
|
8597 |
+ // as long as it's not using inline-template |
|
8598 |
+ if (options._linkerCachable) { |
|
8599 |
+ contentLinkFn = ctor.linker; |
|
8600 |
+ if (!contentLinkFn) { |
|
8601 |
+ contentLinkFn = ctor.linker = compile(el, options); |
|
8602 |
+ } |
|
8603 |
+ } |
|
8604 |
+ |
|
8605 |
+ // link phase |
|
8606 |
+ // make sure to link root with prop scope! |
|
8607 |
+ var rootUnlinkFn = rootLinker(this, el, this._scope); |
|
8608 |
+ var contentUnlinkFn = contentLinkFn ? contentLinkFn(this, el) : compile(el, options)(this, el); |
|
8609 |
+ |
|
8610 |
+ // register composite unlink function |
|
8611 |
+ // to be called during instance destruction |
|
8612 |
+ this._unlinkFn = function () { |
|
8613 |
+ rootUnlinkFn(); |
|
8614 |
+ // passing destroying: true to avoid searching and |
|
8615 |
+ // splicing the directives |
|
8616 |
+ contentUnlinkFn(true); |
|
8617 |
+ }; |
|
8618 |
+ |
|
8619 |
+ // finally replace original |
|
8620 |
+ if (options.replace) { |
|
8621 |
+ replace(original, el); |
|
8622 |
+ } |
|
8623 |
+ |
|
8624 |
+ this._isCompiled = true; |
|
8625 |
+ this._callHook('compiled'); |
|
8626 |
+ }; |
|
8627 |
+ |
|
8628 |
+ /** |
|
8629 |
+ * Initialize instance element. Called in the public |
|
8630 |
+ * $mount() method. |
|
8631 |
+ * |
|
8632 |
+ * @param {Element} el |
|
8633 |
+ */ |
|
8634 |
+ |
|
8635 |
+ Vue.prototype._initElement = function (el) { |
|
8636 |
+ if (isFragment(el)) { |
|
8637 |
+ this._isFragment = true; |
|
8638 |
+ this.$el = this._fragmentStart = el.firstChild; |
|
8639 |
+ this._fragmentEnd = el.lastChild; |
|
8640 |
+ // set persisted text anchors to empty |
|
8641 |
+ if (this._fragmentStart.nodeType === 3) { |
|
8642 |
+ this._fragmentStart.data = this._fragmentEnd.data = ''; |
|
8643 |
+ } |
|
8644 |
+ this._fragment = el; |
|
8645 |
+ } else { |
|
8646 |
+ this.$el = el; |
|
8647 |
+ } |
|
8648 |
+ this.$el.__vue__ = this; |
|
8649 |
+ this._callHook('beforeCompile'); |
|
8650 |
+ }; |
|
8651 |
+ |
|
8652 |
+ /** |
|
8653 |
+ * Create and bind a directive to an element. |
|
8654 |
+ * |
|
8655 |
+ * @param {Object} descriptor - parsed directive descriptor |
|
8656 |
+ * @param {Node} node - target node |
|
8657 |
+ * @param {Vue} [host] - transclusion host component |
|
8658 |
+ * @param {Object} [scope] - v-for scope |
|
8659 |
+ * @param {Fragment} [frag] - owner fragment |
|
8660 |
+ */ |
|
8661 |
+ |
|
8662 |
+ Vue.prototype._bindDir = function (descriptor, node, host, scope, frag) { |
|
8663 |
+ this._directives.push(new Directive(descriptor, this, node, host, scope, frag)); |
|
8664 |
+ }; |
|
8665 |
+ |
|
8666 |
+ /** |
|
8667 |
+ * Teardown an instance, unobserves the data, unbind all the |
|
8668 |
+ * directives, turn off all the event listeners, etc. |
|
8669 |
+ * |
|
8670 |
+ * @param {Boolean} remove - whether to remove the DOM node. |
|
8671 |
+ * @param {Boolean} deferCleanup - if true, defer cleanup to |
|
8672 |
+ * be called later |
|
8673 |
+ */ |
|
8674 |
+ |
|
8675 |
+ Vue.prototype._destroy = function (remove, deferCleanup) { |
|
8676 |
+ if (this._isBeingDestroyed) { |
|
8677 |
+ if (!deferCleanup) { |
|
8678 |
+ this._cleanup(); |
|
8679 |
+ } |
|
8680 |
+ return; |
|
8681 |
+ } |
|
8682 |
+ |
|
8683 |
+ var destroyReady; |
|
8684 |
+ var pendingRemoval; |
|
8685 |
+ |
|
8686 |
+ var self = this; |
|
8687 |
+ // Cleanup should be called either synchronously or asynchronoysly as |
|
8688 |
+ // callback of this.$remove(), or if remove and deferCleanup are false. |
|
8689 |
+ // In any case it should be called after all other removing, unbinding and |
|
8690 |
+ // turning of is done |
|
8691 |
+ var cleanupIfPossible = function cleanupIfPossible() { |
|
8692 |
+ if (destroyReady && !pendingRemoval && !deferCleanup) { |
|
8693 |
+ self._cleanup(); |
|
8694 |
+ } |
|
8695 |
+ }; |
|
8696 |
+ |
|
8697 |
+ // remove DOM element |
|
8698 |
+ if (remove && this.$el) { |
|
8699 |
+ pendingRemoval = true; |
|
8700 |
+ this.$remove(function () { |
|
8701 |
+ pendingRemoval = false; |
|
8702 |
+ cleanupIfPossible(); |
|
8703 |
+ }); |
|
8704 |
+ } |
|
8705 |
+ |
|
8706 |
+ this._callHook('beforeDestroy'); |
|
8707 |
+ this._isBeingDestroyed = true; |
|
8708 |
+ var i; |
|
8709 |
+ // remove self from parent. only necessary |
|
8710 |
+ // if parent is not being destroyed as well. |
|
8711 |
+ var parent = this.$parent; |
|
8712 |
+ if (parent && !parent._isBeingDestroyed) { |
|
8713 |
+ parent.$children.$remove(this); |
|
8714 |
+ // unregister ref (remove: true) |
|
8715 |
+ this._updateRef(true); |
|
8716 |
+ } |
|
8717 |
+ // destroy all children. |
|
8718 |
+ i = this.$children.length; |
|
8719 |
+ while (i--) { |
|
8720 |
+ this.$children[i].$destroy(); |
|
8721 |
+ } |
|
8722 |
+ // teardown props |
|
8723 |
+ if (this._propsUnlinkFn) { |
|
8724 |
+ this._propsUnlinkFn(); |
|
8725 |
+ } |
|
8726 |
+ // teardown all directives. this also tearsdown all |
|
8727 |
+ // directive-owned watchers. |
|
8728 |
+ if (this._unlinkFn) { |
|
8729 |
+ this._unlinkFn(); |
|
8730 |
+ } |
|
8731 |
+ i = this._watchers.length; |
|
8732 |
+ while (i--) { |
|
8733 |
+ this._watchers[i].teardown(); |
|
8734 |
+ } |
|
8735 |
+ // remove reference to self on $el |
|
8736 |
+ if (this.$el) { |
|
8737 |
+ this.$el.__vue__ = null; |
|
8738 |
+ } |
|
8739 |
+ |
|
8740 |
+ destroyReady = true; |
|
8741 |
+ cleanupIfPossible(); |
|
8742 |
+ }; |
|
8743 |
+ |
|
8744 |
+ /** |
|
8745 |
+ * Clean up to ensure garbage collection. |
|
8746 |
+ * This is called after the leave transition if there |
|
8747 |
+ * is any. |
|
8748 |
+ */ |
|
8749 |
+ |
|
8750 |
+ Vue.prototype._cleanup = function () { |
|
8751 |
+ if (this._isDestroyed) { |
|
8752 |
+ return; |
|
8753 |
+ } |
|
8754 |
+ // remove self from owner fragment |
|
8755 |
+ // do it in cleanup so that we can call $destroy with |
|
8756 |
+ // defer right when a fragment is about to be removed. |
|
8757 |
+ if (this._frag) { |
|
8758 |
+ this._frag.children.$remove(this); |
|
8759 |
+ } |
|
8760 |
+ // remove reference from data ob |
|
8761 |
+ // frozen object may not have observer. |
|
8762 |
+ if (this._data && this._data.__ob__) { |
|
8763 |
+ this._data.__ob__.removeVm(this); |
|
8764 |
+ } |
|
8765 |
+ // Clean up references to private properties and other |
|
8766 |
+ // instances. preserve reference to _data so that proxy |
|
8767 |
+ // accessors still work. The only potential side effect |
|
8768 |
+ // here is that mutating the instance after it's destroyed |
|
8769 |
+ // may affect the state of other components that are still |
|
8770 |
+ // observing the same object, but that seems to be a |
|
8771 |
+ // reasonable responsibility for the user rather than |
|
8772 |
+ // always throwing an error on them. |
|
8773 |
+ this.$el = this.$parent = this.$root = this.$children = this._watchers = this._context = this._scope = this._directives = null; |
|
8774 |
+ // call the last hook... |
|
8775 |
+ this._isDestroyed = true; |
|
8776 |
+ this._callHook('destroyed'); |
|
8777 |
+ // turn off all instance listeners. |
|
8778 |
+ this.$off(); |
|
8779 |
+ }; |
|
8780 |
+ } |
|
8781 |
+ |
|
8782 |
+ function miscMixin (Vue) { |
|
8783 |
+ /** |
|
8784 |
+ * Apply a list of filter (descriptors) to a value. |
|
8785 |
+ * Using plain for loops here because this will be called in |
|
8786 |
+ * the getter of any watcher with filters so it is very |
|
8787 |
+ * performance sensitive. |
|
8788 |
+ * |
|
8789 |
+ * @param {*} value |
|
8790 |
+ * @param {*} [oldValue] |
|
8791 |
+ * @param {Array} filters |
|
8792 |
+ * @param {Boolean} write |
|
8793 |
+ * @return {*} |
|
8794 |
+ */ |
|
8795 |
+ |
|
8796 |
+ Vue.prototype._applyFilters = function (value, oldValue, filters, write) { |
|
8797 |
+ var filter, fn, args, arg, offset, i, l, j, k; |
|
8798 |
+ for (i = 0, l = filters.length; i < l; i++) { |
|
8799 |
+ filter = filters[write ? l - i - 1 : i]; |
|
8800 |
+ fn = resolveAsset(this.$options, 'filters', filter.name, true); |
|
8801 |
+ if (!fn) continue; |
|
8802 |
+ fn = write ? fn.write : fn.read || fn; |
|
8803 |
+ if (typeof fn !== 'function') continue; |
|
8804 |
+ args = write ? [value, oldValue] : [value]; |
|
8805 |
+ offset = write ? 2 : 1; |
|
8806 |
+ if (filter.args) { |
|
8807 |
+ for (j = 0, k = filter.args.length; j < k; j++) { |
|
8808 |
+ arg = filter.args[j]; |
|
8809 |
+ args[j + offset] = arg.dynamic ? this.$get(arg.value) : arg.value; |
|
8810 |
+ } |
|
8811 |
+ } |
|
8812 |
+ value = fn.apply(this, args); |
|
8813 |
+ } |
|
8814 |
+ return value; |
|
8815 |
+ }; |
|
8816 |
+ |
|
8817 |
+ /** |
|
8818 |
+ * Resolve a component, depending on whether the component |
|
8819 |
+ * is defined normally or using an async factory function. |
|
8820 |
+ * Resolves synchronously if already resolved, otherwise |
|
8821 |
+ * resolves asynchronously and caches the resolved |
|
8822 |
+ * constructor on the factory. |
|
8823 |
+ * |
|
8824 |
+ * @param {String|Function} value |
|
8825 |
+ * @param {Function} cb |
|
8826 |
+ */ |
|
8827 |
+ |
|
8828 |
+ Vue.prototype._resolveComponent = function (value, cb) { |
|
8829 |
+ var factory; |
|
8830 |
+ if (typeof value === 'function') { |
|
8831 |
+ factory = value; |
|
8832 |
+ } else { |
|
8833 |
+ factory = resolveAsset(this.$options, 'components', value, true); |
|
8834 |
+ } |
|
8835 |
+ /* istanbul ignore if */ |
|
8836 |
+ if (!factory) { |
|
8837 |
+ return; |
|
8838 |
+ } |
|
8839 |
+ // async component factory |
|
8840 |
+ if (!factory.options) { |
|
8841 |
+ if (factory.resolved) { |
|
8842 |
+ // cached |
|
8843 |
+ cb(factory.resolved); |
|
8844 |
+ } else if (factory.requested) { |
|
8845 |
+ // pool callbacks |
|
8846 |
+ factory.pendingCallbacks.push(cb); |
|
8847 |
+ } else { |
|
8848 |
+ factory.requested = true; |
|
8849 |
+ var cbs = factory.pendingCallbacks = [cb]; |
|
8850 |
+ factory.call(this, function resolve(res) { |
|
8851 |
+ if (isPlainObject(res)) { |
|
8852 |
+ res = Vue.extend(res); |
|
8853 |
+ } |
|
8854 |
+ // cache resolved |
|
8855 |
+ factory.resolved = res; |
|
8856 |
+ // invoke callbacks |
|
8857 |
+ for (var i = 0, l = cbs.length; i < l; i++) { |
|
8858 |
+ cbs[i](res); |
|
8859 |
+ } |
|
8860 |
+ }, function reject(reason) { |
|
8861 |
+ 'development' !== 'production' && warn('Failed to resolve async component' + (typeof value === 'string' ? ': ' + value : '') + '. ' + (reason ? '\nReason: ' + reason : '')); |
|
8862 |
+ }); |
|
8863 |
+ } |
|
8864 |
+ } else { |
|
8865 |
+ // normal component |
|
8866 |
+ cb(factory); |
|
8867 |
+ } |
|
8868 |
+ }; |
|
8869 |
+ } |
|
8870 |
+ |
|
8871 |
+ var filterRE$1 = /[^|]\|[^|]/; |
|
8872 |
+ |
|
8873 |
+ function dataAPI (Vue) { |
|
8874 |
+ /** |
|
8875 |
+ * Get the value from an expression on this vm. |
|
8876 |
+ * |
|
8877 |
+ * @param {String} exp |
|
8878 |
+ * @param {Boolean} [asStatement] |
|
8879 |
+ * @return {*} |
|
8880 |
+ */ |
|
8881 |
+ |
|
8882 |
+ Vue.prototype.$get = function (exp, asStatement) { |
|
8883 |
+ var res = parseExpression(exp); |
|
8884 |
+ if (res) { |
|
8885 |
+ if (asStatement) { |
|
8886 |
+ var self = this; |
|
8887 |
+ return function statementHandler() { |
|
8888 |
+ self.$arguments = toArray(arguments); |
|
8889 |
+ var result = res.get.call(self, self); |
|
8890 |
+ self.$arguments = null; |
|
8891 |
+ return result; |
|
8892 |
+ }; |
|
8893 |
+ } else { |
|
8894 |
+ try { |
|
8895 |
+ return res.get.call(this, this); |
|
8896 |
+ } catch (e) {} |
|
8897 |
+ } |
|
8898 |
+ } |
|
8899 |
+ }; |
|
8900 |
+ |
|
8901 |
+ /** |
|
8902 |
+ * Set the value from an expression on this vm. |
|
8903 |
+ * The expression must be a valid left-hand |
|
8904 |
+ * expression in an assignment. |
|
8905 |
+ * |
|
8906 |
+ * @param {String} exp |
|
8907 |
+ * @param {*} val |
|
8908 |
+ */ |
|
8909 |
+ |
|
8910 |
+ Vue.prototype.$set = function (exp, val) { |
|
8911 |
+ var res = parseExpression(exp, true); |
|
8912 |
+ if (res && res.set) { |
|
8913 |
+ res.set.call(this, this, val); |
|
8914 |
+ } |
|
8915 |
+ }; |
|
8916 |
+ |
|
8917 |
+ /** |
|
8918 |
+ * Delete a property on the VM |
|
8919 |
+ * |
|
8920 |
+ * @param {String} key |
|
8921 |
+ */ |
|
8922 |
+ |
|
8923 |
+ Vue.prototype.$delete = function (key) { |
|
8924 |
+ del(this._data, key); |
|
8925 |
+ }; |
|
8926 |
+ |
|
8927 |
+ /** |
|
8928 |
+ * Watch an expression, trigger callback when its |
|
8929 |
+ * value changes. |
|
8930 |
+ * |
|
8931 |
+ * @param {String|Function} expOrFn |
|
8932 |
+ * @param {Function} cb |
|
8933 |
+ * @param {Object} [options] |
|
8934 |
+ * - {Boolean} deep |
|
8935 |
+ * - {Boolean} immediate |
|
8936 |
+ * @return {Function} - unwatchFn |
|
8937 |
+ */ |
|
8938 |
+ |
|
8939 |
+ Vue.prototype.$watch = function (expOrFn, cb, options) { |
|
8940 |
+ var vm = this; |
|
8941 |
+ var parsed; |
|
8942 |
+ if (typeof expOrFn === 'string') { |
|
8943 |
+ parsed = parseDirective(expOrFn); |
|
8944 |
+ expOrFn = parsed.expression; |
|
8945 |
+ } |
|
8946 |
+ var watcher = new Watcher(vm, expOrFn, cb, { |
|
8947 |
+ deep: options && options.deep, |
|
8948 |
+ sync: options && options.sync, |
|
8949 |
+ filters: parsed && parsed.filters, |
|
8950 |
+ user: !options || options.user !== false |
|
8951 |
+ }); |
|
8952 |
+ if (options && options.immediate) { |
|
8953 |
+ cb.call(vm, watcher.value); |
|
8954 |
+ } |
|
8955 |
+ return function unwatchFn() { |
|
8956 |
+ watcher.teardown(); |
|
8957 |
+ }; |
|
8958 |
+ }; |
|
8959 |
+ |
|
8960 |
+ /** |
|
8961 |
+ * Evaluate a text directive, including filters. |
|
8962 |
+ * |
|
8963 |
+ * @param {String} text |
|
8964 |
+ * @param {Boolean} [asStatement] |
|
8965 |
+ * @return {String} |
|
8966 |
+ */ |
|
8967 |
+ |
|
8968 |
+ Vue.prototype.$eval = function (text, asStatement) { |
|
8969 |
+ // check for filters. |
|
8970 |
+ if (filterRE$1.test(text)) { |
|
8971 |
+ var dir = parseDirective(text); |
|
8972 |
+ // the filter regex check might give false positive |
|
8973 |
+ // for pipes inside strings, so it's possible that |
|
8974 |
+ // we don't get any filters here |
|
8975 |
+ var val = this.$get(dir.expression, asStatement); |
|
8976 |
+ return dir.filters ? this._applyFilters(val, null, dir.filters) : val; |
|
8977 |
+ } else { |
|
8978 |
+ // no filter |
|
8979 |
+ return this.$get(text, asStatement); |
|
8980 |
+ } |
|
8981 |
+ }; |
|
8982 |
+ |
|
8983 |
+ /** |
|
8984 |
+ * Interpolate a piece of template text. |
|
8985 |
+ * |
|
8986 |
+ * @param {String} text |
|
8987 |
+ * @return {String} |
|
8988 |
+ */ |
|
8989 |
+ |
|
8990 |
+ Vue.prototype.$interpolate = function (text) { |
|
8991 |
+ var tokens = parseText(text); |
|
8992 |
+ var vm = this; |
|
8993 |
+ if (tokens) { |
|
8994 |
+ if (tokens.length === 1) { |
|
8995 |
+ return vm.$eval(tokens[0].value) + ''; |
|
8996 |
+ } else { |
|
8997 |
+ return tokens.map(function (token) { |
|
8998 |
+ return token.tag ? vm.$eval(token.value) : token.value; |
|
8999 |
+ }).join(''); |
|
9000 |
+ } |
|
9001 |
+ } else { |
|
9002 |
+ return text; |
|
9003 |
+ } |
|
9004 |
+ }; |
|
9005 |
+ |
|
9006 |
+ /** |
|
9007 |
+ * Log instance data as a plain JS object |
|
9008 |
+ * so that it is easier to inspect in console. |
|
9009 |
+ * This method assumes console is available. |
|
9010 |
+ * |
|
9011 |
+ * @param {String} [path] |
|
9012 |
+ */ |
|
9013 |
+ |
|
9014 |
+ Vue.prototype.$log = function (path) { |
|
9015 |
+ var data = path ? getPath(this._data, path) : this._data; |
|
9016 |
+ if (data) { |
|
9017 |
+ data = clean(data); |
|
9018 |
+ } |
|
9019 |
+ // include computed fields |
|
9020 |
+ if (!path) { |
|
9021 |
+ var key; |
|
9022 |
+ for (key in this.$options.computed) { |
|
9023 |
+ data[key] = clean(this[key]); |
|
9024 |
+ } |
|
9025 |
+ if (this._props) { |
|
9026 |
+ for (key in this._props) { |
|
9027 |
+ data[key] = clean(this[key]); |
|
9028 |
+ } |
|
9029 |
+ } |
|
9030 |
+ } |
|
9031 |
+ console.log(data); |
|
9032 |
+ }; |
|
9033 |
+ |
|
9034 |
+ /** |
|
9035 |
+ * "clean" a getter/setter converted object into a plain |
|
9036 |
+ * object copy. |
|
9037 |
+ * |
|
9038 |
+ * @param {Object} - obj |
|
9039 |
+ * @return {Object} |
|
9040 |
+ */ |
|
9041 |
+ |
|
9042 |
+ function clean(obj) { |
|
9043 |
+ return JSON.parse(JSON.stringify(obj)); |
|
9044 |
+ } |
|
9045 |
+ } |
|
9046 |
+ |
|
9047 |
+ function domAPI (Vue) { |
|
9048 |
+ /** |
|
9049 |
+ * Convenience on-instance nextTick. The callback is |
|
9050 |
+ * auto-bound to the instance, and this avoids component |
|
9051 |
+ * modules having to rely on the global Vue. |
|
9052 |
+ * |
|
9053 |
+ * @param {Function} fn |
|
9054 |
+ */ |
|
9055 |
+ |
|
9056 |
+ Vue.prototype.$nextTick = function (fn) { |
|
9057 |
+ nextTick(fn, this); |
|
9058 |
+ }; |
|
9059 |
+ |
|
9060 |
+ /** |
|
9061 |
+ * Append instance to target |
|
9062 |
+ * |
|
9063 |
+ * @param {Node} target |
|
9064 |
+ * @param {Function} [cb] |
|
9065 |
+ * @param {Boolean} [withTransition] - defaults to true |
|
9066 |
+ */ |
|
9067 |
+ |
|
9068 |
+ Vue.prototype.$appendTo = function (target, cb, withTransition) { |
|
9069 |
+ return insert(this, target, cb, withTransition, append, appendWithTransition); |
|
9070 |
+ }; |
|
9071 |
+ |
|
9072 |
+ /** |
|
9073 |
+ * Prepend instance to target |
|
9074 |
+ * |
|
9075 |
+ * @param {Node} target |
|
9076 |
+ * @param {Function} [cb] |
|
9077 |
+ * @param {Boolean} [withTransition] - defaults to true |
|
9078 |
+ */ |
|
9079 |
+ |
|
9080 |
+ Vue.prototype.$prependTo = function (target, cb, withTransition) { |
|
9081 |
+ target = query(target); |
|
9082 |
+ if (target.hasChildNodes()) { |
|
9083 |
+ this.$before(target.firstChild, cb, withTransition); |
|
9084 |
+ } else { |
|
9085 |
+ this.$appendTo(target, cb, withTransition); |
|
9086 |
+ } |
|
9087 |
+ return this; |
|
9088 |
+ }; |
|
9089 |
+ |
|
9090 |
+ /** |
|
9091 |
+ * Insert instance before target |
|
9092 |
+ * |
|
9093 |
+ * @param {Node} target |
|
9094 |
+ * @param {Function} [cb] |
|
9095 |
+ * @param {Boolean} [withTransition] - defaults to true |
|
9096 |
+ */ |
|
9097 |
+ |
|
9098 |
+ Vue.prototype.$before = function (target, cb, withTransition) { |
|
9099 |
+ return insert(this, target, cb, withTransition, beforeWithCb, beforeWithTransition); |
|
9100 |
+ }; |
|
9101 |
+ |
|
9102 |
+ /** |
|
9103 |
+ * Insert instance after target |
|
9104 |
+ * |
|
9105 |
+ * @param {Node} target |
|
9106 |
+ * @param {Function} [cb] |
|
9107 |
+ * @param {Boolean} [withTransition] - defaults to true |
|
9108 |
+ */ |
|
9109 |
+ |
|
9110 |
+ Vue.prototype.$after = function (target, cb, withTransition) { |
|
9111 |
+ target = query(target); |
|
9112 |
+ if (target.nextSibling) { |
|
9113 |
+ this.$before(target.nextSibling, cb, withTransition); |
|
9114 |
+ } else { |
|
9115 |
+ this.$appendTo(target.parentNode, cb, withTransition); |
|
9116 |
+ } |
|
9117 |
+ return this; |
|
9118 |
+ }; |
|
9119 |
+ |
|
9120 |
+ /** |
|
9121 |
+ * Remove instance from DOM |
|
9122 |
+ * |
|
9123 |
+ * @param {Function} [cb] |
|
9124 |
+ * @param {Boolean} [withTransition] - defaults to true |
|
9125 |
+ */ |
|
9126 |
+ |
|
9127 |
+ Vue.prototype.$remove = function (cb, withTransition) { |
|
9128 |
+ if (!this.$el.parentNode) { |
|
9129 |
+ return cb && cb(); |
|
9130 |
+ } |
|
9131 |
+ var inDocument = this._isAttached && inDoc(this.$el); |
|
9132 |
+ // if we are not in document, no need to check |
|
9133 |
+ // for transitions |
|
9134 |
+ if (!inDocument) withTransition = false; |
|
9135 |
+ var self = this; |
|
9136 |
+ var realCb = function realCb() { |
|
9137 |
+ if (inDocument) self._callHook('detached'); |
|
9138 |
+ if (cb) cb(); |
|
9139 |
+ }; |
|
9140 |
+ if (this._isFragment) { |
|
9141 |
+ removeNodeRange(this._fragmentStart, this._fragmentEnd, this, this._fragment, realCb); |
|
9142 |
+ } else { |
|
9143 |
+ var op = withTransition === false ? removeWithCb : removeWithTransition; |
|
9144 |
+ op(this.$el, this, realCb); |
|
9145 |
+ } |
|
9146 |
+ return this; |
|
9147 |
+ }; |
|
9148 |
+ |
|
9149 |
+ /** |
|
9150 |
+ * Shared DOM insertion function. |
|
9151 |
+ * |
|
9152 |
+ * @param {Vue} vm |
|
9153 |
+ * @param {Element} target |
|
9154 |
+ * @param {Function} [cb] |
|
9155 |
+ * @param {Boolean} [withTransition] |
|
9156 |
+ * @param {Function} op1 - op for non-transition insert |
|
9157 |
+ * @param {Function} op2 - op for transition insert |
|
9158 |
+ * @return vm |
|
9159 |
+ */ |
|
9160 |
+ |
|
9161 |
+ function insert(vm, target, cb, withTransition, op1, op2) { |
|
9162 |
+ target = query(target); |
|
9163 |
+ var targetIsDetached = !inDoc(target); |
|
9164 |
+ var op = withTransition === false || targetIsDetached ? op1 : op2; |
|
9165 |
+ var shouldCallHook = !targetIsDetached && !vm._isAttached && !inDoc(vm.$el); |
|
9166 |
+ if (vm._isFragment) { |
|
9167 |
+ mapNodeRange(vm._fragmentStart, vm._fragmentEnd, function (node) { |
|
9168 |
+ op(node, target, vm); |
|
9169 |
+ }); |
|
9170 |
+ cb && cb(); |
|
9171 |
+ } else { |
|
9172 |
+ op(vm.$el, target, vm, cb); |
|
9173 |
+ } |
|
9174 |
+ if (shouldCallHook) { |
|
9175 |
+ vm._callHook('attached'); |
|
9176 |
+ } |
|
9177 |
+ return vm; |
|
9178 |
+ } |
|
9179 |
+ |
|
9180 |
+ /** |
|
9181 |
+ * Check for selectors |
|
9182 |
+ * |
|
9183 |
+ * @param {String|Element} el |
|
9184 |
+ */ |
|
9185 |
+ |
|
9186 |
+ function query(el) { |
|
9187 |
+ return typeof el === 'string' ? document.querySelector(el) : el; |
|
9188 |
+ } |
|
9189 |
+ |
|
9190 |
+ /** |
|
9191 |
+ * Append operation that takes a callback. |
|
9192 |
+ * |
|
9193 |
+ * @param {Node} el |
|
9194 |
+ * @param {Node} target |
|
9195 |
+ * @param {Vue} vm - unused |
|
9196 |
+ * @param {Function} [cb] |
|
9197 |
+ */ |
|
9198 |
+ |
|
9199 |
+ function append(el, target, vm, cb) { |
|
9200 |
+ target.appendChild(el); |
|
9201 |
+ if (cb) cb(); |
|
9202 |
+ } |
|
9203 |
+ |
|
9204 |
+ /** |
|
9205 |
+ * InsertBefore operation that takes a callback. |
|
9206 |
+ * |
|
9207 |
+ * @param {Node} el |
|
9208 |
+ * @param {Node} target |
|
9209 |
+ * @param {Vue} vm - unused |
|
9210 |
+ * @param {Function} [cb] |
|
9211 |
+ */ |
|
9212 |
+ |
|
9213 |
+ function beforeWithCb(el, target, vm, cb) { |
|
9214 |
+ before(el, target); |
|
9215 |
+ if (cb) cb(); |
|
9216 |
+ } |
|
9217 |
+ |
|
9218 |
+ /** |
|
9219 |
+ * Remove operation that takes a callback. |
|
9220 |
+ * |
|
9221 |
+ * @param {Node} el |
|
9222 |
+ * @param {Vue} vm - unused |
|
9223 |
+ * @param {Function} [cb] |
|
9224 |
+ */ |
|
9225 |
+ |
|
9226 |
+ function removeWithCb(el, vm, cb) { |
|
9227 |
+ remove(el); |
|
9228 |
+ if (cb) cb(); |
|
9229 |
+ } |
|
9230 |
+ } |
|
9231 |
+ |
|
9232 |
+ function eventsAPI (Vue) { |
|
9233 |
+ /** |
|
9234 |
+ * Listen on the given `event` with `fn`. |
|
9235 |
+ * |
|
9236 |
+ * @param {String} event |
|
9237 |
+ * @param {Function} fn |
|
9238 |
+ */ |
|
9239 |
+ |
|
9240 |
+ Vue.prototype.$on = function (event, fn) { |
|
9241 |
+ (this._events[event] || (this._events[event] = [])).push(fn); |
|
9242 |
+ modifyListenerCount(this, event, 1); |
|
9243 |
+ return this; |
|
9244 |
+ }; |
|
9245 |
+ |
|
9246 |
+ /** |
|
9247 |
+ * Adds an `event` listener that will be invoked a single |
|
9248 |
+ * time then automatically removed. |
|
9249 |
+ * |
|
9250 |
+ * @param {String} event |
|
9251 |
+ * @param {Function} fn |
|
9252 |
+ */ |
|
9253 |
+ |
|
9254 |
+ Vue.prototype.$once = function (event, fn) { |
|
9255 |
+ var self = this; |
|
9256 |
+ function on() { |
|
9257 |
+ self.$off(event, on); |
|
9258 |
+ fn.apply(this, arguments); |
|
9259 |
+ } |
|
9260 |
+ on.fn = fn; |
|
9261 |
+ this.$on(event, on); |
|
9262 |
+ return this; |
|
9263 |
+ }; |
|
9264 |
+ |
|
9265 |
+ /** |
|
9266 |
+ * Remove the given callback for `event` or all |
|
9267 |
+ * registered callbacks. |
|
9268 |
+ * |
|
9269 |
+ * @param {String} event |
|
9270 |
+ * @param {Function} fn |
|
9271 |
+ */ |
|
9272 |
+ |
|
9273 |
+ Vue.prototype.$off = function (event, fn) { |
|
9274 |
+ var cbs; |
|
9275 |
+ // all |
|
9276 |
+ if (!arguments.length) { |
|
9277 |
+ if (this.$parent) { |
|
9278 |
+ for (event in this._events) { |
|
9279 |
+ cbs = this._events[event]; |
|
9280 |
+ if (cbs) { |
|
9281 |
+ modifyListenerCount(this, event, -cbs.length); |
|
9282 |
+ } |
|
9283 |
+ } |
|
9284 |
+ } |
|
9285 |
+ this._events = {}; |
|
9286 |
+ return this; |
|
9287 |
+ } |
|
9288 |
+ // specific event |
|
9289 |
+ cbs = this._events[event]; |
|
9290 |
+ if (!cbs) { |
|
9291 |
+ return this; |
|
9292 |
+ } |
|
9293 |
+ if (arguments.length === 1) { |
|
9294 |
+ modifyListenerCount(this, event, -cbs.length); |
|
9295 |
+ this._events[event] = null; |
|
9296 |
+ return this; |
|
9297 |
+ } |
|
9298 |
+ // specific handler |
|
9299 |
+ var cb; |
|
9300 |
+ var i = cbs.length; |
|
9301 |
+ while (i--) { |
|
9302 |
+ cb = cbs[i]; |
|
9303 |
+ if (cb === fn || cb.fn === fn) { |
|
9304 |
+ modifyListenerCount(this, event, -1); |
|
9305 |
+ cbs.splice(i, 1); |
|
9306 |
+ break; |
|
9307 |
+ } |
|
9308 |
+ } |
|
9309 |
+ return this; |
|
9310 |
+ }; |
|
9311 |
+ |
|
9312 |
+ /** |
|
9313 |
+ * Trigger an event on self. |
|
9314 |
+ * |
|
9315 |
+ * @param {String|Object} event |
|
9316 |
+ * @return {Boolean} shouldPropagate |
|
9317 |
+ */ |
|
9318 |
+ |
|
9319 |
+ Vue.prototype.$emit = function (event) { |
|
9320 |
+ var isSource = typeof event === 'string'; |
|
9321 |
+ event = isSource ? event : event.name; |
|
9322 |
+ var cbs = this._events[event]; |
|
9323 |
+ var shouldPropagate = isSource || !cbs; |
|
9324 |
+ if (cbs) { |
|
9325 |
+ cbs = cbs.length > 1 ? toArray(cbs) : cbs; |
|
9326 |
+ // this is a somewhat hacky solution to the question raised |
|
9327 |
+ // in #2102: for an inline component listener like <comp @test="doThis">, |
|
9328 |
+ // the propagation handling is somewhat broken. Therefore we |
|
9329 |
+ // need to treat these inline callbacks differently. |
|
9330 |
+ var hasParentCbs = isSource && cbs.some(function (cb) { |
|
9331 |
+ return cb._fromParent; |
|
9332 |
+ }); |
|
9333 |
+ if (hasParentCbs) { |
|
9334 |
+ shouldPropagate = false; |
|
9335 |
+ } |
|
9336 |
+ var args = toArray(arguments, 1); |
|
9337 |
+ for (var i = 0, l = cbs.length; i < l; i++) { |
|
9338 |
+ var cb = cbs[i]; |
|
9339 |
+ var res = cb.apply(this, args); |
|
9340 |
+ if (res === true && (!hasParentCbs || cb._fromParent)) { |
|
9341 |
+ shouldPropagate = true; |
|
9342 |
+ } |
|
9343 |
+ } |
|
9344 |
+ } |
|
9345 |
+ return shouldPropagate; |
|
9346 |
+ }; |
|
9347 |
+ |
|
9348 |
+ /** |
|
9349 |
+ * Recursively broadcast an event to all children instances. |
|
9350 |
+ * |
|
9351 |
+ * @param {String|Object} event |
|
9352 |
+ * @param {...*} additional arguments |
|
9353 |
+ */ |
|
9354 |
+ |
|
9355 |
+ Vue.prototype.$broadcast = function (event) { |
|
9356 |
+ var isSource = typeof event === 'string'; |
|
9357 |
+ event = isSource ? event : event.name; |
|
9358 |
+ // if no child has registered for this event, |
|
9359 |
+ // then there's no need to broadcast. |
|
9360 |
+ if (!this._eventsCount[event]) return; |
|
9361 |
+ var children = this.$children; |
|
9362 |
+ var args = toArray(arguments); |
|
9363 |
+ if (isSource) { |
|
9364 |
+ // use object event to indicate non-source emit |
|
9365 |
+ // on children |
|
9366 |
+ args[0] = { name: event, source: this }; |
|
9367 |
+ } |
|
9368 |
+ for (var i = 0, l = children.length; i < l; i++) { |
|
9369 |
+ var child = children[i]; |
|
9370 |
+ var shouldPropagate = child.$emit.apply(child, args); |
|
9371 |
+ if (shouldPropagate) { |
|
9372 |
+ child.$broadcast.apply(child, args); |
|
9373 |
+ } |
|
9374 |
+ } |
|
9375 |
+ return this; |
|
9376 |
+ }; |
|
9377 |
+ |
|
9378 |
+ /** |
|
9379 |
+ * Recursively propagate an event up the parent chain. |
|
9380 |
+ * |
|
9381 |
+ * @param {String} event |
|
9382 |
+ * @param {...*} additional arguments |
|
9383 |
+ */ |
|
9384 |
+ |
|
9385 |
+ Vue.prototype.$dispatch = function (event) { |
|
9386 |
+ var shouldPropagate = this.$emit.apply(this, arguments); |
|
9387 |
+ if (!shouldPropagate) return; |
|
9388 |
+ var parent = this.$parent; |
|
9389 |
+ var args = toArray(arguments); |
|
9390 |
+ // use object event to indicate non-source emit |
|
9391 |
+ // on parents |
|
9392 |
+ args[0] = { name: event, source: this }; |
|
9393 |
+ while (parent) { |
|
9394 |
+ shouldPropagate = parent.$emit.apply(parent, args); |
|
9395 |
+ parent = shouldPropagate ? parent.$parent : null; |
|
9396 |
+ } |
|
9397 |
+ return this; |
|
9398 |
+ }; |
|
9399 |
+ |
|
9400 |
+ /** |
|
9401 |
+ * Modify the listener counts on all parents. |
|
9402 |
+ * This bookkeeping allows $broadcast to return early when |
|
9403 |
+ * no child has listened to a certain event. |
|
9404 |
+ * |
|
9405 |
+ * @param {Vue} vm |
|
9406 |
+ * @param {String} event |
|
9407 |
+ * @param {Number} count |
|
9408 |
+ */ |
|
9409 |
+ |
|
9410 |
+ var hookRE = /^hook:/; |
|
9411 |
+ function modifyListenerCount(vm, event, count) { |
|
9412 |
+ var parent = vm.$parent; |
|
9413 |
+ // hooks do not get broadcasted so no need |
|
9414 |
+ // to do bookkeeping for them |
|
9415 |
+ if (!parent || !count || hookRE.test(event)) return; |
|
9416 |
+ while (parent) { |
|
9417 |
+ parent._eventsCount[event] = (parent._eventsCount[event] || 0) + count; |
|
9418 |
+ parent = parent.$parent; |
|
9419 |
+ } |
|
9420 |
+ } |
|
9421 |
+ } |
|
9422 |
+ |
|
9423 |
+ function lifecycleAPI (Vue) { |
|
9424 |
+ /** |
|
9425 |
+ * Set instance target element and kick off the compilation |
|
9426 |
+ * process. The passed in `el` can be a selector string, an |
|
9427 |
+ * existing Element, or a DocumentFragment (for block |
|
9428 |
+ * instances). |
|
9429 |
+ * |
|
9430 |
+ * @param {Element|DocumentFragment|string} el |
|
9431 |
+ * @public |
|
9432 |
+ */ |
|
9433 |
+ |
|
9434 |
+ Vue.prototype.$mount = function (el) { |
|
9435 |
+ if (this._isCompiled) { |
|
9436 |
+ 'development' !== 'production' && warn('$mount() should be called only once.', this); |
|
9437 |
+ return; |
|
9438 |
+ } |
|
9439 |
+ el = query(el); |
|
9440 |
+ if (!el) { |
|
9441 |
+ el = document.createElement('div'); |
|
9442 |
+ } |
|
9443 |
+ this._compile(el); |
|
9444 |
+ this._initDOMHooks(); |
|
9445 |
+ if (inDoc(this.$el)) { |
|
9446 |
+ this._callHook('attached'); |
|
9447 |
+ ready.call(this); |
|
9448 |
+ } else { |
|
9449 |
+ this.$once('hook:attached', ready); |
|
9450 |
+ } |
|
9451 |
+ return this; |
|
9452 |
+ }; |
|
9453 |
+ |
|
9454 |
+ /** |
|
9455 |
+ * Mark an instance as ready. |
|
9456 |
+ */ |
|
9457 |
+ |
|
9458 |
+ function ready() { |
|
9459 |
+ this._isAttached = true; |
|
9460 |
+ this._isReady = true; |
|
9461 |
+ this._callHook('ready'); |
|
9462 |
+ } |
|
9463 |
+ |
|
9464 |
+ /** |
|
9465 |
+ * Teardown the instance, simply delegate to the internal |
|
9466 |
+ * _destroy. |
|
9467 |
+ * |
|
9468 |
+ * @param {Boolean} remove |
|
9469 |
+ * @param {Boolean} deferCleanup |
|
9470 |
+ */ |
|
9471 |
+ |
|
9472 |
+ Vue.prototype.$destroy = function (remove, deferCleanup) { |
|
9473 |
+ this._destroy(remove, deferCleanup); |
|
9474 |
+ }; |
|
9475 |
+ |
|
9476 |
+ /** |
|
9477 |
+ * Partially compile a piece of DOM and return a |
|
9478 |
+ * decompile function. |
|
9479 |
+ * |
|
9480 |
+ * @param {Element|DocumentFragment} el |
|
9481 |
+ * @param {Vue} [host] |
|
9482 |
+ * @param {Object} [scope] |
|
9483 |
+ * @param {Fragment} [frag] |
|
9484 |
+ * @return {Function} |
|
9485 |
+ */ |
|
9486 |
+ |
|
9487 |
+ Vue.prototype.$compile = function (el, host, scope, frag) { |
|
9488 |
+ return compile(el, this.$options, true)(this, el, host, scope, frag); |
|
9489 |
+ }; |
|
9490 |
+ } |
|
9491 |
+ |
|
9492 |
+ /** |
|
9493 |
+ * The exposed Vue constructor. |
|
9494 |
+ * |
|
9495 |
+ * API conventions: |
|
9496 |
+ * - public API methods/properties are prefixed with `$` |
|
9497 |
+ * - internal methods/properties are prefixed with `_` |
|
9498 |
+ * - non-prefixed properties are assumed to be proxied user |
|
9499 |
+ * data. |
|
9500 |
+ * |
|
9501 |
+ * @constructor |
|
9502 |
+ * @param {Object} [options] |
|
9503 |
+ * @public |
|
9504 |
+ */ |
|
9505 |
+ |
|
9506 |
+ function Vue(options) { |
|
9507 |
+ this._init(options); |
|
9508 |
+ } |
|
9509 |
+ |
|
9510 |
+ // install internals |
|
9511 |
+ initMixin(Vue); |
|
9512 |
+ stateMixin(Vue); |
|
9513 |
+ eventsMixin(Vue); |
|
9514 |
+ lifecycleMixin(Vue); |
|
9515 |
+ miscMixin(Vue); |
|
9516 |
+ |
|
9517 |
+ // install instance APIs |
|
9518 |
+ dataAPI(Vue); |
|
9519 |
+ domAPI(Vue); |
|
9520 |
+ eventsAPI(Vue); |
|
9521 |
+ lifecycleAPI(Vue); |
|
9522 |
+ |
|
9523 |
+ var slot = { |
|
9524 |
+ |
|
9525 |
+ priority: SLOT, |
|
9526 |
+ params: ['name'], |
|
9527 |
+ |
|
9528 |
+ bind: function bind() { |
|
9529 |
+ // this was resolved during component transclusion |
|
9530 |
+ var name = this.params.name || 'default'; |
|
9531 |
+ var content = this.vm._slotContents && this.vm._slotContents[name]; |
|
9532 |
+ if (!content || !content.hasChildNodes()) { |
|
9533 |
+ this.fallback(); |
|
9534 |
+ } else { |
|
9535 |
+ this.compile(content.cloneNode(true), this.vm._context, this.vm); |
|
9536 |
+ } |
|
9537 |
+ }, |
|
9538 |
+ |
|
9539 |
+ compile: function compile(content, context, host) { |
|
9540 |
+ if (content && context) { |
|
9541 |
+ if (this.el.hasChildNodes() && content.childNodes.length === 1 && content.childNodes[0].nodeType === 1 && content.childNodes[0].hasAttribute('v-if')) { |
|
9542 |
+ // if the inserted slot has v-if |
|
9543 |
+ // inject fallback content as the v-else |
|
9544 |
+ var elseBlock = document.createElement('template'); |
|
9545 |
+ elseBlock.setAttribute('v-else', ''); |
|
9546 |
+ elseBlock.innerHTML = this.el.innerHTML; |
|
9547 |
+ // the else block should be compiled in child scope |
|
9548 |
+ elseBlock._context = this.vm; |
|
9549 |
+ content.appendChild(elseBlock); |
|
9550 |
+ } |
|
9551 |
+ var scope = host ? host._scope : this._scope; |
|
9552 |
+ this.unlink = context.$compile(content, host, scope, this._frag); |
|
9553 |
+ } |
|
9554 |
+ if (content) { |
|
9555 |
+ replace(this.el, content); |
|
9556 |
+ } else { |
|
9557 |
+ remove(this.el); |
|
9558 |
+ } |
|
9559 |
+ }, |
|
9560 |
+ |
|
9561 |
+ fallback: function fallback() { |
|
9562 |
+ this.compile(extractContent(this.el, true), this.vm); |
|
9563 |
+ }, |
|
9564 |
+ |
|
9565 |
+ unbind: function unbind() { |
|
9566 |
+ if (this.unlink) { |
|
9567 |
+ this.unlink(); |
|
9568 |
+ } |
|
9569 |
+ } |
|
9570 |
+ }; |
|
9571 |
+ |
|
9572 |
+ var partial = { |
|
9573 |
+ |
|
9574 |
+ priority: PARTIAL, |
|
9575 |
+ |
|
9576 |
+ params: ['name'], |
|
9577 |
+ |
|
9578 |
+ // watch changes to name for dynamic partials |
|
9579 |
+ paramWatchers: { |
|
9580 |
+ name: function name(value) { |
|
9581 |
+ vIf.remove.call(this); |
|
9582 |
+ if (value) { |
|
9583 |
+ this.insert(value); |
|
9584 |
+ } |
|
9585 |
+ } |
|
9586 |
+ }, |
|
9587 |
+ |
|
9588 |
+ bind: function bind() { |
|
9589 |
+ this.anchor = createAnchor('v-partial'); |
|
9590 |
+ replace(this.el, this.anchor); |
|
9591 |
+ this.insert(this.params.name); |
|
9592 |
+ }, |
|
9593 |
+ |
|
9594 |
+ insert: function insert(id) { |
|
9595 |
+ var partial = resolveAsset(this.vm.$options, 'partials', id, true); |
|
9596 |
+ if (partial) { |
|
9597 |
+ this.factory = new FragmentFactory(this.vm, partial); |
|
9598 |
+ vIf.insert.call(this); |
|
9599 |
+ } |
|
9600 |
+ }, |
|
9601 |
+ |
|
9602 |
+ unbind: function unbind() { |
|
9603 |
+ if (this.frag) { |
|
9604 |
+ this.frag.destroy(); |
|
9605 |
+ } |
|
9606 |
+ } |
|
9607 |
+ }; |
|
9608 |
+ |
|
9609 |
+ var elementDirectives = { |
|
9610 |
+ slot: slot, |
|
9611 |
+ partial: partial |
|
9612 |
+ }; |
|
9613 |
+ |
|
9614 |
+ var convertArray = vFor._postProcess; |
|
9615 |
+ |
|
9616 |
+ /** |
|
9617 |
+ * Limit filter for arrays |
|
9618 |
+ * |
|
9619 |
+ * @param {Number} n |
|
9620 |
+ * @param {Number} offset (Decimal expected) |
|
9621 |
+ */ |
|
9622 |
+ |
|
9623 |
+ function limitBy(arr, n, offset) { |
|
9624 |
+ offset = offset ? parseInt(offset, 10) : 0; |
|
9625 |
+ n = toNumber(n); |
|
9626 |
+ return typeof n === 'number' ? arr.slice(offset, offset + n) : arr; |
|
9627 |
+ } |
|
9628 |
+ |
|
9629 |
+ /** |
|
9630 |
+ * Filter filter for arrays |
|
9631 |
+ * |
|
9632 |
+ * @param {String} search |
|
9633 |
+ * @param {String} [delimiter] |
|
9634 |
+ * @param {String} ...dataKeys |
|
9635 |
+ */ |
|
9636 |
+ |
|
9637 |
+ function filterBy(arr, search, delimiter) { |
|
9638 |
+ arr = convertArray(arr); |
|
9639 |
+ if (search == null) { |
|
9640 |
+ return arr; |
|
9641 |
+ } |
|
9642 |
+ if (typeof search === 'function') { |
|
9643 |
+ return arr.filter(search); |
|
9644 |
+ } |
|
9645 |
+ // cast to lowercase string |
|
9646 |
+ search = ('' + search).toLowerCase(); |
|
9647 |
+ // allow optional `in` delimiter |
|
9648 |
+ // because why not |
|
9649 |
+ var n = delimiter === 'in' ? 3 : 2; |
|
9650 |
+ // extract and flatten keys |
|
9651 |
+ var keys = Array.prototype.concat.apply([], toArray(arguments, n)); |
|
9652 |
+ var res = []; |
|
9653 |
+ var item, key, val, j; |
|
9654 |
+ for (var i = 0, l = arr.length; i < l; i++) { |
|
9655 |
+ item = arr[i]; |
|
9656 |
+ val = item && item.$value || item; |
|
9657 |
+ j = keys.length; |
|
9658 |
+ if (j) { |
|
9659 |
+ while (j--) { |
|
9660 |
+ key = keys[j]; |
|
9661 |
+ if (key === '$key' && contains(item.$key, search) || contains(getPath(val, key), search)) { |
|
9662 |
+ res.push(item); |
|
9663 |
+ break; |
|
9664 |
+ } |
|
9665 |
+ } |
|
9666 |
+ } else if (contains(item, search)) { |
|
9667 |
+ res.push(item); |
|
9668 |
+ } |
|
9669 |
+ } |
|
9670 |
+ return res; |
|
9671 |
+ } |
|
9672 |
+ |
|
9673 |
+ /** |
|
9674 |
+ * Filter filter for arrays |
|
9675 |
+ * |
|
9676 |
+ * @param {String|Array<String>|Function} ...sortKeys |
|
9677 |
+ * @param {Number} [order] |
|
9678 |
+ */ |
|
9679 |
+ |
|
9680 |
+ function orderBy(arr) { |
|
9681 |
+ var comparator = null; |
|
9682 |
+ var sortKeys = undefined; |
|
9683 |
+ arr = convertArray(arr); |
|
9684 |
+ |
|
9685 |
+ // determine order (last argument) |
|
9686 |
+ var args = toArray(arguments, 1); |
|
9687 |
+ var order = args[args.length - 1]; |
|
9688 |
+ if (typeof order === 'number') { |
|
9689 |
+ order = order < 0 ? -1 : 1; |
|
9690 |
+ args = args.length > 1 ? args.slice(0, -1) : args; |
|
9691 |
+ } else { |
|
9692 |
+ order = 1; |
|
9693 |
+ } |
|
9694 |
+ |
|
9695 |
+ // determine sortKeys & comparator |
|
9696 |
+ var firstArg = args[0]; |
|
9697 |
+ if (!firstArg) { |
|
9698 |
+ return arr; |
|
9699 |
+ } else if (typeof firstArg === 'function') { |
|
9700 |
+ // custom comparator |
|
9701 |
+ comparator = function (a, b) { |
|
9702 |
+ return firstArg(a, b) * order; |
|
9703 |
+ }; |
|
9704 |
+ } else { |
|
9705 |
+ // string keys. flatten first |
|
9706 |
+ sortKeys = Array.prototype.concat.apply([], args); |
|
9707 |
+ comparator = function (a, b, i) { |
|
9708 |
+ i = i || 0; |
|
9709 |
+ return i >= sortKeys.length - 1 ? baseCompare(a, b, i) : baseCompare(a, b, i) || comparator(a, b, i + 1); |
|
9710 |
+ }; |
|
9711 |
+ } |
|
9712 |
+ |
|
9713 |
+ function baseCompare(a, b, sortKeyIndex) { |
|
9714 |
+ var sortKey = sortKeys[sortKeyIndex]; |
|
9715 |
+ if (sortKey) { |
|
9716 |
+ if (sortKey !== '$key') { |
|
9717 |
+ if (isObject(a) && '$value' in a) a = a.$value; |
|
9718 |
+ if (isObject(b) && '$value' in b) b = b.$value; |
|
9719 |
+ } |
|
9720 |
+ a = isObject(a) ? getPath(a, sortKey) : a; |
|
9721 |
+ b = isObject(b) ? getPath(b, sortKey) : b; |
|
9722 |
+ } |
|
9723 |
+ return a === b ? 0 : a > b ? order : -order; |
|
9724 |
+ } |
|
9725 |
+ |
|
9726 |
+ // sort on a copy to avoid mutating original array |
|
9727 |
+ return arr.slice().sort(comparator); |
|
9728 |
+ } |
|
9729 |
+ |
|
9730 |
+ /** |
|
9731 |
+ * String contain helper |
|
9732 |
+ * |
|
9733 |
+ * @param {*} val |
|
9734 |
+ * @param {String} search |
|
9735 |
+ */ |
|
9736 |
+ |
|
9737 |
+ function contains(val, search) { |
|
9738 |
+ var i; |
|
9739 |
+ if (isPlainObject(val)) { |
|
9740 |
+ var keys = Object.keys(val); |
|
9741 |
+ i = keys.length; |
|
9742 |
+ while (i--) { |
|
9743 |
+ if (contains(val[keys[i]], search)) { |
|
9744 |
+ return true; |
|
9745 |
+ } |
|
9746 |
+ } |
|
9747 |
+ } else if (isArray(val)) { |
|
9748 |
+ i = val.length; |
|
9749 |
+ while (i--) { |
|
9750 |
+ if (contains(val[i], search)) { |
|
9751 |
+ return true; |
|
9752 |
+ } |
|
9753 |
+ } |
|
9754 |
+ } else if (val != null) { |
|
9755 |
+ return val.toString().toLowerCase().indexOf(search) > -1; |
|
9756 |
+ } |
|
9757 |
+ } |
|
9758 |
+ |
|
9759 |
+ var digitsRE = /(\d{3})(?=\d)/g; |
|
9760 |
+ |
|
9761 |
+ // asset collections must be a plain object. |
|
9762 |
+ var filters = { |
|
9763 |
+ |
|
9764 |
+ orderBy: orderBy, |
|
9765 |
+ filterBy: filterBy, |
|
9766 |
+ limitBy: limitBy, |
|
9767 |
+ |
|
9768 |
+ /** |
|
9769 |
+ * Stringify value. |
|
9770 |
+ * |
|
9771 |
+ * @param {Number} indent |
|
9772 |
+ */ |
|
9773 |
+ |
|
9774 |
+ json: { |
|
9775 |
+ read: function read(value, indent) { |
|
9776 |
+ return typeof value === 'string' ? value : JSON.stringify(value, null, arguments.length > 1 ? indent : 2); |
|
9777 |
+ }, |
|
9778 |
+ write: function write(value) { |
|
9779 |
+ try { |
|
9780 |
+ return JSON.parse(value); |
|
9781 |
+ } catch (e) { |
|
9782 |
+ return value; |
|
9783 |
+ } |
|
9784 |
+ } |
|
9785 |
+ }, |
|
9786 |
+ |
|
9787 |
+ /** |
|
9788 |
+ * 'abc' => 'Abc' |
|
9789 |
+ */ |
|
9790 |
+ |
|
9791 |
+ capitalize: function capitalize(value) { |
|
9792 |
+ if (!value && value !== 0) return ''; |
|
9793 |
+ value = value.toString(); |
|
9794 |
+ return value.charAt(0).toUpperCase() + value.slice(1); |
|
9795 |
+ }, |
|
9796 |
+ |
|
9797 |
+ /** |
|
9798 |
+ * 'abc' => 'ABC' |
|
9799 |
+ */ |
|
9800 |
+ |
|
9801 |
+ uppercase: function uppercase(value) { |
|
9802 |
+ return value || value === 0 ? value.toString().toUpperCase() : ''; |
|
9803 |
+ }, |
|
9804 |
+ |
|
9805 |
+ /** |
|
9806 |
+ * 'AbC' => 'abc' |
|
9807 |
+ */ |
|
9808 |
+ |
|
9809 |
+ lowercase: function lowercase(value) { |
|
9810 |
+ return value || value === 0 ? value.toString().toLowerCase() : ''; |
|
9811 |
+ }, |
|
9812 |
+ |
|
9813 |
+ /** |
|
9814 |
+ * 12345 => $12,345.00 |
|
9815 |
+ * |
|
9816 |
+ * @param {String} sign |
|
9817 |
+ * @param {Number} decimals Decimal places |
|
9818 |
+ */ |
|
9819 |
+ |
|
9820 |
+ currency: function currency(value, _currency, decimals) { |
|
9821 |
+ value = parseFloat(value); |
|
9822 |
+ if (!isFinite(value) || !value && value !== 0) return ''; |
|
9823 |
+ _currency = _currency != null ? _currency : '$'; |
|
9824 |
+ decimals = decimals != null ? decimals : 2; |
|
9825 |
+ var stringified = Math.abs(value).toFixed(decimals); |
|
9826 |
+ var _int = decimals ? stringified.slice(0, -1 - decimals) : stringified; |
|
9827 |
+ var i = _int.length % 3; |
|
9828 |
+ var head = i > 0 ? _int.slice(0, i) + (_int.length > 3 ? ',' : '') : ''; |
|
9829 |
+ var _float = decimals ? stringified.slice(-1 - decimals) : ''; |
|
9830 |
+ var sign = value < 0 ? '-' : ''; |
|
9831 |
+ return sign + _currency + head + _int.slice(i).replace(digitsRE, '$1,') + _float; |
|
9832 |
+ }, |
|
9833 |
+ |
|
9834 |
+ /** |
|
9835 |
+ * 'item' => 'items' |
|
9836 |
+ * |
|
9837 |
+ * @params |
|
9838 |
+ * an array of strings corresponding to |
|
9839 |
+ * the single, double, triple ... forms of the word to |
|
9840 |
+ * be pluralized. When the number to be pluralized |
|
9841 |
+ * exceeds the length of the args, it will use the last |
|
9842 |
+ * entry in the array. |
|
9843 |
+ * |
|
9844 |
+ * e.g. ['single', 'double', 'triple', 'multiple'] |
|
9845 |
+ */ |
|
9846 |
+ |
|
9847 |
+ pluralize: function pluralize(value) { |
|
9848 |
+ var args = toArray(arguments, 1); |
|
9849 |
+ var length = args.length; |
|
9850 |
+ if (length > 1) { |
|
9851 |
+ var index = value % 10 - 1; |
|
9852 |
+ return index in args ? args[index] : args[length - 1]; |
|
9853 |
+ } else { |
|
9854 |
+ return args[0] + (value === 1 ? '' : 's'); |
|
9855 |
+ } |
|
9856 |
+ }, |
|
9857 |
+ |
|
9858 |
+ /** |
|
9859 |
+ * Debounce a handler function. |
|
9860 |
+ * |
|
9861 |
+ * @param {Function} handler |
|
9862 |
+ * @param {Number} delay = 300 |
|
9863 |
+ * @return {Function} |
|
9864 |
+ */ |
|
9865 |
+ |
|
9866 |
+ debounce: function debounce(handler, delay) { |
|
9867 |
+ if (!handler) return; |
|
9868 |
+ if (!delay) { |
|
9869 |
+ delay = 300; |
|
9870 |
+ } |
|
9871 |
+ return _debounce(handler, delay); |
|
9872 |
+ } |
|
9873 |
+ }; |
|
9874 |
+ |
|
9875 |
+ function installGlobalAPI (Vue) { |
|
9876 |
+ /** |
|
9877 |
+ * Vue and every constructor that extends Vue has an |
|
9878 |
+ * associated options object, which can be accessed during |
|
9879 |
+ * compilation steps as `this.constructor.options`. |
|
9880 |
+ * |
|
9881 |
+ * These can be seen as the default options of every |
|
9882 |
+ * Vue instance. |
|
9883 |
+ */ |
|
9884 |
+ |
|
9885 |
+ Vue.options = { |
|
9886 |
+ directives: directives, |
|
9887 |
+ elementDirectives: elementDirectives, |
|
9888 |
+ filters: filters, |
|
9889 |
+ transitions: {}, |
|
9890 |
+ components: {}, |
|
9891 |
+ partials: {}, |
|
9892 |
+ replace: true |
|
9893 |
+ }; |
|
9894 |
+ |
|
9895 |
+ /** |
|
9896 |
+ * Expose useful internals |
|
9897 |
+ */ |
|
9898 |
+ |
|
9899 |
+ Vue.util = util; |
|
9900 |
+ Vue.config = config; |
|
9901 |
+ Vue.set = set; |
|
9902 |
+ Vue['delete'] = del; |
|
9903 |
+ Vue.nextTick = nextTick; |
|
9904 |
+ |
|
9905 |
+ /** |
|
9906 |
+ * The following are exposed for advanced usage / plugins |
|
9907 |
+ */ |
|
9908 |
+ |
|
9909 |
+ Vue.compiler = compiler; |
|
9910 |
+ Vue.FragmentFactory = FragmentFactory; |
|
9911 |
+ Vue.internalDirectives = internalDirectives; |
|
9912 |
+ Vue.parsers = { |
|
9913 |
+ path: path, |
|
9914 |
+ text: text, |
|
9915 |
+ template: template, |
|
9916 |
+ directive: directive, |
|
9917 |
+ expression: expression |
|
9918 |
+ }; |
|
9919 |
+ |
|
9920 |
+ /** |
|
9921 |
+ * Each instance constructor, including Vue, has a unique |
|
9922 |
+ * cid. This enables us to create wrapped "child |
|
9923 |
+ * constructors" for prototypal inheritance and cache them. |
|
9924 |
+ */ |
|
9925 |
+ |
|
9926 |
+ Vue.cid = 0; |
|
9927 |
+ var cid = 1; |
|
9928 |
+ |
|
9929 |
+ /** |
|
9930 |
+ * Class inheritance |
|
9931 |
+ * |
|
9932 |
+ * @param {Object} extendOptions |
|
9933 |
+ */ |
|
9934 |
+ |
|
9935 |
+ Vue.extend = function (extendOptions) { |
|
9936 |
+ extendOptions = extendOptions || {}; |
|
9937 |
+ var Super = this; |
|
9938 |
+ var isFirstExtend = Super.cid === 0; |
|
9939 |
+ if (isFirstExtend && extendOptions._Ctor) { |
|
9940 |
+ return extendOptions._Ctor; |
|
9941 |
+ } |
|
9942 |
+ var name = extendOptions.name || Super.options.name; |
|
9943 |
+ if ('development' !== 'production') { |
|
9944 |
+ if (!/^[a-zA-Z][\w-]*$/.test(name)) { |
|
9945 |
+ warn('Invalid component name: "' + name + '". Component names ' + 'can only contain alphanumeric characaters and the hyphen.'); |
|
9946 |
+ name = null; |
|
9947 |
+ } |
|
9948 |
+ } |
|
9949 |
+ var Sub = createClass(name || 'VueComponent'); |
|
9950 |
+ Sub.prototype = Object.create(Super.prototype); |
|
9951 |
+ Sub.prototype.constructor = Sub; |
|
9952 |
+ Sub.cid = cid++; |
|
9953 |
+ Sub.options = mergeOptions(Super.options, extendOptions); |
|
9954 |
+ Sub['super'] = Super; |
|
9955 |
+ // allow further extension |
|
9956 |
+ Sub.extend = Super.extend; |
|
9957 |
+ // create asset registers, so extended classes |
|
9958 |
+ // can have their private assets too. |
|
9959 |
+ config._assetTypes.forEach(function (type) { |
|
9960 |
+ Sub[type] = Super[type]; |
|
9961 |
+ }); |
|
9962 |
+ // enable recursive self-lookup |
|
9963 |
+ if (name) { |
|
9964 |
+ Sub.options.components[name] = Sub; |
|
9965 |
+ } |
|
9966 |
+ // cache constructor |
|
9967 |
+ if (isFirstExtend) { |
|
9968 |
+ extendOptions._Ctor = Sub; |
|
9969 |
+ } |
|
9970 |
+ return Sub; |
|
9971 |
+ }; |
|
9972 |
+ |
|
9973 |
+ /** |
|
9974 |
+ * A function that returns a sub-class constructor with the |
|
9975 |
+ * given name. This gives us much nicer output when |
|
9976 |
+ * logging instances in the console. |
|
9977 |
+ * |
|
9978 |
+ * @param {String} name |
|
9979 |
+ * @return {Function} |
|
9980 |
+ */ |
|
9981 |
+ |
|
9982 |
+ function createClass(name) { |
|
9983 |
+ /* eslint-disable no-new-func */ |
|
9984 |
+ return new Function('return function ' + classify(name) + ' (options) { this._init(options) }')(); |
|
9985 |
+ /* eslint-enable no-new-func */ |
|
9986 |
+ } |
|
9987 |
+ |
|
9988 |
+ /** |
|
9989 |
+ * Plugin system |
|
9990 |
+ * |
|
9991 |
+ * @param {Object} plugin |
|
9992 |
+ */ |
|
9993 |
+ |
|
9994 |
+ Vue.use = function (plugin) { |
|
9995 |
+ /* istanbul ignore if */ |
|
9996 |
+ if (plugin.installed) { |
|
9997 |
+ return; |
|
9998 |
+ } |
|
9999 |
+ // additional parameters |
|
10000 |
+ var args = toArray(arguments, 1); |
|
10001 |
+ args.unshift(this); |
|
10002 |
+ if (typeof plugin.install === 'function') { |
|
10003 |
+ plugin.install.apply(plugin, args); |
|
10004 |
+ } else { |
|
10005 |
+ plugin.apply(null, args); |
|
10006 |
+ } |
|
10007 |
+ plugin.installed = true; |
|
10008 |
+ return this; |
|
10009 |
+ }; |
|
10010 |
+ |
|
10011 |
+ /** |
|
10012 |
+ * Apply a global mixin by merging it into the default |
|
10013 |
+ * options. |
|
10014 |
+ */ |
|
10015 |
+ |
|
10016 |
+ Vue.mixin = function (mixin) { |
|
10017 |
+ Vue.options = mergeOptions(Vue.options, mixin); |
|
10018 |
+ }; |
|
10019 |
+ |
|
10020 |
+ /** |
|
10021 |
+ * Create asset registration methods with the following |
|
10022 |
+ * signature: |
|
10023 |
+ * |
|
10024 |
+ * @param {String} id |
|
10025 |
+ * @param {*} definition |
|
10026 |
+ */ |
|
10027 |
+ |
|
10028 |
+ config._assetTypes.forEach(function (type) { |
|
10029 |
+ Vue[type] = function (id, definition) { |
|
10030 |
+ if (!definition) { |
|
10031 |
+ return this.options[type + 's'][id]; |
|
10032 |
+ } else { |
|
10033 |
+ /* istanbul ignore if */ |
|
10034 |
+ if ('development' !== 'production') { |
|
10035 |
+ if (type === 'component' && (commonTagRE.test(id) || reservedTagRE.test(id))) { |
|
10036 |
+ warn('Do not use built-in or reserved HTML elements as component ' + 'id: ' + id); |
|
10037 |
+ } |
|
10038 |
+ } |
|
10039 |
+ if (type === 'component' && isPlainObject(definition)) { |
|
10040 |
+ if (!definition.name) { |
|
10041 |
+ definition.name = id; |
|
10042 |
+ } |
|
10043 |
+ definition = Vue.extend(definition); |
|
10044 |
+ } |
|
10045 |
+ this.options[type + 's'][id] = definition; |
|
10046 |
+ return definition; |
|
10047 |
+ } |
|
10048 |
+ }; |
|
10049 |
+ }); |
|
10050 |
+ |
|
10051 |
+ // expose internal transition API |
|
10052 |
+ extend(Vue.transition, transition); |
|
10053 |
+ } |
|
10054 |
+ |
|
10055 |
+ installGlobalAPI(Vue); |
|
10056 |
+ |
|
10057 |
+ Vue.version = '1.0.26'; |
|
10058 |
+ |
|
10059 |
+ // devtools global hook |
|
10060 |
+ /* istanbul ignore next */ |
|
10061 |
+ setTimeout(function () { |
|
10062 |
+ if (config.devtools) { |
|
10063 |
+ if (devtools) { |
|
10064 |
+ devtools.emit('init', Vue); |
|
10065 |
+ } else if ('development' !== 'production' && inBrowser && /Chrome\/\d+/.test(window.navigator.userAgent)) { |
|
10066 |
+ console.log('Download the Vue Devtools for a better development experience:\n' + 'https://github.com/vuejs/vue-devtools'); |
|
10067 |
+ } |
|
10068 |
+ } |
|
10069 |
+ }, 0); |
|
10070 |
+ |
|
10071 |
+ return Vue; |
|
10072 |
+ |
|
10073 |
+})); |
|
0 | 10074 |
\ No newline at end of file |
1 | 10075 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,155 @@ |
1 |
+(defpackage :alimenta.feed-archive |
|
2 |
+ (:use :cl :alexandria :serapeum :fw.lu)) |
|
3 |
+ |
|
4 |
+(in-package :alimenta.feed-archive) |
|
5 |
+ |
|
6 |
+(defvar *feeds*) |
|
7 |
+(defvar *feed-base*) |
|
8 |
+ |
|
9 |
+(defparameter +dirname-format+ |
|
10 |
+ '((:year 4) #\- (:month 2) #\- (:day 2) #\/ (:hour 2) #\- (:min 2) #\/)) |
|
11 |
+ |
|
12 |
+(defun get-store-directory-name (timestamp) |
|
13 |
+ (car |
|
14 |
+ (prog1-let ((result (merge-pathnames |
|
15 |
+ (local-time:format-timestring |
|
16 |
+ nil |
|
17 |
+ (local-time:timestamp-minimize-part timestamp :sec) |
|
18 |
+ :format +dirname-format+) |
|
19 |
+ *feed-base*))) |
|
20 |
+ (ensure-directories-exist result)))) |
|
21 |
+ |
|
22 |
+(defmethod store ((feed alimenta:feed) directory) |
|
23 |
+ (with-accessors ((description alimenta:description) |
|
24 |
+ (feed-link alimenta:feed-link) |
|
25 |
+ (items alimenta:items) |
|
26 |
+ (link alimenta:link) |
|
27 |
+ (source-type alimenta:source-type) |
|
28 |
+ (title alimenta:title)) feed |
|
29 |
+ (prog1-let ((feed-title title) |
|
30 |
+ (feed-store (merge-pathnames (concatenate 'string (puri:uri-host feed-link) |
|
31 |
+ "/") |
|
32 |
+ directory))) |
|
33 |
+ (ensure-directories-exist feed-store) |
|
34 |
+ (with-open-file (index (merge-pathnames "index.json" feed-store) :direction :output) |
|
35 |
+ (yason:with-output (index :indent t) |
|
36 |
+ (yason:with-object () |
|
37 |
+ (yason:encode-object-element "title" title) |
|
38 |
+ (yason:encode-object-element "fetch-url" |
|
39 |
+ (puri:render-uri feed-link nil)) |
|
40 |
+ (yason:encode-object-element "link" link) |
|
41 |
+ ;(yason:encode-object-element "source-type" source-type) |
|
42 |
+ (yason:encode-object-element "description" description) |
|
43 |
+ (yason:with-object-element ("items") |
|
44 |
+ (yason:with-array () |
|
45 |
+ (dolist (item (store items feed-store)) |
|
46 |
+ (destructuring-bind (title path) item |
|
47 |
+ (yason:with-object () |
|
48 |
+ (yason:encode-object-element "title" title) |
|
49 |
+ (yason:encode-object-element "path" path)))))))))))) |
|
50 |
+ |
|
51 |
+(defun older-than-a-week (date) |
|
52 |
+ (let ((week-ago (local-time:timestamp- (local-time:now) |
|
53 |
+ 7 :day))) |
|
54 |
+ (local-time:timestamp< date week-ago))) |
|
55 |
+ |
|
56 |
+(defmethod store ((items sequence) directory) |
|
57 |
+ (map 'list (lambda (item) (store item directory)) |
|
58 |
+ (stable-sort |
|
59 |
+ (sort (remove-if #'older-than-a-week items :key #'alimenta:date) |
|
60 |
+ #'string-lessp |
|
61 |
+ :key #'alimenta:title) |
|
62 |
+ #'local-time:timestamp> |
|
63 |
+ :key #'alimenta:date))) |
|
64 |
+ |
|
65 |
+(defmethod get-id ((item alimenta:item)) |
|
66 |
+ (let* ((digester (ironclad:make-digesting-stream :sha256)) |
|
67 |
+ (digest-stream (flexi-streams:make-flexi-stream digester))) |
|
68 |
+ (princ (alimenta:id item) digest-stream) |
|
69 |
+ (concatenate 'string |
|
70 |
+ (local-time:format-timestring nil (alimenta:date item)) |
|
71 |
+ "-" |
|
72 |
+ (crypto:byte-array-to-hex-string (crypto:produce-digest digester)) |
|
73 |
+ ".json"))) |
|
74 |
+ |
|
75 |
+(defmethod store ((item alimenta:item) directory) |
|
76 |
+ (with-accessors ((author alimenta::author) |
|
77 |
+ (content alimenta:content) |
|
78 |
+ (date alimenta:date) |
|
79 |
+ (id alimenta:id) |
|
80 |
+ (link alimenta:link) |
|
81 |
+ (title alimenta:title)) item |
|
82 |
+ (let* ((date (local-time:format-timestring nil date))) |
|
83 |
+ (prog1-let ((item-title title) |
|
84 |
+ (fn (get-id item))) |
|
85 |
+ (with-open-file (item-f (merge-pathnames fn directory) :direction :output) |
|
86 |
+ (yason:with-output (item-f :indent t) |
|
87 |
+ (yason:with-object () |
|
88 |
+ (yason:encode-object-element "title" title) |
|
89 |
+ (yason:encode-object-element "date" date) |
|
90 |
+ (yason:encode-object-element "author" title) |
|
91 |
+ (yason:encode-object-element "id" (princ-to-string id)) |
|
92 |
+ (yason:encode-object-element "link" link) |
|
93 |
+ (yason:encode-object-element "content" content))))) ))) |
|
94 |
+ |
|
95 |
+ |
|
96 |
+(defun init-feeds (&key feed-list archive-root) |
|
97 |
+ (ubiquitous:restore 'alimenta.feed-archiver) |
|
98 |
+ (let ((default-root (or archive-root |
|
99 |
+ (merge-pathnames ".feed-archive/" |
|
100 |
+ (truename "~/"))))) |
|
101 |
+ (values (ubiquitous:defaulted-value feed-list :feeds) |
|
102 |
+ (ubiquitous:defaulted-value default-root :archive :root)))) |
|
103 |
+ |
|
104 |
+(defun add-feed (feed) |
|
105 |
+ (init-feeds) |
|
106 |
+ (pushnew feed |
|
107 |
+ (ubiquitous:value :feeds) |
|
108 |
+ :test #'equalp)) |
|
109 |
+ |
|
110 |
+(defun safe-pull-feed (feed-url) |
|
111 |
+ (let ((pop-times 0)) |
|
112 |
+ (handler-bind |
|
113 |
+ ((condition |
|
114 |
+ (lambda (c) c |
|
115 |
+ (when (find-restart 'alimenta.rss::pop-token) |
|
116 |
+ (if (< pop-times 50) |
|
117 |
+ (progn (incf pop-times) |
|
118 |
+ (format t "~&Processing error, trying to pop a token (popped ~d times)~%" |
|
119 |
+ pop-times) |
|
120 |
+ (alimenta.rss::pop-token)) |
|
121 |
+ (progn |
|
122 |
+ (break) |
|
123 |
+ (continue))))))) |
|
124 |
+ (prog1 (alimenta.pull-feed:pull-feed feed-url) |
|
125 |
+ (decf pop-times))))) |
|
126 |
+ |
|
127 |
+(defun archive-feeds () |
|
128 |
+ (declare (optimize (debug 3))) |
|
129 |
+ (multiple-value-bind (*feeds* *feed-base*) (init-feeds) |
|
130 |
+ (let ((pull-time (local-time:now))) |
|
131 |
+ (alimenta.pull-feed::with-user-agent ("Feed Archiver v0.1b") |
|
132 |
+ (let* ((pull-directory (get-store-directory-name pull-time)) |
|
133 |
+ (paths (loop for feed-url in *feeds* collect |
|
134 |
+ (with-simple-restart (continue "Skip ~a" feed-url) |
|
135 |
+ (let ((feed (safe-pull-feed feed-url))) |
|
136 |
+ (setf (alimenta:feed-link feed) |
|
137 |
+ feed-url) |
|
138 |
+ (store feed pull-directory)))))) |
|
139 |
+ (with-open-file (index (merge-pathnames "index.json" pull-directory) :direction :output) |
|
140 |
+ (yason:with-output (index :indent t) |
|
141 |
+ (yason:with-object () |
|
142 |
+ (yason:encode-object-element "pull-time" (local-time:format-timestring nil pull-time)) |
|
143 |
+ (yason:encode-object-element "feed-urls" *feeds*) |
|
144 |
+ (yason:with-object-element ("feeds") |
|
145 |
+ (yason:with-array () |
|
146 |
+ (mapcar (lambda (url feed-data) |
|
147 |
+ (yason:with-object () |
|
148 |
+ (yason:encode-object-element "url" url) |
|
149 |
+ (destructuring-bind (title path) feed-data |
|
150 |
+ (yason:encode-object-element "title" title) |
|
151 |
+ (yason:encode-object-element "path" |
|
152 |
+ (princ-to-string |
|
153 |
+ (uiop:enough-pathname path *feed-base*)))))) |
|
154 |
+ *feeds* |
|
155 |
+ paths))))))))))) |