Version Description
- Fixed a bug with numeric validation used a different decimal sign than a dot.
- Fixed a compatibility issue with the The7 theme.
- Display an admin message if there are other Toolset plugins that are not registered. This should not affect local and staging sites.
Download this release
Release Info
Developer | zaantar |
Plugin | Toolset Types – Custom Post Types, Custom Fields and Taxonomies |
Version | 2.2.16 |
Comparing to | |
See all releases |
Code changes from version 2.2.15.1 to 2.2.16
- application/autoload_classmap.php +212 -211
- application/bootstrap.php +8 -8
- application/controllers/ajax.php +19 -1
- application/controllers/asset/manager.php +0 -7
- application/controllers/interop/handler/the7.php +81 -0
- application/controllers/interop/handler/wpml.php +1 -1
- application/controllers/interop/mediator.php +30 -0
- application/controllers/twig_autoloader.php +1 -1
- application/controllers/upgrade.php +15 -0
- application/models/helper/create/layout.php +1 -1
- library/toolset/onthego-resources/onthegosystems-icons/.fontcustom-manifest.json +0 -204
- library/toolset/toolset-common/res/lib/knockout/knockout-3.4.0.debug.js +0 -5871
- library/toolset/toolset-common/res/lib/knockout/knockout-3.4.0.js +0 -123
- library/toolset/toolset-common/visual-editor/res/js/codemirror/.gitattributes +0 -8
- library/toolset/toolset-common/visual-editor/res/js/codemirror/.npmignore +0 -9
- library/toolset/toolset-common/visual-editor/res/js/codemirror/.travis.yml +0 -4
- library/toolset/toolset-common/visual-editor/res/js/codemirror/test/comment_test.js +0 -100
- library/toolset/toolset-common/visual-editor/res/js/codemirror/test/doc_test.js +0 -371
- library/toolset/toolset-common/visual-editor/res/js/codemirror/test/driver.js +0 -138
- library/toolset/toolset-common/visual-editor/res/js/codemirror/test/emacs_test.js +0 -147
- library/toolset/toolset-common/visual-editor/res/js/codemirror/test/index.html +0 -241
- library/toolset/toolset-common/visual-editor/res/js/codemirror/test/lint.js +0 -11
- library/toolset/toolset-common/visual-editor/res/js/codemirror/test/lint/acorn.js +0 -1782
- library/toolset/toolset-common/visual-editor/res/js/codemirror/test/lint/lint.js +0 -166
- library/toolset/toolset-common/visual-editor/res/js/codemirror/test/lint/walk.js +0 -313
- library/toolset/toolset-common/visual-editor/res/js/codemirror/test/mode_test.css +0 -23
- library/toolset/toolset-common/visual-editor/res/js/codemirror/test/mode_test.js +0 -192
- library/toolset/toolset-common/visual-editor/res/js/codemirror/test/multi_test.js +0 -285
- library/toolset/toolset-common/visual-editor/res/js/codemirror/test/phantom_driver.js +0 -31
- library/toolset/toolset-common/visual-editor/res/js/codemirror/test/run.js +0 -31
- library/toolset/toolset-common/visual-editor/res/js/codemirror/test/scroll_test.js +0 -115
- library/toolset/toolset-common/visual-editor/res/js/codemirror/test/search_test.js +0 -62
- library/toolset/toolset-common/visual-editor/res/js/codemirror/test/sql-hint-test.js +0 -189
- library/toolset/toolset-common/visual-editor/res/js/codemirror/test/sublime_test.js +0 -303
- library/toolset/toolset-common/visual-editor/res/js/codemirror/test/test.js +0 -2142
- library/toolset/toolset-common/visual-editor/res/js/codemirror/test/vim_test.js +0 -3955
- library/toolset/types/.travis.yml +0 -29
- library/twig/twig/.editorconfig +0 -18
- library/twig/twig/.php_cs.dist +0 -15
- library/twig/twig/.travis.yml +0 -43
- library/twig/twig/doc/advanced.rst +0 -962
- library/twig/twig/doc/advanced_legacy.rst +0 -885
- library/twig/twig/doc/api.rst +0 -590
- library/twig/twig/doc/coding_standards.rst +0 -101
- library/twig/twig/doc/deprecated.rst +0 -224
- library/twig/twig/doc/filters/abs.rst +0 -18
- library/twig/twig/doc/filters/batch.rst +0 -51
- library/twig/twig/doc/filters/capitalize.rst +0 -11
- library/twig/twig/doc/filters/convert_encoding.rst +0 -28
- library/twig/twig/doc/filters/date.rst +0 -100
- library/twig/twig/doc/filters/date_modify.rst +0 -23
- library/twig/twig/doc/filters/default.rst +0 -33
- library/twig/twig/doc/filters/escape.rst +0 -119
- library/twig/twig/doc/filters/first.rst +0 -25
- library/twig/twig/doc/filters/format.rst +0 -16
- library/twig/twig/doc/filters/index.rst +0 -37
- library/twig/twig/doc/filters/join.rst +0 -23
- library/twig/twig/doc/filters/json_encode.rst +0 -21
- library/twig/twig/doc/filters/keys.rst +0 -11
- library/twig/twig/doc/filters/last.rst +0 -25
- library/twig/twig/doc/filters/length.rst +0 -21
- library/twig/twig/doc/filters/lower.rst +0 -10
- library/twig/twig/doc/filters/merge.rst +0 -48
- library/twig/twig/doc/filters/nl2br.rst +0 -22
- library/twig/twig/doc/filters/number_format.rst +0 -48
- library/twig/twig/doc/filters/raw.rst +0 -36
- library/twig/twig/doc/filters/replace.rst +0 -19
- library/twig/twig/doc/filters/reverse.rst +0 -47
- library/twig/twig/doc/filters/round.rst +0 -37
- library/twig/twig/doc/filters/slice.rst +0 -71
- library/twig/twig/doc/filters/sort.rst +0 -18
- library/twig/twig/doc/filters/split.rst +0 -53
- library/twig/twig/doc/filters/striptags.rst +0 -29
- library/twig/twig/doc/filters/title.rst +0 -11
- library/twig/twig/doc/filters/trim.rst +0 -45
- library/twig/twig/doc/filters/upper.rst +0 -10
- library/twig/twig/doc/filters/url_encode.rst +0 -34
- library/twig/twig/doc/functions/attribute.rst +0 -26
- library/twig/twig/doc/functions/block.rst +0 -41
- library/twig/twig/doc/functions/constant.rst +0 -29
- library/twig/twig/doc/functions/cycle.rst +0 -28
- library/twig/twig/doc/functions/date.rst +0 -55
- library/twig/twig/doc/functions/dump.rst +0 -69
- library/twig/twig/doc/functions/include.rst +0 -84
- library/twig/twig/doc/functions/index.rst +0 -20
- library/twig/twig/doc/functions/max.rst +0 -20
- library/twig/twig/doc/functions/min.rst +0 -20
- library/twig/twig/doc/functions/parent.rst +0 -20
- library/twig/twig/doc/functions/random.rst +0 -29
- library/twig/twig/doc/functions/range.rst +0 -58
- library/twig/twig/doc/functions/source.rst +0 -32
- library/twig/twig/doc/functions/template_from_string.rst +0 -32
- library/twig/twig/doc/index.rst +0 -19
- library/twig/twig/doc/installation.rst +0 -116
- library/twig/twig/doc/internals.rst +0 -142
- library/twig/twig/doc/intro.rst +0 -85
- library/twig/twig/doc/recipes.rst +0 -568
- library/twig/twig/doc/tags/autoescape.rst +0 -81
- library/twig/twig/doc/tags/block.rst +0 -11
- library/twig/twig/doc/tags/do.rst +0 -12
- library/twig/twig/doc/tags/embed.rst +0 -178
- library/twig/twig/doc/tags/extends.rst +0 -272
- library/twig/twig/doc/tags/filter.rst +0 -21
- library/twig/twig/doc/tags/flush.rst +0 -17
- library/twig/twig/doc/tags/for.rst +0 -172
- library/twig/twig/doc/tags/from.rst +0 -8
- library/twig/twig/doc/tags/if.rst +0 -76
- library/twig/twig/doc/tags/import.rst +0 -57
- library/twig/twig/doc/tags/include.rst +0 -90
- library/twig/twig/doc/tags/index.rst +0 -25
- library/twig/twig/doc/tags/macro.rst +0 -103
- library/twig/twig/doc/tags/sandbox.rst +0 -30
- library/twig/twig/doc/tags/set.rst +0 -78
- library/twig/twig/doc/tags/spaceless.rst +0 -37
- library/twig/twig/doc/tags/use.rst +0 -77
application/autoload_classmap.php
CHANGED
@@ -1,215 +1,216 @@
|
|
1 |
<?php
|
2 |
// Generated by ZF2's ./bin/classmap_generator.php
|
3 |
return array(
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
|
|
215 |
);
|
1 |
<?php
|
2 |
// Generated by ZF2's ./bin/classmap_generator.php
|
3 |
return array(
|
4 |
+
'Enlimbo_Forms_Wpcf' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/forms.php',
|
5 |
+
'Toolset_Filesystem_Directory' => dirname( __FILE__ ) . '/../vendor/toolset/filesystem/directory.php',
|
6 |
+
'Toolset_Filesystem_Exception' => dirname( __FILE__ ) . '/../vendor/toolset/filesystem/exception.php',
|
7 |
+
'Toolset_Filesystem_File' => dirname( __FILE__ ) . '/../vendor/toolset/filesystem/file.php',
|
8 |
+
'Types_Admin' => dirname( __FILE__ ) . '/controllers/admin.php',
|
9 |
+
'Types_Admin_Edit_Custom_Fields_Group' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/class.types.admin.edit.custom.fields.group.php',
|
10 |
+
'Types_Admin_Edit_Fields' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/class.types.admin.edit.fields.php',
|
11 |
+
'Types_Admin_Edit_Meta_Fields_Group' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/class.types.admin.edit.meta.fields.group.php',
|
12 |
+
'Types_Admin_Edit_Post_Type' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/class.types.admin.edit.post.type.php',
|
13 |
+
'Types_Admin_Edit_Taxonomy' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/class.types.admin.edit.taxonomy.php',
|
14 |
+
'Types_Admin_Fields' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/class.types.admin.fields.php',
|
15 |
+
'Types_Admin_Menu' => dirname( __FILE__ ) . '/controllers/admin_menu.php',
|
16 |
+
'Types_Admin_Page' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/class.types.admin.page.php',
|
17 |
+
'Types_Admin_Post_Type' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/class.types.admin.post-type.php',
|
18 |
+
'Types_Admin_Post_Types_List_Table' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/class.types.admin.post.types.list.table.php',
|
19 |
+
'Types_Admin_Taxonomies' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/class.types.admin.taxonomies.php',
|
20 |
+
'Types_Admin_Taxonomies_List_Table' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/class.types.admin.taxonomies.list.table.php',
|
21 |
+
'Types_Admin_Usermeta_Control_Table' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/class.types.admin.usermeta.table.php',
|
22 |
+
'Types_Admin_Usermeta_Groups_List_Table' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/class.types.admin.usermeta.groups.list.table.php',
|
23 |
+
'Types_Ajax' => dirname( __FILE__ ) . '/controllers/ajax.php',
|
24 |
+
'Types_Ajax_Handler_Abstract' => dirname( __FILE__ ) . '/controllers/ajax/handler/abstract.php',
|
25 |
+
'Types_Ajax_Handler_Check_Slug_Conflicts' => dirname( __FILE__ ) . '/controllers/ajax/handler/check_slug_conflicts.php',
|
26 |
+
'Types_Ajax_Handler_Field_Control_Action' => dirname( __FILE__ ) . '/controllers/ajax/handler/field_control_action.php',
|
27 |
+
'Types_Ajax_Handler_Interface' => dirname( __FILE__ ) . '/controllers/ajax/handler_interface.php',
|
28 |
+
'Types_Ajax_Handler_Settings_Action' => dirname( __FILE__ ) . '/controllers/ajax/handler/settings_action.php',
|
29 |
+
'Types_Api' => dirname( __FILE__ ) . '/controllers/api.php',
|
30 |
+
'Types_Api_Handler_Filter_Get_Field_Group_Ids_By_Post_Type' => dirname( __FILE__ ) . '/controllers/api/handler/filter_get_field_group_ids_by_post_type.php',
|
31 |
+
'Types_Api_Handler_Import_From_Zip_File' => dirname( __FILE__ ) . '/controllers/api/handler/import_from_zip_file.php',
|
32 |
+
'Types_Api_Handler_Interface' => dirname( __FILE__ ) . '/controllers/api/handler/interface.php',
|
33 |
+
'Types_Api_Handler_Query_Groups' => dirname( __FILE__ ) . '/controllers/api/handler/query_groups.php',
|
34 |
+
'Types_Asset_Help_Tab_Loader' => dirname( __FILE__ ) . '/controllers/asset/help_tab_loader.php',
|
35 |
+
'Types_Asset_Manager' => dirname( __FILE__ ) . '/controllers/asset/manager.php',
|
36 |
+
'Types_Assets' => dirname( __FILE__ ) . '/controllers/assets.php',
|
37 |
+
'Types_Dialog_Box' => dirname( __FILE__ ) . '/controllers/dialog_box.php',
|
38 |
+
'Types_Embedded' => dirname( __FILE__ ) . '/controllers/embedded.php',
|
39 |
+
'Types_Field_Group' => dirname( __FILE__ ) . '/models/field/group.php',
|
40 |
+
'Types_Field_Group_Factory' => dirname( __FILE__ ) . '/models/field/group/factory.php',
|
41 |
+
'Types_Field_Group_Post' => dirname( __FILE__ ) . '/models/field/group/post.php',
|
42 |
+
'Types_Field_Group_Post_Factory' => dirname( __FILE__ ) . '/models/field/group/post_factory.php',
|
43 |
+
'Types_Field_Group_Term' => dirname( __FILE__ ) . '/models/field/group/term.php',
|
44 |
+
'Types_Field_Group_Term_Factory' => dirname( __FILE__ ) . '/models/field/group/term_factory.php',
|
45 |
+
'Types_Field_Group_User' => dirname( __FILE__ ) . '/models/field/group/user.php',
|
46 |
+
'Types_Field_Group_User_Factory' => dirname( __FILE__ ) . '/models/field/group/user_factory.php',
|
47 |
+
'Types_Fields_Conditional' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/class.types.fields.conditional.php',
|
48 |
+
'Types_Field_Type_Converter' => dirname( __FILE__ ) . '/controllers/field/type_converter.php',
|
49 |
+
'Types_Field_Type_Definition_Checkbox' => dirname( __FILE__ ) . '/models/field/type/definition/checkbox.php',
|
50 |
+
'Types_Field_Type_Definition_Checkboxes' => dirname( __FILE__ ) . '/models/field/type/definition/checkboxes.php',
|
51 |
+
'Types_Field_Type_Definition_Date' => dirname( __FILE__ ) . '/models/field/type/definition/date.php',
|
52 |
+
'Types_Field_Type_Definition' => dirname( __FILE__ ) . '/models/field/type/definition.php',
|
53 |
+
'Types_Field_Type_Definition_Factory' => dirname( __FILE__ ) . '/models/field/type/definition_factory.php',
|
54 |
+
'Types_Field_Type_Definition_Numeric' => dirname( __FILE__ ) . '/models/field/type/definition/numeric.php',
|
55 |
+
'Types_Field_Type_Definition_Radio' => dirname( __FILE__ ) . '/models/field/type/definition/radio.php',
|
56 |
+
'Types_Field_Type_Definition_Select' => dirname( __FILE__ ) . '/models/field/type/definition/select.php',
|
57 |
+
'Types_Field_Type_Definition_Singular' => dirname( __FILE__ ) . '/models/field/type/definition/singular.php',
|
58 |
+
'Types_Field_Utils' => dirname( __FILE__ ) . '/controllers/field/utils.php',
|
59 |
+
'Types_Frontend' => dirname( __FILE__ ) . '/controllers/frontend.php',
|
60 |
+
'Types_Helper_Condition_Archive_Exists' => dirname( __FILE__ ) . '/models/helper/condition/archive/exists.php',
|
61 |
+
'Types_Helper_Condition_Archive_Has_Fields' => dirname( __FILE__ ) . '/models/helper/condition/archive/has_fields.php',
|
62 |
+
'Types_Helper_Condition_Archive_Missing' => dirname( __FILE__ ) . '/models/helper/condition/archive/missing.php',
|
63 |
+
'Types_Helper_Condition_Archive_No_Fields' => dirname( __FILE__ ) . '/models/helper/condition/archive/no_fields.php',
|
64 |
+
'Types_Helper_Condition_Archive_No_Support' => dirname( __FILE__ ) . '/models/helper/condition/archive/no_support.php',
|
65 |
+
'Types_Helper_Condition_Archive_Support' => dirname( __FILE__ ) . '/models/helper/condition/archive/support.php',
|
66 |
+
'Types_Helper_Condition_Cred_Active' => dirname( __FILE__ ) . '/models/helper/condition/cred/active.php',
|
67 |
+
'Types_Helper_Condition_Cred_Forms_Exist' => dirname( __FILE__ ) . '/models/helper/condition/cred/forms_exist.php',
|
68 |
+
'Types_Helper_Condition_Cred_Forms_Missing' => dirname( __FILE__ ) . '/models/helper/condition/cred/forms_missing.php',
|
69 |
+
'Types_Helper_Condition_Cred_Missing' => dirname( __FILE__ ) . '/models/helper/condition/cred/missing.php',
|
70 |
+
'Types_Helper_Condition' => dirname( __FILE__ ) . '/models/helper/condition.php',
|
71 |
+
'Types_Helper_Condition_Layouts_Active' => dirname( __FILE__ ) . '/models/helper/condition/layouts/active.php',
|
72 |
+
'Types_Helper_Condition_Layouts_Archive_Exists' => dirname( __FILE__ ) . '/models/helper/condition/layouts/archive_exists.php',
|
73 |
+
'Types_Helper_Condition_Layouts_Archive_Missing' => dirname( __FILE__ ) . '/models/helper/condition/layouts/archive_missing.php',
|
74 |
+
'Types_Helper_Condition_Layouts_Compatible' => dirname( __FILE__ ) . '/models/helper/condition/layouts/compatible.php',
|
75 |
+
'Types_Helper_Condition_Layouts_Missing' => dirname( __FILE__ ) . '/models/helper/condition/layouts/missing.php',
|
76 |
+
'Types_Helper_Condition_Layouts_Template_Exists' => dirname( __FILE__ ) . '/models/helper/condition/layouts/template_exists.php',
|
77 |
+
'Types_Helper_Condition_Layouts_Template_Missing' => dirname( __FILE__ ) . '/models/helper/condition/layouts/template_missing.php',
|
78 |
+
'Types_Helper_Condition_Screen' => dirname( __FILE__ ) . '/models/helper/condition/screen.php',
|
79 |
+
'Types_Helper_Condition_Single_Exists' => dirname( __FILE__ ) . '/models/helper/condition/single/exists.php',
|
80 |
+
'Types_Helper_Condition_Single_Has_Fields' => dirname( __FILE__ ) . '/models/helper/condition/single/has_fields.php',
|
81 |
+
'Types_Helper_Condition_Single_Missing' => dirname( __FILE__ ) . '/models/helper/condition/single/missing.php',
|
82 |
+
'Types_Helper_Condition_Single_No_Fields' => dirname( __FILE__ ) . '/models/helper/condition/single/no_fields.php',
|
83 |
+
'Types_Helper_Condition_Template' => dirname( __FILE__ ) . '/models/helper/condition/template.php',
|
84 |
+
'Types_Helper_Condition_Type_Fields_Assigned' => dirname( __FILE__ ) . '/models/helper/condition/type/fields_assigned.php',
|
85 |
+
'Types_Helper_Condition_Type_No_Post_Or_Page' => dirname( __FILE__ ) . '/models/helper/condition/type/no_post_or_page.php',
|
86 |
+
'Types_Helper_Condition_Type_Post_Or_Page' => dirname( __FILE__ ) . '/models/helper/condition/type/post_or_page.php',
|
87 |
+
'Types_Helper_Condition_Views_Active' => dirname( __FILE__ ) . '/models/helper/condition/views/active.php',
|
88 |
+
'Types_Helper_Condition_Views_Archive_Exists' => dirname( __FILE__ ) . '/models/helper/condition/views/archive_exists.php',
|
89 |
+
'Types_Helper_Condition_Views_Archive_Missing' => dirname( __FILE__ ) . '/models/helper/condition/views/archive_missing.php',
|
90 |
+
'Types_Helper_Condition_Views_Missing' => dirname( __FILE__ ) . '/models/helper/condition/views/missing.php',
|
91 |
+
'Types_Helper_Condition_Views_Template_Exists' => dirname( __FILE__ ) . '/models/helper/condition/views/template_exists.php',
|
92 |
+
'Types_Helper_Condition_Views_Template_Missing' => dirname( __FILE__ ) . '/models/helper/condition/views/template_missing.php',
|
93 |
+
'Types_Helper_Condition_Views_Views_Exist' => dirname( __FILE__ ) . '/models/helper/condition/views/views_exist.php',
|
94 |
+
'Types_Helper_Condition_Views_Views_Missing' => dirname( __FILE__ ) . '/models/helper/condition/views/views_missing.php',
|
95 |
+
'Types_Helper_Create_Content_Template' => dirname( __FILE__ ) . '/models/helper/create/content_template.php',
|
96 |
+
'Types_Helper_Create_Form' => dirname( __FILE__ ) . '/models/helper/create/form.php',
|
97 |
+
'Types_Helper_Create_Layout' => dirname( __FILE__ ) . '/models/helper/create/layout.php',
|
98 |
+
'Types_Helper_Create_View' => dirname( __FILE__ ) . '/models/helper/create/view.php',
|
99 |
+
'Types_Helper_Create_Wordpress_Archive' => dirname( __FILE__ ) . '/models/helper/create/wordpress_archive.php',
|
100 |
+
'Types_Helper_Output_Interface' => dirname( __FILE__ ) . '/models/helper/output/interface.php',
|
101 |
+
'Types_Helper_Output_Meta_Box' => dirname( __FILE__ ) . '/models/helper/output/meta_box.php',
|
102 |
+
'Types_Helper_Placeholder' => dirname( __FILE__ ) . '/models/helper/placeholder.php',
|
103 |
+
'Types_Helper_Twig' => dirname( __FILE__ ) . '/models/helper/twig.php',
|
104 |
+
'Types_Helper_Url' => dirname( __FILE__ ) . '/models/helper/url.php',
|
105 |
+
'Types_Import_Export' => dirname( __FILE__ ) . '/controllers/import_export.php',
|
106 |
+
'Types_Information_Container' => dirname( __FILE__ ) . '/models/information/container.php',
|
107 |
+
'Types_Information_Controller' => dirname( __FILE__ ) . '/controllers/information/controller.php',
|
108 |
+
'Types_Information_Message' => dirname( __FILE__ ) . '/models/information/message.php',
|
109 |
+
'Types_Information_Message_Post_Type' => dirname( __FILE__ ) . '/models/information/message/post_type.php',
|
110 |
+
'Types_Information_Table' => dirname( __FILE__ ) . '/models/information/table.php',
|
111 |
+
'Types_Interop_Handler_Divi' => dirname( __FILE__ ) . '/controllers/interop/handler/divi.php',
|
112 |
+
'Types_Interop_Handler_Interface' => dirname( __FILE__ ) . '/controllers/interop/handler_interface.php',
|
113 |
+
'Types_Interop_Handler_The7' => dirname( __FILE__ ) . '/controllers/interop/handler/the7.php',
|
114 |
+
'Types_Interop_Handler_Use_Any_Font' => dirname( __FILE__ ) . '/controllers/interop/handler/use_any_font.php',
|
115 |
+
'Types_Interop_Handler_Wpml' => dirname( __FILE__ ) . '/controllers/interop/handler/wpml.php',
|
116 |
+
'Types_Interop_Mediator' => dirname( __FILE__ ) . '/controllers/interop/mediator.php',
|
117 |
+
'Types_Main' => dirname( __FILE__ ) . '/controllers/main.php',
|
118 |
+
'Types_Page_Abstract' => dirname( __FILE__ ) . '/controllers/page/abstract.php',
|
119 |
+
'Types_Page_Dashboard' => dirname( __FILE__ ) . '/controllers/page/dashboard.php',
|
120 |
+
'Types_Page_Extension_Edit_Post' => dirname( __FILE__ ) . '/controllers/page/extension/edit_post.php',
|
121 |
+
'Types_Page_Extension_Edit_Post_Fields' => dirname( __FILE__ ) . '/controllers/page/extension/edit_post_fields.php',
|
122 |
+
'Types_Page_Extension_Edit_Post_Type' => dirname( __FILE__ ) . '/controllers/page/extension/edit_post_type.php',
|
123 |
+
'Types_Page_Extension_Settings' => dirname( __FILE__ ) . '/controllers/page/extension/settings.php',
|
124 |
+
'Types_Page_Field_Control' => dirname( __FILE__ ) . '/controllers/page/field_control.php',
|
125 |
+
'Types_Page_Hidden_Helper' => dirname( __FILE__ ) . '/controllers/page/hidden/helper.php',
|
126 |
+
'Types_Post_Type' => dirname( __FILE__ ) . '/models/post_type.php',
|
127 |
+
'Types_Setting_Boolean' => dirname( __FILE__ ) . '/models/setting/boolean.php',
|
128 |
+
'Types_Setting' => dirname( __FILE__ ) . '/models/setting.php',
|
129 |
+
'Types_Setting_Interface' => dirname( __FILE__ ) . '/models/setting/interface.php',
|
130 |
+
'Types_Setting_Option' => dirname( __FILE__ ) . '/models/setting/option.php',
|
131 |
+
'Types_Setting_Option_Interface' => dirname( __FILE__ ) . '/models/setting/option/interface.php',
|
132 |
+
'Types_Setting_Preset_Information_Table' => dirname( __FILE__ ) . '/models/setting/preset/information_table.php',
|
133 |
+
'Types_Taxonomy' => dirname( __FILE__ ) . '/models/taxonomy.php',
|
134 |
+
'Types_Twig_Autoloader' => dirname( __FILE__ ) . '/controllers/twig_autoloader.php',
|
135 |
+
'Types_Upgrade' => dirname( __FILE__ ) . '/controllers/upgrade.php',
|
136 |
+
'Types_Utils' => dirname( __FILE__ ) . '/controllers/utils.php',
|
137 |
+
'Types_Wpml_Field_Group' => dirname( __FILE__ ) . '/models/wpml/field_group.php',
|
138 |
+
'Types_Wpml_Field_Group_String_Description' => dirname( __FILE__ ) . '/models/wpml/field/group/string/description.php',
|
139 |
+
'Types_Wpml_Field_Group_String' => dirname( __FILE__ ) . '/models/wpml/field/group/string.php',
|
140 |
+
'Types_Wpml_Field_Group_String_Name' => dirname( __FILE__ ) . '/models/wpml/field/group/string/name.php',
|
141 |
+
'Types_Wpml_Interface' => dirname( __FILE__ ) . '/models/wpml/interface.php',
|
142 |
+
'Wpcf_Cake_Validation' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/validation-cakephp.php',
|
143 |
+
'WPCF_Conditional' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/conditional.php',
|
144 |
+
'WPCF_Custom_Fields_List_Table' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/class.wpcf.custom.fields.list.table.php',
|
145 |
+
'WPCF_Editor' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/editor.php',
|
146 |
+
'WPCF_Evaluate' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/evaluate.php',
|
147 |
+
'WPCF_Field_Accessor_Abstract' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/accessor/abstract.php',
|
148 |
+
'WPCF_Field_Accessor_Dummy' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/accessor/dummy.php',
|
149 |
+
'WPCF_Field_Accessor_Termmeta' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/accessor/termmeta.php',
|
150 |
+
'WPCF_Field_Accessor_Termmeta_Field' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/accessor/termmeta_field.php',
|
151 |
+
'WPCF_Field_DataMapper_Abstract' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/datamapper/abstract.php',
|
152 |
+
'WPCF_Field_DataMapper_Checkbox' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/datamapper/checkbox.php',
|
153 |
+
'WPCF_Field_DataMapper_Checkboxes' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/datamapper/checkboxes.php',
|
154 |
+
'WPCF_Field_DataMapper_Identity' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/datamapper/identity.php',
|
155 |
+
'WPCF_Field_Data_Saver' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/data_saver.php',
|
156 |
+
'WPCF_Field_Definition_Abstract' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/definition_abstract.php',
|
157 |
+
'WPCF_Field_Definition' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/definition.php',
|
158 |
+
'WPCF_Field_Definition_Factory' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/definition_factory.php',
|
159 |
+
'WPCF_Field_Definition_Factory_Post' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/definition_factory_post.php',
|
160 |
+
'WPCF_Field_Definition_Factory_Term' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/definition_factory_term.php',
|
161 |
+
'WPCF_Field_Definition_Factory_User' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/definition_factory_user.php',
|
162 |
+
'WPCF_Field_Definition_Generic' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/definition_generic.php',
|
163 |
+
'WPCF_Field_Definition_Post' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/definition_post.php',
|
164 |
+
'WPCF_Field_Definition_Term' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/definition_term.php',
|
165 |
+
'WPCF_Field_Definition_User' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/definition_user.php',
|
166 |
+
'WPCF_Field' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field.php',
|
167 |
+
'WPCF_Field_Hooks_API' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/hooks_api.php',
|
168 |
+
'WPCF_Field_Instance_Abstract' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/instance_abstract.php',
|
169 |
+
'WPCF_Field_Instance' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/instance.php',
|
170 |
+
'WPCF_Field_Instance_Term' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/instance_term.php',
|
171 |
+
'WPCF_Field_Instance_Unsaved' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/instance_unsaved.php',
|
172 |
+
'WPCF_Field_Option_Checkboxes' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/option_checkboxes.php',
|
173 |
+
'WPCF_Field_Option_Radio' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/option_radio.php',
|
174 |
+
'WPCF_Field_Option_Select' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/option_select.php',
|
175 |
+
'WPCF_Field_Renderer_Abstract' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/renderer/abstract.php',
|
176 |
+
'WPCF_Field_Renderer_Factory' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/renderer/factory.php',
|
177 |
+
'WPCF_Field_Renderer_Preview_Address' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/renderer/preview/address.php',
|
178 |
+
'WPCF_Field_Renderer_Preview_Base' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/renderer/preview/base.php',
|
179 |
+
'WPCF_Field_Renderer_Preview_Checkbox' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/renderer/preview/checkbox.php',
|
180 |
+
'WPCF_Field_Renderer_Preview_Checkboxes' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/renderer/preview/checkboxes.php',
|
181 |
+
'WPCF_Field_Renderer_Preview_Colorpicker' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/renderer/preview/colorpicker.php',
|
182 |
+
'WPCF_Field_Renderer_Preview_Date' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/renderer/preview/date.php',
|
183 |
+
'WPCF_Field_Renderer_Preview_File' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/renderer/preview/file.php',
|
184 |
+
'WPCF_Field_Renderer_Preview_Image' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/renderer/preview/image.php',
|
185 |
+
'WPCF_Field_Renderer_Preview_Radio' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/renderer/preview/radio.php',
|
186 |
+
'WPCF_Field_Renderer_Preview_Skype' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/renderer/preview/skype.php',
|
187 |
+
'WPCF_Field_Renderer_Preview_Textfield' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/renderer/preview/textfield.php',
|
188 |
+
'WPCF_Field_Renderer_Preview_URL' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/renderer/preview/url.php',
|
189 |
+
'WPCF_Field_Renderer_Toolset_Forms' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field/renderer/toolset_forms.php',
|
190 |
+
'WPCF_Fields' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/fields.php',
|
191 |
+
'WPCF_GUI_Term_Field_Editing' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/gui/term_field_editing.php',
|
192 |
+
'WPCF_Helper_Ajax' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/helper.ajax.php',
|
193 |
+
'WPCF_Import_Export' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/class.wpcf-import-export.php',
|
194 |
+
'WPCF_Loader' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/loader.php',
|
195 |
+
'WPCF_Page_Abstract' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/page/abstract.php',
|
196 |
+
'WPCF_Page_Edit_Termmeta' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/page/edit/termmeta.php',
|
197 |
+
'WPCF_Page_Edit_Termmeta_Form' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/page/edit/termmeta_form.php',
|
198 |
+
'WPCF_Page_Listing_Abstract' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/page/listing/abstract.php',
|
199 |
+
'WPCF_Page_Listing_Table' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/page/listing/table.php',
|
200 |
+
'WPCF_Page_Listing_Termmeta' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/page/listing/termmeta.php',
|
201 |
+
'WPCF_Page_Listing_Termmeta_Table' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/page/listing/termmeta_table.php',
|
202 |
+
'WPCF_Path' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/path.php',
|
203 |
+
'WPCF_Post_Types' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/class.wpcf-post-types.php',
|
204 |
+
'WPCF_Relationship_Child_Form' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/relationship/form-child.php',
|
205 |
+
'WPCF_Relationship' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/relationship.php',
|
206 |
+
'WPCF_Repeater' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/repeater.php',
|
207 |
+
'WPCF_Roles' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/class.wpcf.roles.php',
|
208 |
+
'WPCF_Termmeta_Field' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/field.php',
|
209 |
+
'WPCF_Termmeta_Repeater' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/repeater.php',
|
210 |
+
'WPCF_Types_Marketing' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/class.wpcf.marketing.php',
|
211 |
+
'WPCF_Types_Marketing_Messages' => dirname( __FILE__ ) . '/../vendor/toolset/types/includes/classes/class.wpcf.marketing.messages.php',
|
212 |
+
'WPCF_Usermeta_Field' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/usermeta_field.php',
|
213 |
+
'WPCF_Usermeta_Repeater' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/usermeta_repeater.php',
|
214 |
+
'WPCF_Validation' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/validation.php',
|
215 |
+
'WPCF_WPViews' => dirname( __FILE__ ) . '/../vendor/toolset/types/embedded/classes/wpviews.php',
|
216 |
);
|
application/bootstrap.php
CHANGED
@@ -3,30 +3,30 @@
|
|
3 |
/*
|
4 |
* Autoloader
|
5 |
*/
|
6 |
-
require_once( TYPES_ABSPATH . '/
|
7 |
|
8 |
$autoloader = Toolset_Autoloader::get_instance();
|
9 |
|
10 |
-
$autoloader->add_path( 'Toolset', TYPES_ABSPATH . '/
|
11 |
|
12 |
|
13 |
/*
|
14 |
* Load old Types
|
15 |
*/
|
16 |
if( ! defined( 'WPCF_RELPATH' ) ) {
|
17 |
-
define( 'WPCF_RELPATH', TYPES_RELPATH . '/
|
18 |
}
|
19 |
|
20 |
if( ! defined( 'WPCF_EMBEDDED_TOOLSET_ABSPATH' ) ) {
|
21 |
-
define( 'WPCF_EMBEDDED_TOOLSET_ABSPATH', TYPES_ABSPATH . '/
|
22 |
}
|
23 |
|
24 |
if( ! defined( 'WPCF_EMBEDDED_TOOLSET_RELPATH') ) {
|
25 |
-
define( 'WPCF_EMBEDDED_TOOLSET_RELPATH', TYPES_RELPATH . '/
|
26 |
}
|
27 |
|
28 |
if( ! defined( 'WPTOOLSET_COMMON_PATH' ) ) {
|
29 |
-
define( 'WPTOOLSET_COMMON_PATH', TYPES_ABSPATH . '/
|
30 |
}
|
31 |
|
32 |
if ( !defined( 'EDITOR_ADDON_RELPATH' ) ) {
|
@@ -34,7 +34,7 @@ if ( !defined( 'EDITOR_ADDON_RELPATH' ) ) {
|
|
34 |
}
|
35 |
|
36 |
// installer
|
37 |
-
$installer = TYPES_ABSPATH . '/
|
38 |
if ( file_exists( $installer ) ) {
|
39 |
/** @noinspection PhpIncludeInspection */
|
40 |
include_once $installer;
|
@@ -54,7 +54,7 @@ if ( file_exists( $installer ) ) {
|
|
54 |
require_once( dirname( __FILE__ ) . '/functions.php' );
|
55 |
|
56 |
// Initialize legacy code
|
57 |
-
require_once( dirname( __FILE__ ) . '/../
|
58 |
|
59 |
// Public API
|
60 |
require_once( dirname( __FILE__ ) . '/controllers/main.php' );
|
3 |
/*
|
4 |
* Autoloader
|
5 |
*/
|
6 |
+
require_once( TYPES_ABSPATH . '/vendor/toolset/autoloader/autoloader.php' );
|
7 |
|
8 |
$autoloader = Toolset_Autoloader::get_instance();
|
9 |
|
10 |
+
$autoloader->add_path( 'Toolset', TYPES_ABSPATH . '/vendor/toolset' );
|
11 |
|
12 |
|
13 |
/*
|
14 |
* Load old Types
|
15 |
*/
|
16 |
if( ! defined( 'WPCF_RELPATH' ) ) {
|
17 |
+
define( 'WPCF_RELPATH', TYPES_RELPATH . '/vendor/toolset/types' );
|
18 |
}
|
19 |
|
20 |
if( ! defined( 'WPCF_EMBEDDED_TOOLSET_ABSPATH' ) ) {
|
21 |
+
define( 'WPCF_EMBEDDED_TOOLSET_ABSPATH', TYPES_ABSPATH . '/vendor/toolset' );
|
22 |
}
|
23 |
|
24 |
if( ! defined( 'WPCF_EMBEDDED_TOOLSET_RELPATH') ) {
|
25 |
+
define( 'WPCF_EMBEDDED_TOOLSET_RELPATH', TYPES_RELPATH . '/vendor/toolset' );
|
26 |
}
|
27 |
|
28 |
if( ! defined( 'WPTOOLSET_COMMON_PATH' ) ) {
|
29 |
+
define( 'WPTOOLSET_COMMON_PATH', TYPES_ABSPATH . '/vendor/toolset/toolset-common' );
|
30 |
}
|
31 |
|
32 |
if ( !defined( 'EDITOR_ADDON_RELPATH' ) ) {
|
34 |
}
|
35 |
|
36 |
// installer
|
37 |
+
$installer = TYPES_ABSPATH . '/vendor/otgs/installer/loader.php';
|
38 |
if ( file_exists( $installer ) ) {
|
39 |
/** @noinspection PhpIncludeInspection */
|
40 |
include_once $installer;
|
54 |
require_once( dirname( __FILE__ ) . '/functions.php' );
|
55 |
|
56 |
// Initialize legacy code
|
57 |
+
require_once( dirname( __FILE__ ) . '/../vendor/toolset/types/wpcf.php' );
|
58 |
|
59 |
// Public API
|
60 |
require_once( dirname( __FILE__ ) . '/controllers/main.php' );
|
application/controllers/ajax.php
CHANGED
@@ -238,7 +238,7 @@ final class Types_Ajax {
|
|
238 |
|
239 |
|
240 |
/**
|
241 |
-
* Handles all initialization of except AJAX callbacks itself that is needed when
|
242 |
* we're DOING_AJAX.
|
243 |
*
|
244 |
* Since this is executed on every AJAX call, make sure it's as lightweight as possible.
|
@@ -252,6 +252,11 @@ final class Types_Ajax {
|
|
252 |
add_action( 'create_term', array( $this, 'prepare_for_term_creation' ) );
|
253 |
|
254 |
add_action( 'updated_user_meta', array( $this, 'capture_columnshidden_update' ), 10, 4 );
|
|
|
|
|
|
|
|
|
|
|
255 |
}
|
256 |
|
257 |
|
@@ -319,4 +324,17 @@ final class Types_Ajax {
|
|
319 |
}
|
320 |
}
|
321 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
322 |
}
|
238 |
|
239 |
|
240 |
/**
|
241 |
+
* Handles all initialization of everything except AJAX callbacks itself that is needed when
|
242 |
* we're DOING_AJAX.
|
243 |
*
|
244 |
* Since this is executed on every AJAX call, make sure it's as lightweight as possible.
|
252 |
add_action( 'create_term', array( $this, 'prepare_for_term_creation' ) );
|
253 |
|
254 |
add_action( 'updated_user_meta', array( $this, 'capture_columnshidden_update' ), 10, 4 );
|
255 |
+
|
256 |
+
// Handle partially refactored AJAX callbacks coming from wpcf_ajax_embedded()
|
257 |
+
// or from wpcf_ajax(). The wp_ajax_wpcf_ajax action will be reached only if the $fallthrough variables
|
258 |
+
// in those functions are set to true (which means that the call was not handled).
|
259 |
+
add_action( 'wp_ajax_wpcf_ajax', array( $this, 'do_legacy_wpcf_ajax' ) );
|
260 |
}
|
261 |
|
262 |
|
324 |
}
|
325 |
}
|
326 |
}
|
327 |
+
|
328 |
+
|
329 |
+
/**
|
330 |
+
* This offers a possibility to handle legacy AJAX wp_ajax_wpcf_ajax calls
|
331 |
+
* if they're not handled in the legacy code anymore.
|
332 |
+
*
|
333 |
+
* Note that the method needs to always finish with die() to keep consistency with the legacy code.
|
334 |
+
*
|
335 |
+
* @since 2.2.16
|
336 |
+
*/
|
337 |
+
public function do_legacy_wpcf_ajax() {
|
338 |
+
die();
|
339 |
+
}
|
340 |
}
|
application/controllers/asset/manager.php
CHANGED
@@ -40,13 +40,6 @@ final class Types_Asset_Manager extends Toolset_Assets_Manager {
|
|
40 |
|
41 |
protected function __initialize_scripts() {
|
42 |
|
43 |
-
$this->register_script(
|
44 |
-
self::SCRIPT_KNOCKOUT,
|
45 |
-
TYPES_RELPATH . '/library/knockout/' . $this->choose_script_version( 'knockout-3.4.0.js', 'knockout-3.4.0.debug.js' ),
|
46 |
-
array(),
|
47 |
-
'3.4.0'
|
48 |
-
);
|
49 |
-
|
50 |
$this->register_script(
|
51 |
self::SCRIPT_ADJUST_MENU_LINK,
|
52 |
TYPES_RELPATH . '/public/page/adjust_submenu_links.js',
|
40 |
|
41 |
protected function __initialize_scripts() {
|
42 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
43 |
$this->register_script(
|
44 |
self::SCRIPT_ADJUST_MENU_LINK,
|
45 |
TYPES_RELPATH . '/public/page/adjust_submenu_links.js',
|
application/controllers/interop/handler/the7.php
ADDED
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* The7 theme interoperability handler.
|
4 |
+
*
|
5 |
+
* @since 2.2.16
|
6 |
+
*/
|
7 |
+
class Types_Interop_Handler_The7 implements Types_Interop_Handler_Interface {
|
8 |
+
|
9 |
+
private static $instance;
|
10 |
+
|
11 |
+
public static function initialize() {
|
12 |
+
|
13 |
+
if ( null === self::$instance ) {
|
14 |
+
self::$instance = new self();
|
15 |
+
self::$instance->add_hooks();
|
16 |
+
}
|
17 |
+
|
18 |
+
// Not giving away the instance on purpose.
|
19 |
+
|
20 |
+
}
|
21 |
+
|
22 |
+
|
23 |
+
public function add_hooks() {
|
24 |
+
add_action( 'types_leagacy_editor_callback_init', array( $this, 'remove_presscore_hooks' ) );
|
25 |
+
}
|
26 |
+
|
27 |
+
|
28 |
+
/**
|
29 |
+
* Fix a compatibility issue caused, simply put, by our own terrible mess.
|
30 |
+
*
|
31 |
+
* The issue was visible when activating The7 theme, Views and Types, on the Edit Content Template page (for example).
|
32 |
+
* When trying to add a Types field, the AJAX call to render the dialog markup has failed due to a fatal error.
|
33 |
+
* The result was that the dialog never opened and the user was left with the grey overlay and no error message.
|
34 |
+
*
|
35 |
+
* The fatal error was caused by the fact that the legacy code in Types, namely in embedded/includes/ajax/admin-header.php
|
36 |
+
* is running do_action( 'admin_enqueue_scripts', $hook_suffix ); outside of the context of the
|
37 |
+
* standard admin page loading mechanism.
|
38 |
+
*
|
39 |
+
* That causes problems in The7_Demo_Content_Admin::enqueue_scripts() (which is hooked even during this
|
40 |
+
* non-standard procedure): It accesses the global $tgmpa variable which is assumed to be
|
41 |
+
* an instance of The7_TGMPA but for some reason, it's never initialized.
|
42 |
+
*
|
43 |
+
* The workaround is also extremely ugly: We intercept the AJAX call that renders the dialog markup and check
|
44 |
+
* all admin_enqueue_scripts hooks. If its callback has a prefix coming from The7 theme, we'll remove the action.
|
45 |
+
*
|
46 |
+
* It is safe because we're targeting only a very specific scenario, where the theme's admin assets
|
47 |
+
* aren't needed at all.
|
48 |
+
*
|
49 |
+
* This can be removed when the dialog is finally refactored into something sensible.
|
50 |
+
*
|
51 |
+
* @since 2.2.16
|
52 |
+
*/
|
53 |
+
public function remove_presscore_hooks() {
|
54 |
+
if( toolset_getget( 'action' ) === 'wpcf_ajax' && toolset_getget( 'wpcf_action' ) === 'editor_callback' ) {
|
55 |
+
global $wp_filter;
|
56 |
+
/** @var WP_Hook $admin_enqueue_script_hooks */
|
57 |
+
$admin_enqueue_script_hooks = toolset_getarr( $wp_filter, 'admin_enqueue_scripts', array() );
|
58 |
+
|
59 |
+
foreach( $admin_enqueue_script_hooks->callbacks as $priority => $callbacks_for_priority ) {
|
60 |
+
foreach( $callbacks_for_priority as $callback_id => $callback ) {
|
61 |
+
$function = $callback['function'];
|
62 |
+
|
63 |
+
$the7_string_prefix = 'presscore_';
|
64 |
+
$is_the7_string_callback = ( is_string( $function ) && substr( $function, 0, strlen( $the7_string_prefix ) ) === $the7_string_prefix );
|
65 |
+
|
66 |
+
$the7_class_prefix = 'The7_';
|
67 |
+
$is_the7_class_callback = (
|
68 |
+
is_array( $function )
|
69 |
+
&& count( $function ) === 2
|
70 |
+
&& substr( get_class( $function[0] ), 0, strlen( $the7_class_prefix ) ) === $the7_class_prefix
|
71 |
+
);
|
72 |
+
|
73 |
+
if( $is_the7_class_callback || $is_the7_string_callback ) {
|
74 |
+
remove_action( 'admin_enqueue_scripts', $function, $priority );
|
75 |
+
}
|
76 |
+
}
|
77 |
+
}
|
78 |
+
}
|
79 |
+
}
|
80 |
+
|
81 |
+
}
|
application/controllers/interop/handler/wpml.php
CHANGED
@@ -4,7 +4,7 @@
|
|
4 |
* WPML interoperability handler.
|
5 |
*
|
6 |
* WIP - this stub is to be filled with everything WPML-related.
|
7 |
-
* Look into:
|
8 |
*
|
9 |
* @since 2.2.9
|
10 |
*/
|
4 |
* WPML interoperability handler.
|
5 |
*
|
6 |
* WIP - this stub is to be filled with everything WPML-related.
|
7 |
+
* Look into: vendor/toolset/types/embedded/includes/wpml.php
|
8 |
*
|
9 |
* @since 2.2.9
|
10 |
*/
|
application/controllers/interop/mediator.php
CHANGED
@@ -57,6 +57,10 @@ class Types_Interop_Mediator {
|
|
57 |
array(
|
58 |
'is_needed' => array( $this, 'is_use_any_font_active' ),
|
59 |
'class_name' => 'Use_Any_Font'
|
|
|
|
|
|
|
|
|
60 |
)
|
61 |
);
|
62 |
|
@@ -113,8 +117,34 @@ class Types_Interop_Mediator {
|
|
113 |
}
|
114 |
|
115 |
|
|
|
|
|
|
|
|
|
|
|
116 |
protected function is_use_any_font_active() {
|
117 |
return function_exists( 'uaf_activate' );
|
118 |
}
|
119 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
120 |
}
|
57 |
array(
|
58 |
'is_needed' => array( $this, 'is_use_any_font_active' ),
|
59 |
'class_name' => 'Use_Any_Font'
|
60 |
+
),
|
61 |
+
array(
|
62 |
+
'is_needed' => array( $this, 'is_the7_active' ),
|
63 |
+
'class_name' => 'The7'
|
64 |
)
|
65 |
);
|
66 |
|
117 |
}
|
118 |
|
119 |
|
120 |
+
protected function is_the7_active() {
|
121 |
+
return ( 'the7' === $this->get_theme_slug() );
|
122 |
+
}
|
123 |
+
|
124 |
+
|
125 |
protected function is_use_any_font_active() {
|
126 |
return function_exists( 'uaf_activate' );
|
127 |
}
|
128 |
|
129 |
+
|
130 |
+
/**
|
131 |
+
* Retrieve a "slugized" theme name.
|
132 |
+
*
|
133 |
+
* @return string
|
134 |
+
* @since 2.2.16
|
135 |
+
*/
|
136 |
+
private function get_theme_slug( ){
|
137 |
+
$theme = wp_get_theme();
|
138 |
+
if( is_child_theme() ){
|
139 |
+
$theme_name = $theme->parent()->get('Name');
|
140 |
+
} else {
|
141 |
+
$theme_name = $theme->get('Name');
|
142 |
+
}
|
143 |
+
|
144 |
+
$slug = str_replace('-', '_', sanitize_title( $theme_name ) );
|
145 |
+
|
146 |
+
return $slug;
|
147 |
+
}
|
148 |
+
|
149 |
+
|
150 |
}
|
application/controllers/twig_autoloader.php
CHANGED
@@ -74,7 +74,7 @@ class Types_Twig_Autoloader
|
|
74 |
}
|
75 |
|
76 |
// Modified path to Twig in Types.
|
77 |
-
$file = TYPES_ABSPATH . '/
|
78 |
|
79 |
if( is_file( $file ) ) {
|
80 |
/** @noinspection PhpIncludeInspection */
|
74 |
}
|
75 |
|
76 |
// Modified path to Twig in Types.
|
77 |
+
$file = TYPES_ABSPATH . '/vendor/twig/twig/lib/' . str_replace( array( '_', "\0" ), array( '/', '' ), $class .'.php' );
|
78 |
|
79 |
if( is_file( $file ) ) {
|
80 |
/** @noinspection PhpIncludeInspection */
|
application/controllers/upgrade.php
CHANGED
@@ -179,6 +179,10 @@ class Types_Upgrade {
|
|
179 |
array(
|
180 |
'version' => 2010000,
|
181 |
'callback' => array( $this, 'upgrade_db_to_2010000' )
|
|
|
|
|
|
|
|
|
182 |
)
|
183 |
);
|
184 |
|
@@ -260,5 +264,16 @@ class Types_Upgrade {
|
|
260 |
$roles_manager->clean_the_mess_in_nonadmin_user_caps( $user );
|
261 |
}
|
262 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
263 |
|
264 |
}
|
179 |
array(
|
180 |
'version' => 2010000,
|
181 |
'callback' => array( $this, 'upgrade_db_to_2010000' )
|
182 |
+
),
|
183 |
+
array(
|
184 |
+
'version' => 2021600,
|
185 |
+
'callback' => array( $this, 'upgrade_db_to_2021600' )
|
186 |
)
|
187 |
);
|
188 |
|
264 |
$roles_manager->clean_the_mess_in_nonadmin_user_caps( $user );
|
265 |
}
|
266 |
}
|
267 |
+
|
268 |
+
/**
|
269 |
+
* Upgrade database to 2021600 (Types 2.2.16)
|
270 |
+
*
|
271 |
+
* Fix types-1142 for non admins with an 'admin' username.
|
272 |
+
*/
|
273 |
+
function upgrade_db_to_2021600() {
|
274 |
+
|
275 |
+
$roles_manager = WPCF_Roles::getInstance();
|
276 |
+
$roles_manager->clean_the_mess_in_nonadmin_user_caps( 'admin' );
|
277 |
+
}
|
278 |
|
279 |
}
|
application/models/helper/create/layout.php
CHANGED
@@ -108,7 +108,7 @@ class Types_Helper_Create_Layout {
|
|
108 |
$layout = WPDD_Layouts::create_layout( 12, 'fluid' );
|
109 |
|
110 |
$parent_post_name = '';
|
111 |
-
$parent_ID = apply_filters('ddl-get-default-' . WPDDL_Options::PARENTS_OPTIONS, WPDDL_Options::PARENTS_OPTIONS);
|
112 |
if ($parent_ID) {
|
113 |
$parent_post_name = WPDD_Layouts_Cache_Singleton::get_name_by_id($parent_ID);
|
114 |
}
|
108 |
$layout = WPDD_Layouts::create_layout( 12, 'fluid' );
|
109 |
|
110 |
$parent_post_name = '';
|
111 |
+
$parent_ID = apply_filters('ddl-get-default-' . WPDDL_Options::PARENTS_OPTIONS, 0, WPDDL_Options::PARENTS_OPTIONS);
|
112 |
if ($parent_ID) {
|
113 |
$parent_post_name = WPDD_Layouts_Cache_Singleton::get_name_by_id($parent_ID);
|
114 |
}
|
library/toolset/onthego-resources/onthegosystems-icons/.fontcustom-manifest.json
DELETED
@@ -1,204 +0,0 @@
|
|
1 |
-
{
|
2 |
-
"checksum": {
|
3 |
-
"previous": "c2bf5434fb5ba6db313a98fb4e1bf3479154a52d11ff65763e7707dfe4069a7b",
|
4 |
-
"current": "c2bf5434fb5ba6db313a98fb4e1bf3479154a52d11ff65763e7707dfe4069a7b"
|
5 |
-
},
|
6 |
-
"fonts": [
|
7 |
-
"fonts/onthegosystems-icons_c2bf5434fb5ba6db313a98fb4e1bf347.ttf",
|
8 |
-
"fonts/onthegosystems-icons_c2bf5434fb5ba6db313a98fb4e1bf347.svg",
|
9 |
-
"fonts/onthegosystems-icons_c2bf5434fb5ba6db313a98fb4e1bf347.woff",
|
10 |
-
"fonts/onthegosystems-icons_c2bf5434fb5ba6db313a98fb4e1bf347.eot"
|
11 |
-
],
|
12 |
-
"glyphs": {
|
13 |
-
"access": {
|
14 |
-
"codepoint": 61712,
|
15 |
-
"source": "vectors/access.svg"
|
16 |
-
},
|
17 |
-
"access-logo": {
|
18 |
-
"codepoint": 61713,
|
19 |
-
"source": "vectors/access-logo.svg"
|
20 |
-
},
|
21 |
-
"bootstrap": {
|
22 |
-
"codepoint": 61714,
|
23 |
-
"source": "vectors/bootstrap.svg"
|
24 |
-
},
|
25 |
-
"bootstrap-logo": {
|
26 |
-
"codepoint": 61715,
|
27 |
-
"source": "vectors/bootstrap-logo.svg"
|
28 |
-
},
|
29 |
-
"bootstrap-original-logo": {
|
30 |
-
"codepoint": 61750,
|
31 |
-
"source": "vectors/bootstrap-original-logo.svg"
|
32 |
-
},
|
33 |
-
"bootstrap-pagination": {
|
34 |
-
"codepoint": 61749,
|
35 |
-
"source": "vectors/bootstrap-pagination.svg"
|
36 |
-
},
|
37 |
-
"breadcrumbs": {
|
38 |
-
"codepoint": 61739,
|
39 |
-
"source": "vectors/breadcrumbs.svg"
|
40 |
-
},
|
41 |
-
"btn-dropdowns": {
|
42 |
-
"codepoint": 61740,
|
43 |
-
"source": "vectors/btn-dropdowns.svg"
|
44 |
-
},
|
45 |
-
"buttons": {
|
46 |
-
"codepoint": 61741,
|
47 |
-
"source": "vectors/buttons.svg"
|
48 |
-
},
|
49 |
-
"conditional-alert": {
|
50 |
-
"codepoint": 61730,
|
51 |
-
"source": "vectors/conditional-alert.svg"
|
52 |
-
},
|
53 |
-
"conditional-if": {
|
54 |
-
"codepoint": 61731,
|
55 |
-
"source": "vectors/conditional-if.svg"
|
56 |
-
},
|
57 |
-
"cred": {
|
58 |
-
"codepoint": 61716,
|
59 |
-
"source": "vectors/cred.svg"
|
60 |
-
},
|
61 |
-
"cred-logo": {
|
62 |
-
"codepoint": 61717,
|
63 |
-
"source": "vectors/cred-logo.svg"
|
64 |
-
},
|
65 |
-
"input-groups": {
|
66 |
-
"codepoint": 61742,
|
67 |
-
"source": "vectors/input-groups.svg"
|
68 |
-
},
|
69 |
-
"labels": {
|
70 |
-
"codepoint": 61743,
|
71 |
-
"source": "vectors/labels.svg"
|
72 |
-
},
|
73 |
-
"layouts": {
|
74 |
-
"codepoint": 61718,
|
75 |
-
"source": "vectors/layouts.svg"
|
76 |
-
},
|
77 |
-
"layouts-genesis": {
|
78 |
-
"codepoint": 61737,
|
79 |
-
"source": "vectors/layouts-genesis.svg"
|
80 |
-
},
|
81 |
-
"layouts-genesis-logo": {
|
82 |
-
"codepoint": 61735,
|
83 |
-
"source": "vectors/layouts-genesis-logo.svg"
|
84 |
-
},
|
85 |
-
"layouts-logo": {
|
86 |
-
"codepoint": 61719,
|
87 |
-
"source": "vectors/layouts-logo.svg"
|
88 |
-
},
|
89 |
-
"list-group": {
|
90 |
-
"codepoint": 61744,
|
91 |
-
"source": "vectors/list-group.svg"
|
92 |
-
},
|
93 |
-
"module": {
|
94 |
-
"codepoint": 61720,
|
95 |
-
"source": "vectors/module.svg"
|
96 |
-
},
|
97 |
-
"module-logo": {
|
98 |
-
"codepoint": 61721,
|
99 |
-
"source": "vectors/module-logo.svg"
|
100 |
-
},
|
101 |
-
"navbar": {
|
102 |
-
"codepoint": 61745,
|
103 |
-
"source": "vectors/navbar.svg"
|
104 |
-
},
|
105 |
-
"packager": {
|
106 |
-
"codepoint": 61728,
|
107 |
-
"source": "vectors/packager.svg"
|
108 |
-
},
|
109 |
-
"packager-logo": {
|
110 |
-
"codepoint": 61729,
|
111 |
-
"source": "vectors/packager-logo.svg"
|
112 |
-
},
|
113 |
-
"panels": {
|
114 |
-
"codepoint": 61747,
|
115 |
-
"source": "vectors/panels.svg"
|
116 |
-
},
|
117 |
-
"toolset": {
|
118 |
-
"codepoint": 61738,
|
119 |
-
"source": "vectors/toolset.svg"
|
120 |
-
},
|
121 |
-
"toolset-export": {
|
122 |
-
"codepoint": 61751,
|
123 |
-
"source": "vectors/toolset-export.svg"
|
124 |
-
},
|
125 |
-
"toolset-genesis-logo": {
|
126 |
-
"codepoint": 61736,
|
127 |
-
"source": "vectors/toolset-genesis-logo.svg"
|
128 |
-
},
|
129 |
-
"toolset-logo": {
|
130 |
-
"codepoint": 61722,
|
131 |
-
"source": "vectors/toolset-logo.svg"
|
132 |
-
},
|
133 |
-
"toolset-map": {
|
134 |
-
"codepoint": 61733,
|
135 |
-
"source": "vectors/toolset-map.svg"
|
136 |
-
},
|
137 |
-
"toolset-map-logo": {
|
138 |
-
"codepoint": 61732,
|
139 |
-
"source": "vectors/toolset-map-logo.svg"
|
140 |
-
},
|
141 |
-
"types": {
|
142 |
-
"codepoint": 61723,
|
143 |
-
"source": "vectors/types.svg"
|
144 |
-
},
|
145 |
-
"types-logo": {
|
146 |
-
"codepoint": 61724,
|
147 |
-
"source": "vectors/types-logo.svg"
|
148 |
-
},
|
149 |
-
"views": {
|
150 |
-
"codepoint": 61725,
|
151 |
-
"source": "vectors/views.svg"
|
152 |
-
},
|
153 |
-
"views-logo": {
|
154 |
-
"codepoint": 61726,
|
155 |
-
"source": "vectors/views-logo.svg"
|
156 |
-
},
|
157 |
-
"wells": {
|
158 |
-
"codepoint": 61748,
|
159 |
-
"source": "vectors/wells.svg"
|
160 |
-
},
|
161 |
-
"wpml-logo": {
|
162 |
-
"codepoint": 61727,
|
163 |
-
"source": "vectors/wpml-logo.svg"
|
164 |
-
}
|
165 |
-
},
|
166 |
-
"options": {
|
167 |
-
"autowidth": false,
|
168 |
-
"config": "fontcustom.yml",
|
169 |
-
"css_selector": ".icon-{{glyph}}",
|
170 |
-
"debug": false,
|
171 |
-
"file_hash": false,
|
172 |
-
"font_ascent": 448,
|
173 |
-
"font_descent": 64,
|
174 |
-
"font_design_size": 16,
|
175 |
-
"font_em": 512,
|
176 |
-
"font_name": "onthegosystems-icons",
|
177 |
-
"force": false,
|
178 |
-
"input": {
|
179 |
-
"templates": "vectors",
|
180 |
-
"vectors": "vectors"
|
181 |
-
},
|
182 |
-
"no_hash": false,
|
183 |
-
"output": {
|
184 |
-
"css": "css",
|
185 |
-
"fonts": "fonts",
|
186 |
-
"preview": "fonts"
|
187 |
-
},
|
188 |
-
"preprocessor_path": null,
|
189 |
-
"project_root": {
|
190 |
-
"pwd": null
|
191 |
-
},
|
192 |
-
"quiet": false,
|
193 |
-
"templates": [
|
194 |
-
"css",
|
195 |
-
"scss",
|
196 |
-
"preview"
|
197 |
-
]
|
198 |
-
},
|
199 |
-
"templates": [
|
200 |
-
"css/onthegosystems-icons.css",
|
201 |
-
"css/_onthegosystems-icons.scss",
|
202 |
-
"fonts/onthegosystems-icons-preview.html"
|
203 |
-
]
|
204 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/toolset/toolset-common/res/lib/knockout/knockout-3.4.0.debug.js
DELETED
@@ -1,5871 +0,0 @@
|
|
1 |
-
/*!
|
2 |
-
* Knockout JavaScript library v3.4.0
|
3 |
-
* (c) Steven Sanderson - http://knockoutjs.com/
|
4 |
-
* License: MIT (http://www.opensource.org/licenses/mit-license.php)
|
5 |
-
*/
|
6 |
-
|
7 |
-
(function(){
|
8 |
-
var DEBUG=true;
|
9 |
-
(function(undefined){
|
10 |
-
// (0, eval)('this') is a robust way of getting a reference to the global object
|
11 |
-
// For details, see http://stackoverflow.com/questions/14119988/return-this-0-evalthis/14120023#14120023
|
12 |
-
var window = this || (0, eval)('this'),
|
13 |
-
document = window['document'],
|
14 |
-
navigator = window['navigator'],
|
15 |
-
jQueryInstance = window["jQuery"],
|
16 |
-
JSON = window["JSON"];
|
17 |
-
(function(factory) {
|
18 |
-
// Support three module loading scenarios
|
19 |
-
if (typeof define === 'function' && define['amd']) {
|
20 |
-
// [1] AMD anonymous module
|
21 |
-
define(['exports', 'require'], factory);
|
22 |
-
} else if (typeof exports === 'object' && typeof module === 'object') {
|
23 |
-
// [2] CommonJS/Node.js
|
24 |
-
factory(module['exports'] || exports); // module.exports is for Node.js
|
25 |
-
} else {
|
26 |
-
// [3] No module loader (plain <script> tag) - put directly in global namespace
|
27 |
-
factory(window['ko'] = {});
|
28 |
-
}
|
29 |
-
}(function(koExports, amdRequire){
|
30 |
-
// Internally, all KO objects are attached to koExports (even the non-exported ones whose names will be minified by the closure compiler).
|
31 |
-
// In the future, the following "ko" variable may be made distinct from "koExports" so that private objects are not externally reachable.
|
32 |
-
var ko = typeof koExports !== 'undefined' ? koExports : {};
|
33 |
-
// Google Closure Compiler helpers (used only to make the minified file smaller)
|
34 |
-
ko.exportSymbol = function(koPath, object) {
|
35 |
-
var tokens = koPath.split(".");
|
36 |
-
|
37 |
-
// In the future, "ko" may become distinct from "koExports" (so that non-exported objects are not reachable)
|
38 |
-
// At that point, "target" would be set to: (typeof koExports !== "undefined" ? koExports : ko)
|
39 |
-
var target = ko;
|
40 |
-
|
41 |
-
for (var i = 0; i < tokens.length - 1; i++)
|
42 |
-
target = target[tokens[i]];
|
43 |
-
target[tokens[tokens.length - 1]] = object;
|
44 |
-
};
|
45 |
-
ko.exportProperty = function(owner, publicName, object) {
|
46 |
-
owner[publicName] = object;
|
47 |
-
};
|
48 |
-
ko.version = "3.4.0";
|
49 |
-
|
50 |
-
ko.exportSymbol('version', ko.version);
|
51 |
-
// For any options that may affect various areas of Knockout and aren't directly associated with data binding.
|
52 |
-
ko.options = {
|
53 |
-
'deferUpdates': false,
|
54 |
-
'useOnlyNativeEvents': false
|
55 |
-
};
|
56 |
-
|
57 |
-
//ko.exportSymbol('options', ko.options); // 'options' isn't minified
|
58 |
-
ko.utils = (function () {
|
59 |
-
function objectForEach(obj, action) {
|
60 |
-
for (var prop in obj) {
|
61 |
-
if (obj.hasOwnProperty(prop)) {
|
62 |
-
action(prop, obj[prop]);
|
63 |
-
}
|
64 |
-
}
|
65 |
-
}
|
66 |
-
|
67 |
-
function extend(target, source) {
|
68 |
-
if (source) {
|
69 |
-
for(var prop in source) {
|
70 |
-
if(source.hasOwnProperty(prop)) {
|
71 |
-
target[prop] = source[prop];
|
72 |
-
}
|
73 |
-
}
|
74 |
-
}
|
75 |
-
return target;
|
76 |
-
}
|
77 |
-
|
78 |
-
function setPrototypeOf(obj, proto) {
|
79 |
-
obj.__proto__ = proto;
|
80 |
-
return obj;
|
81 |
-
}
|
82 |
-
|
83 |
-
var canSetPrototype = ({ __proto__: [] } instanceof Array);
|
84 |
-
var canUseSymbols = !DEBUG && typeof Symbol === 'function';
|
85 |
-
|
86 |
-
// Represent the known event types in a compact way, then at runtime transform it into a hash with event name as key (for fast lookup)
|
87 |
-
var knownEvents = {}, knownEventTypesByEventName = {};
|
88 |
-
var keyEventTypeName = (navigator && /Firefox\/2/i.test(navigator.userAgent)) ? 'KeyboardEvent' : 'UIEvents';
|
89 |
-
knownEvents[keyEventTypeName] = ['keyup', 'keydown', 'keypress'];
|
90 |
-
knownEvents['MouseEvents'] = ['click', 'dblclick', 'mousedown', 'mouseup', 'mousemove', 'mouseover', 'mouseout', 'mouseenter', 'mouseleave'];
|
91 |
-
objectForEach(knownEvents, function(eventType, knownEventsForType) {
|
92 |
-
if (knownEventsForType.length) {
|
93 |
-
for (var i = 0, j = knownEventsForType.length; i < j; i++)
|
94 |
-
knownEventTypesByEventName[knownEventsForType[i]] = eventType;
|
95 |
-
}
|
96 |
-
});
|
97 |
-
var eventsThatMustBeRegisteredUsingAttachEvent = { 'propertychange': true }; // Workaround for an IE9 issue - https://github.com/SteveSanderson/knockout/issues/406
|
98 |
-
|
99 |
-
// Detect IE versions for bug workarounds (uses IE conditionals, not UA string, for robustness)
|
100 |
-
// Note that, since IE 10 does not support conditional comments, the following logic only detects IE < 10.
|
101 |
-
// Currently this is by design, since IE 10+ behaves correctly when treated as a standard browser.
|
102 |
-
// If there is a future need to detect specific versions of IE10+, we will amend this.
|
103 |
-
var ieVersion = document && (function() {
|
104 |
-
var version = 3, div = document.createElement('div'), iElems = div.getElementsByTagName('i');
|
105 |
-
|
106 |
-
// Keep constructing conditional HTML blocks until we hit one that resolves to an empty fragment
|
107 |
-
while (
|
108 |
-
div.innerHTML = '<!--[if gt IE ' + (++version) + ']><i></i><![endif]-->',
|
109 |
-
iElems[0]
|
110 |
-
) {}
|
111 |
-
return version > 4 ? version : undefined;
|
112 |
-
}());
|
113 |
-
var isIe6 = ieVersion === 6,
|
114 |
-
isIe7 = ieVersion === 7;
|
115 |
-
|
116 |
-
function isClickOnCheckableElement(element, eventType) {
|
117 |
-
if ((ko.utils.tagNameLower(element) !== "input") || !element.type) return false;
|
118 |
-
if (eventType.toLowerCase() != "click") return false;
|
119 |
-
var inputType = element.type;
|
120 |
-
return (inputType == "checkbox") || (inputType == "radio");
|
121 |
-
}
|
122 |
-
|
123 |
-
// For details on the pattern for changing node classes
|
124 |
-
// see: https://github.com/knockout/knockout/issues/1597
|
125 |
-
var cssClassNameRegex = /\S+/g;
|
126 |
-
|
127 |
-
function toggleDomNodeCssClass(node, classNames, shouldHaveClass) {
|
128 |
-
var addOrRemoveFn;
|
129 |
-
if (classNames) {
|
130 |
-
if (typeof node.classList === 'object') {
|
131 |
-
addOrRemoveFn = node.classList[shouldHaveClass ? 'add' : 'remove'];
|
132 |
-
ko.utils.arrayForEach(classNames.match(cssClassNameRegex), function(className) {
|
133 |
-
addOrRemoveFn.call(node.classList, className);
|
134 |
-
});
|
135 |
-
} else if (typeof node.className['baseVal'] === 'string') {
|
136 |
-
// SVG tag .classNames is an SVGAnimatedString instance
|
137 |
-
toggleObjectClassPropertyString(node.className, 'baseVal', classNames, shouldHaveClass);
|
138 |
-
} else {
|
139 |
-
// node.className ought to be a string.
|
140 |
-
toggleObjectClassPropertyString(node, 'className', classNames, shouldHaveClass);
|
141 |
-
}
|
142 |
-
}
|
143 |
-
}
|
144 |
-
|
145 |
-
function toggleObjectClassPropertyString(obj, prop, classNames, shouldHaveClass) {
|
146 |
-
// obj/prop is either a node/'className' or a SVGAnimatedString/'baseVal'.
|
147 |
-
var currentClassNames = obj[prop].match(cssClassNameRegex) || [];
|
148 |
-
ko.utils.arrayForEach(classNames.match(cssClassNameRegex), function(className) {
|
149 |
-
ko.utils.addOrRemoveItem(currentClassNames, className, shouldHaveClass);
|
150 |
-
});
|
151 |
-
obj[prop] = currentClassNames.join(" ");
|
152 |
-
}
|
153 |
-
|
154 |
-
return {
|
155 |
-
fieldsIncludedWithJsonPost: ['authenticity_token', /^__RequestVerificationToken(_.*)?$/],
|
156 |
-
|
157 |
-
arrayForEach: function (array, action) {
|
158 |
-
for (var i = 0, j = array.length; i < j; i++)
|
159 |
-
action(array[i], i);
|
160 |
-
},
|
161 |
-
|
162 |
-
arrayIndexOf: function (array, item) {
|
163 |
-
if (typeof Array.prototype.indexOf == "function")
|
164 |
-
return Array.prototype.indexOf.call(array, item);
|
165 |
-
for (var i = 0, j = array.length; i < j; i++)
|
166 |
-
if (array[i] === item)
|
167 |
-
return i;
|
168 |
-
return -1;
|
169 |
-
},
|
170 |
-
|
171 |
-
arrayFirst: function (array, predicate, predicateOwner) {
|
172 |
-
for (var i = 0, j = array.length; i < j; i++)
|
173 |
-
if (predicate.call(predicateOwner, array[i], i))
|
174 |
-
return array[i];
|
175 |
-
return null;
|
176 |
-
},
|
177 |
-
|
178 |
-
arrayRemoveItem: function (array, itemToRemove) {
|
179 |
-
var index = ko.utils.arrayIndexOf(array, itemToRemove);
|
180 |
-
if (index > 0) {
|
181 |
-
array.splice(index, 1);
|
182 |
-
}
|
183 |
-
else if (index === 0) {
|
184 |
-
array.shift();
|
185 |
-
}
|
186 |
-
},
|
187 |
-
|
188 |
-
arrayGetDistinctValues: function (array) {
|
189 |
-
array = array || [];
|
190 |
-
var result = [];
|
191 |
-
for (var i = 0, j = array.length; i < j; i++) {
|
192 |
-
if (ko.utils.arrayIndexOf(result, array[i]) < 0)
|
193 |
-
result.push(array[i]);
|
194 |
-
}
|
195 |
-
return result;
|
196 |
-
},
|
197 |
-
|
198 |
-
arrayMap: function (array, mapping) {
|
199 |
-
array = array || [];
|
200 |
-
var result = [];
|
201 |
-
for (var i = 0, j = array.length; i < j; i++)
|
202 |
-
result.push(mapping(array[i], i));
|
203 |
-
return result;
|
204 |
-
},
|
205 |
-
|
206 |
-
arrayFilter: function (array, predicate) {
|
207 |
-
array = array || [];
|
208 |
-
var result = [];
|
209 |
-
for (var i = 0, j = array.length; i < j; i++)
|
210 |
-
if (predicate(array[i], i))
|
211 |
-
result.push(array[i]);
|
212 |
-
return result;
|
213 |
-
},
|
214 |
-
|
215 |
-
arrayPushAll: function (array, valuesToPush) {
|
216 |
-
if (valuesToPush instanceof Array)
|
217 |
-
array.push.apply(array, valuesToPush);
|
218 |
-
else
|
219 |
-
for (var i = 0, j = valuesToPush.length; i < j; i++)
|
220 |
-
array.push(valuesToPush[i]);
|
221 |
-
return array;
|
222 |
-
},
|
223 |
-
|
224 |
-
addOrRemoveItem: function(array, value, included) {
|
225 |
-
var existingEntryIndex = ko.utils.arrayIndexOf(ko.utils.peekObservable(array), value);
|
226 |
-
if (existingEntryIndex < 0) {
|
227 |
-
if (included)
|
228 |
-
array.push(value);
|
229 |
-
} else {
|
230 |
-
if (!included)
|
231 |
-
array.splice(existingEntryIndex, 1);
|
232 |
-
}
|
233 |
-
},
|
234 |
-
|
235 |
-
canSetPrototype: canSetPrototype,
|
236 |
-
|
237 |
-
extend: extend,
|
238 |
-
|
239 |
-
setPrototypeOf: setPrototypeOf,
|
240 |
-
|
241 |
-
setPrototypeOfOrExtend: canSetPrototype ? setPrototypeOf : extend,
|
242 |
-
|
243 |
-
objectForEach: objectForEach,
|
244 |
-
|
245 |
-
objectMap: function(source, mapping) {
|
246 |
-
if (!source)
|
247 |
-
return source;
|
248 |
-
var target = {};
|
249 |
-
for (var prop in source) {
|
250 |
-
if (source.hasOwnProperty(prop)) {
|
251 |
-
target[prop] = mapping(source[prop], prop, source);
|
252 |
-
}
|
253 |
-
}
|
254 |
-
return target;
|
255 |
-
},
|
256 |
-
|
257 |
-
emptyDomNode: function (domNode) {
|
258 |
-
while (domNode.firstChild) {
|
259 |
-
ko.removeNode(domNode.firstChild);
|
260 |
-
}
|
261 |
-
},
|
262 |
-
|
263 |
-
moveCleanedNodesToContainerElement: function(nodes) {
|
264 |
-
// Ensure it's a real array, as we're about to reparent the nodes and
|
265 |
-
// we don't want the underlying collection to change while we're doing that.
|
266 |
-
var nodesArray = ko.utils.makeArray(nodes);
|
267 |
-
var templateDocument = (nodesArray[0] && nodesArray[0].ownerDocument) || document;
|
268 |
-
|
269 |
-
var container = templateDocument.createElement('div');
|
270 |
-
for (var i = 0, j = nodesArray.length; i < j; i++) {
|
271 |
-
container.appendChild(ko.cleanNode(nodesArray[i]));
|
272 |
-
}
|
273 |
-
return container;
|
274 |
-
},
|
275 |
-
|
276 |
-
cloneNodes: function (nodesArray, shouldCleanNodes) {
|
277 |
-
for (var i = 0, j = nodesArray.length, newNodesArray = []; i < j; i++) {
|
278 |
-
var clonedNode = nodesArray[i].cloneNode(true);
|
279 |
-
newNodesArray.push(shouldCleanNodes ? ko.cleanNode(clonedNode) : clonedNode);
|
280 |
-
}
|
281 |
-
return newNodesArray;
|
282 |
-
},
|
283 |
-
|
284 |
-
setDomNodeChildren: function (domNode, childNodes) {
|
285 |
-
ko.utils.emptyDomNode(domNode);
|
286 |
-
if (childNodes) {
|
287 |
-
for (var i = 0, j = childNodes.length; i < j; i++)
|
288 |
-
domNode.appendChild(childNodes[i]);
|
289 |
-
}
|
290 |
-
},
|
291 |
-
|
292 |
-
replaceDomNodes: function (nodeToReplaceOrNodeArray, newNodesArray) {
|
293 |
-
var nodesToReplaceArray = nodeToReplaceOrNodeArray.nodeType ? [nodeToReplaceOrNodeArray] : nodeToReplaceOrNodeArray;
|
294 |
-
if (nodesToReplaceArray.length > 0) {
|
295 |
-
var insertionPoint = nodesToReplaceArray[0];
|
296 |
-
var parent = insertionPoint.parentNode;
|
297 |
-
for (var i = 0, j = newNodesArray.length; i < j; i++)
|
298 |
-
parent.insertBefore(newNodesArray[i], insertionPoint);
|
299 |
-
for (var i = 0, j = nodesToReplaceArray.length; i < j; i++) {
|
300 |
-
ko.removeNode(nodesToReplaceArray[i]);
|
301 |
-
}
|
302 |
-
}
|
303 |
-
},
|
304 |
-
|
305 |
-
fixUpContinuousNodeArray: function(continuousNodeArray, parentNode) {
|
306 |
-
// Before acting on a set of nodes that were previously outputted by a template function, we have to reconcile
|
307 |
-
// them against what is in the DOM right now. It may be that some of the nodes have already been removed, or that
|
308 |
-
// new nodes might have been inserted in the middle, for example by a binding. Also, there may previously have been
|
309 |
-
// leading comment nodes (created by rewritten string-based templates) that have since been removed during binding.
|
310 |
-
// So, this function translates the old "map" output array into its best guess of the set of current DOM nodes.
|
311 |
-
//
|
312 |
-
// Rules:
|
313 |
-
// [A] Any leading nodes that have been removed should be ignored
|
314 |
-
// These most likely correspond to memoization nodes that were already removed during binding
|
315 |
-
// See https://github.com/knockout/knockout/pull/440
|
316 |
-
// [B] Any trailing nodes that have been remove should be ignored
|
317 |
-
// This prevents the code here from adding unrelated nodes to the array while processing rule [C]
|
318 |
-
// See https://github.com/knockout/knockout/pull/1903
|
319 |
-
// [C] We want to output a continuous series of nodes. So, ignore any nodes that have already been removed,
|
320 |
-
// and include any nodes that have been inserted among the previous collection
|
321 |
-
|
322 |
-
if (continuousNodeArray.length) {
|
323 |
-
// The parent node can be a virtual element; so get the real parent node
|
324 |
-
parentNode = (parentNode.nodeType === 8 && parentNode.parentNode) || parentNode;
|
325 |
-
|
326 |
-
// Rule [A]
|
327 |
-
while (continuousNodeArray.length && continuousNodeArray[0].parentNode !== parentNode)
|
328 |
-
continuousNodeArray.splice(0, 1);
|
329 |
-
|
330 |
-
// Rule [B]
|
331 |
-
while (continuousNodeArray.length > 1 && continuousNodeArray[continuousNodeArray.length - 1].parentNode !== parentNode)
|
332 |
-
continuousNodeArray.length--;
|
333 |
-
|
334 |
-
// Rule [C]
|
335 |
-
if (continuousNodeArray.length > 1) {
|
336 |
-
var current = continuousNodeArray[0], last = continuousNodeArray[continuousNodeArray.length - 1];
|
337 |
-
// Replace with the actual new continuous node set
|
338 |
-
continuousNodeArray.length = 0;
|
339 |
-
while (current !== last) {
|
340 |
-
continuousNodeArray.push(current);
|
341 |
-
current = current.nextSibling;
|
342 |
-
}
|
343 |
-
continuousNodeArray.push(last);
|
344 |
-
}
|
345 |
-
}
|
346 |
-
return continuousNodeArray;
|
347 |
-
},
|
348 |
-
|
349 |
-
setOptionNodeSelectionState: function (optionNode, isSelected) {
|
350 |
-
// IE6 sometimes throws "unknown error" if you try to write to .selected directly, whereas Firefox struggles with setAttribute. Pick one based on browser.
|
351 |
-
if (ieVersion < 7)
|
352 |
-
optionNode.setAttribute("selected", isSelected);
|
353 |
-
else
|
354 |
-
optionNode.selected = isSelected;
|
355 |
-
},
|
356 |
-
|
357 |
-
stringTrim: function (string) {
|
358 |
-
return string === null || string === undefined ? '' :
|
359 |
-
string.trim ?
|
360 |
-
string.trim() :
|
361 |
-
string.toString().replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');
|
362 |
-
},
|
363 |
-
|
364 |
-
stringStartsWith: function (string, startsWith) {
|
365 |
-
string = string || "";
|
366 |
-
if (startsWith.length > string.length)
|
367 |
-
return false;
|
368 |
-
return string.substring(0, startsWith.length) === startsWith;
|
369 |
-
},
|
370 |
-
|
371 |
-
domNodeIsContainedBy: function (node, containedByNode) {
|
372 |
-
if (node === containedByNode)
|
373 |
-
return true;
|
374 |
-
if (node.nodeType === 11)
|
375 |
-
return false; // Fixes issue #1162 - can't use node.contains for document fragments on IE8
|
376 |
-
if (containedByNode.contains)
|
377 |
-
return containedByNode.contains(node.nodeType === 3 ? node.parentNode : node);
|
378 |
-
if (containedByNode.compareDocumentPosition)
|
379 |
-
return (containedByNode.compareDocumentPosition(node) & 16) == 16;
|
380 |
-
while (node && node != containedByNode) {
|
381 |
-
node = node.parentNode;
|
382 |
-
}
|
383 |
-
return !!node;
|
384 |
-
},
|
385 |
-
|
386 |
-
domNodeIsAttachedToDocument: function (node) {
|
387 |
-
return ko.utils.domNodeIsContainedBy(node, node.ownerDocument.documentElement);
|
388 |
-
},
|
389 |
-
|
390 |
-
anyDomNodeIsAttachedToDocument: function(nodes) {
|
391 |
-
return !!ko.utils.arrayFirst(nodes, ko.utils.domNodeIsAttachedToDocument);
|
392 |
-
},
|
393 |
-
|
394 |
-
tagNameLower: function(element) {
|
395 |
-
// For HTML elements, tagName will always be upper case; for XHTML elements, it'll be lower case.
|
396 |
-
// Possible future optimization: If we know it's an element from an XHTML document (not HTML),
|
397 |
-
// we don't need to do the .toLowerCase() as it will always be lower case anyway.
|
398 |
-
return element && element.tagName && element.tagName.toLowerCase();
|
399 |
-
},
|
400 |
-
|
401 |
-
catchFunctionErrors: function (delegate) {
|
402 |
-
return ko['onError'] ? function () {
|
403 |
-
try {
|
404 |
-
return delegate.apply(this, arguments);
|
405 |
-
} catch (e) {
|
406 |
-
ko['onError'] && ko['onError'](e);
|
407 |
-
throw e;
|
408 |
-
}
|
409 |
-
} : delegate;
|
410 |
-
},
|
411 |
-
|
412 |
-
setTimeout: function (handler, timeout) {
|
413 |
-
return setTimeout(ko.utils.catchFunctionErrors(handler), timeout);
|
414 |
-
},
|
415 |
-
|
416 |
-
deferError: function (error) {
|
417 |
-
setTimeout(function () {
|
418 |
-
ko['onError'] && ko['onError'](error);
|
419 |
-
throw error;
|
420 |
-
}, 0);
|
421 |
-
},
|
422 |
-
|
423 |
-
registerEventHandler: function (element, eventType, handler) {
|
424 |
-
var wrappedHandler = ko.utils.catchFunctionErrors(handler);
|
425 |
-
|
426 |
-
var mustUseAttachEvent = ieVersion && eventsThatMustBeRegisteredUsingAttachEvent[eventType];
|
427 |
-
if (!ko.options['useOnlyNativeEvents'] && !mustUseAttachEvent && jQueryInstance) {
|
428 |
-
jQueryInstance(element)['bind'](eventType, wrappedHandler);
|
429 |
-
} else if (!mustUseAttachEvent && typeof element.addEventListener == "function")
|
430 |
-
element.addEventListener(eventType, wrappedHandler, false);
|
431 |
-
else if (typeof element.attachEvent != "undefined") {
|
432 |
-
var attachEventHandler = function (event) { wrappedHandler.call(element, event); },
|
433 |
-
attachEventName = "on" + eventType;
|
434 |
-
element.attachEvent(attachEventName, attachEventHandler);
|
435 |
-
|
436 |
-
// IE does not dispose attachEvent handlers automatically (unlike with addEventListener)
|
437 |
-
// so to avoid leaks, we have to remove them manually. See bug #856
|
438 |
-
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
|
439 |
-
element.detachEvent(attachEventName, attachEventHandler);
|
440 |
-
});
|
441 |
-
} else
|
442 |
-
throw new Error("Browser doesn't support addEventListener or attachEvent");
|
443 |
-
},
|
444 |
-
|
445 |
-
triggerEvent: function (element, eventType) {
|
446 |
-
if (!(element && element.nodeType))
|
447 |
-
throw new Error("element must be a DOM node when calling triggerEvent");
|
448 |
-
|
449 |
-
// For click events on checkboxes and radio buttons, jQuery toggles the element checked state *after* the
|
450 |
-
// event handler runs instead of *before*. (This was fixed in 1.9 for checkboxes but not for radio buttons.)
|
451 |
-
// IE doesn't change the checked state when you trigger the click event using "fireEvent".
|
452 |
-
// In both cases, we'll use the click method instead.
|
453 |
-
var useClickWorkaround = isClickOnCheckableElement(element, eventType);
|
454 |
-
|
455 |
-
if (!ko.options['useOnlyNativeEvents'] && jQueryInstance && !useClickWorkaround) {
|
456 |
-
jQueryInstance(element)['trigger'](eventType);
|
457 |
-
} else if (typeof document.createEvent == "function") {
|
458 |
-
if (typeof element.dispatchEvent == "function") {
|
459 |
-
var eventCategory = knownEventTypesByEventName[eventType] || "HTMLEvents";
|
460 |
-
var event = document.createEvent(eventCategory);
|
461 |
-
event.initEvent(eventType, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, element);
|
462 |
-
element.dispatchEvent(event);
|
463 |
-
}
|
464 |
-
else
|
465 |
-
throw new Error("The supplied element doesn't support dispatchEvent");
|
466 |
-
} else if (useClickWorkaround && element.click) {
|
467 |
-
element.click();
|
468 |
-
} else if (typeof element.fireEvent != "undefined") {
|
469 |
-
element.fireEvent("on" + eventType);
|
470 |
-
} else {
|
471 |
-
throw new Error("Browser doesn't support triggering events");
|
472 |
-
}
|
473 |
-
},
|
474 |
-
|
475 |
-
unwrapObservable: function (value) {
|
476 |
-
return ko.isObservable(value) ? value() : value;
|
477 |
-
},
|
478 |
-
|
479 |
-
peekObservable: function (value) {
|
480 |
-
return ko.isObservable(value) ? value.peek() : value;
|
481 |
-
},
|
482 |
-
|
483 |
-
toggleDomNodeCssClass: toggleDomNodeCssClass,
|
484 |
-
|
485 |
-
setTextContent: function(element, textContent) {
|
486 |
-
var value = ko.utils.unwrapObservable(textContent);
|
487 |
-
if ((value === null) || (value === undefined))
|
488 |
-
value = "";
|
489 |
-
|
490 |
-
// We need there to be exactly one child: a text node.
|
491 |
-
// If there are no children, more than one, or if it's not a text node,
|
492 |
-
// we'll clear everything and create a single text node.
|
493 |
-
var innerTextNode = ko.virtualElements.firstChild(element);
|
494 |
-
if (!innerTextNode || innerTextNode.nodeType != 3 || ko.virtualElements.nextSibling(innerTextNode)) {
|
495 |
-
ko.virtualElements.setDomNodeChildren(element, [element.ownerDocument.createTextNode(value)]);
|
496 |
-
} else {
|
497 |
-
innerTextNode.data = value;
|
498 |
-
}
|
499 |
-
|
500 |
-
ko.utils.forceRefresh(element);
|
501 |
-
},
|
502 |
-
|
503 |
-
setElementName: function(element, name) {
|
504 |
-
element.name = name;
|
505 |
-
|
506 |
-
// Workaround IE 6/7 issue
|
507 |
-
// - https://github.com/SteveSanderson/knockout/issues/197
|
508 |
-
// - http://www.matts411.com/post/setting_the_name_attribute_in_ie_dom/
|
509 |
-
if (ieVersion <= 7) {
|
510 |
-
try {
|
511 |
-
element.mergeAttributes(document.createElement("<input name='" + element.name + "'/>"), false);
|
512 |
-
}
|
513 |
-
catch(e) {} // For IE9 with doc mode "IE9 Standards" and browser mode "IE9 Compatibility View"
|
514 |
-
}
|
515 |
-
},
|
516 |
-
|
517 |
-
forceRefresh: function(node) {
|
518 |
-
// Workaround for an IE9 rendering bug - https://github.com/SteveSanderson/knockout/issues/209
|
519 |
-
if (ieVersion >= 9) {
|
520 |
-
// For text nodes and comment nodes (most likely virtual elements), we will have to refresh the container
|
521 |
-
var elem = node.nodeType == 1 ? node : node.parentNode;
|
522 |
-
if (elem.style)
|
523 |
-
elem.style.zoom = elem.style.zoom;
|
524 |
-
}
|
525 |
-
},
|
526 |
-
|
527 |
-
ensureSelectElementIsRenderedCorrectly: function(selectElement) {
|
528 |
-
// Workaround for IE9 rendering bug - it doesn't reliably display all the text in dynamically-added select boxes unless you force it to re-render by updating the width.
|
529 |
-
// (See https://github.com/SteveSanderson/knockout/issues/312, http://stackoverflow.com/questions/5908494/select-only-shows-first-char-of-selected-option)
|
530 |
-
// Also fixes IE7 and IE8 bug that causes selects to be zero width if enclosed by 'if' or 'with'. (See issue #839)
|
531 |
-
if (ieVersion) {
|
532 |
-
var originalWidth = selectElement.style.width;
|
533 |
-
selectElement.style.width = 0;
|
534 |
-
selectElement.style.width = originalWidth;
|
535 |
-
}
|
536 |
-
},
|
537 |
-
|
538 |
-
range: function (min, max) {
|
539 |
-
min = ko.utils.unwrapObservable(min);
|
540 |
-
max = ko.utils.unwrapObservable(max);
|
541 |
-
var result = [];
|
542 |
-
for (var i = min; i <= max; i++)
|
543 |
-
result.push(i);
|
544 |
-
return result;
|
545 |
-
},
|
546 |
-
|
547 |
-
makeArray: function(arrayLikeObject) {
|
548 |
-
var result = [];
|
549 |
-
for (var i = 0, j = arrayLikeObject.length; i < j; i++) {
|
550 |
-
result.push(arrayLikeObject[i]);
|
551 |
-
};
|
552 |
-
return result;
|
553 |
-
},
|
554 |
-
|
555 |
-
createSymbolOrString: function(identifier) {
|
556 |
-
return canUseSymbols ? Symbol(identifier) : identifier;
|
557 |
-
},
|
558 |
-
|
559 |
-
isIe6 : isIe6,
|
560 |
-
isIe7 : isIe7,
|
561 |
-
ieVersion : ieVersion,
|
562 |
-
|
563 |
-
getFormFields: function(form, fieldName) {
|
564 |
-
var fields = ko.utils.makeArray(form.getElementsByTagName("input")).concat(ko.utils.makeArray(form.getElementsByTagName("textarea")));
|
565 |
-
var isMatchingField = (typeof fieldName == 'string')
|
566 |
-
? function(field) { return field.name === fieldName }
|
567 |
-
: function(field) { return fieldName.test(field.name) }; // Treat fieldName as regex or object containing predicate
|
568 |
-
var matches = [];
|
569 |
-
for (var i = fields.length - 1; i >= 0; i--) {
|
570 |
-
if (isMatchingField(fields[i]))
|
571 |
-
matches.push(fields[i]);
|
572 |
-
};
|
573 |
-
return matches;
|
574 |
-
},
|
575 |
-
|
576 |
-
parseJson: function (jsonString) {
|
577 |
-
if (typeof jsonString == "string") {
|
578 |
-
jsonString = ko.utils.stringTrim(jsonString);
|
579 |
-
if (jsonString) {
|
580 |
-
if (JSON && JSON.parse) // Use native parsing where available
|
581 |
-
return JSON.parse(jsonString);
|
582 |
-
return (new Function("return " + jsonString))(); // Fallback on less safe parsing for older browsers
|
583 |
-
}
|
584 |
-
}
|
585 |
-
return null;
|
586 |
-
},
|
587 |
-
|
588 |
-
stringifyJson: function (data, replacer, space) { // replacer and space are optional
|
589 |
-
if (!JSON || !JSON.stringify)
|
590 |
-
throw new Error("Cannot find JSON.stringify(). Some browsers (e.g., IE < 8) don't support it natively, but you can overcome this by adding a script reference to json2.js, downloadable from http://www.json.org/json2.js");
|
591 |
-
return JSON.stringify(ko.utils.unwrapObservable(data), replacer, space);
|
592 |
-
},
|
593 |
-
|
594 |
-
postJson: function (urlOrForm, data, options) {
|
595 |
-
options = options || {};
|
596 |
-
var params = options['params'] || {};
|
597 |
-
var includeFields = options['includeFields'] || this.fieldsIncludedWithJsonPost;
|
598 |
-
var url = urlOrForm;
|
599 |
-
|
600 |
-
// If we were given a form, use its 'action' URL and pick out any requested field values
|
601 |
-
if((typeof urlOrForm == 'object') && (ko.utils.tagNameLower(urlOrForm) === "form")) {
|
602 |
-
var originalForm = urlOrForm;
|
603 |
-
url = originalForm.action;
|
604 |
-
for (var i = includeFields.length - 1; i >= 0; i--) {
|
605 |
-
var fields = ko.utils.getFormFields(originalForm, includeFields[i]);
|
606 |
-
for (var j = fields.length - 1; j >= 0; j--)
|
607 |
-
params[fields[j].name] = fields[j].value;
|
608 |
-
}
|
609 |
-
}
|
610 |
-
|
611 |
-
data = ko.utils.unwrapObservable(data);
|
612 |
-
var form = document.createElement("form");
|
613 |
-
form.style.display = "none";
|
614 |
-
form.action = url;
|
615 |
-
form.method = "post";
|
616 |
-
for (var key in data) {
|
617 |
-
// Since 'data' this is a model object, we include all properties including those inherited from its prototype
|
618 |
-
var input = document.createElement("input");
|
619 |
-
input.type = "hidden";
|
620 |
-
input.name = key;
|
621 |
-
input.value = ko.utils.stringifyJson(ko.utils.unwrapObservable(data[key]));
|
622 |
-
form.appendChild(input);
|
623 |
-
}
|
624 |
-
objectForEach(params, function(key, value) {
|
625 |
-
var input = document.createElement("input");
|
626 |
-
input.type = "hidden";
|
627 |
-
input.name = key;
|
628 |
-
input.value = value;
|
629 |
-
form.appendChild(input);
|
630 |
-
});
|
631 |
-
document.body.appendChild(form);
|
632 |
-
options['submitter'] ? options['submitter'](form) : form.submit();
|
633 |
-
setTimeout(function () { form.parentNode.removeChild(form); }, 0);
|
634 |
-
}
|
635 |
-
}
|
636 |
-
}());
|
637 |
-
|
638 |
-
ko.exportSymbol('utils', ko.utils);
|
639 |
-
ko.exportSymbol('utils.arrayForEach', ko.utils.arrayForEach);
|
640 |
-
ko.exportSymbol('utils.arrayFirst', ko.utils.arrayFirst);
|
641 |
-
ko.exportSymbol('utils.arrayFilter', ko.utils.arrayFilter);
|
642 |
-
ko.exportSymbol('utils.arrayGetDistinctValues', ko.utils.arrayGetDistinctValues);
|
643 |
-
ko.exportSymbol('utils.arrayIndexOf', ko.utils.arrayIndexOf);
|
644 |
-
ko.exportSymbol('utils.arrayMap', ko.utils.arrayMap);
|
645 |
-
ko.exportSymbol('utils.arrayPushAll', ko.utils.arrayPushAll);
|
646 |
-
ko.exportSymbol('utils.arrayRemoveItem', ko.utils.arrayRemoveItem);
|
647 |
-
ko.exportSymbol('utils.extend', ko.utils.extend);
|
648 |
-
ko.exportSymbol('utils.fieldsIncludedWithJsonPost', ko.utils.fieldsIncludedWithJsonPost);
|
649 |
-
ko.exportSymbol('utils.getFormFields', ko.utils.getFormFields);
|
650 |
-
ko.exportSymbol('utils.peekObservable', ko.utils.peekObservable);
|
651 |
-
ko.exportSymbol('utils.postJson', ko.utils.postJson);
|
652 |
-
ko.exportSymbol('utils.parseJson', ko.utils.parseJson);
|
653 |
-
ko.exportSymbol('utils.registerEventHandler', ko.utils.registerEventHandler);
|
654 |
-
ko.exportSymbol('utils.stringifyJson', ko.utils.stringifyJson);
|
655 |
-
ko.exportSymbol('utils.range', ko.utils.range);
|
656 |
-
ko.exportSymbol('utils.toggleDomNodeCssClass', ko.utils.toggleDomNodeCssClass);
|
657 |
-
ko.exportSymbol('utils.triggerEvent', ko.utils.triggerEvent);
|
658 |
-
ko.exportSymbol('utils.unwrapObservable', ko.utils.unwrapObservable);
|
659 |
-
ko.exportSymbol('utils.objectForEach', ko.utils.objectForEach);
|
660 |
-
ko.exportSymbol('utils.addOrRemoveItem', ko.utils.addOrRemoveItem);
|
661 |
-
ko.exportSymbol('utils.setTextContent', ko.utils.setTextContent);
|
662 |
-
ko.exportSymbol('unwrap', ko.utils.unwrapObservable); // Convenient shorthand, because this is used so commonly
|
663 |
-
|
664 |
-
if (!Function.prototype['bind']) {
|
665 |
-
// Function.prototype.bind is a standard part of ECMAScript 5th Edition (December 2009, http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-262.pdf)
|
666 |
-
// In case the browser doesn't implement it natively, provide a JavaScript implementation. This implementation is based on the one in prototype.js
|
667 |
-
Function.prototype['bind'] = function (object) {
|
668 |
-
var originalFunction = this;
|
669 |
-
if (arguments.length === 1) {
|
670 |
-
return function () {
|
671 |
-
return originalFunction.apply(object, arguments);
|
672 |
-
};
|
673 |
-
} else {
|
674 |
-
var partialArgs = Array.prototype.slice.call(arguments, 1);
|
675 |
-
return function () {
|
676 |
-
var args = partialArgs.slice(0);
|
677 |
-
args.push.apply(args, arguments);
|
678 |
-
return originalFunction.apply(object, args);
|
679 |
-
};
|
680 |
-
}
|
681 |
-
};
|
682 |
-
}
|
683 |
-
|
684 |
-
ko.utils.domData = new (function () {
|
685 |
-
var uniqueId = 0;
|
686 |
-
var dataStoreKeyExpandoPropertyName = "__ko__" + (new Date).getTime();
|
687 |
-
var dataStore = {};
|
688 |
-
|
689 |
-
function getAll(node, createIfNotFound) {
|
690 |
-
var dataStoreKey = node[dataStoreKeyExpandoPropertyName];
|
691 |
-
var hasExistingDataStore = dataStoreKey && (dataStoreKey !== "null") && dataStore[dataStoreKey];
|
692 |
-
if (!hasExistingDataStore) {
|
693 |
-
if (!createIfNotFound)
|
694 |
-
return undefined;
|
695 |
-
dataStoreKey = node[dataStoreKeyExpandoPropertyName] = "ko" + uniqueId++;
|
696 |
-
dataStore[dataStoreKey] = {};
|
697 |
-
}
|
698 |
-
return dataStore[dataStoreKey];
|
699 |
-
}
|
700 |
-
|
701 |
-
return {
|
702 |
-
get: function (node, key) {
|
703 |
-
var allDataForNode = getAll(node, false);
|
704 |
-
return allDataForNode === undefined ? undefined : allDataForNode[key];
|
705 |
-
},
|
706 |
-
set: function (node, key, value) {
|
707 |
-
if (value === undefined) {
|
708 |
-
// Make sure we don't actually create a new domData key if we are actually deleting a value
|
709 |
-
if (getAll(node, false) === undefined)
|
710 |
-
return;
|
711 |
-
}
|
712 |
-
var allDataForNode = getAll(node, true);
|
713 |
-
allDataForNode[key] = value;
|
714 |
-
},
|
715 |
-
clear: function (node) {
|
716 |
-
var dataStoreKey = node[dataStoreKeyExpandoPropertyName];
|
717 |
-
if (dataStoreKey) {
|
718 |
-
delete dataStore[dataStoreKey];
|
719 |
-
node[dataStoreKeyExpandoPropertyName] = null;
|
720 |
-
return true; // Exposing "did clean" flag purely so specs can infer whether things have been cleaned up as intended
|
721 |
-
}
|
722 |
-
return false;
|
723 |
-
},
|
724 |
-
|
725 |
-
nextKey: function () {
|
726 |
-
return (uniqueId++) + dataStoreKeyExpandoPropertyName;
|
727 |
-
}
|
728 |
-
};
|
729 |
-
})();
|
730 |
-
|
731 |
-
ko.exportSymbol('utils.domData', ko.utils.domData);
|
732 |
-
ko.exportSymbol('utils.domData.clear', ko.utils.domData.clear); // Exporting only so specs can clear up after themselves fully
|
733 |
-
|
734 |
-
ko.utils.domNodeDisposal = new (function () {
|
735 |
-
var domDataKey = ko.utils.domData.nextKey();
|
736 |
-
var cleanableNodeTypes = { 1: true, 8: true, 9: true }; // Element, Comment, Document
|
737 |
-
var cleanableNodeTypesWithDescendants = { 1: true, 9: true }; // Element, Document
|
738 |
-
|
739 |
-
function getDisposeCallbacksCollection(node, createIfNotFound) {
|
740 |
-
var allDisposeCallbacks = ko.utils.domData.get(node, domDataKey);
|
741 |
-
if ((allDisposeCallbacks === undefined) && createIfNotFound) {
|
742 |
-
allDisposeCallbacks = [];
|
743 |
-
ko.utils.domData.set(node, domDataKey, allDisposeCallbacks);
|
744 |
-
}
|
745 |
-
return allDisposeCallbacks;
|
746 |
-
}
|
747 |
-
function destroyCallbacksCollection(node) {
|
748 |
-
ko.utils.domData.set(node, domDataKey, undefined);
|
749 |
-
}
|
750 |
-
|
751 |
-
function cleanSingleNode(node) {
|
752 |
-
// Run all the dispose callbacks
|
753 |
-
var callbacks = getDisposeCallbacksCollection(node, false);
|
754 |
-
if (callbacks) {
|
755 |
-
callbacks = callbacks.slice(0); // Clone, as the array may be modified during iteration (typically, callbacks will remove themselves)
|
756 |
-
for (var i = 0; i < callbacks.length; i++)
|
757 |
-
callbacks[i](node);
|
758 |
-
}
|
759 |
-
|
760 |
-
// Erase the DOM data
|
761 |
-
ko.utils.domData.clear(node);
|
762 |
-
|
763 |
-
// Perform cleanup needed by external libraries (currently only jQuery, but can be extended)
|
764 |
-
ko.utils.domNodeDisposal["cleanExternalData"](node);
|
765 |
-
|
766 |
-
// Clear any immediate-child comment nodes, as these wouldn't have been found by
|
767 |
-
// node.getElementsByTagName("*") in cleanNode() (comment nodes aren't elements)
|
768 |
-
if (cleanableNodeTypesWithDescendants[node.nodeType])
|
769 |
-
cleanImmediateCommentTypeChildren(node);
|
770 |
-
}
|
771 |
-
|
772 |
-
function cleanImmediateCommentTypeChildren(nodeWithChildren) {
|
773 |
-
var child, nextChild = nodeWithChildren.firstChild;
|
774 |
-
while (child = nextChild) {
|
775 |
-
nextChild = child.nextSibling;
|
776 |
-
if (child.nodeType === 8)
|
777 |
-
cleanSingleNode(child);
|
778 |
-
}
|
779 |
-
}
|
780 |
-
|
781 |
-
return {
|
782 |
-
addDisposeCallback : function(node, callback) {
|
783 |
-
if (typeof callback != "function")
|
784 |
-
throw new Error("Callback must be a function");
|
785 |
-
getDisposeCallbacksCollection(node, true).push(callback);
|
786 |
-
},
|
787 |
-
|
788 |
-
removeDisposeCallback : function(node, callback) {
|
789 |
-
var callbacksCollection = getDisposeCallbacksCollection(node, false);
|
790 |
-
if (callbacksCollection) {
|
791 |
-
ko.utils.arrayRemoveItem(callbacksCollection, callback);
|
792 |
-
if (callbacksCollection.length == 0)
|
793 |
-
destroyCallbacksCollection(node);
|
794 |
-
}
|
795 |
-
},
|
796 |
-
|
797 |
-
cleanNode : function(node) {
|
798 |
-
// First clean this node, where applicable
|
799 |
-
if (cleanableNodeTypes[node.nodeType]) {
|
800 |
-
cleanSingleNode(node);
|
801 |
-
|
802 |
-
// ... then its descendants, where applicable
|
803 |
-
if (cleanableNodeTypesWithDescendants[node.nodeType]) {
|
804 |
-
// Clone the descendants list in case it changes during iteration
|
805 |
-
var descendants = [];
|
806 |
-
ko.utils.arrayPushAll(descendants, node.getElementsByTagName("*"));
|
807 |
-
for (var i = 0, j = descendants.length; i < j; i++)
|
808 |
-
cleanSingleNode(descendants[i]);
|
809 |
-
}
|
810 |
-
}
|
811 |
-
return node;
|
812 |
-
},
|
813 |
-
|
814 |
-
removeNode : function(node) {
|
815 |
-
ko.cleanNode(node);
|
816 |
-
if (node.parentNode)
|
817 |
-
node.parentNode.removeChild(node);
|
818 |
-
},
|
819 |
-
|
820 |
-
"cleanExternalData" : function (node) {
|
821 |
-
// Special support for jQuery here because it's so commonly used.
|
822 |
-
// Many jQuery plugins (including jquery.tmpl) store data using jQuery's equivalent of domData
|
823 |
-
// so notify it to tear down any resources associated with the node & descendants here.
|
824 |
-
if (jQueryInstance && (typeof jQueryInstance['cleanData'] == "function"))
|
825 |
-
jQueryInstance['cleanData']([node]);
|
826 |
-
}
|
827 |
-
};
|
828 |
-
})();
|
829 |
-
ko.cleanNode = ko.utils.domNodeDisposal.cleanNode; // Shorthand name for convenience
|
830 |
-
ko.removeNode = ko.utils.domNodeDisposal.removeNode; // Shorthand name for convenience
|
831 |
-
ko.exportSymbol('cleanNode', ko.cleanNode);
|
832 |
-
ko.exportSymbol('removeNode', ko.removeNode);
|
833 |
-
ko.exportSymbol('utils.domNodeDisposal', ko.utils.domNodeDisposal);
|
834 |
-
ko.exportSymbol('utils.domNodeDisposal.addDisposeCallback', ko.utils.domNodeDisposal.addDisposeCallback);
|
835 |
-
ko.exportSymbol('utils.domNodeDisposal.removeDisposeCallback', ko.utils.domNodeDisposal.removeDisposeCallback);
|
836 |
-
(function () {
|
837 |
-
var none = [0, "", ""],
|
838 |
-
table = [1, "<table>", "</table>"],
|
839 |
-
tbody = [2, "<table><tbody>", "</tbody></table>"],
|
840 |
-
tr = [3, "<table><tbody><tr>", "</tr></tbody></table>"],
|
841 |
-
select = [1, "<select multiple='multiple'>", "</select>"],
|
842 |
-
lookup = {
|
843 |
-
'thead': table,
|
844 |
-
'tbody': table,
|
845 |
-
'tfoot': table,
|
846 |
-
'tr': tbody,
|
847 |
-
'td': tr,
|
848 |
-
'th': tr,
|
849 |
-
'option': select,
|
850 |
-
'optgroup': select
|
851 |
-
},
|
852 |
-
|
853 |
-
// This is needed for old IE if you're *not* using either jQuery or innerShiv. Doesn't affect other cases.
|
854 |
-
mayRequireCreateElementHack = ko.utils.ieVersion <= 8;
|
855 |
-
|
856 |
-
function getWrap(tags) {
|
857 |
-
var m = tags.match(/^<([a-z]+)[ >]/);
|
858 |
-
return (m && lookup[m[1]]) || none;
|
859 |
-
}
|
860 |
-
|
861 |
-
function simpleHtmlParse(html, documentContext) {
|
862 |
-
documentContext || (documentContext = document);
|
863 |
-
var windowContext = documentContext['parentWindow'] || documentContext['defaultView'] || window;
|
864 |
-
|
865 |
-
// Based on jQuery's "clean" function, but only accounting for table-related elements.
|
866 |
-
// If you have referenced jQuery, this won't be used anyway - KO will use jQuery's "clean" function directly
|
867 |
-
|
868 |
-
// Note that there's still an issue in IE < 9 whereby it will discard comment nodes that are the first child of
|
869 |
-
// a descendant node. For example: "<div><!-- mycomment -->abc</div>" will get parsed as "<div>abc</div>"
|
870 |
-
// This won't affect anyone who has referenced jQuery, and there's always the workaround of inserting a dummy node
|
871 |
-
// (possibly a text node) in front of the comment. So, KO does not attempt to workaround this IE issue automatically at present.
|
872 |
-
|
873 |
-
// Trim whitespace, otherwise indexOf won't work as expected
|
874 |
-
var tags = ko.utils.stringTrim(html).toLowerCase(), div = documentContext.createElement("div"),
|
875 |
-
wrap = getWrap(tags),
|
876 |
-
depth = wrap[0];
|
877 |
-
|
878 |
-
// Go to html and back, then peel off extra wrappers
|
879 |
-
// Note that we always prefix with some dummy text, because otherwise, IE<9 will strip out leading comment nodes in descendants. Total madness.
|
880 |
-
var markup = "ignored<div>" + wrap[1] + html + wrap[2] + "</div>";
|
881 |
-
if (typeof windowContext['innerShiv'] == "function") {
|
882 |
-
// Note that innerShiv is deprecated in favour of html5shiv. We should consider adding
|
883 |
-
// support for html5shiv (except if no explicit support is needed, e.g., if html5shiv
|
884 |
-
// somehow shims the native APIs so it just works anyway)
|
885 |
-
div.appendChild(windowContext['innerShiv'](markup));
|
886 |
-
} else {
|
887 |
-
if (mayRequireCreateElementHack) {
|
888 |
-
// The document.createElement('my-element') trick to enable custom elements in IE6-8
|
889 |
-
// only works if we assign innerHTML on an element associated with that document.
|
890 |
-
documentContext.appendChild(div);
|
891 |
-
}
|
892 |
-
|
893 |
-
div.innerHTML = markup;
|
894 |
-
|
895 |
-
if (mayRequireCreateElementHack) {
|
896 |
-
div.parentNode.removeChild(div);
|
897 |
-
}
|
898 |
-
}
|
899 |
-
|
900 |
-
// Move to the right depth
|
901 |
-
while (depth--)
|
902 |
-
div = div.lastChild;
|
903 |
-
|
904 |
-
return ko.utils.makeArray(div.lastChild.childNodes);
|
905 |
-
}
|
906 |
-
|
907 |
-
function jQueryHtmlParse(html, documentContext) {
|
908 |
-
// jQuery's "parseHTML" function was introduced in jQuery 1.8.0 and is a documented public API.
|
909 |
-
if (jQueryInstance['parseHTML']) {
|
910 |
-
return jQueryInstance['parseHTML'](html, documentContext) || []; // Ensure we always return an array and never null
|
911 |
-
} else {
|
912 |
-
// For jQuery < 1.8.0, we fall back on the undocumented internal "clean" function.
|
913 |
-
var elems = jQueryInstance['clean']([html], documentContext);
|
914 |
-
|
915 |
-
// As of jQuery 1.7.1, jQuery parses the HTML by appending it to some dummy parent nodes held in an in-memory document fragment.
|
916 |
-
// Unfortunately, it never clears the dummy parent nodes from the document fragment, so it leaks memory over time.
|
917 |
-
// Fix this by finding the top-most dummy parent element, and detaching it from its owner fragment.
|
918 |
-
if (elems && elems[0]) {
|
919 |
-
// Find the top-most parent element that's a direct child of a document fragment
|
920 |
-
var elem = elems[0];
|
921 |
-
while (elem.parentNode && elem.parentNode.nodeType !== 11 /* i.e., DocumentFragment */)
|
922 |
-
elem = elem.parentNode;
|
923 |
-
// ... then detach it
|
924 |
-
if (elem.parentNode)
|
925 |
-
elem.parentNode.removeChild(elem);
|
926 |
-
}
|
927 |
-
|
928 |
-
return elems;
|
929 |
-
}
|
930 |
-
}
|
931 |
-
|
932 |
-
ko.utils.parseHtmlFragment = function(html, documentContext) {
|
933 |
-
return jQueryInstance ?
|
934 |
-
jQueryHtmlParse(html, documentContext) : // As below, benefit from jQuery's optimisations where possible
|
935 |
-
simpleHtmlParse(html, documentContext); // ... otherwise, this simple logic will do in most common cases.
|
936 |
-
};
|
937 |
-
|
938 |
-
ko.utils.setHtml = function(node, html) {
|
939 |
-
ko.utils.emptyDomNode(node);
|
940 |
-
|
941 |
-
// There's no legitimate reason to display a stringified observable without unwrapping it, so we'll unwrap it
|
942 |
-
html = ko.utils.unwrapObservable(html);
|
943 |
-
|
944 |
-
if ((html !== null) && (html !== undefined)) {
|
945 |
-
if (typeof html != 'string')
|
946 |
-
html = html.toString();
|
947 |
-
|
948 |
-
// jQuery contains a lot of sophisticated code to parse arbitrary HTML fragments,
|
949 |
-
// for example <tr> elements which are not normally allowed to exist on their own.
|
950 |
-
// If you've referenced jQuery we'll use that rather than duplicating its code.
|
951 |
-
if (jQueryInstance) {
|
952 |
-
jQueryInstance(node)['html'](html);
|
953 |
-
} else {
|
954 |
-
// ... otherwise, use KO's own parsing logic.
|
955 |
-
var parsedNodes = ko.utils.parseHtmlFragment(html, node.ownerDocument);
|
956 |
-
for (var i = 0; i < parsedNodes.length; i++)
|
957 |
-
node.appendChild(parsedNodes[i]);
|
958 |
-
}
|
959 |
-
}
|
960 |
-
};
|
961 |
-
})();
|
962 |
-
|
963 |
-
ko.exportSymbol('utils.parseHtmlFragment', ko.utils.parseHtmlFragment);
|
964 |
-
ko.exportSymbol('utils.setHtml', ko.utils.setHtml);
|
965 |
-
|
966 |
-
ko.memoization = (function () {
|
967 |
-
var memos = {};
|
968 |
-
|
969 |
-
function randomMax8HexChars() {
|
970 |
-
return (((1 + Math.random()) * 0x100000000) | 0).toString(16).substring(1);
|
971 |
-
}
|
972 |
-
function generateRandomId() {
|
973 |
-
return randomMax8HexChars() + randomMax8HexChars();
|
974 |
-
}
|
975 |
-
function findMemoNodes(rootNode, appendToArray) {
|
976 |
-
if (!rootNode)
|
977 |
-
return;
|
978 |
-
if (rootNode.nodeType == 8) {
|
979 |
-
var memoId = ko.memoization.parseMemoText(rootNode.nodeValue);
|
980 |
-
if (memoId != null)
|
981 |
-
appendToArray.push({ domNode: rootNode, memoId: memoId });
|
982 |
-
} else if (rootNode.nodeType == 1) {
|
983 |
-
for (var i = 0, childNodes = rootNode.childNodes, j = childNodes.length; i < j; i++)
|
984 |
-
findMemoNodes(childNodes[i], appendToArray);
|
985 |
-
}
|
986 |
-
}
|
987 |
-
|
988 |
-
return {
|
989 |
-
memoize: function (callback) {
|
990 |
-
if (typeof callback != "function")
|
991 |
-
throw new Error("You can only pass a function to ko.memoization.memoize()");
|
992 |
-
var memoId = generateRandomId();
|
993 |
-
memos[memoId] = callback;
|
994 |
-
return "<!--[ko_memo:" + memoId + "]-->";
|
995 |
-
},
|
996 |
-
|
997 |
-
unmemoize: function (memoId, callbackParams) {
|
998 |
-
var callback = memos[memoId];
|
999 |
-
if (callback === undefined)
|
1000 |
-
throw new Error("Couldn't find any memo with ID " + memoId + ". Perhaps it's already been unmemoized.");
|
1001 |
-
try {
|
1002 |
-
callback.apply(null, callbackParams || []);
|
1003 |
-
return true;
|
1004 |
-
}
|
1005 |
-
finally { delete memos[memoId]; }
|
1006 |
-
},
|
1007 |
-
|
1008 |
-
unmemoizeDomNodeAndDescendants: function (domNode, extraCallbackParamsArray) {
|
1009 |
-
var memos = [];
|
1010 |
-
findMemoNodes(domNode, memos);
|
1011 |
-
for (var i = 0, j = memos.length; i < j; i++) {
|
1012 |
-
var node = memos[i].domNode;
|
1013 |
-
var combinedParams = [node];
|
1014 |
-
if (extraCallbackParamsArray)
|
1015 |
-
ko.utils.arrayPushAll(combinedParams, extraCallbackParamsArray);
|
1016 |
-
ko.memoization.unmemoize(memos[i].memoId, combinedParams);
|
1017 |
-
node.nodeValue = ""; // Neuter this node so we don't try to unmemoize it again
|
1018 |
-
if (node.parentNode)
|
1019 |
-
node.parentNode.removeChild(node); // If possible, erase it totally (not always possible - someone else might just hold a reference to it then call unmemoizeDomNodeAndDescendants again)
|
1020 |
-
}
|
1021 |
-
},
|
1022 |
-
|
1023 |
-
parseMemoText: function (memoText) {
|
1024 |
-
var match = memoText.match(/^\[ko_memo\:(.*?)\]$/);
|
1025 |
-
return match ? match[1] : null;
|
1026 |
-
}
|
1027 |
-
};
|
1028 |
-
})();
|
1029 |
-
|
1030 |
-
ko.exportSymbol('memoization', ko.memoization);
|
1031 |
-
ko.exportSymbol('memoization.memoize', ko.memoization.memoize);
|
1032 |
-
ko.exportSymbol('memoization.unmemoize', ko.memoization.unmemoize);
|
1033 |
-
ko.exportSymbol('memoization.parseMemoText', ko.memoization.parseMemoText);
|
1034 |
-
ko.exportSymbol('memoization.unmemoizeDomNodeAndDescendants', ko.memoization.unmemoizeDomNodeAndDescendants);
|
1035 |
-
ko.tasks = (function () {
|
1036 |
-
var scheduler,
|
1037 |
-
taskQueue = [],
|
1038 |
-
taskQueueLength = 0,
|
1039 |
-
nextHandle = 1,
|
1040 |
-
nextIndexToProcess = 0;
|
1041 |
-
|
1042 |
-
if (window['MutationObserver']) {
|
1043 |
-
// Chrome 27+, Firefox 14+, IE 11+, Opera 15+, Safari 6.1+
|
1044 |
-
// From https://github.com/petkaantonov/bluebird * Copyright (c) 2014 Petka Antonov * License: MIT
|
1045 |
-
scheduler = (function (callback) {
|
1046 |
-
var div = document.createElement("div");
|
1047 |
-
new MutationObserver(callback).observe(div, {attributes: true});
|
1048 |
-
return function () { div.classList.toggle("foo"); };
|
1049 |
-
})(scheduledProcess);
|
1050 |
-
} else if (document && "onreadystatechange" in document.createElement("script")) {
|
1051 |
-
// IE 6-10
|
1052 |
-
// From https://github.com/YuzuJS/setImmediate * Copyright (c) 2012 Barnesandnoble.com, llc, Donavon West, and Domenic Denicola * License: MIT
|
1053 |
-
scheduler = function (callback) {
|
1054 |
-
var script = document.createElement("script");
|
1055 |
-
script.onreadystatechange = function () {
|
1056 |
-
script.onreadystatechange = null;
|
1057 |
-
document.documentElement.removeChild(script);
|
1058 |
-
script = null;
|
1059 |
-
callback();
|
1060 |
-
};
|
1061 |
-
document.documentElement.appendChild(script);
|
1062 |
-
};
|
1063 |
-
} else {
|
1064 |
-
scheduler = function (callback) {
|
1065 |
-
setTimeout(callback, 0);
|
1066 |
-
};
|
1067 |
-
}
|
1068 |
-
|
1069 |
-
function processTasks() {
|
1070 |
-
if (taskQueueLength) {
|
1071 |
-
// Each mark represents the end of a logical group of tasks and the number of these groups is
|
1072 |
-
// limited to prevent unchecked recursion.
|
1073 |
-
var mark = taskQueueLength, countMarks = 0;
|
1074 |
-
|
1075 |
-
// nextIndexToProcess keeps track of where we are in the queue; processTasks can be called recursively without issue
|
1076 |
-
for (var task; nextIndexToProcess < taskQueueLength; ) {
|
1077 |
-
if (task = taskQueue[nextIndexToProcess++]) {
|
1078 |
-
if (nextIndexToProcess > mark) {
|
1079 |
-
if (++countMarks >= 5000) {
|
1080 |
-
nextIndexToProcess = taskQueueLength; // skip all tasks remaining in the queue since any of them could be causing the recursion
|
1081 |
-
ko.utils.deferError(Error("'Too much recursion' after processing " + countMarks + " task groups."));
|
1082 |
-
break;
|
1083 |
-
}
|
1084 |
-
mark = taskQueueLength;
|
1085 |
-
}
|
1086 |
-
try {
|
1087 |
-
task();
|
1088 |
-
} catch (ex) {
|
1089 |
-
ko.utils.deferError(ex);
|
1090 |
-
}
|
1091 |
-
}
|
1092 |
-
}
|
1093 |
-
}
|
1094 |
-
}
|
1095 |
-
|
1096 |
-
function scheduledProcess() {
|
1097 |
-
processTasks();
|
1098 |
-
|
1099 |
-
// Reset the queue
|
1100 |
-
nextIndexToProcess = taskQueueLength = taskQueue.length = 0;
|
1101 |
-
}
|
1102 |
-
|
1103 |
-
function scheduleTaskProcessing() {
|
1104 |
-
ko.tasks['scheduler'](scheduledProcess);
|
1105 |
-
}
|
1106 |
-
|
1107 |
-
var tasks = {
|
1108 |
-
'scheduler': scheduler, // Allow overriding the scheduler
|
1109 |
-
|
1110 |
-
schedule: function (func) {
|
1111 |
-
if (!taskQueueLength) {
|
1112 |
-
scheduleTaskProcessing();
|
1113 |
-
}
|
1114 |
-
|
1115 |
-
taskQueue[taskQueueLength++] = func;
|
1116 |
-
return nextHandle++;
|
1117 |
-
},
|
1118 |
-
|
1119 |
-
cancel: function (handle) {
|
1120 |
-
var index = handle - (nextHandle - taskQueueLength);
|
1121 |
-
if (index >= nextIndexToProcess && index < taskQueueLength) {
|
1122 |
-
taskQueue[index] = null;
|
1123 |
-
}
|
1124 |
-
},
|
1125 |
-
|
1126 |
-
// For testing only: reset the queue and return the previous queue length
|
1127 |
-
'resetForTesting': function () {
|
1128 |
-
var length = taskQueueLength - nextIndexToProcess;
|
1129 |
-
nextIndexToProcess = taskQueueLength = taskQueue.length = 0;
|
1130 |
-
return length;
|
1131 |
-
},
|
1132 |
-
|
1133 |
-
runEarly: processTasks
|
1134 |
-
};
|
1135 |
-
|
1136 |
-
return tasks;
|
1137 |
-
})();
|
1138 |
-
|
1139 |
-
ko.exportSymbol('tasks', ko.tasks);
|
1140 |
-
ko.exportSymbol('tasks.schedule', ko.tasks.schedule);
|
1141 |
-
//ko.exportSymbol('tasks.cancel', ko.tasks.cancel); "cancel" isn't minified
|
1142 |
-
ko.exportSymbol('tasks.runEarly', ko.tasks.runEarly);
|
1143 |
-
ko.extenders = {
|
1144 |
-
'throttle': function(target, timeout) {
|
1145 |
-
// Throttling means two things:
|
1146 |
-
|
1147 |
-
// (1) For dependent observables, we throttle *evaluations* so that, no matter how fast its dependencies
|
1148 |
-
// notify updates, the target doesn't re-evaluate (and hence doesn't notify) faster than a certain rate
|
1149 |
-
target['throttleEvaluation'] = timeout;
|
1150 |
-
|
1151 |
-
// (2) For writable targets (observables, or writable dependent observables), we throttle *writes*
|
1152 |
-
// so the target cannot change value synchronously or faster than a certain rate
|
1153 |
-
var writeTimeoutInstance = null;
|
1154 |
-
return ko.dependentObservable({
|
1155 |
-
'read': target,
|
1156 |
-
'write': function(value) {
|
1157 |
-
clearTimeout(writeTimeoutInstance);
|
1158 |
-
writeTimeoutInstance = ko.utils.setTimeout(function() {
|
1159 |
-
target(value);
|
1160 |
-
}, timeout);
|
1161 |
-
}
|
1162 |
-
});
|
1163 |
-
},
|
1164 |
-
|
1165 |
-
'rateLimit': function(target, options) {
|
1166 |
-
var timeout, method, limitFunction;
|
1167 |
-
|
1168 |
-
if (typeof options == 'number') {
|
1169 |
-
timeout = options;
|
1170 |
-
} else {
|
1171 |
-
timeout = options['timeout'];
|
1172 |
-
method = options['method'];
|
1173 |
-
}
|
1174 |
-
|
1175 |
-
// rateLimit supersedes deferred updates
|
1176 |
-
target._deferUpdates = false;
|
1177 |
-
|
1178 |
-
limitFunction = method == 'notifyWhenChangesStop' ? debounce : throttle;
|
1179 |
-
target.limit(function(callback) {
|
1180 |
-
return limitFunction(callback, timeout);
|
1181 |
-
});
|
1182 |
-
},
|
1183 |
-
|
1184 |
-
'deferred': function(target, options) {
|
1185 |
-
if (options !== true) {
|
1186 |
-
throw new Error('The \'deferred\' extender only accepts the value \'true\', because it is not supported to turn deferral off once enabled.')
|
1187 |
-
}
|
1188 |
-
|
1189 |
-
if (!target._deferUpdates) {
|
1190 |
-
target._deferUpdates = true;
|
1191 |
-
target.limit(function (callback) {
|
1192 |
-
var handle;
|
1193 |
-
return function () {
|
1194 |
-
ko.tasks.cancel(handle);
|
1195 |
-
handle = ko.tasks.schedule(callback);
|
1196 |
-
target['notifySubscribers'](undefined, 'dirty');
|
1197 |
-
};
|
1198 |
-
});
|
1199 |
-
}
|
1200 |
-
},
|
1201 |
-
|
1202 |
-
'notify': function(target, notifyWhen) {
|
1203 |
-
target["equalityComparer"] = notifyWhen == "always" ?
|
1204 |
-
null : // null equalityComparer means to always notify
|
1205 |
-
valuesArePrimitiveAndEqual;
|
1206 |
-
}
|
1207 |
-
};
|
1208 |
-
|
1209 |
-
var primitiveTypes = { 'undefined':1, 'boolean':1, 'number':1, 'string':1 };
|
1210 |
-
function valuesArePrimitiveAndEqual(a, b) {
|
1211 |
-
var oldValueIsPrimitive = (a === null) || (typeof(a) in primitiveTypes);
|
1212 |
-
return oldValueIsPrimitive ? (a === b) : false;
|
1213 |
-
}
|
1214 |
-
|
1215 |
-
function throttle(callback, timeout) {
|
1216 |
-
var timeoutInstance;
|
1217 |
-
return function () {
|
1218 |
-
if (!timeoutInstance) {
|
1219 |
-
timeoutInstance = ko.utils.setTimeout(function () {
|
1220 |
-
timeoutInstance = undefined;
|
1221 |
-
callback();
|
1222 |
-
}, timeout);
|
1223 |
-
}
|
1224 |
-
};
|
1225 |
-
}
|
1226 |
-
|
1227 |
-
function debounce(callback, timeout) {
|
1228 |
-
var timeoutInstance;
|
1229 |
-
return function () {
|
1230 |
-
clearTimeout(timeoutInstance);
|
1231 |
-
timeoutInstance = ko.utils.setTimeout(callback, timeout);
|
1232 |
-
};
|
1233 |
-
}
|
1234 |
-
|
1235 |
-
function applyExtenders(requestedExtenders) {
|
1236 |
-
var target = this;
|
1237 |
-
if (requestedExtenders) {
|
1238 |
-
ko.utils.objectForEach(requestedExtenders, function(key, value) {
|
1239 |
-
var extenderHandler = ko.extenders[key];
|
1240 |
-
if (typeof extenderHandler == 'function') {
|
1241 |
-
target = extenderHandler(target, value) || target;
|
1242 |
-
}
|
1243 |
-
});
|
1244 |
-
}
|
1245 |
-
return target;
|
1246 |
-
}
|
1247 |
-
|
1248 |
-
ko.exportSymbol('extenders', ko.extenders);
|
1249 |
-
|
1250 |
-
ko.subscription = function (target, callback, disposeCallback) {
|
1251 |
-
this._target = target;
|
1252 |
-
this.callback = callback;
|
1253 |
-
this.disposeCallback = disposeCallback;
|
1254 |
-
this.isDisposed = false;
|
1255 |
-
ko.exportProperty(this, 'dispose', this.dispose);
|
1256 |
-
};
|
1257 |
-
ko.subscription.prototype.dispose = function () {
|
1258 |
-
this.isDisposed = true;
|
1259 |
-
this.disposeCallback();
|
1260 |
-
};
|
1261 |
-
|
1262 |
-
ko.subscribable = function () {
|
1263 |
-
ko.utils.setPrototypeOfOrExtend(this, ko_subscribable_fn);
|
1264 |
-
ko_subscribable_fn.init(this);
|
1265 |
-
}
|
1266 |
-
|
1267 |
-
var defaultEvent = "change";
|
1268 |
-
|
1269 |
-
// Moved out of "limit" to avoid the extra closure
|
1270 |
-
function limitNotifySubscribers(value, event) {
|
1271 |
-
if (!event || event === defaultEvent) {
|
1272 |
-
this._limitChange(value);
|
1273 |
-
} else if (event === 'beforeChange') {
|
1274 |
-
this._limitBeforeChange(value);
|
1275 |
-
} else {
|
1276 |
-
this._origNotifySubscribers(value, event);
|
1277 |
-
}
|
1278 |
-
}
|
1279 |
-
|
1280 |
-
var ko_subscribable_fn = {
|
1281 |
-
init: function(instance) {
|
1282 |
-
instance._subscriptions = {};
|
1283 |
-
instance._versionNumber = 1;
|
1284 |
-
},
|
1285 |
-
|
1286 |
-
subscribe: function (callback, callbackTarget, event) {
|
1287 |
-
var self = this;
|
1288 |
-
|
1289 |
-
event = event || defaultEvent;
|
1290 |
-
var boundCallback = callbackTarget ? callback.bind(callbackTarget) : callback;
|
1291 |
-
|
1292 |
-
var subscription = new ko.subscription(self, boundCallback, function () {
|
1293 |
-
ko.utils.arrayRemoveItem(self._subscriptions[event], subscription);
|
1294 |
-
if (self.afterSubscriptionRemove)
|
1295 |
-
self.afterSubscriptionRemove(event);
|
1296 |
-
});
|
1297 |
-
|
1298 |
-
if (self.beforeSubscriptionAdd)
|
1299 |
-
self.beforeSubscriptionAdd(event);
|
1300 |
-
|
1301 |
-
if (!self._subscriptions[event])
|
1302 |
-
self._subscriptions[event] = [];
|
1303 |
-
self._subscriptions[event].push(subscription);
|
1304 |
-
|
1305 |
-
return subscription;
|
1306 |
-
},
|
1307 |
-
|
1308 |
-
"notifySubscribers": function (valueToNotify, event) {
|
1309 |
-
event = event || defaultEvent;
|
1310 |
-
if (event === defaultEvent) {
|
1311 |
-
this.updateVersion();
|
1312 |
-
}
|
1313 |
-
if (this.hasSubscriptionsForEvent(event)) {
|
1314 |
-
try {
|
1315 |
-
ko.dependencyDetection.begin(); // Begin suppressing dependency detection (by setting the top frame to undefined)
|
1316 |
-
for (var a = this._subscriptions[event].slice(0), i = 0, subscription; subscription = a[i]; ++i) {
|
1317 |
-
// In case a subscription was disposed during the arrayForEach cycle, check
|
1318 |
-
// for isDisposed on each subscription before invoking its callback
|
1319 |
-
if (!subscription.isDisposed)
|
1320 |
-
subscription.callback(valueToNotify);
|
1321 |
-
}
|
1322 |
-
} finally {
|
1323 |
-
ko.dependencyDetection.end(); // End suppressing dependency detection
|
1324 |
-
}
|
1325 |
-
}
|
1326 |
-
},
|
1327 |
-
|
1328 |
-
getVersion: function () {
|
1329 |
-
return this._versionNumber;
|
1330 |
-
},
|
1331 |
-
|
1332 |
-
hasChanged: function (versionToCheck) {
|
1333 |
-
return this.getVersion() !== versionToCheck;
|
1334 |
-
},
|
1335 |
-
|
1336 |
-
updateVersion: function () {
|
1337 |
-
++this._versionNumber;
|
1338 |
-
},
|
1339 |
-
|
1340 |
-
limit: function(limitFunction) {
|
1341 |
-
var self = this, selfIsObservable = ko.isObservable(self),
|
1342 |
-
ignoreBeforeChange, previousValue, pendingValue, beforeChange = 'beforeChange';
|
1343 |
-
|
1344 |
-
if (!self._origNotifySubscribers) {
|
1345 |
-
self._origNotifySubscribers = self["notifySubscribers"];
|
1346 |
-
self["notifySubscribers"] = limitNotifySubscribers;
|
1347 |
-
}
|
1348 |
-
|
1349 |
-
var finish = limitFunction(function() {
|
1350 |
-
self._notificationIsPending = false;
|
1351 |
-
|
1352 |
-
// If an observable provided a reference to itself, access it to get the latest value.
|
1353 |
-
// This allows computed observables to delay calculating their value until needed.
|
1354 |
-
if (selfIsObservable && pendingValue === self) {
|
1355 |
-
pendingValue = self();
|
1356 |
-
}
|
1357 |
-
ignoreBeforeChange = false;
|
1358 |
-
if (self.isDifferent(previousValue, pendingValue)) {
|
1359 |
-
self._origNotifySubscribers(previousValue = pendingValue);
|
1360 |
-
}
|
1361 |
-
});
|
1362 |
-
|
1363 |
-
self._limitChange = function(value) {
|
1364 |
-
self._notificationIsPending = ignoreBeforeChange = true;
|
1365 |
-
pendingValue = value;
|
1366 |
-
finish();
|
1367 |
-
};
|
1368 |
-
self._limitBeforeChange = function(value) {
|
1369 |
-
if (!ignoreBeforeChange) {
|
1370 |
-
previousValue = value;
|
1371 |
-
self._origNotifySubscribers(value, beforeChange);
|
1372 |
-
}
|
1373 |
-
};
|
1374 |
-
},
|
1375 |
-
|
1376 |
-
hasSubscriptionsForEvent: function(event) {
|
1377 |
-
return this._subscriptions[event] && this._subscriptions[event].length;
|
1378 |
-
},
|
1379 |
-
|
1380 |
-
getSubscriptionsCount: function (event) {
|
1381 |
-
if (event) {
|
1382 |
-
return this._subscriptions[event] && this._subscriptions[event].length || 0;
|
1383 |
-
} else {
|
1384 |
-
var total = 0;
|
1385 |
-
ko.utils.objectForEach(this._subscriptions, function(eventName, subscriptions) {
|
1386 |
-
if (eventName !== 'dirty')
|
1387 |
-
total += subscriptions.length;
|
1388 |
-
});
|
1389 |
-
return total;
|
1390 |
-
}
|
1391 |
-
},
|
1392 |
-
|
1393 |
-
isDifferent: function(oldValue, newValue) {
|
1394 |
-
return !this['equalityComparer'] || !this['equalityComparer'](oldValue, newValue);
|
1395 |
-
},
|
1396 |
-
|
1397 |
-
extend: applyExtenders
|
1398 |
-
};
|
1399 |
-
|
1400 |
-
ko.exportProperty(ko_subscribable_fn, 'subscribe', ko_subscribable_fn.subscribe);
|
1401 |
-
ko.exportProperty(ko_subscribable_fn, 'extend', ko_subscribable_fn.extend);
|
1402 |
-
ko.exportProperty(ko_subscribable_fn, 'getSubscriptionsCount', ko_subscribable_fn.getSubscriptionsCount);
|
1403 |
-
|
1404 |
-
// For browsers that support proto assignment, we overwrite the prototype of each
|
1405 |
-
// observable instance. Since observables are functions, we need Function.prototype
|
1406 |
-
// to still be in the prototype chain.
|
1407 |
-
if (ko.utils.canSetPrototype) {
|
1408 |
-
ko.utils.setPrototypeOf(ko_subscribable_fn, Function.prototype);
|
1409 |
-
}
|
1410 |
-
|
1411 |
-
ko.subscribable['fn'] = ko_subscribable_fn;
|
1412 |
-
|
1413 |
-
|
1414 |
-
ko.isSubscribable = function (instance) {
|
1415 |
-
return instance != null && typeof instance.subscribe == "function" && typeof instance["notifySubscribers"] == "function";
|
1416 |
-
};
|
1417 |
-
|
1418 |
-
ko.exportSymbol('subscribable', ko.subscribable);
|
1419 |
-
ko.exportSymbol('isSubscribable', ko.isSubscribable);
|
1420 |
-
|
1421 |
-
ko.computedContext = ko.dependencyDetection = (function () {
|
1422 |
-
var outerFrames = [],
|
1423 |
-
currentFrame,
|
1424 |
-
lastId = 0;
|
1425 |
-
|
1426 |
-
// Return a unique ID that can be assigned to an observable for dependency tracking.
|
1427 |
-
// Theoretically, you could eventually overflow the number storage size, resulting
|
1428 |
-
// in duplicate IDs. But in JavaScript, the largest exact integral value is 2^53
|
1429 |
-
// or 9,007,199,254,740,992. If you created 1,000,000 IDs per second, it would
|
1430 |
-
// take over 285 years to reach that number.
|
1431 |
-
// Reference http://blog.vjeux.com/2010/javascript/javascript-max_int-number-limits.html
|
1432 |
-
function getId() {
|
1433 |
-
return ++lastId;
|
1434 |
-
}
|
1435 |
-
|
1436 |
-
function begin(options) {
|
1437 |
-
outerFrames.push(currentFrame);
|
1438 |
-
currentFrame = options;
|
1439 |
-
}
|
1440 |
-
|
1441 |
-
function end() {
|
1442 |
-
currentFrame = outerFrames.pop();
|
1443 |
-
}
|
1444 |
-
|
1445 |
-
return {
|
1446 |
-
begin: begin,
|
1447 |
-
|
1448 |
-
end: end,
|
1449 |
-
|
1450 |
-
registerDependency: function (subscribable) {
|
1451 |
-
if (currentFrame) {
|
1452 |
-
if (!ko.isSubscribable(subscribable))
|
1453 |
-
throw new Error("Only subscribable things can act as dependencies");
|
1454 |
-
currentFrame.callback.call(currentFrame.callbackTarget, subscribable, subscribable._id || (subscribable._id = getId()));
|
1455 |
-
}
|
1456 |
-
},
|
1457 |
-
|
1458 |
-
ignore: function (callback, callbackTarget, callbackArgs) {
|
1459 |
-
try {
|
1460 |
-
begin();
|
1461 |
-
return callback.apply(callbackTarget, callbackArgs || []);
|
1462 |
-
} finally {
|
1463 |
-
end();
|
1464 |
-
}
|
1465 |
-
},
|
1466 |
-
|
1467 |
-
getDependenciesCount: function () {
|
1468 |
-
if (currentFrame)
|
1469 |
-
return currentFrame.computed.getDependenciesCount();
|
1470 |
-
},
|
1471 |
-
|
1472 |
-
isInitial: function() {
|
1473 |
-
if (currentFrame)
|
1474 |
-
return currentFrame.isInitial;
|
1475 |
-
}
|
1476 |
-
};
|
1477 |
-
})();
|
1478 |
-
|
1479 |
-
ko.exportSymbol('computedContext', ko.computedContext);
|
1480 |
-
ko.exportSymbol('computedContext.getDependenciesCount', ko.computedContext.getDependenciesCount);
|
1481 |
-
ko.exportSymbol('computedContext.isInitial', ko.computedContext.isInitial);
|
1482 |
-
|
1483 |
-
ko.exportSymbol('ignoreDependencies', ko.ignoreDependencies = ko.dependencyDetection.ignore);
|
1484 |
-
var observableLatestValue = ko.utils.createSymbolOrString('_latestValue');
|
1485 |
-
|
1486 |
-
ko.observable = function (initialValue) {
|
1487 |
-
function observable() {
|
1488 |
-
if (arguments.length > 0) {
|
1489 |
-
// Write
|
1490 |
-
|
1491 |
-
// Ignore writes if the value hasn't changed
|
1492 |
-
if (observable.isDifferent(observable[observableLatestValue], arguments[0])) {
|
1493 |
-
observable.valueWillMutate();
|
1494 |
-
observable[observableLatestValue] = arguments[0];
|
1495 |
-
observable.valueHasMutated();
|
1496 |
-
}
|
1497 |
-
return this; // Permits chained assignments
|
1498 |
-
}
|
1499 |
-
else {
|
1500 |
-
// Read
|
1501 |
-
ko.dependencyDetection.registerDependency(observable); // The caller only needs to be notified of changes if they did a "read" operation
|
1502 |
-
return observable[observableLatestValue];
|
1503 |
-
}
|
1504 |
-
}
|
1505 |
-
|
1506 |
-
observable[observableLatestValue] = initialValue;
|
1507 |
-
|
1508 |
-
// Inherit from 'subscribable'
|
1509 |
-
if (!ko.utils.canSetPrototype) {
|
1510 |
-
// 'subscribable' won't be on the prototype chain unless we put it there directly
|
1511 |
-
ko.utils.extend(observable, ko.subscribable['fn']);
|
1512 |
-
}
|
1513 |
-
ko.subscribable['fn'].init(observable);
|
1514 |
-
|
1515 |
-
// Inherit from 'observable'
|
1516 |
-
ko.utils.setPrototypeOfOrExtend(observable, observableFn);
|
1517 |
-
|
1518 |
-
if (ko.options['deferUpdates']) {
|
1519 |
-
ko.extenders['deferred'](observable, true);
|
1520 |
-
}
|
1521 |
-
|
1522 |
-
return observable;
|
1523 |
-
}
|
1524 |
-
|
1525 |
-
// Define prototype for observables
|
1526 |
-
var observableFn = {
|
1527 |
-
'equalityComparer': valuesArePrimitiveAndEqual,
|
1528 |
-
peek: function() { return this[observableLatestValue]; },
|
1529 |
-
valueHasMutated: function () { this['notifySubscribers'](this[observableLatestValue]); },
|
1530 |
-
valueWillMutate: function () { this['notifySubscribers'](this[observableLatestValue], 'beforeChange'); }
|
1531 |
-
};
|
1532 |
-
|
1533 |
-
// Note that for browsers that don't support proto assignment, the
|
1534 |
-
// inheritance chain is created manually in the ko.observable constructor
|
1535 |
-
if (ko.utils.canSetPrototype) {
|
1536 |
-
ko.utils.setPrototypeOf(observableFn, ko.subscribable['fn']);
|
1537 |
-
}
|
1538 |
-
|
1539 |
-
var protoProperty = ko.observable.protoProperty = '__ko_proto__';
|
1540 |
-
observableFn[protoProperty] = ko.observable;
|
1541 |
-
|
1542 |
-
ko.hasPrototype = function(instance, prototype) {
|
1543 |
-
if ((instance === null) || (instance === undefined) || (instance[protoProperty] === undefined)) return false;
|
1544 |
-
if (instance[protoProperty] === prototype) return true;
|
1545 |
-
return ko.hasPrototype(instance[protoProperty], prototype); // Walk the prototype chain
|
1546 |
-
};
|
1547 |
-
|
1548 |
-
ko.isObservable = function (instance) {
|
1549 |
-
return ko.hasPrototype(instance, ko.observable);
|
1550 |
-
}
|
1551 |
-
ko.isWriteableObservable = function (instance) {
|
1552 |
-
// Observable
|
1553 |
-
if ((typeof instance == 'function') && instance[protoProperty] === ko.observable)
|
1554 |
-
return true;
|
1555 |
-
// Writeable dependent observable
|
1556 |
-
if ((typeof instance == 'function') && (instance[protoProperty] === ko.dependentObservable) && (instance.hasWriteFunction))
|
1557 |
-
return true;
|
1558 |
-
// Anything else
|
1559 |
-
return false;
|
1560 |
-
}
|
1561 |
-
|
1562 |
-
ko.exportSymbol('observable', ko.observable);
|
1563 |
-
ko.exportSymbol('isObservable', ko.isObservable);
|
1564 |
-
ko.exportSymbol('isWriteableObservable', ko.isWriteableObservable);
|
1565 |
-
ko.exportSymbol('isWritableObservable', ko.isWriteableObservable);
|
1566 |
-
ko.exportSymbol('observable.fn', observableFn);
|
1567 |
-
ko.exportProperty(observableFn, 'peek', observableFn.peek);
|
1568 |
-
ko.exportProperty(observableFn, 'valueHasMutated', observableFn.valueHasMutated);
|
1569 |
-
ko.exportProperty(observableFn, 'valueWillMutate', observableFn.valueWillMutate);
|
1570 |
-
ko.observableArray = function (initialValues) {
|
1571 |
-
initialValues = initialValues || [];
|
1572 |
-
|
1573 |
-
if (typeof initialValues != 'object' || !('length' in initialValues))
|
1574 |
-
throw new Error("The argument passed when initializing an observable array must be an array, or null, or undefined.");
|
1575 |
-
|
1576 |
-
var result = ko.observable(initialValues);
|
1577 |
-
ko.utils.setPrototypeOfOrExtend(result, ko.observableArray['fn']);
|
1578 |
-
return result.extend({'trackArrayChanges':true});
|
1579 |
-
};
|
1580 |
-
|
1581 |
-
ko.observableArray['fn'] = {
|
1582 |
-
'remove': function (valueOrPredicate) {
|
1583 |
-
var underlyingArray = this.peek();
|
1584 |
-
var removedValues = [];
|
1585 |
-
var predicate = typeof valueOrPredicate == "function" && !ko.isObservable(valueOrPredicate) ? valueOrPredicate : function (value) { return value === valueOrPredicate; };
|
1586 |
-
for (var i = 0; i < underlyingArray.length; i++) {
|
1587 |
-
var value = underlyingArray[i];
|
1588 |
-
if (predicate(value)) {
|
1589 |
-
if (removedValues.length === 0) {
|
1590 |
-
this.valueWillMutate();
|
1591 |
-
}
|
1592 |
-
removedValues.push(value);
|
1593 |
-
underlyingArray.splice(i, 1);
|
1594 |
-
i--;
|
1595 |
-
}
|
1596 |
-
}
|
1597 |
-
if (removedValues.length) {
|
1598 |
-
this.valueHasMutated();
|
1599 |
-
}
|
1600 |
-
return removedValues;
|
1601 |
-
},
|
1602 |
-
|
1603 |
-
'removeAll': function (arrayOfValues) {
|
1604 |
-
// If you passed zero args, we remove everything
|
1605 |
-
if (arrayOfValues === undefined) {
|
1606 |
-
var underlyingArray = this.peek();
|
1607 |
-
var allValues = underlyingArray.slice(0);
|
1608 |
-
this.valueWillMutate();
|
1609 |
-
underlyingArray.splice(0, underlyingArray.length);
|
1610 |
-
this.valueHasMutated();
|
1611 |
-
return allValues;
|
1612 |
-
}
|
1613 |
-
// If you passed an arg, we interpret it as an array of entries to remove
|
1614 |
-
if (!arrayOfValues)
|
1615 |
-
return [];
|
1616 |
-
return this['remove'](function (value) {
|
1617 |
-
return ko.utils.arrayIndexOf(arrayOfValues, value) >= 0;
|
1618 |
-
});
|
1619 |
-
},
|
1620 |
-
|
1621 |
-
'destroy': function (valueOrPredicate) {
|
1622 |
-
var underlyingArray = this.peek();
|
1623 |
-
var predicate = typeof valueOrPredicate == "function" && !ko.isObservable(valueOrPredicate) ? valueOrPredicate : function (value) { return value === valueOrPredicate; };
|
1624 |
-
this.valueWillMutate();
|
1625 |
-
for (var i = underlyingArray.length - 1; i >= 0; i--) {
|
1626 |
-
var value = underlyingArray[i];
|
1627 |
-
if (predicate(value))
|
1628 |
-
underlyingArray[i]["_destroy"] = true;
|
1629 |
-
}
|
1630 |
-
this.valueHasMutated();
|
1631 |
-
},
|
1632 |
-
|
1633 |
-
'destroyAll': function (arrayOfValues) {
|
1634 |
-
// If you passed zero args, we destroy everything
|
1635 |
-
if (arrayOfValues === undefined)
|
1636 |
-
return this['destroy'](function() { return true });
|
1637 |
-
|
1638 |
-
// If you passed an arg, we interpret it as an array of entries to destroy
|
1639 |
-
if (!arrayOfValues)
|
1640 |
-
return [];
|
1641 |
-
return this['destroy'](function (value) {
|
1642 |
-
return ko.utils.arrayIndexOf(arrayOfValues, value) >= 0;
|
1643 |
-
});
|
1644 |
-
},
|
1645 |
-
|
1646 |
-
'indexOf': function (item) {
|
1647 |
-
var underlyingArray = this();
|
1648 |
-
return ko.utils.arrayIndexOf(underlyingArray, item);
|
1649 |
-
},
|
1650 |
-
|
1651 |
-
'replace': function(oldItem, newItem) {
|
1652 |
-
var index = this['indexOf'](oldItem);
|
1653 |
-
if (index >= 0) {
|
1654 |
-
this.valueWillMutate();
|
1655 |
-
this.peek()[index] = newItem;
|
1656 |
-
this.valueHasMutated();
|
1657 |
-
}
|
1658 |
-
}
|
1659 |
-
};
|
1660 |
-
|
1661 |
-
// Note that for browsers that don't support proto assignment, the
|
1662 |
-
// inheritance chain is created manually in the ko.observableArray constructor
|
1663 |
-
if (ko.utils.canSetPrototype) {
|
1664 |
-
ko.utils.setPrototypeOf(ko.observableArray['fn'], ko.observable['fn']);
|
1665 |
-
}
|
1666 |
-
|
1667 |
-
// Populate ko.observableArray.fn with read/write functions from native arrays
|
1668 |
-
// Important: Do not add any additional functions here that may reasonably be used to *read* data from the array
|
1669 |
-
// because we'll eval them without causing subscriptions, so ko.computed output could end up getting stale
|
1670 |
-
ko.utils.arrayForEach(["pop", "push", "reverse", "shift", "sort", "splice", "unshift"], function (methodName) {
|
1671 |
-
ko.observableArray['fn'][methodName] = function () {
|
1672 |
-
// Use "peek" to avoid creating a subscription in any computed that we're executing in the context of
|
1673 |
-
// (for consistency with mutating regular observables)
|
1674 |
-
var underlyingArray = this.peek();
|
1675 |
-
this.valueWillMutate();
|
1676 |
-
this.cacheDiffForKnownOperation(underlyingArray, methodName, arguments);
|
1677 |
-
var methodCallResult = underlyingArray[methodName].apply(underlyingArray, arguments);
|
1678 |
-
this.valueHasMutated();
|
1679 |
-
// The native sort and reverse methods return a reference to the array, but it makes more sense to return the observable array instead.
|
1680 |
-
return methodCallResult === underlyingArray ? this : methodCallResult;
|
1681 |
-
};
|
1682 |
-
});
|
1683 |
-
|
1684 |
-
// Populate ko.observableArray.fn with read-only functions from native arrays
|
1685 |
-
ko.utils.arrayForEach(["slice"], function (methodName) {
|
1686 |
-
ko.observableArray['fn'][methodName] = function () {
|
1687 |
-
var underlyingArray = this();
|
1688 |
-
return underlyingArray[methodName].apply(underlyingArray, arguments);
|
1689 |
-
};
|
1690 |
-
});
|
1691 |
-
|
1692 |
-
ko.exportSymbol('observableArray', ko.observableArray);
|
1693 |
-
var arrayChangeEventName = 'arrayChange';
|
1694 |
-
ko.extenders['trackArrayChanges'] = function(target, options) {
|
1695 |
-
// Use the provided options--each call to trackArrayChanges overwrites the previously set options
|
1696 |
-
target.compareArrayOptions = {};
|
1697 |
-
if (options && typeof options == "object") {
|
1698 |
-
ko.utils.extend(target.compareArrayOptions, options);
|
1699 |
-
}
|
1700 |
-
target.compareArrayOptions['sparse'] = true;
|
1701 |
-
|
1702 |
-
// Only modify the target observable once
|
1703 |
-
if (target.cacheDiffForKnownOperation) {
|
1704 |
-
return;
|
1705 |
-
}
|
1706 |
-
var trackingChanges = false,
|
1707 |
-
cachedDiff = null,
|
1708 |
-
arrayChangeSubscription,
|
1709 |
-
pendingNotifications = 0,
|
1710 |
-
underlyingBeforeSubscriptionAddFunction = target.beforeSubscriptionAdd,
|
1711 |
-
underlyingAfterSubscriptionRemoveFunction = target.afterSubscriptionRemove;
|
1712 |
-
|
1713 |
-
// Watch "subscribe" calls, and for array change events, ensure change tracking is enabled
|
1714 |
-
target.beforeSubscriptionAdd = function (event) {
|
1715 |
-
if (underlyingBeforeSubscriptionAddFunction)
|
1716 |
-
underlyingBeforeSubscriptionAddFunction.call(target, event);
|
1717 |
-
if (event === arrayChangeEventName) {
|
1718 |
-
trackChanges();
|
1719 |
-
}
|
1720 |
-
};
|
1721 |
-
// Watch "dispose" calls, and for array change events, ensure change tracking is disabled when all are disposed
|
1722 |
-
target.afterSubscriptionRemove = function (event) {
|
1723 |
-
if (underlyingAfterSubscriptionRemoveFunction)
|
1724 |
-
underlyingAfterSubscriptionRemoveFunction.call(target, event);
|
1725 |
-
if (event === arrayChangeEventName && !target.hasSubscriptionsForEvent(arrayChangeEventName)) {
|
1726 |
-
arrayChangeSubscription.dispose();
|
1727 |
-
trackingChanges = false;
|
1728 |
-
}
|
1729 |
-
};
|
1730 |
-
|
1731 |
-
function trackChanges() {
|
1732 |
-
// Calling 'trackChanges' multiple times is the same as calling it once
|
1733 |
-
if (trackingChanges) {
|
1734 |
-
return;
|
1735 |
-
}
|
1736 |
-
|
1737 |
-
trackingChanges = true;
|
1738 |
-
|
1739 |
-
// Intercept "notifySubscribers" to track how many times it was called.
|
1740 |
-
var underlyingNotifySubscribersFunction = target['notifySubscribers'];
|
1741 |
-
target['notifySubscribers'] = function(valueToNotify, event) {
|
1742 |
-
if (!event || event === defaultEvent) {
|
1743 |
-
++pendingNotifications;
|
1744 |
-
}
|
1745 |
-
return underlyingNotifySubscribersFunction.apply(this, arguments);
|
1746 |
-
};
|
1747 |
-
|
1748 |
-
// Each time the array changes value, capture a clone so that on the next
|
1749 |
-
// change it's possible to produce a diff
|
1750 |
-
var previousContents = [].concat(target.peek() || []);
|
1751 |
-
cachedDiff = null;
|
1752 |
-
arrayChangeSubscription = target.subscribe(function(currentContents) {
|
1753 |
-
// Make a copy of the current contents and ensure it's an array
|
1754 |
-
currentContents = [].concat(currentContents || []);
|
1755 |
-
|
1756 |
-
// Compute the diff and issue notifications, but only if someone is listening
|
1757 |
-
if (target.hasSubscriptionsForEvent(arrayChangeEventName)) {
|
1758 |
-
var changes = getChanges(previousContents, currentContents);
|
1759 |
-
}
|
1760 |
-
|
1761 |
-
// Eliminate references to the old, removed items, so they can be GCed
|
1762 |
-
previousContents = currentContents;
|
1763 |
-
cachedDiff = null;
|
1764 |
-
pendingNotifications = 0;
|
1765 |
-
|
1766 |
-
if (changes && changes.length) {
|
1767 |
-
target['notifySubscribers'](changes, arrayChangeEventName);
|
1768 |
-
}
|
1769 |
-
});
|
1770 |
-
}
|
1771 |
-
|
1772 |
-
function getChanges(previousContents, currentContents) {
|
1773 |
-
// We try to re-use cached diffs.
|
1774 |
-
// The scenarios where pendingNotifications > 1 are when using rate-limiting or the Deferred Updates
|
1775 |
-
// plugin, which without this check would not be compatible with arrayChange notifications. Normally,
|
1776 |
-
// notifications are issued immediately so we wouldn't be queueing up more than one.
|
1777 |
-
if (!cachedDiff || pendingNotifications > 1) {
|
1778 |
-
cachedDiff = ko.utils.compareArrays(previousContents, currentContents, target.compareArrayOptions);
|
1779 |
-
}
|
1780 |
-
|
1781 |
-
return cachedDiff;
|
1782 |
-
}
|
1783 |
-
|
1784 |
-
target.cacheDiffForKnownOperation = function(rawArray, operationName, args) {
|
1785 |
-
// Only run if we're currently tracking changes for this observable array
|
1786 |
-
// and there aren't any pending deferred notifications.
|
1787 |
-
if (!trackingChanges || pendingNotifications) {
|
1788 |
-
return;
|
1789 |
-
}
|
1790 |
-
var diff = [],
|
1791 |
-
arrayLength = rawArray.length,
|
1792 |
-
argsLength = args.length,
|
1793 |
-
offset = 0;
|
1794 |
-
|
1795 |
-
function pushDiff(status, value, index) {
|
1796 |
-
return diff[diff.length] = { 'status': status, 'value': value, 'index': index };
|
1797 |
-
}
|
1798 |
-
switch (operationName) {
|
1799 |
-
case 'push':
|
1800 |
-
offset = arrayLength;
|
1801 |
-
case 'unshift':
|
1802 |
-
for (var index = 0; index < argsLength; index++) {
|
1803 |
-
pushDiff('added', args[index], offset + index);
|
1804 |
-
}
|
1805 |
-
break;
|
1806 |
-
|
1807 |
-
case 'pop':
|
1808 |
-
offset = arrayLength - 1;
|
1809 |
-
case 'shift':
|
1810 |
-
if (arrayLength) {
|
1811 |
-
pushDiff('deleted', rawArray[offset], offset);
|
1812 |
-
}
|
1813 |
-
break;
|
1814 |
-
|
1815 |
-
case 'splice':
|
1816 |
-
// Negative start index means 'from end of array'. After that we clamp to [0...arrayLength].
|
1817 |
-
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice
|
1818 |
-
var startIndex = Math.min(Math.max(0, args[0] < 0 ? arrayLength + args[0] : args[0]), arrayLength),
|
1819 |
-
endDeleteIndex = argsLength === 1 ? arrayLength : Math.min(startIndex + (args[1] || 0), arrayLength),
|
1820 |
-
endAddIndex = startIndex + argsLength - 2,
|
1821 |
-
endIndex = Math.max(endDeleteIndex, endAddIndex),
|
1822 |
-
additions = [], deletions = [];
|
1823 |
-
for (var index = startIndex, argsIndex = 2; index < endIndex; ++index, ++argsIndex) {
|
1824 |
-
if (index < endDeleteIndex)
|
1825 |
-
deletions.push(pushDiff('deleted', rawArray[index], index));
|
1826 |
-
if (index < endAddIndex)
|
1827 |
-
additions.push(pushDiff('added', args[argsIndex], index));
|
1828 |
-
}
|
1829 |
-
ko.utils.findMovesInArrayComparison(deletions, additions);
|
1830 |
-
break;
|
1831 |
-
|
1832 |
-
default:
|
1833 |
-
return;
|
1834 |
-
}
|
1835 |
-
cachedDiff = diff;
|
1836 |
-
};
|
1837 |
-
};
|
1838 |
-
var computedState = ko.utils.createSymbolOrString('_state');
|
1839 |
-
|
1840 |
-
ko.computed = ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunctionTarget, options) {
|
1841 |
-
if (typeof evaluatorFunctionOrOptions === "object") {
|
1842 |
-
// Single-parameter syntax - everything is on this "options" param
|
1843 |
-
options = evaluatorFunctionOrOptions;
|
1844 |
-
} else {
|
1845 |
-
// Multi-parameter syntax - construct the options according to the params passed
|
1846 |
-
options = options || {};
|
1847 |
-
if (evaluatorFunctionOrOptions) {
|
1848 |
-
options["read"] = evaluatorFunctionOrOptions;
|
1849 |
-
}
|
1850 |
-
}
|
1851 |
-
if (typeof options["read"] != "function")
|
1852 |
-
throw Error("Pass a function that returns the value of the ko.computed");
|
1853 |
-
|
1854 |
-
var writeFunction = options["write"];
|
1855 |
-
var state = {
|
1856 |
-
latestValue: undefined,
|
1857 |
-
isStale: true,
|
1858 |
-
isBeingEvaluated: false,
|
1859 |
-
suppressDisposalUntilDisposeWhenReturnsFalse: false,
|
1860 |
-
isDisposed: false,
|
1861 |
-
pure: false,
|
1862 |
-
isSleeping: false,
|
1863 |
-
readFunction: options["read"],
|
1864 |
-
evaluatorFunctionTarget: evaluatorFunctionTarget || options["owner"],
|
1865 |
-
disposeWhenNodeIsRemoved: options["disposeWhenNodeIsRemoved"] || options.disposeWhenNodeIsRemoved || null,
|
1866 |
-
disposeWhen: options["disposeWhen"] || options.disposeWhen,
|
1867 |
-
domNodeDisposalCallback: null,
|
1868 |
-
dependencyTracking: {},
|
1869 |
-
dependenciesCount: 0,
|
1870 |
-
evaluationTimeoutInstance: null
|
1871 |
-
};
|
1872 |
-
|
1873 |
-
function computedObservable() {
|
1874 |
-
if (arguments.length > 0) {
|
1875 |
-
if (typeof writeFunction === "function") {
|
1876 |
-
// Writing a value
|
1877 |
-
writeFunction.apply(state.evaluatorFunctionTarget, arguments);
|
1878 |
-
} else {
|
1879 |
-
throw new Error("Cannot write a value to a ko.computed unless you specify a 'write' option. If you wish to read the current value, don't pass any parameters.");
|
1880 |
-
}
|
1881 |
-
return this; // Permits chained assignments
|
1882 |
-
} else {
|
1883 |
-
// Reading the value
|
1884 |
-
ko.dependencyDetection.registerDependency(computedObservable);
|
1885 |
-
if (state.isStale || (state.isSleeping && computedObservable.haveDependenciesChanged())) {
|
1886 |
-
computedObservable.evaluateImmediate();
|
1887 |
-
}
|
1888 |
-
return state.latestValue;
|
1889 |
-
}
|
1890 |
-
}
|
1891 |
-
|
1892 |
-
computedObservable[computedState] = state;
|
1893 |
-
computedObservable.hasWriteFunction = typeof writeFunction === "function";
|
1894 |
-
|
1895 |
-
// Inherit from 'subscribable'
|
1896 |
-
if (!ko.utils.canSetPrototype) {
|
1897 |
-
// 'subscribable' won't be on the prototype chain unless we put it there directly
|
1898 |
-
ko.utils.extend(computedObservable, ko.subscribable['fn']);
|
1899 |
-
}
|
1900 |
-
ko.subscribable['fn'].init(computedObservable);
|
1901 |
-
|
1902 |
-
// Inherit from 'computed'
|
1903 |
-
ko.utils.setPrototypeOfOrExtend(computedObservable, computedFn);
|
1904 |
-
|
1905 |
-
if (options['pure']) {
|
1906 |
-
state.pure = true;
|
1907 |
-
state.isSleeping = true; // Starts off sleeping; will awake on the first subscription
|
1908 |
-
ko.utils.extend(computedObservable, pureComputedOverrides);
|
1909 |
-
} else if (options['deferEvaluation']) {
|
1910 |
-
ko.utils.extend(computedObservable, deferEvaluationOverrides);
|
1911 |
-
}
|
1912 |
-
|
1913 |
-
if (ko.options['deferUpdates']) {
|
1914 |
-
ko.extenders['deferred'](computedObservable, true);
|
1915 |
-
}
|
1916 |
-
|
1917 |
-
if (DEBUG) {
|
1918 |
-
// #1731 - Aid debugging by exposing the computed's options
|
1919 |
-
computedObservable["_options"] = options;
|
1920 |
-
}
|
1921 |
-
|
1922 |
-
if (state.disposeWhenNodeIsRemoved) {
|
1923 |
-
// Since this computed is associated with a DOM node, and we don't want to dispose the computed
|
1924 |
-
// until the DOM node is *removed* from the document (as opposed to never having been in the document),
|
1925 |
-
// we'll prevent disposal until "disposeWhen" first returns false.
|
1926 |
-
state.suppressDisposalUntilDisposeWhenReturnsFalse = true;
|
1927 |
-
|
1928 |
-
// disposeWhenNodeIsRemoved: true can be used to opt into the "only dispose after first false result"
|
1929 |
-
// behaviour even if there's no specific node to watch. In that case, clear the option so we don't try
|
1930 |
-
// to watch for a non-node's disposal. This technique is intended for KO's internal use only and shouldn't
|
1931 |
-
// be documented or used by application code, as it's likely to change in a future version of KO.
|
1932 |
-
if (!state.disposeWhenNodeIsRemoved.nodeType) {
|
1933 |
-
state.disposeWhenNodeIsRemoved = null;
|
1934 |
-
}
|
1935 |
-
}
|
1936 |
-
|
1937 |
-
// Evaluate, unless sleeping or deferEvaluation is true
|
1938 |
-
if (!state.isSleeping && !options['deferEvaluation']) {
|
1939 |
-
computedObservable.evaluateImmediate();
|
1940 |
-
}
|
1941 |
-
|
1942 |
-
// Attach a DOM node disposal callback so that the computed will be proactively disposed as soon as the node is
|
1943 |
-
// removed using ko.removeNode. But skip if isActive is false (there will never be any dependencies to dispose).
|
1944 |
-
if (state.disposeWhenNodeIsRemoved && computedObservable.isActive()) {
|
1945 |
-
ko.utils.domNodeDisposal.addDisposeCallback(state.disposeWhenNodeIsRemoved, state.domNodeDisposalCallback = function () {
|
1946 |
-
computedObservable.dispose();
|
1947 |
-
});
|
1948 |
-
}
|
1949 |
-
|
1950 |
-
return computedObservable;
|
1951 |
-
};
|
1952 |
-
|
1953 |
-
// Utility function that disposes a given dependencyTracking entry
|
1954 |
-
function computedDisposeDependencyCallback(id, entryToDispose) {
|
1955 |
-
if (entryToDispose !== null && entryToDispose.dispose) {
|
1956 |
-
entryToDispose.dispose();
|
1957 |
-
}
|
1958 |
-
}
|
1959 |
-
|
1960 |
-
// This function gets called each time a dependency is detected while evaluating a computed.
|
1961 |
-
// It's factored out as a shared function to avoid creating unnecessary function instances during evaluation.
|
1962 |
-
function computedBeginDependencyDetectionCallback(subscribable, id) {
|
1963 |
-
var computedObservable = this.computedObservable,
|
1964 |
-
state = computedObservable[computedState];
|
1965 |
-
if (!state.isDisposed) {
|
1966 |
-
if (this.disposalCount && this.disposalCandidates[id]) {
|
1967 |
-
// Don't want to dispose this subscription, as it's still being used
|
1968 |
-
computedObservable.addDependencyTracking(id, subscribable, this.disposalCandidates[id]);
|
1969 |
-
this.disposalCandidates[id] = null; // No need to actually delete the property - disposalCandidates is a transient object anyway
|
1970 |
-
--this.disposalCount;
|
1971 |
-
} else if (!state.dependencyTracking[id]) {
|
1972 |
-
// Brand new subscription - add it
|
1973 |
-
computedObservable.addDependencyTracking(id, subscribable, state.isSleeping ? { _target: subscribable } : computedObservable.subscribeToDependency(subscribable));
|
1974 |
-
}
|
1975 |
-
}
|
1976 |
-
}
|
1977 |
-
|
1978 |
-
var computedFn = {
|
1979 |
-
"equalityComparer": valuesArePrimitiveAndEqual,
|
1980 |
-
getDependenciesCount: function () {
|
1981 |
-
return this[computedState].dependenciesCount;
|
1982 |
-
},
|
1983 |
-
addDependencyTracking: function (id, target, trackingObj) {
|
1984 |
-
if (this[computedState].pure && target === this) {
|
1985 |
-
throw Error("A 'pure' computed must not be called recursively");
|
1986 |
-
}
|
1987 |
-
|
1988 |
-
this[computedState].dependencyTracking[id] = trackingObj;
|
1989 |
-
trackingObj._order = this[computedState].dependenciesCount++;
|
1990 |
-
trackingObj._version = target.getVersion();
|
1991 |
-
},
|
1992 |
-
haveDependenciesChanged: function () {
|
1993 |
-
var id, dependency, dependencyTracking = this[computedState].dependencyTracking;
|
1994 |
-
for (id in dependencyTracking) {
|
1995 |
-
if (dependencyTracking.hasOwnProperty(id)) {
|
1996 |
-
dependency = dependencyTracking[id];
|
1997 |
-
if (dependency._target.hasChanged(dependency._version)) {
|
1998 |
-
return true;
|
1999 |
-
}
|
2000 |
-
}
|
2001 |
-
}
|
2002 |
-
},
|
2003 |
-
markDirty: function () {
|
2004 |
-
// Process "dirty" events if we can handle delayed notifications
|
2005 |
-
if (this._evalDelayed && !this[computedState].isBeingEvaluated) {
|
2006 |
-
this._evalDelayed();
|
2007 |
-
}
|
2008 |
-
},
|
2009 |
-
isActive: function () {
|
2010 |
-
return this[computedState].isStale || this[computedState].dependenciesCount > 0;
|
2011 |
-
},
|
2012 |
-
respondToChange: function () {
|
2013 |
-
// Ignore "change" events if we've already scheduled a delayed notification
|
2014 |
-
if (!this._notificationIsPending) {
|
2015 |
-
this.evaluatePossiblyAsync();
|
2016 |
-
}
|
2017 |
-
},
|
2018 |
-
subscribeToDependency: function (target) {
|
2019 |
-
if (target._deferUpdates && !this[computedState].disposeWhenNodeIsRemoved) {
|
2020 |
-
var dirtySub = target.subscribe(this.markDirty, this, 'dirty'),
|
2021 |
-
changeSub = target.subscribe(this.respondToChange, this);
|
2022 |
-
return {
|
2023 |
-
_target: target,
|
2024 |
-
dispose: function () {
|
2025 |
-
dirtySub.dispose();
|
2026 |
-
changeSub.dispose();
|
2027 |
-
}
|
2028 |
-
};
|
2029 |
-
} else {
|
2030 |
-
return target.subscribe(this.evaluatePossiblyAsync, this);
|
2031 |
-
}
|
2032 |
-
},
|
2033 |
-
evaluatePossiblyAsync: function () {
|
2034 |
-
var computedObservable = this,
|
2035 |
-
throttleEvaluationTimeout = computedObservable['throttleEvaluation'];
|
2036 |
-
if (throttleEvaluationTimeout && throttleEvaluationTimeout >= 0) {
|
2037 |
-
clearTimeout(this[computedState].evaluationTimeoutInstance);
|
2038 |
-
this[computedState].evaluationTimeoutInstance = ko.utils.setTimeout(function () {
|
2039 |
-
computedObservable.evaluateImmediate(true /*notifyChange*/);
|
2040 |
-
}, throttleEvaluationTimeout);
|
2041 |
-
} else if (computedObservable._evalDelayed) {
|
2042 |
-
computedObservable._evalDelayed();
|
2043 |
-
} else {
|
2044 |
-
computedObservable.evaluateImmediate(true /*notifyChange*/);
|
2045 |
-
}
|
2046 |
-
},
|
2047 |
-
evaluateImmediate: function (notifyChange) {
|
2048 |
-
var computedObservable = this,
|
2049 |
-
state = computedObservable[computedState],
|
2050 |
-
disposeWhen = state.disposeWhen;
|
2051 |
-
|
2052 |
-
if (state.isBeingEvaluated) {
|
2053 |
-
// If the evaluation of a ko.computed causes side effects, it's possible that it will trigger its own re-evaluation.
|
2054 |
-
// This is not desirable (it's hard for a developer to realise a chain of dependencies might cause this, and they almost
|
2055 |
-
// certainly didn't intend infinite re-evaluations). So, for predictability, we simply prevent ko.computeds from causing
|
2056 |
-
// their own re-evaluation. Further discussion at https://github.com/SteveSanderson/knockout/pull/387
|
2057 |
-
return;
|
2058 |
-
}
|
2059 |
-
|
2060 |
-
// Do not evaluate (and possibly capture new dependencies) if disposed
|
2061 |
-
if (state.isDisposed) {
|
2062 |
-
return;
|
2063 |
-
}
|
2064 |
-
|
2065 |
-
if (state.disposeWhenNodeIsRemoved && !ko.utils.domNodeIsAttachedToDocument(state.disposeWhenNodeIsRemoved) || disposeWhen && disposeWhen()) {
|
2066 |
-
// See comment above about suppressDisposalUntilDisposeWhenReturnsFalse
|
2067 |
-
if (!state.suppressDisposalUntilDisposeWhenReturnsFalse) {
|
2068 |
-
computedObservable.dispose();
|
2069 |
-
return;
|
2070 |
-
}
|
2071 |
-
} else {
|
2072 |
-
// It just did return false, so we can stop suppressing now
|
2073 |
-
state.suppressDisposalUntilDisposeWhenReturnsFalse = false;
|
2074 |
-
}
|
2075 |
-
|
2076 |
-
state.isBeingEvaluated = true;
|
2077 |
-
try {
|
2078 |
-
this.evaluateImmediate_CallReadWithDependencyDetection(notifyChange);
|
2079 |
-
} finally {
|
2080 |
-
state.isBeingEvaluated = false;
|
2081 |
-
}
|
2082 |
-
|
2083 |
-
if (!state.dependenciesCount) {
|
2084 |
-
computedObservable.dispose();
|
2085 |
-
}
|
2086 |
-
},
|
2087 |
-
evaluateImmediate_CallReadWithDependencyDetection: function (notifyChange) {
|
2088 |
-
// This function is really just part of the evaluateImmediate logic. You would never call it from anywhere else.
|
2089 |
-
// Factoring it out into a separate function means it can be independent of the try/catch block in evaluateImmediate,
|
2090 |
-
// which contributes to saving about 40% off the CPU overhead of computed evaluation (on V8 at least).
|
2091 |
-
|
2092 |
-
var computedObservable = this,
|
2093 |
-
state = computedObservable[computedState];
|
2094 |
-
|
2095 |
-
// Initially, we assume that none of the subscriptions are still being used (i.e., all are candidates for disposal).
|
2096 |
-
// Then, during evaluation, we cross off any that are in fact still being used.
|
2097 |
-
var isInitial = state.pure ? undefined : !state.dependenciesCount, // If we're evaluating when there are no previous dependencies, it must be the first time
|
2098 |
-
dependencyDetectionContext = {
|
2099 |
-
computedObservable: computedObservable,
|
2100 |
-
disposalCandidates: state.dependencyTracking,
|
2101 |
-
disposalCount: state.dependenciesCount
|
2102 |
-
};
|
2103 |
-
|
2104 |
-
ko.dependencyDetection.begin({
|
2105 |
-
callbackTarget: dependencyDetectionContext,
|
2106 |
-
callback: computedBeginDependencyDetectionCallback,
|
2107 |
-
computed: computedObservable,
|
2108 |
-
isInitial: isInitial
|
2109 |
-
});
|
2110 |
-
|
2111 |
-
state.dependencyTracking = {};
|
2112 |
-
state.dependenciesCount = 0;
|
2113 |
-
|
2114 |
-
var newValue = this.evaluateImmediate_CallReadThenEndDependencyDetection(state, dependencyDetectionContext);
|
2115 |
-
|
2116 |
-
if (computedObservable.isDifferent(state.latestValue, newValue)) {
|
2117 |
-
if (!state.isSleeping) {
|
2118 |
-
computedObservable["notifySubscribers"](state.latestValue, "beforeChange");
|
2119 |
-
}
|
2120 |
-
|
2121 |
-
state.latestValue = newValue;
|
2122 |
-
|
2123 |
-
if (state.isSleeping) {
|
2124 |
-
computedObservable.updateVersion();
|
2125 |
-
} else if (notifyChange) {
|
2126 |
-
computedObservable["notifySubscribers"](state.latestValue);
|
2127 |
-
}
|
2128 |
-
}
|
2129 |
-
|
2130 |
-
if (isInitial) {
|
2131 |
-
computedObservable["notifySubscribers"](state.latestValue, "awake");
|
2132 |
-
}
|
2133 |
-
},
|
2134 |
-
evaluateImmediate_CallReadThenEndDependencyDetection: function (state, dependencyDetectionContext) {
|
2135 |
-
// This function is really part of the evaluateImmediate_CallReadWithDependencyDetection logic.
|
2136 |
-
// You'd never call it from anywhere else. Factoring it out means that evaluateImmediate_CallReadWithDependencyDetection
|
2137 |
-
// can be independent of try/finally blocks, which contributes to saving about 40% off the CPU
|
2138 |
-
// overhead of computed evaluation (on V8 at least).
|
2139 |
-
|
2140 |
-
try {
|
2141 |
-
var readFunction = state.readFunction;
|
2142 |
-
return state.evaluatorFunctionTarget ? readFunction.call(state.evaluatorFunctionTarget) : readFunction();
|
2143 |
-
} finally {
|
2144 |
-
ko.dependencyDetection.end();
|
2145 |
-
|
2146 |
-
// For each subscription no longer being used, remove it from the active subscriptions list and dispose it
|
2147 |
-
if (dependencyDetectionContext.disposalCount && !state.isSleeping) {
|
2148 |
-
ko.utils.objectForEach(dependencyDetectionContext.disposalCandidates, computedDisposeDependencyCallback);
|
2149 |
-
}
|
2150 |
-
|
2151 |
-
state.isStale = false;
|
2152 |
-
}
|
2153 |
-
},
|
2154 |
-
peek: function () {
|
2155 |
-
// Peek won't re-evaluate, except while the computed is sleeping or to get the initial value when "deferEvaluation" is set.
|
2156 |
-
var state = this[computedState];
|
2157 |
-
if ((state.isStale && !state.dependenciesCount) || (state.isSleeping && this.haveDependenciesChanged())) {
|
2158 |
-
this.evaluateImmediate();
|
2159 |
-
}
|
2160 |
-
return state.latestValue;
|
2161 |
-
},
|
2162 |
-
limit: function (limitFunction) {
|
2163 |
-
// Override the limit function with one that delays evaluation as well
|
2164 |
-
ko.subscribable['fn'].limit.call(this, limitFunction);
|
2165 |
-
this._evalDelayed = function () {
|
2166 |
-
this._limitBeforeChange(this[computedState].latestValue);
|
2167 |
-
|
2168 |
-
this[computedState].isStale = true; // Mark as dirty
|
2169 |
-
|
2170 |
-
// Pass the observable to the "limit" code, which will access it when
|
2171 |
-
// it's time to do the notification.
|
2172 |
-
this._limitChange(this);
|
2173 |
-
}
|
2174 |
-
},
|
2175 |
-
dispose: function () {
|
2176 |
-
var state = this[computedState];
|
2177 |
-
if (!state.isSleeping && state.dependencyTracking) {
|
2178 |
-
ko.utils.objectForEach(state.dependencyTracking, function (id, dependency) {
|
2179 |
-
if (dependency.dispose)
|
2180 |
-
dependency.dispose();
|
2181 |
-
});
|
2182 |
-
}
|
2183 |
-
if (state.disposeWhenNodeIsRemoved && state.domNodeDisposalCallback) {
|
2184 |
-
ko.utils.domNodeDisposal.removeDisposeCallback(state.disposeWhenNodeIsRemoved, state.domNodeDisposalCallback);
|
2185 |
-
}
|
2186 |
-
state.dependencyTracking = null;
|
2187 |
-
state.dependenciesCount = 0;
|
2188 |
-
state.isDisposed = true;
|
2189 |
-
state.isStale = false;
|
2190 |
-
state.isSleeping = false;
|
2191 |
-
state.disposeWhenNodeIsRemoved = null;
|
2192 |
-
}
|
2193 |
-
};
|
2194 |
-
|
2195 |
-
var pureComputedOverrides = {
|
2196 |
-
beforeSubscriptionAdd: function (event) {
|
2197 |
-
// If asleep, wake up the computed by subscribing to any dependencies.
|
2198 |
-
var computedObservable = this,
|
2199 |
-
state = computedObservable[computedState];
|
2200 |
-
if (!state.isDisposed && state.isSleeping && event == 'change') {
|
2201 |
-
state.isSleeping = false;
|
2202 |
-
if (state.isStale || computedObservable.haveDependenciesChanged()) {
|
2203 |
-
state.dependencyTracking = null;
|
2204 |
-
state.dependenciesCount = 0;
|
2205 |
-
state.isStale = true;
|
2206 |
-
computedObservable.evaluateImmediate();
|
2207 |
-
} else {
|
2208 |
-
// First put the dependencies in order
|
2209 |
-
var dependeciesOrder = [];
|
2210 |
-
ko.utils.objectForEach(state.dependencyTracking, function (id, dependency) {
|
2211 |
-
dependeciesOrder[dependency._order] = id;
|
2212 |
-
});
|
2213 |
-
// Next, subscribe to each one
|
2214 |
-
ko.utils.arrayForEach(dependeciesOrder, function (id, order) {
|
2215 |
-
var dependency = state.dependencyTracking[id],
|
2216 |
-
subscription = computedObservable.subscribeToDependency(dependency._target);
|
2217 |
-
subscription._order = order;
|
2218 |
-
subscription._version = dependency._version;
|
2219 |
-
state.dependencyTracking[id] = subscription;
|
2220 |
-
});
|
2221 |
-
}
|
2222 |
-
if (!state.isDisposed) { // test since evaluating could trigger disposal
|
2223 |
-
computedObservable["notifySubscribers"](state.latestValue, "awake");
|
2224 |
-
}
|
2225 |
-
}
|
2226 |
-
},
|
2227 |
-
afterSubscriptionRemove: function (event) {
|
2228 |
-
var state = this[computedState];
|
2229 |
-
if (!state.isDisposed && event == 'change' && !this.hasSubscriptionsForEvent('change')) {
|
2230 |
-
ko.utils.objectForEach(state.dependencyTracking, function (id, dependency) {
|
2231 |
-
if (dependency.dispose) {
|
2232 |
-
state.dependencyTracking[id] = {
|
2233 |
-
_target: dependency._target,
|
2234 |
-
_order: dependency._order,
|
2235 |
-
_version: dependency._version
|
2236 |
-
};
|
2237 |
-
dependency.dispose();
|
2238 |
-
}
|
2239 |
-
});
|
2240 |
-
state.isSleeping = true;
|
2241 |
-
this["notifySubscribers"](undefined, "asleep");
|
2242 |
-
}
|
2243 |
-
},
|
2244 |
-
getVersion: function () {
|
2245 |
-
// Because a pure computed is not automatically updated while it is sleeping, we can't
|
2246 |
-
// simply return the version number. Instead, we check if any of the dependencies have
|
2247 |
-
// changed and conditionally re-evaluate the computed observable.
|
2248 |
-
var state = this[computedState];
|
2249 |
-
if (state.isSleeping && (state.isStale || this.haveDependenciesChanged())) {
|
2250 |
-
this.evaluateImmediate();
|
2251 |
-
}
|
2252 |
-
return ko.subscribable['fn'].getVersion.call(this);
|
2253 |
-
}
|
2254 |
-
};
|
2255 |
-
|
2256 |
-
var deferEvaluationOverrides = {
|
2257 |
-
beforeSubscriptionAdd: function (event) {
|
2258 |
-
// This will force a computed with deferEvaluation to evaluate when the first subscription is registered.
|
2259 |
-
if (event == 'change' || event == 'beforeChange') {
|
2260 |
-
this.peek();
|
2261 |
-
}
|
2262 |
-
}
|
2263 |
-
};
|
2264 |
-
|
2265 |
-
// Note that for browsers that don't support proto assignment, the
|
2266 |
-
// inheritance chain is created manually in the ko.computed constructor
|
2267 |
-
if (ko.utils.canSetPrototype) {
|
2268 |
-
ko.utils.setPrototypeOf(computedFn, ko.subscribable['fn']);
|
2269 |
-
}
|
2270 |
-
|
2271 |
-
// Set the proto chain values for ko.hasPrototype
|
2272 |
-
var protoProp = ko.observable.protoProperty; // == "__ko_proto__"
|
2273 |
-
ko.computed[protoProp] = ko.observable;
|
2274 |
-
computedFn[protoProp] = ko.computed;
|
2275 |
-
|
2276 |
-
ko.isComputed = function (instance) {
|
2277 |
-
return ko.hasPrototype(instance, ko.computed);
|
2278 |
-
};
|
2279 |
-
|
2280 |
-
ko.isPureComputed = function (instance) {
|
2281 |
-
return ko.hasPrototype(instance, ko.computed)
|
2282 |
-
&& instance[computedState] && instance[computedState].pure;
|
2283 |
-
};
|
2284 |
-
|
2285 |
-
ko.exportSymbol('computed', ko.computed);
|
2286 |
-
ko.exportSymbol('dependentObservable', ko.computed); // export ko.dependentObservable for backwards compatibility (1.x)
|
2287 |
-
ko.exportSymbol('isComputed', ko.isComputed);
|
2288 |
-
ko.exportSymbol('isPureComputed', ko.isPureComputed);
|
2289 |
-
ko.exportSymbol('computed.fn', computedFn);
|
2290 |
-
ko.exportProperty(computedFn, 'peek', computedFn.peek);
|
2291 |
-
ko.exportProperty(computedFn, 'dispose', computedFn.dispose);
|
2292 |
-
ko.exportProperty(computedFn, 'isActive', computedFn.isActive);
|
2293 |
-
ko.exportProperty(computedFn, 'getDependenciesCount', computedFn.getDependenciesCount);
|
2294 |
-
|
2295 |
-
ko.pureComputed = function (evaluatorFunctionOrOptions, evaluatorFunctionTarget) {
|
2296 |
-
if (typeof evaluatorFunctionOrOptions === 'function') {
|
2297 |
-
return ko.computed(evaluatorFunctionOrOptions, evaluatorFunctionTarget, {'pure':true});
|
2298 |
-
} else {
|
2299 |
-
evaluatorFunctionOrOptions = ko.utils.extend({}, evaluatorFunctionOrOptions); // make a copy of the parameter object
|
2300 |
-
evaluatorFunctionOrOptions['pure'] = true;
|
2301 |
-
return ko.computed(evaluatorFunctionOrOptions, evaluatorFunctionTarget);
|
2302 |
-
}
|
2303 |
-
}
|
2304 |
-
ko.exportSymbol('pureComputed', ko.pureComputed);
|
2305 |
-
|
2306 |
-
(function() {
|
2307 |
-
var maxNestedObservableDepth = 10; // Escape the (unlikely) pathalogical case where an observable's current value is itself (or similar reference cycle)
|
2308 |
-
|
2309 |
-
ko.toJS = function(rootObject) {
|
2310 |
-
if (arguments.length == 0)
|
2311 |
-
throw new Error("When calling ko.toJS, pass the object you want to convert.");
|
2312 |
-
|
2313 |
-
// We just unwrap everything at every level in the object graph
|
2314 |
-
return mapJsObjectGraph(rootObject, function(valueToMap) {
|
2315 |
-
// Loop because an observable's value might in turn be another observable wrapper
|
2316 |
-
for (var i = 0; ko.isObservable(valueToMap) && (i < maxNestedObservableDepth); i++)
|
2317 |
-
valueToMap = valueToMap();
|
2318 |
-
return valueToMap;
|
2319 |
-
});
|
2320 |
-
};
|
2321 |
-
|
2322 |
-
ko.toJSON = function(rootObject, replacer, space) { // replacer and space are optional
|
2323 |
-
var plainJavaScriptObject = ko.toJS(rootObject);
|
2324 |
-
return ko.utils.stringifyJson(plainJavaScriptObject, replacer, space);
|
2325 |
-
};
|
2326 |
-
|
2327 |
-
function mapJsObjectGraph(rootObject, mapInputCallback, visitedObjects) {
|
2328 |
-
visitedObjects = visitedObjects || new objectLookup();
|
2329 |
-
|
2330 |
-
rootObject = mapInputCallback(rootObject);
|
2331 |
-
var canHaveProperties = (typeof rootObject == "object") && (rootObject !== null) && (rootObject !== undefined) && (!(rootObject instanceof RegExp)) && (!(rootObject instanceof Date)) && (!(rootObject instanceof String)) && (!(rootObject instanceof Number)) && (!(rootObject instanceof Boolean));
|
2332 |
-
if (!canHaveProperties)
|
2333 |
-
return rootObject;
|
2334 |
-
|
2335 |
-
var outputProperties = rootObject instanceof Array ? [] : {};
|
2336 |
-
visitedObjects.save(rootObject, outputProperties);
|
2337 |
-
|
2338 |
-
visitPropertiesOrArrayEntries(rootObject, function(indexer) {
|
2339 |
-
var propertyValue = mapInputCallback(rootObject[indexer]);
|
2340 |
-
|
2341 |
-
switch (typeof propertyValue) {
|
2342 |
-
case "boolean":
|
2343 |
-
case "number":
|
2344 |
-
case "string":
|
2345 |
-
case "function":
|
2346 |
-
outputProperties[indexer] = propertyValue;
|
2347 |
-
break;
|
2348 |
-
case "object":
|
2349 |
-
case "undefined":
|
2350 |
-
var previouslyMappedValue = visitedObjects.get(propertyValue);
|
2351 |
-
outputProperties[indexer] = (previouslyMappedValue !== undefined)
|
2352 |
-
? previouslyMappedValue
|
2353 |
-
: mapJsObjectGraph(propertyValue, mapInputCallback, visitedObjects);
|
2354 |
-
break;
|
2355 |
-
}
|
2356 |
-
});
|
2357 |
-
|
2358 |
-
return outputProperties;
|
2359 |
-
}
|
2360 |
-
|
2361 |
-
function visitPropertiesOrArrayEntries(rootObject, visitorCallback) {
|
2362 |
-
if (rootObject instanceof Array) {
|
2363 |
-
for (var i = 0; i < rootObject.length; i++)
|
2364 |
-
visitorCallback(i);
|
2365 |
-
|
2366 |
-
// For arrays, also respect toJSON property for custom mappings (fixes #278)
|
2367 |
-
if (typeof rootObject['toJSON'] == 'function')
|
2368 |
-
visitorCallback('toJSON');
|
2369 |
-
} else {
|
2370 |
-
for (var propertyName in rootObject) {
|
2371 |
-
visitorCallback(propertyName);
|
2372 |
-
}
|
2373 |
-
}
|
2374 |
-
};
|
2375 |
-
|
2376 |
-
function objectLookup() {
|
2377 |
-
this.keys = [];
|
2378 |
-
this.values = [];
|
2379 |
-
};
|
2380 |
-
|
2381 |
-
objectLookup.prototype = {
|
2382 |
-
constructor: objectLookup,
|
2383 |
-
save: function(key, value) {
|
2384 |
-
var existingIndex = ko.utils.arrayIndexOf(this.keys, key);
|
2385 |
-
if (existingIndex >= 0)
|
2386 |
-
this.values[existingIndex] = value;
|
2387 |
-
else {
|
2388 |
-
this.keys.push(key);
|
2389 |
-
this.values.push(value);
|
2390 |
-
}
|
2391 |
-
},
|
2392 |
-
get: function(key) {
|
2393 |
-
var existingIndex = ko.utils.arrayIndexOf(this.keys, key);
|
2394 |
-
return (existingIndex >= 0) ? this.values[existingIndex] : undefined;
|
2395 |
-
}
|
2396 |
-
};
|
2397 |
-
})();
|
2398 |
-
|
2399 |
-
ko.exportSymbol('toJS', ko.toJS);
|
2400 |
-
ko.exportSymbol('toJSON', ko.toJSON);
|
2401 |
-
(function () {
|
2402 |
-
var hasDomDataExpandoProperty = '__ko__hasDomDataOptionValue__';
|
2403 |
-
|
2404 |
-
// Normally, SELECT elements and their OPTIONs can only take value of type 'string' (because the values
|
2405 |
-
// are stored on DOM attributes). ko.selectExtensions provides a way for SELECTs/OPTIONs to have values
|
2406 |
-
// that are arbitrary objects. This is very convenient when implementing things like cascading dropdowns.
|
2407 |
-
ko.selectExtensions = {
|
2408 |
-
readValue : function(element) {
|
2409 |
-
switch (ko.utils.tagNameLower(element)) {
|
2410 |
-
case 'option':
|
2411 |
-
if (element[hasDomDataExpandoProperty] === true)
|
2412 |
-
return ko.utils.domData.get(element, ko.bindingHandlers.options.optionValueDomDataKey);
|
2413 |
-
return ko.utils.ieVersion <= 7
|
2414 |
-
? (element.getAttributeNode('value') && element.getAttributeNode('value').specified ? element.value : element.text)
|
2415 |
-
: element.value;
|
2416 |
-
case 'select':
|
2417 |
-
return element.selectedIndex >= 0 ? ko.selectExtensions.readValue(element.options[element.selectedIndex]) : undefined;
|
2418 |
-
default:
|
2419 |
-
return element.value;
|
2420 |
-
}
|
2421 |
-
},
|
2422 |
-
|
2423 |
-
writeValue: function(element, value, allowUnset) {
|
2424 |
-
switch (ko.utils.tagNameLower(element)) {
|
2425 |
-
case 'option':
|
2426 |
-
switch(typeof value) {
|
2427 |
-
case "string":
|
2428 |
-
ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, undefined);
|
2429 |
-
if (hasDomDataExpandoProperty in element) { // IE <= 8 throws errors if you delete non-existent properties from a DOM node
|
2430 |
-
delete element[hasDomDataExpandoProperty];
|
2431 |
-
}
|
2432 |
-
element.value = value;
|
2433 |
-
break;
|
2434 |
-
default:
|
2435 |
-
// Store arbitrary object using DomData
|
2436 |
-
ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, value);
|
2437 |
-
element[hasDomDataExpandoProperty] = true;
|
2438 |
-
|
2439 |
-
// Special treatment of numbers is just for backward compatibility. KO 1.2.1 wrote numerical values to element.value.
|
2440 |
-
element.value = typeof value === "number" ? value : "";
|
2441 |
-
break;
|
2442 |
-
}
|
2443 |
-
break;
|
2444 |
-
case 'select':
|
2445 |
-
if (value === "" || value === null) // A blank string or null value will select the caption
|
2446 |
-
value = undefined;
|
2447 |
-
var selection = -1;
|
2448 |
-
for (var i = 0, n = element.options.length, optionValue; i < n; ++i) {
|
2449 |
-
optionValue = ko.selectExtensions.readValue(element.options[i]);
|
2450 |
-
// Include special check to handle selecting a caption with a blank string value
|
2451 |
-
if (optionValue == value || (optionValue == "" && value === undefined)) {
|
2452 |
-
selection = i;
|
2453 |
-
break;
|
2454 |
-
}
|
2455 |
-
}
|
2456 |
-
if (allowUnset || selection >= 0 || (value === undefined && element.size > 1)) {
|
2457 |
-
element.selectedIndex = selection;
|
2458 |
-
}
|
2459 |
-
break;
|
2460 |
-
default:
|
2461 |
-
if ((value === null) || (value === undefined))
|
2462 |
-
value = "";
|
2463 |
-
element.value = value;
|
2464 |
-
break;
|
2465 |
-
}
|
2466 |
-
}
|
2467 |
-
};
|
2468 |
-
})();
|
2469 |
-
|
2470 |
-
ko.exportSymbol('selectExtensions', ko.selectExtensions);
|
2471 |
-
ko.exportSymbol('selectExtensions.readValue', ko.selectExtensions.readValue);
|
2472 |
-
ko.exportSymbol('selectExtensions.writeValue', ko.selectExtensions.writeValue);
|
2473 |
-
ko.expressionRewriting = (function () {
|
2474 |
-
var javaScriptReservedWords = ["true", "false", "null", "undefined"];
|
2475 |
-
|
2476 |
-
// Matches something that can be assigned to--either an isolated identifier or something ending with a property accessor
|
2477 |
-
// This is designed to be simple and avoid false negatives, but could produce false positives (e.g., a+b.c).
|
2478 |
-
// This also will not properly handle nested brackets (e.g., obj1[obj2['prop']]; see #911).
|
2479 |
-
var javaScriptAssignmentTarget = /^(?:[$_a-z][$\w]*|(.+)(\.\s*[$_a-z][$\w]*|\[.+\]))$/i;
|
2480 |
-
|
2481 |
-
function getWriteableValue(expression) {
|
2482 |
-
if (ko.utils.arrayIndexOf(javaScriptReservedWords, expression) >= 0)
|
2483 |
-
return false;
|
2484 |
-
var match = expression.match(javaScriptAssignmentTarget);
|
2485 |
-
return match === null ? false : match[1] ? ('Object(' + match[1] + ')' + match[2]) : expression;
|
2486 |
-
}
|
2487 |
-
|
2488 |
-
// The following regular expressions will be used to split an object-literal string into tokens
|
2489 |
-
|
2490 |
-
// These two match strings, either with double quotes or single quotes
|
2491 |
-
var stringDouble = '"(?:[^"\\\\]|\\\\.)*"',
|
2492 |
-
stringSingle = "'(?:[^'\\\\]|\\\\.)*'",
|
2493 |
-
// Matches a regular expression (text enclosed by slashes), but will also match sets of divisions
|
2494 |
-
// as a regular expression (this is handled by the parsing loop below).
|
2495 |
-
stringRegexp = '/(?:[^/\\\\]|\\\\.)*/\w*',
|
2496 |
-
// These characters have special meaning to the parser and must not appear in the middle of a
|
2497 |
-
// token, except as part of a string.
|
2498 |
-
specials = ',"\'{}()/:[\\]',
|
2499 |
-
// Match text (at least two characters) that does not contain any of the above special characters,
|
2500 |
-
// although some of the special characters are allowed to start it (all but the colon and comma).
|
2501 |
-
// The text can contain spaces, but leading or trailing spaces are skipped.
|
2502 |
-
everyThingElse = '[^\\s:,/][^' + specials + ']*[^\\s' + specials + ']',
|
2503 |
-
// Match any non-space character not matched already. This will match colons and commas, since they're
|
2504 |
-
// not matched by "everyThingElse", but will also match any other single character that wasn't already
|
2505 |
-
// matched (for example: in "a: 1, b: 2", each of the non-space characters will be matched by oneNotSpace).
|
2506 |
-
oneNotSpace = '[^\\s]',
|
2507 |
-
|
2508 |
-
// Create the actual regular expression by or-ing the above strings. The order is important.
|
2509 |
-
bindingToken = RegExp(stringDouble + '|' + stringSingle + '|' + stringRegexp + '|' + everyThingElse + '|' + oneNotSpace, 'g'),
|
2510 |
-
|
2511 |
-
// Match end of previous token to determine whether a slash is a division or regex.
|
2512 |
-
divisionLookBehind = /[\])"'A-Za-z0-9_$]+$/,
|
2513 |
-
keywordRegexLookBehind = {'in':1,'return':1,'typeof':1};
|
2514 |
-
|
2515 |
-
function parseObjectLiteral(objectLiteralString) {
|
2516 |
-
// Trim leading and trailing spaces from the string
|
2517 |
-
var str = ko.utils.stringTrim(objectLiteralString);
|
2518 |
-
|
2519 |
-
// Trim braces '{' surrounding the whole object literal
|
2520 |
-
if (str.charCodeAt(0) === 123) str = str.slice(1, -1);
|
2521 |
-
|
2522 |
-
// Split into tokens
|
2523 |
-
var result = [], toks = str.match(bindingToken), key, values = [], depth = 0;
|
2524 |
-
|
2525 |
-
if (toks) {
|
2526 |
-
// Append a comma so that we don't need a separate code block to deal with the last item
|
2527 |
-
toks.push(',');
|
2528 |
-
|
2529 |
-
for (var i = 0, tok; tok = toks[i]; ++i) {
|
2530 |
-
var c = tok.charCodeAt(0);
|
2531 |
-
// A comma signals the end of a key/value pair if depth is zero
|
2532 |
-
if (c === 44) { // ","
|
2533 |
-
if (depth <= 0) {
|
2534 |
-
result.push((key && values.length) ? {key: key, value: values.join('')} : {'unknown': key || values.join('')});
|
2535 |
-
key = depth = 0;
|
2536 |
-
values = [];
|
2537 |
-
continue;
|
2538 |
-
}
|
2539 |
-
// Simply skip the colon that separates the name and value
|
2540 |
-
} else if (c === 58) { // ":"
|
2541 |
-
if (!depth && !key && values.length === 1) {
|
2542 |
-
key = values.pop();
|
2543 |
-
continue;
|
2544 |
-
}
|
2545 |
-
// A set of slashes is initially matched as a regular expression, but could be division
|
2546 |
-
} else if (c === 47 && i && tok.length > 1) { // "/"
|
2547 |
-
// Look at the end of the previous token to determine if the slash is actually division
|
2548 |
-
var match = toks[i-1].match(divisionLookBehind);
|
2549 |
-
if (match && !keywordRegexLookBehind[match[0]]) {
|
2550 |
-
// The slash is actually a division punctuator; re-parse the remainder of the string (not including the slash)
|
2551 |
-
str = str.substr(str.indexOf(tok) + 1);
|
2552 |
-
toks = str.match(bindingToken);
|
2553 |
-
toks.push(',');
|
2554 |
-
i = -1;
|
2555 |
-
// Continue with just the slash
|
2556 |
-
tok = '/';
|
2557 |
-
}
|
2558 |
-
// Increment depth for parentheses, braces, and brackets so that interior commas are ignored
|
2559 |
-
} else if (c === 40 || c === 123 || c === 91) { // '(', '{', '['
|
2560 |
-
++depth;
|
2561 |
-
} else if (c === 41 || c === 125 || c === 93) { // ')', '}', ']'
|
2562 |
-
--depth;
|
2563 |
-
// The key will be the first token; if it's a string, trim the quotes
|
2564 |
-
} else if (!key && !values.length && (c === 34 || c === 39)) { // '"', "'"
|
2565 |
-
tok = tok.slice(1, -1);
|
2566 |
-
}
|
2567 |
-
values.push(tok);
|
2568 |
-
}
|
2569 |
-
}
|
2570 |
-
return result;
|
2571 |
-
}
|
2572 |
-
|
2573 |
-
// Two-way bindings include a write function that allow the handler to update the value even if it's not an observable.
|
2574 |
-
var twoWayBindings = {};
|
2575 |
-
|
2576 |
-
function preProcessBindings(bindingsStringOrKeyValueArray, bindingOptions) {
|
2577 |
-
bindingOptions = bindingOptions || {};
|
2578 |
-
|
2579 |
-
function processKeyValue(key, val) {
|
2580 |
-
var writableVal;
|
2581 |
-
function callPreprocessHook(obj) {
|
2582 |
-
return (obj && obj['preprocess']) ? (val = obj['preprocess'](val, key, processKeyValue)) : true;
|
2583 |
-
}
|
2584 |
-
if (!bindingParams) {
|
2585 |
-
if (!callPreprocessHook(ko['getBindingHandler'](key)))
|
2586 |
-
return;
|
2587 |
-
|
2588 |
-
if (twoWayBindings[key] && (writableVal = getWriteableValue(val))) {
|
2589 |
-
// For two-way bindings, provide a write method in case the value
|
2590 |
-
// isn't a writable observable.
|
2591 |
-
propertyAccessorResultStrings.push("'" + key + "':function(_z){" + writableVal + "=_z}");
|
2592 |
-
}
|
2593 |
-
}
|
2594 |
-
// Values are wrapped in a function so that each value can be accessed independently
|
2595 |
-
if (makeValueAccessors) {
|
2596 |
-
val = 'function(){return ' + val + ' }';
|
2597 |
-
}
|
2598 |
-
resultStrings.push("'" + key + "':" + val);
|
2599 |
-
}
|
2600 |
-
|
2601 |
-
var resultStrings = [],
|
2602 |
-
propertyAccessorResultStrings = [],
|
2603 |
-
makeValueAccessors = bindingOptions['valueAccessors'],
|
2604 |
-
bindingParams = bindingOptions['bindingParams'],
|
2605 |
-
keyValueArray = typeof bindingsStringOrKeyValueArray === "string" ?
|
2606 |
-
parseObjectLiteral(bindingsStringOrKeyValueArray) : bindingsStringOrKeyValueArray;
|
2607 |
-
|
2608 |
-
ko.utils.arrayForEach(keyValueArray, function(keyValue) {
|
2609 |
-
processKeyValue(keyValue.key || keyValue['unknown'], keyValue.value);
|
2610 |
-
});
|
2611 |
-
|
2612 |
-
if (propertyAccessorResultStrings.length)
|
2613 |
-
processKeyValue('_ko_property_writers', "{" + propertyAccessorResultStrings.join(",") + " }");
|
2614 |
-
|
2615 |
-
return resultStrings.join(",");
|
2616 |
-
}
|
2617 |
-
|
2618 |
-
return {
|
2619 |
-
bindingRewriteValidators: [],
|
2620 |
-
|
2621 |
-
twoWayBindings: twoWayBindings,
|
2622 |
-
|
2623 |
-
parseObjectLiteral: parseObjectLiteral,
|
2624 |
-
|
2625 |
-
preProcessBindings: preProcessBindings,
|
2626 |
-
|
2627 |
-
keyValueArrayContainsKey: function(keyValueArray, key) {
|
2628 |
-
for (var i = 0; i < keyValueArray.length; i++)
|
2629 |
-
if (keyValueArray[i]['key'] == key)
|
2630 |
-
return true;
|
2631 |
-
return false;
|
2632 |
-
},
|
2633 |
-
|
2634 |
-
// Internal, private KO utility for updating model properties from within bindings
|
2635 |
-
// property: If the property being updated is (or might be) an observable, pass it here
|
2636 |
-
// If it turns out to be a writable observable, it will be written to directly
|
2637 |
-
// allBindings: An object with a get method to retrieve bindings in the current execution context.
|
2638 |
-
// This will be searched for a '_ko_property_writers' property in case you're writing to a non-observable
|
2639 |
-
// key: The key identifying the property to be written. Example: for { hasFocus: myValue }, write to 'myValue' by specifying the key 'hasFocus'
|
2640 |
-
// value: The value to be written
|
2641 |
-
// checkIfDifferent: If true, and if the property being written is a writable observable, the value will only be written if
|
2642 |
-
// it is !== existing value on that writable observable
|
2643 |
-
writeValueToProperty: function(property, allBindings, key, value, checkIfDifferent) {
|
2644 |
-
if (!property || !ko.isObservable(property)) {
|
2645 |
-
var propWriters = allBindings.get('_ko_property_writers');
|
2646 |
-
if (propWriters && propWriters[key])
|
2647 |
-
propWriters[key](value);
|
2648 |
-
} else if (ko.isWriteableObservable(property) && (!checkIfDifferent || property.peek() !== value)) {
|
2649 |
-
property(value);
|
2650 |
-
}
|
2651 |
-
}
|
2652 |
-
};
|
2653 |
-
})();
|
2654 |
-
|
2655 |
-
ko.exportSymbol('expressionRewriting', ko.expressionRewriting);
|
2656 |
-
ko.exportSymbol('expressionRewriting.bindingRewriteValidators', ko.expressionRewriting.bindingRewriteValidators);
|
2657 |
-
ko.exportSymbol('expressionRewriting.parseObjectLiteral', ko.expressionRewriting.parseObjectLiteral);
|
2658 |
-
ko.exportSymbol('expressionRewriting.preProcessBindings', ko.expressionRewriting.preProcessBindings);
|
2659 |
-
|
2660 |
-
// Making bindings explicitly declare themselves as "two way" isn't ideal in the long term (it would be better if
|
2661 |
-
// all bindings could use an official 'property writer' API without needing to declare that they might). However,
|
2662 |
-
// since this is not, and has never been, a public API (_ko_property_writers was never documented), it's acceptable
|
2663 |
-
// as an internal implementation detail in the short term.
|
2664 |
-
// For those developers who rely on _ko_property_writers in their custom bindings, we expose _twoWayBindings as an
|
2665 |
-
// undocumented feature that makes it relatively easy to upgrade to KO 3.0. However, this is still not an official
|
2666 |
-
// public API, and we reserve the right to remove it at any time if we create a real public property writers API.
|
2667 |
-
ko.exportSymbol('expressionRewriting._twoWayBindings', ko.expressionRewriting.twoWayBindings);
|
2668 |
-
|
2669 |
-
// For backward compatibility, define the following aliases. (Previously, these function names were misleading because
|
2670 |
-
// they referred to JSON specifically, even though they actually work with arbitrary JavaScript object literal expressions.)
|
2671 |
-
ko.exportSymbol('jsonExpressionRewriting', ko.expressionRewriting);
|
2672 |
-
ko.exportSymbol('jsonExpressionRewriting.insertPropertyAccessorsIntoJson', ko.expressionRewriting.preProcessBindings);
|
2673 |
-
(function() {
|
2674 |
-
// "Virtual elements" is an abstraction on top of the usual DOM API which understands the notion that comment nodes
|
2675 |
-
// may be used to represent hierarchy (in addition to the DOM's natural hierarchy).
|
2676 |
-
// If you call the DOM-manipulating functions on ko.virtualElements, you will be able to read and write the state
|
2677 |
-
// of that virtual hierarchy
|
2678 |
-
//
|
2679 |
-
// The point of all this is to support containerless templates (e.g., <!-- ko foreach:someCollection -->blah<!-- /ko -->)
|
2680 |
-
// without having to scatter special cases all over the binding and templating code.
|
2681 |
-
|
2682 |
-
// IE 9 cannot reliably read the "nodeValue" property of a comment node (see https://github.com/SteveSanderson/knockout/issues/186)
|
2683 |
-
// but it does give them a nonstandard alternative property called "text" that it can read reliably. Other browsers don't have that property.
|
2684 |
-
// So, use node.text where available, and node.nodeValue elsewhere
|
2685 |
-
var commentNodesHaveTextProperty = document && document.createComment("test").text === "<!--test-->";
|
2686 |
-
|
2687 |
-
var startCommentRegex = commentNodesHaveTextProperty ? /^<!--\s*ko(?:\s+([\s\S]+))?\s*-->$/ : /^\s*ko(?:\s+([\s\S]+))?\s*$/;
|
2688 |
-
var endCommentRegex = commentNodesHaveTextProperty ? /^<!--\s*\/ko\s*-->$/ : /^\s*\/ko\s*$/;
|
2689 |
-
var htmlTagsWithOptionallyClosingChildren = { 'ul': true, 'ol': true };
|
2690 |
-
|
2691 |
-
function isStartComment(node) {
|
2692 |
-
return (node.nodeType == 8) && startCommentRegex.test(commentNodesHaveTextProperty ? node.text : node.nodeValue);
|
2693 |
-
}
|
2694 |
-
|
2695 |
-
function isEndComment(node) {
|
2696 |
-
return (node.nodeType == 8) && endCommentRegex.test(commentNodesHaveTextProperty ? node.text : node.nodeValue);
|
2697 |
-
}
|
2698 |
-
|
2699 |
-
function getVirtualChildren(startComment, allowUnbalanced) {
|
2700 |
-
var currentNode = startComment;
|
2701 |
-
var depth = 1;
|
2702 |
-
var children = [];
|
2703 |
-
while (currentNode = currentNode.nextSibling) {
|
2704 |
-
if (isEndComment(currentNode)) {
|
2705 |
-
depth--;
|
2706 |
-
if (depth === 0)
|
2707 |
-
return children;
|
2708 |
-
}
|
2709 |
-
|
2710 |
-
children.push(currentNode);
|
2711 |
-
|
2712 |
-
if (isStartComment(currentNode))
|
2713 |
-
depth++;
|
2714 |
-
}
|
2715 |
-
if (!allowUnbalanced)
|
2716 |
-
throw new Error("Cannot find closing comment tag to match: " + startComment.nodeValue);
|
2717 |
-
return null;
|
2718 |
-
}
|
2719 |
-
|
2720 |
-
function getMatchingEndComment(startComment, allowUnbalanced) {
|
2721 |
-
var allVirtualChildren = getVirtualChildren(startComment, allowUnbalanced);
|
2722 |
-
if (allVirtualChildren) {
|
2723 |
-
if (allVirtualChildren.length > 0)
|
2724 |
-
return allVirtualChildren[allVirtualChildren.length - 1].nextSibling;
|
2725 |
-
return startComment.nextSibling;
|
2726 |
-
} else
|
2727 |
-
return null; // Must have no matching end comment, and allowUnbalanced is true
|
2728 |
-
}
|
2729 |
-
|
2730 |
-
function getUnbalancedChildTags(node) {
|
2731 |
-
// e.g., from <div>OK</div><!-- ko blah --><span>Another</span>, returns: <!-- ko blah --><span>Another</span>
|
2732 |
-
// from <div>OK</div><!-- /ko --><!-- /ko -->, returns: <!-- /ko --><!-- /ko -->
|
2733 |
-
var childNode = node.firstChild, captureRemaining = null;
|
2734 |
-
if (childNode) {
|
2735 |
-
do {
|
2736 |
-
if (captureRemaining) // We already hit an unbalanced node and are now just scooping up all subsequent nodes
|
2737 |
-
captureRemaining.push(childNode);
|
2738 |
-
else if (isStartComment(childNode)) {
|
2739 |
-
var matchingEndComment = getMatchingEndComment(childNode, /* allowUnbalanced: */ true);
|
2740 |
-
if (matchingEndComment) // It's a balanced tag, so skip immediately to the end of this virtual set
|
2741 |
-
childNode = matchingEndComment;
|
2742 |
-
else
|
2743 |
-
captureRemaining = [childNode]; // It's unbalanced, so start capturing from this point
|
2744 |
-
} else if (isEndComment(childNode)) {
|
2745 |
-
captureRemaining = [childNode]; // It's unbalanced (if it wasn't, we'd have skipped over it already), so start capturing
|
2746 |
-
}
|
2747 |
-
} while (childNode = childNode.nextSibling);
|
2748 |
-
}
|
2749 |
-
return captureRemaining;
|
2750 |
-
}
|
2751 |
-
|
2752 |
-
ko.virtualElements = {
|
2753 |
-
allowedBindings: {},
|
2754 |
-
|
2755 |
-
childNodes: function(node) {
|
2756 |
-
return isStartComment(node) ? getVirtualChildren(node) : node.childNodes;
|
2757 |
-
},
|
2758 |
-
|
2759 |
-
emptyNode: function(node) {
|
2760 |
-
if (!isStartComment(node))
|
2761 |
-
ko.utils.emptyDomNode(node);
|
2762 |
-
else {
|
2763 |
-
var virtualChildren = ko.virtualElements.childNodes(node);
|
2764 |
-
for (var i = 0, j = virtualChildren.length; i < j; i++)
|
2765 |
-
ko.removeNode(virtualChildren[i]);
|
2766 |
-
}
|
2767 |
-
},
|
2768 |
-
|
2769 |
-
setDomNodeChildren: function(node, childNodes) {
|
2770 |
-
if (!isStartComment(node))
|
2771 |
-
ko.utils.setDomNodeChildren(node, childNodes);
|
2772 |
-
else {
|
2773 |
-
ko.virtualElements.emptyNode(node);
|
2774 |
-
var endCommentNode = node.nextSibling; // Must be the next sibling, as we just emptied the children
|
2775 |
-
for (var i = 0, j = childNodes.length; i < j; i++)
|
2776 |
-
endCommentNode.parentNode.insertBefore(childNodes[i], endCommentNode);
|
2777 |
-
}
|
2778 |
-
},
|
2779 |
-
|
2780 |
-
prepend: function(containerNode, nodeToPrepend) {
|
2781 |
-
if (!isStartComment(containerNode)) {
|
2782 |
-
if (containerNode.firstChild)
|
2783 |
-
containerNode.insertBefore(nodeToPrepend, containerNode.firstChild);
|
2784 |
-
else
|
2785 |
-
containerNode.appendChild(nodeToPrepend);
|
2786 |
-
} else {
|
2787 |
-
// Start comments must always have a parent and at least one following sibling (the end comment)
|
2788 |
-
containerNode.parentNode.insertBefore(nodeToPrepend, containerNode.nextSibling);
|
2789 |
-
}
|
2790 |
-
},
|
2791 |
-
|
2792 |
-
insertAfter: function(containerNode, nodeToInsert, insertAfterNode) {
|
2793 |
-
if (!insertAfterNode) {
|
2794 |
-
ko.virtualElements.prepend(containerNode, nodeToInsert);
|
2795 |
-
} else if (!isStartComment(containerNode)) {
|
2796 |
-
// Insert after insertion point
|
2797 |
-
if (insertAfterNode.nextSibling)
|
2798 |
-
containerNode.insertBefore(nodeToInsert, insertAfterNode.nextSibling);
|
2799 |
-
else
|
2800 |
-
containerNode.appendChild(nodeToInsert);
|
2801 |
-
} else {
|
2802 |
-
// Children of start comments must always have a parent and at least one following sibling (the end comment)
|
2803 |
-
containerNode.parentNode.insertBefore(nodeToInsert, insertAfterNode.nextSibling);
|
2804 |
-
}
|
2805 |
-
},
|
2806 |
-
|
2807 |
-
firstChild: function(node) {
|
2808 |
-
if (!isStartComment(node))
|
2809 |
-
return node.firstChild;
|
2810 |
-
if (!node.nextSibling || isEndComment(node.nextSibling))
|
2811 |
-
return null;
|
2812 |
-
return node.nextSibling;
|
2813 |
-
},
|
2814 |
-
|
2815 |
-
nextSibling: function(node) {
|
2816 |
-
if (isStartComment(node))
|
2817 |
-
node = getMatchingEndComment(node);
|
2818 |
-
if (node.nextSibling && isEndComment(node.nextSibling))
|
2819 |
-
return null;
|
2820 |
-
return node.nextSibling;
|
2821 |
-
},
|
2822 |
-
|
2823 |
-
hasBindingValue: isStartComment,
|
2824 |
-
|
2825 |
-
virtualNodeBindingValue: function(node) {
|
2826 |
-
var regexMatch = (commentNodesHaveTextProperty ? node.text : node.nodeValue).match(startCommentRegex);
|
2827 |
-
return regexMatch ? regexMatch[1] : null;
|
2828 |
-
},
|
2829 |
-
|
2830 |
-
normaliseVirtualElementDomStructure: function(elementVerified) {
|
2831 |
-
// Workaround for https://github.com/SteveSanderson/knockout/issues/155
|
2832 |
-
// (IE <= 8 or IE 9 quirks mode parses your HTML weirdly, treating closing </li> tags as if they don't exist, thereby moving comment nodes
|
2833 |
-
// that are direct descendants of <ul> into the preceding <li>)
|
2834 |
-
if (!htmlTagsWithOptionallyClosingChildren[ko.utils.tagNameLower(elementVerified)])
|
2835 |
-
return;
|
2836 |
-
|
2837 |
-
// Scan immediate children to see if they contain unbalanced comment tags. If they do, those comment tags
|
2838 |
-
// must be intended to appear *after* that child, so move them there.
|
2839 |
-
var childNode = elementVerified.firstChild;
|
2840 |
-
if (childNode) {
|
2841 |
-
do {
|
2842 |
-
if (childNode.nodeType === 1) {
|
2843 |
-
var unbalancedTags = getUnbalancedChildTags(childNode);
|
2844 |
-
if (unbalancedTags) {
|
2845 |
-
// Fix up the DOM by moving the unbalanced tags to where they most likely were intended to be placed - *after* the child
|
2846 |
-
var nodeToInsertBefore = childNode.nextSibling;
|
2847 |
-
for (var i = 0; i < unbalancedTags.length; i++) {
|
2848 |
-
if (nodeToInsertBefore)
|
2849 |
-
elementVerified.insertBefore(unbalancedTags[i], nodeToInsertBefore);
|
2850 |
-
else
|
2851 |
-
elementVerified.appendChild(unbalancedTags[i]);
|
2852 |
-
}
|
2853 |
-
}
|
2854 |
-
}
|
2855 |
-
} while (childNode = childNode.nextSibling);
|
2856 |
-
}
|
2857 |
-
}
|
2858 |
-
};
|
2859 |
-
})();
|
2860 |
-
ko.exportSymbol('virtualElements', ko.virtualElements);
|
2861 |
-
ko.exportSymbol('virtualElements.allowedBindings', ko.virtualElements.allowedBindings);
|
2862 |
-
ko.exportSymbol('virtualElements.emptyNode', ko.virtualElements.emptyNode);
|
2863 |
-
//ko.exportSymbol('virtualElements.firstChild', ko.virtualElements.firstChild); // firstChild is not minified
|
2864 |
-
ko.exportSymbol('virtualElements.insertAfter', ko.virtualElements.insertAfter);
|
2865 |
-
//ko.exportSymbol('virtualElements.nextSibling', ko.virtualElements.nextSibling); // nextSibling is not minified
|
2866 |
-
ko.exportSymbol('virtualElements.prepend', ko.virtualElements.prepend);
|
2867 |
-
ko.exportSymbol('virtualElements.setDomNodeChildren', ko.virtualElements.setDomNodeChildren);
|
2868 |
-
(function() {
|
2869 |
-
var defaultBindingAttributeName = "data-bind";
|
2870 |
-
|
2871 |
-
ko.bindingProvider = function() {
|
2872 |
-
this.bindingCache = {};
|
2873 |
-
};
|
2874 |
-
|
2875 |
-
ko.utils.extend(ko.bindingProvider.prototype, {
|
2876 |
-
'nodeHasBindings': function(node) {
|
2877 |
-
switch (node.nodeType) {
|
2878 |
-
case 1: // Element
|
2879 |
-
return node.getAttribute(defaultBindingAttributeName) != null
|
2880 |
-
|| ko.components['getComponentNameForNode'](node);
|
2881 |
-
case 8: // Comment node
|
2882 |
-
return ko.virtualElements.hasBindingValue(node);
|
2883 |
-
default: return false;
|
2884 |
-
}
|
2885 |
-
},
|
2886 |
-
|
2887 |
-
'getBindings': function(node, bindingContext) {
|
2888 |
-
var bindingsString = this['getBindingsString'](node, bindingContext),
|
2889 |
-
parsedBindings = bindingsString ? this['parseBindingsString'](bindingsString, bindingContext, node) : null;
|
2890 |
-
return ko.components.addBindingsForCustomElement(parsedBindings, node, bindingContext, /* valueAccessors */ false);
|
2891 |
-
},
|
2892 |
-
|
2893 |
-
'getBindingAccessors': function(node, bindingContext) {
|
2894 |
-
var bindingsString = this['getBindingsString'](node, bindingContext),
|
2895 |
-
parsedBindings = bindingsString ? this['parseBindingsString'](bindingsString, bindingContext, node, { 'valueAccessors': true }) : null;
|
2896 |
-
return ko.components.addBindingsForCustomElement(parsedBindings, node, bindingContext, /* valueAccessors */ true);
|
2897 |
-
},
|
2898 |
-
|
2899 |
-
// The following function is only used internally by this default provider.
|
2900 |
-
// It's not part of the interface definition for a general binding provider.
|
2901 |
-
'getBindingsString': function(node, bindingContext) {
|
2902 |
-
switch (node.nodeType) {
|
2903 |
-
case 1: return node.getAttribute(defaultBindingAttributeName); // Element
|
2904 |
-
case 8: return ko.virtualElements.virtualNodeBindingValue(node); // Comment node
|
2905 |
-
default: return null;
|
2906 |
-
}
|
2907 |
-
},
|
2908 |
-
|
2909 |
-
// The following function is only used internally by this default provider.
|
2910 |
-
// It's not part of the interface definition for a general binding provider.
|
2911 |
-
'parseBindingsString': function(bindingsString, bindingContext, node, options) {
|
2912 |
-
try {
|
2913 |
-
var bindingFunction = createBindingsStringEvaluatorViaCache(bindingsString, this.bindingCache, options);
|
2914 |
-
return bindingFunction(bindingContext, node);
|
2915 |
-
} catch (ex) {
|
2916 |
-
ex.message = "Unable to parse bindings.\nBindings value: " + bindingsString + "\nMessage: " + ex.message;
|
2917 |
-
throw ex;
|
2918 |
-
}
|
2919 |
-
}
|
2920 |
-
});
|
2921 |
-
|
2922 |
-
ko.bindingProvider['instance'] = new ko.bindingProvider();
|
2923 |
-
|
2924 |
-
function createBindingsStringEvaluatorViaCache(bindingsString, cache, options) {
|
2925 |
-
var cacheKey = bindingsString + (options && options['valueAccessors'] || '');
|
2926 |
-
return cache[cacheKey]
|
2927 |
-
|| (cache[cacheKey] = createBindingsStringEvaluator(bindingsString, options));
|
2928 |
-
}
|
2929 |
-
|
2930 |
-
function createBindingsStringEvaluator(bindingsString, options) {
|
2931 |
-
// Build the source for a function that evaluates "expression"
|
2932 |
-
// For each scope variable, add an extra level of "with" nesting
|
2933 |
-
// Example result: with(sc1) { with(sc0) { return (expression) } }
|
2934 |
-
var rewrittenBindings = ko.expressionRewriting.preProcessBindings(bindingsString, options),
|
2935 |
-
functionBody = "with($context){with($data||{}){return{" + rewrittenBindings + "}}}";
|
2936 |
-
return new Function("$context", "$element", functionBody);
|
2937 |
-
}
|
2938 |
-
})();
|
2939 |
-
|
2940 |
-
ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
2941 |
-
(function () {
|
2942 |
-
ko.bindingHandlers = {};
|
2943 |
-
|
2944 |
-
// The following element types will not be recursed into during binding.
|
2945 |
-
var bindingDoesNotRecurseIntoElementTypes = {
|
2946 |
-
// Don't want bindings that operate on text nodes to mutate <script> and <textarea> contents,
|
2947 |
-
// because it's unexpected and a potential XSS issue.
|
2948 |
-
// Also bindings should not operate on <template> elements since this breaks in Internet Explorer
|
2949 |
-
// and because such elements' contents are always intended to be bound in a different context
|
2950 |
-
// from where they appear in the document.
|
2951 |
-
'script': true,
|
2952 |
-
'textarea': true,
|
2953 |
-
'template': true
|
2954 |
-
};
|
2955 |
-
|
2956 |
-
// Use an overridable method for retrieving binding handlers so that a plugins may support dynamically created handlers
|
2957 |
-
ko['getBindingHandler'] = function(bindingKey) {
|
2958 |
-
return ko.bindingHandlers[bindingKey];
|
2959 |
-
};
|
2960 |
-
|
2961 |
-
// The ko.bindingContext constructor is only called directly to create the root context. For child
|
2962 |
-
// contexts, use bindingContext.createChildContext or bindingContext.extend.
|
2963 |
-
ko.bindingContext = function(dataItemOrAccessor, parentContext, dataItemAlias, extendCallback) {
|
2964 |
-
|
2965 |
-
// The binding context object includes static properties for the current, parent, and root view models.
|
2966 |
-
// If a view model is actually stored in an observable, the corresponding binding context object, and
|
2967 |
-
// any child contexts, must be updated when the view model is changed.
|
2968 |
-
function updateContext() {
|
2969 |
-
// Most of the time, the context will directly get a view model object, but if a function is given,
|
2970 |
-
// we call the function to retrieve the view model. If the function accesses any observables or returns
|
2971 |
-
// an observable, the dependency is tracked, and those observables can later cause the binding
|
2972 |
-
// context to be updated.
|
2973 |
-
var dataItemOrObservable = isFunc ? dataItemOrAccessor() : dataItemOrAccessor,
|
2974 |
-
dataItem = ko.utils.unwrapObservable(dataItemOrObservable);
|
2975 |
-
|
2976 |
-
if (parentContext) {
|
2977 |
-
// When a "parent" context is given, register a dependency on the parent context. Thus whenever the
|
2978 |
-
// parent context is updated, this context will also be updated.
|
2979 |
-
if (parentContext._subscribable)
|
2980 |
-
parentContext._subscribable();
|
2981 |
-
|
2982 |
-
// Copy $root and any custom properties from the parent context
|
2983 |
-
ko.utils.extend(self, parentContext);
|
2984 |
-
|
2985 |
-
// Because the above copy overwrites our own properties, we need to reset them.
|
2986 |
-
// During the first execution, "subscribable" isn't set, so don't bother doing the update then.
|
2987 |
-
if (subscribable) {
|
2988 |
-
self._subscribable = subscribable;
|
2989 |
-
}
|
2990 |
-
} else {
|
2991 |
-
self['$parents'] = [];
|
2992 |
-
self['$root'] = dataItem;
|
2993 |
-
|
2994 |
-
// Export 'ko' in the binding context so it will be available in bindings and templates
|
2995 |
-
// even if 'ko' isn't exported as a global, such as when using an AMD loader.
|
2996 |
-
// See https://github.com/SteveSanderson/knockout/issues/490
|
2997 |
-
self['ko'] = ko;
|
2998 |
-
}
|
2999 |
-
self['$rawData'] = dataItemOrObservable;
|
3000 |
-
self['$data'] = dataItem;
|
3001 |
-
if (dataItemAlias)
|
3002 |
-
self[dataItemAlias] = dataItem;
|
3003 |
-
|
3004 |
-
// The extendCallback function is provided when creating a child context or extending a context.
|
3005 |
-
// It handles the specific actions needed to finish setting up the binding context. Actions in this
|
3006 |
-
// function could also add dependencies to this binding context.
|
3007 |
-
if (extendCallback)
|
3008 |
-
extendCallback(self, parentContext, dataItem);
|
3009 |
-
|
3010 |
-
return self['$data'];
|
3011 |
-
}
|
3012 |
-
function disposeWhen() {
|
3013 |
-
return nodes && !ko.utils.anyDomNodeIsAttachedToDocument(nodes);
|
3014 |
-
}
|
3015 |
-
|
3016 |
-
var self = this,
|
3017 |
-
isFunc = typeof(dataItemOrAccessor) == "function" && !ko.isObservable(dataItemOrAccessor),
|
3018 |
-
nodes,
|
3019 |
-
subscribable = ko.dependentObservable(updateContext, null, { disposeWhen: disposeWhen, disposeWhenNodeIsRemoved: true });
|
3020 |
-
|
3021 |
-
// At this point, the binding context has been initialized, and the "subscribable" computed observable is
|
3022 |
-
// subscribed to any observables that were accessed in the process. If there is nothing to track, the
|
3023 |
-
// computed will be inactive, and we can safely throw it away. If it's active, the computed is stored in
|
3024 |
-
// the context object.
|
3025 |
-
if (subscribable.isActive()) {
|
3026 |
-
self._subscribable = subscribable;
|
3027 |
-
|
3028 |
-
// Always notify because even if the model ($data) hasn't changed, other context properties might have changed
|
3029 |
-
subscribable['equalityComparer'] = null;
|
3030 |
-
|
3031 |
-
// We need to be able to dispose of this computed observable when it's no longer needed. This would be
|
3032 |
-
// easy if we had a single node to watch, but binding contexts can be used by many different nodes, and
|
3033 |
-
// we cannot assume that those nodes have any relation to each other. So instead we track any node that
|
3034 |
-
// the context is attached to, and dispose the computed when all of those nodes have been cleaned.
|
3035 |
-
|
3036 |
-
// Add properties to *subscribable* instead of *self* because any properties added to *self* may be overwritten on updates
|
3037 |
-
nodes = [];
|
3038 |
-
subscribable._addNode = function(node) {
|
3039 |
-
nodes.push(node);
|
3040 |
-
ko.utils.domNodeDisposal.addDisposeCallback(node, function(node) {
|
3041 |
-
ko.utils.arrayRemoveItem(nodes, node);
|
3042 |
-
if (!nodes.length) {
|
3043 |
-
subscribable.dispose();
|
3044 |
-
self._subscribable = subscribable = undefined;
|
3045 |
-
}
|
3046 |
-
});
|
3047 |
-
};
|
3048 |
-
}
|
3049 |
-
}
|
3050 |
-
|
3051 |
-
// Extend the binding context hierarchy with a new view model object. If the parent context is watching
|
3052 |
-
// any observables, the new child context will automatically get a dependency on the parent context.
|
3053 |
-
// But this does not mean that the $data value of the child context will also get updated. If the child
|
3054 |
-
// view model also depends on the parent view model, you must provide a function that returns the correct
|
3055 |
-
// view model on each update.
|
3056 |
-
ko.bindingContext.prototype['createChildContext'] = function (dataItemOrAccessor, dataItemAlias, extendCallback) {
|
3057 |
-
return new ko.bindingContext(dataItemOrAccessor, this, dataItemAlias, function(self, parentContext) {
|
3058 |
-
// Extend the context hierarchy by setting the appropriate pointers
|
3059 |
-
self['$parentContext'] = parentContext;
|
3060 |
-
self['$parent'] = parentContext['$data'];
|
3061 |
-
self['$parents'] = (parentContext['$parents'] || []).slice(0);
|
3062 |
-
self['$parents'].unshift(self['$parent']);
|
3063 |
-
if (extendCallback)
|
3064 |
-
extendCallback(self);
|
3065 |
-
});
|
3066 |
-
};
|
3067 |
-
|
3068 |
-
// Extend the binding context with new custom properties. This doesn't change the context hierarchy.
|
3069 |
-
// Similarly to "child" contexts, provide a function here to make sure that the correct values are set
|
3070 |
-
// when an observable view model is updated.
|
3071 |
-
ko.bindingContext.prototype['extend'] = function(properties) {
|
3072 |
-
// If the parent context references an observable view model, "_subscribable" will always be the
|
3073 |
-
// latest view model object. If not, "_subscribable" isn't set, and we can use the static "$data" value.
|
3074 |
-
return new ko.bindingContext(this._subscribable || this['$data'], this, null, function(self, parentContext) {
|
3075 |
-
// This "child" context doesn't directly track a parent observable view model,
|
3076 |
-
// so we need to manually set the $rawData value to match the parent.
|
3077 |
-
self['$rawData'] = parentContext['$rawData'];
|
3078 |
-
ko.utils.extend(self, typeof(properties) == "function" ? properties() : properties);
|
3079 |
-
});
|
3080 |
-
};
|
3081 |
-
|
3082 |
-
// Returns the valueAccesor function for a binding value
|
3083 |
-
function makeValueAccessor(value) {
|
3084 |
-
return function() {
|
3085 |
-
return value;
|
3086 |
-
};
|
3087 |
-
}
|
3088 |
-
|
3089 |
-
// Returns the value of a valueAccessor function
|
3090 |
-
function evaluateValueAccessor(valueAccessor) {
|
3091 |
-
return valueAccessor();
|
3092 |
-
}
|
3093 |
-
|
3094 |
-
// Given a function that returns bindings, create and return a new object that contains
|
3095 |
-
// binding value-accessors functions. Each accessor function calls the original function
|
3096 |
-
// so that it always gets the latest value and all dependencies are captured. This is used
|
3097 |
-
// by ko.applyBindingsToNode and getBindingsAndMakeAccessors.
|
3098 |
-
function makeAccessorsFromFunction(callback) {
|
3099 |
-
return ko.utils.objectMap(ko.dependencyDetection.ignore(callback), function(value, key) {
|
3100 |
-
return function() {
|
3101 |
-
return callback()[key];
|
3102 |
-
};
|
3103 |
-
});
|
3104 |
-
}
|
3105 |
-
|
3106 |
-
// Given a bindings function or object, create and return a new object that contains
|
3107 |
-
// binding value-accessors functions. This is used by ko.applyBindingsToNode.
|
3108 |
-
function makeBindingAccessors(bindings, context, node) {
|
3109 |
-
if (typeof bindings === 'function') {
|
3110 |
-
return makeAccessorsFromFunction(bindings.bind(null, context, node));
|
3111 |
-
} else {
|
3112 |
-
return ko.utils.objectMap(bindings, makeValueAccessor);
|
3113 |
-
}
|
3114 |
-
}
|
3115 |
-
|
3116 |
-
// This function is used if the binding provider doesn't include a getBindingAccessors function.
|
3117 |
-
// It must be called with 'this' set to the provider instance.
|
3118 |
-
function getBindingsAndMakeAccessors(node, context) {
|
3119 |
-
return makeAccessorsFromFunction(this['getBindings'].bind(this, node, context));
|
3120 |
-
}
|
3121 |
-
|
3122 |
-
function validateThatBindingIsAllowedForVirtualElements(bindingName) {
|
3123 |
-
var validator = ko.virtualElements.allowedBindings[bindingName];
|
3124 |
-
if (!validator)
|
3125 |
-
throw new Error("The binding '" + bindingName + "' cannot be used with virtual elements")
|
3126 |
-
}
|
3127 |
-
|
3128 |
-
function applyBindingsToDescendantsInternal (bindingContext, elementOrVirtualElement, bindingContextsMayDifferFromDomParentElement) {
|
3129 |
-
var currentChild,
|
3130 |
-
nextInQueue = ko.virtualElements.firstChild(elementOrVirtualElement),
|
3131 |
-
provider = ko.bindingProvider['instance'],
|
3132 |
-
preprocessNode = provider['preprocessNode'];
|
3133 |
-
|
3134 |
-
// Preprocessing allows a binding provider to mutate a node before bindings are applied to it. For example it's
|
3135 |
-
// possible to insert new siblings after it, and/or replace the node with a different one. This can be used to
|
3136 |
-
// implement custom binding syntaxes, such as {{ value }} for string interpolation, or custom element types that
|
3137 |
-
// trigger insertion of <template> contents at that point in the document.
|
3138 |
-
if (preprocessNode) {
|
3139 |
-
while (currentChild = nextInQueue) {
|
3140 |
-
nextInQueue = ko.virtualElements.nextSibling(currentChild);
|
3141 |
-
preprocessNode.call(provider, currentChild);
|
3142 |
-
}
|
3143 |
-
// Reset nextInQueue for the next loop
|
3144 |
-
nextInQueue = ko.virtualElements.firstChild(elementOrVirtualElement);
|
3145 |
-
}
|
3146 |
-
|
3147 |
-
while (currentChild = nextInQueue) {
|
3148 |
-
// Keep a record of the next child *before* applying bindings, in case the binding removes the current child from its position
|
3149 |
-
nextInQueue = ko.virtualElements.nextSibling(currentChild);
|
3150 |
-
applyBindingsToNodeAndDescendantsInternal(bindingContext, currentChild, bindingContextsMayDifferFromDomParentElement);
|
3151 |
-
}
|
3152 |
-
}
|
3153 |
-
|
3154 |
-
function applyBindingsToNodeAndDescendantsInternal (bindingContext, nodeVerified, bindingContextMayDifferFromDomParentElement) {
|
3155 |
-
var shouldBindDescendants = true;
|
3156 |
-
|
3157 |
-
// Perf optimisation: Apply bindings only if...
|
3158 |
-
// (1) We need to store the binding context on this node (because it may differ from the DOM parent node's binding context)
|
3159 |
-
// Note that we can't store binding contexts on non-elements (e.g., text nodes), as IE doesn't allow expando properties for those
|
3160 |
-
// (2) It might have bindings (e.g., it has a data-bind attribute, or it's a marker for a containerless template)
|
3161 |
-
var isElement = (nodeVerified.nodeType === 1);
|
3162 |
-
if (isElement) // Workaround IE <= 8 HTML parsing weirdness
|
3163 |
-
ko.virtualElements.normaliseVirtualElementDomStructure(nodeVerified);
|
3164 |
-
|
3165 |
-
var shouldApplyBindings = (isElement && bindingContextMayDifferFromDomParentElement) // Case (1)
|
3166 |
-
|| ko.bindingProvider['instance']['nodeHasBindings'](nodeVerified); // Case (2)
|
3167 |
-
if (shouldApplyBindings)
|
3168 |
-
shouldBindDescendants = applyBindingsToNodeInternal(nodeVerified, null, bindingContext, bindingContextMayDifferFromDomParentElement)['shouldBindDescendants'];
|
3169 |
-
|
3170 |
-
if (shouldBindDescendants && !bindingDoesNotRecurseIntoElementTypes[ko.utils.tagNameLower(nodeVerified)]) {
|
3171 |
-
// We're recursing automatically into (real or virtual) child nodes without changing binding contexts. So,
|
3172 |
-
// * For children of a *real* element, the binding context is certainly the same as on their DOM .parentNode,
|
3173 |
-
// hence bindingContextsMayDifferFromDomParentElement is false
|
3174 |
-
// * For children of a *virtual* element, we can't be sure. Evaluating .parentNode on those children may
|
3175 |
-
// skip over any number of intermediate virtual elements, any of which might define a custom binding context,
|
3176 |
-
// hence bindingContextsMayDifferFromDomParentElement is true
|
3177 |
-
applyBindingsToDescendantsInternal(bindingContext, nodeVerified, /* bindingContextsMayDifferFromDomParentElement: */ !isElement);
|
3178 |
-
}
|
3179 |
-
}
|
3180 |
-
|
3181 |
-
var boundElementDomDataKey = ko.utils.domData.nextKey();
|
3182 |
-
|
3183 |
-
|
3184 |
-
function topologicalSortBindings(bindings) {
|
3185 |
-
// Depth-first sort
|
3186 |
-
var result = [], // The list of key/handler pairs that we will return
|
3187 |
-
bindingsConsidered = {}, // A temporary record of which bindings are already in 'result'
|
3188 |
-
cyclicDependencyStack = []; // Keeps track of a depth-search so that, if there's a cycle, we know which bindings caused it
|
3189 |
-
ko.utils.objectForEach(bindings, function pushBinding(bindingKey) {
|
3190 |
-
if (!bindingsConsidered[bindingKey]) {
|
3191 |
-
var binding = ko['getBindingHandler'](bindingKey);
|
3192 |
-
if (binding) {
|
3193 |
-
// First add dependencies (if any) of the current binding
|
3194 |
-
if (binding['after']) {
|
3195 |
-
cyclicDependencyStack.push(bindingKey);
|
3196 |
-
ko.utils.arrayForEach(binding['after'], function(bindingDependencyKey) {
|
3197 |
-
if (bindings[bindingDependencyKey]) {
|
3198 |
-
if (ko.utils.arrayIndexOf(cyclicDependencyStack, bindingDependencyKey) !== -1) {
|
3199 |
-
throw Error("Cannot combine the following bindings, because they have a cyclic dependency: " + cyclicDependencyStack.join(", "));
|
3200 |
-
} else {
|
3201 |
-
pushBinding(bindingDependencyKey);
|
3202 |
-
}
|
3203 |
-
}
|
3204 |
-
});
|
3205 |
-
cyclicDependencyStack.length--;
|
3206 |
-
}
|
3207 |
-
// Next add the current binding
|
3208 |
-
result.push({ key: bindingKey, handler: binding });
|
3209 |
-
}
|
3210 |
-
bindingsConsidered[bindingKey] = true;
|
3211 |
-
}
|
3212 |
-
});
|
3213 |
-
|
3214 |
-
return result;
|
3215 |
-
}
|
3216 |
-
|
3217 |
-
function applyBindingsToNodeInternal(node, sourceBindings, bindingContext, bindingContextMayDifferFromDomParentElement) {
|
3218 |
-
// Prevent multiple applyBindings calls for the same node, except when a binding value is specified
|
3219 |
-
var alreadyBound = ko.utils.domData.get(node, boundElementDomDataKey);
|
3220 |
-
if (!sourceBindings) {
|
3221 |
-
if (alreadyBound) {
|
3222 |
-
throw Error("You cannot apply bindings multiple times to the same element.");
|
3223 |
-
}
|
3224 |
-
ko.utils.domData.set(node, boundElementDomDataKey, true);
|
3225 |
-
}
|
3226 |
-
|
3227 |
-
// Optimization: Don't store the binding context on this node if it's definitely the same as on node.parentNode, because
|
3228 |
-
// we can easily recover it just by scanning up the node's ancestors in the DOM
|
3229 |
-
// (note: here, parent node means "real DOM parent" not "virtual parent", as there's no O(1) way to find the virtual parent)
|
3230 |
-
if (!alreadyBound && bindingContextMayDifferFromDomParentElement)
|
3231 |
-
ko.storedBindingContextForNode(node, bindingContext);
|
3232 |
-
|
3233 |
-
// Use bindings if given, otherwise fall back on asking the bindings provider to give us some bindings
|
3234 |
-
var bindings;
|
3235 |
-
if (sourceBindings && typeof sourceBindings !== 'function') {
|
3236 |
-
bindings = sourceBindings;
|
3237 |
-
} else {
|
3238 |
-
var provider = ko.bindingProvider['instance'],
|
3239 |
-
getBindings = provider['getBindingAccessors'] || getBindingsAndMakeAccessors;
|
3240 |
-
|
3241 |
-
// Get the binding from the provider within a computed observable so that we can update the bindings whenever
|
3242 |
-
// the binding context is updated or if the binding provider accesses observables.
|
3243 |
-
var bindingsUpdater = ko.dependentObservable(
|
3244 |
-
function() {
|
3245 |
-
bindings = sourceBindings ? sourceBindings(bindingContext, node) : getBindings.call(provider, node, bindingContext);
|
3246 |
-
// Register a dependency on the binding context to support observable view models.
|
3247 |
-
if (bindings && bindingContext._subscribable)
|
3248 |
-
bindingContext._subscribable();
|
3249 |
-
return bindings;
|
3250 |
-
},
|
3251 |
-
null, { disposeWhenNodeIsRemoved: node }
|
3252 |
-
);
|
3253 |
-
|
3254 |
-
if (!bindings || !bindingsUpdater.isActive())
|
3255 |
-
bindingsUpdater = null;
|
3256 |
-
}
|
3257 |
-
|
3258 |
-
var bindingHandlerThatControlsDescendantBindings;
|
3259 |
-
if (bindings) {
|
3260 |
-
// Return the value accessor for a given binding. When bindings are static (won't be updated because of a binding
|
3261 |
-
// context update), just return the value accessor from the binding. Otherwise, return a function that always gets
|
3262 |
-
// the latest binding value and registers a dependency on the binding updater.
|
3263 |
-
var getValueAccessor = bindingsUpdater
|
3264 |
-
? function(bindingKey) {
|
3265 |
-
return function() {
|
3266 |
-
return evaluateValueAccessor(bindingsUpdater()[bindingKey]);
|
3267 |
-
};
|
3268 |
-
} : function(bindingKey) {
|
3269 |
-
return bindings[bindingKey];
|
3270 |
-
};
|
3271 |
-
|
3272 |
-
// Use of allBindings as a function is maintained for backwards compatibility, but its use is deprecated
|
3273 |
-
function allBindings() {
|
3274 |
-
return ko.utils.objectMap(bindingsUpdater ? bindingsUpdater() : bindings, evaluateValueAccessor);
|
3275 |
-
}
|
3276 |
-
// The following is the 3.x allBindings API
|
3277 |
-
allBindings['get'] = function(key) {
|
3278 |
-
return bindings[key] && evaluateValueAccessor(getValueAccessor(key));
|
3279 |
-
};
|
3280 |
-
allBindings['has'] = function(key) {
|
3281 |
-
return key in bindings;
|
3282 |
-
};
|
3283 |
-
|
3284 |
-
// First put the bindings into the right order
|
3285 |
-
var orderedBindings = topologicalSortBindings(bindings);
|
3286 |
-
|
3287 |
-
// Go through the sorted bindings, calling init and update for each
|
3288 |
-
ko.utils.arrayForEach(orderedBindings, function(bindingKeyAndHandler) {
|
3289 |
-
// Note that topologicalSortBindings has already filtered out any nonexistent binding handlers,
|
3290 |
-
// so bindingKeyAndHandler.handler will always be nonnull.
|
3291 |
-
var handlerInitFn = bindingKeyAndHandler.handler["init"],
|
3292 |
-
handlerUpdateFn = bindingKeyAndHandler.handler["update"],
|
3293 |
-
bindingKey = bindingKeyAndHandler.key;
|
3294 |
-
|
3295 |
-
if (node.nodeType === 8) {
|
3296 |
-
validateThatBindingIsAllowedForVirtualElements(bindingKey);
|
3297 |
-
}
|
3298 |
-
|
3299 |
-
try {
|
3300 |
-
// Run init, ignoring any dependencies
|
3301 |
-
if (typeof handlerInitFn == "function") {
|
3302 |
-
ko.dependencyDetection.ignore(function() {
|
3303 |
-
var initResult = handlerInitFn(node, getValueAccessor(bindingKey), allBindings, bindingContext['$data'], bindingContext);
|
3304 |
-
|
3305 |
-
// If this binding handler claims to control descendant bindings, make a note of this
|
3306 |
-
if (initResult && initResult['controlsDescendantBindings']) {
|
3307 |
-
if (bindingHandlerThatControlsDescendantBindings !== undefined)
|
3308 |
-
throw new Error("Multiple bindings (" + bindingHandlerThatControlsDescendantBindings + " and " + bindingKey + ") are trying to control descendant bindings of the same element. You cannot use these bindings together on the same element.");
|
3309 |
-
bindingHandlerThatControlsDescendantBindings = bindingKey;
|
3310 |
-
}
|
3311 |
-
});
|
3312 |
-
}
|
3313 |
-
|
3314 |
-
// Run update in its own computed wrapper
|
3315 |
-
if (typeof handlerUpdateFn == "function") {
|
3316 |
-
ko.dependentObservable(
|
3317 |
-
function() {
|
3318 |
-
handlerUpdateFn(node, getValueAccessor(bindingKey), allBindings, bindingContext['$data'], bindingContext);
|
3319 |
-
},
|
3320 |
-
null,
|
3321 |
-
{ disposeWhenNodeIsRemoved: node }
|
3322 |
-
);
|
3323 |
-
}
|
3324 |
-
} catch (ex) {
|
3325 |
-
ex.message = "Unable to process binding \"" + bindingKey + ": " + bindings[bindingKey] + "\"\nMessage: " + ex.message;
|
3326 |
-
throw ex;
|
3327 |
-
}
|
3328 |
-
});
|
3329 |
-
}
|
3330 |
-
|
3331 |
-
return {
|
3332 |
-
'shouldBindDescendants': bindingHandlerThatControlsDescendantBindings === undefined
|
3333 |
-
};
|
3334 |
-
};
|
3335 |
-
|
3336 |
-
var storedBindingContextDomDataKey = ko.utils.domData.nextKey();
|
3337 |
-
ko.storedBindingContextForNode = function (node, bindingContext) {
|
3338 |
-
if (arguments.length == 2) {
|
3339 |
-
ko.utils.domData.set(node, storedBindingContextDomDataKey, bindingContext);
|
3340 |
-
if (bindingContext._subscribable)
|
3341 |
-
bindingContext._subscribable._addNode(node);
|
3342 |
-
} else {
|
3343 |
-
return ko.utils.domData.get(node, storedBindingContextDomDataKey);
|
3344 |
-
}
|
3345 |
-
}
|
3346 |
-
|
3347 |
-
function getBindingContext(viewModelOrBindingContext) {
|
3348 |
-
return viewModelOrBindingContext && (viewModelOrBindingContext instanceof ko.bindingContext)
|
3349 |
-
? viewModelOrBindingContext
|
3350 |
-
: new ko.bindingContext(viewModelOrBindingContext);
|
3351 |
-
}
|
3352 |
-
|
3353 |
-
ko.applyBindingAccessorsToNode = function (node, bindings, viewModelOrBindingContext) {
|
3354 |
-
if (node.nodeType === 1) // If it's an element, workaround IE <= 8 HTML parsing weirdness
|
3355 |
-
ko.virtualElements.normaliseVirtualElementDomStructure(node);
|
3356 |
-
return applyBindingsToNodeInternal(node, bindings, getBindingContext(viewModelOrBindingContext), true);
|
3357 |
-
};
|
3358 |
-
|
3359 |
-
ko.applyBindingsToNode = function (node, bindings, viewModelOrBindingContext) {
|
3360 |
-
var context = getBindingContext(viewModelOrBindingContext);
|
3361 |
-
return ko.applyBindingAccessorsToNode(node, makeBindingAccessors(bindings, context, node), context);
|
3362 |
-
};
|
3363 |
-
|
3364 |
-
ko.applyBindingsToDescendants = function(viewModelOrBindingContext, rootNode) {
|
3365 |
-
if (rootNode.nodeType === 1 || rootNode.nodeType === 8)
|
3366 |
-
applyBindingsToDescendantsInternal(getBindingContext(viewModelOrBindingContext), rootNode, true);
|
3367 |
-
};
|
3368 |
-
|
3369 |
-
ko.applyBindings = function (viewModelOrBindingContext, rootNode) {
|
3370 |
-
// If jQuery is loaded after Knockout, we won't initially have access to it. So save it here.
|
3371 |
-
if (!jQueryInstance && window['jQuery']) {
|
3372 |
-
jQueryInstance = window['jQuery'];
|
3373 |
-
}
|
3374 |
-
|
3375 |
-
if (rootNode && (rootNode.nodeType !== 1) && (rootNode.nodeType !== 8))
|
3376 |
-
throw new Error("ko.applyBindings: first parameter should be your view model; second parameter should be a DOM node");
|
3377 |
-
rootNode = rootNode || window.document.body; // Make "rootNode" parameter optional
|
3378 |
-
|
3379 |
-
applyBindingsToNodeAndDescendantsInternal(getBindingContext(viewModelOrBindingContext), rootNode, true);
|
3380 |
-
};
|
3381 |
-
|
3382 |
-
// Retrieving binding context from arbitrary nodes
|
3383 |
-
ko.contextFor = function(node) {
|
3384 |
-
// We can only do something meaningful for elements and comment nodes (in particular, not text nodes, as IE can't store domdata for them)
|
3385 |
-
switch (node.nodeType) {
|
3386 |
-
case 1:
|
3387 |
-
case 8:
|
3388 |
-
var context = ko.storedBindingContextForNode(node);
|
3389 |
-
if (context) return context;
|
3390 |
-
if (node.parentNode) return ko.contextFor(node.parentNode);
|
3391 |
-
break;
|
3392 |
-
}
|
3393 |
-
return undefined;
|
3394 |
-
};
|
3395 |
-
ko.dataFor = function(node) {
|
3396 |
-
var context = ko.contextFor(node);
|
3397 |
-
return context ? context['$data'] : undefined;
|
3398 |
-
};
|
3399 |
-
|
3400 |
-
ko.exportSymbol('bindingHandlers', ko.bindingHandlers);
|
3401 |
-
ko.exportSymbol('applyBindings', ko.applyBindings);
|
3402 |
-
ko.exportSymbol('applyBindingsToDescendants', ko.applyBindingsToDescendants);
|
3403 |
-
ko.exportSymbol('applyBindingAccessorsToNode', ko.applyBindingAccessorsToNode);
|
3404 |
-
ko.exportSymbol('applyBindingsToNode', ko.applyBindingsToNode);
|
3405 |
-
ko.exportSymbol('contextFor', ko.contextFor);
|
3406 |
-
ko.exportSymbol('dataFor', ko.dataFor);
|
3407 |
-
})();
|
3408 |
-
(function(undefined) {
|
3409 |
-
var loadingSubscribablesCache = {}, // Tracks component loads that are currently in flight
|
3410 |
-
loadedDefinitionsCache = {}; // Tracks component loads that have already completed
|
3411 |
-
|
3412 |
-
ko.components = {
|
3413 |
-
get: function(componentName, callback) {
|
3414 |
-
var cachedDefinition = getObjectOwnProperty(loadedDefinitionsCache, componentName);
|
3415 |
-
if (cachedDefinition) {
|
3416 |
-
// It's already loaded and cached. Reuse the same definition object.
|
3417 |
-
// Note that for API consistency, even cache hits complete asynchronously by default.
|
3418 |
-
// You can bypass this by putting synchronous:true on your component config.
|
3419 |
-
if (cachedDefinition.isSynchronousComponent) {
|
3420 |
-
ko.dependencyDetection.ignore(function() { // See comment in loaderRegistryBehaviors.js for reasoning
|
3421 |
-
callback(cachedDefinition.definition);
|
3422 |
-
});
|
3423 |
-
} else {
|
3424 |
-
ko.tasks.schedule(function() { callback(cachedDefinition.definition); });
|
3425 |
-
}
|
3426 |
-
} else {
|
3427 |
-
// Join the loading process that is already underway, or start a new one.
|
3428 |
-
loadComponentAndNotify(componentName, callback);
|
3429 |
-
}
|
3430 |
-
},
|
3431 |
-
|
3432 |
-
clearCachedDefinition: function(componentName) {
|
3433 |
-
delete loadedDefinitionsCache[componentName];
|
3434 |
-
},
|
3435 |
-
|
3436 |
-
_getFirstResultFromLoaders: getFirstResultFromLoaders
|
3437 |
-
};
|
3438 |
-
|
3439 |
-
function getObjectOwnProperty(obj, propName) {
|
3440 |
-
return obj.hasOwnProperty(propName) ? obj[propName] : undefined;
|
3441 |
-
}
|
3442 |
-
|
3443 |
-
function loadComponentAndNotify(componentName, callback) {
|
3444 |
-
var subscribable = getObjectOwnProperty(loadingSubscribablesCache, componentName),
|
3445 |
-
completedAsync;
|
3446 |
-
if (!subscribable) {
|
3447 |
-
// It's not started loading yet. Start loading, and when it's done, move it to loadedDefinitionsCache.
|
3448 |
-
subscribable = loadingSubscribablesCache[componentName] = new ko.subscribable();
|
3449 |
-
subscribable.subscribe(callback);
|
3450 |
-
|
3451 |
-
beginLoadingComponent(componentName, function(definition, config) {
|
3452 |
-
var isSynchronousComponent = !!(config && config['synchronous']);
|
3453 |
-
loadedDefinitionsCache[componentName] = { definition: definition, isSynchronousComponent: isSynchronousComponent };
|
3454 |
-
delete loadingSubscribablesCache[componentName];
|
3455 |
-
|
3456 |
-
// For API consistency, all loads complete asynchronously. However we want to avoid
|
3457 |
-
// adding an extra task schedule if it's unnecessary (i.e., the completion is already
|
3458 |
-
// async).
|
3459 |
-
//
|
3460 |
-
// You can bypass the 'always asynchronous' feature by putting the synchronous:true
|
3461 |
-
// flag on your component configuration when you register it.
|
3462 |
-
if (completedAsync || isSynchronousComponent) {
|
3463 |
-
// Note that notifySubscribers ignores any dependencies read within the callback.
|
3464 |
-
// See comment in loaderRegistryBehaviors.js for reasoning
|
3465 |
-
subscribable['notifySubscribers'](definition);
|
3466 |
-
} else {
|
3467 |
-
ko.tasks.schedule(function() {
|
3468 |
-
subscribable['notifySubscribers'](definition);
|
3469 |
-
});
|
3470 |
-
}
|
3471 |
-
});
|
3472 |
-
completedAsync = true;
|
3473 |
-
} else {
|
3474 |
-
subscribable.subscribe(callback);
|
3475 |
-
}
|
3476 |
-
}
|
3477 |
-
|
3478 |
-
function beginLoadingComponent(componentName, callback) {
|
3479 |
-
getFirstResultFromLoaders('getConfig', [componentName], function(config) {
|
3480 |
-
if (config) {
|
3481 |
-
// We have a config, so now load its definition
|
3482 |
-
getFirstResultFromLoaders('loadComponent', [componentName, config], function(definition) {
|
3483 |
-
callback(definition, config);
|
3484 |
-
});
|
3485 |
-
} else {
|
3486 |
-
// The component has no config - it's unknown to all the loaders.
|
3487 |
-
// Note that this is not an error (e.g., a module loading error) - that would abort the
|
3488 |
-
// process and this callback would not run. For this callback to run, all loaders must
|
3489 |
-
// have confirmed they don't know about this component.
|
3490 |
-
callback(null, null);
|
3491 |
-
}
|
3492 |
-
});
|
3493 |
-
}
|
3494 |
-
|
3495 |
-
function getFirstResultFromLoaders(methodName, argsExceptCallback, callback, candidateLoaders) {
|
3496 |
-
// On the first call in the stack, start with the full set of loaders
|
3497 |
-
if (!candidateLoaders) {
|
3498 |
-
candidateLoaders = ko.components['loaders'].slice(0); // Use a copy, because we'll be mutating this array
|
3499 |
-
}
|
3500 |
-
|
3501 |
-
// Try the next candidate
|
3502 |
-
var currentCandidateLoader = candidateLoaders.shift();
|
3503 |
-
if (currentCandidateLoader) {
|
3504 |
-
var methodInstance = currentCandidateLoader[methodName];
|
3505 |
-
if (methodInstance) {
|
3506 |
-
var wasAborted = false,
|
3507 |
-
synchronousReturnValue = methodInstance.apply(currentCandidateLoader, argsExceptCallback.concat(function(result) {
|
3508 |
-
if (wasAborted) {
|
3509 |
-
callback(null);
|
3510 |
-
} else if (result !== null) {
|
3511 |
-
// This candidate returned a value. Use it.
|
3512 |
-
callback(result);
|
3513 |
-
} else {
|
3514 |
-
// Try the next candidate
|
3515 |
-
getFirstResultFromLoaders(methodName, argsExceptCallback, callback, candidateLoaders);
|
3516 |
-
}
|
3517 |
-
}));
|
3518 |
-
|
3519 |
-
// Currently, loaders may not return anything synchronously. This leaves open the possibility
|
3520 |
-
// that we'll extend the API to support synchronous return values in the future. It won't be
|
3521 |
-
// a breaking change, because currently no loader is allowed to return anything except undefined.
|
3522 |
-
if (synchronousReturnValue !== undefined) {
|
3523 |
-
wasAborted = true;
|
3524 |
-
|
3525 |
-
// Method to suppress exceptions will remain undocumented. This is only to keep
|
3526 |
-
// KO's specs running tidily, since we can observe the loading got aborted without
|
3527 |
-
// having exceptions cluttering up the console too.
|
3528 |
-
if (!currentCandidateLoader['suppressLoaderExceptions']) {
|
3529 |
-
throw new Error('Component loaders must supply values by invoking the callback, not by returning values synchronously.');
|
3530 |
-
}
|
3531 |
-
}
|
3532 |
-
} else {
|
3533 |
-
// This candidate doesn't have the relevant handler. Synchronously move on to the next one.
|
3534 |
-
getFirstResultFromLoaders(methodName, argsExceptCallback, callback, candidateLoaders);
|
3535 |
-
}
|
3536 |
-
} else {
|
3537 |
-
// No candidates returned a value
|
3538 |
-
callback(null);
|
3539 |
-
}
|
3540 |
-
}
|
3541 |
-
|
3542 |
-
// Reference the loaders via string name so it's possible for developers
|
3543 |
-
// to replace the whole array by assigning to ko.components.loaders
|
3544 |
-
ko.components['loaders'] = [];
|
3545 |
-
|
3546 |
-
ko.exportSymbol('components', ko.components);
|
3547 |
-
ko.exportSymbol('components.get', ko.components.get);
|
3548 |
-
ko.exportSymbol('components.clearCachedDefinition', ko.components.clearCachedDefinition);
|
3549 |
-
})();
|
3550 |
-
(function(undefined) {
|
3551 |
-
|
3552 |
-
// The default loader is responsible for two things:
|
3553 |
-
// 1. Maintaining the default in-memory registry of component configuration objects
|
3554 |
-
// (i.e., the thing you're writing to when you call ko.components.register(someName, ...))
|
3555 |
-
// 2. Answering requests for components by fetching configuration objects
|
3556 |
-
// from that default in-memory registry and resolving them into standard
|
3557 |
-
// component definition objects (of the form { createViewModel: ..., template: ... })
|
3558 |
-
// Custom loaders may override either of these facilities, i.e.,
|
3559 |
-
// 1. To supply configuration objects from some other source (e.g., conventions)
|
3560 |
-
// 2. Or, to resolve configuration objects by loading viewmodels/templates via arbitrary logic.
|
3561 |
-
|
3562 |
-
var defaultConfigRegistry = {};
|
3563 |
-
|
3564 |
-
ko.components.register = function(componentName, config) {
|
3565 |
-
if (!config) {
|
3566 |
-
throw new Error('Invalid configuration for ' + componentName);
|
3567 |
-
}
|
3568 |
-
|
3569 |
-
if (ko.components.isRegistered(componentName)) {
|
3570 |
-
throw new Error('Component ' + componentName + ' is already registered');
|
3571 |
-
}
|
3572 |
-
|
3573 |
-
defaultConfigRegistry[componentName] = config;
|
3574 |
-
};
|
3575 |
-
|
3576 |
-
ko.components.isRegistered = function(componentName) {
|
3577 |
-
return defaultConfigRegistry.hasOwnProperty(componentName);
|
3578 |
-
};
|
3579 |
-
|
3580 |
-
ko.components.unregister = function(componentName) {
|
3581 |
-
delete defaultConfigRegistry[componentName];
|
3582 |
-
ko.components.clearCachedDefinition(componentName);
|
3583 |
-
};
|
3584 |
-
|
3585 |
-
ko.components.defaultLoader = {
|
3586 |
-
'getConfig': function(componentName, callback) {
|
3587 |
-
var result = defaultConfigRegistry.hasOwnProperty(componentName)
|
3588 |
-
? defaultConfigRegistry[componentName]
|
3589 |
-
: null;
|
3590 |
-
callback(result);
|
3591 |
-
},
|
3592 |
-
|
3593 |
-
'loadComponent': function(componentName, config, callback) {
|
3594 |
-
var errorCallback = makeErrorCallback(componentName);
|
3595 |
-
possiblyGetConfigFromAmd(errorCallback, config, function(loadedConfig) {
|
3596 |
-
resolveConfig(componentName, errorCallback, loadedConfig, callback);
|
3597 |
-
});
|
3598 |
-
},
|
3599 |
-
|
3600 |
-
'loadTemplate': function(componentName, templateConfig, callback) {
|
3601 |
-
resolveTemplate(makeErrorCallback(componentName), templateConfig, callback);
|
3602 |
-
},
|
3603 |
-
|
3604 |
-
'loadViewModel': function(componentName, viewModelConfig, callback) {
|
3605 |
-
resolveViewModel(makeErrorCallback(componentName), viewModelConfig, callback);
|
3606 |
-
}
|
3607 |
-
};
|
3608 |
-
|
3609 |
-
var createViewModelKey = 'createViewModel';
|
3610 |
-
|
3611 |
-
// Takes a config object of the form { template: ..., viewModel: ... }, and asynchronously convert it
|
3612 |
-
// into the standard component definition format:
|
3613 |
-
// { template: <ArrayOfDomNodes>, createViewModel: function(params, componentInfo) { ... } }.
|
3614 |
-
// Since both template and viewModel may need to be resolved asynchronously, both tasks are performed
|
3615 |
-
// in parallel, and the results joined when both are ready. We don't depend on any promises infrastructure,
|
3616 |
-
// so this is implemented manually below.
|
3617 |
-
function resolveConfig(componentName, errorCallback, config, callback) {
|
3618 |
-
var result = {},
|
3619 |
-
makeCallBackWhenZero = 2,
|
3620 |
-
tryIssueCallback = function() {
|
3621 |
-
if (--makeCallBackWhenZero === 0) {
|
3622 |
-
callback(result);
|
3623 |
-
}
|
3624 |
-
},
|
3625 |
-
templateConfig = config['template'],
|
3626 |
-
viewModelConfig = config['viewModel'];
|
3627 |
-
|
3628 |
-
if (templateConfig) {
|
3629 |
-
possiblyGetConfigFromAmd(errorCallback, templateConfig, function(loadedConfig) {
|
3630 |
-
ko.components._getFirstResultFromLoaders('loadTemplate', [componentName, loadedConfig], function(resolvedTemplate) {
|
3631 |
-
result['template'] = resolvedTemplate;
|
3632 |
-
tryIssueCallback();
|
3633 |
-
});
|
3634 |
-
});
|
3635 |
-
} else {
|
3636 |
-
tryIssueCallback();
|
3637 |
-
}
|
3638 |
-
|
3639 |
-
if (viewModelConfig) {
|
3640 |
-
possiblyGetConfigFromAmd(errorCallback, viewModelConfig, function(loadedConfig) {
|
3641 |
-
ko.components._getFirstResultFromLoaders('loadViewModel', [componentName, loadedConfig], function(resolvedViewModel) {
|
3642 |
-
result[createViewModelKey] = resolvedViewModel;
|
3643 |
-
tryIssueCallback();
|
3644 |
-
});
|
3645 |
-
});
|
3646 |
-
} else {
|
3647 |
-
tryIssueCallback();
|
3648 |
-
}
|
3649 |
-
}
|
3650 |
-
|
3651 |
-
function resolveTemplate(errorCallback, templateConfig, callback) {
|
3652 |
-
if (typeof templateConfig === 'string') {
|
3653 |
-
// Markup - parse it
|
3654 |
-
callback(ko.utils.parseHtmlFragment(templateConfig));
|
3655 |
-
} else if (templateConfig instanceof Array) {
|
3656 |
-
// Assume already an array of DOM nodes - pass through unchanged
|
3657 |
-
callback(templateConfig);
|
3658 |
-
} else if (isDocumentFragment(templateConfig)) {
|
3659 |
-
// Document fragment - use its child nodes
|
3660 |
-
callback(ko.utils.makeArray(templateConfig.childNodes));
|
3661 |
-
} else if (templateConfig['element']) {
|
3662 |
-
var element = templateConfig['element'];
|
3663 |
-
if (isDomElement(element)) {
|
3664 |
-
// Element instance - copy its child nodes
|
3665 |
-
callback(cloneNodesFromTemplateSourceElement(element));
|
3666 |
-
} else if (typeof element === 'string') {
|
3667 |
-
// Element ID - find it, then copy its child nodes
|
3668 |
-
var elemInstance = document.getElementById(element);
|
3669 |
-
if (elemInstance) {
|
3670 |
-
callback(cloneNodesFromTemplateSourceElement(elemInstance));
|
3671 |
-
} else {
|
3672 |
-
errorCallback('Cannot find element with ID ' + element);
|
3673 |
-
}
|
3674 |
-
} else {
|
3675 |
-
errorCallback('Unknown element type: ' + element);
|
3676 |
-
}
|
3677 |
-
} else {
|
3678 |
-
errorCallback('Unknown template value: ' + templateConfig);
|
3679 |
-
}
|
3680 |
-
}
|
3681 |
-
|
3682 |
-
function resolveViewModel(errorCallback, viewModelConfig, callback) {
|
3683 |
-
if (typeof viewModelConfig === 'function') {
|
3684 |
-
// Constructor - convert to standard factory function format
|
3685 |
-
// By design, this does *not* supply componentInfo to the constructor, as the intent is that
|
3686 |
-
// componentInfo contains non-viewmodel data (e.g., the component's element) that should only
|
3687 |
-
// be used in factory functions, not viewmodel constructors.
|
3688 |
-
callback(function (params /*, componentInfo */) {
|
3689 |
-
return new viewModelConfig(params);
|
3690 |
-
});
|
3691 |
-
} else if (typeof viewModelConfig[createViewModelKey] === 'function') {
|
3692 |
-
// Already a factory function - use it as-is
|
3693 |
-
callback(viewModelConfig[createViewModelKey]);
|
3694 |
-
} else if ('instance' in viewModelConfig) {
|
3695 |
-
// Fixed object instance - promote to createViewModel format for API consistency
|
3696 |
-
var fixedInstance = viewModelConfig['instance'];
|
3697 |
-
callback(function (params, componentInfo) {
|
3698 |
-
return fixedInstance;
|
3699 |
-
});
|
3700 |
-
} else if ('viewModel' in viewModelConfig) {
|
3701 |
-
// Resolved AMD module whose value is of the form { viewModel: ... }
|
3702 |
-
resolveViewModel(errorCallback, viewModelConfig['viewModel'], callback);
|
3703 |
-
} else {
|
3704 |
-
errorCallback('Unknown viewModel value: ' + viewModelConfig);
|
3705 |
-
}
|
3706 |
-
}
|
3707 |
-
|
3708 |
-
function cloneNodesFromTemplateSourceElement(elemInstance) {
|
3709 |
-
switch (ko.utils.tagNameLower(elemInstance)) {
|
3710 |
-
case 'script':
|
3711 |
-
return ko.utils.parseHtmlFragment(elemInstance.text);
|
3712 |
-
case 'textarea':
|
3713 |
-
return ko.utils.parseHtmlFragment(elemInstance.value);
|
3714 |
-
case 'template':
|
3715 |
-
// For browsers with proper <template> element support (i.e., where the .content property
|
3716 |
-
// gives a document fragment), use that document fragment.
|
3717 |
-
if (isDocumentFragment(elemInstance.content)) {
|
3718 |
-
return ko.utils.cloneNodes(elemInstance.content.childNodes);
|
3719 |
-
}
|
3720 |
-
}
|
3721 |
-
|
3722 |
-
// Regular elements such as <div>, and <template> elements on old browsers that don't really
|
3723 |
-
// understand <template> and just treat it as a regular container
|
3724 |
-
return ko.utils.cloneNodes(elemInstance.childNodes);
|
3725 |
-
}
|
3726 |
-
|
3727 |
-
function isDomElement(obj) {
|
3728 |
-
if (window['HTMLElement']) {
|
3729 |
-
return obj instanceof HTMLElement;
|
3730 |
-
} else {
|
3731 |
-
return obj && obj.tagName && obj.nodeType === 1;
|
3732 |
-
}
|
3733 |
-
}
|
3734 |
-
|
3735 |
-
function isDocumentFragment(obj) {
|
3736 |
-
if (window['DocumentFragment']) {
|
3737 |
-
return obj instanceof DocumentFragment;
|
3738 |
-
} else {
|
3739 |
-
return obj && obj.nodeType === 11;
|
3740 |
-
}
|
3741 |
-
}
|
3742 |
-
|
3743 |
-
function possiblyGetConfigFromAmd(errorCallback, config, callback) {
|
3744 |
-
if (typeof config['require'] === 'string') {
|
3745 |
-
// The config is the value of an AMD module
|
3746 |
-
if (amdRequire || window['require']) {
|
3747 |
-
(amdRequire || window['require'])([config['require']], callback);
|
3748 |
-
} else {
|
3749 |
-
errorCallback('Uses require, but no AMD loader is present');
|
3750 |
-
}
|
3751 |
-
} else {
|
3752 |
-
callback(config);
|
3753 |
-
}
|
3754 |
-
}
|
3755 |
-
|
3756 |
-
function makeErrorCallback(componentName) {
|
3757 |
-
return function (message) {
|
3758 |
-
throw new Error('Component \'' + componentName + '\': ' + message);
|
3759 |
-
};
|
3760 |
-
}
|
3761 |
-
|
3762 |
-
ko.exportSymbol('components.register', ko.components.register);
|
3763 |
-
ko.exportSymbol('components.isRegistered', ko.components.isRegistered);
|
3764 |
-
ko.exportSymbol('components.unregister', ko.components.unregister);
|
3765 |
-
|
3766 |
-
// Expose the default loader so that developers can directly ask it for configuration
|
3767 |
-
// or to resolve configuration
|
3768 |
-
ko.exportSymbol('components.defaultLoader', ko.components.defaultLoader);
|
3769 |
-
|
3770 |
-
// By default, the default loader is the only registered component loader
|
3771 |
-
ko.components['loaders'].push(ko.components.defaultLoader);
|
3772 |
-
|
3773 |
-
// Privately expose the underlying config registry for use in old-IE shim
|
3774 |
-
ko.components._allRegisteredComponents = defaultConfigRegistry;
|
3775 |
-
})();
|
3776 |
-
(function (undefined) {
|
3777 |
-
// Overridable API for determining which component name applies to a given node. By overriding this,
|
3778 |
-
// you can for example map specific tagNames to components that are not preregistered.
|
3779 |
-
ko.components['getComponentNameForNode'] = function(node) {
|
3780 |
-
var tagNameLower = ko.utils.tagNameLower(node);
|
3781 |
-
if (ko.components.isRegistered(tagNameLower)) {
|
3782 |
-
// Try to determine that this node can be considered a *custom* element; see https://github.com/knockout/knockout/issues/1603
|
3783 |
-
if (tagNameLower.indexOf('-') != -1 || ('' + node) == "[object HTMLUnknownElement]" || (ko.utils.ieVersion <= 8 && node.tagName === tagNameLower)) {
|
3784 |
-
return tagNameLower;
|
3785 |
-
}
|
3786 |
-
}
|
3787 |
-
};
|
3788 |
-
|
3789 |
-
ko.components.addBindingsForCustomElement = function(allBindings, node, bindingContext, valueAccessors) {
|
3790 |
-
// Determine if it's really a custom element matching a component
|
3791 |
-
if (node.nodeType === 1) {
|
3792 |
-
var componentName = ko.components['getComponentNameForNode'](node);
|
3793 |
-
if (componentName) {
|
3794 |
-
// It does represent a component, so add a component binding for it
|
3795 |
-
allBindings = allBindings || {};
|
3796 |
-
|
3797 |
-
if (allBindings['component']) {
|
3798 |
-
// Avoid silently overwriting some other 'component' binding that may already be on the element
|
3799 |
-
throw new Error('Cannot use the "component" binding on a custom element matching a component');
|
3800 |
-
}
|
3801 |
-
|
3802 |
-
var componentBindingValue = { 'name': componentName, 'params': getComponentParamsFromCustomElement(node, bindingContext) };
|
3803 |
-
|
3804 |
-
allBindings['component'] = valueAccessors
|
3805 |
-
? function() { return componentBindingValue; }
|
3806 |
-
: componentBindingValue;
|
3807 |
-
}
|
3808 |
-
}
|
3809 |
-
|
3810 |
-
return allBindings;
|
3811 |
-
}
|
3812 |
-
|
3813 |
-
var nativeBindingProviderInstance = new ko.bindingProvider();
|
3814 |
-
|
3815 |
-
function getComponentParamsFromCustomElement(elem, bindingContext) {
|
3816 |
-
var paramsAttribute = elem.getAttribute('params');
|
3817 |
-
|
3818 |
-
if (paramsAttribute) {
|
3819 |
-
var params = nativeBindingProviderInstance['parseBindingsString'](paramsAttribute, bindingContext, elem, { 'valueAccessors': true, 'bindingParams': true }),
|
3820 |
-
rawParamComputedValues = ko.utils.objectMap(params, function(paramValue, paramName) {
|
3821 |
-
return ko.computed(paramValue, null, { disposeWhenNodeIsRemoved: elem });
|
3822 |
-
}),
|
3823 |
-
result = ko.utils.objectMap(rawParamComputedValues, function(paramValueComputed, paramName) {
|
3824 |
-
var paramValue = paramValueComputed.peek();
|
3825 |
-
// Does the evaluation of the parameter value unwrap any observables?
|
3826 |
-
if (!paramValueComputed.isActive()) {
|
3827 |
-
// No it doesn't, so there's no need for any computed wrapper. Just pass through the supplied value directly.
|
3828 |
-
// Example: "someVal: firstName, age: 123" (whether or not firstName is an observable/computed)
|
3829 |
-
return paramValue;
|
3830 |
-
} else {
|
3831 |
-
// Yes it does. Supply a computed property that unwraps both the outer (binding expression)
|
3832 |
-
// level of observability, and any inner (resulting model value) level of observability.
|
3833 |
-
// This means the component doesn't have to worry about multiple unwrapping. If the value is a
|
3834 |
-
// writable observable, the computed will also be writable and pass the value on to the observable.
|
3835 |
-
return ko.computed({
|
3836 |
-
'read': function() {
|
3837 |
-
return ko.utils.unwrapObservable(paramValueComputed());
|
3838 |
-
},
|
3839 |
-
'write': ko.isWriteableObservable(paramValue) && function(value) {
|
3840 |
-
paramValueComputed()(value);
|
3841 |
-
},
|
3842 |
-
disposeWhenNodeIsRemoved: elem
|
3843 |
-
});
|
3844 |
-
}
|
3845 |
-
});
|
3846 |
-
|
3847 |
-
// Give access to the raw computeds, as long as that wouldn't overwrite any custom param also called '$raw'
|
3848 |
-
// This is in case the developer wants to react to outer (binding) observability separately from inner
|
3849 |
-
// (model value) observability, or in case the model value observable has subobservables.
|
3850 |
-
if (!result.hasOwnProperty('$raw')) {
|
3851 |
-
result['$raw'] = rawParamComputedValues;
|
3852 |
-
}
|
3853 |
-
|
3854 |
-
return result;
|
3855 |
-
} else {
|
3856 |
-
// For consistency, absence of a "params" attribute is treated the same as the presence of
|
3857 |
-
// any empty one. Otherwise component viewmodels need special code to check whether or not
|
3858 |
-
// 'params' or 'params.$raw' is null/undefined before reading subproperties, which is annoying.
|
3859 |
-
return { '$raw': {} };
|
3860 |
-
}
|
3861 |
-
}
|
3862 |
-
|
3863 |
-
// --------------------------------------------------------------------------------
|
3864 |
-
// Compatibility code for older (pre-HTML5) IE browsers
|
3865 |
-
|
3866 |
-
if (ko.utils.ieVersion < 9) {
|
3867 |
-
// Whenever you preregister a component, enable it as a custom element in the current document
|
3868 |
-
ko.components['register'] = (function(originalFunction) {
|
3869 |
-
return function(componentName) {
|
3870 |
-
document.createElement(componentName); // Allows IE<9 to parse markup containing the custom element
|
3871 |
-
return originalFunction.apply(this, arguments);
|
3872 |
-
}
|
3873 |
-
})(ko.components['register']);
|
3874 |
-
|
3875 |
-
// Whenever you create a document fragment, enable all preregistered component names as custom elements
|
3876 |
-
// This is needed to make innerShiv/jQuery HTML parsing correctly handle the custom elements
|
3877 |
-
document.createDocumentFragment = (function(originalFunction) {
|
3878 |
-
return function() {
|
3879 |
-
var newDocFrag = originalFunction(),
|
3880 |
-
allComponents = ko.components._allRegisteredComponents;
|
3881 |
-
for (var componentName in allComponents) {
|
3882 |
-
if (allComponents.hasOwnProperty(componentName)) {
|
3883 |
-
newDocFrag.createElement(componentName);
|
3884 |
-
}
|
3885 |
-
}
|
3886 |
-
return newDocFrag;
|
3887 |
-
};
|
3888 |
-
})(document.createDocumentFragment);
|
3889 |
-
}
|
3890 |
-
})();(function(undefined) {
|
3891 |
-
|
3892 |
-
var componentLoadingOperationUniqueId = 0;
|
3893 |
-
|
3894 |
-
ko.bindingHandlers['component'] = {
|
3895 |
-
'init': function(element, valueAccessor, ignored1, ignored2, bindingContext) {
|
3896 |
-
var currentViewModel,
|
3897 |
-
currentLoadingOperationId,
|
3898 |
-
disposeAssociatedComponentViewModel = function () {
|
3899 |
-
var currentViewModelDispose = currentViewModel && currentViewModel['dispose'];
|
3900 |
-
if (typeof currentViewModelDispose === 'function') {
|
3901 |
-
currentViewModelDispose.call(currentViewModel);
|
3902 |
-
}
|
3903 |
-
currentViewModel = null;
|
3904 |
-
// Any in-flight loading operation is no longer relevant, so make sure we ignore its completion
|
3905 |
-
currentLoadingOperationId = null;
|
3906 |
-
},
|
3907 |
-
originalChildNodes = ko.utils.makeArray(ko.virtualElements.childNodes(element));
|
3908 |
-
|
3909 |
-
ko.utils.domNodeDisposal.addDisposeCallback(element, disposeAssociatedComponentViewModel);
|
3910 |
-
|
3911 |
-
ko.computed(function () {
|
3912 |
-
var value = ko.utils.unwrapObservable(valueAccessor()),
|
3913 |
-
componentName, componentParams;
|
3914 |
-
|
3915 |
-
if (typeof value === 'string') {
|
3916 |
-
componentName = value;
|
3917 |
-
} else {
|
3918 |
-
componentName = ko.utils.unwrapObservable(value['name']);
|
3919 |
-
componentParams = ko.utils.unwrapObservable(value['params']);
|
3920 |
-
}
|
3921 |
-
|
3922 |
-
if (!componentName) {
|
3923 |
-
throw new Error('No component name specified');
|
3924 |
-
}
|
3925 |
-
|
3926 |
-
var loadingOperationId = currentLoadingOperationId = ++componentLoadingOperationUniqueId;
|
3927 |
-
ko.components.get(componentName, function(componentDefinition) {
|
3928 |
-
// If this is not the current load operation for this element, ignore it.
|
3929 |
-
if (currentLoadingOperationId !== loadingOperationId) {
|
3930 |
-
return;
|
3931 |
-
}
|
3932 |
-
|
3933 |
-
// Clean up previous state
|
3934 |
-
disposeAssociatedComponentViewModel();
|
3935 |
-
|
3936 |
-
// Instantiate and bind new component. Implicitly this cleans any old DOM nodes.
|
3937 |
-
if (!componentDefinition) {
|
3938 |
-
throw new Error('Unknown component \'' + componentName + '\'');
|
3939 |
-
}
|
3940 |
-
cloneTemplateIntoElement(componentName, componentDefinition, element);
|
3941 |
-
var componentViewModel = createViewModel(componentDefinition, element, originalChildNodes, componentParams),
|
3942 |
-
childBindingContext = bindingContext['createChildContext'](componentViewModel, /* dataItemAlias */ undefined, function(ctx) {
|
3943 |
-
ctx['$component'] = componentViewModel;
|
3944 |
-
ctx['$componentTemplateNodes'] = originalChildNodes;
|
3945 |
-
});
|
3946 |
-
currentViewModel = componentViewModel;
|
3947 |
-
ko.applyBindingsToDescendants(childBindingContext, element);
|
3948 |
-
});
|
3949 |
-
}, null, { disposeWhenNodeIsRemoved: element });
|
3950 |
-
|
3951 |
-
return { 'controlsDescendantBindings': true };
|
3952 |
-
}
|
3953 |
-
};
|
3954 |
-
|
3955 |
-
ko.virtualElements.allowedBindings['component'] = true;
|
3956 |
-
|
3957 |
-
function cloneTemplateIntoElement(componentName, componentDefinition, element) {
|
3958 |
-
var template = componentDefinition['template'];
|
3959 |
-
if (!template) {
|
3960 |
-
throw new Error('Component \'' + componentName + '\' has no template');
|
3961 |
-
}
|
3962 |
-
|
3963 |
-
var clonedNodesArray = ko.utils.cloneNodes(template);
|
3964 |
-
ko.virtualElements.setDomNodeChildren(element, clonedNodesArray);
|
3965 |
-
}
|
3966 |
-
|
3967 |
-
function createViewModel(componentDefinition, element, originalChildNodes, componentParams) {
|
3968 |
-
var componentViewModelFactory = componentDefinition['createViewModel'];
|
3969 |
-
return componentViewModelFactory
|
3970 |
-
? componentViewModelFactory.call(componentDefinition, componentParams, { 'element': element, 'templateNodes': originalChildNodes })
|
3971 |
-
: componentParams; // Template-only component
|
3972 |
-
}
|
3973 |
-
|
3974 |
-
})();
|
3975 |
-
var attrHtmlToJavascriptMap = { 'class': 'className', 'for': 'htmlFor' };
|
3976 |
-
ko.bindingHandlers['attr'] = {
|
3977 |
-
'update': function(element, valueAccessor, allBindings) {
|
3978 |
-
var value = ko.utils.unwrapObservable(valueAccessor()) || {};
|
3979 |
-
ko.utils.objectForEach(value, function(attrName, attrValue) {
|
3980 |
-
attrValue = ko.utils.unwrapObservable(attrValue);
|
3981 |
-
|
3982 |
-
// To cover cases like "attr: { checked:someProp }", we want to remove the attribute entirely
|
3983 |
-
// when someProp is a "no value"-like value (strictly null, false, or undefined)
|
3984 |
-
// (because the absence of the "checked" attr is how to mark an element as not checked, etc.)
|
3985 |
-
var toRemove = (attrValue === false) || (attrValue === null) || (attrValue === undefined);
|
3986 |
-
if (toRemove)
|
3987 |
-
element.removeAttribute(attrName);
|
3988 |
-
|
3989 |
-
// In IE <= 7 and IE8 Quirks Mode, you have to use the Javascript property name instead of the
|
3990 |
-
// HTML attribute name for certain attributes. IE8 Standards Mode supports the correct behavior,
|
3991 |
-
// but instead of figuring out the mode, we'll just set the attribute through the Javascript
|
3992 |
-
// property for IE <= 8.
|
3993 |
-
if (ko.utils.ieVersion <= 8 && attrName in attrHtmlToJavascriptMap) {
|
3994 |
-
attrName = attrHtmlToJavascriptMap[attrName];
|
3995 |
-
if (toRemove)
|
3996 |
-
element.removeAttribute(attrName);
|
3997 |
-
else
|
3998 |
-
element[attrName] = attrValue;
|
3999 |
-
} else if (!toRemove) {
|
4000 |
-
element.setAttribute(attrName, attrValue.toString());
|
4001 |
-
}
|
4002 |
-
|
4003 |
-
// Treat "name" specially - although you can think of it as an attribute, it also needs
|
4004 |
-
// special handling on older versions of IE (https://github.com/SteveSanderson/knockout/pull/333)
|
4005 |
-
// Deliberately being case-sensitive here because XHTML would regard "Name" as a different thing
|
4006 |
-
// entirely, and there's no strong reason to allow for such casing in HTML.
|
4007 |
-
if (attrName === "name") {
|
4008 |
-
ko.utils.setElementName(element, toRemove ? "" : attrValue.toString());
|
4009 |
-
}
|
4010 |
-
});
|
4011 |
-
}
|
4012 |
-
};
|
4013 |
-
(function() {
|
4014 |
-
|
4015 |
-
ko.bindingHandlers['checked'] = {
|
4016 |
-
'after': ['value', 'attr'],
|
4017 |
-
'init': function (element, valueAccessor, allBindings) {
|
4018 |
-
var checkedValue = ko.pureComputed(function() {
|
4019 |
-
// Treat "value" like "checkedValue" when it is included with "checked" binding
|
4020 |
-
if (allBindings['has']('checkedValue')) {
|
4021 |
-
return ko.utils.unwrapObservable(allBindings.get('checkedValue'));
|
4022 |
-
} else if (allBindings['has']('value')) {
|
4023 |
-
return ko.utils.unwrapObservable(allBindings.get('value'));
|
4024 |
-
}
|
4025 |
-
|
4026 |
-
return element.value;
|
4027 |
-
});
|
4028 |
-
|
4029 |
-
function updateModel() {
|
4030 |
-
// This updates the model value from the view value.
|
4031 |
-
// It runs in response to DOM events (click) and changes in checkedValue.
|
4032 |
-
var isChecked = element.checked,
|
4033 |
-
elemValue = useCheckedValue ? checkedValue() : isChecked;
|
4034 |
-
|
4035 |
-
// When we're first setting up this computed, don't change any model state.
|
4036 |
-
if (ko.computedContext.isInitial()) {
|
4037 |
-
return;
|
4038 |
-
}
|
4039 |
-
|
4040 |
-
// We can ignore unchecked radio buttons, because some other radio
|
4041 |
-
// button will be getting checked, and that one can take care of updating state.
|
4042 |
-
if (isRadio && !isChecked) {
|
4043 |
-
return;
|
4044 |
-
}
|
4045 |
-
|
4046 |
-
var modelValue = ko.dependencyDetection.ignore(valueAccessor);
|
4047 |
-
if (valueIsArray) {
|
4048 |
-
var writableValue = rawValueIsNonArrayObservable ? modelValue.peek() : modelValue;
|
4049 |
-
if (oldElemValue !== elemValue) {
|
4050 |
-
// When we're responding to the checkedValue changing, and the element is
|
4051 |
-
// currently checked, replace the old elem value with the new elem value
|
4052 |
-
// in the model array.
|
4053 |
-
if (isChecked) {
|
4054 |
-
ko.utils.addOrRemoveItem(writableValue, elemValue, true);
|
4055 |
-
ko.utils.addOrRemoveItem(writableValue, oldElemValue, false);
|
4056 |
-
}
|
4057 |
-
|
4058 |
-
oldElemValue = elemValue;
|
4059 |
-
} else {
|
4060 |
-
// When we're responding to the user having checked/unchecked a checkbox,
|
4061 |
-
// add/remove the element value to the model array.
|
4062 |
-
ko.utils.addOrRemoveItem(writableValue, elemValue, isChecked);
|
4063 |
-
}
|
4064 |
-
if (rawValueIsNonArrayObservable && ko.isWriteableObservable(modelValue)) {
|
4065 |
-
modelValue(writableValue);
|
4066 |
-
}
|
4067 |
-
} else {
|
4068 |
-
ko.expressionRewriting.writeValueToProperty(modelValue, allBindings, 'checked', elemValue, true);
|
4069 |
-
}
|
4070 |
-
};
|
4071 |
-
|
4072 |
-
function updateView() {
|
4073 |
-
// This updates the view value from the model value.
|
4074 |
-
// It runs in response to changes in the bound (checked) value.
|
4075 |
-
var modelValue = ko.utils.unwrapObservable(valueAccessor());
|
4076 |
-
|
4077 |
-
if (valueIsArray) {
|
4078 |
-
// When a checkbox is bound to an array, being checked represents its value being present in that array
|
4079 |
-
element.checked = ko.utils.arrayIndexOf(modelValue, checkedValue()) >= 0;
|
4080 |
-
} else if (isCheckbox) {
|
4081 |
-
// When a checkbox is bound to any other value (not an array), being checked represents the value being trueish
|
4082 |
-
element.checked = modelValue;
|
4083 |
-
} else {
|
4084 |
-
// For radio buttons, being checked means that the radio button's value corresponds to the model value
|
4085 |
-
element.checked = (checkedValue() === modelValue);
|
4086 |
-
}
|
4087 |
-
};
|
4088 |
-
|
4089 |
-
var isCheckbox = element.type == "checkbox",
|
4090 |
-
isRadio = element.type == "radio";
|
4091 |
-
|
4092 |
-
// Only bind to check boxes and radio buttons
|
4093 |
-
if (!isCheckbox && !isRadio) {
|
4094 |
-
return;
|
4095 |
-
}
|
4096 |
-
|
4097 |
-
var rawValue = valueAccessor(),
|
4098 |
-
valueIsArray = isCheckbox && (ko.utils.unwrapObservable(rawValue) instanceof Array),
|
4099 |
-
rawValueIsNonArrayObservable = !(valueIsArray && rawValue.push && rawValue.splice),
|
4100 |
-
oldElemValue = valueIsArray ? checkedValue() : undefined,
|
4101 |
-
useCheckedValue = isRadio || valueIsArray;
|
4102 |
-
|
4103 |
-
// IE 6 won't allow radio buttons to be selected unless they have a name
|
4104 |
-
if (isRadio && !element.name)
|
4105 |
-
ko.bindingHandlers['uniqueName']['init'](element, function() { return true });
|
4106 |
-
|
4107 |
-
// Set up two computeds to update the binding:
|
4108 |
-
|
4109 |
-
// The first responds to changes in the checkedValue value and to element clicks
|
4110 |
-
ko.computed(updateModel, null, { disposeWhenNodeIsRemoved: element });
|
4111 |
-
ko.utils.registerEventHandler(element, "click", updateModel);
|
4112 |
-
|
4113 |
-
// The second responds to changes in the model value (the one associated with the checked binding)
|
4114 |
-
ko.computed(updateView, null, { disposeWhenNodeIsRemoved: element });
|
4115 |
-
|
4116 |
-
rawValue = undefined;
|
4117 |
-
}
|
4118 |
-
};
|
4119 |
-
ko.expressionRewriting.twoWayBindings['checked'] = true;
|
4120 |
-
|
4121 |
-
ko.bindingHandlers['checkedValue'] = {
|
4122 |
-
'update': function (element, valueAccessor) {
|
4123 |
-
element.value = ko.utils.unwrapObservable(valueAccessor());
|
4124 |
-
}
|
4125 |
-
};
|
4126 |
-
|
4127 |
-
})();var classesWrittenByBindingKey = '__ko__cssValue';
|
4128 |
-
ko.bindingHandlers['css'] = {
|
4129 |
-
'update': function (element, valueAccessor) {
|
4130 |
-
var value = ko.utils.unwrapObservable(valueAccessor());
|
4131 |
-
if (value !== null && typeof value == "object") {
|
4132 |
-
ko.utils.objectForEach(value, function(className, shouldHaveClass) {
|
4133 |
-
shouldHaveClass = ko.utils.unwrapObservable(shouldHaveClass);
|
4134 |
-
ko.utils.toggleDomNodeCssClass(element, className, shouldHaveClass);
|
4135 |
-
});
|
4136 |
-
} else {
|
4137 |
-
value = ko.utils.stringTrim(String(value || '')); // Make sure we don't try to store or set a non-string value
|
4138 |
-
ko.utils.toggleDomNodeCssClass(element, element[classesWrittenByBindingKey], false);
|
4139 |
-
element[classesWrittenByBindingKey] = value;
|
4140 |
-
ko.utils.toggleDomNodeCssClass(element, value, true);
|
4141 |
-
}
|
4142 |
-
}
|
4143 |
-
};
|
4144 |
-
ko.bindingHandlers['enable'] = {
|
4145 |
-
'update': function (element, valueAccessor) {
|
4146 |
-
var value = ko.utils.unwrapObservable(valueAccessor());
|
4147 |
-
if (value && element.disabled)
|
4148 |
-
element.removeAttribute("disabled");
|
4149 |
-
else if ((!value) && (!element.disabled))
|
4150 |
-
element.disabled = true;
|
4151 |
-
}
|
4152 |
-
};
|
4153 |
-
|
4154 |
-
ko.bindingHandlers['disable'] = {
|
4155 |
-
'update': function (element, valueAccessor) {
|
4156 |
-
ko.bindingHandlers['enable']['update'](element, function() { return !ko.utils.unwrapObservable(valueAccessor()) });
|
4157 |
-
}
|
4158 |
-
};
|
4159 |
-
// For certain common events (currently just 'click'), allow a simplified data-binding syntax
|
4160 |
-
// e.g. click:handler instead of the usual full-length event:{click:handler}
|
4161 |
-
function makeEventHandlerShortcut(eventName) {
|
4162 |
-
ko.bindingHandlers[eventName] = {
|
4163 |
-
'init': function(element, valueAccessor, allBindings, viewModel, bindingContext) {
|
4164 |
-
var newValueAccessor = function () {
|
4165 |
-
var result = {};
|
4166 |
-
result[eventName] = valueAccessor();
|
4167 |
-
return result;
|
4168 |
-
};
|
4169 |
-
return ko.bindingHandlers['event']['init'].call(this, element, newValueAccessor, allBindings, viewModel, bindingContext);
|
4170 |
-
}
|
4171 |
-
}
|
4172 |
-
}
|
4173 |
-
|
4174 |
-
ko.bindingHandlers['event'] = {
|
4175 |
-
'init' : function (element, valueAccessor, allBindings, viewModel, bindingContext) {
|
4176 |
-
var eventsToHandle = valueAccessor() || {};
|
4177 |
-
ko.utils.objectForEach(eventsToHandle, function(eventName) {
|
4178 |
-
if (typeof eventName == "string") {
|
4179 |
-
ko.utils.registerEventHandler(element, eventName, function (event) {
|
4180 |
-
var handlerReturnValue;
|
4181 |
-
var handlerFunction = valueAccessor()[eventName];
|
4182 |
-
if (!handlerFunction)
|
4183 |
-
return;
|
4184 |
-
|
4185 |
-
try {
|
4186 |
-
// Take all the event args, and prefix with the viewmodel
|
4187 |
-
var argsForHandler = ko.utils.makeArray(arguments);
|
4188 |
-
viewModel = bindingContext['$data'];
|
4189 |
-
argsForHandler.unshift(viewModel);
|
4190 |
-
handlerReturnValue = handlerFunction.apply(viewModel, argsForHandler);
|
4191 |
-
} finally {
|
4192 |
-
if (handlerReturnValue !== true) { // Normally we want to prevent default action. Developer can override this be explicitly returning true.
|
4193 |
-
if (event.preventDefault)
|
4194 |
-
event.preventDefault();
|
4195 |
-
else
|
4196 |
-
event.returnValue = false;
|
4197 |
-
}
|
4198 |
-
}
|
4199 |
-
|
4200 |
-
var bubble = allBindings.get(eventName + 'Bubble') !== false;
|
4201 |
-
if (!bubble) {
|
4202 |
-
event.cancelBubble = true;
|
4203 |
-
if (event.stopPropagation)
|
4204 |
-
event.stopPropagation();
|
4205 |
-
}
|
4206 |
-
});
|
4207 |
-
}
|
4208 |
-
});
|
4209 |
-
}
|
4210 |
-
};
|
4211 |
-
// "foreach: someExpression" is equivalent to "template: { foreach: someExpression }"
|
4212 |
-
// "foreach: { data: someExpression, afterAdd: myfn }" is equivalent to "template: { foreach: someExpression, afterAdd: myfn }"
|
4213 |
-
ko.bindingHandlers['foreach'] = {
|
4214 |
-
makeTemplateValueAccessor: function(valueAccessor) {
|
4215 |
-
return function() {
|
4216 |
-
var modelValue = valueAccessor(),
|
4217 |
-
unwrappedValue = ko.utils.peekObservable(modelValue); // Unwrap without setting a dependency here
|
4218 |
-
|
4219 |
-
// If unwrappedValue is the array, pass in the wrapped value on its own
|
4220 |
-
// The value will be unwrapped and tracked within the template binding
|
4221 |
-
// (See https://github.com/SteveSanderson/knockout/issues/523)
|
4222 |
-
if ((!unwrappedValue) || typeof unwrappedValue.length == "number")
|
4223 |
-
return { 'foreach': modelValue, 'templateEngine': ko.nativeTemplateEngine.instance };
|
4224 |
-
|
4225 |
-
// If unwrappedValue.data is the array, preserve all relevant options and unwrap again value so we get updates
|
4226 |
-
ko.utils.unwrapObservable(modelValue);
|
4227 |
-
return {
|
4228 |
-
'foreach': unwrappedValue['data'],
|
4229 |
-
'as': unwrappedValue['as'],
|
4230 |
-
'includeDestroyed': unwrappedValue['includeDestroyed'],
|
4231 |
-
'afterAdd': unwrappedValue['afterAdd'],
|
4232 |
-
'beforeRemove': unwrappedValue['beforeRemove'],
|
4233 |
-
'afterRender': unwrappedValue['afterRender'],
|
4234 |
-
'beforeMove': unwrappedValue['beforeMove'],
|
4235 |
-
'afterMove': unwrappedValue['afterMove'],
|
4236 |
-
'templateEngine': ko.nativeTemplateEngine.instance
|
4237 |
-
};
|
4238 |
-
};
|
4239 |
-
},
|
4240 |
-
'init': function(element, valueAccessor, allBindings, viewModel, bindingContext) {
|
4241 |
-
return ko.bindingHandlers['template']['init'](element, ko.bindingHandlers['foreach'].makeTemplateValueAccessor(valueAccessor));
|
4242 |
-
},
|
4243 |
-
'update': function(element, valueAccessor, allBindings, viewModel, bindingContext) {
|
4244 |
-
return ko.bindingHandlers['template']['update'](element, ko.bindingHandlers['foreach'].makeTemplateValueAccessor(valueAccessor), allBindings, viewModel, bindingContext);
|
4245 |
-
}
|
4246 |
-
};
|
4247 |
-
ko.expressionRewriting.bindingRewriteValidators['foreach'] = false; // Can't rewrite control flow bindings
|
4248 |
-
ko.virtualElements.allowedBindings['foreach'] = true;
|
4249 |
-
var hasfocusUpdatingProperty = '__ko_hasfocusUpdating';
|
4250 |
-
var hasfocusLastValue = '__ko_hasfocusLastValue';
|
4251 |
-
ko.bindingHandlers['hasfocus'] = {
|
4252 |
-
'init': function(element, valueAccessor, allBindings) {
|
4253 |
-
var handleElementFocusChange = function(isFocused) {
|
4254 |
-
// Where possible, ignore which event was raised and determine focus state using activeElement,
|
4255 |
-
// as this avoids phantom focus/blur events raised when changing tabs in modern browsers.
|
4256 |
-
// However, not all KO-targeted browsers (Firefox 2) support activeElement. For those browsers,
|
4257 |
-
// prevent a loss of focus when changing tabs/windows by setting a flag that prevents hasfocus
|
4258 |
-
// from calling 'blur()' on the element when it loses focus.
|
4259 |
-
// Discussion at https://github.com/SteveSanderson/knockout/pull/352
|
4260 |
-
element[hasfocusUpdatingProperty] = true;
|
4261 |
-
var ownerDoc = element.ownerDocument;
|
4262 |
-
if ("activeElement" in ownerDoc) {
|
4263 |
-
var active;
|
4264 |
-
try {
|
4265 |
-
active = ownerDoc.activeElement;
|
4266 |
-
} catch(e) {
|
4267 |
-
// IE9 throws if you access activeElement during page load (see issue #703)
|
4268 |
-
active = ownerDoc.body;
|
4269 |
-
}
|
4270 |
-
isFocused = (active === element);
|
4271 |
-
}
|
4272 |
-
var modelValue = valueAccessor();
|
4273 |
-
ko.expressionRewriting.writeValueToProperty(modelValue, allBindings, 'hasfocus', isFocused, true);
|
4274 |
-
|
4275 |
-
//cache the latest value, so we can avoid unnecessarily calling focus/blur in the update function
|
4276 |
-
element[hasfocusLastValue] = isFocused;
|
4277 |
-
element[hasfocusUpdatingProperty] = false;
|
4278 |
-
};
|
4279 |
-
var handleElementFocusIn = handleElementFocusChange.bind(null, true);
|
4280 |
-
var handleElementFocusOut = handleElementFocusChange.bind(null, false);
|
4281 |
-
|
4282 |
-
ko.utils.registerEventHandler(element, "focus", handleElementFocusIn);
|
4283 |
-
ko.utils.registerEventHandler(element, "focusin", handleElementFocusIn); // For IE
|
4284 |
-
ko.utils.registerEventHandler(element, "blur", handleElementFocusOut);
|
4285 |
-
ko.utils.registerEventHandler(element, "focusout", handleElementFocusOut); // For IE
|
4286 |
-
},
|
4287 |
-
'update': function(element, valueAccessor) {
|
4288 |
-
var value = !!ko.utils.unwrapObservable(valueAccessor());
|
4289 |
-
|
4290 |
-
if (!element[hasfocusUpdatingProperty] && element[hasfocusLastValue] !== value) {
|
4291 |
-
value ? element.focus() : element.blur();
|
4292 |
-
|
4293 |
-
// In IE, the blur method doesn't always cause the element to lose focus (for example, if the window is not in focus).
|
4294 |
-
// Setting focus to the body element does seem to be reliable in IE, but should only be used if we know that the current
|
4295 |
-
// element was focused already.
|
4296 |
-
if (!value && element[hasfocusLastValue]) {
|
4297 |
-
element.ownerDocument.body.focus();
|
4298 |
-
}
|
4299 |
-
|
4300 |
-
// For IE, which doesn't reliably fire "focus" or "blur" events synchronously
|
4301 |
-
ko.dependencyDetection.ignore(ko.utils.triggerEvent, null, [element, value ? "focusin" : "focusout"]);
|
4302 |
-
}
|
4303 |
-
}
|
4304 |
-
};
|
4305 |
-
ko.expressionRewriting.twoWayBindings['hasfocus'] = true;
|
4306 |
-
|
4307 |
-
ko.bindingHandlers['hasFocus'] = ko.bindingHandlers['hasfocus']; // Make "hasFocus" an alias
|
4308 |
-
ko.expressionRewriting.twoWayBindings['hasFocus'] = true;
|
4309 |
-
ko.bindingHandlers['html'] = {
|
4310 |
-
'init': function() {
|
4311 |
-
// Prevent binding on the dynamically-injected HTML (as developers are unlikely to expect that, and it has security implications)
|
4312 |
-
return { 'controlsDescendantBindings': true };
|
4313 |
-
},
|
4314 |
-
'update': function (element, valueAccessor) {
|
4315 |
-
// setHtml will unwrap the value if needed
|
4316 |
-
ko.utils.setHtml(element, valueAccessor());
|
4317 |
-
}
|
4318 |
-
};
|
4319 |
-
// Makes a binding like with or if
|
4320 |
-
function makeWithIfBinding(bindingKey, isWith, isNot, makeContextCallback) {
|
4321 |
-
ko.bindingHandlers[bindingKey] = {
|
4322 |
-
'init': function(element, valueAccessor, allBindings, viewModel, bindingContext) {
|
4323 |
-
var didDisplayOnLastUpdate,
|
4324 |
-
savedNodes;
|
4325 |
-
ko.computed(function() {
|
4326 |
-
var dataValue = ko.utils.unwrapObservable(valueAccessor()),
|
4327 |
-
shouldDisplay = !isNot !== !dataValue, // equivalent to isNot ? !dataValue : !!dataValue
|
4328 |
-
isFirstRender = !savedNodes,
|
4329 |
-
needsRefresh = isFirstRender || isWith || (shouldDisplay !== didDisplayOnLastUpdate);
|
4330 |
-
|
4331 |
-
if (needsRefresh) {
|
4332 |
-
// Save a copy of the inner nodes on the initial update, but only if we have dependencies.
|
4333 |
-
if (isFirstRender && ko.computedContext.getDependenciesCount()) {
|
4334 |
-
savedNodes = ko.utils.cloneNodes(ko.virtualElements.childNodes(element), true /* shouldCleanNodes */);
|
4335 |
-
}
|
4336 |
-
|
4337 |
-
if (shouldDisplay) {
|
4338 |
-
if (!isFirstRender) {
|
4339 |
-
ko.virtualElements.setDomNodeChildren(element, ko.utils.cloneNodes(savedNodes));
|
4340 |
-
}
|
4341 |
-
ko.applyBindingsToDescendants(makeContextCallback ? makeContextCallback(bindingContext, dataValue) : bindingContext, element);
|
4342 |
-
} else {
|
4343 |
-
ko.virtualElements.emptyNode(element);
|
4344 |
-
}
|
4345 |
-
|
4346 |
-
didDisplayOnLastUpdate = shouldDisplay;
|
4347 |
-
}
|
4348 |
-
}, null, { disposeWhenNodeIsRemoved: element });
|
4349 |
-
return { 'controlsDescendantBindings': true };
|
4350 |
-
}
|
4351 |
-
};
|
4352 |
-
ko.expressionRewriting.bindingRewriteValidators[bindingKey] = false; // Can't rewrite control flow bindings
|
4353 |
-
ko.virtualElements.allowedBindings[bindingKey] = true;
|
4354 |
-
}
|
4355 |
-
|
4356 |
-
// Construct the actual binding handlers
|
4357 |
-
makeWithIfBinding('if');
|
4358 |
-
makeWithIfBinding('ifnot', false /* isWith */, true /* isNot */);
|
4359 |
-
makeWithIfBinding('with', true /* isWith */, false /* isNot */,
|
4360 |
-
function(bindingContext, dataValue) {
|
4361 |
-
return bindingContext['createChildContext'](dataValue);
|
4362 |
-
}
|
4363 |
-
);
|
4364 |
-
var captionPlaceholder = {};
|
4365 |
-
ko.bindingHandlers['options'] = {
|
4366 |
-
'init': function(element) {
|
4367 |
-
if (ko.utils.tagNameLower(element) !== "select")
|
4368 |
-
throw new Error("options binding applies only to SELECT elements");
|
4369 |
-
|
4370 |
-
// Remove all existing <option>s.
|
4371 |
-
while (element.length > 0) {
|
4372 |
-
element.remove(0);
|
4373 |
-
}
|
4374 |
-
|
4375 |
-
// Ensures that the binding processor doesn't try to bind the options
|
4376 |
-
return { 'controlsDescendantBindings': true };
|
4377 |
-
},
|
4378 |
-
'update': function (element, valueAccessor, allBindings) {
|
4379 |
-
function selectedOptions() {
|
4380 |
-
return ko.utils.arrayFilter(element.options, function (node) { return node.selected; });
|
4381 |
-
}
|
4382 |
-
|
4383 |
-
var selectWasPreviouslyEmpty = element.length == 0,
|
4384 |
-
multiple = element.multiple,
|
4385 |
-
previousScrollTop = (!selectWasPreviouslyEmpty && multiple) ? element.scrollTop : null,
|
4386 |
-
unwrappedArray = ko.utils.unwrapObservable(valueAccessor()),
|
4387 |
-
valueAllowUnset = allBindings.get('valueAllowUnset') && allBindings['has']('value'),
|
4388 |
-
includeDestroyed = allBindings.get('optionsIncludeDestroyed'),
|
4389 |
-
arrayToDomNodeChildrenOptions = {},
|
4390 |
-
captionValue,
|
4391 |
-
filteredArray,
|
4392 |
-
previousSelectedValues = [];
|
4393 |
-
|
4394 |
-
if (!valueAllowUnset) {
|
4395 |
-
if (multiple) {
|
4396 |
-
previousSelectedValues = ko.utils.arrayMap(selectedOptions(), ko.selectExtensions.readValue);
|
4397 |
-
} else if (element.selectedIndex >= 0) {
|
4398 |
-
previousSelectedValues.push(ko.selectExtensions.readValue(element.options[element.selectedIndex]));
|
4399 |
-
}
|
4400 |
-
}
|
4401 |
-
|
4402 |
-
if (unwrappedArray) {
|
4403 |
-
if (typeof unwrappedArray.length == "undefined") // Coerce single value into array
|
4404 |
-
unwrappedArray = [unwrappedArray];
|
4405 |
-
|
4406 |
-
// Filter out any entries marked as destroyed
|
4407 |
-
filteredArray = ko.utils.arrayFilter(unwrappedArray, function(item) {
|
4408 |
-
return includeDestroyed || item === undefined || item === null || !ko.utils.unwrapObservable(item['_destroy']);
|
4409 |
-
});
|
4410 |
-
|
4411 |
-
// If caption is included, add it to the array
|
4412 |
-
if (allBindings['has']('optionsCaption')) {
|
4413 |
-
captionValue = ko.utils.unwrapObservable(allBindings.get('optionsCaption'));
|
4414 |
-
// If caption value is null or undefined, don't show a caption
|
4415 |
-
if (captionValue !== null && captionValue !== undefined) {
|
4416 |
-
filteredArray.unshift(captionPlaceholder);
|
4417 |
-
}
|
4418 |
-
}
|
4419 |
-
} else {
|
4420 |
-
// If a falsy value is provided (e.g. null), we'll simply empty the select element
|
4421 |
-
}
|
4422 |
-
|
4423 |
-
function applyToObject(object, predicate, defaultValue) {
|
4424 |
-
var predicateType = typeof predicate;
|
4425 |
-
if (predicateType == "function") // Given a function; run it against the data value
|
4426 |
-
return predicate(object);
|
4427 |
-
else if (predicateType == "string") // Given a string; treat it as a property name on the data value
|
4428 |
-
return object[predicate];
|
4429 |
-
else // Given no optionsText arg; use the data value itself
|
4430 |
-
return defaultValue;
|
4431 |
-
}
|
4432 |
-
|
4433 |
-
// The following functions can run at two different times:
|
4434 |
-
// The first is when the whole array is being updated directly from this binding handler.
|
4435 |
-
// The second is when an observable value for a specific array entry is updated.
|
4436 |
-
// oldOptions will be empty in the first case, but will be filled with the previously generated option in the second.
|
4437 |
-
var itemUpdate = false;
|
4438 |
-
function optionForArrayItem(arrayEntry, index, oldOptions) {
|
4439 |
-
if (oldOptions.length) {
|
4440 |
-
previousSelectedValues = !valueAllowUnset && oldOptions[0].selected ? [ ko.selectExtensions.readValue(oldOptions[0]) ] : [];
|
4441 |
-
itemUpdate = true;
|
4442 |
-
}
|
4443 |
-
var option = element.ownerDocument.createElement("option");
|
4444 |
-
if (arrayEntry === captionPlaceholder) {
|
4445 |
-
ko.utils.setTextContent(option, allBindings.get('optionsCaption'));
|
4446 |
-
ko.selectExtensions.writeValue(option, undefined);
|
4447 |
-
} else {
|
4448 |
-
// Apply a value to the option element
|
4449 |
-
var optionValue = applyToObject(arrayEntry, allBindings.get('optionsValue'), arrayEntry);
|
4450 |
-
ko.selectExtensions.writeValue(option, ko.utils.unwrapObservable(optionValue));
|
4451 |
-
|
4452 |
-
// Apply some text to the option element
|
4453 |
-
var optionText = applyToObject(arrayEntry, allBindings.get('optionsText'), optionValue);
|
4454 |
-
ko.utils.setTextContent(option, optionText);
|
4455 |
-
}
|
4456 |
-
return [option];
|
4457 |
-
}
|
4458 |
-
|
4459 |
-
// By using a beforeRemove callback, we delay the removal until after new items are added. This fixes a selection
|
4460 |
-
// problem in IE<=8 and Firefox. See https://github.com/knockout/knockout/issues/1208
|
4461 |
-
arrayToDomNodeChildrenOptions['beforeRemove'] =
|
4462 |
-
function (option) {
|
4463 |
-
element.removeChild(option);
|
4464 |
-
};
|
4465 |
-
|
4466 |
-
function setSelectionCallback(arrayEntry, newOptions) {
|
4467 |
-
if (itemUpdate && valueAllowUnset) {
|
4468 |
-
// The model value is authoritative, so make sure its value is the one selected
|
4469 |
-
// There is no need to use dependencyDetection.ignore since setDomNodeChildrenFromArrayMapping does so already.
|
4470 |
-
ko.selectExtensions.writeValue(element, ko.utils.unwrapObservable(allBindings.get('value')), true /* allowUnset */);
|
4471 |
-
} else if (previousSelectedValues.length) {
|
4472 |
-
// IE6 doesn't like us to assign selection to OPTION nodes before they're added to the document.
|
4473 |
-
// That's why we first added them without selection. Now it's time to set the selection.
|
4474 |
-
var isSelected = ko.utils.arrayIndexOf(previousSelectedValues, ko.selectExtensions.readValue(newOptions[0])) >= 0;
|
4475 |
-
ko.utils.setOptionNodeSelectionState(newOptions[0], isSelected);
|
4476 |
-
|
4477 |
-
// If this option was changed from being selected during a single-item update, notify the change
|
4478 |
-
if (itemUpdate && !isSelected) {
|
4479 |
-
ko.dependencyDetection.ignore(ko.utils.triggerEvent, null, [element, "change"]);
|
4480 |
-
}
|
4481 |
-
}
|
4482 |
-
}
|
4483 |
-
|
4484 |
-
var callback = setSelectionCallback;
|
4485 |
-
if (allBindings['has']('optionsAfterRender') && typeof allBindings.get('optionsAfterRender') == "function") {
|
4486 |
-
callback = function(arrayEntry, newOptions) {
|
4487 |
-
setSelectionCallback(arrayEntry, newOptions);
|
4488 |
-
ko.dependencyDetection.ignore(allBindings.get('optionsAfterRender'), null, [newOptions[0], arrayEntry !== captionPlaceholder ? arrayEntry : undefined]);
|
4489 |
-
}
|
4490 |
-
}
|
4491 |
-
|
4492 |
-
ko.utils.setDomNodeChildrenFromArrayMapping(element, filteredArray, optionForArrayItem, arrayToDomNodeChildrenOptions, callback);
|
4493 |
-
|
4494 |
-
ko.dependencyDetection.ignore(function () {
|
4495 |
-
if (valueAllowUnset) {
|
4496 |
-
// The model value is authoritative, so make sure its value is the one selected
|
4497 |
-
ko.selectExtensions.writeValue(element, ko.utils.unwrapObservable(allBindings.get('value')), true /* allowUnset */);
|
4498 |
-
} else {
|
4499 |
-
// Determine if the selection has changed as a result of updating the options list
|
4500 |
-
var selectionChanged;
|
4501 |
-
if (multiple) {
|
4502 |
-
// For a multiple-select box, compare the new selection count to the previous one
|
4503 |
-
// But if nothing was selected before, the selection can't have changed
|
4504 |
-
selectionChanged = previousSelectedValues.length && selectedOptions().length < previousSelectedValues.length;
|
4505 |
-
} else {
|
4506 |
-
// For a single-select box, compare the current value to the previous value
|
4507 |
-
// But if nothing was selected before or nothing is selected now, just look for a change in selection
|
4508 |
-
selectionChanged = (previousSelectedValues.length && element.selectedIndex >= 0)
|
4509 |
-
? (ko.selectExtensions.readValue(element.options[element.selectedIndex]) !== previousSelectedValues[0])
|
4510 |
-
: (previousSelectedValues.length || element.selectedIndex >= 0);
|
4511 |
-
}
|
4512 |
-
|
4513 |
-
// Ensure consistency between model value and selected option.
|
4514 |
-
// If the dropdown was changed so that selection is no longer the same,
|
4515 |
-
// notify the value or selectedOptions binding.
|
4516 |
-
if (selectionChanged) {
|
4517 |
-
ko.utils.triggerEvent(element, "change");
|
4518 |
-
}
|
4519 |
-
}
|
4520 |
-
});
|
4521 |
-
|
4522 |
-
// Workaround for IE bug
|
4523 |
-
ko.utils.ensureSelectElementIsRenderedCorrectly(element);
|
4524 |
-
|
4525 |
-
if (previousScrollTop && Math.abs(previousScrollTop - element.scrollTop) > 20)
|
4526 |
-
element.scrollTop = previousScrollTop;
|
4527 |
-
}
|
4528 |
-
};
|
4529 |
-
ko.bindingHandlers['options'].optionValueDomDataKey = ko.utils.domData.nextKey();
|
4530 |
-
ko.bindingHandlers['selectedOptions'] = {
|
4531 |
-
'after': ['options', 'foreach'],
|
4532 |
-
'init': function (element, valueAccessor, allBindings) {
|
4533 |
-
ko.utils.registerEventHandler(element, "change", function () {
|
4534 |
-
var value = valueAccessor(), valueToWrite = [];
|
4535 |
-
ko.utils.arrayForEach(element.getElementsByTagName("option"), function(node) {
|
4536 |
-
if (node.selected)
|
4537 |
-
valueToWrite.push(ko.selectExtensions.readValue(node));
|
4538 |
-
});
|
4539 |
-
ko.expressionRewriting.writeValueToProperty(value, allBindings, 'selectedOptions', valueToWrite);
|
4540 |
-
});
|
4541 |
-
},
|
4542 |
-
'update': function (element, valueAccessor) {
|
4543 |
-
if (ko.utils.tagNameLower(element) != "select")
|
4544 |
-
throw new Error("values binding applies only to SELECT elements");
|
4545 |
-
|
4546 |
-
var newValue = ko.utils.unwrapObservable(valueAccessor()),
|
4547 |
-
previousScrollTop = element.scrollTop;
|
4548 |
-
|
4549 |
-
if (newValue && typeof newValue.length == "number") {
|
4550 |
-
ko.utils.arrayForEach(element.getElementsByTagName("option"), function(node) {
|
4551 |
-
var isSelected = ko.utils.arrayIndexOf(newValue, ko.selectExtensions.readValue(node)) >= 0;
|
4552 |
-
if (node.selected != isSelected) { // This check prevents flashing of the select element in IE
|
4553 |
-
ko.utils.setOptionNodeSelectionState(node, isSelected);
|
4554 |
-
}
|
4555 |
-
});
|
4556 |
-
}
|
4557 |
-
|
4558 |
-
element.scrollTop = previousScrollTop;
|
4559 |
-
}
|
4560 |
-
};
|
4561 |
-
ko.expressionRewriting.twoWayBindings['selectedOptions'] = true;
|
4562 |
-
ko.bindingHandlers['style'] = {
|
4563 |
-
'update': function (element, valueAccessor) {
|
4564 |
-
var value = ko.utils.unwrapObservable(valueAccessor() || {});
|
4565 |
-
ko.utils.objectForEach(value, function(styleName, styleValue) {
|
4566 |
-
styleValue = ko.utils.unwrapObservable(styleValue);
|
4567 |
-
|
4568 |
-
if (styleValue === null || styleValue === undefined || styleValue === false) {
|
4569 |
-
// Empty string removes the value, whereas null/undefined have no effect
|
4570 |
-
styleValue = "";
|
4571 |
-
}
|
4572 |
-
|
4573 |
-
element.style[styleName] = styleValue;
|
4574 |
-
});
|
4575 |
-
}
|
4576 |
-
};
|
4577 |
-
ko.bindingHandlers['submit'] = {
|
4578 |
-
'init': function (element, valueAccessor, allBindings, viewModel, bindingContext) {
|
4579 |
-
if (typeof valueAccessor() != "function")
|
4580 |
-
throw new Error("The value for a submit binding must be a function");
|
4581 |
-
ko.utils.registerEventHandler(element, "submit", function (event) {
|
4582 |
-
var handlerReturnValue;
|
4583 |
-
var value = valueAccessor();
|
4584 |
-
try { handlerReturnValue = value.call(bindingContext['$data'], element); }
|
4585 |
-
finally {
|
4586 |
-
if (handlerReturnValue !== true) { // Normally we want to prevent default action. Developer can override this be explicitly returning true.
|
4587 |
-
if (event.preventDefault)
|
4588 |
-
event.preventDefault();
|
4589 |
-
else
|
4590 |
-
event.returnValue = false;
|
4591 |
-
}
|
4592 |
-
}
|
4593 |
-
});
|
4594 |
-
}
|
4595 |
-
};
|
4596 |
-
ko.bindingHandlers['text'] = {
|
4597 |
-
'init': function() {
|
4598 |
-
// Prevent binding on the dynamically-injected text node (as developers are unlikely to expect that, and it has security implications).
|
4599 |
-
// It should also make things faster, as we no longer have to consider whether the text node might be bindable.
|
4600 |
-
return { 'controlsDescendantBindings': true };
|
4601 |
-
},
|
4602 |
-
'update': function (element, valueAccessor) {
|
4603 |
-
ko.utils.setTextContent(element, valueAccessor());
|
4604 |
-
}
|
4605 |
-
};
|
4606 |
-
ko.virtualElements.allowedBindings['text'] = true;
|
4607 |
-
(function () {
|
4608 |
-
|
4609 |
-
if (window && window.navigator) {
|
4610 |
-
var parseVersion = function (matches) {
|
4611 |
-
if (matches) {
|
4612 |
-
return parseFloat(matches[1]);
|
4613 |
-
}
|
4614 |
-
};
|
4615 |
-
|
4616 |
-
// Detect various browser versions because some old versions don't fully support the 'input' event
|
4617 |
-
var operaVersion = window.opera && window.opera.version && parseInt(window.opera.version()),
|
4618 |
-
userAgent = window.navigator.userAgent,
|
4619 |
-
safariVersion = parseVersion(userAgent.match(/^(?:(?!chrome).)*version\/([^ ]*) safari/i)),
|
4620 |
-
firefoxVersion = parseVersion(userAgent.match(/Firefox\/([^ ]*)/));
|
4621 |
-
}
|
4622 |
-
|
4623 |
-
// IE 8 and 9 have bugs that prevent the normal events from firing when the value changes.
|
4624 |
-
// But it does fire the 'selectionchange' event on many of those, presumably because the
|
4625 |
-
// cursor is moving and that counts as the selection changing. The 'selectionchange' event is
|
4626 |
-
// fired at the document level only and doesn't directly indicate which element changed. We
|
4627 |
-
// set up just one event handler for the document and use 'activeElement' to determine which
|
4628 |
-
// element was changed.
|
4629 |
-
if (ko.utils.ieVersion < 10) {
|
4630 |
-
var selectionChangeRegisteredName = ko.utils.domData.nextKey(),
|
4631 |
-
selectionChangeHandlerName = ko.utils.domData.nextKey();
|
4632 |
-
var selectionChangeHandler = function(event) {
|
4633 |
-
var target = this.activeElement,
|
4634 |
-
handler = target && ko.utils.domData.get(target, selectionChangeHandlerName);
|
4635 |
-
if (handler) {
|
4636 |
-
handler(event);
|
4637 |
-
}
|
4638 |
-
};
|
4639 |
-
var registerForSelectionChangeEvent = function (element, handler) {
|
4640 |
-
var ownerDoc = element.ownerDocument;
|
4641 |
-
if (!ko.utils.domData.get(ownerDoc, selectionChangeRegisteredName)) {
|
4642 |
-
ko.utils.domData.set(ownerDoc, selectionChangeRegisteredName, true);
|
4643 |
-
ko.utils.registerEventHandler(ownerDoc, 'selectionchange', selectionChangeHandler);
|
4644 |
-
}
|
4645 |
-
ko.utils.domData.set(element, selectionChangeHandlerName, handler);
|
4646 |
-
};
|
4647 |
-
}
|
4648 |
-
|
4649 |
-
ko.bindingHandlers['textInput'] = {
|
4650 |
-
'init': function (element, valueAccessor, allBindings) {
|
4651 |
-
|
4652 |
-
var previousElementValue = element.value,
|
4653 |
-
timeoutHandle,
|
4654 |
-
elementValueBeforeEvent;
|
4655 |
-
|
4656 |
-
var updateModel = function (event) {
|
4657 |
-
clearTimeout(timeoutHandle);
|
4658 |
-
elementValueBeforeEvent = timeoutHandle = undefined;
|
4659 |
-
|
4660 |
-
var elementValue = element.value;
|
4661 |
-
if (previousElementValue !== elementValue) {
|
4662 |
-
// Provide a way for tests to know exactly which event was processed
|
4663 |
-
if (DEBUG && event) element['_ko_textInputProcessedEvent'] = event.type;
|
4664 |
-
previousElementValue = elementValue;
|
4665 |
-
ko.expressionRewriting.writeValueToProperty(valueAccessor(), allBindings, 'textInput', elementValue);
|
4666 |
-
}
|
4667 |
-
};
|
4668 |
-
|
4669 |
-
var deferUpdateModel = function (event) {
|
4670 |
-
if (!timeoutHandle) {
|
4671 |
-
// The elementValueBeforeEvent variable is set *only* during the brief gap between an
|
4672 |
-
// event firing and the updateModel function running. This allows us to ignore model
|
4673 |
-
// updates that are from the previous state of the element, usually due to techniques
|
4674 |
-
// such as rateLimit. Such updates, if not ignored, can cause keystrokes to be lost.
|
4675 |
-
elementValueBeforeEvent = element.value;
|
4676 |
-
var handler = DEBUG ? updateModel.bind(element, {type: event.type}) : updateModel;
|
4677 |
-
timeoutHandle = ko.utils.setTimeout(handler, 4);
|
4678 |
-
}
|
4679 |
-
};
|
4680 |
-
|
4681 |
-
// IE9 will mess up the DOM if you handle events synchronously which results in DOM changes (such as other bindings);
|
4682 |
-
// so we'll make sure all updates are asynchronous
|
4683 |
-
var ieUpdateModel = ko.utils.ieVersion == 9 ? deferUpdateModel : updateModel;
|
4684 |
-
|
4685 |
-
var updateView = function () {
|
4686 |
-
var modelValue = ko.utils.unwrapObservable(valueAccessor());
|
4687 |
-
|
4688 |
-
if (modelValue === null || modelValue === undefined) {
|
4689 |
-
modelValue = '';
|
4690 |
-
}
|
4691 |
-
|
4692 |
-
if (elementValueBeforeEvent !== undefined && modelValue === elementValueBeforeEvent) {
|
4693 |
-
ko.utils.setTimeout(updateView, 4);
|
4694 |
-
return;
|
4695 |
-
}
|
4696 |
-
|
4697 |
-
// Update the element only if the element and model are different. On some browsers, updating the value
|
4698 |
-
// will move the cursor to the end of the input, which would be bad while the user is typing.
|
4699 |
-
if (element.value !== modelValue) {
|
4700 |
-
previousElementValue = modelValue; // Make sure we ignore events (propertychange) that result from updating the value
|
4701 |
-
element.value = modelValue;
|
4702 |
-
}
|
4703 |
-
};
|
4704 |
-
|
4705 |
-
var onEvent = function (event, handler) {
|
4706 |
-
ko.utils.registerEventHandler(element, event, handler);
|
4707 |
-
};
|
4708 |
-
|
4709 |
-
if (DEBUG && ko.bindingHandlers['textInput']['_forceUpdateOn']) {
|
4710 |
-
// Provide a way for tests to specify exactly which events are bound
|
4711 |
-
ko.utils.arrayForEach(ko.bindingHandlers['textInput']['_forceUpdateOn'], function(eventName) {
|
4712 |
-
if (eventName.slice(0,5) == 'after') {
|
4713 |
-
onEvent(eventName.slice(5), deferUpdateModel);
|
4714 |
-
} else {
|
4715 |
-
onEvent(eventName, updateModel);
|
4716 |
-
}
|
4717 |
-
});
|
4718 |
-
} else {
|
4719 |
-
if (ko.utils.ieVersion < 10) {
|
4720 |
-
// Internet Explorer <= 8 doesn't support the 'input' event, but does include 'propertychange' that fires whenever
|
4721 |
-
// any property of an element changes. Unlike 'input', it also fires if a property is changed from JavaScript code,
|
4722 |
-
// but that's an acceptable compromise for this binding. IE 9 does support 'input', but since it doesn't fire it
|
4723 |
-
// when using autocomplete, we'll use 'propertychange' for it also.
|
4724 |
-
onEvent('propertychange', function(event) {
|
4725 |
-
if (event.propertyName === 'value') {
|
4726 |
-
ieUpdateModel(event);
|
4727 |
-
}
|
4728 |
-
});
|
4729 |
-
|
4730 |
-
if (ko.utils.ieVersion == 8) {
|
4731 |
-
// IE 8 has a bug where it fails to fire 'propertychange' on the first update following a value change from
|
4732 |
-
// JavaScript code. It also doesn't fire if you clear the entire value. To fix this, we bind to the following
|
4733 |
-
// events too.
|
4734 |
-
onEvent('keyup', updateModel); // A single keystoke
|
4735 |
-
onEvent('keydown', updateModel); // The first character when a key is held down
|
4736 |
-
}
|
4737 |
-
if (ko.utils.ieVersion >= 8) {
|
4738 |
-
// Internet Explorer 9 doesn't fire the 'input' event when deleting text, including using
|
4739 |
-
// the backspace, delete, or ctrl-x keys, clicking the 'x' to clear the input, dragging text
|
4740 |
-
// out of the field, and cutting or deleting text using the context menu. 'selectionchange'
|
4741 |
-
// can detect all of those except dragging text out of the field, for which we use 'dragend'.
|
4742 |
-
// These are also needed in IE8 because of the bug described above.
|
4743 |
-
registerForSelectionChangeEvent(element, ieUpdateModel); // 'selectionchange' covers cut, paste, drop, delete, etc.
|
4744 |
-
onEvent('dragend', deferUpdateModel);
|
4745 |
-
}
|
4746 |
-
} else {
|
4747 |
-
// All other supported browsers support the 'input' event, which fires whenever the content of the element is changed
|
4748 |
-
// through the user interface.
|
4749 |
-
onEvent('input', updateModel);
|
4750 |
-
|
4751 |
-
if (safariVersion < 5 && ko.utils.tagNameLower(element) === "textarea") {
|
4752 |
-
// Safari <5 doesn't fire the 'input' event for <textarea> elements (it does fire 'textInput'
|
4753 |
-
// but only when typing). So we'll just catch as much as we can with keydown, cut, and paste.
|
4754 |
-
onEvent('keydown', deferUpdateModel);
|
4755 |
-
onEvent('paste', deferUpdateModel);
|
4756 |
-
onEvent('cut', deferUpdateModel);
|
4757 |
-
} else if (operaVersion < 11) {
|
4758 |
-
// Opera 10 doesn't always fire the 'input' event for cut, paste, undo & drop operations.
|
4759 |
-
// We can try to catch some of those using 'keydown'.
|
4760 |
-
onEvent('keydown', deferUpdateModel);
|
4761 |
-
} else if (firefoxVersion < 4.0) {
|
4762 |
-
// Firefox <= 3.6 doesn't fire the 'input' event when text is filled in through autocomplete
|
4763 |
-
onEvent('DOMAutoComplete', updateModel);
|
4764 |
-
|
4765 |
-
// Firefox <=3.5 doesn't fire the 'input' event when text is dropped into the input.
|
4766 |
-
onEvent('dragdrop', updateModel); // <3.5
|
4767 |
-
onEvent('drop', updateModel); // 3.5
|
4768 |
-
}
|
4769 |
-
}
|
4770 |
-
}
|
4771 |
-
|
4772 |
-
// Bind to the change event so that we can catch programmatic updates of the value that fire this event.
|
4773 |
-
onEvent('change', updateModel);
|
4774 |
-
|
4775 |
-
ko.computed(updateView, null, { disposeWhenNodeIsRemoved: element });
|
4776 |
-
}
|
4777 |
-
};
|
4778 |
-
ko.expressionRewriting.twoWayBindings['textInput'] = true;
|
4779 |
-
|
4780 |
-
// textinput is an alias for textInput
|
4781 |
-
ko.bindingHandlers['textinput'] = {
|
4782 |
-
// preprocess is the only way to set up a full alias
|
4783 |
-
'preprocess': function (value, name, addBinding) {
|
4784 |
-
addBinding('textInput', value);
|
4785 |
-
}
|
4786 |
-
};
|
4787 |
-
|
4788 |
-
})();ko.bindingHandlers['uniqueName'] = {
|
4789 |
-
'init': function (element, valueAccessor) {
|
4790 |
-
if (valueAccessor()) {
|
4791 |
-
var name = "ko_unique_" + (++ko.bindingHandlers['uniqueName'].currentIndex);
|
4792 |
-
ko.utils.setElementName(element, name);
|
4793 |
-
}
|
4794 |
-
}
|
4795 |
-
};
|
4796 |
-
ko.bindingHandlers['uniqueName'].currentIndex = 0;
|
4797 |
-
ko.bindingHandlers['value'] = {
|
4798 |
-
'after': ['options', 'foreach'],
|
4799 |
-
'init': function (element, valueAccessor, allBindings) {
|
4800 |
-
// If the value binding is placed on a radio/checkbox, then just pass through to checkedValue and quit
|
4801 |
-
if (element.tagName.toLowerCase() == "input" && (element.type == "checkbox" || element.type == "radio")) {
|
4802 |
-
ko.applyBindingAccessorsToNode(element, { 'checkedValue': valueAccessor });
|
4803 |
-
return;
|
4804 |
-
}
|
4805 |
-
|
4806 |
-
// Always catch "change" event; possibly other events too if asked
|
4807 |
-
var eventsToCatch = ["change"];
|
4808 |
-
var requestedEventsToCatch = allBindings.get("valueUpdate");
|
4809 |
-
var propertyChangedFired = false;
|
4810 |
-
var elementValueBeforeEvent = null;
|
4811 |
-
|
4812 |
-
if (requestedEventsToCatch) {
|
4813 |
-
if (typeof requestedEventsToCatch == "string") // Allow both individual event names, and arrays of event names
|
4814 |
-
requestedEventsToCatch = [requestedEventsToCatch];
|
4815 |
-
ko.utils.arrayPushAll(eventsToCatch, requestedEventsToCatch);
|
4816 |
-
eventsToCatch = ko.utils.arrayGetDistinctValues(eventsToCatch);
|
4817 |
-
}
|
4818 |
-
|
4819 |
-
var valueUpdateHandler = function() {
|
4820 |
-
elementValueBeforeEvent = null;
|
4821 |
-
propertyChangedFired = false;
|
4822 |
-
var modelValue = valueAccessor();
|
4823 |
-
var elementValue = ko.selectExtensions.readValue(element);
|
4824 |
-
ko.expressionRewriting.writeValueToProperty(modelValue, allBindings, 'value', elementValue);
|
4825 |
-
}
|
4826 |
-
|
4827 |
-
// Workaround for https://github.com/SteveSanderson/knockout/issues/122
|
4828 |
-
// IE doesn't fire "change" events on textboxes if the user selects a value from its autocomplete list
|
4829 |
-
var ieAutoCompleteHackNeeded = ko.utils.ieVersion && element.tagName.toLowerCase() == "input" && element.type == "text"
|
4830 |
-
&& element.autocomplete != "off" && (!element.form || element.form.autocomplete != "off");
|
4831 |
-
if (ieAutoCompleteHackNeeded && ko.utils.arrayIndexOf(eventsToCatch, "propertychange") == -1) {
|
4832 |
-
ko.utils.registerEventHandler(element, "propertychange", function () { propertyChangedFired = true });
|
4833 |
-
ko.utils.registerEventHandler(element, "focus", function () { propertyChangedFired = false });
|
4834 |
-
ko.utils.registerEventHandler(element, "blur", function() {
|
4835 |
-
if (propertyChangedFired) {
|
4836 |
-
valueUpdateHandler();
|
4837 |
-
}
|
4838 |
-
});
|
4839 |
-
}
|
4840 |
-
|
4841 |
-
ko.utils.arrayForEach(eventsToCatch, function(eventName) {
|
4842 |
-
// The syntax "after<eventname>" means "run the handler asynchronously after the event"
|
4843 |
-
// This is useful, for example, to catch "keydown" events after the browser has updated the control
|
4844 |
-
// (otherwise, ko.selectExtensions.readValue(this) will receive the control's value *before* the key event)
|
4845 |
-
var handler = valueUpdateHandler;
|
4846 |
-
if (ko.utils.stringStartsWith(eventName, "after")) {
|
4847 |
-
handler = function() {
|
4848 |
-
// The elementValueBeforeEvent variable is non-null *only* during the brief gap between
|
4849 |
-
// a keyX event firing and the valueUpdateHandler running, which is scheduled to happen
|
4850 |
-
// at the earliest asynchronous opportunity. We store this temporary information so that
|
4851 |
-
// if, between keyX and valueUpdateHandler, the underlying model value changes separately,
|
4852 |
-
// we can overwrite that model value change with the value the user just typed. Otherwise,
|
4853 |
-
// techniques like rateLimit can trigger model changes at critical moments that will
|
4854 |
-
// override the user's inputs, causing keystrokes to be lost.
|
4855 |
-
elementValueBeforeEvent = ko.selectExtensions.readValue(element);
|
4856 |
-
ko.utils.setTimeout(valueUpdateHandler, 0);
|
4857 |
-
};
|
4858 |
-
eventName = eventName.substring("after".length);
|
4859 |
-
}
|
4860 |
-
ko.utils.registerEventHandler(element, eventName, handler);
|
4861 |
-
});
|
4862 |
-
|
4863 |
-
var updateFromModel = function () {
|
4864 |
-
var newValue = ko.utils.unwrapObservable(valueAccessor());
|
4865 |
-
var elementValue = ko.selectExtensions.readValue(element);
|
4866 |
-
|
4867 |
-
if (elementValueBeforeEvent !== null && newValue === elementValueBeforeEvent) {
|
4868 |
-
ko.utils.setTimeout(updateFromModel, 0);
|
4869 |
-
return;
|
4870 |
-
}
|
4871 |
-
|
4872 |
-
var valueHasChanged = (newValue !== elementValue);
|
4873 |
-
|
4874 |
-
if (valueHasChanged) {
|
4875 |
-
if (ko.utils.tagNameLower(element) === "select") {
|
4876 |
-
var allowUnset = allBindings.get('valueAllowUnset');
|
4877 |
-
var applyValueAction = function () {
|
4878 |
-
ko.selectExtensions.writeValue(element, newValue, allowUnset);
|
4879 |
-
};
|
4880 |
-
applyValueAction();
|
4881 |
-
|
4882 |
-
if (!allowUnset && newValue !== ko.selectExtensions.readValue(element)) {
|
4883 |
-
// If you try to set a model value that can't be represented in an already-populated dropdown, reject that change,
|
4884 |
-
// because you're not allowed to have a model value that disagrees with a visible UI selection.
|
4885 |
-
ko.dependencyDetection.ignore(ko.utils.triggerEvent, null, [element, "change"]);
|
4886 |
-
} else {
|
4887 |
-
// Workaround for IE6 bug: It won't reliably apply values to SELECT nodes during the same execution thread
|
4888 |
-
// right after you've changed the set of OPTION nodes on it. So for that node type, we'll schedule a second thread
|
4889 |
-
// to apply the value as well.
|
4890 |
-
ko.utils.setTimeout(applyValueAction, 0);
|
4891 |
-
}
|
4892 |
-
} else {
|
4893 |
-
ko.selectExtensions.writeValue(element, newValue);
|
4894 |
-
}
|
4895 |
-
}
|
4896 |
-
};
|
4897 |
-
|
4898 |
-
ko.computed(updateFromModel, null, { disposeWhenNodeIsRemoved: element });
|
4899 |
-
},
|
4900 |
-
'update': function() {} // Keep for backwards compatibility with code that may have wrapped value binding
|
4901 |
-
};
|
4902 |
-
ko.expressionRewriting.twoWayBindings['value'] = true;
|
4903 |
-
ko.bindingHandlers['visible'] = {
|
4904 |
-
'update': function (element, valueAccessor) {
|
4905 |
-
var value = ko.utils.unwrapObservable(valueAccessor());
|
4906 |
-
var isCurrentlyVisible = !(element.style.display == "none");
|
4907 |
-
if (value && !isCurrentlyVisible)
|
4908 |
-
element.style.display = "";
|
4909 |
-
else if ((!value) && isCurrentlyVisible)
|
4910 |
-
element.style.display = "none";
|
4911 |
-
}
|
4912 |
-
};
|
4913 |
-
// 'click' is just a shorthand for the usual full-length event:{click:handler}
|
4914 |
-
makeEventHandlerShortcut('click');
|
4915 |
-
// If you want to make a custom template engine,
|
4916 |
-
//
|
4917 |
-
// [1] Inherit from this class (like ko.nativeTemplateEngine does)
|
4918 |
-
// [2] Override 'renderTemplateSource', supplying a function with this signature:
|
4919 |
-
//
|
4920 |
-
// function (templateSource, bindingContext, options) {
|
4921 |
-
// // - templateSource.text() is the text of the template you should render
|
4922 |
-
// // - bindingContext.$data is the data you should pass into the template
|
4923 |
-
// // - you might also want to make bindingContext.$parent, bindingContext.$parents,
|
4924 |
-
// // and bindingContext.$root available in the template too
|
4925 |
-
// // - options gives you access to any other properties set on "data-bind: { template: options }"
|
4926 |
-
// // - templateDocument is the document object of the template
|
4927 |
-
// //
|
4928 |
-
// // Return value: an array of DOM nodes
|
4929 |
-
// }
|
4930 |
-
//
|
4931 |
-
// [3] Override 'createJavaScriptEvaluatorBlock', supplying a function with this signature:
|
4932 |
-
//
|
4933 |
-
// function (script) {
|
4934 |
-
// // Return value: Whatever syntax means "Evaluate the JavaScript statement 'script' and output the result"
|
4935 |
-
// // For example, the jquery.tmpl template engine converts 'someScript' to '${ someScript }'
|
4936 |
-
// }
|
4937 |
-
//
|
4938 |
-
// This is only necessary if you want to allow data-bind attributes to reference arbitrary template variables.
|
4939 |
-
// If you don't want to allow that, you can set the property 'allowTemplateRewriting' to false (like ko.nativeTemplateEngine does)
|
4940 |
-
// and then you don't need to override 'createJavaScriptEvaluatorBlock'.
|
4941 |
-
|
4942 |
-
ko.templateEngine = function () { };
|
4943 |
-
|
4944 |
-
ko.templateEngine.prototype['renderTemplateSource'] = function (templateSource, bindingContext, options, templateDocument) {
|
4945 |
-
throw new Error("Override renderTemplateSource");
|
4946 |
-
};
|
4947 |
-
|
4948 |
-
ko.templateEngine.prototype['createJavaScriptEvaluatorBlock'] = function (script) {
|
4949 |
-
throw new Error("Override createJavaScriptEvaluatorBlock");
|
4950 |
-
};
|
4951 |
-
|
4952 |
-
ko.templateEngine.prototype['makeTemplateSource'] = function(template, templateDocument) {
|
4953 |
-
// Named template
|
4954 |
-
if (typeof template == "string") {
|
4955 |
-
templateDocument = templateDocument || document;
|
4956 |
-
var elem = templateDocument.getElementById(template);
|
4957 |
-
if (!elem)
|
4958 |
-
throw new Error("Cannot find template with ID " + template);
|
4959 |
-
return new ko.templateSources.domElement(elem);
|
4960 |
-
} else if ((template.nodeType == 1) || (template.nodeType == 8)) {
|
4961 |
-
// Anonymous template
|
4962 |
-
return new ko.templateSources.anonymousTemplate(template);
|
4963 |
-
} else
|
4964 |
-
throw new Error("Unknown template type: " + template);
|
4965 |
-
};
|
4966 |
-
|
4967 |
-
ko.templateEngine.prototype['renderTemplate'] = function (template, bindingContext, options, templateDocument) {
|
4968 |
-
var templateSource = this['makeTemplateSource'](template, templateDocument);
|
4969 |
-
return this['renderTemplateSource'](templateSource, bindingContext, options, templateDocument);
|
4970 |
-
};
|
4971 |
-
|
4972 |
-
ko.templateEngine.prototype['isTemplateRewritten'] = function (template, templateDocument) {
|
4973 |
-
// Skip rewriting if requested
|
4974 |
-
if (this['allowTemplateRewriting'] === false)
|
4975 |
-
return true;
|
4976 |
-
return this['makeTemplateSource'](template, templateDocument)['data']("isRewritten");
|
4977 |
-
};
|
4978 |
-
|
4979 |
-
ko.templateEngine.prototype['rewriteTemplate'] = function (template, rewriterCallback, templateDocument) {
|
4980 |
-
var templateSource = this['makeTemplateSource'](template, templateDocument);
|
4981 |
-
var rewritten = rewriterCallback(templateSource['text']());
|
4982 |
-
templateSource['text'](rewritten);
|
4983 |
-
templateSource['data']("isRewritten", true);
|
4984 |
-
};
|
4985 |
-
|
4986 |
-
ko.exportSymbol('templateEngine', ko.templateEngine);
|
4987 |
-
|
4988 |
-
ko.templateRewriting = (function () {
|
4989 |
-
var memoizeDataBindingAttributeSyntaxRegex = /(<([a-z]+\d*)(?:\s+(?!data-bind\s*=\s*)[a-z0-9\-]+(?:=(?:\"[^\"]*\"|\'[^\']*\'|[^>]*))?)*\s+)data-bind\s*=\s*(["'])([\s\S]*?)\3/gi;
|
4990 |
-
var memoizeVirtualContainerBindingSyntaxRegex = /<!--\s*ko\b\s*([\s\S]*?)\s*-->/g;
|
4991 |
-
|
4992 |
-
function validateDataBindValuesForRewriting(keyValueArray) {
|
4993 |
-
var allValidators = ko.expressionRewriting.bindingRewriteValidators;
|
4994 |
-
for (var i = 0; i < keyValueArray.length; i++) {
|
4995 |
-
var key = keyValueArray[i]['key'];
|
4996 |
-
if (allValidators.hasOwnProperty(key)) {
|
4997 |
-
var validator = allValidators[key];
|
4998 |
-
|
4999 |
-
if (typeof validator === "function") {
|
5000 |
-
var possibleErrorMessage = validator(keyValueArray[i]['value']);
|
5001 |
-
if (possibleErrorMessage)
|
5002 |
-
throw new Error(possibleErrorMessage);
|
5003 |
-
} else if (!validator) {
|
5004 |
-
throw new Error("This template engine does not support the '" + key + "' binding within its templates");
|
5005 |
-
}
|
5006 |
-
}
|
5007 |
-
}
|
5008 |
-
}
|
5009 |
-
|
5010 |
-
function constructMemoizedTagReplacement(dataBindAttributeValue, tagToRetain, nodeName, templateEngine) {
|
5011 |
-
var dataBindKeyValueArray = ko.expressionRewriting.parseObjectLiteral(dataBindAttributeValue);
|
5012 |
-
validateDataBindValuesForRewriting(dataBindKeyValueArray);
|
5013 |
-
var rewrittenDataBindAttributeValue = ko.expressionRewriting.preProcessBindings(dataBindKeyValueArray, {'valueAccessors':true});
|
5014 |
-
|
5015 |
-
// For no obvious reason, Opera fails to evaluate rewrittenDataBindAttributeValue unless it's wrapped in an additional
|
5016 |
-
// anonymous function, even though Opera's built-in debugger can evaluate it anyway. No other browser requires this
|
5017 |
-
// extra indirection.
|
5018 |
-
var applyBindingsToNextSiblingScript =
|
5019 |
-
"ko.__tr_ambtns(function($context,$element){return(function(){return{ " + rewrittenDataBindAttributeValue + " } })()},'" + nodeName.toLowerCase() + "')";
|
5020 |
-
return templateEngine['createJavaScriptEvaluatorBlock'](applyBindingsToNextSiblingScript) + tagToRetain;
|
5021 |
-
}
|
5022 |
-
|
5023 |
-
return {
|
5024 |
-
ensureTemplateIsRewritten: function (template, templateEngine, templateDocument) {
|
5025 |
-
if (!templateEngine['isTemplateRewritten'](template, templateDocument))
|
5026 |
-
templateEngine['rewriteTemplate'](template, function (htmlString) {
|
5027 |
-
return ko.templateRewriting.memoizeBindingAttributeSyntax(htmlString, templateEngine);
|
5028 |
-
}, templateDocument);
|
5029 |
-
},
|
5030 |
-
|
5031 |
-
memoizeBindingAttributeSyntax: function (htmlString, templateEngine) {
|
5032 |
-
return htmlString.replace(memoizeDataBindingAttributeSyntaxRegex, function () {
|
5033 |
-
return constructMemoizedTagReplacement(/* dataBindAttributeValue: */ arguments[4], /* tagToRetain: */ arguments[1], /* nodeName: */ arguments[2], templateEngine);
|
5034 |
-
}).replace(memoizeVirtualContainerBindingSyntaxRegex, function() {
|
5035 |
-
return constructMemoizedTagReplacement(/* dataBindAttributeValue: */ arguments[1], /* tagToRetain: */ "<!-- ko -->", /* nodeName: */ "#comment", templateEngine);
|
5036 |
-
});
|
5037 |
-
},
|
5038 |
-
|
5039 |
-
applyMemoizedBindingsToNextSibling: function (bindings, nodeName) {
|
5040 |
-
return ko.memoization.memoize(function (domNode, bindingContext) {
|
5041 |
-
var nodeToBind = domNode.nextSibling;
|
5042 |
-
if (nodeToBind && nodeToBind.nodeName.toLowerCase() === nodeName) {
|
5043 |
-
ko.applyBindingAccessorsToNode(nodeToBind, bindings, bindingContext);
|
5044 |
-
}
|
5045 |
-
});
|
5046 |
-
}
|
5047 |
-
}
|
5048 |
-
})();
|
5049 |
-
|
5050 |
-
|
5051 |
-
// Exported only because it has to be referenced by string lookup from within rewritten template
|
5052 |
-
ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextSibling);
|
5053 |
-
(function() {
|
5054 |
-
// A template source represents a read/write way of accessing a template. This is to eliminate the need for template loading/saving
|
5055 |
-
// logic to be duplicated in every template engine (and means they can all work with anonymous templates, etc.)
|
5056 |
-
//
|
5057 |
-
// Two are provided by default:
|
5058 |
-
// 1. ko.templateSources.domElement - reads/writes the text content of an arbitrary DOM element
|
5059 |
-
// 2. ko.templateSources.anonymousElement - uses ko.utils.domData to read/write text *associated* with the DOM element, but
|
5060 |
-
// without reading/writing the actual element text content, since it will be overwritten
|
5061 |
-
// with the rendered template output.
|
5062 |
-
// You can implement your own template source if you want to fetch/store templates somewhere other than in DOM elements.
|
5063 |
-
// Template sources need to have the following functions:
|
5064 |
-
// text() - returns the template text from your storage location
|
5065 |
-
// text(value) - writes the supplied template text to your storage location
|
5066 |
-
// data(key) - reads values stored using data(key, value) - see below
|
5067 |
-
// data(key, value) - associates "value" with this template and the key "key". Is used to store information like "isRewritten".
|
5068 |
-
//
|
5069 |
-
// Optionally, template sources can also have the following functions:
|
5070 |
-
// nodes() - returns a DOM element containing the nodes of this template, where available
|
5071 |
-
// nodes(value) - writes the given DOM element to your storage location
|
5072 |
-
// If a DOM element is available for a given template source, template engines are encouraged to use it in preference over text()
|
5073 |
-
// for improved speed. However, all templateSources must supply text() even if they don't supply nodes().
|
5074 |
-
//
|
5075 |
-
// Once you've implemented a templateSource, make your template engine use it by subclassing whatever template engine you were
|
5076 |
-
// using and overriding "makeTemplateSource" to return an instance of your custom template source.
|
5077 |
-
|
5078 |
-
ko.templateSources = {};
|
5079 |
-
|
5080 |
-
// ---- ko.templateSources.domElement -----
|
5081 |
-
|
5082 |
-
// template types
|
5083 |
-
var templateScript = 1,
|
5084 |
-
templateTextArea = 2,
|
5085 |
-
templateTemplate = 3,
|
5086 |
-
templateElement = 4;
|
5087 |
-
|
5088 |
-
ko.templateSources.domElement = function(element) {
|
5089 |
-
this.domElement = element;
|
5090 |
-
|
5091 |
-
if (element) {
|
5092 |
-
var tagNameLower = ko.utils.tagNameLower(element);
|
5093 |
-
this.templateType =
|
5094 |
-
tagNameLower === "script" ? templateScript :
|
5095 |
-
tagNameLower === "textarea" ? templateTextArea :
|
5096 |
-
// For browsers with proper <template> element support, where the .content property gives a document fragment
|
5097 |
-
tagNameLower == "template" && element.content && element.content.nodeType === 11 ? templateTemplate :
|
5098 |
-
templateElement;
|
5099 |
-
}
|
5100 |
-
}
|
5101 |
-
|
5102 |
-
ko.templateSources.domElement.prototype['text'] = function(/* valueToWrite */) {
|
5103 |
-
var elemContentsProperty = this.templateType === templateScript ? "text"
|
5104 |
-
: this.templateType === templateTextArea ? "value"
|
5105 |
-
: "innerHTML";
|
5106 |
-
|
5107 |
-
if (arguments.length == 0) {
|
5108 |
-
return this.domElement[elemContentsProperty];
|
5109 |
-
} else {
|
5110 |
-
var valueToWrite = arguments[0];
|
5111 |
-
if (elemContentsProperty === "innerHTML")
|
5112 |
-
ko.utils.setHtml(this.domElement, valueToWrite);
|
5113 |
-
else
|
5114 |
-
this.domElement[elemContentsProperty] = valueToWrite;
|
5115 |
-
}
|
5116 |
-
};
|
5117 |
-
|
5118 |
-
var dataDomDataPrefix = ko.utils.domData.nextKey() + "_";
|
5119 |
-
ko.templateSources.domElement.prototype['data'] = function(key /*, valueToWrite */) {
|
5120 |
-
if (arguments.length === 1) {
|
5121 |
-
return ko.utils.domData.get(this.domElement, dataDomDataPrefix + key);
|
5122 |
-
} else {
|
5123 |
-
ko.utils.domData.set(this.domElement, dataDomDataPrefix + key, arguments[1]);
|
5124 |
-
}
|
5125 |
-
};
|
5126 |
-
|
5127 |
-
var templatesDomDataKey = ko.utils.domData.nextKey();
|
5128 |
-
function getTemplateDomData(element) {
|
5129 |
-
return ko.utils.domData.get(element, templatesDomDataKey) || {};
|
5130 |
-
}
|
5131 |
-
function setTemplateDomData(element, data) {
|
5132 |
-
ko.utils.domData.set(element, templatesDomDataKey, data);
|
5133 |
-
}
|
5134 |
-
|
5135 |
-
ko.templateSources.domElement.prototype['nodes'] = function(/* valueToWrite */) {
|
5136 |
-
var element = this.domElement;
|
5137 |
-
if (arguments.length == 0) {
|
5138 |
-
var templateData = getTemplateDomData(element),
|
5139 |
-
containerData = templateData.containerData;
|
5140 |
-
return containerData || (
|
5141 |
-
this.templateType === templateTemplate ? element.content :
|
5142 |
-
this.templateType === templateElement ? element :
|
5143 |
-
undefined);
|
5144 |
-
} else {
|
5145 |
-
var valueToWrite = arguments[0];
|
5146 |
-
setTemplateDomData(element, {containerData: valueToWrite});
|
5147 |
-
}
|
5148 |
-
};
|
5149 |
-
|
5150 |
-
// ---- ko.templateSources.anonymousTemplate -----
|
5151 |
-
// Anonymous templates are normally saved/retrieved as DOM nodes through "nodes".
|
5152 |
-
// For compatibility, you can also read "text"; it will be serialized from the nodes on demand.
|
5153 |
-
// Writing to "text" is still supported, but then the template data will not be available as DOM nodes.
|
5154 |
-
|
5155 |
-
ko.templateSources.anonymousTemplate = function(element) {
|
5156 |
-
this.domElement = element;
|
5157 |
-
}
|
5158 |
-
ko.templateSources.anonymousTemplate.prototype = new ko.templateSources.domElement();
|
5159 |
-
ko.templateSources.anonymousTemplate.prototype.constructor = ko.templateSources.anonymousTemplate;
|
5160 |
-
ko.templateSources.anonymousTemplate.prototype['text'] = function(/* valueToWrite */) {
|
5161 |
-
if (arguments.length == 0) {
|
5162 |
-
var templateData = getTemplateDomData(this.domElement);
|
5163 |
-
if (templateData.textData === undefined && templateData.containerData)
|
5164 |
-
templateData.textData = templateData.containerData.innerHTML;
|
5165 |
-
return templateData.textData;
|
5166 |
-
} else {
|
5167 |
-
var valueToWrite = arguments[0];
|
5168 |
-
setTemplateDomData(this.domElement, {textData: valueToWrite});
|
5169 |
-
}
|
5170 |
-
};
|
5171 |
-
|
5172 |
-
ko.exportSymbol('templateSources', ko.templateSources);
|
5173 |
-
ko.exportSymbol('templateSources.domElement', ko.templateSources.domElement);
|
5174 |
-
ko.exportSymbol('templateSources.anonymousTemplate', ko.templateSources.anonymousTemplate);
|
5175 |
-
})();
|
5176 |
-
(function () {
|
5177 |
-
var _templateEngine;
|
5178 |
-
ko.setTemplateEngine = function (templateEngine) {
|
5179 |
-
if ((templateEngine != undefined) && !(templateEngine instanceof ko.templateEngine))
|
5180 |
-
throw new Error("templateEngine must inherit from ko.templateEngine");
|
5181 |
-
_templateEngine = templateEngine;
|
5182 |
-
}
|
5183 |
-
|
5184 |
-
function invokeForEachNodeInContinuousRange(firstNode, lastNode, action) {
|
5185 |
-
var node, nextInQueue = firstNode, firstOutOfRangeNode = ko.virtualElements.nextSibling(lastNode);
|
5186 |
-
while (nextInQueue && ((node = nextInQueue) !== firstOutOfRangeNode)) {
|
5187 |
-
nextInQueue = ko.virtualElements.nextSibling(node);
|
5188 |
-
action(node, nextInQueue);
|
5189 |
-
}
|
5190 |
-
}
|
5191 |
-
|
5192 |
-
function activateBindingsOnContinuousNodeArray(continuousNodeArray, bindingContext) {
|
5193 |
-
// To be used on any nodes that have been rendered by a template and have been inserted into some parent element
|
5194 |
-
// Walks through continuousNodeArray (which *must* be continuous, i.e., an uninterrupted sequence of sibling nodes, because
|
5195 |
-
// the algorithm for walking them relies on this), and for each top-level item in the virtual-element sense,
|
5196 |
-
// (1) Does a regular "applyBindings" to associate bindingContext with this node and to activate any non-memoized bindings
|
5197 |
-
// (2) Unmemoizes any memos in the DOM subtree (e.g., to activate bindings that had been memoized during template rewriting)
|
5198 |
-
|
5199 |
-
if (continuousNodeArray.length) {
|
5200 |
-
var firstNode = continuousNodeArray[0],
|
5201 |
-
lastNode = continuousNodeArray[continuousNodeArray.length - 1],
|
5202 |
-
parentNode = firstNode.parentNode,
|
5203 |
-
provider = ko.bindingProvider['instance'],
|
5204 |
-
preprocessNode = provider['preprocessNode'];
|
5205 |
-
|
5206 |
-
if (preprocessNode) {
|
5207 |
-
invokeForEachNodeInContinuousRange(firstNode, lastNode, function(node, nextNodeInRange) {
|
5208 |
-
var nodePreviousSibling = node.previousSibling;
|
5209 |
-
var newNodes = preprocessNode.call(provider, node);
|
5210 |
-
if (newNodes) {
|
5211 |
-
if (node === firstNode)
|
5212 |
-
firstNode = newNodes[0] || nextNodeInRange;
|
5213 |
-
if (node === lastNode)
|
5214 |
-
lastNode = newNodes[newNodes.length - 1] || nodePreviousSibling;
|
5215 |
-
}
|
5216 |
-
});
|
5217 |
-
|
5218 |
-
// Because preprocessNode can change the nodes, including the first and last nodes, update continuousNodeArray to match.
|
5219 |
-
// We need the full set, including inner nodes, because the unmemoize step might remove the first node (and so the real
|
5220 |
-
// first node needs to be in the array).
|
5221 |
-
continuousNodeArray.length = 0;
|
5222 |
-
if (!firstNode) { // preprocessNode might have removed all the nodes, in which case there's nothing left to do
|
5223 |
-
return;
|
5224 |
-
}
|
5225 |
-
if (firstNode === lastNode) {
|
5226 |
-
continuousNodeArray.push(firstNode);
|
5227 |
-
} else {
|
5228 |
-
continuousNodeArray.push(firstNode, lastNode);
|
5229 |
-
ko.utils.fixUpContinuousNodeArray(continuousNodeArray, parentNode);
|
5230 |
-
}
|
5231 |
-
}
|
5232 |
-
|
5233 |
-
// Need to applyBindings *before* unmemoziation, because unmemoization might introduce extra nodes (that we don't want to re-bind)
|
5234 |
-
// whereas a regular applyBindings won't introduce new memoized nodes
|
5235 |
-
invokeForEachNodeInContinuousRange(firstNode, lastNode, function(node) {
|
5236 |
-
if (node.nodeType === 1 || node.nodeType === 8)
|
5237 |
-
ko.applyBindings(bindingContext, node);
|
5238 |
-
});
|
5239 |
-
invokeForEachNodeInContinuousRange(firstNode, lastNode, function(node) {
|
5240 |
-
if (node.nodeType === 1 || node.nodeType === 8)
|
5241 |
-
ko.memoization.unmemoizeDomNodeAndDescendants(node, [bindingContext]);
|
5242 |
-
});
|
5243 |
-
|
5244 |
-
// Make sure any changes done by applyBindings or unmemoize are reflected in the array
|
5245 |
-
ko.utils.fixUpContinuousNodeArray(continuousNodeArray, parentNode);
|
5246 |
-
}
|
5247 |
-
}
|
5248 |
-
|
5249 |
-
function getFirstNodeFromPossibleArray(nodeOrNodeArray) {
|
5250 |
-
return nodeOrNodeArray.nodeType ? nodeOrNodeArray
|
5251 |
-
: nodeOrNodeArray.length > 0 ? nodeOrNodeArray[0]
|
5252 |
-
: null;
|
5253 |
-
}
|
5254 |
-
|
5255 |
-
function executeTemplate(targetNodeOrNodeArray, renderMode, template, bindingContext, options) {
|
5256 |
-
options = options || {};
|
5257 |
-
var firstTargetNode = targetNodeOrNodeArray && getFirstNodeFromPossibleArray(targetNodeOrNodeArray);
|
5258 |
-
var templateDocument = (firstTargetNode || template || {}).ownerDocument;
|
5259 |
-
var templateEngineToUse = (options['templateEngine'] || _templateEngine);
|
5260 |
-
ko.templateRewriting.ensureTemplateIsRewritten(template, templateEngineToUse, templateDocument);
|
5261 |
-
var renderedNodesArray = templateEngineToUse['renderTemplate'](template, bindingContext, options, templateDocument);
|
5262 |
-
|
5263 |
-
// Loosely check result is an array of DOM nodes
|
5264 |
-
if ((typeof renderedNodesArray.length != "number") || (renderedNodesArray.length > 0 && typeof renderedNodesArray[0].nodeType != "number"))
|
5265 |
-
throw new Error("Template engine must return an array of DOM nodes");
|
5266 |
-
|
5267 |
-
var haveAddedNodesToParent = false;
|
5268 |
-
switch (renderMode) {
|
5269 |
-
case "replaceChildren":
|
5270 |
-
ko.virtualElements.setDomNodeChildren(targetNodeOrNodeArray, renderedNodesArray);
|
5271 |
-
haveAddedNodesToParent = true;
|
5272 |
-
break;
|
5273 |
-
case "replaceNode":
|
5274 |
-
ko.utils.replaceDomNodes(targetNodeOrNodeArray, renderedNodesArray);
|
5275 |
-
haveAddedNodesToParent = true;
|
5276 |
-
break;
|
5277 |
-
case "ignoreTargetNode": break;
|
5278 |
-
default:
|
5279 |
-
throw new Error("Unknown renderMode: " + renderMode);
|
5280 |
-
}
|
5281 |
-
|
5282 |
-
if (haveAddedNodesToParent) {
|
5283 |
-
activateBindingsOnContinuousNodeArray(renderedNodesArray, bindingContext);
|
5284 |
-
if (options['afterRender'])
|
5285 |
-
ko.dependencyDetection.ignore(options['afterRender'], null, [renderedNodesArray, bindingContext['$data']]);
|
5286 |
-
}
|
5287 |
-
|
5288 |
-
return renderedNodesArray;
|
5289 |
-
}
|
5290 |
-
|
5291 |
-
function resolveTemplateName(template, data, context) {
|
5292 |
-
// The template can be specified as:
|
5293 |
-
if (ko.isObservable(template)) {
|
5294 |
-
// 1. An observable, with string value
|
5295 |
-
return template();
|
5296 |
-
} else if (typeof template === 'function') {
|
5297 |
-
// 2. A function of (data, context) returning a string
|
5298 |
-
return template(data, context);
|
5299 |
-
} else {
|
5300 |
-
// 3. A string
|
5301 |
-
return template;
|
5302 |
-
}
|
5303 |
-
}
|
5304 |
-
|
5305 |
-
ko.renderTemplate = function (template, dataOrBindingContext, options, targetNodeOrNodeArray, renderMode) {
|
5306 |
-
options = options || {};
|
5307 |
-
if ((options['templateEngine'] || _templateEngine) == undefined)
|
5308 |
-
throw new Error("Set a template engine before calling renderTemplate");
|
5309 |
-
renderMode = renderMode || "replaceChildren";
|
5310 |
-
|
5311 |
-
if (targetNodeOrNodeArray) {
|
5312 |
-
var firstTargetNode = getFirstNodeFromPossibleArray(targetNodeOrNodeArray);
|
5313 |
-
|
5314 |
-
var whenToDispose = function () { return (!firstTargetNode) || !ko.utils.domNodeIsAttachedToDocument(firstTargetNode); }; // Passive disposal (on next evaluation)
|
5315 |
-
var activelyDisposeWhenNodeIsRemoved = (firstTargetNode && renderMode == "replaceNode") ? firstTargetNode.parentNode : firstTargetNode;
|
5316 |
-
|
5317 |
-
return ko.dependentObservable( // So the DOM is automatically updated when any dependency changes
|
5318 |
-
function () {
|
5319 |
-
// Ensure we've got a proper binding context to work with
|
5320 |
-
var bindingContext = (dataOrBindingContext && (dataOrBindingContext instanceof ko.bindingContext))
|
5321 |
-
? dataOrBindingContext
|
5322 |
-
: new ko.bindingContext(ko.utils.unwrapObservable(dataOrBindingContext));
|
5323 |
-
|
5324 |
-
var templateName = resolveTemplateName(template, bindingContext['$data'], bindingContext),
|
5325 |
-
renderedNodesArray = executeTemplate(targetNodeOrNodeArray, renderMode, templateName, bindingContext, options);
|
5326 |
-
|
5327 |
-
if (renderMode == "replaceNode") {
|
5328 |
-
targetNodeOrNodeArray = renderedNodesArray;
|
5329 |
-
firstTargetNode = getFirstNodeFromPossibleArray(targetNodeOrNodeArray);
|
5330 |
-
}
|
5331 |
-
},
|
5332 |
-
null,
|
5333 |
-
{ disposeWhen: whenToDispose, disposeWhenNodeIsRemoved: activelyDisposeWhenNodeIsRemoved }
|
5334 |
-
);
|
5335 |
-
} else {
|
5336 |
-
// We don't yet have a DOM node to evaluate, so use a memo and render the template later when there is a DOM node
|
5337 |
-
return ko.memoization.memoize(function (domNode) {
|
5338 |
-
ko.renderTemplate(template, dataOrBindingContext, options, domNode, "replaceNode");
|
5339 |
-
});
|
5340 |
-
}
|
5341 |
-
};
|
5342 |
-
|
5343 |
-
ko.renderTemplateForEach = function (template, arrayOrObservableArray, options, targetNode, parentBindingContext) {
|
5344 |
-
// Since setDomNodeChildrenFromArrayMapping always calls executeTemplateForArrayItem and then
|
5345 |
-
// activateBindingsCallback for added items, we can store the binding context in the former to use in the latter.
|
5346 |
-
var arrayItemContext;
|
5347 |
-
|
5348 |
-
// This will be called by setDomNodeChildrenFromArrayMapping to get the nodes to add to targetNode
|
5349 |
-
var executeTemplateForArrayItem = function (arrayValue, index) {
|
5350 |
-
// Support selecting template as a function of the data being rendered
|
5351 |
-
arrayItemContext = parentBindingContext['createChildContext'](arrayValue, options['as'], function(context) {
|
5352 |
-
context['$index'] = index;
|
5353 |
-
});
|
5354 |
-
|
5355 |
-
var templateName = resolveTemplateName(template, arrayValue, arrayItemContext);
|
5356 |
-
return executeTemplate(null, "ignoreTargetNode", templateName, arrayItemContext, options);
|
5357 |
-
}
|
5358 |
-
|
5359 |
-
// This will be called whenever setDomNodeChildrenFromArrayMapping has added nodes to targetNode
|
5360 |
-
var activateBindingsCallback = function(arrayValue, addedNodesArray, index) {
|
5361 |
-
activateBindingsOnContinuousNodeArray(addedNodesArray, arrayItemContext);
|
5362 |
-
if (options['afterRender'])
|
5363 |
-
options['afterRender'](addedNodesArray, arrayValue);
|
5364 |
-
|
5365 |
-
// release the "cache" variable, so that it can be collected by
|
5366 |
-
// the GC when its value isn't used from within the bindings anymore.
|
5367 |
-
arrayItemContext = null;
|
5368 |
-
};
|
5369 |
-
|
5370 |
-
return ko.dependentObservable(function () {
|
5371 |
-
var unwrappedArray = ko.utils.unwrapObservable(arrayOrObservableArray) || [];
|
5372 |
-
if (typeof unwrappedArray.length == "undefined") // Coerce single value into array
|
5373 |
-
unwrappedArray = [unwrappedArray];
|
5374 |
-
|
5375 |
-
// Filter out any entries marked as destroyed
|
5376 |
-
var filteredArray = ko.utils.arrayFilter(unwrappedArray, function(item) {
|
5377 |
-
return options['includeDestroyed'] || item === undefined || item === null || !ko.utils.unwrapObservable(item['_destroy']);
|
5378 |
-
});
|
5379 |
-
|
5380 |
-
// Call setDomNodeChildrenFromArrayMapping, ignoring any observables unwrapped within (most likely from a callback function).
|
5381 |
-
// If the array items are observables, though, they will be unwrapped in executeTemplateForArrayItem and managed within setDomNodeChildrenFromArrayMapping.
|
5382 |
-
ko.dependencyDetection.ignore(ko.utils.setDomNodeChildrenFromArrayMapping, null, [targetNode, filteredArray, executeTemplateForArrayItem, options, activateBindingsCallback]);
|
5383 |
-
|
5384 |
-
}, null, { disposeWhenNodeIsRemoved: targetNode });
|
5385 |
-
};
|
5386 |
-
|
5387 |
-
var templateComputedDomDataKey = ko.utils.domData.nextKey();
|
5388 |
-
function disposeOldComputedAndStoreNewOne(element, newComputed) {
|
5389 |
-
var oldComputed = ko.utils.domData.get(element, templateComputedDomDataKey);
|
5390 |
-
if (oldComputed && (typeof(oldComputed.dispose) == 'function'))
|
5391 |
-
oldComputed.dispose();
|
5392 |
-
ko.utils.domData.set(element, templateComputedDomDataKey, (newComputed && newComputed.isActive()) ? newComputed : undefined);
|
5393 |
-
}
|
5394 |
-
|
5395 |
-
ko.bindingHandlers['template'] = {
|
5396 |
-
'init': function(element, valueAccessor) {
|
5397 |
-
// Support anonymous templates
|
5398 |
-
var bindingValue = ko.utils.unwrapObservable(valueAccessor());
|
5399 |
-
if (typeof bindingValue == "string" || bindingValue['name']) {
|
5400 |
-
// It's a named template - clear the element
|
5401 |
-
ko.virtualElements.emptyNode(element);
|
5402 |
-
} else if ('nodes' in bindingValue) {
|
5403 |
-
// We've been given an array of DOM nodes. Save them as the template source.
|
5404 |
-
// There is no known use case for the node array being an observable array (if the output
|
5405 |
-
// varies, put that behavior *into* your template - that's what templates are for), and
|
5406 |
-
// the implementation would be a mess, so assert that it's not observable.
|
5407 |
-
var nodes = bindingValue['nodes'] || [];
|
5408 |
-
if (ko.isObservable(nodes)) {
|
5409 |
-
throw new Error('The "nodes" option must be a plain, non-observable array.');
|
5410 |
-
}
|
5411 |
-
var container = ko.utils.moveCleanedNodesToContainerElement(nodes); // This also removes the nodes from their current parent
|
5412 |
-
new ko.templateSources.anonymousTemplate(element)['nodes'](container);
|
5413 |
-
} else {
|
5414 |
-
// It's an anonymous template - store the element contents, then clear the element
|
5415 |
-
var templateNodes = ko.virtualElements.childNodes(element),
|
5416 |
-
container = ko.utils.moveCleanedNodesToContainerElement(templateNodes); // This also removes the nodes from their current parent
|
5417 |
-
new ko.templateSources.anonymousTemplate(element)['nodes'](container);
|
5418 |
-
}
|
5419 |
-
return { 'controlsDescendantBindings': true };
|
5420 |
-
},
|
5421 |
-
'update': function (element, valueAccessor, allBindings, viewModel, bindingContext) {
|
5422 |
-
var value = valueAccessor(),
|
5423 |
-
dataValue,
|
5424 |
-
options = ko.utils.unwrapObservable(value),
|
5425 |
-
shouldDisplay = true,
|
5426 |
-
templateComputed = null,
|
5427 |
-
templateName;
|
5428 |
-
|
5429 |
-
if (typeof options == "string") {
|
5430 |
-
templateName = value;
|
5431 |
-
options = {};
|
5432 |
-
} else {
|
5433 |
-
templateName = options['name'];
|
5434 |
-
|
5435 |
-
// Support "if"/"ifnot" conditions
|
5436 |
-
if ('if' in options)
|
5437 |
-
shouldDisplay = ko.utils.unwrapObservable(options['if']);
|
5438 |
-
if (shouldDisplay && 'ifnot' in options)
|
5439 |
-
shouldDisplay = !ko.utils.unwrapObservable(options['ifnot']);
|
5440 |
-
|
5441 |
-
dataValue = ko.utils.unwrapObservable(options['data']);
|
5442 |
-
}
|
5443 |
-
|
5444 |
-
if ('foreach' in options) {
|
5445 |
-
// Render once for each data point (treating data set as empty if shouldDisplay==false)
|
5446 |
-
var dataArray = (shouldDisplay && options['foreach']) || [];
|
5447 |
-
templateComputed = ko.renderTemplateForEach(templateName || element, dataArray, options, element, bindingContext);
|
5448 |
-
} else if (!shouldDisplay) {
|
5449 |
-
ko.virtualElements.emptyNode(element);
|
5450 |
-
} else {
|
5451 |
-
// Render once for this single data point (or use the viewModel if no data was provided)
|
5452 |
-
var innerBindingContext = ('data' in options) ?
|
5453 |
-
bindingContext['createChildContext'](dataValue, options['as']) : // Given an explitit 'data' value, we create a child binding context for it
|
5454 |
-
bindingContext; // Given no explicit 'data' value, we retain the same binding context
|
5455 |
-
templateComputed = ko.renderTemplate(templateName || element, innerBindingContext, options, element);
|
5456 |
-
}
|
5457 |
-
|
5458 |
-
// It only makes sense to have a single template computed per element (otherwise which one should have its output displayed?)
|
5459 |
-
disposeOldComputedAndStoreNewOne(element, templateComputed);
|
5460 |
-
}
|
5461 |
-
};
|
5462 |
-
|
5463 |
-
// Anonymous templates can't be rewritten. Give a nice error message if you try to do it.
|
5464 |
-
ko.expressionRewriting.bindingRewriteValidators['template'] = function(bindingValue) {
|
5465 |
-
var parsedBindingValue = ko.expressionRewriting.parseObjectLiteral(bindingValue);
|
5466 |
-
|
5467 |
-
if ((parsedBindingValue.length == 1) && parsedBindingValue[0]['unknown'])
|
5468 |
-
return null; // It looks like a string literal, not an object literal, so treat it as a named template (which is allowed for rewriting)
|
5469 |
-
|
5470 |
-
if (ko.expressionRewriting.keyValueArrayContainsKey(parsedBindingValue, "name"))
|
5471 |
-
return null; // Named templates can be rewritten, so return "no error"
|
5472 |
-
return "This template engine does not support anonymous templates nested within its templates";
|
5473 |
-
};
|
5474 |
-
|
5475 |
-
ko.virtualElements.allowedBindings['template'] = true;
|
5476 |
-
})();
|
5477 |
-
|
5478 |
-
ko.exportSymbol('setTemplateEngine', ko.setTemplateEngine);
|
5479 |
-
ko.exportSymbol('renderTemplate', ko.renderTemplate);
|
5480 |
-
// Go through the items that have been added and deleted and try to find matches between them.
|
5481 |
-
ko.utils.findMovesInArrayComparison = function (left, right, limitFailedCompares) {
|
5482 |
-
if (left.length && right.length) {
|
5483 |
-
var failedCompares, l, r, leftItem, rightItem;
|
5484 |
-
for (failedCompares = l = 0; (!limitFailedCompares || failedCompares < limitFailedCompares) && (leftItem = left[l]); ++l) {
|
5485 |
-
for (r = 0; rightItem = right[r]; ++r) {
|
5486 |
-
if (leftItem['value'] === rightItem['value']) {
|
5487 |
-
leftItem['moved'] = rightItem['index'];
|
5488 |
-
rightItem['moved'] = leftItem['index'];
|
5489 |
-
right.splice(r, 1); // This item is marked as moved; so remove it from right list
|
5490 |
-
failedCompares = r = 0; // Reset failed compares count because we're checking for consecutive failures
|
5491 |
-
break;
|
5492 |
-
}
|
5493 |
-
}
|
5494 |
-
failedCompares += r;
|
5495 |
-
}
|
5496 |
-
}
|
5497 |
-
};
|
5498 |
-
|
5499 |
-
ko.utils.compareArrays = (function () {
|
5500 |
-
var statusNotInOld = 'added', statusNotInNew = 'deleted';
|
5501 |
-
|
5502 |
-
// Simple calculation based on Levenshtein distance.
|
5503 |
-
function compareArrays(oldArray, newArray, options) {
|
5504 |
-
// For backward compatibility, if the third arg is actually a bool, interpret
|
5505 |
-
// it as the old parameter 'dontLimitMoves'. Newer code should use { dontLimitMoves: true }.
|
5506 |
-
options = (typeof options === 'boolean') ? { 'dontLimitMoves': options } : (options || {});
|
5507 |
-
oldArray = oldArray || [];
|
5508 |
-
newArray = newArray || [];
|
5509 |
-
|
5510 |
-
if (oldArray.length < newArray.length)
|
5511 |
-
return compareSmallArrayToBigArray(oldArray, newArray, statusNotInOld, statusNotInNew, options);
|
5512 |
-
else
|
5513 |
-
return compareSmallArrayToBigArray(newArray, oldArray, statusNotInNew, statusNotInOld, options);
|
5514 |
-
}
|
5515 |
-
|
5516 |
-
function compareSmallArrayToBigArray(smlArray, bigArray, statusNotInSml, statusNotInBig, options) {
|
5517 |
-
var myMin = Math.min,
|
5518 |
-
myMax = Math.max,
|
5519 |
-
editDistanceMatrix = [],
|
5520 |
-
smlIndex, smlIndexMax = smlArray.length,
|
5521 |
-
bigIndex, bigIndexMax = bigArray.length,
|
5522 |
-
compareRange = (bigIndexMax - smlIndexMax) || 1,
|
5523 |
-
maxDistance = smlIndexMax + bigIndexMax + 1,
|
5524 |
-
thisRow, lastRow,
|
5525 |
-
bigIndexMaxForRow, bigIndexMinForRow;
|
5526 |
-
|
5527 |
-
for (smlIndex = 0; smlIndex <= smlIndexMax; smlIndex++) {
|
5528 |
-
lastRow = thisRow;
|
5529 |
-
editDistanceMatrix.push(thisRow = []);
|
5530 |
-
bigIndexMaxForRow = myMin(bigIndexMax, smlIndex + compareRange);
|
5531 |
-
bigIndexMinForRow = myMax(0, smlIndex - 1);
|
5532 |
-
for (bigIndex = bigIndexMinForRow; bigIndex <= bigIndexMaxForRow; bigIndex++) {
|
5533 |
-
if (!bigIndex)
|
5534 |
-
thisRow[bigIndex] = smlIndex + 1;
|
5535 |
-
else if (!smlIndex) // Top row - transform empty array into new array via additions
|
5536 |
-
thisRow[bigIndex] = bigIndex + 1;
|
5537 |
-
else if (smlArray[smlIndex - 1] === bigArray[bigIndex - 1])
|
5538 |
-
thisRow[bigIndex] = lastRow[bigIndex - 1]; // copy value (no edit)
|
5539 |
-
else {
|
5540 |
-
var northDistance = lastRow[bigIndex] || maxDistance; // not in big (deletion)
|
5541 |
-
var westDistance = thisRow[bigIndex - 1] || maxDistance; // not in small (addition)
|
5542 |
-
thisRow[bigIndex] = myMin(northDistance, westDistance) + 1;
|
5543 |
-
}
|
5544 |
-
}
|
5545 |
-
}
|
5546 |
-
|
5547 |
-
var editScript = [], meMinusOne, notInSml = [], notInBig = [];
|
5548 |
-
for (smlIndex = smlIndexMax, bigIndex = bigIndexMax; smlIndex || bigIndex;) {
|
5549 |
-
meMinusOne = editDistanceMatrix[smlIndex][bigIndex] - 1;
|
5550 |
-
if (bigIndex && meMinusOne === editDistanceMatrix[smlIndex][bigIndex-1]) {
|
5551 |
-
notInSml.push(editScript[editScript.length] = { // added
|
5552 |
-
'status': statusNotInSml,
|
5553 |
-
'value': bigArray[--bigIndex],
|
5554 |
-
'index': bigIndex });
|
5555 |
-
} else if (smlIndex && meMinusOne === editDistanceMatrix[smlIndex - 1][bigIndex]) {
|
5556 |
-
notInBig.push(editScript[editScript.length] = { // deleted
|
5557 |
-
'status': statusNotInBig,
|
5558 |
-
'value': smlArray[--smlIndex],
|
5559 |
-
'index': smlIndex });
|
5560 |
-
} else {
|
5561 |
-
--bigIndex;
|
5562 |
-
--smlIndex;
|
5563 |
-
if (!options['sparse']) {
|
5564 |
-
editScript.push({
|
5565 |
-
'status': "retained",
|
5566 |
-
'value': bigArray[bigIndex] });
|
5567 |
-
}
|
5568 |
-
}
|
5569 |
-
}
|
5570 |
-
|
5571 |
-
// Set a limit on the number of consecutive non-matching comparisons; having it a multiple of
|
5572 |
-
// smlIndexMax keeps the time complexity of this algorithm linear.
|
5573 |
-
ko.utils.findMovesInArrayComparison(notInBig, notInSml, !options['dontLimitMoves'] && smlIndexMax * 10);
|
5574 |
-
|
5575 |
-
return editScript.reverse();
|
5576 |
-
}
|
5577 |
-
|
5578 |
-
return compareArrays;
|
5579 |
-
})();
|
5580 |
-
|
5581 |
-
ko.exportSymbol('utils.compareArrays', ko.utils.compareArrays);
|
5582 |
-
(function () {
|
5583 |
-
// Objective:
|
5584 |
-
// * Given an input array, a container DOM node, and a function from array elements to arrays of DOM nodes,
|
5585 |
-
// map the array elements to arrays of DOM nodes, concatenate together all these arrays, and use them to populate the container DOM node
|
5586 |
-
// * Next time we're given the same combination of things (with the array possibly having mutated), update the container DOM node
|
5587 |
-
// so that its children is again the concatenation of the mappings of the array elements, but don't re-map any array elements that we
|
5588 |
-
// previously mapped - retain those nodes, and just insert/delete other ones
|
5589 |
-
|
5590 |
-
// "callbackAfterAddingNodes" will be invoked after any "mapping"-generated nodes are inserted into the container node
|
5591 |
-
// You can use this, for example, to activate bindings on those nodes.
|
5592 |
-
|
5593 |
-
function mapNodeAndRefreshWhenChanged(containerNode, mapping, valueToMap, callbackAfterAddingNodes, index) {
|
5594 |
-
// Map this array value inside a dependentObservable so we re-map when any dependency changes
|
5595 |
-
var mappedNodes = [];
|
5596 |
-
var dependentObservable = ko.dependentObservable(function() {
|
5597 |
-
var newMappedNodes = mapping(valueToMap, index, ko.utils.fixUpContinuousNodeArray(mappedNodes, containerNode)) || [];
|
5598 |
-
|
5599 |
-
// On subsequent evaluations, just replace the previously-inserted DOM nodes
|
5600 |
-
if (mappedNodes.length > 0) {
|
5601 |
-
ko.utils.replaceDomNodes(mappedNodes, newMappedNodes);
|
5602 |
-
if (callbackAfterAddingNodes)
|
5603 |
-
ko.dependencyDetection.ignore(callbackAfterAddingNodes, null, [valueToMap, newMappedNodes, index]);
|
5604 |
-
}
|
5605 |
-
|
5606 |
-
// Replace the contents of the mappedNodes array, thereby updating the record
|
5607 |
-
// of which nodes would be deleted if valueToMap was itself later removed
|
5608 |
-
mappedNodes.length = 0;
|
5609 |
-
ko.utils.arrayPushAll(mappedNodes, newMappedNodes);
|
5610 |
-
}, null, { disposeWhenNodeIsRemoved: containerNode, disposeWhen: function() { return !ko.utils.anyDomNodeIsAttachedToDocument(mappedNodes); } });
|
5611 |
-
return { mappedNodes : mappedNodes, dependentObservable : (dependentObservable.isActive() ? dependentObservable : undefined) };
|
5612 |
-
}
|
5613 |
-
|
5614 |
-
var lastMappingResultDomDataKey = ko.utils.domData.nextKey(),
|
5615 |
-
deletedItemDummyValue = ko.utils.domData.nextKey();
|
5616 |
-
|
5617 |
-
ko.utils.setDomNodeChildrenFromArrayMapping = function (domNode, array, mapping, options, callbackAfterAddingNodes) {
|
5618 |
-
// Compare the provided array against the previous one
|
5619 |
-
array = array || [];
|
5620 |
-
options = options || {};
|
5621 |
-
var isFirstExecution = ko.utils.domData.get(domNode, lastMappingResultDomDataKey) === undefined;
|
5622 |
-
var lastMappingResult = ko.utils.domData.get(domNode, lastMappingResultDomDataKey) || [];
|
5623 |
-
var lastArray = ko.utils.arrayMap(lastMappingResult, function (x) { return x.arrayEntry; });
|
5624 |
-
var editScript = ko.utils.compareArrays(lastArray, array, options['dontLimitMoves']);
|
5625 |
-
|
5626 |
-
// Build the new mapping result
|
5627 |
-
var newMappingResult = [];
|
5628 |
-
var lastMappingResultIndex = 0;
|
5629 |
-
var newMappingResultIndex = 0;
|
5630 |
-
|
5631 |
-
var nodesToDelete = [];
|
5632 |
-
var itemsToProcess = [];
|
5633 |
-
var itemsForBeforeRemoveCallbacks = [];
|
5634 |
-
var itemsForMoveCallbacks = [];
|
5635 |
-
var itemsForAfterAddCallbacks = [];
|
5636 |
-
var mapData;
|
5637 |
-
|
5638 |
-
function itemMovedOrRetained(editScriptIndex, oldPosition) {
|
5639 |
-
mapData = lastMappingResult[oldPosition];
|
5640 |
-
if (newMappingResultIndex !== oldPosition)
|
5641 |
-
itemsForMoveCallbacks[editScriptIndex] = mapData;
|
5642 |
-
// Since updating the index might change the nodes, do so before calling fixUpContinuousNodeArray
|
5643 |
-
mapData.indexObservable(newMappingResultIndex++);
|
5644 |
-
ko.utils.fixUpContinuousNodeArray(mapData.mappedNodes, domNode);
|
5645 |
-
newMappingResult.push(mapData);
|
5646 |
-
itemsToProcess.push(mapData);
|
5647 |
-
}
|
5648 |
-
|
5649 |
-
function callCallback(callback, items) {
|
5650 |
-
if (callback) {
|
5651 |
-
for (var i = 0, n = items.length; i < n; i++) {
|
5652 |
-
if (items[i]) {
|
5653 |
-
ko.utils.arrayForEach(items[i].mappedNodes, function(node) {
|
5654 |
-
callback(node, i, items[i].arrayEntry);
|
5655 |
-
});
|
5656 |
-
}
|
5657 |
-
}
|
5658 |
-
}
|
5659 |
-
}
|
5660 |
-
|
5661 |
-
for (var i = 0, editScriptItem, movedIndex; editScriptItem = editScript[i]; i++) {
|
5662 |
-
movedIndex = editScriptItem['moved'];
|
5663 |
-
switch (editScriptItem['status']) {
|
5664 |
-
case "deleted":
|
5665 |
-
if (movedIndex === undefined) {
|
5666 |
-
mapData = lastMappingResult[lastMappingResultIndex];
|
5667 |
-
|
5668 |
-
// Stop tracking changes to the mapping for these nodes
|
5669 |
-
if (mapData.dependentObservable) {
|
5670 |
-
mapData.dependentObservable.dispose();
|
5671 |
-
mapData.dependentObservable = undefined;
|
5672 |
-
}
|
5673 |
-
|
5674 |
-
// Queue these nodes for later removal
|
5675 |
-
if (ko.utils.fixUpContinuousNodeArray(mapData.mappedNodes, domNode).length) {
|
5676 |
-
if (options['beforeRemove']) {
|
5677 |
-
newMappingResult.push(mapData);
|
5678 |
-
itemsToProcess.push(mapData);
|
5679 |
-
if (mapData.arrayEntry === deletedItemDummyValue) {
|
5680 |
-
mapData = null;
|
5681 |
-
} else {
|
5682 |
-
itemsForBeforeRemoveCallbacks[i] = mapData;
|
5683 |
-
}
|
5684 |
-
}
|
5685 |
-
if (mapData) {
|
5686 |
-
nodesToDelete.push.apply(nodesToDelete, mapData.mappedNodes);
|
5687 |
-
}
|
5688 |
-
}
|
5689 |
-
}
|
5690 |
-
lastMappingResultIndex++;
|
5691 |
-
break;
|
5692 |
-
|
5693 |
-
case "retained":
|
5694 |
-
itemMovedOrRetained(i, lastMappingResultIndex++);
|
5695 |
-
break;
|
5696 |
-
|
5697 |
-
case "added":
|
5698 |
-
if (movedIndex !== undefined) {
|
5699 |
-
itemMovedOrRetained(i, movedIndex);
|
5700 |
-
} else {
|
5701 |
-
mapData = { arrayEntry: editScriptItem['value'], indexObservable: ko.observable(newMappingResultIndex++) };
|
5702 |
-
newMappingResult.push(mapData);
|
5703 |
-
itemsToProcess.push(mapData);
|
5704 |
-
if (!isFirstExecution)
|
5705 |
-
itemsForAfterAddCallbacks[i] = mapData;
|
5706 |
-
}
|
5707 |
-
break;
|
5708 |
-
}
|
5709 |
-
}
|
5710 |
-
|
5711 |
-
// Store a copy of the array items we just considered so we can difference it next time
|
5712 |
-
ko.utils.domData.set(domNode, lastMappingResultDomDataKey, newMappingResult);
|
5713 |
-
|
5714 |
-
// Call beforeMove first before any changes have been made to the DOM
|
5715 |
-
callCallback(options['beforeMove'], itemsForMoveCallbacks);
|
5716 |
-
|
5717 |
-
// Next remove nodes for deleted items (or just clean if there's a beforeRemove callback)
|
5718 |
-
ko.utils.arrayForEach(nodesToDelete, options['beforeRemove'] ? ko.cleanNode : ko.removeNode);
|
5719 |
-
|
5720 |
-
// Next add/reorder the remaining items (will include deleted items if there's a beforeRemove callback)
|
5721 |
-
for (var i = 0, nextNode = ko.virtualElements.firstChild(domNode), lastNode, node; mapData = itemsToProcess[i]; i++) {
|
5722 |
-
// Get nodes for newly added items
|
5723 |
-
if (!mapData.mappedNodes)
|
5724 |
-
ko.utils.extend(mapData, mapNodeAndRefreshWhenChanged(domNode, mapping, mapData.arrayEntry, callbackAfterAddingNodes, mapData.indexObservable));
|
5725 |
-
|
5726 |
-
// Put nodes in the right place if they aren't there already
|
5727 |
-
for (var j = 0; node = mapData.mappedNodes[j]; nextNode = node.nextSibling, lastNode = node, j++) {
|
5728 |
-
if (node !== nextNode)
|
5729 |
-
ko.virtualElements.insertAfter(domNode, node, lastNode);
|
5730 |
-
}
|
5731 |
-
|
5732 |
-
// Run the callbacks for newly added nodes (for example, to apply bindings, etc.)
|
5733 |
-
if (!mapData.initialized && callbackAfterAddingNodes) {
|
5734 |
-
callbackAfterAddingNodes(mapData.arrayEntry, mapData.mappedNodes, mapData.indexObservable);
|
5735 |
-
mapData.initialized = true;
|
5736 |
-
}
|
5737 |
-
}
|
5738 |
-
|
5739 |
-
// If there's a beforeRemove callback, call it after reordering.
|
5740 |
-
// Note that we assume that the beforeRemove callback will usually be used to remove the nodes using
|
5741 |
-
// some sort of animation, which is why we first reorder the nodes that will be removed. If the
|
5742 |
-
// callback instead removes the nodes right away, it would be more efficient to skip reordering them.
|
5743 |
-
// Perhaps we'll make that change in the future if this scenario becomes more common.
|
5744 |
-
callCallback(options['beforeRemove'], itemsForBeforeRemoveCallbacks);
|
5745 |
-
|
5746 |
-
// Replace the stored values of deleted items with a dummy value. This provides two benefits: it marks this item
|
5747 |
-
// as already "removed" so we won't call beforeRemove for it again, and it ensures that the item won't match up
|
5748 |
-
// with an actual item in the array and appear as "retained" or "moved".
|
5749 |
-
for (i = 0; i < itemsForBeforeRemoveCallbacks.length; ++i) {
|
5750 |
-
if (itemsForBeforeRemoveCallbacks[i]) {
|
5751 |
-
itemsForBeforeRemoveCallbacks[i].arrayEntry = deletedItemDummyValue;
|
5752 |
-
}
|
5753 |
-
}
|
5754 |
-
|
5755 |
-
// Finally call afterMove and afterAdd callbacks
|
5756 |
-
callCallback(options['afterMove'], itemsForMoveCallbacks);
|
5757 |
-
callCallback(options['afterAdd'], itemsForAfterAddCallbacks);
|
5758 |
-
}
|
5759 |
-
})();
|
5760 |
-
|
5761 |
-
ko.exportSymbol('utils.setDomNodeChildrenFromArrayMapping', ko.utils.setDomNodeChildrenFromArrayMapping);
|
5762 |
-
ko.nativeTemplateEngine = function () {
|
5763 |
-
this['allowTemplateRewriting'] = false;
|
5764 |
-
}
|
5765 |
-
|
5766 |
-
ko.nativeTemplateEngine.prototype = new ko.templateEngine();
|
5767 |
-
ko.nativeTemplateEngine.prototype.constructor = ko.nativeTemplateEngine;
|
5768 |
-
ko.nativeTemplateEngine.prototype['renderTemplateSource'] = function (templateSource, bindingContext, options, templateDocument) {
|
5769 |
-
var useNodesIfAvailable = !(ko.utils.ieVersion < 9), // IE<9 cloneNode doesn't work properly
|
5770 |
-
templateNodesFunc = useNodesIfAvailable ? templateSource['nodes'] : null,
|
5771 |
-
templateNodes = templateNodesFunc ? templateSource['nodes']() : null;
|
5772 |
-
|
5773 |
-
if (templateNodes) {
|
5774 |
-
return ko.utils.makeArray(templateNodes.cloneNode(true).childNodes);
|
5775 |
-
} else {
|
5776 |
-
var templateText = templateSource['text']();
|
5777 |
-
return ko.utils.parseHtmlFragment(templateText, templateDocument);
|
5778 |
-
}
|
5779 |
-
};
|
5780 |
-
|
5781 |
-
ko.nativeTemplateEngine.instance = new ko.nativeTemplateEngine();
|
5782 |
-
ko.setTemplateEngine(ko.nativeTemplateEngine.instance);
|
5783 |
-
|
5784 |
-
ko.exportSymbol('nativeTemplateEngine', ko.nativeTemplateEngine);
|
5785 |
-
(function() {
|
5786 |
-
ko.jqueryTmplTemplateEngine = function () {
|
5787 |
-
// Detect which version of jquery-tmpl you're using. Unfortunately jquery-tmpl
|
5788 |
-
// doesn't expose a version number, so we have to infer it.
|
5789 |
-
// Note that as of Knockout 1.3, we only support jQuery.tmpl 1.0.0pre and later,
|
5790 |
-
// which KO internally refers to as version "2", so older versions are no longer detected.
|
5791 |
-
var jQueryTmplVersion = this.jQueryTmplVersion = (function() {
|
5792 |
-
if (!jQueryInstance || !(jQueryInstance['tmpl']))
|
5793 |
-
return 0;
|
5794 |
-
// Since it exposes no official version number, we use our own numbering system. To be updated as jquery-tmpl evolves.
|
5795 |
-
try {
|
5796 |
-
if (jQueryInstance['tmpl']['tag']['tmpl']['open'].toString().indexOf('__') >= 0) {
|
5797 |
-
// Since 1.0.0pre, custom tags should append markup to an array called "__"
|
5798 |
-
return 2; // Final version of jquery.tmpl
|
5799 |
-
}
|
5800 |
-
} catch(ex) { /* Apparently not the version we were looking for */ }
|
5801 |
-
|
5802 |
-
return 1; // Any older version that we don't support
|
5803 |
-
})();
|
5804 |
-
|
5805 |
-
function ensureHasReferencedJQueryTemplates() {
|
5806 |
-
if (jQueryTmplVersion < 2)
|
5807 |
-
throw new Error("Your version of jQuery.tmpl is too old. Please upgrade to jQuery.tmpl 1.0.0pre or later.");
|
5808 |
-
}
|
5809 |
-
|
5810 |
-
function executeTemplate(compiledTemplate, data, jQueryTemplateOptions) {
|
5811 |
-
return jQueryInstance['tmpl'](compiledTemplate, data, jQueryTemplateOptions);
|
5812 |
-
}
|
5813 |
-
|
5814 |
-
this['renderTemplateSource'] = function(templateSource, bindingContext, options, templateDocument) {
|
5815 |
-
templateDocument = templateDocument || document;
|
5816 |
-
options = options || {};
|
5817 |
-
ensureHasReferencedJQueryTemplates();
|
5818 |
-
|
5819 |
-
// Ensure we have stored a precompiled version of this template (don't want to reparse on every render)
|
5820 |
-
var precompiled = templateSource['data']('precompiled');
|
5821 |
-
if (!precompiled) {
|
5822 |
-
var templateText = templateSource['text']() || "";
|
5823 |
-
// Wrap in "with($whatever.koBindingContext) { ... }"
|
5824 |
-
templateText = "{{ko_with $item.koBindingContext}}" + templateText + "{{/ko_with}}";
|
5825 |
-
|
5826 |
-
precompiled = jQueryInstance['template'](null, templateText);
|
5827 |
-
templateSource['data']('precompiled', precompiled);
|
5828 |
-
}
|
5829 |
-
|
5830 |
-
var data = [bindingContext['$data']]; // Prewrap the data in an array to stop jquery.tmpl from trying to unwrap any arrays
|
5831 |
-
var jQueryTemplateOptions = jQueryInstance['extend']({ 'koBindingContext': bindingContext }, options['templateOptions']);
|
5832 |
-
|
5833 |
-
var resultNodes = executeTemplate(precompiled, data, jQueryTemplateOptions);
|
5834 |
-
resultNodes['appendTo'](templateDocument.createElement("div")); // Using "appendTo" forces jQuery/jQuery.tmpl to perform necessary cleanup work
|
5835 |
-
|
5836 |
-
jQueryInstance['fragments'] = {}; // Clear jQuery's fragment cache to avoid a memory leak after a large number of template renders
|
5837 |
-
return resultNodes;
|
5838 |
-
};
|
5839 |
-
|
5840 |
-
this['createJavaScriptEvaluatorBlock'] = function(script) {
|
5841 |
-
return "{{ko_code ((function() { return " + script + " })()) }}";
|
5842 |
-
};
|
5843 |
-
|
5844 |
-
this['addTemplate'] = function(templateName, templateMarkup) {
|
5845 |
-
document.write("<script type='text/html' id='" + templateName + "'>" + templateMarkup + "<" + "/script>");
|
5846 |
-
};
|
5847 |
-
|
5848 |
-
if (jQueryTmplVersion > 0) {
|
5849 |
-
jQueryInstance['tmpl']['tag']['ko_code'] = {
|
5850 |
-
open: "__.push($1 || '');"
|
5851 |
-
};
|
5852 |
-
jQueryInstance['tmpl']['tag']['ko_with'] = {
|
5853 |
-
open: "with($1) {",
|
5854 |
-
close: "} "
|
5855 |
-
};
|
5856 |
-
}
|
5857 |
-
};
|
5858 |
-
|
5859 |
-
ko.jqueryTmplTemplateEngine.prototype = new ko.templateEngine();
|
5860 |
-
ko.jqueryTmplTemplateEngine.prototype.constructor = ko.jqueryTmplTemplateEngine;
|
5861 |
-
|
5862 |
-
// Use this one by default *only if jquery.tmpl is referenced*
|
5863 |
-
var jqueryTmplTemplateEngineInstance = new ko.jqueryTmplTemplateEngine();
|
5864 |
-
if (jqueryTmplTemplateEngineInstance.jQueryTmplVersion > 0)
|
5865 |
-
ko.setTemplateEngine(jqueryTmplTemplateEngineInstance);
|
5866 |
-
|
5867 |
-
ko.exportSymbol('jqueryTmplTemplateEngine', ko.jqueryTmplTemplateEngine);
|
5868 |
-
})();
|
5869 |
-
}));
|
5870 |
-
}());
|
5871 |
-
})();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/toolset/toolset-common/res/lib/knockout/knockout-3.4.0.js
DELETED
@@ -1,123 +0,0 @@
|
|
1 |
-
/*!
|
2 |
-
* Knockout JavaScript library v3.4.0
|
3 |
-
* (c) Steven Sanderson - http://knockoutjs.com/
|
4 |
-
* License: MIT (http://www.opensource.org/licenses/mit-license.php)
|
5 |
-
*/
|
6 |
-
|
7 |
-
(function() {(function(n){var x=this||(0,eval)("this"),u=x.document,M=x.navigator,v=x.jQuery,F=x.JSON;(function(n){"function"===typeof define&&define.amd?define(["exports","require"],n):"object"===typeof exports&&"object"===typeof module?n(module.exports||exports):n(x.ko={})})(function(N,O){function J(a,c){return null===a||typeof a in T?a===c:!1}function U(b,c){var d;return function(){d||(d=a.a.setTimeout(function(){d=n;b()},c))}}function V(b,c){var d;return function(){clearTimeout(d);d=a.a.setTimeout(b,c)}}function W(a,
|
8 |
-
c){c&&c!==I?"beforeChange"===c?this.Kb(a):this.Ha(a,c):this.Lb(a)}function X(a,c){null!==c&&c.k&&c.k()}function Y(a,c){var d=this.Hc,e=d[s];e.R||(this.lb&&this.Ma[c]?(d.Pb(c,a,this.Ma[c]),this.Ma[c]=null,--this.lb):e.r[c]||d.Pb(c,a,e.s?{ia:a}:d.uc(a)))}function K(b,c,d,e){a.d[b]={init:function(b,g,k,l,m){var h,r;a.m(function(){var q=a.a.c(g()),p=!d!==!q,A=!r;if(A||c||p!==h)A&&a.va.Aa()&&(r=a.a.ua(a.f.childNodes(b),!0)),p?(A||a.f.da(b,a.a.ua(r)),a.eb(e?e(m,q):m,b)):a.f.xa(b),h=p},null,{i:b});return{controlsDescendantBindings:!0}}};
|
9 |
-
a.h.ta[b]=!1;a.f.Z[b]=!0}var a="undefined"!==typeof N?N:{};a.b=function(b,c){for(var d=b.split("."),e=a,f=0;f<d.length-1;f++)e=e[d[f]];e[d[d.length-1]]=c};a.G=function(a,c,d){a[c]=d};a.version="3.4.0";a.b("version",a.version);a.options={deferUpdates:!1,useOnlyNativeEvents:!1};a.a=function(){function b(a,b){for(var c in a)a.hasOwnProperty(c)&&b(c,a[c])}function c(a,b){if(b)for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return a}function d(a,b){a.__proto__=b;return a}function e(b,c,d,e){var h=b[c].match(r)||
|
10 |
-
[];a.a.q(d.match(r),function(b){a.a.pa(h,b,e)});b[c]=h.join(" ")}var f={__proto__:[]}instanceof Array,g="function"===typeof Symbol,k={},l={};k[M&&/Firefox\/2/i.test(M.userAgent)?"KeyboardEvent":"UIEvents"]=["keyup","keydown","keypress"];k.MouseEvents="click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave".split(" ");b(k,function(a,b){if(b.length)for(var c=0,d=b.length;c<d;c++)l[b[c]]=a});var m={propertychange:!0},h=u&&function(){for(var a=3,b=u.createElement("div"),c=
|
11 |
-
b.getElementsByTagName("i");b.innerHTML="\x3c!--[if gt IE "+ ++a+"]><i></i><![endif]--\x3e",c[0];);return 4<a?a:n}(),r=/\S+/g;return{cc:["authenticity_token",/^__RequestVerificationToken(_.*)?$/],q:function(a,b){for(var c=0,d=a.length;c<d;c++)b(a[c],c)},o:function(a,b){if("function"==typeof Array.prototype.indexOf)return Array.prototype.indexOf.call(a,b);for(var c=0,d=a.length;c<d;c++)if(a[c]===b)return c;return-1},Sb:function(a,b,c){for(var d=0,e=a.length;d<e;d++)if(b.call(c,a[d],d))return a[d];
|
12 |
-
return null},La:function(b,c){var d=a.a.o(b,c);0<d?b.splice(d,1):0===d&&b.shift()},Tb:function(b){b=b||[];for(var c=[],d=0,e=b.length;d<e;d++)0>a.a.o(c,b[d])&&c.push(b[d]);return c},fb:function(a,b){a=a||[];for(var c=[],d=0,e=a.length;d<e;d++)c.push(b(a[d],d));return c},Ka:function(a,b){a=a||[];for(var c=[],d=0,e=a.length;d<e;d++)b(a[d],d)&&c.push(a[d]);return c},ra:function(a,b){if(b instanceof Array)a.push.apply(a,b);else for(var c=0,d=b.length;c<d;c++)a.push(b[c]);return a},pa:function(b,c,d){var e=
|
13 |
-
a.a.o(a.a.zb(b),c);0>e?d&&b.push(c):d||b.splice(e,1)},ka:f,extend:c,Xa:d,Ya:f?d:c,D:b,Ca:function(a,b){if(!a)return a;var c={},d;for(d in a)a.hasOwnProperty(d)&&(c[d]=b(a[d],d,a));return c},ob:function(b){for(;b.firstChild;)a.removeNode(b.firstChild)},jc:function(b){b=a.a.V(b);for(var c=(b[0]&&b[0].ownerDocument||u).createElement("div"),d=0,e=b.length;d<e;d++)c.appendChild(a.$(b[d]));return c},ua:function(b,c){for(var d=0,e=b.length,h=[];d<e;d++){var m=b[d].cloneNode(!0);h.push(c?a.$(m):m)}return h},
|
14 |
-
da:function(b,c){a.a.ob(b);if(c)for(var d=0,e=c.length;d<e;d++)b.appendChild(c[d])},qc:function(b,c){var d=b.nodeType?[b]:b;if(0<d.length){for(var e=d[0],h=e.parentNode,m=0,l=c.length;m<l;m++)h.insertBefore(c[m],e);m=0;for(l=d.length;m<l;m++)a.removeNode(d[m])}},za:function(a,b){if(a.length){for(b=8===b.nodeType&&b.parentNode||b;a.length&&a[0].parentNode!==b;)a.splice(0,1);for(;1<a.length&&a[a.length-1].parentNode!==b;)a.length--;if(1<a.length){var c=a[0],d=a[a.length-1];for(a.length=0;c!==d;)a.push(c),
|
15 |
-
c=c.nextSibling;a.push(d)}}return a},sc:function(a,b){7>h?a.setAttribute("selected",b):a.selected=b},$a:function(a){return null===a||a===n?"":a.trim?a.trim():a.toString().replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")},nd:function(a,b){a=a||"";return b.length>a.length?!1:a.substring(0,b.length)===b},Mc:function(a,b){if(a===b)return!0;if(11===a.nodeType)return!1;if(b.contains)return b.contains(3===a.nodeType?a.parentNode:a);if(b.compareDocumentPosition)return 16==(b.compareDocumentPosition(a)&16);for(;a&&a!=
|
16 |
-
b;)a=a.parentNode;return!!a},nb:function(b){return a.a.Mc(b,b.ownerDocument.documentElement)},Qb:function(b){return!!a.a.Sb(b,a.a.nb)},A:function(a){return a&&a.tagName&&a.tagName.toLowerCase()},Wb:function(b){return a.onError?function(){try{return b.apply(this,arguments)}catch(c){throw a.onError&&a.onError(c),c;}}:b},setTimeout:function(b,c){return setTimeout(a.a.Wb(b),c)},$b:function(b){setTimeout(function(){a.onError&&a.onError(b);throw b;},0)},p:function(b,c,d){var e=a.a.Wb(d);d=h&&m[c];if(a.options.useOnlyNativeEvents||
|
17 |
-
d||!v)if(d||"function"!=typeof b.addEventListener)if("undefined"!=typeof b.attachEvent){var l=function(a){e.call(b,a)},f="on"+c;b.attachEvent(f,l);a.a.F.oa(b,function(){b.detachEvent(f,l)})}else throw Error("Browser doesn't support addEventListener or attachEvent");else b.addEventListener(c,e,!1);else v(b).bind(c,e)},Da:function(b,c){if(!b||!b.nodeType)throw Error("element must be a DOM node when calling triggerEvent");var d;"input"===a.a.A(b)&&b.type&&"click"==c.toLowerCase()?(d=b.type,d="checkbox"==
|
18 |
-
d||"radio"==d):d=!1;if(a.options.useOnlyNativeEvents||!v||d)if("function"==typeof u.createEvent)if("function"==typeof b.dispatchEvent)d=u.createEvent(l[c]||"HTMLEvents"),d.initEvent(c,!0,!0,x,0,0,0,0,0,!1,!1,!1,!1,0,b),b.dispatchEvent(d);else throw Error("The supplied element doesn't support dispatchEvent");else if(d&&b.click)b.click();else if("undefined"!=typeof b.fireEvent)b.fireEvent("on"+c);else throw Error("Browser doesn't support triggering events");else v(b).trigger(c)},c:function(b){return a.H(b)?
|
19 |
-
b():b},zb:function(b){return a.H(b)?b.t():b},bb:function(b,c,d){var h;c&&("object"===typeof b.classList?(h=b.classList[d?"add":"remove"],a.a.q(c.match(r),function(a){h.call(b.classList,a)})):"string"===typeof b.className.baseVal?e(b.className,"baseVal",c,d):e(b,"className",c,d))},Za:function(b,c){var d=a.a.c(c);if(null===d||d===n)d="";var e=a.f.firstChild(b);!e||3!=e.nodeType||a.f.nextSibling(e)?a.f.da(b,[b.ownerDocument.createTextNode(d)]):e.data=d;a.a.Rc(b)},rc:function(a,b){a.name=b;if(7>=h)try{a.mergeAttributes(u.createElement("<input name='"+
|
20 |
-
a.name+"'/>"),!1)}catch(c){}},Rc:function(a){9<=h&&(a=1==a.nodeType?a:a.parentNode,a.style&&(a.style.zoom=a.style.zoom))},Nc:function(a){if(h){var b=a.style.width;a.style.width=0;a.style.width=b}},hd:function(b,c){b=a.a.c(b);c=a.a.c(c);for(var d=[],e=b;e<=c;e++)d.push(e);return d},V:function(a){for(var b=[],c=0,d=a.length;c<d;c++)b.push(a[c]);return b},Yb:function(a){return g?Symbol(a):a},rd:6===h,sd:7===h,C:h,ec:function(b,c){for(var d=a.a.V(b.getElementsByTagName("input")).concat(a.a.V(b.getElementsByTagName("textarea"))),
|
21 |
-
e="string"==typeof c?function(a){return a.name===c}:function(a){return c.test(a.name)},h=[],m=d.length-1;0<=m;m--)e(d[m])&&h.push(d[m]);return h},ed:function(b){return"string"==typeof b&&(b=a.a.$a(b))?F&&F.parse?F.parse(b):(new Function("return "+b))():null},Eb:function(b,c,d){if(!F||!F.stringify)throw Error("Cannot find JSON.stringify(). Some browsers (e.g., IE < 8) don't support it natively, but you can overcome this by adding a script reference to json2.js, downloadable from http://www.json.org/json2.js");
|
22 |
-
return F.stringify(a.a.c(b),c,d)},fd:function(c,d,e){e=e||{};var h=e.params||{},m=e.includeFields||this.cc,l=c;if("object"==typeof c&&"form"===a.a.A(c))for(var l=c.action,f=m.length-1;0<=f;f--)for(var g=a.a.ec(c,m[f]),k=g.length-1;0<=k;k--)h[g[k].name]=g[k].value;d=a.a.c(d);var r=u.createElement("form");r.style.display="none";r.action=l;r.method="post";for(var n in d)c=u.createElement("input"),c.type="hidden",c.name=n,c.value=a.a.Eb(a.a.c(d[n])),r.appendChild(c);b(h,function(a,b){var c=u.createElement("input");
|
23 |
-
c.type="hidden";c.name=a;c.value=b;r.appendChild(c)});u.body.appendChild(r);e.submitter?e.submitter(r):r.submit();setTimeout(function(){r.parentNode.removeChild(r)},0)}}}();a.b("utils",a.a);a.b("utils.arrayForEach",a.a.q);a.b("utils.arrayFirst",a.a.Sb);a.b("utils.arrayFilter",a.a.Ka);a.b("utils.arrayGetDistinctValues",a.a.Tb);a.b("utils.arrayIndexOf",a.a.o);a.b("utils.arrayMap",a.a.fb);a.b("utils.arrayPushAll",a.a.ra);a.b("utils.arrayRemoveItem",a.a.La);a.b("utils.extend",a.a.extend);a.b("utils.fieldsIncludedWithJsonPost",
|
24 |
-
a.a.cc);a.b("utils.getFormFields",a.a.ec);a.b("utils.peekObservable",a.a.zb);a.b("utils.postJson",a.a.fd);a.b("utils.parseJson",a.a.ed);a.b("utils.registerEventHandler",a.a.p);a.b("utils.stringifyJson",a.a.Eb);a.b("utils.range",a.a.hd);a.b("utils.toggleDomNodeCssClass",a.a.bb);a.b("utils.triggerEvent",a.a.Da);a.b("utils.unwrapObservable",a.a.c);a.b("utils.objectForEach",a.a.D);a.b("utils.addOrRemoveItem",a.a.pa);a.b("utils.setTextContent",a.a.Za);a.b("unwrap",a.a.c);Function.prototype.bind||(Function.prototype.bind=
|
25 |
-
function(a){var c=this;if(1===arguments.length)return function(){return c.apply(a,arguments)};var d=Array.prototype.slice.call(arguments,1);return function(){var e=d.slice(0);e.push.apply(e,arguments);return c.apply(a,e)}});a.a.e=new function(){function a(b,g){var k=b[d];if(!k||"null"===k||!e[k]){if(!g)return n;k=b[d]="ko"+c++;e[k]={}}return e[k]}var c=0,d="__ko__"+(new Date).getTime(),e={};return{get:function(c,d){var e=a(c,!1);return e===n?n:e[d]},set:function(c,d,e){if(e!==n||a(c,!1)!==n)a(c,!0)[d]=
|
26 |
-
e},clear:function(a){var b=a[d];return b?(delete e[b],a[d]=null,!0):!1},I:function(){return c++ +d}}};a.b("utils.domData",a.a.e);a.b("utils.domData.clear",a.a.e.clear);a.a.F=new function(){function b(b,c){var e=a.a.e.get(b,d);e===n&&c&&(e=[],a.a.e.set(b,d,e));return e}function c(d){var e=b(d,!1);if(e)for(var e=e.slice(0),l=0;l<e.length;l++)e[l](d);a.a.e.clear(d);a.a.F.cleanExternalData(d);if(f[d.nodeType])for(e=d.firstChild;d=e;)e=d.nextSibling,8===d.nodeType&&c(d)}var d=a.a.e.I(),e={1:!0,8:!0,9:!0},
|
27 |
-
f={1:!0,9:!0};return{oa:function(a,c){if("function"!=typeof c)throw Error("Callback must be a function");b(a,!0).push(c)},pc:function(c,e){var l=b(c,!1);l&&(a.a.La(l,e),0==l.length&&a.a.e.set(c,d,n))},$:function(b){if(e[b.nodeType]&&(c(b),f[b.nodeType])){var d=[];a.a.ra(d,b.getElementsByTagName("*"));for(var l=0,m=d.length;l<m;l++)c(d[l])}return b},removeNode:function(b){a.$(b);b.parentNode&&b.parentNode.removeChild(b)},cleanExternalData:function(a){v&&"function"==typeof v.cleanData&&v.cleanData([a])}}};
|
28 |
-
a.$=a.a.F.$;a.removeNode=a.a.F.removeNode;a.b("cleanNode",a.$);a.b("removeNode",a.removeNode);a.b("utils.domNodeDisposal",a.a.F);a.b("utils.domNodeDisposal.addDisposeCallback",a.a.F.oa);a.b("utils.domNodeDisposal.removeDisposeCallback",a.a.F.pc);(function(){var b=[0,"",""],c=[1,"<table>","</table>"],d=[3,"<table><tbody><tr>","</tr></tbody></table>"],e=[1,"<select multiple='multiple'>","</select>"],f={thead:c,tbody:c,tfoot:c,tr:[2,"<table><tbody>","</tbody></table>"],td:d,th:d,option:e,optgroup:e},
|
29 |
-
g=8>=a.a.C;a.a.ma=function(c,d){var e;if(v)if(v.parseHTML)e=v.parseHTML(c,d)||[];else{if((e=v.clean([c],d))&&e[0]){for(var h=e[0];h.parentNode&&11!==h.parentNode.nodeType;)h=h.parentNode;h.parentNode&&h.parentNode.removeChild(h)}}else{(e=d)||(e=u);var h=e.parentWindow||e.defaultView||x,r=a.a.$a(c).toLowerCase(),q=e.createElement("div"),p;p=(r=r.match(/^<([a-z]+)[ >]/))&&f[r[1]]||b;r=p[0];p="ignored<div>"+p[1]+c+p[2]+"</div>";"function"==typeof h.innerShiv?q.appendChild(h.innerShiv(p)):(g&&e.appendChild(q),
|
30 |
-
q.innerHTML=p,g&&q.parentNode.removeChild(q));for(;r--;)q=q.lastChild;e=a.a.V(q.lastChild.childNodes)}return e};a.a.Cb=function(b,c){a.a.ob(b);c=a.a.c(c);if(null!==c&&c!==n)if("string"!=typeof c&&(c=c.toString()),v)v(b).html(c);else for(var d=a.a.ma(c,b.ownerDocument),e=0;e<d.length;e++)b.appendChild(d[e])}})();a.b("utils.parseHtmlFragment",a.a.ma);a.b("utils.setHtml",a.a.Cb);a.M=function(){function b(c,e){if(c)if(8==c.nodeType){var f=a.M.lc(c.nodeValue);null!=f&&e.push({Lc:c,cd:f})}else if(1==c.nodeType)for(var f=
|
31 |
-
0,g=c.childNodes,k=g.length;f<k;f++)b(g[f],e)}var c={};return{wb:function(a){if("function"!=typeof a)throw Error("You can only pass a function to ko.memoization.memoize()");var b=(4294967296*(1+Math.random())|0).toString(16).substring(1)+(4294967296*(1+Math.random())|0).toString(16).substring(1);c[b]=a;return"\x3c!--[ko_memo:"+b+"]--\x3e"},xc:function(a,b){var f=c[a];if(f===n)throw Error("Couldn't find any memo with ID "+a+". Perhaps it's already been unmemoized.");try{return f.apply(null,b||[]),
|
32 |
-
!0}finally{delete c[a]}},yc:function(c,e){var f=[];b(c,f);for(var g=0,k=f.length;g<k;g++){var l=f[g].Lc,m=[l];e&&a.a.ra(m,e);a.M.xc(f[g].cd,m);l.nodeValue="";l.parentNode&&l.parentNode.removeChild(l)}},lc:function(a){return(a=a.match(/^\[ko_memo\:(.*?)\]$/))?a[1]:null}}}();a.b("memoization",a.M);a.b("memoization.memoize",a.M.wb);a.b("memoization.unmemoize",a.M.xc);a.b("memoization.parseMemoText",a.M.lc);a.b("memoization.unmemoizeDomNodeAndDescendants",a.M.yc);a.Y=function(){function b(){if(e)for(var b=
|
33 |
-
e,c=0,m;g<e;)if(m=d[g++]){if(g>b){if(5E3<=++c){g=e;a.a.$b(Error("'Too much recursion' after processing "+c+" task groups."));break}b=e}try{m()}catch(h){a.a.$b(h)}}}function c(){b();g=e=d.length=0}var d=[],e=0,f=1,g=0;return{scheduler:x.MutationObserver?function(a){var b=u.createElement("div");(new MutationObserver(a)).observe(b,{attributes:!0});return function(){b.classList.toggle("foo")}}(c):u&&"onreadystatechange"in u.createElement("script")?function(a){var b=u.createElement("script");b.onreadystatechange=
|
34 |
-
function(){b.onreadystatechange=null;u.documentElement.removeChild(b);b=null;a()};u.documentElement.appendChild(b)}:function(a){setTimeout(a,0)},Wa:function(b){e||a.Y.scheduler(c);d[e++]=b;return f++},cancel:function(a){a-=f-e;a>=g&&a<e&&(d[a]=null)},resetForTesting:function(){var a=e-g;g=e=d.length=0;return a},md:b}}();a.b("tasks",a.Y);a.b("tasks.schedule",a.Y.Wa);a.b("tasks.runEarly",a.Y.md);a.ya={throttle:function(b,c){b.throttleEvaluation=c;var d=null;return a.B({read:b,write:function(e){clearTimeout(d);
|
35 |
-
d=a.a.setTimeout(function(){b(e)},c)}})},rateLimit:function(a,c){var d,e,f;"number"==typeof c?d=c:(d=c.timeout,e=c.method);a.cb=!1;f="notifyWhenChangesStop"==e?V:U;a.Ta(function(a){return f(a,d)})},deferred:function(b,c){if(!0!==c)throw Error("The 'deferred' extender only accepts the value 'true', because it is not supported to turn deferral off once enabled.");b.cb||(b.cb=!0,b.Ta(function(c){var e;return function(){a.Y.cancel(e);e=a.Y.Wa(c);b.notifySubscribers(n,"dirty")}}))},notify:function(a,c){a.equalityComparer=
|
36 |
-
"always"==c?null:J}};var T={undefined:1,"boolean":1,number:1,string:1};a.b("extenders",a.ya);a.vc=function(b,c,d){this.ia=b;this.gb=c;this.Kc=d;this.R=!1;a.G(this,"dispose",this.k)};a.vc.prototype.k=function(){this.R=!0;this.Kc()};a.J=function(){a.a.Ya(this,D);D.rb(this)};var I="change",D={rb:function(a){a.K={};a.Nb=1},X:function(b,c,d){var e=this;d=d||I;var f=new a.vc(e,c?b.bind(c):b,function(){a.a.La(e.K[d],f);e.Ia&&e.Ia(d)});e.sa&&e.sa(d);e.K[d]||(e.K[d]=[]);e.K[d].push(f);return f},notifySubscribers:function(b,
|
37 |
-
c){c=c||I;c===I&&this.zc();if(this.Pa(c))try{a.l.Ub();for(var d=this.K[c].slice(0),e=0,f;f=d[e];++e)f.R||f.gb(b)}finally{a.l.end()}},Na:function(){return this.Nb},Uc:function(a){return this.Na()!==a},zc:function(){++this.Nb},Ta:function(b){var c=this,d=a.H(c),e,f,g;c.Ha||(c.Ha=c.notifySubscribers,c.notifySubscribers=W);var k=b(function(){c.Mb=!1;d&&g===c&&(g=c());e=!1;c.tb(f,g)&&c.Ha(f=g)});c.Lb=function(a){c.Mb=e=!0;g=a;k()};c.Kb=function(a){e||(f=a,c.Ha(a,"beforeChange"))}},Pa:function(a){return this.K[a]&&
|
38 |
-
this.K[a].length},Sc:function(b){if(b)return this.K[b]&&this.K[b].length||0;var c=0;a.a.D(this.K,function(a,b){"dirty"!==a&&(c+=b.length)});return c},tb:function(a,c){return!this.equalityComparer||!this.equalityComparer(a,c)},extend:function(b){var c=this;b&&a.a.D(b,function(b,e){var f=a.ya[b];"function"==typeof f&&(c=f(c,e)||c)});return c}};a.G(D,"subscribe",D.X);a.G(D,"extend",D.extend);a.G(D,"getSubscriptionsCount",D.Sc);a.a.ka&&a.a.Xa(D,Function.prototype);a.J.fn=D;a.hc=function(a){return null!=
|
39 |
-
a&&"function"==typeof a.X&&"function"==typeof a.notifySubscribers};a.b("subscribable",a.J);a.b("isSubscribable",a.hc);a.va=a.l=function(){function b(a){d.push(e);e=a}function c(){e=d.pop()}var d=[],e,f=0;return{Ub:b,end:c,oc:function(b){if(e){if(!a.hc(b))throw Error("Only subscribable things can act as dependencies");e.gb.call(e.Gc,b,b.Cc||(b.Cc=++f))}},w:function(a,d,e){try{return b(),a.apply(d,e||[])}finally{c()}},Aa:function(){if(e)return e.m.Aa()},Sa:function(){if(e)return e.Sa}}}();a.b("computedContext",
|
40 |
-
a.va);a.b("computedContext.getDependenciesCount",a.va.Aa);a.b("computedContext.isInitial",a.va.Sa);a.b("ignoreDependencies",a.qd=a.l.w);var E=a.a.Yb("_latestValue");a.N=function(b){function c(){if(0<arguments.length)return c.tb(c[E],arguments[0])&&(c.ga(),c[E]=arguments[0],c.fa()),this;a.l.oc(c);return c[E]}c[E]=b;a.a.ka||a.a.extend(c,a.J.fn);a.J.fn.rb(c);a.a.Ya(c,B);a.options.deferUpdates&&a.ya.deferred(c,!0);return c};var B={equalityComparer:J,t:function(){return this[E]},fa:function(){this.notifySubscribers(this[E])},
|
41 |
-
ga:function(){this.notifySubscribers(this[E],"beforeChange")}};a.a.ka&&a.a.Xa(B,a.J.fn);var H=a.N.gd="__ko_proto__";B[H]=a.N;a.Oa=function(b,c){return null===b||b===n||b[H]===n?!1:b[H]===c?!0:a.Oa(b[H],c)};a.H=function(b){return a.Oa(b,a.N)};a.Ba=function(b){return"function"==typeof b&&b[H]===a.N||"function"==typeof b&&b[H]===a.B&&b.Vc?!0:!1};a.b("observable",a.N);a.b("isObservable",a.H);a.b("isWriteableObservable",a.Ba);a.b("isWritableObservable",a.Ba);a.b("observable.fn",B);a.G(B,"peek",B.t);a.G(B,
|
42 |
-
"valueHasMutated",B.fa);a.G(B,"valueWillMutate",B.ga);a.la=function(b){b=b||[];if("object"!=typeof b||!("length"in b))throw Error("The argument passed when initializing an observable array must be an array, or null, or undefined.");b=a.N(b);a.a.Ya(b,a.la.fn);return b.extend({trackArrayChanges:!0})};a.la.fn={remove:function(b){for(var c=this.t(),d=[],e="function"!=typeof b||a.H(b)?function(a){return a===b}:b,f=0;f<c.length;f++){var g=c[f];e(g)&&(0===d.length&&this.ga(),d.push(g),c.splice(f,1),f--)}d.length&&
|
43 |
-
this.fa();return d},removeAll:function(b){if(b===n){var c=this.t(),d=c.slice(0);this.ga();c.splice(0,c.length);this.fa();return d}return b?this.remove(function(c){return 0<=a.a.o(b,c)}):[]},destroy:function(b){var c=this.t(),d="function"!=typeof b||a.H(b)?function(a){return a===b}:b;this.ga();for(var e=c.length-1;0<=e;e--)d(c[e])&&(c[e]._destroy=!0);this.fa()},destroyAll:function(b){return b===n?this.destroy(function(){return!0}):b?this.destroy(function(c){return 0<=a.a.o(b,c)}):[]},indexOf:function(b){var c=
|
44 |
-
this();return a.a.o(c,b)},replace:function(a,c){var d=this.indexOf(a);0<=d&&(this.ga(),this.t()[d]=c,this.fa())}};a.a.ka&&a.a.Xa(a.la.fn,a.N.fn);a.a.q("pop push reverse shift sort splice unshift".split(" "),function(b){a.la.fn[b]=function(){var a=this.t();this.ga();this.Vb(a,b,arguments);var d=a[b].apply(a,arguments);this.fa();return d===a?this:d}});a.a.q(["slice"],function(b){a.la.fn[b]=function(){var a=this();return a[b].apply(a,arguments)}});a.b("observableArray",a.la);a.ya.trackArrayChanges=function(b,
|
45 |
-
c){function d(){if(!e){e=!0;var c=b.notifySubscribers;b.notifySubscribers=function(a,b){b&&b!==I||++k;return c.apply(this,arguments)};var d=[].concat(b.t()||[]);f=null;g=b.X(function(c){c=[].concat(c||[]);if(b.Pa("arrayChange")){var e;if(!f||1<k)f=a.a.ib(d,c,b.hb);e=f}d=c;f=null;k=0;e&&e.length&&b.notifySubscribers(e,"arrayChange")})}}b.hb={};c&&"object"==typeof c&&a.a.extend(b.hb,c);b.hb.sparse=!0;if(!b.Vb){var e=!1,f=null,g,k=0,l=b.sa,m=b.Ia;b.sa=function(a){l&&l.call(b,a);"arrayChange"===a&&d()};
|
46 |
-
b.Ia=function(a){m&&m.call(b,a);"arrayChange"!==a||b.Pa("arrayChange")||(g.k(),e=!1)};b.Vb=function(b,c,d){function m(a,b,c){return l[l.length]={status:a,value:b,index:c}}if(e&&!k){var l=[],g=b.length,t=d.length,G=0;switch(c){case "push":G=g;case "unshift":for(c=0;c<t;c++)m("added",d[c],G+c);break;case "pop":G=g-1;case "shift":g&&m("deleted",b[G],G);break;case "splice":c=Math.min(Math.max(0,0>d[0]?g+d[0]:d[0]),g);for(var g=1===t?g:Math.min(c+(d[1]||0),g),t=c+t-2,G=Math.max(g,t),P=[],n=[],Q=2;c<G;++c,
|
47 |
-
++Q)c<g&&n.push(m("deleted",b[c],c)),c<t&&P.push(m("added",d[Q],c));a.a.dc(n,P);break;default:return}f=l}}}};var s=a.a.Yb("_state");a.m=a.B=function(b,c,d){function e(){if(0<arguments.length){if("function"===typeof f)f.apply(g.pb,arguments);else throw Error("Cannot write a value to a ko.computed unless you specify a 'write' option. If you wish to read the current value, don't pass any parameters.");return this}a.l.oc(e);(g.S||g.s&&e.Qa())&&e.aa();return g.T}"object"===typeof b?d=b:(d=d||{},b&&(d.read=
|
48 |
-
b));if("function"!=typeof d.read)throw Error("Pass a function that returns the value of the ko.computed");var f=d.write,g={T:n,S:!0,Ra:!1,Fb:!1,R:!1,Va:!1,s:!1,jd:d.read,pb:c||d.owner,i:d.disposeWhenNodeIsRemoved||d.i||null,wa:d.disposeWhen||d.wa,mb:null,r:{},L:0,bc:null};e[s]=g;e.Vc="function"===typeof f;a.a.ka||a.a.extend(e,a.J.fn);a.J.fn.rb(e);a.a.Ya(e,z);d.pure?(g.Va=!0,g.s=!0,a.a.extend(e,$)):d.deferEvaluation&&a.a.extend(e,aa);a.options.deferUpdates&&a.ya.deferred(e,!0);g.i&&(g.Fb=!0,g.i.nodeType||
|
49 |
-
(g.i=null));g.s||d.deferEvaluation||e.aa();g.i&&e.ba()&&a.a.F.oa(g.i,g.mb=function(){e.k()});return e};var z={equalityComparer:J,Aa:function(){return this[s].L},Pb:function(a,c,d){if(this[s].Va&&c===this)throw Error("A 'pure' computed must not be called recursively");this[s].r[a]=d;d.Ga=this[s].L++;d.na=c.Na()},Qa:function(){var a,c,d=this[s].r;for(a in d)if(d.hasOwnProperty(a)&&(c=d[a],c.ia.Uc(c.na)))return!0},bd:function(){this.Fa&&!this[s].Ra&&this.Fa()},ba:function(){return this[s].S||0<this[s].L},
|
50 |
-
ld:function(){this.Mb||this.ac()},uc:function(a){if(a.cb&&!this[s].i){var c=a.X(this.bd,this,"dirty"),d=a.X(this.ld,this);return{ia:a,k:function(){c.k();d.k()}}}return a.X(this.ac,this)},ac:function(){var b=this,c=b.throttleEvaluation;c&&0<=c?(clearTimeout(this[s].bc),this[s].bc=a.a.setTimeout(function(){b.aa(!0)},c)):b.Fa?b.Fa():b.aa(!0)},aa:function(b){var c=this[s],d=c.wa;if(!c.Ra&&!c.R){if(c.i&&!a.a.nb(c.i)||d&&d()){if(!c.Fb){this.k();return}}else c.Fb=!1;c.Ra=!0;try{this.Qc(b)}finally{c.Ra=!1}c.L||
|
51 |
-
this.k()}},Qc:function(b){var c=this[s],d=c.Va?n:!c.L,e={Hc:this,Ma:c.r,lb:c.L};a.l.Ub({Gc:e,gb:Y,m:this,Sa:d});c.r={};c.L=0;e=this.Pc(c,e);this.tb(c.T,e)&&(c.s||this.notifySubscribers(c.T,"beforeChange"),c.T=e,c.s?this.zc():b&&this.notifySubscribers(c.T));d&&this.notifySubscribers(c.T,"awake")},Pc:function(b,c){try{var d=b.jd;return b.pb?d.call(b.pb):d()}finally{a.l.end(),c.lb&&!b.s&&a.a.D(c.Ma,X),b.S=!1}},t:function(){var a=this[s];(a.S&&!a.L||a.s&&this.Qa())&&this.aa();return a.T},Ta:function(b){a.J.fn.Ta.call(this,
|
52 |
-
b);this.Fa=function(){this.Kb(this[s].T);this[s].S=!0;this.Lb(this)}},k:function(){var b=this[s];!b.s&&b.r&&a.a.D(b.r,function(a,b){b.k&&b.k()});b.i&&b.mb&&a.a.F.pc(b.i,b.mb);b.r=null;b.L=0;b.R=!0;b.S=!1;b.s=!1;b.i=null}},$={sa:function(b){var c=this,d=c[s];if(!d.R&&d.s&&"change"==b){d.s=!1;if(d.S||c.Qa())d.r=null,d.L=0,d.S=!0,c.aa();else{var e=[];a.a.D(d.r,function(a,b){e[b.Ga]=a});a.a.q(e,function(a,b){var e=d.r[a],l=c.uc(e.ia);l.Ga=b;l.na=e.na;d.r[a]=l})}d.R||c.notifySubscribers(d.T,"awake")}},
|
53 |
-
Ia:function(b){var c=this[s];c.R||"change"!=b||this.Pa("change")||(a.a.D(c.r,function(a,b){b.k&&(c.r[a]={ia:b.ia,Ga:b.Ga,na:b.na},b.k())}),c.s=!0,this.notifySubscribers(n,"asleep"))},Na:function(){var b=this[s];b.s&&(b.S||this.Qa())&&this.aa();return a.J.fn.Na.call(this)}},aa={sa:function(a){"change"!=a&&"beforeChange"!=a||this.t()}};a.a.ka&&a.a.Xa(z,a.J.fn);var R=a.N.gd;a.m[R]=a.N;z[R]=a.m;a.Xc=function(b){return a.Oa(b,a.m)};a.Yc=function(b){return a.Oa(b,a.m)&&b[s]&&b[s].Va};a.b("computed",a.m);
|
54 |
-
a.b("dependentObservable",a.m);a.b("isComputed",a.Xc);a.b("isPureComputed",a.Yc);a.b("computed.fn",z);a.G(z,"peek",z.t);a.G(z,"dispose",z.k);a.G(z,"isActive",z.ba);a.G(z,"getDependenciesCount",z.Aa);a.nc=function(b,c){if("function"===typeof b)return a.m(b,c,{pure:!0});b=a.a.extend({},b);b.pure=!0;return a.m(b,c)};a.b("pureComputed",a.nc);(function(){function b(a,f,g){g=g||new d;a=f(a);if("object"!=typeof a||null===a||a===n||a instanceof RegExp||a instanceof Date||a instanceof String||a instanceof
|
55 |
-
Number||a instanceof Boolean)return a;var k=a instanceof Array?[]:{};g.save(a,k);c(a,function(c){var d=f(a[c]);switch(typeof d){case "boolean":case "number":case "string":case "function":k[c]=d;break;case "object":case "undefined":var h=g.get(d);k[c]=h!==n?h:b(d,f,g)}});return k}function c(a,b){if(a instanceof Array){for(var c=0;c<a.length;c++)b(c);"function"==typeof a.toJSON&&b("toJSON")}else for(c in a)b(c)}function d(){this.keys=[];this.Ib=[]}a.wc=function(c){if(0==arguments.length)throw Error("When calling ko.toJS, pass the object you want to convert.");
|
56 |
-
return b(c,function(b){for(var c=0;a.H(b)&&10>c;c++)b=b();return b})};a.toJSON=function(b,c,d){b=a.wc(b);return a.a.Eb(b,c,d)};d.prototype={save:function(b,c){var d=a.a.o(this.keys,b);0<=d?this.Ib[d]=c:(this.keys.push(b),this.Ib.push(c))},get:function(b){b=a.a.o(this.keys,b);return 0<=b?this.Ib[b]:n}}})();a.b("toJS",a.wc);a.b("toJSON",a.toJSON);(function(){a.j={u:function(b){switch(a.a.A(b)){case "option":return!0===b.__ko__hasDomDataOptionValue__?a.a.e.get(b,a.d.options.xb):7>=a.a.C?b.getAttributeNode("value")&&
|
57 |
-
b.getAttributeNode("value").specified?b.value:b.text:b.value;case "select":return 0<=b.selectedIndex?a.j.u(b.options[b.selectedIndex]):n;default:return b.value}},ha:function(b,c,d){switch(a.a.A(b)){case "option":switch(typeof c){case "string":a.a.e.set(b,a.d.options.xb,n);"__ko__hasDomDataOptionValue__"in b&&delete b.__ko__hasDomDataOptionValue__;b.value=c;break;default:a.a.e.set(b,a.d.options.xb,c),b.__ko__hasDomDataOptionValue__=!0,b.value="number"===typeof c?c:""}break;case "select":if(""===c||
|
58 |
-
null===c)c=n;for(var e=-1,f=0,g=b.options.length,k;f<g;++f)if(k=a.j.u(b.options[f]),k==c||""==k&&c===n){e=f;break}if(d||0<=e||c===n&&1<b.size)b.selectedIndex=e;break;default:if(null===c||c===n)c="";b.value=c}}}})();a.b("selectExtensions",a.j);a.b("selectExtensions.readValue",a.j.u);a.b("selectExtensions.writeValue",a.j.ha);a.h=function(){function b(b){b=a.a.$a(b);123===b.charCodeAt(0)&&(b=b.slice(1,-1));var c=[],d=b.match(e),r,k=[],p=0;if(d){d.push(",");for(var A=0,y;y=d[A];++A){var t=y.charCodeAt(0);
|
59 |
-
if(44===t){if(0>=p){c.push(r&&k.length?{key:r,value:k.join("")}:{unknown:r||k.join("")});r=p=0;k=[];continue}}else if(58===t){if(!p&&!r&&1===k.length){r=k.pop();continue}}else 47===t&&A&&1<y.length?(t=d[A-1].match(f))&&!g[t[0]]&&(b=b.substr(b.indexOf(y)+1),d=b.match(e),d.push(","),A=-1,y="/"):40===t||123===t||91===t?++p:41===t||125===t||93===t?--p:r||k.length||34!==t&&39!==t||(y=y.slice(1,-1));k.push(y)}}return c}var c=["true","false","null","undefined"],d=/^(?:[$_a-z][$\w]*|(.+)(\.\s*[$_a-z][$\w]*|\[.+\]))$/i,
|
60 |
-
e=RegExp("\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'|/(?:[^/\\\\]|\\\\.)*/w*|[^\\s:,/][^,\"'{}()/:[\\]]*[^\\s,\"'{}()/:[\\]]|[^\\s]","g"),f=/[\])"'A-Za-z0-9_$]+$/,g={"in":1,"return":1,"typeof":1},k={};return{ta:[],ea:k,yb:b,Ua:function(e,m){function h(b,e){var m;if(!A){var l=a.getBindingHandler(b);if(l&&l.preprocess&&!(e=l.preprocess(e,b,h)))return;if(l=k[b])m=e,0<=a.a.o(c,m)?m=!1:(l=m.match(d),m=null===l?!1:l[1]?"Object("+l[1]+")"+l[2]:m),l=m;l&&g.push("'"+b+"':function(_z){"+m+"=_z}")}p&&(e=
|
61 |
-
"function(){return "+e+" }");f.push("'"+b+"':"+e)}m=m||{};var f=[],g=[],p=m.valueAccessors,A=m.bindingParams,y="string"===typeof e?b(e):e;a.a.q(y,function(a){h(a.key||a.unknown,a.value)});g.length&&h("_ko_property_writers","{"+g.join(",")+" }");return f.join(",")},ad:function(a,b){for(var c=0;c<a.length;c++)if(a[c].key==b)return!0;return!1},Ea:function(b,c,d,e,f){if(b&&a.H(b))!a.Ba(b)||f&&b.t()===e||b(e);else if((b=c.get("_ko_property_writers"))&&b[d])b[d](e)}}}();a.b("expressionRewriting",a.h);a.b("expressionRewriting.bindingRewriteValidators",
|
62 |
-
a.h.ta);a.b("expressionRewriting.parseObjectLiteral",a.h.yb);a.b("expressionRewriting.preProcessBindings",a.h.Ua);a.b("expressionRewriting._twoWayBindings",a.h.ea);a.b("jsonExpressionRewriting",a.h);a.b("jsonExpressionRewriting.insertPropertyAccessorsIntoJson",a.h.Ua);(function(){function b(a){return 8==a.nodeType&&g.test(f?a.text:a.nodeValue)}function c(a){return 8==a.nodeType&&k.test(f?a.text:a.nodeValue)}function d(a,d){for(var e=a,f=1,l=[];e=e.nextSibling;){if(c(e)&&(f--,0===f))return l;l.push(e);
|
63 |
-
b(e)&&f++}if(!d)throw Error("Cannot find closing comment tag to match: "+a.nodeValue);return null}function e(a,b){var c=d(a,b);return c?0<c.length?c[c.length-1].nextSibling:a.nextSibling:null}var f=u&&"\x3c!--test--\x3e"===u.createComment("test").text,g=f?/^\x3c!--\s*ko(?:\s+([\s\S]+))?\s*--\x3e$/:/^\s*ko(?:\s+([\s\S]+))?\s*$/,k=f?/^\x3c!--\s*\/ko\s*--\x3e$/:/^\s*\/ko\s*$/,l={ul:!0,ol:!0};a.f={Z:{},childNodes:function(a){return b(a)?d(a):a.childNodes},xa:function(c){if(b(c)){c=a.f.childNodes(c);for(var d=
|
64 |
-
0,e=c.length;d<e;d++)a.removeNode(c[d])}else a.a.ob(c)},da:function(c,d){if(b(c)){a.f.xa(c);for(var e=c.nextSibling,f=0,l=d.length;f<l;f++)e.parentNode.insertBefore(d[f],e)}else a.a.da(c,d)},mc:function(a,c){b(a)?a.parentNode.insertBefore(c,a.nextSibling):a.firstChild?a.insertBefore(c,a.firstChild):a.appendChild(c)},gc:function(c,d,e){e?b(c)?c.parentNode.insertBefore(d,e.nextSibling):e.nextSibling?c.insertBefore(d,e.nextSibling):c.appendChild(d):a.f.mc(c,d)},firstChild:function(a){return b(a)?!a.nextSibling||
|
65 |
-
c(a.nextSibling)?null:a.nextSibling:a.firstChild},nextSibling:function(a){b(a)&&(a=e(a));return a.nextSibling&&c(a.nextSibling)?null:a.nextSibling},Tc:b,pd:function(a){return(a=(f?a.text:a.nodeValue).match(g))?a[1]:null},kc:function(d){if(l[a.a.A(d)]){var h=d.firstChild;if(h){do if(1===h.nodeType){var f;f=h.firstChild;var g=null;if(f){do if(g)g.push(f);else if(b(f)){var k=e(f,!0);k?f=k:g=[f]}else c(f)&&(g=[f]);while(f=f.nextSibling)}if(f=g)for(g=h.nextSibling,k=0;k<f.length;k++)g?d.insertBefore(f[k],
|
66 |
-
g):d.appendChild(f[k])}while(h=h.nextSibling)}}}}})();a.b("virtualElements",a.f);a.b("virtualElements.allowedBindings",a.f.Z);a.b("virtualElements.emptyNode",a.f.xa);a.b("virtualElements.insertAfter",a.f.gc);a.b("virtualElements.prepend",a.f.mc);a.b("virtualElements.setDomNodeChildren",a.f.da);(function(){a.Q=function(){this.Fc={}};a.a.extend(a.Q.prototype,{nodeHasBindings:function(b){switch(b.nodeType){case 1:return null!=b.getAttribute("data-bind")||a.g.getComponentNameForNode(b);case 8:return a.f.Tc(b);
|
67 |
-
default:return!1}},getBindings:function(b,c){var d=this.getBindingsString(b,c),d=d?this.parseBindingsString(d,c,b):null;return a.g.Ob(d,b,c,!1)},getBindingAccessors:function(b,c){var d=this.getBindingsString(b,c),d=d?this.parseBindingsString(d,c,b,{valueAccessors:!0}):null;return a.g.Ob(d,b,c,!0)},getBindingsString:function(b){switch(b.nodeType){case 1:return b.getAttribute("data-bind");case 8:return a.f.pd(b);default:return null}},parseBindingsString:function(b,c,d,e){try{var f=this.Fc,g=b+(e&&e.valueAccessors||
|
68 |
-
""),k;if(!(k=f[g])){var l,m="with($context){with($data||{}){return{"+a.h.Ua(b,e)+"}}}";l=new Function("$context","$element",m);k=f[g]=l}return k(c,d)}catch(h){throw h.message="Unable to parse bindings.\nBindings value: "+b+"\nMessage: "+h.message,h;}}});a.Q.instance=new a.Q})();a.b("bindingProvider",a.Q);(function(){function b(a){return function(){return a}}function c(a){return a()}function d(b){return a.a.Ca(a.l.w(b),function(a,c){return function(){return b()[c]}})}function e(c,e,h){return"function"===
|
69 |
-
typeof c?d(c.bind(null,e,h)):a.a.Ca(c,b)}function f(a,b){return d(this.getBindings.bind(this,a,b))}function g(b,c,d){var e,h=a.f.firstChild(c),f=a.Q.instance,m=f.preprocessNode;if(m){for(;e=h;)h=a.f.nextSibling(e),m.call(f,e);h=a.f.firstChild(c)}for(;e=h;)h=a.f.nextSibling(e),k(b,e,d)}function k(b,c,d){var e=!0,h=1===c.nodeType;h&&a.f.kc(c);if(h&&d||a.Q.instance.nodeHasBindings(c))e=m(c,null,b,d).shouldBindDescendants;e&&!r[a.a.A(c)]&&g(b,c,!h)}function l(b){var c=[],d={},e=[];a.a.D(b,function Z(h){if(!d[h]){var f=
|
70 |
-
a.getBindingHandler(h);f&&(f.after&&(e.push(h),a.a.q(f.after,function(c){if(b[c]){if(-1!==a.a.o(e,c))throw Error("Cannot combine the following bindings, because they have a cyclic dependency: "+e.join(", "));Z(c)}}),e.length--),c.push({key:h,fc:f}));d[h]=!0}});return c}function m(b,d,e,h){var m=a.a.e.get(b,q);if(!d){if(m)throw Error("You cannot apply bindings multiple times to the same element.");a.a.e.set(b,q,!0)}!m&&h&&a.tc(b,e);var g;if(d&&"function"!==typeof d)g=d;else{var k=a.Q.instance,r=k.getBindingAccessors||
|
71 |
-
f,p=a.B(function(){(g=d?d(e,b):r.call(k,b,e))&&e.P&&e.P();return g},null,{i:b});g&&p.ba()||(p=null)}var u;if(g){var v=p?function(a){return function(){return c(p()[a])}}:function(a){return g[a]},s=function(){return a.a.Ca(p?p():g,c)};s.get=function(a){return g[a]&&c(v(a))};s.has=function(a){return a in g};h=l(g);a.a.q(h,function(c){var d=c.fc.init,h=c.fc.update,f=c.key;if(8===b.nodeType&&!a.f.Z[f])throw Error("The binding '"+f+"' cannot be used with virtual elements");try{"function"==typeof d&&a.l.w(function(){var a=
|
72 |
-
d(b,v(f),s,e.$data,e);if(a&&a.controlsDescendantBindings){if(u!==n)throw Error("Multiple bindings ("+u+" and "+f+") are trying to control descendant bindings of the same element. You cannot use these bindings together on the same element.");u=f}}),"function"==typeof h&&a.B(function(){h(b,v(f),s,e.$data,e)},null,{i:b})}catch(m){throw m.message='Unable to process binding "'+f+": "+g[f]+'"\nMessage: '+m.message,m;}})}return{shouldBindDescendants:u===n}}function h(b){return b&&b instanceof a.U?b:new a.U(b)}
|
73 |
-
a.d={};var r={script:!0,textarea:!0,template:!0};a.getBindingHandler=function(b){return a.d[b]};a.U=function(b,c,d,e){var h=this,f="function"==typeof b&&!a.H(b),m,g=a.B(function(){var m=f?b():b,l=a.a.c(m);c?(c.P&&c.P(),a.a.extend(h,c),g&&(h.P=g)):(h.$parents=[],h.$root=l,h.ko=a);h.$rawData=m;h.$data=l;d&&(h[d]=l);e&&e(h,c,l);return h.$data},null,{wa:function(){return m&&!a.a.Qb(m)},i:!0});g.ba()&&(h.P=g,g.equalityComparer=null,m=[],g.Ac=function(b){m.push(b);a.a.F.oa(b,function(b){a.a.La(m,b);m.length||
|
74 |
-
(g.k(),h.P=g=n)})})};a.U.prototype.createChildContext=function(b,c,d){return new a.U(b,this,c,function(a,b){a.$parentContext=b;a.$parent=b.$data;a.$parents=(b.$parents||[]).slice(0);a.$parents.unshift(a.$parent);d&&d(a)})};a.U.prototype.extend=function(b){return new a.U(this.P||this.$data,this,null,function(c,d){c.$rawData=d.$rawData;a.a.extend(c,"function"==typeof b?b():b)})};var q=a.a.e.I(),p=a.a.e.I();a.tc=function(b,c){if(2==arguments.length)a.a.e.set(b,p,c),c.P&&c.P.Ac(b);else return a.a.e.get(b,
|
75 |
-
p)};a.Ja=function(b,c,d){1===b.nodeType&&a.f.kc(b);return m(b,c,h(d),!0)};a.Dc=function(b,c,d){d=h(d);return a.Ja(b,e(c,d,b),d)};a.eb=function(a,b){1!==b.nodeType&&8!==b.nodeType||g(h(a),b,!0)};a.Rb=function(a,b){!v&&x.jQuery&&(v=x.jQuery);if(b&&1!==b.nodeType&&8!==b.nodeType)throw Error("ko.applyBindings: first parameter should be your view model; second parameter should be a DOM node");b=b||x.document.body;k(h(a),b,!0)};a.kb=function(b){switch(b.nodeType){case 1:case 8:var c=a.tc(b);if(c)return c;
|
76 |
-
if(b.parentNode)return a.kb(b.parentNode)}return n};a.Jc=function(b){return(b=a.kb(b))?b.$data:n};a.b("bindingHandlers",a.d);a.b("applyBindings",a.Rb);a.b("applyBindingsToDescendants",a.eb);a.b("applyBindingAccessorsToNode",a.Ja);a.b("applyBindingsToNode",a.Dc);a.b("contextFor",a.kb);a.b("dataFor",a.Jc)})();(function(b){function c(c,e){var m=f.hasOwnProperty(c)?f[c]:b,h;m?m.X(e):(m=f[c]=new a.J,m.X(e),d(c,function(b,d){var e=!(!d||!d.synchronous);g[c]={definition:b,Zc:e};delete f[c];h||e?m.notifySubscribers(b):
|
77 |
-
a.Y.Wa(function(){m.notifySubscribers(b)})}),h=!0)}function d(a,b){e("getConfig",[a],function(c){c?e("loadComponent",[a,c],function(a){b(a,c)}):b(null,null)})}function e(c,d,f,h){h||(h=a.g.loaders.slice(0));var g=h.shift();if(g){var q=g[c];if(q){var p=!1;if(q.apply(g,d.concat(function(a){p?f(null):null!==a?f(a):e(c,d,f,h)}))!==b&&(p=!0,!g.suppressLoaderExceptions))throw Error("Component loaders must supply values by invoking the callback, not by returning values synchronously.");}else e(c,d,f,h)}else f(null)}
|
78 |
-
var f={},g={};a.g={get:function(d,e){var f=g.hasOwnProperty(d)?g[d]:b;f?f.Zc?a.l.w(function(){e(f.definition)}):a.Y.Wa(function(){e(f.definition)}):c(d,e)},Xb:function(a){delete g[a]},Jb:e};a.g.loaders=[];a.b("components",a.g);a.b("components.get",a.g.get);a.b("components.clearCachedDefinition",a.g.Xb)})();(function(){function b(b,c,d,e){function g(){0===--y&&e(k)}var k={},y=2,t=d.template;d=d.viewModel;t?f(c,t,function(c){a.g.Jb("loadTemplate",[b,c],function(a){k.template=a;g()})}):g();d?f(c,d,function(c){a.g.Jb("loadViewModel",
|
79 |
-
[b,c],function(a){k[l]=a;g()})}):g()}function c(a,b,d){if("function"===typeof b)d(function(a){return new b(a)});else if("function"===typeof b[l])d(b[l]);else if("instance"in b){var e=b.instance;d(function(){return e})}else"viewModel"in b?c(a,b.viewModel,d):a("Unknown viewModel value: "+b)}function d(b){switch(a.a.A(b)){case "script":return a.a.ma(b.text);case "textarea":return a.a.ma(b.value);case "template":if(e(b.content))return a.a.ua(b.content.childNodes)}return a.a.ua(b.childNodes)}function e(a){return x.DocumentFragment?
|
80 |
-
a instanceof DocumentFragment:a&&11===a.nodeType}function f(a,b,c){"string"===typeof b.require?O||x.require?(O||x.require)([b.require],c):a("Uses require, but no AMD loader is present"):c(b)}function g(a){return function(b){throw Error("Component '"+a+"': "+b);}}var k={};a.g.register=function(b,c){if(!c)throw Error("Invalid configuration for "+b);if(a.g.ub(b))throw Error("Component "+b+" is already registered");k[b]=c};a.g.ub=function(a){return k.hasOwnProperty(a)};a.g.od=function(b){delete k[b];
|
81 |
-
a.g.Xb(b)};a.g.Zb={getConfig:function(a,b){b(k.hasOwnProperty(a)?k[a]:null)},loadComponent:function(a,c,d){var e=g(a);f(e,c,function(c){b(a,e,c,d)})},loadTemplate:function(b,c,f){b=g(b);if("string"===typeof c)f(a.a.ma(c));else if(c instanceof Array)f(c);else if(e(c))f(a.a.V(c.childNodes));else if(c.element)if(c=c.element,x.HTMLElement?c instanceof HTMLElement:c&&c.tagName&&1===c.nodeType)f(d(c));else if("string"===typeof c){var l=u.getElementById(c);l?f(d(l)):b("Cannot find element with ID "+c)}else b("Unknown element type: "+
|
82 |
-
c);else b("Unknown template value: "+c)},loadViewModel:function(a,b,d){c(g(a),b,d)}};var l="createViewModel";a.b("components.register",a.g.register);a.b("components.isRegistered",a.g.ub);a.b("components.unregister",a.g.od);a.b("components.defaultLoader",a.g.Zb);a.g.loaders.push(a.g.Zb);a.g.Bc=k})();(function(){function b(b,e){var f=b.getAttribute("params");if(f){var f=c.parseBindingsString(f,e,b,{valueAccessors:!0,bindingParams:!0}),f=a.a.Ca(f,function(c){return a.m(c,null,{i:b})}),g=a.a.Ca(f,function(c){var e=
|
83 |
-
c.t();return c.ba()?a.m({read:function(){return a.a.c(c())},write:a.Ba(e)&&function(a){c()(a)},i:b}):e});g.hasOwnProperty("$raw")||(g.$raw=f);return g}return{$raw:{}}}a.g.getComponentNameForNode=function(b){var c=a.a.A(b);if(a.g.ub(c)&&(-1!=c.indexOf("-")||"[object HTMLUnknownElement]"==""+b||8>=a.a.C&&b.tagName===c))return c};a.g.Ob=function(c,e,f,g){if(1===e.nodeType){var k=a.g.getComponentNameForNode(e);if(k){c=c||{};if(c.component)throw Error('Cannot use the "component" binding on a custom element matching a component');
|
84 |
-
var l={name:k,params:b(e,f)};c.component=g?function(){return l}:l}}return c};var c=new a.Q;9>a.a.C&&(a.g.register=function(a){return function(b){u.createElement(b);return a.apply(this,arguments)}}(a.g.register),u.createDocumentFragment=function(b){return function(){var c=b(),f=a.g.Bc,g;for(g in f)f.hasOwnProperty(g)&&c.createElement(g);return c}}(u.createDocumentFragment))})();(function(b){function c(b,c,d){c=c.template;if(!c)throw Error("Component '"+b+"' has no template");b=a.a.ua(c);a.f.da(d,b)}
|
85 |
-
function d(a,b,c,d){var e=a.createViewModel;return e?e.call(a,d,{element:b,templateNodes:c}):d}var e=0;a.d.component={init:function(f,g,k,l,m){function h(){var a=r&&r.dispose;"function"===typeof a&&a.call(r);q=r=null}var r,q,p=a.a.V(a.f.childNodes(f));a.a.F.oa(f,h);a.m(function(){var l=a.a.c(g()),k,t;"string"===typeof l?k=l:(k=a.a.c(l.name),t=a.a.c(l.params));if(!k)throw Error("No component name specified");var n=q=++e;a.g.get(k,function(e){if(q===n){h();if(!e)throw Error("Unknown component '"+k+
|
86 |
-
"'");c(k,e,f);var g=d(e,f,p,t);e=m.createChildContext(g,b,function(a){a.$component=g;a.$componentTemplateNodes=p});r=g;a.eb(e,f)}})},null,{i:f});return{controlsDescendantBindings:!0}}};a.f.Z.component=!0})();var S={"class":"className","for":"htmlFor"};a.d.attr={update:function(b,c){var d=a.a.c(c())||{};a.a.D(d,function(c,d){d=a.a.c(d);var g=!1===d||null===d||d===n;g&&b.removeAttribute(c);8>=a.a.C&&c in S?(c=S[c],g?b.removeAttribute(c):b[c]=d):g||b.setAttribute(c,d.toString());"name"===c&&a.a.rc(b,
|
87 |
-
g?"":d.toString())})}};(function(){a.d.checked={after:["value","attr"],init:function(b,c,d){function e(){var e=b.checked,f=p?g():e;if(!a.va.Sa()&&(!l||e)){var m=a.l.w(c);if(h){var k=r?m.t():m;q!==f?(e&&(a.a.pa(k,f,!0),a.a.pa(k,q,!1)),q=f):a.a.pa(k,f,e);r&&a.Ba(m)&&m(k)}else a.h.Ea(m,d,"checked",f,!0)}}function f(){var d=a.a.c(c());b.checked=h?0<=a.a.o(d,g()):k?d:g()===d}var g=a.nc(function(){return d.has("checkedValue")?a.a.c(d.get("checkedValue")):d.has("value")?a.a.c(d.get("value")):b.value}),k=
|
88 |
-
"checkbox"==b.type,l="radio"==b.type;if(k||l){var m=c(),h=k&&a.a.c(m)instanceof Array,r=!(h&&m.push&&m.splice),q=h?g():n,p=l||h;l&&!b.name&&a.d.uniqueName.init(b,function(){return!0});a.m(e,null,{i:b});a.a.p(b,"click",e);a.m(f,null,{i:b});m=n}}};a.h.ea.checked=!0;a.d.checkedValue={update:function(b,c){b.value=a.a.c(c())}}})();a.d.css={update:function(b,c){var d=a.a.c(c());null!==d&&"object"==typeof d?a.a.D(d,function(c,d){d=a.a.c(d);a.a.bb(b,c,d)}):(d=a.a.$a(String(d||"")),a.a.bb(b,b.__ko__cssValue,
|
89 |
-
!1),b.__ko__cssValue=d,a.a.bb(b,d,!0))}};a.d.enable={update:function(b,c){var d=a.a.c(c());d&&b.disabled?b.removeAttribute("disabled"):d||b.disabled||(b.disabled=!0)}};a.d.disable={update:function(b,c){a.d.enable.update(b,function(){return!a.a.c(c())})}};a.d.event={init:function(b,c,d,e,f){var g=c()||{};a.a.D(g,function(g){"string"==typeof g&&a.a.p(b,g,function(b){var m,h=c()[g];if(h){try{var r=a.a.V(arguments);e=f.$data;r.unshift(e);m=h.apply(e,r)}finally{!0!==m&&(b.preventDefault?b.preventDefault():
|
90 |
-
b.returnValue=!1)}!1===d.get(g+"Bubble")&&(b.cancelBubble=!0,b.stopPropagation&&b.stopPropagation())}})})}};a.d.foreach={ic:function(b){return function(){var c=b(),d=a.a.zb(c);if(!d||"number"==typeof d.length)return{foreach:c,templateEngine:a.W.sb};a.a.c(c);return{foreach:d.data,as:d.as,includeDestroyed:d.includeDestroyed,afterAdd:d.afterAdd,beforeRemove:d.beforeRemove,afterRender:d.afterRender,beforeMove:d.beforeMove,afterMove:d.afterMove,templateEngine:a.W.sb}}},init:function(b,c){return a.d.template.init(b,
|
91 |
-
a.d.foreach.ic(c))},update:function(b,c,d,e,f){return a.d.template.update(b,a.d.foreach.ic(c),d,e,f)}};a.h.ta.foreach=!1;a.f.Z.foreach=!0;a.d.hasfocus={init:function(b,c,d){function e(e){b.__ko_hasfocusUpdating=!0;var f=b.ownerDocument;if("activeElement"in f){var g;try{g=f.activeElement}catch(h){g=f.body}e=g===b}f=c();a.h.Ea(f,d,"hasfocus",e,!0);b.__ko_hasfocusLastValue=e;b.__ko_hasfocusUpdating=!1}var f=e.bind(null,!0),g=e.bind(null,!1);a.a.p(b,"focus",f);a.a.p(b,"focusin",f);a.a.p(b,"blur",g);a.a.p(b,
|
92 |
-
"focusout",g)},update:function(b,c){var d=!!a.a.c(c());b.__ko_hasfocusUpdating||b.__ko_hasfocusLastValue===d||(d?b.focus():b.blur(),!d&&b.__ko_hasfocusLastValue&&b.ownerDocument.body.focus(),a.l.w(a.a.Da,null,[b,d?"focusin":"focusout"]))}};a.h.ea.hasfocus=!0;a.d.hasFocus=a.d.hasfocus;a.h.ea.hasFocus=!0;a.d.html={init:function(){return{controlsDescendantBindings:!0}},update:function(b,c){a.a.Cb(b,c())}};K("if");K("ifnot",!1,!0);K("with",!0,!1,function(a,c){return a.createChildContext(c)});var L={};
|
93 |
-
a.d.options={init:function(b){if("select"!==a.a.A(b))throw Error("options binding applies only to SELECT elements");for(;0<b.length;)b.remove(0);return{controlsDescendantBindings:!0}},update:function(b,c,d){function e(){return a.a.Ka(b.options,function(a){return a.selected})}function f(a,b,c){var d=typeof b;return"function"==d?b(a):"string"==d?a[b]:c}function g(c,e){if(A&&h)a.j.ha(b,a.a.c(d.get("value")),!0);else if(p.length){var f=0<=a.a.o(p,a.j.u(e[0]));a.a.sc(e[0],f);A&&!f&&a.l.w(a.a.Da,null,[b,
|
94 |
-
"change"])}}var k=b.multiple,l=0!=b.length&&k?b.scrollTop:null,m=a.a.c(c()),h=d.get("valueAllowUnset")&&d.has("value"),r=d.get("optionsIncludeDestroyed");c={};var q,p=[];h||(k?p=a.a.fb(e(),a.j.u):0<=b.selectedIndex&&p.push(a.j.u(b.options[b.selectedIndex])));m&&("undefined"==typeof m.length&&(m=[m]),q=a.a.Ka(m,function(b){return r||b===n||null===b||!a.a.c(b._destroy)}),d.has("optionsCaption")&&(m=a.a.c(d.get("optionsCaption")),null!==m&&m!==n&&q.unshift(L)));var A=!1;c.beforeRemove=function(a){b.removeChild(a)};
|
95 |
-
m=g;d.has("optionsAfterRender")&&"function"==typeof d.get("optionsAfterRender")&&(m=function(b,c){g(0,c);a.l.w(d.get("optionsAfterRender"),null,[c[0],b!==L?b:n])});a.a.Bb(b,q,function(c,e,g){g.length&&(p=!h&&g[0].selected?[a.j.u(g[0])]:[],A=!0);e=b.ownerDocument.createElement("option");c===L?(a.a.Za(e,d.get("optionsCaption")),a.j.ha(e,n)):(g=f(c,d.get("optionsValue"),c),a.j.ha(e,a.a.c(g)),c=f(c,d.get("optionsText"),g),a.a.Za(e,c));return[e]},c,m);a.l.w(function(){h?a.j.ha(b,a.a.c(d.get("value")),
|
96 |
-
!0):(k?p.length&&e().length<p.length:p.length&&0<=b.selectedIndex?a.j.u(b.options[b.selectedIndex])!==p[0]:p.length||0<=b.selectedIndex)&&a.a.Da(b,"change")});a.a.Nc(b);l&&20<Math.abs(l-b.scrollTop)&&(b.scrollTop=l)}};a.d.options.xb=a.a.e.I();a.d.selectedOptions={after:["options","foreach"],init:function(b,c,d){a.a.p(b,"change",function(){var e=c(),f=[];a.a.q(b.getElementsByTagName("option"),function(b){b.selected&&f.push(a.j.u(b))});a.h.Ea(e,d,"selectedOptions",f)})},update:function(b,c){if("select"!=
|
97 |
-
a.a.A(b))throw Error("values binding applies only to SELECT elements");var d=a.a.c(c()),e=b.scrollTop;d&&"number"==typeof d.length&&a.a.q(b.getElementsByTagName("option"),function(b){var c=0<=a.a.o(d,a.j.u(b));b.selected!=c&&a.a.sc(b,c)});b.scrollTop=e}};a.h.ea.selectedOptions=!0;a.d.style={update:function(b,c){var d=a.a.c(c()||{});a.a.D(d,function(c,d){d=a.a.c(d);if(null===d||d===n||!1===d)d="";b.style[c]=d})}};a.d.submit={init:function(b,c,d,e,f){if("function"!=typeof c())throw Error("The value for a submit binding must be a function");
|
98 |
-
a.a.p(b,"submit",function(a){var d,e=c();try{d=e.call(f.$data,b)}finally{!0!==d&&(a.preventDefault?a.preventDefault():a.returnValue=!1)}})}};a.d.text={init:function(){return{controlsDescendantBindings:!0}},update:function(b,c){a.a.Za(b,c())}};a.f.Z.text=!0;(function(){if(x&&x.navigator)var b=function(a){if(a)return parseFloat(a[1])},c=x.opera&&x.opera.version&&parseInt(x.opera.version()),d=x.navigator.userAgent,e=b(d.match(/^(?:(?!chrome).)*version\/([^ ]*) safari/i)),f=b(d.match(/Firefox\/([^ ]*)/));
|
99 |
-
if(10>a.a.C)var g=a.a.e.I(),k=a.a.e.I(),l=function(b){var c=this.activeElement;(c=c&&a.a.e.get(c,k))&&c(b)},m=function(b,c){var d=b.ownerDocument;a.a.e.get(d,g)||(a.a.e.set(d,g,!0),a.a.p(d,"selectionchange",l));a.a.e.set(b,k,c)};a.d.textInput={init:function(b,d,g){function l(c,d){a.a.p(b,c,d)}function k(){var c=a.a.c(d());if(null===c||c===n)c="";v!==n&&c===v?a.a.setTimeout(k,4):b.value!==c&&(u=c,b.value=c)}function y(){s||(v=b.value,s=a.a.setTimeout(t,4))}function t(){clearTimeout(s);v=s=n;var c=
|
100 |
-
b.value;u!==c&&(u=c,a.h.Ea(d(),g,"textInput",c))}var u=b.value,s,v,x=9==a.a.C?y:t;10>a.a.C?(l("propertychange",function(a){"value"===a.propertyName&&x(a)}),8==a.a.C&&(l("keyup",t),l("keydown",t)),8<=a.a.C&&(m(b,x),l("dragend",y))):(l("input",t),5>e&&"textarea"===a.a.A(b)?(l("keydown",y),l("paste",y),l("cut",y)):11>c?l("keydown",y):4>f&&(l("DOMAutoComplete",t),l("dragdrop",t),l("drop",t)));l("change",t);a.m(k,null,{i:b})}};a.h.ea.textInput=!0;a.d.textinput={preprocess:function(a,b,c){c("textInput",
|
101 |
-
a)}}})();a.d.uniqueName={init:function(b,c){if(c()){var d="ko_unique_"+ ++a.d.uniqueName.Ic;a.a.rc(b,d)}}};a.d.uniqueName.Ic=0;a.d.value={after:["options","foreach"],init:function(b,c,d){if("input"!=b.tagName.toLowerCase()||"checkbox"!=b.type&&"radio"!=b.type){var e=["change"],f=d.get("valueUpdate"),g=!1,k=null;f&&("string"==typeof f&&(f=[f]),a.a.ra(e,f),e=a.a.Tb(e));var l=function(){k=null;g=!1;var e=c(),f=a.j.u(b);a.h.Ea(e,d,"value",f)};!a.a.C||"input"!=b.tagName.toLowerCase()||"text"!=b.type||
|
102 |
-
"off"==b.autocomplete||b.form&&"off"==b.form.autocomplete||-1!=a.a.o(e,"propertychange")||(a.a.p(b,"propertychange",function(){g=!0}),a.a.p(b,"focus",function(){g=!1}),a.a.p(b,"blur",function(){g&&l()}));a.a.q(e,function(c){var d=l;a.a.nd(c,"after")&&(d=function(){k=a.j.u(b);a.a.setTimeout(l,0)},c=c.substring(5));a.a.p(b,c,d)});var m=function(){var e=a.a.c(c()),f=a.j.u(b);if(null!==k&&e===k)a.a.setTimeout(m,0);else if(e!==f)if("select"===a.a.A(b)){var g=d.get("valueAllowUnset"),f=function(){a.j.ha(b,
|
103 |
-
e,g)};f();g||e===a.j.u(b)?a.a.setTimeout(f,0):a.l.w(a.a.Da,null,[b,"change"])}else a.j.ha(b,e)};a.m(m,null,{i:b})}else a.Ja(b,{checkedValue:c})},update:function(){}};a.h.ea.value=!0;a.d.visible={update:function(b,c){var d=a.a.c(c()),e="none"!=b.style.display;d&&!e?b.style.display="":!d&&e&&(b.style.display="none")}};(function(b){a.d[b]={init:function(c,d,e,f,g){return a.d.event.init.call(this,c,function(){var a={};a[b]=d();return a},e,f,g)}}})("click");a.O=function(){};a.O.prototype.renderTemplateSource=
|
104 |
-
function(){throw Error("Override renderTemplateSource");};a.O.prototype.createJavaScriptEvaluatorBlock=function(){throw Error("Override createJavaScriptEvaluatorBlock");};a.O.prototype.makeTemplateSource=function(b,c){if("string"==typeof b){c=c||u;var d=c.getElementById(b);if(!d)throw Error("Cannot find template with ID "+b);return new a.v.n(d)}if(1==b.nodeType||8==b.nodeType)return new a.v.qa(b);throw Error("Unknown template type: "+b);};a.O.prototype.renderTemplate=function(a,c,d,e){a=this.makeTemplateSource(a,
|
105 |
-
e);return this.renderTemplateSource(a,c,d,e)};a.O.prototype.isTemplateRewritten=function(a,c){return!1===this.allowTemplateRewriting?!0:this.makeTemplateSource(a,c).data("isRewritten")};a.O.prototype.rewriteTemplate=function(a,c,d){a=this.makeTemplateSource(a,d);c=c(a.text());a.text(c);a.data("isRewritten",!0)};a.b("templateEngine",a.O);a.Gb=function(){function b(b,c,d,k){b=a.h.yb(b);for(var l=a.h.ta,m=0;m<b.length;m++){var h=b[m].key;if(l.hasOwnProperty(h)){var r=l[h];if("function"===typeof r){if(h=
|
106 |
-
r(b[m].value))throw Error(h);}else if(!r)throw Error("This template engine does not support the '"+h+"' binding within its templates");}}d="ko.__tr_ambtns(function($context,$element){return(function(){return{ "+a.h.Ua(b,{valueAccessors:!0})+" } })()},'"+d.toLowerCase()+"')";return k.createJavaScriptEvaluatorBlock(d)+c}var c=/(<([a-z]+\d*)(?:\s+(?!data-bind\s*=\s*)[a-z0-9\-]+(?:=(?:\"[^\"]*\"|\'[^\']*\'|[^>]*))?)*\s+)data-bind\s*=\s*(["'])([\s\S]*?)\3/gi,d=/\x3c!--\s*ko\b\s*([\s\S]*?)\s*--\x3e/g;return{Oc:function(b,
|
107 |
-
c,d){c.isTemplateRewritten(b,d)||c.rewriteTemplate(b,function(b){return a.Gb.dd(b,c)},d)},dd:function(a,f){return a.replace(c,function(a,c,d,e,h){return b(h,c,d,f)}).replace(d,function(a,c){return b(c,"\x3c!-- ko --\x3e","#comment",f)})},Ec:function(b,c){return a.M.wb(function(d,k){var l=d.nextSibling;l&&l.nodeName.toLowerCase()===c&&a.Ja(l,b,k)})}}}();a.b("__tr_ambtns",a.Gb.Ec);(function(){a.v={};a.v.n=function(b){if(this.n=b){var c=a.a.A(b);this.ab="script"===c?1:"textarea"===c?2:"template"==c&&
|
108 |
-
b.content&&11===b.content.nodeType?3:4}};a.v.n.prototype.text=function(){var b=1===this.ab?"text":2===this.ab?"value":"innerHTML";if(0==arguments.length)return this.n[b];var c=arguments[0];"innerHTML"===b?a.a.Cb(this.n,c):this.n[b]=c};var b=a.a.e.I()+"_";a.v.n.prototype.data=function(c){if(1===arguments.length)return a.a.e.get(this.n,b+c);a.a.e.set(this.n,b+c,arguments[1])};var c=a.a.e.I();a.v.n.prototype.nodes=function(){var b=this.n;if(0==arguments.length)return(a.a.e.get(b,c)||{}).jb||(3===this.ab?
|
109 |
-
b.content:4===this.ab?b:n);a.a.e.set(b,c,{jb:arguments[0]})};a.v.qa=function(a){this.n=a};a.v.qa.prototype=new a.v.n;a.v.qa.prototype.text=function(){if(0==arguments.length){var b=a.a.e.get(this.n,c)||{};b.Hb===n&&b.jb&&(b.Hb=b.jb.innerHTML);return b.Hb}a.a.e.set(this.n,c,{Hb:arguments[0]})};a.b("templateSources",a.v);a.b("templateSources.domElement",a.v.n);a.b("templateSources.anonymousTemplate",a.v.qa)})();(function(){function b(b,c,d){var e;for(c=a.f.nextSibling(c);b&&(e=b)!==c;)b=a.f.nextSibling(e),
|
110 |
-
d(e,b)}function c(c,d){if(c.length){var e=c[0],f=c[c.length-1],g=e.parentNode,k=a.Q.instance,n=k.preprocessNode;if(n){b(e,f,function(a,b){var c=a.previousSibling,d=n.call(k,a);d&&(a===e&&(e=d[0]||b),a===f&&(f=d[d.length-1]||c))});c.length=0;if(!e)return;e===f?c.push(e):(c.push(e,f),a.a.za(c,g))}b(e,f,function(b){1!==b.nodeType&&8!==b.nodeType||a.Rb(d,b)});b(e,f,function(b){1!==b.nodeType&&8!==b.nodeType||a.M.yc(b,[d])});a.a.za(c,g)}}function d(a){return a.nodeType?a:0<a.length?a[0]:null}function e(b,
|
111 |
-
e,f,k,q){q=q||{};var p=(b&&d(b)||f||{}).ownerDocument,n=q.templateEngine||g;a.Gb.Oc(f,n,p);f=n.renderTemplate(f,k,q,p);if("number"!=typeof f.length||0<f.length&&"number"!=typeof f[0].nodeType)throw Error("Template engine must return an array of DOM nodes");p=!1;switch(e){case "replaceChildren":a.f.da(b,f);p=!0;break;case "replaceNode":a.a.qc(b,f);p=!0;break;case "ignoreTargetNode":break;default:throw Error("Unknown renderMode: "+e);}p&&(c(f,k),q.afterRender&&a.l.w(q.afterRender,null,[f,k.$data]));
|
112 |
-
return f}function f(b,c,d){return a.H(b)?b():"function"===typeof b?b(c,d):b}var g;a.Db=function(b){if(b!=n&&!(b instanceof a.O))throw Error("templateEngine must inherit from ko.templateEngine");g=b};a.Ab=function(b,c,h,k,q){h=h||{};if((h.templateEngine||g)==n)throw Error("Set a template engine before calling renderTemplate");q=q||"replaceChildren";if(k){var p=d(k);return a.B(function(){var g=c&&c instanceof a.U?c:new a.U(a.a.c(c)),n=f(b,g.$data,g),g=e(k,q,n,g,h);"replaceNode"==q&&(k=g,p=d(k))},null,
|
113 |
-
{wa:function(){return!p||!a.a.nb(p)},i:p&&"replaceNode"==q?p.parentNode:p})}return a.M.wb(function(d){a.Ab(b,c,h,d,"replaceNode")})};a.kd=function(b,d,g,k,q){function p(a,b){c(b,s);g.afterRender&&g.afterRender(b,a);s=null}function u(a,c){s=q.createChildContext(a,g.as,function(a){a.$index=c});var d=f(b,a,s);return e(null,"ignoreTargetNode",d,s,g)}var s;return a.B(function(){var b=a.a.c(d)||[];"undefined"==typeof b.length&&(b=[b]);b=a.a.Ka(b,function(b){return g.includeDestroyed||b===n||null===b||!a.a.c(b._destroy)});
|
114 |
-
a.l.w(a.a.Bb,null,[k,b,u,g,p])},null,{i:k})};var k=a.a.e.I();a.d.template={init:function(b,c){var d=a.a.c(c());if("string"==typeof d||d.name)a.f.xa(b);else{if("nodes"in d){if(d=d.nodes||[],a.H(d))throw Error('The "nodes" option must be a plain, non-observable array.');}else d=a.f.childNodes(b);d=a.a.jc(d);(new a.v.qa(b)).nodes(d)}return{controlsDescendantBindings:!0}},update:function(b,c,d,e,f){var g=c(),s;c=a.a.c(g);d=!0;e=null;"string"==typeof c?c={}:(g=c.name,"if"in c&&(d=a.a.c(c["if"])),d&&"ifnot"in
|
115 |
-
c&&(d=!a.a.c(c.ifnot)),s=a.a.c(c.data));"foreach"in c?e=a.kd(g||b,d&&c.foreach||[],c,b,f):d?(f="data"in c?f.createChildContext(s,c.as):f,e=a.Ab(g||b,f,c,b)):a.f.xa(b);f=e;(s=a.a.e.get(b,k))&&"function"==typeof s.k&&s.k();a.a.e.set(b,k,f&&f.ba()?f:n)}};a.h.ta.template=function(b){b=a.h.yb(b);return 1==b.length&&b[0].unknown||a.h.ad(b,"name")?null:"This template engine does not support anonymous templates nested within its templates"};a.f.Z.template=!0})();a.b("setTemplateEngine",a.Db);a.b("renderTemplate",
|
116 |
-
a.Ab);a.a.dc=function(a,c,d){if(a.length&&c.length){var e,f,g,k,l;for(e=f=0;(!d||e<d)&&(k=a[f]);++f){for(g=0;l=c[g];++g)if(k.value===l.value){k.moved=l.index;l.moved=k.index;c.splice(g,1);e=g=0;break}e+=g}}};a.a.ib=function(){function b(b,d,e,f,g){var k=Math.min,l=Math.max,m=[],h,n=b.length,q,p=d.length,s=p-n||1,u=n+p+1,t,v,x;for(h=0;h<=n;h++)for(v=t,m.push(t=[]),x=k(p,h+s),q=l(0,h-1);q<=x;q++)t[q]=q?h?b[h-1]===d[q-1]?v[q-1]:k(v[q]||u,t[q-1]||u)+1:q+1:h+1;k=[];l=[];s=[];h=n;for(q=p;h||q;)p=m[h][q]-
|
117 |
-
1,q&&p===m[h][q-1]?l.push(k[k.length]={status:e,value:d[--q],index:q}):h&&p===m[h-1][q]?s.push(k[k.length]={status:f,value:b[--h],index:h}):(--q,--h,g.sparse||k.push({status:"retained",value:d[q]}));a.a.dc(s,l,!g.dontLimitMoves&&10*n);return k.reverse()}return function(a,d,e){e="boolean"===typeof e?{dontLimitMoves:e}:e||{};a=a||[];d=d||[];return a.length<d.length?b(a,d,"added","deleted",e):b(d,a,"deleted","added",e)}}();a.b("utils.compareArrays",a.a.ib);(function(){function b(b,c,d,k,l){var m=[],
|
118 |
-
h=a.B(function(){var h=c(d,l,a.a.za(m,b))||[];0<m.length&&(a.a.qc(m,h),k&&a.l.w(k,null,[d,h,l]));m.length=0;a.a.ra(m,h)},null,{i:b,wa:function(){return!a.a.Qb(m)}});return{ca:m,B:h.ba()?h:n}}var c=a.a.e.I(),d=a.a.e.I();a.a.Bb=function(e,f,g,k,l){function m(b,c){w=q[c];v!==c&&(D[b]=w);w.qb(v++);a.a.za(w.ca,e);u.push(w);z.push(w)}function h(b,c){if(b)for(var d=0,e=c.length;d<e;d++)c[d]&&a.a.q(c[d].ca,function(a){b(a,d,c[d].ja)})}f=f||[];k=k||{};var r=a.a.e.get(e,c)===n,q=a.a.e.get(e,c)||[],p=a.a.fb(q,
|
119 |
-
function(a){return a.ja}),s=a.a.ib(p,f,k.dontLimitMoves),u=[],t=0,v=0,x=[],z=[];f=[];for(var D=[],p=[],w,C=0,B,E;B=s[C];C++)switch(E=B.moved,B.status){case "deleted":E===n&&(w=q[t],w.B&&(w.B.k(),w.B=n),a.a.za(w.ca,e).length&&(k.beforeRemove&&(u.push(w),z.push(w),w.ja===d?w=null:f[C]=w),w&&x.push.apply(x,w.ca)));t++;break;case "retained":m(C,t++);break;case "added":E!==n?m(C,E):(w={ja:B.value,qb:a.N(v++)},u.push(w),z.push(w),r||(p[C]=w))}a.a.e.set(e,c,u);h(k.beforeMove,D);a.a.q(x,k.beforeRemove?a.$:
|
120 |
-
a.removeNode);for(var C=0,r=a.f.firstChild(e),F;w=z[C];C++){w.ca||a.a.extend(w,b(e,g,w.ja,l,w.qb));for(t=0;s=w.ca[t];r=s.nextSibling,F=s,t++)s!==r&&a.f.gc(e,s,F);!w.Wc&&l&&(l(w.ja,w.ca,w.qb),w.Wc=!0)}h(k.beforeRemove,f);for(C=0;C<f.length;++C)f[C]&&(f[C].ja=d);h(k.afterMove,D);h(k.afterAdd,p)}})();a.b("utils.setDomNodeChildrenFromArrayMapping",a.a.Bb);a.W=function(){this.allowTemplateRewriting=!1};a.W.prototype=new a.O;a.W.prototype.renderTemplateSource=function(b,c,d,e){if(c=(9>a.a.C?0:b.nodes)?
|
121 |
-
b.nodes():null)return a.a.V(c.cloneNode(!0).childNodes);b=b.text();return a.a.ma(b,e)};a.W.sb=new a.W;a.Db(a.W.sb);a.b("nativeTemplateEngine",a.W);(function(){a.vb=function(){var a=this.$c=function(){if(!v||!v.tmpl)return 0;try{if(0<=v.tmpl.tag.tmpl.open.toString().indexOf("__"))return 2}catch(a){}return 1}();this.renderTemplateSource=function(b,e,f,g){g=g||u;f=f||{};if(2>a)throw Error("Your version of jQuery.tmpl is too old. Please upgrade to jQuery.tmpl 1.0.0pre or later.");var k=b.data("precompiled");
|
122 |
-
k||(k=b.text()||"",k=v.template(null,"{{ko_with $item.koBindingContext}}"+k+"{{/ko_with}}"),b.data("precompiled",k));b=[e.$data];e=v.extend({koBindingContext:e},f.templateOptions);e=v.tmpl(k,b,e);e.appendTo(g.createElement("div"));v.fragments={};return e};this.createJavaScriptEvaluatorBlock=function(a){return"{{ko_code ((function() { return "+a+" })()) }}"};this.addTemplate=function(a,b){u.write("<script type='text/html' id='"+a+"'>"+b+"\x3c/script>")};0<a&&(v.tmpl.tag.ko_code={open:"__.push($1 || '');"},
|
123 |
-
v.tmpl.tag.ko_with={open:"with($1) {",close:"} "})};a.vb.prototype=new a.O;var b=new a.vb;0<b.$c&&a.Db(b);a.b("jqueryTmplTemplateEngine",a.vb)})()})})();})();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/toolset/toolset-common/visual-editor/res/js/codemirror/.gitattributes
DELETED
@@ -1,8 +0,0 @@
|
|
1 |
-
*.txt text
|
2 |
-
*.js text
|
3 |
-
*.html text
|
4 |
-
*.md text
|
5 |
-
*.json text
|
6 |
-
*.yml text
|
7 |
-
*.css text
|
8 |
-
*.svg text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/toolset/toolset-common/visual-editor/res/js/codemirror/.npmignore
DELETED
@@ -1,9 +0,0 @@
|
|
1 |
-
/node_modules
|
2 |
-
/demo
|
3 |
-
/doc
|
4 |
-
/test
|
5 |
-
/index.html
|
6 |
-
/mode/*/*test.js
|
7 |
-
/mode/*/*.html
|
8 |
-
/mode/index.html
|
9 |
-
.*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/toolset/toolset-common/visual-editor/res/js/codemirror/.travis.yml
DELETED
@@ -1,4 +0,0 @@
|
|
1 |
-
language: node_js
|
2 |
-
node_js:
|
3 |
-
- stable
|
4 |
-
sudo: false
|
|
|
|
|
|
|
|
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/comment_test.js
DELETED
@@ -1,100 +0,0 @@
|
|
1 |
-
namespace = "comment_";
|
2 |
-
|
3 |
-
(function() {
|
4 |
-
function test(name, mode, run, before, after) {
|
5 |
-
return testCM(name, function(cm) {
|
6 |
-
run(cm);
|
7 |
-
eq(cm.getValue(), after);
|
8 |
-
}, {value: before, mode: mode});
|
9 |
-
}
|
10 |
-
|
11 |
-
var simpleProg = "function foo() {\n return bar;\n}";
|
12 |
-
var inlineBlock = "foo(/* bar */ true);";
|
13 |
-
var inlineBlocks = "foo(/* bar */ true, /* baz */ false);";
|
14 |
-
var multiLineInlineBlock = ["above();", "foo(/* bar */ true);", "below();"];
|
15 |
-
|
16 |
-
test("block", "javascript", function(cm) {
|
17 |
-
cm.blockComment(Pos(0, 3), Pos(3, 0), {blockCommentLead: " *"});
|
18 |
-
}, simpleProg + "\n", "/* function foo() {\n * return bar;\n * }\n */");
|
19 |
-
|
20 |
-
test("blockToggle", "javascript", function(cm) {
|
21 |
-
cm.blockComment(Pos(0, 3), Pos(2, 0), {blockCommentLead: " *"});
|
22 |
-
cm.uncomment(Pos(0, 3), Pos(2, 0), {blockCommentLead: " *"});
|
23 |
-
}, simpleProg, simpleProg);
|
24 |
-
|
25 |
-
test("blockToggle2", "javascript", function(cm) {
|
26 |
-
cm.setCursor({line: 0, ch: 7 /* inside the block comment */});
|
27 |
-
cm.execCommand("toggleComment");
|
28 |
-
}, inlineBlock, "foo(bar true);");
|
29 |
-
|
30 |
-
// This test should work but currently fails.
|
31 |
-
// test("blockToggle3", "javascript", function(cm) {
|
32 |
-
// cm.setCursor({line: 0, ch: 7 /* inside the first block comment */});
|
33 |
-
// cm.execCommand("toggleComment");
|
34 |
-
// }, inlineBlocks, "foo(bar true, /* baz */ false);");
|
35 |
-
|
36 |
-
test("line", "javascript", function(cm) {
|
37 |
-
cm.lineComment(Pos(1, 1), Pos(1, 1));
|
38 |
-
}, simpleProg, "function foo() {\n// return bar;\n}");
|
39 |
-
|
40 |
-
test("lineToggle", "javascript", function(cm) {
|
41 |
-
cm.lineComment(Pos(0, 0), Pos(2, 1));
|
42 |
-
cm.uncomment(Pos(0, 0), Pos(2, 1));
|
43 |
-
}, simpleProg, simpleProg);
|
44 |
-
|
45 |
-
test("fallbackToBlock", "css", function(cm) {
|
46 |
-
cm.lineComment(Pos(0, 0), Pos(2, 1));
|
47 |
-
}, "html {\n border: none;\n}", "/* html {\n border: none;\n} */");
|
48 |
-
|
49 |
-
test("fallbackToLine", "ruby", function(cm) {
|
50 |
-
cm.blockComment(Pos(0, 0), Pos(1));
|
51 |
-
}, "def blah()\n return hah\n", "# def blah()\n# return hah\n");
|
52 |
-
|
53 |
-
test("ignoreExternalBlockComments", "javascript", function(cm) {
|
54 |
-
cm.execCommand("toggleComment");
|
55 |
-
}, inlineBlocks, "// " + inlineBlocks);
|
56 |
-
|
57 |
-
test("ignoreExternalBlockComments2", "javascript", function(cm) {
|
58 |
-
cm.setCursor({line: 0, ch: null /* eol */});
|
59 |
-
cm.execCommand("toggleComment");
|
60 |
-
}, inlineBlocks, "// " + inlineBlocks);
|
61 |
-
|
62 |
-
test("ignoreExternalBlockCommentsMultiLineAbove", "javascript", function(cm) {
|
63 |
-
cm.setSelection({line: 0, ch: 0}, {line: 1, ch: 1});
|
64 |
-
cm.execCommand("toggleComment");
|
65 |
-
}, multiLineInlineBlock.join("\n"), ["// " + multiLineInlineBlock[0],
|
66 |
-
"// " + multiLineInlineBlock[1],
|
67 |
-
multiLineInlineBlock[2]].join("\n"));
|
68 |
-
|
69 |
-
test("ignoreExternalBlockCommentsMultiLineBelow", "javascript", function(cm) {
|
70 |
-
cm.setSelection({line: 1, ch: 13 /* after end of block comment */}, {line: 2, ch: 1});
|
71 |
-
cm.execCommand("toggleComment");
|
72 |
-
}, multiLineInlineBlock.join("\n"), [multiLineInlineBlock[0],
|
73 |
-
"// " + multiLineInlineBlock[1],
|
74 |
-
"// " + multiLineInlineBlock[2]].join("\n"));
|
75 |
-
|
76 |
-
test("commentRange", "javascript", function(cm) {
|
77 |
-
cm.blockComment(Pos(1, 2), Pos(1, 13), {fullLines: false});
|
78 |
-
}, simpleProg, "function foo() {\n /*return bar;*/\n}");
|
79 |
-
|
80 |
-
test("indented", "javascript", function(cm) {
|
81 |
-
cm.lineComment(Pos(1, 0), Pos(2), {indent: true});
|
82 |
-
}, simpleProg, "function foo() {\n // return bar;\n // }");
|
83 |
-
|
84 |
-
test("singleEmptyLine", "javascript", function(cm) {
|
85 |
-
cm.setCursor(1);
|
86 |
-
cm.execCommand("toggleComment");
|
87 |
-
}, "a;\n\nb;", "a;\n// \nb;");
|
88 |
-
|
89 |
-
test("dontMessWithStrings", "javascript", function(cm) {
|
90 |
-
cm.execCommand("toggleComment");
|
91 |
-
}, "console.log(\"/*string*/\");", "// console.log(\"/*string*/\");");
|
92 |
-
|
93 |
-
test("dontMessWithStrings2", "javascript", function(cm) {
|
94 |
-
cm.execCommand("toggleComment");
|
95 |
-
}, "console.log(\"// string\");", "// console.log(\"// string\");");
|
96 |
-
|
97 |
-
test("dontMessWithStrings3", "javascript", function(cm) {
|
98 |
-
cm.execCommand("toggleComment");
|
99 |
-
}, "// console.log(\"// string\");", "console.log(\"// string\");");
|
100 |
-
})();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/doc_test.js
DELETED
@@ -1,371 +0,0 @@
|
|
1 |
-
(function() {
|
2 |
-
// A minilanguage for instantiating linked CodeMirror instances and Docs
|
3 |
-
function instantiateSpec(spec, place, opts) {
|
4 |
-
var names = {}, pos = 0, l = spec.length, editors = [];
|
5 |
-
while (spec) {
|
6 |
-
var m = spec.match(/^(\w+)(\*?)(?:='([^\']*)'|<(~?)(\w+)(?:\/(\d+)-(\d+))?)\s*/);
|
7 |
-
var name = m[1], isDoc = m[2], cur;
|
8 |
-
if (m[3]) {
|
9 |
-
cur = isDoc ? CodeMirror.Doc(m[3]) : CodeMirror(place, clone(opts, {value: m[3]}));
|
10 |
-
} else {
|
11 |
-
var other = m[5];
|
12 |
-
if (!names.hasOwnProperty(other)) {
|
13 |
-
names[other] = editors.length;
|
14 |
-
editors.push(CodeMirror(place, opts));
|
15 |
-
}
|
16 |
-
var doc = editors[names[other]].linkedDoc({
|
17 |
-
sharedHist: !m[4],
|
18 |
-
from: m[6] ? Number(m[6]) : null,
|
19 |
-
to: m[7] ? Number(m[7]) : null
|
20 |
-
});
|
21 |
-
cur = isDoc ? doc : CodeMirror(place, clone(opts, {value: doc}));
|
22 |
-
}
|
23 |
-
names[name] = editors.length;
|
24 |
-
editors.push(cur);
|
25 |
-
spec = spec.slice(m[0].length);
|
26 |
-
}
|
27 |
-
return editors;
|
28 |
-
}
|
29 |
-
|
30 |
-
function clone(obj, props) {
|
31 |
-
if (!obj) return;
|
32 |
-
clone.prototype = obj;
|
33 |
-
var inst = new clone();
|
34 |
-
if (props) for (var n in props) if (props.hasOwnProperty(n))
|
35 |
-
inst[n] = props[n];
|
36 |
-
return inst;
|
37 |
-
}
|
38 |
-
|
39 |
-
function eqAll(val) {
|
40 |
-
var end = arguments.length, msg = null;
|
41 |
-
if (typeof arguments[end-1] == "string")
|
42 |
-
msg = arguments[--end];
|
43 |
-
if (i == end) throw new Error("No editors provided to eqAll");
|
44 |
-
for (var i = 1; i < end; ++i)
|
45 |
-
eq(arguments[i].getValue(), val, msg)
|
46 |
-
}
|
47 |
-
|
48 |
-
function testDoc(name, spec, run, opts, expectFail) {
|
49 |
-
if (!opts) opts = {};
|
50 |
-
|
51 |
-
return test("doc_" + name, function() {
|
52 |
-
var place = document.getElementById("testground");
|
53 |
-
var editors = instantiateSpec(spec, place, opts);
|
54 |
-
var successful = false;
|
55 |
-
|
56 |
-
try {
|
57 |
-
run.apply(null, editors);
|
58 |
-
successful = true;
|
59 |
-
} finally {
|
60 |
-
if (!successful || verbose) {
|
61 |
-
place.style.visibility = "visible";
|
62 |
-
} else {
|
63 |
-
for (var i = 0; i < editors.length; ++i)
|
64 |
-
if (editors[i] instanceof CodeMirror)
|
65 |
-
place.removeChild(editors[i].getWrapperElement());
|
66 |
-
}
|
67 |
-
}
|
68 |
-
}, expectFail);
|
69 |
-
}
|
70 |
-
|
71 |
-
var ie_lt8 = /MSIE [1-7]\b/.test(navigator.userAgent);
|
72 |
-
|
73 |
-
function testBasic(a, b) {
|
74 |
-
eqAll("x", a, b);
|
75 |
-
a.setValue("hey");
|
76 |
-
eqAll("hey", a, b);
|
77 |
-
b.setValue("wow");
|
78 |
-
eqAll("wow", a, b);
|
79 |
-
a.replaceRange("u\nv\nw", Pos(0, 3));
|
80 |
-
b.replaceRange("i", Pos(0, 4));
|
81 |
-
b.replaceRange("j", Pos(2, 1));
|
82 |
-
eqAll("wowui\nv\nwj", a, b);
|
83 |
-
}
|
84 |
-
|
85 |
-
testDoc("basic", "A='x' B<A", testBasic);
|
86 |
-
testDoc("basicSeparate", "A='x' B<~A", testBasic);
|
87 |
-
|
88 |
-
testDoc("sharedHist", "A='ab\ncd\nef' B<A", function(a, b) {
|
89 |
-
a.replaceRange("x", Pos(0));
|
90 |
-
b.replaceRange("y", Pos(1));
|
91 |
-
a.replaceRange("z", Pos(2));
|
92 |
-
eqAll("abx\ncdy\nefz", a, b);
|
93 |
-
a.undo();
|
94 |
-
a.undo();
|
95 |
-
eqAll("abx\ncd\nef", a, b);
|
96 |
-
a.redo();
|
97 |
-
eqAll("abx\ncdy\nef", a, b);
|
98 |
-
b.redo();
|
99 |
-
eqAll("abx\ncdy\nefz", a, b);
|
100 |
-
a.undo(); b.undo(); a.undo(); a.undo();
|
101 |
-
eqAll("ab\ncd\nef", a, b);
|
102 |
-
}, null, ie_lt8);
|
103 |
-
|
104 |
-
testDoc("undoIntact", "A='ab\ncd\nef' B<~A", function(a, b) {
|
105 |
-
a.replaceRange("x", Pos(0));
|
106 |
-
b.replaceRange("y", Pos(1));
|
107 |
-
a.replaceRange("z", Pos(2));
|
108 |
-
a.replaceRange("q", Pos(0));
|
109 |
-
eqAll("abxq\ncdy\nefz", a, b);
|
110 |
-
a.undo();
|
111 |
-
a.undo();
|
112 |
-
eqAll("abx\ncdy\nef", a, b);
|
113 |
-
b.undo();
|
114 |
-
eqAll("abx\ncd\nef", a, b);
|
115 |
-
a.redo();
|
116 |
-
eqAll("abx\ncd\nefz", a, b);
|
117 |
-
a.redo();
|
118 |
-
eqAll("abxq\ncd\nefz", a, b);
|
119 |
-
a.undo(); a.undo(); a.undo(); a.undo();
|
120 |
-
eqAll("ab\ncd\nef", a, b);
|
121 |
-
b.redo();
|
122 |
-
eqAll("ab\ncdy\nef", a, b);
|
123 |
-
});
|
124 |
-
|
125 |
-
testDoc("undoConflict", "A='ab\ncd\nef' B<~A", function(a, b) {
|
126 |
-
a.replaceRange("x", Pos(0));
|
127 |
-
a.replaceRange("z", Pos(2));
|
128 |
-
// This should clear the first undo event in a, but not the second
|
129 |
-
b.replaceRange("y", Pos(0));
|
130 |
-
a.undo(); a.undo();
|
131 |
-
eqAll("abxy\ncd\nef", a, b);
|
132 |
-
a.replaceRange("u", Pos(2));
|
133 |
-
a.replaceRange("v", Pos(0));
|
134 |
-
// This should clear both events in a
|
135 |
-
b.replaceRange("w", Pos(0));
|
136 |
-
a.undo(); a.undo();
|
137 |
-
eqAll("abxyvw\ncd\nefu", a, b);
|
138 |
-
});
|
139 |
-
|
140 |
-
testDoc("doubleRebase", "A='ab\ncd\nef\ng' B<~A C<B", function(a, b, c) {
|
141 |
-
c.replaceRange("u", Pos(3));
|
142 |
-
a.replaceRange("", Pos(0, 0), Pos(1, 0));
|
143 |
-
c.undo();
|
144 |
-
eqAll("cd\nef\ng", a, b, c);
|
145 |
-
});
|
146 |
-
|
147 |
-
testDoc("undoUpdate", "A='ab\ncd\nef' B<~A", function(a, b) {
|
148 |
-
a.replaceRange("x", Pos(2));
|
149 |
-
b.replaceRange("u\nv\nw\n", Pos(0, 0));
|
150 |
-
a.undo();
|
151 |
-
eqAll("u\nv\nw\nab\ncd\nef", a, b);
|
152 |
-
a.redo();
|
153 |
-
eqAll("u\nv\nw\nab\ncd\nefx", a, b);
|
154 |
-
a.undo();
|
155 |
-
eqAll("u\nv\nw\nab\ncd\nef", a, b);
|
156 |
-
b.undo();
|
157 |
-
a.redo();
|
158 |
-
eqAll("ab\ncd\nefx", a, b);
|
159 |
-
a.undo();
|
160 |
-
eqAll("ab\ncd\nef", a, b);
|
161 |
-
});
|
162 |
-
|
163 |
-
testDoc("undoKeepRanges", "A='abcdefg' B<A", function(a, b) {
|
164 |
-
var m = a.markText(Pos(0, 1), Pos(0, 3), {className: "foo"});
|
165 |
-
b.replaceRange("x", Pos(0, 0));
|
166 |
-
eqPos(m.find().from, Pos(0, 2));
|
167 |
-
b.replaceRange("yzzy", Pos(0, 1), Pos(0));
|
168 |
-
eq(m.find(), null);
|
169 |
-
b.undo();
|
170 |
-
eqPos(m.find().from, Pos(0, 2));
|
171 |
-
b.undo();
|
172 |
-
eqPos(m.find().from, Pos(0, 1));
|
173 |
-
});
|
174 |
-
|
175 |
-
testDoc("longChain", "A='uv' B<A C<B D<C", function(a, b, c, d) {
|
176 |
-
a.replaceSelection("X");
|
177 |
-
eqAll("Xuv", a, b, c, d);
|
178 |
-
d.replaceRange("Y", Pos(0));
|
179 |
-
eqAll("XuvY", a, b, c, d);
|
180 |
-
});
|
181 |
-
|
182 |
-
testDoc("broadCast", "B<A C<A D<A E<A", function(a, b, c, d, e) {
|
183 |
-
b.setValue("uu");
|
184 |
-
eqAll("uu", a, b, c, d, e);
|
185 |
-
a.replaceRange("v", Pos(0, 1));
|
186 |
-
eqAll("uvu", a, b, c, d, e);
|
187 |
-
});
|
188 |
-
|
189 |
-
// A and B share a history, C and D share a separate one
|
190 |
-
testDoc("islands", "A='x\ny\nz' B<A C<~A D<C", function(a, b, c, d) {
|
191 |
-
a.replaceRange("u", Pos(0));
|
192 |
-
d.replaceRange("v", Pos(2));
|
193 |
-
b.undo();
|
194 |
-
eqAll("x\ny\nzv", a, b, c, d);
|
195 |
-
c.undo();
|
196 |
-
eqAll("x\ny\nz", a, b, c, d);
|
197 |
-
a.redo();
|
198 |
-
eqAll("xu\ny\nz", a, b, c, d);
|
199 |
-
d.redo();
|
200 |
-
eqAll("xu\ny\nzv", a, b, c, d);
|
201 |
-
});
|
202 |
-
|
203 |
-
testDoc("unlink", "B<A C<A D<B", function(a, b, c, d) {
|
204 |
-
a.setValue("hi");
|
205 |
-
b.unlinkDoc(a);
|
206 |
-
d.setValue("aye");
|
207 |
-
eqAll("hi", a, c);
|
208 |
-
eqAll("aye", b, d);
|
209 |
-
a.setValue("oo");
|
210 |
-
eqAll("oo", a, c);
|
211 |
-
eqAll("aye", b, d);
|
212 |
-
});
|
213 |
-
|
214 |
-
testDoc("bareDoc", "A*='foo' B*<A C<B", function(a, b, c) {
|
215 |
-
is(a instanceof CodeMirror.Doc);
|
216 |
-
is(b instanceof CodeMirror.Doc);
|
217 |
-
is(c instanceof CodeMirror);
|
218 |
-
eqAll("foo", a, b, c);
|
219 |
-
a.replaceRange("hey", Pos(0, 0), Pos(0));
|
220 |
-
c.replaceRange("!", Pos(0));
|
221 |
-
eqAll("hey!", a, b, c);
|
222 |
-
b.unlinkDoc(a);
|
223 |
-
b.setValue("x");
|
224 |
-
eqAll("x", b, c);
|
225 |
-
eqAll("hey!", a);
|
226 |
-
});
|
227 |
-
|
228 |
-
testDoc("swapDoc", "A='a' B*='b' C<A", function(a, b, c) {
|
229 |
-
var d = a.swapDoc(b);
|
230 |
-
d.setValue("x");
|
231 |
-
eqAll("x", c, d);
|
232 |
-
eqAll("b", a, b);
|
233 |
-
});
|
234 |
-
|
235 |
-
testDoc("docKeepsScroll", "A='x' B*='y'", function(a, b) {
|
236 |
-
addDoc(a, 200, 200);
|
237 |
-
a.scrollIntoView(Pos(199, 200));
|
238 |
-
var c = a.swapDoc(b);
|
239 |
-
a.swapDoc(c);
|
240 |
-
var pos = a.getScrollInfo();
|
241 |
-
is(pos.left > 0, "not at left");
|
242 |
-
is(pos.top > 0, "not at top");
|
243 |
-
});
|
244 |
-
|
245 |
-
testDoc("copyDoc", "A='u'", function(a) {
|
246 |
-
var copy = a.getDoc().copy(true);
|
247 |
-
a.setValue("foo");
|
248 |
-
copy.setValue("bar");
|
249 |
-
var old = a.swapDoc(copy);
|
250 |
-
eq(a.getValue(), "bar");
|
251 |
-
a.undo();
|
252 |
-
eq(a.getValue(), "u");
|
253 |
-
a.swapDoc(old);
|
254 |
-
eq(a.getValue(), "foo");
|
255 |
-
eq(old.historySize().undo, 1);
|
256 |
-
eq(old.copy(false).historySize().undo, 0);
|
257 |
-
});
|
258 |
-
|
259 |
-
testDoc("docKeepsMode", "A='1+1'", function(a) {
|
260 |
-
var other = CodeMirror.Doc("hi", "text/x-markdown");
|
261 |
-
a.setOption("mode", "text/javascript");
|
262 |
-
var old = a.swapDoc(other);
|
263 |
-
eq(a.getOption("mode"), "text/x-markdown");
|
264 |
-
eq(a.getMode().name, "markdown");
|
265 |
-
a.swapDoc(old);
|
266 |
-
eq(a.getOption("mode"), "text/javascript");
|
267 |
-
eq(a.getMode().name, "javascript");
|
268 |
-
});
|
269 |
-
|
270 |
-
testDoc("subview", "A='1\n2\n3\n4\n5' B<~A/1-3", function(a, b) {
|
271 |
-
eq(b.getValue(), "2\n3");
|
272 |
-
eq(b.firstLine(), 1);
|
273 |
-
b.setCursor(Pos(4));
|
274 |
-
eqPos(b.getCursor(), Pos(2, 1));
|
275 |
-
a.replaceRange("-1\n0\n", Pos(0, 0));
|
276 |
-
eq(b.firstLine(), 3);
|
277 |
-
eqPos(b.getCursor(), Pos(4, 1));
|
278 |
-
a.undo();
|
279 |
-
eqPos(b.getCursor(), Pos(2, 1));
|
280 |
-
b.replaceRange("oyoy\n", Pos(2, 0));
|
281 |
-
eq(a.getValue(), "1\n2\noyoy\n3\n4\n5");
|
282 |
-
b.undo();
|
283 |
-
eq(a.getValue(), "1\n2\n3\n4\n5");
|
284 |
-
});
|
285 |
-
|
286 |
-
testDoc("subviewEditOnBoundary", "A='11\n22\n33\n44\n55' B<~A/1-4", function(a, b) {
|
287 |
-
a.replaceRange("x\nyy\nz", Pos(0, 1), Pos(2, 1));
|
288 |
-
eq(b.firstLine(), 2);
|
289 |
-
eq(b.lineCount(), 2);
|
290 |
-
eq(b.getValue(), "z3\n44");
|
291 |
-
a.replaceRange("q\nrr\ns", Pos(3, 1), Pos(4, 1));
|
292 |
-
eq(b.firstLine(), 2);
|
293 |
-
eq(b.getValue(), "z3\n4q");
|
294 |
-
eq(a.getValue(), "1x\nyy\nz3\n4q\nrr\ns5");
|
295 |
-
a.execCommand("selectAll");
|
296 |
-
a.replaceSelection("!");
|
297 |
-
eqAll("!", a, b);
|
298 |
-
});
|
299 |
-
|
300 |
-
|
301 |
-
testDoc("sharedMarker", "A='ab\ncd\nef\ngh' B<A C<~A/1-2", function(a, b, c) {
|
302 |
-
var mark = b.markText(Pos(0, 1), Pos(3, 1),
|
303 |
-
{className: "cm-searching", shared: true});
|
304 |
-
var found = a.findMarksAt(Pos(0, 2));
|
305 |
-
eq(found.length, 1);
|
306 |
-
eq(found[0], mark);
|
307 |
-
eq(c.findMarksAt(Pos(1, 1)).length, 1);
|
308 |
-
eqPos(mark.find().from, Pos(0, 1));
|
309 |
-
eqPos(mark.find().to, Pos(3, 1));
|
310 |
-
b.replaceRange("x\ny\n", Pos(0, 0));
|
311 |
-
eqPos(mark.find().from, Pos(2, 1));
|
312 |
-
eqPos(mark.find().to, Pos(5, 1));
|
313 |
-
var cleared = 0;
|
314 |
-
CodeMirror.on(mark, "clear", function() {++cleared;});
|
315 |
-
b.operation(function(){mark.clear();});
|
316 |
-
eq(a.findMarksAt(Pos(3, 1)).length, 0);
|
317 |
-
eq(b.findMarksAt(Pos(3, 1)).length, 0);
|
318 |
-
eq(c.findMarksAt(Pos(3, 1)).length, 0);
|
319 |
-
eq(mark.find(), null);
|
320 |
-
eq(cleared, 1);
|
321 |
-
});
|
322 |
-
|
323 |
-
testDoc("sharedMarkerCopy", "A='abcde'", function(a) {
|
324 |
-
var shared = a.markText(Pos(0, 1), Pos(0, 3), {shared: true});
|
325 |
-
var b = a.linkedDoc();
|
326 |
-
var found = b.findMarksAt(Pos(0, 2));
|
327 |
-
eq(found.length, 1);
|
328 |
-
eq(found[0], shared);
|
329 |
-
shared.clear();
|
330 |
-
eq(b.findMarksAt(Pos(0, 2)), 0);
|
331 |
-
});
|
332 |
-
|
333 |
-
testDoc("sharedMarkerDetach", "A='abcde' B<A C<B", function(a, b, c) {
|
334 |
-
var shared = a.markText(Pos(0, 1), Pos(0, 3), {shared: true});
|
335 |
-
a.unlinkDoc(b);
|
336 |
-
var inB = b.findMarksAt(Pos(0, 2));
|
337 |
-
eq(inB.length, 1);
|
338 |
-
is(inB[0] != shared);
|
339 |
-
var inC = c.findMarksAt(Pos(0, 2));
|
340 |
-
eq(inC.length, 1);
|
341 |
-
is(inC[0] != shared);
|
342 |
-
inC[0].clear();
|
343 |
-
is(shared.find());
|
344 |
-
});
|
345 |
-
|
346 |
-
testDoc("sharedBookmark", "A='ab\ncd\nef\ngh' B<A C<~A/1-2", function(a, b, c) {
|
347 |
-
var mark = b.setBookmark(Pos(1, 1), {shared: true});
|
348 |
-
var found = a.findMarksAt(Pos(1, 1));
|
349 |
-
eq(found.length, 1);
|
350 |
-
eq(found[0], mark);
|
351 |
-
eq(c.findMarksAt(Pos(1, 1)).length, 1);
|
352 |
-
eqPos(mark.find(), Pos(1, 1));
|
353 |
-
b.replaceRange("x\ny\n", Pos(0, 0));
|
354 |
-
eqPos(mark.find(), Pos(3, 1));
|
355 |
-
var cleared = 0;
|
356 |
-
CodeMirror.on(mark, "clear", function() {++cleared;});
|
357 |
-
b.operation(function() {mark.clear();});
|
358 |
-
eq(a.findMarks(Pos(0, 0), Pos(5)).length, 0);
|
359 |
-
eq(b.findMarks(Pos(0, 0), Pos(5)).length, 0);
|
360 |
-
eq(c.findMarks(Pos(0, 0), Pos(5)).length, 0);
|
361 |
-
eq(mark.find(), null);
|
362 |
-
eq(cleared, 1);
|
363 |
-
});
|
364 |
-
|
365 |
-
testDoc("undoInSubview", "A='line 0\nline 1\nline 2\nline 3\nline 4' B<A/1-4", function(a, b) {
|
366 |
-
b.replaceRange("x", Pos(2, 0));
|
367 |
-
a.undo();
|
368 |
-
eq(a.getValue(), "line 0\nline 1\nline 2\nline 3\nline 4");
|
369 |
-
eq(b.getValue(), "line 1\nline 2\nline 3");
|
370 |
-
});
|
371 |
-
})();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/driver.js
DELETED
@@ -1,138 +0,0 @@
|
|
1 |
-
var tests = [], filters = [], allNames = [];
|
2 |
-
|
3 |
-
function Failure(why) {this.message = why;}
|
4 |
-
Failure.prototype.toString = function() { return this.message; };
|
5 |
-
|
6 |
-
function indexOf(collection, elt) {
|
7 |
-
if (collection.indexOf) return collection.indexOf(elt);
|
8 |
-
for (var i = 0, e = collection.length; i < e; ++i)
|
9 |
-
if (collection[i] == elt) return i;
|
10 |
-
return -1;
|
11 |
-
}
|
12 |
-
|
13 |
-
function test(name, run, expectedFail) {
|
14 |
-
// Force unique names
|
15 |
-
var originalName = name;
|
16 |
-
var i = 2; // Second function would be NAME_2
|
17 |
-
while (indexOf(allNames, name) !== -1){
|
18 |
-
name = originalName + "_" + i;
|
19 |
-
i++;
|
20 |
-
}
|
21 |
-
allNames.push(name);
|
22 |
-
// Add test
|
23 |
-
tests.push({name: name, func: run, expectedFail: expectedFail});
|
24 |
-
return name;
|
25 |
-
}
|
26 |
-
var namespace = "";
|
27 |
-
function testCM(name, run, opts, expectedFail) {
|
28 |
-
return test(namespace + name, function() {
|
29 |
-
var place = document.getElementById("testground"), cm = window.cm = CodeMirror(place, opts);
|
30 |
-
var successful = false;
|
31 |
-
try {
|
32 |
-
run(cm);
|
33 |
-
successful = true;
|
34 |
-
} finally {
|
35 |
-
if (!successful || verbose) {
|
36 |
-
place.style.visibility = "visible";
|
37 |
-
} else {
|
38 |
-
place.removeChild(cm.getWrapperElement());
|
39 |
-
}
|
40 |
-
}
|
41 |
-
}, expectedFail);
|
42 |
-
}
|
43 |
-
|
44 |
-
function runTests(callback) {
|
45 |
-
var totalTime = 0;
|
46 |
-
function step(i) {
|
47 |
-
for (;;) {
|
48 |
-
if (i === tests.length) {
|
49 |
-
running = false;
|
50 |
-
return callback("done");
|
51 |
-
}
|
52 |
-
var test = tests[i], skip = false;
|
53 |
-
if (filters.length) {
|
54 |
-
skip = true;
|
55 |
-
for (var j = 0; j < filters.length; j++)
|
56 |
-
if (test.name.match(filters[j])) skip = false;
|
57 |
-
}
|
58 |
-
if (skip) {
|
59 |
-
callback("skipped", test.name, message);
|
60 |
-
i++;
|
61 |
-
} else {
|
62 |
-
break;
|
63 |
-
}
|
64 |
-
}
|
65 |
-
var expFail = test.expectedFail, startTime = +new Date, threw = false;
|
66 |
-
try {
|
67 |
-
var message = test.func();
|
68 |
-
} catch(e) {
|
69 |
-
threw = true;
|
70 |
-
if (expFail) callback("expected", test.name);
|
71 |
-
else if (e instanceof Failure) callback("fail", test.name, e.message);
|
72 |
-
else {
|
73 |
-
var pos = /(?:\bat |@).*?([^\/:]+):(\d+)/.exec(e.stack);
|
74 |
-
if (pos) console["log"](e.stack);
|
75 |
-
callback("error", test.name, e.toString() + (pos ? " (" + pos[1] + ":" + pos[2] + ")" : ""));
|
76 |
-
}
|
77 |
-
}
|
78 |
-
if (!threw) {
|
79 |
-
if (expFail) callback("fail", test.name, message || "expected failure, but succeeded");
|
80 |
-
else callback("ok", test.name, message);
|
81 |
-
}
|
82 |
-
if (!quit) { // Run next test
|
83 |
-
var delay = 0;
|
84 |
-
totalTime += (+new Date) - startTime;
|
85 |
-
if (totalTime > 500){
|
86 |
-
totalTime = 0;
|
87 |
-
delay = 50;
|
88 |
-
}
|
89 |
-
setTimeout(function(){step(i + 1);}, delay);
|
90 |
-
} else { // Quit tests
|
91 |
-
running = false;
|
92 |
-
return null;
|
93 |
-
}
|
94 |
-
}
|
95 |
-
step(0);
|
96 |
-
}
|
97 |
-
|
98 |
-
function label(str, msg) {
|
99 |
-
if (msg) return str + " (" + msg + ")";
|
100 |
-
return str;
|
101 |
-
}
|
102 |
-
function eq(a, b, msg) {
|
103 |
-
if (a != b) throw new Failure(label(a + " != " + b, msg));
|
104 |
-
}
|
105 |
-
function near(a, b, margin, msg) {
|
106 |
-
if (Math.abs(a - b) > margin)
|
107 |
-
throw new Failure(label(a + " is not close to " + b + " (" + margin + ")", msg));
|
108 |
-
}
|
109 |
-
function eqPos(a, b, msg) {
|
110 |
-
function str(p) { return "{line:" + p.line + ",ch:" + p.ch + "}"; }
|
111 |
-
if (a == b) return;
|
112 |
-
if (a == null) throw new Failure(label("comparing null to " + str(b), msg));
|
113 |
-
if (b == null) throw new Failure(label("comparing " + str(a) + " to null", msg));
|
114 |
-
if (a.line != b.line || a.ch != b.ch) throw new Failure(label(str(a) + " != " + str(b), msg));
|
115 |
-
}
|
116 |
-
function is(a, msg) {
|
117 |
-
if (!a) throw new Failure(label("assertion failed", msg));
|
118 |
-
}
|
119 |
-
|
120 |
-
function countTests() {
|
121 |
-
if (!filters.length) return tests.length;
|
122 |
-
var sum = 0;
|
123 |
-
for (var i = 0; i < tests.length; ++i) {
|
124 |
-
var name = tests[i].name;
|
125 |
-
for (var j = 0; j < filters.length; j++) {
|
126 |
-
if (name.match(filters[j])) {
|
127 |
-
++sum;
|
128 |
-
break;
|
129 |
-
}
|
130 |
-
}
|
131 |
-
}
|
132 |
-
return sum;
|
133 |
-
}
|
134 |
-
|
135 |
-
function parseTestFilter(s) {
|
136 |
-
if (/_\*$/.test(s)) return new RegExp("^" + s.slice(0, s.length - 2), "i");
|
137 |
-
else return new RegExp(s, "i");
|
138 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/emacs_test.js
DELETED
@@ -1,147 +0,0 @@
|
|
1 |
-
(function() {
|
2 |
-
"use strict";
|
3 |
-
|
4 |
-
var Pos = CodeMirror.Pos;
|
5 |
-
namespace = "emacs_";
|
6 |
-
|
7 |
-
var eventCache = {};
|
8 |
-
function fakeEvent(keyName) {
|
9 |
-
var event = eventCache[key];
|
10 |
-
if (event) return event;
|
11 |
-
|
12 |
-
var ctrl, shift, alt;
|
13 |
-
var key = keyName.replace(/\w+-/g, function(type) {
|
14 |
-
if (type == "Ctrl-") ctrl = true;
|
15 |
-
else if (type == "Alt-") alt = true;
|
16 |
-
else if (type == "Shift-") shift = true;
|
17 |
-
return "";
|
18 |
-
});
|
19 |
-
var code;
|
20 |
-
for (var c in CodeMirror.keyNames)
|
21 |
-
if (CodeMirror.keyNames[c] == key) { code = c; break; }
|
22 |
-
if (c == null) throw new Error("Unknown key: " + key);
|
23 |
-
|
24 |
-
return eventCache[keyName] = {
|
25 |
-
type: "keydown", keyCode: code, ctrlKey: ctrl, shiftKey: shift, altKey: alt,
|
26 |
-
preventDefault: function(){}, stopPropagation: function(){}
|
27 |
-
};
|
28 |
-
}
|
29 |
-
|
30 |
-
function sim(name, start /*, actions... */) {
|
31 |
-
var keys = Array.prototype.slice.call(arguments, 2);
|
32 |
-
testCM(name, function(cm) {
|
33 |
-
for (var i = 0; i < keys.length; ++i) {
|
34 |
-
var cur = keys[i];
|
35 |
-
if (cur instanceof Pos) cm.setCursor(cur);
|
36 |
-
else if (cur.call) cur(cm);
|
37 |
-
else cm.triggerOnKeyDown(fakeEvent(cur));
|
38 |
-
}
|
39 |
-
}, {keyMap: "emacs", value: start, mode: "javascript"});
|
40 |
-
}
|
41 |
-
|
42 |
-
function at(line, ch) { return function(cm) { eqPos(cm.getCursor(), Pos(line, ch)); }; }
|
43 |
-
function txt(str) { return function(cm) { eq(cm.getValue(), str); }; }
|
44 |
-
|
45 |
-
sim("motionHSimple", "abc", "Ctrl-F", "Ctrl-F", "Ctrl-B", at(0, 1));
|
46 |
-
sim("motionHMulti", "abcde",
|
47 |
-
"Ctrl-4", "Ctrl-F", at(0, 4), "Ctrl--", "Ctrl-2", "Ctrl-F", at(0, 2),
|
48 |
-
"Ctrl-5", "Ctrl-B", at(0, 0));
|
49 |
-
|
50 |
-
sim("motionHWord", "abc. def ghi",
|
51 |
-
"Alt-F", at(0, 3), "Alt-F", at(0, 8),
|
52 |
-
"Ctrl-B", "Alt-B", at(0, 5), "Alt-B", at(0, 0));
|
53 |
-
sim("motionHWordMulti", "abc. def ghi ",
|
54 |
-
"Ctrl-3", "Alt-F", at(0, 12), "Ctrl-2", "Alt-B", at(0, 5),
|
55 |
-
"Ctrl--", "Alt-B", at(0, 8));
|
56 |
-
|
57 |
-
sim("motionVSimple", "a\nb\nc\n", "Ctrl-N", "Ctrl-N", "Ctrl-P", at(1, 0));
|
58 |
-
sim("motionVMulti", "a\nb\nc\nd\ne\n",
|
59 |
-
"Ctrl-2", "Ctrl-N", at(2, 0), "Ctrl-F", "Ctrl--", "Ctrl-N", at(1, 1),
|
60 |
-
"Ctrl--", "Ctrl-3", "Ctrl-P", at(4, 1));
|
61 |
-
|
62 |
-
sim("killYank", "abc\ndef\nghi",
|
63 |
-
"Ctrl-F", "Ctrl-Space", "Ctrl-N", "Ctrl-N", "Ctrl-W", "Ctrl-E", "Ctrl-Y",
|
64 |
-
txt("ahibc\ndef\ng"));
|
65 |
-
sim("killRing", "abcdef",
|
66 |
-
"Ctrl-Space", "Ctrl-F", "Ctrl-W", "Ctrl-Space", "Ctrl-F", "Ctrl-W",
|
67 |
-
"Ctrl-Y", "Alt-Y",
|
68 |
-
txt("acdef"));
|
69 |
-
sim("copyYank", "abcd",
|
70 |
-
"Ctrl-Space", "Ctrl-E", "Alt-W", "Ctrl-Y",
|
71 |
-
txt("abcdabcd"));
|
72 |
-
|
73 |
-
sim("killLineSimple", "foo\nbar", "Ctrl-F", "Ctrl-K", txt("f\nbar"));
|
74 |
-
sim("killLineEmptyLine", "foo\n \nbar", "Ctrl-N", "Ctrl-K", txt("foo\nbar"));
|
75 |
-
sim("killLineMulti", "foo\nbar\nbaz",
|
76 |
-
"Ctrl-F", "Ctrl-F", "Ctrl-K", "Ctrl-K", "Ctrl-K", "Ctrl-A", "Ctrl-Y",
|
77 |
-
txt("o\nbarfo\nbaz"));
|
78 |
-
|
79 |
-
sim("moveByParagraph", "abc\ndef\n\n\nhij\nklm\n\n",
|
80 |
-
"Ctrl-F", "Ctrl-Down", at(2, 0), "Ctrl-Down", at(6, 0),
|
81 |
-
"Ctrl-N", "Ctrl-Up", at(3, 0), "Ctrl-Up", at(0, 0),
|
82 |
-
Pos(1, 2), "Ctrl-Down", at(2, 0), Pos(4, 2), "Ctrl-Up", at(3, 0));
|
83 |
-
sim("moveByParagraphMulti", "abc\n\ndef\n\nhij\n\nklm",
|
84 |
-
"Ctrl-U", "2", "Ctrl-Down", at(3, 0),
|
85 |
-
"Shift-Alt-.", "Ctrl-3", "Ctrl-Up", at(1, 0));
|
86 |
-
|
87 |
-
sim("moveBySentence", "sentence one! sentence\ntwo\n\nparagraph two",
|
88 |
-
"Alt-E", at(0, 13), "Alt-E", at(1, 3), "Ctrl-F", "Alt-A", at(0, 13));
|
89 |
-
|
90 |
-
sim("moveByExpr", "function foo(a, b) {}",
|
91 |
-
"Ctrl-Alt-F", at(0, 8), "Ctrl-Alt-F", at(0, 12), "Ctrl-Alt-F", at(0, 18),
|
92 |
-
"Ctrl-Alt-B", at(0, 12), "Ctrl-Alt-B", at(0, 9));
|
93 |
-
sim("moveByExprMulti", "foo bar baz bug",
|
94 |
-
"Ctrl-2", "Ctrl-Alt-F", at(0, 7),
|
95 |
-
"Ctrl--", "Ctrl-Alt-F", at(0, 4),
|
96 |
-
"Ctrl--", "Ctrl-2", "Ctrl-Alt-B", at(0, 11));
|
97 |
-
sim("delExpr", "var x = [\n a,\n b\n c\n];",
|
98 |
-
Pos(0, 8), "Ctrl-Alt-K", txt("var x = ;"), "Ctrl-/",
|
99 |
-
Pos(4, 1), "Ctrl-Alt-Backspace", txt("var x = ;"));
|
100 |
-
sim("delExprMulti", "foo bar baz",
|
101 |
-
"Ctrl-2", "Ctrl-Alt-K", txt(" baz"),
|
102 |
-
"Ctrl-/", "Ctrl-E", "Ctrl-2", "Ctrl-Alt-Backspace", txt("foo "));
|
103 |
-
|
104 |
-
sim("justOneSpace", "hi bye ",
|
105 |
-
Pos(0, 4), "Alt-Space", txt("hi bye "),
|
106 |
-
Pos(0, 4), "Alt-Space", txt("hi b ye "),
|
107 |
-
"Ctrl-A", "Alt-Space", "Ctrl-E", "Alt-Space", txt(" hi b ye "));
|
108 |
-
|
109 |
-
sim("openLine", "foo bar", "Alt-F", "Ctrl-O", txt("foo\n bar"))
|
110 |
-
|
111 |
-
sim("transposeChar", "abcd\ne",
|
112 |
-
"Ctrl-F", "Ctrl-T", "Ctrl-T", txt("bcad\ne"), at(0, 3),
|
113 |
-
"Ctrl-F", "Ctrl-T", "Ctrl-T", "Ctrl-T", txt("bcda\ne"), at(0, 4),
|
114 |
-
"Ctrl-F", "Ctrl-T", txt("bcde\na"), at(1, 0));
|
115 |
-
|
116 |
-
sim("manipWordCase", "foo BAR bAZ",
|
117 |
-
"Alt-C", "Alt-L", "Alt-U", txt("Foo bar BAZ"),
|
118 |
-
"Ctrl-A", "Alt-U", "Alt-L", "Alt-C", txt("FOO bar Baz"));
|
119 |
-
sim("manipWordCaseMulti", "foo Bar bAz",
|
120 |
-
"Ctrl-2", "Alt-U", txt("FOO BAR bAz"),
|
121 |
-
"Ctrl-A", "Ctrl-3", "Alt-C", txt("Foo Bar Baz"));
|
122 |
-
|
123 |
-
sim("upExpr", "foo {\n bar[];\n baz(blah);\n}",
|
124 |
-
Pos(2, 7), "Ctrl-Alt-U", at(2, 5), "Ctrl-Alt-U", at(0, 4));
|
125 |
-
sim("transposeExpr", "do foo[bar] dah",
|
126 |
-
Pos(0, 6), "Ctrl-Alt-T", txt("do [bar]foo dah"));
|
127 |
-
|
128 |
-
sim("clearMark", "abcde", Pos(0, 2), "Ctrl-Space", "Ctrl-F", "Ctrl-F",
|
129 |
-
"Ctrl-G", "Ctrl-W", txt("abcde"));
|
130 |
-
|
131 |
-
sim("delRegion", "abcde", "Ctrl-Space", "Ctrl-F", "Ctrl-F", "Delete", txt("cde"));
|
132 |
-
sim("backspaceRegion", "abcde", "Ctrl-Space", "Ctrl-F", "Ctrl-F", "Backspace", txt("cde"));
|
133 |
-
|
134 |
-
testCM("save", function(cm) {
|
135 |
-
var saved = false;
|
136 |
-
CodeMirror.commands.save = function(cm) { saved = cm.getValue(); };
|
137 |
-
cm.triggerOnKeyDown(fakeEvent("Ctrl-X"));
|
138 |
-
cm.triggerOnKeyDown(fakeEvent("Ctrl-S"));
|
139 |
-
is(saved, "hi");
|
140 |
-
}, {value: "hi", keyMap: "emacs"});
|
141 |
-
|
142 |
-
testCM("gotoInvalidLineFloat", function(cm) {
|
143 |
-
cm.openDialog = function(_, cb) { cb("2.2"); };
|
144 |
-
cm.triggerOnKeyDown(fakeEvent("Alt-G"));
|
145 |
-
cm.triggerOnKeyDown(fakeEvent("G"));
|
146 |
-
}, {value: "1\n2\n3\n4", keyMap: "emacs"});
|
147 |
-
})();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/index.html
DELETED
@@ -1,241 +0,0 @@
|
|
1 |
-
<!doctype html>
|
2 |
-
|
3 |
-
<meta charset="utf-8"/>
|
4 |
-
<title>CodeMirror: Test Suite</title>
|
5 |
-
<link rel=stylesheet href="../doc/docs.css">
|
6 |
-
|
7 |
-
<link rel="stylesheet" href="../lib/codemirror.css">
|
8 |
-
<link rel="stylesheet" href="mode_test.css">
|
9 |
-
<script src="../doc/activebookmark.js"></script>
|
10 |
-
<script src="../lib/codemirror.js"></script>
|
11 |
-
<script src="../addon/mode/overlay.js"></script>
|
12 |
-
<script src="../addon/mode/multiplex.js"></script>
|
13 |
-
<script src="../addon/search/searchcursor.js"></script>
|
14 |
-
<script src="../addon/dialog/dialog.js"></script>
|
15 |
-
<script src="../addon/edit/matchbrackets.js"></script>
|
16 |
-
<script src="../addon/hint/sql-hint.js"></script>
|
17 |
-
<script src="../addon/comment/comment.js"></script>
|
18 |
-
<script src="../mode/css/css.js"></script>
|
19 |
-
<script src="../mode/clike/clike.js"></script>
|
20 |
-
<!-- clike must be after css or vim and sublime tests will fail -->
|
21 |
-
<script src="../mode/gfm/gfm.js"></script>
|
22 |
-
<script src="../mode/haml/haml.js"></script>
|
23 |
-
<script src="../mode/htmlmixed/htmlmixed.js"></script>
|
24 |
-
<script src="../mode/javascript/javascript.js"></script>
|
25 |
-
<script src="../mode/markdown/markdown.js"></script>
|
26 |
-
<script src="../mode/php/php.js"></script>
|
27 |
-
<script src="../mode/ruby/ruby.js"></script>
|
28 |
-
<script src="../mode/shell/shell.js"></script>
|
29 |
-
<script src="../mode/slim/slim.js"></script>
|
30 |
-
<script src="../mode/sql/sql.js"></script>
|
31 |
-
<script src="../mode/stex/stex.js"></script>
|
32 |
-
<script src="../mode/textile/textile.js"></script>
|
33 |
-
<script src="../mode/verilog/verilog.js"></script>
|
34 |
-
<script src="../mode/xml/xml.js"></script>
|
35 |
-
<script src="../mode/xquery/xquery.js"></script>
|
36 |
-
<script src="../keymap/emacs.js"></script>
|
37 |
-
<script src="../keymap/sublime.js"></script>
|
38 |
-
<script src="../keymap/vim.js"></script>
|
39 |
-
|
40 |
-
<style type="text/css">
|
41 |
-
.ok {color: #090;}
|
42 |
-
.fail {color: #e00;}
|
43 |
-
.error {color: #c90;}
|
44 |
-
.done {font-weight: bold;}
|
45 |
-
#progress {
|
46 |
-
background: #45d;
|
47 |
-
color: white;
|
48 |
-
text-shadow: 0 0 1px #45d, 0 0 2px #45d, 0 0 3px #45d;
|
49 |
-
font-weight: bold;
|
50 |
-
white-space: pre;
|
51 |
-
}
|
52 |
-
#testground {
|
53 |
-
visibility: hidden;
|
54 |
-
}
|
55 |
-
#testground.offscreen {
|
56 |
-
visibility: visible;
|
57 |
-
position: absolute;
|
58 |
-
left: -10000px;
|
59 |
-
top: -10000px;
|
60 |
-
}
|
61 |
-
.CodeMirror { border: 1px solid black; }
|
62 |
-
</style>
|
63 |
-
|
64 |
-
<div id=nav>
|
65 |
-
<a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo src="../doc/logo.png"></a>
|
66 |
-
|
67 |
-
<ul>
|
68 |
-
<li><a href="../index.html">Home</a>
|
69 |
-
<li><a href="../doc/manual.html">Manual</a>
|
70 |
-
<li><a href="https://github.com/codemirror/codemirror">Code</a>
|
71 |
-
</ul>
|
72 |
-
<ul>
|
73 |
-
<li><a class=active href="#">Test suite</a>
|
74 |
-
</ul>
|
75 |
-
</div>
|
76 |
-
|
77 |
-
<article>
|
78 |
-
<h2>Test Suite</h2>
|
79 |
-
|
80 |
-
<p>A limited set of programmatic sanity tests for CodeMirror.</p>
|
81 |
-
|
82 |
-
<div style="border: 1px solid black; padding: 1px; max-width: 700px;">
|
83 |
-
<div style="width: 0px;" id=progress><div style="padding: 3px;">Ran <span id="progress_ran">0</span><span id="progress_total"> of 0</span> tests</div></div>
|
84 |
-
</div>
|
85 |
-
<p id=status>Please enable JavaScript...</p>
|
86 |
-
<div id=output></div>
|
87 |
-
|
88 |
-
<div id=testground></div>
|
89 |
-
|
90 |
-
<script src="driver.js"></script>
|
91 |
-
<script src="test.js"></script>
|
92 |
-
<script src="doc_test.js"></script>
|
93 |
-
<script src="multi_test.js"></script>
|
94 |
-
<script src="scroll_test.js"></script>
|
95 |
-
<script src="comment_test.js"></script>
|
96 |
-
<script src="search_test.js"></script>
|
97 |
-
<script src="mode_test.js"></script>
|
98 |
-
|
99 |
-
<script src="../mode/clike/test.js"></script>
|
100 |
-
<script src="../mode/css/test.js"></script>
|
101 |
-
<script src="../mode/css/scss_test.js"></script>
|
102 |
-
<script src="../mode/css/less_test.js"></script>
|
103 |
-
<script src="../mode/gfm/test.js"></script>
|
104 |
-
<script src="../mode/haml/test.js"></script>
|
105 |
-
<script src="../mode/javascript/test.js"></script>
|
106 |
-
<script src="../mode/markdown/test.js"></script>
|
107 |
-
<script src="../mode/php/test.js"></script>
|
108 |
-
<script src="../mode/ruby/test.js"></script>
|
109 |
-
<script src="../mode/shell/test.js"></script>
|
110 |
-
<script src="../mode/slim/test.js"></script>
|
111 |
-
<script src="../mode/stex/test.js"></script>
|
112 |
-
<script src="../mode/textile/test.js"></script>
|
113 |
-
<script src="../mode/verilog/test.js"></script>
|
114 |
-
<script src="../mode/xml/test.js"></script>
|
115 |
-
<script src="../mode/xquery/test.js"></script>
|
116 |
-
<script src="../addon/mode/multiplex_test.js"></script>
|
117 |
-
<script src="emacs_test.js"></script>
|
118 |
-
<script src="sql-hint-test.js"></script>
|
119 |
-
<script src="sublime_test.js"></script>
|
120 |
-
<script src="vim_test.js"></script>
|
121 |
-
<script>
|
122 |
-
window.onload = runHarness;
|
123 |
-
CodeMirror.on(window, 'hashchange', runHarness);
|
124 |
-
|
125 |
-
function esc(str) {
|
126 |
-
return str.replace(/[<&]/, function(ch) { return ch == "<" ? "<" : "&"; });
|
127 |
-
}
|
128 |
-
|
129 |
-
var output = document.getElementById("output"),
|
130 |
-
progress = document.getElementById("progress"),
|
131 |
-
progressRan = document.getElementById("progress_ran").childNodes[0],
|
132 |
-
progressTotal = document.getElementById("progress_total").childNodes[0];
|
133 |
-
var count = 0,
|
134 |
-
failed = 0,
|
135 |
-
skipped = 0,
|
136 |
-
bad = "",
|
137 |
-
running = false, // Flag that states tests are running
|
138 |
-
quit = false, // Flag to quit tests ASAP
|
139 |
-
verbose = false; // Adds message for *every* test to output
|
140 |
-
|
141 |
-
function runHarness(){
|
142 |
-
if (running) {
|
143 |
-
quit = true;
|
144 |
-
setStatus("Restarting tests...", '', true);
|
145 |
-
setTimeout(function(){runHarness();}, 500);
|
146 |
-
return;
|
147 |
-
}
|
148 |
-
filters = [];
|
149 |
-
verbose = false;
|
150 |
-
if (window.location.hash.substr(1)){
|
151 |
-
var strings = window.location.hash.substr(1).split(",");
|
152 |
-
while (strings.length) {
|
153 |
-
var s = strings.shift();
|
154 |
-
if (s === "verbose")
|
155 |
-
verbose = true;
|
156 |
-
else
|
157 |
-
filters.push(parseTestFilter(decodeURIComponent(s)));
|
158 |
-
}
|
159 |
-
}
|
160 |
-
quit = false;
|
161 |
-
running = true;
|
162 |
-
setStatus("Loading tests...");
|
163 |
-
count = 0;
|
164 |
-
failed = 0;
|
165 |
-
skipped = 0;
|
166 |
-
bad = "";
|
167 |
-
totalTests = countTests();
|
168 |
-
progressTotal.nodeValue = " of " + totalTests;
|
169 |
-
progressRan.nodeValue = count;
|
170 |
-
output.innerHTML = '';
|
171 |
-
document.getElementById("testground").innerHTML = "<form>" +
|
172 |
-
"<textarea id=\"code\" name=\"code\"></textarea>" +
|
173 |
-
"<input type=submit value=ok name=submit>" +
|
174 |
-
"</form>";
|
175 |
-
runTests(displayTest);
|
176 |
-
}
|
177 |
-
|
178 |
-
function setStatus(message, className, force){
|
179 |
-
if (quit && !force) return;
|
180 |
-
if (!message) throw("must provide message");
|
181 |
-
var status = document.getElementById("status").childNodes[0];
|
182 |
-
status.nodeValue = message;
|
183 |
-
status.parentNode.className = className;
|
184 |
-
}
|
185 |
-
function addOutput(name, className, code){
|
186 |
-
var newOutput = document.createElement("dl");
|
187 |
-
var newTitle = document.createElement("dt");
|
188 |
-
newTitle.className = className;
|
189 |
-
newTitle.appendChild(document.createTextNode(name));
|
190 |
-
newOutput.appendChild(newTitle);
|
191 |
-
var newMessage = document.createElement("dd");
|
192 |
-
newMessage.innerHTML = code;
|
193 |
-
newOutput.appendChild(newTitle);
|
194 |
-
newOutput.appendChild(newMessage);
|
195 |
-
output.appendChild(newOutput);
|
196 |
-
}
|
197 |
-
function displayTest(type, name, customMessage) {
|
198 |
-
var message = "???";
|
199 |
-
if (type != "done" && type != "skipped") ++count;
|
200 |
-
progress.style.width = (count * (progress.parentNode.clientWidth - 2) / totalTests) + "px";
|
201 |
-
progressRan.nodeValue = count;
|
202 |
-
if (type == "ok") {
|
203 |
-
message = "Test '" + name + "' succeeded";
|
204 |
-
if (!verbose) customMessage = false;
|
205 |
-
} else if (type == "skipped") {
|
206 |
-
message = "Test '" + name + "' skipped";
|
207 |
-
++skipped;
|
208 |
-
if (!verbose) customMessage = false;
|
209 |
-
} else if (type == "expected") {
|
210 |
-
message = "Test '" + name + "' failed as expected";
|
211 |
-
if (!verbose) customMessage = false;
|
212 |
-
} else if (type == "error" || type == "fail") {
|
213 |
-
++failed;
|
214 |
-
message = "Test '" + name + "' failed";
|
215 |
-
} else if (type == "done") {
|
216 |
-
if (failed) {
|
217 |
-
type += " fail";
|
218 |
-
message = failed + " failure" + (failed > 1 ? "s" : "");
|
219 |
-
} else if (count < totalTests) {
|
220 |
-
failed = totalTests - count;
|
221 |
-
type += " fail";
|
222 |
-
message = failed + " failure" + (failed > 1 ? "s" : "");
|
223 |
-
} else {
|
224 |
-
type += " ok";
|
225 |
-
message = "All passed";
|
226 |
-
if (skipped) {
|
227 |
-
message += " (" + skipped + " skipped)";
|
228 |
-
}
|
229 |
-
}
|
230 |
-
progressTotal.nodeValue = '';
|
231 |
-
customMessage = true; // Hack to avoid adding to output
|
232 |
-
}
|
233 |
-
if (verbose && !customMessage) customMessage = message;
|
234 |
-
setStatus(message, type);
|
235 |
-
if (customMessage && customMessage.length > 0) {
|
236 |
-
addOutput(name, type, customMessage);
|
237 |
-
}
|
238 |
-
}
|
239 |
-
</script>
|
240 |
-
|
241 |
-
</article>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/lint.js
DELETED
@@ -1,11 +0,0 @@
|
|
1 |
-
var blint = require("blint");
|
2 |
-
|
3 |
-
["mode", "lib", "addon", "keymap"].forEach(function(dir) {
|
4 |
-
blint.checkDir(dir, {
|
5 |
-
browser: true,
|
6 |
-
allowedGlobals: ["CodeMirror", "define", "test", "requirejs"],
|
7 |
-
blob: "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http:\/\/codemirror.net\/LICENSE\n\n"
|
8 |
-
});
|
9 |
-
});
|
10 |
-
|
11 |
-
module.exports = {ok: blint.success()};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/lint/acorn.js
DELETED
@@ -1,1782 +0,0 @@
|
|
1 |
-
// Acorn is a tiny, fast JavaScript parser written in JavaScript.
|
2 |
-
//
|
3 |
-
// Acorn was written by Marijn Haverbeke and released under an MIT
|
4 |
-
// license. The Unicode regexps (for identifiers and whitespace) were
|
5 |
-
// taken from [Esprima](http://esprima.org) by Ariya Hidayat.
|
6 |
-
//
|
7 |
-
// Git repositories for Acorn are available at
|
8 |
-
//
|
9 |
-
// http://marijnhaverbeke.nl/git/acorn
|
10 |
-
// https://github.com/marijnh/acorn.git
|
11 |
-
//
|
12 |
-
// Please use the [github bug tracker][ghbt] to report issues.
|
13 |
-
//
|
14 |
-
// [ghbt]: https://github.com/marijnh/acorn/issues
|
15 |
-
//
|
16 |
-
// This file defines the main parser interface. The library also comes
|
17 |
-
// with a [error-tolerant parser][dammit] and an
|
18 |
-
// [abstract syntax tree walker][walk], defined in other files.
|
19 |
-
//
|
20 |
-
// [dammit]: acorn_loose.js
|
21 |
-
// [walk]: util/walk.js
|
22 |
-
|
23 |
-
(function(root, mod) {
|
24 |
-
if (typeof exports == "object" && typeof module == "object") return mod(exports); // CommonJS
|
25 |
-
if (typeof define == "function" && define.amd) return define(["exports"], mod); // AMD
|
26 |
-
mod(root.acorn || (root.acorn = {})); // Plain browser env
|
27 |
-
})(this, function(exports) {
|
28 |
-
"use strict";
|
29 |
-
|
30 |
-
exports.version = "0.4.1";
|
31 |
-
|
32 |
-
// The main exported interface (under `self.acorn` when in the
|
33 |
-
// browser) is a `parse` function that takes a code string and
|
34 |
-
// returns an abstract syntax tree as specified by [Mozilla parser
|
35 |
-
// API][api], with the caveat that the SpiderMonkey-specific syntax
|
36 |
-
// (`let`, `yield`, inline XML, etc) is not recognized.
|
37 |
-
//
|
38 |
-
// [api]: https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API
|
39 |
-
|
40 |
-
var options, input, inputLen, sourceFile;
|
41 |
-
|
42 |
-
exports.parse = function(inpt, opts) {
|
43 |
-
input = String(inpt); inputLen = input.length;
|
44 |
-
setOptions(opts);
|
45 |
-
initTokenState();
|
46 |
-
return parseTopLevel(options.program);
|
47 |
-
};
|
48 |
-
|
49 |
-
// A second optional argument can be given to further configure
|
50 |
-
// the parser process. These options are recognized:
|
51 |
-
|
52 |
-
var defaultOptions = exports.defaultOptions = {
|
53 |
-
// `ecmaVersion` indicates the ECMAScript version to parse. Must
|
54 |
-
// be either 3 or 5. This
|
55 |
-
// influences support for strict mode, the set of reserved words, and
|
56 |
-
// support for getters and setter.
|
57 |
-
ecmaVersion: 5,
|
58 |
-
// Turn on `strictSemicolons` to prevent the parser from doing
|
59 |
-
// automatic semicolon insertion.
|
60 |
-
strictSemicolons: false,
|
61 |
-
// When `allowTrailingCommas` is false, the parser will not allow
|
62 |
-
// trailing commas in array and object literals.
|
63 |
-
allowTrailingCommas: true,
|
64 |
-
// By default, reserved words are not enforced. Enable
|
65 |
-
// `forbidReserved` to enforce them. When this option has the
|
66 |
-
// value "everywhere", reserved words and keywords can also not be
|
67 |
-
// used as property names.
|
68 |
-
forbidReserved: false,
|
69 |
-
// When enabled, a return at the top level is not considered an
|
70 |
-
// error.
|
71 |
-
allowReturnOutsideFunction: false,
|
72 |
-
// When `locations` is on, `loc` properties holding objects with
|
73 |
-
// `start` and `end` properties in `{line, column}` form (with
|
74 |
-
// line being 1-based and column 0-based) will be attached to the
|
75 |
-
// nodes.
|
76 |
-
locations: false,
|
77 |
-
// A function can be passed as `onComment` option, which will
|
78 |
-
// cause Acorn to call that function with `(block, text, start,
|
79 |
-
// end)` parameters whenever a comment is skipped. `block` is a
|
80 |
-
// boolean indicating whether this is a block (`/* */`) comment,
|
81 |
-
// `text` is the content of the comment, and `start` and `end` are
|
82 |
-
// character offsets that denote the start and end of the comment.
|
83 |
-
// When the `locations` option is on, two more parameters are
|
84 |
-
// passed, the full `{line, column}` locations of the start and
|
85 |
-
// end of the comments. Note that you are not allowed to call the
|
86 |
-
// parser from the callback—that will corrupt its internal state.
|
87 |
-
onComment: null,
|
88 |
-
// Nodes have their start and end characters offsets recorded in
|
89 |
-
// `start` and `end` properties (directly on the node, rather than
|
90 |
-
// the `loc` object, which holds line/column data. To also add a
|
91 |
-
// [semi-standardized][range] `range` property holding a `[start,
|
92 |
-
// end]` array with the same numbers, set the `ranges` option to
|
93 |
-
// `true`.
|
94 |
-
//
|
95 |
-
// [range]: https://bugzilla.mozilla.org/show_bug.cgi?id=745678
|
96 |
-
ranges: false,
|
97 |
-
// It is possible to parse multiple files into a single AST by
|
98 |
-
// passing the tree produced by parsing the first file as
|
99 |
-
// `program` option in subsequent parses. This will add the
|
100 |
-
// toplevel forms of the parsed file to the `Program` (top) node
|
101 |
-
// of an existing parse tree.
|
102 |
-
program: null,
|
103 |
-
// When `locations` is on, you can pass this to record the source
|
104 |
-
// file in every node's `loc` object.
|
105 |
-
sourceFile: null,
|
106 |
-
// This value, if given, is stored in every node, whether
|
107 |
-
// `locations` is on or off.
|
108 |
-
directSourceFile: null
|
109 |
-
};
|
110 |
-
|
111 |
-
function setOptions(opts) {
|
112 |
-
options = opts || {};
|
113 |
-
for (var opt in defaultOptions) if (!Object.prototype.hasOwnProperty.call(options, opt))
|
114 |
-
options[opt] = defaultOptions[opt];
|
115 |
-
sourceFile = options.sourceFile || null;
|
116 |
-
}
|
117 |
-
|
118 |
-
// The `getLineInfo` function is mostly useful when the
|
119 |
-
// `locations` option is off (for performance reasons) and you
|
120 |
-
// want to find the line/column position for a given character
|
121 |
-
// offset. `input` should be the code string that the offset refers
|
122 |
-
// into.
|
123 |
-
|
124 |
-
var getLineInfo = exports.getLineInfo = function(input, offset) {
|
125 |
-
for (var line = 1, cur = 0;;) {
|
126 |
-
lineBreak.lastIndex = cur;
|
127 |
-
var match = lineBreak.exec(input);
|
128 |
-
if (match && match.index < offset) {
|
129 |
-
++line;
|
130 |
-
cur = match.index + match[0].length;
|
131 |
-
} else break;
|
132 |
-
}
|
133 |
-
return {line: line, column: offset - cur};
|
134 |
-
};
|
135 |
-
|
136 |
-
// Acorn is organized as a tokenizer and a recursive-descent parser.
|
137 |
-
// The `tokenize` export provides an interface to the tokenizer.
|
138 |
-
// Because the tokenizer is optimized for being efficiently used by
|
139 |
-
// the Acorn parser itself, this interface is somewhat crude and not
|
140 |
-
// very modular. Performing another parse or call to `tokenize` will
|
141 |
-
// reset the internal state, and invalidate existing tokenizers.
|
142 |
-
|
143 |
-
exports.tokenize = function(inpt, opts) {
|
144 |
-
input = String(inpt); inputLen = input.length;
|
145 |
-
setOptions(opts);
|
146 |
-
initTokenState();
|
147 |
-
|
148 |
-
var t = {};
|
149 |
-
function getToken(forceRegexp) {
|
150 |
-
lastEnd = tokEnd;
|
151 |
-
readToken(forceRegexp);
|
152 |
-
t.start = tokStart; t.end = tokEnd;
|
153 |
-
t.startLoc = tokStartLoc; t.endLoc = tokEndLoc;
|
154 |
-
t.type = tokType; t.value = tokVal;
|
155 |
-
return t;
|
156 |
-
}
|
157 |
-
getToken.jumpTo = function(pos, reAllowed) {
|
158 |
-
tokPos = pos;
|
159 |
-
if (options.locations) {
|
160 |
-
tokCurLine = 1;
|
161 |
-
tokLineStart = lineBreak.lastIndex = 0;
|
162 |
-
var match;
|
163 |
-
while ((match = lineBreak.exec(input)) && match.index < pos) {
|
164 |
-
++tokCurLine;
|
165 |
-
tokLineStart = match.index + match[0].length;
|
166 |
-
}
|
167 |
-
}
|
168 |
-
tokRegexpAllowed = reAllowed;
|
169 |
-
skipSpace();
|
170 |
-
};
|
171 |
-
return getToken;
|
172 |
-
};
|
173 |
-
|
174 |
-
// State is kept in (closure-)global variables. We already saw the
|
175 |
-
// `options`, `input`, and `inputLen` variables above.
|
176 |
-
|
177 |
-
// The current position of the tokenizer in the input.
|
178 |
-
|
179 |
-
var tokPos;
|
180 |
-
|
181 |
-
// The start and end offsets of the current token.
|
182 |
-
|
183 |
-
var tokStart, tokEnd;
|
184 |
-
|
185 |
-
// When `options.locations` is true, these hold objects
|
186 |
-
// containing the tokens start and end line/column pairs.
|
187 |
-
|
188 |
-
var tokStartLoc, tokEndLoc;
|
189 |
-
|
190 |
-
// The type and value of the current token. Token types are objects,
|
191 |
-
// named by variables against which they can be compared, and
|
192 |
-
// holding properties that describe them (indicating, for example,
|
193 |
-
// the precedence of an infix operator, and the original name of a
|
194 |
-
// keyword token). The kind of value that's held in `tokVal` depends
|
195 |
-
// on the type of the token. For literals, it is the literal value,
|
196 |
-
// for operators, the operator name, and so on.
|
197 |
-
|
198 |
-
var tokType, tokVal;
|
199 |
-
|
200 |
-
// Interal state for the tokenizer. To distinguish between division
|
201 |
-
// operators and regular expressions, it remembers whether the last
|
202 |
-
// token was one that is allowed to be followed by an expression.
|
203 |
-
// (If it is, a slash is probably a regexp, if it isn't it's a
|
204 |
-
// division operator. See the `parseStatement` function for a
|
205 |
-
// caveat.)
|
206 |
-
|
207 |
-
var tokRegexpAllowed;
|
208 |
-
|
209 |
-
// When `options.locations` is true, these are used to keep
|
210 |
-
// track of the current line, and know when a new line has been
|
211 |
-
// entered.
|
212 |
-
|
213 |
-
var tokCurLine, tokLineStart;
|
214 |
-
|
215 |
-
// These store the position of the previous token, which is useful
|
216 |
-
// when finishing a node and assigning its `end` position.
|
217 |
-
|
218 |
-
var lastStart, lastEnd, lastEndLoc;
|
219 |
-
|
220 |
-
// This is the parser's state. `inFunction` is used to reject
|
221 |
-
// `return` statements outside of functions, `labels` to verify that
|
222 |
-
// `break` and `continue` have somewhere to jump to, and `strict`
|
223 |
-
// indicates whether strict mode is on.
|
224 |
-
|
225 |
-
var inFunction, labels, strict;
|
226 |
-
|
227 |
-
// This function is used to raise exceptions on parse errors. It
|
228 |
-
// takes an offset integer (into the current `input`) to indicate
|
229 |
-
// the location of the error, attaches the position to the end
|
230 |
-
// of the error message, and then raises a `SyntaxError` with that
|
231 |
-
// message.
|
232 |
-
|
233 |
-
function raise(pos, message) {
|
234 |
-
var loc = getLineInfo(input, pos);
|
235 |
-
message += " (" + loc.line + ":" + loc.column + ")";
|
236 |
-
var err = new SyntaxError(message);
|
237 |
-
err.pos = pos; err.loc = loc; err.raisedAt = tokPos;
|
238 |
-
throw err;
|
239 |
-
}
|
240 |
-
|
241 |
-
// Reused empty array added for node fields that are always empty.
|
242 |
-
|
243 |
-
var empty = [];
|
244 |
-
|
245 |
-
// ## Token types
|
246 |
-
|
247 |
-
// The assignment of fine-grained, information-carrying type objects
|
248 |
-
// allows the tokenizer to store the information it has about a
|
249 |
-
// token in a way that is very cheap for the parser to look up.
|
250 |
-
|
251 |
-
// All token type variables start with an underscore, to make them
|
252 |
-
// easy to recognize.
|
253 |
-
|
254 |
-
// These are the general types. The `type` property is only used to
|
255 |
-
// make them recognizeable when debugging.
|
256 |
-
|
257 |
-
var _num = {type: "num"}, _regexp = {type: "regexp"}, _string = {type: "string"};
|
258 |
-
var _name = {type: "name"}, _eof = {type: "eof"};
|
259 |
-
|
260 |
-
// Keyword tokens. The `keyword` property (also used in keyword-like
|
261 |
-
// operators) indicates that the token originated from an
|
262 |
-
// identifier-like word, which is used when parsing property names.
|
263 |
-
//
|
264 |
-
// The `beforeExpr` property is used to disambiguate between regular
|
265 |
-
// expressions and divisions. It is set on all token types that can
|
266 |
-
// be followed by an expression (thus, a slash after them would be a
|
267 |
-
// regular expression).
|
268 |
-
//
|
269 |
-
// `isLoop` marks a keyword as starting a loop, which is important
|
270 |
-
// to know when parsing a label, in order to allow or disallow
|
271 |
-
// continue jumps to that label.
|
272 |
-
|
273 |
-
var _break = {keyword: "break"}, _case = {keyword: "case", beforeExpr: true}, _catch = {keyword: "catch"};
|
274 |
-
var _continue = {keyword: "continue"}, _debugger = {keyword: "debugger"}, _default = {keyword: "default"};
|
275 |
-
var _do = {keyword: "do", isLoop: true}, _else = {keyword: "else", beforeExpr: true};
|
276 |
-
var _finally = {keyword: "finally"}, _for = {keyword: "for", isLoop: true}, _function = {keyword: "function"};
|
277 |
-
var _if = {keyword: "if"}, _return = {keyword: "return", beforeExpr: true}, _switch = {keyword: "switch"};
|
278 |
-
var _throw = {keyword: "throw", beforeExpr: true}, _try = {keyword: "try"}, _var = {keyword: "var"};
|
279 |
-
var _while = {keyword: "while", isLoop: true}, _with = {keyword: "with"}, _new = {keyword: "new", beforeExpr: true};
|
280 |
-
var _this = {keyword: "this"};
|
281 |
-
|
282 |
-
// The keywords that denote values.
|
283 |
-
|
284 |
-
var _null = {keyword: "null", atomValue: null}, _true = {keyword: "true", atomValue: true};
|
285 |
-
var _false = {keyword: "false", atomValue: false};
|
286 |
-
|
287 |
-
// Some keywords are treated as regular operators. `in` sometimes
|
288 |
-
// (when parsing `for`) needs to be tested against specifically, so
|
289 |
-
// we assign a variable name to it for quick comparing.
|
290 |
-
|
291 |
-
var _in = {keyword: "in", binop: 7, beforeExpr: true};
|
292 |
-
|
293 |
-
// Map keyword names to token types.
|
294 |
-
|
295 |
-
var keywordTypes = {"break": _break, "case": _case, "catch": _catch,
|
296 |
-
"continue": _continue, "debugger": _debugger, "default": _default,
|
297 |
-
"do": _do, "else": _else, "finally": _finally, "for": _for,
|
298 |
-
"function": _function, "if": _if, "return": _return, "switch": _switch,
|
299 |
-
"throw": _throw, "try": _try, "var": _var, "while": _while, "with": _with,
|
300 |
-
"null": _null, "true": _true, "false": _false, "new": _new, "in": _in,
|
301 |
-
"instanceof": {keyword: "instanceof", binop: 7, beforeExpr: true}, "this": _this,
|
302 |
-
"typeof": {keyword: "typeof", prefix: true, beforeExpr: true},
|
303 |
-
"void": {keyword: "void", prefix: true, beforeExpr: true},
|
304 |
-
"delete": {keyword: "delete", prefix: true, beforeExpr: true}};
|
305 |
-
|
306 |
-
// Punctuation token types. Again, the `type` property is purely for debugging.
|
307 |
-
|
308 |
-
var _bracketL = {type: "[", beforeExpr: true}, _bracketR = {type: "]"}, _braceL = {type: "{", beforeExpr: true};
|
309 |
-
var _braceR = {type: "}"}, _parenL = {type: "(", beforeExpr: true}, _parenR = {type: ")"};
|
310 |
-
var _comma = {type: ",", beforeExpr: true}, _semi = {type: ";", beforeExpr: true};
|
311 |
-
var _colon = {type: ":", beforeExpr: true}, _dot = {type: "."}, _question = {type: "?", beforeExpr: true};
|
312 |
-
|
313 |
-
// Operators. These carry several kinds of properties to help the
|
314 |
-
// parser use them properly (the presence of these properties is
|
315 |
-
// what categorizes them as operators).
|
316 |
-
//
|
317 |
-
// `binop`, when present, specifies that this operator is a binary
|
318 |
-
// operator, and will refer to its precedence.
|
319 |
-
//
|
320 |
-
// `prefix` and `postfix` mark the operator as a prefix or postfix
|
321 |
-
// unary operator. `isUpdate` specifies that the node produced by
|
322 |
-
// the operator should be of type UpdateExpression rather than
|
323 |
-
// simply UnaryExpression (`++` and `--`).
|
324 |
-
//
|
325 |
-
// `isAssign` marks all of `=`, `+=`, `-=` etcetera, which act as
|
326 |
-
// binary operators with a very low precedence, that should result
|
327 |
-
// in AssignmentExpression nodes.
|
328 |
-
|
329 |
-
var _slash = {binop: 10, beforeExpr: true}, _eq = {isAssign: true, beforeExpr: true};
|
330 |
-
var _assign = {isAssign: true, beforeExpr: true};
|
331 |
-
var _incDec = {postfix: true, prefix: true, isUpdate: true}, _prefix = {prefix: true, beforeExpr: true};
|
332 |
-
var _logicalOR = {binop: 1, beforeExpr: true};
|
333 |
-
var _logicalAND = {binop: 2, beforeExpr: true};
|
334 |
-
var _bitwiseOR = {binop: 3, beforeExpr: true};
|
335 |
-
var _bitwiseXOR = {binop: 4, beforeExpr: true};
|
336 |
-
var _bitwiseAND = {binop: 5, beforeExpr: true};
|
337 |
-
var _equality = {binop: 6, beforeExpr: true};
|
338 |
-
var _relational = {binop: 7, beforeExpr: true};
|
339 |
-
var _bitShift = {binop: 8, beforeExpr: true};
|
340 |
-
var _plusMin = {binop: 9, prefix: true, beforeExpr: true};
|
341 |
-
var _multiplyModulo = {binop: 10, beforeExpr: true};
|
342 |
-
|
343 |
-
// Provide access to the token types for external users of the
|
344 |
-
// tokenizer.
|
345 |
-
|
346 |
-
exports.tokTypes = {bracketL: _bracketL, bracketR: _bracketR, braceL: _braceL, braceR: _braceR,
|
347 |
-
parenL: _parenL, parenR: _parenR, comma: _comma, semi: _semi, colon: _colon,
|
348 |
-
dot: _dot, question: _question, slash: _slash, eq: _eq, name: _name, eof: _eof,
|
349 |
-
num: _num, regexp: _regexp, string: _string};
|
350 |
-
for (var kw in keywordTypes) exports.tokTypes["_" + kw] = keywordTypes[kw];
|
351 |
-
|
352 |
-
// This is a trick taken from Esprima. It turns out that, on
|
353 |
-
// non-Chrome browsers, to check whether a string is in a set, a
|
354 |
-
// predicate containing a big ugly `switch` statement is faster than
|
355 |
-
// a regular expression, and on Chrome the two are about on par.
|
356 |
-
// This function uses `eval` (non-lexical) to produce such a
|
357 |
-
// predicate from a space-separated string of words.
|
358 |
-
//
|
359 |
-
// It starts by sorting the words by length.
|
360 |
-
|
361 |
-
function makePredicate(words) {
|
362 |
-
words = words.split(" ");
|
363 |
-
var f = "", cats = [];
|
364 |
-
out: for (var i = 0; i < words.length; ++i) {
|
365 |
-
for (var j = 0; j < cats.length; ++j)
|
366 |
-
if (cats[j][0].length == words[i].length) {
|
367 |
-
cats[j].push(words[i]);
|
368 |
-
continue out;
|
369 |
-
}
|
370 |
-
cats.push([words[i]]);
|
371 |
-
}
|
372 |
-
function compareTo(arr) {
|
373 |
-
if (arr.length == 1) return f += "return str === " + JSON.stringify(arr[0]) + ";";
|
374 |
-
f += "switch(str){";
|
375 |
-
for (var i = 0; i < arr.length; ++i) f += "case " + JSON.stringify(arr[i]) + ":";
|
376 |
-
f += "return true}return false;";
|
377 |
-
}
|
378 |
-
|
379 |
-
// When there are more than three length categories, an outer
|
380 |
-
// switch first dispatches on the lengths, to save on comparisons.
|
381 |
-
|
382 |
-
if (cats.length > 3) {
|
383 |
-
cats.sort(function(a, b) {return b.length - a.length;});
|
384 |
-
f += "switch(str.length){";
|
385 |
-
for (var i = 0; i < cats.length; ++i) {
|
386 |
-
var cat = cats[i];
|
387 |
-
f += "case " + cat[0].length + ":";
|
388 |
-
compareTo(cat);
|
389 |
-
}
|
390 |
-
f += "}";
|
391 |
-
|
392 |
-
// Otherwise, simply generate a flat `switch` statement.
|
393 |
-
|
394 |
-
} else {
|
395 |
-
compareTo(words);
|
396 |
-
}
|
397 |
-
return new Function("str", f);
|
398 |
-
}
|
399 |
-
|
400 |
-
// The ECMAScript 3 reserved word list.
|
401 |
-
|
402 |
-
var isReservedWord3 = makePredicate("abstract boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized throws transient volatile");
|
403 |
-
|
404 |
-
// ECMAScript 5 reserved words.
|
405 |
-
|
406 |
-
var isReservedWord5 = makePredicate("class enum extends super const export import");
|
407 |
-
|
408 |
-
// The additional reserved words in strict mode.
|
409 |
-
|
410 |
-
var isStrictReservedWord = makePredicate("implements interface let package private protected public static yield");
|
411 |
-
|
412 |
-
// The forbidden variable names in strict mode.
|
413 |
-
|
414 |
-
var isStrictBadIdWord = makePredicate("eval arguments");
|
415 |
-
|
416 |
-
// And the keywords.
|
417 |
-
|
418 |
-
var isKeyword = makePredicate("break case catch continue debugger default do else finally for function if return switch throw try var while with null true false instanceof typeof void delete new in this");
|
419 |
-
|
420 |
-
// ## Character categories
|
421 |
-
|
422 |
-
// Big ugly regular expressions that match characters in the
|
423 |
-
// whitespace, identifier, and identifier-start categories. These
|
424 |
-
// are only applied when a character is found to actually have a
|
425 |
-
// code point above 128.
|
426 |
-
|
427 |
-
var nonASCIIwhitespace = /[\u1680\u180e\u2000-\u200a\u202f\u205f\u3000\ufeff]/;
|
428 |
-
var nonASCIIidentifierStartChars = "\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05d0-\u05ea\u05f0-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u08a0\u08a2-\u08ac\u0904-\u0939\u093d\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097f\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd\u09ce\u09dc\u09dd\u09df-\u09e1\u09f0\u09f1\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3d\u0b5c\u0b5d\u0b5f-\u0b61\u0b71\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bd0\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c33\u0c35-\u0c39\u0c3d\u0c58\u0c59\u0c60\u0c61\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd\u0cde\u0ce0\u0ce1\u0cf1\u0cf2\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\u0d60\u0d61\u0d7a-\u0d7f\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0e01-\u0e30\u0e32\u0e33\u0e40-\u0e46\u0e81\u0e82\u0e84\u0e87\u0e88\u0e8a\u0e8d\u0e94-\u0e97\u0e99-\u0e9f\u0ea1-\u0ea3\u0ea5\u0ea7\u0eaa\u0eab\u0ead-\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0-\u0ec4\u0ec6\u0edc-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065\u1066\u106e-\u1070\u1075-\u1081\u108e\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1380-\u138f\u13a0-\u13f4\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f0\u1700-\u170c\u170e-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1877\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191c\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19c1-\u19c7\u1a00-\u1a16\u1a20-\u1a54\u1aa7\u1b05-\u1b33\u1b45-\u1b4b\u1b83-\u1ba0\u1bae\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1ce9-\u1cec\u1cee-\u1cf1\u1cf5\u1cf6\u1d00-\u1dbf\u1e00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u2071\u207f\u2090-\u209c\u2102\u2107\u210a-\u2113\u2115\u2119-\u211d\u2124\u2126\u2128\u212a-\u212d\u212f-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u2e2f\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309d-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312d\u3131-\u318e\u31a0-\u31ba\u31f0-\u31ff\u3400-\u4db5\u4e00-\u9fcc\ua000-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua66e\ua67f-\ua697\ua6a0-\ua6ef\ua717-\ua71f\ua722-\ua788\ua78b-\ua78e\ua790-\ua793\ua7a0-\ua7aa\ua7f8-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9cf\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa76\uaa7a\uaa80-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadd\uaae0-\uaaea\uaaf2-\uaaf4\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uabc0-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d\ufb1f-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\uff21-\uff3a\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc";
|
429 |
-
var nonASCIIidentifierChars = "\u0300-\u036f\u0483-\u0487\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u0620-\u0649\u0672-\u06d3\u06e7-\u06e8\u06fb-\u06fc\u0730-\u074a\u0800-\u0814\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0840-\u0857\u08e4-\u08fe\u0900-\u0903\u093a-\u093c\u093e-\u094f\u0951-\u0957\u0962-\u0963\u0966-\u096f\u0981-\u0983\u09bc\u09be-\u09c4\u09c7\u09c8\u09d7\u09df-\u09e0\u0a01-\u0a03\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a66-\u0a71\u0a75\u0a81-\u0a83\u0abc\u0abe-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ae2-\u0ae3\u0ae6-\u0aef\u0b01-\u0b03\u0b3c\u0b3e-\u0b44\u0b47\u0b48\u0b4b-\u0b4d\u0b56\u0b57\u0b5f-\u0b60\u0b66-\u0b6f\u0b82\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd7\u0be6-\u0bef\u0c01-\u0c03\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62-\u0c63\u0c66-\u0c6f\u0c82\u0c83\u0cbc\u0cbe-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5\u0cd6\u0ce2-\u0ce3\u0ce6-\u0cef\u0d02\u0d03\u0d46-\u0d48\u0d57\u0d62-\u0d63\u0d66-\u0d6f\u0d82\u0d83\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0df2\u0df3\u0e34-\u0e3a\u0e40-\u0e45\u0e50-\u0e59\u0eb4-\u0eb9\u0ec8-\u0ecd\u0ed0-\u0ed9\u0f18\u0f19\u0f20-\u0f29\u0f35\u0f37\u0f39\u0f41-\u0f47\u0f71-\u0f84\u0f86-\u0f87\u0f8d-\u0f97\u0f99-\u0fbc\u0fc6\u1000-\u1029\u1040-\u1049\u1067-\u106d\u1071-\u1074\u1082-\u108d\u108f-\u109d\u135d-\u135f\u170e-\u1710\u1720-\u1730\u1740-\u1750\u1772\u1773\u1780-\u17b2\u17dd\u17e0-\u17e9\u180b-\u180d\u1810-\u1819\u1920-\u192b\u1930-\u193b\u1951-\u196d\u19b0-\u19c0\u19c8-\u19c9\u19d0-\u19d9\u1a00-\u1a15\u1a20-\u1a53\u1a60-\u1a7c\u1a7f-\u1a89\u1a90-\u1a99\u1b46-\u1b4b\u1b50-\u1b59\u1b6b-\u1b73\u1bb0-\u1bb9\u1be6-\u1bf3\u1c00-\u1c22\u1c40-\u1c49\u1c5b-\u1c7d\u1cd0-\u1cd2\u1d00-\u1dbe\u1e01-\u1f15\u200c\u200d\u203f\u2040\u2054\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2d81-\u2d96\u2de0-\u2dff\u3021-\u3028\u3099\u309a\ua640-\ua66d\ua674-\ua67d\ua69f\ua6f0-\ua6f1\ua7f8-\ua800\ua806\ua80b\ua823-\ua827\ua880-\ua881\ua8b4-\ua8c4\ua8d0-\ua8d9\ua8f3-\ua8f7\ua900-\ua909\ua926-\ua92d\ua930-\ua945\ua980-\ua983\ua9b3-\ua9c0\uaa00-\uaa27\uaa40-\uaa41\uaa4c-\uaa4d\uaa50-\uaa59\uaa7b\uaae0-\uaae9\uaaf2-\uaaf3\uabc0-\uabe1\uabec\uabed\uabf0-\uabf9\ufb20-\ufb28\ufe00-\ufe0f\ufe20-\ufe26\ufe33\ufe34\ufe4d-\ufe4f\uff10-\uff19\uff3f";
|
430 |
-
var nonASCIIidentifierStart = new RegExp("[" + nonASCIIidentifierStartChars + "]");
|
431 |
-
var nonASCIIidentifier = new RegExp("[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]");
|
432 |
-
|
433 |
-
// Whether a single character denotes a newline.
|
434 |
-
|
435 |
-
var newline = /[\n\r\u2028\u2029]/;
|
436 |
-
|
437 |
-
// Matches a whole line break (where CRLF is considered a single
|
438 |
-
// line break). Used to count lines.
|
439 |
-
|
440 |
-
var lineBreak = /\r\n|[\n\r\u2028\u2029]/g;
|
441 |
-
|
442 |
-
// Test whether a given character code starts an identifier.
|
443 |
-
|
444 |
-
var isIdentifierStart = exports.isIdentifierStart = function(code) {
|
445 |
-
if (code < 65) return code === 36;
|
446 |
-
if (code < 91) return true;
|
447 |
-
if (code < 97) return code === 95;
|
448 |
-
if (code < 123)return true;
|
449 |
-
return code >= 0xaa && nonASCIIidentifierStart.test(String.fromCharCode(code));
|
450 |
-
};
|
451 |
-
|
452 |
-
// Test whether a given character is part of an identifier.
|
453 |
-
|
454 |
-
var isIdentifierChar = exports.isIdentifierChar = function(code) {
|
455 |
-
if (code < 48) return code === 36;
|
456 |
-
if (code < 58) return true;
|
457 |
-
if (code < 65) return false;
|
458 |
-
if (code < 91) return true;
|
459 |
-
if (code < 97) return code === 95;
|
460 |
-
if (code < 123)return true;
|
461 |
-
return code >= 0xaa && nonASCIIidentifier.test(String.fromCharCode(code));
|
462 |
-
};
|
463 |
-
|
464 |
-
// ## Tokenizer
|
465 |
-
|
466 |
-
// These are used when `options.locations` is on, for the
|
467 |
-
// `tokStartLoc` and `tokEndLoc` properties.
|
468 |
-
|
469 |
-
function line_loc_t() {
|
470 |
-
this.line = tokCurLine;
|
471 |
-
this.column = tokPos - tokLineStart;
|
472 |
-
}
|
473 |
-
|
474 |
-
// Reset the token state. Used at the start of a parse.
|
475 |
-
|
476 |
-
function initTokenState() {
|
477 |
-
tokCurLine = 1;
|
478 |
-
tokPos = tokLineStart = 0;
|
479 |
-
tokRegexpAllowed = true;
|
480 |
-
skipSpace();
|
481 |
-
}
|
482 |
-
|
483 |
-
// Called at the end of every token. Sets `tokEnd`, `tokVal`, and
|
484 |
-
// `tokRegexpAllowed`, and skips the space after the token, so that
|
485 |
-
// the next one's `tokStart` will point at the right position.
|
486 |
-
|
487 |
-
function finishToken(type, val) {
|
488 |
-
tokEnd = tokPos;
|
489 |
-
if (options.locations) tokEndLoc = new line_loc_t;
|
490 |
-
tokType = type;
|
491 |
-
skipSpace();
|
492 |
-
tokVal = val;
|
493 |
-
tokRegexpAllowed = type.beforeExpr;
|
494 |
-
}
|
495 |
-
|
496 |
-
function skipBlockComment() {
|
497 |
-
var startLoc = options.onComment && options.locations && new line_loc_t;
|
498 |
-
var start = tokPos, end = input.indexOf("*/", tokPos += 2);
|
499 |
-
if (end === -1) raise(tokPos - 2, "Unterminated comment");
|
500 |
-
tokPos = end + 2;
|
501 |
-
if (options.locations) {
|
502 |
-
lineBreak.lastIndex = start;
|
503 |
-
var match;
|
504 |
-
while ((match = lineBreak.exec(input)) && match.index < tokPos) {
|
505 |
-
++tokCurLine;
|
506 |
-
tokLineStart = match.index + match[0].length;
|
507 |
-
}
|
508 |
-
}
|
509 |
-
if (options.onComment)
|
510 |
-
options.onComment(true, input.slice(start + 2, end), start, tokPos,
|
511 |
-
startLoc, options.locations && new line_loc_t);
|
512 |
-
}
|
513 |
-
|
514 |
-
function skipLineComment() {
|
515 |
-
var start = tokPos;
|
516 |
-
var startLoc = options.onComment && options.locations && new line_loc_t;
|
517 |
-
var ch = input.charCodeAt(tokPos+=2);
|
518 |
-
while (tokPos < inputLen && ch !== 10 && ch !== 13 && ch !== 8232 && ch !== 8233) {
|
519 |
-
++tokPos;
|
520 |
-
ch = input.charCodeAt(tokPos);
|
521 |
-
}
|
522 |
-
if (options.onComment)
|
523 |
-
options.onComment(false, input.slice(start + 2, tokPos), start, tokPos,
|
524 |
-
startLoc, options.locations && new line_loc_t);
|
525 |
-
}
|
526 |
-
|
527 |
-
// Called at the start of the parse and after every token. Skips
|
528 |
-
// whitespace and comments, and.
|
529 |
-
|
530 |
-
function skipSpace() {
|
531 |
-
while (tokPos < inputLen) {
|
532 |
-
var ch = input.charCodeAt(tokPos);
|
533 |
-
if (ch === 32) { // ' '
|
534 |
-
++tokPos;
|
535 |
-
} else if (ch === 13) {
|
536 |
-
++tokPos;
|
537 |
-
var next = input.charCodeAt(tokPos);
|
538 |
-
if (next === 10) {
|
539 |
-
++tokPos;
|
540 |
-
}
|
541 |
-
if (options.locations) {
|
542 |
-
++tokCurLine;
|
543 |
-
tokLineStart = tokPos;
|
544 |
-
}
|
545 |
-
} else if (ch === 10 || ch === 8232 || ch === 8233) {
|
546 |
-
++tokPos;
|
547 |
-
if (options.locations) {
|
548 |
-
++tokCurLine;
|
549 |
-
tokLineStart = tokPos;
|
550 |
-
}
|
551 |
-
} else if (ch > 8 && ch < 14) {
|
552 |
-
++tokPos;
|
553 |
-
} else if (ch === 47) { // '/'
|
554 |
-
var next = input.charCodeAt(tokPos + 1);
|
555 |
-
if (next === 42) { // '*'
|
556 |
-
skipBlockComment();
|
557 |
-
} else if (next === 47) { // '/'
|
558 |
-
skipLineComment();
|
559 |
-
} else break;
|
560 |
-
} else if (ch === 160) { // '\xa0'
|
561 |
-
++tokPos;
|
562 |
-
} else if (ch >= 5760 && nonASCIIwhitespace.test(String.fromCharCode(ch))) {
|
563 |
-
++tokPos;
|
564 |
-
} else {
|
565 |
-
break;
|
566 |
-
}
|
567 |
-
}
|
568 |
-
}
|
569 |
-
|
570 |
-
// ### Token reading
|
571 |
-
|
572 |
-
// This is the function that is called to fetch the next token. It
|
573 |
-
// is somewhat obscure, because it works in character codes rather
|
574 |
-
// than characters, and because operator parsing has been inlined
|
575 |
-
// into it.
|
576 |
-
//
|
577 |
-
// All in the name of speed.
|
578 |
-
//
|
579 |
-
// The `forceRegexp` parameter is used in the one case where the
|
580 |
-
// `tokRegexpAllowed` trick does not work. See `parseStatement`.
|
581 |
-
|
582 |
-
function readToken_dot() {
|
583 |
-
var next = input.charCodeAt(tokPos + 1);
|
584 |
-
if (next >= 48 && next <= 57) return readNumber(true);
|
585 |
-
++tokPos;
|
586 |
-
return finishToken(_dot);
|
587 |
-
}
|
588 |
-
|
589 |
-
function readToken_slash() { // '/'
|
590 |
-
var next = input.charCodeAt(tokPos + 1);
|
591 |
-
if (tokRegexpAllowed) {++tokPos; return readRegexp();}
|
592 |
-
if (next === 61) return finishOp(_assign, 2);
|
593 |
-
return finishOp(_slash, 1);
|
594 |
-
}
|
595 |
-
|
596 |
-
function readToken_mult_modulo() { // '%*'
|
597 |
-
var next = input.charCodeAt(tokPos + 1);
|
598 |
-
if (next === 61) return finishOp(_assign, 2);
|
599 |
-
return finishOp(_multiplyModulo, 1);
|
600 |
-
}
|
601 |
-
|
602 |
-
function readToken_pipe_amp(code) { // '|&'
|
603 |
-
var next = input.charCodeAt(tokPos + 1);
|
604 |
-
if (next === code) return finishOp(code === 124 ? _logicalOR : _logicalAND, 2);
|
605 |
-
if (next === 61) return finishOp(_assign, 2);
|
606 |
-
return finishOp(code === 124 ? _bitwiseOR : _bitwiseAND, 1);
|
607 |
-
}
|
608 |
-
|
609 |
-
function readToken_caret() { // '^'
|
610 |
-
var next = input.charCodeAt(tokPos + 1);
|
611 |
-
if (next === 61) return finishOp(_assign, 2);
|
612 |
-
return finishOp(_bitwiseXOR, 1);
|
613 |
-
}
|
614 |
-
|
615 |
-
function readToken_plus_min(code) { // '+-'
|
616 |
-
var next = input.charCodeAt(tokPos + 1);
|
617 |
-
if (next === code) {
|
618 |
-
if (next == 45 && input.charCodeAt(tokPos + 2) == 62 &&
|
619 |
-
newline.test(input.slice(lastEnd, tokPos))) {
|
620 |
-
// A `-->` line comment
|
621 |
-
tokPos += 3;
|
622 |
-
skipLineComment();
|
623 |
-
skipSpace();
|
624 |
-
return readToken();
|
625 |
-
}
|
626 |
-
return finishOp(_incDec, 2);
|
627 |
-
}
|
628 |
-
if (next === 61) return finishOp(_assign, 2);
|
629 |
-
return finishOp(_plusMin, 1);
|
630 |
-
}
|
631 |
-
|
632 |
-
function readToken_lt_gt(code) { // '<>'
|
633 |
-
var next = input.charCodeAt(tokPos + 1);
|
634 |
-
var size = 1;
|
635 |
-
if (next === code) {
|
636 |
-
size = code === 62 && input.charCodeAt(tokPos + 2) === 62 ? 3 : 2;
|
637 |
-
if (input.charCodeAt(tokPos + size) === 61) return finishOp(_assign, size + 1);
|
638 |
-
return finishOp(_bitShift, size);
|
639 |
-
}
|
640 |
-
if (next == 33 && code == 60 && input.charCodeAt(tokPos + 2) == 45 &&
|
641 |
-
input.charCodeAt(tokPos + 3) == 45) {
|
642 |
-
// `<!--`, an XML-style comment that should be interpreted as a line comment
|
643 |
-
tokPos += 4;
|
644 |
-
skipLineComment();
|
645 |
-
skipSpace();
|
646 |
-
return readToken();
|
647 |
-
}
|
648 |
-
if (next === 61)
|
649 |
-
size = input.charCodeAt(tokPos + 2) === 61 ? 3 : 2;
|
650 |
-
return finishOp(_relational, size);
|
651 |
-
}
|
652 |
-
|
653 |
-
function readToken_eq_excl(code) { // '=!'
|
654 |
-
var next = input.charCodeAt(tokPos + 1);
|
655 |
-
if (next === 61) return finishOp(_equality, input.charCodeAt(tokPos + 2) === 61 ? 3 : 2);
|
656 |
-
return finishOp(code === 61 ? _eq : _prefix, 1);
|
657 |
-
}
|
658 |
-
|
659 |
-
function getTokenFromCode(code) {
|
660 |
-
switch(code) {
|
661 |
-
// The interpretation of a dot depends on whether it is followed
|
662 |
-
// by a digit.
|
663 |
-
case 46: // '.'
|
664 |
-
return readToken_dot();
|
665 |
-
|
666 |
-
// Punctuation tokens.
|
667 |
-
case 40: ++tokPos; return finishToken(_parenL);
|
668 |
-
case 41: ++tokPos; return finishToken(_parenR);
|
669 |
-
case 59: ++tokPos; return finishToken(_semi);
|
670 |
-
case 44: ++tokPos; return finishToken(_comma);
|
671 |
-
case 91: ++tokPos; return finishToken(_bracketL);
|
672 |
-
case 93: ++tokPos; return finishToken(_bracketR);
|
673 |
-
case 123: ++tokPos; return finishToken(_braceL);
|
674 |
-
case 125: ++tokPos; return finishToken(_braceR);
|
675 |
-
case 58: ++tokPos; return finishToken(_colon);
|
676 |
-
case 63: ++tokPos; return finishToken(_question);
|
677 |
-
|
678 |
-
// '0x' is a hexadecimal number.
|
679 |
-
case 48: // '0'
|
680 |
-
var next = input.charCodeAt(tokPos + 1);
|
681 |
-
if (next === 120 || next === 88) return readHexNumber();
|
682 |
-
// Anything else beginning with a digit is an integer, octal
|
683 |
-
// number, or float.
|
684 |
-
case 49: case 50: case 51: case 52: case 53: case 54: case 55: case 56: case 57: // 1-9
|
685 |
-
return readNumber(false);
|
686 |
-
|
687 |
-
// Quotes produce strings.
|
688 |
-
case 34: case 39: // '"', "'"
|
689 |
-
return readString(code);
|
690 |
-
|
691 |
-
// Operators are parsed inline in tiny state machines. '=' (61) is
|
692 |
-
// often referred to. `finishOp` simply skips the amount of
|
693 |
-
// characters it is given as second argument, and returns a token
|
694 |
-
// of the type given by its first argument.
|
695 |
-
|
696 |
-
case 47: // '/'
|
697 |
-
return readToken_slash(code);
|
698 |
-
|
699 |
-
case 37: case 42: // '%*'
|
700 |
-
return readToken_mult_modulo();
|
701 |
-
|
702 |
-
case 124: case 38: // '|&'
|
703 |
-
return readToken_pipe_amp(code);
|
704 |
-
|
705 |
-
case 94: // '^'
|
706 |
-
return readToken_caret();
|
707 |
-
|
708 |
-
case 43: case 45: // '+-'
|
709 |
-
return readToken_plus_min(code);
|
710 |
-
|
711 |
-
case 60: case 62: // '<>'
|
712 |
-
return readToken_lt_gt(code);
|
713 |
-
|
714 |
-
case 61: case 33: // '=!'
|
715 |
-
return readToken_eq_excl(code);
|
716 |
-
|
717 |
-
case 126: // '~'
|
718 |
-
return finishOp(_prefix, 1);
|
719 |
-
}
|
720 |
-
|
721 |
-
return false;
|
722 |
-
}
|
723 |
-
|
724 |
-
function readToken(forceRegexp) {
|
725 |
-
if (!forceRegexp) tokStart = tokPos;
|
726 |
-
else tokPos = tokStart + 1;
|
727 |
-
if (options.locations) tokStartLoc = new line_loc_t;
|
728 |
-
if (forceRegexp) return readRegexp();
|
729 |
-
if (tokPos >= inputLen) return finishToken(_eof);
|
730 |
-
|
731 |
-
var code = input.charCodeAt(tokPos);
|
732 |
-
// Identifier or keyword. '\uXXXX' sequences are allowed in
|
733 |
-
// identifiers, so '\' also dispatches to that.
|
734 |
-
if (isIdentifierStart(code) || code === 92 /* '\' */) return readWord();
|
735 |
-
|
736 |
-
var tok = getTokenFromCode(code);
|
737 |
-
|
738 |
-
if (tok === false) {
|
739 |
-
// If we are here, we either found a non-ASCII identifier
|
740 |
-
// character, or something that's entirely disallowed.
|
741 |
-
var ch = String.fromCharCode(code);
|
742 |
-
if (ch === "\\" || nonASCIIidentifierStart.test(ch)) return readWord();
|
743 |
-
raise(tokPos, "Unexpected character '" + ch + "'");
|
744 |
-
}
|
745 |
-
return tok;
|
746 |
-
}
|
747 |
-
|
748 |
-
function finishOp(type, size) {
|
749 |
-
var str = input.slice(tokPos, tokPos + size);
|
750 |
-
tokPos += size;
|
751 |
-
finishToken(type, str);
|
752 |
-
}
|
753 |
-
|
754 |
-
// Parse a regular expression. Some context-awareness is necessary,
|
755 |
-
// since a '/' inside a '[]' set does not end the expression.
|
756 |
-
|
757 |
-
function readRegexp() {
|
758 |
-
var content = "", escaped, inClass, start = tokPos;
|
759 |
-
for (;;) {
|
760 |
-
if (tokPos >= inputLen) raise(start, "Unterminated regular expression");
|
761 |
-
var ch = input.charAt(tokPos);
|
762 |
-
if (newline.test(ch)) raise(start, "Unterminated regular expression");
|
763 |
-
if (!escaped) {
|
764 |
-
if (ch === "[") inClass = true;
|
765 |
-
else if (ch === "]" && inClass) inClass = false;
|
766 |
-
else if (ch === "/" && !inClass) break;
|
767 |
-
escaped = ch === "\\";
|
768 |
-
} else escaped = false;
|
769 |
-
++tokPos;
|
770 |
-
}
|
771 |
-
var content = input.slice(start, tokPos);
|
772 |
-
++tokPos;
|
773 |
-
// Need to use `readWord1` because '\uXXXX' sequences are allowed
|
774 |
-
// here (don't ask).
|
775 |
-
var mods = readWord1();
|
776 |
-
if (mods && !/^[gmsiy]*$/.test(mods)) raise(start, "Invalid regexp flag");
|
777 |
-
try {
|
778 |
-
var value = new RegExp(content, mods);
|
779 |
-
} catch (e) {
|
780 |
-
if (e instanceof SyntaxError) raise(start, e.message);
|
781 |
-
raise(e);
|
782 |
-
}
|
783 |
-
return finishToken(_regexp, value);
|
784 |
-
}
|
785 |
-
|
786 |
-
// Read an integer in the given radix. Return null if zero digits
|
787 |
-
// were read, the integer value otherwise. When `len` is given, this
|
788 |
-
// will return `null` unless the integer has exactly `len` digits.
|
789 |
-
|
790 |
-
function readInt(radix, len) {
|
791 |
-
var start = tokPos, total = 0;
|
792 |
-
for (var i = 0, e = len == null ? Infinity : len; i < e; ++i) {
|
793 |
-
var code = input.charCodeAt(tokPos), val;
|
794 |
-
if (code >= 97) val = code - 97 + 10; // a
|
795 |
-
else if (code >= 65) val = code - 65 + 10; // A
|
796 |
-
else if (code >= 48 && code <= 57) val = code - 48; // 0-9
|
797 |
-
else val = Infinity;
|
798 |
-
if (val >= radix) break;
|
799 |
-
++tokPos;
|
800 |
-
total = total * radix + val;
|
801 |
-
}
|
802 |
-
if (tokPos === start || len != null && tokPos - start !== len) return null;
|
803 |
-
|
804 |
-
return total;
|
805 |
-
}
|
806 |
-
|
807 |
-
function readHexNumber() {
|
808 |
-
tokPos += 2; // 0x
|
809 |
-
var val = readInt(16);
|
810 |
-
if (val == null) raise(tokStart + 2, "Expected hexadecimal number");
|
811 |
-
if (isIdentifierStart(input.charCodeAt(tokPos))) raise(tokPos, "Identifier directly after number");
|
812 |
-
return finishToken(_num, val);
|
813 |
-
}
|
814 |
-
|
815 |
-
// Read an integer, octal integer, or floating-point number.
|
816 |
-
|
817 |
-
function readNumber(startsWithDot) {
|
818 |
-
var start = tokPos, isFloat = false, octal = input.charCodeAt(tokPos) === 48;
|
819 |
-
if (!startsWithDot && readInt(10) === null) raise(start, "Invalid number");
|
820 |
-
if (input.charCodeAt(tokPos) === 46) {
|
821 |
-
++tokPos;
|
822 |
-
readInt(10);
|
823 |
-
isFloat = true;
|
824 |
-
}
|
825 |
-
var next = input.charCodeAt(tokPos);
|
826 |
-
if (next === 69 || next === 101) { // 'eE'
|
827 |
-
next = input.charCodeAt(++tokPos);
|
828 |
-
if (next === 43 || next === 45) ++tokPos; // '+-'
|
829 |
-
if (readInt(10) === null) raise(start, "Invalid number");
|
830 |
-
isFloat = true;
|
831 |
-
}
|
832 |
-
if (isIdentifierStart(input.charCodeAt(tokPos))) raise(tokPos, "Identifier directly after number");
|
833 |
-
|
834 |
-
var str = input.slice(start, tokPos), val;
|
835 |
-
if (isFloat) val = parseFloat(str);
|
836 |
-
else if (!octal || str.length === 1) val = parseInt(str, 10);
|
837 |
-
else if (/[89]/.test(str) || strict) raise(start, "Invalid number");
|
838 |
-
else val = parseInt(str, 8);
|
839 |
-
return finishToken(_num, val);
|
840 |
-
}
|
841 |
-
|
842 |
-
// Read a string value, interpreting backslash-escapes.
|
843 |
-
|
844 |
-
function readString(quote) {
|
845 |
-
tokPos++;
|
846 |
-
var out = "";
|
847 |
-
for (;;) {
|
848 |
-
if (tokPos >= inputLen) raise(tokStart, "Unterminated string constant");
|
849 |
-
var ch = input.charCodeAt(tokPos);
|
850 |
-
if (ch === quote) {
|
851 |
-
++tokPos;
|
852 |
-
return finishToken(_string, out);
|
853 |
-
}
|
854 |
-
if (ch === 92) { // '\'
|
855 |
-
ch = input.charCodeAt(++tokPos);
|
856 |
-
var octal = /^[0-7]+/.exec(input.slice(tokPos, tokPos + 3));
|
857 |
-
if (octal) octal = octal[0];
|
858 |
-
while (octal && parseInt(octal, 8) > 255) octal = octal.slice(0, -1);
|
859 |
-
if (octal === "0") octal = null;
|
860 |
-
++tokPos;
|
861 |
-
if (octal) {
|
862 |
-
if (strict) raise(tokPos - 2, "Octal literal in strict mode");
|
863 |
-
out += String.fromCharCode(parseInt(octal, 8));
|
864 |
-
tokPos += octal.length - 1;
|
865 |
-
} else {
|
866 |
-
switch (ch) {
|
867 |
-
case 110: out += "\n"; break; // 'n' -> '\n'
|
868 |
-
case 114: out += "\r"; break; // 'r' -> '\r'
|
869 |
-
case 120: out += String.fromCharCode(readHexChar(2)); break; // 'x'
|
870 |
-
case 117: out += String.fromCharCode(readHexChar(4)); break; // 'u'
|
871 |
-
case 85: out += String.fromCharCode(readHexChar(8)); break; // 'U'
|
872 |
-
case 116: out += "\t"; break; // 't' -> '\t'
|
873 |
-
case 98: out += "\b"; break; // 'b' -> '\b'
|
874 |
-
case 118: out += "\u000b"; break; // 'v' -> '\u000b'
|
875 |
-
case 102: out += "\f"; break; // 'f' -> '\f'
|
876 |
-
case 48: out += "\0"; break; // 0 -> '\0'
|
877 |
-
case 13: if (input.charCodeAt(tokPos) === 10) ++tokPos; // '\r\n'
|
878 |
-
case 10: // ' \n'
|
879 |
-
if (options.locations) { tokLineStart = tokPos; ++tokCurLine; }
|
880 |
-
break;
|
881 |
-
default: out += String.fromCharCode(ch); break;
|
882 |
-
}
|
883 |
-
}
|
884 |
-
} else {
|
885 |
-
if (ch === 13 || ch === 10 || ch === 8232 || ch === 8233) raise(tokStart, "Unterminated string constant");
|
886 |
-
out += String.fromCharCode(ch); // '\'
|
887 |
-
++tokPos;
|
888 |
-
}
|
889 |
-
}
|
890 |
-
}
|
891 |
-
|
892 |
-
// Used to read character escape sequences ('\x', '\u', '\U').
|
893 |
-
|
894 |
-
function readHexChar(len) {
|
895 |
-
var n = readInt(16, len);
|
896 |
-
if (n === null) raise(tokStart, "Bad character escape sequence");
|
897 |
-
return n;
|
898 |
-
}
|
899 |
-
|
900 |
-
// Used to signal to callers of `readWord1` whether the word
|
901 |
-
// contained any escape sequences. This is needed because words with
|
902 |
-
// escape sequences must not be interpreted as keywords.
|
903 |
-
|
904 |
-
var containsEsc;
|
905 |
-
|
906 |
-
// Read an identifier, and return it as a string. Sets `containsEsc`
|
907 |
-
// to whether the word contained a '\u' escape.
|
908 |
-
//
|
909 |
-
// Only builds up the word character-by-character when it actually
|
910 |
-
// containeds an escape, as a micro-optimization.
|
911 |
-
|
912 |
-
function readWord1() {
|
913 |
-
containsEsc = false;
|
914 |
-
var word, first = true, start = tokPos;
|
915 |
-
for (;;) {
|
916 |
-
var ch = input.charCodeAt(tokPos);
|
917 |
-
if (isIdentifierChar(ch)) {
|
918 |
-
if (containsEsc) word += input.charAt(tokPos);
|
919 |
-
++tokPos;
|
920 |
-
} else if (ch === 92) { // "\"
|
921 |
-
if (!containsEsc) word = input.slice(start, tokPos);
|
922 |
-
containsEsc = true;
|
923 |
-
if (input.charCodeAt(++tokPos) != 117) // "u"
|
924 |
-
raise(tokPos, "Expecting Unicode escape sequence \\uXXXX");
|
925 |
-
++tokPos;
|
926 |
-
var esc = readHexChar(4);
|
927 |
-
var escStr = String.fromCharCode(esc);
|
928 |
-
if (!escStr) raise(tokPos - 1, "Invalid Unicode escape");
|
929 |
-
if (!(first ? isIdentifierStart(esc) : isIdentifierChar(esc)))
|
930 |
-
raise(tokPos - 4, "Invalid Unicode escape");
|
931 |
-
word += escStr;
|
932 |
-
} else {
|
933 |
-
break;
|
934 |
-
}
|
935 |
-
first = false;
|
936 |
-
}
|
937 |
-
return containsEsc ? word : input.slice(start, tokPos);
|
938 |
-
}
|
939 |
-
|
940 |
-
// Read an identifier or keyword token. Will check for reserved
|
941 |
-
// words when necessary.
|
942 |
-
|
943 |
-
function readWord() {
|
944 |
-
var word = readWord1();
|
945 |
-
var type = _name;
|
946 |
-
if (!containsEsc && isKeyword(word))
|
947 |
-
type = keywordTypes[word];
|
948 |
-
return finishToken(type, word);
|
949 |
-
}
|
950 |
-
|
951 |
-
// ## Parser
|
952 |
-
|
953 |
-
// A recursive descent parser operates by defining functions for all
|
954 |
-
// syntactic elements, and recursively calling those, each function
|
955 |
-
// advancing the input stream and returning an AST node. Precedence
|
956 |
-
// of constructs (for example, the fact that `!x[1]` means `!(x[1])`
|
957 |
-
// instead of `(!x)[1]` is handled by the fact that the parser
|
958 |
-
// function that parses unary prefix operators is called first, and
|
959 |
-
// in turn calls the function that parses `[]` subscripts — that
|
960 |
-
// way, it'll receive the node for `x[1]` already parsed, and wraps
|
961 |
-
// *that* in the unary operator node.
|
962 |
-
//
|
963 |
-
// Acorn uses an [operator precedence parser][opp] to handle binary
|
964 |
-
// operator precedence, because it is much more compact than using
|
965 |
-
// the technique outlined above, which uses different, nesting
|
966 |
-
// functions to specify precedence, for all of the ten binary
|
967 |
-
// precedence levels that JavaScript defines.
|
968 |
-
//
|
969 |
-
// [opp]: http://en.wikipedia.org/wiki/Operator-precedence_parser
|
970 |
-
|
971 |
-
// ### Parser utilities
|
972 |
-
|
973 |
-
// Continue to the next token.
|
974 |
-
|
975 |
-
function next() {
|
976 |
-
lastStart = tokStart;
|
977 |
-
lastEnd = tokEnd;
|
978 |
-
lastEndLoc = tokEndLoc;
|
979 |
-
readToken();
|
980 |
-
}
|
981 |
-
|
982 |
-
// Enter strict mode. Re-reads the next token to please pedantic
|
983 |
-
// tests ("use strict"; 010; -- should fail).
|
984 |
-
|
985 |
-
function setStrict(strct) {
|
986 |
-
strict = strct;
|
987 |
-
tokPos = tokStart;
|
988 |
-
if (options.locations) {
|
989 |
-
while (tokPos < tokLineStart) {
|
990 |
-
tokLineStart = input.lastIndexOf("\n", tokLineStart - 2) + 1;
|
991 |
-
--tokCurLine;
|
992 |
-
}
|
993 |
-
}
|
994 |
-
skipSpace();
|
995 |
-
readToken();
|
996 |
-
}
|
997 |
-
|
998 |
-
// Start an AST node, attaching a start offset.
|
999 |
-
|
1000 |
-
function node_t() {
|
1001 |
-
this.type = null;
|
1002 |
-
this.start = tokStart;
|
1003 |
-
this.end = null;
|
1004 |
-
}
|
1005 |
-
|
1006 |
-
function node_loc_t() {
|
1007 |
-
this.start = tokStartLoc;
|
1008 |
-
this.end = null;
|
1009 |
-
if (sourceFile !== null) this.source = sourceFile;
|
1010 |
-
}
|
1011 |
-
|
1012 |
-
function startNode() {
|
1013 |
-
var node = new node_t();
|
1014 |
-
if (options.locations)
|
1015 |
-
node.loc = new node_loc_t();
|
1016 |
-
if (options.directSourceFile)
|
1017 |
-
node.sourceFile = options.directSourceFile;
|
1018 |
-
if (options.ranges)
|
1019 |
-
node.range = [tokStart, 0];
|
1020 |
-
return node;
|
1021 |
-
}
|
1022 |
-
|
1023 |
-
// Start a node whose start offset information should be based on
|
1024 |
-
// the start of another node. For example, a binary operator node is
|
1025 |
-
// only started after its left-hand side has already been parsed.
|
1026 |
-
|
1027 |
-
function startNodeFrom(other) {
|
1028 |
-
var node = new node_t();
|
1029 |
-
node.start = other.start;
|
1030 |
-
if (options.locations) {
|
1031 |
-
node.loc = new node_loc_t();
|
1032 |
-
node.loc.start = other.loc.start;
|
1033 |
-
}
|
1034 |
-
if (options.ranges)
|
1035 |
-
node.range = [other.range[0], 0];
|
1036 |
-
|
1037 |
-
return node;
|
1038 |
-
}
|
1039 |
-
|
1040 |
-
// Finish an AST node, adding `type` and `end` properties.
|
1041 |
-
|
1042 |
-
function finishNode(node, type) {
|
1043 |
-
node.type = type;
|
1044 |
-
node.end = lastEnd;
|
1045 |
-
if (options.locations)
|
1046 |
-
node.loc.end = lastEndLoc;
|
1047 |
-
if (options.ranges)
|
1048 |
-
node.range[1] = lastEnd;
|
1049 |
-
return node;
|
1050 |
-
}
|
1051 |
-
|
1052 |
-
// Test whether a statement node is the string literal `"use strict"`.
|
1053 |
-
|
1054 |
-
function isUseStrict(stmt) {
|
1055 |
-
return options.ecmaVersion >= 5 && stmt.type === "ExpressionStatement" &&
|
1056 |
-
stmt.expression.type === "Literal" && stmt.expression.value === "use strict";
|
1057 |
-
}
|
1058 |
-
|
1059 |
-
// Predicate that tests whether the next token is of the given
|
1060 |
-
// type, and if yes, consumes it as a side effect.
|
1061 |
-
|
1062 |
-
function eat(type) {
|
1063 |
-
if (tokType === type) {
|
1064 |
-
next();
|
1065 |
-
return true;
|
1066 |
-
}
|
1067 |
-
}
|
1068 |
-
|
1069 |
-
// Test whether a semicolon can be inserted at the current position.
|
1070 |
-
|
1071 |
-
function canInsertSemicolon() {
|
1072 |
-
return !options.strictSemicolons &&
|
1073 |
-
(tokType === _eof || tokType === _braceR || newline.test(input.slice(lastEnd, tokStart)));
|
1074 |
-
}
|
1075 |
-
|
1076 |
-
// Consume a semicolon, or, failing that, see if we are allowed to
|
1077 |
-
// pretend that there is a semicolon at this position.
|
1078 |
-
|
1079 |
-
function semicolon() {
|
1080 |
-
if (!eat(_semi) && !canInsertSemicolon()) unexpected();
|
1081 |
-
}
|
1082 |
-
|
1083 |
-
// Expect a token of a given type. If found, consume it, otherwise,
|
1084 |
-
// raise an unexpected token error.
|
1085 |
-
|
1086 |
-
function expect(type) {
|
1087 |
-
if (tokType === type) next();
|
1088 |
-
else unexpected();
|
1089 |
-
}
|
1090 |
-
|
1091 |
-
// Raise an unexpected token error.
|
1092 |
-
|
1093 |
-
function unexpected() {
|
1094 |
-
raise(tokStart, "Unexpected token");
|
1095 |
-
}
|
1096 |
-
|
1097 |
-
// Verify that a node is an lval — something that can be assigned
|
1098 |
-
// to.
|
1099 |
-
|
1100 |
-
function checkLVal(expr) {
|
1101 |
-
if (expr.type !== "Identifier" && expr.type !== "MemberExpression")
|
1102 |
-
raise(expr.start, "Assigning to rvalue");
|
1103 |
-
if (strict && expr.type === "Identifier" && isStrictBadIdWord(expr.name))
|
1104 |
-
raise(expr.start, "Assigning to " + expr.name + " in strict mode");
|
1105 |
-
}
|
1106 |
-
|
1107 |
-
// ### Statement parsing
|
1108 |
-
|
1109 |
-
// Parse a program. Initializes the parser, reads any number of
|
1110 |
-
// statements, and wraps them in a Program node. Optionally takes a
|
1111 |
-
// `program` argument. If present, the statements will be appended
|
1112 |
-
// to its body instead of creating a new node.
|
1113 |
-
|
1114 |
-
function parseTopLevel(program) {
|
1115 |
-
lastStart = lastEnd = tokPos;
|
1116 |
-
if (options.locations) lastEndLoc = new line_loc_t;
|
1117 |
-
inFunction = strict = null;
|
1118 |
-
labels = [];
|
1119 |
-
readToken();
|
1120 |
-
|
1121 |
-
var node = program || startNode(), first = true;
|
1122 |
-
if (!program) node.body = [];
|
1123 |
-
while (tokType !== _eof) {
|
1124 |
-
var stmt = parseStatement();
|
1125 |
-
node.body.push(stmt);
|
1126 |
-
if (first && isUseStrict(stmt)) setStrict(true);
|
1127 |
-
first = false;
|
1128 |
-
}
|
1129 |
-
return finishNode(node, "Program");
|
1130 |
-
}
|
1131 |
-
|
1132 |
-
var loopLabel = {kind: "loop"}, switchLabel = {kind: "switch"};
|
1133 |
-
|
1134 |
-
// Parse a single statement.
|
1135 |
-
//
|
1136 |
-
// If expecting a statement and finding a slash operator, parse a
|
1137 |
-
// regular expression literal. This is to handle cases like
|
1138 |
-
// `if (foo) /blah/.exec(foo);`, where looking at the previous token
|
1139 |
-
// does not help.
|
1140 |
-
|
1141 |
-
function parseStatement() {
|
1142 |
-
if (tokType === _slash || tokType === _assign && tokVal == "/=")
|
1143 |
-
readToken(true);
|
1144 |
-
|
1145 |
-
var starttype = tokType, node = startNode();
|
1146 |
-
|
1147 |
-
// Most types of statements are recognized by the keyword they
|
1148 |
-
// start with. Many are trivial to parse, some require a bit of
|
1149 |
-
// complexity.
|
1150 |
-
|
1151 |
-
switch (starttype) {
|
1152 |
-
case _break: case _continue:
|
1153 |
-
next();
|
1154 |
-
var isBreak = starttype === _break;
|
1155 |
-
if (eat(_semi) || canInsertSemicolon()) node.label = null;
|
1156 |
-
else if (tokType !== _name) unexpected();
|
1157 |
-
else {
|
1158 |
-
node.label = parseIdent();
|
1159 |
-
semicolon();
|
1160 |
-
}
|
1161 |
-
|
1162 |
-
// Verify that there is an actual destination to break or
|
1163 |
-
// continue to.
|
1164 |
-
for (var i = 0; i < labels.length; ++i) {
|
1165 |
-
var lab = labels[i];
|
1166 |
-
if (node.label == null || lab.name === node.label.name) {
|
1167 |
-
if (lab.kind != null && (isBreak || lab.kind === "loop")) break;
|
1168 |
-
if (node.label && isBreak) break;
|
1169 |
-
}
|
1170 |
-
}
|
1171 |
-
if (i === labels.length) raise(node.start, "Unsyntactic " + starttype.keyword);
|
1172 |
-
return finishNode(node, isBreak ? "BreakStatement" : "ContinueStatement");
|
1173 |
-
|
1174 |
-
case _debugger:
|
1175 |
-
next();
|
1176 |
-
semicolon();
|
1177 |
-
return finishNode(node, "DebuggerStatement");
|
1178 |
-
|
1179 |
-
case _do:
|
1180 |
-
next();
|
1181 |
-
labels.push(loopLabel);
|
1182 |
-
node.body = parseStatement();
|
1183 |
-
labels.pop();
|
1184 |
-
expect(_while);
|
1185 |
-
node.test = parseParenExpression();
|
1186 |
-
semicolon();
|
1187 |
-
return finishNode(node, "DoWhileStatement");
|
1188 |
-
|
1189 |
-
// Disambiguating between a `for` and a `for`/`in` loop is
|
1190 |
-
// non-trivial. Basically, we have to parse the init `var`
|
1191 |
-
// statement or expression, disallowing the `in` operator (see
|
1192 |
-
// the second parameter to `parseExpression`), and then check
|
1193 |
-
// whether the next token is `in`. When there is no init part
|
1194 |
-
// (semicolon immediately after the opening parenthesis), it is
|
1195 |
-
// a regular `for` loop.
|
1196 |
-
|
1197 |
-
case _for:
|
1198 |
-
next();
|
1199 |
-
labels.push(loopLabel);
|
1200 |
-
expect(_parenL);
|
1201 |
-
if (tokType === _semi) return parseFor(node, null);
|
1202 |
-
if (tokType === _var) {
|
1203 |
-
var init = startNode();
|
1204 |
-
next();
|
1205 |
-
parseVar(init, true);
|
1206 |
-
finishNode(init, "VariableDeclaration");
|
1207 |
-
if (init.declarations.length === 1 && eat(_in))
|
1208 |
-
return parseForIn(node, init);
|
1209 |
-
return parseFor(node, init);
|
1210 |
-
}
|
1211 |
-
var init = parseExpression(false, true);
|
1212 |
-
if (eat(_in)) {checkLVal(init); return parseForIn(node, init);}
|
1213 |
-
return parseFor(node, init);
|
1214 |
-
|
1215 |
-
case _function:
|
1216 |
-
next();
|
1217 |
-
return parseFunction(node, true);
|
1218 |
-
|
1219 |
-
case _if:
|
1220 |
-
next();
|
1221 |
-
node.test = parseParenExpression();
|
1222 |
-
node.consequent = parseStatement();
|
1223 |
-
node.alternate = eat(_else) ? parseStatement() : null;
|
1224 |
-
return finishNode(node, "IfStatement");
|
1225 |
-
|
1226 |
-
case _return:
|
1227 |
-
if (!inFunction && !options.allowReturnOutsideFunction)
|
1228 |
-
raise(tokStart, "'return' outside of function");
|
1229 |
-
next();
|
1230 |
-
|
1231 |
-
// In `return` (and `break`/`continue`), the keywords with
|
1232 |
-
// optional arguments, we eagerly look for a semicolon or the
|
1233 |
-
// possibility to insert one.
|
1234 |
-
|
1235 |
-
if (eat(_semi) || canInsertSemicolon()) node.argument = null;
|
1236 |
-
else { node.argument = parseExpression(); semicolon(); }
|
1237 |
-
return finishNode(node, "ReturnStatement");
|
1238 |
-
|
1239 |
-
case _switch:
|
1240 |
-
next();
|
1241 |
-
node.discriminant = parseParenExpression();
|
1242 |
-
node.cases = [];
|
1243 |
-
expect(_braceL);
|
1244 |
-
labels.push(switchLabel);
|
1245 |
-
|
1246 |
-
// Statements under must be grouped (by label) in SwitchCase
|
1247 |
-
// nodes. `cur` is used to keep the node that we are currently
|
1248 |
-
// adding statements to.
|
1249 |
-
|
1250 |
-
for (var cur, sawDefault; tokType != _braceR;) {
|
1251 |
-
if (tokType === _case || tokType === _default) {
|
1252 |
-
var isCase = tokType === _case;
|
1253 |
-
if (cur) finishNode(cur, "SwitchCase");
|
1254 |
-
node.cases.push(cur = startNode());
|
1255 |
-
cur.consequent = [];
|
1256 |
-
next();
|
1257 |
-
if (isCase) cur.test = parseExpression();
|
1258 |
-
else {
|
1259 |
-
if (sawDefault) raise(lastStart, "Multiple default clauses"); sawDefault = true;
|
1260 |
-
cur.test = null;
|
1261 |
-
}
|
1262 |
-
expect(_colon);
|
1263 |
-
} else {
|
1264 |
-
if (!cur) unexpected();
|
1265 |
-
cur.consequent.push(parseStatement());
|
1266 |
-
}
|
1267 |
-
}
|
1268 |
-
if (cur) finishNode(cur, "SwitchCase");
|
1269 |
-
next(); // Closing brace
|
1270 |
-
labels.pop();
|
1271 |
-
return finishNode(node, "SwitchStatement");
|
1272 |
-
|
1273 |
-
case _throw:
|
1274 |
-
next();
|
1275 |
-
if (newline.test(input.slice(lastEnd, tokStart)))
|
1276 |
-
raise(lastEnd, "Illegal newline after throw");
|
1277 |
-
node.argument = parseExpression();
|
1278 |
-
semicolon();
|
1279 |
-
return finishNode(node, "ThrowStatement");
|
1280 |
-
|
1281 |
-
case _try:
|
1282 |
-
next();
|
1283 |
-
node.block = parseBlock();
|
1284 |
-
node.handler = null;
|
1285 |
-
if (tokType === _catch) {
|
1286 |
-
var clause = startNode();
|
1287 |
-
next();
|
1288 |
-
expect(_parenL);
|
1289 |
-
clause.param = parseIdent();
|
1290 |
-
if (strict && isStrictBadIdWord(clause.param.name))
|
1291 |
-
raise(clause.param.start, "Binding " + clause.param.name + " in strict mode");
|
1292 |
-
expect(_parenR);
|
1293 |
-
clause.guard = null;
|
1294 |
-
clause.body = parseBlock();
|
1295 |
-
node.handler = finishNode(clause, "CatchClause");
|
1296 |
-
}
|
1297 |
-
node.guardedHandlers = empty;
|
1298 |
-
node.finalizer = eat(_finally) ? parseBlock() : null;
|
1299 |
-
if (!node.handler && !node.finalizer)
|
1300 |
-
raise(node.start, "Missing catch or finally clause");
|
1301 |
-
return finishNode(node, "TryStatement");
|
1302 |
-
|
1303 |
-
case _var:
|
1304 |
-
next();
|
1305 |
-
parseVar(node);
|
1306 |
-
semicolon();
|
1307 |
-
return finishNode(node, "VariableDeclaration");
|
1308 |
-
|
1309 |
-
case _while:
|
1310 |
-
next();
|
1311 |
-
node.test = parseParenExpression();
|
1312 |
-
labels.push(loopLabel);
|
1313 |
-
node.body = parseStatement();
|
1314 |
-
labels.pop();
|
1315 |
-
return finishNode(node, "WhileStatement");
|
1316 |
-
|
1317 |
-
case _with:
|
1318 |
-
if (strict) raise(tokStart, "'with' in strict mode");
|
1319 |
-
next();
|
1320 |
-
node.object = parseParenExpression();
|
1321 |
-
node.body = parseStatement();
|
1322 |
-
return finishNode(node, "WithStatement");
|
1323 |
-
|
1324 |
-
case _braceL:
|
1325 |
-
return parseBlock();
|
1326 |
-
|
1327 |
-
case _semi:
|
1328 |
-
next();
|
1329 |
-
return finishNode(node, "EmptyStatement");
|
1330 |
-
|
1331 |
-
// If the statement does not start with a statement keyword or a
|
1332 |
-
// brace, it's an ExpressionStatement or LabeledStatement. We
|
1333 |
-
// simply start parsing an expression, and afterwards, if the
|
1334 |
-
// next token is a colon and the expression was a simple
|
1335 |
-
// Identifier node, we switch to interpreting it as a label.
|
1336 |
-
|
1337 |
-
default:
|
1338 |
-
var maybeName = tokVal, expr = parseExpression();
|
1339 |
-
if (starttype === _name && expr.type === "Identifier" && eat(_colon)) {
|
1340 |
-
for (var i = 0; i < labels.length; ++i)
|
1341 |
-
if (labels[i].name === maybeName) raise(expr.start, "Label '" + maybeName + "' is already declared");
|
1342 |
-
var kind = tokType.isLoop ? "loop" : tokType === _switch ? "switch" : null;
|
1343 |
-
labels.push({name: maybeName, kind: kind});
|
1344 |
-
node.body = parseStatement();
|
1345 |
-
labels.pop();
|
1346 |
-
node.label = expr;
|
1347 |
-
return finishNode(node, "LabeledStatement");
|
1348 |
-
} else {
|
1349 |
-
node.expression = expr;
|
1350 |
-
semicolon();
|
1351 |
-
return finishNode(node, "ExpressionStatement");
|
1352 |
-
}
|
1353 |
-
}
|
1354 |
-
}
|
1355 |
-
|
1356 |
-
// Used for constructs like `switch` and `if` that insist on
|
1357 |
-
// parentheses around their expression.
|
1358 |
-
|
1359 |
-
function parseParenExpression() {
|
1360 |
-
expect(_parenL);
|
1361 |
-
var val = parseExpression();
|
1362 |
-
expect(_parenR);
|
1363 |
-
return val;
|
1364 |
-
}
|
1365 |
-
|
1366 |
-
// Parse a semicolon-enclosed block of statements, handling `"use
|
1367 |
-
// strict"` declarations when `allowStrict` is true (used for
|
1368 |
-
// function bodies).
|
1369 |
-
|
1370 |
-
function parseBlock(allowStrict) {
|
1371 |
-
var node = startNode(), first = true, strict = false, oldStrict;
|
1372 |
-
node.body = [];
|
1373 |
-
expect(_braceL);
|
1374 |
-
while (!eat(_braceR)) {
|
1375 |
-
var stmt = parseStatement();
|
1376 |
-
node.body.push(stmt);
|
1377 |
-
if (first && allowStrict && isUseStrict(stmt)) {
|
1378 |
-
oldStrict = strict;
|
1379 |
-
setStrict(strict = true);
|
1380 |
-
}
|
1381 |
-
first = false;
|
1382 |
-
}
|
1383 |
-
if (strict && !oldStrict) setStrict(false);
|
1384 |
-
return finishNode(node, "BlockStatement");
|
1385 |
-
}
|
1386 |
-
|
1387 |
-
// Parse a regular `for` loop. The disambiguation code in
|
1388 |
-
// `parseStatement` will already have parsed the init statement or
|
1389 |
-
// expression.
|
1390 |
-
|
1391 |
-
function parseFor(node, init) {
|
1392 |
-
node.init = init;
|
1393 |
-
expect(_semi);
|
1394 |
-
node.test = tokType === _semi ? null : parseExpression();
|
1395 |
-
expect(_semi);
|
1396 |
-
node.update = tokType === _parenR ? null : parseExpression();
|
1397 |
-
expect(_parenR);
|
1398 |
-
node.body = parseStatement();
|
1399 |
-
labels.pop();
|
1400 |
-
return finishNode(node, "ForStatement");
|
1401 |
-
}
|
1402 |
-
|
1403 |
-
// Parse a `for`/`in` loop.
|
1404 |
-
|
1405 |
-
function parseForIn(node, init) {
|
1406 |
-
node.left = init;
|
1407 |
-
node.right = parseExpression();
|
1408 |
-
expect(_parenR);
|
1409 |
-
node.body = parseStatement();
|
1410 |
-
labels.pop();
|
1411 |
-
return finishNode(node, "ForInStatement");
|
1412 |
-
}
|
1413 |
-
|
1414 |
-
// Parse a list of variable declarations.
|
1415 |
-
|
1416 |
-
function parseVar(node, noIn) {
|
1417 |
-
node.declarations = [];
|
1418 |
-
node.kind = "var";
|
1419 |
-
for (;;) {
|
1420 |
-
var decl = startNode();
|
1421 |
-
decl.id = parseIdent();
|
1422 |
-
if (strict && isStrictBadIdWord(decl.id.name))
|
1423 |
-
raise(decl.id.start, "Binding " + decl.id.name + " in strict mode");
|
1424 |
-
decl.init = eat(_eq) ? parseExpression(true, noIn) : null;
|
1425 |
-
node.declarations.push(finishNode(decl, "VariableDeclarator"));
|
1426 |
-
if (!eat(_comma)) break;
|
1427 |
-
}
|
1428 |
-
return node;
|
1429 |
-
}
|
1430 |
-
|
1431 |
-
// ### Expression parsing
|
1432 |
-
|
1433 |
-
// These nest, from the most general expression type at the top to
|
1434 |
-
// 'atomic', nondivisible expression types at the bottom. Most of
|
1435 |
-
// the functions will simply let the function(s) below them parse,
|
1436 |
-
// and, *if* the syntactic construct they handle is present, wrap
|
1437 |
-
// the AST node that the inner parser gave them in another node.
|
1438 |
-
|
1439 |
-
// Parse a full expression. The arguments are used to forbid comma
|
1440 |
-
// sequences (in argument lists, array literals, or object literals)
|
1441 |
-
// or the `in` operator (in for loops initalization expressions).
|
1442 |
-
|
1443 |
-
function parseExpression(noComma, noIn) {
|
1444 |
-
var expr = parseMaybeAssign(noIn);
|
1445 |
-
if (!noComma && tokType === _comma) {
|
1446 |
-
var node = startNodeFrom(expr);
|
1447 |
-
node.expressions = [expr];
|
1448 |
-
while (eat(_comma)) node.expressions.push(parseMaybeAssign(noIn));
|
1449 |
-
return finishNode(node, "SequenceExpression");
|
1450 |
-
}
|
1451 |
-
return expr;
|
1452 |
-
}
|
1453 |
-
|
1454 |
-
// Parse an assignment expression. This includes applications of
|
1455 |
-
// operators like `+=`.
|
1456 |
-
|
1457 |
-
function parseMaybeAssign(noIn) {
|
1458 |
-
var left = parseMaybeConditional(noIn);
|
1459 |
-
if (tokType.isAssign) {
|
1460 |
-
var node = startNodeFrom(left);
|
1461 |
-
node.operator = tokVal;
|
1462 |
-
node.left = left;
|
1463 |
-
next();
|
1464 |
-
node.right = parseMaybeAssign(noIn);
|
1465 |
-
checkLVal(left);
|
1466 |
-
return finishNode(node, "AssignmentExpression");
|
1467 |
-
}
|
1468 |
-
return left;
|
1469 |
-
}
|
1470 |
-
|
1471 |
-
// Parse a ternary conditional (`?:`) operator.
|
1472 |
-
|
1473 |
-
function parseMaybeConditional(noIn) {
|
1474 |
-
var expr = parseExprOps(noIn);
|
1475 |
-
if (eat(_question)) {
|
1476 |
-
var node = startNodeFrom(expr);
|
1477 |
-
node.test = expr;
|
1478 |
-
node.consequent = parseExpression(true);
|
1479 |
-
expect(_colon);
|
1480 |
-
node.alternate = parseExpression(true, noIn);
|
1481 |
-
return finishNode(node, "ConditionalExpression");
|
1482 |
-
}
|
1483 |
-
return expr;
|
1484 |
-
}
|
1485 |
-
|
1486 |
-
// Start the precedence parser.
|
1487 |
-
|
1488 |
-
function parseExprOps(noIn) {
|
1489 |
-
return parseExprOp(parseMaybeUnary(), -1, noIn);
|
1490 |
-
}
|
1491 |
-
|
1492 |
-
// Parse binary operators with the operator precedence parsing
|
1493 |
-
// algorithm. `left` is the left-hand side of the operator.
|
1494 |
-
// `minPrec` provides context that allows the function to stop and
|
1495 |
-
// defer further parser to one of its callers when it encounters an
|
1496 |
-
// operator that has a lower precedence than the set it is parsing.
|
1497 |
-
|
1498 |
-
function parseExprOp(left, minPrec, noIn) {
|
1499 |
-
var prec = tokType.binop;
|
1500 |
-
if (prec != null && (!noIn || tokType !== _in)) {
|
1501 |
-
if (prec > minPrec) {
|
1502 |
-
var node = startNodeFrom(left);
|
1503 |
-
node.left = left;
|
1504 |
-
node.operator = tokVal;
|
1505 |
-
var op = tokType;
|
1506 |
-
next();
|
1507 |
-
node.right = parseExprOp(parseMaybeUnary(), prec, noIn);
|
1508 |
-
var exprNode = finishNode(node, (op === _logicalOR || op === _logicalAND) ? "LogicalExpression" : "BinaryExpression");
|
1509 |
-
return parseExprOp(exprNode, minPrec, noIn);
|
1510 |
-
}
|
1511 |
-
}
|
1512 |
-
return left;
|
1513 |
-
}
|
1514 |
-
|
1515 |
-
// Parse unary operators, both prefix and postfix.
|
1516 |
-
|
1517 |
-
function parseMaybeUnary() {
|
1518 |
-
if (tokType.prefix) {
|
1519 |
-
var node = startNode(), update = tokType.isUpdate;
|
1520 |
-
node.operator = tokVal;
|
1521 |
-
node.prefix = true;
|
1522 |
-
tokRegexpAllowed = true;
|
1523 |
-
next();
|
1524 |
-
node.argument = parseMaybeUnary();
|
1525 |
-
if (update) checkLVal(node.argument);
|
1526 |
-
else if (strict && node.operator === "delete" &&
|
1527 |
-
node.argument.type === "Identifier")
|
1528 |
-
raise(node.start, "Deleting local variable in strict mode");
|
1529 |
-
return finishNode(node, update ? "UpdateExpression" : "UnaryExpression");
|
1530 |
-
}
|
1531 |
-
var expr = parseExprSubscripts();
|
1532 |
-
while (tokType.postfix && !canInsertSemicolon()) {
|
1533 |
-
var node = startNodeFrom(expr);
|
1534 |
-
node.operator = tokVal;
|
1535 |
-
node.prefix = false;
|
1536 |
-
node.argument = expr;
|
1537 |
-
checkLVal(expr);
|
1538 |
-
next();
|
1539 |
-
expr = finishNode(node, "UpdateExpression");
|
1540 |
-
}
|
1541 |
-
return expr;
|
1542 |
-
}
|
1543 |
-
|
1544 |
-
// Parse call, dot, and `[]`-subscript expressions.
|
1545 |
-
|
1546 |
-
function parseExprSubscripts() {
|
1547 |
-
return parseSubscripts(parseExprAtom());
|
1548 |
-
}
|
1549 |
-
|
1550 |
-
function parseSubscripts(base, noCalls) {
|
1551 |
-
if (eat(_dot)) {
|
1552 |
-
var node = startNodeFrom(base);
|
1553 |
-
node.object = base;
|
1554 |
-
node.property = parseIdent(true);
|
1555 |
-
node.computed = false;
|
1556 |
-
return parseSubscripts(finishNode(node, "MemberExpression"), noCalls);
|
1557 |
-
} else if (eat(_bracketL)) {
|
1558 |
-
var node = startNodeFrom(base);
|
1559 |
-
node.object = base;
|
1560 |
-
node.property = parseExpression();
|
1561 |
-
node.computed = true;
|
1562 |
-
expect(_bracketR);
|
1563 |
-
return parseSubscripts(finishNode(node, "MemberExpression"), noCalls);
|
1564 |
-
} else if (!noCalls && eat(_parenL)) {
|
1565 |
-
var node = startNodeFrom(base);
|
1566 |
-
node.callee = base;
|
1567 |
-
node.arguments = parseExprList(_parenR, false);
|
1568 |
-
return parseSubscripts(finishNode(node, "CallExpression"), noCalls);
|
1569 |
-
} else return base;
|
1570 |
-
}
|
1571 |
-
|
1572 |
-
// Parse an atomic expression — either a single token that is an
|
1573 |
-
// expression, an expression started by a keyword like `function` or
|
1574 |
-
// `new`, or an expression wrapped in punctuation like `()`, `[]`,
|
1575 |
-
// or `{}`.
|
1576 |
-
|
1577 |
-
function parseExprAtom() {
|
1578 |
-
switch (tokType) {
|
1579 |
-
case _this:
|
1580 |
-
var node = startNode();
|
1581 |
-
next();
|
1582 |
-
return finishNode(node, "ThisExpression");
|
1583 |
-
case _name:
|
1584 |
-
return parseIdent();
|
1585 |
-
case _num: case _string: case _regexp:
|
1586 |
-
var node = startNode();
|
1587 |
-
node.value = tokVal;
|
1588 |
-
node.raw = input.slice(tokStart, tokEnd);
|
1589 |
-
next();
|
1590 |
-
return finishNode(node, "Literal");
|
1591 |
-
|
1592 |
-
case _null: case _true: case _false:
|
1593 |
-
var node = startNode();
|
1594 |
-
node.value = tokType.atomValue;
|
1595 |
-
node.raw = tokType.keyword;
|
1596 |
-
next();
|
1597 |
-
return finishNode(node, "Literal");
|
1598 |
-
|
1599 |
-
case _parenL:
|
1600 |
-
var tokStartLoc1 = tokStartLoc, tokStart1 = tokStart;
|
1601 |
-
next();
|
1602 |
-
var val = parseExpression();
|
1603 |
-
val.start = tokStart1;
|
1604 |
-
val.end = tokEnd;
|
1605 |
-
if (options.locations) {
|
1606 |
-
val.loc.start = tokStartLoc1;
|
1607 |
-
val.loc.end = tokEndLoc;
|
1608 |
-
}
|
1609 |
-
if (options.ranges)
|
1610 |
-
val.range = [tokStart1, tokEnd];
|
1611 |
-
expect(_parenR);
|
1612 |
-
return val;
|
1613 |
-
|
1614 |
-
case _bracketL:
|
1615 |
-
var node = startNode();
|
1616 |
-
next();
|
1617 |
-
node.elements = parseExprList(_bracketR, true, true);
|
1618 |
-
return finishNode(node, "ArrayExpression");
|
1619 |
-
|
1620 |
-
case _braceL:
|
1621 |
-
return parseObj();
|
1622 |
-
|
1623 |
-
case _function:
|
1624 |
-
var node = startNode();
|
1625 |
-
next();
|
1626 |
-
return parseFunction(node, false);
|
1627 |
-
|
1628 |
-
case _new:
|
1629 |
-
return parseNew();
|
1630 |
-
|
1631 |
-
default:
|
1632 |
-
unexpected();
|
1633 |
-
}
|
1634 |
-
}
|
1635 |
-
|
1636 |
-
// New's precedence is slightly tricky. It must allow its argument
|
1637 |
-
// to be a `[]` or dot subscript expression, but not a call — at
|
1638 |
-
// least, not without wrapping it in parentheses. Thus, it uses the
|
1639 |
-
|
1640 |
-
function parseNew() {
|
1641 |
-
var node = startNode();
|
1642 |
-
next();
|
1643 |
-
node.callee = parseSubscripts(parseExprAtom(), true);
|
1644 |
-
if (eat(_parenL)) node.arguments = parseExprList(_parenR, false);
|
1645 |
-
else node.arguments = empty;
|
1646 |
-
return finishNode(node, "NewExpression");
|
1647 |
-
}
|
1648 |
-
|
1649 |
-
// Parse an object literal.
|
1650 |
-
|
1651 |
-
function parseObj() {
|
1652 |
-
var node = startNode(), first = true, sawGetSet = false;
|
1653 |
-
node.properties = [];
|
1654 |
-
next();
|
1655 |
-
while (!eat(_braceR)) {
|
1656 |
-
if (!first) {
|
1657 |
-
expect(_comma);
|
1658 |
-
if (options.allowTrailingCommas && eat(_braceR)) break;
|
1659 |
-
} else first = false;
|
1660 |
-
|
1661 |
-
var prop = {key: parsePropertyName()}, isGetSet = false, kind;
|
1662 |
-
if (eat(_colon)) {
|
1663 |
-
prop.value = parseExpression(true);
|
1664 |
-
kind = prop.kind = "init";
|
1665 |
-
} else if (options.ecmaVersion >= 5 && prop.key.type === "Identifier" &&
|
1666 |
-
(prop.key.name === "get" || prop.key.name === "set")) {
|
1667 |
-
isGetSet = sawGetSet = true;
|
1668 |
-
kind = prop.kind = prop.key.name;
|
1669 |
-
prop.key = parsePropertyName();
|
1670 |
-
if (tokType !== _parenL) unexpected();
|
1671 |
-
prop.value = parseFunction(startNode(), false);
|
1672 |
-
} else unexpected();
|
1673 |
-
|
1674 |
-
// getters and setters are not allowed to clash — either with
|
1675 |
-
// each other or with an init property — and in strict mode,
|
1676 |
-
// init properties are also not allowed to be repeated.
|
1677 |
-
|
1678 |
-
if (prop.key.type === "Identifier" && (strict || sawGetSet)) {
|
1679 |
-
for (var i = 0; i < node.properties.length; ++i) {
|
1680 |
-
var other = node.properties[i];
|
1681 |
-
if (other.key.name === prop.key.name) {
|
1682 |
-
var conflict = kind == other.kind || isGetSet && other.kind === "init" ||
|
1683 |
-
kind === "init" && (other.kind === "get" || other.kind === "set");
|
1684 |
-
if (conflict && !strict && kind === "init" && other.kind === "init") conflict = false;
|
1685 |
-
if (conflict) raise(prop.key.start, "Redefinition of property");
|
1686 |
-
}
|
1687 |
-
}
|
1688 |
-
}
|
1689 |
-
node.properties.push(prop);
|
1690 |
-
}
|
1691 |
-
return finishNode(node, "ObjectExpression");
|
1692 |
-
}
|
1693 |
-
|
1694 |
-
function parsePropertyName() {
|
1695 |
-
if (tokType === _num || tokType === _string) return parseExprAtom();
|
1696 |
-
return parseIdent(true);
|
1697 |
-
}
|
1698 |
-
|
1699 |
-
// Parse a function declaration or literal (depending on the
|
1700 |
-
// `isStatement` parameter).
|
1701 |
-
|
1702 |
-
function parseFunction(node, isStatement) {
|
1703 |
-
if (tokType === _name) node.id = parseIdent();
|
1704 |
-
else if (isStatement) unexpected();
|
1705 |
-
else node.id = null;
|
1706 |
-
node.params = [];
|
1707 |
-
var first = true;
|
1708 |
-
expect(_parenL);
|
1709 |
-
while (!eat(_parenR)) {
|
1710 |
-
if (!first) expect(_comma); else first = false;
|
1711 |
-
node.params.push(parseIdent());
|
1712 |
-
}
|
1713 |
-
|
1714 |
-
// Start a new scope with regard to labels and the `inFunction`
|
1715 |
-
// flag (restore them to their old value afterwards).
|
1716 |
-
var oldInFunc = inFunction, oldLabels = labels;
|
1717 |
-
inFunction = true; labels = [];
|
1718 |
-
node.body = parseBlock(true);
|
1719 |
-
inFunction = oldInFunc; labels = oldLabels;
|
1720 |
-
|
1721 |
-
// If this is a strict mode function, verify that argument names
|
1722 |
-
// are not repeated, and it does not try to bind the words `eval`
|
1723 |
-
// or `arguments`.
|
1724 |
-
if (strict || node.body.body.length && isUseStrict(node.body.body[0])) {
|
1725 |
-
for (var i = node.id ? -1 : 0; i < node.params.length; ++i) {
|
1726 |
-
var id = i < 0 ? node.id : node.params[i];
|
1727 |
-
if (isStrictReservedWord(id.name) || isStrictBadIdWord(id.name))
|
1728 |
-
raise(id.start, "Defining '" + id.name + "' in strict mode");
|
1729 |
-
if (i >= 0) for (var j = 0; j < i; ++j) if (id.name === node.params[j].name)
|
1730 |
-
raise(id.start, "Argument name clash in strict mode");
|
1731 |
-
}
|
1732 |
-
}
|
1733 |
-
|
1734 |
-
return finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression");
|
1735 |
-
}
|
1736 |
-
|
1737 |
-
// Parses a comma-separated list of expressions, and returns them as
|
1738 |
-
// an array. `close` is the token type that ends the list, and
|
1739 |
-
// `allowEmpty` can be turned on to allow subsequent commas with
|
1740 |
-
// nothing in between them to be parsed as `null` (which is needed
|
1741 |
-
// for array literals).
|
1742 |
-
|
1743 |
-
function parseExprList(close, allowTrailingComma, allowEmpty) {
|
1744 |
-
var elts = [], first = true;
|
1745 |
-
while (!eat(close)) {
|
1746 |
-
if (!first) {
|
1747 |
-
expect(_comma);
|
1748 |
-
if (allowTrailingComma && options.allowTrailingCommas && eat(close)) break;
|
1749 |
-
} else first = false;
|
1750 |
-
|
1751 |
-
if (allowEmpty && tokType === _comma) elts.push(null);
|
1752 |
-
else elts.push(parseExpression(true));
|
1753 |
-
}
|
1754 |
-
return elts;
|
1755 |
-
}
|
1756 |
-
|
1757 |
-
// Parse the next token as an identifier. If `liberal` is true (used
|
1758 |
-
// when parsing properties), it will also convert keywords into
|
1759 |
-
// identifiers.
|
1760 |
-
|
1761 |
-
function parseIdent(liberal) {
|
1762 |
-
var node = startNode();
|
1763 |
-
if (liberal && options.forbidReserved == "everywhere") liberal = false;
|
1764 |
-
if (tokType === _name) {
|
1765 |
-
if (!liberal &&
|
1766 |
-
(options.forbidReserved &&
|
1767 |
-
(options.ecmaVersion === 3 ? isReservedWord3 : isReservedWord5)(tokVal) ||
|
1768 |
-
strict && isStrictReservedWord(tokVal)) &&
|
1769 |
-
input.slice(tokStart, tokEnd).indexOf("\\") == -1)
|
1770 |
-
raise(tokStart, "The keyword '" + tokVal + "' is reserved");
|
1771 |
-
node.name = tokVal;
|
1772 |
-
} else if (liberal && tokType.keyword) {
|
1773 |
-
node.name = tokType.keyword;
|
1774 |
-
} else {
|
1775 |
-
unexpected();
|
1776 |
-
}
|
1777 |
-
tokRegexpAllowed = false;
|
1778 |
-
next();
|
1779 |
-
return finishNode(node, "Identifier");
|
1780 |
-
}
|
1781 |
-
|
1782 |
-
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/lint/lint.js
DELETED
@@ -1,166 +0,0 @@
|
|
1 |
-
/*
|
2 |
-
Simple linter, based on the Acorn [1] parser module
|
3 |
-
|
4 |
-
All of the existing linters either cramp my style or have huge
|
5 |
-
dependencies (Closure). So here's a very simple, non-invasive one
|
6 |
-
that only spots
|
7 |
-
|
8 |
-
- missing semicolons and trailing commas
|
9 |
-
- variables or properties that are reserved words
|
10 |
-
- assigning to a variable you didn't declare
|
11 |
-
- access to non-whitelisted globals
|
12 |
-
(use a '// declare global: foo, bar' comment to declare extra
|
13 |
-
globals in a file)
|
14 |
-
|
15 |
-
[1]: https://github.com/marijnh/acorn/
|
16 |
-
*/
|
17 |
-
|
18 |
-
var topAllowedGlobals = Object.create(null);
|
19 |
-
("Error RegExp Number String Array Function Object Math Date undefined " +
|
20 |
-
"parseInt parseFloat Infinity NaN isNaN " +
|
21 |
-
"window document navigator prompt alert confirm console " +
|
22 |
-
"screen FileReader Worker postMessage importScripts " +
|
23 |
-
"setInterval clearInterval setTimeout clearTimeout " +
|
24 |
-
"CodeMirror " +
|
25 |
-
"test exports require module define requirejs")
|
26 |
-
.split(" ").forEach(function(n) { topAllowedGlobals[n] = true; });
|
27 |
-
|
28 |
-
var fs = require("fs"), acorn = require("./acorn.js"), walk = require("./walk.js");
|
29 |
-
|
30 |
-
var scopePasser = walk.make({
|
31 |
-
ScopeBody: function(node, prev, c) { c(node, node.scope); }
|
32 |
-
});
|
33 |
-
|
34 |
-
var cBlob = /^\/\/ CodeMirror, copyright \(c\) by Marijn Haverbeke and others\n\/\/ Distributed under an MIT license: http:\/\/codemirror.net\/LICENSE\n\n/;
|
35 |
-
|
36 |
-
function checkFile(fileName) {
|
37 |
-
var file = fs.readFileSync(fileName, "utf8"), notAllowed;
|
38 |
-
if (notAllowed = file.match(/[\x00-\x08\x0b\x0c\x0e-\x19\uFEFF\t]|[ \t]\n/)) {
|
39 |
-
var msg;
|
40 |
-
if (notAllowed[0] == "\t") msg = "Found tab character";
|
41 |
-
else if (notAllowed[0].indexOf("\n") > -1) msg = "Trailing whitespace";
|
42 |
-
else msg = "Undesirable character " + notAllowed[0].charCodeAt(0);
|
43 |
-
var info = acorn.getLineInfo(file, notAllowed.index);
|
44 |
-
fail(msg + " at line " + info.line + ", column " + info.column, {source: fileName});
|
45 |
-
}
|
46 |
-
|
47 |
-
if (!cBlob.test(file))
|
48 |
-
fail("Missing license blob", {source: fileName});
|
49 |
-
|
50 |
-
var globalsSeen = Object.create(null);
|
51 |
-
|
52 |
-
try {
|
53 |
-
var parsed = acorn.parse(file, {
|
54 |
-
locations: true,
|
55 |
-
ecmaVersion: 3,
|
56 |
-
strictSemicolons: true,
|
57 |
-
allowTrailingCommas: false,
|
58 |
-
forbidReserved: "everywhere",
|
59 |
-
sourceFile: fileName
|
60 |
-
});
|
61 |
-
} catch (e) {
|
62 |
-
fail(e.message, {source: fileName});
|
63 |
-
return;
|
64 |
-
}
|
65 |
-
|
66 |
-
var scopes = [];
|
67 |
-
|
68 |
-
walk.simple(parsed, {
|
69 |
-
ScopeBody: function(node, scope) {
|
70 |
-
node.scope = scope;
|
71 |
-
scopes.push(scope);
|
72 |
-
}
|
73 |
-
}, walk.scopeVisitor, {vars: Object.create(null)});
|
74 |
-
|
75 |
-
var ignoredGlobals = Object.create(null);
|
76 |
-
|
77 |
-
function inScope(name, scope) {
|
78 |
-
for (var cur = scope; cur; cur = cur.prev)
|
79 |
-
if (name in cur.vars) return true;
|
80 |
-
}
|
81 |
-
function checkLHS(node, scope) {
|
82 |
-
if (node.type == "Identifier" && !(node.name in ignoredGlobals) &&
|
83 |
-
!inScope(node.name, scope)) {
|
84 |
-
ignoredGlobals[node.name] = true;
|
85 |
-
fail("Assignment to global variable", node.loc);
|
86 |
-
}
|
87 |
-
}
|
88 |
-
|
89 |
-
walk.simple(parsed, {
|
90 |
-
UpdateExpression: function(node, scope) {checkLHS(node.argument, scope);},
|
91 |
-
AssignmentExpression: function(node, scope) {checkLHS(node.left, scope);},
|
92 |
-
Identifier: function(node, scope) {
|
93 |
-
if (node.name == "arguments") return;
|
94 |
-
// Mark used identifiers
|
95 |
-
for (var cur = scope; cur; cur = cur.prev)
|
96 |
-
if (node.name in cur.vars) {
|
97 |
-
cur.vars[node.name].used = true;
|
98 |
-
return;
|
99 |
-
}
|
100 |
-
globalsSeen[node.name] = node.loc;
|
101 |
-
},
|
102 |
-
FunctionExpression: function(node) {
|
103 |
-
if (node.id) fail("Named function expression", node.loc);
|
104 |
-
},
|
105 |
-
ForStatement: function(node) {
|
106 |
-
checkReusedIndex(node);
|
107 |
-
},
|
108 |
-
MemberExpression: function(node) {
|
109 |
-
if (node.object.type == "Identifier" && node.object.name == "console" && !node.computed)
|
110 |
-
fail("Found console." + node.property.name, node.loc);
|
111 |
-
},
|
112 |
-
DebuggerStatement: function(node) {
|
113 |
-
fail("Found debugger statement", node.loc);
|
114 |
-
}
|
115 |
-
}, scopePasser);
|
116 |
-
|
117 |
-
function checkReusedIndex(node) {
|
118 |
-
if (!node.init || node.init.type != "VariableDeclaration") return;
|
119 |
-
var name = node.init.declarations[0].id.name;
|
120 |
-
walk.recursive(node.body, null, {
|
121 |
-
Function: function() {},
|
122 |
-
VariableDeclaration: function(node, st, c) {
|
123 |
-
for (var i = 0; i < node.declarations.length; i++)
|
124 |
-
if (node.declarations[i].id.name == name)
|
125 |
-
fail("redefined loop variable", node.declarations[i].id.loc);
|
126 |
-
walk.base.VariableDeclaration(node, st, c);
|
127 |
-
}
|
128 |
-
});
|
129 |
-
}
|
130 |
-
|
131 |
-
var allowedGlobals = Object.create(topAllowedGlobals), m;
|
132 |
-
if (m = file.match(/\/\/ declare global:\s+(.*)/))
|
133 |
-
m[1].split(/,\s*/g).forEach(function(n) { allowedGlobals[n] = true; });
|
134 |
-
for (var glob in globalsSeen)
|
135 |
-
if (!(glob in allowedGlobals))
|
136 |
-
fail("Access to global variable " + glob + ". Add a '// declare global: " + glob +
|
137 |
-
"' comment or add this variable in test/lint/lint.js.", globalsSeen[glob]);
|
138 |
-
|
139 |
-
for (var i = 0; i < scopes.length; ++i) {
|
140 |
-
var scope = scopes[i];
|
141 |
-
for (var name in scope.vars) {
|
142 |
-
var info = scope.vars[name];
|
143 |
-
if (!info.used && info.type != "catch clause" && info.type != "function name" && name.charAt(0) != "_")
|
144 |
-
fail("Unused " + info.type + " " + name, info.node.loc);
|
145 |
-
}
|
146 |
-
}
|
147 |
-
}
|
148 |
-
|
149 |
-
var failed = false;
|
150 |
-
function fail(msg, pos) {
|
151 |
-
if (pos.start) msg += " (" + pos.start.line + ":" + pos.start.column + ")";
|
152 |
-
console.log(pos.source + ": " + msg);
|
153 |
-
failed = true;
|
154 |
-
}
|
155 |
-
|
156 |
-
function checkDir(dir) {
|
157 |
-
fs.readdirSync(dir).forEach(function(file) {
|
158 |
-
var fname = dir + "/" + file;
|
159 |
-
if (/\.js$/.test(file)) checkFile(fname);
|
160 |
-
else if (fs.lstatSync(fname).isDirectory()) checkDir(fname);
|
161 |
-
});
|
162 |
-
}
|
163 |
-
|
164 |
-
exports.checkDir = checkDir;
|
165 |
-
exports.checkFile = checkFile;
|
166 |
-
exports.success = function() { return !failed; };
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/lint/walk.js
DELETED
@@ -1,313 +0,0 @@
|
|
1 |
-
// AST walker module for Mozilla Parser API compatible trees
|
2 |
-
|
3 |
-
(function(mod) {
|
4 |
-
if (typeof exports == "object" && typeof module == "object") return mod(exports); // CommonJS
|
5 |
-
if (typeof define == "function" && define.amd) return define(["exports"], mod); // AMD
|
6 |
-
mod((this.acorn || (this.acorn = {})).walk = {}); // Plain browser env
|
7 |
-
})(function(exports) {
|
8 |
-
"use strict";
|
9 |
-
|
10 |
-
// A simple walk is one where you simply specify callbacks to be
|
11 |
-
// called on specific nodes. The last two arguments are optional. A
|
12 |
-
// simple use would be
|
13 |
-
//
|
14 |
-
// walk.simple(myTree, {
|
15 |
-
// Expression: function(node) { ... }
|
16 |
-
// });
|
17 |
-
//
|
18 |
-
// to do something with all expressions. All Parser API node types
|
19 |
-
// can be used to identify node types, as well as Expression,
|
20 |
-
// Statement, and ScopeBody, which denote categories of nodes.
|
21 |
-
//
|
22 |
-
// The base argument can be used to pass a custom (recursive)
|
23 |
-
// walker, and state can be used to give this walked an initial
|
24 |
-
// state.
|
25 |
-
exports.simple = function(node, visitors, base, state) {
|
26 |
-
if (!base) base = exports.base;
|
27 |
-
function c(node, st, override) {
|
28 |
-
var type = override || node.type, found = visitors[type];
|
29 |
-
base[type](node, st, c);
|
30 |
-
if (found) found(node, st);
|
31 |
-
}
|
32 |
-
c(node, state);
|
33 |
-
};
|
34 |
-
|
35 |
-
// A recursive walk is one where your functions override the default
|
36 |
-
// walkers. They can modify and replace the state parameter that's
|
37 |
-
// threaded through the walk, and can opt how and whether to walk
|
38 |
-
// their child nodes (by calling their third argument on these
|
39 |
-
// nodes).
|
40 |
-
exports.recursive = function(node, state, funcs, base) {
|
41 |
-
var visitor = funcs ? exports.make(funcs, base) : base;
|
42 |
-
function c(node, st, override) {
|
43 |
-
visitor[override || node.type](node, st, c);
|
44 |
-
}
|
45 |
-
c(node, state);
|
46 |
-
};
|
47 |
-
|
48 |
-
function makeTest(test) {
|
49 |
-
if (typeof test == "string")
|
50 |
-
return function(type) { return type == test; };
|
51 |
-
else if (!test)
|
52 |
-
return function() { return true; };
|
53 |
-
else
|
54 |
-
return test;
|
55 |
-
}
|
56 |
-
|
57 |
-
function Found(node, state) { this.node = node; this.state = state; }
|
58 |
-
|
59 |
-
// Find a node with a given start, end, and type (all are optional,
|
60 |
-
// null can be used as wildcard). Returns a {node, state} object, or
|
61 |
-
// undefined when it doesn't find a matching node.
|
62 |
-
exports.findNodeAt = function(node, start, end, test, base, state) {
|
63 |
-
test = makeTest(test);
|
64 |
-
try {
|
65 |
-
if (!base) base = exports.base;
|
66 |
-
var c = function(node, st, override) {
|
67 |
-
var type = override || node.type;
|
68 |
-
if ((start == null || node.start <= start) &&
|
69 |
-
(end == null || node.end >= end))
|
70 |
-
base[type](node, st, c);
|
71 |
-
if (test(type, node) &&
|
72 |
-
(start == null || node.start == start) &&
|
73 |
-
(end == null || node.end == end))
|
74 |
-
throw new Found(node, st);
|
75 |
-
};
|
76 |
-
c(node, state);
|
77 |
-
} catch (e) {
|
78 |
-
if (e instanceof Found) return e;
|
79 |
-
throw e;
|
80 |
-
}
|
81 |
-
};
|
82 |
-
|
83 |
-
// Find the innermost node of a given type that contains the given
|
84 |
-
// position. Interface similar to findNodeAt.
|
85 |
-
exports.findNodeAround = function(node, pos, test, base, state) {
|
86 |
-
test = makeTest(test);
|
87 |
-
try {
|
88 |
-
if (!base) base = exports.base;
|
89 |
-
var c = function(node, st, override) {
|
90 |
-
var type = override || node.type;
|
91 |
-
if (node.start > pos || node.end < pos) return;
|
92 |
-
base[type](node, st, c);
|
93 |
-
if (test(type, node)) throw new Found(node, st);
|
94 |
-
};
|
95 |
-
c(node, state);
|
96 |
-
} catch (e) {
|
97 |
-
if (e instanceof Found) return e;
|
98 |
-
throw e;
|
99 |
-
}
|
100 |
-
};
|
101 |
-
|
102 |
-
// Find the outermost matching node after a given position.
|
103 |
-
exports.findNodeAfter = function(node, pos, test, base, state) {
|
104 |
-
test = makeTest(test);
|
105 |
-
try {
|
106 |
-
if (!base) base = exports.base;
|
107 |
-
var c = function(node, st, override) {
|
108 |
-
if (node.end < pos) return;
|
109 |
-
var type = override || node.type;
|
110 |
-
if (node.start >= pos && test(type, node)) throw new Found(node, st);
|
111 |
-
base[type](node, st, c);
|
112 |
-
};
|
113 |
-
c(node, state);
|
114 |
-
} catch (e) {
|
115 |
-
if (e instanceof Found) return e;
|
116 |
-
throw e;
|
117 |
-
}
|
118 |
-
};
|
119 |
-
|
120 |
-
// Find the outermost matching node before a given position.
|
121 |
-
exports.findNodeBefore = function(node, pos, test, base, state) {
|
122 |
-
test = makeTest(test);
|
123 |
-
if (!base) base = exports.base;
|
124 |
-
var max;
|
125 |
-
var c = function(node, st, override) {
|
126 |
-
if (node.start > pos) return;
|
127 |
-
var type = override || node.type;
|
128 |
-
if (node.end <= pos && (!max || max.node.end < node.end) && test(type, node))
|
129 |
-
max = new Found(node, st);
|
130 |
-
base[type](node, st, c);
|
131 |
-
};
|
132 |
-
c(node, state);
|
133 |
-
return max;
|
134 |
-
};
|
135 |
-
|
136 |
-
// Used to create a custom walker. Will fill in all missing node
|
137 |
-
// type properties with the defaults.
|
138 |
-
exports.make = function(funcs, base) {
|
139 |
-
if (!base) base = exports.base;
|
140 |
-
var visitor = {};
|
141 |
-
for (var type in base) visitor[type] = base[type];
|
142 |
-
for (var type in funcs) visitor[type] = funcs[type];
|
143 |
-
return visitor;
|
144 |
-
};
|
145 |
-
|
146 |
-
function skipThrough(node, st, c) { c(node, st); }
|
147 |
-
function ignore(_node, _st, _c) {}
|
148 |
-
|
149 |
-
// Node walkers.
|
150 |
-
|
151 |
-
var base = exports.base = {};
|
152 |
-
base.Program = base.BlockStatement = function(node, st, c) {
|
153 |
-
for (var i = 0; i < node.body.length; ++i)
|
154 |
-
c(node.body[i], st, "Statement");
|
155 |
-
};
|
156 |
-
base.Statement = skipThrough;
|
157 |
-
base.EmptyStatement = ignore;
|
158 |
-
base.ExpressionStatement = function(node, st, c) {
|
159 |
-
c(node.expression, st, "Expression");
|
160 |
-
};
|
161 |
-
base.IfStatement = function(node, st, c) {
|
162 |
-
c(node.test, st, "Expression");
|
163 |
-
c(node.consequent, st, "Statement");
|
164 |
-
if (node.alternate) c(node.alternate, st, "Statement");
|
165 |
-
};
|
166 |
-
base.LabeledStatement = function(node, st, c) {
|
167 |
-
c(node.body, st, "Statement");
|
168 |
-
};
|
169 |
-
base.BreakStatement = base.ContinueStatement = ignore;
|
170 |
-
base.WithStatement = function(node, st, c) {
|
171 |
-
c(node.object, st, "Expression");
|
172 |
-
c(node.body, st, "Statement");
|
173 |
-
};
|
174 |
-
base.SwitchStatement = function(node, st, c) {
|
175 |
-
c(node.discriminant, st, "Expression");
|
176 |
-
for (var i = 0; i < node.cases.length; ++i) {
|
177 |
-
var cs = node.cases[i];
|
178 |
-
if (cs.test) c(cs.test, st, "Expression");
|
179 |
-
for (var j = 0; j < cs.consequent.length; ++j)
|
180 |
-
c(cs.consequent[j], st, "Statement");
|
181 |
-
}
|
182 |
-
};
|
183 |
-
base.ReturnStatement = function(node, st, c) {
|
184 |
-
if (node.argument) c(node.argument, st, "Expression");
|
185 |
-
};
|
186 |
-
base.ThrowStatement = function(node, st, c) {
|
187 |
-
c(node.argument, st, "Expression");
|
188 |
-
};
|
189 |
-
base.TryStatement = function(node, st, c) {
|
190 |
-
c(node.block, st, "Statement");
|
191 |
-
if (node.handler) c(node.handler.body, st, "ScopeBody");
|
192 |
-
if (node.finalizer) c(node.finalizer, st, "Statement");
|
193 |
-
};
|
194 |
-
base.WhileStatement = function(node, st, c) {
|
195 |
-
c(node.test, st, "Expression");
|
196 |
-
c(node.body, st, "Statement");
|
197 |
-
};
|
198 |
-
base.DoWhileStatement = base.WhileStatement;
|
199 |
-
base.ForStatement = function(node, st, c) {
|
200 |
-
if (node.init) c(node.init, st, "ForInit");
|
201 |
-
if (node.test) c(node.test, st, "Expression");
|
202 |
-
if (node.update) c(node.update, st, "Expression");
|
203 |
-
c(node.body, st, "Statement");
|
204 |
-
};
|
205 |
-
base.ForInStatement = function(node, st, c) {
|
206 |
-
c(node.left, st, "ForInit");
|
207 |
-
c(node.right, st, "Expression");
|
208 |
-
c(node.body, st, "Statement");
|
209 |
-
};
|
210 |
-
base.ForInit = function(node, st, c) {
|
211 |
-
if (node.type == "VariableDeclaration") c(node, st);
|
212 |
-
else c(node, st, "Expression");
|
213 |
-
};
|
214 |
-
base.DebuggerStatement = ignore;
|
215 |
-
|
216 |
-
base.FunctionDeclaration = function(node, st, c) {
|
217 |
-
c(node, st, "Function");
|
218 |
-
};
|
219 |
-
base.VariableDeclaration = function(node, st, c) {
|
220 |
-
for (var i = 0; i < node.declarations.length; ++i) {
|
221 |
-
var decl = node.declarations[i];
|
222 |
-
if (decl.init) c(decl.init, st, "Expression");
|
223 |
-
}
|
224 |
-
};
|
225 |
-
|
226 |
-
base.Function = function(node, st, c) {
|
227 |
-
c(node.body, st, "ScopeBody");
|
228 |
-
};
|
229 |
-
base.ScopeBody = function(node, st, c) {
|
230 |
-
c(node, st, "Statement");
|
231 |
-
};
|
232 |
-
|
233 |
-
base.Expression = skipThrough;
|
234 |
-
base.ThisExpression = ignore;
|
235 |
-
base.ArrayExpression = function(node, st, c) {
|
236 |
-
for (var i = 0; i < node.elements.length; ++i) {
|
237 |
-
var elt = node.elements[i];
|
238 |
-
if (elt) c(elt, st, "Expression");
|
239 |
-
}
|
240 |
-
};
|
241 |
-
base.ObjectExpression = function(node, st, c) {
|
242 |
-
for (var i = 0; i < node.properties.length; ++i)
|
243 |
-
c(node.properties[i].value, st, "Expression");
|
244 |
-
};
|
245 |
-
base.FunctionExpression = base.FunctionDeclaration;
|
246 |
-
base.SequenceExpression = function(node, st, c) {
|
247 |
-
for (var i = 0; i < node.expressions.length; ++i)
|
248 |
-
c(node.expressions[i], st, "Expression");
|
249 |
-
};
|
250 |
-
base.UnaryExpression = base.UpdateExpression = function(node, st, c) {
|
251 |
-
c(node.argument, st, "Expression");
|
252 |
-
};
|
253 |
-
base.BinaryExpression = base.AssignmentExpression = base.LogicalExpression = function(node, st, c) {
|
254 |
-
c(node.left, st, "Expression");
|
255 |
-
c(node.right, st, "Expression");
|
256 |
-
};
|
257 |
-
base.ConditionalExpression = function(node, st, c) {
|
258 |
-
c(node.test, st, "Expression");
|
259 |
-
c(node.consequent, st, "Expression");
|
260 |
-
c(node.alternate, st, "Expression");
|
261 |
-
};
|
262 |
-
base.NewExpression = base.CallExpression = function(node, st, c) {
|
263 |
-
c(node.callee, st, "Expression");
|
264 |
-
if (node.arguments) for (var i = 0; i < node.arguments.length; ++i)
|
265 |
-
c(node.arguments[i], st, "Expression");
|
266 |
-
};
|
267 |
-
base.MemberExpression = function(node, st, c) {
|
268 |
-
c(node.object, st, "Expression");
|
269 |
-
if (node.computed) c(node.property, st, "Expression");
|
270 |
-
};
|
271 |
-
base.Identifier = base.Literal = ignore;
|
272 |
-
|
273 |
-
// A custom walker that keeps track of the scope chain and the
|
274 |
-
// variables defined in it.
|
275 |
-
function makeScope(prev, isCatch) {
|
276 |
-
return {vars: Object.create(null), prev: prev, isCatch: isCatch};
|
277 |
-
}
|
278 |
-
function normalScope(scope) {
|
279 |
-
while (scope.isCatch) scope = scope.prev;
|
280 |
-
return scope;
|
281 |
-
}
|
282 |
-
exports.scopeVisitor = exports.make({
|
283 |
-
Function: function(node, scope, c) {
|
284 |
-
var inner = makeScope(scope);
|
285 |
-
for (var i = 0; i < node.params.length; ++i)
|
286 |
-
inner.vars[node.params[i].name] = {type: "argument", node: node.params[i]};
|
287 |
-
if (node.id) {
|
288 |
-
var decl = node.type == "FunctionDeclaration";
|
289 |
-
(decl ? normalScope(scope) : inner).vars[node.id.name] =
|
290 |
-
{type: decl ? "function" : "function name", node: node.id};
|
291 |
-
}
|
292 |
-
c(node.body, inner, "ScopeBody");
|
293 |
-
},
|
294 |
-
TryStatement: function(node, scope, c) {
|
295 |
-
c(node.block, scope, "Statement");
|
296 |
-
if (node.handler) {
|
297 |
-
var inner = makeScope(scope, true);
|
298 |
-
inner.vars[node.handler.param.name] = {type: "catch clause", node: node.handler.param};
|
299 |
-
c(node.handler.body, inner, "ScopeBody");
|
300 |
-
}
|
301 |
-
if (node.finalizer) c(node.finalizer, scope, "Statement");
|
302 |
-
},
|
303 |
-
VariableDeclaration: function(node, scope, c) {
|
304 |
-
var target = normalScope(scope);
|
305 |
-
for (var i = 0; i < node.declarations.length; ++i) {
|
306 |
-
var decl = node.declarations[i];
|
307 |
-
target.vars[decl.id.name] = {type: "var", node: decl.id};
|
308 |
-
if (decl.init) c(decl.init, scope, "Expression");
|
309 |
-
}
|
310 |
-
}
|
311 |
-
});
|
312 |
-
|
313 |
-
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/mode_test.css
DELETED
@@ -1,23 +0,0 @@
|
|
1 |
-
.mt-output .mt-token {
|
2 |
-
border: 1px solid #ddd;
|
3 |
-
white-space: pre;
|
4 |
-
font-family: "Consolas", monospace;
|
5 |
-
text-align: center;
|
6 |
-
}
|
7 |
-
|
8 |
-
.mt-output .mt-style {
|
9 |
-
font-size: x-small;
|
10 |
-
}
|
11 |
-
|
12 |
-
.mt-output .mt-state {
|
13 |
-
font-size: x-small;
|
14 |
-
vertical-align: top;
|
15 |
-
}
|
16 |
-
|
17 |
-
.mt-output .mt-state-row {
|
18 |
-
display: none;
|
19 |
-
}
|
20 |
-
|
21 |
-
.mt-state-unhide .mt-output .mt-state-row {
|
22 |
-
display: table-row;
|
23 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/mode_test.js
DELETED
@@ -1,192 +0,0 @@
|
|
1 |
-
/**
|
2 |
-
* Helper to test CodeMirror highlighting modes. It pretty prints output of the
|
3 |
-
* highlighter and can check against expected styles.
|
4 |
-
*
|
5 |
-
* Mode tests are registered by calling test.mode(testName, mode,
|
6 |
-
* tokens), where mode is a mode object as returned by
|
7 |
-
* CodeMirror.getMode, and tokens is an array of lines that make up
|
8 |
-
* the test.
|
9 |
-
*
|
10 |
-
* These lines are strings, in which styled stretches of code are
|
11 |
-
* enclosed in brackets `[]`, and prefixed by their style. For
|
12 |
-
* example, `[keyword if]`. Brackets in the code itself must be
|
13 |
-
* duplicated to prevent them from being interpreted as token
|
14 |
-
* boundaries. For example `a[[i]]` for `a[i]`. If a token has
|
15 |
-
* multiple styles, the styles must be separated by ampersands, for
|
16 |
-
* example `[tag&error </hmtl>]`.
|
17 |
-
*
|
18 |
-
* See the test.js files in the css, markdown, gfm, and stex mode
|
19 |
-
* directories for examples.
|
20 |
-
*/
|
21 |
-
(function() {
|
22 |
-
function findSingle(str, pos, ch) {
|
23 |
-
for (;;) {
|
24 |
-
var found = str.indexOf(ch, pos);
|
25 |
-
if (found == -1) return null;
|
26 |
-
if (str.charAt(found + 1) != ch) return found;
|
27 |
-
pos = found + 2;
|
28 |
-
}
|
29 |
-
}
|
30 |
-
|
31 |
-
var styleName = /[\w&-_]+/g;
|
32 |
-
function parseTokens(strs) {
|
33 |
-
var tokens = [], plain = "";
|
34 |
-
for (var i = 0; i < strs.length; ++i) {
|
35 |
-
if (i) plain += "\n";
|
36 |
-
var str = strs[i], pos = 0;
|
37 |
-
while (pos < str.length) {
|
38 |
-
var style = null, text;
|
39 |
-
if (str.charAt(pos) == "[" && str.charAt(pos+1) != "[") {
|
40 |
-
styleName.lastIndex = pos + 1;
|
41 |
-
var m = styleName.exec(str);
|
42 |
-
style = m[0].replace(/&/g, " ");
|
43 |
-
var textStart = pos + style.length + 2;
|
44 |
-
var end = findSingle(str, textStart, "]");
|
45 |
-
if (end == null) throw new Error("Unterminated token at " + pos + " in '" + str + "'" + style);
|
46 |
-
text = str.slice(textStart, end);
|
47 |
-
pos = end + 1;
|
48 |
-
} else {
|
49 |
-
var end = findSingle(str, pos, "[");
|
50 |
-
if (end == null) end = str.length;
|
51 |
-
text = str.slice(pos, end);
|
52 |
-
pos = end;
|
53 |
-
}
|
54 |
-
text = text.replace(/\[\[|\]\]/g, function(s) {return s.charAt(0);});
|
55 |
-
tokens.push({style: style, text: text});
|
56 |
-
plain += text;
|
57 |
-
}
|
58 |
-
}
|
59 |
-
return {tokens: tokens, plain: plain};
|
60 |
-
}
|
61 |
-
|
62 |
-
test.mode = function(name, mode, tokens, modeName) {
|
63 |
-
var data = parseTokens(tokens);
|
64 |
-
return test((modeName || mode.name) + "_" + name, function() {
|
65 |
-
return compare(data.plain, data.tokens, mode);
|
66 |
-
});
|
67 |
-
};
|
68 |
-
|
69 |
-
function esc(str) {
|
70 |
-
return str.replace('&', '&').replace('<', '<').replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
71 |
-
;
|
72 |
-
}
|
73 |
-
|
74 |
-
function compare(text, expected, mode) {
|
75 |
-
|
76 |
-
var expectedOutput = [];
|
77 |
-
for (var i = 0; i < expected.length; ++i) {
|
78 |
-
var sty = expected[i].style;
|
79 |
-
if (sty && sty.indexOf(" ")) sty = sty.split(' ').sort().join(' ');
|
80 |
-
expectedOutput.push({style: sty, text: expected[i].text});
|
81 |
-
}
|
82 |
-
|
83 |
-
var observedOutput = highlight(text, mode);
|
84 |
-
|
85 |
-
var s = "";
|
86 |
-
var diff = highlightOutputsDifferent(expectedOutput, observedOutput);
|
87 |
-
if (diff != null) {
|
88 |
-
s += '<div class="mt-test mt-fail">';
|
89 |
-
s += '<pre>' + esc(text) + '</pre>';
|
90 |
-
s += '<div class="cm-s-default">';
|
91 |
-
s += 'expected:';
|
92 |
-
s += prettyPrintOutputTable(expectedOutput, diff);
|
93 |
-
s += 'observed: [<a onclick="this.parentElement.className+=\' mt-state-unhide\'">display states</a>]';
|
94 |
-
s += prettyPrintOutputTable(observedOutput, diff);
|
95 |
-
s += '</div>';
|
96 |
-
s += '</div>';
|
97 |
-
}
|
98 |
-
if (observedOutput.indentFailures) {
|
99 |
-
for (var i = 0; i < observedOutput.indentFailures.length; i++)
|
100 |
-
s += "<div class='mt-test mt-fail'>" + esc(observedOutput.indentFailures[i]) + "</div>";
|
101 |
-
}
|
102 |
-
if (s) throw new Failure(s);
|
103 |
-
}
|
104 |
-
|
105 |
-
function stringify(obj) {
|
106 |
-
function replacer(key, obj) {
|
107 |
-
if (typeof obj == "function") {
|
108 |
-
var m = obj.toString().match(/function\s*[^\s(]*/);
|
109 |
-
return m ? m[0] : "function";
|
110 |
-
}
|
111 |
-
return obj;
|
112 |
-
}
|
113 |
-
if (window.JSON && JSON.stringify)
|
114 |
-
return JSON.stringify(obj, replacer, 2);
|
115 |
-
return "[unsupported]"; // Fail safely if no native JSON.
|
116 |
-
}
|
117 |
-
|
118 |
-
function highlight(string, mode) {
|
119 |
-
var state = mode.startState();
|
120 |
-
|
121 |
-
var lines = string.replace(/\r\n/g,'\n').split('\n');
|
122 |
-
var st = [], pos = 0;
|
123 |
-
for (var i = 0; i < lines.length; ++i) {
|
124 |
-
var line = lines[i], newLine = true;
|
125 |
-
if (mode.indent) {
|
126 |
-
var ws = line.match(/^\s*/)[0];
|
127 |
-
var indent = mode.indent(state, line.slice(ws.length));
|
128 |
-
if (indent != CodeMirror.Pass && indent != ws.length)
|
129 |
-
(st.indentFailures || (st.indentFailures = [])).push(
|
130 |
-
"Indentation of line " + (i + 1) + " is " + indent + " (expected " + ws.length + ")");
|
131 |
-
}
|
132 |
-
var stream = new CodeMirror.StringStream(line);
|
133 |
-
if (line == "" && mode.blankLine) mode.blankLine(state);
|
134 |
-
/* Start copied code from CodeMirror.highlight */
|
135 |
-
while (!stream.eol()) {
|
136 |
-
for (var j = 0; j < 10 && stream.start >= stream.pos; j++)
|
137 |
-
var compare = mode.token(stream, state);
|
138 |
-
if (j == 10)
|
139 |
-
throw new Failure("Failed to advance the stream." + stream.string + " " + stream.pos);
|
140 |
-
var substr = stream.current();
|
141 |
-
if (compare && compare.indexOf(" ") > -1) compare = compare.split(' ').sort().join(' ');
|
142 |
-
stream.start = stream.pos;
|
143 |
-
if (pos && st[pos-1].style == compare && !newLine) {
|
144 |
-
st[pos-1].text += substr;
|
145 |
-
} else if (substr) {
|
146 |
-
st[pos++] = {style: compare, text: substr, state: stringify(state)};
|
147 |
-
}
|
148 |
-
// Give up when line is ridiculously long
|
149 |
-
if (stream.pos > 5000) {
|
150 |
-
st[pos++] = {style: null, text: this.text.slice(stream.pos)};
|
151 |
-
break;
|
152 |
-
}
|
153 |
-
newLine = false;
|
154 |
-
}
|
155 |
-
}
|
156 |
-
|
157 |
-
return st;
|
158 |
-
}
|
159 |
-
|
160 |
-
function highlightOutputsDifferent(o1, o2) {
|
161 |
-
var minLen = Math.min(o1.length, o2.length);
|
162 |
-
for (var i = 0; i < minLen; ++i)
|
163 |
-
if (o1[i].style != o2[i].style || o1[i].text != o2[i].text) return i;
|
164 |
-
if (o1.length > minLen || o2.length > minLen) return minLen;
|
165 |
-
}
|
166 |
-
|
167 |
-
function prettyPrintOutputTable(output, diffAt) {
|
168 |
-
var s = '<table class="mt-output">';
|
169 |
-
s += '<tr>';
|
170 |
-
for (var i = 0; i < output.length; ++i) {
|
171 |
-
var style = output[i].style, val = output[i].text;
|
172 |
-
s +=
|
173 |
-
'<td class="mt-token"' + (i == diffAt * 2 ? " style='background: pink'" : "") + '>' +
|
174 |
-
'<span class="cm-' + esc(String(style)) + '">' +
|
175 |
-
esc(val.replace(/ /g,'\xb7')) + // · MIDDLE DOT
|
176 |
-
'</span>' +
|
177 |
-
'</td>';
|
178 |
-
}
|
179 |
-
s += '</tr><tr>';
|
180 |
-
for (var i = 0; i < output.length; ++i) {
|
181 |
-
s += '<td class="mt-style"><span>' + (output[i].style || null) + '</span></td>';
|
182 |
-
}
|
183 |
-
if(output[0].state) {
|
184 |
-
s += '</tr><tr class="mt-state-row" title="State AFTER each token">';
|
185 |
-
for (var i = 0; i < output.length; ++i) {
|
186 |
-
s += '<td class="mt-state"><pre>' + esc(output[i].state) + '</pre></td>';
|
187 |
-
}
|
188 |
-
}
|
189 |
-
s += '</tr></table>';
|
190 |
-
return s;
|
191 |
-
}
|
192 |
-
})();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/multi_test.js
DELETED
@@ -1,285 +0,0 @@
|
|
1 |
-
(function() {
|
2 |
-
namespace = "multi_";
|
3 |
-
|
4 |
-
function hasSelections(cm) {
|
5 |
-
var sels = cm.listSelections();
|
6 |
-
var given = (arguments.length - 1) / 4;
|
7 |
-
if (sels.length != given)
|
8 |
-
throw new Failure("expected " + given + " selections, found " + sels.length);
|
9 |
-
for (var i = 0, p = 1; i < given; i++, p += 4) {
|
10 |
-
var anchor = Pos(arguments[p], arguments[p + 1]);
|
11 |
-
var head = Pos(arguments[p + 2], arguments[p + 3]);
|
12 |
-
eqPos(sels[i].anchor, anchor, "anchor of selection " + i);
|
13 |
-
eqPos(sels[i].head, head, "head of selection " + i);
|
14 |
-
}
|
15 |
-
}
|
16 |
-
function hasCursors(cm) {
|
17 |
-
var sels = cm.listSelections();
|
18 |
-
var given = (arguments.length - 1) / 2;
|
19 |
-
if (sels.length != given)
|
20 |
-
throw new Failure("expected " + given + " selections, found " + sels.length);
|
21 |
-
for (var i = 0, p = 1; i < given; i++, p += 2) {
|
22 |
-
eqPos(sels[i].anchor, sels[i].head, "something selected for " + i);
|
23 |
-
var head = Pos(arguments[p], arguments[p + 1]);
|
24 |
-
eqPos(sels[i].head, head, "selection " + i);
|
25 |
-
}
|
26 |
-
}
|
27 |
-
|
28 |
-
testCM("getSelection", function(cm) {
|
29 |
-
select(cm, {anchor: Pos(0, 0), head: Pos(1, 2)}, {anchor: Pos(2, 2), head: Pos(2, 0)});
|
30 |
-
eq(cm.getSelection(), "1234\n56\n90");
|
31 |
-
eq(cm.getSelection(false).join("|"), "1234|56|90");
|
32 |
-
eq(cm.getSelections().join("|"), "1234\n56|90");
|
33 |
-
}, {value: "1234\n5678\n90"});
|
34 |
-
|
35 |
-
testCM("setSelection", function(cm) {
|
36 |
-
select(cm, Pos(3, 0), Pos(0, 0), {anchor: Pos(2, 5), head: Pos(1, 0)});
|
37 |
-
hasSelections(cm, 0, 0, 0, 0,
|
38 |
-
2, 5, 1, 0,
|
39 |
-
3, 0, 3, 0);
|
40 |
-
cm.setSelection(Pos(1, 2), Pos(1, 1));
|
41 |
-
hasSelections(cm, 1, 2, 1, 1);
|
42 |
-
select(cm, {anchor: Pos(1, 1), head: Pos(2, 4)},
|
43 |
-
{anchor: Pos(0, 0), head: Pos(1, 3)},
|
44 |
-
Pos(3, 0), Pos(2, 2));
|
45 |
-
hasSelections(cm, 0, 0, 2, 4,
|
46 |
-
3, 0, 3, 0);
|
47 |
-
cm.setSelections([{anchor: Pos(0, 1), head: Pos(0, 2)},
|
48 |
-
{anchor: Pos(1, 1), head: Pos(1, 2)},
|
49 |
-
{anchor: Pos(2, 1), head: Pos(2, 2)}], 1);
|
50 |
-
eqPos(cm.getCursor("head"), Pos(1, 2));
|
51 |
-
eqPos(cm.getCursor("anchor"), Pos(1, 1));
|
52 |
-
eqPos(cm.getCursor("from"), Pos(1, 1));
|
53 |
-
eqPos(cm.getCursor("to"), Pos(1, 2));
|
54 |
-
cm.setCursor(Pos(1, 1));
|
55 |
-
hasCursors(cm, 1, 1);
|
56 |
-
}, {value: "abcde\nabcde\nabcde\n"});
|
57 |
-
|
58 |
-
testCM("somethingSelected", function(cm) {
|
59 |
-
select(cm, Pos(0, 1), {anchor: Pos(0, 3), head: Pos(0, 5)});
|
60 |
-
eq(cm.somethingSelected(), true);
|
61 |
-
select(cm, Pos(0, 1), Pos(0, 3), Pos(0, 5));
|
62 |
-
eq(cm.somethingSelected(), false);
|
63 |
-
}, {value: "123456789"});
|
64 |
-
|
65 |
-
testCM("extendSelection", function(cm) {
|
66 |
-
select(cm, Pos(0, 1), Pos(1, 1), Pos(2, 1));
|
67 |
-
cm.setExtending(true);
|
68 |
-
cm.extendSelections([Pos(0, 2), Pos(1, 0), Pos(2, 3)]);
|
69 |
-
hasSelections(cm, 0, 1, 0, 2,
|
70 |
-
1, 1, 1, 0,
|
71 |
-
2, 1, 2, 3);
|
72 |
-
cm.extendSelection(Pos(2, 4), Pos(2, 0));
|
73 |
-
hasSelections(cm, 2, 4, 2, 0);
|
74 |
-
}, {value: "1234\n1234\n1234"});
|
75 |
-
|
76 |
-
testCM("addSelection", function(cm) {
|
77 |
-
select(cm, Pos(0, 1), Pos(1, 1));
|
78 |
-
cm.addSelection(Pos(0, 0), Pos(0, 4));
|
79 |
-
hasSelections(cm, 0, 0, 0, 4,
|
80 |
-
1, 1, 1, 1);
|
81 |
-
cm.addSelection(Pos(2, 2));
|
82 |
-
hasSelections(cm, 0, 0, 0, 4,
|
83 |
-
1, 1, 1, 1,
|
84 |
-
2, 2, 2, 2);
|
85 |
-
}, {value: "1234\n1234\n1234"});
|
86 |
-
|
87 |
-
testCM("replaceSelection", function(cm) {
|
88 |
-
var selections = [{anchor: Pos(0, 0), head: Pos(0, 1)},
|
89 |
-
{anchor: Pos(0, 2), head: Pos(0, 3)},
|
90 |
-
{anchor: Pos(0, 4), head: Pos(0, 5)},
|
91 |
-
{anchor: Pos(2, 1), head: Pos(2, 4)},
|
92 |
-
{anchor: Pos(2, 5), head: Pos(2, 6)}];
|
93 |
-
var val = "123456\n123456\n123456";
|
94 |
-
cm.setValue(val);
|
95 |
-
cm.setSelections(selections);
|
96 |
-
cm.replaceSelection("ab", "around");
|
97 |
-
eq(cm.getValue(), "ab2ab4ab6\n123456\n1ab5ab");
|
98 |
-
hasSelections(cm, 0, 0, 0, 2,
|
99 |
-
0, 3, 0, 5,
|
100 |
-
0, 6, 0, 8,
|
101 |
-
2, 1, 2, 3,
|
102 |
-
2, 4, 2, 6);
|
103 |
-
cm.setValue(val);
|
104 |
-
cm.setSelections(selections);
|
105 |
-
cm.replaceSelection("", "around");
|
106 |
-
eq(cm.getValue(), "246\n123456\n15");
|
107 |
-
hasSelections(cm, 0, 0, 0, 0,
|
108 |
-
0, 1, 0, 1,
|
109 |
-
0, 2, 0, 2,
|
110 |
-
2, 1, 2, 1,
|
111 |
-
2, 2, 2, 2);
|
112 |
-
cm.setValue(val);
|
113 |
-
cm.setSelections(selections);
|
114 |
-
cm.replaceSelection("X\nY\nZ", "around");
|
115 |
-
hasSelections(cm, 0, 0, 2, 1,
|
116 |
-
2, 2, 4, 1,
|
117 |
-
4, 2, 6, 1,
|
118 |
-
8, 1, 10, 1,
|
119 |
-
10, 2, 12, 1);
|
120 |
-
cm.replaceSelection("a", "around");
|
121 |
-
hasSelections(cm, 0, 0, 0, 1,
|
122 |
-
0, 2, 0, 3,
|
123 |
-
0, 4, 0, 5,
|
124 |
-
2, 1, 2, 2,
|
125 |
-
2, 3, 2, 4);
|
126 |
-
cm.replaceSelection("xy", "start");
|
127 |
-
hasSelections(cm, 0, 0, 0, 0,
|
128 |
-
0, 3, 0, 3,
|
129 |
-
0, 6, 0, 6,
|
130 |
-
2, 1, 2, 1,
|
131 |
-
2, 4, 2, 4);
|
132 |
-
cm.replaceSelection("z\nf");
|
133 |
-
hasSelections(cm, 1, 1, 1, 1,
|
134 |
-
2, 1, 2, 1,
|
135 |
-
3, 1, 3, 1,
|
136 |
-
6, 1, 6, 1,
|
137 |
-
7, 1, 7, 1);
|
138 |
-
eq(cm.getValue(), "z\nfxy2z\nfxy4z\nfxy6\n123456\n1z\nfxy5z\nfxy");
|
139 |
-
});
|
140 |
-
|
141 |
-
function select(cm) {
|
142 |
-
var sels = [];
|
143 |
-
for (var i = 1; i < arguments.length; i++) {
|
144 |
-
var arg = arguments[i];
|
145 |
-
if (arg.head) sels.push(arg);
|
146 |
-
else sels.push({head: arg, anchor: arg});
|
147 |
-
}
|
148 |
-
cm.setSelections(sels, sels.length - 1);
|
149 |
-
}
|
150 |
-
|
151 |
-
testCM("indentSelection", function(cm) {
|
152 |
-
select(cm, Pos(0, 1), Pos(1, 1));
|
153 |
-
cm.indentSelection(4);
|
154 |
-
eq(cm.getValue(), " foo\n bar\nbaz");
|
155 |
-
|
156 |
-
select(cm, Pos(0, 2), Pos(0, 3), Pos(0, 4));
|
157 |
-
cm.indentSelection(-2);
|
158 |
-
eq(cm.getValue(), " foo\n bar\nbaz");
|
159 |
-
|
160 |
-
select(cm, {anchor: Pos(0, 0), head: Pos(1, 2)},
|
161 |
-
{anchor: Pos(1, 3), head: Pos(2, 0)});
|
162 |
-
cm.indentSelection(-2);
|
163 |
-
eq(cm.getValue(), "foo\n bar\nbaz");
|
164 |
-
}, {value: "foo\nbar\nbaz"});
|
165 |
-
|
166 |
-
testCM("killLine", function(cm) {
|
167 |
-
select(cm, Pos(0, 1), Pos(0, 2), Pos(1, 1));
|
168 |
-
cm.execCommand("killLine");
|
169 |
-
eq(cm.getValue(), "f\nb\nbaz");
|
170 |
-
cm.execCommand("killLine");
|
171 |
-
eq(cm.getValue(), "fbbaz");
|
172 |
-
cm.setValue("foo\nbar\nbaz");
|
173 |
-
select(cm, Pos(0, 1), {anchor: Pos(0, 2), head: Pos(2, 1)});
|
174 |
-
cm.execCommand("killLine");
|
175 |
-
eq(cm.getValue(), "faz");
|
176 |
-
}, {value: "foo\nbar\nbaz"});
|
177 |
-
|
178 |
-
testCM("deleteLine", function(cm) {
|
179 |
-
select(cm, Pos(0, 0),
|
180 |
-
{head: Pos(0, 1), anchor: Pos(2, 0)},
|
181 |
-
Pos(4, 0));
|
182 |
-
cm.execCommand("deleteLine");
|
183 |
-
eq(cm.getValue(), "4\n6\n7");
|
184 |
-
select(cm, Pos(2, 1));
|
185 |
-
cm.execCommand("deleteLine");
|
186 |
-
eq(cm.getValue(), "4\n6\n");
|
187 |
-
}, {value: "1\n2\n3\n4\n5\n6\n7"});
|
188 |
-
|
189 |
-
testCM("deleteH", function(cm) {
|
190 |
-
select(cm, Pos(0, 4), {anchor: Pos(1, 4), head: Pos(1, 5)});
|
191 |
-
cm.execCommand("delWordAfter");
|
192 |
-
eq(cm.getValue(), "foo bar baz\nabc ef ghi\n");
|
193 |
-
cm.execCommand("delWordAfter");
|
194 |
-
eq(cm.getValue(), "foo baz\nabc ghi\n");
|
195 |
-
cm.execCommand("delCharBefore");
|
196 |
-
cm.execCommand("delCharBefore");
|
197 |
-
eq(cm.getValue(), "fo baz\nab ghi\n");
|
198 |
-
select(cm, Pos(0, 3), Pos(0, 4), Pos(0, 5));
|
199 |
-
cm.execCommand("delWordAfter");
|
200 |
-
eq(cm.getValue(), "fo \nab ghi\n");
|
201 |
-
}, {value: "foo bar baz\nabc def ghi\n"});
|
202 |
-
|
203 |
-
testCM("goLineStart", function(cm) {
|
204 |
-
select(cm, Pos(0, 2), Pos(0, 3), Pos(1, 1));
|
205 |
-
cm.execCommand("goLineStart");
|
206 |
-
hasCursors(cm, 0, 0, 1, 0);
|
207 |
-
select(cm, Pos(1, 1), Pos(0, 1));
|
208 |
-
cm.setExtending(true);
|
209 |
-
cm.execCommand("goLineStart");
|
210 |
-
hasSelections(cm, 0, 1, 0, 0,
|
211 |
-
1, 1, 1, 0);
|
212 |
-
}, {value: "foo\nbar\nbaz"});
|
213 |
-
|
214 |
-
testCM("moveV", function(cm) {
|
215 |
-
select(cm, Pos(0, 2), Pos(1, 2));
|
216 |
-
cm.execCommand("goLineDown");
|
217 |
-
hasCursors(cm, 1, 2, 2, 2);
|
218 |
-
cm.execCommand("goLineUp");
|
219 |
-
hasCursors(cm, 0, 2, 1, 2);
|
220 |
-
cm.execCommand("goLineUp");
|
221 |
-
hasCursors(cm, 0, 0, 0, 2);
|
222 |
-
cm.execCommand("goLineUp");
|
223 |
-
hasCursors(cm, 0, 0);
|
224 |
-
select(cm, Pos(0, 2), Pos(1, 2));
|
225 |
-
cm.setExtending(true);
|
226 |
-
cm.execCommand("goLineDown");
|
227 |
-
hasSelections(cm, 0, 2, 2, 2);
|
228 |
-
}, {value: "12345\n12345\n12345"});
|
229 |
-
|
230 |
-
testCM("moveH", function(cm) {
|
231 |
-
select(cm, Pos(0, 1), Pos(0, 3), Pos(0, 5), Pos(2, 3));
|
232 |
-
cm.execCommand("goCharRight");
|
233 |
-
hasCursors(cm, 0, 2, 0, 4, 1, 0, 2, 4);
|
234 |
-
cm.execCommand("goCharLeft");
|
235 |
-
hasCursors(cm, 0, 1, 0, 3, 0, 5, 2, 3);
|
236 |
-
for (var i = 0; i < 15; i++)
|
237 |
-
cm.execCommand("goCharRight");
|
238 |
-
hasCursors(cm, 2, 4, 2, 5);
|
239 |
-
}, {value: "12345\n12345\n12345"});
|
240 |
-
|
241 |
-
testCM("newlineAndIndent", function(cm) {
|
242 |
-
select(cm, Pos(0, 5), Pos(1, 5));
|
243 |
-
cm.execCommand("newlineAndIndent");
|
244 |
-
hasCursors(cm, 1, 2, 3, 2);
|
245 |
-
eq(cm.getValue(), "x = [\n 1];\ny = [\n 2];");
|
246 |
-
cm.undo();
|
247 |
-
eq(cm.getValue(), "x = [1];\ny = [2];");
|
248 |
-
hasCursors(cm, 0, 5, 1, 5);
|
249 |
-
select(cm, Pos(0, 5), Pos(0, 6));
|
250 |
-
cm.execCommand("newlineAndIndent");
|
251 |
-
hasCursors(cm, 1, 2, 2, 0);
|
252 |
-
eq(cm.getValue(), "x = [\n 1\n];\ny = [2];");
|
253 |
-
}, {value: "x = [1];\ny = [2];", mode: "javascript"});
|
254 |
-
|
255 |
-
testCM("goDocStartEnd", function(cm) {
|
256 |
-
select(cm, Pos(0, 1), Pos(1, 1));
|
257 |
-
cm.execCommand("goDocStart");
|
258 |
-
hasCursors(cm, 0, 0);
|
259 |
-
select(cm, Pos(0, 1), Pos(1, 1));
|
260 |
-
cm.execCommand("goDocEnd");
|
261 |
-
hasCursors(cm, 1, 3);
|
262 |
-
select(cm, Pos(0, 1), Pos(1, 1));
|
263 |
-
cm.setExtending(true);
|
264 |
-
cm.execCommand("goDocEnd");
|
265 |
-
hasSelections(cm, 1, 1, 1, 3);
|
266 |
-
}, {value: "abc\ndef"});
|
267 |
-
|
268 |
-
testCM("selectionHistory", function(cm) {
|
269 |
-
for (var i = 0; i < 3; ++i)
|
270 |
-
cm.addSelection(Pos(0, i * 2), Pos(0, i * 2 + 1));
|
271 |
-
cm.execCommand("undoSelection");
|
272 |
-
eq(cm.getSelection(), "1\n2");
|
273 |
-
cm.execCommand("undoSelection");
|
274 |
-
eq(cm.getSelection(), "1");
|
275 |
-
cm.execCommand("undoSelection");
|
276 |
-
eq(cm.getSelection(), "");
|
277 |
-
eqPos(cm.getCursor(), Pos(0, 0));
|
278 |
-
cm.execCommand("redoSelection");
|
279 |
-
eq(cm.getSelection(), "1");
|
280 |
-
cm.execCommand("redoSelection");
|
281 |
-
eq(cm.getSelection(), "1\n2");
|
282 |
-
cm.execCommand("redoSelection");
|
283 |
-
eq(cm.getSelection(), "1\n2\n3");
|
284 |
-
}, {value: "1 2 3"});
|
285 |
-
})();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/phantom_driver.js
DELETED
@@ -1,31 +0,0 @@
|
|
1 |
-
var page = require('webpage').create();
|
2 |
-
|
3 |
-
page.open("http://localhost:3000/test/index.html", function (status) {
|
4 |
-
if (status != "success") {
|
5 |
-
console.log("page couldn't be loaded successfully");
|
6 |
-
phantom.exit(1);
|
7 |
-
}
|
8 |
-
waitFor(function () {
|
9 |
-
return page.evaluate(function () {
|
10 |
-
var output = document.getElementById('status');
|
11 |
-
if (!output) { return false; }
|
12 |
-
return (/^(\d+ failures?|all passed)/i).test(output.innerText);
|
13 |
-
});
|
14 |
-
}, function () {
|
15 |
-
var failed = page.evaluate(function () { return window.failed; });
|
16 |
-
var output = page.evaluate(function () {
|
17 |
-
return document.getElementById('output').innerText + "\n" +
|
18 |
-
document.getElementById('status').innerText;
|
19 |
-
});
|
20 |
-
console.log(output);
|
21 |
-
phantom.exit(failed > 0 ? 1 : 0);
|
22 |
-
});
|
23 |
-
});
|
24 |
-
|
25 |
-
function waitFor (test, cb) {
|
26 |
-
if (test()) {
|
27 |
-
cb();
|
28 |
-
} else {
|
29 |
-
setTimeout(function () { waitFor(test, cb); }, 250);
|
30 |
-
}
|
31 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/run.js
DELETED
@@ -1,31 +0,0 @@
|
|
1 |
-
#!/usr/bin/env node
|
2 |
-
|
3 |
-
var ok = require("./lint").ok;
|
4 |
-
|
5 |
-
var files = new (require('node-static').Server)();
|
6 |
-
|
7 |
-
var server = require('http').createServer(function (req, res) {
|
8 |
-
req.addListener('end', function () {
|
9 |
-
files.serve(req, res, function (err/*, result */) {
|
10 |
-
if (err) {
|
11 |
-
console.error(err);
|
12 |
-
process.exit(1);
|
13 |
-
}
|
14 |
-
});
|
15 |
-
}).resume();
|
16 |
-
}).addListener('error', function (err) {
|
17 |
-
throw err;
|
18 |
-
}).listen(3000, function () {
|
19 |
-
var childProcess = require('child_process');
|
20 |
-
var phantomjs = require("phantomjs");
|
21 |
-
var childArgs = [
|
22 |
-
require("path").join(__dirname, 'phantom_driver.js')
|
23 |
-
];
|
24 |
-
childProcess.execFile(phantomjs.path, childArgs, function (err, stdout, stderr) {
|
25 |
-
server.close();
|
26 |
-
console.log(stdout);
|
27 |
-
if (err) console.error(err);
|
28 |
-
if (stderr) console.error(stderr);
|
29 |
-
process.exit(err || stderr || !ok ? 1 : 0);
|
30 |
-
});
|
31 |
-
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/scroll_test.js
DELETED
@@ -1,115 +0,0 @@
|
|
1 |
-
(function() {
|
2 |
-
"use strict";
|
3 |
-
|
4 |
-
namespace = "scroll_";
|
5 |
-
|
6 |
-
testCM("bars_hidden", function(cm) {
|
7 |
-
for (var i = 0;; i++) {
|
8 |
-
var wrapBox = cm.getWrapperElement().getBoundingClientRect();
|
9 |
-
var scrollBox = cm.getScrollerElement().getBoundingClientRect();
|
10 |
-
is(wrapBox.bottom < scrollBox.bottom - 10);
|
11 |
-
is(wrapBox.right < scrollBox.right - 10);
|
12 |
-
if (i == 1) break;
|
13 |
-
cm.getWrapperElement().style.height = "auto";
|
14 |
-
cm.refresh();
|
15 |
-
}
|
16 |
-
});
|
17 |
-
|
18 |
-
function barH(cm) { return byClassName(cm.getWrapperElement(), "CodeMirror-hscrollbar")[0]; }
|
19 |
-
function barV(cm) { return byClassName(cm.getWrapperElement(), "CodeMirror-vscrollbar")[0]; }
|
20 |
-
|
21 |
-
function displayBottom(cm, scrollbar) {
|
22 |
-
if (scrollbar)
|
23 |
-
return barH(cm).getBoundingClientRect().top;
|
24 |
-
else
|
25 |
-
return cm.getWrapperElement().getBoundingClientRect().bottom - 1;
|
26 |
-
}
|
27 |
-
|
28 |
-
function displayRight(cm, scrollbar) {
|
29 |
-
if (scrollbar)
|
30 |
-
return barV(cm).getBoundingClientRect().left;
|
31 |
-
else
|
32 |
-
return cm.getWrapperElement().getBoundingClientRect().right - 1;
|
33 |
-
}
|
34 |
-
|
35 |
-
function testMovedownFixed(cm, hScroll) {
|
36 |
-
cm.setSize("100px", "100px");
|
37 |
-
if (hScroll) cm.setValue(new Array(100).join("x"));
|
38 |
-
var bottom = displayBottom(cm, hScroll);
|
39 |
-
for (var i = 0; i < 30; i++) {
|
40 |
-
cm.replaceSelection("x\n");
|
41 |
-
var cursorBottom = cm.cursorCoords(null, "window").bottom;
|
42 |
-
is(cursorBottom <= bottom);
|
43 |
-
}
|
44 |
-
is(cursorBottom >= bottom - 5);
|
45 |
-
}
|
46 |
-
|
47 |
-
testCM("movedown_fixed", function(cm) {testMovedownFixed(cm, false);});
|
48 |
-
testCM("movedown_hscroll_fixed", function(cm) {testMovedownFixed(cm, true);});
|
49 |
-
|
50 |
-
function testMovedownResize(cm, hScroll) {
|
51 |
-
cm.getWrapperElement().style.height = "auto";
|
52 |
-
if (hScroll) cm.setValue(new Array(100).join("x"));
|
53 |
-
cm.refresh();
|
54 |
-
for (var i = 0; i < 30; i++) {
|
55 |
-
cm.replaceSelection("x\n");
|
56 |
-
var bottom = displayBottom(cm, hScroll);
|
57 |
-
var cursorBottom = cm.cursorCoords(null, "window").bottom;
|
58 |
-
is(cursorBottom <= bottom);
|
59 |
-
is(cursorBottom >= bottom - 5);
|
60 |
-
}
|
61 |
-
}
|
62 |
-
|
63 |
-
testCM("movedown_resize", function(cm) {testMovedownResize(cm, false);});
|
64 |
-
testCM("movedown_hscroll_resize", function(cm) {testMovedownResize(cm, true);});
|
65 |
-
|
66 |
-
function testMoveright(cm, wrap, scroll) {
|
67 |
-
cm.setSize("100px", "100px");
|
68 |
-
if (wrap) cm.setOption("lineWrapping", true);
|
69 |
-
if (scroll) {
|
70 |
-
cm.setValue("\n" + new Array(100).join("x\n"));
|
71 |
-
cm.setCursor(Pos(0, 0));
|
72 |
-
}
|
73 |
-
var right = displayRight(cm, scroll);
|
74 |
-
for (var i = 0; i < 10; i++) {
|
75 |
-
cm.replaceSelection("xxxxxxxxxx");
|
76 |
-
var cursorRight = cm.cursorCoords(null, "window").right;
|
77 |
-
is(cursorRight < right);
|
78 |
-
}
|
79 |
-
if (!wrap) is(cursorRight > right - 20);
|
80 |
-
}
|
81 |
-
|
82 |
-
testCM("moveright", function(cm) {testMoveright(cm, false, false);});
|
83 |
-
testCM("moveright_wrap", function(cm) {testMoveright(cm, true, false);});
|
84 |
-
testCM("moveright_scroll", function(cm) {testMoveright(cm, false, true);});
|
85 |
-
testCM("moveright_scroll_wrap", function(cm) {testMoveright(cm, true, true);});
|
86 |
-
|
87 |
-
testCM("suddenly_wide", function(cm) {
|
88 |
-
addDoc(cm, 100, 100);
|
89 |
-
cm.replaceSelection(new Array(600).join("l ") + "\n");
|
90 |
-
cm.execCommand("goLineUp");
|
91 |
-
cm.execCommand("goLineEnd");
|
92 |
-
is(barH(cm).scrollLeft > cm.getScrollerElement().scrollLeft - 1);
|
93 |
-
});
|
94 |
-
|
95 |
-
testCM("wrap_changes_height", function(cm) {
|
96 |
-
var line = new Array(20).join("a ") + "\n";
|
97 |
-
cm.setValue(new Array(20).join(line));
|
98 |
-
var box = cm.getWrapperElement().getBoundingClientRect();
|
99 |
-
cm.setSize(cm.cursorCoords(Pos(0), "window").right - box.left + 2,
|
100 |
-
cm.cursorCoords(Pos(19, 0), "window").bottom - box.top + 2);
|
101 |
-
cm.setCursor(Pos(19, 0));
|
102 |
-
cm.replaceSelection("\n");
|
103 |
-
is(cm.cursorCoords(null, "window").bottom < displayBottom(cm, false));
|
104 |
-
}, {lineWrapping: true});
|
105 |
-
|
106 |
-
testCM("height_auto_with_gutter_expect_no_scroll_after_line_delete", function(cm) {
|
107 |
-
cm.setSize(null, "auto");
|
108 |
-
cm.setValue("x\n");
|
109 |
-
cm.execCommand("goDocEnd");
|
110 |
-
cm.execCommand("delCharBefore");
|
111 |
-
eq(cm.getScrollInfo().top, 0);
|
112 |
-
cm.scrollTo(null, 10);
|
113 |
-
is(cm.getScrollInfo().top < 5);
|
114 |
-
}, {lineNumbers: true});
|
115 |
-
})();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/search_test.js
DELETED
@@ -1,62 +0,0 @@
|
|
1 |
-
(function() {
|
2 |
-
"use strict";
|
3 |
-
|
4 |
-
function test(name) {
|
5 |
-
var text = Array.prototype.slice.call(arguments, 1, arguments.length - 1).join("\n");
|
6 |
-
var body = arguments[arguments.length - 1];
|
7 |
-
return window.test("search_" + name, function() {
|
8 |
-
body(new CodeMirror.Doc(text));
|
9 |
-
});
|
10 |
-
}
|
11 |
-
|
12 |
-
function run(doc, query, insensitive) {
|
13 |
-
var cursor = doc.getSearchCursor(query, null, insensitive);
|
14 |
-
for (var i = 3; i < arguments.length; i += 4) {
|
15 |
-
var found = cursor.findNext();
|
16 |
-
is(found, "not enough results (forward)");
|
17 |
-
eqPos(Pos(arguments[i], arguments[i + 1]), cursor.from(), "from, forward, " + (i - 3) / 4);
|
18 |
-
eqPos(Pos(arguments[i + 2], arguments[i + 3]), cursor.to(), "to, forward, " + (i - 3) / 4);
|
19 |
-
}
|
20 |
-
is(!cursor.findNext(), "too many matches (forward)");
|
21 |
-
for (var i = arguments.length - 4; i >= 3; i -= 4) {
|
22 |
-
var found = cursor.findPrevious();
|
23 |
-
is(found, "not enough results (backwards)");
|
24 |
-
eqPos(Pos(arguments[i], arguments[i + 1]), cursor.from(), "from, backwards, " + (i - 3) / 4);
|
25 |
-
eqPos(Pos(arguments[i + 2], arguments[i + 3]), cursor.to(), "to, backwards, " + (i - 3) / 4);
|
26 |
-
}
|
27 |
-
is(!cursor.findPrevious(), "too many matches (backwards)");
|
28 |
-
}
|
29 |
-
|
30 |
-
test("simple", "abcdefg", "abcdefg", function(doc) {
|
31 |
-
run(doc, "cde", false, 0, 2, 0, 5, 1, 2, 1, 5);
|
32 |
-
});
|
33 |
-
|
34 |
-
test("multiline", "hallo", "goodbye", function(doc) {
|
35 |
-
run(doc, "llo\ngoo", false, 0, 2, 1, 3);
|
36 |
-
run(doc, "blah\nhall", false);
|
37 |
-
run(doc, "bye\neye", false);
|
38 |
-
});
|
39 |
-
|
40 |
-
test("regexp", "abcde", "abcde", function(doc) {
|
41 |
-
run(doc, /bcd/, false, 0, 1, 0, 4, 1, 1, 1, 4);
|
42 |
-
run(doc, /BCD/, false);
|
43 |
-
run(doc, /BCD/i, false, 0, 1, 0, 4, 1, 1, 1, 4);
|
44 |
-
});
|
45 |
-
|
46 |
-
test("insensitive", "hallo", "HALLO", "oink", "hAllO", function(doc) {
|
47 |
-
run(doc, "All", false, 3, 1, 3, 4);
|
48 |
-
run(doc, "All", true, 0, 1, 0, 4, 1, 1, 1, 4, 3, 1, 3, 4);
|
49 |
-
});
|
50 |
-
|
51 |
-
test("multilineInsensitive", "zie ginds komT", "De Stoomboot", "uit Spanje weer aan", function(doc) {
|
52 |
-
run(doc, "komt\nde stoomboot\nuit", false);
|
53 |
-
run(doc, "komt\nde stoomboot\nuit", true, 0, 10, 2, 3);
|
54 |
-
run(doc, "kOMt\ndE stOOmboot\nuiT", true, 0, 10, 2, 3);
|
55 |
-
});
|
56 |
-
|
57 |
-
test("expandingCaseFold", "<b>İİ İİ</b>", "<b>uu uu</b>", function(doc) {
|
58 |
-
if (phantom) return; // A Phantom bug makes this hang
|
59 |
-
run(doc, "</b>", true, 0, 8, 0, 12, 1, 8, 1, 12);
|
60 |
-
run(doc, "İİ", true, 0, 3, 0, 5, 0, 6, 0, 8);
|
61 |
-
});
|
62 |
-
})();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/sql-hint-test.js
DELETED
@@ -1,189 +0,0 @@
|
|
1 |
-
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
2 |
-
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
3 |
-
|
4 |
-
(function() {
|
5 |
-
var Pos = CodeMirror.Pos;
|
6 |
-
|
7 |
-
var simpleTables = {
|
8 |
-
"users": ["name", "score", "birthDate"],
|
9 |
-
"xcountries": ["name", "population", "size"]
|
10 |
-
};
|
11 |
-
|
12 |
-
var schemaTables = {
|
13 |
-
"schema.users": ["name", "score", "birthDate"],
|
14 |
-
"schema.countries": ["name", "population", "size"]
|
15 |
-
};
|
16 |
-
|
17 |
-
var displayTextTables = [{
|
18 |
-
text: "mytable",
|
19 |
-
displayText: "mytable | The main table",
|
20 |
-
columns: [{text: "id", displayText: "id | Unique ID"},
|
21 |
-
{text: "name", displayText: "name | The name"}]
|
22 |
-
}];
|
23 |
-
|
24 |
-
namespace = "sql-hint_";
|
25 |
-
|
26 |
-
function test(name, spec) {
|
27 |
-
testCM(name, function(cm) {
|
28 |
-
cm.setValue(spec.value);
|
29 |
-
cm.setCursor(spec.cursor);
|
30 |
-
var completion = CodeMirror.hint.sql(cm, {tables: spec.tables});
|
31 |
-
if (!deepCompare(completion.list, spec.list))
|
32 |
-
throw new Failure("Wrong completion results " + JSON.stringify(completion.list) + " vs " + JSON.stringify(spec.list));
|
33 |
-
eqPos(completion.from, spec.from);
|
34 |
-
eqPos(completion.to, spec.to);
|
35 |
-
}, {
|
36 |
-
value: spec.value,
|
37 |
-
mode: "text/x-mysql"
|
38 |
-
});
|
39 |
-
}
|
40 |
-
|
41 |
-
test("keywords", {
|
42 |
-
value: "SEL",
|
43 |
-
cursor: Pos(0, 3),
|
44 |
-
list: ["SELECT"],
|
45 |
-
from: Pos(0, 0),
|
46 |
-
to: Pos(0, 3)
|
47 |
-
});
|
48 |
-
|
49 |
-
test("from", {
|
50 |
-
value: "SELECT * fr",
|
51 |
-
cursor: Pos(0, 11),
|
52 |
-
list: ["FROM"],
|
53 |
-
from: Pos(0, 9),
|
54 |
-
to: Pos(0, 11)
|
55 |
-
});
|
56 |
-
|
57 |
-
test("table", {
|
58 |
-
value: "SELECT xc",
|
59 |
-
cursor: Pos(0, 9),
|
60 |
-
tables: simpleTables,
|
61 |
-
list: ["xcountries"],
|
62 |
-
from: Pos(0, 7),
|
63 |
-
to: Pos(0, 9)
|
64 |
-
});
|
65 |
-
|
66 |
-
test("columns", {
|
67 |
-
value: "SELECT users.",
|
68 |
-
cursor: Pos(0, 13),
|
69 |
-
tables: simpleTables,
|
70 |
-
list: ["users.name", "users.score", "users.birthDate"],
|
71 |
-
from: Pos(0, 7),
|
72 |
-
to: Pos(0, 13)
|
73 |
-
});
|
74 |
-
|
75 |
-
test("singlecolumn", {
|
76 |
-
value: "SELECT users.na",
|
77 |
-
cursor: Pos(0, 15),
|
78 |
-
tables: simpleTables,
|
79 |
-
list: ["users.name"],
|
80 |
-
from: Pos(0, 7),
|
81 |
-
to: Pos(0, 15)
|
82 |
-
});
|
83 |
-
|
84 |
-
test("quoted", {
|
85 |
-
value: "SELECT `users`.`na",
|
86 |
-
cursor: Pos(0, 18),
|
87 |
-
tables: simpleTables,
|
88 |
-
list: ["`users`.`name`"],
|
89 |
-
from: Pos(0, 7),
|
90 |
-
to: Pos(0, 18)
|
91 |
-
});
|
92 |
-
|
93 |
-
test("quotedcolumn", {
|
94 |
-
value: "SELECT users.`na",
|
95 |
-
cursor: Pos(0, 16),
|
96 |
-
tables: simpleTables,
|
97 |
-
list: ["`users`.`name`"],
|
98 |
-
from: Pos(0, 7),
|
99 |
-
to: Pos(0, 16)
|
100 |
-
});
|
101 |
-
|
102 |
-
test("schema", {
|
103 |
-
value: "SELECT schem",
|
104 |
-
cursor: Pos(0, 12),
|
105 |
-
tables: schemaTables,
|
106 |
-
list: ["schema.users", "schema.countries",
|
107 |
-
"SCHEMA", "SCHEMA_NAME", "SCHEMAS"],
|
108 |
-
from: Pos(0, 7),
|
109 |
-
to: Pos(0, 12)
|
110 |
-
});
|
111 |
-
|
112 |
-
test("schemaquoted", {
|
113 |
-
value: "SELECT `sch",
|
114 |
-
cursor: Pos(0, 11),
|
115 |
-
tables: schemaTables,
|
116 |
-
list: ["`schema`.`users`", "`schema`.`countries`"],
|
117 |
-
from: Pos(0, 7),
|
118 |
-
to: Pos(0, 11)
|
119 |
-
});
|
120 |
-
|
121 |
-
test("schemacolumn", {
|
122 |
-
value: "SELECT schema.users.",
|
123 |
-
cursor: Pos(0, 20),
|
124 |
-
tables: schemaTables,
|
125 |
-
list: ["schema.users.name",
|
126 |
-
"schema.users.score",
|
127 |
-
"schema.users.birthDate"],
|
128 |
-
from: Pos(0, 7),
|
129 |
-
to: Pos(0, 20)
|
130 |
-
});
|
131 |
-
|
132 |
-
test("schemacolumnquoted", {
|
133 |
-
value: "SELECT `schema`.`users`.",
|
134 |
-
cursor: Pos(0, 24),
|
135 |
-
tables: schemaTables,
|
136 |
-
list: ["`schema`.`users`.`name`",
|
137 |
-
"`schema`.`users`.`score`",
|
138 |
-
"`schema`.`users`.`birthDate`"],
|
139 |
-
from: Pos(0, 7),
|
140 |
-
to: Pos(0, 24)
|
141 |
-
});
|
142 |
-
|
143 |
-
test("displayText_table", {
|
144 |
-
value: "SELECT myt",
|
145 |
-
cursor: Pos(0, 10),
|
146 |
-
tables: displayTextTables,
|
147 |
-
list: displayTextTables,
|
148 |
-
from: Pos(0, 7),
|
149 |
-
to: Pos(0, 10)
|
150 |
-
});
|
151 |
-
|
152 |
-
test("displayText_column", {
|
153 |
-
value: "SELECT mytable.",
|
154 |
-
cursor: Pos(0, 15),
|
155 |
-
tables: displayTextTables,
|
156 |
-
list: [{text: "mytable.id", displayText: "id | Unique ID"},
|
157 |
-
{text: "mytable.name", displayText: "name | The name"}],
|
158 |
-
from: Pos(0, 7),
|
159 |
-
to: Pos(0, 15)
|
160 |
-
});
|
161 |
-
|
162 |
-
test("alias_complete", {
|
163 |
-
value: "SELECT t. FROM users t",
|
164 |
-
cursor: Pos(0, 9),
|
165 |
-
tables: simpleTables,
|
166 |
-
list: ["t.name", "t.score", "t.birthDate"],
|
167 |
-
from: Pos(0, 7),
|
168 |
-
to: Pos(0, 9)
|
169 |
-
});
|
170 |
-
|
171 |
-
test("alias_complete_with_displayText", {
|
172 |
-
value: "SELECT t. FROM mytable t",
|
173 |
-
cursor: Pos(0, 9),
|
174 |
-
tables: displayTextTables,
|
175 |
-
list: [{text: "t.id", displayText: "id | Unique ID"},
|
176 |
-
{text: "t.name", displayText: "name | The name"}],
|
177 |
-
from: Pos(0, 7),
|
178 |
-
to: Pos(0, 9)
|
179 |
-
})
|
180 |
-
|
181 |
-
function deepCompare(a, b) {
|
182 |
-
if (!a || typeof a != "object")
|
183 |
-
return a === b;
|
184 |
-
if (!b || typeof b != "object")
|
185 |
-
return false;
|
186 |
-
for (var prop in a) if (!deepCompare(a[prop], b[prop])) return false;
|
187 |
-
return true;
|
188 |
-
}
|
189 |
-
})();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/sublime_test.js
DELETED
@@ -1,303 +0,0 @@
|
|
1 |
-
(function() {
|
2 |
-
"use strict";
|
3 |
-
|
4 |
-
var Pos = CodeMirror.Pos;
|
5 |
-
namespace = "sublime_";
|
6 |
-
|
7 |
-
function stTest(name) {
|
8 |
-
var actions = Array.prototype.slice.call(arguments, 1);
|
9 |
-
testCM(name, function(cm) {
|
10 |
-
for (var i = 0; i < actions.length; i++) {
|
11 |
-
var action = actions[i];
|
12 |
-
if (typeof action == "string" && i == 0)
|
13 |
-
cm.setValue(action);
|
14 |
-
else if (typeof action == "string")
|
15 |
-
cm.execCommand(action);
|
16 |
-
else if (action instanceof Pos)
|
17 |
-
cm.setCursor(action);
|
18 |
-
else
|
19 |
-
action(cm);
|
20 |
-
}
|
21 |
-
});
|
22 |
-
}
|
23 |
-
|
24 |
-
function at(line, ch, msg) {
|
25 |
-
return function(cm) {
|
26 |
-
eq(cm.listSelections().length, 1);
|
27 |
-
eqPos(cm.getCursor("head"), Pos(line, ch), msg);
|
28 |
-
eqPos(cm.getCursor("anchor"), Pos(line, ch), msg);
|
29 |
-
};
|
30 |
-
}
|
31 |
-
|
32 |
-
function val(content, msg) {
|
33 |
-
return function(cm) { eq(cm.getValue(), content, msg); };
|
34 |
-
}
|
35 |
-
|
36 |
-
function argsToRanges(args) {
|
37 |
-
if (args.length % 4) throw new Error("Wrong number of arguments for ranges.");
|
38 |
-
var ranges = [];
|
39 |
-
for (var i = 0; i < args.length; i += 4)
|
40 |
-
ranges.push({anchor: Pos(args[i], args[i + 1]),
|
41 |
-
head: Pos(args[i + 2], args[i + 3])});
|
42 |
-
return ranges;
|
43 |
-
}
|
44 |
-
|
45 |
-
function setSel() {
|
46 |
-
var ranges = argsToRanges(arguments);
|
47 |
-
return function(cm) { cm.setSelections(ranges, 0); };
|
48 |
-
}
|
49 |
-
|
50 |
-
function hasSel() {
|
51 |
-
var ranges = argsToRanges(arguments);
|
52 |
-
return function(cm) {
|
53 |
-
var sels = cm.listSelections();
|
54 |
-
if (sels.length != ranges.length)
|
55 |
-
throw new Failure("Expected " + ranges.length + " selections, but found " + sels.length);
|
56 |
-
for (var i = 0; i < sels.length; i++) {
|
57 |
-
eqPos(sels[i].anchor, ranges[i].anchor, "anchor " + i);
|
58 |
-
eqPos(sels[i].head, ranges[i].head, "head " + i);
|
59 |
-
}
|
60 |
-
};
|
61 |
-
}
|
62 |
-
|
63 |
-
stTest("bySubword", "the foo_bar DooDahBah \n a",
|
64 |
-
"goSubwordLeft", at(0, 0),
|
65 |
-
"goSubwordRight", at(0, 3),
|
66 |
-
"goSubwordRight", at(0, 7),
|
67 |
-
"goSubwordRight", at(0, 11),
|
68 |
-
"goSubwordRight", at(0, 15),
|
69 |
-
"goSubwordRight", at(0, 18),
|
70 |
-
"goSubwordRight", at(0, 21),
|
71 |
-
"goSubwordRight", at(0, 22),
|
72 |
-
"goSubwordRight", at(1, 0),
|
73 |
-
"goSubwordRight", at(1, 2),
|
74 |
-
"goSubwordRight", at(1, 2),
|
75 |
-
"goSubwordLeft", at(1, 1),
|
76 |
-
"goSubwordLeft", at(1, 0),
|
77 |
-
"goSubwordLeft", at(0, 22),
|
78 |
-
"goSubwordLeft", at(0, 18),
|
79 |
-
"goSubwordLeft", at(0, 15),
|
80 |
-
"goSubwordLeft", at(0, 12),
|
81 |
-
"goSubwordLeft", at(0, 8),
|
82 |
-
"goSubwordLeft", at(0, 4),
|
83 |
-
"goSubwordLeft", at(0, 0));
|
84 |
-
|
85 |
-
stTest("splitSelectionByLine", "abc\ndef\nghi",
|
86 |
-
setSel(0, 1, 2, 2),
|
87 |
-
"splitSelectionByLine",
|
88 |
-
hasSel(0, 1, 0, 3,
|
89 |
-
1, 0, 1, 3,
|
90 |
-
2, 0, 2, 2));
|
91 |
-
|
92 |
-
stTest("splitSelectionByLineMulti", "abc\ndef\nghi\njkl",
|
93 |
-
setSel(0, 1, 1, 1,
|
94 |
-
1, 2, 3, 2,
|
95 |
-
3, 3, 3, 3),
|
96 |
-
"splitSelectionByLine",
|
97 |
-
hasSel(0, 1, 0, 3,
|
98 |
-
1, 0, 1, 1,
|
99 |
-
1, 2, 1, 3,
|
100 |
-
2, 0, 2, 3,
|
101 |
-
3, 0, 3, 2,
|
102 |
-
3, 3, 3, 3));
|
103 |
-
|
104 |
-
stTest("selectLine", "abc\ndef\nghi",
|
105 |
-
setSel(0, 1, 0, 1,
|
106 |
-
2, 0, 2, 1),
|
107 |
-
"selectLine",
|
108 |
-
hasSel(0, 0, 1, 0,
|
109 |
-
2, 0, 2, 3),
|
110 |
-
setSel(0, 1, 1, 0),
|
111 |
-
"selectLine",
|
112 |
-
hasSel(0, 0, 2, 0));
|
113 |
-
|
114 |
-
stTest("insertLineAfter", "abcde\nfghijkl\nmn",
|
115 |
-
setSel(0, 1, 0, 1,
|
116 |
-
0, 3, 0, 3,
|
117 |
-
1, 2, 1, 2,
|
118 |
-
1, 3, 1, 5), "insertLineAfter",
|
119 |
-
hasSel(1, 0, 1, 0,
|
120 |
-
3, 0, 3, 0), val("abcde\n\nfghijkl\n\nmn"));
|
121 |
-
|
122 |
-
stTest("insertLineBefore", "abcde\nfghijkl\nmn",
|
123 |
-
setSel(0, 1, 0, 1,
|
124 |
-
0, 3, 0, 3,
|
125 |
-
1, 2, 1, 2,
|
126 |
-
1, 3, 1, 5), "insertLineBefore",
|
127 |
-
hasSel(0, 0, 0, 0,
|
128 |
-
2, 0, 2, 0), val("\nabcde\n\nfghijkl\nmn"));
|
129 |
-
|
130 |
-
stTest("selectNextOccurrence", "a foo bar\nfoobar foo",
|
131 |
-
setSel(0, 2, 0, 5),
|
132 |
-
"selectNextOccurrence", hasSel(0, 2, 0, 5,
|
133 |
-
1, 0, 1, 3),
|
134 |
-
"selectNextOccurrence", hasSel(0, 2, 0, 5,
|
135 |
-
1, 0, 1, 3,
|
136 |
-
1, 7, 1, 10),
|
137 |
-
"selectNextOccurrence", hasSel(0, 2, 0, 5,
|
138 |
-
1, 0, 1, 3,
|
139 |
-
1, 7, 1, 10),
|
140 |
-
Pos(0, 3), "selectNextOccurrence", hasSel(0, 2, 0, 5),
|
141 |
-
"selectNextOccurrence", hasSel(0, 2, 0, 5,
|
142 |
-
1, 7, 1, 10),
|
143 |
-
setSel(0, 6, 0, 9),
|
144 |
-
"selectNextOccurrence", hasSel(0, 6, 0, 9,
|
145 |
-
1, 3, 1, 6));
|
146 |
-
|
147 |
-
stTest("selectScope", "foo(a) {\n bar[1, 2];\n}",
|
148 |
-
"selectScope", hasSel(0, 0, 2, 1),
|
149 |
-
Pos(0, 4), "selectScope", hasSel(0, 4, 0, 5),
|
150 |
-
Pos(0, 5), "selectScope", hasSel(0, 4, 0, 5),
|
151 |
-
Pos(0, 6), "selectScope", hasSel(0, 0, 2, 1),
|
152 |
-
Pos(0, 8), "selectScope", hasSel(0, 8, 2, 0),
|
153 |
-
Pos(1, 2), "selectScope", hasSel(0, 8, 2, 0),
|
154 |
-
Pos(1, 6), "selectScope", hasSel(1, 6, 1, 10),
|
155 |
-
Pos(1, 9), "selectScope", hasSel(1, 6, 1, 10));
|
156 |
-
|
157 |
-
stTest("goToBracket", "foo(a) {\n bar[1, 2];\n}",
|
158 |
-
Pos(0, 0), "goToBracket", at(0, 0),
|
159 |
-
Pos(0, 4), "goToBracket", at(0, 5), "goToBracket", at(0, 4),
|
160 |
-
Pos(0, 8), "goToBracket", at(2, 0), "goToBracket", at(0, 8),
|
161 |
-
Pos(1, 2), "goToBracket", at(2, 0),
|
162 |
-
Pos(1, 7), "goToBracket", at(1, 10), "goToBracket", at(1, 6));
|
163 |
-
|
164 |
-
stTest("swapLine", "1\n2\n3---\n4\n5",
|
165 |
-
"swapLineDown", val("2\n1\n3---\n4\n5"),
|
166 |
-
"swapLineUp", val("1\n2\n3---\n4\n5"),
|
167 |
-
"swapLineUp", val("1\n2\n3---\n4\n5"),
|
168 |
-
Pos(4, 1), "swapLineDown", val("1\n2\n3---\n4\n5"),
|
169 |
-
setSel(0, 1, 0, 1,
|
170 |
-
1, 0, 2, 0,
|
171 |
-
2, 2, 2, 2),
|
172 |
-
"swapLineDown", val("4\n1\n2\n3---\n5"),
|
173 |
-
hasSel(1, 1, 1, 1,
|
174 |
-
2, 0, 3, 0,
|
175 |
-
3, 2, 3, 2),
|
176 |
-
"swapLineUp", val("1\n2\n3---\n4\n5"),
|
177 |
-
hasSel(0, 1, 0, 1,
|
178 |
-
1, 0, 2, 0,
|
179 |
-
2, 2, 2, 2));
|
180 |
-
|
181 |
-
stTest("swapLineEmptyBottomSel", "1\n2\n3",
|
182 |
-
setSel(0, 1, 1, 0),
|
183 |
-
"swapLineDown", val("2\n1\n3"), hasSel(1, 1, 2, 0),
|
184 |
-
"swapLineUp", val("1\n2\n3"), hasSel(0, 1, 1, 0),
|
185 |
-
"swapLineUp", val("1\n2\n3"), hasSel(0, 0, 0, 0));
|
186 |
-
|
187 |
-
stTest("swapLineUpFromEnd", "a\nb\nc",
|
188 |
-
Pos(2, 1), "swapLineUp",
|
189 |
-
hasSel(1, 1, 1, 1), val("a\nc\nb"));
|
190 |
-
|
191 |
-
stTest("joinLines", "abc\ndef\nghi\njkl",
|
192 |
-
"joinLines", val("abc def\nghi\njkl"), at(0, 4),
|
193 |
-
"undo",
|
194 |
-
setSel(0, 2, 1, 1), "joinLines",
|
195 |
-
val("abc def ghi\njkl"), hasSel(0, 2, 0, 8),
|
196 |
-
"undo",
|
197 |
-
setSel(0, 1, 0, 1,
|
198 |
-
1, 1, 1, 1,
|
199 |
-
3, 1, 3, 1), "joinLines",
|
200 |
-
val("abc def ghi\njkl"), hasSel(0, 4, 0, 4,
|
201 |
-
0, 8, 0, 8,
|
202 |
-
1, 3, 1, 3));
|
203 |
-
|
204 |
-
stTest("duplicateLine", "abc\ndef\nghi",
|
205 |
-
Pos(1, 0), "duplicateLine", val("abc\ndef\ndef\nghi"), at(2, 0),
|
206 |
-
"undo",
|
207 |
-
setSel(0, 1, 0, 1,
|
208 |
-
1, 1, 1, 1,
|
209 |
-
2, 1, 2, 1), "duplicateLine",
|
210 |
-
val("abc\nabc\ndef\ndef\nghi\nghi"), hasSel(1, 1, 1, 1,
|
211 |
-
3, 1, 3, 1,
|
212 |
-
5, 1, 5, 1));
|
213 |
-
stTest("duplicateLineSelection", "abcdef",
|
214 |
-
setSel(0, 1, 0, 1,
|
215 |
-
0, 2, 0, 4,
|
216 |
-
0, 5, 0, 5),
|
217 |
-
"duplicateLine",
|
218 |
-
val("abcdef\nabcdcdef\nabcdcdef"), hasSel(2, 1, 2, 1,
|
219 |
-
2, 4, 2, 6,
|
220 |
-
2, 7, 2, 7));
|
221 |
-
|
222 |
-
stTest("selectLinesUpward", "123\n345\n789\n012",
|
223 |
-
setSel(0, 1, 0, 1,
|
224 |
-
1, 1, 1, 3,
|
225 |
-
2, 0, 2, 0,
|
226 |
-
3, 0, 3, 0),
|
227 |
-
"selectLinesUpward",
|
228 |
-
hasSel(0, 1, 0, 1,
|
229 |
-
0, 3, 0, 3,
|
230 |
-
1, 0, 1, 0,
|
231 |
-
1, 1, 1, 3,
|
232 |
-
2, 0, 2, 0,
|
233 |
-
3, 0, 3, 0));
|
234 |
-
|
235 |
-
stTest("selectLinesDownward", "123\n345\n789\n012",
|
236 |
-
setSel(0, 1, 0, 1,
|
237 |
-
1, 1, 1, 3,
|
238 |
-
2, 0, 2, 0,
|
239 |
-
3, 0, 3, 0),
|
240 |
-
"selectLinesDownward",
|
241 |
-
hasSel(0, 1, 0, 1,
|
242 |
-
1, 1, 1, 3,
|
243 |
-
2, 0, 2, 0,
|
244 |
-
2, 3, 2, 3,
|
245 |
-
3, 0, 3, 0));
|
246 |
-
|
247 |
-
stTest("sortLines", "c\nb\na\nC\nB\nA",
|
248 |
-
"sortLines", val("A\nB\nC\na\nb\nc"),
|
249 |
-
"undo",
|
250 |
-
setSel(0, 0, 2, 0,
|
251 |
-
3, 0, 5, 0),
|
252 |
-
"sortLines", val("a\nb\nc\nA\nB\nC"),
|
253 |
-
hasSel(0, 0, 2, 1,
|
254 |
-
3, 0, 5, 1),
|
255 |
-
"undo",
|
256 |
-
setSel(1, 0, 4, 0), "sortLinesInsensitive", val("c\na\nB\nb\nC\nA"));
|
257 |
-
|
258 |
-
stTest("bookmarks", "abc\ndef\nghi\njkl",
|
259 |
-
Pos(0, 1), "toggleBookmark",
|
260 |
-
setSel(1, 1, 1, 2), "toggleBookmark",
|
261 |
-
setSel(2, 1, 2, 2), "toggleBookmark",
|
262 |
-
"nextBookmark", hasSel(0, 1, 0, 1),
|
263 |
-
"nextBookmark", hasSel(1, 1, 1, 2),
|
264 |
-
"nextBookmark", hasSel(2, 1, 2, 2),
|
265 |
-
"prevBookmark", hasSel(1, 1, 1, 2),
|
266 |
-
"prevBookmark", hasSel(0, 1, 0, 1),
|
267 |
-
"prevBookmark", hasSel(2, 1, 2, 2),
|
268 |
-
"prevBookmark", hasSel(1, 1, 1, 2),
|
269 |
-
"toggleBookmark",
|
270 |
-
"prevBookmark", hasSel(2, 1, 2, 2),
|
271 |
-
"prevBookmark", hasSel(0, 1, 0, 1),
|
272 |
-
"selectBookmarks", hasSel(0, 1, 0, 1,
|
273 |
-
2, 1, 2, 2),
|
274 |
-
"clearBookmarks",
|
275 |
-
Pos(0, 0), "selectBookmarks", at(0, 0));
|
276 |
-
|
277 |
-
stTest("upAndDowncaseAtCursor", "abc\ndef x\nghI",
|
278 |
-
setSel(0, 1, 0, 3,
|
279 |
-
1, 1, 1, 1,
|
280 |
-
1, 4, 1, 4), "upcaseAtCursor",
|
281 |
-
val("aBC\nDEF x\nghI"), hasSel(0, 1, 0, 3,
|
282 |
-
1, 3, 1, 3,
|
283 |
-
1, 4, 1, 4),
|
284 |
-
"downcaseAtCursor",
|
285 |
-
val("abc\ndef x\nghI"), hasSel(0, 1, 0, 3,
|
286 |
-
1, 3, 1, 3,
|
287 |
-
1, 4, 1, 4));
|
288 |
-
|
289 |
-
stTest("mark", "abc\ndef\nghi",
|
290 |
-
Pos(1, 1), "setSublimeMark",
|
291 |
-
Pos(2, 1), "selectToSublimeMark", hasSel(2, 1, 1, 1),
|
292 |
-
Pos(0, 1), "swapWithSublimeMark", at(1, 1), "swapWithSublimeMark", at(0, 1),
|
293 |
-
"deleteToSublimeMark", val("aef\nghi"),
|
294 |
-
"sublimeYank", val("abc\ndef\nghi"), at(1, 1));
|
295 |
-
|
296 |
-
stTest("findUnder", "foo foobar a",
|
297 |
-
"findUnder", hasSel(0, 4, 0, 7),
|
298 |
-
"findUnder", hasSel(0, 0, 0, 3),
|
299 |
-
"findUnderPrevious", hasSel(0, 4, 0, 7),
|
300 |
-
"findUnderPrevious", hasSel(0, 0, 0, 3),
|
301 |
-
Pos(0, 4), "findUnder", hasSel(0, 4, 0, 10),
|
302 |
-
Pos(0, 11), "findUnder", hasSel(0, 11, 0, 11));
|
303 |
-
})();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/test.js
DELETED
@@ -1,2142 +0,0 @@
|
|
1 |
-
var Pos = CodeMirror.Pos;
|
2 |
-
|
3 |
-
CodeMirror.defaults.rtlMoveVisually = true;
|
4 |
-
|
5 |
-
function forEach(arr, f) {
|
6 |
-
for (var i = 0, e = arr.length; i < e; ++i) f(arr[i], i);
|
7 |
-
}
|
8 |
-
|
9 |
-
function addDoc(cm, width, height) {
|
10 |
-
var content = [], line = "";
|
11 |
-
for (var i = 0; i < width; ++i) line += "x";
|
12 |
-
for (var i = 0; i < height; ++i) content.push(line);
|
13 |
-
cm.setValue(content.join("\n"));
|
14 |
-
}
|
15 |
-
|
16 |
-
function byClassName(elt, cls) {
|
17 |
-
if (elt.getElementsByClassName) return elt.getElementsByClassName(cls);
|
18 |
-
var found = [], re = new RegExp("\\b" + cls + "\\b");
|
19 |
-
function search(elt) {
|
20 |
-
if (elt.nodeType == 3) return;
|
21 |
-
if (re.test(elt.className)) found.push(elt);
|
22 |
-
for (var i = 0, e = elt.childNodes.length; i < e; ++i)
|
23 |
-
search(elt.childNodes[i]);
|
24 |
-
}
|
25 |
-
search(elt);
|
26 |
-
return found;
|
27 |
-
}
|
28 |
-
|
29 |
-
var ie_lt8 = /MSIE [1-7]\b/.test(navigator.userAgent);
|
30 |
-
var ie_lt9 = /MSIE [1-8]\b/.test(navigator.userAgent);
|
31 |
-
var mac = /Mac/.test(navigator.platform);
|
32 |
-
var phantom = /PhantomJS/.test(navigator.userAgent);
|
33 |
-
var opera = /Opera\/\./.test(navigator.userAgent);
|
34 |
-
var opera_version = opera && navigator.userAgent.match(/Version\/(\d+\.\d+)/);
|
35 |
-
if (opera_version) opera_version = Number(opera_version);
|
36 |
-
var opera_lt10 = opera && (!opera_version || opera_version < 10);
|
37 |
-
|
38 |
-
namespace = "core_";
|
39 |
-
|
40 |
-
test("core_fromTextArea", function() {
|
41 |
-
var te = document.getElementById("code");
|
42 |
-
te.value = "CONTENT";
|
43 |
-
var cm = CodeMirror.fromTextArea(te);
|
44 |
-
is(!te.offsetHeight);
|
45 |
-
eq(cm.getValue(), "CONTENT");
|
46 |
-
cm.setValue("foo\nbar");
|
47 |
-
eq(cm.getValue(), "foo\nbar");
|
48 |
-
cm.save();
|
49 |
-
is(/^foo\r?\nbar$/.test(te.value));
|
50 |
-
cm.setValue("xxx");
|
51 |
-
cm.toTextArea();
|
52 |
-
is(te.offsetHeight);
|
53 |
-
eq(te.value, "xxx");
|
54 |
-
});
|
55 |
-
|
56 |
-
testCM("getRange", function(cm) {
|
57 |
-
eq(cm.getLine(0), "1234");
|
58 |
-
eq(cm.getLine(1), "5678");
|
59 |
-
eq(cm.getLine(2), null);
|
60 |
-
eq(cm.getLine(-1), null);
|
61 |
-
eq(cm.getRange(Pos(0, 0), Pos(0, 3)), "123");
|
62 |
-
eq(cm.getRange(Pos(0, -1), Pos(0, 200)), "1234");
|
63 |
-
eq(cm.getRange(Pos(0, 2), Pos(1, 2)), "34\n56");
|
64 |
-
eq(cm.getRange(Pos(1, 2), Pos(100, 0)), "78");
|
65 |
-
}, {value: "1234\n5678"});
|
66 |
-
|
67 |
-
testCM("replaceRange", function(cm) {
|
68 |
-
eq(cm.getValue(), "");
|
69 |
-
cm.replaceRange("foo\n", Pos(0, 0));
|
70 |
-
eq(cm.getValue(), "foo\n");
|
71 |
-
cm.replaceRange("a\nb", Pos(0, 1));
|
72 |
-
eq(cm.getValue(), "fa\nboo\n");
|
73 |
-
eq(cm.lineCount(), 3);
|
74 |
-
cm.replaceRange("xyzzy", Pos(0, 0), Pos(1, 1));
|
75 |
-
eq(cm.getValue(), "xyzzyoo\n");
|
76 |
-
cm.replaceRange("abc", Pos(0, 0), Pos(10, 0));
|
77 |
-
eq(cm.getValue(), "abc");
|
78 |
-
eq(cm.lineCount(), 1);
|
79 |
-
});
|
80 |
-
|
81 |
-
testCM("selection", function(cm) {
|
82 |
-
cm.setSelection(Pos(0, 4), Pos(2, 2));
|
83 |
-
is(cm.somethingSelected());
|
84 |
-
eq(cm.getSelection(), "11\n222222\n33");
|
85 |
-
eqPos(cm.getCursor(false), Pos(2, 2));
|
86 |
-
eqPos(cm.getCursor(true), Pos(0, 4));
|
87 |
-
cm.setSelection(Pos(1, 0));
|
88 |
-
is(!cm.somethingSelected());
|
89 |
-
eq(cm.getSelection(), "");
|
90 |
-
eqPos(cm.getCursor(true), Pos(1, 0));
|
91 |
-
cm.replaceSelection("abc", "around");
|
92 |
-
eq(cm.getSelection(), "abc");
|
93 |
-
eq(cm.getValue(), "111111\nabc222222\n333333");
|
94 |
-
cm.replaceSelection("def", "end");
|
95 |
-
eq(cm.getSelection(), "");
|
96 |
-
eqPos(cm.getCursor(true), Pos(1, 3));
|
97 |
-
cm.setCursor(Pos(2, 1));
|
98 |
-
eqPos(cm.getCursor(true), Pos(2, 1));
|
99 |
-
cm.setCursor(1, 2);
|
100 |
-
eqPos(cm.getCursor(true), Pos(1, 2));
|
101 |
-
}, {value: "111111\n222222\n333333"});
|
102 |
-
|
103 |
-
testCM("extendSelection", function(cm) {
|
104 |
-
cm.setExtending(true);
|
105 |
-
addDoc(cm, 10, 10);
|
106 |
-
cm.setSelection(Pos(3, 5));
|
107 |
-
eqPos(cm.getCursor("head"), Pos(3, 5));
|
108 |
-
eqPos(cm.getCursor("anchor"), Pos(3, 5));
|
109 |
-
cm.setSelection(Pos(2, 5), Pos(5, 5));
|
110 |
-
eqPos(cm.getCursor("head"), Pos(5, 5));
|
111 |
-
eqPos(cm.getCursor("anchor"), Pos(2, 5));
|
112 |
-
eqPos(cm.getCursor("start"), Pos(2, 5));
|
113 |
-
eqPos(cm.getCursor("end"), Pos(5, 5));
|
114 |
-
cm.setSelection(Pos(5, 5), Pos(2, 5));
|
115 |
-
eqPos(cm.getCursor("head"), Pos(2, 5));
|
116 |
-
eqPos(cm.getCursor("anchor"), Pos(5, 5));
|
117 |
-
eqPos(cm.getCursor("start"), Pos(2, 5));
|
118 |
-
eqPos(cm.getCursor("end"), Pos(5, 5));
|
119 |
-
cm.extendSelection(Pos(3, 2));
|
120 |
-
eqPos(cm.getCursor("head"), Pos(3, 2));
|
121 |
-
eqPos(cm.getCursor("anchor"), Pos(5, 5));
|
122 |
-
cm.extendSelection(Pos(6, 2));
|
123 |
-
eqPos(cm.getCursor("head"), Pos(6, 2));
|
124 |
-
eqPos(cm.getCursor("anchor"), Pos(5, 5));
|
125 |
-
cm.extendSelection(Pos(6, 3), Pos(6, 4));
|
126 |
-
eqPos(cm.getCursor("head"), Pos(6, 4));
|
127 |
-
eqPos(cm.getCursor("anchor"), Pos(5, 5));
|
128 |
-
cm.extendSelection(Pos(0, 3), Pos(0, 4));
|
129 |
-
eqPos(cm.getCursor("head"), Pos(0, 3));
|
130 |
-
eqPos(cm.getCursor("anchor"), Pos(5, 5));
|
131 |
-
cm.extendSelection(Pos(4, 5), Pos(6, 5));
|
132 |
-
eqPos(cm.getCursor("head"), Pos(6, 5));
|
133 |
-
eqPos(cm.getCursor("anchor"), Pos(4, 5));
|
134 |
-
cm.setExtending(false);
|
135 |
-
cm.extendSelection(Pos(0, 3), Pos(0, 4));
|
136 |
-
eqPos(cm.getCursor("head"), Pos(0, 3));
|
137 |
-
eqPos(cm.getCursor("anchor"), Pos(0, 4));
|
138 |
-
});
|
139 |
-
|
140 |
-
testCM("lines", function(cm) {
|
141 |
-
eq(cm.getLine(0), "111111");
|
142 |
-
eq(cm.getLine(1), "222222");
|
143 |
-
eq(cm.getLine(-1), null);
|
144 |
-
cm.replaceRange("", Pos(1, 0), Pos(2, 0))
|
145 |
-
cm.replaceRange("abc", Pos(1, 0), Pos(1));
|
146 |
-
eq(cm.getValue(), "111111\nabc");
|
147 |
-
}, {value: "111111\n222222\n333333"});
|
148 |
-
|
149 |
-
testCM("indent", function(cm) {
|
150 |
-
cm.indentLine(1);
|
151 |
-
eq(cm.getLine(1), " blah();");
|
152 |
-
cm.setOption("indentUnit", 8);
|
153 |
-
cm.indentLine(1);
|
154 |
-
eq(cm.getLine(1), "\tblah();");
|
155 |
-
cm.setOption("indentUnit", 10);
|
156 |
-
cm.setOption("tabSize", 4);
|
157 |
-
cm.indentLine(1);
|
158 |
-
eq(cm.getLine(1), "\t\t blah();");
|
159 |
-
}, {value: "if (x) {\nblah();\n}", indentUnit: 3, indentWithTabs: true, tabSize: 8});
|
160 |
-
|
161 |
-
testCM("indentByNumber", function(cm) {
|
162 |
-
cm.indentLine(0, 2);
|
163 |
-
eq(cm.getLine(0), " foo");
|
164 |
-
cm.indentLine(0, -200);
|
165 |
-
eq(cm.getLine(0), "foo");
|
166 |
-
cm.setSelection(Pos(0, 0), Pos(1, 2));
|
167 |
-
cm.indentSelection(3);
|
168 |
-
eq(cm.getValue(), " foo\n bar\nbaz");
|
169 |
-
}, {value: "foo\nbar\nbaz"});
|
170 |
-
|
171 |
-
test("core_defaults", function() {
|
172 |
-
var defsCopy = {}, defs = CodeMirror.defaults;
|
173 |
-
for (var opt in defs) defsCopy[opt] = defs[opt];
|
174 |
-
defs.indentUnit = 5;
|
175 |
-
defs.value = "uu";
|
176 |
-
defs.indentWithTabs = true;
|
177 |
-
defs.tabindex = 55;
|
178 |
-
var place = document.getElementById("testground"), cm = CodeMirror(place);
|
179 |
-
try {
|
180 |
-
eq(cm.getOption("indentUnit"), 5);
|
181 |
-
cm.setOption("indentUnit", 10);
|
182 |
-
eq(defs.indentUnit, 5);
|
183 |
-
eq(cm.getValue(), "uu");
|
184 |
-
eq(cm.getOption("indentWithTabs"), true);
|
185 |
-
eq(cm.getInputField().tabIndex, 55);
|
186 |
-
}
|
187 |
-
finally {
|
188 |
-
for (var opt in defsCopy) defs[opt] = defsCopy[opt];
|
189 |
-
place.removeChild(cm.getWrapperElement());
|
190 |
-
}
|
191 |
-
});
|
192 |
-
|
193 |
-
testCM("lineInfo", function(cm) {
|
194 |
-
eq(cm.lineInfo(-1), null);
|
195 |
-
var mark = document.createElement("span");
|
196 |
-
var lh = cm.setGutterMarker(1, "FOO", mark);
|
197 |
-
var info = cm.lineInfo(1);
|
198 |
-
eq(info.text, "222222");
|
199 |
-
eq(info.gutterMarkers.FOO, mark);
|
200 |
-
eq(info.line, 1);
|
201 |
-
eq(cm.lineInfo(2).gutterMarkers, null);
|
202 |
-
cm.setGutterMarker(lh, "FOO", null);
|
203 |
-
eq(cm.lineInfo(1).gutterMarkers, null);
|
204 |
-
cm.setGutterMarker(1, "FOO", mark);
|
205 |
-
cm.setGutterMarker(0, "FOO", mark);
|
206 |
-
cm.clearGutter("FOO");
|
207 |
-
eq(cm.lineInfo(0).gutterMarkers, null);
|
208 |
-
eq(cm.lineInfo(1).gutterMarkers, null);
|
209 |
-
}, {value: "111111\n222222\n333333"});
|
210 |
-
|
211 |
-
testCM("coords", function(cm) {
|
212 |
-
cm.setSize(null, 100);
|
213 |
-
addDoc(cm, 32, 200);
|
214 |
-
var top = cm.charCoords(Pos(0, 0));
|
215 |
-
var bot = cm.charCoords(Pos(200, 30));
|
216 |
-
is(top.left < bot.left);
|
217 |
-
is(top.top < bot.top);
|
218 |
-
is(top.top < top.bottom);
|
219 |
-
cm.scrollTo(null, 100);
|
220 |
-
var top2 = cm.charCoords(Pos(0, 0));
|
221 |
-
is(top.top > top2.top);
|
222 |
-
eq(top.left, top2.left);
|
223 |
-
});
|
224 |
-
|
225 |
-
testCM("coordsChar", function(cm) {
|
226 |
-
addDoc(cm, 35, 70);
|
227 |
-
for (var i = 0; i < 2; ++i) {
|
228 |
-
var sys = i ? "local" : "page";
|
229 |
-
for (var ch = 0; ch <= 35; ch += 5) {
|
230 |
-
for (var line = 0; line < 70; line += 5) {
|
231 |
-
cm.setCursor(line, ch);
|
232 |
-
var coords = cm.charCoords(Pos(line, ch), sys);
|
233 |
-
var pos = cm.coordsChar({left: coords.left + 1, top: coords.top + 1}, sys);
|
234 |
-
eqPos(pos, Pos(line, ch));
|
235 |
-
}
|
236 |
-
}
|
237 |
-
}
|
238 |
-
}, {lineNumbers: true});
|
239 |
-
|
240 |
-
testCM("posFromIndex", function(cm) {
|
241 |
-
cm.setValue(
|
242 |
-
"This function should\n" +
|
243 |
-
"convert a zero based index\n" +
|
244 |
-
"to line and ch."
|
245 |
-
);
|
246 |
-
|
247 |
-
var examples = [
|
248 |
-
{ index: -1, line: 0, ch: 0 }, // <- Tests clipping
|
249 |
-
{ index: 0, line: 0, ch: 0 },
|
250 |
-
{ index: 10, line: 0, ch: 10 },
|
251 |
-
{ index: 39, line: 1, ch: 18 },
|
252 |
-
{ index: 55, line: 2, ch: 7 },
|
253 |
-
{ index: 63, line: 2, ch: 15 },
|
254 |
-
{ index: 64, line: 2, ch: 15 } // <- Tests clipping
|
255 |
-
];
|
256 |
-
|
257 |
-
for (var i = 0; i < examples.length; i++) {
|
258 |
-
var example = examples[i];
|
259 |
-
var pos = cm.posFromIndex(example.index);
|
260 |
-
eq(pos.line, example.line);
|
261 |
-
eq(pos.ch, example.ch);
|
262 |
-
if (example.index >= 0 && example.index < 64)
|
263 |
-
eq(cm.indexFromPos(pos), example.index);
|
264 |
-
}
|
265 |
-
});
|
266 |
-
|
267 |
-
testCM("undo", function(cm) {
|
268 |
-
cm.replaceRange("def", Pos(0, 0), Pos(0));
|
269 |
-
eq(cm.historySize().undo, 1);
|
270 |
-
cm.undo();
|
271 |
-
eq(cm.getValue(), "abc");
|
272 |
-
eq(cm.historySize().undo, 0);
|
273 |
-
eq(cm.historySize().redo, 1);
|
274 |
-
cm.redo();
|
275 |
-
eq(cm.getValue(), "def");
|
276 |
-
eq(cm.historySize().undo, 1);
|
277 |
-
eq(cm.historySize().redo, 0);
|
278 |
-
cm.setValue("1\n\n\n2");
|
279 |
-
cm.clearHistory();
|
280 |
-
eq(cm.historySize().undo, 0);
|
281 |
-
for (var i = 0; i < 20; ++i) {
|
282 |
-
cm.replaceRange("a", Pos(0, 0));
|
283 |
-
cm.replaceRange("b", Pos(3, 0));
|
284 |
-
}
|
285 |
-
eq(cm.historySize().undo, 40);
|
286 |
-
for (var i = 0; i < 40; ++i)
|
287 |
-
cm.undo();
|
288 |
-
eq(cm.historySize().redo, 40);
|
289 |
-
eq(cm.getValue(), "1\n\n\n2");
|
290 |
-
}, {value: "abc"});
|
291 |
-
|
292 |
-
testCM("undoDepth", function(cm) {
|
293 |
-
cm.replaceRange("d", Pos(0));
|
294 |
-
cm.replaceRange("e", Pos(0));
|
295 |
-
cm.replaceRange("f", Pos(0));
|
296 |
-
cm.undo(); cm.undo(); cm.undo();
|
297 |
-
eq(cm.getValue(), "abcd");
|
298 |
-
}, {value: "abc", undoDepth: 4});
|
299 |
-
|
300 |
-
testCM("undoDoesntClearValue", function(cm) {
|
301 |
-
cm.undo();
|
302 |
-
eq(cm.getValue(), "x");
|
303 |
-
}, {value: "x"});
|
304 |
-
|
305 |
-
testCM("undoMultiLine", function(cm) {
|
306 |
-
cm.operation(function() {
|
307 |
-
cm.replaceRange("x", Pos(0, 0));
|
308 |
-
cm.replaceRange("y", Pos(1, 0));
|
309 |
-
});
|
310 |
-
cm.undo();
|
311 |
-
eq(cm.getValue(), "abc\ndef\nghi");
|
312 |
-
cm.operation(function() {
|
313 |
-
cm.replaceRange("y", Pos(1, 0));
|
314 |
-
cm.replaceRange("x", Pos(0, 0));
|
315 |
-
});
|
316 |
-
cm.undo();
|
317 |
-
eq(cm.getValue(), "abc\ndef\nghi");
|
318 |
-
cm.operation(function() {
|
319 |
-
cm.replaceRange("y", Pos(2, 0));
|
320 |
-
cm.replaceRange("x", Pos(1, 0));
|
321 |
-
cm.replaceRange("z", Pos(2, 0));
|
322 |
-
});
|
323 |
-
cm.undo();
|
324 |
-
eq(cm.getValue(), "abc\ndef\nghi", 3);
|
325 |
-
}, {value: "abc\ndef\nghi"});
|
326 |
-
|
327 |
-
testCM("undoComposite", function(cm) {
|
328 |
-
cm.replaceRange("y", Pos(1));
|
329 |
-
cm.operation(function() {
|
330 |
-
cm.replaceRange("x", Pos(0));
|
331 |
-
cm.replaceRange("z", Pos(2));
|
332 |
-
});
|
333 |
-
eq(cm.getValue(), "ax\nby\ncz\n");
|
334 |
-
cm.undo();
|
335 |
-
eq(cm.getValue(), "a\nby\nc\n");
|
336 |
-
cm.undo();
|
337 |
-
eq(cm.getValue(), "a\nb\nc\n");
|
338 |
-
cm.redo(); cm.redo();
|
339 |
-
eq(cm.getValue(), "ax\nby\ncz\n");
|
340 |
-
}, {value: "a\nb\nc\n"});
|
341 |
-
|
342 |
-
testCM("undoSelection", function(cm) {
|
343 |
-
cm.setSelection(Pos(0, 2), Pos(0, 4));
|
344 |
-
cm.replaceSelection("");
|
345 |
-
cm.setCursor(Pos(1, 0));
|
346 |
-
cm.undo();
|
347 |
-
eqPos(cm.getCursor(true), Pos(0, 2));
|
348 |
-
eqPos(cm.getCursor(false), Pos(0, 4));
|
349 |
-
cm.setCursor(Pos(1, 0));
|
350 |
-
cm.redo();
|
351 |
-
eqPos(cm.getCursor(true), Pos(0, 2));
|
352 |
-
eqPos(cm.getCursor(false), Pos(0, 2));
|
353 |
-
}, {value: "abcdefgh\n"});
|
354 |
-
|
355 |
-
testCM("undoSelectionAsBefore", function(cm) {
|
356 |
-
cm.replaceSelection("abc", "around");
|
357 |
-
cm.undo();
|
358 |
-
cm.redo();
|
359 |
-
eq(cm.getSelection(), "abc");
|
360 |
-
});
|
361 |
-
|
362 |
-
testCM("selectionChangeConfusesHistory", function(cm) {
|
363 |
-
cm.replaceSelection("abc", null, "dontmerge");
|
364 |
-
cm.operation(function() {
|
365 |
-
cm.setCursor(Pos(0, 0));
|
366 |
-
cm.replaceSelection("abc", null, "dontmerge");
|
367 |
-
});
|
368 |
-
eq(cm.historySize().undo, 2);
|
369 |
-
});
|
370 |
-
|
371 |
-
testCM("markTextSingleLine", function(cm) {
|
372 |
-
forEach([{a: 0, b: 1, c: "", f: 2, t: 5},
|
373 |
-
{a: 0, b: 4, c: "", f: 0, t: 2},
|
374 |
-
{a: 1, b: 2, c: "x", f: 3, t: 6},
|
375 |
-
{a: 4, b: 5, c: "", f: 3, t: 5},
|
376 |
-
{a: 4, b: 5, c: "xx", f: 3, t: 7},
|
377 |
-
{a: 2, b: 5, c: "", f: 2, t: 3},
|
378 |
-
{a: 2, b: 5, c: "abcd", f: 6, t: 7},
|
379 |
-
{a: 2, b: 6, c: "x", f: null, t: null},
|
380 |
-
{a: 3, b: 6, c: "", f: null, t: null},
|
381 |
-
{a: 0, b: 9, c: "hallo", f: null, t: null},
|
382 |
-
{a: 4, b: 6, c: "x", f: 3, t: 4},
|
383 |
-
{a: 4, b: 8, c: "", f: 3, t: 4},
|
384 |
-
{a: 6, b: 6, c: "a", f: 3, t: 6},
|
385 |
-
{a: 8, b: 9, c: "", f: 3, t: 6}], function(test) {
|
386 |
-
cm.setValue("1234567890");
|
387 |
-
var r = cm.markText(Pos(0, 3), Pos(0, 6), {className: "foo"});
|
388 |
-
cm.replaceRange(test.c, Pos(0, test.a), Pos(0, test.b));
|
389 |
-
var f = r.find();
|
390 |
-
eq(f && f.from.ch, test.f); eq(f && f.to.ch, test.t);
|
391 |
-
});
|
392 |
-
});
|
393 |
-
|
394 |
-
testCM("markTextMultiLine", function(cm) {
|
395 |
-
function p(v) { return v && Pos(v[0], v[1]); }
|
396 |
-
forEach([{a: [0, 0], b: [0, 5], c: "", f: [0, 0], t: [2, 5]},
|
397 |
-
{a: [0, 0], b: [0, 5], c: "foo\n", f: [1, 0], t: [3, 5]},
|
398 |
-
{a: [0, 1], b: [0, 10], c: "", f: [0, 1], t: [2, 5]},
|
399 |
-
{a: [0, 5], b: [0, 6], c: "x", f: [0, 6], t: [2, 5]},
|
400 |
-
{a: [0, 0], b: [1, 0], c: "", f: [0, 0], t: [1, 5]},
|
401 |
-
{a: [0, 6], b: [2, 4], c: "", f: [0, 5], t: [0, 7]},
|
402 |
-
{a: [0, 6], b: [2, 4], c: "aa", f: [0, 5], t: [0, 9]},
|
403 |
-
{a: [1, 2], b: [1, 8], c: "", f: [0, 5], t: [2, 5]},
|
404 |
-
{a: [0, 5], b: [2, 5], c: "xx", f: null, t: null},
|
405 |
-
{a: [0, 0], b: [2, 10], c: "x", f: null, t: null},
|
406 |
-
{a: [1, 5], b: [2, 5], c: "", f: [0, 5], t: [1, 5]},
|
407 |
-
{a: [2, 0], b: [2, 3], c: "", f: [0, 5], t: [2, 2]},
|
408 |
-
{a: [2, 5], b: [3, 0], c: "a\nb", f: [0, 5], t: [2, 5]},
|
409 |
-
{a: [2, 3], b: [3, 0], c: "x", f: [0, 5], t: [2, 3]},
|
410 |
-
{a: [1, 1], b: [1, 9], c: "1\n2\n3", f: [0, 5], t: [4, 5]}], function(test) {
|
411 |
-
cm.setValue("aaaaaaaaaa\nbbbbbbbbbb\ncccccccccc\ndddddddd\n");
|
412 |
-
var r = cm.markText(Pos(0, 5), Pos(2, 5),
|
413 |
-
{className: "CodeMirror-matchingbracket"});
|
414 |
-
cm.replaceRange(test.c, p(test.a), p(test.b));
|
415 |
-
var f = r.find();
|
416 |
-
eqPos(f && f.from, p(test.f)); eqPos(f && f.to, p(test.t));
|
417 |
-
});
|
418 |
-
});
|
419 |
-
|
420 |
-
testCM("markTextUndo", function(cm) {
|
421 |
-
var marker1, marker2, bookmark;
|
422 |
-
marker1 = cm.markText(Pos(0, 1), Pos(0, 3),
|
423 |
-
{className: "CodeMirror-matchingbracket"});
|
424 |
-
marker2 = cm.markText(Pos(0, 0), Pos(2, 1),
|
425 |
-
{className: "CodeMirror-matchingbracket"});
|
426 |
-
bookmark = cm.setBookmark(Pos(1, 5));
|
427 |
-
cm.operation(function(){
|
428 |
-
cm.replaceRange("foo", Pos(0, 2));
|
429 |
-
cm.replaceRange("bar\nbaz\nbug\n", Pos(2, 0), Pos(3, 0));
|
430 |
-
});
|
431 |
-
var v1 = cm.getValue();
|
432 |
-
cm.setValue("");
|
433 |
-
eq(marker1.find(), null); eq(marker2.find(), null); eq(bookmark.find(), null);
|
434 |
-
cm.undo();
|
435 |
-
eqPos(bookmark.find(), Pos(1, 5), "still there");
|
436 |
-
cm.undo();
|
437 |
-
var m1Pos = marker1.find(), m2Pos = marker2.find();
|
438 |
-
eqPos(m1Pos.from, Pos(0, 1)); eqPos(m1Pos.to, Pos(0, 3));
|
439 |
-
eqPos(m2Pos.from, Pos(0, 0)); eqPos(m2Pos.to, Pos(2, 1));
|
440 |
-
eqPos(bookmark.find(), Pos(1, 5));
|
441 |
-
cm.redo(); cm.redo();
|
442 |
-
eq(bookmark.find(), null);
|
443 |
-
cm.undo();
|
444 |
-
eqPos(bookmark.find(), Pos(1, 5));
|
445 |
-
eq(cm.getValue(), v1);
|
446 |
-
}, {value: "1234\n56789\n00\n"});
|
447 |
-
|
448 |
-
testCM("markTextStayGone", function(cm) {
|
449 |
-
var m1 = cm.markText(Pos(0, 0), Pos(0, 1));
|
450 |
-
cm.replaceRange("hi", Pos(0, 2));
|
451 |
-
m1.clear();
|
452 |
-
cm.undo();
|
453 |
-
eq(m1.find(), null);
|
454 |
-
}, {value: "hello"});
|
455 |
-
|
456 |
-
testCM("markTextAllowEmpty", function(cm) {
|
457 |
-
var m1 = cm.markText(Pos(0, 1), Pos(0, 2), {clearWhenEmpty: false});
|
458 |
-
is(m1.find());
|
459 |
-
cm.replaceRange("x", Pos(0, 0));
|
460 |
-
is(m1.find());
|
461 |
-
cm.replaceRange("y", Pos(0, 2));
|
462 |
-
is(m1.find());
|
463 |
-
cm.replaceRange("z", Pos(0, 3), Pos(0, 4));
|
464 |
-
is(!m1.find());
|
465 |
-
var m2 = cm.markText(Pos(0, 1), Pos(0, 2), {clearWhenEmpty: false,
|
466 |
-
inclusiveLeft: true,
|
467 |
-
inclusiveRight: true});
|
468 |
-
cm.replaceRange("q", Pos(0, 1), Pos(0, 2));
|
469 |
-
is(m2.find());
|
470 |
-
cm.replaceRange("", Pos(0, 0), Pos(0, 3));
|
471 |
-
is(!m2.find());
|
472 |
-
var m3 = cm.markText(Pos(0, 1), Pos(0, 1), {clearWhenEmpty: false});
|
473 |
-
cm.replaceRange("a", Pos(0, 3));
|
474 |
-
is(m3.find());
|
475 |
-
cm.replaceRange("b", Pos(0, 1));
|
476 |
-
is(!m3.find());
|
477 |
-
}, {value: "abcde"});
|
478 |
-
|
479 |
-
testCM("markTextStacked", function(cm) {
|
480 |
-
var m1 = cm.markText(Pos(0, 0), Pos(0, 0), {clearWhenEmpty: false});
|
481 |
-
var m2 = cm.markText(Pos(0, 0), Pos(0, 0), {clearWhenEmpty: false});
|
482 |
-
cm.replaceRange("B", Pos(0, 1));
|
483 |
-
is(m1.find() && m2.find());
|
484 |
-
}, {value: "A"});
|
485 |
-
|
486 |
-
testCM("undoPreservesNewMarks", function(cm) {
|
487 |
-
cm.markText(Pos(0, 3), Pos(0, 4));
|
488 |
-
cm.markText(Pos(1, 1), Pos(1, 3));
|
489 |
-
cm.replaceRange("", Pos(0, 3), Pos(3, 1));
|
490 |
-
var mBefore = cm.markText(Pos(0, 0), Pos(0, 1));
|
491 |
-
var mAfter = cm.markText(Pos(0, 5), Pos(0, 6));
|
492 |
-
var mAround = cm.markText(Pos(0, 2), Pos(0, 4));
|
493 |
-
cm.undo();
|
494 |
-
eqPos(mBefore.find().from, Pos(0, 0));
|
495 |
-
eqPos(mBefore.find().to, Pos(0, 1));
|
496 |
-
eqPos(mAfter.find().from, Pos(3, 3));
|
497 |
-
eqPos(mAfter.find().to, Pos(3, 4));
|
498 |
-
eqPos(mAround.find().from, Pos(0, 2));
|
499 |
-
eqPos(mAround.find().to, Pos(3, 2));
|
500 |
-
var found = cm.findMarksAt(Pos(2, 2));
|
501 |
-
eq(found.length, 1);
|
502 |
-
eq(found[0], mAround);
|
503 |
-
}, {value: "aaaa\nbbbb\ncccc\ndddd"});
|
504 |
-
|
505 |
-
testCM("markClearBetween", function(cm) {
|
506 |
-
cm.setValue("aaa\nbbb\nccc\nddd\n");
|
507 |
-
cm.markText(Pos(0, 0), Pos(2));
|
508 |
-
cm.replaceRange("aaa\nbbb\nccc", Pos(0, 0), Pos(2));
|
509 |
-
eq(cm.findMarksAt(Pos(1, 1)).length, 0);
|
510 |
-
});
|
511 |
-
|
512 |
-
testCM("deleteSpanCollapsedInclusiveLeft", function(cm) {
|
513 |
-
var from = Pos(1, 0), to = Pos(1, 1);
|
514 |
-
var m = cm.markText(from, to, {collapsed: true, inclusiveLeft: true});
|
515 |
-
// Delete collapsed span.
|
516 |
-
cm.replaceRange("", from, to);
|
517 |
-
}, {value: "abc\nX\ndef"});
|
518 |
-
|
519 |
-
testCM("markTextCSS", function(cm) {
|
520 |
-
function present() {
|
521 |
-
var spans = cm.display.lineDiv.getElementsByTagName("span");
|
522 |
-
for (var i = 0; i < spans.length; i++)
|
523 |
-
if (spans[i].style.color == "cyan" && span[i].textContent == "cdefg") return true;
|
524 |
-
}
|
525 |
-
var m = cm.markText(Pos(0, 2), Pos(0, 6), {css: "color: cyan"});
|
526 |
-
m.clear();
|
527 |
-
is(!present());
|
528 |
-
}, {value: "abcdefgh"});
|
529 |
-
|
530 |
-
testCM("bookmark", function(cm) {
|
531 |
-
function p(v) { return v && Pos(v[0], v[1]); }
|
532 |
-
forEach([{a: [1, 0], b: [1, 1], c: "", d: [1, 4]},
|
533 |
-
{a: [1, 1], b: [1, 1], c: "xx", d: [1, 7]},
|
534 |
-
{a: [1, 4], b: [1, 5], c: "ab", d: [1, 6]},
|
535 |
-
{a: [1, 4], b: [1, 6], c: "", d: null},
|
536 |
-
{a: [1, 5], b: [1, 6], c: "abc", d: [1, 5]},
|
537 |
-
{a: [1, 6], b: [1, 8], c: "", d: [1, 5]},
|
538 |
-
{a: [1, 4], b: [1, 4], c: "\n\n", d: [3, 1]},
|
539 |
-
{bm: [1, 9], a: [1, 1], b: [1, 1], c: "\n", d: [2, 8]}], function(test) {
|
540 |
-
cm.setValue("1234567890\n1234567890\n1234567890");
|
541 |
-
var b = cm.setBookmark(p(test.bm) || Pos(1, 5));
|
542 |
-
cm.replaceRange(test.c, p(test.a), p(test.b));
|
543 |
-
eqPos(b.find(), p(test.d));
|
544 |
-
});
|
545 |
-
});
|
546 |
-
|
547 |
-
testCM("bookmarkInsertLeft", function(cm) {
|
548 |
-
var br = cm.setBookmark(Pos(0, 2), {insertLeft: false});
|
549 |
-
var bl = cm.setBookmark(Pos(0, 2), {insertLeft: true});
|
550 |
-
cm.setCursor(Pos(0, 2));
|
551 |
-
cm.replaceSelection("hi");
|
552 |
-
eqPos(br.find(), Pos(0, 2));
|
553 |
-
eqPos(bl.find(), Pos(0, 4));
|
554 |
-
cm.replaceRange("", Pos(0, 4), Pos(0, 5));
|
555 |
-
cm.replaceRange("", Pos(0, 2), Pos(0, 4));
|
556 |
-
cm.replaceRange("", Pos(0, 1), Pos(0, 2));
|
557 |
-
// Verify that deleting next to bookmarks doesn't kill them
|
558 |
-
eqPos(br.find(), Pos(0, 1));
|
559 |
-
eqPos(bl.find(), Pos(0, 1));
|
560 |
-
}, {value: "abcdef"});
|
561 |
-
|
562 |
-
testCM("bookmarkCursor", function(cm) {
|
563 |
-
var pos01 = cm.cursorCoords(Pos(0, 1)), pos11 = cm.cursorCoords(Pos(1, 1)),
|
564 |
-
pos20 = cm.cursorCoords(Pos(2, 0)), pos30 = cm.cursorCoords(Pos(3, 0)),
|
565 |
-
pos41 = cm.cursorCoords(Pos(4, 1));
|
566 |
-
cm.setBookmark(Pos(0, 1), {widget: document.createTextNode("←"), insertLeft: true});
|
567 |
-
cm.setBookmark(Pos(2, 0), {widget: document.createTextNode("←"), insertLeft: true});
|
568 |
-
cm.setBookmark(Pos(1, 1), {widget: document.createTextNode("→")});
|
569 |
-
cm.setBookmark(Pos(3, 0), {widget: document.createTextNode("→")});
|
570 |
-
var new01 = cm.cursorCoords(Pos(0, 1)), new11 = cm.cursorCoords(Pos(1, 1)),
|
571 |
-
new20 = cm.cursorCoords(Pos(2, 0)), new30 = cm.cursorCoords(Pos(3, 0));
|
572 |
-
near(new01.left, pos01.left, 1);
|
573 |
-
near(new01.top, pos01.top, 1);
|
574 |
-
is(new11.left > pos11.left, "at right, middle of line");
|
575 |
-
near(new11.top == pos11.top, 1);
|
576 |
-
near(new20.left, pos20.left, 1);
|
577 |
-
near(new20.top, pos20.top, 1);
|
578 |
-
is(new30.left > pos30.left, "at right, empty line");
|
579 |
-
near(new30.top, pos30, 1);
|
580 |
-
cm.setBookmark(Pos(4, 0), {widget: document.createTextNode("→")});
|
581 |
-
is(cm.cursorCoords(Pos(4, 1)).left > pos41.left, "single-char bug");
|
582 |
-
}, {value: "foo\nbar\n\n\nx\ny"});
|
583 |
-
|
584 |
-
testCM("multiBookmarkCursor", function(cm) {
|
585 |
-
if (phantom) return;
|
586 |
-
var ms = [], m;
|
587 |
-
function add(insertLeft) {
|
588 |
-
for (var i = 0; i < 3; ++i) {
|
589 |
-
var node = document.createElement("span");
|
590 |
-
node.innerHTML = "X";
|
591 |
-
ms.push(cm.setBookmark(Pos(0, 1), {widget: node, insertLeft: insertLeft}));
|
592 |
-
}
|
593 |
-
}
|
594 |
-
var base1 = cm.cursorCoords(Pos(0, 1)).left, base4 = cm.cursorCoords(Pos(0, 4)).left;
|
595 |
-
add(true);
|
596 |
-
near(base1, cm.cursorCoords(Pos(0, 1)).left, 1);
|
597 |
-
while (m = ms.pop()) m.clear();
|
598 |
-
add(false);
|
599 |
-
near(base4, cm.cursorCoords(Pos(0, 1)).left, 1);
|
600 |
-
}, {value: "abcdefg"});
|
601 |
-
|
602 |
-
testCM("getAllMarks", function(cm) {
|
603 |
-
addDoc(cm, 10, 10);
|
604 |
-
var m1 = cm.setBookmark(Pos(0, 2));
|
605 |
-
var m2 = cm.markText(Pos(0, 2), Pos(3, 2));
|
606 |
-
var m3 = cm.markText(Pos(1, 2), Pos(1, 8));
|
607 |
-
var m4 = cm.markText(Pos(8, 0), Pos(9, 0));
|
608 |
-
eq(cm.getAllMarks().length, 4);
|
609 |
-
m1.clear();
|
610 |
-
m3.clear();
|
611 |
-
eq(cm.getAllMarks().length, 2);
|
612 |
-
});
|
613 |
-
|
614 |
-
testCM("setValueClears", function(cm) {
|
615 |
-
cm.addLineClass(0, "wrap", "foo");
|
616 |
-
var mark = cm.markText(Pos(0, 0), Pos(1, 1), {inclusiveLeft: true, inclusiveRight: true});
|
617 |
-
cm.setValue("foo");
|
618 |
-
is(!cm.lineInfo(0).wrapClass);
|
619 |
-
is(!mark.find());
|
620 |
-
}, {value: "a\nb"});
|
621 |
-
|
622 |
-
testCM("bug577", function(cm) {
|
623 |
-
cm.setValue("a\nb");
|
624 |
-
cm.clearHistory();
|
625 |
-
cm.setValue("fooooo");
|
626 |
-
cm.undo();
|
627 |
-
});
|
628 |
-
|
629 |
-
testCM("scrollSnap", function(cm) {
|
630 |
-
cm.setSize(100, 100);
|
631 |
-
addDoc(cm, 200, 200);
|
632 |
-
cm.setCursor(Pos(100, 180));
|
633 |
-
var info = cm.getScrollInfo();
|
634 |
-
is(info.left > 0 && info.top > 0);
|
635 |
-
cm.setCursor(Pos(0, 0));
|
636 |
-
info = cm.getScrollInfo();
|
637 |
-
is(info.left == 0 && info.top == 0, "scrolled clean to top");
|
638 |
-
cm.setCursor(Pos(100, 180));
|
639 |
-
cm.setCursor(Pos(199, 0));
|
640 |
-
info = cm.getScrollInfo();
|
641 |
-
is(info.left == 0 && info.top + 2 > info.height - cm.getScrollerElement().clientHeight, "scrolled clean to bottom");
|
642 |
-
});
|
643 |
-
|
644 |
-
testCM("scrollIntoView", function(cm) {
|
645 |
-
if (phantom) return;
|
646 |
-
var outer = cm.getWrapperElement().getBoundingClientRect();
|
647 |
-
function test(line, ch, msg) {
|
648 |
-
var pos = Pos(line, ch);
|
649 |
-
cm.scrollIntoView(pos);
|
650 |
-
var box = cm.charCoords(pos, "window");
|
651 |
-
is(box.left >= outer.left, msg + " (left)");
|
652 |
-
is(box.right <= outer.right, msg + " (right)");
|
653 |
-
is(box.top >= outer.top, msg + " (top)");
|
654 |
-
is(box.bottom <= outer.bottom, msg + " (bottom)");
|
655 |
-
}
|
656 |
-
addDoc(cm, 200, 200);
|
657 |
-
test(199, 199, "bottom right");
|
658 |
-
test(0, 0, "top left");
|
659 |
-
test(100, 100, "center");
|
660 |
-
test(199, 0, "bottom left");
|
661 |
-
test(0, 199, "top right");
|
662 |
-
test(100, 100, "center again");
|
663 |
-
});
|
664 |
-
|
665 |
-
testCM("scrollBackAndForth", function(cm) {
|
666 |
-
addDoc(cm, 1, 200);
|
667 |
-
cm.operation(function() {
|
668 |
-
cm.scrollIntoView(Pos(199, 0));
|
669 |
-
cm.scrollIntoView(Pos(4, 0));
|
670 |
-
});
|
671 |
-
is(cm.getScrollInfo().top > 0);
|
672 |
-
});
|
673 |
-
|
674 |
-
testCM("selectAllNoScroll", function(cm) {
|
675 |
-
addDoc(cm, 1, 200);
|
676 |
-
cm.execCommand("selectAll");
|
677 |
-
eq(cm.getScrollInfo().top, 0);
|
678 |
-
cm.setCursor(199);
|
679 |
-
cm.execCommand("selectAll");
|
680 |
-
is(cm.getScrollInfo().top > 0);
|
681 |
-
});
|
682 |
-
|
683 |
-
testCM("selectionPos", function(cm) {
|
684 |
-
if (phantom || cm.getOption("inputStyle") != "textarea") return;
|
685 |
-
cm.setSize(100, 100);
|
686 |
-
addDoc(cm, 200, 100);
|
687 |
-
cm.setSelection(Pos(1, 100), Pos(98, 100));
|
688 |
-
var lineWidth = cm.charCoords(Pos(0, 200), "local").left;
|
689 |
-
var lineHeight = (cm.charCoords(Pos(99)).top - cm.charCoords(Pos(0)).top) / 100;
|
690 |
-
cm.scrollTo(0, 0);
|
691 |
-
var selElt = byClassName(cm.getWrapperElement(), "CodeMirror-selected");
|
692 |
-
var outer = cm.getWrapperElement().getBoundingClientRect();
|
693 |
-
var sawMiddle, sawTop, sawBottom;
|
694 |
-
for (var i = 0, e = selElt.length; i < e; ++i) {
|
695 |
-
var box = selElt[i].getBoundingClientRect();
|
696 |
-
var atLeft = box.left - outer.left < 30;
|
697 |
-
var width = box.right - box.left;
|
698 |
-
var atRight = box.right - outer.left > .8 * lineWidth;
|
699 |
-
if (atLeft && atRight) {
|
700 |
-
sawMiddle = true;
|
701 |
-
is(box.bottom - box.top > 90 * lineHeight, "middle high");
|
702 |
-
is(width > .9 * lineWidth, "middle wide");
|
703 |
-
} else {
|
704 |
-
is(width > .4 * lineWidth, "top/bot wide enough");
|
705 |
-
is(width < .6 * lineWidth, "top/bot slim enough");
|
706 |
-
if (atLeft) {
|
707 |
-
sawBottom = true;
|
708 |
-
is(box.top - outer.top > 96 * lineHeight, "bot below");
|
709 |
-
} else if (atRight) {
|
710 |
-
sawTop = true;
|
711 |
-
is(box.top - outer.top < 2.1 * lineHeight, "top above");
|
712 |
-
}
|
713 |
-
}
|
714 |
-
}
|
715 |
-
is(sawTop && sawBottom && sawMiddle, "all parts");
|
716 |
-
}, null);
|
717 |
-
|
718 |
-
testCM("restoreHistory", function(cm) {
|
719 |
-
cm.setValue("abc\ndef");
|
720 |
-
cm.replaceRange("hello", Pos(1, 0), Pos(1));
|
721 |
-
cm.replaceRange("goop", Pos(0, 0), Pos(0));
|
722 |
-
cm.undo();
|
723 |
-
var storedVal = cm.getValue(), storedHist = cm.getHistory();
|
724 |
-
if (window.JSON) storedHist = JSON.parse(JSON.stringify(storedHist));
|
725 |
-
eq(storedVal, "abc\nhello");
|
726 |
-
cm.setValue("");
|
727 |
-
cm.clearHistory();
|
728 |
-
eq(cm.historySize().undo, 0);
|
729 |
-
cm.setValue(storedVal);
|
730 |
-
cm.setHistory(storedHist);
|
731 |
-
cm.redo();
|
732 |
-
eq(cm.getValue(), "goop\nhello");
|
733 |
-
cm.undo(); cm.undo();
|
734 |
-
eq(cm.getValue(), "abc\ndef");
|
735 |
-
});
|
736 |
-
|
737 |
-
testCM("doubleScrollbar", function(cm) {
|
738 |
-
var dummy = document.body.appendChild(document.createElement("p"));
|
739 |
-
dummy.style.cssText = "height: 50px; overflow: scroll; width: 50px";
|
740 |
-
var scrollbarWidth = dummy.offsetWidth + 1 - dummy.clientWidth;
|
741 |
-
document.body.removeChild(dummy);
|
742 |
-
if (scrollbarWidth < 2) return;
|
743 |
-
cm.setSize(null, 100);
|
744 |
-
addDoc(cm, 1, 300);
|
745 |
-
var wrap = cm.getWrapperElement();
|
746 |
-
is(wrap.offsetWidth - byClassName(wrap, "CodeMirror-lines")[0].offsetWidth <= scrollbarWidth * 1.5);
|
747 |
-
});
|
748 |
-
|
749 |
-
testCM("weirdLinebreaks", function(cm) {
|
750 |
-
cm.setValue("foo\nbar\rbaz\r\nquux\n\rplop");
|
751 |
-
is(cm.getValue(), "foo\nbar\nbaz\nquux\n\nplop");
|
752 |
-
is(cm.lineCount(), 6);
|
753 |
-
cm.setValue("\n\n");
|
754 |
-
is(cm.lineCount(), 3);
|
755 |
-
});
|
756 |
-
|
757 |
-
testCM("setSize", function(cm) {
|
758 |
-
cm.setSize(100, 100);
|
759 |
-
var wrap = cm.getWrapperElement();
|
760 |
-
is(wrap.offsetWidth, 100);
|
761 |
-
is(wrap.offsetHeight, 100);
|
762 |
-
cm.setSize("100%", "3em");
|
763 |
-
is(wrap.style.width, "100%");
|
764 |
-
is(wrap.style.height, "3em");
|
765 |
-
cm.setSize(null, 40);
|
766 |
-
is(wrap.style.width, "100%");
|
767 |
-
is(wrap.style.height, "40px");
|
768 |
-
});
|
769 |
-
|
770 |
-
function foldLines(cm, start, end, autoClear) {
|
771 |
-
return cm.markText(Pos(start, 0), Pos(end - 1), {
|
772 |
-
inclusiveLeft: true,
|
773 |
-
inclusiveRight: true,
|
774 |
-
collapsed: true,
|
775 |
-
clearOnEnter: autoClear
|
776 |
-
});
|
777 |
-
}
|
778 |
-
|
779 |
-
testCM("collapsedLines", function(cm) {
|
780 |
-
addDoc(cm, 4, 10);
|
781 |
-
var range = foldLines(cm, 4, 5), cleared = 0;
|
782 |
-
CodeMirror.on(range, "clear", function() {cleared++;});
|
783 |
-
cm.setCursor(Pos(3, 0));
|
784 |
-
CodeMirror.commands.goLineDown(cm);
|
785 |
-
eqPos(cm.getCursor(), Pos(5, 0));
|
786 |
-
cm.replaceRange("abcdefg", Pos(3, 0), Pos(3));
|
787 |
-
cm.setCursor(Pos(3, 6));
|
788 |
-
CodeMirror.commands.goLineDown(cm);
|
789 |
-
eqPos(cm.getCursor(), Pos(5, 4));
|
790 |
-
cm.replaceRange("ab", Pos(3, 0), Pos(3));
|
791 |
-
cm.setCursor(Pos(3, 2));
|
792 |
-
CodeMirror.commands.goLineDown(cm);
|
793 |
-
eqPos(cm.getCursor(), Pos(5, 2));
|
794 |
-
cm.operation(function() {range.clear(); range.clear();});
|
795 |
-
eq(cleared, 1);
|
796 |
-
});
|
797 |
-
|
798 |
-
testCM("collapsedRangeCoordsChar", function(cm) {
|
799 |
-
var pos_1_3 = cm.charCoords(Pos(1, 3));
|
800 |
-
pos_1_3.left += 2; pos_1_3.top += 2;
|
801 |
-
var opts = {collapsed: true, inclusiveLeft: true, inclusiveRight: true};
|
802 |
-
var m1 = cm.markText(Pos(0, 0), Pos(2, 0), opts);
|
803 |
-
eqPos(cm.coordsChar(pos_1_3), Pos(3, 3));
|
804 |
-
m1.clear();
|
805 |
-
var m1 = cm.markText(Pos(0, 0), Pos(1, 1), {collapsed: true, inclusiveLeft: true});
|
806 |
-
var m2 = cm.markText(Pos(1, 1), Pos(2, 0), {collapsed: true, inclusiveRight: true});
|
807 |
-
eqPos(cm.coordsChar(pos_1_3), Pos(3, 3));
|
808 |
-
m1.clear(); m2.clear();
|
809 |
-
var m1 = cm.markText(Pos(0, 0), Pos(1, 6), opts);
|
810 |
-
eqPos(cm.coordsChar(pos_1_3), Pos(3, 3));
|
811 |
-
}, {value: "123456\nabcdef\nghijkl\nmnopqr\n"});
|
812 |
-
|
813 |
-
testCM("collapsedRangeBetweenLinesSelected", function(cm) {
|
814 |
-
if (cm.getOption("inputStyle") != "textarea") return;
|
815 |
-
var widget = document.createElement("span");
|
816 |
-
widget.textContent = "\u2194";
|
817 |
-
cm.markText(Pos(0, 3), Pos(1, 0), {replacedWith: widget});
|
818 |
-
cm.setSelection(Pos(0, 3), Pos(1, 0));
|
819 |
-
var selElts = byClassName(cm.getWrapperElement(), "CodeMirror-selected");
|
820 |
-
for (var i = 0, w = 0; i < selElts.length; i++)
|
821 |
-
w += selElts[i].offsetWidth;
|
822 |
-
is(w > 0);
|
823 |
-
}, {value: "one\ntwo"});
|
824 |
-
|
825 |
-
testCM("randomCollapsedRanges", function(cm) {
|
826 |
-
addDoc(cm, 20, 500);
|
827 |
-
cm.operation(function() {
|
828 |
-
for (var i = 0; i < 200; i++) {
|
829 |
-
var start = Pos(Math.floor(Math.random() * 500), Math.floor(Math.random() * 20));
|
830 |
-
if (i % 4)
|
831 |
-
try { cm.markText(start, Pos(start.line + 2, 1), {collapsed: true}); }
|
832 |
-
catch(e) { if (!/overlapping/.test(String(e))) throw e; }
|
833 |
-
else
|
834 |
-
cm.markText(start, Pos(start.line, start.ch + 4), {"className": "foo"});
|
835 |
-
}
|
836 |
-
});
|
837 |
-
});
|
838 |
-
|
839 |
-
testCM("hiddenLinesAutoUnfold", function(cm) {
|
840 |
-
var range = foldLines(cm, 1, 3, true), cleared = 0;
|
841 |
-
CodeMirror.on(range, "clear", function() {cleared++;});
|
842 |
-
cm.setCursor(Pos(3, 0));
|
843 |
-
eq(cleared, 0);
|
844 |
-
cm.execCommand("goCharLeft");
|
845 |
-
eq(cleared, 1);
|
846 |
-
range = foldLines(cm, 1, 3, true);
|
847 |
-
CodeMirror.on(range, "clear", function() {cleared++;});
|
848 |
-
eqPos(cm.getCursor(), Pos(3, 0));
|
849 |
-
cm.setCursor(Pos(0, 3));
|
850 |
-
cm.execCommand("goCharRight");
|
851 |
-
eq(cleared, 2);
|
852 |
-
}, {value: "abc\ndef\nghi\njkl"});
|
853 |
-
|
854 |
-
testCM("hiddenLinesSelectAll", function(cm) { // Issue #484
|
855 |
-
addDoc(cm, 4, 20);
|
856 |
-
foldLines(cm, 0, 10);
|
857 |
-
foldLines(cm, 11, 20);
|
858 |
-
CodeMirror.commands.selectAll(cm);
|
859 |
-
eqPos(cm.getCursor(true), Pos(10, 0));
|
860 |
-
eqPos(cm.getCursor(false), Pos(10, 4));
|
861 |
-
});
|
862 |
-
|
863 |
-
|
864 |
-
testCM("everythingFolded", function(cm) {
|
865 |
-
addDoc(cm, 2, 2);
|
866 |
-
function enterPress() {
|
867 |
-
cm.triggerOnKeyDown({type: "keydown", keyCode: 13, preventDefault: function(){}, stopPropagation: function(){}});
|
868 |
-
}
|
869 |
-
var fold = foldLines(cm, 0, 2);
|
870 |
-
enterPress();
|
871 |
-
eq(cm.getValue(), "xx\nxx");
|
872 |
-
fold.clear();
|
873 |
-
fold = foldLines(cm, 0, 2, true);
|
874 |
-
eq(fold.find(), null);
|
875 |
-
enterPress();
|
876 |
-
eq(cm.getValue(), "\nxx\nxx");
|
877 |
-
});
|
878 |
-
|
879 |
-
testCM("structuredFold", function(cm) {
|
880 |
-
if (phantom) return;
|
881 |
-
addDoc(cm, 4, 8);
|
882 |
-
var range = cm.markText(Pos(1, 2), Pos(6, 2), {
|
883 |
-
replacedWith: document.createTextNode("Q")
|
884 |
-
});
|
885 |
-
cm.setCursor(0, 3);
|
886 |
-
CodeMirror.commands.goLineDown(cm);
|
887 |
-
eqPos(cm.getCursor(), Pos(6, 2));
|
888 |
-
CodeMirror.commands.goCharLeft(cm);
|
889 |
-
eqPos(cm.getCursor(), Pos(1, 2));
|
890 |
-
CodeMirror.commands.delCharAfter(cm);
|
891 |
-
eq(cm.getValue(), "xxxx\nxxxx\nxxxx");
|
892 |
-
addDoc(cm, 4, 8);
|
893 |
-
range = cm.markText(Pos(1, 2), Pos(6, 2), {
|
894 |
-
replacedWith: document.createTextNode("M"),
|
895 |
-
clearOnEnter: true
|
896 |
-
});
|
897 |
-
var cleared = 0;
|
898 |
-
CodeMirror.on(range, "clear", function(){++cleared;});
|
899 |
-
cm.setCursor(0, 3);
|
900 |
-
CodeMirror.commands.goLineDown(cm);
|
901 |
-
eqPos(cm.getCursor(), Pos(6, 2));
|
902 |
-
CodeMirror.commands.goCharLeft(cm);
|
903 |
-
eqPos(cm.getCursor(), Pos(6, 1));
|
904 |
-
eq(cleared, 1);
|
905 |
-
range.clear();
|
906 |
-
eq(cleared, 1);
|
907 |
-
range = cm.markText(Pos(1, 2), Pos(6, 2), {
|
908 |
-
replacedWith: document.createTextNode("Q"),
|
909 |
-
clearOnEnter: true
|
910 |
-
});
|
911 |
-
range.clear();
|
912 |
-
cm.setCursor(1, 2);
|
913 |
-
CodeMirror.commands.goCharRight(cm);
|
914 |
-
eqPos(cm.getCursor(), Pos(1, 3));
|
915 |
-
range = cm.markText(Pos(2, 0), Pos(4, 4), {
|
916 |
-
replacedWith: document.createTextNode("M")
|
917 |
-
});
|
918 |
-
cm.setCursor(1, 0);
|
919 |
-
CodeMirror.commands.goLineDown(cm);
|
920 |
-
eqPos(cm.getCursor(), Pos(2, 0));
|
921 |
-
}, null);
|
922 |
-
|
923 |
-
testCM("nestedFold", function(cm) {
|
924 |
-
addDoc(cm, 10, 3);
|
925 |
-
function fold(ll, cl, lr, cr) {
|
926 |
-
return cm.markText(Pos(ll, cl), Pos(lr, cr), {collapsed: true});
|
927 |
-
}
|
928 |
-
var inner1 = fold(0, 6, 1, 3), inner2 = fold(0, 2, 1, 8), outer = fold(0, 1, 2, 3), inner0 = fold(0, 5, 0, 6);
|
929 |
-
cm.setCursor(0, 1);
|
930 |
-
CodeMirror.commands.goCharRight(cm);
|
931 |
-
eqPos(cm.getCursor(), Pos(2, 3));
|
932 |
-
inner0.clear();
|
933 |
-
CodeMirror.commands.goCharLeft(cm);
|
934 |
-
eqPos(cm.getCursor(), Pos(0, 1));
|
935 |
-
outer.clear();
|
936 |
-
CodeMirror.commands.goCharRight(cm);
|
937 |
-
eqPos(cm.getCursor(), Pos(0, 2));
|
938 |
-
CodeMirror.commands.goCharRight(cm);
|
939 |
-
eqPos(cm.getCursor(), Pos(1, 8));
|
940 |
-
inner2.clear();
|
941 |
-
CodeMirror.commands.goCharLeft(cm);
|
942 |
-
eqPos(cm.getCursor(), Pos(1, 7));
|
943 |
-
cm.setCursor(0, 5);
|
944 |
-
CodeMirror.commands.goCharRight(cm);
|
945 |
-
eqPos(cm.getCursor(), Pos(0, 6));
|
946 |
-
CodeMirror.commands.goCharRight(cm);
|
947 |
-
eqPos(cm.getCursor(), Pos(1, 3));
|
948 |
-
});
|
949 |
-
|
950 |
-
testCM("badNestedFold", function(cm) {
|
951 |
-
addDoc(cm, 4, 4);
|
952 |
-
cm.markText(Pos(0, 2), Pos(3, 2), {collapsed: true});
|
953 |
-
var caught;
|
954 |
-
try {cm.markText(Pos(0, 1), Pos(0, 3), {collapsed: true});}
|
955 |
-
catch(e) {caught = e;}
|
956 |
-
is(caught instanceof Error, "no error");
|
957 |
-
is(/overlap/i.test(caught.message), "wrong error");
|
958 |
-
});
|
959 |
-
|
960 |
-
testCM("nestedFoldOnSide", function(cm) {
|
961 |
-
var m1 = cm.markText(Pos(0, 1), Pos(2, 1), {collapsed: true, inclusiveRight: true});
|
962 |
-
var m2 = cm.markText(Pos(0, 1), Pos(0, 2), {collapsed: true});
|
963 |
-
cm.markText(Pos(0, 1), Pos(0, 2), {collapsed: true}).clear();
|
964 |
-
try { cm.markText(Pos(0, 1), Pos(0, 2), {collapsed: true, inclusiveLeft: true}); }
|
965 |
-
catch(e) { var caught = e; }
|
966 |
-
is(caught && /overlap/i.test(caught.message));
|
967 |
-
var m3 = cm.markText(Pos(2, 0), Pos(2, 1), {collapsed: true});
|
968 |
-
var m4 = cm.markText(Pos(2, 0), Pos(2, 1), {collapse: true, inclusiveRight: true});
|
969 |
-
m1.clear(); m4.clear();
|
970 |
-
m1 = cm.markText(Pos(0, 1), Pos(2, 1), {collapsed: true});
|
971 |
-
cm.markText(Pos(2, 0), Pos(2, 1), {collapsed: true}).clear();
|
972 |
-
try { cm.markText(Pos(2, 0), Pos(2, 1), {collapsed: true, inclusiveRight: true}); }
|
973 |
-
catch(e) { var caught = e; }
|
974 |
-
is(caught && /overlap/i.test(caught.message));
|
975 |
-
}, {value: "ab\ncd\ef"});
|
976 |
-
|
977 |
-
testCM("editInFold", function(cm) {
|
978 |
-
addDoc(cm, 4, 6);
|
979 |
-
var m = cm.markText(Pos(1, 2), Pos(3, 2), {collapsed: true});
|
980 |
-
cm.replaceRange("", Pos(0, 0), Pos(1, 3));
|
981 |
-
cm.replaceRange("", Pos(2, 1), Pos(3, 3));
|
982 |
-
cm.replaceRange("a\nb\nc\nd", Pos(0, 1), Pos(1, 0));
|
983 |
-
cm.cursorCoords(Pos(0, 0));
|
984 |
-
});
|
985 |
-
|
986 |
-
testCM("wrappingInlineWidget", function(cm) {
|
987 |
-
cm.setSize("11em");
|
988 |
-
var w = document.createElement("span");
|
989 |
-
w.style.color = "red";
|
990 |
-
w.innerHTML = "one two three four";
|
991 |
-
cm.markText(Pos(0, 6), Pos(0, 9), {replacedWith: w});
|
992 |
-
var cur0 = cm.cursorCoords(Pos(0, 0)), cur1 = cm.cursorCoords(Pos(0, 10));
|
993 |
-
is(cur0.top < cur1.top);
|
994 |
-
is(cur0.bottom < cur1.bottom);
|
995 |
-
var curL = cm.cursorCoords(Pos(0, 6)), curR = cm.cursorCoords(Pos(0, 9));
|
996 |
-
eq(curL.top, cur0.top);
|
997 |
-
eq(curL.bottom, cur0.bottom);
|
998 |
-
eq(curR.top, cur1.top);
|
999 |
-
eq(curR.bottom, cur1.bottom);
|
1000 |
-
cm.replaceRange("", Pos(0, 9), Pos(0));
|
1001 |
-
curR = cm.cursorCoords(Pos(0, 9));
|
1002 |
-
if (phantom) return;
|
1003 |
-
eq(curR.top, cur1.top);
|
1004 |
-
eq(curR.bottom, cur1.bottom);
|
1005 |
-
}, {value: "1 2 3 xxx 4", lineWrapping: true});
|
1006 |
-
|
1007 |
-
testCM("showEmptyWidgetSpan", function(cm) {
|
1008 |
-
var marker = cm.markText(Pos(0, 2), Pos(0, 2), {
|
1009 |
-
clearWhenEmpty: false,
|
1010 |
-
replacedWith: document.createTextNode("X")
|
1011 |
-
});
|
1012 |
-
eq(cm.display.view[0].text.textContent, "abXc");
|
1013 |
-
}, {value: "abc"});
|
1014 |
-
|
1015 |
-
testCM("changedInlineWidget", function(cm) {
|
1016 |
-
cm.setSize("10em");
|
1017 |
-
var w = document.createElement("span");
|
1018 |
-
w.innerHTML = "x";
|
1019 |
-
var m = cm.markText(Pos(0, 4), Pos(0, 5), {replacedWith: w});
|
1020 |
-
w.innerHTML = "and now the widget is really really long all of a sudden and a scrollbar is needed";
|
1021 |
-
m.changed();
|
1022 |
-
var hScroll = byClassName(cm.getWrapperElement(), "CodeMirror-hscrollbar")[0];
|
1023 |
-
is(hScroll.scrollWidth > hScroll.clientWidth);
|
1024 |
-
}, {value: "hello there"});
|
1025 |
-
|
1026 |
-
testCM("changedBookmark", function(cm) {
|
1027 |
-
cm.setSize("10em");
|
1028 |
-
var w = document.createElement("span");
|
1029 |
-
w.innerHTML = "x";
|
1030 |
-
var m = cm.setBookmark(Pos(0, 4), {widget: w});
|
1031 |
-
w.innerHTML = "and now the widget is really really long all of a sudden and a scrollbar is needed";
|
1032 |
-
m.changed();
|
1033 |
-
var hScroll = byClassName(cm.getWrapperElement(), "CodeMirror-hscrollbar")[0];
|
1034 |
-
is(hScroll.scrollWidth > hScroll.clientWidth);
|
1035 |
-
}, {value: "abcdefg"});
|
1036 |
-
|
1037 |
-
testCM("inlineWidget", function(cm) {
|
1038 |
-
var w = cm.setBookmark(Pos(0, 2), {widget: document.createTextNode("uu")});
|
1039 |
-
cm.setCursor(0, 2);
|
1040 |
-
CodeMirror.commands.goLineDown(cm);
|
1041 |
-
eqPos(cm.getCursor(), Pos(1, 4));
|
1042 |
-
cm.setCursor(0, 2);
|
1043 |
-
cm.replaceSelection("hi");
|
1044 |
-
eqPos(w.find(), Pos(0, 2));
|
1045 |
-
cm.setCursor(0, 1);
|
1046 |
-
cm.replaceSelection("ay");
|
1047 |
-
eqPos(w.find(), Pos(0, 4));
|
1048 |
-
eq(cm.getLine(0), "uayuhiuu");
|
1049 |
-
}, {value: "uuuu\nuuuuuu"});
|
1050 |
-
|
1051 |
-
testCM("wrappingAndResizing", function(cm) {
|
1052 |
-
cm.setSize(null, "auto");
|
1053 |
-
cm.setOption("lineWrapping", true);
|
1054 |
-
var wrap = cm.getWrapperElement(), h0 = wrap.offsetHeight;
|
1055 |
-
var doc = "xxx xxx xxx xxx xxx";
|
1056 |
-
cm.setValue(doc);
|
1057 |
-
for (var step = 10, w = cm.charCoords(Pos(0, 18), "div").right;; w += step) {
|
1058 |
-
cm.setSize(w);
|
1059 |
-
if (wrap.offsetHeight <= h0 * (opera_lt10 ? 1.2 : 1.5)) {
|
1060 |
-
if (step == 10) { w -= 10; step = 1; }
|
1061 |
-
else break;
|
1062 |
-
}
|
1063 |
-
}
|
1064 |
-
// Ensure that putting the cursor at the end of the maximally long
|
1065 |
-
// line doesn't cause wrapping to happen.
|
1066 |
-
cm.setCursor(Pos(0, doc.length));
|
1067 |
-
eq(wrap.offsetHeight, h0);
|
1068 |
-
cm.replaceSelection("x");
|
1069 |
-
is(wrap.offsetHeight > h0, "wrapping happens");
|
1070 |
-
// Now add a max-height and, in a document consisting of
|
1071 |
-
// almost-wrapped lines, go over it so that a scrollbar appears.
|
1072 |
-
cm.setValue(doc + "\n" + doc + "\n");
|
1073 |
-
cm.getScrollerElement().style.maxHeight = "100px";
|
1074 |
-
cm.replaceRange("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n!\n", Pos(2, 0));
|
1075 |
-
forEach([Pos(0, doc.length), Pos(0, doc.length - 1),
|
1076 |
-
Pos(0, 0), Pos(1, doc.length), Pos(1, doc.length - 1)],
|
1077 |
-
function(pos) {
|
1078 |
-
var coords = cm.charCoords(pos);
|
1079 |
-
eqPos(pos, cm.coordsChar({left: coords.left + 2, top: coords.top + 5}));
|
1080 |
-
});
|
1081 |
-
}, null, ie_lt8);
|
1082 |
-
|
1083 |
-
testCM("measureEndOfLine", function(cm) {
|
1084 |
-
cm.setSize(null, "auto");
|
1085 |
-
var inner = byClassName(cm.getWrapperElement(), "CodeMirror-lines")[0].firstChild;
|
1086 |
-
var lh = inner.offsetHeight;
|
1087 |
-
for (var step = 10, w = cm.charCoords(Pos(0, 7), "div").right;; w += step) {
|
1088 |
-
cm.setSize(w);
|
1089 |
-
if (inner.offsetHeight < 2.5 * lh) {
|
1090 |
-
if (step == 10) { w -= 10; step = 1; }
|
1091 |
-
else break;
|
1092 |
-
}
|
1093 |
-
}
|
1094 |
-
cm.setValue(cm.getValue() + "\n\n");
|
1095 |
-
var endPos = cm.charCoords(Pos(0, 18), "local");
|
1096 |
-
is(endPos.top > lh * .8, "not at top");
|
1097 |
-
is(endPos.left > w - 20, "not at right");
|
1098 |
-
endPos = cm.charCoords(Pos(0, 18));
|
1099 |
-
eqPos(cm.coordsChar({left: endPos.left, top: endPos.top + 5}), Pos(0, 18));
|
1100 |
-
}, {mode: "text/html", value: "<!-- foo barrr -->", lineWrapping: true}, ie_lt8 || opera_lt10);
|
1101 |
-
|
1102 |
-
testCM("scrollVerticallyAndHorizontally", function(cm) {
|
1103 |
-
if (cm.getOption("inputStyle") != "textarea") return;
|
1104 |
-
cm.setSize(100, 100);
|
1105 |
-
addDoc(cm, 40, 40);
|
1106 |
-
cm.setCursor(39);
|
1107 |
-
var wrap = cm.getWrapperElement(), bar = byClassName(wrap, "CodeMirror-vscrollbar")[0];
|
1108 |
-
is(bar.offsetHeight < wrap.offsetHeight, "vertical scrollbar limited by horizontal one");
|
1109 |
-
var cursorBox = byClassName(wrap, "CodeMirror-cursor")[0].getBoundingClientRect();
|
1110 |
-
var editorBox = wrap.getBoundingClientRect();
|
1111 |
-
is(cursorBox.bottom < editorBox.top + cm.getScrollerElement().clientHeight,
|
1112 |
-
"bottom line visible");
|
1113 |
-
}, {lineNumbers: true});
|
1114 |
-
|
1115 |
-
testCM("moveVstuck", function(cm) {
|
1116 |
-
var lines = byClassName(cm.getWrapperElement(), "CodeMirror-lines")[0].firstChild, h0 = lines.offsetHeight;
|
1117 |
-
var val = "fooooooooooooooooooooooooo baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaar\n";
|
1118 |
-
cm.setValue(val);
|
1119 |
-
for (var w = cm.charCoords(Pos(0, 26), "div").right * 2.8;; w += 5) {
|
1120 |
-
cm.setSize(w);
|
1121 |
-
if (lines.offsetHeight <= 3.5 * h0) break;
|
1122 |
-
}
|
1123 |
-
cm.setCursor(Pos(0, val.length - 1));
|
1124 |
-
cm.moveV(-1, "line");
|
1125 |
-
eqPos(cm.getCursor(), Pos(0, 26));
|
1126 |
-
}, {lineWrapping: true}, ie_lt8 || opera_lt10);
|
1127 |
-
|
1128 |
-
testCM("collapseOnMove", function(cm) {
|
1129 |
-
cm.setSelection(Pos(0, 1), Pos(2, 4));
|
1130 |
-
cm.execCommand("goLineUp");
|
1131 |
-
is(!cm.somethingSelected());
|
1132 |
-
eqPos(cm.getCursor(), Pos(0, 1));
|
1133 |
-
cm.setSelection(Pos(0, 1), Pos(2, 4));
|
1134 |
-
cm.execCommand("goPageDown");
|
1135 |
-
is(!cm.somethingSelected());
|
1136 |
-
eqPos(cm.getCursor(), Pos(2, 4));
|
1137 |
-
cm.execCommand("goLineUp");
|
1138 |
-
cm.execCommand("goLineUp");
|
1139 |
-
eqPos(cm.getCursor(), Pos(0, 4));
|
1140 |
-
cm.setSelection(Pos(0, 1), Pos(2, 4));
|
1141 |
-
cm.execCommand("goCharLeft");
|
1142 |
-
is(!cm.somethingSelected());
|
1143 |
-
eqPos(cm.getCursor(), Pos(0, 1));
|
1144 |
-
}, {value: "aaaaa\nb\nccccc"});
|
1145 |
-
|
1146 |
-
testCM("clickTab", function(cm) {
|
1147 |
-
var p0 = cm.charCoords(Pos(0, 0));
|
1148 |
-
eqPos(cm.coordsChar({left: p0.left + 5, top: p0.top + 5}), Pos(0, 0));
|
1149 |
-
eqPos(cm.coordsChar({left: p0.right - 5, top: p0.top + 5}), Pos(0, 1));
|
1150 |
-
}, {value: "\t\n\n", lineWrapping: true, tabSize: 8});
|
1151 |
-
|
1152 |
-
testCM("verticalScroll", function(cm) {
|
1153 |
-
cm.setSize(100, 200);
|
1154 |
-
cm.setValue("foo\nbar\nbaz\n");
|
1155 |
-
var sc = cm.getScrollerElement(), baseWidth = sc.scrollWidth;
|
1156 |
-
cm.replaceRange("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah", Pos(0, 0), Pos(0));
|
1157 |
-
is(sc.scrollWidth > baseWidth, "scrollbar present");
|
1158 |
-
cm.replaceRange("foo", Pos(0, 0), Pos(0));
|
1159 |
-
if (!phantom) eq(sc.scrollWidth, baseWidth, "scrollbar gone");
|
1160 |
-
cm.replaceRange("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah", Pos(0, 0), Pos(0));
|
1161 |
-
cm.replaceRange("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbh", Pos(1, 0), Pos(1));
|
1162 |
-
is(sc.scrollWidth > baseWidth, "present again");
|
1163 |
-
var curWidth = sc.scrollWidth;
|
1164 |
-
cm.replaceRange("foo", Pos(0, 0), Pos(0));
|
1165 |
-
is(sc.scrollWidth < curWidth, "scrollbar smaller");
|
1166 |
-
is(sc.scrollWidth > baseWidth, "but still present");
|
1167 |
-
});
|
1168 |
-
|
1169 |
-
testCM("extraKeys", function(cm) {
|
1170 |
-
var outcome;
|
1171 |
-
function fakeKey(expected, code, props) {
|
1172 |
-
if (typeof code == "string") code = code.charCodeAt(0);
|
1173 |
-
var e = {type: "keydown", keyCode: code, preventDefault: function(){}, stopPropagation: function(){}};
|
1174 |
-
if (props) for (var n in props) e[n] = props[n];
|
1175 |
-
outcome = null;
|
1176 |
-
cm.triggerOnKeyDown(e);
|
1177 |
-
eq(outcome, expected);
|
1178 |
-
}
|
1179 |
-
CodeMirror.commands.testCommand = function() {outcome = "tc";};
|
1180 |
-
CodeMirror.commands.goTestCommand = function() {outcome = "gtc";};
|
1181 |
-
cm.setOption("extraKeys", {"Shift-X": function() {outcome = "sx";},
|
1182 |
-
"X": function() {outcome = "x";},
|
1183 |
-
"Ctrl-Alt-U": function() {outcome = "cau";},
|
1184 |
-
"End": "testCommand",
|
1185 |
-
"Home": "goTestCommand",
|
1186 |
-
"Tab": false});
|
1187 |
-
fakeKey(null, "U");
|
1188 |
-
fakeKey("cau", "U", {ctrlKey: true, altKey: true});
|
1189 |
-
fakeKey(null, "U", {shiftKey: true, ctrlKey: true, altKey: true});
|
1190 |
-
fakeKey("x", "X");
|
1191 |
-
fakeKey("sx", "X", {shiftKey: true});
|
1192 |
-
fakeKey("tc", 35);
|
1193 |
-
fakeKey(null, 35, {shiftKey: true});
|
1194 |
-
fakeKey("gtc", 36);
|
1195 |
-
fakeKey("gtc", 36, {shiftKey: true});
|
1196 |
-
fakeKey(null, 9);
|
1197 |
-
}, null, window.opera && mac);
|
1198 |
-
|
1199 |
-
testCM("wordMovementCommands", function(cm) {
|
1200 |
-
cm.execCommand("goWordLeft");
|
1201 |
-
eqPos(cm.getCursor(), Pos(0, 0));
|
1202 |
-
cm.execCommand("goWordRight"); cm.execCommand("goWordRight");
|
1203 |
-
eqPos(cm.getCursor(), Pos(0, 7));
|
1204 |
-
cm.execCommand("goWordLeft");
|
1205 |
-
eqPos(cm.getCursor(), Pos(0, 5));
|
1206 |
-
cm.execCommand("goWordRight"); cm.execCommand("goWordRight");
|
1207 |
-
eqPos(cm.getCursor(), Pos(0, 12));
|
1208 |
-
cm.execCommand("goWordLeft");
|
1209 |
-
eqPos(cm.getCursor(), Pos(0, 9));
|
1210 |
-
cm.execCommand("goWordRight"); cm.execCommand("goWordRight"); cm.execCommand("goWordRight");
|
1211 |
-
eqPos(cm.getCursor(), Pos(0, 24));
|
1212 |
-
cm.execCommand("goWordRight"); cm.execCommand("goWordRight");
|
1213 |
-
eqPos(cm.getCursor(), Pos(1, 9));
|
1214 |
-
cm.execCommand("goWordRight");
|
1215 |
-
eqPos(cm.getCursor(), Pos(1, 13));
|
1216 |
-
cm.execCommand("goWordRight"); cm.execCommand("goWordRight");
|
1217 |
-
eqPos(cm.getCursor(), Pos(2, 0));
|
1218 |
-
}, {value: "this is (the) firstline.\na foo12\u00e9\u00f8\u00d7bar\n"});
|
1219 |
-
|
1220 |
-
testCM("groupMovementCommands", function(cm) {
|
1221 |
-
cm.execCommand("goGroupLeft");
|
1222 |
-
eqPos(cm.getCursor(), Pos(0, 0));
|
1223 |
-
cm.execCommand("goGroupRight");
|
1224 |
-
eqPos(cm.getCursor(), Pos(0, 4));
|
1225 |
-
cm.execCommand("goGroupRight");
|
1226 |
-
eqPos(cm.getCursor(), Pos(0, 7));
|
1227 |
-
cm.execCommand("goGroupRight");
|
1228 |
-
eqPos(cm.getCursor(), Pos(0, 10));
|
1229 |
-
cm.execCommand("goGroupLeft");
|
1230 |
-
eqPos(cm.getCursor(), Pos(0, 7));
|
1231 |
-
cm.execCommand("goGroupRight"); cm.execCommand("goGroupRight"); cm.execCommand("goGroupRight");
|
1232 |
-
eqPos(cm.getCursor(), Pos(0, 15));
|
1233 |
-
cm.setCursor(Pos(0, 17));
|
1234 |
-
cm.execCommand("goGroupLeft");
|
1235 |
-
eqPos(cm.getCursor(), Pos(0, 16));
|
1236 |
-
cm.execCommand("goGroupLeft");
|
1237 |
-
eqPos(cm.getCursor(), Pos(0, 14));
|
1238 |
-
cm.execCommand("goGroupRight"); cm.execCommand("goGroupRight");
|
1239 |
-
eqPos(cm.getCursor(), Pos(0, 20));
|
1240 |
-
cm.execCommand("goGroupRight");
|
1241 |
-
eqPos(cm.getCursor(), Pos(1, 0));
|
1242 |
-
cm.execCommand("goGroupRight");
|
1243 |
-
eqPos(cm.getCursor(), Pos(1, 2));
|
1244 |
-
cm.execCommand("goGroupRight");
|
1245 |
-
eqPos(cm.getCursor(), Pos(1, 5));
|
1246 |
-
cm.execCommand("goGroupLeft"); cm.execCommand("goGroupLeft");
|
1247 |
-
eqPos(cm.getCursor(), Pos(1, 0));
|
1248 |
-
cm.execCommand("goGroupLeft");
|
1249 |
-
eqPos(cm.getCursor(), Pos(0, 20));
|
1250 |
-
cm.execCommand("goGroupLeft");
|
1251 |
-
eqPos(cm.getCursor(), Pos(0, 16));
|
1252 |
-
}, {value: "booo ba---quux. ffff\n abc d"});
|
1253 |
-
|
1254 |
-
testCM("groupsAndWhitespace", function(cm) {
|
1255 |
-
var positions = [Pos(0, 0), Pos(0, 2), Pos(0, 5), Pos(0, 9), Pos(0, 11),
|
1256 |
-
Pos(1, 0), Pos(1, 2), Pos(1, 5)];
|
1257 |
-
for (var i = 1; i < positions.length; i++) {
|
1258 |
-
cm.execCommand("goGroupRight");
|
1259 |
-
eqPos(cm.getCursor(), positions[i]);
|
1260 |
-
}
|
1261 |
-
for (var i = positions.length - 2; i >= 0; i--) {
|
1262 |
-
cm.execCommand("goGroupLeft");
|
1263 |
-
eqPos(cm.getCursor(), i == 2 ? Pos(0, 6) : positions[i]);
|
1264 |
-
}
|
1265 |
-
}, {value: " foo +++ \n bar"});
|
1266 |
-
|
1267 |
-
testCM("charMovementCommands", function(cm) {
|
1268 |
-
cm.execCommand("goCharLeft"); cm.execCommand("goColumnLeft");
|
1269 |
-
eqPos(cm.getCursor(), Pos(0, 0));
|
1270 |
-
cm.execCommand("goCharRight"); cm.execCommand("goCharRight");
|
1271 |
-
eqPos(cm.getCursor(), Pos(0, 2));
|
1272 |
-
cm.setCursor(Pos(1, 0));
|
1273 |
-
cm.execCommand("goColumnLeft");
|
1274 |
-
eqPos(cm.getCursor(), Pos(1, 0));
|
1275 |
-
cm.execCommand("goCharLeft");
|
1276 |
-
eqPos(cm.getCursor(), Pos(0, 5));
|
1277 |
-
cm.execCommand("goColumnRight");
|
1278 |
-
eqPos(cm.getCursor(), Pos(0, 5));
|
1279 |
-
cm.execCommand("goCharRight");
|
1280 |
-
eqPos(cm.getCursor(), Pos(1, 0));
|
1281 |
-
cm.execCommand("goLineEnd");
|
1282 |
-
eqPos(cm.getCursor(), Pos(1, 5));
|
1283 |
-
cm.execCommand("goLineStartSmart");
|
1284 |
-
eqPos(cm.getCursor(), Pos(1, 1));
|
1285 |
-
cm.execCommand("goLineStartSmart");
|
1286 |
-
eqPos(cm.getCursor(), Pos(1, 0));
|
1287 |
-
cm.setCursor(Pos(2, 0));
|
1288 |
-
cm.execCommand("goCharRight"); cm.execCommand("goColumnRight");
|
1289 |
-
eqPos(cm.getCursor(), Pos(2, 0));
|
1290 |
-
}, {value: "line1\n ine2\n"});
|
1291 |
-
|
1292 |
-
testCM("verticalMovementCommands", function(cm) {
|
1293 |
-
cm.execCommand("goLineUp");
|
1294 |
-
eqPos(cm.getCursor(), Pos(0, 0));
|
1295 |
-
cm.execCommand("goLineDown");
|
1296 |
-
if (!phantom) // This fails in PhantomJS, though not in a real Webkit
|
1297 |
-
eqPos(cm.getCursor(), Pos(1, 0));
|
1298 |
-
cm.setCursor(Pos(1, 12));
|
1299 |
-
cm.execCommand("goLineDown");
|
1300 |
-
eqPos(cm.getCursor(), Pos(2, 5));
|
1301 |
-
cm.execCommand("goLineDown");
|
1302 |
-
eqPos(cm.getCursor(), Pos(3, 0));
|
1303 |
-
cm.execCommand("goLineUp");
|
1304 |
-
eqPos(cm.getCursor(), Pos(2, 5));
|
1305 |
-
cm.execCommand("goLineUp");
|
1306 |
-
eqPos(cm.getCursor(), Pos(1, 12));
|
1307 |
-
cm.execCommand("goPageDown");
|
1308 |
-
eqPos(cm.getCursor(), Pos(5, 0));
|
1309 |
-
cm.execCommand("goPageDown"); cm.execCommand("goLineDown");
|
1310 |
-
eqPos(cm.getCursor(), Pos(5, 0));
|
1311 |
-
cm.execCommand("goPageUp");
|
1312 |
-
eqPos(cm.getCursor(), Pos(0, 0));
|
1313 |
-
}, {value: "line1\nlong long line2\nline3\n\nline5\n"});
|
1314 |
-
|
1315 |
-
testCM("verticalMovementCommandsWrapping", function(cm) {
|
1316 |
-
cm.setSize(120);
|
1317 |
-
cm.setCursor(Pos(0, 5));
|
1318 |
-
cm.execCommand("goLineDown");
|
1319 |
-
eq(cm.getCursor().line, 0);
|
1320 |
-
is(cm.getCursor().ch > 5, "moved beyond wrap");
|
1321 |
-
for (var i = 0; ; ++i) {
|
1322 |
-
is(i < 20, "no endless loop");
|
1323 |
-
cm.execCommand("goLineDown");
|
1324 |
-
var cur = cm.getCursor();
|
1325 |
-
if (cur.line == 1) eq(cur.ch, 5);
|
1326 |
-
if (cur.line == 2) { eq(cur.ch, 1); break; }
|
1327 |
-
}
|
1328 |
-
}, {value: "a very long line that wraps around somehow so that we can test cursor movement\nshortone\nk",
|
1329 |
-
lineWrapping: true});
|
1330 |
-
|
1331 |
-
testCM("rtlMovement", function(cm) {
|
1332 |
-
if (cm.getOption("inputStyle") != "textarea") return;
|
1333 |
-
forEach(["خحج", "خحabcخحج", "abخحخحجcd", "abخde", "abخح2342خ1حج", "خ1ح2خح3حxج",
|
1334 |
-
"خحcd", "1خحcd", "abcdeح1ج", "خمرحبها مها!", "foobarر", "خ ة ق",
|
1335 |
-
"<img src=\"/בדיקה3.jpg\">", "يتم السحب في 05 فبراير 2014"], function(line) {
|
1336 |
-
var inv = line.charCodeAt(0) > 128;
|
1337 |
-
cm.setValue(line + "\n"); cm.execCommand(inv ? "goLineEnd" : "goLineStart");
|
1338 |
-
var cursors = byClassName(cm.getWrapperElement(), "CodeMirror-cursors")[0];
|
1339 |
-
var cursor = cursors.firstChild;
|
1340 |
-
var prevX = cursor.offsetLeft, prevY = cursor.offsetTop;
|
1341 |
-
for (var i = 0; i <= line.length; ++i) {
|
1342 |
-
cm.execCommand("goCharRight");
|
1343 |
-
cursor = cursors.firstChild;
|
1344 |
-
if (i == line.length) is(cursor.offsetTop > prevY, "next line");
|
1345 |
-
else is(cursor.offsetLeft > prevX, "moved right");
|
1346 |
-
prevX = cursor.offsetLeft; prevY = cursor.offsetTop;
|
1347 |
-
}
|
1348 |
-
cm.setCursor(0, 0); cm.execCommand(inv ? "goLineStart" : "goLineEnd");
|
1349 |
-
prevX = cursors.firstChild.offsetLeft;
|
1350 |
-
for (var i = 0; i < line.length; ++i) {
|
1351 |
-
cm.execCommand("goCharLeft");
|
1352 |
-
cursor = cursors.firstChild;
|
1353 |
-
is(cursor.offsetLeft < prevX, "moved left");
|
1354 |
-
prevX = cursor.offsetLeft;
|
1355 |
-
}
|
1356 |
-
});
|
1357 |
-
}, null, ie_lt9);
|
1358 |
-
|
1359 |
-
// Verify that updating a line clears its bidi ordering
|
1360 |
-
testCM("bidiUpdate", function(cm) {
|
1361 |
-
cm.setCursor(Pos(0, 2));
|
1362 |
-
cm.replaceSelection("خحج", "start");
|
1363 |
-
cm.execCommand("goCharRight");
|
1364 |
-
eqPos(cm.getCursor(), Pos(0, 4));
|
1365 |
-
}, {value: "abcd\n"});
|
1366 |
-
|
1367 |
-
testCM("movebyTextUnit", function(cm) {
|
1368 |
-
cm.setValue("בְּרֵאשִ\nééé́\n");
|
1369 |
-
cm.execCommand("goLineEnd");
|
1370 |
-
for (var i = 0; i < 4; ++i) cm.execCommand("goCharRight");
|
1371 |
-
eqPos(cm.getCursor(), Pos(0, 0));
|
1372 |
-
cm.execCommand("goCharRight");
|
1373 |
-
eqPos(cm.getCursor(), Pos(1, 0));
|
1374 |
-
cm.execCommand("goCharRight");
|
1375 |
-
cm.execCommand("goCharRight");
|
1376 |
-
eqPos(cm.getCursor(), Pos(1, 4));
|
1377 |
-
cm.execCommand("goCharRight");
|
1378 |
-
eqPos(cm.getCursor(), Pos(1, 7));
|
1379 |
-
});
|
1380 |
-
|
1381 |
-
testCM("lineChangeEvents", function(cm) {
|
1382 |
-
addDoc(cm, 3, 5);
|
1383 |
-
var log = [], want = ["ch 0", "ch 1", "del 2", "ch 0", "ch 0", "del 1", "del 3", "del 4"];
|
1384 |
-
for (var i = 0; i < 5; ++i) {
|
1385 |
-
CodeMirror.on(cm.getLineHandle(i), "delete", function(i) {
|
1386 |
-
return function() {log.push("del " + i);};
|
1387 |
-
}(i));
|
1388 |
-
CodeMirror.on(cm.getLineHandle(i), "change", function(i) {
|
1389 |
-
return function() {log.push("ch " + i);};
|
1390 |
-
}(i));
|
1391 |
-
}
|
1392 |
-
cm.replaceRange("x", Pos(0, 1));
|
1393 |
-
cm.replaceRange("xy", Pos(1, 1), Pos(2));
|
1394 |
-
cm.replaceRange("foo\nbar", Pos(0, 1));
|
1395 |
-
cm.replaceRange("", Pos(0, 0), Pos(cm.lineCount()));
|
1396 |
-
eq(log.length, want.length, "same length");
|
1397 |
-
for (var i = 0; i < log.length; ++i)
|
1398 |
-
eq(log[i], want[i]);
|
1399 |
-
});
|
1400 |
-
|
1401 |
-
testCM("scrollEntirelyToRight", function(cm) {
|
1402 |
-
if (phantom || cm.getOption("inputStyle") != "textarea") return;
|
1403 |
-
addDoc(cm, 500, 2);
|
1404 |
-
cm.setCursor(Pos(0, 500));
|
1405 |
-
var wrap = cm.getWrapperElement(), cur = byClassName(wrap, "CodeMirror-cursor")[0];
|
1406 |
-
is(wrap.getBoundingClientRect().right > cur.getBoundingClientRect().left);
|
1407 |
-
});
|
1408 |
-
|
1409 |
-
testCM("lineWidgets", function(cm) {
|
1410 |
-
addDoc(cm, 500, 3);
|
1411 |
-
var last = cm.charCoords(Pos(2, 0));
|
1412 |
-
var node = document.createElement("div");
|
1413 |
-
node.innerHTML = "hi";
|
1414 |
-
var widget = cm.addLineWidget(1, node);
|
1415 |
-
is(last.top < cm.charCoords(Pos(2, 0)).top, "took up space");
|
1416 |
-
cm.setCursor(Pos(1, 1));
|
1417 |
-
cm.execCommand("goLineDown");
|
1418 |
-
eqPos(cm.getCursor(), Pos(2, 1));
|
1419 |
-
cm.execCommand("goLineUp");
|
1420 |
-
eqPos(cm.getCursor(), Pos(1, 1));
|
1421 |
-
});
|
1422 |
-
|
1423 |
-
testCM("lineWidgetFocus", function(cm) {
|
1424 |
-
var place = document.getElementById("testground");
|
1425 |
-
place.className = "offscreen";
|
1426 |
-
try {
|
1427 |
-
addDoc(cm, 500, 10);
|
1428 |
-
var node = document.createElement("input");
|
1429 |
-
var widget = cm.addLineWidget(1, node);
|
1430 |
-
node.focus();
|
1431 |
-
eq(document.activeElement, node);
|
1432 |
-
cm.replaceRange("new stuff", Pos(1, 0));
|
1433 |
-
eq(document.activeElement, node);
|
1434 |
-
} finally {
|
1435 |
-
place.className = "";
|
1436 |
-
}
|
1437 |
-
});
|
1438 |
-
|
1439 |
-
testCM("lineWidgetCautiousRedraw", function(cm) {
|
1440 |
-
var node = document.createElement("div");
|
1441 |
-
node.innerHTML = "hahah";
|
1442 |
-
var w = cm.addLineWidget(0, node);
|
1443 |
-
var redrawn = false;
|
1444 |
-
w.on("redraw", function() { redrawn = true; });
|
1445 |
-
cm.replaceSelection("0");
|
1446 |
-
is(!redrawn);
|
1447 |
-
}, {value: "123\n456"});
|
1448 |
-
|
1449 |
-
|
1450 |
-
var knownScrollbarWidth;
|
1451 |
-
function scrollbarWidth(measure) {
|
1452 |
-
if (knownScrollbarWidth != null) return knownScrollbarWidth;
|
1453 |
-
var div = document.createElement('div');
|
1454 |
-
div.style.cssText = "width: 50px; height: 50px; overflow-x: scroll";
|
1455 |
-
document.body.appendChild(div);
|
1456 |
-
knownScrollbarWidth = div.offsetHeight - div.clientHeight;
|
1457 |
-
document.body.removeChild(div);
|
1458 |
-
return knownScrollbarWidth || 0;
|
1459 |
-
}
|
1460 |
-
|
1461 |
-
testCM("lineWidgetChanged", function(cm) {
|
1462 |
-
addDoc(cm, 2, 300);
|
1463 |
-
var halfScrollbarWidth = scrollbarWidth(cm.display.measure)/2;
|
1464 |
-
cm.setOption('lineNumbers', true);
|
1465 |
-
cm.setSize(600, cm.defaultTextHeight() * 50);
|
1466 |
-
cm.scrollTo(null, cm.heightAtLine(125, "local"));
|
1467 |
-
|
1468 |
-
var expectedWidgetHeight = 60;
|
1469 |
-
var expectedLinesInWidget = 3;
|
1470 |
-
function w() {
|
1471 |
-
var node = document.createElement("div");
|
1472 |
-
// we use these children with just under half width of the line to check measurements are made with correct width
|
1473 |
-
// when placed in the measure div.
|
1474 |
-
// If the widget is measured at a width much narrower than it is displayed at, the underHalf children will span two lines and break the test.
|
1475 |
-
// If the widget is measured at a width much wider than it is displayed at, the overHalf children will combine and break the test.
|
1476 |
-
// Note that this test only checks widgets where coverGutter is true, because these require extra styling to get the width right.
|
1477 |
-
// It may also be worthwhile to check this for non-coverGutter widgets.
|
1478 |
-
// Visually:
|
1479 |
-
// Good:
|
1480 |
-
// | ------------- display width ------------- |
|
1481 |
-
// | ------- widget-width when measured ------ |
|
1482 |
-
// | | -- under-half -- | | -- under-half -- | |
|
1483 |
-
// | | --- over-half --- | |
|
1484 |
-
// | | --- over-half --- | |
|
1485 |
-
// Height: measured as 3 lines, same as it will be when actually displayed
|
1486 |
-
|
1487 |
-
// Bad (too narrow):
|
1488 |
-
// | ------------- display width ------------- |
|
1489 |
-
// | ------ widget-width when measured ----- | < -- uh oh
|
1490 |
-
// | | -- under-half -- | |
|
1491 |
-
// | | -- under-half -- | | < -- when measured, shoved to next line
|
1492 |
-
// | | --- over-half --- | |
|
1493 |
-
// | | --- over-half --- | |
|
1494 |
-
// Height: measured as 4 lines, more than expected . Will be displayed as 3 lines!
|
1495 |
-
|
1496 |
-
// Bad (too wide):
|
1497 |
-
// | ------------- display width ------------- |
|
1498 |
-
// | -------- widget-width when measured ------- | < -- uh oh
|
1499 |
-
// | | -- under-half -- | | -- under-half -- | |
|
1500 |
-
// | | --- over-half --- | | --- over-half --- | | < -- when measured, combined on one line
|
1501 |
-
// Height: measured as 2 lines, less than expected. Will be displayed as 3 lines!
|
1502 |
-
|
1503 |
-
var barelyUnderHalfWidthHtml = '<div style="display: inline-block; height: 1px; width: '+(285 - halfScrollbarWidth)+'px;"></div>';
|
1504 |
-
var barelyOverHalfWidthHtml = '<div style="display: inline-block; height: 1px; width: '+(305 - halfScrollbarWidth)+'px;"></div>';
|
1505 |
-
node.innerHTML = new Array(3).join(barelyUnderHalfWidthHtml) + new Array(3).join(barelyOverHalfWidthHtml);
|
1506 |
-
node.style.cssText = "background: yellow;font-size:0;line-height: " + (expectedWidgetHeight/expectedLinesInWidget) + "px;";
|
1507 |
-
return node;
|
1508 |
-
}
|
1509 |
-
var info0 = cm.getScrollInfo();
|
1510 |
-
var w0 = cm.addLineWidget(0, w(), { coverGutter: true });
|
1511 |
-
var w150 = cm.addLineWidget(150, w(), { coverGutter: true });
|
1512 |
-
var w300 = cm.addLineWidget(300, w(), { coverGutter: true });
|
1513 |
-
var info1 = cm.getScrollInfo();
|
1514 |
-
eq(info0.height + (3 * expectedWidgetHeight), info1.height);
|
1515 |
-
eq(info0.top + expectedWidgetHeight, info1.top);
|
1516 |
-
expectedWidgetHeight = 12;
|
1517 |
-
w0.node.style.lineHeight = w150.node.style.lineHeight = w300.node.style.lineHeight = (expectedWidgetHeight/expectedLinesInWidget) + "px";
|
1518 |
-
w0.changed(); w150.changed(); w300.changed();
|
1519 |
-
var info2 = cm.getScrollInfo();
|
1520 |
-
eq(info0.height + (3 * expectedWidgetHeight), info2.height);
|
1521 |
-
eq(info0.top + expectedWidgetHeight, info2.top);
|
1522 |
-
});
|
1523 |
-
|
1524 |
-
testCM("getLineNumber", function(cm) {
|
1525 |
-
addDoc(cm, 2, 20);
|
1526 |
-
var h1 = cm.getLineHandle(1);
|
1527 |
-
eq(cm.getLineNumber(h1), 1);
|
1528 |
-
cm.replaceRange("hi\nbye\n", Pos(0, 0));
|
1529 |
-
eq(cm.getLineNumber(h1), 3);
|
1530 |
-
cm.setValue("");
|
1531 |
-
eq(cm.getLineNumber(h1), null);
|
1532 |
-
});
|
1533 |
-
|
1534 |
-
testCM("jumpTheGap", function(cm) {
|
1535 |
-
if (phantom) return;
|
1536 |
-
var longLine = "abcdef ghiklmnop qrstuvw xyz ";
|
1537 |
-
longLine += longLine; longLine += longLine; longLine += longLine;
|
1538 |
-
cm.replaceRange(longLine, Pos(2, 0), Pos(2));
|
1539 |
-
cm.setSize("200px", null);
|
1540 |
-
cm.getWrapperElement().style.lineHeight = 2;
|
1541 |
-
cm.refresh();
|
1542 |
-
cm.setCursor(Pos(0, 1));
|
1543 |
-
cm.execCommand("goLineDown");
|
1544 |
-
eqPos(cm.getCursor(), Pos(1, 1));
|
1545 |
-
cm.execCommand("goLineDown");
|
1546 |
-
eqPos(cm.getCursor(), Pos(2, 1));
|
1547 |
-
cm.execCommand("goLineDown");
|
1548 |
-
eq(cm.getCursor().line, 2);
|
1549 |
-
is(cm.getCursor().ch > 1);
|
1550 |
-
cm.execCommand("goLineUp");
|
1551 |
-
eqPos(cm.getCursor(), Pos(2, 1));
|
1552 |
-
cm.execCommand("goLineUp");
|
1553 |
-
eqPos(cm.getCursor(), Pos(1, 1));
|
1554 |
-
var node = document.createElement("div");
|
1555 |
-
node.innerHTML = "hi"; node.style.height = "30px";
|
1556 |
-
cm.addLineWidget(0, node);
|
1557 |
-
cm.addLineWidget(1, node.cloneNode(true), {above: true});
|
1558 |
-
cm.setCursor(Pos(0, 2));
|
1559 |
-
cm.execCommand("goLineDown");
|
1560 |
-
eqPos(cm.getCursor(), Pos(1, 2));
|
1561 |
-
cm.execCommand("goLineUp");
|
1562 |
-
eqPos(cm.getCursor(), Pos(0, 2));
|
1563 |
-
}, {lineWrapping: true, value: "abc\ndef\nghi\njkl\n"});
|
1564 |
-
|
1565 |
-
testCM("addLineClass", function(cm) {
|
1566 |
-
function cls(line, text, bg, wrap, gutter) {
|
1567 |
-
var i = cm.lineInfo(line);
|
1568 |
-
eq(i.textClass, text);
|
1569 |
-
eq(i.bgClass, bg);
|
1570 |
-
eq(i.wrapClass, wrap);
|
1571 |
-
if (typeof i.handle.gutterClass !== 'undefined') {
|
1572 |
-
eq(i.handle.gutterClass, gutter);
|
1573 |
-
}
|
1574 |
-
}
|
1575 |
-
cm.addLineClass(0, "text", "foo");
|
1576 |
-
cm.addLineClass(0, "text", "bar");
|
1577 |
-
cm.addLineClass(1, "background", "baz");
|
1578 |
-
cm.addLineClass(1, "wrap", "foo");
|
1579 |
-
cm.addLineClass(1, "gutter", "gutter-class");
|
1580 |
-
cls(0, "foo bar", null, null, null);
|
1581 |
-
cls(1, null, "baz", "foo", "gutter-class");
|
1582 |
-
var lines = cm.display.lineDiv;
|
1583 |
-
eq(byClassName(lines, "foo").length, 2);
|
1584 |
-
eq(byClassName(lines, "bar").length, 1);
|
1585 |
-
eq(byClassName(lines, "baz").length, 1);
|
1586 |
-
eq(byClassName(lines, "gutter-class").length, 2); // Gutter classes are reflected in 2 nodes
|
1587 |
-
cm.removeLineClass(0, "text", "foo");
|
1588 |
-
cls(0, "bar", null, null, null);
|
1589 |
-
cm.removeLineClass(0, "text", "foo");
|
1590 |
-
cls(0, "bar", null, null, null);
|
1591 |
-
cm.removeLineClass(0, "text", "bar");
|
1592 |
-
cls(0, null, null, null);
|
1593 |
-
|
1594 |
-
cm.addLineClass(1, "wrap", "quux");
|
1595 |
-
cls(1, null, "baz", "foo quux", "gutter-class");
|
1596 |
-
cm.removeLineClass(1, "wrap");
|
1597 |
-
cls(1, null, "baz", null, "gutter-class");
|
1598 |
-
cm.removeLineClass(1, "gutter", "gutter-class");
|
1599 |
-
eq(byClassName(lines, "gutter-class").length, 0);
|
1600 |
-
cls(1, null, "baz", null, null);
|
1601 |
-
|
1602 |
-
cm.addLineClass(1, "gutter", "gutter-class");
|
1603 |
-
cls(1, null, "baz", null, "gutter-class");
|
1604 |
-
cm.removeLineClass(1, "gutter", "gutter-class");
|
1605 |
-
cls(1, null, "baz", null, null);
|
1606 |
-
|
1607 |
-
}, {value: "hohoho\n", lineNumbers: true});
|
1608 |
-
|
1609 |
-
testCM("atomicMarker", function(cm) {
|
1610 |
-
addDoc(cm, 10, 10);
|
1611 |
-
function atom(ll, cl, lr, cr, li, ri) {
|
1612 |
-
return cm.markText(Pos(ll, cl), Pos(lr, cr),
|
1613 |
-
{atomic: true, inclusiveLeft: li, inclusiveRight: ri});
|
1614 |
-
}
|
1615 |
-
var m = atom(0, 1, 0, 5);
|
1616 |
-
cm.setCursor(Pos(0, 1));
|
1617 |
-
cm.execCommand("goCharRight");
|
1618 |
-
eqPos(cm.getCursor(), Pos(0, 5));
|
1619 |
-
cm.execCommand("goCharLeft");
|
1620 |
-
eqPos(cm.getCursor(), Pos(0, 1));
|
1621 |
-
m.clear();
|
1622 |
-
m = atom(0, 0, 0, 5, true);
|
1623 |
-
eqPos(cm.getCursor(), Pos(0, 5), "pushed out");
|
1624 |
-
cm.execCommand("goCharLeft");
|
1625 |
-
eqPos(cm.getCursor(), Pos(0, 5));
|
1626 |
-
m.clear();
|
1627 |
-
m = atom(8, 4, 9, 10, false, true);
|
1628 |
-
cm.setCursor(Pos(9, 8));
|
1629 |
-
eqPos(cm.getCursor(), Pos(8, 4), "set");
|
1630 |
-
cm.execCommand("goCharRight");
|
1631 |
-
eqPos(cm.getCursor(), Pos(8, 4), "char right");
|
1632 |
-
cm.execCommand("goLineDown");
|
1633 |
-
eqPos(cm.getCursor(), Pos(8, 4), "line down");
|
1634 |
-
cm.execCommand("goCharLeft");
|
1635 |
-
eqPos(cm.getCursor(), Pos(8, 3));
|
1636 |
-
m.clear();
|
1637 |
-
m = atom(1, 1, 3, 8);
|
1638 |
-
cm.setCursor(Pos(0, 0));
|
1639 |
-
cm.setCursor(Pos(2, 0));
|
1640 |
-
eqPos(cm.getCursor(), Pos(3, 8));
|
1641 |
-
cm.execCommand("goCharLeft");
|
1642 |
-
eqPos(cm.getCursor(), Pos(1, 1));
|
1643 |
-
cm.execCommand("goCharRight");
|
1644 |
-
eqPos(cm.getCursor(), Pos(3, 8));
|
1645 |
-
cm.execCommand("goLineUp");
|
1646 |
-
eqPos(cm.getCursor(), Pos(1, 1));
|
1647 |
-
cm.execCommand("goLineDown");
|
1648 |
-
eqPos(cm.getCursor(), Pos(3, 8));
|
1649 |
-
cm.execCommand("delCharBefore");
|
1650 |
-
eq(cm.getValue().length, 80, "del chunk");
|
1651 |
-
m = atom(3, 0, 5, 5);
|
1652 |
-
cm.setCursor(Pos(3, 0));
|
1653 |
-
cm.execCommand("delWordAfter");
|
1654 |
-
eq(cm.getValue().length, 53, "del chunk");
|
1655 |
-
});
|
1656 |
-
|
1657 |
-
testCM("selectionBias", function(cm) {
|
1658 |
-
cm.markText(Pos(0, 1), Pos(0, 3), {atomic: true});
|
1659 |
-
cm.setCursor(Pos(0, 2));
|
1660 |
-
eqPos(cm.getCursor(), Pos(0, 3));
|
1661 |
-
cm.setCursor(Pos(0, 2));
|
1662 |
-
eqPos(cm.getCursor(), Pos(0, 1));
|
1663 |
-
cm.setCursor(Pos(0, 2), null, {bias: -1});
|
1664 |
-
eqPos(cm.getCursor(), Pos(0, 1));
|
1665 |
-
cm.setCursor(Pos(0, 4));
|
1666 |
-
cm.setCursor(Pos(0, 2), null, {bias: 1});
|
1667 |
-
eqPos(cm.getCursor(), Pos(0, 3));
|
1668 |
-
}, {value: "12345"});
|
1669 |
-
|
1670 |
-
testCM("selectionHomeEnd", function(cm) {
|
1671 |
-
cm.markText(Pos(1, 0), Pos(1, 1), {atomic: true, inclusiveLeft: true});
|
1672 |
-
cm.markText(Pos(1, 3), Pos(1, 4), {atomic: true, inclusiveRight: true});
|
1673 |
-
cm.setCursor(Pos(1, 2));
|
1674 |
-
cm.execCommand("goLineStart");
|
1675 |
-
eqPos(cm.getCursor(), Pos(1, 1));
|
1676 |
-
cm.execCommand("goLineEnd");
|
1677 |
-
eqPos(cm.getCursor(), Pos(1, 3));
|
1678 |
-
}, {value: "ab\ncdef\ngh"});
|
1679 |
-
|
1680 |
-
testCM("readOnlyMarker", function(cm) {
|
1681 |
-
function mark(ll, cl, lr, cr, at) {
|
1682 |
-
return cm.markText(Pos(ll, cl), Pos(lr, cr),
|
1683 |
-
{readOnly: true, atomic: at});
|
1684 |
-
}
|
1685 |
-
var m = mark(0, 1, 0, 4);
|
1686 |
-
cm.setCursor(Pos(0, 2));
|
1687 |
-
cm.replaceSelection("hi", "end");
|
1688 |
-
eqPos(cm.getCursor(), Pos(0, 2));
|
1689 |
-
eq(cm.getLine(0), "abcde");
|
1690 |
-
cm.execCommand("selectAll");
|
1691 |
-
cm.replaceSelection("oops", "around");
|
1692 |
-
eq(cm.getValue(), "oopsbcd");
|
1693 |
-
cm.undo();
|
1694 |
-
eqPos(m.find().from, Pos(0, 1));
|
1695 |
-
eqPos(m.find().to, Pos(0, 4));
|
1696 |
-
m.clear();
|
1697 |
-
cm.setCursor(Pos(0, 2));
|
1698 |
-
cm.replaceSelection("hi", "around");
|
1699 |
-
eq(cm.getLine(0), "abhicde");
|
1700 |
-
eqPos(cm.getCursor(), Pos(0, 4));
|
1701 |
-
m = mark(0, 2, 2, 2, true);
|
1702 |
-
cm.setSelection(Pos(1, 1), Pos(2, 4));
|
1703 |
-
cm.replaceSelection("t", "end");
|
1704 |
-
eqPos(cm.getCursor(), Pos(2, 3));
|
1705 |
-
eq(cm.getLine(2), "klto");
|
1706 |
-
cm.execCommand("goCharLeft");
|
1707 |
-
cm.execCommand("goCharLeft");
|
1708 |
-
eqPos(cm.getCursor(), Pos(0, 2));
|
1709 |
-
cm.setSelection(Pos(0, 1), Pos(0, 3));
|
1710 |
-
cm.replaceSelection("xx", "around");
|
1711 |
-
eqPos(cm.getCursor(), Pos(0, 3));
|
1712 |
-
eq(cm.getLine(0), "axxhicde");
|
1713 |
-
}, {value: "abcde\nfghij\nklmno\n"});
|
1714 |
-
|
1715 |
-
testCM("dirtyBit", function(cm) {
|
1716 |
-
eq(cm.isClean(), true);
|
1717 |
-
cm.replaceSelection("boo", null, "test");
|
1718 |
-
eq(cm.isClean(), false);
|
1719 |
-
cm.undo();
|
1720 |
-
eq(cm.isClean(), true);
|
1721 |
-
cm.replaceSelection("boo", null, "test");
|
1722 |
-
cm.replaceSelection("baz", null, "test");
|
1723 |
-
cm.undo();
|
1724 |
-
eq(cm.isClean(), false);
|
1725 |
-
cm.markClean();
|
1726 |
-
eq(cm.isClean(), true);
|
1727 |
-
cm.undo();
|
1728 |
-
eq(cm.isClean(), false);
|
1729 |
-
cm.redo();
|
1730 |
-
eq(cm.isClean(), true);
|
1731 |
-
});
|
1732 |
-
|
1733 |
-
testCM("changeGeneration", function(cm) {
|
1734 |
-
cm.replaceSelection("x");
|
1735 |
-
var softGen = cm.changeGeneration();
|
1736 |
-
cm.replaceSelection("x");
|
1737 |
-
cm.undo();
|
1738 |
-
eq(cm.getValue(), "");
|
1739 |
-
is(!cm.isClean(softGen));
|
1740 |
-
cm.replaceSelection("x");
|
1741 |
-
var hardGen = cm.changeGeneration(true);
|
1742 |
-
cm.replaceSelection("x");
|
1743 |
-
cm.undo();
|
1744 |
-
eq(cm.getValue(), "x");
|
1745 |
-
is(cm.isClean(hardGen));
|
1746 |
-
});
|
1747 |
-
|
1748 |
-
testCM("addKeyMap", function(cm) {
|
1749 |
-
function sendKey(code) {
|
1750 |
-
cm.triggerOnKeyDown({type: "keydown", keyCode: code,
|
1751 |
-
preventDefault: function(){}, stopPropagation: function(){}});
|
1752 |
-
}
|
1753 |
-
|
1754 |
-
sendKey(39);
|
1755 |
-
eqPos(cm.getCursor(), Pos(0, 1));
|
1756 |
-
var test = 0;
|
1757 |
-
var map1 = {Right: function() { ++test; }}, map2 = {Right: function() { test += 10; }}
|
1758 |
-
cm.addKeyMap(map1);
|
1759 |
-
sendKey(39);
|
1760 |
-
eqPos(cm.getCursor(), Pos(0, 1));
|
1761 |
-
eq(test, 1);
|
1762 |
-
cm.addKeyMap(map2, true);
|
1763 |
-
sendKey(39);
|
1764 |
-
eq(test, 2);
|
1765 |
-
cm.removeKeyMap(map1);
|
1766 |
-
sendKey(39);
|
1767 |
-
eq(test, 12);
|
1768 |
-
cm.removeKeyMap(map2);
|
1769 |
-
sendKey(39);
|
1770 |
-
eq(test, 12);
|
1771 |
-
eqPos(cm.getCursor(), Pos(0, 2));
|
1772 |
-
cm.addKeyMap({Right: function() { test = 55; }, name: "mymap"});
|
1773 |
-
sendKey(39);
|
1774 |
-
eq(test, 55);
|
1775 |
-
cm.removeKeyMap("mymap");
|
1776 |
-
sendKey(39);
|
1777 |
-
eqPos(cm.getCursor(), Pos(0, 3));
|
1778 |
-
}, {value: "abc"});
|
1779 |
-
|
1780 |
-
testCM("findPosH", function(cm) {
|
1781 |
-
forEach([{from: Pos(0, 0), to: Pos(0, 1), by: 1},
|
1782 |
-
{from: Pos(0, 0), to: Pos(0, 0), by: -1, hitSide: true},
|
1783 |
-
{from: Pos(0, 0), to: Pos(0, 4), by: 1, unit: "word"},
|
1784 |
-
{from: Pos(0, 0), to: Pos(0, 8), by: 2, unit: "word"},
|
1785 |
-
{from: Pos(0, 0), to: Pos(2, 0), by: 20, unit: "word", hitSide: true},
|
1786 |
-
{from: Pos(0, 7), to: Pos(0, 5), by: -1, unit: "word"},
|
1787 |
-
{from: Pos(0, 4), to: Pos(0, 8), by: 1, unit: "word"},
|
1788 |
-
{from: Pos(1, 0), to: Pos(1, 18), by: 3, unit: "word"},
|
1789 |
-
{from: Pos(1, 22), to: Pos(1, 5), by: -3, unit: "word"},
|
1790 |
-
{from: Pos(1, 15), to: Pos(1, 10), by: -5},
|
1791 |
-
{from: Pos(1, 15), to: Pos(1, 10), by: -5, unit: "column"},
|
1792 |
-
{from: Pos(1, 15), to: Pos(1, 0), by: -50, unit: "column", hitSide: true},
|
1793 |
-
{from: Pos(1, 15), to: Pos(1, 24), by: 50, unit: "column", hitSide: true},
|
1794 |
-
{from: Pos(1, 15), to: Pos(2, 0), by: 50, hitSide: true}], function(t) {
|
1795 |
-
var r = cm.findPosH(t.from, t.by, t.unit || "char");
|
1796 |
-
eqPos(r, t.to);
|
1797 |
-
eq(!!r.hitSide, !!t.hitSide);
|
1798 |
-
});
|
1799 |
-
}, {value: "line one\nline two.something.other\n"});
|
1800 |
-
|
1801 |
-
testCM("beforeChange", function(cm) {
|
1802 |
-
cm.on("beforeChange", function(cm, change) {
|
1803 |
-
var text = [];
|
1804 |
-
for (var i = 0; i < change.text.length; ++i)
|
1805 |
-
text.push(change.text[i].replace(/\s/g, "_"));
|
1806 |
-
change.update(null, null, text);
|
1807 |
-
});
|
1808 |
-
cm.setValue("hello, i am a\nnew document\n");
|
1809 |
-
eq(cm.getValue(), "hello,_i_am_a\nnew_document\n");
|
1810 |
-
CodeMirror.on(cm.getDoc(), "beforeChange", function(doc, change) {
|
1811 |
-
if (change.from.line == 0) change.cancel();
|
1812 |
-
});
|
1813 |
-
cm.setValue("oops"); // Canceled
|
1814 |
-
eq(cm.getValue(), "hello,_i_am_a\nnew_document\n");
|
1815 |
-
cm.replaceRange("hey hey hey", Pos(1, 0), Pos(2, 0));
|
1816 |
-
eq(cm.getValue(), "hello,_i_am_a\nhey_hey_hey");
|
1817 |
-
}, {value: "abcdefghijk"});
|
1818 |
-
|
1819 |
-
testCM("beforeChangeUndo", function(cm) {
|
1820 |
-
cm.replaceRange("hi", Pos(0, 0), Pos(0));
|
1821 |
-
cm.replaceRange("bye", Pos(0, 0), Pos(0));
|
1822 |
-
eq(cm.historySize().undo, 2);
|
1823 |
-
cm.on("beforeChange", function(cm, change) {
|
1824 |
-
is(!change.update);
|
1825 |
-
change.cancel();
|
1826 |
-
});
|
1827 |
-
cm.undo();
|
1828 |
-
eq(cm.historySize().undo, 0);
|
1829 |
-
eq(cm.getValue(), "bye\ntwo");
|
1830 |
-
}, {value: "one\ntwo"});
|
1831 |
-
|
1832 |
-
testCM("beforeSelectionChange", function(cm) {
|
1833 |
-
function notAtEnd(cm, pos) {
|
1834 |
-
var len = cm.getLine(pos.line).length;
|
1835 |
-
if (!len || pos.ch == len) return Pos(pos.line, pos.ch - 1);
|
1836 |
-
return pos;
|
1837 |
-
}
|
1838 |
-
cm.on("beforeSelectionChange", function(cm, obj) {
|
1839 |
-
obj.update([{anchor: notAtEnd(cm, obj.ranges[0].anchor),
|
1840 |
-
head: notAtEnd(cm, obj.ranges[0].head)}]);
|
1841 |
-
});
|
1842 |
-
|
1843 |
-
addDoc(cm, 10, 10);
|
1844 |
-
cm.execCommand("goLineEnd");
|
1845 |
-
eqPos(cm.getCursor(), Pos(0, 9));
|
1846 |
-
cm.execCommand("selectAll");
|
1847 |
-
eqPos(cm.getCursor("start"), Pos(0, 0));
|
1848 |
-
eqPos(cm.getCursor("end"), Pos(9, 9));
|
1849 |
-
});
|
1850 |
-
|
1851 |
-
testCM("change_removedText", function(cm) {
|
1852 |
-
cm.setValue("abc\ndef");
|
1853 |
-
|
1854 |
-
var removedText = [];
|
1855 |
-
cm.on("change", function(cm, change) {
|
1856 |
-
removedText.push(change.removed);
|
1857 |
-
});
|
1858 |
-
|
1859 |
-
cm.operation(function() {
|
1860 |
-
cm.replaceRange("xyz", Pos(0, 0), Pos(1,1));
|
1861 |
-
cm.replaceRange("123", Pos(0,0));
|
1862 |
-
});
|
1863 |
-
|
1864 |
-
eq(removedText.length, 2);
|
1865 |
-
eq(removedText[0].join("\n"), "abc\nd");
|
1866 |
-
eq(removedText[1].join("\n"), "");
|
1867 |
-
|
1868 |
-
var removedText = [];
|
1869 |
-
cm.undo();
|
1870 |
-
eq(removedText.length, 2);
|
1871 |
-
eq(removedText[0].join("\n"), "123");
|
1872 |
-
eq(removedText[1].join("\n"), "xyz");
|
1873 |
-
|
1874 |
-
var removedText = [];
|
1875 |
-
cm.redo();
|
1876 |
-
eq(removedText.length, 2);
|
1877 |
-
eq(removedText[0].join("\n"), "abc\nd");
|
1878 |
-
eq(removedText[1].join("\n"), "");
|
1879 |
-
});
|
1880 |
-
|
1881 |
-
testCM("lineStyleFromMode", function(cm) {
|
1882 |
-
CodeMirror.defineMode("test_mode", function() {
|
1883 |
-
return {token: function(stream) {
|
1884 |
-
if (stream.match(/^\[[^\]]*\]/)) return " line-brackets ";
|
1885 |
-
if (stream.match(/^\([^\)]*\)/)) return " line-background-parens ";
|
1886 |
-
if (stream.match(/^<[^>]*>/)) return " span line-line line-background-bg ";
|
1887 |
-
stream.match(/^\s+|^\S+/);
|
1888 |
-
}};
|
1889 |
-
});
|
1890 |
-
cm.setOption("mode", "test_mode");
|
1891 |
-
var bracketElts = byClassName(cm.getWrapperElement(), "brackets");
|
1892 |
-
eq(bracketElts.length, 1, "brackets count");
|
1893 |
-
eq(bracketElts[0].nodeName, "PRE");
|
1894 |
-
is(!/brackets.*brackets/.test(bracketElts[0].className));
|
1895 |
-
var parenElts = byClassName(cm.getWrapperElement(), "parens");
|
1896 |
-
eq(parenElts.length, 1, "parens count");
|
1897 |
-
eq(parenElts[0].nodeName, "DIV");
|
1898 |
-
is(!/parens.*parens/.test(parenElts[0].className));
|
1899 |
-
eq(parenElts[0].parentElement.nodeName, "DIV");
|
1900 |
-
|
1901 |
-
eq(byClassName(cm.getWrapperElement(), "bg").length, 1);
|
1902 |
-
eq(byClassName(cm.getWrapperElement(), "line").length, 1);
|
1903 |
-
var spanElts = byClassName(cm.getWrapperElement(), "cm-span");
|
1904 |
-
eq(spanElts.length, 2);
|
1905 |
-
is(/^\s*cm-span\s*$/.test(spanElts[0].className));
|
1906 |
-
}, {value: "line1: [br] [br]\nline2: (par) (par)\nline3: <tag> <tag>"});
|
1907 |
-
|
1908 |
-
testCM("lineStyleFromBlankLine", function(cm) {
|
1909 |
-
CodeMirror.defineMode("lineStyleFromBlankLine_mode", function() {
|
1910 |
-
return {token: function(stream) { stream.skipToEnd(); return "comment"; },
|
1911 |
-
blankLine: function() { return "line-blank"; }};
|
1912 |
-
});
|
1913 |
-
cm.setOption("mode", "lineStyleFromBlankLine_mode");
|
1914 |
-
var blankElts = byClassName(cm.getWrapperElement(), "blank");
|
1915 |
-
eq(blankElts.length, 1);
|
1916 |
-
eq(blankElts[0].nodeName, "PRE");
|
1917 |
-
cm.replaceRange("x", Pos(1, 0));
|
1918 |
-
blankElts = byClassName(cm.getWrapperElement(), "blank");
|
1919 |
-
eq(blankElts.length, 0);
|
1920 |
-
}, {value: "foo\n\nbar"});
|
1921 |
-
|
1922 |
-
CodeMirror.registerHelper("xxx", "a", "A");
|
1923 |
-
CodeMirror.registerHelper("xxx", "b", "B");
|
1924 |
-
CodeMirror.defineMode("yyy", function() {
|
1925 |
-
return {
|
1926 |
-
token: function(stream) { stream.skipToEnd(); },
|
1927 |
-
xxx: ["a", "b", "q"]
|
1928 |
-
};
|
1929 |
-
});
|
1930 |
-
CodeMirror.registerGlobalHelper("xxx", "c", function(m) { return m.enableC; }, "C");
|
1931 |
-
|
1932 |
-
testCM("helpers", function(cm) {
|
1933 |
-
cm.setOption("mode", "yyy");
|
1934 |
-
eq(cm.getHelpers(Pos(0, 0), "xxx").join("/"), "A/B");
|
1935 |
-
cm.setOption("mode", {name: "yyy", modeProps: {xxx: "b", enableC: true}});
|
1936 |
-
eq(cm.getHelpers(Pos(0, 0), "xxx").join("/"), "B/C");
|
1937 |
-
cm.setOption("mode", "javascript");
|
1938 |
-
eq(cm.getHelpers(Pos(0, 0), "xxx").join("/"), "");
|
1939 |
-
});
|
1940 |
-
|
1941 |
-
testCM("selectionHistory", function(cm) {
|
1942 |
-
for (var i = 0; i < 3; i++) {
|
1943 |
-
cm.setExtending(true);
|
1944 |
-
cm.execCommand("goCharRight");
|
1945 |
-
cm.setExtending(false);
|
1946 |
-
cm.execCommand("goCharRight");
|
1947 |
-
cm.execCommand("goCharRight");
|
1948 |
-
}
|
1949 |
-
cm.execCommand("undoSelection");
|
1950 |
-
eq(cm.getSelection(), "c");
|
1951 |
-
cm.execCommand("undoSelection");
|
1952 |
-
eq(cm.getSelection(), "");
|
1953 |
-
eqPos(cm.getCursor(), Pos(0, 4));
|
1954 |
-
cm.execCommand("undoSelection");
|
1955 |
-
eq(cm.getSelection(), "b");
|
1956 |
-
cm.execCommand("redoSelection");
|
1957 |
-
eq(cm.getSelection(), "");
|
1958 |
-
eqPos(cm.getCursor(), Pos(0, 4));
|
1959 |
-
cm.execCommand("redoSelection");
|
1960 |
-
eq(cm.getSelection(), "c");
|
1961 |
-
cm.execCommand("redoSelection");
|
1962 |
-
eq(cm.getSelection(), "");
|
1963 |
-
eqPos(cm.getCursor(), Pos(0, 6));
|
1964 |
-
}, {value: "a b c d"});
|
1965 |
-
|
1966 |
-
testCM("selectionChangeReducesRedo", function(cm) {
|
1967 |
-
cm.replaceSelection("X");
|
1968 |
-
cm.execCommand("goCharRight");
|
1969 |
-
cm.undoSelection();
|
1970 |
-
cm.execCommand("selectAll");
|
1971 |
-
cm.undoSelection();
|
1972 |
-
eq(cm.getValue(), "Xabc");
|
1973 |
-
eqPos(cm.getCursor(), Pos(0, 1));
|
1974 |
-
cm.undoSelection();
|
1975 |
-
eq(cm.getValue(), "abc");
|
1976 |
-
}, {value: "abc"});
|
1977 |
-
|
1978 |
-
testCM("selectionHistoryNonOverlapping", function(cm) {
|
1979 |
-
cm.setSelection(Pos(0, 0), Pos(0, 1));
|
1980 |
-
cm.setSelection(Pos(0, 2), Pos(0, 3));
|
1981 |
-
cm.execCommand("undoSelection");
|
1982 |
-
eqPos(cm.getCursor("anchor"), Pos(0, 0));
|
1983 |
-
eqPos(cm.getCursor("head"), Pos(0, 1));
|
1984 |
-
}, {value: "1234"});
|
1985 |
-
|
1986 |
-
testCM("cursorMotionSplitsHistory", function(cm) {
|
1987 |
-
cm.replaceSelection("a");
|
1988 |
-
cm.execCommand("goCharRight");
|
1989 |
-
cm.replaceSelection("b");
|
1990 |
-
cm.replaceSelection("c");
|
1991 |
-
cm.undo();
|
1992 |
-
eq(cm.getValue(), "a1234");
|
1993 |
-
eqPos(cm.getCursor(), Pos(0, 2));
|
1994 |
-
cm.undo();
|
1995 |
-
eq(cm.getValue(), "1234");
|
1996 |
-
eqPos(cm.getCursor(), Pos(0, 0));
|
1997 |
-
}, {value: "1234"});
|
1998 |
-
|
1999 |
-
testCM("selChangeInOperationDoesNotSplit", function(cm) {
|
2000 |
-
for (var i = 0; i < 4; i++) {
|
2001 |
-
cm.operation(function() {
|
2002 |
-
cm.replaceSelection("x");
|
2003 |
-
cm.setCursor(Pos(0, cm.getCursor().ch - 1));
|
2004 |
-
});
|
2005 |
-
}
|
2006 |
-
eqPos(cm.getCursor(), Pos(0, 0));
|
2007 |
-
eq(cm.getValue(), "xxxxa");
|
2008 |
-
cm.undo();
|
2009 |
-
eq(cm.getValue(), "a");
|
2010 |
-
}, {value: "a"});
|
2011 |
-
|
2012 |
-
testCM("alwaysMergeSelEventWithChangeOrigin", function(cm) {
|
2013 |
-
cm.replaceSelection("U", null, "foo");
|
2014 |
-
cm.setSelection(Pos(0, 0), Pos(0, 1), {origin: "foo"});
|
2015 |
-
cm.undoSelection();
|
2016 |
-
eq(cm.getValue(), "a");
|
2017 |
-
cm.replaceSelection("V", null, "foo");
|
2018 |
-
cm.setSelection(Pos(0, 0), Pos(0, 1), {origin: "bar"});
|
2019 |
-
cm.undoSelection();
|
2020 |
-
eq(cm.getValue(), "Va");
|
2021 |
-
}, {value: "a"});
|
2022 |
-
|
2023 |
-
testCM("getTokenAt", function(cm) {
|
2024 |
-
var tokPlus = cm.getTokenAt(Pos(0, 2));
|
2025 |
-
eq(tokPlus.type, "operator");
|
2026 |
-
eq(tokPlus.string, "+");
|
2027 |
-
var toks = cm.getLineTokens(0);
|
2028 |
-
eq(toks.length, 3);
|
2029 |
-
forEach([["number", "1"], ["operator", "+"], ["number", "2"]], function(expect, i) {
|
2030 |
-
eq(toks[i].type, expect[0]);
|
2031 |
-
eq(toks[i].string, expect[1]);
|
2032 |
-
});
|
2033 |
-
}, {value: "1+2", mode: "javascript"});
|
2034 |
-
|
2035 |
-
testCM("getTokenTypeAt", function(cm) {
|
2036 |
-
eq(cm.getTokenTypeAt(Pos(0, 0)), "number");
|
2037 |
-
eq(cm.getTokenTypeAt(Pos(0, 6)), "string");
|
2038 |
-
cm.addOverlay({
|
2039 |
-
token: function(stream) {
|
2040 |
-
if (stream.match("foo")) return "foo";
|
2041 |
-
else stream.next();
|
2042 |
-
}
|
2043 |
-
});
|
2044 |
-
eq(byClassName(cm.getWrapperElement(), "cm-foo").length, 1);
|
2045 |
-
eq(cm.getTokenTypeAt(Pos(0, 6)), "string");
|
2046 |
-
}, {value: "1 + 'foo'", mode: "javascript"});
|
2047 |
-
|
2048 |
-
testCM("resizeLineWidget", function(cm) {
|
2049 |
-
addDoc(cm, 200, 3);
|
2050 |
-
var widget = document.createElement("pre");
|
2051 |
-
widget.innerHTML = "imwidget";
|
2052 |
-
widget.style.background = "yellow";
|
2053 |
-
cm.addLineWidget(1, widget, {noHScroll: true});
|
2054 |
-
cm.setSize(40);
|
2055 |
-
is(widget.parentNode.offsetWidth < 42);
|
2056 |
-
});
|
2057 |
-
|
2058 |
-
testCM("combinedOperations", function(cm) {
|
2059 |
-
var place = document.getElementById("testground");
|
2060 |
-
var other = CodeMirror(place, {value: "123"});
|
2061 |
-
try {
|
2062 |
-
cm.operation(function() {
|
2063 |
-
cm.addLineClass(0, "wrap", "foo");
|
2064 |
-
other.addLineClass(0, "wrap", "foo");
|
2065 |
-
});
|
2066 |
-
eq(byClassName(cm.getWrapperElement(), "foo").length, 1);
|
2067 |
-
eq(byClassName(other.getWrapperElement(), "foo").length, 1);
|
2068 |
-
cm.operation(function() {
|
2069 |
-
cm.removeLineClass(0, "wrap", "foo");
|
2070 |
-
other.removeLineClass(0, "wrap", "foo");
|
2071 |
-
});
|
2072 |
-
eq(byClassName(cm.getWrapperElement(), "foo").length, 0);
|
2073 |
-
eq(byClassName(other.getWrapperElement(), "foo").length, 0);
|
2074 |
-
} finally {
|
2075 |
-
place.removeChild(other.getWrapperElement());
|
2076 |
-
}
|
2077 |
-
}, {value: "abc"});
|
2078 |
-
|
2079 |
-
testCM("eventOrder", function(cm) {
|
2080 |
-
var seen = [];
|
2081 |
-
cm.on("change", function() {
|
2082 |
-
if (!seen.length) cm.replaceSelection(".");
|
2083 |
-
seen.push("change");
|
2084 |
-
});
|
2085 |
-
cm.on("cursorActivity", function() {
|
2086 |
-
cm.replaceSelection("!");
|
2087 |
-
seen.push("activity");
|
2088 |
-
});
|
2089 |
-
cm.replaceSelection("/");
|
2090 |
-
eq(seen.join(","), "change,change,activity,change");
|
2091 |
-
});
|
2092 |
-
|
2093 |
-
testCM("splitSpaces_nonspecial", function(cm) {
|
2094 |
-
eq(byClassName(cm.getWrapperElement(), "cm-invalidchar").length, 0);
|
2095 |
-
}, {
|
2096 |
-
specialChars: /[\u00a0]/,
|
2097 |
-
value: "spaces -> <- between"
|
2098 |
-
});
|
2099 |
-
|
2100 |
-
test("core_rmClass", function() {
|
2101 |
-
var node = document.createElement("div");
|
2102 |
-
node.className = "foo-bar baz-quux yadda";
|
2103 |
-
CodeMirror.rmClass(node, "quux");
|
2104 |
-
eq(node.className, "foo-bar baz-quux yadda");
|
2105 |
-
CodeMirror.rmClass(node, "baz-quux");
|
2106 |
-
eq(node.className, "foo-bar yadda");
|
2107 |
-
CodeMirror.rmClass(node, "yadda");
|
2108 |
-
eq(node.className, "foo-bar");
|
2109 |
-
CodeMirror.rmClass(node, "foo-bar");
|
2110 |
-
eq(node.className, "");
|
2111 |
-
node.className = " foo ";
|
2112 |
-
CodeMirror.rmClass(node, "foo");
|
2113 |
-
eq(node.className, "");
|
2114 |
-
});
|
2115 |
-
|
2116 |
-
test("core_addClass", function() {
|
2117 |
-
var node = document.createElement("div");
|
2118 |
-
CodeMirror.addClass(node, "a");
|
2119 |
-
eq(node.className, "a");
|
2120 |
-
CodeMirror.addClass(node, "a");
|
2121 |
-
eq(node.className, "a");
|
2122 |
-
CodeMirror.addClass(node, "b");
|
2123 |
-
eq(node.className, "a b");
|
2124 |
-
CodeMirror.addClass(node, "a");
|
2125 |
-
CodeMirror.addClass(node, "b");
|
2126 |
-
eq(node.className, "a b");
|
2127 |
-
});
|
2128 |
-
|
2129 |
-
testCM("lineSeparator", function(cm) {
|
2130 |
-
eq(cm.lineCount(), 3);
|
2131 |
-
eq(cm.getLine(1), "bar\r");
|
2132 |
-
eq(cm.getLine(2), "baz\rquux");
|
2133 |
-
cm.setOption("lineSeparator", "\r");
|
2134 |
-
eq(cm.lineCount(), 5);
|
2135 |
-
eq(cm.getLine(4), "quux");
|
2136 |
-
eq(cm.getValue(), "foo\rbar\r\rbaz\rquux");
|
2137 |
-
eq(cm.getValue("\n"), "foo\nbar\n\nbaz\nquux");
|
2138 |
-
cm.setOption("lineSeparator", null);
|
2139 |
-
cm.setValue("foo\nbar\r\nbaz\rquux");
|
2140 |
-
eq(cm.lineCount(), 4);
|
2141 |
-
}, {value: "foo\nbar\r\nbaz\rquux",
|
2142 |
-
lineSeparator: "\n"});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/toolset/toolset-common/visual-editor/res/js/codemirror/test/vim_test.js
DELETED
@@ -1,3955 +0,0 @@
|
|
1 |
-
CodeMirror.Vim.suppressErrorLogging = true;
|
2 |
-
|
3 |
-
var code = '' +
|
4 |
-
' wOrd1 (#%\n' +
|
5 |
-
' word3] \n' +
|
6 |
-
'aopop pop 0 1 2 3 4\n' +
|
7 |
-
' (a) [b] {c} \n' +
|
8 |
-
'int getchar(void) {\n' +
|
9 |
-
' static char buf[BUFSIZ];\n' +
|
10 |
-
' static char *bufp = buf;\n' +
|
11 |
-
' if (n == 0) { /* buffer is empty */\n' +
|
12 |
-
' n = read(0, buf, sizeof buf);\n' +
|
13 |
-
' bufp = buf;\n' +
|
14 |
-
' }\n' +
|
15 |
-
'\n' +
|
16 |
-
' return (--n >= 0) ? (unsigned char) *bufp++ : EOF;\n' +
|
17 |
-
' \n' +
|
18 |
-
'}\n';
|
19 |
-
|
20 |
-
var lines = (function() {
|
21 |
-
lineText = code.split('\n');
|
22 |
-
var ret = [];
|
23 |
-
for (var i = 0; i < lineText.length; i++) {
|
24 |
-
ret[i] = {
|
25 |
-
line: i,
|
26 |
-
length: lineText[i].length,
|
27 |
-
lineText: lineText[i],
|
28 |
-
textStart: /^\s*/.exec(lineText[i])[0].length
|
29 |
-
};
|
30 |
-
}
|
31 |
-
return ret;
|
32 |
-
})();
|
33 |
-
var endOfDocument = makeCursor(lines.length - 1,
|
34 |
-
lines[lines.length - 1].length);
|
35 |
-
var wordLine = lines[0];
|
36 |
-
var bigWordLine = lines[1];
|
37 |
-
var charLine = lines[2];
|
38 |
-
var bracesLine = lines[3];
|
39 |
-
var seekBraceLine = lines[4];
|
40 |
-
|
41 |
-
var word1 = {
|
42 |
-
start: { line: wordLine.line, ch: 1 },
|
43 |
-
end: { line: wordLine.line, ch: 5 }
|
44 |
-
};
|
45 |
-
var word2 = {
|
46 |
-
start: { line: wordLine.line, ch: word1.end.ch + 2 },
|
47 |
-
end: { line: wordLine.line, ch: word1.end.ch + 4 }
|
48 |
-
};
|
49 |
-
var word3 = {
|
50 |
-
start: { line: bigWordLine.line, ch: 1 },
|
51 |
-
end: { line: bigWordLine.line, ch: 5 }
|
52 |
-
};
|
53 |
-
var bigWord1 = word1;
|
54 |
-
var bigWord2 = word2;
|
55 |
-
var bigWord3 = {
|
56 |
-
start: { line: bigWordLine.line, ch: 1 },
|
57 |
-
end: { line: bigWordLine.line, ch: 7 }
|
58 |
-
};
|
59 |
-
var bigWord4 = {
|
60 |
-
start: { line: bigWordLine.line, ch: bigWord1.end.ch + 3 },
|
61 |
-
end: { line: bigWordLine.line, ch: bigWord1.end.ch + 7 }
|
62 |
-
};
|
63 |
-
|
64 |
-
var oChars = [ { line: charLine.line, ch: 1 },
|
65 |
-
{ line: charLine.line, ch: 3 },
|
66 |
-
{ line: charLine.line, ch: 7 } ];
|
67 |
-
var pChars = [ { line: charLine.line, ch: 2 },
|
68 |
-
{ line: charLine.line, ch: 4 },
|
69 |
-
{ line: charLine.line, ch: 6 },
|
70 |
-
{ line: charLine.line, ch: 8 } ];
|
71 |
-
var numChars = [ { line: charLine.line, ch: 10 },
|
72 |
-
{ line: charLine.line, ch: 12 },
|
73 |
-
{ line: charLine.line, ch: 14 },
|
74 |
-
{ line: charLine.line, ch: 16 },
|
75 |
-
{ line: charLine.line, ch: 18 }];
|
76 |
-
var parens1 = {
|
77 |
-
start: { line: bracesLine.line, ch: 1 },
|
78 |
-
end: { line: bracesLine.line, ch: 3 }
|
79 |
-
};
|
80 |
-
var squares1 = {
|
81 |
-
start: { line: bracesLine.line, ch: 5 },
|
82 |
-
end: { line: bracesLine.line, ch: 7 }
|
83 |
-
};
|
84 |
-
var curlys1 = {
|
85 |
-
start: { line: bracesLine.line, ch: 9 },
|
86 |
-
end: { line: bracesLine.line, ch: 11 }
|
87 |
-
};
|
88 |
-
var seekOutside = {
|
89 |
-
start: { line: seekBraceLine.line, ch: 1 },
|
90 |
-
end: { line: seekBraceLine.line, ch: 16 }
|
91 |
-
};
|
92 |
-
var seekInside = {
|
93 |
-
start: { line: seekBraceLine.line, ch: 14 },
|
94 |
-
end: { line: seekBraceLine.line, ch: 11 }
|
95 |
-
};
|
96 |
-
|
97 |
-
function copyCursor(cur) {
|
98 |
-
return { ch: cur.ch, line: cur.line };
|
99 |
-
}
|
100 |
-
|
101 |
-
function forEach(arr, func) {
|
102 |
-
for (var i = 0; i < arr.length; i++) {
|
103 |
-
func(arr[i], i, arr);
|
104 |
-
}
|
105 |
-
}
|
106 |
-
|
107 |
-
function testVim(name, run, opts, expectedFail) {
|
108 |
-
var vimOpts = {
|
109 |
-
lineNumbers: true,
|
110 |
-
vimMode: true,
|
111 |
-
showCursorWhenSelecting: true,
|
112 |
-
value: code
|
113 |
-
};
|
114 |
-
for (var prop in opts) {
|
115 |
-
if (opts.hasOwnProperty(prop)) {
|
116 |
-
vimOpts[prop] = opts[prop];
|
117 |
-
}
|
118 |
-
}
|
119 |
-
return test('vim_' + name, function() {
|
120 |
-
var place = document.getElementById("testground");
|
121 |
-
var cm = CodeMirror(place, vimOpts);
|
122 |
-
var vim = CodeMirror.Vim.maybeInitVimState_(cm);
|
123 |
-
|
124 |
-
function doKeysFn(cm) {
|
125 |
-
return function(args) {
|
126 |
-
if (args instanceof Array) {
|
127 |
-
arguments = args;
|
128 |
-
}
|
129 |
-
for (var i = 0; i < arguments.length; i++) {
|
130 |
-
CodeMirror.Vim.handleKey(cm, arguments[i]);
|
131 |
-
}
|
132 |
-
}
|
133 |
-
}
|
134 |
-
function doInsertModeKeysFn(cm) {
|
135 |
-
return function(args) {
|
136 |
-
if (args instanceof Array) { arguments = args; }
|
137 |
-
function executeHandler(handler) {
|
138 |
-
if (typeof handler == 'string') {
|
139 |
-
CodeMirror.commands[handler](cm);
|
140 |
-
} else {
|
141 |
-
handler(cm);
|
142 |
-
}
|
143 |
-
return true;
|
144 |
-
}
|
145 |
-
for (var i = 0; i < arguments.length; i++) {
|
146 |
-
var key = arguments[i];
|
147 |
-
// Find key in keymap and handle.
|
148 |
-
var handled = CodeMirror.lookupKey(key, 'vim-insert', executeHandler);
|
149 |
-
// Record for insert mode.
|
150 |
-
if (handled == "handled" && cm.state.vim.insertMode && arguments[i] != 'Esc') {
|
151 |
-
var lastChange = CodeMirror.Vim.getVimGlobalState_().macroModeState.lastInsertModeChanges;
|
152 |
-
if (lastChange) {
|
153 |
-
lastChange.changes.push(new CodeMirror.Vim.InsertModeKey(key));
|
154 |
-
}
|
155 |
-
}
|
156 |
-
}
|
157 |
-
}
|
158 |
-
}
|
159 |
-
function doExFn(cm) {
|
160 |
-
return function(command) {
|
161 |
-
cm.openDialog = helpers.fakeOpenDialog(command);
|
162 |
-
helpers.doKeys(':');
|
163 |
-
}
|
164 |
-
}
|
165 |
-
function assertCursorAtFn(cm) {
|
166 |
-
return function(line, ch) {
|
167 |
-
var pos;
|
168 |
-
if (ch == null && typeof line.line == 'number') {
|
169 |
-
pos = line;
|
170 |
-
} else {
|
171 |
-
pos = makeCursor(line, ch);
|
172 |
-
}
|
173 |
-
eqPos(pos, cm.getCursor());
|
174 |
-
}
|
175 |
-
}
|
176 |
-
function fakeOpenDialog(result) {
|
177 |
-
return function(text, callback) {
|
178 |
-
return callback(result);
|
179 |
-
}
|
180 |
-
}
|
181 |
-
function fakeOpenNotification(matcher) {
|
182 |
-
return function(text) {
|
183 |
-
matcher(text);
|
184 |
-
}
|
185 |
-
}
|
186 |
-
var helpers = {
|
187 |
-
doKeys: doKeysFn(cm),
|
188 |
-
// Warning: Only emulates keymap events, not character insertions. Use
|
189 |
-
// replaceRange to simulate character insertions.
|
190 |
-
// Keys are in CodeMirror format, NOT vim format.
|
191 |
-
doInsertModeKeys: doInsertModeKeysFn(cm),
|
192 |
-
doEx: doExFn(cm),
|
193 |
-
assertCursorAt: assertCursorAtFn(cm),
|
194 |
-
fakeOpenDialog: fakeOpenDialog,
|
195 |
-
fakeOpenNotification: fakeOpenNotification,
|
196 |
-
getRegisterController: function() {
|
197 |
-
return CodeMirror.Vim.getRegisterController();
|
198 |
-
}
|
199 |
-
}
|
200 |
-
CodeMirror.Vim.resetVimGlobalState_();
|
201 |
-
var successful = false;
|
202 |
-
var savedOpenNotification = cm.openNotification;
|
203 |
-
var savedOpenDialog = cm.openDialog;
|
204 |
-
try {
|
205 |
-
run(cm, vim, helpers);
|
206 |
-
successful = true;
|
207 |
-
} finally {
|
208 |
-
cm.openNotification = savedOpenNotification;
|
209 |
-
cm.openDialog = savedOpenDialog;
|
210 |
-
if (!successful || verbose) {
|
211 |
-
place.style.visibility = "visible";
|
212 |
-
} else {
|
213 |
-
place.removeChild(cm.getWrapperElement());
|
214 |
-
}
|
215 |
-
}
|
216 |
-
}, expectedFail);
|
217 |
-
};
|
218 |
-
testVim('qq@q', function(cm, vim, helpers) {
|
219 |
-
cm.setCursor(0, 0);
|
220 |
-
helpers.doKeys('q', 'q', 'l', 'l', 'q');
|
221 |
-
helpers.assertCursorAt(0,2);
|
222 |
-
helpers.doKeys('@', 'q');
|
223 |
-
helpers.assertCursorAt(0,4);
|
224 |
-
}, { value: ' '});
|
225 |
-
testVim('@@', function(cm, vim, helpers) {
|
226 |
-
cm.setCursor(0, 0);
|
227 |
-
helpers.doKeys('q', 'q', 'l', 'l', 'q');
|
228 |
-
helpers.assertCursorAt(0,2);
|
229 |
-
helpers.doKeys('@', 'q');
|
230 |
-
helpers.assertCursorAt(0,4);
|
231 |
-
helpers.doKeys('@', '@');
|
232 |
-
helpers.assertCursorAt(0,6);
|
233 |
-
}, { value: ' '});
|
234 |
-
var jumplistScene = ''+
|
235 |
-
'word\n'+
|
236 |
-
'(word)\n'+
|
237 |
-
'{word\n'+
|
238 |
-
'word.\n'+
|
239 |
-
'\n'+
|
240 |
-
'word search\n'+
|
241 |
-
'}word\n'+
|
242 |
-
'word\n'+
|
243 |
-
'word\n';
|
244 |
-
function testJumplist(name, keys, endPos, startPos, dialog) {
|
245 |
-
endPos = makeCursor(endPos[0], endPos[1]);
|
246 |
-
startPos = makeCursor(startPos[0], startPos[1]);
|
247 |
-
testVim(name, function(cm, vim, helpers) {
|
248 |
-
CodeMirror.Vim.resetVimGlobalState_();
|
249 |
-
if(dialog)cm.openDialog = helpers.fakeOpenDialog('word');
|
250 |
-
cm.setCursor(startPos);
|
251 |
-
helpers.doKeys.apply(null, keys);
|
252 |
-
helpers.assertCursorAt(endPos);
|
253 |
-
}, {value: jumplistScene});
|
254 |
-
};
|
255 |
-
testJumplist('jumplist_H', ['H', '<C-o>'], [5,2], [5,2]);
|
256 |
-
testJumplist('jumplist_M', ['M', '<C-o>'], [2,2], [2,2]);
|
257 |
-
testJumplist('jumplist_L', ['L', '<C-o>'], [2,2], [2,2]);
|
258 |
-
testJumplist('jumplist_[[', ['[', '[', '<C-o>'], [5,2], [5,2]);
|
259 |
-
testJumplist('jumplist_]]', [']', ']', '<C-o>'], [2,2], [2,2]);
|
260 |
-
testJumplist('jumplist_G', ['G', '<C-o>'], [5,2], [5,2]);
|
261 |
-
testJumplist('jumplist_gg', ['g', 'g', '<C-o>'], [5,2], [5,2]);
|
262 |
-
testJumplist('jumplist_%', ['%', '<C-o>'], [1,5], [1,5]);
|
263 |
-
testJumplist('jumplist_{', ['{', '<C-o>'], [1,5], [1,5]);
|
264 |
-
testJumplist('jumplist_}', ['}', '<C-o>'], [1,5], [1,5]);
|
265 |
-
testJumplist('jumplist_\'', ['m', 'a', 'h', '\'', 'a', 'h', '<C-i>'], [1,0], [1,5]);
|
266 |
-
testJumplist('jumplist_`', ['m', 'a', 'h', '`', 'a', 'h', '<C-i>'], [1,5], [1,5]);
|
267 |
-
testJumplist('jumplist_*_cachedCursor', ['*', '<C-o>'], [1,3], [1,3]);
|
268 |
-
testJumplist('jumplist_#_cachedCursor', ['#', '<C-o>'], [1,3], [1,3]);
|
269 |
-
testJumplist('jumplist_n', ['#', 'n', '<C-o>'], [1,1], [2,3]);
|
270 |
-
testJumplist('jumplist_N', ['#', 'N', '<C-o>'], [1,1], [2,3]);
|
271 |
-
testJumplist('jumplist_repeat_<c-o>', ['*', '*', '*', '3', '<C-o>'], [2,3], [2,3]);
|
272 |
-
testJumplist('jumplist_repeat_<c-i>', ['*', '*', '*', '3', '<C-o>', '2', '<C-i>'], [5,0], [2,3]);
|
273 |
-
testJumplist('jumplist_repeated_motion', ['3', '*', '<C-o>'], [2,3], [2,3]);
|
274 |
-
testJumplist('jumplist_/', ['/', '<C-o>'], [2,3], [2,3], 'dialog');
|
275 |
-
testJumplist('jumplist_?', ['?', '<C-o>'], [2,3], [2,3], 'dialog');
|
276 |
-
testJumplist('jumplist_skip_delted_mark<c-o>',
|
277 |
-
['*', 'n', 'n', 'k', 'd', 'k', '<C-o>', '<C-o>', '<C-o>'],
|
278 |
-
[0,2], [0,2]);
|
279 |
-
testJumplist('jumplist_skip_delted_mark<c-i>',
|
280 |
-
['*', 'n', 'n', 'k', 'd', 'k', '<C-o>', '<C-i>', '<C-i>'],
|
281 |
-
[1,0], [0,2]);
|
282 |
-
|
283 |
-
/**
|
284 |
-
* @param name Name of the test
|
285 |
-
* @param keys An array of keys or a string with a single key to simulate.
|
286 |
-
* @param endPos The expected end position of the cursor.
|
287 |
-
* @param startPos The position the cursor should start at, defaults to 0, 0.
|
288 |
-
*/
|
289 |
-
function testMotion(name, keys, endPos, startPos) {
|
290 |
-
testVim(name, function(cm, vim, helpers) {
|
291 |
-
if (!startPos) {
|
292 |
-
startPos = { line: 0, ch: 0 };
|
293 |
-
}
|
294 |
-
cm.setCursor(startPos);
|
295 |
-
helpers.doKeys(keys);
|
296 |
-
helpers.assertCursorAt(endPos);
|
297 |
-
});
|
298 |
-
};
|
299 |
-
|
300 |
-
function makeCursor(line, ch) {
|
301 |
-
return { line: line, ch: ch };
|
302 |
-
};
|
303 |
-
|
304 |
-
function offsetCursor(cur, offsetLine, offsetCh) {
|
305 |
-
return { line: cur.line + offsetLine, ch: cur.ch + offsetCh };
|
306 |
-
};
|
307 |
-
|
308 |
-
// Motion tests
|
309 |
-
testMotion('|', '|', makeCursor(0, 0), makeCursor(0,4));
|
310 |
-
testMotion('|_repeat', ['3', '|'], makeCursor(0, 2), makeCursor(0,4));
|
311 |
-
testMotion('h', 'h', makeCursor(0, 0), word1.start);
|
312 |
-
testMotion('h_repeat', ['3', 'h'], offsetCursor(word1.end, 0, -3), word1.end);
|
313 |
-
testMotion('l', 'l', makeCursor(0, 1));
|
314 |
-
testMotion('l_repeat', ['2', 'l'], makeCursor(0, 2));
|
315 |
-
testMotion('j', 'j', offsetCursor(word1.end, 1, 0), word1.end);
|
316 |
-
testMotion('j_repeat', ['2', 'j'], offsetCursor(word1.end, 2, 0), word1.end);
|
317 |
-
testMotion('j_repeat_clip', ['1000', 'j'], endOfDocument);
|
318 |
-
testMotion('k', 'k', offsetCursor(word3.end, -1, 0), word3.end);
|
319 |
-
testMotion('k_repeat', ['2', 'k'], makeCursor(0, 4), makeCursor(2, 4));
|
320 |
-
testMotion('k_repeat_clip', ['1000', 'k'], makeCursor(0, 4), makeCursor(2, 4));
|
321 |
-
testMotion('w', 'w', word1.start);
|
322 |
-
testMotion('w_multiple_newlines_no_space', 'w', makeCursor(12, 2), makeCursor(11, 2));
|
323 |
-
testMotion('w_multiple_newlines_with_space', 'w', makeCursor(14, 0), makeCursor(12, 51));
|
324 |
-
testMotion('w_repeat', ['2', 'w'], word2.start);
|
325 |
-
testMotion('w_wrap', ['w'], word3.start, word2.start);
|
326 |
-
testMotion('w_endOfDocument', 'w', endOfDocument, endOfDocument);
|
327 |
-
testMotion('w_start_to_end', ['1000', 'w'], endOfDocument, makeCursor(0, 0));
|
328 |
-
testMotion('W', 'W', bigWord1.start);
|
329 |
-
testMotion('W_repeat', ['2', 'W'], bigWord3.start, bigWord1.start);
|
330 |
-
testMotion('e', 'e', word1.end);
|
331 |
-
testMotion('e_repeat', ['2', 'e'], word2.end);
|
332 |
-
testMotion('e_wrap', 'e', word3.end, word2.end);
|
333 |
-
testMotion('e_endOfDocument', 'e', endOfDocument, endOfDocument);
|
334 |
-
testMotion('e_start_to_end', ['1000', 'e'], endOfDocument, makeCursor(0, 0));
|
335 |
-
testMotion('b', 'b', word3.start, word3.end);
|
336 |
-
testMotion('b_repeat', ['2', 'b'], word2.start, word3.end);
|
337 |
-
testMotion('b_wrap', 'b', word2.start, word3.start);
|
338 |
-
testMotion('b_startOfDocument', 'b', makeCursor(0, 0), makeCursor(0, 0));
|
339 |
-
testMotion('b_end_to_start', ['1000', 'b'], makeCursor(0, 0), endOfDocument);
|
340 |
-
testMotion('ge', ['g', 'e'], word2.end, word3.end);
|
341 |
-
testMotion('ge_repeat', ['2', 'g', 'e'], word1.end, word3.start);
|
342 |
-
testMotion('ge_wrap', ['g', 'e'], word2.end, word3.start);
|
343 |
-
testMotion('ge_startOfDocument', ['g', 'e'], makeCursor(0, 0),
|
344 |
-
makeCursor(0, 0));
|
345 |
-
testMotion('ge_end_to_start', ['1000', 'g', 'e'], makeCursor(0, 0), endOfDocument);
|
346 |
-
testMotion('gg', ['g', 'g'], makeCursor(lines[0].line, lines[0].textStart),
|
347 |
-
makeCursor(3, 1));
|
348 |
-
testMotion('gg_repeat', ['3', 'g', 'g'],
|
349 |
-
makeCursor(lines[2].line, lines[2].textStart));
|
350 |
-
testMotion('G', 'G',
|
351 |
-
makeCursor(lines[lines.length - 1].line, lines[lines.length - 1].textStart),
|
352 |
-
makeCursor(3, 1));
|
353 |
-
testMotion('G_repeat', ['3', 'G'], makeCursor(lines[2].line,
|
354 |
-
lines[2].textStart));
|
355 |
-
// TODO: Make the test code long enough to test Ctrl-F and Ctrl-B.
|
356 |
-
testMotion('0', '0', makeCursor(0, 0), makeCursor(0, 8));
|
357 |
-
testMotion('^', '^', makeCursor(0, lines[0].textStart), makeCursor(0, 8));
|
358 |
-
testMotion('+', '+', makeCursor(1, lines[1].textStart), makeCursor(0, 8));
|
359 |
-
testMotion('-', '-', makeCursor(0, lines[0].textStart), makeCursor(1, 4));
|
360 |
-
testMotion('_', ['6','_'], makeCursor(5, lines[5].textStart), makeCursor(0, 8));
|
361 |
-
testMotion('$', '$', makeCursor(0, lines[0].length - 1), makeCursor(0, 1));
|
362 |
-
testMotion('$_repeat', ['2', '$'], makeCursor(1, lines[1].length - 1),
|
363 |
-
makeCursor(0, 3));
|
364 |
-
testMotion('f', ['f', 'p'], pChars[0], makeCursor(charLine.line, 0));
|
365 |
-
testMotion('f_repeat', ['2', 'f', 'p'], pChars[2], pChars[0]);
|
366 |
-
testMotion('f_num', ['f', '2'], numChars[2], makeCursor(charLine.line, 0));
|
367 |
-
testMotion('t', ['t','p'], offsetCursor(pChars[0], 0, -1),
|
368 |
-
makeCursor(charLine.line, 0));
|
369 |
-
testMotion('t_repeat', ['2', 't', 'p'], offsetCursor(pChars[2], 0, -1),
|
370 |
-
pChars[0]);
|
371 |
-
testMotion('F', ['F', 'p'], pChars[0], pChars[1]);
|
372 |
-
testMotion('F_repeat', ['2', 'F', 'p'], pChars[0], pChars[2]);
|
373 |
-
testMotion('T', ['T', 'p'], offsetCursor(pChars[0], 0, 1), pChars[1]);
|
374 |
-
testMotion('T_repeat', ['2', 'T', 'p'], offsetCursor(pChars[0], 0, 1), pChars[2]);
|
375 |
-
testMotion('%_parens', ['%'], parens1.end, parens1.start);
|
376 |
-
testMotion('%_squares', ['%'], squares1.end, squares1.start);
|
377 |
-
testMotion('%_braces', ['%'], curlys1.end, curlys1.start);
|
378 |
-
testMotion('%_seek_outside', ['%'], seekOutside.end, seekOutside.start);
|
379 |
-
testMotion('%_seek_inside', ['%'], seekInside.end, seekInside.start);
|
380 |
-
testVim('%_seek_skip', function(cm, vim, helpers) {
|
381 |
-
cm.setCursor(0,0);
|
382 |
-
helpers.doKeys(['%']);
|
383 |
-
helpers.assertCursorAt(0,9);
|
384 |
-
}, {value:'01234"("()'});
|
385 |
-
testVim('%_skip_string', function(cm, vim, helpers) {
|
386 |
-
cm.setCursor(0,0);
|
387 |
-
helpers.doKeys(['%']);
|
388 |
-
helpers.assertCursorAt(0,4);
|
389 |
-
cm.setCursor(0,2);
|
390 |
-
helpers.doKeys(['%']);
|
391 |
-
helpers.assertCursorAt(0,0);
|
392 |
-
}, {value:'(")")'});
|
393 |
-
testVim('%_skip_comment', function(cm, vim, helpers) {
|
394 |
-
cm.setCursor(0,0);
|
395 |
-
helpers.doKeys(['%']);
|
396 |
-
helpers.assertCursorAt(0,6);
|
397 |
-
cm.setCursor(0,3);
|
398 |
-
helpers.doKeys(['%']);
|
399 |
-
helpers.assertCursorAt(0,0);
|
400 |
-
}, {value:'(/*)*/)'});
|
401 |
-
// Make sure that moving down after going to the end of a line always leaves you
|
402 |
-
// at the end of a line, but preserves the offset in other cases
|
403 |
-
testVim('Changing lines after Eol operation', function(cm, vim, helpers) {
|
404 |
-
cm.setCursor(0,0);
|
405 |
-
helpers.doKeys(['$']);
|
406 |
-
helpers.doKeys(['j']);
|
407 |
-
// After moving to Eol and then down, we should be at Eol of line 2
|
408 |
-
helpers.assertCursorAt({ line: 1, ch: lines[1].length - 1 });
|
409 |
-
helpers.doKeys(['j']);
|
410 |
-
// After moving down, we should be at Eol of line 3
|
411 |
-
helpers.assertCursorAt({ line: 2, ch: lines[2].length - 1 });
|
412 |
-
helpers.doKeys(['h']);
|
413 |
-
helpers.doKeys(['j']);
|
414 |
-
// After moving back one space and then down, since line 4 is shorter than line 2, we should
|
415 |
-
// be at Eol of line 2 - 1
|
416 |
-
helpers.assertCursorAt({ line: 3, ch: lines[3].length - 1 });
|
417 |
-
helpers.doKeys(['j']);
|
418 |
-
helpers.doKeys(['j']);
|
419 |
-
// After moving down again, since line 3 has enough characters, we should be back to the
|
420 |
-
// same place we were at on line 1
|
421 |
-
helpers.assertCursorAt({ line: 5, ch: lines[2].length - 2 });
|
422 |
-
});
|
423 |
-
//making sure gj and gk recover from clipping
|
424 |
-
testVim('gj_gk_clipping', function(cm,vim,helpers){
|
425 |
-
cm.setCursor(0, 1);
|
426 |
-
helpers.doKeys('g','j','g','j');
|
427 |
-
helpers.assertCursorAt(2, 1);
|
428 |
-
helpers.doKeys('g','k','g','k');
|
429 |
-
helpers.assertCursorAt(0, 1);
|
430 |
-
},{value: 'line 1\n\nline 2'});
|
431 |
-
//testing a mix of j/k and gj/gk
|
432 |
-
testVim('j_k_and_gj_gk', function(cm,vim,helpers){
|
433 |
-
cm.setSize(120);
|
434 |
-
cm.setCursor(0, 0);
|
435 |
-
//go to the last character on the first line
|
436 |
-
helpers.doKeys('$');
|
437 |
-
//move up/down on the column within the wrapped line
|
438 |
-
//side-effect: cursor is not locked to eol anymore
|
439 |
-
helpers.doKeys('g','k');
|
440 |
-
var cur=cm.getCursor();
|
441 |
-
eq(cur.line,0);
|
442 |
-
is((cur.ch<176),'gk didn\'t move cursor back (1)');
|
443 |
-
helpers.doKeys('g','j');
|
444 |
-
helpers.assertCursorAt(0, 176);
|
445 |
-
//should move to character 177 on line 2 (j/k preserve character index within line)
|
446 |
-
helpers.doKeys('j');
|
447 |
-
//due to different line wrapping, the cursor can be on a different screen-x now
|
448 |
-
//gj and gk preserve screen-x on movement, much like moveV
|
449 |
-
helpers.doKeys('3','g','k');
|
450 |
-
cur=cm.getCursor();
|
451 |
-
eq(cur.line,1);
|
452 |
-
is((cur.ch<176),'gk didn\'t move cursor back (2)');
|
453 |
-
helpers.doKeys('g','j','2','g','j');
|
454 |
-
//should return to the same character-index
|
455 |
-
helpers.doKeys('k');
|
456 |
-
helpers.assertCursorAt(0, 176);
|
457 |
-
},{ lineWrapping:true, value: 'This line is intentially long to test movement of gj and gk over wrapped lines. I will start on the end of this line, then make a step up and back to set the origin for j and k.\nThis line is supposed to be even longer than the previous. I will jump here and make another wiggle with gj and gk, before I jump back to the line above. Both wiggles should not change my cursor\'s target character but both j/k and gj/gk change each other\'s reference position.'});
|
458 |
-
testVim('gj_gk', function(cm, vim, helpers) {
|
459 |
-
if (phantom) return;
|
460 |
-
cm.setSize(120);
|
461 |
-
// Test top of document edge case.
|
462 |
-
cm.setCursor(0, 4);
|
463 |
-
helpers.doKeys('g', 'j');
|
464 |
-
helpers.doKeys('10', 'g', 'k');
|
465 |
-
helpers.assertCursorAt(0, 4);
|
466 |
-
|
467 |
-
// Test moving down preserves column position.
|
468 |
-
helpers.doKeys('g', 'j');
|
469 |
-
var pos1 = cm.getCursor();
|
470 |
-
var expectedPos2 = { line: 0, ch: (pos1.ch - 4) * 2 + 4};
|
471 |
-
helpers.doKeys('g', 'j');
|
472 |
-
helpers.assertCursorAt(expectedPos2);
|
473 |
-
|
474 |
-
// Move to the last character
|
475 |
-
cm.setCursor(0, 0);
|
476 |
-
// Move left to reset HSPos
|
477 |
-
helpers.doKeys('h');
|
478 |
-
// Test bottom of document edge case.
|
479 |
-
helpers.doKeys('100', 'g', 'j');
|
480 |
-
var endingPos = cm.getCursor();
|
481 |
-
is(endingPos != 0, 'gj should not be on wrapped line 0');
|
482 |
-
var topLeftCharCoords = cm.charCoords(makeCursor(0, 0));
|
483 |
-
var endingCharCoords = cm.charCoords(endingPos);
|
484 |
-
is(topLeftCharCoords.left == endingCharCoords.left, 'gj should end up on column 0');
|
485 |
-
},{ lineNumbers: false, lineWrapping:true, value: 'Thislineisintentiallylongtotestmovementofgjandgkoverwrappedlines.' });
|
486 |
-
testVim('}', function(cm, vim, helpers) {
|
487 |
-
cm.setCursor(0, 0);
|
488 |
-
helpers.doKeys('}');
|
489 |
-
helpers.assertCursorAt(1, 0);
|
490 |
-
cm.setCursor(0, 0);
|
491 |
-
helpers.doKeys('2', '}');
|
492 |
-
helpers.assertCursorAt(4, 0);
|
493 |
-
cm.setCursor(0, 0);
|
494 |
-
helpers.doKeys('6', '}');
|
495 |
-
helpers.assertCursorAt(5, 0);
|
496 |
-
}, { value: 'a\n\nb\nc\n\nd' });
|
497 |
-
testVim('{', function(cm, vim, helpers) {
|
498 |
-
cm.setCursor(5, 0);
|
499 |
-
helpers.doKeys('{');
|
500 |
-
helpers.assertCursorAt(4, 0);
|
501 |
-
cm.setCursor(5, 0);
|
502 |
-
helpers.doKeys('2', '{');
|
503 |
-
helpers.assertCursorAt(1, 0);
|
504 |
-
cm.setCursor(5, 0);
|
505 |
-
helpers.doKeys('6', '{');
|
506 |
-
helpers.assertCursorAt(0, 0);
|
507 |
-
}, { value: 'a\n\nb\nc\n\nd' });
|
508 |
-
testVim('paragraph_motions', function(cm, vim, helpers) {
|
509 |
-
cm.setCursor(10, 0);
|
510 |
-
helpers.doKeys('{');
|
511 |
-
helpers.assertCursorAt(4, 0);
|
512 |
-
helpers.doKeys('{');
|
513 |
-
helpers.assertCursorAt(0, 0);
|
514 |
-
helpers.doKeys('2', '}');
|
515 |
-
helpers.assertCursorAt(7, 0);
|
516 |
-
helpers.doKeys('2', '}');
|
517 |
-
helpers.assertCursorAt(16, 0);
|
518 |
-
|
519 |
-
cm.setCursor(9, 0);
|
520 |
-
helpers.doKeys('}');
|
521 |
-
helpers.assertCursorAt(14, 0);
|
522 |
-
|
523 |
-
cm.setCursor(6, 0);
|
524 |
-
helpers.doKeys('}');
|
525 |
-
helpers.assertCursorAt(7, 0);
|
526 |
-
|
527 |
-
// ip inside empty space
|
528 |
-
cm.setCursor(10, 0);
|
529 |
-
helpers.doKeys('v', 'i', 'p');
|
530 |
-
eqPos(Pos(7, 0), cm.getCursor('anchor'));
|
531 |
-
eqPos(Pos(12, 0), cm.getCursor('head'));
|
532 |
-
helpers.doKeys('i', 'p');
|
533 |
-
eqPos(Pos(7, 0), cm.getCursor('anchor'));
|
534 |
-
eqPos(Pos(13, 1), cm.getCursor('head'));
|
535 |
-
helpers.doKeys('2', 'i', 'p');
|
536 |
-
eqPos(Pos(7, 0), cm.getCursor('anchor'));
|
537 |
-
eqPos(Pos(16, 1), cm.getCursor('head'));
|
538 |
-
|
539 |
-
// should switch to visualLine mode
|
540 |
-
cm.setCursor(14, 0);
|
541 |
-
helpers.doKeys('<Esc>', 'v', 'i', 'p');
|
542 |
-
helpers.assertCursorAt(14, 0);
|
543 |
-
|
544 |
-
cm.setCursor(14, 0);
|
545 |
-
helpers.doKeys('<Esc>', 'V', 'i', 'p');
|
546 |
-
eqPos(Pos(16, 1), cm.getCursor('head'));
|
547 |
-
|
548 |
-
// ap inside empty space
|
549 |
-
cm.setCursor(10, 0);
|
550 |
-
helpers.doKeys('<Esc>', 'v', 'a', 'p');
|
551 |
-
eqPos(Pos(7, 0), cm.getCursor('anchor'));
|
552 |
-
eqPos(Pos(13, 1), cm.getCursor('head'));
|
553 |
-
helpers.doKeys('a', 'p');
|
554 |
-
eqPos(Pos(7, 0), cm.getCursor('anchor'));
|
555 |
-
eqPos(Pos(16, 1), cm.getCursor('head'));
|
556 |
-
|
557 |
-
cm.setCursor(13, 0);
|
558 |
-
helpers.doKeys('v', 'a', 'p');
|
559 |
-
eqPos(Pos(13, 0), cm.getCursor('anchor'));
|
560 |
-
eqPos(Pos(14, 0), cm.getCursor('head'));
|
561 |
-
|
562 |
-
cm.setCursor(16, 0);
|
563 |
-
helpers.doKeys('v', 'a', 'p');
|
564 |
-
eqPos(Pos(14, 0), cm.getCursor('anchor'));
|
565 |
-
eqPos(Pos(16, 1), cm.getCursor('head'));
|
566 |
-
|
567 |
-
cm.setCursor(0, 0);
|
568 |
-
helpers.doKeys('v', 'a', 'p');
|
569 |
-
eqPos(Pos(0, 0), cm.getCursor('anchor'));
|
570 |
-
eqPos(Pos(4, 0), cm.getCursor('head'));
|
571 |
-
|
572 |
-
cm.setCursor(0, 0);
|
573 |
-
helpers.doKeys('d', 'i', 'p');
|
574 |
-
var register = helpers.getRegisterController().getRegister();
|
575 |
-
eq('a\na\n', register.toString());
|
576 |
-
is(register.linewise);
|
577 |
-
helpers.doKeys('3', 'j', 'p');
|
578 |
-
helpers.doKeys('y', 'i', 'p');
|
579 |
-
is(register.linewise);
|
580 |
-
eq('b\na\na\nc\n', register.toString());
|
581 |
-
}, { value: 'a\na\n\n\n\nb\nc\n\n\n\n\n\n\nd\n\ne\nf' });
|
582 |
-
|
583 |
-
// Operator tests
|
584 |
-
testVim('dl', function(cm, vim, helpers) {
|
585 |
-
var curStart = makeCursor(0, 0);
|
586 |
-
cm.setCursor(curStart);
|
587 |
-
helpers.doKeys('d', 'l');
|
588 |
-
eq('word1 ', cm.getValue());
|
589 |
-
var register = helpers.getRegisterController().getRegister();
|
590 |
-
eq(' ', register.toString());
|
591 |
-
is(!register.linewise);
|
592 |
-
eqPos(curStart, cm.getCursor());
|
593 |
-
}, { value: ' word1 ' });
|
594 |
-
testVim('dl_eol', function(cm, vim, helpers) {
|
595 |
-
cm.setCursor(0, 6);
|
596 |
-
helpers.doKeys('d', 'l');
|
597 |
-
eq(' word1', cm.getValue());
|
598 |
-
var register = helpers.getRegisterController().getRegister();
|
599 |
-
eq(' ', register.toString());
|
600 |
-
is(!register.linewise);
|
601 |
-
helpers.assertCursorAt(0, 5);
|
602 |
-
}, { value: ' word1 ' });
|
603 |
-
testVim('dl_repeat', function(cm, vim, helpers) {
|
604 |
-
var curStart = makeCursor(0, 0);
|
605 |
-
cm.setCursor(curStart);
|
606 |
-
helpers.doKeys('2', 'd', 'l');
|
607 |
-
eq('ord1 ', cm.getValue());
|
608 |
-
var register = helpers.getRegisterController().getRegister();
|
609 |
-
eq(' w', register.toString());
|
610 |
-
is(!register.linewise);
|
611 |
-
eqPos(curStart, cm.getCursor());
|
612 |
-
}, { value: ' word1 ' });
|
613 |
-
testVim('dh', function(cm, vim, helpers) {
|
614 |
-
var curStart = makeCursor(0, 3);
|
615 |
-
cm.setCursor(curStart);
|
616 |
-
helpers.doKeys('d', 'h');
|
617 |
-
eq(' wrd1 ', cm.getValue());
|
618 |
-
var register = helpers.getRegisterController().getRegister();
|
619 |
-
eq('o', register.toString());
|
620 |
-
is(!register.linewise);
|
621 |
-
eqPos(offsetCursor(curStart, 0 , -1), cm.getCursor());
|
622 |
-
}, { value: ' word1 ' });
|
623 |
-
testVim('dj', function(cm, vim, helpers) {
|
624 |
-
var curStart = makeCursor(0, 3);
|
625 |
-
cm.setCursor(curStart);
|
626 |
-
helpers.doKeys('d', 'j');
|
627 |
-
eq(' word3', cm.getValue());
|
628 |
-
var register = helpers.getRegisterController().getRegister();
|
629 |
-
eq(' word1\nword2\n', register.toString());
|
630 |
-
is(register.linewise);
|
631 |
-
helpers.assertCursorAt(0, 1);
|
632 |
-
}, { value: ' word1\nword2\n word3' });
|
633 |
-
testVim('dj_end_of_document', function(cm, vim, helpers) {
|
634 |
-
var curStart = makeCursor(0, 3);
|
635 |
-
cm.setCursor(curStart);
|
636 |
-
helpers.doKeys('d', 'j');
|
637 |
-
eq(' word1 ', cm.getValue());
|
638 |
-
var register = helpers.getRegisterController().getRegister();
|
639 |
-
eq('', register.toString());
|
640 |
-
is(!register.linewise);
|
641 |
-
helpers.assertCursorAt(0, 3);
|
642 |
-
}, { value: ' word1 ' });
|
643 |
-
testVim('dk', function(cm, vim, helpers) {
|
644 |
-
var curStart = makeCursor(1, 3);
|
645 |
-
cm.setCursor(curStart);
|
646 |
-
helpers.doKeys('d', 'k');
|
647 |
-
eq(' word3', cm.getValue());
|
648 |
-
var register = helpers.getRegisterController().getRegister();
|
649 |
-
eq(' word1\nword2\n', register.toString());
|
650 |
-
is(register.linewise);
|
651 |
-
helpers.assertCursorAt(0, 1);
|
652 |
-
}, { value: ' word1\nword2\n word3' });
|
653 |
-
testVim('dk_start_of_document', function(cm, vim, helpers) {
|
654 |
-
var curStart = makeCursor(0, 3);
|
655 |
-
cm.setCursor(curStart);
|
656 |
-
helpers.doKeys('d', 'k');
|
657 |
-
eq(' word1 ', cm.getValue());
|
658 |
-
var register = helpers.getRegisterController().getRegister();
|
659 |
-
eq('', register.toString());
|
660 |
-
is(!register.linewise);
|
661 |
-
helpers.assertCursorAt(0, 3);
|
662 |
-
}, { value: ' word1 ' });
|
663 |
-
testVim('dw_space', function(cm, vim, helpers) {
|
664 |
-
var curStart = makeCursor(0, 0);
|
665 |
-
cm.setCursor(curStart);
|
666 |
-
helpers.doKeys('d', 'w');
|
667 |
-
eq('word1 ', cm.getValue());
|
668 |
-
var register = helpers.getRegisterController().getRegister();
|
669 |
-
eq(' ', register.toString());
|
670 |
-
is(!register.linewise);
|
671 |
-
eqPos(curStart, cm.getCursor());
|
672 |
-
}, { value: ' word1 ' });
|
673 |
-
testVim('dw_word', function(cm, vim, helpers) {
|
674 |
-
var curStart = makeCursor(0, 1);
|
675 |
-
cm.setCursor(curStart);
|
676 |
-
helpers.doKeys('d', 'w');
|
677 |
-
eq(' word2', cm.getValue());
|
678 |
-
var register = helpers.getRegisterController().getRegister();
|
679 |
-
eq('word1 ', register.toString());
|
680 |
-
is(!register.linewise);
|
681 |
-
eqPos(curStart, cm.getCursor());
|
682 |
-
}, { value: ' word1 word2' });
|
683 |
-
testVim('dw_unicode_word', function(cm, vim, helpers) {
|
684 |
-
helpers.doKeys('d', 'w');
|
685 |
-
eq(cm.getValue().length, 10);
|
686 |
-
helpers.doKeys('d', 'w');
|
687 |
-
eq(cm.getValue().length, 6);
|
688 |
-
helpers.doKeys('d', 'w');
|
689 |
-
eq(cm.getValue().length, 5);
|
690 |
-
helpers.doKeys('d', 'e');
|
691 |
-
eq(cm.getValue().length, 2);
|
692 |
-
}, { value: ' \u0562\u0561\u0580\u0587\xbbe\xb5g ' });
|
693 |
-
testVim('dw_only_word', function(cm, vim, helpers) {
|
694 |
-
// Test that if there is only 1 word left, dw deletes till the end of the
|
695 |
-
// line.
|
696 |
-
cm.setCursor(0, 1);
|
697 |
-
helpers.doKeys('d', 'w');
|
698 |
-
eq(' ', cm.getValue());
|
699 |
-
var register = helpers.getRegisterController().getRegister();
|
700 |
-
eq('word1 ', register.toString());
|
701 |
-
is(!register.linewise);
|
702 |
-
helpers.assertCursorAt(0, 0);
|
703 |
-
}, { value: ' word1 ' });
|
704 |
-
testVim('dw_eol', function(cm, vim, helpers) {
|
705 |
-
// Assert that dw does not delete the newline if last word to delete is at end
|
706 |
-
// of line.
|
707 |
-
cm.setCursor(0, 1);
|
708 |
-
helpers.doKeys('d', 'w');
|
709 |
-
eq(' \nword2', cm.getValue());
|
710 |
-
var register = helpers.getRegisterController().getRegister();
|
711 |
-
eq('word1', register.toString());
|
712 |
-
is(!register.linewise);
|
713 |
-
helpers.assertCursorAt(0, 0);
|
714 |
-
}, { value: ' word1\nword2' });
|
715 |
-
testVim('dw_eol_with_multiple_newlines', function(cm, vim, helpers) {
|
716 |
-
// Assert that dw does not delete the newline if last word to delete is at end
|
717 |
-
// of line and it is followed by multiple newlines.
|
718 |
-
cm.setCursor(0, 1);
|
719 |
-
helpers.doKeys('d', 'w');
|
720 |
-
eq(' \n\nword2', cm.getValue());
|
721 |
-
var register = helpers.getRegisterController().getRegister();
|
722 |
-
eq('word1', register.toString());
|
723 |
-
is(!register.linewise);
|
724 |
-
helpers.assertCursorAt(0, 0);
|
725 |
-
}, { value: ' word1\n\nword2' });
|
726 |
-
testVim('dw_empty_line_followed_by_whitespace', function(cm, vim, helpers) {
|
727 |
-
cm.setCursor(0, 0);
|
728 |
-
helpers.doKeys('d', 'w');
|
729 |
-
eq(' \nword', cm.getValue());
|
730 |
-
}, { value: '\n \nword' });
|
731 |
-
testVim('dw_empty_line_followed_by_word', function(cm, vim, helpers) {
|
732 |
-
cm.setCursor(0, 0);
|
733 |
-
helpers.doKeys('d', 'w');
|
734 |
-
eq('word', cm.getValue());
|
735 |
-
}, { value: '\nword' });
|
736 |
-
testVim('dw_empty_line_followed_by_empty_line', function(cm, vim, helpers) {
|
737 |
-
cm.setCursor(0, 0);
|
738 |
-
helpers.doKeys('d', 'w');
|
739 |
-
eq('\n', cm.getValue());
|
740 |
-
}, { value: '\n\n' });
|
741 |
-
testVim('dw_whitespace_followed_by_whitespace', function(cm, vim, helpers) {
|
742 |
-
cm.setCursor(0, 0);
|
743 |
-
helpers.doKeys('d', 'w');
|
744 |
-
eq('\n \n', cm.getValue());
|
745 |
-
}, { value: ' \n \n' });
|
746 |
-
testVim('dw_whitespace_followed_by_empty_line', function(cm, vim, helpers) {
|
747 |
-
cm.setCursor(0, 0);
|
748 |
-
helpers.doKeys('d', 'w');
|
749 |
-
eq('\n\n', cm.getValue());
|
750 |
-
}, { value: ' \n\n' });
|
751 |
-
testVim('dw_word_whitespace_word', function(cm, vim, helpers) {
|
752 |
-
cm.setCursor(0, 0);
|
753 |
-
helpers.doKeys('d', 'w');
|
754 |
-
eq('\n \nword2', cm.getValue());
|
755 |
-
}, { value: 'word1\n \nword2'})
|
756 |
-
testVim('dw_end_of_document', function(cm, vim, helpers) {
|
757 |
-
cm.setCursor(1, 2);
|
758 |
-
helpers.doKeys('d', 'w');
|
759 |
-
eq('\nab', cm.getValue());
|
760 |
-
}, { value: '\nabc' });
|
761 |
-
testVim('dw_repeat', function(cm, vim, helpers) {
|
762 |
-
// Assert that dw does delete newline if it should go to the next line, and
|
763 |
-
// that repeat works properly.
|
764 |
-
cm.setCursor(0, 1);
|
765 |
-
helpers.doKeys('d', '2', 'w');
|
766 |
-
eq(' ', cm.getValue());
|
767 |
-
var register = helpers.getRegisterController().getRegister();
|
768 |
-
eq('word1\nword2', register.toString());
|
769 |
-
is(!register.linewise);
|
770 |
-
helpers.assertCursorAt(0, 0);
|
771 |
-
}, { value: ' word1\nword2' });
|
772 |
-
testVim('de_word_start_and_empty_lines', function(cm, vim, helpers) {
|
773 |
-
cm.setCursor(0, 0);
|
774 |
-
helpers.doKeys('d', 'e');
|
775 |
-
eq('\n\n', cm.getValue());
|
776 |
-
}, { value: 'word\n\n' });
|
777 |
-
testVim('de_word_end_and_empty_lines', function(cm, vim, helpers) {
|
778 |
-
cm.setCursor(0, 3);
|
779 |
-
helpers.doKeys('d', 'e');
|
780 |
-
eq('wor', cm.getValue());
|
781 |
-
}, { value: 'word\n\n\n' });
|
782 |
-
testVim('de_whitespace_and_empty_lines', function(cm, vim, helpers) {
|
783 |
-
cm.setCursor(0, 0);
|
784 |
-
helpers.doKeys('d', 'e');
|
785 |
-
eq('', cm.getValue());
|
786 |
-
}, { value: ' \n\n\n' });
|
787 |
-
testVim('de_end_of_document', function(cm, vim, helpers) {
|
788 |
-
cm.setCursor(1, 2);
|
789 |
-
helpers.doKeys('d', 'e');
|
790 |
-
eq('\nab', cm.getValue());
|
791 |
-
}, { value: '\nabc' });
|
792 |
-
testVim('db_empty_lines', function(cm, vim, helpers) {
|
793 |
-
cm.setCursor(2, 0);
|
794 |
-
helpers.doKeys('d', 'b');
|
795 |
-
eq('\n\n', cm.getValue());
|
796 |
-
}, { value: '\n\n\n' });
|
797 |
-
testVim('db_word_start_and_empty_lines', function(cm, vim, helpers) {
|
798 |
-
cm.setCursor(2, 0);
|
799 |
-
helpers.doKeys('d', 'b');
|
800 |
-
eq('\nword', cm.getValue());
|
801 |
-
}, { value: '\n\nword' });
|
802 |
-
testVim('db_word_end_and_empty_lines', function(cm, vim, helpers) {
|
803 |
-
cm.setCursor(2, 3);
|
804 |
-
helpers.doKeys('d', 'b');
|
805 |
-
eq('\n\nd', cm.getValue());
|
806 |
-
}, { value: '\n\nword' });
|
807 |
-
testVim('db_whitespace_and_empty_lines', function(cm, vim, helpers) {
|
808 |
-
cm.setCursor(2, 0);
|
809 |
-
helpers.doKeys('d', 'b');
|
810 |
-
eq('', cm.getValue());
|
811 |
-
}, { value: '\n \n' });
|
812 |
-
testVim('db_start_of_document', function(cm, vim, helpers) {
|
813 |
-
cm.setCursor(0, 0);
|
814 |
-
helpers.doKeys('d', 'b');
|
815 |
-
eq('abc\n', cm.getValue());
|
816 |
-
}, { value: 'abc\n' });
|
817 |
-
testVim('dge_empty_lines', function(cm, vim, helpers) {
|
818 |
-
cm.setCursor(1, 0);
|
819 |
-
helpers.doKeys('d', 'g', 'e');
|
820 |
-
// Note: In real VIM the result should be '', but it's not quite consistent,
|
821 |
-
// since 2 newlines are deleted. But in the similar case of word\n\n, only
|
822 |
-
// 1 newline is deleted. We'll diverge from VIM's behavior since it's much
|
823 |
-
// easier this way.
|
824 |
-
eq('\n', cm.getValue());
|
825 |
-
}, { value: '\n\n' });
|
826 |
-
testVim('dge_word_and_empty_lines', function(cm, vim, helpers) {
|
827 |
-
cm.setCursor(1, 0);
|
828 |
-
helpers.doKeys('d', 'g', 'e');
|
829 |
-
eq('wor\n', cm.getValue());
|
830 |
-
}, { value: 'word\n\n'});
|
831 |
-
testVim('dge_whitespace_and_empty_lines', function(cm, vim, helpers) {
|
832 |
-
cm.setCursor(2, 0);
|
833 |
-
helpers.doKeys('d', 'g', 'e');
|
834 |
-
eq('', cm.getValue());
|
835 |
-
}, { value: '\n \n' });
|
836 |
-
testVim('dge_start_of_document', function(cm, vim, helpers) {
|
837 |
-
cm.setCursor(0, 0);
|
838 |
-
helpers.doKeys('d', 'g', 'e');
|
839 |
-
eq('bc\n', cm.getValue());
|
840 |
-
}, { value: 'abc\n' });
|
841 |
-
testVim('d_inclusive', function(cm, vim, helpers) {
|
842 |
-
// Assert that when inclusive is set, the character the cursor is on gets
|
843 |
-
// deleted too.
|
844 |
-
var curStart = makeCursor(0, 1);
|
845 |
-
cm.setCursor(curStart);
|
846 |
-
helpers.doKeys('d', 'e');
|
847 |
-
eq(' ', cm.getValue());
|
848 |
-
var register = helpers.getRegisterController().getRegister();
|
849 |
-
eq('word1', register.toString());
|
850 |
-
is(!register.linewise);
|
851 |
-
eqPos(curStart, cm.getCursor());
|
852 |
-
}, { value: ' word1 ' });
|
853 |
-
testVim('d_reverse', function(cm, vim, helpers) {
|
854 |
-
// Test that deleting in reverse works.
|
855 |
-
cm.setCursor(1, 0);
|
856 |
-
helpers.doKeys('d', 'b');
|
857 |
-
eq(' word2 ', cm.getValue());
|
858 |
-
var register = helpers.getRegisterController().getRegister();
|
859 |
-
eq('word1\n', register.toString());
|
860 |
-
is(!register.linewise);
|
861 |
-
helpers.assertCursorAt(0, 1);
|
862 |
-
}, { value: ' word1\nword2 ' });
|
863 |
-
testVim('dd', function(cm, vim, helpers) {
|
864 |
-
cm.setCursor(0, 3);
|
865 |
-
var expectedBuffer = cm.getRange({ line: 0, ch: 0 },
|
866 |
-
{ line: 1, ch: 0 });
|
867 |
-
var expectedLineCount = cm.lineCount() - 1;
|
868 |
-
helpers.doKeys('d', 'd');
|
869 |
-
eq(expectedLineCount, cm.lineCount());
|
870 |
-
var register = helpers.getRegisterController().getRegister();
|
871 |
-
eq(expectedBuffer, register.toString());
|
872 |
-
is(register.linewise);
|
873 |
-
helpers.assertCursorAt(0, lines[1].textStart);
|
874 |
-
});
|
875 |
-
testVim('dd_prefix_repeat', function(cm, vim, helpers) {
|
876 |
-
cm.setCursor(0, 3);
|
877 |
-
var expectedBuffer = cm.getRange({ line: 0, ch: 0 },
|
878 |
-
{ line: 2, ch: 0 });
|
879 |
-
var expectedLineCount = cm.lineCount() - 2;
|
880 |
-
helpers.doKeys('2', 'd', 'd');
|
881 |
-
eq(expectedLineCount, cm.lineCount());
|
882 |
-
var register = helpers.getRegisterController().getRegister();
|
883 |
-
eq(expectedBuffer, register.toString());
|
884 |
-
is(register.linewise);
|
885 |
-
helpers.assertCursorAt(0, lines[2].textStart);
|
886 |
-
});
|
887 |
-
testVim('dd_motion_repeat', function(cm, vim, helpers) {
|
888 |
-
cm.setCursor(0, 3);
|
889 |
-
var expectedBuffer = cm.getRange({ line: 0, ch: 0 },
|
890 |
-
{ line: 2, ch: 0 });
|
891 |
-
var expectedLineCount = cm.lineCount() - 2;
|
892 |
-
helpers.doKeys('d', '2', 'd');
|
893 |
-
eq(expectedLineCount, cm.lineCount());
|
894 |
-
var register = helpers.getRegisterController().getRegister();
|
895 |
-
eq(expectedBuffer, register.toString());
|
896 |
-
is(register.linewise);
|
897 |
-
helpers.assertCursorAt(0, lines[2].textStart);
|
898 |
-
});
|
899 |
-
testVim('dd_multiply_repeat', function(cm, vim, helpers) {
|
900 |
-
cm.setCursor(0, 3);
|
901 |
-
var expectedBuffer = cm.getRange({ line: 0, ch: 0 },
|
902 |
-
{ line: 6, ch: 0 });
|
903 |
-
var expectedLineCount = cm.lineCount() - 6;
|
904 |
-
helpers.doKeys('2', 'd', '3', 'd');
|
905 |
-
eq(expectedLineCount, cm.lineCount());
|
906 |
-
var register = helpers.getRegisterController().getRegister();
|
907 |
-
eq(expectedBuffer, register.toString());
|
908 |
-
is(register.linewise);
|
909 |
-
helpers.assertCursorAt(0, lines[6].textStart);
|
910 |
-
});
|
911 |
-
testVim('dd_lastline', function(cm, vim, helpers) {
|
912 |
-
cm.setCursor(cm.lineCount(), 0);
|
913 |
-
var expectedLineCount = cm.lineCount() - 1;
|
914 |
-
helpers.doKeys('d', 'd');
|
915 |
-
eq(expectedLineCount, cm.lineCount());
|
916 |
-
helpers.assertCursorAt(cm.lineCount() - 1, 0);
|
917 |
-
});
|
918 |
-
testVim('dd_only_line', function(cm, vim, helpers) {
|
919 |
-
cm.setCursor(0, 0);
|
920 |
-
var expectedRegister = cm.getValue() + "\n";
|
921 |
-
helpers.doKeys('d','d');
|
922 |
-
eq(1, cm.lineCount());
|
923 |
-
eq('', cm.getValue());
|
924 |
-
var register = helpers.getRegisterController().getRegister();
|
925 |
-
eq(expectedRegister, register.toString());
|
926 |
-
}, { value: "thisistheonlyline" });
|
927 |
-
// Yank commands should behave the exact same as d commands, expect that nothing
|
928 |
-
// gets deleted.
|
929 |
-
testVim('yw_repeat', function(cm, vim, helpers) {
|
930 |
-
// Assert that yw does yank newline if it should go to the next line, and
|
931 |
-
// that repeat works properly.
|
932 |
-
var curStart = makeCursor(0, 1);
|
933 |
-
cm.setCursor(curStart);
|
934 |
-
helpers.doKeys('y', '2', 'w');
|
935 |
-
eq(' word1\nword2', cm.getValue());
|
936 |
-
var register = helpers.getRegisterController().getRegister();
|
937 |
-
eq('word1\nword2', register.toString());
|
938 |
-
is(!register.linewise);
|
939 |
-
eqPos(curStart, cm.getCursor());
|
940 |
-
}, { value: ' word1\nword2' });
|
941 |
-
testVim('yy_multiply_repeat', function(cm, vim, helpers) {
|
942 |
-
var curStart = makeCursor(0, 3);
|
943 |
-
cm.setCursor(curStart);
|
944 |
-
var expectedBuffer = cm.getRange({ line: 0, ch: 0 },
|
945 |
-
{ line: 6, ch: 0 });
|
946 |
-
var expectedLineCount = cm.lineCount();
|
947 |
-
helpers.doKeys('2', 'y', '3', 'y');
|
948 |
-
eq(expectedLineCount, cm.lineCount());
|
949 |
-
var register = helpers.getRegisterController().getRegister();
|
950 |
-
eq(expectedBuffer, register.toString());
|
951 |
-
is(register.linewise);
|
952 |
-
eqPos(curStart, cm.getCursor());
|
953 |
-
});
|
954 |
-
// Change commands behave like d commands except that it also enters insert
|
955 |
-
// mode. In addition, when the change is linewise, an additional newline is
|
956 |
-
// inserted so that insert mode starts on that line.
|
957 |
-
testVim('cw', function(cm, vim, helpers) {
|
958 |
-
cm.setCursor(0, 0);
|
959 |
-
helpers.doKeys('c', '2', 'w');
|
960 |
-
eq(' word3', cm.getValue());
|
961 |
-
helpers.assertCursorAt(0, 0);
|
962 |
-
}, { value: 'word1 word2 word3'});
|
963 |
-
testVim('cw_repeat', function(cm, vim, helpers) {
|
964 |
-
// Assert that cw does delete newline if it should go to the next line, and
|
965 |
-
// that repeat works properly.
|
966 |
-
var curStart = makeCursor(0, 1);
|
967 |
-
cm.setCursor(curStart);
|
968 |
-
helpers.doKeys('c', '2', 'w');
|
969 |
-
eq(' ', cm.getValue());
|
970 |
-
var register = helpers.getRegisterController().getRegister();
|
971 |
-
eq('word1\nword2', register.toString());
|
972 |
-
is(!register.linewise);
|
973 |
-
eqPos(curStart, cm.getCursor());
|
974 |
-
eq('vim-insert', cm.getOption('keyMap'));
|
975 |
-
}, { value: ' word1\nword2' });
|
976 |
-
testVim('cc_multiply_repeat', function(cm, vim, helpers) {
|
977 |
-
cm.setCursor(0, 3);
|
978 |
-
var expectedBuffer = cm.getRange({ line: 0, ch: 0 },
|
979 |
-
{ line: 6, ch: 0 });
|
980 |
-
var expectedLineCount = cm.lineCount() - 5;
|
981 |
-
helpers.doKeys('2', 'c', '3', 'c');
|
982 |
-
eq(expectedLineCount, cm.lineCount());
|
983 |
-
var register = helpers.getRegisterController().getRegister();
|
984 |
-
eq(expectedBuffer, register.toString());
|
985 |
-
is(register.linewise);
|
986 |
-
eq('vim-insert', cm.getOption('keyMap'));
|
987 |
-
});
|
988 |
-
testVim('ct', function(cm, vim, helpers) {
|
989 |
-
cm.setCursor(0, 9);
|
990 |
-
helpers.doKeys('c', 't', 'w');
|
991 |
-
eq(' word1 word3', cm.getValue());
|
992 |
-
helpers.doKeys('<Esc>', 'c', '|');
|
993 |
-
eq(' word3', cm.getValue());
|
994 |
-
helpers.assertCursorAt(0, 0);
|
995 |
-
helpers.doKeys('<Esc>', '2', 'u', 'w', 'h');
|
996 |
-
helpers.doKeys('c', '2', 'g', 'e');
|
997 |
-
eq(' wordword3', cm.getValue());
|
998 |
-
}, { value: ' word1 word2 word3'});
|
999 |
-
testVim('cc_should_not_append_to_document', function(cm, vim, helpers) {
|
1000 |
-
var expectedLineCount = cm.lineCount();
|
1001 |
-
cm.setCursor(cm.lastLine(), 0);
|
1002 |
-
helpers.doKeys('c', 'c');
|
1003 |
-
eq(expectedLineCount, cm.lineCount());
|
1004 |
-
});
|
1005 |
-
function fillArray(val, times) {
|
1006 |
-
var arr = [];
|
1007 |
-
for (var i = 0; i < times; i++) {
|
1008 |
-
arr.push(val);
|
1009 |
-
}
|
1010 |
-
return arr;
|
1011 |
-
}
|
1012 |
-
testVim('c_visual_block', function(cm, vim, helpers) {
|
1013 |
-
cm.setCursor(0, 1);
|
1014 |
-
helpers.doKeys('<C-v>', '2', 'j', 'l', 'l', 'l', 'c');
|
1015 |
-
var replacement = fillArray('hello', 3);
|
1016 |
-
cm.replaceSelections(replacement);
|
1017 |
-
eq('1hello\n5hello\nahellofg', cm.getValue());
|
1018 |
-
helpers.doKeys('<Esc>');
|
1019 |
-
cm.setCursor(2, 3);
|
1020 |
-
helpers.doKeys('<C-v>', '2', 'k', 'h', 'C');
|
1021 |
-
replacement = fillArray('world', 3);
|
1022 |
-
cm.replaceSelections(replacement);
|
1023 |
-
eq('1hworld\n5hworld\nahworld', cm.getValue());
|
1024 |
-
}, {value: '1234\n5678\nabcdefg'});
|
1025 |
-
testVim('c_visual_block_replay', function(cm, vim, helpers) {
|
1026 |
-
cm.setCursor(0, 1);
|
1027 |
-
helpers.doKeys('<C-v>', '2', 'j', 'l', 'c');
|
1028 |
-
var replacement = fillArray('fo', 3);
|
1029 |
-
cm.replaceSelections(replacement);
|
1030 |
-
eq('1fo4\n5fo8\nafodefg', cm.getValue());
|
1031 |
-
helpers.doKeys('<Esc>');
|
1032 |
-
cm.setCursor(0, 0);
|
1033 |
-
helpers.doKeys('.');
|
1034 |
-
eq('foo4\nfoo8\nfoodefg', cm.getValue());
|
1035 |
-
}, {value: '1234\n5678\nabcdefg'});
|
1036 |
-
|
1037 |
-
testVim('d_visual_block', function(cm, vim, helpers) {
|
1038 |
-
cm.setCursor(0, 1);
|
1039 |
-
helpers.doKeys('<C-v>', '2', 'j', 'l', 'l', 'l', 'd');
|
1040 |
-
eq('1\n5\nafg', cm.getValue());
|
1041 |
-
}, {value: '1234\n5678\nabcdefg'});
|
1042 |
-
testVim('D_visual_block', function(cm, vim, helpers) {
|
1043 |
-
cm.setCursor(0, 1);
|
1044 |
-
helpers.doKeys('<C-v>', '2', 'j', 'l', 'D');
|
1045 |
-
eq('1\n5\na', cm.getValue());
|
1046 |
-
}, {value: '1234\n5678\nabcdefg'});
|
1047 |
-
|
1048 |
-
// Swapcase commands edit in place and do not modify registers.
|
1049 |
-
testVim('g~w_repeat', function(cm, vim, helpers) {
|
1050 |
-
// Assert that dw does delete newline if it should go to the next line, and
|
1051 |
-
// that repeat works properly.
|
1052 |
-
var curStart = makeCursor(0, 1);
|
1053 |
-
cm.setCursor(curStart);
|
1054 |
-
helpers.doKeys('g', '~', '2', 'w');
|
1055 |
-
eq(' WORD1\nWORD2', cm.getValue());
|
1056 |
-
var register = helpers.getRegisterController().getRegister();
|
1057 |
-
eq('', register.toString());
|
1058 |
-
is(!register.linewise);
|
1059 |
-
eqPos(curStart, cm.getCursor());
|
1060 |
-
}, { value: ' word1\nword2' });
|
1061 |
-
testVim('g~g~', function(cm, vim, helpers) {
|
1062 |
-
var curStart = makeCursor(0, 3);
|
1063 |
-
cm.setCursor(curStart);
|
1064 |
-
var expectedLineCount = cm.lineCount();
|
1065 |
-
var expectedValue = cm.getValue().toUpperCase();
|
1066 |
-
helpers.doKeys('2', 'g', '~', '3', 'g', '~');
|
1067 |
-
eq(expectedValue, cm.getValue());
|
1068 |
-
var register = helpers.getRegisterController().getRegister();
|
1069 |
-
eq('', register.toString());
|
1070 |
-
is(!register.linewise);
|
1071 |
-
eqPos(curStart, cm.getCursor());
|
1072 |
-
}, { value: ' word1\nword2\nword3\nword4\nword5\nword6' });
|
1073 |
-
testVim('gu_and_gU', function(cm, vim, helpers) {
|
1074 |
-
var curStart = makeCursor(0, 7);
|
1075 |
-
var value = cm.getValue();
|
1076 |
-
cm.setCursor(curStart);
|
1077 |
-
helpers.doKeys('2', 'g', 'U', 'w');
|
1078 |
-
eq(cm.getValue(), 'wa wb xX WC wd');
|
1079 |
-
eqPos(curStart, cm.getCursor());
|
1080 |
-
helpers.doKeys('2', 'g', 'u', 'w');
|
1081 |
-
eq(cm.getValue(), value);
|
1082 |
-
|
1083 |
-
helpers.doKeys('2', 'g', 'U', 'B');
|
1084 |
-
eq(cm.getValue(), 'wa WB Xx wc wd');
|
1085 |
-
eqPos(makeCursor(0, 3), cm.getCursor());
|
1086 |
-
|
1087 |
-
cm.setCursor(makeCursor(0, 4));
|
1088 |
-
helpers.doKeys('g', 'u', 'i', 'w');
|
1089 |
-
eq(cm.getValue(), 'wa wb Xx wc wd');
|
1090 |
-
eqPos(makeCursor(0, 3), cm.getCursor());
|
1091 |
-
|
1092 |
-
// TODO: support gUgU guu
|
1093 |
-
// eqPos(makeCursor(0, 0), cm.getCursor());
|
1094 |
-
|
1095 |
-
var register = helpers.getRegisterController().getRegister();
|
1096 |
-
eq('', register.toString());
|
1097 |
-
is(!register.linewise);
|
1098 |
-
}, { value: 'wa wb xx wc wd' });
|
1099 |
-
testVim('visual_block_~', function(cm, vim, helpers) {
|
1100 |
-
cm.setCursor(1, 1);
|
1101 |
-
helpers.doKeys('<C-v>', 'l', 'l', 'j', '~');
|
1102 |
-
helpers.assertCursorAt(1, 1);
|
1103 |
-
eq('hello\nwoRLd\naBCDe', cm.getValue());
|
1104 |
-
cm.setCursor(2, 0);
|
1105 |
-
helpers.doKeys('v', 'l', 'l', '~');
|
1106 |
-
helpers.assertCursorAt(2, 0);
|
1107 |
-
eq('hello\nwoRLd\nAbcDe', cm.getValue());
|
1108 |
-
},{value: 'hello\nwOrld\nabcde' });
|
1109 |
-
testVim('._swapCase_visualBlock', function(cm, vim, helpers) {
|
1110 |
-
helpers.doKeys('<C-v>', 'j', 'j', 'l', '~');
|
1111 |
-
cm.setCursor(0, 3);
|
1112 |
-
helpers.doKeys('.');
|
1113 |
-
eq('HelLO\nWorLd\nAbcdE', cm.getValue());
|
1114 |
-
},{value: 'hEllo\nwOrlD\naBcDe' });
|
1115 |
-
testVim('._delete_visualBlock', function(cm, vim, helpers) {
|
1116 |
-
helpers.doKeys('<C-v>', 'j', 'x');
|
1117 |
-
eq('ive\ne\nsome\nsugar', cm.getValue());
|
1118 |
-
helpers.doKeys('.');
|
1119 |
-
eq('ve\n\nsome\nsugar', cm.getValue());
|
1120 |
-
helpers.doKeys('j', 'j', '.');
|
1121 |
-
eq('ve\n\nome\nugar', cm.getValue());
|
1122 |
-
helpers.doKeys('u', '<C-r>', '.');
|
1123 |
-
eq('ve\n\nme\ngar', cm.getValue());
|
1124 |
-
},{value: 'give\nme\nsome\nsugar' });
|
1125 |
-
testVim('>{motion}', function(cm, vim, helpers) {
|
1126 |
-
cm.setCursor(1, 3);
|
1127 |
-
var expectedLineCount = cm.lineCount();
|
1128 |
-
var expectedValue = ' word1\n word2\nword3 ';
|
1129 |
-
helpers.doKeys('>', 'k');
|
1130 |
-
eq(expectedValue, cm.getValue());
|
1131 |
-
var register = helpers.getRegisterController().getRegister();
|
1132 |
-
eq('', register.toString());
|
1133 |
-
is(!register.linewise);
|
1134 |
-
helpers.assertCursorAt(0, 3);
|
1135 |
-
}, { value: ' word1\nword2\nword3 ', indentUnit: 2 });
|
1136 |
-
testVim('>>', function(cm, vim, helpers) {
|
1137 |
-
cm.setCursor(0, 3);
|
1138 |
-
var expectedLineCount = cm.lineCount();
|
1139 |
-
var expectedValue = ' word1\n word2\nword3 ';
|
1140 |
-
helpers.doKeys('2', '>', '>');
|
1141 |
-
eq(expectedValue, cm.getValue());
|
1142 |
-
var register = helpers.getRegisterController().getRegister();
|
1143 |
-
eq('', register.toString());
|
1144 |
-
is(!register.linewise);
|
1145 |
-
helpers.assertCursorAt(0, 3);
|
1146 |
-
}, { value: ' word1\nword2\nword3 ', indentUnit: 2 });
|
1147 |
-
testVim('<{motion}', function(cm, vim, helpers) {
|
1148 |
-
cm.setCursor(1, 3);
|
1149 |
-
var expectedLineCount = cm.lineCount();
|
1150 |
-
var expectedValue = ' word1\nword2\nword3 ';
|
1151 |
-
helpers.doKeys('<', 'k');
|
1152 |
-
eq(expectedValue, cm.getValue());
|
1153 |
-
var register = helpers.getRegisterController().getRegister();
|
1154 |
-
eq('', register.toString());
|
1155 |
-
is(!register.linewise);
|
1156 |
-
helpers.assertCursorAt(0, 1);
|
1157 |
-
}, { value: ' word1\n word2\nword3 ', indentUnit: 2 });
|
1158 |
-
testVim('<<', function(cm, vim, helpers) {
|
1159 |
-
cm.setCursor(0, 3);
|
1160 |
-
var expectedLineCount = cm.lineCount();
|
1161 |
-
var expectedValue = ' word1\nword2\nword3 ';
|
1162 |
-
helpers.doKeys('2', '<', '<');
|
1163 |
-
eq(expectedValue, cm.getValue());
|
1164 |
-
var register = helpers.getRegisterController().getRegister();
|
1165 |
-
eq('', register.toString());
|
1166 |
-
is(!register.linewise);
|
1167 |
-
helpers.assertCursorAt(0, 1);
|
1168 |
-
}, { value: ' word1\n word2\nword3 ', indentUnit: 2 });
|
1169 |
-
|
1170 |
-
// Edit tests
|
1171 |
-
function testEdit(name, before, pos, edit, after) {
|
1172 |
-
return testVim(name, function(cm, vim, helpers) {
|
1173 |
-
var ch = before.search(pos)
|
1174 |
-
var line = before.substring(0, ch).split('\n').length - 1;
|
1175 |
-
if (line) {
|
1176 |
-
ch = before.substring(0, ch).split('\n').pop().length;
|
1177 |
-
}
|
1178 |
-
cm.setCursor(line, ch);
|
1179 |
-
helpers.doKeys.apply(this, edit.split(''));
|
1180 |
-
eq(after, cm.getValue());
|
1181 |
-
}, {value: before});
|
1182 |
-
}
|
1183 |
-
|
1184 |
-
// These Delete tests effectively cover word-wise Change, Visual & Yank.
|
1185 |
-
// Tabs are used as differentiated whitespace to catch edge cases.
|
1186 |
-
// Normal word:
|
1187 |
-
testEdit('diw_mid_spc', 'foo \tbAr\t baz', /A/, 'diw', 'foo \t\t baz');
|
1188 |
-
testEdit('daw_mid_spc', 'foo \tbAr\t baz', /A/, 'daw', 'foo \tbaz');
|
1189 |
-
testEdit('diw_mid_punct', 'foo \tbAr.\t baz', /A/, 'diw', 'foo \t.\t baz');
|
1190 |
-
testEdit('daw_mid_punct', 'foo \tbAr.\t baz', /A/, 'daw', 'foo.\t baz');
|
1191 |
-
testEdit('diw_mid_punct2', 'foo \t,bAr.\t baz', /A/, 'diw', 'foo \t,.\t baz');
|
1192 |
-
testEdit('daw_mid_punct2', 'foo \t,bAr.\t baz', /A/, 'daw', 'foo \t,.\t baz');
|
1193 |
-
testEdit('diw_start_spc', 'bAr \tbaz', /A/, 'diw', ' \tbaz');
|
1194 |
-
testEdit('daw_start_spc', 'bAr \tbaz', /A/, 'daw', 'baz');
|
1195 |
-
testEdit('diw_start_punct', 'bAr. \tbaz', /A/, 'diw', '. \tbaz');
|
1196 |
-
testEdit('daw_start_punct', 'bAr. \tbaz', /A/, 'daw', '. \tbaz');
|
1197 |
-
testEdit('diw_end_spc', 'foo \tbAr', /A/, 'diw', 'foo \t');
|
1198 |
-
testEdit('daw_end_spc', 'foo \tbAr', /A/, 'daw', 'foo');
|
1199 |
-
testEdit('diw_end_punct', 'foo \tbAr.', /A/, 'diw', 'foo \t.');
|
1200 |
-
testEdit('daw_end_punct', 'foo \tbAr.', /A/, 'daw', 'foo.');
|
1201 |
-
// Big word:
|
1202 |
-
testEdit('diW_mid_spc', 'foo \tbAr\t baz', /A/, 'diW', 'foo \t\t baz');
|
1203 |
-
testEdit('daW_mid_spc', 'foo \tbAr\t baz', /A/, 'daW', 'foo \tbaz');
|
1204 |
-
testEdit('diW_mid_punct', 'foo \tbAr.\t baz', /A/, 'diW', 'foo \t\t baz');
|
1205 |
-
testEdit('daW_mid_punct', 'foo \tbAr.\t baz', /A/, 'daW', 'foo \tbaz');
|
1206 |
-
testEdit('diW_mid_punct2', 'foo \t,bAr.\t baz', /A/, 'diW', 'foo \t\t baz');
|
1207 |
-
testEdit('daW_mid_punct2', 'foo \t,bAr.\t baz', /A/, 'daW', 'foo \tbaz');
|
1208 |
-
testEdit('diW_start_spc', 'bAr\t baz', /A/, 'diW', '\t baz');
|
1209 |
-
testEdit('daW_start_spc', 'bAr\t baz', /A/, 'daW', 'baz');
|
1210 |
-
testEdit('diW_start_punct', 'bAr.\t baz', /A/, 'diW', '\t baz');
|
1211 |
-
testEdit('daW_start_punct', 'bAr.\t baz', /A/, 'daW', 'baz');
|
1212 |
-
testEdit('diW_end_spc', 'foo \tbAr', /A/, 'diW', 'foo \t');
|
1213 |
-
testEdit('daW_end_spc', 'foo \tbAr', /A/, 'daW', 'foo');
|
1214 |
-
testEdit('diW_end_punct', 'foo \tbAr.', /A/, 'diW', 'foo \t');
|
1215 |
-
testEdit('daW_end_punct', 'foo \tbAr.', /A/, 'daW', 'foo');
|
1216 |
-
// Deleting text objects
|
1217 |
-
// Open and close on same line
|
1218 |
-
testEdit('di(_open_spc', 'foo (bAr) baz', /\(/, 'di(', 'foo () baz');
|
1219 |
-
testEdit('di)_open_spc', 'foo (bAr) baz', /\(/, 'di)', 'foo () baz');
|
1220 |
-
testEdit('dib_open_spc', 'foo (bAr) baz', /\(/, 'dib', 'foo () baz');
|
1221 |
-
testEdit('da(_open_spc', 'foo (bAr) baz', /\(/, 'da(', 'foo baz');
|
1222 |
-
testEdit('da)_open_spc', 'foo (bAr) baz', /\(/, 'da)', 'foo baz');
|
1223 |
-
|
1224 |
-
testEdit('di(_middle_spc', 'foo (bAr) baz', /A/, 'di(', 'foo () baz');
|
1225 |
-
testEdit('di)_middle_spc', 'foo (bAr) baz', /A/, 'di)', 'foo () baz');
|
1226 |
-
testEdit('da(_middle_spc', 'foo (bAr) baz', /A/, 'da(', 'foo baz');
|
1227 |
-
testEdit('da)_middle_spc', 'foo (bAr) baz', /A/, 'da)', 'foo baz');
|
1228 |
-
|
1229 |
-
testEdit('di(_close_spc', 'foo (bAr) baz', /\)/, 'di(', 'foo () baz');
|
1230 |
-
testEdit('di)_close_spc', 'foo (bAr) baz', /\)/, 'di)', 'foo () baz');
|
1231 |
-
testEdit('da(_close_spc', 'foo (bAr) baz', /\)/, 'da(', 'foo baz');
|
1232 |
-
testEdit('da)_close_spc', 'foo (bAr) baz', /\)/, 'da)', 'foo baz');
|
1233 |
-
|
1234 |
-
// delete around and inner b.
|
1235 |
-
testEdit('dab_on_(_should_delete_around_()block', 'o( in(abc) )', /\(a/, 'dab', 'o( in )');
|
1236 |
-
|
1237 |
-
// delete around and inner B.
|
1238 |
-
testEdit('daB_on_{_should_delete_around_{}block', 'o{ in{abc} }', /{a/, 'daB', 'o{ in }');
|
1239 |
-
testEdit('diB_on_{_should_delete_inner_{}block', 'o{ in{abc} }', /{a/, 'diB', 'o{ in{} }');
|
1240 |
-
|
1241 |
-
testEdit('da{_on_{_should_delete_inner_block', 'o{ in{abc} }', /{a/, 'da{', 'o{ in }');
|
1242 |
-
testEdit('di[_on_(_should_not_delete', 'foo (bAr) baz', /\(/, 'di[', 'foo (bAr) baz');
|
1243 |
-
testEdit('di[_on_)_should_not_delete', 'foo (bAr) baz', /\)/, 'di[', 'foo (bAr) baz');
|
1244 |
-
testEdit('da[_on_(_should_not_delete', 'foo (bAr) baz', /\(/, 'da[', 'foo (bAr) baz');
|
1245 |
-
testEdit('da[_on_)_should_not_delete', 'foo (bAr) baz', /\)/, 'da[', 'foo (bAr) baz');
|
1246 |
-
testMotion('di(_outside_should_stay', ['d', 'i', '('], { line: 0, ch: 0}, { line: 0, ch: 0});
|
1247 |
-
|
1248 |
-
// Open and close on different lines, equally indented
|
1249 |
-
testEdit('di{_middle_spc', 'a{\n\tbar\n}b', /r/, 'di{', 'a{}b');
|
1250 |
-
testEdit('di}_middle_spc', 'a{\n\tbar\n}b', /r/, 'di}', 'a{}b');
|
1251 |
-
testEdit('da{_middle_spc', 'a{\n\tbar\n}b', /r/, 'da{', 'ab');
|
1252 |
-
testEdit('da}_middle_spc', 'a{\n\tbar\n}b', /r/, 'da}', 'ab');
|
1253 |
-
testEdit('daB_middle_spc', 'a{\n\tbar\n}b', /r/, 'daB', 'ab');
|
1254 |
-
|
1255 |
-
// open and close on diff lines, open indented less than close
|
1256 |
-
testEdit('di{_middle_spc', 'a{\n\tbar\n\t}b', /r/, 'di{', 'a{}b');
|
1257 |
-
testEdit('di}_middle_spc', 'a{\n\tbar\n\t}b', /r/, 'di}', 'a{}b');
|
1258 |
-
testEdit('da{_middle_spc', 'a{\n\tbar\n\t}b', /r/, 'da{', 'ab');
|
1259 |
-
testEdit('da}_middle_spc', 'a{\n\tbar\n\t}b', /r/, 'da}', 'ab');
|
1260 |
-
|
1261 |
-
// open and close on diff lines, open indented more than close
|
1262 |
-
testEdit('di[_middle_spc', 'a\t[\n\tbar\n]b', /r/, 'di[', 'a\t[]b');
|
1263 |
-
testEdit('di]_middle_spc', 'a\t[\n\tbar\n]b', /r/, 'di]', 'a\t[]b');
|
1264 |
-
testEdit('da[_middle_spc', 'a\t[\n\tbar\n]b', /r/, 'da[', 'a\tb');
|
1265 |
-
testEdit('da]_middle_spc', 'a\t[\n\tbar\n]b', /r/, 'da]', 'a\tb');
|
1266 |
-
|
1267 |
-
function testSelection(name, before, pos, keys, sel) {
|
1268 |
-
return testVim(name, function(cm, vim, helpers) {
|
1269 |
-
var ch = before.search(pos)
|
1270 |
-
var line = before.substring(0, ch).split('\n').length - 1;
|
1271 |
-
if (line) {
|
1272 |
-
ch = before.substring(0, ch).split('\n').pop().length;
|
1273 |
-
}
|
1274 |
-
cm.setCursor(line, ch);
|
1275 |
-
helpers.doKeys.apply(this, keys.split(''));
|
1276 |
-
eq(sel, cm.getSelection());
|
1277 |
-
}, {value: before});
|
1278 |
-
}
|
1279 |
-
testSelection('viw_middle_spc', 'foo \tbAr\t baz', /A/, 'viw', 'bAr');
|
1280 |
-
testSelection('vaw_middle_spc', 'foo \tbAr\t baz', /A/, 'vaw', 'bAr\t ');
|
1281 |
-
testSelection('viw_middle_punct', 'foo \tbAr,\t baz', /A/, 'viw', 'bAr');
|
1282 |
-
testSelection('vaW_middle_punct', 'foo \tbAr,\t baz', /A/, 'vaW', 'bAr,\t ');
|
1283 |
-
testSelection('viw_start_spc', 'foo \tbAr\t baz', /b/, 'viw', 'bAr');
|
1284 |
-
testSelection('viw_end_spc', 'foo \tbAr\t baz', /r/, 'viw', 'bAr');
|
1285 |
-
testSelection('viw_eol', 'foo \tbAr', /r/, 'viw', 'bAr');
|
1286 |
-
testSelection('vi{_middle_spc', 'a{\n\tbar\n\t}b', /r/, 'vi{', '\n\tbar\n\t');
|
1287 |
-
testSelection('va{_middle_spc', 'a{\n\tbar\n\t}b', /r/, 'va{', '{\n\tbar\n\t}');
|
1288 |
-
|
1289 |
-
testVim('mouse_select', function(cm, vim, helpers) {
|
1290 |
-
cm.setSelection(Pos(0, 2), Pos(0, 4), {origin: '*mouse'});
|
1291 |
-
is(cm.state.vim.visualMode);
|
1292 |
-
is(!cm.state.vim.visualLine);
|
1293 |
-
is(!cm.state.vim.visualBlock);
|
1294 |
-
helpers.doKeys('<Esc>');
|
1295 |
-
is(!cm.somethingSelected());
|
1296 |
-
helpers.doKeys('g', 'v');
|
1297 |
-
eq('cd', cm.getSelection());
|
1298 |
-
}, {value: 'abcdef'});
|
1299 |
-
|
1300 |
-
// Operator-motion tests
|
1301 |
-
testVim('D', function(cm, vim, helpers) {
|
1302 |
-
cm.setCursor(0, 3);
|
1303 |
-
helpers.doKeys('D');
|
1304 |
-
eq(' wo\nword2\n word3', cm.getValue());
|
1305 |
-
var register = helpers.getRegisterController().getRegister();
|
1306 |
-
eq('rd1', register.toString());
|
1307 |
-
is(!register.linewise);
|
1308 |
-
helpers.assertCursorAt(0, 2);
|
1309 |
-
}, { value: ' word1\nword2\n word3' });
|
1310 |
-
testVim('C', function(cm, vim, helpers) {
|
1311 |
-
var curStart = makeCursor(0, 3);
|
1312 |
-
cm.setCursor(curStart);
|
1313 |
-
helpers.doKeys('C');
|
1314 |
-
eq(' wo\nword2\n word3', cm.getValue());
|
1315 |
-
var register = helpers.getRegisterController().getRegister();
|
1316 |
-
eq('rd1', register.toString());
|
1317 |
-
is(!register.linewise);
|
1318 |
-
eqPos(curStart, cm.getCursor());
|
1319 |
-
eq('vim-insert', cm.getOption('keyMap'));
|
1320 |
-
}, { value: ' word1\nword2\n word3' });
|
1321 |
-
testVim('Y', function(cm, vim, helpers) {
|
1322 |
-
var curStart = makeCursor(0, 3);
|
1323 |
-
cm.setCursor(curStart);
|
1324 |
-
helpers.doKeys('Y');
|
1325 |
-
eq(' word1\nword2\n word3', cm.getValue());
|
1326 |
-
var register = helpers.getRegisterController().getRegister();
|
1327 |
-
eq('rd1', register.toString());
|
1328 |
-
is(!register.linewise);
|
1329 |
-
helpers.assertCursorAt(0, 3);
|
1330 |
-
}, { value: ' word1\nword2\n word3' });
|
1331 |
-
testVim('~', function(cm, vim, helpers) {
|
1332 |
-
helpers.doKeys('3', '~');
|
1333 |
-
eq('ABCdefg', cm.getValue());
|
1334 |
-
helpers.assertCursorAt(0, 3);
|
1335 |
-
}, { value: 'abcdefg' });
|
1336 |
-
|
1337 |
-
// Action tests
|
1338 |
-
testVim('ctrl-a', function(cm, vim, helpers) {
|
1339 |
-
cm.setCursor(0, 0);
|
1340 |
-
helpers.doKeys('<C-a>');
|
1341 |
-
eq('-9', cm.getValue());
|
1342 |
-
helpers.assertCursorAt(0, 1);
|
1343 |
-
helpers.doKeys('2','<C-a>');
|
1344 |
-
eq('-7', cm.getValue());
|
1345 |
-
}, {value: '-10'});
|
1346 |
-
testVim('ctrl-x', function(cm, vim, helpers) {
|
1347 |
-
cm.setCursor(0, 0);
|
1348 |
-
helpers.doKeys('<C-x>');
|
1349 |
-
eq('-1', cm.getValue());
|
1350 |
-
helpers.assertCursorAt(0, 1);
|
1351 |
-
helpers.doKeys('2','<C-x>');
|
1352 |
-
eq('-3', cm.getValue());
|
1353 |
-
}, {value: '0'});
|
1354 |
-
testVim('<C-x>/<C-a> search forward', function(cm, vim, helpers) {
|
1355 |
-
forEach(['<C-x>', '<C-a>'], function(key) {
|
1356 |
-
cm.setCursor(0, 0);
|
1357 |
-
helpers.doKeys(key);
|
1358 |
-
helpers.assertCursorAt(0, 5);
|
1359 |
-
helpers.doKeys('l');
|
1360 |
-
helpers.doKeys(key);
|
1361 |
-
helpers.assertCursorAt(0, 10);
|
1362 |
-
cm.setCursor(0, 11);
|
1363 |
-
helpers.doKeys(key);
|
1364 |
-
helpers.assertCursorAt(0, 11);
|
1365 |
-
});
|
1366 |
-
}, {value: '__jmp1 jmp2 jmp'});
|
1367 |
-
testVim('a', function(cm, vim, helpers) {
|
1368 |
-
cm.setCursor(0, 1);
|
1369 |
-
helpers.doKeys('a');
|
1370 |
-
helpers.assertCursorAt(0, 2);
|
1371 |
-
eq('vim-insert', cm.getOption('keyMap'));
|
1372 |
-
});
|
1373 |
-
testVim('a_eol', function(cm, vim, helpers) {
|
1374 |
-
cm.setCursor(0, lines[0].length - 1);
|
1375 |
-
helpers.doKeys('a');
|
1376 |
-
helpers.assertCursorAt(0, lines[0].length);
|
1377 |
-
eq('vim-insert', cm.getOption('keyMap'));
|
1378 |
-
});
|
1379 |
-
testVim('A_endOfSelectedArea', function(cm, vim, helpers) {
|
1380 |
-
cm.setCursor(0, 0);
|
1381 |
-
helpers.doKeys('v', 'j', 'l');
|
1382 |
-
helpers.doKeys('A');
|
1383 |
-
helpers.assertCursorAt(1, 2);
|
1384 |
-
eq('vim-insert', cm.getOption('keyMap'));
|
1385 |
-
}, {value: 'foo\nbar'});
|
1386 |
-
testVim('i', function(cm, vim, helpers) {
|
1387 |
-
cm.setCursor(0, 1);
|
1388 |
-
helpers.doKeys('i');
|
1389 |
-
helpers.assertCursorAt(0, 1);
|
1390 |
-
eq('vim-insert', cm.getOption('keyMap'));
|
1391 |
-
});
|
1392 |
-
testVim('i_repeat', function(cm, vim, helpers) {
|
1393 |
-
helpers.doKeys('3', 'i');
|
1394 |
-
cm.replaceRange('test', cm.getCursor());
|
1395 |
-
helpers.doKeys('<Esc>');
|
1396 |
-
eq('testtesttest', cm.getValue());
|
1397 |
-
helpers.assertCursorAt(0, 11);
|
1398 |
-
}, { value: '' });
|
1399 |
-
testVim('i_repeat_delete', function(cm, vim, helpers) {
|
1400 |
-
cm.setCursor(0, 4);
|
1401 |
-
helpers.doKeys('2', 'i');
|
1402 |
-
cm.replaceRange('z', cm.getCursor());
|
1403 |
-
helpers.doInsertModeKeys('Backspace', 'Backspace');
|
1404 |
-
helpers.doKeys('<Esc>');
|
1405 |
-
eq('abe', cm.getValue());
|
1406 |
-
helpers.assertCursorAt(0, 1);
|
1407 |
-
}, { value: 'abcde' });
|
1408 |
-
testVim('A', function(cm, vim, helpers) {
|
1409 |
-
helpers.doKeys('A');
|
1410 |
-
helpers.assertCursorAt(0, lines[0].length);
|
1411 |
-
eq('vim-insert', cm.getOption('keyMap'));
|
1412 |
-
});
|
1413 |
-
testVim('A_visual_block', function(cm, vim, helpers) {
|
1414 |
-
cm.setCursor(0, 1);
|
1415 |
-
helpers.doKeys('<C-v>', '2', 'j', 'l', 'l', 'A');
|
1416 |
-
var replacement = new Array(cm.listSelections().length+1).join('hello ').split(' ');
|
1417 |
-
replacement.pop();
|
1418 |
-
cm.replaceSelections(replacement);
|
1419 |
-
eq('testhello\nmehello\npleahellose', cm.getValue());
|
1420 |
-
helpers.doKeys('<Esc>');
|
1421 |
-
cm.setCursor(0, 0);
|
1422 |
-
helpers.doKeys('.');
|
1423 |
-
// TODO this doesn't work yet
|
1424 |
-
// eq('teshellothello\nme hello hello\nplehelloahellose', cm.getValue());
|
1425 |
-
}, {value: 'test\nme\nplease'});
|
1426 |
-
testVim('I', function(cm, vim, helpers) {
|
1427 |
-
cm.setCursor(0, 4);
|
1428 |
-
helpers.doKeys('I');
|
1429 |
-
helpers.assertCursorAt(0, lines[0].textStart);
|
1430 |
-
eq('vim-insert', cm.getOption('keyMap'));
|
1431 |
-
});
|
1432 |
-
testVim('I_repeat', function(cm, vim, helpers) {
|
1433 |
-
cm.setCursor(0, 1);
|
1434 |
-
helpers.doKeys('3', 'I');
|
1435 |
-
cm.replaceRange('test', cm.getCursor());
|
1436 |
-
helpers.doKeys('<Esc>');
|
1437 |
-
eq('testtesttestblah', cm.getValue());
|
1438 |
-
helpers.assertCursorAt(0, 11);
|
1439 |
-
}, { value: 'blah' });
|
1440 |
-
testVim('I_visual_block', function(cm, vim, helpers) {
|
1441 |
-
cm.setCursor(0, 0);
|
1442 |
-
helpers.doKeys('<C-v>', '2', 'j', 'l', 'l', 'I');
|
1443 |
-
var replacement = new Array(cm.listSelections().length+1).join('hello ').split(' ');
|
1444 |
-
replacement.pop();
|
1445 |
-
cm.replaceSelections(replacement);
|
1446 |
-
eq('hellotest\nhellome\nhelloplease', cm.getValue());
|
1447 |
-
}, {value: 'test\nme\nplease'});
|
1448 |
-
testVim('o', function(cm, vim, helpers) {
|
1449 |
-
cm.setCursor(0, 4);
|
1450 |
-
helpers.doKeys('o');
|
1451 |
-
eq('word1\n\nword2', cm.getValue());
|
1452 |
-
helpers.assertCursorAt(1, 0);
|
1453 |
-
eq('vim-insert', cm.getOption('keyMap'));
|
1454 |
-
}, { value: 'word1\nword2' });
|
1455 |
-
testVim('o_repeat', function(cm, vim, helpers) {
|
1456 |
-
cm.setCursor(0, 0);
|
1457 |
-
helpers.doKeys('3', 'o');
|
1458 |
-
cm.replaceRange('test', cm.getCursor());
|
1459 |
-
helpers.doKeys('<Esc>');
|
1460 |
-
eq('\ntest\ntest\ntest', cm.getValue());
|
1461 |
-
helpers.assertCursorAt(3, 3);
|
1462 |
-
}, { value: '' });
|
1463 |
-
testVim('O', function(cm, vim, helpers) {
|
1464 |
-
cm.setCursor(0, 4);
|
1465 |
-
helpers.doKeys('O');
|
1466 |
-
eq('\nword1\nword2', cm.getValue());
|
1467 |
-
helpers.assertCursorAt(0, 0);
|
1468 |
-
eq('vim-insert', cm.getOption('keyMap'));
|
1469 |
-
}, { value: 'word1\nword2' });
|
1470 |
-
testVim('J', function(cm, vim, helpers) {
|
1471 |
-
cm.setCursor(0, 4);
|
1472 |
-
helpers.doKeys('J');
|
1473 |
-
var expectedValue = 'word1 word2\nword3\n word4';
|
1474 |
-
eq(expectedValue, cm.getValue());
|
1475 |
-
helpers.assertCursorAt(0, expectedValue.indexOf('word2') - 1);
|
1476 |
-
}, { value: 'word1 \n word2\nword3\n word4' });
|
1477 |
-
testVim('J_repeat', function(cm, vim, helpers) {
|
1478 |
-
cm.setCursor(0, 4);
|
1479 |
-
helpers.doKeys('3', 'J');
|
1480 |
-
var expectedValue = 'word1 word2 word3\n word4';
|
1481 |
-
eq(expectedValue, cm.getValue());
|
1482 |
-
helpers.assertCursorAt(0, expectedValue.indexOf('word3') - 1);
|
1483 |
-
}, { value: 'word1 \n word2\nword3\n word4' });
|
1484 |
-
testVim('p', function(cm, vim, helpers) {
|
1485 |
-
cm.setCursor(0, 1);
|
1486 |
-
helpers.getRegisterController().pushText('"', 'yank', 'abc\ndef', false);
|
1487 |
-
helpers.doKeys('p');
|
1488 |
-
eq('__abc\ndef_', cm.getValue());
|
1489 |
-
helpers.assertCursorAt(1, 2);
|
1490 |
-
}, { value: '___' });
|
1491 |
-
testVim('p_register', function(cm, vim, helpers) {
|
1492 |
-
cm.setCursor(0, 1);
|
1493 |
-
helpers.getRegisterController().getRegister('a').setText('abc\ndef', false);
|
1494 |
-
helpers.doKeys('"', 'a', 'p');
|
1495 |
-
eq('__abc\ndef_', cm.getValue());
|
1496 |
-
helpers.assertCursorAt(1, 2);
|
1497 |
-
}, { value: '___' });
|
1498 |
-
testVim('p_wrong_register', function(cm, vim, helpers) {
|
1499 |
-
cm.setCursor(0, 1);
|
1500 |
-
helpers.getRegisterController().getRegister('a').setText('abc\ndef', false);
|
1501 |
-
helpers.doKeys('p');
|
1502 |
-
eq('___', cm.getValue());
|
1503 |
-
helpers.assertCursorAt(0, 1);
|
1504 |
-
}, { value: '___' });
|
1505 |
-
testVim('p_line', function(cm, vim, helpers) {
|
1506 |
-
cm.setCursor(0, 1);
|
1507 |
-
helpers.getRegisterController().pushText('"', 'yank', ' a\nd\n', true);
|
1508 |
-
helpers.doKeys('2', 'p');
|
1509 |
-
eq('___\n a\nd\n a\nd', cm.getValue());
|
1510 |
-
helpers.assertCursorAt(1, 2);
|
1511 |
-
}, { value: '___' });
|
1512 |
-
testVim('p_lastline', function(cm, vim, helpers) {
|
1513 |
-
cm.setCursor(0, 1);
|
1514 |
-
helpers.getRegisterController().pushText('"', 'yank', ' a\nd', true);
|
1515 |
-
helpers.doKeys('2', 'p');
|
1516 |
-
eq('___\n a\nd\n a\nd', cm.getValue());
|
1517 |
-
helpers.assertCursorAt(1, 2);
|
1518 |
-
}, { value: '___' });
|
1519 |
-
testVim(']p_first_indent_is_smaller', function(cm, vim, helpers) {
|
1520 |
-
helpers.getRegisterController().pushText('"', 'yank', ' abc\n def\n', true);
|
1521 |
-
helpers.doKeys(']', 'p');
|
1522 |
-
eq(' ___\n abc\n def', cm.getValue());
|
1523 |
-
}, { value: ' ___' });
|
1524 |
-
testVim(']p_first_indent_is_larger', function(cm, vim, helpers) {
|
1525 |
-
helpers.getRegisterController().pushText('"', 'yank', ' abc\n def\n', true);
|
1526 |
-
helpers.doKeys(']', 'p');
|
1527 |
-
eq(' ___\n abc\ndef', cm.getValue());
|
1528 |
-
}, { value: ' ___' });
|
1529 |
-
testVim(']p_with_tab_indents', function(cm, vim, helpers) {
|
1530 |
-
helpers.getRegisterController().pushText('"', 'yank', '\t\tabc\n\t\t\tdef\n', true);
|
1531 |
-
helpers.doKeys(']', 'p');
|
1532 |
-
eq('\t___\n\tabc\n\t\tdef', cm.getValue());
|
1533 |
-
}, { value: '\t___', indentWithTabs: true});
|
1534 |
-
testVim(']p_with_spaces_translated_to_tabs', function(cm, vim, helpers) {
|
1535 |
-
helpers.getRegisterController().pushText('"', 'yank', ' abc\n def\n', true);
|
1536 |
-
helpers.doKeys(']', 'p');
|
1537 |
-
eq('\t___\n\tabc\n\t\tdef', cm.getValue());
|
1538 |
-
}, { value: '\t___', indentWithTabs: true, tabSize: 2 });
|
1539 |
-
testVim('[p', function(cm, vim, helpers) {
|
1540 |
-
helpers.getRegisterController().pushText('"', 'yank', ' abc\n def\n', true);
|
1541 |
-
helpers.doKeys('[', 'p');
|
1542 |
-
eq(' abc\n def\n ___', cm.getValue());
|
1543 |
-
}, { value: ' ___' });
|
1544 |
-
testVim('P', function(cm, vim, helpers) {
|
1545 |
-
cm.setCursor(0, 1);
|
1546 |
-
helpers.getRegisterController().pushText('"', 'yank', 'abc\ndef', false);
|
1547 |
-
helpers.doKeys('P');
|
1548 |
-
eq('_abc\ndef__', cm.getValue());
|
1549 |
-
helpers.assertCursorAt(1, 3);
|
1550 |
-
}, { value: '___' });
|
1551 |
-
testVim('P_line', function(cm, vim, helpers) {
|
1552 |
-
cm.setCursor(0, 1);
|
1553 |
-
helpers.getRegisterController().pushText('"', 'yank', ' a\nd\n', true);
|
1554 |
-
helpers.doKeys('2', 'P');
|
1555 |
-
eq(' a\nd\n a\nd\n___', cm.getValue());
|
1556 |
-
helpers.assertCursorAt(0, 2);
|
1557 |
-
}, { value: '___' });
|
1558 |
-
testVim('r', function(cm, vim, helpers) {
|
1559 |
-
cm.setCursor(0, 1);
|
1560 |
-
helpers.doKeys('3', 'r', 'u');
|
1561 |
-
eq('wuuuet\nanother', cm.getValue(),'3r failed');
|
1562 |
-
helpers.assertCursorAt(0, 3);
|
1563 |
-
cm.setCursor(0, 4);
|
1564 |
-
helpers.doKeys('v', 'j', 'h', 'r', '<Space>');
|
1565 |
-
eq('wuuu \n her', cm.getValue(),'Replacing selection by space-characters failed');
|
1566 |
-
}, { value: 'wordet\nanother' });
|
1567 |
-
testVim('r_visual_block', function(cm, vim, helpers) {
|
1568 |
-
cm.setCursor(2, 3);
|
1569 |
-
helpers.doKeys('<C-v>', 'k', 'k', 'h', 'h', 'r', 'l');
|
1570 |
-
eq('1lll\n5lll\nalllefg', cm.getValue());
|
1571 |
-
helpers.doKeys('<C-v>', 'l', 'j', 'r', '<Space>');
|
1572 |
-
eq('1 l\n5 l\nalllefg', cm.getValue());
|
1573 |
-
cm.setCursor(2, 0);
|
1574 |
-
helpers.doKeys('o');
|
1575 |
-
helpers.doKeys('<Esc>');
|
1576 |
-
cm.replaceRange('\t\t', cm.getCursor());
|
1577 |
-
helpers.doKeys('<C-v>', 'h', 'h', 'r', 'r');
|
1578 |
-
eq('1 l\n5 l\nalllefg\nrrrrrrrr', cm.getValue());
|
1579 |
-
}, {value: '1234\n5678\nabcdefg'});
|
1580 |
-
testVim('R', function(cm, vim, helpers) {
|
1581 |
-
cm.setCursor(0, 1);
|
1582 |
-
helpers.doKeys('R');
|
1583 |
-
helpers.assertCursorAt(0, 1);
|
1584 |
-
eq('vim-replace', cm.getOption('keyMap'));
|
1585 |
-
is(cm.state.overwrite, 'Setting overwrite state failed');
|
1586 |
-
});
|
1587 |
-
testVim('mark', function(cm, vim, helpers) {
|
1588 |
-
cm.setCursor(2, 2);
|
1589 |
-
helpers.doKeys('m', 't');
|
1590 |
-
cm.setCursor(0, 0);
|
1591 |
-
helpers.doKeys('`', 't');
|
1592 |
-
helpers.assertCursorAt(2, 2);
|
1593 |
-
cm.setCursor(2, 0);
|
1594 |
-
cm.replaceRange(' h', cm.getCursor());
|
1595 |
-
cm.setCursor(0, 0);
|
1596 |
-
helpers.doKeys('\'', 't');
|
1597 |
-
helpers.assertCursorAt(2, 3);
|
1598 |
-
});
|
1599 |
-
testVim('jumpToMark_next', function(cm, vim, helpers) {
|
1600 |
-
cm.setCursor(2, 2);
|
1601 |
-
helpers.doKeys('m', 't');
|
1602 |
-
cm.setCursor(0, 0);
|
1603 |
-
helpers.doKeys(']', '`');
|
1604 |
-
helpers.assertCursorAt(2, 2);
|
1605 |
-
cm.setCursor(0, 0);
|
1606 |
-
helpers.doKeys(']', '\'');
|
1607 |
-
helpers.assertCursorAt(2, 0);
|
1608 |
-
});
|
1609 |
-
testVim('jumpToMark_next_repeat', function(cm, vim, helpers) {
|
1610 |
-
cm.setCursor(2, 2);
|
1611 |
-
helpers.doKeys('m', 'a');
|
1612 |
-
cm.setCursor(3, 2);
|
1613 |
-
helpers.doKeys('m', 'b');
|
1614 |
-
cm.setCursor(4, 2);
|
1615 |
-
helpers.doKeys('m', 'c');
|
1616 |
-
cm.setCursor(0, 0);
|
1617 |
-
helpers.doKeys('2', ']', '`');
|
1618 |
-
helpers.assertCursorAt(3, 2);
|
1619 |
-
cm.setCursor(0, 0);
|
1620 |
-
helpers.doKeys('2', ']', '\'');
|
1621 |
-
helpers.assertCursorAt(3, 1);
|
1622 |
-
});
|
1623 |
-
testVim('jumpToMark_next_sameline', function(cm, vim, helpers) {
|
1624 |
-
cm.setCursor(2, 0);
|
1625 |
-
helpers.doKeys('m', 'a');
|
1626 |
-
cm.setCursor(2, 4);
|
1627 |
-
helpers.doKeys('m', 'b');
|
1628 |
-
cm.setCursor(2, 2);
|
1629 |
-
helpers.doKeys(']', '`');
|
1630 |
-
helpers.assertCursorAt(2, 4);
|
1631 |
-
});
|
1632 |
-
testVim('jumpToMark_next_onlyprev', function(cm, vim, helpers) {
|
1633 |
-
cm.setCursor(2, 0);
|
1634 |
-
helpers.doKeys('m', 'a');
|
1635 |
-
cm.setCursor(4, 0);
|
1636 |
-
helpers.doKeys(']', '`');
|
1637 |
-
helpers.assertCursorAt(4, 0);
|
1638 |
-
});
|
1639 |
-
testVim('jumpToMark_next_nomark', function(cm, vim, helpers) {
|
1640 |
-
cm.setCursor(2, 2);
|
1641 |
-
helpers.doKeys(']', '`');
|
1642 |
-
helpers.assertCursorAt(2, 2);
|
1643 |
-
helpers.doKeys(']', '\'');
|
1644 |
-
helpers.assertCursorAt(2, 0);
|
1645 |
-
});
|
1646 |
-
testVim('jumpToMark_next_linewise_over', function(cm, vim, helpers) {
|
1647 |
-
cm.setCursor(2, 2);
|
1648 |
-
helpers.doKeys('m', 'a');
|
1649 |
-
cm.setCursor(3, 4);
|
1650 |
-
helpers.doKeys('m', 'b');
|
1651 |
-
cm.setCursor(2, 1);
|
1652 |
-
helpers.doKeys(']', '\'');
|
1653 |
-
helpers.assertCursorAt(3, 1);
|
1654 |
-
});
|
1655 |
-
testVim('jumpToMark_next_action', function(cm, vim, helpers) {
|
1656 |
-
cm.setCursor(2, 2);
|
1657 |
-
helpers.doKeys('m', 't');
|
1658 |
-
cm.setCursor(0, 0);
|
1659 |
-
helpers.doKeys('d', ']', '`');
|
1660 |
-
helpers.assertCursorAt(0, 0);
|
1661 |
-
var actual = cm.getLine(0);
|
1662 |
-
var expected = 'pop pop 0 1 2 3 4';
|
1663 |
-
eq(actual, expected, "Deleting while jumping to the next mark failed.");
|
1664 |
-
});
|
1665 |
-
testVim('jumpToMark_next_line_action', function(cm, vim, helpers) {
|
1666 |
-
cm.setCursor(2, 2);
|
1667 |
-
helpers.doKeys('m', 't');
|
1668 |
-
cm.setCursor(0, 0);
|
1669 |
-
helpers.doKeys('d', ']', '\'');
|
1670 |
-
helpers.assertCursorAt(0, 1);
|
1671 |
-
var actual = cm.getLine(0);
|
1672 |
-
var expected = ' (a) [b] {c} '
|
1673 |
-
eq(actual, expected, "Deleting while jumping to the next mark line failed.");
|
1674 |
-
});
|
1675 |
-
testVim('jumpToMark_prev', function(cm, vim, helpers) {
|
1676 |
-
cm.setCursor(2, 2);
|
1677 |
-
helpers.doKeys('m', 't');
|
1678 |
-
cm.setCursor(4, 0);
|
1679 |
-
helpers.doKeys('[', '`');
|
1680 |
-
helpers.assertCursorAt(2, 2);
|
1681 |
-
cm.setCursor(4, 0);
|
1682 |
-
helpers.doKeys('[', '\'');
|
1683 |
-
helpers.assertCursorAt(2, 0);
|
1684 |
-
});
|
1685 |
-
testVim('jumpToMark_prev_repeat', function(cm, vim, helpers) {
|
1686 |
-
cm.setCursor(2, 2);
|
1687 |
-
helpers.doKeys('m', 'a');
|
1688 |
-
cm.setCursor(3, 2);
|
1689 |
-
helpers.doKeys('m', 'b');
|
1690 |
-
cm.setCursor(4, 2);
|
1691 |
-
helpers.doKeys('m', 'c');
|
1692 |
-
cm.setCursor(5, 0);
|
1693 |
-
helpers.doKeys('2', '[', '`');
|
1694 |
-
helpers.assertCursorAt(3, 2);
|
1695 |
-
cm.setCursor(5, 0);
|
1696 |
-
helpers.doKeys('2', '[', '\'');
|
1697 |
-
helpers.assertCursorAt(3, 1);
|
1698 |
-
});
|
1699 |
-
testVim('jumpToMark_prev_sameline', function(cm, vim, helpers) {
|
1700 |
-
cm.setCursor(2, 0);
|
1701 |
-
helpers.doKeys('m', 'a');
|
1702 |
-
cm.setCursor(2, 4);
|
1703 |
-
helpers.doKeys('m', 'b');
|
1704 |
-
cm.setCursor(2, 2);
|
1705 |
-
helpers.doKeys('[', '`');
|
1706 |
-
helpers.assertCursorAt(2, 0);
|
1707 |
-
});
|
1708 |
-
testVim('jumpToMark_prev_onlynext', function(cm, vim, helpers) {
|
1709 |
-
cm.setCursor(4, 4);
|
1710 |
-
helpers.doKeys('m', 'a');
|
1711 |
-
cm.setCursor(2, 0);
|
1712 |
-
helpers.doKeys('[', '`');
|
1713 |
-
helpers.assertCursorAt(2, 0);
|
1714 |
-
});
|
1715 |
-
testVim('jumpToMark_prev_nomark', function(cm, vim, helpers) {
|
1716 |
-
cm.setCursor(2, 2);
|
1717 |
-
helpers.doKeys('[', '`');
|
1718 |
-
helpers.assertCursorAt(2, 2);
|
1719 |
-
helpers.doKeys('[', '\'');
|
1720 |
-
helpers.assertCursorAt(2, 0);
|
1721 |
-
});
|
1722 |
-
testVim('jumpToMark_prev_linewise_over', function(cm, vim, helpers) {
|
1723 |
-
cm.setCursor(2, 2);
|
1724 |
-
helpers.doKeys('m', 'a');
|
1725 |
-
cm.setCursor(3, 4);
|
1726 |
-
helpers.doKeys('m', 'b');
|
1727 |
-
cm.setCursor(3, 6);
|
1728 |
-
helpers.doKeys('[', '\'');
|
1729 |
-
helpers.assertCursorAt(2, 0);
|
1730 |
-
});
|
1731 |
-
testVim('delmark_single', function(cm, vim, helpers) {
|
1732 |
-
cm.setCursor(1, 2);
|
1733 |
-
helpers.doKeys('m', 't');
|
1734 |
-
helpers.doEx('delmarks t');
|
1735 |
-
cm.setCursor(0, 0);
|
1736 |
-
helpers.doKeys('`', 't');
|
1737 |
-
helpers.assertCursorAt(0, 0);
|
1738 |
-
});
|
1739 |
-
testVim('delmark_range', function(cm, vim, helpers) {
|
1740 |
-
cm.setCursor(1, 2);
|
1741 |
-
helpers.doKeys('m', 'a');
|
1742 |
-
cm.setCursor(2, 2);
|
1743 |
-
helpers.doKeys('m', 'b');
|
1744 |
-
cm.setCursor(3, 2);
|
1745 |
-
helpers.doKeys('m', 'c');
|
1746 |
-
cm.setCursor(4, 2);
|
1747 |
-
helpers.doKeys('m', 'd');
|
1748 |
-
cm.setCursor(5, 2);
|
1749 |
-
helpers.doKeys('m', 'e');
|
1750 |
-
helpers.doEx('delmarks b-d');
|
1751 |
-
cm.setCursor(0, 0);
|
1752 |
-
helpers.doKeys('`', 'a');
|
1753 |
-
helpers.assertCursorAt(1, 2);
|
1754 |
-
helpers.doKeys('`', 'b');
|
1755 |
-
helpers.assertCursorAt(1, 2);
|
1756 |
-
helpers.doKeys('`', 'c');
|
1757 |
-
helpers.assertCursorAt(1, 2);
|
1758 |
-
helpers.doKeys('`', 'd');
|
1759 |
-
helpers.assertCursorAt(1, 2);
|
1760 |
-
helpers.doKeys('`', 'e');
|
1761 |
-
helpers.assertCursorAt(5, 2);
|
1762 |
-
});
|
1763 |
-
testVim('delmark_multi', function(cm, vim, helpers) {
|
1764 |
-
cm.setCursor(1, 2);
|
1765 |
-
helpers.doKeys('m', 'a');
|
1766 |
-
cm.setCursor(2, 2);
|
1767 |
-
helpers.doKeys('m', 'b');
|
1768 |
-
cm.setCursor(3, 2);
|
1769 |
-
helpers.doKeys('m', 'c');
|
1770 |
-
cm.setCursor(4, 2);
|
1771 |
-
helpers.doKeys('m', 'd');
|
1772 |
-
cm.setCursor(5, 2);
|
1773 |
-
helpers.doKeys('m', 'e');
|
1774 |
-
helpers.doEx('delmarks bcd');
|
1775 |
-
cm.setCursor(0, 0);
|
1776 |
-
helpers.doKeys('`', 'a');
|
1777 |
-
helpers.assertCursorAt(1, 2);
|
1778 |
-
helpers.doKeys('`', 'b');
|
1779 |
-
helpers.assertCursorAt(1, 2);
|
1780 |
-
helpers.doKeys('`', 'c');
|
1781 |
-
helpers.assertCursorAt(1, 2);
|
1782 |
-
helpers.doKeys('`', 'd');
|
1783 |
-
helpers.assertCursorAt(1, 2);
|
1784 |
-
helpers.doKeys('`', 'e');
|
1785 |
-
helpers.assertCursorAt(5, 2);
|
1786 |
-
});
|
1787 |
-
testVim('delmark_multi_space', function(cm, vim, helpers) {
|
1788 |
-
cm.setCursor(1, 2);
|
1789 |
-
helpers.doKeys('m', 'a');
|
1790 |
-
cm.setCursor(2, 2);
|
1791 |
-
helpers.doKeys('m', 'b');
|
1792 |
-
cm.setCursor(3, 2);
|
1793 |
-
helpers.doKeys('m', 'c');
|
1794 |
-
cm.setCursor(4, 2);
|
1795 |
-
helpers.doKeys('m', 'd');
|
1796 |
-
cm.setCursor(5, 2);
|
1797 |
-
helpers.doKeys('m', 'e');
|
1798 |
-
helpers.doEx('delmarks b c d');
|
1799 |
-
cm.setCursor(0, 0);
|
1800 |
-
helpers.doKeys('`', 'a');
|
1801 |
-
helpers.assertCursorAt(1, 2);
|
1802 |
-
helpers.doKeys('`', 'b');
|
1803 |
-
helpers.assertCursorAt(1, 2);
|
1804 |
-
helpers.doKeys('`', 'c');
|
1805 |
-
helpers.assertCursorAt(1, 2);
|
1806 |
-
helpers.doKeys('`', 'd');
|
1807 |
-
helpers.assertCursorAt(1, 2);
|
1808 |
-
helpers.doKeys('`', 'e');
|
1809 |
-
helpers.assertCursorAt(5, 2);
|
1810 |
-
});
|
1811 |
-
testVim('delmark_all', function(cm, vim, helpers) {
|
1812 |
-
cm.setCursor(1, 2);
|
1813 |
-
helpers.doKeys('m', 'a');
|
1814 |
-
cm.setCursor(2, 2);
|
1815 |
-
helpers.doKeys('m', 'b');
|
1816 |
-
cm.setCursor(3, 2);
|
1817 |
-
helpers.doKeys('m', 'c');
|
1818 |
-
cm.setCursor(4, 2);
|
1819 |
-
helpers.doKeys('m', 'd');
|
1820 |
-
cm.setCursor(5, 2);
|
1821 |
-
helpers.doKeys('m', 'e');
|
1822 |
-
helpers.doEx('delmarks a b-de');
|
1823 |
-
cm.setCursor(0, 0);
|
1824 |
-
helpers.doKeys('`', 'a');
|
1825 |
-
helpers.assertCursorAt(0, 0);
|
1826 |
-
helpers.doKeys('`', 'b');
|
1827 |
-
helpers.assertCursorAt(0, 0);
|
1828 |
-
helpers.doKeys('`', 'c');
|
1829 |
-
helpers.assertCursorAt(0, 0);
|
1830 |
-
helpers.doKeys('`', 'd');
|
1831 |
-
helpers.assertCursorAt(0, 0);
|
1832 |
-
helpers.doKeys('`', 'e');
|
1833 |
-
helpers.assertCursorAt(0, 0);
|
1834 |
-
});
|
1835 |
-
testVim('visual', function(cm, vim, helpers) {
|
1836 |
-
helpers.doKeys('l', 'v', 'l', 'l');
|
1837 |
-
helpers.assertCursorAt(0, 4);
|
1838 |
-
eqPos(makeCursor(0, 1), cm.getCursor('anchor'));
|
1839 |
-
helpers.doKeys('d');
|
1840 |
-
eq('15', cm.getValue());
|
1841 |
-
}, { value: '12345' });
|
1842 |
-
testVim('visual_yank', function(cm, vim, helpers) {
|
1843 |
-
helpers.doKeys('v', '3', 'l', 'y');
|
1844 |
-
helpers.assertCursorAt(0, 0);
|
1845 |
-
helpers.doKeys('p');
|
1846 |
-
eq('aa te test for yank', cm.getValue());
|
1847 |
-
}, { value: 'a test for yank' })
|
1848 |
-
testVim('visual_w', function(cm, vim, helpers) {
|
1849 |
-
helpers.doKeys('v', 'w');
|
1850 |
-
eq(cm.getSelection(), 'motion t');
|
1851 |
-
}, { value: 'motion test'});
|
1852 |
-
testVim('visual_initial_selection', function(cm, vim, helpers) {
|
1853 |
-
cm.setCursor(0, 1);
|
1854 |
-
helpers.doKeys('v');
|
1855 |
-
cm.getSelection('n');
|
1856 |
-
}, { value: 'init'});
|
1857 |
-
testVim('visual_crossover_left', function(cm, vim, helpers) {
|
1858 |
-
cm.setCursor(0, 2);
|
1859 |
-
helpers.doKeys('v', 'l', 'h', 'h');
|
1860 |
-
cm.getSelection('ro');
|
1861 |
-
}, { value: 'cross'});
|
1862 |
-
testVim('visual_crossover_left', function(cm, vim, helpers) {
|
1863 |
-
cm.setCursor(0, 2);
|
1864 |
-
helpers.doKeys('v', 'h', 'l', 'l');
|
1865 |
-
cm.getSelection('os');
|
1866 |
-
}, { value: 'cross'});
|
1867 |
-
testVim('visual_crossover_up', function(cm, vim, helpers) {
|
1868 |
-
cm.setCursor(3, 2);
|
1869 |
-
helpers.doKeys('v', 'j', 'k', 'k');
|
1870 |
-
eqPos(Pos(2, 2), cm.getCursor('head'));
|
1871 |
-
eqPos(Pos(3, 3), cm.getCursor('anchor'));
|
1872 |
-
helpers.doKeys('k');
|
1873 |
-
eqPos(Pos(1, 2), cm.getCursor('head'));
|
1874 |
-
eqPos(Pos(3, 3), cm.getCursor('anchor'));
|
1875 |
-
}, { value: 'cross\ncross\ncross\ncross\ncross\n'});
|
1876 |
-
testVim('visual_crossover_down', function(cm, vim, helpers) {
|
1877 |
-
cm.setCursor(1, 2);
|
1878 |
-
helpers.doKeys('v', 'k', 'j', 'j');
|
1879 |
-
eqPos(Pos(2, 3), cm.getCursor('head'));
|
1880 |
-
eqPos(Pos(1, 2), cm.getCursor('anchor'));
|
1881 |
-
helpers.doKeys('j');
|
1882 |
-
eqPos(Pos(3, 3), cm.getCursor('head'));
|
1883 |
-
eqPos(Pos(1, 2), cm.getCursor('anchor'));
|
1884 |
-
}, { value: 'cross\ncross\ncross\ncross\ncross\n'});
|
1885 |
-
testVim('visual_exit', function(cm, vim, helpers) {
|
1886 |
-
helpers.doKeys('<C-v>', 'l', 'j', 'j', '<Esc>');
|
1887 |
-
eqPos(cm.getCursor('anchor'), cm.getCursor('head'));
|
1888 |
-
eq(vim.visualMode, false);
|
1889 |
-
}, { value: 'hello\nworld\nfoo' });
|
1890 |
-
testVim('visual_line', function(cm, vim, helpers) {
|
1891 |
-
helpers.doKeys('l', 'V', 'l', 'j', 'j', 'd');
|
1892 |
-
eq(' 4\n 5', cm.getValue());
|
1893 |
-
}, { value: ' 1\n 2\n 3\n 4\n 5' });
|
1894 |
-
testVim('visual_block_move_to_eol', function(cm, vim, helpers) {
|
1895 |
-
// moveToEol should move all block cursors to end of line
|
1896 |
-
cm.setCursor(0, 0);
|
1897 |
-
helpers.doKeys('<C-v>', 'G', '$');
|
1898 |
-
var selections = cm.getSelections().join();
|
1899 |
-
eq('123,45,6', selections);
|
1900 |
-
// Checks that with cursor at Infinity, finding words backwards still works.
|
1901 |
-
helpers.doKeys('2', 'k', 'b');
|
1902 |
-
selections = cm.getSelections().join();
|
1903 |
-
eq('1', selections);
|
1904 |
-
}, {value: '123\n45\n6'});
|
1905 |
-
testVim('visual_block_different_line_lengths', function(cm, vim, helpers) {
|
1906 |
-
// test the block selection with lines of different length
|
1907 |
-
// i.e. extending the selection
|
1908 |
-
// till the end of the longest line.
|
1909 |
-
helpers.doKeys('<C-v>', 'l', 'j', 'j', '6', 'l', 'd');
|
1910 |
-
helpers.doKeys('d', 'd', 'd', 'd');
|
1911 |
-
eq('', cm.getValue());
|
1912 |
-
}, {value: '1234\n5678\nabcdefg'});
|
1913 |
-
testVim('visual_block_truncate_on_short_line', function(cm, vim, helpers) {
|
1914 |
-
// check for left side selection in case
|
1915 |
-
// of moving up to a shorter line.
|
1916 |
-
cm.replaceRange('', cm.getCursor());
|
1917 |
-
cm.setCursor(3, 4);
|
1918 |
-
helpers.doKeys('<C-v>', 'l', 'k', 'k', 'd');
|
1919 |
-
eq('hello world\n{\ntis\nsa!', cm.getValue());
|
1920 |
-
}, {value: 'hello world\n{\nthis is\nsparta!'});
|
1921 |
-
testVim('visual_block_corners', function(cm, vim, helpers) {
|
1922 |
-
cm.setCursor(1, 2);
|
1923 |
-
helpers.doKeys('<C-v>', '2', 'l', 'k');
|
1924 |
-
// circle around the anchor
|
1925 |
-
// and check the selections
|
1926 |
-
var selections = cm.getSelections();
|
1927 |
-
eq('345891', selections.join(''));
|
1928 |
-
helpers.doKeys('4', 'h');
|
1929 |
-
selections = cm.getSelections();
|
1930 |
-
eq('123678', selections.join(''));
|
1931 |
-
helpers.doKeys('j', 'j');
|
1932 |
-
selections = cm.getSelections();
|
1933 |
-
eq('678abc', selections.join(''));
|
1934 |
-
helpers.doKeys('4', 'l');
|
1935 |
-
selections = cm.getSelections();
|
1936 |
-
eq('891cde', selections.join(''));
|
1937 |
-
}, {value: '12345\n67891\nabcde'});
|
1938 |
-
testVim('visual_block_mode_switch', function(cm, vim, helpers) {
|
1939 |
-
// switch between visual modes
|
1940 |
-
cm.setCursor(1, 1);
|
1941 |
-
// blockwise to characterwise visual
|
1942 |
-
helpers.doKeys('<C-v>', 'j', 'l', 'v');
|
1943 |
-
selections = cm.getSelections();
|
1944 |
-
eq('7891\nabc', selections.join(''));
|
1945 |
-
// characterwise to blockwise
|
1946 |
-
helpers.doKeys('<C-v>');
|
1947 |
-
selections = cm.getSelections();
|
1948 |
-
eq('78bc', selections.join(''));
|
1949 |
-
// blockwise to linewise visual
|
1950 |
-
helpers.doKeys('V');
|
1951 |
-
selections = cm.getSelections();
|
1952 |
-
eq('67891\nabcde', selections.join(''));
|
1953 |
-
}, {value: '12345\n67891\nabcde'});
|
1954 |
-
testVim('visual_block_crossing_short_line', function(cm, vim, helpers) {
|
1955 |
-
// visual block with long and short lines
|
1956 |
-
cm.setCursor(0, 3);
|
1957 |
-
helpers.doKeys('<C-v>', 'j', 'j', 'j');
|
1958 |
-
var selections = cm.getSelections().join();
|
1959 |
-
eq('4,,d,b', selections);
|
1960 |
-
helpers.doKeys('3', 'k');
|
1961 |
-
selections = cm.getSelections().join();
|
1962 |
-
eq('4', selections);
|
1963 |
-
helpers.doKeys('5', 'j', 'k');
|
1964 |
-
selections = cm.getSelections().join("");
|
1965 |
-
eq(10, selections.length);
|
1966 |
-
}, {value: '123456\n78\nabcdefg\nfoobar\n}\n'});
|
1967 |
-
testVim('visual_block_curPos_on_exit', function(cm, vim, helpers) {
|
1968 |
-
cm.setCursor(0, 0);
|
1969 |
-
helpers.doKeys('<C-v>', '3' , 'l', '<Esc>');
|
1970 |
-
eqPos(makeCursor(0, 3), cm.getCursor());
|
1971 |
-
helpers.doKeys('h', '<C-v>', '2' , 'j' ,'3' , 'l');
|
1972 |
-
eq(cm.getSelections().join(), "3456,,cdef");
|
1973 |
-
helpers.doKeys('4' , 'h');
|
1974 |
-
eq(cm.getSelections().join(), "23,8,bc");
|
1975 |
-
helpers.doKeys('2' , 'l');
|
1976 |
-
eq(cm.getSelections().join(), "34,,cd");
|
1977 |
-
}, {value: '123456\n78\nabcdefg\nfoobar'});
|
1978 |
-
|
1979 |
-
testVim('visual_marks', function(cm, vim, helpers) {
|
1980 |
-
helpers.doKeys('l', 'v', 'l', 'l', 'j', 'j', 'v');
|
1981 |
-
// Test visual mode marks
|
1982 |
-
cm.setCursor(2, 1);
|
1983 |
-
helpers.doKeys('\'', '<');
|
1984 |
-
helpers.assertCursorAt(0, 1);
|
1985 |
-
helpers.doKeys('\'', '>');
|
1986 |
-
helpers.assertCursorAt(2, 0);
|
1987 |
-
});
|
1988 |
-
testVim('visual_join', function(cm, vim, helpers) {
|
1989 |
-
helpers.doKeys('l', 'V', 'l', 'j', 'j', 'J');
|
1990 |
-
eq(' 1 2 3\n 4\n 5', cm.getValue());
|
1991 |
-
is(!vim.visualMode);
|
1992 |
-
}, { value: ' 1\n 2\n 3\n 4\n 5' });
|
1993 |
-
testVim('visual_join_2', function(cm, vim, helpers) {
|
1994 |
-
helpers.doKeys('G', 'V', 'g', 'g', 'J');
|
1995 |
-
eq('1 2 3 4 5 6 ', cm.getValue());
|
1996 |
-
is(!vim.visualMode);
|
1997 |
-
}, { value: '1\n2\n3\n4\n5\n6\n'});
|
1998 |
-
testVim('visual_blank', function(cm, vim, helpers) {
|
1999 |
-
helpers.doKeys('v', 'k');
|
2000 |
-
eq(vim.visualMode, true);
|
2001 |
-
}, { value: '\n' });
|
2002 |
-
testVim('reselect_visual', function(cm, vim, helpers) {
|
2003 |
-
helpers.doKeys('l', 'v', 'l', 'l', 'l', 'y', 'g', 'v');
|
2004 |
-
helpers.assertCursorAt(0, 5);
|
2005 |
-
eqPos(makeCursor(0, 1), cm.getCursor('anchor'));
|
2006 |
-
helpers.doKeys('v');
|
2007 |
-
cm.setCursor(1, 0);
|
2008 |
-
helpers.doKeys('v', 'l', 'l', 'p');
|
2009 |
-
eq('123456\n2345\nbar', cm.getValue());
|
2010 |
-
cm.setCursor(0, 0);
|
2011 |
-
helpers.doKeys('g', 'v');
|
2012 |
-
// here the fake cursor is at (1, 3)
|
2013 |
-
helpers.assertCursorAt(1, 4);
|
2014 |
-
eqPos(makeCursor(1, 0), cm.getCursor('anchor'));
|
2015 |
-
helpers.doKeys('v');
|
2016 |
-
cm.setCursor(2, 0);
|
2017 |
-
helpers.doKeys('v', 'l', 'l', 'g', 'v');
|
2018 |
-
helpers.assertCursorAt(1, 4);
|
2019 |
-
eqPos(makeCursor(1, 0), cm.getCursor('anchor'));
|
2020 |
-
helpers.doKeys('g', 'v');
|
2021 |
-
helpers.assertCursorAt(2, 3);
|
2022 |
-
eqPos(makeCursor(2, 0), cm.getCursor('anchor'));
|
2023 |
-
eq('123456\n2345\nbar', cm.getValue());
|
2024 |
-
}, { value: '123456\nfoo\nbar' });
|
2025 |
-
testVim('reselect_visual_line', function(cm, vim, helpers) {
|
2026 |
-
helpers.doKeys('l', 'V', 'j', 'j', 'V', 'g', 'v', 'd');
|
2027 |
-
eq('foo\nand\nbar', cm.getValue());
|
2028 |
-
cm.setCursor(1, 0);
|
2029 |
-
helpers.doKeys('V', 'y', 'j');
|
2030 |
-
helpers.doKeys('V', 'p' , 'g', 'v', 'd');
|
2031 |
-
eq('foo\nand', cm.getValue());
|
2032 |
-
}, { value: 'hello\nthis\nis\nfoo\nand\nbar' });
|
2033 |
-
testVim('reselect_visual_block', function(cm, vim, helpers) {
|
2034 |
-
cm.setCursor(1, 2);
|
2035 |
-
helpers.doKeys('<C-v>', 'k', 'h', '<C-v>');
|
2036 |
-
cm.setCursor(2, 1);
|
2037 |
-
helpers.doKeys('v', 'l', 'g', 'v');
|
2038 |
-
eqPos(Pos(1, 2), vim.sel.anchor);
|
2039 |
-
eqPos(Pos(0, 1), vim.sel.head);
|
2040 |
-
// Ensure selection is done with visual block mode rather than one
|
2041 |
-
// continuous range.
|
2042 |
-
eq(cm.getSelections().join(''), '23oo')
|
2043 |
-
helpers.doKeys('g', 'v');
|
2044 |
-
eqPos(Pos(2, 1), vim.sel.anchor);
|
2045 |
-
eqPos(Pos(2, 2), vim.sel.head);
|
2046 |
-
helpers.doKeys('<Esc>');
|
2047 |
-
// Ensure selection of deleted range
|
2048 |
-
cm.setCursor(1, 1);
|
2049 |
-
helpers.doKeys('v', '<C-v>', 'j', 'd', 'g', 'v');
|
2050 |
-
eq(cm.getSelections().join(''), 'or');
|
2051 |
-
}, { value: '123456\nfoo\nbar' });
|
2052 |
-
testVim('s_normal', function(cm, vim, helpers) {
|
2053 |
-
cm.setCursor(0, 1);
|
2054 |
-
helpers.doKeys('s');
|
2055 |
-
helpers.doKeys('<Esc>');
|
2056 |
-
eq('ac', cm.getValue());
|
2057 |
-
}, { value: 'abc'});
|
2058 |
-
testVim('s_visual', function(cm, vim, helpers) {
|
2059 |
-
cm.setCursor(0, 1);
|
2060 |
-
helpers.doKeys('v', 's');
|
2061 |
-
helpers.doKeys('<Esc>');
|
2062 |
-
helpers.assertCursorAt(0, 0);
|
2063 |
-
eq('ac', cm.getValue());
|
2064 |
-
}, { value: 'abc'});
|
2065 |
-
testVim('o_visual', function(cm, vim, helpers) {
|
2066 |
-
cm.setCursor(0,0);
|
2067 |
-
helpers.doKeys('v','l','l','l','o');
|
2068 |
-
helpers.assertCursorAt(0,0);
|
2069 |
-
helpers.doKeys('v','v','j','j','j','o');
|
2070 |
-
helpers.assertCursorAt(0,0);
|
2071 |
-
helpers.doKeys('O');
|
2072 |
-
helpers.doKeys('l','l')
|
2073 |
-
helpers.assertCursorAt(3, 3);
|
2074 |
-
helpers.doKeys('d');
|
2075 |
-
eq('p',cm.getValue());
|
2076 |
-
}, { value: 'abcd\nefgh\nijkl\nmnop'});
|
2077 |
-
testVim('o_visual_block', function(cm, vim, helpers) {
|
2078 |
-
cm.setCursor(0, 1);
|
2079 |
-
helpers.doKeys('<C-v>','3','j','l','l', 'o');
|
2080 |
-
eqPos(Pos(3, 3), vim.sel.anchor);
|
2081 |
-
eqPos(Pos(0, 1), vim.sel.head);
|
2082 |
-
helpers.doKeys('O');
|
2083 |
-
eqPos(Pos(3, 1), vim.sel.anchor);
|
2084 |
-
eqPos(Pos(0, 3), vim.sel.head);
|
2085 |
-
helpers.doKeys('o');
|
2086 |
-
eqPos(Pos(0, 3), vim.sel.anchor);
|
2087 |
-
eqPos(Pos(3, 1), vim.sel.head);
|
2088 |
-
}, { value: 'abcd\nefgh\nijkl\nmnop'});
|
2089 |
-
testVim('changeCase_visual', function(cm, vim, helpers) {
|
2090 |
-
cm.setCursor(0, 0);
|
2091 |
-
helpers.doKeys('v', 'l', 'l');
|
2092 |
-
helpers.doKeys('U');
|
2093 |
-
helpers.assertCursorAt(0, 0);
|
2094 |
-
helpers.doKeys('v', 'l', 'l');
|
2095 |
-
helpers.doKeys('u');
|
2096 |
-
helpers.assertCursorAt(0, 0);
|
2097 |
-
helpers.doKeys('l', 'l', 'l', '.');
|
2098 |
-
helpers.assertCursorAt(0, 3);
|
2099 |
-
cm.setCursor(0, 0);
|
2100 |
-
helpers.doKeys('q', 'a', 'v', 'j', 'U', 'q');
|
2101 |
-
helpers.assertCursorAt(0, 0);
|
2102 |
-
helpers.doKeys('j', '@', 'a');
|
2103 |
-
helpers.assertCursorAt(1, 0);
|
2104 |
-
cm.setCursor(3, 0);
|
2105 |
-
helpers.doKeys('V', 'U', 'j', '.');
|
2106 |
-
eq('ABCDEF\nGHIJKL\nMnopq\nSHORT LINE\nLONG LINE OF TEXT', cm.getValue());
|
2107 |
-
}, { value: 'abcdef\nghijkl\nmnopq\nshort line\nlong line of text'});
|
2108 |
-
testVim('changeCase_visual_block', function(cm, vim, helpers) {
|
2109 |
-
cm.setCursor(2, 1);
|
2110 |
-
helpers.doKeys('<C-v>', 'k', 'k', 'h', 'U');
|
2111 |
-
eq('ABcdef\nGHijkl\nMNopq\nfoo', cm.getValue());
|
2112 |
-
cm.setCursor(0, 2);
|
2113 |
-
helpers.doKeys('.');
|
2114 |
-
eq('ABCDef\nGHIJkl\nMNOPq\nfoo', cm.getValue());
|
2115 |
-
// check when last line is shorter.
|
2116 |
-
cm.setCursor(2, 2);
|
2117 |
-
helpers.doKeys('.');
|
2118 |
-
eq('ABCDef\nGHIJkl\nMNOPq\nfoO', cm.getValue());
|
2119 |
-
}, { value: 'abcdef\nghijkl\nmnopq\nfoo'});
|
2120 |
-
testVim('visual_paste', function(cm, vim, helpers) {
|
2121 |
-
cm.setCursor(0, 0);
|
2122 |
-
helpers.doKeys('v', 'l', 'l', 'y');
|
2123 |
-
helpers.assertCursorAt(0, 0);
|
2124 |
-
helpers.doKeys('3', 'l', 'j', 'v', 'l', 'p');
|
2125 |
-
helpers.assertCursorAt(1, 5);
|
2126 |
-
eq('this is a\nunithitest for visual paste', cm.getValue());
|
2127 |
-
cm.setCursor(0, 0);
|
2128 |
-
// in case of pasting whole line
|
2129 |
-
helpers.doKeys('y', 'y');
|
2130 |
-
cm.setCursor(1, 6);
|
2131 |
-
helpers.doKeys('v', 'l', 'l', 'l', 'p');
|
2132 |
-
helpers.assertCursorAt(2, 0);
|
2133 |
-
eq('this is a\nunithi\nthis is a\n for visual paste', cm.getValue());
|
2134 |
-
}, { value: 'this is a\nunit test for visual paste'});
|
2135 |
-
|
2136 |
-
// This checks the contents of the register used to paste the text
|
2137 |
-
testVim('v_paste_from_register', function(cm, vim, helpers) {
|
2138 |
-
cm.setCursor(0, 0);
|
2139 |
-
helpers.doKeys('"', 'a', 'y', 'w');
|
2140 |
-
cm.setCursor(1, 0);
|
2141 |
-
helpers.doKeys('v', 'p');
|
2142 |
-
cm.openDialog = helpers.fakeOpenDialog('registers');
|
2143 |
-
cm.openNotification = helpers.fakeOpenNotification(function(text) {
|
2144 |
-
is(/a\s+register/.test(text));
|
2145 |
-
});
|
2146 |
-
}, { value: 'register contents\nare not erased'});
|
2147 |
-
testVim('S_normal', function(cm, vim, helpers) {
|
2148 |
-
cm.setCursor(0, 1);
|
2149 |
-
helpers.doKeys('j', 'S');
|
2150 |
-
helpers.doKeys('<Esc>');
|
2151 |
-
helpers.assertCursorAt(1, 0);
|
2152 |
-
eq('aa\n\ncc', cm.getValue());
|
2153 |
-
}, { value: 'aa\nbb\ncc'});
|
2154 |
-
testVim('blockwise_paste', function(cm, vim, helpers) {
|
2155 |
-
cm.setCursor(0, 0);
|
2156 |
-
helpers.doKeys('<C-v>', '3', 'j', 'l', 'y');
|
2157 |
-
cm.setCursor(0, 2);
|
2158 |
-
// paste one char after the current cursor position
|
2159 |
-
helpers.doKeys('p');
|
2160 |
-
eq('helhelo\nworwold\nfoofo\nbarba', cm.getValue());
|
2161 |
-
cm.setCursor(0, 0);
|
2162 |
-
helpers.doKeys('v', '4', 'l', 'y');
|
2163 |
-
cm.setCursor(0, 0);
|
2164 |
-
helpers.doKeys('<C-v>', '3', 'j', 'p');
|
2165 |
-
eq('helheelhelo\norwold\noofo\narba', cm.getValue());
|
2166 |
-
}, { value: 'hello\nworld\nfoo\nbar'});
|
2167 |
-
testVim('blockwise_paste_long/short_line', function(cm, vim, helpers) {
|
2168 |
-
// extend short lines in case of different line lengths.
|
2169 |
-
cm.setCursor(0, 0);
|
2170 |
-
helpers.doKeys('<C-v>', 'j', 'j', 'y');
|
2171 |
-
cm.setCursor(0, 3);
|
2172 |
-
helpers.doKeys('p');
|
2173 |
-
eq('hellho\nfoo f\nbar b', cm.getValue());
|
2174 |
-
}, { value: 'hello\nfoo\nbar'});
|
2175 |
-
testVim('blockwise_paste_cut_paste', function(cm, vim, helpers) {
|
2176 |
-
cm.setCursor(0, 0);
|
2177 |
-
helpers.doKeys('<C-v>', '2', 'j', 'x');
|
2178 |
-
cm.setCursor(0, 0);
|
2179 |
-
helpers.doKeys('P');
|
2180 |
-
eq('cut\nand\npaste\nme', cm.getValue());
|
2181 |
-
}, { value: 'cut\nand\npaste\nme'});
|
2182 |
-
testVim('blockwise_paste_from_register', function(cm, vim, helpers) {
|
2183 |
-
cm.setCursor(0, 0);
|
2184 |
-
helpers.doKeys('<C-v>', '2', 'j', '"', 'a', 'y');
|
2185 |
-
cm.setCursor(0, 3);
|
2186 |
-
helpers.doKeys('"', 'a', 'p');
|
2187 |
-
eq('foobfar\nhellho\nworlwd', cm.getValue());
|
2188 |
-
}, { value: 'foobar\nhello\nworld'});
|
2189 |
-
testVim('blockwise_paste_last_line', function(cm, vim, helpers) {
|
2190 |
-
cm.setCursor(0, 0);
|
2191 |
-
helpers.doKeys('<C-v>', '2', 'j', 'l', 'y');
|
2192 |
-
cm.setCursor(3, 0);
|
2193 |
-
helpers.doKeys('p');
|
2194 |
-
eq('cut\nand\npaste\nmcue\n an\n pa', cm.getValue());
|
2195 |
-
}, { value: 'cut\nand\npaste\nme'});
|
2196 |
-
|
2197 |
-
testVim('S_visual', function(cm, vim, helpers) {
|
2198 |
-
cm.setCursor(0, 1);
|
2199 |
-
helpers.doKeys('v', 'j', 'S');
|
2200 |
-
helpers.doKeys('<Esc>');
|
2201 |
-
helpers.assertCursorAt(0, 0);
|
2202 |
-
eq('\ncc', cm.getValue());
|
2203 |
-
}, { value: 'aa\nbb\ncc'});
|
2204 |
-
|
2205 |
-
testVim('d_/', function(cm, vim, helpers) {
|
2206 |
-
cm.openDialog = helpers.fakeOpenDialog('match');
|
2207 |
-
helpers.doKeys('2', 'd', '/');
|
2208 |
-
helpers.assertCursorAt(0, 0);
|
2209 |
-
eq('match \n next', cm.getValue());
|
2210 |
-
cm.openDialog = helpers.fakeOpenDialog('2');
|
2211 |
-
helpers.doKeys('d', ':');
|
2212 |
-
// TODO eq(' next', cm.getValue());
|
2213 |
-
}, { value: 'text match match \n next' });
|
2214 |
-
testVim('/ and n/N', function(cm, vim, helpers) {
|
2215 |
-
cm.openDialog = helpers.fakeOpenDialog('match');
|
2216 |
-
helpers.doKeys('/');
|
2217 |
-
helpers.assertCursorAt(0, 11);
|
2218 |
-
helpers.doKeys('n');
|
2219 |
-
helpers.assertCursorAt(1, 6);
|
2220 |
-
helpers.doKeys('N');
|
2221 |
-
helpers.assertCursorAt(0, 11);
|
2222 |
-
|
2223 |
-
cm.setCursor(0, 0);
|
2224 |
-
helpers.doKeys('2', '/');
|
2225 |
-
helpers.assertCursorAt(1, 6);
|
2226 |
-
}, { value: 'match nope match \n nope Match' });
|
2227 |
-
testVim('/_case', function(cm, vim, helpers) {
|
2228 |
-
cm.openDialog = helpers.fakeOpenDialog('Match');
|
2229 |
-
helpers.doKeys('/');
|
2230 |
-
helpers.assertCursorAt(1, 6);
|
2231 |
-
}, { value: 'match nope match \n nope Match' });
|
2232 |
-
testVim('/_2_pcre', function(cm, vim, helpers) {
|
2233 |
-
CodeMirror.Vim.setOption('pcre', true);
|
2234 |
-
cm.openDialog = helpers.fakeOpenDialog('(word){2}');
|
2235 |
-
helpers.doKeys('/');
|
2236 |
-
helpers.assertCursorAt(1, 9);
|
2237 |
-
helpers.doKeys('n');
|
2238 |
-
helpers.assertCursorAt(2, 1);
|
2239 |
-
}, { value: 'word\n another wordword\n wordwordword\n' });
|
2240 |
-
testVim('/_2_nopcre', function(cm, vim, helpers) {
|
2241 |
-
CodeMirror.Vim.setOption('pcre', false);
|
2242 |
-
cm.openDialog = helpers.fakeOpenDialog('\\(word\\)\\{2}');
|
2243 |
-
helpers.doKeys('/');
|
2244 |
-
helpers.assertCursorAt(1, 9);
|
2245 |
-
helpers.doKeys('n');
|
2246 |
-
helpers.assertCursorAt(2, 1);
|
2247 |
-
}, { value: 'word\n another wordword\n wordwordword\n' });
|
2248 |
-
testVim('/_nongreedy', function(cm, vim, helpers) {
|
2249 |
-
cm.openDialog = helpers.fakeOpenDialog('aa');
|
2250 |
-
helpers.doKeys('/');
|
2251 |
-
helpers.assertCursorAt(0, 4);
|
2252 |
-
helpers.doKeys('n');
|
2253 |
-
helpers.assertCursorAt(1, 3);
|
2254 |
-
helpers.doKeys('n');
|
2255 |
-
helpers.assertCursorAt(0, 0);
|
2256 |
-
}, { value: 'aaa aa \n a aa'});
|
2257 |
-
testVim('?_nongreedy', function(cm, vim, helpers) {
|
2258 |
-
cm.openDialog = helpers.fakeOpenDialog('aa');
|
2259 |
-
helpers.doKeys('?');
|
2260 |
-
helpers.assertCursorAt(1, 3);
|
2261 |
-
helpers.doKeys('n');
|
2262 |
-
helpers.assertCursorAt(0, 4);
|
2263 |
-
helpers.doKeys('n');
|
2264 |
-
helpers.assertCursorAt(0, 0);
|
2265 |
-
}, { value: 'aaa aa \n a aa'});
|
2266 |
-
testVim('/_greedy', function(cm, vim, helpers) {
|
2267 |
-
cm.openDialog = helpers.fakeOpenDialog('a+');
|
2268 |
-
helpers.doKeys('/');
|
2269 |
-
helpers.assertCursorAt(0, 4);
|
2270 |
-
helpers.doKeys('n');
|
2271 |
-
helpers.assertCursorAt(1, 1);
|
2272 |
-
helpers.doKeys('n');
|
2273 |
-
helpers.assertCursorAt(1, 3);
|
2274 |
-
helpers.doKeys('n');
|
2275 |
-
helpers.assertCursorAt(0, 0);
|
2276 |
-
}, { value: 'aaa aa \n a aa'});
|
2277 |
-
testVim('?_greedy', function(cm, vim, helpers) {
|
2278 |
-
cm.openDialog = helpers.fakeOpenDialog('a+');
|
2279 |
-
helpers.doKeys('?');
|
2280 |
-
helpers.assertCursorAt(1, 3);
|
2281 |
-
helpers.doKeys('n');
|
2282 |
-
helpers.assertCursorAt(1, 1);
|
2283 |
-
helpers.doKeys('n');
|
2284 |
-
helpers.assertCursorAt(0, 4);
|
2285 |
-
helpers.doKeys('n');
|
2286 |
-
helpers.assertCursorAt(0, 0);
|
2287 |
-
}, { value: 'aaa aa \n a aa'});
|
2288 |
-
testVim('/_greedy_0_or_more', function(cm, vim, helpers) {
|
2289 |
-
cm.openDialog = helpers.fakeOpenDialog('a*');
|
2290 |
-
helpers.doKeys('/');
|
2291 |
-
helpers.assertCursorAt(0, 3);
|
2292 |
-
helpers.doKeys('n');
|
2293 |
-
helpers.assertCursorAt(0, 4);
|
2294 |
-
helpers.doKeys('n');
|
2295 |
-
helpers.assertCursorAt(0, 5);
|
2296 |
-
helpers.doKeys('n');
|
2297 |
-
helpers.assertCursorAt(1, 0);
|
2298 |
-
helpers.doKeys('n');
|
2299 |
-
helpers.assertCursorAt(1, 1);
|
2300 |
-
helpers.doKeys('n');
|
2301 |
-
helpers.assertCursorAt(0, 0);
|
2302 |
-
}, { value: 'aaa aa\n aa'});
|
2303 |
-
testVim('?_greedy_0_or_more', function(cm, vim, helpers) {
|
2304 |
-
cm.openDialog = helpers.fakeOpenDialog('a*');
|
2305 |
-
helpers.doKeys('?');
|
2306 |
-
helpers.assertCursorAt(1, 1);
|
2307 |
-
helpers.doKeys('n');
|
2308 |
-
helpers.assertCursorAt(1, 0);
|
2309 |
-
helpers.doKeys('n');
|
2310 |
-
helpers.assertCursorAt(0, 5);
|
2311 |
-
helpers.doKeys('n');
|
2312 |
-
helpers.assertCursorAt(0, 4);
|
2313 |
-
helpers.doKeys('n');
|
2314 |
-
helpers.assertCursorAt(0, 3);
|
2315 |
-
helpers.doKeys('n');
|
2316 |
-
helpers.assertCursorAt(0, 0);
|
2317 |
-
}, { value: 'aaa aa\n aa'});
|
2318 |
-
testVim('? and n/N', function(cm, vim, helpers) {
|
2319 |
-
cm.openDialog = helpers.fakeOpenDialog('match');
|
2320 |
-
helpers.doKeys('?');
|
2321 |
-
helpers.assertCursorAt(1, 6);
|
2322 |
-
helpers.doKeys('n');
|
2323 |
-
helpers.assertCursorAt(0, 11);
|
2324 |
-
helpers.doKeys('N');
|
2325 |
-
helpers.assertCursorAt(1, 6);
|
2326 |
-
|
2327 |
-
cm.setCursor(0, 0);
|
2328 |
-
helpers.doKeys('2', '?');
|
2329 |
-
helpers.assertCursorAt(0, 11);
|
2330 |
-
}, { value: 'match nope match \n nope Match' });
|
2331 |
-
testVim('*', function(cm, vim, helpers) {
|
2332 |
-
cm.setCursor(0, 9);
|
2333 |
-
helpers.doKeys('*');
|
2334 |
-
helpers.assertCursorAt(0, 22);
|
2335 |
-
|
2336 |
-
cm.setCursor(0, 9);
|
2337 |
-
helpers.doKeys('2', '*');
|
2338 |
-
helpers.assertCursorAt(1, 8);
|
2339 |
-
}, { value: 'nomatch match nomatch match \nnomatch Match' });
|
2340 |
-
testVim('*_no_word', function(cm, vim, helpers) {
|
2341 |
-
cm.setCursor(0, 0);
|
2342 |
-
helpers.doKeys('*');
|
2343 |
-
helpers.assertCursorAt(0, 0);
|
2344 |
-
}, { value: ' \n match \n' });
|
2345 |
-
testVim('*_symbol', function(cm, vim, helpers) {
|
2346 |
-
cm.setCursor(0, 0);
|
2347 |
-
helpers.doKeys('*');
|
2348 |
-
helpers.assertCursorAt(1, 0);
|
2349 |
-
}, { value: ' /}\n/} match \n' });
|
2350 |
-
testVim('#', function(cm, vim, helpers) {
|
2351 |
-
cm.setCursor(0, 9);
|
2352 |
-
helpers.doKeys('#');
|
2353 |
-
helpers.assertCursorAt(1, 8);
|
2354 |
-
|
2355 |
-
cm.setCursor(0, 9);
|
2356 |
-
helpers.doKeys('2', '#');
|
2357 |
-
helpers.assertCursorAt(0, 22);
|
2358 |
-
}, { value: 'nomatch match nomatch match \nnomatch Match' });
|
2359 |
-
testVim('*_seek', function(cm, vim, helpers) {
|
2360 |
-
// Should skip over space and symbols.
|
2361 |
-
cm.setCursor(0, 3);
|
2362 |
-
helpers.doKeys('*');
|
2363 |
-
helpers.assertCursorAt(0, 22);
|
2364 |
-
}, { value: ' := match nomatch match \nnomatch Match' });
|
2365 |
-
testVim('#', function(cm, vim, helpers) {
|
2366 |
-
// Should skip over space and symbols.
|
2367 |
-
cm.setCursor(0, 3);
|
2368 |
-
helpers.doKeys('#');
|
2369 |
-
helpers.assertCursorAt(1, 8);
|
2370 |
-
}, { value: ' := match nomatch match \nnomatch Match' });
|
2371 |
-
testVim('g*', function(cm, vim, helpers) {
|
2372 |
-
cm.setCursor(0, 8);
|
2373 |
-
helpers.doKeys('g', '*');
|
2374 |
-
helpers.assertCursorAt(0, 18);
|
2375 |
-
cm.setCursor(0, 8);
|
2376 |
-
helpers.doKeys('3', 'g', '*');
|
2377 |
-
helpers.assertCursorAt(1, 8);
|
2378 |
-
}, { value: 'matches match alsoMatch\nmatchme matching' });
|
2379 |
-
testVim('g#', function(cm, vim, helpers) {
|
2380 |
-
cm.setCursor(0, 8);
|
2381 |
-
helpers.doKeys('g', '#');
|
2382 |
-
helpers.assertCursorAt(0, 0);
|
2383 |
-
cm.setCursor(0, 8);
|
2384 |
-
helpers.doKeys('3', 'g', '#');
|
2385 |
-
helpers.assertCursorAt(1, 0);
|
2386 |
-
}, { value: 'matches match alsoMatch\nmatchme matching' });
|
2387 |
-
testVim('macro_insert', function(cm, vim, helpers) {
|
2388 |
-
cm.setCursor(0, 0);
|
2389 |
-
helpers.doKeys('q', 'a', '0', 'i');
|
2390 |
-
cm.replaceRange('foo', cm.getCursor());
|
2391 |
-
helpers.doKeys('<Esc>');
|
2392 |
-
helpers.doKeys('q', '@', 'a');
|
2393 |
-
eq('foofoo', cm.getValue());
|
2394 |
-
}, { value: ''});
|
2395 |
-
testVim('macro_insert_repeat', function(cm, vim, helpers) {
|
2396 |
-
cm.setCursor(0, 0);
|
2397 |
-
helpers.doKeys('q', 'a', '$', 'a');
|
2398 |
-
cm.replaceRange('larry.', cm.getCursor());
|
2399 |
-
helpers.doKeys('<Esc>');
|
2400 |
-
helpers.doKeys('a');
|
2401 |
-
cm.replaceRange('curly.', cm.getCursor());
|
2402 |
-
helpers.doKeys('<Esc>');
|
2403 |
-
helpers.doKeys('q');
|
2404 |
-
helpers.doKeys('a');
|
2405 |
-
cm.replaceRange('moe.', cm.getCursor());
|
2406 |
-
helpers.doKeys('<Esc>');
|
2407 |
-
helpers.doKeys('@', 'a');
|
2408 |
-
// At this point, the most recent edit should be the 2nd insert change
|
2409 |
-
// inside the macro, i.e. "curly.".
|
2410 |
-
helpers.doKeys('.');
|
2411 |
-
eq('larry.curly.moe.larry.curly.curly.', cm.getValue());
|
2412 |
-
}, { value: ''});
|
2413 |
-
testVim('macro_space', function(cm, vim, helpers) {
|
2414 |
-
cm.setCursor(0, 0);
|
2415 |
-
helpers.doKeys('<Space>', '<Space>');
|
2416 |
-
helpers.assertCursorAt(0, 2);
|
2417 |
-
helpers.doKeys('q', 'a', '<Space>', '<Space>', 'q');
|
2418 |
-
helpers.assertCursorAt(0, 4);
|
2419 |
-
helpers.doKeys('@', 'a');
|
2420 |
-
helpers.assertCursorAt(0, 6);
|
2421 |
-
helpers.doKeys('@', 'a');
|
2422 |
-
helpers.assertCursorAt(0, 8);
|
2423 |
-
}, { value: 'one line of text.'});
|
2424 |
-
testVim('macro_t_search', function(cm, vim, helpers) {
|
2425 |
-
cm.setCursor(0, 0);
|
2426 |
-
helpers.doKeys('q', 'a', 't', 'e', 'q');
|
2427 |
-
helpers.assertCursorAt(0, 1);
|
2428 |
-
helpers.doKeys('l', '@', 'a');
|
2429 |
-
helpers.assertCursorAt(0, 6);
|
2430 |
-
helpers.doKeys('l', ';');
|
2431 |
-
helpers.assertCursorAt(0, 12);
|
2432 |
-
}, { value: 'one line of text.'});
|
2433 |
-
testVim('macro_f_search', function(cm, vim, helpers) {
|
2434 |
-
cm.setCursor(0, 0);
|
2435 |
-
helpers.doKeys('q', 'b', 'f', 'e', 'q');
|
2436 |
-
helpers.assertCursorAt(0, 2);
|
2437 |
-
helpers.doKeys('@', 'b');
|
2438 |
-
helpers.assertCursorAt(0, 7);
|
2439 |
-
helpers.doKeys(';');
|
2440 |
-
helpers.assertCursorAt(0, 13);
|
2441 |
-
}, { value: 'one line of text.'});
|
2442 |
-
testVim('macro_slash_search', function(cm, vim, helpers) {
|
2443 |
-
cm.setCursor(0, 0);
|
2444 |
-
helpers.doKeys('q', 'c');
|
2445 |
-
cm.openDialog = helpers.fakeOpenDialog('e');
|
2446 |
-
helpers.doKeys('/', 'q');
|
2447 |
-
helpers.assertCursorAt(0, 2);
|
2448 |
-
helpers.doKeys('@', 'c');
|
2449 |
-
helpers.assertCursorAt(0, 7);
|
2450 |
-
helpers.doKeys('n');
|
2451 |
-
helpers.assertCursorAt(0, 13);
|
2452 |
-
}, { value: 'one line of text.'});
|
2453 |
-
testVim('macro_multislash_search', function(cm, vim, helpers) {
|
2454 |
-
cm.setCursor(0, 0);
|
2455 |
-
helpers.doKeys('q', 'd');
|
2456 |
-
cm.openDialog = helpers.fakeOpenDialog('e');
|
2457 |
-
helpers.doKeys('/');
|
2458 |
-
cm.openDialog = helpers.fakeOpenDialog('t');
|
2459 |
-
helpers.doKeys('/', 'q');
|
2460 |
-
helpers.assertCursorAt(0, 12);
|
2461 |
-
helpers.doKeys('@', 'd');
|
2462 |
-
helpers.assertCursorAt(0, 15);
|
2463 |
-
}, { value: 'one line of text to rule them all.'});
|
2464 |
-
testVim('macro_last_ex_command_register', function (cm, vim, helpers) {
|
2465 |
-
cm.setCursor(0, 0);
|
2466 |
-
helpers.doEx('s/a/b');
|
2467 |
-
helpers.doKeys('2', '@', ':');
|
2468 |
-
eq('bbbaa', cm.getValue());
|
2469 |
-
helpers.assertCursorAt(0, 2);
|
2470 |
-
}, { value: 'aaaaa'});
|
2471 |
-
testVim('macro_parens', function(cm, vim, helpers) {
|
2472 |
-
cm.setCursor(0, 0);
|
2473 |
-
helpers.doKeys('q', 'z', 'i');
|
2474 |
-
cm.replaceRange('(', cm.getCursor());
|
2475 |
-
helpers.doKeys('<Esc>');
|
2476 |
-
helpers.doKeys('e', 'a');
|
2477 |
-
cm.replaceRange(')', cm.getCursor());
|
2478 |
-
helpers.doKeys('<Esc>');
|
2479 |
-
helpers.doKeys('q');
|
2480 |
-
helpers.doKeys('w', '@', 'z');
|
2481 |
-
helpers.doKeys('w', '@', 'z');
|
2482 |
-
eq('(see) (spot) (run)', cm.getValue());
|
2483 |
-
}, { value: 'see spot run'});
|
2484 |
-
testVim('macro_overwrite', function(cm, vim, helpers) {
|
2485 |
-
cm.setCursor(0, 0);
|
2486 |
-
helpers.doKeys('q', 'z', '0', 'i');
|
2487 |
-
cm.replaceRange('I ', cm.getCursor());
|
2488 |
-
helpers.doKeys('<Esc>');
|
2489 |
-
helpers.doKeys('q');
|
2490 |
-
helpers.doKeys('e');
|
2491 |
-
// Now replace the macro with something else.
|
2492 |
-
helpers.doKeys('q', 'z', 'a');
|
2493 |
-
cm.replaceRange('.', cm.getCursor());
|
2494 |
-
helpers.doKeys('<Esc>');
|
2495 |
-
helpers.doKeys('q');
|
2496 |
-
helpers.doKeys('e', '@', 'z');
|
2497 |
-
helpers.doKeys('e', '@', 'z');
|
2498 |
-
eq('I see. spot. run.', cm.getValue());
|
2499 |
-
}, { value: 'see spot run'});
|
2500 |
-
testVim('macro_search_f', function(cm, vim, helpers) {
|
2501 |
-
cm.setCursor(0, 0);
|
2502 |
-
helpers.doKeys('q', 'a', 'f', ' ');
|
2503 |
-
helpers.assertCursorAt(0,3);
|
2504 |
-
helpers.doKeys('q', '0');
|
2505 |
-
helpers.assertCursorAt(0,0);
|
2506 |
-
helpers.doKeys('@', 'a');
|
2507 |
-
helpers.assertCursorAt(0,3);
|
2508 |
-
}, { value: 'The quick brown fox jumped over the lazy dog.'});
|
2509 |
-
testVim('macro_search_2f', function(cm, vim, helpers) {
|
2510 |
-
cm.setCursor(0, 0);
|
2511 |
-
helpers.doKeys('q', 'a', '2', 'f', ' ');
|
2512 |
-
helpers.assertCursorAt(0,9);
|
2513 |
-
helpers.doKeys('q', '0');
|
2514 |
-
helpers.assertCursorAt(0,0);
|
2515 |
-
helpers.doKeys('@', 'a');
|
2516 |
-
helpers.assertCursorAt(0,9);
|
2517 |
-
}, { value: 'The quick brown fox jumped over the lazy dog.'});
|
2518 |
-
testVim('yank_register', function(cm, vim, helpers) {
|
2519 |
-
cm.setCursor(0, 0);
|
2520 |
-
helpers.doKeys('"', 'a', 'y', 'y');
|
2521 |
-
helpers.doKeys('j', '"', 'b', 'y', 'y');
|
2522 |
-
cm.openDialog = helpers.fakeOpenDialog('registers');
|
2523 |
-
cm.openNotification = helpers.fakeOpenNotification(function(text) {
|
2524 |
-
is(/a\s+foo/.test(text));
|
2525 |
-
is(/b\s+bar/.test(text));
|
2526 |
-
});
|
2527 |
-
helpers.doKeys(':');
|
2528 |
-
}, { value: 'foo\nbar'});
|
2529 |
-
testVim('yank_visual_block', function(cm, vim, helpers) {
|
2530 |
-
cm.setCursor(0, 1);
|
2531 |
-
helpers.doKeys('<C-v>', 'l', 'j', '"', 'a', 'y');
|
2532 |
-
cm.openNotification = helpers.fakeOpenNotification(function(text) {
|
2533 |
-
is(/a\s+oo\nar/.test(text));
|
2534 |
-
});
|
2535 |
-
helpers.doKeys(':');
|
2536 |
-
}, { value: 'foo\nbar'});
|
2537 |
-
testVim('yank_append_line_to_line_register', function(cm, vim, helpers) {
|
2538 |
-
cm.setCursor(0, 0);
|
2539 |
-
helpers.doKeys('"', 'a', 'y', 'y');
|
2540 |
-
helpers.doKeys('j', '"', 'A', 'y', 'y');
|
2541 |
-
cm.openDialog = helpers.fakeOpenDialog('registers');
|
2542 |
-
cm.openNotification = helpers.fakeOpenNotification(function(text) {
|
2543 |
-
is(/a\s+foo\nbar/.test(text));
|
2544 |
-
is(/"\s+foo\nbar/.test(text));
|
2545 |
-
});
|
2546 |
-
helpers.doKeys(':');
|
2547 |
-
}, { value: 'foo\nbar'});
|
2548 |
-
testVim('yank_append_word_to_word_register', function(cm, vim, helpers) {
|
2549 |
-
cm.setCursor(0, 0);
|
2550 |
-
helpers.doKeys('"', 'a', 'y', 'w');
|
2551 |
-
helpers.doKeys('j', '"', 'A', 'y', 'w');
|
2552 |
-
cm.openDialog = helpers.fakeOpenDialog('registers');
|
2553 |
-
cm.openNotification = helpers.fakeOpenNotification(function(text) {
|
2554 |
-
is(/a\s+foobar/.test(text));
|
2555 |
-
is(/"\s+foobar/.test(text));
|
2556 |
-
});
|
2557 |
-
helpers.doKeys(':');
|
2558 |
-
}, { value: 'foo\nbar'});
|
2559 |
-
testVim('yank_append_line_to_word_register', function(cm, vim, helpers) {
|
2560 |
-
cm.setCursor(0, 0);
|
2561 |
-
helpers.doKeys('"', 'a', 'y', 'w');
|
2562 |
-
helpers.doKeys('j', '"', 'A', 'y', 'y');
|
2563 |
-
cm.openDialog = helpers.fakeOpenDialog('registers');
|
2564 |
-
cm.openNotification = helpers.fakeOpenNotification(function(text) {
|
2565 |
-
is(/a\s+foo\nbar/.test(text));
|
2566 |
-
is(/"\s+foo\nbar/.test(text));
|
2567 |
-
});
|
2568 |
-
helpers.doKeys(':');
|
2569 |
-
}, { value: 'foo\nbar'});
|
2570 |
-
testVim('yank_append_word_to_line_register', function(cm, vim, helpers) {
|
2571 |
-
cm.setCursor(0, 0);
|
2572 |
-
helpers.doKeys('"', 'a', 'y', 'y');
|
2573 |
-
helpers.doKeys('j', '"', 'A', 'y', 'w');
|
2574 |
-
cm.openDialog = helpers.fakeOpenDialog('registers');
|
2575 |
-
cm.openNotification = helpers.fakeOpenNotification(function(text) {
|
2576 |
-
is(/a\s+foo\nbar/.test(text));
|
2577 |
-
is(/"\s+foo\nbar/.test(text));
|
2578 |
-
});
|
2579 |
-
helpers.doKeys(':');
|
2580 |
-
}, { value: 'foo\nbar'});
|
2581 |
-
testVim('macro_register', function(cm, vim, helpers) {
|
2582 |
-
cm.setCursor(0, 0);
|
2583 |
-
helpers.doKeys('q', 'a', 'i');
|
2584 |
-
cm.replaceRange('gangnam', cm.getCursor());
|
2585 |
-
helpers.doKeys('<Esc>');
|
2586 |
-
helpers.doKeys('q');
|
2587 |
-
helpers.doKeys('q', 'b', 'o');
|
2588 |
-
cm.replaceRange('style', cm.getCursor());
|
2589 |
-
helpers.doKeys('<Esc>');
|
2590 |
-
helpers.doKeys('q');
|
2591 |
-
cm.openDialog = helpers.fakeOpenDialog('registers');
|
2592 |
-
cm.openNotification = helpers.fakeOpenNotification(function(text) {
|
2593 |
-
is(/a\s+i/.test(text));
|
2594 |
-
is(/b\s+o/.test(text));
|
2595 |
-
});
|
2596 |
-
helpers.doKeys(':');
|
2597 |
-
}, { value: ''});
|
2598 |
-
testVim('._register', function(cm,vim,helpers) {
|
2599 |
-
cm.setCursor(0,0);
|
2600 |
-
helpers.doKeys('i');
|
2601 |
-
cm.replaceRange('foo',cm.getCursor());
|
2602 |
-
helpers.doKeys('<Esc>');
|
2603 |
-
cm.openDialog = helpers.fakeOpenDialog('registers');
|
2604 |
-
cm.openNotification = helpers.fakeOpenNotification(function(text) {
|
2605 |
-
is(/\.\s+foo/.test(text));
|
2606 |
-
});
|
2607 |
-
helpers.doKeys(':');
|
2608 |
-
}, {value: ''});
|
2609 |
-
testVim(':_register', function(cm,vim,helpers) {
|
2610 |
-
helpers.doEx('bar');
|
2611 |
-
cm.openDialog = helpers.fakeOpenDialog('registers');
|
2612 |
-
cm.openNotification = helpers.fakeOpenNotification(function(text) {
|
2613 |
-
is(/:\s+bar/.test(text));
|
2614 |
-
});
|
2615 |
-
helpers.doKeys(':');
|
2616 |
-
}, {value: ''});
|
2617 |
-
testVim('search_register_escape', function(cm, vim, helpers) {
|
2618 |
-
// Check that the register is restored if the user escapes rather than confirms.
|
2619 |
-
cm.openDialog = helpers.fakeOpenDialog('waldo');
|
2620 |
-
helpers.doKeys('/');
|
2621 |
-
var onKeyDown;
|
2622 |
-
var onKeyUp;
|
2623 |
-
var KEYCODES = {
|
2624 |
-
f: 70,
|
2625 |
-
o: 79,
|
2626 |
-
Esc: 27
|
2627 |
-
};
|
2628 |
-
cm.openDialog = function(template, callback, options) {
|
2629 |
-
onKeyDown = options.onKeyDown;
|
2630 |
-
onKeyUp = options.onKeyUp;
|
2631 |
-
};
|
2632 |
-
var close = function() {};
|
2633 |
-
helpers.doKeys('/');
|
2634 |
-
// Fake some keyboard events coming in.
|
2635 |
-
onKeyDown({keyCode: KEYCODES.f}, '', close);
|
2636 |
-
onKeyUp({keyCode: KEYCODES.f}, '', close);
|
2637 |
-
onKeyDown({keyCode: KEYCODES.o}, 'f', close);
|
2638 |
-
onKeyUp({keyCode: KEYCODES.o}, 'f', close);
|
2639 |
-
onKeyDown({keyCode: KEYCODES.o}, 'fo', close);
|
2640 |
-
onKeyUp({keyCode: KEYCODES.o}, 'fo', close);
|
2641 |
-
onKeyDown({keyCode: KEYCODES.Esc}, 'foo', close);
|
2642 |
-
cm.openDialog = helpers.fakeOpenDialog('registers');
|
2643 |
-
cm.openNotification = helpers.fakeOpenNotification(function(text) {
|
2644 |
-
is(/waldo/.test(text));
|
2645 |
-
is(!/foo/.test(text));
|
2646 |
-
});
|
2647 |
-
helpers.doKeys(':');
|
2648 |
-
}, {value: ''});
|
2649 |
-
testVim('search_register', function(cm, vim, helpers) {
|
2650 |
-
cm.openDialog = helpers.fakeOpenDialog('foo');
|
2651 |
-
helpers.doKeys('/');
|
2652 |
-
cm.openDialog = helpers.fakeOpenDialog('registers');
|
2653 |
-
cm.openNotification = helpers.fakeOpenNotification(function(text) {
|
2654 |
-
is(/\/\s+foo/.test(text));
|
2655 |
-
});
|
2656 |
-
helpers.doKeys(':');
|
2657 |
-
}, {value: ''});
|
2658 |
-
testVim('search_history', function(cm, vim, helpers) {
|
2659 |
-
cm.openDialog = helpers.fakeOpenDialog('this');
|
2660 |
-
helpers.doKeys('/');
|
2661 |
-
cm.openDialog = helpers.fakeOpenDialog('checks');
|
2662 |
-
helpers.doKeys('/');
|
2663 |
-
cm.openDialog = helpers.fakeOpenDialog('search');
|
2664 |
-
helpers.doKeys('/');
|
2665 |
-
cm.openDialog = helpers.fakeOpenDialog('history');
|
2666 |
-
helpers.doKeys('/');
|
2667 |
-
cm.openDialog = helpers.fakeOpenDialog('checks');
|
2668 |
-
helpers.doKeys('/');
|
2669 |
-
var onKeyDown;
|
2670 |
-
var onKeyUp;
|
2671 |
-
var query = '';
|
2672 |
-
var keyCodes = {
|
2673 |
-
Up: 38,
|
2674 |
-
Down: 40
|
2675 |
-
};
|
2676 |
-
cm.openDialog = function(template, callback, options) {
|
2677 |
-
onKeyUp = options.onKeyUp;
|
2678 |
-
onKeyDown = options.onKeyDown;
|
2679 |
-
};
|
2680 |
-
var close = function(newVal) {
|
2681 |
-
if (typeof newVal == 'string') query = newVal;
|
2682 |
-
}
|
2683 |
-
helpers.doKeys('/');
|
2684 |
-
onKeyDown({keyCode: keyCodes.Up}, query, close);
|
2685 |
-
onKeyUp({keyCode: keyCodes.Up}, query, close);
|
2686 |
-
eq(query, 'checks');
|
2687 |
-
onKeyDown({keyCode: keyCodes.Up}, query, close);
|
2688 |
-
onKeyUp({keyCode: keyCodes.Up}, query, close);
|
2689 |
-
eq(query, 'history');
|
2690 |
-
onKeyDown({keyCode: keyCodes.Up}, query, close);
|
2691 |
-
onKeyUp({keyCode: keyCodes.Up}, query, close);
|
2692 |
-
eq(query, 'search');
|
2693 |
-
onKeyDown({keyCode: keyCodes.Up}, query, close);
|
2694 |
-
onKeyUp({keyCode: keyCodes.Up}, query, close);
|
2695 |
-
eq(query, 'this');
|
2696 |
-
onKeyDown({keyCode: keyCodes.Down}, query, close);
|
2697 |
-
onKeyUp({keyCode: keyCodes.Down}, query, close);
|
2698 |
-
eq(query, 'search');
|
2699 |
-
}, {value: ''});
|
2700 |
-
testVim('exCommand_history', function(cm, vim, helpers) {
|
2701 |
-
cm.openDialog = helpers.fakeOpenDialog('registers');
|
2702 |
-
helpers.doKeys(':');
|
2703 |
-
cm.openDialog = helpers.fakeOpenDialog('sort');
|
2704 |
-
helpers.doKeys(':');
|
2705 |
-
cm.openDialog = helpers.fakeOpenDialog('map');
|
2706 |
-
helpers.doKeys(':');
|
2707 |
-
cm.openDialog = helpers.fakeOpenDialog('invalid');
|
2708 |
-
helpers.doKeys(':');
|
2709 |
-
var onKeyDown;
|
2710 |
-
var onKeyUp;
|
2711 |
-
var input = '';
|
2712 |
-
var keyCodes = {
|
2713 |
-
Up: 38,
|
2714 |
-
Down: 40,
|
2715 |
-
s: 115
|
2716 |
-
};
|
2717 |
-
cm.openDialog = function(template, callback, options) {
|
2718 |
-
onKeyUp = options.onKeyUp;
|
2719 |
-
onKeyDown = options.onKeyDown;
|
2720 |
-
};
|
2721 |
-
var close = function(newVal) {
|
2722 |
-
if (typeof newVal == 'string') input = newVal;
|
2723 |
-
}
|
2724 |
-
helpers.doKeys(':');
|
2725 |
-
onKeyDown({keyCode: keyCodes.Up}, input, close);
|
2726 |
-
eq(input, 'invalid');
|
2727 |
-
onKeyDown({keyCode: keyCodes.Up}, input, close);
|
2728 |
-
eq(input, 'map');
|
2729 |
-
onKeyDown({keyCode: keyCodes.Up}, input, close);
|
2730 |
-
eq(input, 'sort');
|
2731 |
-
onKeyDown({keyCode: keyCodes.Up}, input, close);
|
2732 |
-
eq(input, 'registers');
|
2733 |
-
onKeyDown({keyCode: keyCodes.s}, '', close);
|
2734 |
-
input = 's';
|
2735 |
-
onKeyDown({keyCode: keyCodes.Up}, input, close);
|
2736 |
-
eq(input, 'sort');
|
2737 |
-
}, {value: ''});
|
2738 |
-
testVim('search_clear', function(cm, vim, helpers) {
|
2739 |
-
var onKeyDown;
|
2740 |
-
var input = '';
|
2741 |
-
var keyCodes = {
|
2742 |
-
Ctrl: 17,
|
2743 |
-
u: 85
|
2744 |
-
};
|
2745 |
-
cm.openDialog = function(template, callback, options) {
|
2746 |
-
onKeyDown = options.onKeyDown;
|
2747 |
-
};
|
2748 |
-
var close = function(newVal) {
|
2749 |
-
if (typeof newVal == 'string') input = newVal;
|
2750 |
-
}
|
2751 |
-
helpers.doKeys('/');
|
2752 |
-
input = 'foo';
|
2753 |
-
onKeyDown({keyCode: keyCodes.Ctrl}, input, close);
|
2754 |
-
onKeyDown({keyCode: keyCodes.u, ctrlKey: true}, input, close);
|
2755 |
-
eq(input, '');
|
2756 |
-
});
|
2757 |
-
testVim('exCommand_clear', function(cm, vim, helpers) {
|
2758 |
-
var onKeyDown;
|
2759 |
-
var input = '';
|
2760 |
-
var keyCodes = {
|
2761 |
-
Ctrl: 17,
|
2762 |
-
u: 85
|
2763 |
-
};
|
2764 |
-
cm.openDialog = function(template, callback, options) {
|
2765 |
-
onKeyDown = options.onKeyDown;
|
2766 |
-
};
|
2767 |
-
var close = function(newVal) {
|
2768 |
-
if (typeof newVal == 'string') input = newVal;
|
2769 |
-
}
|
2770 |
-
helpers.doKeys(':');
|
2771 |
-
input = 'foo';
|
2772 |
-
onKeyDown({keyCode: keyCodes.Ctrl}, input, close);
|
2773 |
-
onKeyDown({keyCode: keyCodes.u, ctrlKey: true}, input, close);
|
2774 |
-
eq(input, '');
|
2775 |
-
});
|
2776 |
-
testVim('.', function(cm, vim, helpers) {
|
2777 |
-
cm.setCursor(0, 0);
|
2778 |
-
helpers.doKeys('2', 'd', 'w');
|
2779 |
-
helpers.doKeys('.');
|
2780 |
-
eq('5 6', cm.getValue());
|
2781 |
-
}, { value: '1 2 3 4 5 6'});
|
2782 |
-
testVim('._repeat', function(cm, vim, helpers) {
|
2783 |
-
cm.setCursor(0, 0);
|
2784 |
-
helpers.doKeys('2', 'd', 'w');
|
2785 |
-
helpers.doKeys('3', '.');
|
2786 |
-
eq('6', cm.getValue());
|
2787 |
-
}, { value: '1 2 3 4 5 6'});
|
2788 |
-
testVim('._insert', function(cm, vim, helpers) {
|
2789 |
-
helpers.doKeys('i');
|
2790 |
-
cm.replaceRange('test', cm.getCursor());
|
2791 |
-
helpers.doKeys('<Esc>');
|
2792 |
-
helpers.doKeys('.');
|
2793 |
-
eq('testestt', cm.getValue());
|
2794 |
-
helpers.assertCursorAt(0, 6);
|
2795 |
-
}, { value: ''});
|
2796 |
-
testVim('._insert_repeat', function(cm, vim, helpers) {
|
2797 |
-
helpers.doKeys('i');
|
2798 |
-
cm.replaceRange('test', cm.getCursor());
|
2799 |
-
cm.setCursor(0, 4);
|
2800 |
-
helpers.doKeys('<Esc>');
|
2801 |
-
helpers.doKeys('2', '.');
|
2802 |
-
eq('testesttestt', cm.getValue());
|
2803 |
-
helpers.assertCursorAt(0, 10);
|
2804 |
-
}, { value: ''});
|
2805 |
-
testVim('._repeat_insert', function(cm, vim, helpers) {
|
2806 |
-
helpers.doKeys('3', 'i');
|
2807 |
-
cm.replaceRange('te', cm.getCursor());
|
2808 |
-
cm.setCursor(0, 2);
|
2809 |
-
helpers.doKeys('<Esc>');
|
2810 |
-
helpers.doKeys('.');
|
2811 |
-
eq('tetettetetee', cm.getValue());
|
2812 |
-
helpers.assertCursorAt(0, 10);
|
2813 |
-
}, { value: ''});
|
2814 |
-
testVim('._insert_o', function(cm, vim, helpers) {
|
2815 |
-
helpers.doKeys('o');
|
2816 |
-
cm.replaceRange('z', cm.getCursor());
|
2817 |
-
cm.setCursor(1, 1);
|
2818 |
-
helpers.doKeys('<Esc>');
|
2819 |
-
helpers.doKeys('.');
|
2820 |
-
eq('\nz\nz', cm.getValue());
|
2821 |
-
helpers.assertCursorAt(2, 0);
|
2822 |
-
}, { value: ''});
|
2823 |
-
testVim('._insert_o_repeat', function(cm, vim, helpers) {
|
2824 |
-
helpers.doKeys('o');
|
2825 |
-
cm.replaceRange('z', cm.getCursor());
|
2826 |
-
helpers.doKeys('<Esc>');
|
2827 |
-
cm.setCursor(1, 0);
|
2828 |
-
helpers.doKeys('2', '.');
|
2829 |
-
eq('\nz\nz\nz', cm.getValue());
|
2830 |
-
helpers.assertCursorAt(3, 0);
|
2831 |
-
}, { value: ''});
|
2832 |
-
testVim('._insert_o_indent', function(cm, vim, helpers) {
|
2833 |
-
helpers.doKeys('o');
|
2834 |
-
cm.replaceRange('z', cm.getCursor());
|
2835 |
-
helpers.doKeys('<Esc>');
|
2836 |
-
cm.setCursor(1, 2);
|
2837 |
-
helpers.doKeys('.');
|
2838 |
-
eq('{\n z\n z', cm.getValue());
|
2839 |
-
helpers.assertCursorAt(2, 2);
|
2840 |
-
}, { value: '{'});
|
2841 |
-
testVim('._insert_cw', function(cm, vim, helpers) {
|
2842 |
-
helpers.doKeys('c', 'w');
|
2843 |
-
cm.replaceRange('test', cm.getCursor());
|
2844 |
-
helpers.doKeys('<Esc>');
|
2845 |
-
cm.setCursor(0, 3);
|
2846 |
-
helpers.doKeys('2', 'l');
|
2847 |
-
helpers.doKeys('.');
|
2848 |
-
eq('test test word3', cm.getValue());
|
2849 |
-
helpers.assertCursorAt(0, 8);
|
2850 |
-
}, { value: 'word1 word2 word3' });
|
2851 |
-
testVim('._insert_cw_repeat', function(cm, vim, helpers) {
|
2852 |
-
// For some reason, repeat cw in desktop VIM will does not repeat insert mode
|
2853 |
-
// changes. Will conform to that behavior.
|
2854 |
-
helpers.doKeys('c', 'w');
|
2855 |
-
cm.replaceRange('test', cm.getCursor());
|
2856 |
-
helpers.doKeys('<Esc>');
|
2857 |
-
cm.setCursor(0, 4);
|
2858 |
-
helpers.doKeys('l');
|
2859 |
-
helpers.doKeys('2', '.');
|
2860 |
-
eq('test test', cm.getValue());
|
2861 |
-
helpers.assertCursorAt(0, 8);
|
2862 |
-
}, { value: 'word1 word2 word3' });
|
2863 |
-
testVim('._delete', function(cm, vim, helpers) {
|
2864 |
-
cm.setCursor(0, 5);
|
2865 |
-
helpers.doKeys('i');
|
2866 |
-
helpers.doInsertModeKeys('Backspace');
|
2867 |
-
helpers.doKeys('<Esc>');
|
2868 |
-
helpers.doKeys('.');
|
2869 |
-
eq('zace', cm.getValue());
|
2870 |
-
helpers.assertCursorAt(0, 1);
|
2871 |
-
}, { value: 'zabcde'});
|
2872 |
-
testVim('._delete_repeat', function(cm, vim, helpers) {
|
2873 |
-
cm.setCursor(0, 6);
|
2874 |
-
helpers.doKeys('i');
|
2875 |
-
helpers.doInsertModeKeys('Backspace');
|
2876 |
-
helpers.doKeys('<Esc>');
|
2877 |
-
helpers.doKeys('2', '.');
|
2878 |
-
eq('zzce', cm.getValue());
|
2879 |
-
helpers.assertCursorAt(0, 1);
|
2880 |
-
}, { value: 'zzabcde'});
|
2881 |
-
testVim('._visual_>', function(cm, vim, helpers) {
|
2882 |
-
cm.setCursor(0, 0);
|
2883 |
-
helpers.doKeys('V', 'j', '>');
|
2884 |
-
cm.setCursor(2, 0)
|
2885 |
-
helpers.doKeys('.');
|
2886 |
-
eq(' 1\n 2\n 3\n 4', cm.getValue());
|
2887 |
-
helpers.assertCursorAt(2, 2);
|
2888 |
-
}, { value: '1\n2\n3\n4'});
|
2889 |
-
testVim('f;', function(cm, vim, helpers) {
|
2890 |
-
cm.setCursor(0, 0);
|
2891 |
-
helpers.doKeys('f', 'x');
|
2892 |
-
helpers.doKeys(';');
|
2893 |
-
helpers.doKeys('2', ';');
|
2894 |
-
eq(9, cm.getCursor().ch);
|
2895 |
-
}, { value: '01x3xx678x'});
|
2896 |
-
testVim('F;', function(cm, vim, helpers) {
|
2897 |
-
cm.setCursor(0, 8);
|
2898 |
-
helpers.doKeys('F', 'x');
|
2899 |
-
helpers.doKeys(';');
|
2900 |
-
helpers.doKeys('2', ';');
|
2901 |
-
eq(2, cm.getCursor().ch);
|
2902 |
-
}, { value: '01x3xx6x8x'});
|
2903 |
-
testVim('t;', function(cm, vim, helpers) {
|
2904 |
-
cm.setCursor(0, 0);
|
2905 |
-
helpers.doKeys('t', 'x');
|
2906 |
-
helpers.doKeys(';');
|
2907 |
-
helpers.doKeys('2', ';');
|
2908 |
-
eq(8, cm.getCursor().ch);
|
2909 |
-
}, { value: '01x3xx678x'});
|
2910 |
-
testVim('T;', function(cm, vim, helpers) {
|
2911 |
-
cm.setCursor(0, 9);
|
2912 |
-
helpers.doKeys('T', 'x');
|
2913 |
-
helpers.doKeys(';');
|
2914 |
-
helpers.doKeys('2', ';');
|
2915 |
-
eq(2, cm.getCursor().ch);
|
2916 |
-
}, { value: '0xx3xx678x'});
|
2917 |
-
testVim('f,', function(cm, vim, helpers) {
|
2918 |
-
cm.setCursor(0, 6);
|
2919 |
-
helpers.doKeys('f', 'x');
|
2920 |
-
helpers.doKeys(',');
|
2921 |
-
helpers.doKeys('2', ',');
|
2922 |
-
eq(2, cm.getCursor().ch);
|
2923 |
-
}, { value: '01x3xx678x'});
|
2924 |
-
testVim('F,', function(cm, vim, helpers) {
|
2925 |
-
cm.setCursor(0, 3);
|
2926 |
-
helpers.doKeys('F', 'x');
|
2927 |
-
helpers.doKeys(',');
|
2928 |
-
helpers.doKeys('2', ',');
|
2929 |
-
eq(9, cm.getCursor().ch);
|
2930 |
-
}, { value: '01x3xx678x'});
|
2931 |
-
testVim('t,', function(cm, vim, helpers) {
|
2932 |
-
cm.setCursor(0, 6);
|
2933 |
-
helpers.doKeys('t', 'x');
|
2934 |
-
helpers.doKeys(',');
|
2935 |
-
helpers.doKeys('2', ',');
|
2936 |
-
eq(3, cm.getCursor().ch);
|
2937 |
-
}, { value: '01x3xx678x'});
|
2938 |
-
testVim('T,', function(cm, vim, helpers) {
|
2939 |
-
cm.setCursor(0, 4);
|
2940 |
-
helpers.doKeys('T', 'x');
|
2941 |
-
helpers.doKeys(',');
|
2942 |
-
helpers.doKeys('2', ',');
|
2943 |
-
eq(8, cm.getCursor().ch);
|
2944 |
-
}, { value: '01x3xx67xx'});
|
2945 |
-
testVim('fd,;', function(cm, vim, helpers) {
|
2946 |
-
cm.setCursor(0, 0);
|
2947 |
-
helpers.doKeys('f', '4');
|
2948 |
-
cm.setCursor(0, 0);
|
2949 |
-
helpers.doKeys('d', ';');
|
2950 |
-
eq('56789', cm.getValue());
|
2951 |
-
helpers.doKeys('u');
|
2952 |
-
cm.setCursor(0, 9);
|
2953 |
-
helpers.doKeys('d', ',');
|
2954 |
-
eq('01239', cm.getValue());
|
2955 |
-
}, { value: '0123456789'});
|
2956 |
-
testVim('Fd,;', function(cm, vim, helpers) {
|
2957 |
-
cm.setCursor(0, 9);
|
2958 |
-
helpers.doKeys('F', '4');
|
2959 |
-
cm.setCursor(0, 9);
|
2960 |
-
helpers.doKeys('d', ';');
|
2961 |
-
eq('01239', cm.getValue());
|
2962 |
-
helpers.doKeys('u');
|
2963 |
-
cm.setCursor(0, 0);
|
2964 |
-
helpers.doKeys('d', ',');
|
2965 |
-
eq('56789', cm.getValue());
|
2966 |
-
}, { value: '0123456789'});
|
2967 |
-
testVim('td,;', function(cm, vim, helpers) {
|
2968 |
-
cm.setCursor(0, 0);
|
2969 |
-
helpers.doKeys('t', '4');
|
2970 |
-
cm.setCursor(0, 0);
|
2971 |
-
helpers.doKeys('d', ';');
|
2972 |
-
eq('456789', cm.getValue());
|
2973 |
-
helpers.doKeys('u');
|
2974 |
-
cm.setCursor(0, 9);
|
2975 |
-
helpers.doKeys('d', ',');
|
2976 |
-
eq('012349', cm.getValue());
|
2977 |
-
}, { value: '0123456789'});
|
2978 |
-
testVim('Td,;', function(cm, vim, helpers) {
|
2979 |
-
cm.setCursor(0, 9);
|
2980 |
-
helpers.doKeys('T', '4');
|
2981 |
-
cm.setCursor(0, 9);
|
2982 |
-
helpers.doKeys('d', ';');
|
2983 |
-
eq('012349', cm.getValue());
|
2984 |
-
helpers.doKeys('u');
|
2985 |
-
cm.setCursor(0, 0);
|
2986 |
-
helpers.doKeys('d', ',');
|
2987 |
-
eq('456789', cm.getValue());
|
2988 |
-
}, { value: '0123456789'});
|
2989 |
-
testVim('fc,;', function(cm, vim, helpers) {
|
2990 |
-
cm.setCursor(0, 0);
|
2991 |
-
helpers.doKeys('f', '4');
|
2992 |
-
cm.setCursor(0, 0);
|
2993 |
-
helpers.doKeys('c', ';', '<Esc>');
|
2994 |
-
eq('56789', cm.getValue());
|
2995 |
-
helpers.doKeys('u');
|
2996 |
-
cm.setCursor(0, 9);
|
2997 |
-
helpers.doKeys('c', ',');
|
2998 |
-
eq('01239', cm.getValue());
|
2999 |
-
}, { value: '0123456789'});
|
3000 |
-
testVim('Fc,;', function(cm, vim, helpers) {
|
3001 |
-
cm.setCursor(0, 9);
|
3002 |
-
helpers.doKeys('F', '4');
|
3003 |
-
cm.setCursor(0, 9);
|
3004 |
-
helpers.doKeys('c', ';', '<Esc>');
|
3005 |
-
eq('01239', cm.getValue());
|
3006 |
-
helpers.doKeys('u');
|
3007 |
-
cm.setCursor(0, 0);
|
3008 |
-
helpers.doKeys('c', ',');
|
3009 |
-
eq('56789', cm.getValue());
|
3010 |
-
}, { value: '0123456789'});
|
3011 |
-
testVim('tc,;', function(cm, vim, helpers) {
|
3012 |
-
cm.setCursor(0, 0);
|
3013 |
-
helpers.doKeys('t', '4');
|
3014 |
-
cm.setCursor(0, 0);
|
3015 |
-
helpers.doKeys('c', ';', '<Esc>');
|
3016 |
-
eq('456789', cm.getValue());
|
3017 |
-
helpers.doKeys('u');
|
3018 |
-
cm.setCursor(0, 9);
|
3019 |
-
helpers.doKeys('c', ',');
|
3020 |
-
eq('012349', cm.getValue());
|
3021 |
-
}, { value: '0123456789'});
|
3022 |
-
testVim('Tc,;', function(cm, vim, helpers) {
|
3023 |
-
cm.setCursor(0, 9);
|
3024 |
-
helpers.doKeys('T', '4');
|
3025 |
-
cm.setCursor(0, 9);
|
3026 |
-
helpers.doKeys('c', ';', '<Esc>');
|
3027 |
-
eq('012349', cm.getValue());
|
3028 |
-
helpers.doKeys('u');
|
3029 |
-
cm.setCursor(0, 0);
|
3030 |
-
helpers.doKeys('c', ',');
|
3031 |
-
eq('456789', cm.getValue());
|
3032 |
-
}, { value: '0123456789'});
|
3033 |
-
testVim('fy,;', function(cm, vim, helpers) {
|
3034 |
-
cm.setCursor(0, 0);
|
3035 |
-
helpers.doKeys('f', '4');
|
3036 |
-
cm.setCursor(0, 0);
|
3037 |
-
helpers.doKeys('y', ';', 'P');
|
3038 |
-
eq('012340123456789', cm.getValue());
|
3039 |
-
helpers.doKeys('u');
|
3040 |
-
cm.setCursor(0, 9);
|
3041 |
-
helpers.doKeys('y', ',', 'P');
|
3042 |
-
eq('012345678456789', cm.getValue());
|
3043 |
-
}, { value: '0123456789'});
|
3044 |
-
testVim('Fy,;', function(cm, vim, helpers) {
|
3045 |
-
cm.setCursor(0, 9);
|
3046 |
-
helpers.doKeys('F', '4');
|
3047 |
-
cm.setCursor(0, 9);
|
3048 |
-
helpers.doKeys('y', ';', 'p');
|
3049 |
-
eq('012345678945678', cm.getValue());
|
3050 |
-
helpers.doKeys('u');
|
3051 |
-
cm.setCursor(0, 0);
|
3052 |
-
helpers.doKeys('y', ',', 'P');
|
3053 |
-
eq('012340123456789', cm.getValue());
|
3054 |
-
}, { value: '0123456789'});
|
3055 |
-
testVim('ty,;', function(cm, vim, helpers) {
|
3056 |
-
cm.setCursor(0, 0);
|
3057 |
-
helpers.doKeys('t', '4');
|
3058 |
-
cm.setCursor(0, 0);
|
3059 |
-
helpers.doKeys('y', ';', 'P');
|
3060 |
-
eq('01230123456789', cm.getValue());
|
3061 |
-
helpers.doKeys('u');
|
3062 |
-
cm.setCursor(0, 9);
|
3063 |
-
helpers.doKeys('y', ',', 'p');
|
3064 |
-
eq('01234567895678', cm.getValue());
|
3065 |
-
}, { value: '0123456789'});
|
3066 |
-
testVim('Ty,;', function(cm, vim, helpers) {
|
3067 |
-
cm.setCursor(0, 9);
|
3068 |
-
helpers.doKeys('T', '4');
|
3069 |
-
cm.setCursor(0, 9);
|
3070 |
-
helpers.doKeys('y', ';', 'p');
|
3071 |
-
eq('01234567895678', cm.getValue());
|
3072 |
-
helpers.doKeys('u');
|
3073 |
-
cm.setCursor(0, 0);
|
3074 |
-
helpers.doKeys('y', ',', 'P');
|
3075 |
-
eq('01230123456789', cm.getValue());
|
3076 |
-
}, { value: '0123456789'});
|
3077 |
-
testVim('HML', function(cm, vim, helpers) {
|
3078 |
-
var lines = 35;
|
3079 |
-
var textHeight = cm.defaultTextHeight();
|
3080 |
-
cm.setSize(600, lines*textHeight);
|
3081 |
-
cm.setCursor(120, 0);
|
3082 |
-
helpers.doKeys('H');
|
3083 |
-
helpers.assertCursorAt(86, 2);
|
3084 |
-
helpers.doKeys('L');
|
3085 |
-
helpers.assertCursorAt(120, 4);
|
3086 |
-
helpers.doKeys('M');
|
3087 |
-
helpers.assertCursorAt(103,4);
|
3088 |
-
}, { value: (function(){
|
3089 |
-
var lines = new Array(100);
|
3090 |
-
var upper = ' xx\n';
|
3091 |
-
var lower = ' xx\n';
|
3092 |
-
upper = lines.join(upper);
|
3093 |
-
lower = lines.join(lower);
|
3094 |
-
return upper + lower;
|
3095 |
-
})()});
|
3096 |
-
|
3097 |
-
var zVals = [];
|
3098 |
-
forEach(['zb','zz','zt','z-','z.','z<CR>'], function(e, idx){
|
3099 |
-
var lineNum = 250;
|
3100 |
-
var lines = 35;
|
3101 |
-
testVim(e, function(cm, vim, helpers) {
|
3102 |
-
var k1 = e[0];
|
3103 |
-
var k2 = e.substring(1);
|
3104 |
-
var textHeight = cm.defaultTextHeight();
|
3105 |
-
cm.setSize(600, lines*textHeight);
|
3106 |
-
cm.setCursor(lineNum, 0);
|
3107 |
-
helpers.doKeys(k1, k2);
|
3108 |
-
zVals[idx] = cm.getScrollInfo().top;
|
3109 |
-
}, { value: (function(){
|
3110 |
-
return new Array(500).join('\n');
|
3111 |
-
})()});
|
3112 |
-
});
|
3113 |
-
testVim('zb<zz', function(cm, vim, helpers){
|
3114 |
-
eq(zVals[0]<zVals[1], true);
|
3115 |
-
});
|
3116 |
-
testVim('zz<zt', function(cm, vim, helpers){
|
3117 |
-
eq(zVals[1]<zVals[2], true);
|
3118 |
-
});
|
3119 |
-
testVim('zb==z-', function(cm, vim, helpers){
|
3120 |
-
eq(zVals[0], zVals[3]);
|
3121 |
-
});
|
3122 |
-
testVim('zz==z.', function(cm, vim, helpers){
|
3123 |
-
eq(zVals[1], zVals[4]);
|
3124 |
-
});
|
3125 |
-
testVim('zt==z<CR>', function(cm, vim, helpers){
|
3126 |
-
eq(zVals[2], zVals[5]);
|
3127 |
-
});
|
3128 |
-
|
3129 |
-
var moveTillCharacterSandbox =
|
3130 |
-
'The quick brown fox \n';
|
3131 |
-
testVim('moveTillCharacter', function(cm, vim, helpers){
|
3132 |
-
cm.setCursor(0, 0);
|
3133 |
-
// Search for the 'q'.
|
3134 |
-
cm.openDialog = helpers.fakeOpenDialog('q');
|
3135 |
-
helpers.doKeys('/');
|
3136 |
-
eq(4, cm.getCursor().ch);
|
3137 |
-
// Jump to just before the first o in the list.
|
3138 |
-
helpers.doKeys('t');
|
3139 |
-
helpers.doKeys('o');
|
3140 |
-
eq('The quick brown fox \n', cm.getValue());
|
3141 |
-
// Delete that one character.
|
3142 |
-
helpers.doKeys('d');
|
3143 |
-
helpers.doKeys('t');
|
3144 |
-
helpers.doKeys('o');
|
3145 |
-
eq('The quick bown fox \n', cm.getValue());
|
3146 |
-
// Delete everything until the next 'o'.
|
3147 |
-
helpers.doKeys('.');
|
3148 |
-
eq('The quick box \n', cm.getValue());
|
3149 |
-
// An unmatched character should have no effect.
|
3150 |
-
helpers.doKeys('d');
|
3151 |
-
helpers.doKeys('t');
|
3152 |
-
helpers.doKeys('q');
|
3153 |
-
eq('The quick box \n', cm.getValue());
|
3154 |
-
// Matches should only be possible on single lines.
|
3155 |
-
helpers.doKeys('d');
|
3156 |
-
helpers.doKeys('t');
|
3157 |
-
helpers.doKeys('z');
|
3158 |
-
eq('The quick box \n', cm.getValue());
|
3159 |
-
// After all that, the search for 'q' should still be active, so the 'N' command
|
3160 |
-
// can run it again in reverse. Use that to delete everything back to the 'q'.
|
3161 |
-
helpers.doKeys('d');
|
3162 |
-
helpers.doKeys('N');
|
3163 |
-
eq('The ox \n', cm.getValue());
|
3164 |
-
eq(4, cm.getCursor().ch);
|
3165 |
-
}, { value: moveTillCharacterSandbox});
|
3166 |
-
testVim('searchForPipe', function(cm, vim, helpers){
|
3167 |
-
CodeMirror.Vim.setOption('pcre', false);
|
3168 |
-
cm.setCursor(0, 0);
|
3169 |
-
// Search for the '|'.
|
3170 |
-
cm.openDialog = helpers.fakeOpenDialog('|');
|
3171 |
-
helpers.doKeys('/');
|
3172 |
-
eq(4, cm.getCursor().ch);
|
3173 |
-
}, { value: 'this|that'});
|
3174 |
-
|
3175 |
-
|
3176 |
-
var scrollMotionSandbox =
|
3177 |
-
'\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n';
|
3178 |
-
testVim('scrollMotion', function(cm, vim, helpers){
|
3179 |
-
var prevCursor, prevScrollInfo;
|
3180 |
-
cm.setCursor(0, 0);
|
3181 |
-
// ctrl-y at the top of the file should have no effect.
|
3182 |
-
helpers.doKeys('<C-y>');
|
3183 |
-
eq(0, cm.getCursor().line);
|
3184 |
-
prevScrollInfo = cm.getScrollInfo();
|
3185 |
-
helpers.doKeys('<C-e>');
|
3186 |
-
eq(1, cm.getCursor().line);
|
3187 |
-
is(prevScrollInfo.top < cm.getScrollInfo().top);
|
3188 |
-
// Jump to the end of the sandbox.
|
3189 |
-
cm.setCursor(1000, 0);
|
3190 |
-
prevCursor = cm.getCursor();
|
3191 |
-
// ctrl-e at the bottom of the file should have no effect.
|
3192 |
-
helpers.doKeys('<C-e>');
|
3193 |
-
eq(prevCursor.line, cm.getCursor().line);
|
3194 |
-
prevScrollInfo = cm.getScrollInfo();
|
3195 |
-
helpers.doKeys('<C-y>');
|
3196 |
-
eq(prevCursor.line - 1, cm.getCursor().line, "Y");
|
3197 |
-
is(prevScrollInfo.top > cm.getScrollInfo().top);
|
3198 |
-
}, { value: scrollMotionSandbox});
|
3199 |
-
|
3200 |
-
var squareBracketMotionSandbox = ''+
|
3201 |
-
'({\n'+//0
|
3202 |
-
' ({\n'+//11
|
3203 |
-
' /*comment {\n'+//2
|
3204 |
-
' */(\n'+//3
|
3205 |
-
'#else \n'+//4
|
3206 |
-
' /* )\n'+//5
|
3207 |
-
'#if }\n'+//6
|
3208 |
-
' )}*/\n'+//7
|
3209 |
-
')}\n'+//8
|
3210 |
-
'{}\n'+//9
|
3211 |
-
'#else {{\n'+//10
|
3212 |
-
'{}\n'+//11
|
3213 |
-
'}\n'+//12
|
3214 |
-
'{\n'+//13
|
3215 |
-
'#endif\n'+//14
|
3216 |
-
'}\n'+//15
|
3217 |
-
'}\n'+//16
|
3218 |
-
'#else';//17
|
3219 |
-
testVim('[[, ]]', function(cm, vim, helpers) {
|
3220 |
-
cm.setCursor(0, 0);
|
3221 |
-
helpers.doKeys(']', ']');
|
3222 |
-
helpers.assertCursorAt(9,0);
|
3223 |
-
helpers.doKeys('2', ']', ']');
|
3224 |
-
helpers.assertCursorAt(13,0);
|
3225 |
-
helpers.doKeys(']', ']');
|
3226 |
-
helpers.assertCursorAt(17,0);
|
3227 |
-
helpers.doKeys('[', '[');
|
3228 |
-
helpers.assertCursorAt(13,0);
|
3229 |
-
helpers.doKeys('2', '[', '[');
|
3230 |
-
helpers.assertCursorAt(9,0);
|
3231 |
-
helpers.doKeys('[', '[');
|
3232 |
-
helpers.assertCursorAt(0,0);
|
3233 |
-
}, { value: squareBracketMotionSandbox});
|
3234 |
-
testVim('[], ][', function(cm, vim, helpers) {
|
3235 |
-
cm.setCursor(0, 0);
|
3236 |
-
helpers.doKeys(']', '[');
|
3237 |
-
helpers.assertCursorAt(12,0);
|
3238 |
-
helpers.doKeys('2', ']', '[');
|
3239 |
-
helpers.assertCursorAt(16,0);
|
3240 |
-
helpers.doKeys(']', '[');
|
3241 |
-
helpers.assertCursorAt(17,0);
|
3242 |
-
helpers.doKeys('[', ']');
|
3243 |
-
helpers.assertCursorAt(16,0);
|
3244 |
-
helpers.doKeys('2', '[', ']');
|
3245 |
-
helpers.assertCursorAt(12,0);
|
3246 |
-
helpers.doKeys('[', ']');
|
3247 |
-
helpers.assertCursorAt(0,0);
|
3248 |
-
}, { value: squareBracketMotionSandbox});
|
3249 |
-
testVim('[{, ]}', function(cm, vim, helpers) {
|
3250 |
-
cm.setCursor(4, 10);
|
3251 |
-
helpers.doKeys('[', '{');
|
3252 |
-
helpers.assertCursorAt(2,12);
|
3253 |
-
helpers.doKeys('2', '[', '{');
|
3254 |
-
helpers.assertCursorAt(0,1);
|
3255 |
-
cm.setCursor(4, 10);
|
3256 |
-
helpers.doKeys(']', '}');
|
3257 |
-
helpers.assertCursorAt(6,11);
|
3258 |
-
helpers.doKeys('2', ']', '}');
|
3259 |
-
helpers.assertCursorAt(8,1);
|
3260 |
-
cm.setCursor(0,1);
|
3261 |
-
helpers.doKeys(']', '}');
|
3262 |
-
helpers.assertCursorAt(8,1);
|
3263 |
-
helpers.doKeys('[', '{');
|
3264 |
-
helpers.assertCursorAt(0,1);
|
3265 |
-
}, { value: squareBracketMotionSandbox});
|
3266 |
-
testVim('[(, ])', function(cm, vim, helpers) {
|
3267 |
-
cm.setCursor(4, 10);
|
3268 |
-
helpers.doKeys('[', '(');
|
3269 |
-
helpers.assertCursorAt(3,14);
|
3270 |
-
helpers.doKeys('2', '[', '(');
|
3271 |
-
helpers.assertCursorAt(0,0);
|
3272 |
-
cm.setCursor(4, 10);
|
3273 |
-
helpers.doKeys(']', ')');
|
3274 |
-
helpers.assertCursorAt(5,11);
|
3275 |
-
helpers.doKeys('2', ']', ')');
|
3276 |
-
helpers.assertCursorAt(8,0);
|
3277 |
-
helpers.doKeys('[', '(');
|
3278 |
-
helpers.assertCursorAt(0,0);
|
3279 |
-
helpers.doKeys(']', ')');
|
3280 |
-
helpers.assertCursorAt(8,0);
|
3281 |
-
}, { value: squareBracketMotionSandbox});
|
3282 |
-
testVim('[*, ]*, [/, ]/', function(cm, vim, helpers) {
|
3283 |
-
forEach(['*', '/'], function(key){
|
3284 |
-
cm.setCursor(7, 0);
|
3285 |
-
helpers.doKeys('2', '[', key);
|
3286 |
-
helpers.assertCursorAt(2,2);
|
3287 |
-
helpers.doKeys('2', ']', key);
|
3288 |
-
helpers.assertCursorAt(7,5);
|
3289 |
-
});
|
3290 |
-
}, { value: squareBracketMotionSandbox});
|
3291 |
-
testVim('[#, ]#', function(cm, vim, helpers) {
|
3292 |
-
cm.setCursor(10, 3);
|
3293 |
-
helpers.doKeys('2', '[', '#');
|
3294 |
-
helpers.assertCursorAt(4,0);
|
3295 |
-
helpers.doKeys('5', ']', '#');
|
3296 |
-
helpers.assertCursorAt(17,0);
|
3297 |
-
cm.setCursor(10, 3);
|
3298 |
-
helpers.doKeys(']', '#');
|
3299 |
-
helpers.assertCursorAt(14,0);
|
3300 |
-
}, { value: squareBracketMotionSandbox});
|
3301 |
-
testVim('[m, ]m, [M, ]M', function(cm, vim, helpers) {
|
3302 |
-
cm.setCursor(11, 0);
|
3303 |
-
helpers.doKeys('[', 'm');
|
3304 |
-
helpers.assertCursorAt(10,7);
|
3305 |
-
helpers.doKeys('4', '[', 'm');
|
3306 |
-
helpers.assertCursorAt(1,3);
|
3307 |
-
helpers.doKeys('5', ']', 'm');
|
3308 |
-
helpers.assertCursorAt(11,0);
|
3309 |
-
helpers.doKeys('[', 'M');
|
3310 |
-
helpers.assertCursorAt(9,1);
|
3311 |
-
helpers.doKeys('3', ']', 'M');
|
3312 |
-
helpers.assertCursorAt(15,0);
|
3313 |
-
helpers.doKeys('5', '[', 'M');
|
3314 |
-
helpers.assertCursorAt(7,3);
|
3315 |
-
}, { value: squareBracketMotionSandbox});
|
3316 |
-
|
3317 |
-
// Ex mode tests
|
3318 |
-
testVim('ex_go_to_line', function(cm, vim, helpers) {
|
3319 |
-
cm.setCursor(0, 0);
|
3320 |
-
helpers.doEx('4');
|
3321 |
-
helpers.assertCursorAt(3, 0);
|
3322 |
-
}, { value: 'a\nb\nc\nd\ne\n'});
|
3323 |
-
testVim('ex_write', function(cm, vim, helpers) {
|
3324 |
-
var tmp = CodeMirror.commands.save;
|
3325 |
-
var written;
|
3326 |
-
var actualCm;
|
3327 |
-
CodeMirror.commands.save = function(cm) {
|
3328 |
-
written = true;
|
3329 |
-
actualCm = cm;
|
3330 |
-
};
|
3331 |
-
// Test that w, wr, wri ... write all trigger :write.
|
3332 |
-
var command = 'write';
|
3333 |
-
for (var i = 1; i < command.length; i++) {
|
3334 |
-
written = false;
|
3335 |
-
actualCm = null;
|
3336 |
-
helpers.doEx(command.substring(0, i));
|
3337 |
-
eq(written, true);
|
3338 |
-
eq(actualCm, cm);
|
3339 |
-
}
|
3340 |
-
CodeMirror.commands.save = tmp;
|
3341 |
-
});
|
3342 |
-
testVim('ex_sort', function(cm, vim, helpers) {
|
3343 |
-
helpers.doEx('sort');
|
3344 |
-
eq('Z\na\nb\nc\nd', cm.getValue());
|
3345 |
-
}, { value: 'b\nZ\nd\nc\na'});
|
3346 |
-
testVim('ex_sort_reverse', function(cm, vim, helpers) {
|
3347 |
-
helpers.doEx('sort!');
|
3348 |
-
eq('d\nc\nb\na', cm.getValue());
|
3349 |
-
}, { value: 'b\nd\nc\na'});
|
3350 |
-
testVim('ex_sort_range', function(cm, vim, helpers) {
|
3351 |
-
helpers.doEx('2,3sort');
|
3352 |
-
eq('b\nc\nd\na', cm.getValue());
|
3353 |
-
}, { value: 'b\nd\nc\na'});
|
3354 |
-
testVim('ex_sort_oneline', function(cm, vim, helpers) {
|
3355 |
-
helpers.doEx('2sort');
|
3356 |
-
// Expect no change.
|
3357 |
-
eq('b\nd\nc\na', cm.getValue());
|
3358 |
-
}, { value: 'b\nd\nc\na'});
|
3359 |
-
testVim('ex_sort_ignoreCase', function(cm, vim, helpers) {
|
3360 |
-
helpers.doEx('sort i');
|
3361 |
-
eq('a\nb\nc\nd\nZ', cm.getValue());
|
3362 |
-
}, { value: 'b\nZ\nd\nc\na'});
|
3363 |
-
testVim('ex_sort_unique', function(cm, vim, helpers) {
|
3364 |
-
helpers.doEx('sort u');
|
3365 |
-
eq('Z\na\nb\nc\nd', cm.getValue());
|
3366 |
-
}, { value: 'b\nZ\na\na\nd\na\nc\na'});
|
3367 |
-
testVim('ex_sort_decimal', function(cm, vim, helpers) {
|
3368 |
-
helpers.doEx('sort d');
|
3369 |
-
eq('d3\n s5\n6\n.9', cm.getValue());
|
3370 |
-
}, { value: '6\nd3\n s5\n.9'});
|
3371 |
-
testVim('ex_sort_decimal_negative', function(cm, vim, helpers) {
|
3372 |
-
helpers.doEx('sort d');
|
3373 |
-
eq('z-9\nd3\n s5\n6\n.9', cm.getValue());
|
3374 |
-
}, { value: '6\nd3\n s5\n.9\nz-9'});
|
3375 |
-
testVim('ex_sort_decimal_reverse', function(cm, vim, helpers) {
|
3376 |
-
helpers.doEx('sort! d');
|
3377 |
-
eq('.9\n6\n s5\nd3', cm.getValue());
|
3378 |
-
}, { value: '6\nd3\n s5\n.9'});
|
3379 |
-
testVim('ex_sort_hex', function(cm, vim, helpers) {
|
3380 |
-
helpers.doEx('sort x');
|
3381 |
-
eq(' s5\n6\n.9\n&0xB\nd3', cm.getValue());
|
3382 |
-
}, { value: '6\nd3\n s5\n&0xB\n.9'});
|
3383 |
-
testVim('ex_sort_octal', function(cm, vim, helpers) {
|
3384 |
-
helpers.doEx('sort o');
|
3385 |
-
eq('.8\n.9\nd3\n s5\n6', cm.getValue());
|
3386 |
-
}, { value: '6\nd3\n s5\n.9\n.8'});
|
3387 |
-
testVim('ex_sort_decimal_mixed', function(cm, vim, helpers) {
|
3388 |
-
helpers.doEx('sort d');
|
3389 |
-
eq('y\nz\nc1\nb2\na3', cm.getValue());
|
3390 |
-
}, { value: 'a3\nz\nc1\ny\nb2'});
|
3391 |
-
testVim('ex_sort_decimal_mixed_reverse', function(cm, vim, helpers) {
|
3392 |
-
helpers.doEx('sort! d');
|
3393 |
-
eq('a3\nb2\nc1\nz\ny', cm.getValue());
|
3394 |
-
}, { value: 'a3\nz\nc1\ny\nb2'});
|
3395 |
-
testVim('ex_sort_patterns_not_supported', function(cm, vim, helpers) {
|
3396 |
-
var notified = false;
|
3397 |
-
cm.openNotification = helpers.fakeOpenNotification(function(text) {
|
3398 |
-
notified = /patterns not supported/.test(text);
|
3399 |
-
});
|
3400 |
-
helpers.doEx('sort /abc/');
|
3401 |
-
is(notified, 'No notification.');
|
3402 |
-
});
|
3403 |
-
// test for :global command
|
3404 |
-
testVim('ex_global', function(cm, vim, helpers) {
|
3405 |
-
cm.setCursor(0, 0);
|
3406 |
-
helpers.doEx('g/one/s//two');
|
3407 |
-
eq('two two\n two two\n two two', cm.getValue());
|
3408 |
-
helpers.doEx('1,2g/two/s//one');
|
3409 |
-
eq('one one\n one one\n two two', cm.getValue());
|
3410 |
-
}, {value: 'one one\n one one\n one one'});
|
3411 |
-
testVim('ex_global_confirm', function(cm, vim, helpers) {
|
3412 |
-
cm.setCursor(0, 0);
|
3413 |
-
var onKeyDown;
|
3414 |
-
var openDialogSave = cm.openDialog;
|
3415 |
-
var KEYCODES = {
|
3416 |
-
a: 65,
|
3417 |
-
n: 78,
|
3418 |
-
q: 81,
|
3419 |
-
y: 89
|
3420 |
-
};
|
3421 |
-
// Intercept the ex command, 'global'
|
3422 |
-
cm.openDialog = function(template, callback, options) {
|
3423 |
-
// Intercept the prompt for the embedded ex command, 'substitute'
|
3424 |
-
cm.openDialog = function(template, callback, options) {
|
3425 |
-
onKeyDown = options.onKeyDown;
|
3426 |
-
};
|
3427 |
-
callback('g/one/s//two/gc');
|
3428 |
-
};
|
3429 |
-
helpers.doKeys(':');
|
3430 |
-
var close = function() {};
|
3431 |
-
onKeyDown({keyCode: KEYCODES.n}, '', close);
|
3432 |
-
onKeyDown({keyCode: KEYCODES.y}, '', close);
|
3433 |
-
onKeyDown({keyCode: KEYCODES.a}, '', close);
|
3434 |
-
onKeyDown({keyCode: KEYCODES.q}, '', close);
|
3435 |
-
onKeyDown({keyCode: KEYCODES.y}, '', close);
|
3436 |
-
eq('one two\n two two\n one one\n two one\n one one', cm.getValue());
|
3437 |
-
}, {value: 'one one\n one one\n one one\n one one\n one one'});
|
3438 |
-
// Basic substitute tests.
|
3439 |
-
testVim('ex_substitute_same_line', function(cm, vim, helpers) {
|
3440 |
-
cm.setCursor(1, 0);
|
3441 |
-
helpers.doEx('s/one/two/g');
|
3442 |
-
eq('one one\n two two', cm.getValue());
|
3443 |
-
}, { value: 'one one\n one one'});
|
3444 |
-
testVim('ex_substitute_full_file', function(cm, vim, helpers) {
|
3445 |
-
cm.setCursor(1, 0);
|
3446 |
-
helpers.doEx('%s/one/two/g');
|
3447 |
-
eq('two two\n two two', cm.getValue());
|
3448 |
-
}, { value: 'one one\n one one'});
|
3449 |
-
testVim('ex_substitute_input_range', function(cm, vim, helpers) {
|
3450 |
-
cm.setCursor(1, 0);
|
3451 |
-
helpers.doEx('1,3s/\\d/0/g');
|
3452 |
-
eq('0\n0\n0\n4', cm.getValue());
|
3453 |
-
}, { value: '1\n2\n3\n4' });
|
3454 |
-
testVim('ex_substitute_visual_range', function(cm, vim, helpers) {
|
3455 |
-
cm.setCursor(1, 0);
|
3456 |
-
// Set last visual mode selection marks '< and '> at lines 2 and 4
|
3457 |
-
helpers.doKeys('V', '2', 'j', 'v');
|
3458 |
-
helpers.doEx('\'<,\'>s/\\d/0/g');
|
3459 |
-
eq('1\n0\n0\n0\n5', cm.getValue());
|
3460 |
-
}, { value: '1\n2\n3\n4\n5' });
|
3461 |
-
testVim('ex_substitute_empty_query', function(cm, vim, helpers) {
|
3462 |
-
// If the query is empty, use last query.
|
3463 |
-
cm.setCursor(1, 0);
|
3464 |
-
cm.openDialog = helpers.fakeOpenDialog('1');
|
3465 |
-
helpers.doKeys('/');
|
3466 |
-
helpers.doEx('s//b/g');
|
3467 |
-
eq('abb ab2 ab3', cm.getValue());
|
3468 |
-
}, { value: 'a11 a12 a13' });
|
3469 |
-
testVim('ex_substitute_javascript', function(cm, vim, helpers) {
|
3470 |
-
CodeMirror.Vim.setOption('pcre', false);
|
3471 |
-
cm.setCursor(1, 0);
|
3472 |
-
// Throw all the things that javascript likes to treat as special values
|
3473 |
-
// into the replace part. All should be literal (this is VIM).
|
3474 |
-
helpers.doEx('s/\\(\\d+\\)/$$ $\' $` $& \\1/g')
|
3475 |
-
eq('a $$ $\' $` $& 0 b', cm.getValue());
|
3476 |
-
}, { value: 'a 0 b' });
|
3477 |
-
testVim('ex_substitute_empty_arguments', function(cm,vim,helpers) {
|
3478 |
-
cm.setCursor(0, 0);
|
3479 |
-
helpers.doEx('s/a/b/g');
|
3480 |
-
cm.setCursor(1, 0);
|
3481 |
-
helpers.doEx('s');
|
3482 |
-
eq('b b\nb a', cm.getValue());
|
3483 |
-
}, {value: 'a a\na a'});
|
3484 |
-
|
3485 |
-
// More complex substitute tests that test both pcre and nopcre options.
|
3486 |
-
function testSubstitute(name, options) {
|
3487 |
-
testVim(name + '_pcre', function(cm, vim, helpers) {
|
3488 |
-
cm.setCursor(1, 0);
|
3489 |
-
CodeMirror.Vim.setOption('pcre', true);
|
3490 |
-
helpers.doEx(options.expr);
|
3491 |
-
eq(options.expectedValue, cm.getValue());
|
3492 |
-
}, options);
|
3493 |
-
// If no noPcreExpr is defined, assume that it's the same as the expr.
|
3494 |
-
var noPcreExpr = options.noPcreExpr ? options.noPcreExpr : options.expr;
|
3495 |
-
testVim(name + '_nopcre', function(cm, vim, helpers) {
|
3496 |
-
cm.setCursor(1, 0);
|
3497 |
-
CodeMirror.Vim.setOption('pcre', false);
|
3498 |
-
helpers.doEx(noPcreExpr);
|
3499 |
-
eq(options.expectedValue, cm.getValue());
|
3500 |
-
}, options);
|
3501 |
-
}
|
3502 |
-
testSubstitute('ex_substitute_capture', {
|
3503 |
-
value: 'a11 a12 a13',
|
3504 |
-
expectedValue: 'a1111 a1212 a1313',
|
3505 |
-
// $n is a backreference
|
3506 |
-
expr: 's/(\\d+)/$1$1/g',
|
3507 |
-
// \n is a backreference.
|
3508 |
-
noPcreExpr: 's/\\(\\d+\\)/\\1\\1/g'});
|
3509 |
-
testSubstitute('ex_substitute_capture2', {
|
3510 |
-
value: 'a 0 b',
|
3511 |
-
expectedValue: 'a $00 b',
|
3512 |
-
expr: 's/(\\d+)/$$$1$1/g',
|
3513 |
-
noPcreExpr: 's/\\(\\d+\\)/$\\1\\1/g'});
|
3514 |
-
testSubstitute('ex_substitute_nocapture', {
|
3515 |
-
value: 'a11 a12 a13',
|
3516 |
-
expectedValue: 'a$1$1 a$1$1 a$1$1',
|
3517 |
-
expr: 's/(\\d+)/$$1$$1/g',
|
3518 |
-
noPcreExpr: 's/\\(\\d+\\)/$1$1/g'});
|
3519 |
-
testSubstitute('ex_substitute_nocapture2', {
|
3520 |
-
value: 'a 0 b',
|
3521 |
-
expectedValue: 'a $10 b',
|
3522 |
-
expr: 's/(\\d+)/$$1$1/g',
|
3523 |
-
noPcreExpr: 's/\\(\\d+\\)/\\$1\\1/g'});
|
3524 |
-
testSubstitute('ex_substitute_nocapture', {
|
3525 |
-
value: 'a b c',
|
3526 |
-
expectedValue: 'a $ c',
|
3527 |
-
expr: 's/b/$$/',
|
3528 |
-
noPcreExpr: 's/b/$/'});
|
3529 |
-
testSubstitute('ex_substitute_slash_regex', {
|
3530 |
-
value: 'one/two \n three/four',
|
3531 |
-
expectedValue: 'one|two \n three|four',
|
3532 |
-
expr: '%s/\\//|'});
|
3533 |
-
testSubstitute('ex_substitute_pipe_regex', {
|
3534 |
-
value: 'one|two \n three|four',
|
3535 |
-
expectedValue: 'one,two \n three,four',
|
3536 |
-
expr: '%s/\\|/,/',
|
3537 |
-
noPcreExpr: '%s/|/,/'});
|
3538 |
-
testSubstitute('ex_substitute_or_regex', {
|
3539 |
-
value: 'one|two \n three|four',
|
3540 |
-
expectedValue: 'ana|twa \n thraa|faar',
|
3541 |
-
expr: '%s/o|e|u/a/g',
|
3542 |
-
noPcreExpr: '%s/o\\|e\\|u/a/g'});
|
3543 |
-
testSubstitute('ex_substitute_or_word_regex', {
|
3544 |
-
value: 'one|two \n three|four',
|
3545 |
-
expectedValue: 'five|five \n three|four',
|
3546 |
-
expr: '%s/(one|two)/five/g',
|
3547 |
-
noPcreExpr: '%s/\\(one\\|two\\)/five/g'});
|
3548 |
-
testSubstitute('ex_substitute_backslashslash_regex', {
|
3549 |
-
value: 'one\\two \n three\\four',
|
3550 |
-
expectedValue: 'one,two \n three,four',
|
3551 |
-
expr: '%s/\\\\/,'});
|
3552 |
-
testSubstitute('ex_substitute_slash_replacement', {
|
3553 |
-
value: 'one,two \n three,four',
|
3554 |
-
expectedValue: 'one/two \n three/four',
|
3555 |
-
expr: '%s/,/\\/'});
|
3556 |
-
testSubstitute('ex_substitute_backslash_replacement', {
|
3557 |
-
value: 'one,two \n three,four',
|
3558 |
-
expectedValue: 'one\\two \n three\\four',
|
3559 |
-
expr: '%s/,/\\\\/g'});
|
3560 |
-
testSubstitute('ex_substitute_multibackslash_replacement', {
|
3561 |
-
value: 'one,two \n three,four',
|
3562 |
-
expectedValue: 'one\\\\\\\\two \n three\\\\\\\\four', // 2*8 backslashes.
|
3563 |
-
expr: '%s/,/\\\\\\\\\\\\\\\\/g'}); // 16 backslashes.
|
3564 |
-
testSubstitute('ex_substitute_newline_replacement', {
|
3565 |
-
value: 'one,two \n three,four',
|
3566 |
-
expectedValue: 'one\ntwo \n three\nfour',
|
3567 |
-
expr: '%s/,/\\n/g'});
|
3568 |
-
testSubstitute('ex_substitute_braces_word', {
|
3569 |
-
value: 'ababab abb ab{2}',
|
3570 |
-
expectedValue: 'ab abb ab{2}',
|
3571 |
-
expr: '%s/(ab){2}//g',
|
3572 |
-
noPcreExpr: '%s/\\(ab\\)\\{2\\}//g'});
|
3573 |
-
testSubstitute('ex_substitute_braces_range', {
|
3574 |
-
value: 'a aa aaa aaaa',
|
3575 |
-
expectedValue: 'a a',
|
3576 |
-
expr: '%s/a{2,3}//g',
|
3577 |
-
noPcreExpr: '%s/a\\{2,3\\}//g'});
|
3578 |
-
testSubstitute('ex_substitute_braces_literal', {
|
3579 |
-
value: 'ababab abb ab{2}',
|
3580 |
-
expectedValue: 'ababab abb ',
|
3581 |
-
expr: '%s/ab\\{2\\}//g',
|
3582 |
-
noPcreExpr: '%s/ab{2}//g'});
|
3583 |
-
testSubstitute('ex_substitute_braces_char', {
|
3584 |
-
value: 'ababab abb ab{2}',
|
3585 |
-
expectedValue: 'ababab ab{2}',
|
3586 |
-
expr: '%s/ab{2}//g',
|
3587 |
-
noPcreExpr: '%s/ab\\{2\\}//g'});
|
3588 |
-
testSubstitute('ex_substitute_braces_no_escape', {
|
3589 |
-
value: 'ababab abb ab{2}',
|
3590 |
-
expectedValue: 'ababab ab{2}',
|
3591 |
-
expr: '%s/ab{2}//g',
|
3592 |
-
noPcreExpr: '%s/ab\\{2}//g'});
|
3593 |
-
testSubstitute('ex_substitute_count', {
|
3594 |
-
value: '1\n2\n3\n4',
|
3595 |
-
expectedValue: '1\n0\n0\n4',
|
3596 |
-
expr: 's/\\d/0/i 2'});
|
3597 |
-
testSubstitute('ex_substitute_count_with_range', {
|
3598 |
-
value: '1\n2\n3\n4',
|
3599 |
-
expectedValue: '1\n2\n0\n0',
|
3600 |
-
expr: '1,3s/\\d/0/ 3'});
|
3601 |
-
testSubstitute('ex_substitute_not_global', {
|
3602 |
-
value: 'aaa\nbaa\ncaa',
|
3603 |
-
expectedValue: 'xaa\nbxa\ncxa',
|
3604 |
-
expr: '%s/a/x/'});
|
3605 |
-
function testSubstituteConfirm(name, command, initialValue, expectedValue, keys, finalPos) {
|
3606 |
-
testVim(name, function(cm, vim, helpers) {
|
3607 |
-
var savedOpenDialog = cm.openDialog;
|
3608 |
-
var savedKeyName = CodeMirror.keyName;
|
3609 |
-
var onKeyDown;
|
3610 |
-
var recordedCallback;
|
3611 |
-
var closed = true; // Start out closed, set false on second openDialog.
|
3612 |
-
function close() {
|
3613 |
-
closed = true;
|
3614 |
-
}
|
3615 |
-
// First openDialog should save callback.
|
3616 |
-
cm.openDialog = function(template, callback, options) {
|
3617 |
-
recordedCallback = callback;
|
3618 |
-
}
|
3619 |
-
// Do first openDialog.
|
3620 |
-
helpers.doKeys(':');
|
3621 |
-
// Second openDialog should save keyDown handler.
|
3622 |
-
cm.openDialog = function(template, callback, options) {
|
3623 |
-
onKeyDown = options.onKeyDown;
|
3624 |
-
closed = false;
|
3625 |
-
};
|
3626 |
-
// Return the command to Vim and trigger second openDialog.
|
3627 |
-
recordedCallback(command);
|
3628 |
-
// The event should really use keyCode, but here just mock it out and use
|
3629 |
-
// key and replace keyName to just return key.
|
3630 |
-
CodeMirror.keyName = function (e) { return e.key; }
|
3631 |
-
keys = keys.toUpperCase();
|
3632 |
-
for (var i = 0; i < keys.length; i++) {
|
3633 |
-
is(!closed);
|
3634 |
-
onKeyDown({ key: keys.charAt(i) }, '', close);
|
3635 |
-
}
|
3636 |
-
try {
|
3637 |
-
eq(expectedValue, cm.getValue());
|
3638 |
-
helpers.assertCursorAt(finalPos);
|
3639 |
-
is(closed);
|
3640 |
-
} catch(e) {
|
3641 |
-
throw e
|
3642 |
-
} finally {
|
3643 |
-
// Restore overriden functions.
|
3644 |
-
CodeMirror.keyName = savedKeyName;
|
3645 |
-
cm.openDialog = savedOpenDialog;
|
3646 |
-
}
|
3647 |
-
}, { value: initialValue });
|
3648 |
-
};
|
3649 |
-
testSubstituteConfirm('ex_substitute_confirm_emptydoc',
|
3650 |
-
'%s/x/b/c', '', '', '', makeCursor(0, 0));
|
3651 |
-
testSubstituteConfirm('ex_substitute_confirm_nomatch',
|
3652 |
-
'%s/x/b/c', 'ba a\nbab', 'ba a\nbab', '', makeCursor(0, 0));
|
3653 |
-
testSubstituteConfirm('ex_substitute_confirm_accept',
|
3654 |
-
'%s/a/b/cg', 'ba a\nbab', 'bb b\nbbb', 'yyy', makeCursor(1, 1));
|
3655 |
-
testSubstituteConfirm('ex_substitute_confirm_random_keys',
|
3656 |
-
'%s/a/b/cg', 'ba a\nbab', 'bb b\nbbb', 'ysdkywerty', makeCursor(1, 1));
|
3657 |
-
testSubstituteConfirm('ex_substitute_confirm_some',
|
3658 |
-
'%s/a/b/cg', 'ba a\nbab', 'bb a\nbbb', 'yny', makeCursor(1, 1));
|
3659 |
-
testSubstituteConfirm('ex_substitute_confirm_all',
|
3660 |
-
'%s/a/b/cg', 'ba a\nbab', 'bb b\nbbb', 'a', makeCursor(1, 1));
|
3661 |
-
testSubstituteConfirm('ex_substitute_confirm_accept_then_all',
|
3662 |
-
'%s/a/b/cg', 'ba a\nbab', 'bb b\nbbb', 'ya', makeCursor(1, 1));
|
3663 |
-
testSubstituteConfirm('ex_substitute_confirm_quit',
|
3664 |
-
'%s/a/b/cg', 'ba a\nbab', 'bb a\nbab', 'yq', makeCursor(0, 3));
|
3665 |
-
testSubstituteConfirm('ex_substitute_confirm_last',
|
3666 |
-
'%s/a/b/cg', 'ba a\nbab', 'bb b\nbab', 'yl', makeCursor(0, 3));
|
3667 |
-
testSubstituteConfirm('ex_substitute_confirm_oneline',
|
3668 |
-
'1s/a/b/cg', 'ba a\nbab', 'bb b\nbab', 'yl', makeCursor(0, 3));
|
3669 |
-
testSubstituteConfirm('ex_substitute_confirm_range_accept',
|
3670 |
-
'1,2s/a/b/cg', 'aa\na \na\na', 'bb\nb \na\na', 'yyy', makeCursor(1, 0));
|
3671 |
-
testSubstituteConfirm('ex_substitute_confirm_range_some',
|
3672 |
-
'1,3s/a/b/cg', 'aa\na \na\na', 'ba\nb \nb\na', 'ynyy', makeCursor(2, 0));
|
3673 |
-
testSubstituteConfirm('ex_substitute_confirm_range_all',
|
3674 |
-
'1,3s/a/b/cg', 'aa\na \na\na', 'bb\nb \nb\na', 'a', makeCursor(2, 0));
|
3675 |
-
testSubstituteConfirm('ex_substitute_confirm_range_last',
|
3676 |
-
'1,3s/a/b/cg', 'aa\na \na\na', 'bb\nb \na\na', 'yyl', makeCursor(1, 0));
|
3677 |
-
//:noh should clear highlighting of search-results but allow to resume search through n
|
3678 |
-
testVim('ex_noh_clearSearchHighlight', function(cm, vim, helpers) {
|
3679 |
-
cm.openDialog = helpers.fakeOpenDialog('match');
|
3680 |
-
helpers.doKeys('?');
|
3681 |
-
helpers.doEx('noh');
|
3682 |
-
eq(vim.searchState_.getOverlay(),null,'match-highlighting wasn\'t cleared');
|
3683 |
-
helpers.doKeys('n');
|
3684 |
-
helpers.assertCursorAt(0, 11,'can\'t resume search after clearing highlighting');
|
3685 |
-
}, { value: 'match nope match \n nope Match' });
|
3686 |
-
testVim('set_boolean', function(cm, vim, helpers) {
|
3687 |
-
CodeMirror.Vim.defineOption('testoption', true, 'boolean');
|
3688 |
-
// Test default value is set.
|
3689 |
-
is(CodeMirror.Vim.getOption('testoption'));
|
3690 |
-
try {
|
3691 |
-
// Test fail to set to non-boolean
|
3692 |
-
CodeMirror.Vim.setOption('testoption', '5');
|
3693 |
-
fail();
|
3694 |
-
} catch (expected) {};
|
3695 |
-
// Test setOption
|
3696 |
-
CodeMirror.Vim.setOption('testoption', false);
|
3697 |
-
is(!CodeMirror.Vim.getOption('testoption'));
|
3698 |
-
});
|
3699 |
-
testVim('ex_set_boolean', function(cm, vim, helpers) {
|
3700 |
-
CodeMirror.Vim.defineOption('testoption', true, 'boolean');
|
3701 |
-
// Test default value is set.
|
3702 |
-
is(CodeMirror.Vim.getOption('testoption'));
|
3703 |
-
try {
|
3704 |
-
// Test fail to set to non-boolean
|
3705 |
-
helpers.doEx('set testoption=22');
|
3706 |
-
fail();
|
3707 |
-
} catch (expected) {};
|
3708 |
-
// Test setOption
|
3709 |
-
helpers.doEx('set notestoption');
|
3710 |
-
is(!CodeMirror.Vim.getOption('testoption'));
|
3711 |
-
});
|
3712 |
-
testVim('set_string', function(cm, vim, helpers) {
|
3713 |
-
CodeMirror.Vim.defineOption('testoption', 'a', 'string');
|
3714 |
-
// Test default value is set.
|
3715 |
-
eq('a', CodeMirror.Vim.getOption('testoption'));
|
3716 |
-
try {
|
3717 |
-
// Test fail to set non-string.
|
3718 |
-
CodeMirror.Vim.setOption('testoption', true);
|
3719 |
-
fail();
|
3720 |
-
} catch (expected) {};
|
3721 |
-
try {
|
3722 |
-
// Test fail to set 'notestoption'
|
3723 |
-
CodeMirror.Vim.setOption('notestoption', 'b');
|
3724 |
-
fail();
|
3725 |
-
} catch (expected) {};
|
3726 |
-
// Test setOption
|
3727 |
-
CodeMirror.Vim.setOption('testoption', 'c');
|
3728 |
-
eq('c', CodeMirror.Vim.getOption('testoption'));
|
3729 |
-
});
|
3730 |
-
testVim('ex_set_string', function(cm, vim, helpers) {
|
3731 |
-
CodeMirror.Vim.defineOption('testopt', 'a', 'string');
|
3732 |
-
// Test default value is set.
|
3733 |
-
eq('a', CodeMirror.Vim.getOption('testopt'));
|
3734 |
-
try {
|
3735 |
-
// Test fail to set 'notestopt'
|
3736 |
-
helpers.doEx('set notestopt=b');
|
3737 |
-
fail();
|
3738 |
-
} catch (expected) {};
|
3739 |
-
// Test setOption
|
3740 |
-
helpers.doEx('set testopt=c')
|
3741 |
-
eq('c', CodeMirror.Vim.getOption('testopt'));
|
3742 |
-
helpers.doEx('set testopt=c')
|
3743 |
-
eq('c', CodeMirror.Vim.getOption('testopt', cm)); //local || global
|
3744 |
-
eq('c', CodeMirror.Vim.getOption('testopt', cm, {scope: 'local'})); // local
|
3745 |
-
eq('c', CodeMirror.Vim.getOption('testopt', cm, {scope: 'global'})); // global
|
3746 |
-
eq('c', CodeMirror.Vim.getOption('testopt')); // global
|
3747 |
-
// Test setOption global
|
3748 |
-
helpers.doEx('setg testopt=d')
|
3749 |
-
eq('c', CodeMirror.Vim.getOption('testopt', cm));
|
3750 |
-
eq('c', CodeMirror.Vim.getOption('testopt', cm, {scope: 'local'}));
|
3751 |
-
eq('d', CodeMirror.Vim.getOption('testopt', cm, {scope: 'global'}));
|
3752 |
-
eq('d', CodeMirror.Vim.getOption('testopt'));
|
3753 |
-
// Test setOption local
|
3754 |
-
helpers.doEx('setl testopt=e')
|
3755 |
-
eq('e', CodeMirror.Vim.getOption('testopt', cm));
|
3756 |
-
eq('e', CodeMirror.Vim.getOption('testopt', cm, {scope: 'local'}));
|
3757 |
-
eq('d', CodeMirror.Vim.getOption('testopt', cm, {scope: 'global'}));
|
3758 |
-
eq('d', CodeMirror.Vim.getOption('testopt'));
|
3759 |
-
});
|
3760 |
-
testVim('ex_set_callback', function(cm, vim, helpers) {
|
3761 |
-
var global;
|
3762 |
-
|
3763 |
-
function cb(val, cm, cfg) {
|
3764 |
-
if (val === undefined) {
|
3765 |
-
// Getter
|
3766 |
-
if (cm) {
|
3767 |
-
return cm._local;
|
3768 |
-
} else {
|
3769 |
-
return global;
|
3770 |
-
}
|
3771 |
-
} else {
|
3772 |
-
// Setter
|
3773 |
-
if (cm) {
|
3774 |
-
cm._local = val;
|
3775 |
-
} else {
|
3776 |
-
global = val;
|
3777 |
-
}
|
3778 |
-
}
|
3779 |
-
}
|
3780 |
-
|
3781 |
-
CodeMirror.Vim.defineOption('testopt', 'a', 'string', cb);
|
3782 |
-
// Test default value is set.
|
3783 |
-
eq('a', CodeMirror.Vim.getOption('testopt'));
|
3784 |
-
try {
|
3785 |
-
// Test fail to set 'notestopt'
|
3786 |
-
helpers.doEx('set notestopt=b');
|
3787 |
-
fail();
|
3788 |
-
} catch (expected) {};
|
3789 |
-
// Test setOption (Identical to the string tests, but via callback instead)
|
3790 |
-
helpers.doEx('set testopt=c')
|
3791 |
-
eq('c', CodeMirror.Vim.getOption('testopt', cm)); //local || global
|
3792 |
-
eq('c', CodeMirror.Vim.getOption('testopt', cm, {scope: 'local'})); // local
|
3793 |
-
eq('c', CodeMirror.Vim.getOption('testopt', cm, {scope: 'global'})); // global
|
3794 |
-
eq('c', CodeMirror.Vim.getOption('testopt')); // global
|
3795 |
-
// Test setOption global
|
3796 |
-
helpers.doEx('setg testopt=d')
|
3797 |
-
eq('c', CodeMirror.Vim.getOption('testopt', cm));
|
3798 |
-
eq('c', CodeMirror.Vim.getOption('testopt', cm, {scope: 'local'}));
|
3799 |
-
eq('d', CodeMirror.Vim.getOption('testopt', cm, {scope: 'global'}));
|
3800 |
-
eq('d', CodeMirror.Vim.getOption('testopt'));
|
3801 |
-
// Test setOption local
|
3802 |
-
helpers.doEx('setl testopt=e')
|
3803 |
-
eq('e', CodeMirror.Vim.getOption('testopt', cm));
|
3804 |
-
eq('e', CodeMirror.Vim.getOption('testopt', cm, {scope: 'local'}));
|
3805 |
-
eq('d', CodeMirror.Vim.getOption('testopt', cm, {scope: 'global'}));
|
3806 |
-
eq('d', CodeMirror.Vim.getOption('testopt'));
|
3807 |
-
})
|
3808 |
-
testVim('ex_set_filetype', function(cm, vim, helpers) {
|
3809 |
-
CodeMirror.defineMode('test_mode', function() {
|
3810 |
-
return {token: function(stream) {
|
3811 |
-
stream.match(/^\s+|^\S+/);
|
3812 |
-
}};
|
3813 |
-
});
|
3814 |
-
CodeMirror.defineMode('test_mode_2', function() {
|
3815 |
-
return {token: function(stream) {
|
3816 |
-
stream.match(/^\s+|^\S+/);
|
3817 |
-
}};
|
3818 |
-
});
|
3819 |
-
// Test mode is set.
|
3820 |
-
helpers.doEx('set filetype=test_mode');
|
3821 |
-
eq('test_mode', cm.getMode().name);
|
3822 |
-
// Test 'ft' alias also sets mode.
|
3823 |
-
helpers.doEx('set ft=test_mode_2');
|
3824 |
-
eq('test_mode_2', cm.getMode().name);
|
3825 |
-
});
|
3826 |
-
testVim('ex_set_filetype_null', function(cm, vim, helpers) {
|
3827 |
-
CodeMirror.defineMode('test_mode', function() {
|
3828 |
-
return {token: function(stream) {
|
3829 |
-
stream.match(/^\s+|^\S+/);
|
3830 |
-
}};
|
3831 |
-
});
|
3832 |
-
cm.setOption('mode', 'test_mode');
|
3833 |
-
// Test mode is set to null.
|
3834 |
-
helpers.doEx('set filetype=');
|
3835 |
-
eq('null', cm.getMode().name);
|
3836 |
-
});
|
3837 |
-
// TODO: Reset key maps after each test.
|
3838 |
-
testVim('ex_map_key2key', function(cm, vim, helpers) {
|
3839 |
-
helpers.doEx('map a x');
|
3840 |
-
helpers.doKeys('a');
|
3841 |
-
helpers.assertCursorAt(0, 0);
|
3842 |
-
eq('bc', cm.getValue());
|
3843 |
-
}, { value: 'abc' });
|
3844 |
-
testVim('ex_unmap_key2key', function(cm, vim, helpers) {
|
3845 |
-
helpers.doEx('unmap a');
|
3846 |
-
helpers.doKeys('a');
|
3847 |
-
eq('vim-insert', cm.getOption('keyMap'));
|
3848 |
-
}, { value: 'abc' });
|
3849 |
-
testVim('ex_unmap_key2key_does_not_remove_default', function(cm, vim, helpers) {
|
3850 |
-
try {
|
3851 |
-
helpers.doEx('unmap a');
|
3852 |
-
fail();
|
3853 |
-
} catch (expected) {}
|
3854 |
-
helpers.doKeys('a');
|
3855 |
-
eq('vim-insert', cm.getOption('keyMap'));
|
3856 |
-
}, { value: 'abc' });
|
3857 |
-
testVim('ex_map_key2key_to_colon', function(cm, vim, helpers) {
|
3858 |
-
helpers.doEx('map ; :');
|
3859 |
-
var dialogOpened = false;
|
3860 |
-
cm.openDialog = function() {
|
3861 |
-
dialogOpened = true;
|
3862 |
-
}
|
3863 |
-
helpers.doKeys(';');
|
3864 |
-
eq(dialogOpened, true);
|
3865 |
-
});
|
3866 |
-
testVim('ex_map_ex2key:', function(cm, vim, helpers) {
|
3867 |
-
helpers.doEx('map :del x');
|
3868 |
-
helpers.doEx('del');
|
3869 |
-
helpers.assertCursorAt(0, 0);
|
3870 |
-
eq('bc', cm.getValue());
|
3871 |
-
}, { value: 'abc' });
|
3872 |
-
testVim('ex_map_ex2ex', function(cm, vim, helpers) {
|
3873 |
-
helpers.doEx('map :del :w');
|
3874 |
-
var tmp = CodeMirror.commands.save;
|
3875 |
-
var written = false;
|
3876 |
-
var actualCm;
|
3877 |
-
CodeMirror.commands.save = function(cm) {
|
3878 |
-
written = true;
|
3879 |
-
actualCm = cm;
|
3880 |
-
};
|
3881 |
-
helpers.doEx('del');
|
3882 |
-
CodeMirror.commands.save = tmp;
|
3883 |
-
eq(written, true);
|
3884 |
-
eq(actualCm, cm);
|
3885 |
-
});
|
3886 |
-
testVim('ex_map_key2ex', function(cm, vim, helpers) {
|
3887 |
-
helpers.doEx('map a :w');
|
3888 |
-
var tmp = CodeMirror.commands.save;
|
3889 |
-
var written = false;
|
3890 |
-
var actualCm;
|
3891 |
-
CodeMirror.commands.save = function(cm) {
|
3892 |
-
written = true;
|
3893 |
-
actualCm = cm;
|
3894 |
-
};
|
3895 |
-
helpers.doKeys('a');
|
3896 |
-
CodeMirror.commands.save = tmp;
|
3897 |
-
eq(written, true);
|
3898 |
-
eq(actualCm, cm);
|
3899 |
-
});
|
3900 |
-
testVim('ex_map_key2key_visual_api', function(cm, vim, helpers) {
|
3901 |
-
CodeMirror.Vim.map('b', ':w', 'visual');
|
3902 |
-
var tmp = CodeMirror.commands.save;
|
3903 |
-
var written = false;
|
3904 |
-
var actualCm;
|
3905 |
-
CodeMirror.commands.save = function(cm) {
|
3906 |
-
written = true;
|
3907 |
-
actualCm = cm;
|
3908 |
-
};
|
3909 |
-
// Mapping should not work in normal mode.
|
3910 |
-
helpers.doKeys('b');
|
3911 |
-
eq(written, false);
|
3912 |
-
// Mapping should work in visual mode.
|
3913 |
-
helpers.doKeys('v', 'b');
|
3914 |
-
eq(written, true);
|
3915 |
-
eq(actualCm, cm);
|
3916 |
-
|
3917 |
-
CodeMirror.commands.save = tmp;
|
3918 |
-
});
|
3919 |
-
testVim('ex_imap', function(cm, vim, helpers) {
|
3920 |
-
CodeMirror.Vim.map('jk', '<Esc>', 'insert');
|
3921 |
-
helpers.doKeys('i');
|
3922 |
-
is(vim.insertMode);
|
3923 |
-
helpers.doKeys('j', 'k');
|
3924 |
-
is(!vim.insertMode);
|
3925 |
-
})
|
3926 |
-
|
3927 |
-
// Testing registration of functions as ex-commands and mapping to <Key>-keys
|
3928 |
-
testVim('ex_api_test', function(cm, vim, helpers) {
|
3929 |
-
var res=false;
|
3930 |
-
var val='from';
|
3931 |
-
CodeMirror.Vim.defineEx('extest','ext',function(cm,params){
|
3932 |
-
if(params.args)val=params.args[0];
|
3933 |
-
else res=true;
|
3934 |
-
});
|
3935 |
-
helpers.doEx(':ext to');
|
3936 |
-
eq(val,'to','Defining ex-command failed');
|
3937 |
-
CodeMirror.Vim.map('<C-CR><Space>',':ext');
|
3938 |
-
helpers.doKeys('<C-CR>','<Space>');
|
3939 |
-
is(res,'Mapping to key failed');
|
3940 |
-
});
|
3941 |
-
// For now, this test needs to be last because it messes up : for future tests.
|
3942 |
-
testVim('ex_map_key2key_from_colon', function(cm, vim, helpers) {
|
3943 |
-
helpers.doEx('map : x');
|
3944 |
-
helpers.doKeys(':');
|
3945 |
-
helpers.assertCursorAt(0, 0);
|
3946 |
-
eq('bc', cm.getValue());
|
3947 |
-
}, { value: 'abc' });
|
3948 |
-
|
3949 |
-
// Test event handlers
|
3950 |
-
testVim('beforeSelectionChange', function(cm, vim, helpers) {
|
3951 |
-
cm.setCursor(0, 100);
|
3952 |
-
eqPos(cm.getCursor('head'), cm.getCursor('anchor'));
|
3953 |
-
}, { value: 'abc' });
|
3954 |
-
|
3955 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/toolset/types/.travis.yml
DELETED
@@ -1,29 +0,0 @@
|
|
1 |
-
language: php
|
2 |
-
|
3 |
-
notifications:
|
4 |
-
email:
|
5 |
-
on_success: never
|
6 |
-
on_failure: change
|
7 |
-
|
8 |
-
branches:
|
9 |
-
only:
|
10 |
-
- master
|
11 |
-
|
12 |
-
php:
|
13 |
-
- 5.3
|
14 |
-
- 5.6
|
15 |
-
|
16 |
-
env:
|
17 |
-
- WP_VERSION=latest WP_MULTISITE=0
|
18 |
-
- WP_VERSION=3.7 WP_MULTISITE=0
|
19 |
-
- WP_VERSION=4.4.1 WP_MULTISITE=0
|
20 |
-
|
21 |
-
matrix:
|
22 |
-
include:
|
23 |
-
- php: 5.3
|
24 |
-
env: WP_VERSION=latest WP_MULTISITE=1
|
25 |
-
|
26 |
-
before_script:
|
27 |
-
- bash bin/install-wp-tests.sh wordpress_test root '' localhost $WP_VERSION
|
28 |
-
|
29 |
-
script: phpunit
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/.editorconfig
DELETED
@@ -1,18 +0,0 @@
|
|
1 |
-
; top-most EditorConfig file
|
2 |
-
root = true
|
3 |
-
|
4 |
-
; Unix-style newlines
|
5 |
-
[*]
|
6 |
-
end_of_line = LF
|
7 |
-
|
8 |
-
[*.php]
|
9 |
-
indent_style = space
|
10 |
-
indent_size = 4
|
11 |
-
|
12 |
-
[*.test]
|
13 |
-
indent_style = space
|
14 |
-
indent_size = 4
|
15 |
-
|
16 |
-
[*.rst]
|
17 |
-
indent_style = space
|
18 |
-
indent_size = 4
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/.php_cs.dist
DELETED
@@ -1,15 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
return PhpCsFixer\Config::create()
|
4 |
-
->setRules(array(
|
5 |
-
'@Symfony' => true,
|
6 |
-
'@Symfony:risky' => true,
|
7 |
-
'array_syntax' => array('syntax' => 'long'),
|
8 |
-
'php_unit_fqcn_annotation' => false,
|
9 |
-
'no_unreachable_default_argument_value' => false,
|
10 |
-
'braces' => array('allow_single_line_closure' => true),
|
11 |
-
'heredoc_to_nowdoc' => false,
|
12 |
-
))
|
13 |
-
->setRiskyAllowed(true)
|
14 |
-
->setFinder(PhpCsFixer\Finder::create()->in(__DIR__))
|
15 |
-
;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/.travis.yml
DELETED
@@ -1,43 +0,0 @@
|
|
1 |
-
language: php
|
2 |
-
|
3 |
-
sudo: false
|
4 |
-
|
5 |
-
cache:
|
6 |
-
directories:
|
7 |
-
- vendor
|
8 |
-
- $HOME/.composer/cache/files
|
9 |
-
|
10 |
-
php:
|
11 |
-
- 5.2
|
12 |
-
- 5.3
|
13 |
-
- 5.4
|
14 |
-
- 5.5
|
15 |
-
- 5.6
|
16 |
-
- 7.0
|
17 |
-
- 7.1
|
18 |
-
- hhvm
|
19 |
-
|
20 |
-
env:
|
21 |
-
- TWIG_EXT=no
|
22 |
-
- TWIG_EXT=yes
|
23 |
-
|
24 |
-
before_install:
|
25 |
-
- if [[ ! $TRAVIS_PHP_VERSION = hhvm* ]]; then phpenv config-rm xdebug.ini || echo "xdebug not available"; fi
|
26 |
-
|
27 |
-
install:
|
28 |
-
# Composer is not available on PHP 5.2
|
29 |
-
- if [ ${TRAVIS_PHP_VERSION:0:3} != "5.2" ]; then travis_retry composer install; fi
|
30 |
-
|
31 |
-
before_script:
|
32 |
-
- if [ "$TWIG_EXT" == "yes" ]; then sh -c "cd ext/twig && phpize && ./configure --enable-twig && make && make install"; fi
|
33 |
-
- if [ "$TWIG_EXT" == "yes" ]; then echo "extension=twig.so" >> `php --ini | grep "Loaded Configuration" | sed -e "s|.*:\s*||"`; fi
|
34 |
-
|
35 |
-
matrix:
|
36 |
-
fast_finish: true
|
37 |
-
exclude:
|
38 |
-
- php: hhvm
|
39 |
-
env: TWIG_EXT=yes
|
40 |
-
- php: 7.0
|
41 |
-
env: TWIG_EXT=yes
|
42 |
-
- php: 7.1
|
43 |
-
env: TWIG_EXT=yes
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/advanced.rst
DELETED
@@ -1,962 +0,0 @@
|
|
1 |
-
Extending Twig
|
2 |
-
==============
|
3 |
-
|
4 |
-
.. caution::
|
5 |
-
|
6 |
-
This section describes how to extend Twig as of **Twig 1.12**. If you are
|
7 |
-
using an older version, read the :doc:`legacy<advanced_legacy>` chapter
|
8 |
-
instead.
|
9 |
-
|
10 |
-
Twig can be extended in many ways; you can add extra tags, filters, tests,
|
11 |
-
operators, global variables, and functions. You can even extend the parser
|
12 |
-
itself with node visitors.
|
13 |
-
|
14 |
-
.. note::
|
15 |
-
|
16 |
-
The first section of this chapter describes how to extend Twig easily. If
|
17 |
-
you want to reuse your changes in different projects or if you want to
|
18 |
-
share them with others, you should then create an extension as described
|
19 |
-
in the following section.
|
20 |
-
|
21 |
-
.. caution::
|
22 |
-
|
23 |
-
When extending Twig without creating an extension, Twig won't be able to
|
24 |
-
recompile your templates when the PHP code is updated. To see your changes
|
25 |
-
in real-time, either disable template caching or package your code into an
|
26 |
-
extension (see the next section of this chapter).
|
27 |
-
|
28 |
-
Before extending Twig, you must understand the differences between all the
|
29 |
-
different possible extension points and when to use them.
|
30 |
-
|
31 |
-
First, remember that Twig has two main language constructs:
|
32 |
-
|
33 |
-
* ``{{ }}``: used to print the result of an expression evaluation;
|
34 |
-
|
35 |
-
* ``{% %}``: used to execute statements.
|
36 |
-
|
37 |
-
To understand why Twig exposes so many extension points, let's see how to
|
38 |
-
implement a *Lorem ipsum* generator (it needs to know the number of words to
|
39 |
-
generate).
|
40 |
-
|
41 |
-
You can use a ``lipsum`` *tag*:
|
42 |
-
|
43 |
-
.. code-block:: jinja
|
44 |
-
|
45 |
-
{% lipsum 40 %}
|
46 |
-
|
47 |
-
That works, but using a tag for ``lipsum`` is not a good idea for at least
|
48 |
-
three main reasons:
|
49 |
-
|
50 |
-
* ``lipsum`` is not a language construct;
|
51 |
-
* The tag outputs something;
|
52 |
-
* The tag is not flexible as you cannot use it in an expression:
|
53 |
-
|
54 |
-
.. code-block:: jinja
|
55 |
-
|
56 |
-
{{ 'some text' ~ {% lipsum 40 %} ~ 'some more text' }}
|
57 |
-
|
58 |
-
In fact, you rarely need to create tags; and that's good news because tags are
|
59 |
-
the most complex extension point of Twig.
|
60 |
-
|
61 |
-
Now, let's use a ``lipsum`` *filter*:
|
62 |
-
|
63 |
-
.. code-block:: jinja
|
64 |
-
|
65 |
-
{{ 40|lipsum }}
|
66 |
-
|
67 |
-
Again, it works, but it looks weird. A filter transforms the passed value to
|
68 |
-
something else but here we use the value to indicate the number of words to
|
69 |
-
generate (so, ``40`` is an argument of the filter, not the value we want to
|
70 |
-
transform).
|
71 |
-
|
72 |
-
Next, let's use a ``lipsum`` *function*:
|
73 |
-
|
74 |
-
.. code-block:: jinja
|
75 |
-
|
76 |
-
{{ lipsum(40) }}
|
77 |
-
|
78 |
-
Here we go. For this specific example, the creation of a function is the
|
79 |
-
extension point to use. And you can use it anywhere an expression is accepted:
|
80 |
-
|
81 |
-
.. code-block:: jinja
|
82 |
-
|
83 |
-
{{ 'some text' ~ lipsum(40) ~ 'some more text' }}
|
84 |
-
|
85 |
-
{% set lipsum = lipsum(40) %}
|
86 |
-
|
87 |
-
Last but not the least, you can also use a *global* object with a method able
|
88 |
-
to generate lorem ipsum text:
|
89 |
-
|
90 |
-
.. code-block:: jinja
|
91 |
-
|
92 |
-
{{ text.lipsum(40) }}
|
93 |
-
|
94 |
-
As a rule of thumb, use functions for frequently used features and global
|
95 |
-
objects for everything else.
|
96 |
-
|
97 |
-
Keep in mind the following when you want to extend Twig:
|
98 |
-
|
99 |
-
========== ========================== ========== =========================
|
100 |
-
What? Implementation difficulty? How often? When?
|
101 |
-
========== ========================== ========== =========================
|
102 |
-
*macro* trivial frequent Content generation
|
103 |
-
*global* trivial frequent Helper object
|
104 |
-
*function* trivial frequent Content generation
|
105 |
-
*filter* trivial frequent Value transformation
|
106 |
-
*tag* complex rare DSL language construct
|
107 |
-
*test* trivial rare Boolean decision
|
108 |
-
*operator* trivial rare Values transformation
|
109 |
-
========== ========================== ========== =========================
|
110 |
-
|
111 |
-
Globals
|
112 |
-
-------
|
113 |
-
|
114 |
-
A global variable is like any other template variable, except that it's
|
115 |
-
available in all templates and macros::
|
116 |
-
|
117 |
-
$twig = new Twig_Environment($loader);
|
118 |
-
$twig->addGlobal('text', new Text());
|
119 |
-
|
120 |
-
You can then use the ``text`` variable anywhere in a template:
|
121 |
-
|
122 |
-
.. code-block:: jinja
|
123 |
-
|
124 |
-
{{ text.lipsum(40) }}
|
125 |
-
|
126 |
-
Filters
|
127 |
-
-------
|
128 |
-
|
129 |
-
Creating a filter is as simple as associating a name with a PHP callable::
|
130 |
-
|
131 |
-
// an anonymous function
|
132 |
-
$filter = new Twig_SimpleFilter('rot13', function ($string) {
|
133 |
-
return str_rot13($string);
|
134 |
-
});
|
135 |
-
|
136 |
-
// or a simple PHP function
|
137 |
-
$filter = new Twig_SimpleFilter('rot13', 'str_rot13');
|
138 |
-
|
139 |
-
// or a class static method
|
140 |
-
$filter = new Twig_SimpleFilter('rot13', array('SomeClass', 'rot13Filter'));
|
141 |
-
$filter = new Twig_SimpleFilter('rot13', 'SomeClass::rot13Filter');
|
142 |
-
|
143 |
-
// or a class method
|
144 |
-
$filter = new Twig_SimpleFilter('rot13', array($this, 'rot13Filter'));
|
145 |
-
// the one below needs a runtime implementation (see below for more information)
|
146 |
-
$filter = new Twig_SimpleFilter('rot13', array('SomeClass', 'rot13Filter'));
|
147 |
-
|
148 |
-
The first argument passed to the ``Twig_SimpleFilter`` constructor is the name
|
149 |
-
of the filter you will use in templates and the second one is the PHP callable
|
150 |
-
to associate with it.
|
151 |
-
|
152 |
-
Then, add the filter to your Twig environment::
|
153 |
-
|
154 |
-
$twig = new Twig_Environment($loader);
|
155 |
-
$twig->addFilter($filter);
|
156 |
-
|
157 |
-
And here is how to use it in a template:
|
158 |
-
|
159 |
-
.. code-block:: jinja
|
160 |
-
|
161 |
-
{{ 'Twig'|rot13 }}
|
162 |
-
|
163 |
-
{# will output Gjvt #}
|
164 |
-
|
165 |
-
When called by Twig, the PHP callable receives the left side of the filter
|
166 |
-
(before the pipe ``|``) as the first argument and the extra arguments passed
|
167 |
-
to the filter (within parentheses ``()``) as extra arguments.
|
168 |
-
|
169 |
-
For instance, the following code:
|
170 |
-
|
171 |
-
.. code-block:: jinja
|
172 |
-
|
173 |
-
{{ 'TWIG'|lower }}
|
174 |
-
{{ now|date('d/m/Y') }}
|
175 |
-
|
176 |
-
is compiled to something like the following::
|
177 |
-
|
178 |
-
<?php echo strtolower('TWIG') ?>
|
179 |
-
<?php echo twig_date_format_filter($now, 'd/m/Y') ?>
|
180 |
-
|
181 |
-
The ``Twig_SimpleFilter`` class takes an array of options as its last
|
182 |
-
argument::
|
183 |
-
|
184 |
-
$filter = new Twig_SimpleFilter('rot13', 'str_rot13', $options);
|
185 |
-
|
186 |
-
Environment-aware Filters
|
187 |
-
~~~~~~~~~~~~~~~~~~~~~~~~~
|
188 |
-
|
189 |
-
If you want to access the current environment instance in your filter, set the
|
190 |
-
``needs_environment`` option to ``true``; Twig will pass the current
|
191 |
-
environment as the first argument to the filter call::
|
192 |
-
|
193 |
-
$filter = new Twig_SimpleFilter('rot13', function (Twig_Environment $env, $string) {
|
194 |
-
// get the current charset for instance
|
195 |
-
$charset = $env->getCharset();
|
196 |
-
|
197 |
-
return str_rot13($string);
|
198 |
-
}, array('needs_environment' => true));
|
199 |
-
|
200 |
-
Context-aware Filters
|
201 |
-
~~~~~~~~~~~~~~~~~~~~~
|
202 |
-
|
203 |
-
If you want to access the current context in your filter, set the
|
204 |
-
``needs_context`` option to ``true``; Twig will pass the current context as
|
205 |
-
the first argument to the filter call (or the second one if
|
206 |
-
``needs_environment`` is also set to ``true``)::
|
207 |
-
|
208 |
-
$filter = new Twig_SimpleFilter('rot13', function ($context, $string) {
|
209 |
-
// ...
|
210 |
-
}, array('needs_context' => true));
|
211 |
-
|
212 |
-
$filter = new Twig_SimpleFilter('rot13', function (Twig_Environment $env, $context, $string) {
|
213 |
-
// ...
|
214 |
-
}, array('needs_context' => true, 'needs_environment' => true));
|
215 |
-
|
216 |
-
Automatic Escaping
|
217 |
-
~~~~~~~~~~~~~~~~~~
|
218 |
-
|
219 |
-
If automatic escaping is enabled, the output of the filter may be escaped
|
220 |
-
before printing. If your filter acts as an escaper (or explicitly outputs HTML
|
221 |
-
or JavaScript code), you will want the raw output to be printed. In such a
|
222 |
-
case, set the ``is_safe`` option::
|
223 |
-
|
224 |
-
$filter = new Twig_SimpleFilter('nl2br', 'nl2br', array('is_safe' => array('html')));
|
225 |
-
|
226 |
-
Some filters may need to work on input that is already escaped or safe, for
|
227 |
-
example when adding (safe) HTML tags to originally unsafe output. In such a
|
228 |
-
case, set the ``pre_escape`` option to escape the input data before it is run
|
229 |
-
through your filter::
|
230 |
-
|
231 |
-
$filter = new Twig_SimpleFilter('somefilter', 'somefilter', array('pre_escape' => 'html', 'is_safe' => array('html')));
|
232 |
-
|
233 |
-
Variadic Filters
|
234 |
-
~~~~~~~~~~~~~~~~
|
235 |
-
|
236 |
-
.. versionadded:: 1.19
|
237 |
-
Support for variadic filters was added in Twig 1.19.
|
238 |
-
|
239 |
-
When a filter should accept an arbitrary number of arguments, set the
|
240 |
-
``is_variadic`` option to ``true``; Twig will pass the extra arguments as the
|
241 |
-
last argument to the filter call as an array::
|
242 |
-
|
243 |
-
$filter = new Twig_SimpleFilter('thumbnail', function ($file, array $options = array()) {
|
244 |
-
// ...
|
245 |
-
}, array('is_variadic' => true));
|
246 |
-
|
247 |
-
Be warned that named arguments passed to a variadic filter cannot be checked
|
248 |
-
for validity as they will automatically end up in the option array.
|
249 |
-
|
250 |
-
Dynamic Filters
|
251 |
-
~~~~~~~~~~~~~~~
|
252 |
-
|
253 |
-
A filter name containing the special ``*`` character is a dynamic filter as
|
254 |
-
the ``*`` can be any string::
|
255 |
-
|
256 |
-
$filter = new Twig_SimpleFilter('*_path', function ($name, $arguments) {
|
257 |
-
// ...
|
258 |
-
});
|
259 |
-
|
260 |
-
The following filters will be matched by the above defined dynamic filter:
|
261 |
-
|
262 |
-
* ``product_path``
|
263 |
-
* ``category_path``
|
264 |
-
|
265 |
-
A dynamic filter can define more than one dynamic parts::
|
266 |
-
|
267 |
-
$filter = new Twig_SimpleFilter('*_path_*', function ($name, $suffix, $arguments) {
|
268 |
-
// ...
|
269 |
-
});
|
270 |
-
|
271 |
-
The filter will receive all dynamic part values before the normal filter
|
272 |
-
arguments, but after the environment and the context. For instance, a call to
|
273 |
-
``'foo'|a_path_b()`` will result in the following arguments to be passed to
|
274 |
-
the filter: ``('a', 'b', 'foo')``.
|
275 |
-
|
276 |
-
Deprecated Filters
|
277 |
-
~~~~~~~~~~~~~~~~~~
|
278 |
-
|
279 |
-
.. versionadded:: 1.21
|
280 |
-
Support for deprecated filters was added in Twig 1.21.
|
281 |
-
|
282 |
-
You can mark a filter as being deprecated by setting the ``deprecated`` option
|
283 |
-
to ``true``. You can also give an alternative filter that replaces the
|
284 |
-
deprecated one when that makes sense::
|
285 |
-
|
286 |
-
$filter = new Twig_SimpleFilter('obsolete', function () {
|
287 |
-
// ...
|
288 |
-
}, array('deprecated' => true, 'alternative' => 'new_one'));
|
289 |
-
|
290 |
-
When a filter is deprecated, Twig emits a deprecation notice when compiling a
|
291 |
-
template using it. See :ref:`deprecation-notices` for more information.
|
292 |
-
|
293 |
-
Functions
|
294 |
-
---------
|
295 |
-
|
296 |
-
Functions are defined in the exact same way as filters, but you need to create
|
297 |
-
an instance of ``Twig_SimpleFunction``::
|
298 |
-
|
299 |
-
$twig = new Twig_Environment($loader);
|
300 |
-
$function = new Twig_SimpleFunction('function_name', function () {
|
301 |
-
// ...
|
302 |
-
});
|
303 |
-
$twig->addFunction($function);
|
304 |
-
|
305 |
-
Functions support the same features as filters, except for the ``pre_escape``
|
306 |
-
and ``preserves_safety`` options.
|
307 |
-
|
308 |
-
Tests
|
309 |
-
-----
|
310 |
-
|
311 |
-
Tests are defined in the exact same way as filters and functions, but you need
|
312 |
-
to create an instance of ``Twig_SimpleTest``::
|
313 |
-
|
314 |
-
$twig = new Twig_Environment($loader);
|
315 |
-
$test = new Twig_SimpleTest('test_name', function () {
|
316 |
-
// ...
|
317 |
-
});
|
318 |
-
$twig->addTest($test);
|
319 |
-
|
320 |
-
Tests allow you to create custom application specific logic for evaluating
|
321 |
-
boolean conditions. As a simple example, let's create a Twig test that checks if
|
322 |
-
objects are 'red'::
|
323 |
-
|
324 |
-
$twig = new Twig_Environment($loader);
|
325 |
-
$test = new Twig_SimpleTest('red', function ($value) {
|
326 |
-
if (isset($value->color) && $value->color == 'red') {
|
327 |
-
return true;
|
328 |
-
}
|
329 |
-
if (isset($value->paint) && $value->paint == 'red') {
|
330 |
-
return true;
|
331 |
-
}
|
332 |
-
return false;
|
333 |
-
});
|
334 |
-
$twig->addTest($test);
|
335 |
-
|
336 |
-
Test functions should always return true/false.
|
337 |
-
|
338 |
-
When creating tests you can use the ``node_class`` option to provide custom test
|
339 |
-
compilation. This is useful if your test can be compiled into PHP primitives.
|
340 |
-
This is used by many of the tests built into Twig::
|
341 |
-
|
342 |
-
$twig = new Twig_Environment($loader);
|
343 |
-
$test = new Twig_SimpleTest(
|
344 |
-
'odd',
|
345 |
-
null,
|
346 |
-
array('node_class' => 'Twig_Node_Expression_Test_Odd'));
|
347 |
-
$twig->addTest($test);
|
348 |
-
|
349 |
-
class Twig_Node_Expression_Test_Odd extends Twig_Node_Expression_Test
|
350 |
-
{
|
351 |
-
public function compile(Twig_Compiler $compiler)
|
352 |
-
{
|
353 |
-
$compiler
|
354 |
-
->raw('(')
|
355 |
-
->subcompile($this->getNode('node'))
|
356 |
-
->raw(' % 2 == 1')
|
357 |
-
->raw(')')
|
358 |
-
;
|
359 |
-
}
|
360 |
-
}
|
361 |
-
|
362 |
-
The above example shows how you can create tests that use a node class. The
|
363 |
-
node class has access to one sub-node called 'node'. This sub-node contains the
|
364 |
-
value that is being tested. When the ``odd`` filter is used in code such as:
|
365 |
-
|
366 |
-
.. code-block:: jinja
|
367 |
-
|
368 |
-
{% if my_value is odd %}
|
369 |
-
|
370 |
-
The ``node`` sub-node will contain an expression of ``my_value``. Node-based
|
371 |
-
tests also have access to the ``arguments`` node. This node will contain the
|
372 |
-
various other arguments that have been provided to your test.
|
373 |
-
|
374 |
-
If you want to pass a variable number of positional or named arguments to the
|
375 |
-
test, set the ``is_variadic`` option to ``true``. Tests also support dynamic
|
376 |
-
name feature as filters and functions.
|
377 |
-
|
378 |
-
Tags
|
379 |
-
----
|
380 |
-
|
381 |
-
One of the most exciting features of a template engine like Twig is the
|
382 |
-
possibility to define new language constructs. This is also the most complex
|
383 |
-
feature as you need to understand how Twig's internals work.
|
384 |
-
|
385 |
-
Let's create a simple ``set`` tag that allows the definition of simple
|
386 |
-
variables from within a template. The tag can be used like follows:
|
387 |
-
|
388 |
-
.. code-block:: jinja
|
389 |
-
|
390 |
-
{% set name = "value" %}
|
391 |
-
|
392 |
-
{{ name }}
|
393 |
-
|
394 |
-
{# should output value #}
|
395 |
-
|
396 |
-
.. note::
|
397 |
-
|
398 |
-
The ``set`` tag is part of the Core extension and as such is always
|
399 |
-
available. The built-in version is slightly more powerful and supports
|
400 |
-
multiple assignments by default (cf. the template designers chapter for
|
401 |
-
more information).
|
402 |
-
|
403 |
-
Three steps are needed to define a new tag:
|
404 |
-
|
405 |
-
* Defining a Token Parser class (responsible for parsing the template code);
|
406 |
-
|
407 |
-
* Defining a Node class (responsible for converting the parsed code to PHP);
|
408 |
-
|
409 |
-
* Registering the tag.
|
410 |
-
|
411 |
-
Registering a new tag
|
412 |
-
~~~~~~~~~~~~~~~~~~~~~
|
413 |
-
|
414 |
-
Adding a tag is as simple as calling the ``addTokenParser`` method on the
|
415 |
-
``Twig_Environment`` instance::
|
416 |
-
|
417 |
-
$twig = new Twig_Environment($loader);
|
418 |
-
$twig->addTokenParser(new Project_Set_TokenParser());
|
419 |
-
|
420 |
-
Defining a Token Parser
|
421 |
-
~~~~~~~~~~~~~~~~~~~~~~~
|
422 |
-
|
423 |
-
Now, let's see the actual code of this class::
|
424 |
-
|
425 |
-
class Project_Set_TokenParser extends Twig_TokenParser
|
426 |
-
{
|
427 |
-
public function parse(Twig_Token $token)
|
428 |
-
{
|
429 |
-
$parser = $this->parser;
|
430 |
-
$stream = $parser->getStream();
|
431 |
-
|
432 |
-
$name = $stream->expect(Twig_Token::NAME_TYPE)->getValue();
|
433 |
-
$stream->expect(Twig_Token::OPERATOR_TYPE, '=');
|
434 |
-
$value = $parser->getExpressionParser()->parseExpression();
|
435 |
-
$stream->expect(Twig_Token::BLOCK_END_TYPE);
|
436 |
-
|
437 |
-
return new Project_Set_Node($name, $value, $token->getLine(), $this->getTag());
|
438 |
-
}
|
439 |
-
|
440 |
-
public function getTag()
|
441 |
-
{
|
442 |
-
return 'set';
|
443 |
-
}
|
444 |
-
}
|
445 |
-
|
446 |
-
The ``getTag()`` method must return the tag we want to parse, here ``set``.
|
447 |
-
|
448 |
-
The ``parse()`` method is invoked whenever the parser encounters a ``set``
|
449 |
-
tag. It should return a ``Twig_Node`` instance that represents the node (the
|
450 |
-
``Project_Set_Node`` calls creating is explained in the next section).
|
451 |
-
|
452 |
-
The parsing process is simplified thanks to a bunch of methods you can call
|
453 |
-
from the token stream (``$this->parser->getStream()``):
|
454 |
-
|
455 |
-
* ``getCurrent()``: Gets the current token in the stream.
|
456 |
-
|
457 |
-
* ``next()``: Moves to the next token in the stream, *but returns the old one*.
|
458 |
-
|
459 |
-
* ``test($type)``, ``test($value)`` or ``test($type, $value)``: Determines whether
|
460 |
-
the current token is of a particular type or value (or both). The value may be an
|
461 |
-
array of several possible values.
|
462 |
-
|
463 |
-
* ``expect($type[, $value[, $message]])``: If the current token isn't of the given
|
464 |
-
type/value a syntax error is thrown. Otherwise, if the type and value are correct,
|
465 |
-
the token is returned and the stream moves to the next token.
|
466 |
-
|
467 |
-
* ``look()``: Looks a the next token without consuming it.
|
468 |
-
|
469 |
-
Parsing expressions is done by calling the ``parseExpression()`` like we did for
|
470 |
-
the ``set`` tag.
|
471 |
-
|
472 |
-
.. tip::
|
473 |
-
|
474 |
-
Reading the existing ``TokenParser`` classes is the best way to learn all
|
475 |
-
the nitty-gritty details of the parsing process.
|
476 |
-
|
477 |
-
Defining a Node
|
478 |
-
~~~~~~~~~~~~~~~
|
479 |
-
|
480 |
-
The ``Project_Set_Node`` class itself is rather simple::
|
481 |
-
|
482 |
-
class Project_Set_Node extends Twig_Node
|
483 |
-
{
|
484 |
-
public function __construct($name, Twig_Node_Expression $value, $line, $tag = null)
|
485 |
-
{
|
486 |
-
parent::__construct(array('value' => $value), array('name' => $name), $line, $tag);
|
487 |
-
}
|
488 |
-
|
489 |
-
public function compile(Twig_Compiler $compiler)
|
490 |
-
{
|
491 |
-
$compiler
|
492 |
-
->addDebugInfo($this)
|
493 |
-
->write('$context[\''.$this->getAttribute('name').'\'] = ')
|
494 |
-
->subcompile($this->getNode('value'))
|
495 |
-
->raw(";\n")
|
496 |
-
;
|
497 |
-
}
|
498 |
-
}
|
499 |
-
|
500 |
-
The compiler implements a fluid interface and provides methods that helps the
|
501 |
-
developer generate beautiful and readable PHP code:
|
502 |
-
|
503 |
-
* ``subcompile()``: Compiles a node.
|
504 |
-
|
505 |
-
* ``raw()``: Writes the given string as is.
|
506 |
-
|
507 |
-
* ``write()``: Writes the given string by adding indentation at the beginning
|
508 |
-
of each line.
|
509 |
-
|
510 |
-
* ``string()``: Writes a quoted string.
|
511 |
-
|
512 |
-
* ``repr()``: Writes a PHP representation of a given value (see
|
513 |
-
``Twig_Node_For`` for a usage example).
|
514 |
-
|
515 |
-
* ``addDebugInfo()``: Adds the line of the original template file related to
|
516 |
-
the current node as a comment.
|
517 |
-
|
518 |
-
* ``indent()``: Indents the generated code (see ``Twig_Node_Block`` for a
|
519 |
-
usage example).
|
520 |
-
|
521 |
-
* ``outdent()``: Outdents the generated code (see ``Twig_Node_Block`` for a
|
522 |
-
usage example).
|
523 |
-
|
524 |
-
.. _creating_extensions:
|
525 |
-
|
526 |
-
Creating an Extension
|
527 |
-
---------------------
|
528 |
-
|
529 |
-
The main motivation for writing an extension is to move often used code into a
|
530 |
-
reusable class like adding support for internationalization. An extension can
|
531 |
-
define tags, filters, tests, operators, global variables, functions, and node
|
532 |
-
visitors.
|
533 |
-
|
534 |
-
Most of the time, it is useful to create a single extension for your project,
|
535 |
-
to host all the specific tags and filters you want to add to Twig.
|
536 |
-
|
537 |
-
.. tip::
|
538 |
-
|
539 |
-
When packaging your code into an extension, Twig is smart enough to
|
540 |
-
recompile your templates whenever you make a change to it (when
|
541 |
-
``auto_reload`` is enabled).
|
542 |
-
|
543 |
-
.. note::
|
544 |
-
|
545 |
-
Before writing your own extensions, have a look at the Twig official
|
546 |
-
extension repository: http://github.com/twigphp/Twig-extensions.
|
547 |
-
|
548 |
-
An extension is a class that implements the following interface::
|
549 |
-
|
550 |
-
interface Twig_ExtensionInterface
|
551 |
-
{
|
552 |
-
/**
|
553 |
-
* Initializes the runtime environment.
|
554 |
-
*
|
555 |
-
* This is where you can load some file that contains filter functions for instance.
|
556 |
-
*
|
557 |
-
* @deprecated since 1.23 (to be removed in 2.0), implement Twig_Extension_InitRuntimeInterface instead
|
558 |
-
*/
|
559 |
-
function initRuntime(Twig_Environment $environment);
|
560 |
-
|
561 |
-
/**
|
562 |
-
* Returns the token parser instances to add to the existing list.
|
563 |
-
*
|
564 |
-
* @return (Twig_TokenParserInterface|Twig_TokenParserBrokerInterface)[]
|
565 |
-
*/
|
566 |
-
function getTokenParsers();
|
567 |
-
|
568 |
-
/**
|
569 |
-
* Returns the node visitor instances to add to the existing list.
|
570 |
-
*
|
571 |
-
* @return Twig_NodeVisitorInterface[]
|
572 |
-
*/
|
573 |
-
function getNodeVisitors();
|
574 |
-
|
575 |
-
/**
|
576 |
-
* Returns a list of filters to add to the existing list.
|
577 |
-
*
|
578 |
-
* @return Twig_SimpleFilter[]
|
579 |
-
*/
|
580 |
-
function getFilters();
|
581 |
-
|
582 |
-
/**
|
583 |
-
* Returns a list of tests to add to the existing list.
|
584 |
-
*
|
585 |
-
* @return Twig_SimpleTest[]
|
586 |
-
*/
|
587 |
-
function getTests();
|
588 |
-
|
589 |
-
/**
|
590 |
-
* Returns a list of functions to add to the existing list.
|
591 |
-
*
|
592 |
-
* @return Twig_SimpleFunction[]
|
593 |
-
*/
|
594 |
-
function getFunctions();
|
595 |
-
|
596 |
-
/**
|
597 |
-
* Returns a list of operators to add to the existing list.
|
598 |
-
*
|
599 |
-
* @return array<array> First array of unary operators, second array of binary operators
|
600 |
-
*/
|
601 |
-
function getOperators();
|
602 |
-
|
603 |
-
/**
|
604 |
-
* Returns a list of global variables to add to the existing list.
|
605 |
-
*
|
606 |
-
* @return array An array of global variables
|
607 |
-
*
|
608 |
-
* @deprecated since 1.23 (to be removed in 2.0), implement Twig_Extension_GlobalsInterface instead
|
609 |
-
*/
|
610 |
-
function getGlobals();
|
611 |
-
|
612 |
-
/**
|
613 |
-
* Returns the name of the extension.
|
614 |
-
*
|
615 |
-
* @return string The extension name
|
616 |
-
*
|
617 |
-
* @deprecated since 1.26 (to be removed in 2.0), not used anymore internally
|
618 |
-
*/
|
619 |
-
function getName();
|
620 |
-
}
|
621 |
-
|
622 |
-
To keep your extension class clean and lean, inherit from the built-in
|
623 |
-
``Twig_Extension`` class instead of implementing the interface as it provides
|
624 |
-
empty implementations for all methods:
|
625 |
-
|
626 |
-
class Project_Twig_Extension extends Twig_Extension
|
627 |
-
{
|
628 |
-
}
|
629 |
-
|
630 |
-
Of course, this extension does nothing for now. We will customize it in the
|
631 |
-
next sections.
|
632 |
-
|
633 |
-
.. note::
|
634 |
-
|
635 |
-
Prior to Twig 1.26, you must implement the ``getName()`` method which must
|
636 |
-
return a unique identifier for the extension.
|
637 |
-
|
638 |
-
Twig does not care where you save your extension on the filesystem, as all
|
639 |
-
extensions must be registered explicitly to be available in your templates.
|
640 |
-
|
641 |
-
You can register an extension by using the ``addExtension()`` method on your
|
642 |
-
main ``Environment`` object::
|
643 |
-
|
644 |
-
$twig = new Twig_Environment($loader);
|
645 |
-
$twig->addExtension(new Project_Twig_Extension());
|
646 |
-
|
647 |
-
.. tip::
|
648 |
-
|
649 |
-
The Twig core extensions are great examples of how extensions work.
|
650 |
-
|
651 |
-
Globals
|
652 |
-
~~~~~~~
|
653 |
-
|
654 |
-
Global variables can be registered in an extension via the ``getGlobals()``
|
655 |
-
method::
|
656 |
-
|
657 |
-
class Project_Twig_Extension extends Twig_Extension implements Twig_Extension_GlobalsInterface
|
658 |
-
{
|
659 |
-
public function getGlobals()
|
660 |
-
{
|
661 |
-
return array(
|
662 |
-
'text' => new Text(),
|
663 |
-
);
|
664 |
-
}
|
665 |
-
|
666 |
-
// ...
|
667 |
-
}
|
668 |
-
|
669 |
-
Functions
|
670 |
-
~~~~~~~~~
|
671 |
-
|
672 |
-
Functions can be registered in an extension via the ``getFunctions()``
|
673 |
-
method::
|
674 |
-
|
675 |
-
class Project_Twig_Extension extends Twig_Extension
|
676 |
-
{
|
677 |
-
public function getFunctions()
|
678 |
-
{
|
679 |
-
return array(
|
680 |
-
new Twig_SimpleFunction('lipsum', 'generate_lipsum'),
|
681 |
-
);
|
682 |
-
}
|
683 |
-
|
684 |
-
// ...
|
685 |
-
}
|
686 |
-
|
687 |
-
Filters
|
688 |
-
~~~~~~~
|
689 |
-
|
690 |
-
To add a filter to an extension, you need to override the ``getFilters()``
|
691 |
-
method. This method must return an array of filters to add to the Twig
|
692 |
-
environment::
|
693 |
-
|
694 |
-
class Project_Twig_Extension extends Twig_Extension
|
695 |
-
{
|
696 |
-
public function getFilters()
|
697 |
-
{
|
698 |
-
return array(
|
699 |
-
new Twig_SimpleFilter('rot13', 'str_rot13'),
|
700 |
-
);
|
701 |
-
}
|
702 |
-
|
703 |
-
// ...
|
704 |
-
}
|
705 |
-
|
706 |
-
Tags
|
707 |
-
~~~~
|
708 |
-
|
709 |
-
Adding a tag in an extension can be done by overriding the
|
710 |
-
``getTokenParsers()`` method. This method must return an array of tags to add
|
711 |
-
to the Twig environment::
|
712 |
-
|
713 |
-
class Project_Twig_Extension extends Twig_Extension
|
714 |
-
{
|
715 |
-
public function getTokenParsers()
|
716 |
-
{
|
717 |
-
return array(new Project_Set_TokenParser());
|
718 |
-
}
|
719 |
-
|
720 |
-
// ...
|
721 |
-
}
|
722 |
-
|
723 |
-
In the above code, we have added a single new tag, defined by the
|
724 |
-
``Project_Set_TokenParser`` class. The ``Project_Set_TokenParser`` class is
|
725 |
-
responsible for parsing the tag and compiling it to PHP.
|
726 |
-
|
727 |
-
Operators
|
728 |
-
~~~~~~~~~
|
729 |
-
|
730 |
-
The ``getOperators()`` methods lets you add new operators. Here is how to add
|
731 |
-
``!``, ``||``, and ``&&`` operators::
|
732 |
-
|
733 |
-
class Project_Twig_Extension extends Twig_Extension
|
734 |
-
{
|
735 |
-
public function getOperators()
|
736 |
-
{
|
737 |
-
return array(
|
738 |
-
array(
|
739 |
-
'!' => array('precedence' => 50, 'class' => 'Twig_Node_Expression_Unary_Not'),
|
740 |
-
),
|
741 |
-
array(
|
742 |
-
'||' => array('precedence' => 10, 'class' => 'Twig_Node_Expression_Binary_Or', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
|
743 |
-
'&&' => array('precedence' => 15, 'class' => 'Twig_Node_Expression_Binary_And', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
|
744 |
-
),
|
745 |
-
);
|
746 |
-
}
|
747 |
-
|
748 |
-
// ...
|
749 |
-
}
|
750 |
-
|
751 |
-
Tests
|
752 |
-
~~~~~
|
753 |
-
|
754 |
-
The ``getTests()`` method lets you add new test functions::
|
755 |
-
|
756 |
-
class Project_Twig_Extension extends Twig_Extension
|
757 |
-
{
|
758 |
-
public function getTests()
|
759 |
-
{
|
760 |
-
return array(
|
761 |
-
new Twig_SimpleTest('even', 'twig_test_even'),
|
762 |
-
);
|
763 |
-
}
|
764 |
-
|
765 |
-
// ...
|
766 |
-
}
|
767 |
-
|
768 |
-
Definition vs Runtime
|
769 |
-
~~~~~~~~~~~~~~~~~~~~~
|
770 |
-
|
771 |
-
Twig filters, functions, and tests runtime implementations can be defined as
|
772 |
-
any valid PHP callable:
|
773 |
-
|
774 |
-
* **functions/static methods**: Simple to implement and fast (used by all Twig
|
775 |
-
core extensions); but it is hard for the runtime to depend on external
|
776 |
-
objects;
|
777 |
-
|
778 |
-
* **closures**: Simple to implement;
|
779 |
-
|
780 |
-
* **object methods**: More flexible and required if your runtime code depends
|
781 |
-
on external objects.
|
782 |
-
|
783 |
-
The simplest way to use methods is to define them on the extension itself::
|
784 |
-
|
785 |
-
class Project_Twig_Extension extends Twig_Extension
|
786 |
-
{
|
787 |
-
private $rot13Provider;
|
788 |
-
|
789 |
-
public function __construct($rot13Provider)
|
790 |
-
{
|
791 |
-
$this->rot13Provider = $rot13Provider;
|
792 |
-
}
|
793 |
-
|
794 |
-
public function getFunctions()
|
795 |
-
{
|
796 |
-
return array(
|
797 |
-
new Twig_SimpleFunction('rot13', array($this, 'rot13')),
|
798 |
-
);
|
799 |
-
}
|
800 |
-
|
801 |
-
public function rot13($value)
|
802 |
-
{
|
803 |
-
return $rot13Provider->rot13($value);
|
804 |
-
}
|
805 |
-
}
|
806 |
-
|
807 |
-
This is very convenient but not recommended as it makes template compilation
|
808 |
-
depend on runtime dependencies even if they are not needed (think for instance
|
809 |
-
as a dependency that connects to a database engine).
|
810 |
-
|
811 |
-
As of Twig 1.26, you can easily decouple the extension definitions from their
|
812 |
-
runtime implementations by registering a ``Twig_RuntimeLoaderInterface``
|
813 |
-
instance on the environment that knows how to instantiate such runtime classes
|
814 |
-
(runtime classes must be autoload-able)::
|
815 |
-
|
816 |
-
class RuntimeLoader implements Twig_RuntimeLoaderInterface
|
817 |
-
{
|
818 |
-
public function load($class)
|
819 |
-
{
|
820 |
-
// implement the logic to create an instance of $class
|
821 |
-
// and inject its dependencies
|
822 |
-
// most of the time, it means using your dependency injection container
|
823 |
-
if ('Project_Twig_RuntimeExtension' === $class) {
|
824 |
-
return new $class(new Rot13Provider());
|
825 |
-
} else {
|
826 |
-
// ...
|
827 |
-
}
|
828 |
-
}
|
829 |
-
}
|
830 |
-
|
831 |
-
$twig->addRuntimeLoader(new RuntimeLoader());
|
832 |
-
|
833 |
-
.. note::
|
834 |
-
|
835 |
-
As of Twig 1.32, Twig comes with a PSR-11 compatible runtime loader
|
836 |
-
(``Twig_ContainerRuntimeLoader``) that works on PHP 5.3+.
|
837 |
-
|
838 |
-
It is now possible to move the runtime logic to a new
|
839 |
-
``Project_Twig_RuntimeExtension`` class and use it directly in the extension::
|
840 |
-
|
841 |
-
class Project_Twig_RuntimeExtension extends Twig_Extension
|
842 |
-
{
|
843 |
-
private $rot13Provider;
|
844 |
-
|
845 |
-
public function __construct($rot13Provider)
|
846 |
-
{
|
847 |
-
$this->rot13Provider = $rot13Provider;
|
848 |
-
}
|
849 |
-
|
850 |
-
public function rot13($value)
|
851 |
-
{
|
852 |
-
return $rot13Provider->rot13($value);
|
853 |
-
}
|
854 |
-
}
|
855 |
-
|
856 |
-
class Project_Twig_Extension extends Twig_Extension
|
857 |
-
{
|
858 |
-
public function getFunctions()
|
859 |
-
{
|
860 |
-
return array(
|
861 |
-
new Twig_SimpleFunction('rot13', array('Project_Twig_RuntimeExtension', 'rot13')),
|
862 |
-
// or
|
863 |
-
new Twig_SimpleFunction('rot13', 'Project_Twig_RuntimeExtension::rot13'),
|
864 |
-
);
|
865 |
-
}
|
866 |
-
}
|
867 |
-
|
868 |
-
Overloading
|
869 |
-
-----------
|
870 |
-
|
871 |
-
To overload an already defined filter, test, operator, global variable, or
|
872 |
-
function, re-define it in an extension and register it **as late as
|
873 |
-
possible** (order matters)::
|
874 |
-
|
875 |
-
class MyCoreExtension extends Twig_Extension
|
876 |
-
{
|
877 |
-
public function getFilters()
|
878 |
-
{
|
879 |
-
return array(
|
880 |
-
new Twig_SimpleFilter('date', array($this, 'dateFilter')),
|
881 |
-
);
|
882 |
-
}
|
883 |
-
|
884 |
-
public function dateFilter($timestamp, $format = 'F j, Y H:i')
|
885 |
-
{
|
886 |
-
// do something different from the built-in date filter
|
887 |
-
}
|
888 |
-
}
|
889 |
-
|
890 |
-
$twig = new Twig_Environment($loader);
|
891 |
-
$twig->addExtension(new MyCoreExtension());
|
892 |
-
|
893 |
-
Here, we have overloaded the built-in ``date`` filter with a custom one.
|
894 |
-
|
895 |
-
If you do the same on the ``Twig_Environment`` itself, beware that it takes
|
896 |
-
precedence over any other registered extensions::
|
897 |
-
|
898 |
-
$twig = new Twig_Environment($loader);
|
899 |
-
$twig->addFilter(new Twig_SimpleFilter('date', function ($timestamp, $format = 'F j, Y H:i') {
|
900 |
-
// do something different from the built-in date filter
|
901 |
-
}));
|
902 |
-
// the date filter will come from the above registration, not
|
903 |
-
// from the registered extension below
|
904 |
-
$twig->addExtension(new MyCoreExtension());
|
905 |
-
|
906 |
-
.. caution::
|
907 |
-
|
908 |
-
Note that overloading the built-in Twig elements is not recommended as it
|
909 |
-
might be confusing.
|
910 |
-
|
911 |
-
Testing an Extension
|
912 |
-
--------------------
|
913 |
-
|
914 |
-
Functional Tests
|
915 |
-
~~~~~~~~~~~~~~~~
|
916 |
-
|
917 |
-
You can create functional tests for extensions simply by creating the
|
918 |
-
following file structure in your test directory::
|
919 |
-
|
920 |
-
Fixtures/
|
921 |
-
filters/
|
922 |
-
foo.test
|
923 |
-
bar.test
|
924 |
-
functions/
|
925 |
-
foo.test
|
926 |
-
bar.test
|
927 |
-
tags/
|
928 |
-
foo.test
|
929 |
-
bar.test
|
930 |
-
IntegrationTest.php
|
931 |
-
|
932 |
-
The ``IntegrationTest.php`` file should look like this::
|
933 |
-
|
934 |
-
class Project_Tests_IntegrationTest extends Twig_Test_IntegrationTestCase
|
935 |
-
{
|
936 |
-
public function getExtensions()
|
937 |
-
{
|
938 |
-
return array(
|
939 |
-
new Project_Twig_Extension1(),
|
940 |
-
new Project_Twig_Extension2(),
|
941 |
-
);
|
942 |
-
}
|
943 |
-
|
944 |
-
public function getFixturesDir()
|
945 |
-
{
|
946 |
-
return dirname(__FILE__).'/Fixtures/';
|
947 |
-
}
|
948 |
-
}
|
949 |
-
|
950 |
-
Fixtures examples can be found within the Twig repository
|
951 |
-
`tests/Twig/Fixtures`_ directory.
|
952 |
-
|
953 |
-
Node Tests
|
954 |
-
~~~~~~~~~~
|
955 |
-
|
956 |
-
Testing the node visitors can be complex, so extend your test cases from
|
957 |
-
``Twig_Test_NodeTestCase``. Examples can be found in the Twig repository
|
958 |
-
`tests/Twig/Node`_ directory.
|
959 |
-
|
960 |
-
.. _`rot13`: http://www.php.net/manual/en/function.str-rot13.php
|
961 |
-
.. _`tests/Twig/Fixtures`: https://github.com/twigphp/Twig/tree/master/test/Twig/Tests/Fixtures
|
962 |
-
.. _`tests/Twig/Node`: https://github.com/twigphp/Twig/tree/master/test/Twig/Tests/Node
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/advanced_legacy.rst
DELETED
@@ -1,885 +0,0 @@
|
|
1 |
-
Extending Twig
|
2 |
-
==============
|
3 |
-
|
4 |
-
.. caution::
|
5 |
-
|
6 |
-
This section describes how to extends Twig for versions **older than
|
7 |
-
1.12**. If you are using a newer version, read the :doc:`newer<advanced>`
|
8 |
-
chapter instead.
|
9 |
-
|
10 |
-
Twig can be extended in many ways; you can add extra tags, filters, tests,
|
11 |
-
operators, global variables, and functions. You can even extend the parser
|
12 |
-
itself with node visitors.
|
13 |
-
|
14 |
-
.. note::
|
15 |
-
|
16 |
-
The first section of this chapter describes how to extend Twig easily. If
|
17 |
-
you want to reuse your changes in different projects or if you want to
|
18 |
-
share them with others, you should then create an extension as described
|
19 |
-
in the following section.
|
20 |
-
|
21 |
-
.. caution::
|
22 |
-
|
23 |
-
When extending Twig by calling methods on the Twig environment instance,
|
24 |
-
Twig won't be able to recompile your templates when the PHP code is
|
25 |
-
updated. To see your changes in real-time, either disable template caching
|
26 |
-
or package your code into an extension (see the next section of this
|
27 |
-
chapter).
|
28 |
-
|
29 |
-
Before extending Twig, you must understand the differences between all the
|
30 |
-
different possible extension points and when to use them.
|
31 |
-
|
32 |
-
First, remember that Twig has two main language constructs:
|
33 |
-
|
34 |
-
* ``{{ }}``: used to print the result of an expression evaluation;
|
35 |
-
|
36 |
-
* ``{% %}``: used to execute statements.
|
37 |
-
|
38 |
-
To understand why Twig exposes so many extension points, let's see how to
|
39 |
-
implement a *Lorem ipsum* generator (it needs to know the number of words to
|
40 |
-
generate).
|
41 |
-
|
42 |
-
You can use a ``lipsum`` *tag*:
|
43 |
-
|
44 |
-
.. code-block:: jinja
|
45 |
-
|
46 |
-
{% lipsum 40 %}
|
47 |
-
|
48 |
-
That works, but using a tag for ``lipsum`` is not a good idea for at least
|
49 |
-
three main reasons:
|
50 |
-
|
51 |
-
* ``lipsum`` is not a language construct;
|
52 |
-
* The tag outputs something;
|
53 |
-
* The tag is not flexible as you cannot use it in an expression:
|
54 |
-
|
55 |
-
.. code-block:: jinja
|
56 |
-
|
57 |
-
{{ 'some text' ~ {% lipsum 40 %} ~ 'some more text' }}
|
58 |
-
|
59 |
-
In fact, you rarely need to create tags; and that's good news because tags are
|
60 |
-
the most complex extension point of Twig.
|
61 |
-
|
62 |
-
Now, let's use a ``lipsum`` *filter*:
|
63 |
-
|
64 |
-
.. code-block:: jinja
|
65 |
-
|
66 |
-
{{ 40|lipsum }}
|
67 |
-
|
68 |
-
Again, it works, but it looks weird. A filter transforms the passed value to
|
69 |
-
something else but here we use the value to indicate the number of words to
|
70 |
-
generate (so, ``40`` is an argument of the filter, not the value we want to
|
71 |
-
transform).
|
72 |
-
|
73 |
-
Next, let's use a ``lipsum`` *function*:
|
74 |
-
|
75 |
-
.. code-block:: jinja
|
76 |
-
|
77 |
-
{{ lipsum(40) }}
|
78 |
-
|
79 |
-
Here we go. For this specific example, the creation of a function is the
|
80 |
-
extension point to use. And you can use it anywhere an expression is accepted:
|
81 |
-
|
82 |
-
.. code-block:: jinja
|
83 |
-
|
84 |
-
{{ 'some text' ~ ipsum(40) ~ 'some more text' }}
|
85 |
-
|
86 |
-
{% set ipsum = ipsum(40) %}
|
87 |
-
|
88 |
-
Last but not the least, you can also use a *global* object with a method able
|
89 |
-
to generate lorem ipsum text:
|
90 |
-
|
91 |
-
.. code-block:: jinja
|
92 |
-
|
93 |
-
{{ text.lipsum(40) }}
|
94 |
-
|
95 |
-
As a rule of thumb, use functions for frequently used features and global
|
96 |
-
objects for everything else.
|
97 |
-
|
98 |
-
Keep in mind the following when you want to extend Twig:
|
99 |
-
|
100 |
-
========== ========================== ========== =========================
|
101 |
-
What? Implementation difficulty? How often? When?
|
102 |
-
========== ========================== ========== =========================
|
103 |
-
*macro* trivial frequent Content generation
|
104 |
-
*global* trivial frequent Helper object
|
105 |
-
*function* trivial frequent Content generation
|
106 |
-
*filter* trivial frequent Value transformation
|
107 |
-
*tag* complex rare DSL language construct
|
108 |
-
*test* trivial rare Boolean decision
|
109 |
-
*operator* trivial rare Values transformation
|
110 |
-
========== ========================== ========== =========================
|
111 |
-
|
112 |
-
Globals
|
113 |
-
-------
|
114 |
-
|
115 |
-
A global variable is like any other template variable, except that it's
|
116 |
-
available in all templates and macros::
|
117 |
-
|
118 |
-
$twig = new Twig_Environment($loader);
|
119 |
-
$twig->addGlobal('text', new Text());
|
120 |
-
|
121 |
-
You can then use the ``text`` variable anywhere in a template:
|
122 |
-
|
123 |
-
.. code-block:: jinja
|
124 |
-
|
125 |
-
{{ text.lipsum(40) }}
|
126 |
-
|
127 |
-
Filters
|
128 |
-
-------
|
129 |
-
|
130 |
-
A filter is a regular PHP function or an object method that takes the left
|
131 |
-
side of the filter (before the pipe ``|``) as first argument and the extra
|
132 |
-
arguments passed to the filter (within parentheses ``()``) as extra arguments.
|
133 |
-
|
134 |
-
Defining a filter is as easy as associating the filter name with a PHP
|
135 |
-
callable. For instance, let's say you have the following code in a template:
|
136 |
-
|
137 |
-
.. code-block:: jinja
|
138 |
-
|
139 |
-
{{ 'TWIG'|lower }}
|
140 |
-
|
141 |
-
When compiling this template to PHP, Twig looks for the PHP callable
|
142 |
-
associated with the ``lower`` filter. The ``lower`` filter is a built-in Twig
|
143 |
-
filter, and it is simply mapped to the PHP ``strtolower()`` function. After
|
144 |
-
compilation, the generated PHP code is roughly equivalent to:
|
145 |
-
|
146 |
-
.. code-block:: html+php
|
147 |
-
|
148 |
-
<?php echo strtolower('TWIG') ?>
|
149 |
-
|
150 |
-
As you can see, the ``'TWIG'`` string is passed as a first argument to the PHP
|
151 |
-
function.
|
152 |
-
|
153 |
-
A filter can also take extra arguments like in the following example:
|
154 |
-
|
155 |
-
.. code-block:: jinja
|
156 |
-
|
157 |
-
{{ now|date('d/m/Y') }}
|
158 |
-
|
159 |
-
In this case, the extra arguments are passed to the function after the main
|
160 |
-
argument, and the compiled code is equivalent to:
|
161 |
-
|
162 |
-
.. code-block:: html+php
|
163 |
-
|
164 |
-
<?php echo twig_date_format_filter($now, 'd/m/Y') ?>
|
165 |
-
|
166 |
-
Let's see how to create a new filter.
|
167 |
-
|
168 |
-
In this section, we will create a ``rot13`` filter, which should return the
|
169 |
-
`rot13`_ transformation of a string. Here is an example of its usage and the
|
170 |
-
expected output:
|
171 |
-
|
172 |
-
.. code-block:: jinja
|
173 |
-
|
174 |
-
{{ "Twig"|rot13 }}
|
175 |
-
|
176 |
-
{# should displays Gjvt #}
|
177 |
-
|
178 |
-
Adding a filter is as simple as calling the ``addFilter()`` method on the
|
179 |
-
``Twig_Environment`` instance::
|
180 |
-
|
181 |
-
$twig = new Twig_Environment($loader);
|
182 |
-
$twig->addFilter('rot13', new Twig_Filter_Function('str_rot13'));
|
183 |
-
|
184 |
-
The second argument of ``addFilter()`` is an instance of ``Twig_Filter``.
|
185 |
-
Here, we use ``Twig_Filter_Function`` as the filter is a PHP function. The
|
186 |
-
first argument passed to the ``Twig_Filter_Function`` constructor is the name
|
187 |
-
of the PHP function to call, here ``str_rot13``, a native PHP function.
|
188 |
-
|
189 |
-
Let's say I now want to be able to add a prefix before the converted string:
|
190 |
-
|
191 |
-
.. code-block:: jinja
|
192 |
-
|
193 |
-
{{ "Twig"|rot13('prefix_') }}
|
194 |
-
|
195 |
-
{# should displays prefix_Gjvt #}
|
196 |
-
|
197 |
-
As the PHP ``str_rot13()`` function does not support this requirement, let's
|
198 |
-
create a new PHP function::
|
199 |
-
|
200 |
-
function project_compute_rot13($string, $prefix = '')
|
201 |
-
{
|
202 |
-
return $prefix.str_rot13($string);
|
203 |
-
}
|
204 |
-
|
205 |
-
As you can see, the ``prefix`` argument of the filter is passed as an extra
|
206 |
-
argument to the ``project_compute_rot13()`` function.
|
207 |
-
|
208 |
-
Adding this filter is as easy as before::
|
209 |
-
|
210 |
-
$twig->addFilter('rot13', new Twig_Filter_Function('project_compute_rot13'));
|
211 |
-
|
212 |
-
For better encapsulation, a filter can also be defined as a static method of a
|
213 |
-
class. The ``Twig_Filter_Function`` class can also be used to register such
|
214 |
-
static methods as filters::
|
215 |
-
|
216 |
-
$twig->addFilter('rot13', new Twig_Filter_Function('SomeClass::rot13Filter'));
|
217 |
-
|
218 |
-
.. tip::
|
219 |
-
|
220 |
-
In an extension, you can also define a filter as a static method of the
|
221 |
-
extension class.
|
222 |
-
|
223 |
-
Environment aware Filters
|
224 |
-
~~~~~~~~~~~~~~~~~~~~~~~~~
|
225 |
-
|
226 |
-
The ``Twig_Filter`` classes take options as their last argument. For instance,
|
227 |
-
if you want access to the current environment instance in your filter, set the
|
228 |
-
``needs_environment`` option to ``true``::
|
229 |
-
|
230 |
-
$filter = new Twig_Filter_Function('str_rot13', array('needs_environment' => true));
|
231 |
-
|
232 |
-
Twig will then pass the current environment as the first argument to the
|
233 |
-
filter call::
|
234 |
-
|
235 |
-
function twig_compute_rot13(Twig_Environment $env, $string)
|
236 |
-
{
|
237 |
-
// get the current charset for instance
|
238 |
-
$charset = $env->getCharset();
|
239 |
-
|
240 |
-
return str_rot13($string);
|
241 |
-
}
|
242 |
-
|
243 |
-
Automatic Escaping
|
244 |
-
~~~~~~~~~~~~~~~~~~
|
245 |
-
|
246 |
-
If automatic escaping is enabled, the output of the filter may be escaped
|
247 |
-
before printing. If your filter acts as an escaper (or explicitly outputs HTML
|
248 |
-
or JavaScript code), you will want the raw output to be printed. In such a
|
249 |
-
case, set the ``is_safe`` option::
|
250 |
-
|
251 |
-
$filter = new Twig_Filter_Function('nl2br', array('is_safe' => array('html')));
|
252 |
-
|
253 |
-
Some filters may need to work on input that is already escaped or safe, for
|
254 |
-
example when adding (safe) HTML tags to originally unsafe output. In such a
|
255 |
-
case, set the ``pre_escape`` option to escape the input data before it is run
|
256 |
-
through your filter::
|
257 |
-
|
258 |
-
$filter = new Twig_Filter_Function('somefilter', array('pre_escape' => 'html', 'is_safe' => array('html')));
|
259 |
-
|
260 |
-
Dynamic Filters
|
261 |
-
~~~~~~~~~~~~~~~
|
262 |
-
|
263 |
-
.. versionadded:: 1.5
|
264 |
-
Dynamic filters support was added in Twig 1.5.
|
265 |
-
|
266 |
-
A filter name containing the special ``*`` character is a dynamic filter as
|
267 |
-
the ``*`` can be any string::
|
268 |
-
|
269 |
-
$twig->addFilter('*_path_*', new Twig_Filter_Function('twig_path'));
|
270 |
-
|
271 |
-
function twig_path($name, $arguments)
|
272 |
-
{
|
273 |
-
// ...
|
274 |
-
}
|
275 |
-
|
276 |
-
The following filters will be matched by the above defined dynamic filter:
|
277 |
-
|
278 |
-
* ``product_path``
|
279 |
-
* ``category_path``
|
280 |
-
|
281 |
-
A dynamic filter can define more than one dynamic parts::
|
282 |
-
|
283 |
-
$twig->addFilter('*_path_*', new Twig_Filter_Function('twig_path'));
|
284 |
-
|
285 |
-
function twig_path($name, $suffix, $arguments)
|
286 |
-
{
|
287 |
-
// ...
|
288 |
-
}
|
289 |
-
|
290 |
-
The filter will receive all dynamic part values before the normal filters
|
291 |
-
arguments. For instance, a call to ``'foo'|a_path_b()`` will result in the
|
292 |
-
following PHP call: ``twig_path('a', 'b', 'foo')``.
|
293 |
-
|
294 |
-
Functions
|
295 |
-
---------
|
296 |
-
|
297 |
-
A function is a regular PHP function or an object method that can be called from
|
298 |
-
templates.
|
299 |
-
|
300 |
-
.. code-block:: jinja
|
301 |
-
|
302 |
-
{{ constant("DATE_W3C") }}
|
303 |
-
|
304 |
-
When compiling this template to PHP, Twig looks for the PHP callable
|
305 |
-
associated with the ``constant`` function. The ``constant`` function is a built-in Twig
|
306 |
-
function, and it is simply mapped to the PHP ``constant()`` function. After
|
307 |
-
compilation, the generated PHP code is roughly equivalent to:
|
308 |
-
|
309 |
-
.. code-block:: html+php
|
310 |
-
|
311 |
-
<?php echo constant('DATE_W3C') ?>
|
312 |
-
|
313 |
-
Adding a function is similar to adding a filter. This can be done by calling the
|
314 |
-
``addFunction()`` method on the ``Twig_Environment`` instance::
|
315 |
-
|
316 |
-
$twig = new Twig_Environment($loader);
|
317 |
-
$twig->addFunction('functionName', new Twig_Function_Function('someFunction'));
|
318 |
-
|
319 |
-
You can also expose extension methods as functions in your templates::
|
320 |
-
|
321 |
-
// $this is an object that implements Twig_ExtensionInterface.
|
322 |
-
$twig = new Twig_Environment($loader);
|
323 |
-
$twig->addFunction('otherFunction', new Twig_Function_Method($this, 'someMethod'));
|
324 |
-
|
325 |
-
Functions also support ``needs_environment`` and ``is_safe`` parameters.
|
326 |
-
|
327 |
-
Dynamic Functions
|
328 |
-
~~~~~~~~~~~~~~~~~
|
329 |
-
|
330 |
-
.. versionadded:: 1.5
|
331 |
-
Dynamic functions support was added in Twig 1.5.
|
332 |
-
|
333 |
-
A function name containing the special ``*`` character is a dynamic function
|
334 |
-
as the ``*`` can be any string::
|
335 |
-
|
336 |
-
$twig->addFunction('*_path', new Twig_Function_Function('twig_path'));
|
337 |
-
|
338 |
-
function twig_path($name, $arguments)
|
339 |
-
{
|
340 |
-
// ...
|
341 |
-
}
|
342 |
-
|
343 |
-
The following functions will be matched by the above defined dynamic function:
|
344 |
-
|
345 |
-
* ``product_path``
|
346 |
-
* ``category_path``
|
347 |
-
|
348 |
-
A dynamic function can define more than one dynamic parts::
|
349 |
-
|
350 |
-
$twig->addFilter('*_path_*', new Twig_Filter_Function('twig_path'));
|
351 |
-
|
352 |
-
function twig_path($name, $suffix, $arguments)
|
353 |
-
{
|
354 |
-
// ...
|
355 |
-
}
|
356 |
-
|
357 |
-
The function will receive all dynamic part values before the normal functions
|
358 |
-
arguments. For instance, a call to ``a_path_b('foo')`` will result in the
|
359 |
-
following PHP call: ``twig_path('a', 'b', 'foo')``.
|
360 |
-
|
361 |
-
Tags
|
362 |
-
----
|
363 |
-
|
364 |
-
One of the most exciting feature of a template engine like Twig is the
|
365 |
-
possibility to define new language constructs. This is also the most complex
|
366 |
-
feature as you need to understand how Twig's internals work.
|
367 |
-
|
368 |
-
Let's create a simple ``set`` tag that allows the definition of simple
|
369 |
-
variables from within a template. The tag can be used like follows:
|
370 |
-
|
371 |
-
.. code-block:: jinja
|
372 |
-
|
373 |
-
{% set name = "value" %}
|
374 |
-
|
375 |
-
{{ name }}
|
376 |
-
|
377 |
-
{# should output value #}
|
378 |
-
|
379 |
-
.. note::
|
380 |
-
|
381 |
-
The ``set`` tag is part of the Core extension and as such is always
|
382 |
-
available. The built-in version is slightly more powerful and supports
|
383 |
-
multiple assignments by default (cf. the template designers chapter for
|
384 |
-
more information).
|
385 |
-
|
386 |
-
Three steps are needed to define a new tag:
|
387 |
-
|
388 |
-
* Defining a Token Parser class (responsible for parsing the template code);
|
389 |
-
|
390 |
-
* Defining a Node class (responsible for converting the parsed code to PHP);
|
391 |
-
|
392 |
-
* Registering the tag.
|
393 |
-
|
394 |
-
Registering a new tag
|
395 |
-
~~~~~~~~~~~~~~~~~~~~~
|
396 |
-
|
397 |
-
Adding a tag is as simple as calling the ``addTokenParser`` method on the
|
398 |
-
``Twig_Environment`` instance::
|
399 |
-
|
400 |
-
$twig = new Twig_Environment($loader);
|
401 |
-
$twig->addTokenParser(new Project_Set_TokenParser());
|
402 |
-
|
403 |
-
Defining a Token Parser
|
404 |
-
~~~~~~~~~~~~~~~~~~~~~~~
|
405 |
-
|
406 |
-
Now, let's see the actual code of this class::
|
407 |
-
|
408 |
-
class Project_Set_TokenParser extends Twig_TokenParser
|
409 |
-
{
|
410 |
-
public function parse(Twig_Token $token)
|
411 |
-
{
|
412 |
-
$lineno = $token->getLine();
|
413 |
-
$name = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE)->getValue();
|
414 |
-
$this->parser->getStream()->expect(Twig_Token::OPERATOR_TYPE, '=');
|
415 |
-
$value = $this->parser->getExpressionParser()->parseExpression();
|
416 |
-
|
417 |
-
$this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
|
418 |
-
|
419 |
-
return new Project_Set_Node($name, $value, $lineno, $this->getTag());
|
420 |
-
}
|
421 |
-
|
422 |
-
public function getTag()
|
423 |
-
{
|
424 |
-
return 'set';
|
425 |
-
}
|
426 |
-
}
|
427 |
-
|
428 |
-
The ``getTag()`` method must return the tag we want to parse, here ``set``.
|
429 |
-
|
430 |
-
The ``parse()`` method is invoked whenever the parser encounters a ``set``
|
431 |
-
tag. It should return a ``Twig_Node`` instance that represents the node (the
|
432 |
-
``Project_Set_Node`` calls creating is explained in the next section).
|
433 |
-
|
434 |
-
The parsing process is simplified thanks to a bunch of methods you can call
|
435 |
-
from the token stream (``$this->parser->getStream()``):
|
436 |
-
|
437 |
-
* ``getCurrent()``: Gets the current token in the stream.
|
438 |
-
|
439 |
-
* ``next()``: Moves to the next token in the stream, *but returns the old one*.
|
440 |
-
|
441 |
-
* ``test($type)``, ``test($value)`` or ``test($type, $value)``: Determines whether
|
442 |
-
the current token is of a particular type or value (or both). The value may be an
|
443 |
-
array of several possible values.
|
444 |
-
|
445 |
-
* ``expect($type[, $value[, $message]])``: If the current token isn't of the given
|
446 |
-
type/value a syntax error is thrown. Otherwise, if the type and value are correct,
|
447 |
-
the token is returned and the stream moves to the next token.
|
448 |
-
|
449 |
-
* ``look()``: Looks a the next token without consuming it.
|
450 |
-
|
451 |
-
Parsing expressions is done by calling the ``parseExpression()`` like we did for
|
452 |
-
the ``set`` tag.
|
453 |
-
|
454 |
-
.. tip::
|
455 |
-
|
456 |
-
Reading the existing ``TokenParser`` classes is the best way to learn all
|
457 |
-
the nitty-gritty details of the parsing process.
|
458 |
-
|
459 |
-
Defining a Node
|
460 |
-
~~~~~~~~~~~~~~~
|
461 |
-
|
462 |
-
The ``Project_Set_Node`` class itself is rather simple::
|
463 |
-
|
464 |
-
class Project_Set_Node extends Twig_Node
|
465 |
-
{
|
466 |
-
public function __construct($name, Twig_Node_Expression $value, $lineno, $tag = null)
|
467 |
-
{
|
468 |
-
parent::__construct(array('value' => $value), array('name' => $name), $lineno, $tag);
|
469 |
-
}
|
470 |
-
|
471 |
-
public function compile(Twig_Compiler $compiler)
|
472 |
-
{
|
473 |
-
$compiler
|
474 |
-
->addDebugInfo($this)
|
475 |
-
->write('$context[\''.$this->getAttribute('name').'\'] = ')
|
476 |
-
->subcompile($this->getNode('value'))
|
477 |
-
->raw(";\n")
|
478 |
-
;
|
479 |
-
}
|
480 |
-
}
|
481 |
-
|
482 |
-
The compiler implements a fluid interface and provides methods that helps the
|
483 |
-
developer generate beautiful and readable PHP code:
|
484 |
-
|
485 |
-
* ``subcompile()``: Compiles a node.
|
486 |
-
|
487 |
-
* ``raw()``: Writes the given string as is.
|
488 |
-
|
489 |
-
* ``write()``: Writes the given string by adding indentation at the beginning
|
490 |
-
of each line.
|
491 |
-
|
492 |
-
* ``string()``: Writes a quoted string.
|
493 |
-
|
494 |
-
* ``repr()``: Writes a PHP representation of a given value (see
|
495 |
-
``Twig_Node_For`` for a usage example).
|
496 |
-
|
497 |
-
* ``addDebugInfo()``: Adds the line of the original template file related to
|
498 |
-
the current node as a comment.
|
499 |
-
|
500 |
-
* ``indent()``: Indents the generated code (see ``Twig_Node_Block`` for a
|
501 |
-
usage example).
|
502 |
-
|
503 |
-
* ``outdent()``: Outdents the generated code (see ``Twig_Node_Block`` for a
|
504 |
-
usage example).
|
505 |
-
|
506 |
-
.. _creating_extensions:
|
507 |
-
|
508 |
-
Creating an Extension
|
509 |
-
---------------------
|
510 |
-
|
511 |
-
The main motivation for writing an extension is to move often used code into a
|
512 |
-
reusable class like adding support for internationalization. An extension can
|
513 |
-
define tags, filters, tests, operators, global variables, functions, and node
|
514 |
-
visitors.
|
515 |
-
|
516 |
-
Creating an extension also makes for a better separation of code that is
|
517 |
-
executed at compilation time and code needed at runtime. As such, it makes
|
518 |
-
your code faster.
|
519 |
-
|
520 |
-
Most of the time, it is useful to create a single extension for your project,
|
521 |
-
to host all the specific tags and filters you want to add to Twig.
|
522 |
-
|
523 |
-
.. tip::
|
524 |
-
|
525 |
-
When packaging your code into an extension, Twig is smart enough to
|
526 |
-
recompile your templates whenever you make a change to it (when the
|
527 |
-
``auto_reload`` is enabled).
|
528 |
-
|
529 |
-
.. note::
|
530 |
-
|
531 |
-
Before writing your own extensions, have a look at the Twig official
|
532 |
-
extension repository: http://github.com/twigphp/Twig-extensions.
|
533 |
-
|
534 |
-
An extension is a class that implements the following interface::
|
535 |
-
|
536 |
-
interface Twig_ExtensionInterface
|
537 |
-
{
|
538 |
-
/**
|
539 |
-
* Initializes the runtime environment.
|
540 |
-
*
|
541 |
-
* This is where you can load some file that contains filter functions for instance.
|
542 |
-
*/
|
543 |
-
function initRuntime(Twig_Environment $environment);
|
544 |
-
|
545 |
-
/**
|
546 |
-
* Returns the token parser instances to add to the existing list.
|
547 |
-
*
|
548 |
-
* @return (Twig_TokenParserInterface|Twig_TokenParserBrokerInterface)[]
|
549 |
-
*/
|
550 |
-
function getTokenParsers();
|
551 |
-
|
552 |
-
/**
|
553 |
-
* Returns the node visitor instances to add to the existing list.
|
554 |
-
*
|
555 |
-
* @return Twig_NodeVisitorInterface[]
|
556 |
-
*/
|
557 |
-
function getNodeVisitors();
|
558 |
-
|
559 |
-
/**
|
560 |
-
* Returns a list of filters to add to the existing list.
|
561 |
-
*
|
562 |
-
* @return Twig_SimpleFilter[]
|
563 |
-
*/
|
564 |
-
function getFilters();
|
565 |
-
|
566 |
-
/**
|
567 |
-
* Returns a list of tests to add to the existing list.
|
568 |
-
*
|
569 |
-
* @return Twig_SimpleTest[]
|
570 |
-
*/
|
571 |
-
function getTests();
|
572 |
-
|
573 |
-
/**
|
574 |
-
* Returns a list of functions to add to the existing list.
|
575 |
-
*
|
576 |
-
* @return Twig_SimpleFunction[]
|
577 |
-
*/
|
578 |
-
function getFunctions();
|
579 |
-
|
580 |
-
/**
|
581 |
-
* Returns a list of operators to add to the existing list.
|
582 |
-
*
|
583 |
-
* @return array<array> First array of unary operators, second array of binary operators
|
584 |
-
*/
|
585 |
-
function getOperators();
|
586 |
-
|
587 |
-
/**
|
588 |
-
* Returns a list of global variables to add to the existing list.
|
589 |
-
*
|
590 |
-
* @return array An array of global variables
|
591 |
-
*/
|
592 |
-
function getGlobals();
|
593 |
-
|
594 |
-
/**
|
595 |
-
* Returns the name of the extension.
|
596 |
-
*
|
597 |
-
* @return string The extension name
|
598 |
-
*/
|
599 |
-
function getName();
|
600 |
-
}
|
601 |
-
|
602 |
-
To keep your extension class clean and lean, it can inherit from the built-in
|
603 |
-
``Twig_Extension`` class instead of implementing the whole interface. That
|
604 |
-
way, you just need to implement the ``getName()`` method as the
|
605 |
-
``Twig_Extension`` provides empty implementations for all other methods.
|
606 |
-
|
607 |
-
The ``getName()`` method must return a unique identifier for your extension.
|
608 |
-
|
609 |
-
Now, with this information in mind, let's create the most basic extension
|
610 |
-
possible::
|
611 |
-
|
612 |
-
class Project_Twig_Extension extends Twig_Extension
|
613 |
-
{
|
614 |
-
public function getName()
|
615 |
-
{
|
616 |
-
return 'project';
|
617 |
-
}
|
618 |
-
}
|
619 |
-
|
620 |
-
.. note::
|
621 |
-
|
622 |
-
Of course, this extension does nothing for now. We will customize it in
|
623 |
-
the next sections.
|
624 |
-
|
625 |
-
Twig does not care where you save your extension on the filesystem, as all
|
626 |
-
extensions must be registered explicitly to be available in your templates.
|
627 |
-
|
628 |
-
You can register an extension by using the ``addExtension()`` method on your
|
629 |
-
main ``Environment`` object::
|
630 |
-
|
631 |
-
$twig = new Twig_Environment($loader);
|
632 |
-
$twig->addExtension(new Project_Twig_Extension());
|
633 |
-
|
634 |
-
Of course, you need to first load the extension file by either using
|
635 |
-
``require_once()`` or by using an autoloader (see `spl_autoload_register()`_).
|
636 |
-
|
637 |
-
.. tip::
|
638 |
-
|
639 |
-
The bundled extensions are great examples of how extensions work.
|
640 |
-
|
641 |
-
Globals
|
642 |
-
~~~~~~~
|
643 |
-
|
644 |
-
Global variables can be registered in an extension via the ``getGlobals()``
|
645 |
-
method::
|
646 |
-
|
647 |
-
class Project_Twig_Extension extends Twig_Extension
|
648 |
-
{
|
649 |
-
public function getGlobals()
|
650 |
-
{
|
651 |
-
return array(
|
652 |
-
'text' => new Text(),
|
653 |
-
);
|
654 |
-
}
|
655 |
-
|
656 |
-
// ...
|
657 |
-
}
|
658 |
-
|
659 |
-
Functions
|
660 |
-
~~~~~~~~~
|
661 |
-
|
662 |
-
Functions can be registered in an extension via the ``getFunctions()``
|
663 |
-
method::
|
664 |
-
|
665 |
-
class Project_Twig_Extension extends Twig_Extension
|
666 |
-
{
|
667 |
-
public function getFunctions()
|
668 |
-
{
|
669 |
-
return array(
|
670 |
-
'lipsum' => new Twig_Function_Function('generate_lipsum'),
|
671 |
-
);
|
672 |
-
}
|
673 |
-
|
674 |
-
// ...
|
675 |
-
}
|
676 |
-
|
677 |
-
Filters
|
678 |
-
~~~~~~~
|
679 |
-
|
680 |
-
To add a filter to an extension, you need to override the ``getFilters()``
|
681 |
-
method. This method must return an array of filters to add to the Twig
|
682 |
-
environment::
|
683 |
-
|
684 |
-
class Project_Twig_Extension extends Twig_Extension
|
685 |
-
{
|
686 |
-
public function getFilters()
|
687 |
-
{
|
688 |
-
return array(
|
689 |
-
'rot13' => new Twig_Filter_Function('str_rot13'),
|
690 |
-
);
|
691 |
-
}
|
692 |
-
|
693 |
-
// ...
|
694 |
-
}
|
695 |
-
|
696 |
-
As you can see in the above code, the ``getFilters()`` method returns an array
|
697 |
-
where keys are the name of the filters (``rot13``) and the values the
|
698 |
-
definition of the filter (``new Twig_Filter_Function('str_rot13')``).
|
699 |
-
|
700 |
-
As seen in the previous chapter, you can also define filters as static methods
|
701 |
-
on the extension class::
|
702 |
-
|
703 |
-
$twig->addFilter('rot13', new Twig_Filter_Function('Project_Twig_Extension::rot13Filter'));
|
704 |
-
|
705 |
-
You can also use ``Twig_Filter_Method`` instead of ``Twig_Filter_Function``
|
706 |
-
when defining a filter to use a method::
|
707 |
-
|
708 |
-
class Project_Twig_Extension extends Twig_Extension
|
709 |
-
{
|
710 |
-
public function getFilters()
|
711 |
-
{
|
712 |
-
return array(
|
713 |
-
'rot13' => new Twig_Filter_Method($this, 'rot13Filter'),
|
714 |
-
);
|
715 |
-
}
|
716 |
-
|
717 |
-
public function rot13Filter($string)
|
718 |
-
{
|
719 |
-
return str_rot13($string);
|
720 |
-
}
|
721 |
-
|
722 |
-
// ...
|
723 |
-
}
|
724 |
-
|
725 |
-
The first argument of the ``Twig_Filter_Method`` constructor is always
|
726 |
-
``$this``, the current extension object. The second one is the name of the
|
727 |
-
method to call.
|
728 |
-
|
729 |
-
Using methods for filters is a great way to package your filter without
|
730 |
-
polluting the global namespace. This also gives the developer more flexibility
|
731 |
-
at the cost of a small overhead.
|
732 |
-
|
733 |
-
Overriding default Filters
|
734 |
-
..........................
|
735 |
-
|
736 |
-
If some default core filters do not suit your needs, you can easily override
|
737 |
-
them by creating your own extension. Just use the same names as the one you
|
738 |
-
want to override::
|
739 |
-
|
740 |
-
class MyCoreExtension extends Twig_Extension
|
741 |
-
{
|
742 |
-
public function getFilters()
|
743 |
-
{
|
744 |
-
return array(
|
745 |
-
'date' => new Twig_Filter_Method($this, 'dateFilter'),
|
746 |
-
// ...
|
747 |
-
);
|
748 |
-
}
|
749 |
-
|
750 |
-
public function dateFilter($timestamp, $format = 'F j, Y H:i')
|
751 |
-
{
|
752 |
-
return '...'.twig_date_format_filter($timestamp, $format);
|
753 |
-
}
|
754 |
-
|
755 |
-
public function getName()
|
756 |
-
{
|
757 |
-
return 'project';
|
758 |
-
}
|
759 |
-
}
|
760 |
-
|
761 |
-
Here, we override the ``date`` filter with a custom one. Using this extension
|
762 |
-
is as simple as registering the ``MyCoreExtension`` extension by calling the
|
763 |
-
``addExtension()`` method on the environment instance::
|
764 |
-
|
765 |
-
$twig = new Twig_Environment($loader);
|
766 |
-
$twig->addExtension(new MyCoreExtension());
|
767 |
-
|
768 |
-
Tags
|
769 |
-
~~~~
|
770 |
-
|
771 |
-
Adding a tag in an extension can be done by overriding the
|
772 |
-
``getTokenParsers()`` method. This method must return an array of tags to add
|
773 |
-
to the Twig environment::
|
774 |
-
|
775 |
-
class Project_Twig_Extension extends Twig_Extension
|
776 |
-
{
|
777 |
-
public function getTokenParsers()
|
778 |
-
{
|
779 |
-
return array(new Project_Set_TokenParser());
|
780 |
-
}
|
781 |
-
|
782 |
-
// ...
|
783 |
-
}
|
784 |
-
|
785 |
-
In the above code, we have added a single new tag, defined by the
|
786 |
-
``Project_Set_TokenParser`` class. The ``Project_Set_TokenParser`` class is
|
787 |
-
responsible for parsing the tag and compiling it to PHP.
|
788 |
-
|
789 |
-
Operators
|
790 |
-
~~~~~~~~~
|
791 |
-
|
792 |
-
The ``getOperators()`` methods allows to add new operators. Here is how to add
|
793 |
-
``!``, ``||``, and ``&&`` operators::
|
794 |
-
|
795 |
-
class Project_Twig_Extension extends Twig_Extension
|
796 |
-
{
|
797 |
-
public function getOperators()
|
798 |
-
{
|
799 |
-
return array(
|
800 |
-
array(
|
801 |
-
'!' => array('precedence' => 50, 'class' => 'Twig_Node_Expression_Unary_Not'),
|
802 |
-
),
|
803 |
-
array(
|
804 |
-
'||' => array('precedence' => 10, 'class' => 'Twig_Node_Expression_Binary_Or', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
|
805 |
-
'&&' => array('precedence' => 15, 'class' => 'Twig_Node_Expression_Binary_And', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
|
806 |
-
),
|
807 |
-
);
|
808 |
-
}
|
809 |
-
|
810 |
-
// ...
|
811 |
-
}
|
812 |
-
|
813 |
-
Tests
|
814 |
-
~~~~~
|
815 |
-
|
816 |
-
The ``getTests()`` methods allows to add new test functions::
|
817 |
-
|
818 |
-
class Project_Twig_Extension extends Twig_Extension
|
819 |
-
{
|
820 |
-
public function getTests()
|
821 |
-
{
|
822 |
-
return array(
|
823 |
-
'even' => new Twig_Test_Function('twig_test_even'),
|
824 |
-
);
|
825 |
-
}
|
826 |
-
|
827 |
-
// ...
|
828 |
-
}
|
829 |
-
|
830 |
-
Testing an Extension
|
831 |
-
--------------------
|
832 |
-
|
833 |
-
.. versionadded:: 1.10
|
834 |
-
Support for functional tests was added in Twig 1.10.
|
835 |
-
|
836 |
-
Functional Tests
|
837 |
-
~~~~~~~~~~~~~~~~
|
838 |
-
|
839 |
-
You can create functional tests for extensions simply by creating the
|
840 |
-
following file structure in your test directory::
|
841 |
-
|
842 |
-
Fixtures/
|
843 |
-
filters/
|
844 |
-
foo.test
|
845 |
-
bar.test
|
846 |
-
functions/
|
847 |
-
foo.test
|
848 |
-
bar.test
|
849 |
-
tags/
|
850 |
-
foo.test
|
851 |
-
bar.test
|
852 |
-
IntegrationTest.php
|
853 |
-
|
854 |
-
The ``IntegrationTest.php`` file should look like this::
|
855 |
-
|
856 |
-
class Project_Tests_IntegrationTest extends Twig_Test_IntegrationTestCase
|
857 |
-
{
|
858 |
-
public function getExtensions()
|
859 |
-
{
|
860 |
-
return array(
|
861 |
-
new Project_Twig_Extension1(),
|
862 |
-
new Project_Twig_Extension2(),
|
863 |
-
);
|
864 |
-
}
|
865 |
-
|
866 |
-
public function getFixturesDir()
|
867 |
-
{
|
868 |
-
return dirname(__FILE__).'/Fixtures/';
|
869 |
-
}
|
870 |
-
}
|
871 |
-
|
872 |
-
Fixtures examples can be found within the Twig repository
|
873 |
-
`tests/Twig/Fixtures`_ directory.
|
874 |
-
|
875 |
-
Node Tests
|
876 |
-
~~~~~~~~~~
|
877 |
-
|
878 |
-
Testing the node visitors can be complex, so extend your test cases from
|
879 |
-
``Twig_Test_NodeTestCase``. Examples can be found in the Twig repository
|
880 |
-
`tests/Twig/Node`_ directory.
|
881 |
-
|
882 |
-
.. _`spl_autoload_register()`: http://www.php.net/spl_autoload_register
|
883 |
-
.. _`rot13`: http://www.php.net/manual/en/function.str-rot13.php
|
884 |
-
.. _`tests/Twig/Fixtures`: https://github.com/twigphp/Twig/tree/master/test/Twig/Tests/Fixtures
|
885 |
-
.. _`tests/Twig/Node`: https://github.com/twigphp/Twig/tree/master/test/Twig/Tests/Node
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/api.rst
DELETED
@@ -1,590 +0,0 @@
|
|
1 |
-
Twig for Developers
|
2 |
-
===================
|
3 |
-
|
4 |
-
This chapter describes the API to Twig and not the template language. It will
|
5 |
-
be most useful as reference to those implementing the template interface to
|
6 |
-
the application and not those who are creating Twig templates.
|
7 |
-
|
8 |
-
Basics
|
9 |
-
------
|
10 |
-
|
11 |
-
Twig uses a central object called the **environment** (of class
|
12 |
-
``Twig_Environment``). Instances of this class are used to store the
|
13 |
-
configuration and extensions, and are used to load templates from the file
|
14 |
-
system or other locations.
|
15 |
-
|
16 |
-
Most applications will create one ``Twig_Environment`` object on application
|
17 |
-
initialization and use that to load templates. In some cases it's however
|
18 |
-
useful to have multiple environments side by side, if different configurations
|
19 |
-
are in use.
|
20 |
-
|
21 |
-
The simplest way to configure Twig to load templates for your application
|
22 |
-
looks roughly like this::
|
23 |
-
|
24 |
-
require_once '/path/to/lib/Twig/Autoloader.php';
|
25 |
-
Twig_Autoloader::register();
|
26 |
-
|
27 |
-
$loader = new Twig_Loader_Filesystem('/path/to/templates');
|
28 |
-
$twig = new Twig_Environment($loader, array(
|
29 |
-
'cache' => '/path/to/compilation_cache',
|
30 |
-
));
|
31 |
-
|
32 |
-
This will create a template environment with the default settings and a loader
|
33 |
-
that looks up the templates in the ``/path/to/templates/`` folder. Different
|
34 |
-
loaders are available and you can also write your own if you want to load
|
35 |
-
templates from a database or other resources.
|
36 |
-
|
37 |
-
.. note::
|
38 |
-
|
39 |
-
Notice that the second argument of the environment is an array of options.
|
40 |
-
The ``cache`` option is a compilation cache directory, where Twig caches
|
41 |
-
the compiled templates to avoid the parsing phase for sub-sequent
|
42 |
-
requests. It is very different from the cache you might want to add for
|
43 |
-
the evaluated templates. For such a need, you can use any available PHP
|
44 |
-
cache library.
|
45 |
-
|
46 |
-
Rendering Templates
|
47 |
-
-------------------
|
48 |
-
|
49 |
-
To load a template from a Twig environment, call the ``load()`` method which
|
50 |
-
returns a ``Twig_TemplateWrapper`` instance::
|
51 |
-
|
52 |
-
$template = $twig->load('index.html');
|
53 |
-
|
54 |
-
.. note::
|
55 |
-
|
56 |
-
Before Twig 1.28, you should use ``loadTemplate()`` instead which returns a
|
57 |
-
``Twig_Template`` instance.
|
58 |
-
|
59 |
-
To render the template with some variables, call the ``render()`` method::
|
60 |
-
|
61 |
-
echo $template->render(array('the' => 'variables', 'go' => 'here'));
|
62 |
-
|
63 |
-
.. note::
|
64 |
-
|
65 |
-
The ``display()`` method is a shortcut to output the template directly.
|
66 |
-
|
67 |
-
You can also load and render the template in one fell swoop::
|
68 |
-
|
69 |
-
echo $twig->render('index.html', array('the' => 'variables', 'go' => 'here'));
|
70 |
-
|
71 |
-
.. versionadded:: 1.28
|
72 |
-
The possibility to render blocks from the API was added in Twig 1.28.
|
73 |
-
|
74 |
-
If a template defines blocks, they can be rendered individually via the
|
75 |
-
``renderBlock()`` call::
|
76 |
-
|
77 |
-
echo $template->renderBlock('block_name', array('the' => 'variables', 'go' => 'here'));
|
78 |
-
|
79 |
-
.. _environment_options:
|
80 |
-
|
81 |
-
Environment Options
|
82 |
-
-------------------
|
83 |
-
|
84 |
-
When creating a new ``Twig_Environment`` instance, you can pass an array of
|
85 |
-
options as the constructor second argument::
|
86 |
-
|
87 |
-
$twig = new Twig_Environment($loader, array('debug' => true));
|
88 |
-
|
89 |
-
The following options are available:
|
90 |
-
|
91 |
-
* ``debug`` *boolean*
|
92 |
-
|
93 |
-
When set to ``true``, the generated templates have a
|
94 |
-
``__toString()`` method that you can use to display the generated nodes
|
95 |
-
(default to ``false``).
|
96 |
-
|
97 |
-
* ``charset`` *string* (defaults to ``utf-8``)
|
98 |
-
|
99 |
-
The charset used by the templates.
|
100 |
-
|
101 |
-
* ``base_template_class`` *string* (defaults to ``Twig_Template``)
|
102 |
-
|
103 |
-
The base template class to use for generated
|
104 |
-
templates.
|
105 |
-
|
106 |
-
* ``cache`` *string* or ``false``
|
107 |
-
|
108 |
-
An absolute path where to store the compiled templates, or
|
109 |
-
``false`` to disable caching (which is the default).
|
110 |
-
|
111 |
-
* ``auto_reload`` *boolean*
|
112 |
-
|
113 |
-
When developing with Twig, it's useful to recompile the
|
114 |
-
template whenever the source code changes. If you don't provide a value for
|
115 |
-
the ``auto_reload`` option, it will be determined automatically based on the
|
116 |
-
``debug`` value.
|
117 |
-
|
118 |
-
* ``strict_variables`` *boolean*
|
119 |
-
|
120 |
-
If set to ``false``, Twig will silently ignore invalid
|
121 |
-
variables (variables and or attributes/methods that do not exist) and
|
122 |
-
replace them with a ``null`` value. When set to ``true``, Twig throws an
|
123 |
-
exception instead (default to ``false``).
|
124 |
-
|
125 |
-
* ``autoescape`` *string* or *boolean*
|
126 |
-
|
127 |
-
If set to ``true``, HTML auto-escaping will be enabled by
|
128 |
-
default for all templates (default to ``true``).
|
129 |
-
|
130 |
-
As of Twig 1.8, you can set the escaping strategy to use (``html``, ``js``,
|
131 |
-
``false`` to disable).
|
132 |
-
|
133 |
-
As of Twig 1.9, you can set the escaping strategy to use (``css``, ``url``,
|
134 |
-
``html_attr``, or a PHP callback that takes the template name and must
|
135 |
-
return the escaping strategy to use -- the callback cannot be a function name
|
136 |
-
to avoid collision with built-in escaping strategies).
|
137 |
-
|
138 |
-
As of Twig 1.17, the ``filename`` escaping strategy (renamed to ``name`` as
|
139 |
-
of Twig 1.27) determines the escaping strategy to use for a template based on
|
140 |
-
the template filename extension (this strategy does not incur any overhead at
|
141 |
-
runtime as auto-escaping is done at compilation time.)
|
142 |
-
|
143 |
-
* ``optimizations`` *integer*
|
144 |
-
|
145 |
-
A flag that indicates which optimizations to apply
|
146 |
-
(default to ``-1`` -- all optimizations are enabled; set it to ``0`` to
|
147 |
-
disable).
|
148 |
-
|
149 |
-
Loaders
|
150 |
-
-------
|
151 |
-
|
152 |
-
Loaders are responsible for loading templates from a resource such as the file
|
153 |
-
system.
|
154 |
-
|
155 |
-
Compilation Cache
|
156 |
-
~~~~~~~~~~~~~~~~~
|
157 |
-
|
158 |
-
All template loaders can cache the compiled templates on the filesystem for
|
159 |
-
future reuse. It speeds up Twig a lot as templates are only compiled once; and
|
160 |
-
the performance boost is even larger if you use a PHP accelerator such as APC.
|
161 |
-
See the ``cache`` and ``auto_reload`` options of ``Twig_Environment`` above
|
162 |
-
for more information.
|
163 |
-
|
164 |
-
Built-in Loaders
|
165 |
-
~~~~~~~~~~~~~~~~
|
166 |
-
|
167 |
-
Here is a list of the built-in loaders Twig provides:
|
168 |
-
|
169 |
-
``Twig_Loader_Filesystem``
|
170 |
-
..........................
|
171 |
-
|
172 |
-
.. versionadded:: 1.10
|
173 |
-
The ``prependPath()`` and support for namespaces were added in Twig 1.10.
|
174 |
-
|
175 |
-
.. versionadded:: 1.27
|
176 |
-
Relative paths support was added in Twig 1.27.
|
177 |
-
|
178 |
-
``Twig_Loader_Filesystem`` loads templates from the file system. This loader
|
179 |
-
can find templates in folders on the file system and is the preferred way to
|
180 |
-
load them::
|
181 |
-
|
182 |
-
$loader = new Twig_Loader_Filesystem($templateDir);
|
183 |
-
|
184 |
-
It can also look for templates in an array of directories::
|
185 |
-
|
186 |
-
$loader = new Twig_Loader_Filesystem(array($templateDir1, $templateDir2));
|
187 |
-
|
188 |
-
With such a configuration, Twig will first look for templates in
|
189 |
-
``$templateDir1`` and if they do not exist, it will fallback to look for them
|
190 |
-
in the ``$templateDir2``.
|
191 |
-
|
192 |
-
You can add or prepend paths via the ``addPath()`` and ``prependPath()``
|
193 |
-
methods::
|
194 |
-
|
195 |
-
$loader->addPath($templateDir3);
|
196 |
-
$loader->prependPath($templateDir4);
|
197 |
-
|
198 |
-
The filesystem loader also supports namespaced templates. This allows to group
|
199 |
-
your templates under different namespaces which have their own template paths.
|
200 |
-
|
201 |
-
When using the ``setPaths()``, ``addPath()``, and ``prependPath()`` methods,
|
202 |
-
specify the namespace as the second argument (when not specified, these
|
203 |
-
methods act on the "main" namespace)::
|
204 |
-
|
205 |
-
$loader->addPath($templateDir, 'admin');
|
206 |
-
|
207 |
-
Namespaced templates can be accessed via the special
|
208 |
-
``@namespace_name/template_path`` notation::
|
209 |
-
|
210 |
-
$twig->render('@admin/index.html', array());
|
211 |
-
|
212 |
-
``Twig_Loader_Filesystem`` support absolute and relative paths. Using relative
|
213 |
-
paths is preferred as it makes the cache keys independent of the project root
|
214 |
-
directory (for instance, it allows warming the cache from a build server where
|
215 |
-
the directory might be different from the one used on production servers)::
|
216 |
-
|
217 |
-
$loader = new Twig_Loader_Filesystem('templates', getcwd().'/..');
|
218 |
-
|
219 |
-
.. note::
|
220 |
-
|
221 |
-
When not passing the root path as a second argument, Twig uses ``getcwd()``
|
222 |
-
for relative paths.
|
223 |
-
|
224 |
-
``Twig_Loader_Array``
|
225 |
-
.....................
|
226 |
-
|
227 |
-
``Twig_Loader_Array`` loads a template from a PHP array. It's passed an array
|
228 |
-
of strings bound to template names::
|
229 |
-
|
230 |
-
$loader = new Twig_Loader_Array(array(
|
231 |
-
'index.html' => 'Hello {{ name }}!',
|
232 |
-
));
|
233 |
-
$twig = new Twig_Environment($loader);
|
234 |
-
|
235 |
-
echo $twig->render('index.html', array('name' => 'Fabien'));
|
236 |
-
|
237 |
-
This loader is very useful for unit testing. It can also be used for small
|
238 |
-
projects where storing all templates in a single PHP file might make sense.
|
239 |
-
|
240 |
-
.. tip::
|
241 |
-
|
242 |
-
When using the ``Array`` or ``String`` loaders with a cache mechanism, you
|
243 |
-
should know that a new cache key is generated each time a template content
|
244 |
-
"changes" (the cache key being the source code of the template). If you
|
245 |
-
don't want to see your cache grows out of control, you need to take care
|
246 |
-
of clearing the old cache file by yourself.
|
247 |
-
|
248 |
-
``Twig_Loader_Chain``
|
249 |
-
.....................
|
250 |
-
|
251 |
-
``Twig_Loader_Chain`` delegates the loading of templates to other loaders::
|
252 |
-
|
253 |
-
$loader1 = new Twig_Loader_Array(array(
|
254 |
-
'base.html' => '{% block content %}{% endblock %}',
|
255 |
-
));
|
256 |
-
$loader2 = new Twig_Loader_Array(array(
|
257 |
-
'index.html' => '{% extends "base.html" %}{% block content %}Hello {{ name }}{% endblock %}',
|
258 |
-
'base.html' => 'Will never be loaded',
|
259 |
-
));
|
260 |
-
|
261 |
-
$loader = new Twig_Loader_Chain(array($loader1, $loader2));
|
262 |
-
|
263 |
-
$twig = new Twig_Environment($loader);
|
264 |
-
|
265 |
-
When looking for a template, Twig will try each loader in turn and it will
|
266 |
-
return as soon as the template is found. When rendering the ``index.html``
|
267 |
-
template from the above example, Twig will load it with ``$loader2`` but the
|
268 |
-
``base.html`` template will be loaded from ``$loader1``.
|
269 |
-
|
270 |
-
``Twig_Loader_Chain`` accepts any loader that implements
|
271 |
-
``Twig_LoaderInterface``.
|
272 |
-
|
273 |
-
.. note::
|
274 |
-
|
275 |
-
You can also add loaders via the ``addLoader()`` method.
|
276 |
-
|
277 |
-
Create your own Loader
|
278 |
-
~~~~~~~~~~~~~~~~~~~~~~
|
279 |
-
|
280 |
-
All loaders implement the ``Twig_LoaderInterface``::
|
281 |
-
|
282 |
-
interface Twig_LoaderInterface
|
283 |
-
{
|
284 |
-
/**
|
285 |
-
* Gets the source code of a template, given its name.
|
286 |
-
*
|
287 |
-
* @param string $name string The name of the template to load
|
288 |
-
*
|
289 |
-
* @return string The template source code
|
290 |
-
*
|
291 |
-
* @deprecated since 1.27 (to be removed in 2.0), implement Twig_SourceContextLoaderInterface
|
292 |
-
*/
|
293 |
-
function getSource($name);
|
294 |
-
|
295 |
-
/**
|
296 |
-
* Gets the cache key to use for the cache for a given template name.
|
297 |
-
*
|
298 |
-
* @param string $name string The name of the template to load
|
299 |
-
*
|
300 |
-
* @return string The cache key
|
301 |
-
*/
|
302 |
-
function getCacheKey($name);
|
303 |
-
|
304 |
-
/**
|
305 |
-
* Returns true if the template is still fresh.
|
306 |
-
*
|
307 |
-
* @param string $name The template name
|
308 |
-
* @param timestamp $time The last modification time of the cached template
|
309 |
-
*/
|
310 |
-
function isFresh($name, $time);
|
311 |
-
}
|
312 |
-
|
313 |
-
The ``isFresh()`` method must return ``true`` if the current cached template
|
314 |
-
is still fresh, given the last modification time, or ``false`` otherwise.
|
315 |
-
|
316 |
-
.. note::
|
317 |
-
|
318 |
-
As of Twig 1.27, you should also implement
|
319 |
-
``Twig_SourceContextLoaderInterface`` to avoid deprecation notices.
|
320 |
-
|
321 |
-
.. tip::
|
322 |
-
|
323 |
-
As of Twig 1.11.0, you can also implement ``Twig_ExistsLoaderInterface``
|
324 |
-
to make your loader faster when used with the chain loader.
|
325 |
-
|
326 |
-
Using Extensions
|
327 |
-
----------------
|
328 |
-
|
329 |
-
Twig extensions are packages that add new features to Twig. Using an
|
330 |
-
extension is as simple as using the ``addExtension()`` method::
|
331 |
-
|
332 |
-
$twig->addExtension(new Twig_Extension_Sandbox());
|
333 |
-
|
334 |
-
Twig comes bundled with the following extensions:
|
335 |
-
|
336 |
-
* *Twig_Extension_Core*: Defines all the core features of Twig.
|
337 |
-
|
338 |
-
* *Twig_Extension_Escaper*: Adds automatic output-escaping and the possibility
|
339 |
-
to escape/unescape blocks of code.
|
340 |
-
|
341 |
-
* *Twig_Extension_Sandbox*: Adds a sandbox mode to the default Twig
|
342 |
-
environment, making it safe to evaluate untrusted code.
|
343 |
-
|
344 |
-
* *Twig_Extension_Profiler*: Enabled the built-in Twig profiler (as of Twig
|
345 |
-
1.18).
|
346 |
-
|
347 |
-
* *Twig_Extension_Optimizer*: Optimizes the node tree before compilation.
|
348 |
-
|
349 |
-
The core, escaper, and optimizer extensions do not need to be added to the
|
350 |
-
Twig environment, as they are registered by default.
|
351 |
-
|
352 |
-
Built-in Extensions
|
353 |
-
-------------------
|
354 |
-
|
355 |
-
This section describes the features added by the built-in extensions.
|
356 |
-
|
357 |
-
.. tip::
|
358 |
-
|
359 |
-
Read the chapter about extending Twig to learn how to create your own
|
360 |
-
extensions.
|
361 |
-
|
362 |
-
Core Extension
|
363 |
-
~~~~~~~~~~~~~~
|
364 |
-
|
365 |
-
The ``core`` extension defines all the core features of Twig:
|
366 |
-
|
367 |
-
* :doc:`Tags <tags/index>`;
|
368 |
-
* :doc:`Filters <filters/index>`;
|
369 |
-
* :doc:`Functions <functions/index>`;
|
370 |
-
* :doc:`Tests <tests/index>`.
|
371 |
-
|
372 |
-
Escaper Extension
|
373 |
-
~~~~~~~~~~~~~~~~~
|
374 |
-
|
375 |
-
The ``escaper`` extension adds automatic output escaping to Twig. It defines a
|
376 |
-
tag, ``autoescape``, and a filter, ``raw``.
|
377 |
-
|
378 |
-
When creating the escaper extension, you can switch on or off the global
|
379 |
-
output escaping strategy::
|
380 |
-
|
381 |
-
$escaper = new Twig_Extension_Escaper('html');
|
382 |
-
$twig->addExtension($escaper);
|
383 |
-
|
384 |
-
If set to ``html``, all variables in templates are escaped (using the ``html``
|
385 |
-
escaping strategy), except those using the ``raw`` filter:
|
386 |
-
|
387 |
-
.. code-block:: jinja
|
388 |
-
|
389 |
-
{{ article.to_html|raw }}
|
390 |
-
|
391 |
-
You can also change the escaping mode locally by using the ``autoescape`` tag
|
392 |
-
(see the :doc:`autoescape<tags/autoescape>` doc for the syntax used before
|
393 |
-
Twig 1.8):
|
394 |
-
|
395 |
-
.. code-block:: jinja
|
396 |
-
|
397 |
-
{% autoescape 'html' %}
|
398 |
-
{{ var }}
|
399 |
-
{{ var|raw }} {# var won't be escaped #}
|
400 |
-
{{ var|escape }} {# var won't be double-escaped #}
|
401 |
-
{% endautoescape %}
|
402 |
-
|
403 |
-
.. warning::
|
404 |
-
|
405 |
-
The ``autoescape`` tag has no effect on included files.
|
406 |
-
|
407 |
-
The escaping rules are implemented as follows:
|
408 |
-
|
409 |
-
* Literals (integers, booleans, arrays, ...) used in the template directly as
|
410 |
-
variables or filter arguments are never automatically escaped:
|
411 |
-
|
412 |
-
.. code-block:: jinja
|
413 |
-
|
414 |
-
{{ "Twig<br />" }} {# won't be escaped #}
|
415 |
-
|
416 |
-
{% set text = "Twig<br />" %}
|
417 |
-
{{ text }} {# will be escaped #}
|
418 |
-
|
419 |
-
* Expressions which the result is always a literal or a variable marked safe
|
420 |
-
are never automatically escaped:
|
421 |
-
|
422 |
-
.. code-block:: jinja
|
423 |
-
|
424 |
-
{{ foo ? "Twig<br />" : "<br />Twig" }} {# won't be escaped #}
|
425 |
-
|
426 |
-
{% set text = "Twig<br />" %}
|
427 |
-
{{ foo ? text : "<br />Twig" }} {# will be escaped #}
|
428 |
-
|
429 |
-
{% set text = "Twig<br />" %}
|
430 |
-
{{ foo ? text|raw : "<br />Twig" }} {# won't be escaped #}
|
431 |
-
|
432 |
-
{% set text = "Twig<br />" %}
|
433 |
-
{{ foo ? text|escape : "<br />Twig" }} {# the result of the expression won't be escaped #}
|
434 |
-
|
435 |
-
* Escaping is applied before printing, after any other filter is applied:
|
436 |
-
|
437 |
-
.. code-block:: jinja
|
438 |
-
|
439 |
-
{{ var|upper }} {# is equivalent to {{ var|upper|escape }} #}
|
440 |
-
|
441 |
-
* The `raw` filter should only be used at the end of the filter chain:
|
442 |
-
|
443 |
-
.. code-block:: jinja
|
444 |
-
|
445 |
-
{{ var|raw|upper }} {# will be escaped #}
|
446 |
-
|
447 |
-
{{ var|upper|raw }} {# won't be escaped #}
|
448 |
-
|
449 |
-
* Automatic escaping is not applied if the last filter in the chain is marked
|
450 |
-
safe for the current context (e.g. ``html`` or ``js``). ``escape`` and
|
451 |
-
``escape('html')`` are marked safe for HTML, ``escape('js')`` is marked
|
452 |
-
safe for JavaScript, ``raw`` is marked safe for everything.
|
453 |
-
|
454 |
-
.. code-block:: jinja
|
455 |
-
|
456 |
-
{% autoescape 'js' %}
|
457 |
-
{{ var|escape('html') }} {# will be escaped for HTML and JavaScript #}
|
458 |
-
{{ var }} {# will be escaped for JavaScript #}
|
459 |
-
{{ var|escape('js') }} {# won't be double-escaped #}
|
460 |
-
{% endautoescape %}
|
461 |
-
|
462 |
-
.. note::
|
463 |
-
|
464 |
-
Note that autoescaping has some limitations as escaping is applied on
|
465 |
-
expressions after evaluation. For instance, when working with
|
466 |
-
concatenation, ``{{ foo|raw ~ bar }}`` won't give the expected result as
|
467 |
-
escaping is applied on the result of the concatenation, not on the
|
468 |
-
individual variables (so, the ``raw`` filter won't have any effect here).
|
469 |
-
|
470 |
-
Sandbox Extension
|
471 |
-
~~~~~~~~~~~~~~~~~
|
472 |
-
|
473 |
-
The ``sandbox`` extension can be used to evaluate untrusted code. Access to
|
474 |
-
unsafe attributes and methods is prohibited. The sandbox security is managed
|
475 |
-
by a policy instance. By default, Twig comes with one policy class:
|
476 |
-
``Twig_Sandbox_SecurityPolicy``. This class allows you to white-list some
|
477 |
-
tags, filters, properties, and methods::
|
478 |
-
|
479 |
-
$tags = array('if');
|
480 |
-
$filters = array('upper');
|
481 |
-
$methods = array(
|
482 |
-
'Article' => array('getTitle', 'getBody'),
|
483 |
-
);
|
484 |
-
$properties = array(
|
485 |
-
'Article' => array('title', 'body'),
|
486 |
-
);
|
487 |
-
$functions = array('range');
|
488 |
-
$policy = new Twig_Sandbox_SecurityPolicy($tags, $filters, $methods, $properties, $functions);
|
489 |
-
|
490 |
-
With the previous configuration, the security policy will only allow usage of
|
491 |
-
the ``if`` tag, and the ``upper`` filter. Moreover, the templates will only be
|
492 |
-
able to call the ``getTitle()`` and ``getBody()`` methods on ``Article``
|
493 |
-
objects, and the ``title`` and ``body`` public properties. Everything else
|
494 |
-
won't be allowed and will generate a ``Twig_Sandbox_SecurityError`` exception.
|
495 |
-
|
496 |
-
The policy object is the first argument of the sandbox constructor::
|
497 |
-
|
498 |
-
$sandbox = new Twig_Extension_Sandbox($policy);
|
499 |
-
$twig->addExtension($sandbox);
|
500 |
-
|
501 |
-
By default, the sandbox mode is disabled and should be enabled when including
|
502 |
-
untrusted template code by using the ``sandbox`` tag:
|
503 |
-
|
504 |
-
.. code-block:: jinja
|
505 |
-
|
506 |
-
{% sandbox %}
|
507 |
-
{% include 'user.html' %}
|
508 |
-
{% endsandbox %}
|
509 |
-
|
510 |
-
You can sandbox all templates by passing ``true`` as the second argument of
|
511 |
-
the extension constructor::
|
512 |
-
|
513 |
-
$sandbox = new Twig_Extension_Sandbox($policy, true);
|
514 |
-
|
515 |
-
Profiler Extension
|
516 |
-
~~~~~~~~~~~~~~~~~~
|
517 |
-
|
518 |
-
.. versionadded:: 1.18
|
519 |
-
The Profile extension was added in Twig 1.18.
|
520 |
-
|
521 |
-
The ``profiler`` extension enables a profiler for Twig templates; it should
|
522 |
-
only be used on your development machines as it adds some overhead::
|
523 |
-
|
524 |
-
$profile = new Twig_Profiler_Profile();
|
525 |
-
$twig->addExtension(new Twig_Extension_Profiler($profile));
|
526 |
-
|
527 |
-
$dumper = new Twig_Profiler_Dumper_Text();
|
528 |
-
echo $dumper->dump($profile);
|
529 |
-
|
530 |
-
A profile contains information about time and memory consumption for template,
|
531 |
-
block, and macro executions.
|
532 |
-
|
533 |
-
You can also dump the data in a `Blackfire.io <https://blackfire.io/>`_
|
534 |
-
compatible format::
|
535 |
-
|
536 |
-
$dumper = new Twig_Profiler_Dumper_Blackfire();
|
537 |
-
file_put_contents('/path/to/profile.prof', $dumper->dump($profile));
|
538 |
-
|
539 |
-
Upload the profile to visualize it (create a `free account
|
540 |
-
<https://blackfire.io/signup>`_ first):
|
541 |
-
|
542 |
-
.. code-block:: sh
|
543 |
-
|
544 |
-
blackfire --slot=7 upload /path/to/profile.prof
|
545 |
-
|
546 |
-
Optimizer Extension
|
547 |
-
~~~~~~~~~~~~~~~~~~~
|
548 |
-
|
549 |
-
The ``optimizer`` extension optimizes the node tree before compilation::
|
550 |
-
|
551 |
-
$twig->addExtension(new Twig_Extension_Optimizer());
|
552 |
-
|
553 |
-
By default, all optimizations are turned on. You can select the ones you want
|
554 |
-
to enable by passing them to the constructor::
|
555 |
-
|
556 |
-
$optimizer = new Twig_Extension_Optimizer(Twig_NodeVisitor_Optimizer::OPTIMIZE_FOR);
|
557 |
-
|
558 |
-
$twig->addExtension($optimizer);
|
559 |
-
|
560 |
-
Twig supports the following optimizations:
|
561 |
-
|
562 |
-
* ``Twig_NodeVisitor_Optimizer::OPTIMIZE_ALL``, enables all optimizations
|
563 |
-
(this is the default value).
|
564 |
-
* ``Twig_NodeVisitor_Optimizer::OPTIMIZE_NONE``, disables all optimizations.
|
565 |
-
This reduces the compilation time, but it can increase the execution time
|
566 |
-
and the consumed memory.
|
567 |
-
* ``Twig_NodeVisitor_Optimizer::OPTIMIZE_FOR``, optimizes the ``for`` tag by
|
568 |
-
removing the ``loop`` variable creation whenever possible.
|
569 |
-
* ``Twig_NodeVisitor_Optimizer::OPTIMIZE_RAW_FILTER``, removes the ``raw``
|
570 |
-
filter whenever possible.
|
571 |
-
* ``Twig_NodeVisitor_Optimizer::OPTIMIZE_VAR_ACCESS``, simplifies the creation
|
572 |
-
and access of variables in the compiled templates whenever possible.
|
573 |
-
|
574 |
-
Exceptions
|
575 |
-
----------
|
576 |
-
|
577 |
-
Twig can throw exceptions:
|
578 |
-
|
579 |
-
* ``Twig_Error``: The base exception for all errors.
|
580 |
-
|
581 |
-
* ``Twig_Error_Syntax``: Thrown to tell the user that there is a problem with
|
582 |
-
the template syntax.
|
583 |
-
|
584 |
-
* ``Twig_Error_Runtime``: Thrown when an error occurs at runtime (when a filter
|
585 |
-
does not exist for instance).
|
586 |
-
|
587 |
-
* ``Twig_Error_Loader``: Thrown when an error occurs during template loading.
|
588 |
-
|
589 |
-
* ``Twig_Sandbox_SecurityError``: Thrown when an unallowed tag, filter, or
|
590 |
-
method is called in a sandboxed template.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/coding_standards.rst
DELETED
@@ -1,101 +0,0 @@
|
|
1 |
-
Coding Standards
|
2 |
-
================
|
3 |
-
|
4 |
-
When writing Twig templates, we recommend you to follow these official coding
|
5 |
-
standards:
|
6 |
-
|
7 |
-
* Put one (and only one) space after the start of a delimiter (``{{``, ``{%``,
|
8 |
-
and ``{#``) and before the end of a delimiter (``}}``, ``%}``, and ``#}``):
|
9 |
-
|
10 |
-
.. code-block:: jinja
|
11 |
-
|
12 |
-
{{ foo }}
|
13 |
-
{# comment #}
|
14 |
-
{% if foo %}{% endif %}
|
15 |
-
|
16 |
-
When using the whitespace control character, do not put any spaces between
|
17 |
-
it and the delimiter:
|
18 |
-
|
19 |
-
.. code-block:: jinja
|
20 |
-
|
21 |
-
{{- foo -}}
|
22 |
-
{#- comment -#}
|
23 |
-
{%- if foo -%}{%- endif -%}
|
24 |
-
|
25 |
-
* Put one (and only one) space before and after the following operators:
|
26 |
-
comparison operators (``==``, ``!=``, ``<``, ``>``, ``>=``, ``<=``), math
|
27 |
-
operators (``+``, ``-``, ``/``, ``*``, ``%``, ``//``, ``**``), logic
|
28 |
-
operators (``not``, ``and``, ``or``), ``~``, ``is``, ``in``, and the ternary
|
29 |
-
operator (``?:``):
|
30 |
-
|
31 |
-
.. code-block:: jinja
|
32 |
-
|
33 |
-
{{ 1 + 2 }}
|
34 |
-
{{ foo ~ bar }}
|
35 |
-
{{ true ? true : false }}
|
36 |
-
|
37 |
-
* Put one (and only one) space after the ``:`` sign in hashes and ``,`` in
|
38 |
-
arrays and hashes:
|
39 |
-
|
40 |
-
.. code-block:: jinja
|
41 |
-
|
42 |
-
{{ [1, 2, 3] }}
|
43 |
-
{{ {'foo': 'bar'} }}
|
44 |
-
|
45 |
-
* Do not put any spaces after an opening parenthesis and before a closing
|
46 |
-
parenthesis in expressions:
|
47 |
-
|
48 |
-
.. code-block:: jinja
|
49 |
-
|
50 |
-
{{ 1 + (2 * 3) }}
|
51 |
-
|
52 |
-
* Do not put any spaces before and after string delimiters:
|
53 |
-
|
54 |
-
.. code-block:: jinja
|
55 |
-
|
56 |
-
{{ 'foo' }}
|
57 |
-
{{ "foo" }}
|
58 |
-
|
59 |
-
* Do not put any spaces before and after the following operators: ``|``,
|
60 |
-
``.``, ``..``, ``[]``:
|
61 |
-
|
62 |
-
.. code-block:: jinja
|
63 |
-
|
64 |
-
{{ foo|upper|lower }}
|
65 |
-
{{ user.name }}
|
66 |
-
{{ user[name] }}
|
67 |
-
{% for i in 1..12 %}{% endfor %}
|
68 |
-
|
69 |
-
* Do not put any spaces before and after the parenthesis used for filter and
|
70 |
-
function calls:
|
71 |
-
|
72 |
-
.. code-block:: jinja
|
73 |
-
|
74 |
-
{{ foo|default('foo') }}
|
75 |
-
{{ range(1..10) }}
|
76 |
-
|
77 |
-
* Do not put any spaces before and after the opening and the closing of arrays
|
78 |
-
and hashes:
|
79 |
-
|
80 |
-
.. code-block:: jinja
|
81 |
-
|
82 |
-
{{ [1, 2, 3] }}
|
83 |
-
{{ {'foo': 'bar'} }}
|
84 |
-
|
85 |
-
* Use lower cased and underscored variable names:
|
86 |
-
|
87 |
-
.. code-block:: jinja
|
88 |
-
|
89 |
-
{% set foo = 'foo' %}
|
90 |
-
{% set foo_bar = 'foo' %}
|
91 |
-
|
92 |
-
* Indent your code inside tags (use the same indentation as the one used for
|
93 |
-
the target language of the rendered template):
|
94 |
-
|
95 |
-
.. code-block:: jinja
|
96 |
-
|
97 |
-
{% block foo %}
|
98 |
-
{% if true %}
|
99 |
-
true
|
100 |
-
{% endif %}
|
101 |
-
{% endblock %}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/deprecated.rst
DELETED
@@ -1,224 +0,0 @@
|
|
1 |
-
Deprecated Features
|
2 |
-
===================
|
3 |
-
|
4 |
-
This document lists all deprecated features in Twig. Deprecated features are
|
5 |
-
kept for backward compatibility and removed in the next major release (a
|
6 |
-
feature that was deprecated in Twig 1.x is removed in Twig 2.0).
|
7 |
-
|
8 |
-
Deprecation Notices
|
9 |
-
-------------------
|
10 |
-
|
11 |
-
As of Twig 1.21, Twig generates deprecation notices when a template uses
|
12 |
-
deprecated features. See :ref:`deprecation-notices` for more information.
|
13 |
-
|
14 |
-
Macros
|
15 |
-
------
|
16 |
-
|
17 |
-
As of Twig 2.0, macros imported in a file are not available in child templates
|
18 |
-
anymore (via an ``include`` call for instance). You need to import macros
|
19 |
-
explicitly in each file where you are using them.
|
20 |
-
|
21 |
-
Token Parsers
|
22 |
-
-------------
|
23 |
-
|
24 |
-
* As of Twig 1.x, the token parser broker sub-system is deprecated. The
|
25 |
-
following class and interface will be removed in 2.0:
|
26 |
-
|
27 |
-
* ``Twig_TokenParserBrokerInterface``
|
28 |
-
* ``Twig_TokenParserBroker``
|
29 |
-
|
30 |
-
* As of Twig 1.27, ``Twig_Parser::getFilename()`` is deprecated. From a token
|
31 |
-
parser, use ``$this->parser->getStream()->getSourceContext()->getPath()`` instead.
|
32 |
-
|
33 |
-
* As of Twig 1.27, ``Twig_Parser::getEnvironment()`` is deprecated.
|
34 |
-
|
35 |
-
Extensions
|
36 |
-
----------
|
37 |
-
|
38 |
-
* As of Twig 1.x, the ability to remove an extension is deprecated and the
|
39 |
-
``Twig_Environment::removeExtension()`` method will be removed in 2.0.
|
40 |
-
|
41 |
-
* As of Twig 1.23, the ``Twig_ExtensionInterface::initRuntime()`` method is
|
42 |
-
deprecated. You have two options to avoid the deprecation notice: if you
|
43 |
-
implement this method to store the environment for your custom filters,
|
44 |
-
functions, or tests, use the ``needs_environment`` option instead; if you
|
45 |
-
have more complex needs, explicitly implement
|
46 |
-
``Twig_Extension_InitRuntimeInterface`` (not recommended).
|
47 |
-
|
48 |
-
* As of Twig 1.23, the ``Twig_ExtensionInterface::getGlobals()`` method is
|
49 |
-
deprecated. Implement ``Twig_Extension_GlobalsInterface`` to avoid
|
50 |
-
deprecation notices.
|
51 |
-
|
52 |
-
* As of Twig 1.26, the ``Twig_ExtensionInterface::getName()`` method is
|
53 |
-
deprecated and it is not used internally anymore.
|
54 |
-
|
55 |
-
PEAR
|
56 |
-
----
|
57 |
-
|
58 |
-
PEAR support has been discontinued in Twig 1.15.1, and no PEAR packages are
|
59 |
-
provided anymore. Use Composer instead.
|
60 |
-
|
61 |
-
Filters
|
62 |
-
-------
|
63 |
-
|
64 |
-
* As of Twig 1.x, use ``Twig_SimpleFilter`` to add a filter. The following
|
65 |
-
classes and interfaces will be removed in 2.0:
|
66 |
-
|
67 |
-
* ``Twig_FilterInterface``
|
68 |
-
* ``Twig_FilterCallableInterface``
|
69 |
-
* ``Twig_Filter``
|
70 |
-
* ``Twig_Filter_Function``
|
71 |
-
* ``Twig_Filter_Method``
|
72 |
-
* ``Twig_Filter_Node``
|
73 |
-
|
74 |
-
* As of Twig 2.x, the ``Twig_SimpleFilter`` class is deprecated and will be
|
75 |
-
removed in Twig 3.x (use ``Twig_Filter`` instead). In Twig 2.x,
|
76 |
-
``Twig_SimpleFilter`` is just an alias for ``Twig_Filter``.
|
77 |
-
|
78 |
-
Functions
|
79 |
-
---------
|
80 |
-
|
81 |
-
* As of Twig 1.x, use ``Twig_SimpleFunction`` to add a function. The following
|
82 |
-
classes and interfaces will be removed in 2.0:
|
83 |
-
|
84 |
-
* ``Twig_FunctionInterface``
|
85 |
-
* ``Twig_FunctionCallableInterface``
|
86 |
-
* ``Twig_Function``
|
87 |
-
* ``Twig_Function_Function``
|
88 |
-
* ``Twig_Function_Method``
|
89 |
-
* ``Twig_Function_Node``
|
90 |
-
|
91 |
-
* As of Twig 2.x, the ``Twig_SimpleFunction`` class is deprecated and will be
|
92 |
-
removed in Twig 3.x (use ``Twig_Function`` instead). In Twig 2.x,
|
93 |
-
``Twig_SimpleFunction`` is just an alias for ``Twig_Function``.
|
94 |
-
|
95 |
-
Tests
|
96 |
-
-----
|
97 |
-
|
98 |
-
* As of Twig 1.x, use ``Twig_SimpleTest`` to add a test. The following classes
|
99 |
-
and interfaces will be removed in 2.0:
|
100 |
-
|
101 |
-
* ``Twig_TestInterface``
|
102 |
-
* ``Twig_TestCallableInterface``
|
103 |
-
* ``Twig_Test``
|
104 |
-
* ``Twig_Test_Function``
|
105 |
-
* ``Twig_Test_Method``
|
106 |
-
* ``Twig_Test_Node``
|
107 |
-
|
108 |
-
* As of Twig 2.x, the ``Twig_SimpleTest`` class is deprecated and will be
|
109 |
-
removed in Twig 3.x (use ``Twig_Test`` instead). In Twig 2.x,
|
110 |
-
``Twig_SimpleTest`` is just an alias for ``Twig_Test``.
|
111 |
-
|
112 |
-
* The ``sameas`` and ``divisibleby`` tests are deprecated in favor of ``same
|
113 |
-
as`` and ``divisible by`` respectively.
|
114 |
-
|
115 |
-
Tags
|
116 |
-
----
|
117 |
-
|
118 |
-
* As of Twig 1.x, the ``raw`` tag is deprecated. You should use ``verbatim``
|
119 |
-
instead.
|
120 |
-
|
121 |
-
Nodes
|
122 |
-
-----
|
123 |
-
|
124 |
-
* As of Twig 1.x, ``Node::toXml()`` is deprecated and will be removed in Twig
|
125 |
-
2.0.
|
126 |
-
|
127 |
-
* As of Twig 1.26, ``Node::$nodes`` should only contains ``Twig_Node``
|
128 |
-
instances, storing a ``null`` value is deprecated and won't be possible in
|
129 |
-
Twig 2.x.
|
130 |
-
|
131 |
-
* As of Twig 1.27, the ``filename`` attribute on ``Twig_Node_Module`` is
|
132 |
-
deprecated. Use ``getName()`` instead.
|
133 |
-
|
134 |
-
* As of Twig 1.27, the ``Twig_Node::getFilename()/Twig_Node::getLine()``
|
135 |
-
methods are deprecated, use
|
136 |
-
``Twig_Node::getTemplateName()/Twig_Node::getTemplateLine()`` instead.
|
137 |
-
|
138 |
-
Interfaces
|
139 |
-
----------
|
140 |
-
|
141 |
-
* As of Twig 2.x, the following interfaces are deprecated and empty (they will
|
142 |
-
be removed in Twig 3.0):
|
143 |
-
|
144 |
-
* ``Twig_CompilerInterface`` (use ``Twig_Compiler`` instead)
|
145 |
-
* ``Twig_LexerInterface`` (use ``Twig_Lexer`` instead)
|
146 |
-
* ``Twig_NodeInterface`` (use ``Twig_Node`` instead)
|
147 |
-
* ``Twig_ParserInterface`` (use ``Twig_Parser`` instead)
|
148 |
-
* ``Twig_ExistsLoaderInterface`` (merged with ``Twig_LoaderInterface``)
|
149 |
-
* ``Twig_SourceContextLoaderInterface`` (merged with ``Twig_LoaderInterface``)
|
150 |
-
* ``Twig_TemplateInterface`` (use ``Twig_Template`` instead, and use
|
151 |
-
those constants Twig_Template::ANY_CALL, Twig_Template::ARRAY_CALL,
|
152 |
-
Twig_Template::METHOD_CALL)
|
153 |
-
|
154 |
-
Compiler
|
155 |
-
--------
|
156 |
-
|
157 |
-
* As of Twig 1.26, the ``Twig_Compiler::getFilename()`` has been deprecated.
|
158 |
-
You should not use it anyway as its values is not reliable.
|
159 |
-
|
160 |
-
* As of Twig 1.27, the ``Twig_Compiler::addIndentation()`` has been deprecated.
|
161 |
-
Use ``Twig_Compiler::write('')`` instead.
|
162 |
-
|
163 |
-
Loaders
|
164 |
-
-------
|
165 |
-
|
166 |
-
* As of Twig 1.x, ``Twig_Loader_String`` is deprecated and will be removed in
|
167 |
-
2.0. You can render a string via ``Twig_Environment::createTemplate()``.
|
168 |
-
|
169 |
-
* As of Twig 1.27, ``Twig_LoaderInterface::getSource()`` is deprecated.
|
170 |
-
Implement ``Twig_SourceContextLoaderInterface`` instead and use
|
171 |
-
``getSourceContext()``.
|
172 |
-
|
173 |
-
Node Visitors
|
174 |
-
-------------
|
175 |
-
|
176 |
-
* Because of the removal of ``Twig_NodeInterface`` in 2.0, you need to extend
|
177 |
-
``Twig_BaseNodeVisitor`` instead of implementing ``Twig_NodeVisitorInterface``
|
178 |
-
directly to make your node visitors compatible with both Twig 1.x and 2.x.
|
179 |
-
|
180 |
-
Globals
|
181 |
-
-------
|
182 |
-
|
183 |
-
* As of Twig 2.x, the ability to register a global variable after the runtime
|
184 |
-
or the extensions have been initialized is not possible anymore (but
|
185 |
-
changing the value of an already registered global is possible).
|
186 |
-
|
187 |
-
* As of Twig 1.x, using the ``_self`` global variable to get access to the
|
188 |
-
current ``Twig_Template`` instance is deprecated; most usages only need the
|
189 |
-
current template name, which will continue to work in Twig 2.0. In Twig 2.0,
|
190 |
-
``_self`` returns the current template name instead of the current
|
191 |
-
``Twig_Template`` instance. If you are using ``{{ _self.templateName }}``,
|
192 |
-
just replace it with ``{{ _self }}``.
|
193 |
-
|
194 |
-
Miscellaneous
|
195 |
-
-------------
|
196 |
-
|
197 |
-
* As of Twig 1.x, ``Twig_Environment::clearTemplateCache()``,
|
198 |
-
``Twig_Environment::writeCacheFile()``,
|
199 |
-
``Twig_Environment::clearCacheFiles()``,
|
200 |
-
``Twig_Environment::getCacheFilename()``,
|
201 |
-
``Twig_Environment::getTemplateClassPrefix()``,
|
202 |
-
``Twig_Environment::getLexer()``, ``Twig_Environment::getParser()``, and
|
203 |
-
``Twig_Environment::getCompiler()`` are deprecated and will be removed in 2.0.
|
204 |
-
|
205 |
-
* As of Twig 1.x, ``Twig_Template::getEnvironment()`` and
|
206 |
-
``Twig_TemplateInterface::getEnvironment()`` are deprecated and will be
|
207 |
-
removed in 2.0.
|
208 |
-
|
209 |
-
* As of Twig 1.21, setting the environment option ``autoescape`` to ``true`` is
|
210 |
-
deprecated and will be removed in 2.0. Use ``"html"`` instead.
|
211 |
-
|
212 |
-
* As of Twig 1.27, ``Twig_Error::getTemplateFile()`` and
|
213 |
-
``Twig_Error::setTemplateFile()`` are deprecated. Use
|
214 |
-
``Twig_Error::getTemplateName()`` and ``Twig_Error::setTemplateName()``
|
215 |
-
instead.
|
216 |
-
|
217 |
-
* As of Twig 1.27, ``Twig_Template::getSource()`` is deprecated. Use
|
218 |
-
``Twig_Template::getSourceContext()`` instead.
|
219 |
-
|
220 |
-
* As of Twig 1.27, ``Twig_Parser::addHandler()`` and
|
221 |
-
``Twig_Parser::addNodeVisitor()`` are deprecated and will be removed in 2.0.
|
222 |
-
|
223 |
-
* As of Twig 1.29, some classes are marked as being final via the `@final`
|
224 |
-
annotation. Those classes will be marked as final in 2.0.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/filters/abs.rst
DELETED
@@ -1,18 +0,0 @@
|
|
1 |
-
``abs``
|
2 |
-
=======
|
3 |
-
|
4 |
-
The ``abs`` filter returns the absolute value.
|
5 |
-
|
6 |
-
.. code-block:: jinja
|
7 |
-
|
8 |
-
{# number = -5 #}
|
9 |
-
|
10 |
-
{{ number|abs }}
|
11 |
-
|
12 |
-
{# outputs 5 #}
|
13 |
-
|
14 |
-
.. note::
|
15 |
-
|
16 |
-
Internally, Twig uses the PHP `abs`_ function.
|
17 |
-
|
18 |
-
.. _`abs`: http://php.net/abs
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/filters/batch.rst
DELETED
@@ -1,51 +0,0 @@
|
|
1 |
-
``batch``
|
2 |
-
=========
|
3 |
-
|
4 |
-
.. versionadded:: 1.12.3
|
5 |
-
The ``batch`` filter was added in Twig 1.12.3.
|
6 |
-
|
7 |
-
The ``batch`` filter "batches" items by returning a list of lists with the
|
8 |
-
given number of items. A second parameter can be provided and used to fill in
|
9 |
-
missing items:
|
10 |
-
|
11 |
-
.. code-block:: jinja
|
12 |
-
|
13 |
-
{% set items = ['a', 'b', 'c', 'd', 'e', 'f', 'g'] %}
|
14 |
-
|
15 |
-
<table>
|
16 |
-
{% for row in items|batch(3, 'No item') %}
|
17 |
-
<tr>
|
18 |
-
{% for column in row %}
|
19 |
-
<td>{{ column }}</td>
|
20 |
-
{% endfor %}
|
21 |
-
</tr>
|
22 |
-
{% endfor %}
|
23 |
-
</table>
|
24 |
-
|
25 |
-
The above example will be rendered as:
|
26 |
-
|
27 |
-
.. code-block:: jinja
|
28 |
-
|
29 |
-
<table>
|
30 |
-
<tr>
|
31 |
-
<td>a</td>
|
32 |
-
<td>b</td>
|
33 |
-
<td>c</td>
|
34 |
-
</tr>
|
35 |
-
<tr>
|
36 |
-
<td>d</td>
|
37 |
-
<td>e</td>
|
38 |
-
<td>f</td>
|
39 |
-
</tr>
|
40 |
-
<tr>
|
41 |
-
<td>g</td>
|
42 |
-
<td>No item</td>
|
43 |
-
<td>No item</td>
|
44 |
-
</tr>
|
45 |
-
</table>
|
46 |
-
|
47 |
-
Arguments
|
48 |
-
---------
|
49 |
-
|
50 |
-
* ``size``: The size of the batch; fractional numbers will be rounded up
|
51 |
-
* ``fill``: Used to fill in missing items
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/filters/capitalize.rst
DELETED
@@ -1,11 +0,0 @@
|
|
1 |
-
``capitalize``
|
2 |
-
==============
|
3 |
-
|
4 |
-
The ``capitalize`` filter capitalizes a value. The first character will be
|
5 |
-
uppercase, all others lowercase:
|
6 |
-
|
7 |
-
.. code-block:: jinja
|
8 |
-
|
9 |
-
{{ 'my first car'|capitalize }}
|
10 |
-
|
11 |
-
{# outputs 'My first car' #}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/filters/convert_encoding.rst
DELETED
@@ -1,28 +0,0 @@
|
|
1 |
-
``convert_encoding``
|
2 |
-
====================
|
3 |
-
|
4 |
-
.. versionadded:: 1.4
|
5 |
-
The ``convert_encoding`` filter was added in Twig 1.4.
|
6 |
-
|
7 |
-
The ``convert_encoding`` filter converts a string from one encoding to
|
8 |
-
another. The first argument is the expected output charset and the second one
|
9 |
-
is the input charset:
|
10 |
-
|
11 |
-
.. code-block:: jinja
|
12 |
-
|
13 |
-
{{ data|convert_encoding('UTF-8', 'iso-2022-jp') }}
|
14 |
-
|
15 |
-
.. note::
|
16 |
-
|
17 |
-
This filter relies on the `iconv`_ or `mbstring`_ extension, so one of
|
18 |
-
them must be installed. In case both are installed, `mbstring`_ is used by
|
19 |
-
default (Twig before 1.8.1 uses `iconv`_ by default).
|
20 |
-
|
21 |
-
Arguments
|
22 |
-
---------
|
23 |
-
|
24 |
-
* ``to``: The output charset
|
25 |
-
* ``from``: The input charset
|
26 |
-
|
27 |
-
.. _`iconv`: http://php.net/iconv
|
28 |
-
.. _`mbstring`: http://php.net/mbstring
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/filters/date.rst
DELETED
@@ -1,100 +0,0 @@
|
|
1 |
-
``date``
|
2 |
-
========
|
3 |
-
|
4 |
-
.. versionadded:: 1.1
|
5 |
-
The timezone support has been added in Twig 1.1.
|
6 |
-
|
7 |
-
.. versionadded:: 1.5
|
8 |
-
The default date format support has been added in Twig 1.5.
|
9 |
-
|
10 |
-
.. versionadded:: 1.6.1
|
11 |
-
The default timezone support has been added in Twig 1.6.1.
|
12 |
-
|
13 |
-
.. versionadded:: 1.11.0
|
14 |
-
The introduction of the false value for the timezone was introduced in Twig 1.11.0
|
15 |
-
|
16 |
-
The ``date`` filter formats a date to a given format:
|
17 |
-
|
18 |
-
.. code-block:: jinja
|
19 |
-
|
20 |
-
{{ post.published_at|date("m/d/Y") }}
|
21 |
-
|
22 |
-
The format specifier is the same as supported by `date`_,
|
23 |
-
except when the filtered data is of type `DateInterval`_, when the format must conform to
|
24 |
-
`DateInterval::format`_ instead.
|
25 |
-
|
26 |
-
The ``date`` filter accepts strings (it must be in a format supported by the
|
27 |
-
`strtotime`_ function), `DateTime`_ instances, or `DateInterval`_ instances. For
|
28 |
-
instance, to display the current date, filter the word "now":
|
29 |
-
|
30 |
-
.. code-block:: jinja
|
31 |
-
|
32 |
-
{{ "now"|date("m/d/Y") }}
|
33 |
-
|
34 |
-
To escape words and characters in the date format use ``\\`` in front of each
|
35 |
-
character:
|
36 |
-
|
37 |
-
.. code-block:: jinja
|
38 |
-
|
39 |
-
{{ post.published_at|date("F jS \\a\\t g:ia") }}
|
40 |
-
|
41 |
-
If the value passed to the ``date`` filter is ``null``, it will return the
|
42 |
-
current date by default. If an empty string is desired instead of the current
|
43 |
-
date, use a ternary operator:
|
44 |
-
|
45 |
-
.. code-block:: jinja
|
46 |
-
|
47 |
-
{{ post.published_at is empty ? "" : post.published_at|date("m/d/Y") }}
|
48 |
-
|
49 |
-
If no format is provided, Twig will use the default one: ``F j, Y H:i``. This
|
50 |
-
default can be easily changed by calling the ``setDateFormat()`` method on the
|
51 |
-
``core`` extension instance. The first argument is the default format for
|
52 |
-
dates and the second one is the default format for date intervals:
|
53 |
-
|
54 |
-
.. code-block:: php
|
55 |
-
|
56 |
-
$twig = new Twig_Environment($loader);
|
57 |
-
$twig->getExtension('Twig_Extension_Core')->setDateFormat('d/m/Y', '%d days');
|
58 |
-
|
59 |
-
// before Twig 1.26
|
60 |
-
$twig->getExtension('core')->setDateFormat('d/m/Y', '%d days');
|
61 |
-
|
62 |
-
Timezone
|
63 |
-
--------
|
64 |
-
|
65 |
-
By default, the date is displayed by applying the default timezone (the one
|
66 |
-
specified in php.ini or declared in Twig -- see below), but you can override
|
67 |
-
it by explicitly specifying a timezone:
|
68 |
-
|
69 |
-
.. code-block:: jinja
|
70 |
-
|
71 |
-
{{ post.published_at|date("m/d/Y", "Europe/Paris") }}
|
72 |
-
|
73 |
-
If the date is already a DateTime object, and if you want to keep its current
|
74 |
-
timezone, pass ``false`` as the timezone value:
|
75 |
-
|
76 |
-
.. code-block:: jinja
|
77 |
-
|
78 |
-
{{ post.published_at|date("m/d/Y", false) }}
|
79 |
-
|
80 |
-
The default timezone can also be set globally by calling ``setTimezone()``:
|
81 |
-
|
82 |
-
.. code-block:: php
|
83 |
-
|
84 |
-
$twig = new Twig_Environment($loader);
|
85 |
-
$twig->getExtension('Twig_Extension_Core')->setTimezone('Europe/Paris');
|
86 |
-
|
87 |
-
// before Twig 1.26
|
88 |
-
$twig->getExtension('core')->setTimezone('Europe/Paris');
|
89 |
-
|
90 |
-
Arguments
|
91 |
-
---------
|
92 |
-
|
93 |
-
* ``format``: The date format
|
94 |
-
* ``timezone``: The date timezone
|
95 |
-
|
96 |
-
.. _`strtotime`: http://www.php.net/strtotime
|
97 |
-
.. _`DateTime`: http://www.php.net/DateTime
|
98 |
-
.. _`DateInterval`: http://www.php.net/DateInterval
|
99 |
-
.. _`date`: http://www.php.net/date
|
100 |
-
.. _`DateInterval::format`: http://www.php.net/DateInterval.format
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/filters/date_modify.rst
DELETED
@@ -1,23 +0,0 @@
|
|
1 |
-
``date_modify``
|
2 |
-
===============
|
3 |
-
|
4 |
-
.. versionadded:: 1.9.0
|
5 |
-
The date_modify filter has been added in Twig 1.9.0.
|
6 |
-
|
7 |
-
The ``date_modify`` filter modifies a date with a given modifier string:
|
8 |
-
|
9 |
-
.. code-block:: jinja
|
10 |
-
|
11 |
-
{{ post.published_at|date_modify("+1 day")|date("m/d/Y") }}
|
12 |
-
|
13 |
-
The ``date_modify`` filter accepts strings (it must be in a format supported
|
14 |
-
by the `strtotime`_ function) or `DateTime`_ instances. You can easily combine
|
15 |
-
it with the :doc:`date<date>` filter for formatting.
|
16 |
-
|
17 |
-
Arguments
|
18 |
-
---------
|
19 |
-
|
20 |
-
* ``modifier``: The modifier
|
21 |
-
|
22 |
-
.. _`strtotime`: http://www.php.net/strtotime
|
23 |
-
.. _`DateTime`: http://www.php.net/DateTime
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/filters/default.rst
DELETED
@@ -1,33 +0,0 @@
|
|
1 |
-
``default``
|
2 |
-
===========
|
3 |
-
|
4 |
-
The ``default`` filter returns the passed default value if the value is
|
5 |
-
undefined or empty, otherwise the value of the variable:
|
6 |
-
|
7 |
-
.. code-block:: jinja
|
8 |
-
|
9 |
-
{{ var|default('var is not defined') }}
|
10 |
-
|
11 |
-
{{ var.foo|default('foo item on var is not defined') }}
|
12 |
-
|
13 |
-
{{ var['foo']|default('foo item on var is not defined') }}
|
14 |
-
|
15 |
-
{{ ''|default('passed var is empty') }}
|
16 |
-
|
17 |
-
When using the ``default`` filter on an expression that uses variables in some
|
18 |
-
method calls, be sure to use the ``default`` filter whenever a variable can be
|
19 |
-
undefined:
|
20 |
-
|
21 |
-
.. code-block:: jinja
|
22 |
-
|
23 |
-
{{ var.method(foo|default('foo'))|default('foo') }}
|
24 |
-
|
25 |
-
.. note::
|
26 |
-
|
27 |
-
Read the documentation for the :doc:`defined<../tests/defined>` and
|
28 |
-
:doc:`empty<../tests/empty>` tests to learn more about their semantics.
|
29 |
-
|
30 |
-
Arguments
|
31 |
-
---------
|
32 |
-
|
33 |
-
* ``default``: The default value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/filters/escape.rst
DELETED
@@ -1,119 +0,0 @@
|
|
1 |
-
``escape``
|
2 |
-
==========
|
3 |
-
|
4 |
-
.. versionadded:: 1.9.0
|
5 |
-
The ``css``, ``url``, and ``html_attr`` strategies were added in Twig
|
6 |
-
1.9.0.
|
7 |
-
|
8 |
-
.. versionadded:: 1.14.0
|
9 |
-
The ability to define custom escapers was added in Twig 1.14.0.
|
10 |
-
|
11 |
-
The ``escape`` filter escapes a string for safe insertion into the final
|
12 |
-
output. It supports different escaping strategies depending on the template
|
13 |
-
context.
|
14 |
-
|
15 |
-
By default, it uses the HTML escaping strategy:
|
16 |
-
|
17 |
-
.. code-block:: jinja
|
18 |
-
|
19 |
-
{{ user.username|escape }}
|
20 |
-
|
21 |
-
For convenience, the ``e`` filter is defined as an alias:
|
22 |
-
|
23 |
-
.. code-block:: jinja
|
24 |
-
|
25 |
-
{{ user.username|e }}
|
26 |
-
|
27 |
-
The ``escape`` filter can also be used in other contexts than HTML thanks to
|
28 |
-
an optional argument which defines the escaping strategy to use:
|
29 |
-
|
30 |
-
.. code-block:: jinja
|
31 |
-
|
32 |
-
{{ user.username|e }}
|
33 |
-
{# is equivalent to #}
|
34 |
-
{{ user.username|e('html') }}
|
35 |
-
|
36 |
-
And here is how to escape variables included in JavaScript code:
|
37 |
-
|
38 |
-
.. code-block:: jinja
|
39 |
-
|
40 |
-
{{ user.username|escape('js') }}
|
41 |
-
{{ user.username|e('js') }}
|
42 |
-
|
43 |
-
The ``escape`` filter supports the following escaping strategies:
|
44 |
-
|
45 |
-
* ``html``: escapes a string for the **HTML body** context.
|
46 |
-
|
47 |
-
* ``js``: escapes a string for the **JavaScript context**.
|
48 |
-
|
49 |
-
* ``css``: escapes a string for the **CSS context**. CSS escaping can be
|
50 |
-
applied to any string being inserted into CSS and escapes everything except
|
51 |
-
alphanumerics.
|
52 |
-
|
53 |
-
* ``url``: escapes a string for the **URI or parameter contexts**. This should
|
54 |
-
not be used to escape an entire URI; only a subcomponent being inserted.
|
55 |
-
|
56 |
-
* ``html_attr``: escapes a string for the **HTML attribute** context.
|
57 |
-
|
58 |
-
.. note::
|
59 |
-
|
60 |
-
Internally, ``escape`` uses the PHP native `htmlspecialchars`_ function
|
61 |
-
for the HTML escaping strategy.
|
62 |
-
|
63 |
-
.. caution::
|
64 |
-
|
65 |
-
When using automatic escaping, Twig tries to not double-escape a variable
|
66 |
-
when the automatic escaping strategy is the same as the one applied by the
|
67 |
-
escape filter; but that does not work when using a variable as the
|
68 |
-
escaping strategy:
|
69 |
-
|
70 |
-
.. code-block:: jinja
|
71 |
-
|
72 |
-
{% set strategy = 'html' %}
|
73 |
-
|
74 |
-
{% autoescape 'html' %}
|
75 |
-
{{ var|escape('html') }} {# won't be double-escaped #}
|
76 |
-
{{ var|escape(strategy) }} {# will be double-escaped #}
|
77 |
-
{% endautoescape %}
|
78 |
-
|
79 |
-
When using a variable as the escaping strategy, you should disable
|
80 |
-
automatic escaping:
|
81 |
-
|
82 |
-
.. code-block:: jinja
|
83 |
-
|
84 |
-
{% set strategy = 'html' %}
|
85 |
-
|
86 |
-
{% autoescape 'html' %}
|
87 |
-
{{ var|escape(strategy)|raw }} {# won't be double-escaped #}
|
88 |
-
{% endautoescape %}
|
89 |
-
|
90 |
-
Custom Escapers
|
91 |
-
---------------
|
92 |
-
|
93 |
-
You can define custom escapers by calling the ``setEscaper()`` method on the
|
94 |
-
``core`` extension instance. The first argument is the escaper name (to be
|
95 |
-
used in the ``escape`` call) and the second one must be a valid PHP callable:
|
96 |
-
|
97 |
-
.. code-block:: php
|
98 |
-
|
99 |
-
$twig = new Twig_Environment($loader);
|
100 |
-
$twig->getExtension('Twig_Extension_Core')->setEscaper('csv', 'csv_escaper');
|
101 |
-
|
102 |
-
// before Twig 1.26
|
103 |
-
$twig->getExtension('core')->setEscaper('csv', 'csv_escaper');
|
104 |
-
|
105 |
-
When called by Twig, the callable receives the Twig environment instance, the
|
106 |
-
string to escape, and the charset.
|
107 |
-
|
108 |
-
.. note::
|
109 |
-
|
110 |
-
Built-in escapers cannot be overridden mainly they should be considered as
|
111 |
-
the final implementation and also for better performance.
|
112 |
-
|
113 |
-
Arguments
|
114 |
-
---------
|
115 |
-
|
116 |
-
* ``strategy``: The escaping strategy
|
117 |
-
* ``charset``: The string charset
|
118 |
-
|
119 |
-
.. _`htmlspecialchars`: http://php.net/htmlspecialchars
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/filters/first.rst
DELETED
@@ -1,25 +0,0 @@
|
|
1 |
-
``first``
|
2 |
-
=========
|
3 |
-
|
4 |
-
.. versionadded:: 1.12.2
|
5 |
-
The ``first`` filter was added in Twig 1.12.2.
|
6 |
-
|
7 |
-
The ``first`` filter returns the first "element" of a sequence, a mapping, or
|
8 |
-
a string:
|
9 |
-
|
10 |
-
.. code-block:: jinja
|
11 |
-
|
12 |
-
{{ [1, 2, 3, 4]|first }}
|
13 |
-
{# outputs 1 #}
|
14 |
-
|
15 |
-
{{ { a: 1, b: 2, c: 3, d: 4 }|first }}
|
16 |
-
{# outputs 1 #}
|
17 |
-
|
18 |
-
{{ '1234'|first }}
|
19 |
-
{# outputs 1 #}
|
20 |
-
|
21 |
-
.. note::
|
22 |
-
|
23 |
-
It also works with objects implementing the `Traversable`_ interface.
|
24 |
-
|
25 |
-
.. _`Traversable`: http://php.net/manual/en/class.traversable.php
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/filters/format.rst
DELETED
@@ -1,16 +0,0 @@
|
|
1 |
-
``format``
|
2 |
-
==========
|
3 |
-
|
4 |
-
The ``format`` filter formats a given string by replacing the placeholders
|
5 |
-
(placeholders follows the `sprintf`_ notation):
|
6 |
-
|
7 |
-
.. code-block:: jinja
|
8 |
-
|
9 |
-
{{ "I like %s and %s."|format(foo, "bar") }}
|
10 |
-
|
11 |
-
{# outputs I like foo and bar
|
12 |
-
if the foo parameter equals to the foo string. #}
|
13 |
-
|
14 |
-
.. _`sprintf`: http://www.php.net/sprintf
|
15 |
-
|
16 |
-
.. seealso:: :doc:`replace<replace>`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/filters/index.rst
DELETED
@@ -1,37 +0,0 @@
|
|
1 |
-
Filters
|
2 |
-
=======
|
3 |
-
|
4 |
-
.. toctree::
|
5 |
-
:maxdepth: 1
|
6 |
-
|
7 |
-
abs
|
8 |
-
batch
|
9 |
-
capitalize
|
10 |
-
convert_encoding
|
11 |
-
date
|
12 |
-
date_modify
|
13 |
-
default
|
14 |
-
escape
|
15 |
-
first
|
16 |
-
format
|
17 |
-
join
|
18 |
-
json_encode
|
19 |
-
keys
|
20 |
-
last
|
21 |
-
length
|
22 |
-
lower
|
23 |
-
merge
|
24 |
-
nl2br
|
25 |
-
number_format
|
26 |
-
raw
|
27 |
-
replace
|
28 |
-
reverse
|
29 |
-
round
|
30 |
-
slice
|
31 |
-
sort
|
32 |
-
split
|
33 |
-
striptags
|
34 |
-
title
|
35 |
-
trim
|
36 |
-
upper
|
37 |
-
url_encode
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/filters/join.rst
DELETED
@@ -1,23 +0,0 @@
|
|
1 |
-
``join``
|
2 |
-
========
|
3 |
-
|
4 |
-
The ``join`` filter returns a string which is the concatenation of the items
|
5 |
-
of a sequence:
|
6 |
-
|
7 |
-
.. code-block:: jinja
|
8 |
-
|
9 |
-
{{ [1, 2, 3]|join }}
|
10 |
-
{# returns 123 #}
|
11 |
-
|
12 |
-
The separator between elements is an empty string per default, but you can
|
13 |
-
define it with the optional first parameter:
|
14 |
-
|
15 |
-
.. code-block:: jinja
|
16 |
-
|
17 |
-
{{ [1, 2, 3]|join('|') }}
|
18 |
-
{# outputs 1|2|3 #}
|
19 |
-
|
20 |
-
Arguments
|
21 |
-
---------
|
22 |
-
|
23 |
-
* ``glue``: The separator
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/filters/json_encode.rst
DELETED
@@ -1,21 +0,0 @@
|
|
1 |
-
``json_encode``
|
2 |
-
===============
|
3 |
-
|
4 |
-
The ``json_encode`` filter returns the JSON representation of a value:
|
5 |
-
|
6 |
-
.. code-block:: jinja
|
7 |
-
|
8 |
-
{{ data|json_encode() }}
|
9 |
-
|
10 |
-
.. note::
|
11 |
-
|
12 |
-
Internally, Twig uses the PHP `json_encode`_ function.
|
13 |
-
|
14 |
-
Arguments
|
15 |
-
---------
|
16 |
-
|
17 |
-
* ``options``: A bitmask of `json_encode options`_ (``{{
|
18 |
-
data|json_encode(constant('JSON_PRETTY_PRINT')) }}``)
|
19 |
-
|
20 |
-
.. _`json_encode`: http://php.net/json_encode
|
21 |
-
.. _`json_encode options`: http://www.php.net/manual/en/json.constants.php
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/filters/keys.rst
DELETED
@@ -1,11 +0,0 @@
|
|
1 |
-
``keys``
|
2 |
-
========
|
3 |
-
|
4 |
-
The ``keys`` filter returns the keys of an array. It is useful when you want to
|
5 |
-
iterate over the keys of an array:
|
6 |
-
|
7 |
-
.. code-block:: jinja
|
8 |
-
|
9 |
-
{% for key in array|keys %}
|
10 |
-
...
|
11 |
-
{% endfor %}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/filters/last.rst
DELETED
@@ -1,25 +0,0 @@
|
|
1 |
-
``last``
|
2 |
-
========
|
3 |
-
|
4 |
-
.. versionadded:: 1.12.2
|
5 |
-
The ``last`` filter was added in Twig 1.12.2.
|
6 |
-
|
7 |
-
The ``last`` filter returns the last "element" of a sequence, a mapping, or
|
8 |
-
a string:
|
9 |
-
|
10 |
-
.. code-block:: jinja
|
11 |
-
|
12 |
-
{{ [1, 2, 3, 4]|last }}
|
13 |
-
{# outputs 4 #}
|
14 |
-
|
15 |
-
{{ { a: 1, b: 2, c: 3, d: 4 }|last }}
|
16 |
-
{# outputs 4 #}
|
17 |
-
|
18 |
-
{{ '1234'|last }}
|
19 |
-
{# outputs 4 #}
|
20 |
-
|
21 |
-
.. note::
|
22 |
-
|
23 |
-
It also works with objects implementing the `Traversable`_ interface.
|
24 |
-
|
25 |
-
.. _`Traversable`: http://php.net/manual/en/class.traversable.php
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/filters/length.rst
DELETED
@@ -1,21 +0,0 @@
|
|
1 |
-
``length``
|
2 |
-
==========
|
3 |
-
|
4 |
-
.. versionadded:: 1.33
|
5 |
-
|
6 |
-
Support for the ``__toString()`` magic method has been added in Twig 1.33.
|
7 |
-
|
8 |
-
The ``length`` filter returns the number of items of a sequence or mapping, or
|
9 |
-
the length of a string.
|
10 |
-
|
11 |
-
For objects that implement the ``Countable`` interface, ``length`` will use the
|
12 |
-
return value of the ``count()`` method.
|
13 |
-
|
14 |
-
For objects that implement the ``__toString()`` magic method (and not ``Countable``),
|
15 |
-
it will return the length of the string provided by that method.
|
16 |
-
|
17 |
-
.. code-block:: jinja
|
18 |
-
|
19 |
-
{% if users|length > 10 %}
|
20 |
-
...
|
21 |
-
{% endif %}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/filters/lower.rst
DELETED
@@ -1,10 +0,0 @@
|
|
1 |
-
``lower``
|
2 |
-
=========
|
3 |
-
|
4 |
-
The ``lower`` filter converts a value to lowercase:
|
5 |
-
|
6 |
-
.. code-block:: jinja
|
7 |
-
|
8 |
-
{{ 'WELCOME'|lower }}
|
9 |
-
|
10 |
-
{# outputs 'welcome' #}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/filters/merge.rst
DELETED
@@ -1,48 +0,0 @@
|
|
1 |
-
``merge``
|
2 |
-
=========
|
3 |
-
|
4 |
-
The ``merge`` filter merges an array with another array:
|
5 |
-
|
6 |
-
.. code-block:: jinja
|
7 |
-
|
8 |
-
{% set values = [1, 2] %}
|
9 |
-
|
10 |
-
{% set values = values|merge(['apple', 'orange']) %}
|
11 |
-
|
12 |
-
{# values now contains [1, 2, 'apple', 'orange'] #}
|
13 |
-
|
14 |
-
New values are added at the end of the existing ones.
|
15 |
-
|
16 |
-
The ``merge`` filter also works on hashes:
|
17 |
-
|
18 |
-
.. code-block:: jinja
|
19 |
-
|
20 |
-
{% set items = { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'unknown' } %}
|
21 |
-
|
22 |
-
{% set items = items|merge({ 'peugeot': 'car', 'renault': 'car' }) %}
|
23 |
-
|
24 |
-
{# items now contains { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'car', 'renault': 'car' } #}
|
25 |
-
|
26 |
-
For hashes, the merging process occurs on the keys: if the key does not
|
27 |
-
already exist, it is added but if the key already exists, its value is
|
28 |
-
overridden.
|
29 |
-
|
30 |
-
.. tip::
|
31 |
-
|
32 |
-
If you want to ensure that some values are defined in an array (by given
|
33 |
-
default values), reverse the two elements in the call:
|
34 |
-
|
35 |
-
.. code-block:: jinja
|
36 |
-
|
37 |
-
{% set items = { 'apple': 'fruit', 'orange': 'fruit' } %}
|
38 |
-
|
39 |
-
{% set items = { 'apple': 'unknown' }|merge(items) %}
|
40 |
-
|
41 |
-
{# items now contains { 'apple': 'fruit', 'orange': 'fruit' } #}
|
42 |
-
|
43 |
-
.. note::
|
44 |
-
|
45 |
-
Internally, Twig uses the PHP `array_merge`_ function. It supports
|
46 |
-
Traversable objects by transforming those to arrays.
|
47 |
-
|
48 |
-
.. _`array_merge`: http://php.net/array_merge
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/filters/nl2br.rst
DELETED
@@ -1,22 +0,0 @@
|
|
1 |
-
``nl2br``
|
2 |
-
=========
|
3 |
-
|
4 |
-
.. versionadded:: 1.5
|
5 |
-
The ``nl2br`` filter was added in Twig 1.5.
|
6 |
-
|
7 |
-
The ``nl2br`` filter inserts HTML line breaks before all newlines in a string:
|
8 |
-
|
9 |
-
.. code-block:: jinja
|
10 |
-
|
11 |
-
{{ "I like Twig.\nYou will like it too."|nl2br }}
|
12 |
-
{# outputs
|
13 |
-
|
14 |
-
I like Twig.<br />
|
15 |
-
You will like it too.
|
16 |
-
|
17 |
-
#}
|
18 |
-
|
19 |
-
.. note::
|
20 |
-
|
21 |
-
The ``nl2br`` filter pre-escapes the input before applying the
|
22 |
-
transformation.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/filters/number_format.rst
DELETED
@@ -1,48 +0,0 @@
|
|
1 |
-
``number_format``
|
2 |
-
=================
|
3 |
-
|
4 |
-
.. versionadded:: 1.5
|
5 |
-
The ``number_format`` filter was added in Twig 1.5
|
6 |
-
|
7 |
-
The ``number_format`` filter formats numbers. It is a wrapper around PHP's
|
8 |
-
`number_format`_ function:
|
9 |
-
|
10 |
-
.. code-block:: jinja
|
11 |
-
|
12 |
-
{{ 200.35|number_format }}
|
13 |
-
|
14 |
-
You can control the number of decimal places, decimal point, and thousands
|
15 |
-
separator using the additional arguments:
|
16 |
-
|
17 |
-
.. code-block:: jinja
|
18 |
-
|
19 |
-
{{ 9800.333|number_format(2, '.', ',') }}
|
20 |
-
|
21 |
-
If no formatting options are provided then Twig will use the default formatting
|
22 |
-
options of:
|
23 |
-
|
24 |
-
* 0 decimal places.
|
25 |
-
* ``.`` as the decimal point.
|
26 |
-
* ``,`` as the thousands separator.
|
27 |
-
|
28 |
-
These defaults can be easily changed through the core extension:
|
29 |
-
|
30 |
-
.. code-block:: php
|
31 |
-
|
32 |
-
$twig = new Twig_Environment($loader);
|
33 |
-
$twig->getExtension('Twig_Extension_Core')->setNumberFormat(3, '.', ',');
|
34 |
-
|
35 |
-
// before Twig 1.26
|
36 |
-
$twig->getExtension('core')->setNumberFormat(3, '.', ',');
|
37 |
-
|
38 |
-
The defaults set for ``number_format`` can be over-ridden upon each call using the
|
39 |
-
additional parameters.
|
40 |
-
|
41 |
-
Arguments
|
42 |
-
---------
|
43 |
-
|
44 |
-
* ``decimal``: The number of decimal points to display
|
45 |
-
* ``decimal_point``: The character(s) to use for the decimal point
|
46 |
-
* ``thousand_sep``: The character(s) to use for the thousands separator
|
47 |
-
|
48 |
-
.. _`number_format`: http://php.net/number_format
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/filters/raw.rst
DELETED
@@ -1,36 +0,0 @@
|
|
1 |
-
``raw``
|
2 |
-
=======
|
3 |
-
|
4 |
-
The ``raw`` filter marks the value as being "safe", which means that in an
|
5 |
-
environment with automatic escaping enabled this variable will not be escaped
|
6 |
-
if ``raw`` is the last filter applied to it:
|
7 |
-
|
8 |
-
.. code-block:: jinja
|
9 |
-
|
10 |
-
{% autoescape %}
|
11 |
-
{{ var|raw }} {# var won't be escaped #}
|
12 |
-
{% endautoescape %}
|
13 |
-
|
14 |
-
.. note::
|
15 |
-
|
16 |
-
Be careful when using the ``raw`` filter inside expressions:
|
17 |
-
|
18 |
-
.. code-block:: jinja
|
19 |
-
|
20 |
-
{% autoescape %}
|
21 |
-
{% set hello = '<strong>Hello</strong>' %}
|
22 |
-
{% set hola = '<strong>Hola</strong>' %}
|
23 |
-
|
24 |
-
{{ false ? '<strong>Hola</strong>' : hello|raw }}
|
25 |
-
does not render the same as
|
26 |
-
{{ false ? hola : hello|raw }}
|
27 |
-
but renders the same as
|
28 |
-
{{ (false ? hola : hello)|raw }}
|
29 |
-
{% endautoescape %}
|
30 |
-
|
31 |
-
The first ternary statement is not escaped: ``hello`` is marked as being
|
32 |
-
safe and Twig does not escape static values (see
|
33 |
-
:doc:`escape<../tags/autoescape>`). In the second ternary statement, even
|
34 |
-
if ``hello`` is marked as safe, ``hola`` remains unsafe and so is the whole
|
35 |
-
expression. The third ternary statement is marked as safe and the result is
|
36 |
-
not escaped.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/filters/replace.rst
DELETED
@@ -1,19 +0,0 @@
|
|
1 |
-
``replace``
|
2 |
-
===========
|
3 |
-
|
4 |
-
The ``replace`` filter formats a given string by replacing the placeholders
|
5 |
-
(placeholders are free-form):
|
6 |
-
|
7 |
-
.. code-block:: jinja
|
8 |
-
|
9 |
-
{{ "I like %this% and %that%."|replace({'%this%': foo, '%that%': "bar"}) }}
|
10 |
-
|
11 |
-
{# outputs I like foo and bar
|
12 |
-
if the foo parameter equals to the foo string. #}
|
13 |
-
|
14 |
-
Arguments
|
15 |
-
---------
|
16 |
-
|
17 |
-
* ``from``: The placeholder values
|
18 |
-
|
19 |
-
.. seealso:: :doc:`format<format>`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/filters/reverse.rst
DELETED
@@ -1,47 +0,0 @@
|
|
1 |
-
``reverse``
|
2 |
-
===========
|
3 |
-
|
4 |
-
.. versionadded:: 1.6
|
5 |
-
Support for strings has been added in Twig 1.6.
|
6 |
-
|
7 |
-
The ``reverse`` filter reverses a sequence, a mapping, or a string:
|
8 |
-
|
9 |
-
.. code-block:: jinja
|
10 |
-
|
11 |
-
{% for user in users|reverse %}
|
12 |
-
...
|
13 |
-
{% endfor %}
|
14 |
-
|
15 |
-
{{ '1234'|reverse }}
|
16 |
-
|
17 |
-
{# outputs 4321 #}
|
18 |
-
|
19 |
-
.. tip::
|
20 |
-
|
21 |
-
For sequences and mappings, numeric keys are not preserved. To reverse
|
22 |
-
them as well, pass ``true`` as an argument to the ``reverse`` filter:
|
23 |
-
|
24 |
-
.. code-block:: jinja
|
25 |
-
|
26 |
-
{% for key, value in {1: "a", 2: "b", 3: "c"}|reverse %}
|
27 |
-
{{ key }}: {{ value }}
|
28 |
-
{%- endfor %}
|
29 |
-
|
30 |
-
{# output: 0: c 1: b 2: a #}
|
31 |
-
|
32 |
-
{% for key, value in {1: "a", 2: "b", 3: "c"}|reverse(true) %}
|
33 |
-
{{ key }}: {{ value }}
|
34 |
-
{%- endfor %}
|
35 |
-
|
36 |
-
{# output: 3: c 2: b 1: a #}
|
37 |
-
|
38 |
-
.. note::
|
39 |
-
|
40 |
-
It also works with objects implementing the `Traversable`_ interface.
|
41 |
-
|
42 |
-
Arguments
|
43 |
-
---------
|
44 |
-
|
45 |
-
* ``preserve_keys``: Preserve keys when reversing a mapping or a sequence.
|
46 |
-
|
47 |
-
.. _`Traversable`: http://php.net/Traversable
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/filters/round.rst
DELETED
@@ -1,37 +0,0 @@
|
|
1 |
-
``round``
|
2 |
-
=========
|
3 |
-
|
4 |
-
.. versionadded:: 1.15.0
|
5 |
-
The ``round`` filter was added in Twig 1.15.0.
|
6 |
-
|
7 |
-
The ``round`` filter rounds a number to a given precision:
|
8 |
-
|
9 |
-
.. code-block:: jinja
|
10 |
-
|
11 |
-
{{ 42.55|round }}
|
12 |
-
{# outputs 43 #}
|
13 |
-
|
14 |
-
{{ 42.55|round(1, 'floor') }}
|
15 |
-
{# outputs 42.5 #}
|
16 |
-
|
17 |
-
The ``round`` filter takes two optional arguments; the first one specifies the
|
18 |
-
precision (default is ``0``) and the second the rounding method (default is
|
19 |
-
``common``):
|
20 |
-
|
21 |
-
* ``common`` rounds either up or down (rounds the value up to precision decimal
|
22 |
-
places away from zero, when it is half way there -- making 1.5 into 2 and
|
23 |
-
-1.5 into -2);
|
24 |
-
|
25 |
-
* ``ceil`` always rounds up;
|
26 |
-
|
27 |
-
* ``floor`` always rounds down.
|
28 |
-
|
29 |
-
.. note::
|
30 |
-
|
31 |
-
The ``//`` operator is equivalent to ``|round(0, 'floor')``.
|
32 |
-
|
33 |
-
Arguments
|
34 |
-
---------
|
35 |
-
|
36 |
-
* ``precision``: The rounding precision
|
37 |
-
* ``method``: The rounding method
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/filters/slice.rst
DELETED
@@ -1,71 +0,0 @@
|
|
1 |
-
``slice``
|
2 |
-
===========
|
3 |
-
|
4 |
-
.. versionadded:: 1.6
|
5 |
-
The ``slice`` filter was added in Twig 1.6.
|
6 |
-
|
7 |
-
The ``slice`` filter extracts a slice of a sequence, a mapping, or a string:
|
8 |
-
|
9 |
-
.. code-block:: jinja
|
10 |
-
|
11 |
-
{% for i in [1, 2, 3, 4, 5]|slice(1, 2) %}
|
12 |
-
{# will iterate over 2 and 3 #}
|
13 |
-
{% endfor %}
|
14 |
-
|
15 |
-
{{ '12345'|slice(1, 2) }}
|
16 |
-
|
17 |
-
{# outputs 23 #}
|
18 |
-
|
19 |
-
You can use any valid expression for both the start and the length:
|
20 |
-
|
21 |
-
.. code-block:: jinja
|
22 |
-
|
23 |
-
{% for i in [1, 2, 3, 4, 5]|slice(start, length) %}
|
24 |
-
{# ... #}
|
25 |
-
{% endfor %}
|
26 |
-
|
27 |
-
As syntactic sugar, you can also use the ``[]`` notation:
|
28 |
-
|
29 |
-
.. code-block:: jinja
|
30 |
-
|
31 |
-
{% for i in [1, 2, 3, 4, 5][start:length] %}
|
32 |
-
{# ... #}
|
33 |
-
{% endfor %}
|
34 |
-
|
35 |
-
{{ '12345'[1:2] }} {# will display "23" #}
|
36 |
-
|
37 |
-
{# you can omit the first argument -- which is the same as 0 #}
|
38 |
-
{{ '12345'[:2] }} {# will display "12" #}
|
39 |
-
|
40 |
-
{# you can omit the last argument -- which will select everything till the end #}
|
41 |
-
{{ '12345'[2:] }} {# will display "345" #}
|
42 |
-
|
43 |
-
The ``slice`` filter works as the `array_slice`_ PHP function for arrays and
|
44 |
-
`mb_substr`_ for strings with a fallback to `substr`_.
|
45 |
-
|
46 |
-
If the start is non-negative, the sequence will start at that start in the
|
47 |
-
variable. If start is negative, the sequence will start that far from the end
|
48 |
-
of the variable.
|
49 |
-
|
50 |
-
If length is given and is positive, then the sequence will have up to that
|
51 |
-
many elements in it. If the variable is shorter than the length, then only the
|
52 |
-
available variable elements will be present. If length is given and is
|
53 |
-
negative then the sequence will stop that many elements from the end of the
|
54 |
-
variable. If it is omitted, then the sequence will have everything from offset
|
55 |
-
up until the end of the variable.
|
56 |
-
|
57 |
-
.. note::
|
58 |
-
|
59 |
-
It also works with objects implementing the `Traversable`_ interface.
|
60 |
-
|
61 |
-
Arguments
|
62 |
-
---------
|
63 |
-
|
64 |
-
* ``start``: The start of the slice
|
65 |
-
* ``length``: The size of the slice
|
66 |
-
* ``preserve_keys``: Whether to preserve key or not (when the input is an array)
|
67 |
-
|
68 |
-
.. _`Traversable`: http://php.net/manual/en/class.traversable.php
|
69 |
-
.. _`array_slice`: http://php.net/array_slice
|
70 |
-
.. _`mb_substr` : http://php.net/mb-substr
|
71 |
-
.. _`substr`: http://php.net/substr
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/filters/sort.rst
DELETED
@@ -1,18 +0,0 @@
|
|
1 |
-
``sort``
|
2 |
-
========
|
3 |
-
|
4 |
-
The ``sort`` filter sorts an array:
|
5 |
-
|
6 |
-
.. code-block:: jinja
|
7 |
-
|
8 |
-
{% for user in users|sort %}
|
9 |
-
...
|
10 |
-
{% endfor %}
|
11 |
-
|
12 |
-
.. note::
|
13 |
-
|
14 |
-
Internally, Twig uses the PHP `asort`_ function to maintain index
|
15 |
-
association. It supports Traversable objects by transforming
|
16 |
-
those to arrays.
|
17 |
-
|
18 |
-
.. _`asort`: http://php.net/asort
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/filters/split.rst
DELETED
@@ -1,53 +0,0 @@
|
|
1 |
-
``split``
|
2 |
-
=========
|
3 |
-
|
4 |
-
.. versionadded:: 1.10.3
|
5 |
-
The ``split`` filter was added in Twig 1.10.3.
|
6 |
-
|
7 |
-
The ``split`` filter splits a string by the given delimiter and returns a list
|
8 |
-
of strings:
|
9 |
-
|
10 |
-
.. code-block:: jinja
|
11 |
-
|
12 |
-
{% set foo = "one,two,three"|split(',') %}
|
13 |
-
{# foo contains ['one', 'two', 'three'] #}
|
14 |
-
|
15 |
-
You can also pass a ``limit`` argument:
|
16 |
-
|
17 |
-
* If ``limit`` is positive, the returned array will contain a maximum of
|
18 |
-
limit elements with the last element containing the rest of string;
|
19 |
-
|
20 |
-
* If ``limit`` is negative, all components except the last -limit are
|
21 |
-
returned;
|
22 |
-
|
23 |
-
* If ``limit`` is zero, then this is treated as 1.
|
24 |
-
|
25 |
-
.. code-block:: jinja
|
26 |
-
|
27 |
-
{% set foo = "one,two,three,four,five"|split(',', 3) %}
|
28 |
-
{# foo contains ['one', 'two', 'three,four,five'] #}
|
29 |
-
|
30 |
-
If the ``delimiter`` is an empty string, then value will be split by equal
|
31 |
-
chunks. Length is set by the ``limit`` argument (one character by default).
|
32 |
-
|
33 |
-
.. code-block:: jinja
|
34 |
-
|
35 |
-
{% set foo = "123"|split('') %}
|
36 |
-
{# foo contains ['1', '2', '3'] #}
|
37 |
-
|
38 |
-
{% set bar = "aabbcc"|split('', 2) %}
|
39 |
-
{# bar contains ['aa', 'bb', 'cc'] #}
|
40 |
-
|
41 |
-
.. note::
|
42 |
-
|
43 |
-
Internally, Twig uses the PHP `explode`_ or `str_split`_ (if delimiter is
|
44 |
-
empty) functions for string splitting.
|
45 |
-
|
46 |
-
Arguments
|
47 |
-
---------
|
48 |
-
|
49 |
-
* ``delimiter``: The delimiter
|
50 |
-
* ``limit``: The limit argument
|
51 |
-
|
52 |
-
.. _`explode`: http://php.net/explode
|
53 |
-
.. _`str_split`: http://php.net/str_split
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/filters/striptags.rst
DELETED
@@ -1,29 +0,0 @@
|
|
1 |
-
``striptags``
|
2 |
-
=============
|
3 |
-
|
4 |
-
The ``striptags`` filter strips SGML/XML tags and replace adjacent whitespace
|
5 |
-
by one space:
|
6 |
-
|
7 |
-
.. code-block:: jinja
|
8 |
-
|
9 |
-
{{ some_html|striptags }}
|
10 |
-
|
11 |
-
You can also provide tags which should not be stripped:
|
12 |
-
|
13 |
-
.. code-block:: jinja
|
14 |
-
|
15 |
-
{{ some_html|striptags('<br><p>') }}
|
16 |
-
|
17 |
-
In this example, the ``<br/>``, ``<br>``, ``<p>``, and ``</p>`` tags won't be
|
18 |
-
removed from the string.
|
19 |
-
|
20 |
-
.. note::
|
21 |
-
|
22 |
-
Internally, Twig uses the PHP `strip_tags`_ function.
|
23 |
-
|
24 |
-
Arguments
|
25 |
-
---------
|
26 |
-
|
27 |
-
* ``allowable_tags``: Tags which should not be stripped
|
28 |
-
|
29 |
-
.. _`strip_tags`: http://php.net/strip_tags
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/filters/title.rst
DELETED
@@ -1,11 +0,0 @@
|
|
1 |
-
``title``
|
2 |
-
=========
|
3 |
-
|
4 |
-
The ``title`` filter returns a titlecased version of the value. Words will
|
5 |
-
start with uppercase letters, all remaining characters are lowercase:
|
6 |
-
|
7 |
-
.. code-block:: jinja
|
8 |
-
|
9 |
-
{{ 'my first car'|title }}
|
10 |
-
|
11 |
-
{# outputs 'My First Car' #}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/filters/trim.rst
DELETED
@@ -1,45 +0,0 @@
|
|
1 |
-
``trim``
|
2 |
-
========
|
3 |
-
|
4 |
-
.. versionadded:: 1.32
|
5 |
-
The ``side`` argument was added in Twig 1.32.
|
6 |
-
|
7 |
-
.. versionadded:: 1.6.2
|
8 |
-
The ``trim`` filter was added in Twig 1.6.2.
|
9 |
-
|
10 |
-
The ``trim`` filter strips whitespace (or other characters) from the beginning
|
11 |
-
and end of a string:
|
12 |
-
|
13 |
-
.. code-block:: jinja
|
14 |
-
|
15 |
-
{{ ' I like Twig. '|trim }}
|
16 |
-
|
17 |
-
{# outputs 'I like Twig.' #}
|
18 |
-
|
19 |
-
{{ ' I like Twig.'|trim('.') }}
|
20 |
-
|
21 |
-
{# outputs ' I like Twig' #}
|
22 |
-
|
23 |
-
{{ ' I like Twig. '|trim(side='left') }}
|
24 |
-
|
25 |
-
{# outputs 'I like Twig. ' #}
|
26 |
-
|
27 |
-
{{ ' I like Twig. '|trim(' ', 'right') }}
|
28 |
-
|
29 |
-
{# outputs ' I like Twig.' #}
|
30 |
-
|
31 |
-
.. note::
|
32 |
-
|
33 |
-
Internally, Twig uses the PHP `trim`_, `ltrim`_, and `rtrim`_ functions.
|
34 |
-
|
35 |
-
Arguments
|
36 |
-
---------
|
37 |
-
|
38 |
-
* ``character_mask``: The characters to strip
|
39 |
-
|
40 |
-
* ``side``: The default is to strip from the left and the right (`both`) sides, but `left`
|
41 |
-
and `right` will strip from either the left side or right side only
|
42 |
-
|
43 |
-
.. _`trim`: http://php.net/trim
|
44 |
-
.. _`ltrim`: http://php.net/ltrim
|
45 |
-
.. _`rtrim`: http://php.net/rtrim
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/filters/upper.rst
DELETED
@@ -1,10 +0,0 @@
|
|
1 |
-
``upper``
|
2 |
-
=========
|
3 |
-
|
4 |
-
The ``upper`` filter converts a value to uppercase:
|
5 |
-
|
6 |
-
.. code-block:: jinja
|
7 |
-
|
8 |
-
{{ 'welcome'|upper }}
|
9 |
-
|
10 |
-
{# outputs 'WELCOME' #}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/filters/url_encode.rst
DELETED
@@ -1,34 +0,0 @@
|
|
1 |
-
``url_encode``
|
2 |
-
==============
|
3 |
-
|
4 |
-
.. versionadded:: 1.12.3
|
5 |
-
Support for encoding an array as query string was added in Twig 1.12.3.
|
6 |
-
|
7 |
-
.. versionadded:: 1.16.0
|
8 |
-
The ``raw`` argument was removed in Twig 1.16.0. Twig now always encodes
|
9 |
-
according to RFC 3986.
|
10 |
-
|
11 |
-
The ``url_encode`` filter percent encodes a given string as URL segment
|
12 |
-
or an array as query string:
|
13 |
-
|
14 |
-
.. code-block:: jinja
|
15 |
-
|
16 |
-
{{ "path-seg*ment"|url_encode }}
|
17 |
-
{# outputs "path-seg%2Ament" #}
|
18 |
-
|
19 |
-
{{ "string with spaces"|url_encode }}
|
20 |
-
{# outputs "string%20with%20spaces" #}
|
21 |
-
|
22 |
-
{{ {'param': 'value', 'foo': 'bar'}|url_encode }}
|
23 |
-
{# outputs "param=value&foo=bar" #}
|
24 |
-
|
25 |
-
.. note::
|
26 |
-
|
27 |
-
Internally, Twig uses the PHP `urlencode`_ (or `rawurlencode`_ if you pass
|
28 |
-
``true`` as the first parameter) or the `http_build_query`_ function. Note
|
29 |
-
that as of Twig 1.16.0, ``urlencode`` **always** uses ``rawurlencode`` (the
|
30 |
-
``raw`` argument was removed.)
|
31 |
-
|
32 |
-
.. _`urlencode`: http://php.net/urlencode
|
33 |
-
.. _`rawurlencode`: http://php.net/rawurlencode
|
34 |
-
.. _`http_build_query`: http://php.net/http_build_query
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/functions/attribute.rst
DELETED
@@ -1,26 +0,0 @@
|
|
1 |
-
``attribute``
|
2 |
-
=============
|
3 |
-
|
4 |
-
.. versionadded:: 1.2
|
5 |
-
The ``attribute`` function was added in Twig 1.2.
|
6 |
-
|
7 |
-
The ``attribute`` function can be used to access a "dynamic" attribute of a
|
8 |
-
variable:
|
9 |
-
|
10 |
-
.. code-block:: jinja
|
11 |
-
|
12 |
-
{{ attribute(object, method) }}
|
13 |
-
{{ attribute(object, method, arguments) }}
|
14 |
-
{{ attribute(array, item) }}
|
15 |
-
|
16 |
-
In addition, the ``defined`` test can check for the existence of a dynamic
|
17 |
-
attribute:
|
18 |
-
|
19 |
-
.. code-block:: jinja
|
20 |
-
|
21 |
-
{{ attribute(object, method) is defined ? 'Method exists' : 'Method does not exist' }}
|
22 |
-
|
23 |
-
.. note::
|
24 |
-
|
25 |
-
The resolution algorithm is the same as the one used for the ``.``
|
26 |
-
notation, except that the item can be any valid expression.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/functions/block.rst
DELETED
@@ -1,41 +0,0 @@
|
|
1 |
-
``block``
|
2 |
-
=========
|
3 |
-
|
4 |
-
.. versionadded: 1.28
|
5 |
-
Using ``block`` with the ``defined`` test was added in Twig 1.28.
|
6 |
-
|
7 |
-
.. versionadded: 1.28
|
8 |
-
Support for the template argument was added in Twig 1.28.
|
9 |
-
|
10 |
-
When a template uses inheritance and if you want to print a block multiple
|
11 |
-
times, use the ``block`` function:
|
12 |
-
|
13 |
-
.. code-block:: jinja
|
14 |
-
|
15 |
-
<title>{% block title %}{% endblock %}</title>
|
16 |
-
|
17 |
-
<h1>{{ block('title') }}</h1>
|
18 |
-
|
19 |
-
{% block body %}{% endblock %}
|
20 |
-
|
21 |
-
The ``block`` function can also be used to display one block of another
|
22 |
-
template:
|
23 |
-
|
24 |
-
.. code-block:: jinja
|
25 |
-
|
26 |
-
{{ block("title", "common_blocks.twig") }}
|
27 |
-
|
28 |
-
Use the ``defined`` test to check if a block exists in the context of the
|
29 |
-
current template:
|
30 |
-
|
31 |
-
.. code-block:: jinja
|
32 |
-
|
33 |
-
{% if block("footer") is defined %}
|
34 |
-
...
|
35 |
-
{% endif %}
|
36 |
-
|
37 |
-
{% if block("footer", "common_blocks.twig") is defined %}
|
38 |
-
...
|
39 |
-
{% endif %}
|
40 |
-
|
41 |
-
.. seealso:: :doc:`extends<../tags/extends>`, :doc:`parent<../functions/parent>`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/functions/constant.rst
DELETED
@@ -1,29 +0,0 @@
|
|
1 |
-
``constant``
|
2 |
-
============
|
3 |
-
|
4 |
-
.. versionadded: 1.12.1
|
5 |
-
constant now accepts object instances as the second argument.
|
6 |
-
|
7 |
-
.. versionadded: 1.28
|
8 |
-
Using ``constant`` with the ``defined`` test was added in Twig 1.28.
|
9 |
-
|
10 |
-
``constant`` returns the constant value for a given string:
|
11 |
-
|
12 |
-
.. code-block:: jinja
|
13 |
-
|
14 |
-
{{ some_date|date(constant('DATE_W3C')) }}
|
15 |
-
{{ constant('Namespace\\Classname::CONSTANT_NAME') }}
|
16 |
-
|
17 |
-
As of 1.12.1 you can read constants from object instances as well:
|
18 |
-
|
19 |
-
.. code-block:: jinja
|
20 |
-
|
21 |
-
{{ constant('RSS', date) }}
|
22 |
-
|
23 |
-
Use the ``defined`` test to check if a constant is defined:
|
24 |
-
|
25 |
-
.. code-block:: jinja
|
26 |
-
|
27 |
-
{% if constant('SOME_CONST') is defined %}
|
28 |
-
...
|
29 |
-
{% endif %}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/functions/cycle.rst
DELETED
@@ -1,28 +0,0 @@
|
|
1 |
-
``cycle``
|
2 |
-
=========
|
3 |
-
|
4 |
-
The ``cycle`` function cycles on an array of values:
|
5 |
-
|
6 |
-
.. code-block:: jinja
|
7 |
-
|
8 |
-
{% set start_year = date() | date('Y') %}
|
9 |
-
{% set end_year = start_year + 5 %}
|
10 |
-
|
11 |
-
{% for year in start_year..end_year %}
|
12 |
-
{{ cycle(['odd', 'even'], loop.index0) }}
|
13 |
-
{% endfor %}
|
14 |
-
|
15 |
-
The array can contain any number of values:
|
16 |
-
|
17 |
-
.. code-block:: jinja
|
18 |
-
|
19 |
-
{% set fruits = ['apple', 'orange', 'citrus'] %}
|
20 |
-
|
21 |
-
{% for i in 0..10 %}
|
22 |
-
{{ cycle(fruits, i) }}
|
23 |
-
{% endfor %}
|
24 |
-
|
25 |
-
Arguments
|
26 |
-
---------
|
27 |
-
|
28 |
-
* ``position``: The cycle position
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/functions/date.rst
DELETED
@@ -1,55 +0,0 @@
|
|
1 |
-
``date``
|
2 |
-
========
|
3 |
-
|
4 |
-
.. versionadded:: 1.6
|
5 |
-
The date function has been added in Twig 1.6.
|
6 |
-
|
7 |
-
.. versionadded:: 1.6.1
|
8 |
-
The default timezone support has been added in Twig 1.6.1.
|
9 |
-
|
10 |
-
Converts an argument to a date to allow date comparison:
|
11 |
-
|
12 |
-
.. code-block:: jinja
|
13 |
-
|
14 |
-
{% if date(user.created_at) < date('-2days') %}
|
15 |
-
{# do something #}
|
16 |
-
{% endif %}
|
17 |
-
|
18 |
-
The argument must be in one of PHP’s supported `date and time formats`_.
|
19 |
-
|
20 |
-
You can pass a timezone as the second argument:
|
21 |
-
|
22 |
-
.. code-block:: jinja
|
23 |
-
|
24 |
-
{% if date(user.created_at) < date('-2days', 'Europe/Paris') %}
|
25 |
-
{# do something #}
|
26 |
-
{% endif %}
|
27 |
-
|
28 |
-
If no argument is passed, the function returns the current date:
|
29 |
-
|
30 |
-
.. code-block:: jinja
|
31 |
-
|
32 |
-
{% if date(user.created_at) < date() %}
|
33 |
-
{# always! #}
|
34 |
-
{% endif %}
|
35 |
-
|
36 |
-
.. note::
|
37 |
-
|
38 |
-
You can set the default timezone globally by calling ``setTimezone()`` on
|
39 |
-
the ``core`` extension instance:
|
40 |
-
|
41 |
-
.. code-block:: php
|
42 |
-
|
43 |
-
$twig = new Twig_Environment($loader);
|
44 |
-
$twig->getExtension('Twig_Extension_Core')->setTimezone('Europe/Paris');
|
45 |
-
|
46 |
-
// before Twig 1.26
|
47 |
-
$twig->getExtension('core')->setTimezone('Europe/Paris');
|
48 |
-
|
49 |
-
Arguments
|
50 |
-
---------
|
51 |
-
|
52 |
-
* ``date``: The date
|
53 |
-
* ``timezone``: The timezone
|
54 |
-
|
55 |
-
.. _`date and time formats`: http://php.net/manual/en/datetime.formats.php
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/functions/dump.rst
DELETED
@@ -1,69 +0,0 @@
|
|
1 |
-
``dump``
|
2 |
-
========
|
3 |
-
|
4 |
-
.. versionadded:: 1.5
|
5 |
-
The ``dump`` function was added in Twig 1.5.
|
6 |
-
|
7 |
-
The ``dump`` function dumps information about a template variable. This is
|
8 |
-
mostly useful to debug a template that does not behave as expected by
|
9 |
-
introspecting its variables:
|
10 |
-
|
11 |
-
.. code-block:: jinja
|
12 |
-
|
13 |
-
{{ dump(user) }}
|
14 |
-
|
15 |
-
.. note::
|
16 |
-
|
17 |
-
The ``dump`` function is not available by default. You must add the
|
18 |
-
``Twig_Extension_Debug`` extension explicitly when creating your Twig
|
19 |
-
environment::
|
20 |
-
|
21 |
-
$twig = new Twig_Environment($loader, array(
|
22 |
-
'debug' => true,
|
23 |
-
// ...
|
24 |
-
));
|
25 |
-
$twig->addExtension(new Twig_Extension_Debug());
|
26 |
-
|
27 |
-
Even when enabled, the ``dump`` function won't display anything if the
|
28 |
-
``debug`` option on the environment is not enabled (to avoid leaking debug
|
29 |
-
information on a production server).
|
30 |
-
|
31 |
-
In an HTML context, wrap the output with a ``pre`` tag to make it easier to
|
32 |
-
read:
|
33 |
-
|
34 |
-
.. code-block:: jinja
|
35 |
-
|
36 |
-
<pre>
|
37 |
-
{{ dump(user) }}
|
38 |
-
</pre>
|
39 |
-
|
40 |
-
.. tip::
|
41 |
-
|
42 |
-
Using a ``pre`` tag is not needed when `XDebug`_ is enabled and
|
43 |
-
``html_errors`` is ``on``; as a bonus, the output is also nicer with
|
44 |
-
XDebug enabled.
|
45 |
-
|
46 |
-
You can debug several variables by passing them as additional arguments:
|
47 |
-
|
48 |
-
.. code-block:: jinja
|
49 |
-
|
50 |
-
{{ dump(user, categories) }}
|
51 |
-
|
52 |
-
If you don't pass any value, all variables from the current context are
|
53 |
-
dumped:
|
54 |
-
|
55 |
-
.. code-block:: jinja
|
56 |
-
|
57 |
-
{{ dump() }}
|
58 |
-
|
59 |
-
.. note::
|
60 |
-
|
61 |
-
Internally, Twig uses the PHP `var_dump`_ function.
|
62 |
-
|
63 |
-
Arguments
|
64 |
-
---------
|
65 |
-
|
66 |
-
* ``context``: The context to dump
|
67 |
-
|
68 |
-
.. _`XDebug`: http://xdebug.org/docs/display
|
69 |
-
.. _`var_dump`: http://php.net/var_dump
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/functions/include.rst
DELETED
@@ -1,84 +0,0 @@
|
|
1 |
-
``include``
|
2 |
-
===========
|
3 |
-
|
4 |
-
.. versionadded:: 1.12
|
5 |
-
The ``include`` function was added in Twig 1.12.
|
6 |
-
|
7 |
-
The ``include`` function returns the rendered content of a template:
|
8 |
-
|
9 |
-
.. code-block:: jinja
|
10 |
-
|
11 |
-
{{ include('template.html') }}
|
12 |
-
{{ include(some_var) }}
|
13 |
-
|
14 |
-
Included templates have access to the variables of the active context.
|
15 |
-
|
16 |
-
If you are using the filesystem loader, the templates are looked for in the
|
17 |
-
paths defined by it.
|
18 |
-
|
19 |
-
The context is passed by default to the template but you can also pass
|
20 |
-
additional variables:
|
21 |
-
|
22 |
-
.. code-block:: jinja
|
23 |
-
|
24 |
-
{# template.html will have access to the variables from the current context and the additional ones provided #}
|
25 |
-
{{ include('template.html', {foo: 'bar'}) }}
|
26 |
-
|
27 |
-
You can disable access to the context by setting ``with_context`` to
|
28 |
-
``false``:
|
29 |
-
|
30 |
-
.. code-block:: jinja
|
31 |
-
|
32 |
-
{# only the foo variable will be accessible #}
|
33 |
-
{{ include('template.html', {foo: 'bar'}, with_context = false) }}
|
34 |
-
|
35 |
-
.. code-block:: jinja
|
36 |
-
|
37 |
-
{# no variables will be accessible #}
|
38 |
-
{{ include('template.html', with_context = false) }}
|
39 |
-
|
40 |
-
And if the expression evaluates to a ``Twig_Template`` or a
|
41 |
-
``Twig_TemplateWrapper`` instance, Twig will use it directly::
|
42 |
-
|
43 |
-
// {{ include(template) }}
|
44 |
-
|
45 |
-
// deprecated as of Twig 1.28
|
46 |
-
$template = $twig->loadTemplate('some_template.twig');
|
47 |
-
|
48 |
-
// as of Twig 1.28
|
49 |
-
$template = $twig->load('some_template.twig');
|
50 |
-
|
51 |
-
$twig->display('template.twig', array('template' => $template));
|
52 |
-
|
53 |
-
When you set the ``ignore_missing`` flag, Twig will return an empty string if
|
54 |
-
the template does not exist:
|
55 |
-
|
56 |
-
.. code-block:: jinja
|
57 |
-
|
58 |
-
{{ include('sidebar.html', ignore_missing = true) }}
|
59 |
-
|
60 |
-
You can also provide a list of templates that are checked for existence before
|
61 |
-
inclusion. The first template that exists will be rendered:
|
62 |
-
|
63 |
-
.. code-block:: jinja
|
64 |
-
|
65 |
-
{{ include(['page_detailed.html', 'page.html']) }}
|
66 |
-
|
67 |
-
If ``ignore_missing`` is set, it will fall back to rendering nothing if none
|
68 |
-
of the templates exist, otherwise it will throw an exception.
|
69 |
-
|
70 |
-
When including a template created by an end user, you should consider
|
71 |
-
sandboxing it:
|
72 |
-
|
73 |
-
.. code-block:: jinja
|
74 |
-
|
75 |
-
{{ include('page.html', sandboxed = true) }}
|
76 |
-
|
77 |
-
Arguments
|
78 |
-
---------
|
79 |
-
|
80 |
-
* ``template``: The template to render
|
81 |
-
* ``variables``: The variables to pass to the template
|
82 |
-
* ``with_context``: Whether to pass the current context variables or not
|
83 |
-
* ``ignore_missing``: Whether to ignore missing templates or not
|
84 |
-
* ``sandboxed``: Whether to sandbox the template or not
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/functions/index.rst
DELETED
@@ -1,20 +0,0 @@
|
|
1 |
-
Functions
|
2 |
-
=========
|
3 |
-
|
4 |
-
.. toctree::
|
5 |
-
:maxdepth: 1
|
6 |
-
|
7 |
-
attribute
|
8 |
-
block
|
9 |
-
constant
|
10 |
-
cycle
|
11 |
-
date
|
12 |
-
dump
|
13 |
-
include
|
14 |
-
max
|
15 |
-
min
|
16 |
-
parent
|
17 |
-
random
|
18 |
-
range
|
19 |
-
source
|
20 |
-
template_from_string
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/functions/max.rst
DELETED
@@ -1,20 +0,0 @@
|
|
1 |
-
``max``
|
2 |
-
=======
|
3 |
-
|
4 |
-
.. versionadded:: 1.15
|
5 |
-
The ``max`` function was added in Twig 1.15.
|
6 |
-
|
7 |
-
``max`` returns the biggest value of a sequence or a set of values:
|
8 |
-
|
9 |
-
.. code-block:: jinja
|
10 |
-
|
11 |
-
{{ max(1, 3, 2) }}
|
12 |
-
{{ max([1, 3, 2]) }}
|
13 |
-
|
14 |
-
When called with a mapping, max ignores keys and only compares values:
|
15 |
-
|
16 |
-
.. code-block:: jinja
|
17 |
-
|
18 |
-
{{ max({2: "e", 1: "a", 3: "b", 5: "d", 4: "c"}) }}
|
19 |
-
{# returns "e" #}
|
20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/functions/min.rst
DELETED
@@ -1,20 +0,0 @@
|
|
1 |
-
``min``
|
2 |
-
=======
|
3 |
-
|
4 |
-
.. versionadded:: 1.15
|
5 |
-
The ``min`` function was added in Twig 1.15.
|
6 |
-
|
7 |
-
``min`` returns the lowest value of a sequence or a set of values:
|
8 |
-
|
9 |
-
.. code-block:: jinja
|
10 |
-
|
11 |
-
{{ min(1, 3, 2) }}
|
12 |
-
{{ min([1, 3, 2]) }}
|
13 |
-
|
14 |
-
When called with a mapping, min ignores keys and only compares values:
|
15 |
-
|
16 |
-
.. code-block:: jinja
|
17 |
-
|
18 |
-
{{ min({2: "e", 3: "a", 1: "b", 5: "d", 4: "c"}) }}
|
19 |
-
{# returns "a" #}
|
20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/functions/parent.rst
DELETED
@@ -1,20 +0,0 @@
|
|
1 |
-
``parent``
|
2 |
-
==========
|
3 |
-
|
4 |
-
When a template uses inheritance, it's possible to render the contents of the
|
5 |
-
parent block when overriding a block by using the ``parent`` function:
|
6 |
-
|
7 |
-
.. code-block:: jinja
|
8 |
-
|
9 |
-
{% extends "base.html" %}
|
10 |
-
|
11 |
-
{% block sidebar %}
|
12 |
-
<h3>Table Of Contents</h3>
|
13 |
-
...
|
14 |
-
{{ parent() }}
|
15 |
-
{% endblock %}
|
16 |
-
|
17 |
-
The ``parent()`` call will return the content of the ``sidebar`` block as
|
18 |
-
defined in the ``base.html`` template.
|
19 |
-
|
20 |
-
.. seealso:: :doc:`extends<../tags/extends>`, :doc:`block<../functions/block>`, :doc:`block<../tags/block>`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/functions/random.rst
DELETED
@@ -1,29 +0,0 @@
|
|
1 |
-
``random``
|
2 |
-
==========
|
3 |
-
|
4 |
-
.. versionadded:: 1.5
|
5 |
-
The ``random`` function was added in Twig 1.5.
|
6 |
-
|
7 |
-
.. versionadded:: 1.6
|
8 |
-
String and integer handling was added in Twig 1.6.
|
9 |
-
|
10 |
-
The ``random`` function returns a random value depending on the supplied
|
11 |
-
parameter type:
|
12 |
-
|
13 |
-
* a random item from a sequence;
|
14 |
-
* a random character from a string;
|
15 |
-
* a random integer between 0 and the integer parameter (inclusive).
|
16 |
-
|
17 |
-
.. code-block:: jinja
|
18 |
-
|
19 |
-
{{ random(['apple', 'orange', 'citrus']) }} {# example output: orange #}
|
20 |
-
{{ random('ABC') }} {# example output: C #}
|
21 |
-
{{ random() }} {# example output: 15386094 (works as the native PHP mt_rand function) #}
|
22 |
-
{{ random(5) }} {# example output: 3 #}
|
23 |
-
|
24 |
-
Arguments
|
25 |
-
---------
|
26 |
-
|
27 |
-
* ``values``: The values
|
28 |
-
|
29 |
-
.. _`mt_rand`: http://php.net/mt_rand
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/functions/range.rst
DELETED
@@ -1,58 +0,0 @@
|
|
1 |
-
``range``
|
2 |
-
=========
|
3 |
-
|
4 |
-
Returns a list containing an arithmetic progression of integers:
|
5 |
-
|
6 |
-
.. code-block:: jinja
|
7 |
-
|
8 |
-
{% for i in range(0, 3) %}
|
9 |
-
{{ i }},
|
10 |
-
{% endfor %}
|
11 |
-
|
12 |
-
{# outputs 0, 1, 2, 3, #}
|
13 |
-
|
14 |
-
When step is given (as the third parameter), it specifies the increment (or
|
15 |
-
decrement for negative values):
|
16 |
-
|
17 |
-
.. code-block:: jinja
|
18 |
-
|
19 |
-
{% for i in range(0, 6, 2) %}
|
20 |
-
{{ i }},
|
21 |
-
{% endfor %}
|
22 |
-
|
23 |
-
{# outputs 0, 2, 4, 6, #}
|
24 |
-
|
25 |
-
.. note::
|
26 |
-
|
27 |
-
Note that if the start is greater than the end, ``range`` assumes a step of
|
28 |
-
``-1``:
|
29 |
-
|
30 |
-
.. code-block:: jinja
|
31 |
-
|
32 |
-
{% for i in range(3, 0) %}
|
33 |
-
{{ i }},
|
34 |
-
{% endfor %}
|
35 |
-
|
36 |
-
{# outputs 3, 2, 1, 0, #}
|
37 |
-
|
38 |
-
The Twig built-in ``..`` operator is just syntactic sugar for the ``range``
|
39 |
-
function (with a step of ``1``, or ``-1`` if the start is greater than the end):
|
40 |
-
|
41 |
-
.. code-block:: jinja
|
42 |
-
|
43 |
-
{% for i in 0..3 %}
|
44 |
-
{{ i }},
|
45 |
-
{% endfor %}
|
46 |
-
|
47 |
-
.. tip::
|
48 |
-
|
49 |
-
The ``range`` function works as the native PHP `range`_ function.
|
50 |
-
|
51 |
-
Arguments
|
52 |
-
---------
|
53 |
-
|
54 |
-
* ``low``: The first value of the sequence.
|
55 |
-
* ``high``: The highest possible value of the sequence.
|
56 |
-
* ``step``: The increment between elements of the sequence.
|
57 |
-
|
58 |
-
.. _`range`: http://php.net/range
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/functions/source.rst
DELETED
@@ -1,32 +0,0 @@
|
|
1 |
-
``source``
|
2 |
-
==========
|
3 |
-
|
4 |
-
.. versionadded:: 1.15
|
5 |
-
The ``source`` function was added in Twig 1.15.
|
6 |
-
|
7 |
-
.. versionadded:: 1.18.3
|
8 |
-
The ``ignore_missing`` flag was added in Twig 1.18.3.
|
9 |
-
|
10 |
-
The ``source`` function returns the content of a template without rendering it:
|
11 |
-
|
12 |
-
.. code-block:: jinja
|
13 |
-
|
14 |
-
{{ source('template.html') }}
|
15 |
-
{{ source(some_var) }}
|
16 |
-
|
17 |
-
When you set the ``ignore_missing`` flag, Twig will return an empty string if
|
18 |
-
the template does not exist:
|
19 |
-
|
20 |
-
.. code-block:: jinja
|
21 |
-
|
22 |
-
{{ source('template.html', ignore_missing = true) }}
|
23 |
-
|
24 |
-
The function uses the same template loaders as the ones used to include
|
25 |
-
templates. So, if you are using the filesystem loader, the templates are looked
|
26 |
-
for in the paths defined by it.
|
27 |
-
|
28 |
-
Arguments
|
29 |
-
---------
|
30 |
-
|
31 |
-
* ``name``: The name of the template to read
|
32 |
-
* ``ignore_missing``: Whether to ignore missing templates or not
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/functions/template_from_string.rst
DELETED
@@ -1,32 +0,0 @@
|
|
1 |
-
``template_from_string``
|
2 |
-
========================
|
3 |
-
|
4 |
-
.. versionadded:: 1.11
|
5 |
-
The ``template_from_string`` function was added in Twig 1.11.
|
6 |
-
|
7 |
-
The ``template_from_string`` function loads a template from a string:
|
8 |
-
|
9 |
-
.. code-block:: jinja
|
10 |
-
|
11 |
-
{{ include(template_from_string("Hello {{ name }}")) }}
|
12 |
-
{{ include(template_from_string(page.template)) }}
|
13 |
-
|
14 |
-
.. note::
|
15 |
-
|
16 |
-
The ``template_from_string`` function is not available by default. You
|
17 |
-
must add the ``Twig_Extension_StringLoader`` extension explicitly when
|
18 |
-
creating your Twig environment::
|
19 |
-
|
20 |
-
$twig = new Twig_Environment(...);
|
21 |
-
$twig->addExtension(new Twig_Extension_StringLoader());
|
22 |
-
|
23 |
-
.. note::
|
24 |
-
|
25 |
-
Even if you will probably always use the ``template_from_string`` function
|
26 |
-
with the ``include`` function, you can use it with any tag or function that
|
27 |
-
takes a template as an argument (like the ``embed`` or ``extends`` tags).
|
28 |
-
|
29 |
-
Arguments
|
30 |
-
---------
|
31 |
-
|
32 |
-
* ``template``: The template
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/index.rst
DELETED
@@ -1,19 +0,0 @@
|
|
1 |
-
Twig
|
2 |
-
====
|
3 |
-
|
4 |
-
.. toctree::
|
5 |
-
:maxdepth: 2
|
6 |
-
|
7 |
-
intro
|
8 |
-
installation
|
9 |
-
templates
|
10 |
-
api
|
11 |
-
advanced
|
12 |
-
internals
|
13 |
-
deprecated
|
14 |
-
recipes
|
15 |
-
coding_standards
|
16 |
-
tags/index
|
17 |
-
filters/index
|
18 |
-
functions/index
|
19 |
-
tests/index
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/installation.rst
DELETED
@@ -1,116 +0,0 @@
|
|
1 |
-
Installation
|
2 |
-
============
|
3 |
-
|
4 |
-
You have multiple ways to install Twig.
|
5 |
-
|
6 |
-
Installing the Twig PHP package
|
7 |
-
-------------------------------
|
8 |
-
|
9 |
-
Installing via Composer (recommended)
|
10 |
-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
11 |
-
|
12 |
-
Install `Composer`_ and run the following command to get the latest version:
|
13 |
-
|
14 |
-
.. code-block:: bash
|
15 |
-
|
16 |
-
composer require twig/twig:~1.0
|
17 |
-
|
18 |
-
Installing from the tarball release
|
19 |
-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
20 |
-
|
21 |
-
1. Download the most recent tarball from the `download page`_
|
22 |
-
2. Verify the integrity of the tarball http://fabien.potencier.org/article/73/signing-project-releases
|
23 |
-
3. Unpack the tarball
|
24 |
-
4. Move the files somewhere in your project
|
25 |
-
|
26 |
-
Installing the development version
|
27 |
-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
28 |
-
|
29 |
-
.. code-block:: bash
|
30 |
-
|
31 |
-
git clone git://github.com/twigphp/Twig.git
|
32 |
-
|
33 |
-
Installing the PEAR package
|
34 |
-
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
35 |
-
|
36 |
-
.. note::
|
37 |
-
|
38 |
-
Using PEAR for installing Twig is deprecated and Twig 1.15.1 was the last
|
39 |
-
version published on the PEAR channel; use Composer instead.
|
40 |
-
|
41 |
-
.. code-block:: bash
|
42 |
-
|
43 |
-
pear channel-discover pear.twig-project.org
|
44 |
-
pear install twig/Twig
|
45 |
-
|
46 |
-
Installing the C extension
|
47 |
-
--------------------------
|
48 |
-
|
49 |
-
.. versionadded:: 1.4
|
50 |
-
The C extension was added in Twig 1.4.
|
51 |
-
|
52 |
-
.. note::
|
53 |
-
|
54 |
-
The C extension is **optional** but it brings some nice performance
|
55 |
-
improvements. Note that the extension is not a replacement for the PHP
|
56 |
-
code; it only implements a small part of the PHP code to improve the
|
57 |
-
performance at runtime; you must still install the regular PHP code.
|
58 |
-
|
59 |
-
Twig comes with a C extension that enhances the performance of the Twig
|
60 |
-
runtime engine; install it like any other PHP extensions:
|
61 |
-
|
62 |
-
.. code-block:: bash
|
63 |
-
|
64 |
-
cd ext/twig
|
65 |
-
phpize
|
66 |
-
./configure
|
67 |
-
make
|
68 |
-
make install
|
69 |
-
|
70 |
-
.. note::
|
71 |
-
|
72 |
-
You can also install the C extension via PEAR (note that this method is
|
73 |
-
deprecated and newer versions of Twig are not available on the PEAR
|
74 |
-
channel):
|
75 |
-
|
76 |
-
.. code-block:: bash
|
77 |
-
|
78 |
-
pear channel-discover pear.twig-project.org
|
79 |
-
pear install twig/CTwig
|
80 |
-
|
81 |
-
For Windows:
|
82 |
-
|
83 |
-
1. Setup the build environment following the `PHP documentation`_
|
84 |
-
2. Put Twig's C extension source code into ``C:\php-sdk\phpdev\vcXX\x86\php-source-directory\ext\twig``
|
85 |
-
3. Use the ``configure --disable-all --enable-cli --enable-twig=shared`` command instead of step 14
|
86 |
-
4. ``nmake``
|
87 |
-
5. Copy the ``C:\php-sdk\phpdev\vcXX\x86\php-source-directory\Release_TS\php_twig.dll`` file to your PHP setup.
|
88 |
-
|
89 |
-
.. tip::
|
90 |
-
|
91 |
-
For Windows ZendServer, ZTS is not enabled as mentioned in `Zend Server
|
92 |
-
FAQ`_.
|
93 |
-
|
94 |
-
You have to use ``configure --disable-all --disable-zts --enable-cli
|
95 |
-
--enable-twig=shared`` to be able to build the twig C extension for
|
96 |
-
ZendServer.
|
97 |
-
|
98 |
-
The built DLL will be available in
|
99 |
-
``C:\\php-sdk\\phpdev\\vcXX\\x86\\php-source-directory\\Release``
|
100 |
-
|
101 |
-
Finally, enable the extension in your ``php.ini`` configuration file:
|
102 |
-
|
103 |
-
.. code-block:: ini
|
104 |
-
|
105 |
-
extension=twig.so #For Unix systems
|
106 |
-
extension=php_twig.dll #For Windows systems
|
107 |
-
|
108 |
-
And from now on, Twig will automatically compile your templates to take
|
109 |
-
advantage of the C extension. Note that this extension does not replace the
|
110 |
-
PHP code but only provides an optimized version of the
|
111 |
-
``Twig_Template::getAttribute()`` method.
|
112 |
-
|
113 |
-
.. _`download page`: https://github.com/twigphp/Twig/tags
|
114 |
-
.. _`Composer`: https://getcomposer.org/download/
|
115 |
-
.. _`PHP documentation`: https://wiki.php.net/internals/windows/stepbystepbuild
|
116 |
-
.. _`Zend Server FAQ`: http://www.zend.com/en/products/server/faq#faqD6
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/internals.rst
DELETED
@@ -1,142 +0,0 @@
|
|
1 |
-
Twig Internals
|
2 |
-
==============
|
3 |
-
|
4 |
-
Twig is very extensible and you can easily hack it. Keep in mind that you
|
5 |
-
should probably try to create an extension before hacking the core, as most
|
6 |
-
features and enhancements can be handled with extensions. This chapter is also
|
7 |
-
useful for people who want to understand how Twig works under the hood.
|
8 |
-
|
9 |
-
How does Twig work?
|
10 |
-
-------------------
|
11 |
-
|
12 |
-
The rendering of a Twig template can be summarized into four key steps:
|
13 |
-
|
14 |
-
* **Load** the template: If the template is already compiled, load it and go
|
15 |
-
to the *evaluation* step, otherwise:
|
16 |
-
|
17 |
-
* First, the **lexer** tokenizes the template source code into small pieces
|
18 |
-
for easier processing;
|
19 |
-
* Then, the **parser** converts the token stream into a meaningful tree
|
20 |
-
of nodes (the Abstract Syntax Tree);
|
21 |
-
* Eventually, the *compiler* transforms the AST into PHP code.
|
22 |
-
|
23 |
-
* **Evaluate** the template: It basically means calling the ``display()``
|
24 |
-
method of the compiled template and passing it the context.
|
25 |
-
|
26 |
-
The Lexer
|
27 |
-
---------
|
28 |
-
|
29 |
-
The lexer tokenizes a template source code into a token stream (each token is
|
30 |
-
an instance of ``Twig_Token``, and the stream is an instance of
|
31 |
-
``Twig_TokenStream``). The default lexer recognizes 13 different token types:
|
32 |
-
|
33 |
-
* ``Twig_Token::BLOCK_START_TYPE``, ``Twig_Token::BLOCK_END_TYPE``: Delimiters for blocks (``{% %}``)
|
34 |
-
* ``Twig_Token::VAR_START_TYPE``, ``Twig_Token::VAR_END_TYPE``: Delimiters for variables (``{{ }}``)
|
35 |
-
* ``Twig_Token::TEXT_TYPE``: A text outside an expression;
|
36 |
-
* ``Twig_Token::NAME_TYPE``: A name in an expression;
|
37 |
-
* ``Twig_Token::NUMBER_TYPE``: A number in an expression;
|
38 |
-
* ``Twig_Token::STRING_TYPE``: A string in an expression;
|
39 |
-
* ``Twig_Token::OPERATOR_TYPE``: An operator;
|
40 |
-
* ``Twig_Token::PUNCTUATION_TYPE``: A punctuation sign;
|
41 |
-
* ``Twig_Token::INTERPOLATION_START_TYPE``, ``Twig_Token::INTERPOLATION_END_TYPE`` (as of Twig 1.5): Delimiters for string interpolation;
|
42 |
-
* ``Twig_Token::EOF_TYPE``: Ends of template.
|
43 |
-
|
44 |
-
You can manually convert a source code into a token stream by calling the
|
45 |
-
``tokenize()`` method of an environment::
|
46 |
-
|
47 |
-
$stream = $twig->tokenize(new Twig_Source($source, $identifier));
|
48 |
-
|
49 |
-
.. versionadded:: 1.27
|
50 |
-
``Twig_Source`` was introduced in version 1.27, pass the source and the
|
51 |
-
identifier directly on previous versions.
|
52 |
-
|
53 |
-
As the stream has a ``__toString()`` method, you can have a textual
|
54 |
-
representation of it by echoing the object::
|
55 |
-
|
56 |
-
echo $stream."\n";
|
57 |
-
|
58 |
-
Here is the output for the ``Hello {{ name }}`` template:
|
59 |
-
|
60 |
-
.. code-block:: text
|
61 |
-
|
62 |
-
TEXT_TYPE(Hello )
|
63 |
-
VAR_START_TYPE()
|
64 |
-
NAME_TYPE(name)
|
65 |
-
VAR_END_TYPE()
|
66 |
-
EOF_TYPE()
|
67 |
-
|
68 |
-
.. note::
|
69 |
-
|
70 |
-
The default lexer (``Twig_Lexer``) can be changed by calling
|
71 |
-
the ``setLexer()`` method::
|
72 |
-
|
73 |
-
$twig->setLexer($lexer);
|
74 |
-
|
75 |
-
The Parser
|
76 |
-
----------
|
77 |
-
|
78 |
-
The parser converts the token stream into an AST (Abstract Syntax Tree), or a
|
79 |
-
node tree (an instance of ``Twig_Node_Module``). The core extension defines
|
80 |
-
the basic nodes like: ``for``, ``if``, ... and the expression nodes.
|
81 |
-
|
82 |
-
You can manually convert a token stream into a node tree by calling the
|
83 |
-
``parse()`` method of an environment::
|
84 |
-
|
85 |
-
$nodes = $twig->parse($stream);
|
86 |
-
|
87 |
-
Echoing the node object gives you a nice representation of the tree::
|
88 |
-
|
89 |
-
echo $nodes."\n";
|
90 |
-
|
91 |
-
Here is the output for the ``Hello {{ name }}`` template:
|
92 |
-
|
93 |
-
.. code-block:: text
|
94 |
-
|
95 |
-
Twig_Node_Module(
|
96 |
-
Twig_Node_Text(Hello )
|
97 |
-
Twig_Node_Print(
|
98 |
-
Twig_Node_Expression_Name(name)
|
99 |
-
)
|
100 |
-
)
|
101 |
-
|
102 |
-
.. note::
|
103 |
-
|
104 |
-
The default parser (``Twig_TokenParser``) can be changed by calling the
|
105 |
-
``setParser()`` method::
|
106 |
-
|
107 |
-
$twig->setParser($parser);
|
108 |
-
|
109 |
-
The Compiler
|
110 |
-
------------
|
111 |
-
|
112 |
-
The last step is done by the compiler. It takes a node tree as an input and
|
113 |
-
generates PHP code usable for runtime execution of the template.
|
114 |
-
|
115 |
-
You can manually compile a node tree to PHP code with the ``compile()`` method
|
116 |
-
of an environment::
|
117 |
-
|
118 |
-
$php = $twig->compile($nodes);
|
119 |
-
|
120 |
-
The generated template for a ``Hello {{ name }}`` template reads as follows
|
121 |
-
(the actual output can differ depending on the version of Twig you are
|
122 |
-
using)::
|
123 |
-
|
124 |
-
/* Hello {{ name }} */
|
125 |
-
class __TwigTemplate_1121b6f109fe93ebe8c6e22e3712bceb extends Twig_Template
|
126 |
-
{
|
127 |
-
protected function doDisplay(array $context, array $blocks = array())
|
128 |
-
{
|
129 |
-
// line 1
|
130 |
-
echo "Hello ";
|
131 |
-
echo twig_escape_filter($this->env, isset($context["name"]) ? $context["name"] : null), "html", null, true);
|
132 |
-
}
|
133 |
-
|
134 |
-
// some more code
|
135 |
-
}
|
136 |
-
|
137 |
-
.. note::
|
138 |
-
|
139 |
-
The default compiler (``Twig_Compiler``) can be changed by calling the
|
140 |
-
``setCompiler()`` method::
|
141 |
-
|
142 |
-
$twig->setCompiler($compiler);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/intro.rst
DELETED
@@ -1,85 +0,0 @@
|
|
1 |
-
Introduction
|
2 |
-
============
|
3 |
-
|
4 |
-
This is the documentation for Twig, the flexible, fast, and secure template
|
5 |
-
engine for PHP.
|
6 |
-
|
7 |
-
If you have any exposure to other text-based template languages, such as
|
8 |
-
Smarty, Django, or Jinja, you should feel right at home with Twig. It's both
|
9 |
-
designer and developer friendly by sticking to PHP's principles and adding
|
10 |
-
functionality useful for templating environments.
|
11 |
-
|
12 |
-
The key-features are...
|
13 |
-
|
14 |
-
* *Fast*: Twig compiles templates down to plain optimized PHP code. The
|
15 |
-
overhead compared to regular PHP code was reduced to the very minimum.
|
16 |
-
|
17 |
-
* *Secure*: Twig has a sandbox mode to evaluate untrusted template code. This
|
18 |
-
allows Twig to be used as a template language for applications where users
|
19 |
-
may modify the template design.
|
20 |
-
|
21 |
-
* *Flexible*: Twig is powered by a flexible lexer and parser. This allows the
|
22 |
-
developer to define their own custom tags and filters, and to create their own DSL.
|
23 |
-
|
24 |
-
Twig is used by many Open-Source projects like Symfony, Drupal8, eZPublish,
|
25 |
-
phpBB, Piwik, OroCRM; and many frameworks have support for it as well like
|
26 |
-
Slim, Yii, Laravel, Codeigniter and Kohana — just to name a few.
|
27 |
-
|
28 |
-
Prerequisites
|
29 |
-
-------------
|
30 |
-
|
31 |
-
Twig needs at least **PHP 5.2.7** to run.
|
32 |
-
|
33 |
-
Installation
|
34 |
-
------------
|
35 |
-
|
36 |
-
The recommended way to install Twig is via Composer:
|
37 |
-
|
38 |
-
.. code-block:: bash
|
39 |
-
|
40 |
-
composer require "twig/twig:~1.0"
|
41 |
-
|
42 |
-
.. note::
|
43 |
-
|
44 |
-
To learn more about the other installation methods, read the
|
45 |
-
:doc:`installation<installation>` chapter; it also explains how to install
|
46 |
-
the Twig C extension.
|
47 |
-
|
48 |
-
Basic API Usage
|
49 |
-
---------------
|
50 |
-
|
51 |
-
This section gives you a brief introduction to the PHP API for Twig.
|
52 |
-
|
53 |
-
.. code-block:: php
|
54 |
-
|
55 |
-
require_once '/path/to/vendor/autoload.php';
|
56 |
-
|
57 |
-
$loader = new Twig_Loader_Array(array(
|
58 |
-
'index' => 'Hello {{ name }}!',
|
59 |
-
));
|
60 |
-
$twig = new Twig_Environment($loader);
|
61 |
-
|
62 |
-
echo $twig->render('index', array('name' => 'Fabien'));
|
63 |
-
|
64 |
-
Twig uses a loader (``Twig_Loader_Array``) to locate templates, and an
|
65 |
-
environment (``Twig_Environment``) to store the configuration.
|
66 |
-
|
67 |
-
The ``render()`` method loads the template passed as a first argument and
|
68 |
-
renders it with the variables passed as a second argument.
|
69 |
-
|
70 |
-
As templates are generally stored on the filesystem, Twig also comes with a
|
71 |
-
filesystem loader::
|
72 |
-
|
73 |
-
$loader = new Twig_Loader_Filesystem('/path/to/templates');
|
74 |
-
$twig = new Twig_Environment($loader, array(
|
75 |
-
'cache' => '/path/to/compilation_cache',
|
76 |
-
));
|
77 |
-
|
78 |
-
echo $twig->render('index.html', array('name' => 'Fabien'));
|
79 |
-
|
80 |
-
.. tip::
|
81 |
-
|
82 |
-
If you are not using Composer, use the Twig built-in autoloader::
|
83 |
-
|
84 |
-
require_once '/path/to/lib/Twig/Autoloader.php';
|
85 |
-
Twig_Autoloader::register();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/recipes.rst
DELETED
@@ -1,568 +0,0 @@
|
|
1 |
-
Recipes
|
2 |
-
=======
|
3 |
-
|
4 |
-
.. _deprecation-notices:
|
5 |
-
|
6 |
-
Displaying Deprecation Notices
|
7 |
-
------------------------------
|
8 |
-
|
9 |
-
.. versionadded:: 1.21
|
10 |
-
This works as of Twig 1.21.
|
11 |
-
|
12 |
-
Deprecated features generate deprecation notices (via a call to the
|
13 |
-
``trigger_error()`` PHP function). By default, they are silenced and never
|
14 |
-
displayed nor logged.
|
15 |
-
|
16 |
-
To easily remove all deprecated feature usages from your templates, write and
|
17 |
-
run a script along the lines of the following::
|
18 |
-
|
19 |
-
require_once __DIR__.'/vendor/autoload.php';
|
20 |
-
|
21 |
-
$twig = create_your_twig_env();
|
22 |
-
|
23 |
-
$deprecations = new Twig_Util_DeprecationCollector($twig);
|
24 |
-
|
25 |
-
print_r($deprecations->collectDir(__DIR__.'/templates'));
|
26 |
-
|
27 |
-
The ``collectDir()`` method compiles all templates found in a directory,
|
28 |
-
catches deprecation notices, and return them.
|
29 |
-
|
30 |
-
.. tip::
|
31 |
-
|
32 |
-
If your templates are not stored on the filesystem, use the ``collect()``
|
33 |
-
method instead. ``collect()`` takes a ``Traversable`` which must return
|
34 |
-
template names as keys and template contents as values (as done by
|
35 |
-
``Twig_Util_TemplateDirIterator``).
|
36 |
-
|
37 |
-
However, this code won't find all deprecations (like using deprecated some Twig
|
38 |
-
classes). To catch all notices, register a custom error handler like the one
|
39 |
-
below::
|
40 |
-
|
41 |
-
$deprecations = array();
|
42 |
-
set_error_handler(function ($type, $msg) use (&$deprecations) {
|
43 |
-
if (E_USER_DEPRECATED === $type) {
|
44 |
-
$deprecations[] = $msg;
|
45 |
-
}
|
46 |
-
});
|
47 |
-
|
48 |
-
// run your application
|
49 |
-
|
50 |
-
print_r($deprecations);
|
51 |
-
|
52 |
-
Note that most deprecation notices are triggered during **compilation**, so
|
53 |
-
they won't be generated when templates are already cached.
|
54 |
-
|
55 |
-
.. tip::
|
56 |
-
|
57 |
-
If you want to manage the deprecation notices from your PHPUnit tests, have
|
58 |
-
a look at the `symfony/phpunit-bridge
|
59 |
-
<https://github.com/symfony/phpunit-bridge>`_ package, which eases the
|
60 |
-
process a lot.
|
61 |
-
|
62 |
-
Making a Layout conditional
|
63 |
-
---------------------------
|
64 |
-
|
65 |
-
Working with Ajax means that the same content is sometimes displayed as is,
|
66 |
-
and sometimes decorated with a layout. As Twig layout template names can be
|
67 |
-
any valid expression, you can pass a variable that evaluates to ``true`` when
|
68 |
-
the request is made via Ajax and choose the layout accordingly:
|
69 |
-
|
70 |
-
.. code-block:: jinja
|
71 |
-
|
72 |
-
{% extends request.ajax ? "base_ajax.html" : "base.html" %}
|
73 |
-
|
74 |
-
{% block content %}
|
75 |
-
This is the content to be displayed.
|
76 |
-
{% endblock %}
|
77 |
-
|
78 |
-
Making an Include dynamic
|
79 |
-
-------------------------
|
80 |
-
|
81 |
-
When including a template, its name does not need to be a string. For
|
82 |
-
instance, the name can depend on the value of a variable:
|
83 |
-
|
84 |
-
.. code-block:: jinja
|
85 |
-
|
86 |
-
{% include var ~ '_foo.html' %}
|
87 |
-
|
88 |
-
If ``var`` evaluates to ``index``, the ``index_foo.html`` template will be
|
89 |
-
rendered.
|
90 |
-
|
91 |
-
As a matter of fact, the template name can be any valid expression, such as
|
92 |
-
the following:
|
93 |
-
|
94 |
-
.. code-block:: jinja
|
95 |
-
|
96 |
-
{% include var|default('index') ~ '_foo.html' %}
|
97 |
-
|
98 |
-
Overriding a Template that also extends itself
|
99 |
-
----------------------------------------------
|
100 |
-
|
101 |
-
A template can be customized in two different ways:
|
102 |
-
|
103 |
-
* *Inheritance*: A template *extends* a parent template and overrides some
|
104 |
-
blocks;
|
105 |
-
|
106 |
-
* *Replacement*: If you use the filesystem loader, Twig loads the first
|
107 |
-
template it finds in a list of configured directories; a template found in a
|
108 |
-
directory *replaces* another one from a directory further in the list.
|
109 |
-
|
110 |
-
But how do you combine both: *replace* a template that also extends itself
|
111 |
-
(aka a template in a directory further in the list)?
|
112 |
-
|
113 |
-
Let's say that your templates are loaded from both ``.../templates/mysite``
|
114 |
-
and ``.../templates/default`` in this order. The ``page.twig`` template,
|
115 |
-
stored in ``.../templates/default`` reads as follows:
|
116 |
-
|
117 |
-
.. code-block:: jinja
|
118 |
-
|
119 |
-
{# page.twig #}
|
120 |
-
{% extends "layout.twig" %}
|
121 |
-
|
122 |
-
{% block content %}
|
123 |
-
{% endblock %}
|
124 |
-
|
125 |
-
You can replace this template by putting a file with the same name in
|
126 |
-
``.../templates/mysite``. And if you want to extend the original template, you
|
127 |
-
might be tempted to write the following:
|
128 |
-
|
129 |
-
.. code-block:: jinja
|
130 |
-
|
131 |
-
{# page.twig in .../templates/mysite #}
|
132 |
-
{% extends "page.twig" %} {# from .../templates/default #}
|
133 |
-
|
134 |
-
Of course, this will not work as Twig will always load the template from
|
135 |
-
``.../templates/mysite``.
|
136 |
-
|
137 |
-
It turns out it is possible to get this to work, by adding a directory right
|
138 |
-
at the end of your template directories, which is the parent of all of the
|
139 |
-
other directories: ``.../templates`` in our case. This has the effect of
|
140 |
-
making every template file within our system uniquely addressable. Most of the
|
141 |
-
time you will use the "normal" paths, but in the special case of wanting to
|
142 |
-
extend a template with an overriding version of itself we can reference its
|
143 |
-
parent's full, unambiguous template path in the extends tag:
|
144 |
-
|
145 |
-
.. code-block:: jinja
|
146 |
-
|
147 |
-
{# page.twig in .../templates/mysite #}
|
148 |
-
{% extends "default/page.twig" %} {# from .../templates #}
|
149 |
-
|
150 |
-
.. note::
|
151 |
-
|
152 |
-
This recipe was inspired by the following Django wiki page:
|
153 |
-
http://code.djangoproject.com/wiki/ExtendingTemplates
|
154 |
-
|
155 |
-
Customizing the Syntax
|
156 |
-
----------------------
|
157 |
-
|
158 |
-
Twig allows some syntax customization for the block delimiters. It's not
|
159 |
-
recommended to use this feature as templates will be tied with your custom
|
160 |
-
syntax. But for specific projects, it can make sense to change the defaults.
|
161 |
-
|
162 |
-
To change the block delimiters, you need to create your own lexer object::
|
163 |
-
|
164 |
-
$twig = new Twig_Environment();
|
165 |
-
|
166 |
-
$lexer = new Twig_Lexer($twig, array(
|
167 |
-
'tag_comment' => array('{#', '#}'),
|
168 |
-
'tag_block' => array('{%', '%}'),
|
169 |
-
'tag_variable' => array('{{', '}}'),
|
170 |
-
'interpolation' => array('#{', '}'),
|
171 |
-
));
|
172 |
-
$twig->setLexer($lexer);
|
173 |
-
|
174 |
-
Here are some configuration example that simulates some other template engines
|
175 |
-
syntax::
|
176 |
-
|
177 |
-
// Ruby erb syntax
|
178 |
-
$lexer = new Twig_Lexer($twig, array(
|
179 |
-
'tag_comment' => array('<%#', '%>'),
|
180 |
-
'tag_block' => array('<%', '%>'),
|
181 |
-
'tag_variable' => array('<%=', '%>'),
|
182 |
-
));
|
183 |
-
|
184 |
-
// SGML Comment Syntax
|
185 |
-
$lexer = new Twig_Lexer($twig, array(
|
186 |
-
'tag_comment' => array('<!--#', '-->'),
|
187 |
-
'tag_block' => array('<!--', '-->'),
|
188 |
-
'tag_variable' => array('${', '}'),
|
189 |
-
));
|
190 |
-
|
191 |
-
// Smarty like
|
192 |
-
$lexer = new Twig_Lexer($twig, array(
|
193 |
-
'tag_comment' => array('{*', '*}'),
|
194 |
-
'tag_block' => array('{', '}'),
|
195 |
-
'tag_variable' => array('{$', '}'),
|
196 |
-
));
|
197 |
-
|
198 |
-
Using dynamic Object Properties
|
199 |
-
-------------------------------
|
200 |
-
|
201 |
-
When Twig encounters a variable like ``article.title``, it tries to find a
|
202 |
-
``title`` public property in the ``article`` object.
|
203 |
-
|
204 |
-
It also works if the property does not exist but is rather defined dynamically
|
205 |
-
thanks to the magic ``__get()`` method; you just need to also implement the
|
206 |
-
``__isset()`` magic method like shown in the following snippet of code::
|
207 |
-
|
208 |
-
class Article
|
209 |
-
{
|
210 |
-
public function __get($name)
|
211 |
-
{
|
212 |
-
if ('title' == $name) {
|
213 |
-
return 'The title';
|
214 |
-
}
|
215 |
-
|
216 |
-
// throw some kind of error
|
217 |
-
}
|
218 |
-
|
219 |
-
public function __isset($name)
|
220 |
-
{
|
221 |
-
if ('title' == $name) {
|
222 |
-
return true;
|
223 |
-
}
|
224 |
-
|
225 |
-
return false;
|
226 |
-
}
|
227 |
-
}
|
228 |
-
|
229 |
-
Accessing the parent Context in Nested Loops
|
230 |
-
--------------------------------------------
|
231 |
-
|
232 |
-
Sometimes, when using nested loops, you need to access the parent context. The
|
233 |
-
parent context is always accessible via the ``loop.parent`` variable. For
|
234 |
-
instance, if you have the following template data::
|
235 |
-
|
236 |
-
$data = array(
|
237 |
-
'topics' => array(
|
238 |
-
'topic1' => array('Message 1 of topic 1', 'Message 2 of topic 1'),
|
239 |
-
'topic2' => array('Message 1 of topic 2', 'Message 2 of topic 2'),
|
240 |
-
),
|
241 |
-
);
|
242 |
-
|
243 |
-
And the following template to display all messages in all topics:
|
244 |
-
|
245 |
-
.. code-block:: jinja
|
246 |
-
|
247 |
-
{% for topic, messages in topics %}
|
248 |
-
* {{ loop.index }}: {{ topic }}
|
249 |
-
{% for message in messages %}
|
250 |
-
- {{ loop.parent.loop.index }}.{{ loop.index }}: {{ message }}
|
251 |
-
{% endfor %}
|
252 |
-
{% endfor %}
|
253 |
-
|
254 |
-
The output will be similar to:
|
255 |
-
|
256 |
-
.. code-block:: text
|
257 |
-
|
258 |
-
* 1: topic1
|
259 |
-
- 1.1: The message 1 of topic 1
|
260 |
-
- 1.2: The message 2 of topic 1
|
261 |
-
* 2: topic2
|
262 |
-
- 2.1: The message 1 of topic 2
|
263 |
-
- 2.2: The message 2 of topic 2
|
264 |
-
|
265 |
-
In the inner loop, the ``loop.parent`` variable is used to access the outer
|
266 |
-
context. So, the index of the current ``topic`` defined in the outer for loop
|
267 |
-
is accessible via the ``loop.parent.loop.index`` variable.
|
268 |
-
|
269 |
-
Defining undefined Functions and Filters on the Fly
|
270 |
-
---------------------------------------------------
|
271 |
-
|
272 |
-
When a function (or a filter) is not defined, Twig defaults to throw a
|
273 |
-
``Twig_Error_Syntax`` exception. However, it can also call a `callback`_ (any
|
274 |
-
valid PHP callable) which should return a function (or a filter).
|
275 |
-
|
276 |
-
For filters, register callbacks with ``registerUndefinedFilterCallback()``.
|
277 |
-
For functions, use ``registerUndefinedFunctionCallback()``::
|
278 |
-
|
279 |
-
// auto-register all native PHP functions as Twig functions
|
280 |
-
// don't try this at home as it's not secure at all!
|
281 |
-
$twig->registerUndefinedFunctionCallback(function ($name) {
|
282 |
-
if (function_exists($name)) {
|
283 |
-
return new Twig_SimpleFunction($name, $name);
|
284 |
-
}
|
285 |
-
|
286 |
-
return false;
|
287 |
-
});
|
288 |
-
|
289 |
-
If the callable is not able to return a valid function (or filter), it must
|
290 |
-
return ``false``.
|
291 |
-
|
292 |
-
If you register more than one callback, Twig will call them in turn until one
|
293 |
-
does not return ``false``.
|
294 |
-
|
295 |
-
.. tip::
|
296 |
-
|
297 |
-
As the resolution of functions and filters is done during compilation,
|
298 |
-
there is no overhead when registering these callbacks.
|
299 |
-
|
300 |
-
Validating the Template Syntax
|
301 |
-
------------------------------
|
302 |
-
|
303 |
-
When template code is provided by a third-party (through a web interface for
|
304 |
-
instance), it might be interesting to validate the template syntax before
|
305 |
-
saving it. If the template code is stored in a `$template` variable, here is
|
306 |
-
how you can do it::
|
307 |
-
|
308 |
-
try {
|
309 |
-
$twig->parse($twig->tokenize(new Twig_Source($template)));
|
310 |
-
|
311 |
-
// the $template is valid
|
312 |
-
} catch (Twig_Error_Syntax $e) {
|
313 |
-
// $template contains one or more syntax errors
|
314 |
-
}
|
315 |
-
|
316 |
-
If you iterate over a set of files, you can pass the filename to the
|
317 |
-
``tokenize()`` method to get the filename in the exception message::
|
318 |
-
|
319 |
-
foreach ($files as $file) {
|
320 |
-
try {
|
321 |
-
$twig->parse($twig->tokenize(new Twig_Source($template, $file->getFilename(), $file)));
|
322 |
-
|
323 |
-
// the $template is valid
|
324 |
-
} catch (Twig_Error_Syntax $e) {
|
325 |
-
// $template contains one or more syntax errors
|
326 |
-
}
|
327 |
-
}
|
328 |
-
|
329 |
-
.. versionadded:: 1.27
|
330 |
-
``Twig_Source`` was introduced in version 1.27, pass the source and the
|
331 |
-
identifier directly on previous versions.
|
332 |
-
|
333 |
-
.. note::
|
334 |
-
|
335 |
-
This method won't catch any sandbox policy violations because the policy
|
336 |
-
is enforced during template rendering (as Twig needs the context for some
|
337 |
-
checks like allowed methods on objects).
|
338 |
-
|
339 |
-
Refreshing modified Templates when OPcache or APC is enabled
|
340 |
-
------------------------------------------------------------
|
341 |
-
|
342 |
-
When using OPcache with ``opcache.validate_timestamps`` set to ``0`` or APC
|
343 |
-
with ``apc.stat`` set to ``0`` and Twig cache enabled, clearing the template
|
344 |
-
cache won't update the cache.
|
345 |
-
|
346 |
-
To get around this, force Twig to invalidate the bytecode cache::
|
347 |
-
|
348 |
-
$twig = new Twig_Environment($loader, array(
|
349 |
-
'cache' => new Twig_Cache_Filesystem('/some/cache/path', Twig_Cache_Filesystem::FORCE_BYTECODE_INVALIDATION),
|
350 |
-
// ...
|
351 |
-
));
|
352 |
-
|
353 |
-
.. note::
|
354 |
-
|
355 |
-
Before Twig 1.22, you should extend ``Twig_Environment`` instead::
|
356 |
-
|
357 |
-
class OpCacheAwareTwigEnvironment extends Twig_Environment
|
358 |
-
{
|
359 |
-
protected function writeCacheFile($file, $content)
|
360 |
-
{
|
361 |
-
parent::writeCacheFile($file, $content);
|
362 |
-
|
363 |
-
// Compile cached file into bytecode cache
|
364 |
-
if (function_exists('opcache_invalidate')) {
|
365 |
-
opcache_invalidate($file, true);
|
366 |
-
} elseif (function_exists('apc_compile_file')) {
|
367 |
-
apc_compile_file($file);
|
368 |
-
}
|
369 |
-
}
|
370 |
-
}
|
371 |
-
|
372 |
-
Reusing a stateful Node Visitor
|
373 |
-
-------------------------------
|
374 |
-
|
375 |
-
When attaching a visitor to a ``Twig_Environment`` instance, Twig uses it to
|
376 |
-
visit *all* templates it compiles. If you need to keep some state information
|
377 |
-
around, you probably want to reset it when visiting a new template.
|
378 |
-
|
379 |
-
This can be easily achieved with the following code::
|
380 |
-
|
381 |
-
protected $someTemplateState = array();
|
382 |
-
|
383 |
-
public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
|
384 |
-
{
|
385 |
-
if ($node instanceof Twig_Node_Module) {
|
386 |
-
// reset the state as we are entering a new template
|
387 |
-
$this->someTemplateState = array();
|
388 |
-
}
|
389 |
-
|
390 |
-
// ...
|
391 |
-
|
392 |
-
return $node;
|
393 |
-
}
|
394 |
-
|
395 |
-
Using a Database to store Templates
|
396 |
-
-----------------------------------
|
397 |
-
|
398 |
-
If you are developing a CMS, templates are usually stored in a database. This
|
399 |
-
recipe gives you a simple PDO template loader you can use as a starting point
|
400 |
-
for your own.
|
401 |
-
|
402 |
-
First, let's create a temporary in-memory SQLite3 database to work with::
|
403 |
-
|
404 |
-
$dbh = new PDO('sqlite::memory:');
|
405 |
-
$dbh->exec('CREATE TABLE templates (name STRING, source STRING, last_modified INTEGER)');
|
406 |
-
$base = '{% block content %}{% endblock %}';
|
407 |
-
$index = '
|
408 |
-
{% extends "base.twig" %}
|
409 |
-
{% block content %}Hello {{ name }}{% endblock %}
|
410 |
-
';
|
411 |
-
$now = time();
|
412 |
-
$dbh->exec("INSERT INTO templates (name, source, last_modified) VALUES ('base.twig', '$base', $now)");
|
413 |
-
$dbh->exec("INSERT INTO templates (name, source, last_modified) VALUES ('index.twig', '$index', $now)");
|
414 |
-
|
415 |
-
We have created a simple ``templates`` table that hosts two templates:
|
416 |
-
``base.twig`` and ``index.twig``.
|
417 |
-
|
418 |
-
Now, let's define a loader able to use this database::
|
419 |
-
|
420 |
-
class DatabaseTwigLoader implements Twig_LoaderInterface, Twig_ExistsLoaderInterface, Twig_SourceContextLoaderInterface
|
421 |
-
{
|
422 |
-
protected $dbh;
|
423 |
-
|
424 |
-
public function __construct(PDO $dbh)
|
425 |
-
{
|
426 |
-
$this->dbh = $dbh;
|
427 |
-
}
|
428 |
-
|
429 |
-
public function getSource($name)
|
430 |
-
{
|
431 |
-
if (false === $source = $this->getValue('source', $name)) {
|
432 |
-
throw new Twig_Error_Loader(sprintf('Template "%s" does not exist.', $name));
|
433 |
-
}
|
434 |
-
|
435 |
-
return $source;
|
436 |
-
}
|
437 |
-
|
438 |
-
// Twig_SourceContextLoaderInterface as of Twig 1.27
|
439 |
-
public function getSourceContext($name)
|
440 |
-
{
|
441 |
-
if (false === $source = $this->getValue('source', $name)) {
|
442 |
-
throw new Twig_Error_Loader(sprintf('Template "%s" does not exist.', $name));
|
443 |
-
}
|
444 |
-
|
445 |
-
return new Twig_Source($source, $name);
|
446 |
-
}
|
447 |
-
|
448 |
-
// Twig_ExistsLoaderInterface as of Twig 1.11
|
449 |
-
public function exists($name)
|
450 |
-
{
|
451 |
-
return $name === $this->getValue('name', $name);
|
452 |
-
}
|
453 |
-
|
454 |
-
public function getCacheKey($name)
|
455 |
-
{
|
456 |
-
return $name;
|
457 |
-
}
|
458 |
-
|
459 |
-
public function isFresh($name, $time)
|
460 |
-
{
|
461 |
-
if (false === $lastModified = $this->getValue('last_modified', $name)) {
|
462 |
-
return false;
|
463 |
-
}
|
464 |
-
|
465 |
-
return $lastModified <= $time;
|
466 |
-
}
|
467 |
-
|
468 |
-
protected function getValue($column, $name)
|
469 |
-
{
|
470 |
-
$sth = $this->dbh->prepare('SELECT '.$column.' FROM templates WHERE name = :name');
|
471 |
-
$sth->execute(array(':name' => (string) $name));
|
472 |
-
|
473 |
-
return $sth->fetchColumn();
|
474 |
-
}
|
475 |
-
}
|
476 |
-
|
477 |
-
Finally, here is an example on how you can use it::
|
478 |
-
|
479 |
-
$loader = new DatabaseTwigLoader($dbh);
|
480 |
-
$twig = new Twig_Environment($loader);
|
481 |
-
|
482 |
-
echo $twig->render('index.twig', array('name' => 'Fabien'));
|
483 |
-
|
484 |
-
Using different Template Sources
|
485 |
-
--------------------------------
|
486 |
-
|
487 |
-
This recipe is the continuation of the previous one. Even if you store the
|
488 |
-
contributed templates in a database, you might want to keep the original/base
|
489 |
-
templates on the filesystem. When templates can be loaded from different
|
490 |
-
sources, you need to use the ``Twig_Loader_Chain`` loader.
|
491 |
-
|
492 |
-
As you can see in the previous recipe, we reference the template in the exact
|
493 |
-
same way as we would have done it with a regular filesystem loader. This is
|
494 |
-
the key to be able to mix and match templates coming from the database, the
|
495 |
-
filesystem, or any other loader for that matter: the template name should be a
|
496 |
-
logical name, and not the path from the filesystem::
|
497 |
-
|
498 |
-
$loader1 = new DatabaseTwigLoader($dbh);
|
499 |
-
$loader2 = new Twig_Loader_Array(array(
|
500 |
-
'base.twig' => '{% block content %}{% endblock %}',
|
501 |
-
));
|
502 |
-
$loader = new Twig_Loader_Chain(array($loader1, $loader2));
|
503 |
-
|
504 |
-
$twig = new Twig_Environment($loader);
|
505 |
-
|
506 |
-
echo $twig->render('index.twig', array('name' => 'Fabien'));
|
507 |
-
|
508 |
-
Now that the ``base.twig`` templates is defined in an array loader, you can
|
509 |
-
remove it from the database, and everything else will still work as before.
|
510 |
-
|
511 |
-
Loading a Template from a String
|
512 |
-
--------------------------------
|
513 |
-
|
514 |
-
From a template, you can easily load a template stored in a string via the
|
515 |
-
``template_from_string`` function (available as of Twig 1.11 via the
|
516 |
-
``Twig_Extension_StringLoader`` extension):
|
517 |
-
|
518 |
-
.. code-block:: jinja
|
519 |
-
|
520 |
-
{{ include(template_from_string("Hello {{ name }}")) }}
|
521 |
-
|
522 |
-
From PHP, it's also possible to load a template stored in a string via
|
523 |
-
``Twig_Environment::createTemplate()`` (available as of Twig 1.18)::
|
524 |
-
|
525 |
-
$template = $twig->createTemplate('hello {{ name }}');
|
526 |
-
echo $template->render(array('name' => 'Fabien'));
|
527 |
-
|
528 |
-
.. note::
|
529 |
-
|
530 |
-
Never use the ``Twig_Loader_String`` loader, which has severe limitations.
|
531 |
-
|
532 |
-
Using Twig and AngularJS in the same Templates
|
533 |
-
----------------------------------------------
|
534 |
-
|
535 |
-
Mixing different template syntaxes in the same file is not a recommended
|
536 |
-
practice as both AngularJS and Twig use the same delimiters in their syntax:
|
537 |
-
``{{`` and ``}}``.
|
538 |
-
|
539 |
-
Still, if you want to use AngularJS and Twig in the same template, there are
|
540 |
-
two ways to make it work depending on the amount of AngularJS you need to
|
541 |
-
include in your templates:
|
542 |
-
|
543 |
-
* Escaping the AngularJS delimiters by wrapping AngularJS sections with the
|
544 |
-
``{% verbatim %}`` tag or by escaping each delimiter via ``{{ '{{' }}`` and
|
545 |
-
``{{ '}}' }}``;
|
546 |
-
|
547 |
-
* Changing the delimiters of one of the template engines (depending on which
|
548 |
-
engine you introduced last):
|
549 |
-
|
550 |
-
* For AngularJS, change the interpolation tags using the
|
551 |
-
``interpolateProvider`` service, for instance at the module initialization
|
552 |
-
time:
|
553 |
-
|
554 |
-
.. code-block:: javascript
|
555 |
-
|
556 |
-
angular.module('myApp', []).config(function($interpolateProvider) {
|
557 |
-
$interpolateProvider.startSymbol('{[').endSymbol(']}');
|
558 |
-
});
|
559 |
-
|
560 |
-
* For Twig, change the delimiters via the ``tag_variable`` Lexer option:
|
561 |
-
|
562 |
-
.. code-block:: php
|
563 |
-
|
564 |
-
$env->setLexer(new Twig_Lexer($env, array(
|
565 |
-
'tag_variable' => array('{[', ']}'),
|
566 |
-
)));
|
567 |
-
|
568 |
-
.. _callback: http://www.php.net/manual/en/function.is-callable.php
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/tags/autoescape.rst
DELETED
@@ -1,81 +0,0 @@
|
|
1 |
-
``autoescape``
|
2 |
-
==============
|
3 |
-
|
4 |
-
Whether automatic escaping is enabled or not, you can mark a section of a
|
5 |
-
template to be escaped or not by using the ``autoescape`` tag:
|
6 |
-
|
7 |
-
.. code-block:: jinja
|
8 |
-
|
9 |
-
{% autoescape %}
|
10 |
-
Everything will be automatically escaped in this block
|
11 |
-
using the HTML strategy
|
12 |
-
{% endautoescape %}
|
13 |
-
|
14 |
-
{% autoescape 'html' %}
|
15 |
-
Everything will be automatically escaped in this block
|
16 |
-
using the HTML strategy
|
17 |
-
{% endautoescape %}
|
18 |
-
|
19 |
-
{% autoescape 'js' %}
|
20 |
-
Everything will be automatically escaped in this block
|
21 |
-
using the js escaping strategy
|
22 |
-
{% endautoescape %}
|
23 |
-
|
24 |
-
{% autoescape false %}
|
25 |
-
Everything will be outputted as is in this block
|
26 |
-
{% endautoescape %}
|
27 |
-
|
28 |
-
.. note::
|
29 |
-
|
30 |
-
Before Twig 1.8, the syntax was different:
|
31 |
-
|
32 |
-
.. code-block:: jinja
|
33 |
-
|
34 |
-
{% autoescape true %}
|
35 |
-
Everything will be automatically escaped in this block
|
36 |
-
using the HTML strategy
|
37 |
-
{% endautoescape %}
|
38 |
-
|
39 |
-
{% autoescape false %}
|
40 |
-
Everything will be outputted as is in this block
|
41 |
-
{% endautoescape %}
|
42 |
-
|
43 |
-
{% autoescape true js %}
|
44 |
-
Everything will be automatically escaped in this block
|
45 |
-
using the js escaping strategy
|
46 |
-
{% endautoescape %}
|
47 |
-
|
48 |
-
When automatic escaping is enabled everything is escaped by default except for
|
49 |
-
values explicitly marked as safe. Those can be marked in the template by using
|
50 |
-
the :doc:`raw<../filters/raw>` filter:
|
51 |
-
|
52 |
-
.. code-block:: jinja
|
53 |
-
|
54 |
-
{% autoescape %}
|
55 |
-
{{ safe_value|raw }}
|
56 |
-
{% endautoescape %}
|
57 |
-
|
58 |
-
Functions returning template data (like :doc:`macros<macro>` and
|
59 |
-
:doc:`parent<../functions/parent>`) always return safe markup.
|
60 |
-
|
61 |
-
.. note::
|
62 |
-
|
63 |
-
Twig is smart enough to not escape an already escaped value by the
|
64 |
-
:doc:`escape<../filters/escape>` filter.
|
65 |
-
|
66 |
-
.. note::
|
67 |
-
|
68 |
-
Twig does not escape static expressions:
|
69 |
-
|
70 |
-
.. code-block:: jinja
|
71 |
-
|
72 |
-
{% set hello = "<strong>Hello</strong>" %}
|
73 |
-
{{ hello }}
|
74 |
-
{{ "<strong>world</strong>" }}
|
75 |
-
|
76 |
-
Will be rendered "<strong>Hello</strong> **world**".
|
77 |
-
|
78 |
-
.. note::
|
79 |
-
|
80 |
-
The chapter :doc:`Twig for Developers<../api>` gives more information
|
81 |
-
about when and how automatic escaping is applied.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/tags/block.rst
DELETED
@@ -1,11 +0,0 @@
|
|
1 |
-
``block``
|
2 |
-
=========
|
3 |
-
|
4 |
-
Blocks are used for inheritance and act as placeholders and replacements at
|
5 |
-
the same time. They are documented in detail in the documentation for the
|
6 |
-
:doc:`extends<../tags/extends>` tag.
|
7 |
-
|
8 |
-
Block names should consist of alphanumeric characters, and underscores. Dashes
|
9 |
-
are not permitted.
|
10 |
-
|
11 |
-
.. seealso:: :doc:`block<../functions/block>`, :doc:`parent<../functions/parent>`, :doc:`use<../tags/use>`, :doc:`extends<../tags/extends>`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/tags/do.rst
DELETED
@@ -1,12 +0,0 @@
|
|
1 |
-
``do``
|
2 |
-
======
|
3 |
-
|
4 |
-
.. versionadded:: 1.5
|
5 |
-
The ``do`` tag was added in Twig 1.5.
|
6 |
-
|
7 |
-
The ``do`` tag works exactly like the regular variable expression (``{{ ...
|
8 |
-
}}``) just that it doesn't print anything:
|
9 |
-
|
10 |
-
.. code-block:: jinja
|
11 |
-
|
12 |
-
{% do 1 + 2 %}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/tags/embed.rst
DELETED
@@ -1,178 +0,0 @@
|
|
1 |
-
``embed``
|
2 |
-
=========
|
3 |
-
|
4 |
-
.. versionadded:: 1.8
|
5 |
-
The ``embed`` tag was added in Twig 1.8.
|
6 |
-
|
7 |
-
The ``embed`` tag combines the behaviour of :doc:`include<include>` and
|
8 |
-
:doc:`extends<extends>`.
|
9 |
-
It allows you to include another template's contents, just like ``include``
|
10 |
-
does. But it also allows you to override any block defined inside the
|
11 |
-
included template, like when extending a template.
|
12 |
-
|
13 |
-
Think of an embedded template as a "micro layout skeleton".
|
14 |
-
|
15 |
-
.. code-block:: jinja
|
16 |
-
|
17 |
-
{% embed "teasers_skeleton.twig" %}
|
18 |
-
{# These blocks are defined in "teasers_skeleton.twig" #}
|
19 |
-
{# and we override them right here: #}
|
20 |
-
{% block left_teaser %}
|
21 |
-
Some content for the left teaser box
|
22 |
-
{% endblock %}
|
23 |
-
{% block right_teaser %}
|
24 |
-
Some content for the right teaser box
|
25 |
-
{% endblock %}
|
26 |
-
{% endembed %}
|
27 |
-
|
28 |
-
The ``embed`` tag takes the idea of template inheritance to the level of
|
29 |
-
content fragments. While template inheritance allows for "document skeletons",
|
30 |
-
which are filled with life by child templates, the ``embed`` tag allows you to
|
31 |
-
create "skeletons" for smaller units of content and re-use and fill them
|
32 |
-
anywhere you like.
|
33 |
-
|
34 |
-
Since the use case may not be obvious, let's look at a simplified example.
|
35 |
-
Imagine a base template shared by multiple HTML pages, defining a single block
|
36 |
-
named "content":
|
37 |
-
|
38 |
-
.. code-block:: text
|
39 |
-
|
40 |
-
┌─── page layout ─────────────────────┐
|
41 |
-
│ │
|
42 |
-
│ ┌── block "content" ──┐ │
|
43 |
-
│ │ │ │
|
44 |
-
│ │ │ │
|
45 |
-
│ │ (child template to │ │
|
46 |
-
│ │ put content here) │ │
|
47 |
-
│ │ │ │
|
48 |
-
│ │ │ │
|
49 |
-
│ └─────────────────────┘ │
|
50 |
-
│ │
|
51 |
-
└─────────────────────────────────────┘
|
52 |
-
|
53 |
-
Some pages ("foo" and "bar") share the same content structure -
|
54 |
-
two vertically stacked boxes:
|
55 |
-
|
56 |
-
.. code-block:: text
|
57 |
-
|
58 |
-
┌─── page layout ─────────────────────┐
|
59 |
-
│ │
|
60 |
-
│ ┌── block "content" ──┐ │
|
61 |
-
│ │ ┌─ block "top" ───┐ │ │
|
62 |
-
│ │ │ │ │ │
|
63 |
-
│ │ └─────────────────┘ │ │
|
64 |
-
│ │ ┌─ block "bottom" ┐ │ │
|
65 |
-
│ │ │ │ │ │
|
66 |
-
│ │ └─────────────────┘ │ │
|
67 |
-
│ └─────────────────────┘ │
|
68 |
-
│ │
|
69 |
-
└─────────────────────────────────────┘
|
70 |
-
|
71 |
-
While other pages ("boom" and "baz") share a different content structure -
|
72 |
-
two boxes side by side:
|
73 |
-
|
74 |
-
.. code-block:: text
|
75 |
-
|
76 |
-
┌─── page layout ─────────────────────┐
|
77 |
-
│ │
|
78 |
-
│ ┌── block "content" ──┐ │
|
79 |
-
│ │ │ │
|
80 |
-
│ │ ┌ block ┐ ┌ block ┐ │ │
|
81 |
-
│ │ │"left" │ │"right"│ │ │
|
82 |
-
│ │ │ │ │ │ │ │
|
83 |
-
│ │ │ │ │ │ │ │
|
84 |
-
│ │ └───────┘ └───────┘ │ │
|
85 |
-
│ └─────────────────────┘ │
|
86 |
-
│ │
|
87 |
-
└─────────────────────────────────────┘
|
88 |
-
|
89 |
-
Without the ``embed`` tag, you have two ways to design your templates:
|
90 |
-
|
91 |
-
* Create two "intermediate" base templates that extend the master layout
|
92 |
-
template: one with vertically stacked boxes to be used by the "foo" and
|
93 |
-
"bar" pages and another one with side-by-side boxes for the "boom" and
|
94 |
-
"baz" pages.
|
95 |
-
|
96 |
-
* Embed the markup for the top/bottom and left/right boxes into each page
|
97 |
-
template directly.
|
98 |
-
|
99 |
-
These two solutions do not scale well because they each have a major drawback:
|
100 |
-
|
101 |
-
* The first solution may indeed work for this simplified example. But imagine
|
102 |
-
we add a sidebar, which may again contain different, recurring structures
|
103 |
-
of content. Now we would need to create intermediate base templates for
|
104 |
-
all occurring combinations of content structure and sidebar structure...
|
105 |
-
and so on.
|
106 |
-
|
107 |
-
* The second solution involves duplication of common code with all its negative
|
108 |
-
consequences: any change involves finding and editing all affected copies
|
109 |
-
of the structure, correctness has to be verified for each copy, copies may
|
110 |
-
go out of sync by careless modifications etc.
|
111 |
-
|
112 |
-
In such a situation, the ``embed`` tag comes in handy. The common layout
|
113 |
-
code can live in a single base template, and the two different content structures,
|
114 |
-
let's call them "micro layouts" go into separate templates which are embedded
|
115 |
-
as necessary:
|
116 |
-
|
117 |
-
Page template ``foo.twig``:
|
118 |
-
|
119 |
-
.. code-block:: jinja
|
120 |
-
|
121 |
-
{% extends "layout_skeleton.twig" %}
|
122 |
-
|
123 |
-
{% block content %}
|
124 |
-
{% embed "vertical_boxes_skeleton.twig" %}
|
125 |
-
{% block top %}
|
126 |
-
Some content for the top box
|
127 |
-
{% endblock %}
|
128 |
-
|
129 |
-
{% block bottom %}
|
130 |
-
Some content for the bottom box
|
131 |
-
{% endblock %}
|
132 |
-
{% endembed %}
|
133 |
-
{% endblock %}
|
134 |
-
|
135 |
-
And here is the code for ``vertical_boxes_skeleton.twig``:
|
136 |
-
|
137 |
-
.. code-block:: html+jinja
|
138 |
-
|
139 |
-
<div class="top_box">
|
140 |
-
{% block top %}
|
141 |
-
Top box default content
|
142 |
-
{% endblock %}
|
143 |
-
</div>
|
144 |
-
|
145 |
-
<div class="bottom_box">
|
146 |
-
{% block bottom %}
|
147 |
-
Bottom box default content
|
148 |
-
{% endblock %}
|
149 |
-
</div>
|
150 |
-
|
151 |
-
The goal of the ``vertical_boxes_skeleton.twig`` template being to factor
|
152 |
-
out the HTML markup for the boxes.
|
153 |
-
|
154 |
-
The ``embed`` tag takes the exact same arguments as the ``include`` tag:
|
155 |
-
|
156 |
-
.. code-block:: jinja
|
157 |
-
|
158 |
-
{% embed "base" with {'foo': 'bar'} %}
|
159 |
-
...
|
160 |
-
{% endembed %}
|
161 |
-
|
162 |
-
{% embed "base" with {'foo': 'bar'} only %}
|
163 |
-
...
|
164 |
-
{% endembed %}
|
165 |
-
|
166 |
-
{% embed "base" ignore missing %}
|
167 |
-
...
|
168 |
-
{% endembed %}
|
169 |
-
|
170 |
-
.. warning::
|
171 |
-
|
172 |
-
As embedded templates do not have "names", auto-escaping strategies based
|
173 |
-
on the template name won't work as expected if you change the context (for
|
174 |
-
instance, if you embed a CSS/JavaScript template into an HTML one). In that
|
175 |
-
case, explicitly set the default auto-escaping strategy with the
|
176 |
-
``autoescape`` tag.
|
177 |
-
|
178 |
-
.. seealso:: :doc:`include<../tags/include>`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/tags/extends.rst
DELETED
@@ -1,272 +0,0 @@
|
|
1 |
-
``extends``
|
2 |
-
===========
|
3 |
-
|
4 |
-
The ``extends`` tag can be used to extend a template from another one.
|
5 |
-
|
6 |
-
.. note::
|
7 |
-
|
8 |
-
Like PHP, Twig does not support multiple inheritance. So you can only have
|
9 |
-
one extends tag called per rendering. However, Twig supports horizontal
|
10 |
-
:doc:`reuse<use>`.
|
11 |
-
|
12 |
-
Let's define a base template, ``base.html``, which defines a simple HTML
|
13 |
-
skeleton document:
|
14 |
-
|
15 |
-
.. code-block:: html+jinja
|
16 |
-
|
17 |
-
<!DOCTYPE html>
|
18 |
-
<html>
|
19 |
-
<head>
|
20 |
-
{% block head %}
|
21 |
-
<link rel="stylesheet" href="style.css" />
|
22 |
-
<title>{% block title %}{% endblock %} - My Webpage</title>
|
23 |
-
{% endblock %}
|
24 |
-
</head>
|
25 |
-
<body>
|
26 |
-
<div id="content">{% block content %}{% endblock %}</div>
|
27 |
-
<div id="footer">
|
28 |
-
{% block footer %}
|
29 |
-
© Copyright 2011 by <a href="http://domain.invalid/">you</a>.
|
30 |
-
{% endblock %}
|
31 |
-
</div>
|
32 |
-
</body>
|
33 |
-
</html>
|
34 |
-
|
35 |
-
In this example, the :doc:`block<block>` tags define four blocks that child
|
36 |
-
templates can fill in.
|
37 |
-
|
38 |
-
All the ``block`` tag does is to tell the template engine that a child
|
39 |
-
template may override those portions of the template.
|
40 |
-
|
41 |
-
Child Template
|
42 |
-
--------------
|
43 |
-
|
44 |
-
A child template might look like this:
|
45 |
-
|
46 |
-
.. code-block:: jinja
|
47 |
-
|
48 |
-
{% extends "base.html" %}
|
49 |
-
|
50 |
-
{% block title %}Index{% endblock %}
|
51 |
-
{% block head %}
|
52 |
-
{{ parent() }}
|
53 |
-
<style type="text/css">
|
54 |
-
.important { color: #336699; }
|
55 |
-
</style>
|
56 |
-
{% endblock %}
|
57 |
-
{% block content %}
|
58 |
-
<h1>Index</h1>
|
59 |
-
<p class="important">
|
60 |
-
Welcome on my awesome homepage.
|
61 |
-
</p>
|
62 |
-
{% endblock %}
|
63 |
-
|
64 |
-
The ``extends`` tag is the key here. It tells the template engine that this
|
65 |
-
template "extends" another template. When the template system evaluates this
|
66 |
-
template, first it locates the parent. The extends tag should be the first tag
|
67 |
-
in the template.
|
68 |
-
|
69 |
-
Note that since the child template doesn't define the ``footer`` block, the
|
70 |
-
value from the parent template is used instead.
|
71 |
-
|
72 |
-
You can't define multiple ``block`` tags with the same name in the same
|
73 |
-
template. This limitation exists because a block tag works in "both"
|
74 |
-
directions. That is, a block tag doesn't just provide a hole to fill - it also
|
75 |
-
defines the content that fills the hole in the *parent*. If there were two
|
76 |
-
similarly-named ``block`` tags in a template, that template's parent wouldn't
|
77 |
-
know which one of the blocks' content to use.
|
78 |
-
|
79 |
-
If you want to print a block multiple times you can however use the
|
80 |
-
``block`` function:
|
81 |
-
|
82 |
-
.. code-block:: jinja
|
83 |
-
|
84 |
-
<title>{% block title %}{% endblock %}</title>
|
85 |
-
<h1>{{ block('title') }}</h1>
|
86 |
-
{% block body %}{% endblock %}
|
87 |
-
|
88 |
-
Parent Blocks
|
89 |
-
-------------
|
90 |
-
|
91 |
-
It's possible to render the contents of the parent block by using the
|
92 |
-
:doc:`parent<../functions/parent>` function. This gives back the results of
|
93 |
-
the parent block:
|
94 |
-
|
95 |
-
.. code-block:: jinja
|
96 |
-
|
97 |
-
{% block sidebar %}
|
98 |
-
<h3>Table Of Contents</h3>
|
99 |
-
...
|
100 |
-
{{ parent() }}
|
101 |
-
{% endblock %}
|
102 |
-
|
103 |
-
Named Block End-Tags
|
104 |
-
--------------------
|
105 |
-
|
106 |
-
Twig allows you to put the name of the block after the end tag for better
|
107 |
-
readability:
|
108 |
-
|
109 |
-
.. code-block:: jinja
|
110 |
-
|
111 |
-
{% block sidebar %}
|
112 |
-
{% block inner_sidebar %}
|
113 |
-
...
|
114 |
-
{% endblock inner_sidebar %}
|
115 |
-
{% endblock sidebar %}
|
116 |
-
|
117 |
-
Of course, the name after the ``endblock`` word must match the block name.
|
118 |
-
|
119 |
-
Block Nesting and Scope
|
120 |
-
-----------------------
|
121 |
-
|
122 |
-
Blocks can be nested for more complex layouts. Per default, blocks have access
|
123 |
-
to variables from outer scopes:
|
124 |
-
|
125 |
-
.. code-block:: jinja
|
126 |
-
|
127 |
-
{% for item in seq %}
|
128 |
-
<li>{% block loop_item %}{{ item }}{% endblock %}</li>
|
129 |
-
{% endfor %}
|
130 |
-
|
131 |
-
Block Shortcuts
|
132 |
-
---------------
|
133 |
-
|
134 |
-
For blocks with little content, it's possible to use a shortcut syntax. The
|
135 |
-
following constructs do the same thing:
|
136 |
-
|
137 |
-
.. code-block:: jinja
|
138 |
-
|
139 |
-
{% block title %}
|
140 |
-
{{ page_title|title }}
|
141 |
-
{% endblock %}
|
142 |
-
|
143 |
-
.. code-block:: jinja
|
144 |
-
|
145 |
-
{% block title page_title|title %}
|
146 |
-
|
147 |
-
Dynamic Inheritance
|
148 |
-
-------------------
|
149 |
-
|
150 |
-
Twig supports dynamic inheritance by using a variable as the base template:
|
151 |
-
|
152 |
-
.. code-block:: jinja
|
153 |
-
|
154 |
-
{% extends some_var %}
|
155 |
-
|
156 |
-
If the variable evaluates to a ``Twig_Template`` or a ``Twig_TemplateWrapper``
|
157 |
-
instance, Twig will use it as the parent template::
|
158 |
-
|
159 |
-
// {% extends layout %}
|
160 |
-
|
161 |
-
// deprecated as of Twig 1.28
|
162 |
-
$layout = $twig->loadTemplate('some_layout_template.twig');
|
163 |
-
|
164 |
-
// as of Twig 1.28
|
165 |
-
$layout = $twig->load('some_layout_template.twig');
|
166 |
-
|
167 |
-
$twig->display('template.twig', array('layout' => $layout));
|
168 |
-
|
169 |
-
.. versionadded:: 1.2
|
170 |
-
The possibility to pass an array of templates has been added in Twig 1.2.
|
171 |
-
|
172 |
-
You can also provide a list of templates that are checked for existence. The
|
173 |
-
first template that exists will be used as a parent:
|
174 |
-
|
175 |
-
.. code-block:: jinja
|
176 |
-
|
177 |
-
{% extends ['layout.html', 'base_layout.html'] %}
|
178 |
-
|
179 |
-
Conditional Inheritance
|
180 |
-
-----------------------
|
181 |
-
|
182 |
-
As the template name for the parent can be any valid Twig expression, it's
|
183 |
-
possible to make the inheritance mechanism conditional:
|
184 |
-
|
185 |
-
.. code-block:: jinja
|
186 |
-
|
187 |
-
{% extends standalone ? "minimum.html" : "base.html" %}
|
188 |
-
|
189 |
-
In this example, the template will extend the "minimum.html" layout template
|
190 |
-
if the ``standalone`` variable evaluates to ``true``, and "base.html"
|
191 |
-
otherwise.
|
192 |
-
|
193 |
-
How do blocks work?
|
194 |
-
-------------------
|
195 |
-
|
196 |
-
A block provides a way to change how a certain part of a template is rendered
|
197 |
-
but it does not interfere in any way with the logic around it.
|
198 |
-
|
199 |
-
Let's take the following example to illustrate how a block works and more
|
200 |
-
importantly, how it does not work:
|
201 |
-
|
202 |
-
.. code-block:: jinja
|
203 |
-
|
204 |
-
{# base.twig #}
|
205 |
-
|
206 |
-
{% for post in posts %}
|
207 |
-
{% block post %}
|
208 |
-
<h1>{{ post.title }}</h1>
|
209 |
-
<p>{{ post.body }}</p>
|
210 |
-
{% endblock %}
|
211 |
-
{% endfor %}
|
212 |
-
|
213 |
-
If you render this template, the result would be exactly the same with or
|
214 |
-
without the ``block`` tag. The ``block`` inside the ``for`` loop is just a way
|
215 |
-
to make it overridable by a child template:
|
216 |
-
|
217 |
-
.. code-block:: jinja
|
218 |
-
|
219 |
-
{# child.twig #}
|
220 |
-
|
221 |
-
{% extends "base.twig" %}
|
222 |
-
|
223 |
-
{% block post %}
|
224 |
-
<article>
|
225 |
-
<header>{{ post.title }}</header>
|
226 |
-
<section>{{ post.text }}</section>
|
227 |
-
</article>
|
228 |
-
{% endblock %}
|
229 |
-
|
230 |
-
Now, when rendering the child template, the loop is going to use the block
|
231 |
-
defined in the child template instead of the one defined in the base one; the
|
232 |
-
executed template is then equivalent to the following one:
|
233 |
-
|
234 |
-
.. code-block:: jinja
|
235 |
-
|
236 |
-
{% for post in posts %}
|
237 |
-
<article>
|
238 |
-
<header>{{ post.title }}</header>
|
239 |
-
<section>{{ post.text }}</section>
|
240 |
-
</article>
|
241 |
-
{% endfor %}
|
242 |
-
|
243 |
-
Let's take another example: a block included within an ``if`` statement:
|
244 |
-
|
245 |
-
.. code-block:: jinja
|
246 |
-
|
247 |
-
{% if posts is empty %}
|
248 |
-
{% block head %}
|
249 |
-
{{ parent() }}
|
250 |
-
|
251 |
-
<meta name="robots" content="noindex, follow">
|
252 |
-
{% endblock head %}
|
253 |
-
{% endif %}
|
254 |
-
|
255 |
-
Contrary to what you might think, this template does not define a block
|
256 |
-
conditionally; it just makes overridable by a child template the output of
|
257 |
-
what will be rendered when the condition is ``true``.
|
258 |
-
|
259 |
-
If you want the output to be displayed conditionally, use the following
|
260 |
-
instead:
|
261 |
-
|
262 |
-
.. code-block:: jinja
|
263 |
-
|
264 |
-
{% block head %}
|
265 |
-
{{ parent() }}
|
266 |
-
|
267 |
-
{% if posts is empty %}
|
268 |
-
<meta name="robots" content="noindex, follow">
|
269 |
-
{% endif %}
|
270 |
-
{% endblock head %}
|
271 |
-
|
272 |
-
.. seealso:: :doc:`block<../functions/block>`, :doc:`block<../tags/block>`, :doc:`parent<../functions/parent>`, :doc:`use<../tags/use>`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/tags/filter.rst
DELETED
@@ -1,21 +0,0 @@
|
|
1 |
-
``filter``
|
2 |
-
==========
|
3 |
-
|
4 |
-
Filter sections allow you to apply regular Twig filters on a block of template
|
5 |
-
data. Just wrap the code in the special ``filter`` section:
|
6 |
-
|
7 |
-
.. code-block:: jinja
|
8 |
-
|
9 |
-
{% filter upper %}
|
10 |
-
This text becomes uppercase
|
11 |
-
{% endfilter %}
|
12 |
-
|
13 |
-
You can also chain filters:
|
14 |
-
|
15 |
-
.. code-block:: jinja
|
16 |
-
|
17 |
-
{% filter lower|escape %}
|
18 |
-
<strong>SOME TEXT</strong>
|
19 |
-
{% endfilter %}
|
20 |
-
|
21 |
-
{# outputs "<strong>some text</strong>" #}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/tags/flush.rst
DELETED
@@ -1,17 +0,0 @@
|
|
1 |
-
``flush``
|
2 |
-
=========
|
3 |
-
|
4 |
-
.. versionadded:: 1.5
|
5 |
-
The flush tag was added in Twig 1.5.
|
6 |
-
|
7 |
-
The ``flush`` tag tells Twig to flush the output buffer:
|
8 |
-
|
9 |
-
.. code-block:: jinja
|
10 |
-
|
11 |
-
{% flush %}
|
12 |
-
|
13 |
-
.. note::
|
14 |
-
|
15 |
-
Internally, Twig uses the PHP `flush`_ function.
|
16 |
-
|
17 |
-
.. _`flush`: http://php.net/flush
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/tags/for.rst
DELETED
@@ -1,172 +0,0 @@
|
|
1 |
-
``for``
|
2 |
-
=======
|
3 |
-
|
4 |
-
Loop over each item in a sequence. For example, to display a list of users
|
5 |
-
provided in a variable called ``users``:
|
6 |
-
|
7 |
-
.. code-block:: jinja
|
8 |
-
|
9 |
-
<h1>Members</h1>
|
10 |
-
<ul>
|
11 |
-
{% for user in users %}
|
12 |
-
<li>{{ user.username|e }}</li>
|
13 |
-
{% endfor %}
|
14 |
-
</ul>
|
15 |
-
|
16 |
-
.. note::
|
17 |
-
|
18 |
-
A sequence can be either an array or an object implementing the
|
19 |
-
``Traversable`` interface.
|
20 |
-
|
21 |
-
If you do need to iterate over a sequence of numbers, you can use the ``..``
|
22 |
-
operator:
|
23 |
-
|
24 |
-
.. code-block:: jinja
|
25 |
-
|
26 |
-
{% for i in 0..10 %}
|
27 |
-
* {{ i }}
|
28 |
-
{% endfor %}
|
29 |
-
|
30 |
-
The above snippet of code would print all numbers from 0 to 10.
|
31 |
-
|
32 |
-
It can be also useful with letters:
|
33 |
-
|
34 |
-
.. code-block:: jinja
|
35 |
-
|
36 |
-
{% for letter in 'a'..'z' %}
|
37 |
-
* {{ letter }}
|
38 |
-
{% endfor %}
|
39 |
-
|
40 |
-
The ``..`` operator can take any expression at both sides:
|
41 |
-
|
42 |
-
.. code-block:: jinja
|
43 |
-
|
44 |
-
{% for letter in 'a'|upper..'z'|upper %}
|
45 |
-
* {{ letter }}
|
46 |
-
{% endfor %}
|
47 |
-
|
48 |
-
.. tip:
|
49 |
-
|
50 |
-
If you need a step different from 1, you can use the ``range`` function
|
51 |
-
instead.
|
52 |
-
|
53 |
-
The `loop` variable
|
54 |
-
-------------------
|
55 |
-
|
56 |
-
Inside of a ``for`` loop block you can access some special variables:
|
57 |
-
|
58 |
-
===================== =============================================================
|
59 |
-
Variable Description
|
60 |
-
===================== =============================================================
|
61 |
-
``loop.index`` The current iteration of the loop. (1 indexed)
|
62 |
-
``loop.index0`` The current iteration of the loop. (0 indexed)
|
63 |
-
``loop.revindex`` The number of iterations from the end of the loop (1 indexed)
|
64 |
-
``loop.revindex0`` The number of iterations from the end of the loop (0 indexed)
|
65 |
-
``loop.first`` True if first iteration
|
66 |
-
``loop.last`` True if last iteration
|
67 |
-
``loop.length`` The number of items in the sequence
|
68 |
-
``loop.parent`` The parent context
|
69 |
-
===================== =============================================================
|
70 |
-
|
71 |
-
.. code-block:: jinja
|
72 |
-
|
73 |
-
{% for user in users %}
|
74 |
-
{{ loop.index }} - {{ user.username }}
|
75 |
-
{% endfor %}
|
76 |
-
|
77 |
-
.. note::
|
78 |
-
|
79 |
-
The ``loop.length``, ``loop.revindex``, ``loop.revindex0``, and
|
80 |
-
``loop.last`` variables are only available for PHP arrays, or objects that
|
81 |
-
implement the ``Countable`` interface. They are also not available when
|
82 |
-
looping with a condition.
|
83 |
-
|
84 |
-
.. versionadded:: 1.2
|
85 |
-
The ``if`` modifier support has been added in Twig 1.2.
|
86 |
-
|
87 |
-
Adding a condition
|
88 |
-
------------------
|
89 |
-
|
90 |
-
Unlike in PHP, it's not possible to ``break`` or ``continue`` in a loop. You
|
91 |
-
can however filter the sequence during iteration which allows you to skip
|
92 |
-
items. The following example skips all the users which are not active:
|
93 |
-
|
94 |
-
.. code-block:: jinja
|
95 |
-
|
96 |
-
<ul>
|
97 |
-
{% for user in users if user.active %}
|
98 |
-
<li>{{ user.username|e }}</li>
|
99 |
-
{% endfor %}
|
100 |
-
</ul>
|
101 |
-
|
102 |
-
The advantage is that the special loop variable will count correctly thus not
|
103 |
-
counting the users not iterated over. Keep in mind that properties like
|
104 |
-
``loop.last`` will not be defined when using loop conditions.
|
105 |
-
|
106 |
-
.. note::
|
107 |
-
|
108 |
-
Using the ``loop`` variable within the condition is not recommended as it
|
109 |
-
will probably not be doing what you expect it to. For instance, adding a
|
110 |
-
condition like ``loop.index > 4`` won't work as the index is only
|
111 |
-
incremented when the condition is true (so the condition will never
|
112 |
-
match).
|
113 |
-
|
114 |
-
The `else` Clause
|
115 |
-
-----------------
|
116 |
-
|
117 |
-
If no iteration took place because the sequence was empty, you can render a
|
118 |
-
replacement block by using ``else``:
|
119 |
-
|
120 |
-
.. code-block:: jinja
|
121 |
-
|
122 |
-
<ul>
|
123 |
-
{% for user in users %}
|
124 |
-
<li>{{ user.username|e }}</li>
|
125 |
-
{% else %}
|
126 |
-
<li><em>no user found</em></li>
|
127 |
-
{% endfor %}
|
128 |
-
</ul>
|
129 |
-
|
130 |
-
Iterating over Keys
|
131 |
-
-------------------
|
132 |
-
|
133 |
-
By default, a loop iterates over the values of the sequence. You can iterate
|
134 |
-
on keys by using the ``keys`` filter:
|
135 |
-
|
136 |
-
.. code-block:: jinja
|
137 |
-
|
138 |
-
<h1>Members</h1>
|
139 |
-
<ul>
|
140 |
-
{% for key in users|keys %}
|
141 |
-
<li>{{ key }}</li>
|
142 |
-
{% endfor %}
|
143 |
-
</ul>
|
144 |
-
|
145 |
-
Iterating over Keys and Values
|
146 |
-
------------------------------
|
147 |
-
|
148 |
-
You can also access both keys and values:
|
149 |
-
|
150 |
-
.. code-block:: jinja
|
151 |
-
|
152 |
-
<h1>Members</h1>
|
153 |
-
<ul>
|
154 |
-
{% for key, user in users %}
|
155 |
-
<li>{{ key }}: {{ user.username|e }}</li>
|
156 |
-
{% endfor %}
|
157 |
-
</ul>
|
158 |
-
|
159 |
-
Iterating over a Subset
|
160 |
-
-----------------------
|
161 |
-
|
162 |
-
You might want to iterate over a subset of values. This can be achieved using
|
163 |
-
the :doc:`slice <../filters/slice>` filter:
|
164 |
-
|
165 |
-
.. code-block:: jinja
|
166 |
-
|
167 |
-
<h1>Top Ten Members</h1>
|
168 |
-
<ul>
|
169 |
-
{% for user in users|slice(0, 10) %}
|
170 |
-
<li>{{ user.username|e }}</li>
|
171 |
-
{% endfor %}
|
172 |
-
</ul>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/tags/from.rst
DELETED
@@ -1,8 +0,0 @@
|
|
1 |
-
``from``
|
2 |
-
========
|
3 |
-
|
4 |
-
The ``from`` tag imports :doc:`macro<../tags/macro>` names into the current
|
5 |
-
namespace. The tag is documented in detail in the documentation for the
|
6 |
-
:doc:`import<../tags/import>` tag.
|
7 |
-
|
8 |
-
.. seealso:: :doc:`macro<../tags/macro>`, :doc:`import<../tags/import>`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/tags/if.rst
DELETED
@@ -1,76 +0,0 @@
|
|
1 |
-
``if``
|
2 |
-
======
|
3 |
-
|
4 |
-
The ``if`` statement in Twig is comparable with the if statements of PHP.
|
5 |
-
|
6 |
-
In the simplest form you can use it to test if an expression evaluates to
|
7 |
-
``true``:
|
8 |
-
|
9 |
-
.. code-block:: jinja
|
10 |
-
|
11 |
-
{% if online == false %}
|
12 |
-
<p>Our website is in maintenance mode. Please, come back later.</p>
|
13 |
-
{% endif %}
|
14 |
-
|
15 |
-
You can also test if an array is not empty:
|
16 |
-
|
17 |
-
.. code-block:: jinja
|
18 |
-
|
19 |
-
{% if users %}
|
20 |
-
<ul>
|
21 |
-
{% for user in users %}
|
22 |
-
<li>{{ user.username|e }}</li>
|
23 |
-
{% endfor %}
|
24 |
-
</ul>
|
25 |
-
{% endif %}
|
26 |
-
|
27 |
-
.. note::
|
28 |
-
|
29 |
-
If you want to test if the variable is defined, use ``if users is
|
30 |
-
defined`` instead.
|
31 |
-
|
32 |
-
You can also use ``not`` to check for values that evaluate to ``false``:
|
33 |
-
|
34 |
-
.. code-block:: jinja
|
35 |
-
|
36 |
-
{% if not user.subscribed %}
|
37 |
-
<p>You are not subscribed to our mailing list.</p>
|
38 |
-
{% endif %}
|
39 |
-
|
40 |
-
For multiple conditions, ``and`` and ``or`` can be used:
|
41 |
-
|
42 |
-
.. code-block:: jinja
|
43 |
-
|
44 |
-
{% if temperature > 18 and temperature < 27 %}
|
45 |
-
<p>It's a nice day for a walk in the park.</p>
|
46 |
-
{% endif %}
|
47 |
-
|
48 |
-
For multiple branches ``elseif`` and ``else`` can be used like in PHP. You can
|
49 |
-
use more complex ``expressions`` there too:
|
50 |
-
|
51 |
-
.. code-block:: jinja
|
52 |
-
|
53 |
-
{% if kenny.sick %}
|
54 |
-
Kenny is sick.
|
55 |
-
{% elseif kenny.dead %}
|
56 |
-
You killed Kenny! You bastard!!!
|
57 |
-
{% else %}
|
58 |
-
Kenny looks okay --- so far
|
59 |
-
{% endif %}
|
60 |
-
|
61 |
-
.. note::
|
62 |
-
|
63 |
-
The rules to determine if an expression is ``true`` or ``false`` are the
|
64 |
-
same as in PHP; here are the edge cases rules:
|
65 |
-
|
66 |
-
====================== ====================
|
67 |
-
Value Boolean evaluation
|
68 |
-
====================== ====================
|
69 |
-
empty string false
|
70 |
-
numeric zero false
|
71 |
-
whitespace-only string true
|
72 |
-
empty array false
|
73 |
-
null false
|
74 |
-
non-empty array true
|
75 |
-
object true
|
76 |
-
====================== ====================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/tags/import.rst
DELETED
@@ -1,57 +0,0 @@
|
|
1 |
-
``import``
|
2 |
-
==========
|
3 |
-
|
4 |
-
Twig supports putting often used code into :doc:`macros<../tags/macro>`. These
|
5 |
-
macros can go into different templates and get imported from there.
|
6 |
-
|
7 |
-
There are two ways to import templates. You can import the complete template
|
8 |
-
into a variable or request specific macros from it.
|
9 |
-
|
10 |
-
Imagine we have a helper module that renders forms (called ``forms.html``):
|
11 |
-
|
12 |
-
.. code-block:: jinja
|
13 |
-
|
14 |
-
{% macro input(name, value, type, size) %}
|
15 |
-
<input type="{{ type|default('text') }}" name="{{ name }}" value="{{ value|e }}" size="{{ size|default(20) }}" />
|
16 |
-
{% endmacro %}
|
17 |
-
|
18 |
-
{% macro textarea(name, value, rows, cols) %}
|
19 |
-
<textarea name="{{ name }}" rows="{{ rows|default(10) }}" cols="{{ cols|default(40) }}">{{ value|e }}</textarea>
|
20 |
-
{% endmacro %}
|
21 |
-
|
22 |
-
The easiest and most flexible is importing the whole module into a variable.
|
23 |
-
That way you can access the attributes:
|
24 |
-
|
25 |
-
.. code-block:: jinja
|
26 |
-
|
27 |
-
{% import 'forms.html' as forms %}
|
28 |
-
|
29 |
-
<dl>
|
30 |
-
<dt>Username</dt>
|
31 |
-
<dd>{{ forms.input('username') }}</dd>
|
32 |
-
<dt>Password</dt>
|
33 |
-
<dd>{{ forms.input('password', null, 'password') }}</dd>
|
34 |
-
</dl>
|
35 |
-
<p>{{ forms.textarea('comment') }}</p>
|
36 |
-
|
37 |
-
Alternatively you can import names from the template into the current
|
38 |
-
namespace:
|
39 |
-
|
40 |
-
.. code-block:: jinja
|
41 |
-
|
42 |
-
{% from 'forms.html' import input as input_field, textarea %}
|
43 |
-
|
44 |
-
<dl>
|
45 |
-
<dt>Username</dt>
|
46 |
-
<dd>{{ input_field('username') }}</dd>
|
47 |
-
<dt>Password</dt>
|
48 |
-
<dd>{{ input_field('password', '', 'password') }}</dd>
|
49 |
-
</dl>
|
50 |
-
<p>{{ textarea('comment') }}</p>
|
51 |
-
|
52 |
-
.. tip::
|
53 |
-
|
54 |
-
To import macros from the current file, use the special ``_self`` variable
|
55 |
-
for the source.
|
56 |
-
|
57 |
-
.. seealso:: :doc:`macro<../tags/macro>`, :doc:`from<../tags/from>`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/tags/include.rst
DELETED
@@ -1,90 +0,0 @@
|
|
1 |
-
``include``
|
2 |
-
===========
|
3 |
-
|
4 |
-
The ``include`` statement includes a template and returns the rendered content
|
5 |
-
of that file into the current namespace:
|
6 |
-
|
7 |
-
.. code-block:: jinja
|
8 |
-
|
9 |
-
{% include 'header.html' %}
|
10 |
-
Body
|
11 |
-
{% include 'footer.html' %}
|
12 |
-
|
13 |
-
Included templates have access to the variables of the active context.
|
14 |
-
|
15 |
-
If you are using the filesystem loader, the templates are looked for in the
|
16 |
-
paths defined by it.
|
17 |
-
|
18 |
-
You can add additional variables by passing them after the ``with`` keyword:
|
19 |
-
|
20 |
-
.. code-block:: jinja
|
21 |
-
|
22 |
-
{# template.html will have access to the variables from the current context and the additional ones provided #}
|
23 |
-
{% include 'template.html' with {'foo': 'bar'} %}
|
24 |
-
|
25 |
-
{% set vars = {'foo': 'bar'} %}
|
26 |
-
{% include 'template.html' with vars %}
|
27 |
-
|
28 |
-
You can disable access to the context by appending the ``only`` keyword:
|
29 |
-
|
30 |
-
.. code-block:: jinja
|
31 |
-
|
32 |
-
{# only the foo variable will be accessible #}
|
33 |
-
{% include 'template.html' with {'foo': 'bar'} only %}
|
34 |
-
|
35 |
-
.. code-block:: jinja
|
36 |
-
|
37 |
-
{# no variables will be accessible #}
|
38 |
-
{% include 'template.html' only %}
|
39 |
-
|
40 |
-
.. tip::
|
41 |
-
|
42 |
-
When including a template created by an end user, you should consider
|
43 |
-
sandboxing it. More information in the :doc:`Twig for Developers<../api>`
|
44 |
-
chapter and in the :doc:`sandbox<../tags/sandbox>` tag documentation.
|
45 |
-
|
46 |
-
The template name can be any valid Twig expression:
|
47 |
-
|
48 |
-
.. code-block:: jinja
|
49 |
-
|
50 |
-
{% include some_var %}
|
51 |
-
{% include ajax ? 'ajax.html' : 'not_ajax.html' %}
|
52 |
-
|
53 |
-
And if the expression evaluates to a ``Twig_Template`` or a
|
54 |
-
``Twig_TemplateWrapper`` instance, Twig will use it directly::
|
55 |
-
|
56 |
-
// {% include template %}
|
57 |
-
|
58 |
-
// deprecated as of Twig 1.28
|
59 |
-
$template = $twig->loadTemplate('some_template.twig');
|
60 |
-
|
61 |
-
// as of Twig 1.28
|
62 |
-
$template = $twig->load('some_template.twig');
|
63 |
-
|
64 |
-
$twig->display('template.twig', array('template' => $template));
|
65 |
-
|
66 |
-
.. versionadded:: 1.2
|
67 |
-
The ``ignore missing`` feature has been added in Twig 1.2.
|
68 |
-
|
69 |
-
You can mark an include with ``ignore missing`` in which case Twig will ignore
|
70 |
-
the statement if the template to be included does not exist. It has to be
|
71 |
-
placed just after the template name. Here some valid examples:
|
72 |
-
|
73 |
-
.. code-block:: jinja
|
74 |
-
|
75 |
-
{% include 'sidebar.html' ignore missing %}
|
76 |
-
{% include 'sidebar.html' ignore missing with {'foo': 'bar'} %}
|
77 |
-
{% include 'sidebar.html' ignore missing only %}
|
78 |
-
|
79 |
-
.. versionadded:: 1.2
|
80 |
-
The possibility to pass an array of templates has been added in Twig 1.2.
|
81 |
-
|
82 |
-
You can also provide a list of templates that are checked for existence before
|
83 |
-
inclusion. The first template that exists will be included:
|
84 |
-
|
85 |
-
.. code-block:: jinja
|
86 |
-
|
87 |
-
{% include ['page_detailed.html', 'page.html'] %}
|
88 |
-
|
89 |
-
If ``ignore missing`` is given, it will fall back to rendering nothing if none
|
90 |
-
of the templates exist, otherwise it will throw an exception.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/tags/index.rst
DELETED
@@ -1,25 +0,0 @@
|
|
1 |
-
Tags
|
2 |
-
====
|
3 |
-
|
4 |
-
.. toctree::
|
5 |
-
:maxdepth: 1
|
6 |
-
|
7 |
-
autoescape
|
8 |
-
block
|
9 |
-
do
|
10 |
-
embed
|
11 |
-
extends
|
12 |
-
filter
|
13 |
-
flush
|
14 |
-
for
|
15 |
-
from
|
16 |
-
if
|
17 |
-
import
|
18 |
-
include
|
19 |
-
macro
|
20 |
-
sandbox
|
21 |
-
set
|
22 |
-
spaceless
|
23 |
-
use
|
24 |
-
verbatim
|
25 |
-
with
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/tags/macro.rst
DELETED
@@ -1,103 +0,0 @@
|
|
1 |
-
``macro``
|
2 |
-
=========
|
3 |
-
|
4 |
-
Macros are comparable with functions in regular programming languages. They
|
5 |
-
are useful to put often used HTML idioms into reusable elements to not repeat
|
6 |
-
yourself.
|
7 |
-
|
8 |
-
Here is a small example of a macro that renders a form element:
|
9 |
-
|
10 |
-
.. code-block:: jinja
|
11 |
-
|
12 |
-
{% macro input(name, value, type, size) %}
|
13 |
-
<input type="{{ type|default('text') }}" name="{{ name }}" value="{{ value|e }}" size="{{ size|default(20) }}" />
|
14 |
-
{% endmacro %}
|
15 |
-
|
16 |
-
Macros differ from native PHP functions in a few ways:
|
17 |
-
|
18 |
-
* Default argument values are defined by using the ``default`` filter in the
|
19 |
-
macro body;
|
20 |
-
|
21 |
-
* Arguments of a macro are always optional.
|
22 |
-
|
23 |
-
* If extra positional arguments are passed to a macro, they end up in the
|
24 |
-
special ``varargs`` variable as a list of values.
|
25 |
-
|
26 |
-
But as with PHP functions, macros don't have access to the current template
|
27 |
-
variables.
|
28 |
-
|
29 |
-
.. tip::
|
30 |
-
|
31 |
-
You can pass the whole context as an argument by using the special
|
32 |
-
``_context`` variable.
|
33 |
-
|
34 |
-
Import
|
35 |
-
------
|
36 |
-
|
37 |
-
Macros can be defined in any template, and need to be "imported" before being
|
38 |
-
used (see the documentation for the :doc:`import<../tags/import>` tag for more
|
39 |
-
information):
|
40 |
-
|
41 |
-
.. code-block:: jinja
|
42 |
-
|
43 |
-
{% import "forms.html" as forms %}
|
44 |
-
|
45 |
-
The above ``import`` call imports the "forms.html" file (which can contain only
|
46 |
-
macros, or a template and some macros), and import the functions as items of
|
47 |
-
the ``forms`` variable.
|
48 |
-
|
49 |
-
The macro can then be called at will:
|
50 |
-
|
51 |
-
.. code-block:: jinja
|
52 |
-
|
53 |
-
<p>{{ forms.input('username') }}</p>
|
54 |
-
<p>{{ forms.input('password', null, 'password') }}</p>
|
55 |
-
|
56 |
-
If macros are defined and used in the same template, you can use the
|
57 |
-
special ``_self`` variable to import them:
|
58 |
-
|
59 |
-
.. code-block:: jinja
|
60 |
-
|
61 |
-
{% import _self as forms %}
|
62 |
-
|
63 |
-
<p>{{ forms.input('username') }}</p>
|
64 |
-
|
65 |
-
.. warning::
|
66 |
-
|
67 |
-
When you define a macro in the template where you are going to use it, you
|
68 |
-
might be tempted to call the macro directly via ``_self.input()`` instead
|
69 |
-
of importing it; even if seems to work, this is just a side-effect of the
|
70 |
-
current implementation and it won't work anymore in Twig 2.x.
|
71 |
-
|
72 |
-
When you want to use a macro in another macro from the same file, you need to
|
73 |
-
import it locally:
|
74 |
-
|
75 |
-
.. code-block:: jinja
|
76 |
-
|
77 |
-
{% macro input(name, value, type, size) %}
|
78 |
-
<input type="{{ type|default('text') }}" name="{{ name }}" value="{{ value|e }}" size="{{ size|default(20) }}" />
|
79 |
-
{% endmacro %}
|
80 |
-
|
81 |
-
{% macro wrapped_input(name, value, type, size) %}
|
82 |
-
{% import _self as forms %}
|
83 |
-
|
84 |
-
<div class="field">
|
85 |
-
{{ forms.input(name, value, type, size) }}
|
86 |
-
</div>
|
87 |
-
{% endmacro %}
|
88 |
-
|
89 |
-
Named Macro End-Tags
|
90 |
-
--------------------
|
91 |
-
|
92 |
-
Twig allows you to put the name of the macro after the end tag for better
|
93 |
-
readability:
|
94 |
-
|
95 |
-
.. code-block:: jinja
|
96 |
-
|
97 |
-
{% macro input() %}
|
98 |
-
...
|
99 |
-
{% endmacro input %}
|
100 |
-
|
101 |
-
Of course, the name after the ``endmacro`` word must match the macro name.
|
102 |
-
|
103 |
-
.. seealso:: :doc:`from<../tags/from>`, :doc:`import<../tags/import>`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/tags/sandbox.rst
DELETED
@@ -1,30 +0,0 @@
|
|
1 |
-
``sandbox``
|
2 |
-
===========
|
3 |
-
|
4 |
-
The ``sandbox`` tag can be used to enable the sandboxing mode for an included
|
5 |
-
template, when sandboxing is not enabled globally for the Twig environment:
|
6 |
-
|
7 |
-
.. code-block:: jinja
|
8 |
-
|
9 |
-
{% sandbox %}
|
10 |
-
{% include 'user.html' %}
|
11 |
-
{% endsandbox %}
|
12 |
-
|
13 |
-
.. warning::
|
14 |
-
|
15 |
-
The ``sandbox`` tag is only available when the sandbox extension is
|
16 |
-
enabled (see the :doc:`Twig for Developers<../api>` chapter).
|
17 |
-
|
18 |
-
.. note::
|
19 |
-
|
20 |
-
The ``sandbox`` tag can only be used to sandbox an include tag and it
|
21 |
-
cannot be used to sandbox a section of a template. The following example
|
22 |
-
won't work:
|
23 |
-
|
24 |
-
.. code-block:: jinja
|
25 |
-
|
26 |
-
{% sandbox %}
|
27 |
-
{% for i in 1..2 %}
|
28 |
-
{{ i }}
|
29 |
-
{% endfor %}
|
30 |
-
{% endsandbox %}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/tags/set.rst
DELETED
@@ -1,78 +0,0 @@
|
|
1 |
-
``set``
|
2 |
-
=======
|
3 |
-
|
4 |
-
Inside code blocks you can also assign values to variables. Assignments use
|
5 |
-
the ``set`` tag and can have multiple targets.
|
6 |
-
|
7 |
-
Here is how you can assign the ``bar`` value to the ``foo`` variable:
|
8 |
-
|
9 |
-
.. code-block:: jinja
|
10 |
-
|
11 |
-
{% set foo = 'bar' %}
|
12 |
-
|
13 |
-
After the ``set`` call, the ``foo`` variable is available in the template like
|
14 |
-
any other ones:
|
15 |
-
|
16 |
-
.. code-block:: jinja
|
17 |
-
|
18 |
-
{# displays bar #}
|
19 |
-
{{ foo }}
|
20 |
-
|
21 |
-
The assigned value can be any valid :ref:`Twig expressions
|
22 |
-
<twig-expressions>`:
|
23 |
-
|
24 |
-
.. code-block:: jinja
|
25 |
-
|
26 |
-
{% set foo = [1, 2] %}
|
27 |
-
{% set foo = {'foo': 'bar'} %}
|
28 |
-
{% set foo = 'foo' ~ 'bar' %}
|
29 |
-
|
30 |
-
Several variables can be assigned in one block:
|
31 |
-
|
32 |
-
.. code-block:: jinja
|
33 |
-
|
34 |
-
{% set foo, bar = 'foo', 'bar' %}
|
35 |
-
|
36 |
-
{# is equivalent to #}
|
37 |
-
|
38 |
-
{% set foo = 'foo' %}
|
39 |
-
{% set bar = 'bar' %}
|
40 |
-
|
41 |
-
The ``set`` tag can also be used to 'capture' chunks of text:
|
42 |
-
|
43 |
-
.. code-block:: jinja
|
44 |
-
|
45 |
-
{% set foo %}
|
46 |
-
<div id="pagination">
|
47 |
-
...
|
48 |
-
</div>
|
49 |
-
{% endset %}
|
50 |
-
|
51 |
-
.. caution::
|
52 |
-
|
53 |
-
If you enable automatic output escaping, Twig will only consider the
|
54 |
-
content to be safe when capturing chunks of text.
|
55 |
-
|
56 |
-
.. note::
|
57 |
-
|
58 |
-
Note that loops are scoped in Twig; therefore a variable declared inside a
|
59 |
-
``for`` loop is not accessible outside the loop itself:
|
60 |
-
|
61 |
-
.. code-block:: jinja
|
62 |
-
|
63 |
-
{% for item in list %}
|
64 |
-
{% set foo = item %}
|
65 |
-
{% endfor %}
|
66 |
-
|
67 |
-
{# foo is NOT available #}
|
68 |
-
|
69 |
-
If you want to access the variable, just declare it before the loop:
|
70 |
-
|
71 |
-
.. code-block:: jinja
|
72 |
-
|
73 |
-
{% set foo = "" %}
|
74 |
-
{% for item in list %}
|
75 |
-
{% set foo = item %}
|
76 |
-
{% endfor %}
|
77 |
-
|
78 |
-
{# foo is available #}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/tags/spaceless.rst
DELETED
@@ -1,37 +0,0 @@
|
|
1 |
-
``spaceless``
|
2 |
-
=============
|
3 |
-
|
4 |
-
Use the ``spaceless`` tag to remove whitespace *between HTML tags*, not
|
5 |
-
whitespace within HTML tags or whitespace in plain text:
|
6 |
-
|
7 |
-
.. code-block:: jinja
|
8 |
-
|
9 |
-
{% spaceless %}
|
10 |
-
<div>
|
11 |
-
<strong>foo</strong>
|
12 |
-
</div>
|
13 |
-
{% endspaceless %}
|
14 |
-
|
15 |
-
{# output will be <div><strong>foo</strong></div> #}
|
16 |
-
|
17 |
-
This tag is not meant to "optimize" the size of the generated HTML content but
|
18 |
-
merely to avoid extra whitespace between HTML tags to avoid browser rendering
|
19 |
-
quirks under some circumstances.
|
20 |
-
|
21 |
-
.. tip::
|
22 |
-
|
23 |
-
If you want to optimize the size of the generated HTML content, gzip
|
24 |
-
compress the output instead.
|
25 |
-
|
26 |
-
.. tip::
|
27 |
-
|
28 |
-
If you want to create a tag that actually removes all extra whitespace in
|
29 |
-
an HTML string, be warned that this is not as easy as it seems to be
|
30 |
-
(think of ``textarea`` or ``pre`` tags for instance). Using a third-party
|
31 |
-
library like Tidy is probably a better idea.
|
32 |
-
|
33 |
-
.. tip::
|
34 |
-
|
35 |
-
For more information on whitespace control, read the
|
36 |
-
:ref:`dedicated section <templates-whitespace-control>` of the documentation and learn how
|
37 |
-
you can also use the whitespace control modifier on your tags.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
library/twig/twig/doc/tags/use.rst
DELETED
@@ -1,124 +0,0 @@
|
|
1 |
-
``use``
|
2 |
-
=======
|
3 |
-
|
4 |
-
.. versionadded:: 1.1
|
5 |
-
Horizontal reuse was added in Twig 1.1.
|
6 |
-
|
7 |
-
.. note::
|
8 |
-
|
9 |
-
Horizontal reuse is an advanced Twig feature that is hardly ever needed in
|
10 |
-
regular templates. It is mainly used by projects that need to make
|
11 |
-
template blocks reusable without using inheritance.
|
12 |
-
|
13 |
-
Template inheritance is one of the most powerful features of Twig but it is
|
14 |
-
limited to single inheritance; a template can only extend one other template.
|
15 |
-
This limitation makes template inheritance simple to understand and easy to
|
16 |
-
debug:
|
17 |
-
|
18 |
-
.. code-block:: jinja
|
19 |
-
|
20 |
-
{% extends "base.html" %}
|
21 |
-
|
22 |
-
{% block title %}{% endblock %}
|
23 |
-
{% block content %}{% endblock %}
|
24 |
-
|
25 |
-
Horizontal reuse is a way to achieve the same goal as multiple inheritance,
|
26 |
-
but without the associated complexity:
|
27 |
-
|
28 |
-
.. code-block:: jinja
|
29 |
-
|
30 |
-
{% extends "base.html" %}
|
31 |
-
|
32 |
-
{% use "blocks.html" %}
|
33 |
-
|
34 |
-
{% block title %}{% endblock %}
|
35 |
-
{% block content %}{% endblock %}
|
36 |
-
|
37 |
-
The ``use`` statement tells Twig to import the blocks defined in
|
38 |
-
``blocks.html`` into the current template (it's like macros, but for blocks):
|
39 |
-
|
40 |
-
.. code-block:: jinja
|
41 |
-
|
42 |
-
{# blocks.html #}
|
43 |
-
|
44 |
-
{% block sidebar %}{% endblock %}
|
45 |
-
|
46 |
-
In this example, the ``use`` statement imports the ``sidebar`` block into the
|
47 |
-
main template. The code is mostly equivalent to the following one (the
|
48 |
-
imported blocks are not outputted automatically):
|
49 |
-
|
50 |
-
.. code-block:: jinja
|
51 |
-
|
52 |
-
{% extends "base.html" %}
|
53 |
-
|
54 |
-
{% block sidebar %}{% endblock %}
|
55 |
-
{% block title %}{% endblock %}
|
56 |
-
{% block content %}{% endblock %}
|
57 |
-
|
58 |
-
.. note::
|
59 |
-
|
60 |
-
The ``use`` tag only imports a template if it does not extend another
|
61 |
-
template, if it does not define macros, and if the body is empty. But it
|
62 |
-
can *use* other templates.
|
63 |
-
|
64 |
-
.. note::
|
65 |
-
|
66 |
-
Because ``use`` statements are resolved independently of the context
|
67 |
-
passed to the template, the template reference cannot be an expression.
|
68 |
-
|
69 |
-
The main template can also override any imported block. If the template
|
70 |
-
already defines the ``sidebar`` block, then the one defined in ``blocks.html``
|
71 |
-
is ignored. To avoid name conflicts, you can rename imported blocks:
|
72 |
-
|
73 |
-
.. code-block:: jinja
|
74 |
-
|
75 |
-
{% extends "base.html" %}
|
76 |
-
|
77 |
-
{% use "blocks.html" with sidebar as base_sid
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|