User registration & user profile – Profile Builder - Version 3.8.1

Version Description

  • Feature: added new field types: Heading, Input, Textarea, Select, Select2, Checkbox, Radio
  • Feature: added Avatar field which lets your users manage their avatar on your website from the Profile Builder forms
  • Feature: added Email Customizer feature which lets you edit all the emails that the plugin sends with custom content and tags for the defined fields
  • Fix: issue with licence activation not working on multisite
Download this release

Release Info

Developer raster02
Plugin Icon 128x128 User registration & user profile – Profile Builder
Version 3.8.1
Comparing to
See all releases

Code changes from version 3.8.0 to 3.8.1

Files changed (142) hide show
  1. admin/add-ons.php +7 -7
  2. admin/advanced-settings/includes/fields/remove-all-fields-from-backend.php +8 -0
  3. admin/advanced-settings/includes/forms/back-end-validation.php +4 -0
  4. admin/advanced-settings/includes/views/view-fields.php +40 -43
  5. admin/basic-info.php +9 -12
  6. admin/general-settings.php +43 -1
  7. admin/manage-fields.php +59 -12
  8. admin/register-version.php +1 -1
  9. assets/lib/Mustache/Autoloader.php +75 -0
  10. assets/lib/Mustache/Compiler.php +476 -0
  11. assets/lib/Mustache/Context.php +149 -0
  12. assets/lib/Mustache/Engine.php +748 -0
  13. assets/lib/Mustache/Exception.php +18 -0
  14. assets/lib/Mustache/Exception/InvalidArgumentException.php +18 -0
  15. assets/lib/Mustache/Exception/LogicException.php +18 -0
  16. assets/lib/Mustache/Exception/RuntimeException.php +18 -0
  17. assets/lib/Mustache/Exception/SyntaxException.php +29 -0
  18. assets/lib/Mustache/Exception/UnknownFilterException.php +29 -0
  19. assets/lib/Mustache/Exception/UnknownHelperException.php +29 -0
  20. assets/lib/Mustache/Exception/UnknownTemplateException.php +29 -0
  21. assets/lib/Mustache/HelperCollection.php +170 -0
  22. assets/lib/Mustache/LambdaHelper.php +49 -0
  23. assets/lib/Mustache/Loader.php +28 -0
  24. assets/lib/Mustache/Loader/ArrayLoader.php +78 -0
  25. assets/lib/Mustache/Loader/CascadingLoader.php +69 -0
  26. assets/lib/Mustache/Loader/FilesystemLoader.php +124 -0
  27. assets/lib/Mustache/Loader/InlineLoader.php +123 -0
  28. assets/lib/Mustache/Loader/MutableLoader.php +32 -0
  29. assets/lib/Mustache/Loader/StringLoader.php +40 -0
  30. assets/lib/Mustache/Logger.php +135 -0
  31. assets/lib/Mustache/Logger/AbstractLogger.php +121 -0
  32. assets/lib/Mustache/Logger/StreamLogger.php +193 -0
  33. assets/lib/Mustache/Parser.php +194 -0
  34. assets/lib/Mustache/Template.php +177 -0
  35. assets/lib/Mustache/Tokenizer.php +278 -0
  36. assets/lib/class-mustache-templates/class-mustache-templates.css +193 -0
  37. assets/lib/class-mustache-templates/class-mustache-templates.js +80 -0
  38. assets/lib/class-mustache-templates/class-mustache-templates.php +604 -0
  39. assets/lib/codemirror/LICENSE +19 -0
  40. assets/lib/codemirror/README.md +11 -0
  41. assets/lib/codemirror/addon/comment/comment.js +145 -0
  42. assets/lib/codemirror/addon/comment/continuecomment.js +54 -0
  43. assets/lib/codemirror/addon/dialog/dialog.css +32 -0
  44. assets/lib/codemirror/addon/dialog/dialog.js +80 -0
  45. assets/lib/codemirror/addon/display/fullscreen.css +6 -0
  46. assets/lib/codemirror/addon/display/fullscreen.js +30 -0
  47. assets/lib/codemirror/addon/display/placeholder.js +54 -0
  48. assets/lib/codemirror/addon/edit/closebrackets.js +82 -0
  49. assets/lib/codemirror/addon/edit/closetag.js +87 -0
  50. assets/lib/codemirror/addon/edit/continuelist.js +25 -0
  51. assets/lib/codemirror/addon/edit/matchbrackets.js +86 -0
  52. assets/lib/codemirror/addon/edit/matchtags.js +56 -0
  53. assets/lib/codemirror/addon/edit/trailingspace.js +15 -0
  54. assets/lib/codemirror/addon/fold/brace-fold.js +93 -0
  55. assets/lib/codemirror/addon/fold/comment-fold.js +40 -0
  56. assets/lib/codemirror/addon/fold/foldcode.js +73 -0
  57. assets/lib/codemirror/addon/fold/foldgutter.js +122 -0
  58. assets/lib/codemirror/addon/fold/indent-fold.js +12 -0
  59. assets/lib/codemirror/addon/fold/xml-fold.js +167 -0
  60. assets/lib/codemirror/addon/hint/anyword-hint.js +34 -0
  61. assets/lib/codemirror/addon/hint/css-hint.js +50 -0
  62. assets/lib/codemirror/addon/hint/html-hint.js +337 -0
  63. assets/lib/codemirror/addon/hint/javascript-hint.js +146 -0
  64. assets/lib/codemirror/addon/hint/pig-hint.js +119 -0
  65. assets/lib/codemirror/addon/hint/python-hint.js +95 -0
  66. assets/lib/codemirror/addon/hint/show-hint.css +38 -0
  67. assets/lib/codemirror/addon/hint/show-hint.js +274 -0
  68. assets/lib/codemirror/addon/hint/xml-hint.js +68 -0
  69. assets/lib/codemirror/addon/lint/coffeescript-lint.js +25 -0
  70. assets/lib/codemirror/addon/lint/css-lint.js +17 -0
  71. assets/lib/codemirror/addon/lint/javascript-lint.js +124 -0
  72. assets/lib/codemirror/addon/lint/json-lint.js +15 -0
  73. assets/lib/codemirror/addon/lint/lint.css +72 -0
  74. assets/lib/codemirror/addon/lint/lint.js +203 -0
  75. assets/lib/codemirror/addon/merge/dep/diff_match_patch.js +50 -0
  76. assets/lib/codemirror/addon/merge/merge.css +92 -0
  77. assets/lib/codemirror/addon/merge/merge.js +473 -0
  78. assets/lib/codemirror/addon/mode/loadmode.js +51 -0
  79. assets/lib/codemirror/addon/mode/multiplex.js +101 -0
  80. assets/lib/codemirror/addon/mode/multiplex_test.js +30 -0
  81. assets/lib/codemirror/addon/mode/overlay.js +59 -0
  82. assets/lib/codemirror/addon/runmode/colorize.js +29 -0
  83. assets/lib/codemirror/addon/runmode/runmode-standalone.js +132 -0
  84. assets/lib/codemirror/addon/runmode/runmode.js +56 -0
  85. assets/lib/codemirror/addon/runmode/runmode.node.js +103 -0
  86. assets/lib/codemirror/addon/scroll/scrollpastend.js +34 -0
  87. assets/lib/codemirror/addon/search/match-highlighter.js +91 -0
  88. assets/lib/codemirror/addon/search/search.js +131 -0
  89. assets/lib/codemirror/addon/search/searchcursor.js +143 -0
  90. assets/lib/codemirror/addon/selection/active-line.js +39 -0
  91. assets/lib/codemirror/addon/selection/mark-selection.js +108 -0
  92. assets/lib/codemirror/addon/tern/tern.css +85 -0
  93. assets/lib/codemirror/addon/tern/tern.js +631 -0
  94. assets/lib/codemirror/addon/tern/worker.js +39 -0
  95. assets/lib/codemirror/lib/codemirror.css +273 -0
  96. assets/lib/codemirror/lib/codemirror.js +5887 -0
  97. assets/lib/codemirror/mode/css/css.js +627 -0
  98. assets/lib/codemirror/mode/css/index.html +70 -0
  99. assets/lib/codemirror/mode/css/scss.html +157 -0
  100. assets/lib/codemirror/mode/css/scss_test.js +80 -0
  101. assets/lib/codemirror/mode/css/test.js +126 -0
  102. assets/lib/codemirror/mode/htmlembedded/htmlembedded.js +73 -0
  103. assets/lib/codemirror/mode/htmlembedded/index.html +60 -0
  104. assets/lib/codemirror/mode/htmlmixed/htmlmixed.js +104 -0
  105. assets/lib/codemirror/mode/htmlmixed/index.html +85 -0
  106. assets/lib/codemirror/mode/http/http.js +98 -0
  107. assets/lib/codemirror/mode/http/index.html +45 -0
  108. assets/lib/codemirror/mode/index.html +110 -0
  109. assets/lib/codemirror/mode/javascript/index.html +107 -0
  110. assets/lib/codemirror/mode/javascript/javascript.js +480 -0
  111. assets/lib/codemirror/mode/javascript/test.js +10 -0
  112. assets/lib/codemirror/mode/javascript/typescript.html +61 -0
  113. assets/lib/codemirror/mode/meta.js +87 -0
  114. assets/lib/codemirror/mode/php/index.html +62 -0
  115. assets/lib/codemirror/mode/php/php.js +132 -0
  116. assets/lib/codemirror/mode/xml/index.html +57 -0
  117. assets/lib/codemirror/mode/xml/xml.js +341 -0
  118. assets/lib/wck-api/wordpress-creation-kit.php +42 -6
  119. features/email-customizer/admin-email-customizer.php +205 -0
  120. features/email-customizer/email-customizer.php +1286 -0
  121. features/email-customizer/user-email-customizer.php +409 -0
  122. features/functions.php +12 -12
  123. front-end/default-fields/avatar/avatar.php +263 -0
  124. front-end/default-fields/checkbox/checkbox.php +122 -0
  125. front-end/default-fields/default-fields.php +16 -1
  126. front-end/default-fields/fields-functions.php +66 -0
  127. front-end/default-fields/heading/heading.php +23 -0
  128. front-end/default-fields/input/input.php +71 -0
  129. front-end/default-fields/radio/radio.php +95 -0
  130. front-end/default-fields/select/select.php +105 -0
  131. front-end/default-fields/select2/select2.css +21 -0
  132. front-end/default-fields/select2/select2.js +23 -0
  133. front-end/default-fields/select2/select2.php +152 -0
  134. front-end/default-fields/textarea/textarea.php +78 -0
  135. front-end/default-fields/upload/upload.css +69 -0
  136. front-end/default-fields/upload/upload.js +242 -0
  137. front-end/default-fields/upload/upload_helper_functions.php +380 -0
  138. front-end/login.php +6 -2
  139. index.php +27 -14
  140. readme.txt +10 -4
  141. translation/profile-builder.catalog.php +19 -20
  142. translation/profile-builder.pot +508 -512
admin/add-ons.php CHANGED
@@ -61,13 +61,13 @@ function wppb_add_ons_content() {
61
  'icon' => 'pro_user_listing.png',
62
  'doc_url' => 'https://www.cozmoslabs.com/docs/profile-builder-2/modules/user-listing/?utm_source=wpbackend&utm_medium=clientsite&utm_content=add-on-page&utm_campaign=PBPro',
63
  ),
64
- array( 'slug' => 'wppb_emailCustomizer',
65
- 'type' => 'add-on',
66
- 'name' => __( 'Email Customizer', 'profile-builder' ),
67
- 'description' => __( 'Simple to use customization of the WordPress Registration Emails', 'profile-builder' ),
68
- 'icon' => 'pro_email_customizer.png',
69
- 'doc_url' => 'https://www.cozmoslabs.com/docs/profile-builder-2/modules/user-email-customizer/?utm_source=wpbackend&utm_medium=clientsite&utm_content=add-on-page&utm_campaign=PBPro',
70
- ),
71
  array( 'slug' => 'wppb_customRedirect',
72
  'type' => 'add-on',
73
  'name' => __( 'Custom Redirects', 'profile-builder' ),
61
  'icon' => 'pro_user_listing.png',
62
  'doc_url' => 'https://www.cozmoslabs.com/docs/profile-builder-2/modules/user-listing/?utm_source=wpbackend&utm_medium=clientsite&utm_content=add-on-page&utm_campaign=PBPro',
63
  ),
64
+ // array( 'slug' => 'wppb_emailCustomizer',
65
+ // 'type' => 'add-on',
66
+ // 'name' => __( 'Email Customizer', 'profile-builder' ),
67
+ // 'description' => __( 'Simple to use customization of the WordPress Registration Emails', 'profile-builder' ),
68
+ // 'icon' => 'pro_email_customizer.png',
69
+ // 'doc_url' => 'https://www.cozmoslabs.com/docs/profile-builder-2/modules/user-email-customizer/?utm_source=wpbackend&utm_medium=clientsite&utm_content=add-on-page&utm_campaign=PBPro',
70
+ // ),
71
  array( 'slug' => 'wppb_customRedirect',
72
  'type' => 'add-on',
73
  'name' => __( 'Custom Redirects', 'profile-builder' ),
admin/advanced-settings/includes/fields/remove-all-fields-from-backend.php CHANGED
@@ -2,9 +2,17 @@
2
  /* Hide all profile fields in backend */
3
  add_action('admin_init', 'wppb_toolbox_remove_all_fields_from_backend');
4
  function wppb_toolbox_remove_all_fields_from_backend(){
 
5
  remove_action( 'show_user_profile', 'display_profile_extra_fields_in_admin', 10 );
6
  remove_action( 'edit_user_profile', 'display_profile_extra_fields_in_admin', 10 );
7
  remove_action( 'personal_options_update', 'save_profile_extra_fields_in_admin', 10 );
8
  remove_action( 'edit_user_profile_update', 'save_profile_extra_fields_in_admin', 10 );
9
  remove_action( 'user_profile_update_errors', 'wppb_validate_backend_fields', 10, 3 );
 
 
 
 
 
 
 
10
  }
2
  /* Hide all profile fields in backend */
3
  add_action('admin_init', 'wppb_toolbox_remove_all_fields_from_backend');
4
  function wppb_toolbox_remove_all_fields_from_backend(){
5
+ // old
6
  remove_action( 'show_user_profile', 'display_profile_extra_fields_in_admin', 10 );
7
  remove_action( 'edit_user_profile', 'display_profile_extra_fields_in_admin', 10 );
8
  remove_action( 'personal_options_update', 'save_profile_extra_fields_in_admin', 10 );
9
  remove_action( 'edit_user_profile_update', 'save_profile_extra_fields_in_admin', 10 );
10
  remove_action( 'user_profile_update_errors', 'wppb_validate_backend_fields', 10, 3 );
11
+
12
+ // new
13
+ remove_action( 'show_user_profile', 'wppb_display_fields_in_admin', 10 );
14
+ remove_action( 'edit_user_profile', 'wppb_display_fields_in_admin', 10 );
15
+ remove_action( 'personal_options_update', 'wppb_save_fields_in_admin', 10 );
16
+ remove_action( 'edit_user_profile_update', 'wppb_save_fields_in_admin', 10 );
17
+ remove_action( 'user_profile_update_errors', 'wppb_validate_fields_in_admin', 10, 3 );
18
  }
admin/advanced-settings/includes/forms/back-end-validation.php CHANGED
@@ -2,5 +2,9 @@
2
 
3
  add_action ('user_profile_update_errors', 'wppb_toolbox_remove_backend_profile_validation', 5);
4
  function wppb_toolbox_remove_backend_profile_validation(){
 
5
  remove_action( 'user_profile_update_errors', 'wppb_validate_backend_fields', 10, 3);
 
 
 
6
  }
2
 
3
  add_action ('user_profile_update_errors', 'wppb_toolbox_remove_backend_profile_validation', 5);
4
  function wppb_toolbox_remove_backend_profile_validation(){
5
+ // old
6
  remove_action( 'user_profile_update_errors', 'wppb_validate_backend_fields', 10, 3);
7
+
8
+ // new
9
+ remove_action( 'user_profile_update_errors', 'wppb_validate_fields_in_admin', 10, 3);
10
  }
admin/advanced-settings/includes/views/view-fields.php CHANGED
@@ -255,57 +255,54 @@
255
  </tr>
256
  <?php endif; ?>
257
 
258
- <tr>
259
- <th><?php esc_html_e( 'Show the Password field visibility toggle', 'profile-builder' ); ?></th>
260
 
261
- <td>
262
- <label><input type="checkbox" name="wppb_toolbox_fields_settings[password-visibility-hide]"<?php echo ( ( isset( $settings['password-visibility-hide'] ) && ( $settings['password-visibility-hide'] == 'yes' ) ) ? ' checked' : '' ); ?> value="yes">
263
- <?php esc_html_e( 'Yes', 'profile-builder' ); ?>
264
- </label>
265
 
266
- <ul>
267
- <li class="description">
268
- <?php esc_html_e( 'Activating this option will show a visibility toggle button for all Password fields.', 'profile-builder' ); ?>
269
- </li>
270
- </ul>
271
- </td>
272
- </tr>
273
 
274
- <?php if ( defined( 'WPPB_PAID_PLUGIN_DIR' ) && file_exists( WPPB_PAID_PLUGIN_DIR . '/front-end/extra-fields/extra-fields.php' ) ) : ?>
275
- <tr>
276
- <th><?php esc_html_e( 'Remove All Extra Fields from Backend edit profile page.', 'profile-builder' ); ?></th>
277
 
278
- <td>
279
- <label><input type="checkbox" name="wppb_toolbox_fields_settings[remove-all-fields-from-backend]"<?php echo ( ( isset( $settings['remove-all-fields-from-backend'] ) && ( $settings['remove-all-fields-from-backend'] == 'yes' ) ) ? ' checked' : '' ); ?> value="yes">
280
- <?php esc_html_e( 'Yes', 'profile-builder' ); ?>
281
- </label>
282
 
283
- <ul>
284
- <li class="description">
285
- <?php esc_html_e( 'If you activate this option, it will remove all custom fields from the backend profile page created with Profile Builder.', 'profile-builder' ); ?>
286
- </li>
287
- </ul>
288
- </td>
289
- </tr>
290
- <?php endif; ?>
291
 
292
- <?php if ( defined( 'WPPB_PAID_PLUGIN_DIR' ) && file_exists( WPPB_PAID_PLUGIN_DIR . '/front-end/extra-fields/extra-fields.php' ) ) : ?>
293
- <tr>
294
- <th><?php esc_html_e( 'Update database entries when changing meta key', 'profile-builder' ); ?></th>
 
 
 
 
295
 
296
- <td>
297
- <label><input type="checkbox" name="wppb_toolbox_fields_settings[update-db-meta-keys]"<?php echo ( ( isset( $settings['update-db-meta-keys'] ) && ( $settings['update-db-meta-keys'] == 'yes' ) ) ? ' checked' : '' ); ?> value="yes">
298
- <?php esc_html_e( 'Yes', 'profile-builder' ); ?>
299
- </label>
300
 
301
- <ul>
302
- <li class="description">
303
- <?php esc_html_e( 'If you activate this option, when changing the meta key of a field, existing entries from the database will be updated as well.', 'profile-builder' ); ?>
304
- </li>
305
- </ul>
306
- </td>
307
- </tr>
308
- <?php endif; ?>
 
 
 
 
309
 
310
  </table>
311
 
255
  </tr>
256
  <?php endif; ?>
257
 
258
+ <tr>
259
+ <th><?php esc_html_e( 'Show the Password field visibility toggle', 'profile-builder' ); ?></th>
260
 
261
+ <td>
262
+ <label><input type="checkbox" name="wppb_toolbox_fields_settings[password-visibility-hide]"<?php echo ( ( isset( $settings['password-visibility-hide'] ) && ( $settings['password-visibility-hide'] == 'yes' ) ) ? ' checked' : '' ); ?> value="yes">
263
+ <?php esc_html_e( 'Yes', 'profile-builder' ); ?>
264
+ </label>
265
 
266
+ <ul>
267
+ <li class="description">
268
+ <?php esc_html_e( 'Activating this option will show a visibility toggle button for all Password fields.', 'profile-builder' ); ?>
269
+ </li>
270
+ </ul>
271
+ </td>
272
+ </tr>
273
 
 
 
 
274
 
275
+ <tr>
276
+ <th><?php esc_html_e( 'Remove All Extra Fields from Backend edit profile page.', 'profile-builder' ); ?></th>
 
 
277
 
278
+ <td>
279
+ <label><input type="checkbox" name="wppb_toolbox_fields_settings[remove-all-fields-from-backend]"<?php echo ( ( isset( $settings['remove-all-fields-from-backend'] ) && ( $settings['remove-all-fields-from-backend'] == 'yes' ) ) ? ' checked' : '' ); ?> value="yes">
280
+ <?php esc_html_e( 'Yes', 'profile-builder' ); ?>
281
+ </label>
 
 
 
 
282
 
283
+ <ul>
284
+ <li class="description">
285
+ <?php esc_html_e( 'If you activate this option, it will remove all custom fields from the backend profile page created with Profile Builder.', 'profile-builder' ); ?>
286
+ </li>
287
+ </ul>
288
+ </td>
289
+ </tr>
290
 
291
+ <tr>
292
+ <th><?php esc_html_e( 'Update database entries when changing meta key', 'profile-builder' ); ?></th>
 
 
293
 
294
+ <td>
295
+ <label><input type="checkbox" name="wppb_toolbox_fields_settings[update-db-meta-keys]"<?php echo ( ( isset( $settings['update-db-meta-keys'] ) && ( $settings['update-db-meta-keys'] == 'yes' ) ) ? ' checked' : '' ); ?> value="yes">
296
+ <?php esc_html_e( 'Yes', 'profile-builder' ); ?>
297
+ </label>
298
+
299
+ <ul>
300
+ <li class="description">
301
+ <?php esc_html_e( 'If you activate this option, when changing the meta key of a field, existing entries from the database will be updated as well.', 'profile-builder' ); ?>
302
+ </li>
303
+ </ul>
304
+ </td>
305
+ </tr>
306
 
307
  </table>
308
 
admin/basic-info.php CHANGED
@@ -87,6 +87,10 @@ function wppb_basic_info_content() {
87
  <h3><?php esc_html_e( 'Content Restriction', 'profile-builder' ); ?></h3>
88
  <p><?php esc_html_e( 'Restrict users from accessing certain pages, posts or custom post types based on user role or logged-in status.', 'profile-builder' ); ?></p>
89
  </div>
 
 
 
 
90
  <div>
91
  <h3><?php esc_html_e( 'Minimum Password Length and Strength Meter', 'profile-builder' ); ?></h3>
92
  <p><?php esc_html_e( 'Eliminate weak passwords altogether by setting a minimum password length and enforcing a certain password strength.', 'profile-builder' ); ?></p>
@@ -118,27 +122,24 @@ function wppb_basic_info_content() {
118
  <p><a href="admin.php?page=manage-fields" class="button"><?php esc_html_e( 'Get started with extra fields', 'profile-builder' ); ?></a></p>
119
  <?php } ?>
120
  <ul style="float: left; margin-right: 50px;">
121
- <li><?php esc_html_e( 'Avatar Upload', 'profile-builder' ); ?></li>
122
  <li><?php esc_html_e( 'Generic Uploads', 'profile-builder' ); ?></li>
123
  <li><?php esc_html_e( 'Agree To Terms Checkbox', 'profile-builder' ); ?></li>
124
  <li><?php esc_html_e( 'Datepicker', 'profile-builder' ); ?> </li>
125
  <li><?php esc_html_e( 'Timepicker', 'profile-builder' ); ?> </li>
126
  <li><?php esc_html_e( 'Colorpicker', 'profile-builder' ); ?> </li>
127
- <li><?php esc_html_e( 'reCAPTCHA', 'profile-builder' ); ?></li>
128
  <li><?php esc_html_e( 'Country Select', 'profile-builder' ); ?></li>
129
  <li><?php esc_html_e( 'Currency Select', 'profile-builder' ); ?></li>
130
  <li><?php esc_html_e( 'Timezone Select', 'profile-builder' ); ?></li>
131
  </ul>
132
 
133
  <ul style="float: left;">
134
- <li><?php esc_html_e( 'Input / Hidden Input', 'profile-builder' ); ?></li>
 
 
 
135
  <li><?php esc_html_e( 'Number', 'profile-builder' ); ?></li>
136
- <li><?php esc_html_e( 'Checkbox', 'profile-builder' ); ?></li>
137
- <li><?php esc_html_e( 'Select', 'profile-builder' ); ?></li>
138
- <li><?php esc_html_e( 'Radio Buttons', 'profile-builder' ); ?></li>
139
- <li><?php esc_html_e( 'Textarea', 'profile-builder' ); ?></li>
140
  <li><?php esc_html_e( 'Validation', 'profile-builder' ); ?></li>
141
- <li><?php esc_html_e( 'Map', 'profile-builder' ); ?></li>
142
  <li><?php esc_html_e( 'HTML', 'profile-builder' ); ?></li>
143
  </ul>
144
  </div>
@@ -166,10 +167,6 @@ function wppb_basic_info_content() {
166
  <p><?php printf( esc_html__( 'To create a page containing the users registered to this current site/blog, insert the following shortcode in a page of your chosing: %s.', 'profile-builder' ), '<strong class="nowrap">[wppb-list-users]</strong>' ); ?></p>
167
  <?php endif;?>
168
  </div>
169
- <div>
170
- <h3><?php esc_html_e( 'Email Customizer', 'profile-builder' ); ?></h3>
171
- <p><?php esc_html_e( 'Personalize all emails sent to your users or admins. On registration, email confirmation, admin approval / un-approval.', 'profile-builder' ); ?></p>
172
- </div>
173
  <div>
174
  <h3><?php esc_html_e( 'Custom Redirects', 'profile-builder' ); ?></h3>
175
  <p><?php esc_html_e( 'Keep your users out of the WordPress dashboard, redirect them to the front-page after login or registration, everything is just a few clicks away.', 'profile-builder' ); ?></p>
87
  <h3><?php esc_html_e( 'Content Restriction', 'profile-builder' ); ?></h3>
88
  <p><?php esc_html_e( 'Restrict users from accessing certain pages, posts or custom post types based on user role or logged-in status.', 'profile-builder' ); ?></p>
89
  </div>
90
+ <div>
91
+ <h3><?php esc_html_e( 'Email Customizer', 'profile-builder' ); ?></h3>
92
+ <p><?php esc_html_e( 'Personalize all emails sent to your users or admins. On registration, email confirmation, admin approval / un-approval.', 'profile-builder' ); ?></p>
93
+ </div>
94
  <div>
95
  <h3><?php esc_html_e( 'Minimum Password Length and Strength Meter', 'profile-builder' ); ?></h3>
96
  <p><?php esc_html_e( 'Eliminate weak passwords altogether by setting a minimum password length and enforcing a certain password strength.', 'profile-builder' ); ?></p>
122
  <p><a href="admin.php?page=manage-fields" class="button"><?php esc_html_e( 'Get started with extra fields', 'profile-builder' ); ?></a></p>
123
  <?php } ?>
124
  <ul style="float: left; margin-right: 50px;">
 
125
  <li><?php esc_html_e( 'Generic Uploads', 'profile-builder' ); ?></li>
126
  <li><?php esc_html_e( 'Agree To Terms Checkbox', 'profile-builder' ); ?></li>
127
  <li><?php esc_html_e( 'Datepicker', 'profile-builder' ); ?> </li>
128
  <li><?php esc_html_e( 'Timepicker', 'profile-builder' ); ?> </li>
129
  <li><?php esc_html_e( 'Colorpicker', 'profile-builder' ); ?> </li>
 
130
  <li><?php esc_html_e( 'Country Select', 'profile-builder' ); ?></li>
131
  <li><?php esc_html_e( 'Currency Select', 'profile-builder' ); ?></li>
132
  <li><?php esc_html_e( 'Timezone Select', 'profile-builder' ); ?></li>
133
  </ul>
134
 
135
  <ul style="float: left;">
136
+ <li><?php esc_html_e( 'Map', 'profile-builder' ); ?></li>
137
+ <li><?php esc_html_e( 'Select 2 Multiple', 'profile-builder' ); ?></li>
138
+ <li><?php esc_html_e( 'Phone', 'profile-builder' ); ?></li>
139
+ <li><?php esc_html_e( 'Hidden Input', 'profile-builder' ); ?></li>
140
  <li><?php esc_html_e( 'Number', 'profile-builder' ); ?></li>
 
 
 
 
141
  <li><?php esc_html_e( 'Validation', 'profile-builder' ); ?></li>
142
+ <li><?php esc_html_e( 'Select CPT', 'profile-builder' ); ?></li>
143
  <li><?php esc_html_e( 'HTML', 'profile-builder' ); ?></li>
144
  </ul>
145
  </div>
167
  <p><?php printf( esc_html__( 'To create a page containing the users registered to this current site/blog, insert the following shortcode in a page of your chosing: %s.', 'profile-builder' ), '<strong class="nowrap">[wppb-list-users]</strong>' ); ?></p>
168
  <?php endif;?>
169
  </div>
 
 
 
 
170
  <div>
171
  <h3><?php esc_html_e( 'Custom Redirects', 'profile-builder' ); ?></h3>
172
  <p><?php esc_html_e( 'Keep your users out of the WordPress dashboard, redirect them to the front-page after login or registration, everything is just a few clicks away.', 'profile-builder' ); ?></p>
admin/general-settings.php CHANGED
@@ -31,7 +31,13 @@ function wppb_get_settings_pages(){
31
  }
32
 
33
  //add sub-pages here for email customizer
34
- if ( defined( 'WPPB_PAID_PLUGIN_DIR' ) && file_exists( WPPB_PAID_PLUGIN_DIR . '/add-ons/add-ons.php' ) ) {
 
 
 
 
 
 
35
  if( ( isset($wppb_module_settings['wppb_emailCustomizerAdmin']) && $wppb_module_settings['wppb_emailCustomizerAdmin'] == 'show' ) || ( isset($wppb_module_settings['wppb_emailCustomizer']) && $wppb_module_settings['wppb_emailCustomizer'] == 'show') ){
36
  $settings_pages['pages']['user-email-customizer'] = __( 'Email Customizer', 'profile-builder' );
37
  $settings_pages['sub-pages']['user-email-customizer']['user-email-customizer'] = __( 'User Emails', 'profile-builder' );
@@ -339,6 +345,42 @@ function wppb_general_settings_content() {
339
  </td>
340
  </tr>
341
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
342
  <?php do_action( 'wppb_extra_general_settings', $wppb_generalSettings ); ?>
343
  </table>
344
 
31
  }
32
 
33
  //add sub-pages here for email customizer
34
+ if( file_exists( WPPB_PLUGIN_DIR . '/features/email-customizer/email-customizer.php' ) ){
35
+
36
+ $settings_pages['pages']['user-email-customizer'] = __( 'Email Customizer', 'profile-builder' );
37
+ $settings_pages['sub-pages']['user-email-customizer']['user-email-customizer'] = __( 'User Emails', 'profile-builder' );
38
+ $settings_pages['sub-pages']['user-email-customizer']['admin-email-customizer'] = __( 'Administrator Emails', 'profile-builder' );
39
+
40
+ } else if ( defined( 'WPPB_PAID_PLUGIN_DIR' ) && file_exists( WPPB_PAID_PLUGIN_DIR . '/add-ons/add-ons.php' ) ) {
41
  if( ( isset($wppb_module_settings['wppb_emailCustomizerAdmin']) && $wppb_module_settings['wppb_emailCustomizerAdmin'] == 'show' ) || ( isset($wppb_module_settings['wppb_emailCustomizer']) && $wppb_module_settings['wppb_emailCustomizer'] == 'show') ){
42
  $settings_pages['pages']['user-email-customizer'] = __( 'Email Customizer', 'profile-builder' );
43
  $settings_pages['sub-pages']['user-email-customizer']['user-email-customizer'] = __( 'User Emails', 'profile-builder' );
345
  </td>
346
  </tr>
347
 
348
+ <tr>
349
+ <th scope="row">
350
+ <?php esc_html_e( 'Select Recover Password Page:', 'profile-builder' ); ?>
351
+ </th>
352
+ <td>
353
+ <select name="wppb_general_settings[lost_password_page]" class="wppb-select">
354
+ <option value=""> <?php esc_html_e( 'None', 'profile-builder' ); ?></option>
355
+ <?php
356
+ $args = array(
357
+ 'post_type' => 'page',
358
+ 'post_status' => 'publish',
359
+ 'numberposts' => -1,
360
+ 'orderby' => 'name',
361
+ 'order' => 'ASC'
362
+ );
363
+ $pages = get_posts( $args );
364
+
365
+ foreach ( $pages as $key => $value ){
366
+ echo '<option value="'.esc_attr( $value->guid ).'"';
367
+ if ( $wppb_generalSettings['lost_password_page'] == $value->guid )
368
+ echo ' selected';
369
+
370
+ echo '>' . esc_html( $value->post_title ) . '</option>';
371
+ }
372
+
373
+ ?>
374
+
375
+ </select>
376
+
377
+ <ul>
378
+ <li class="description"><?php printf( esc_html__( 'Select the page which contains the %1$s[wppb-recover-password]%2$s shortcode.', 'profile-builder' ), '<strong>','</strong>' ) ?> </li>
379
+ </ul>
380
+
381
+ </td>
382
+ </tr>
383
+
384
  <?php do_action( 'wppb_extra_general_settings', $wppb_generalSettings ); ?>
385
  </table>
386
 
admin/manage-fields.php CHANGED
@@ -53,12 +53,23 @@ function wppb_populate_manage_fields(){
53
  'standard' =>
54
  array(
55
  'label' => __('Standard', 'profile-builder'),
56
- 'options' => array(),
 
 
 
 
 
 
 
 
 
57
  ),
58
  'advanced' =>
59
  array(
60
  'label' => __('Advanced', 'profile-builder'),
61
- 'options' => array(),
 
 
62
  ),
63
  'other' =>
64
  array(
@@ -83,20 +94,20 @@ function wppb_populate_manage_fields(){
83
  }
84
 
85
  if( PROFILE_BUILDER != 'Profile Builder Free' ) {
86
- $manage_field_types['optgroups']['standard']['options'][] = 'Heading';
87
- $manage_field_types['optgroups']['standard']['options'][] = 'Input';
88
  $manage_field_types['optgroups']['standard']['options'][] = 'Number';
89
  $manage_field_types['optgroups']['standard']['options'][] = 'Input (Hidden)';
90
  $manage_field_types['optgroups']['standard']['options'][] = 'Language';
91
- $manage_field_types['optgroups']['standard']['options'][] = 'Textarea';
92
  $manage_field_types['optgroups']['standard']['options'][] = 'WYSIWYG';
93
- $manage_field_types['optgroups']['standard']['options'][] = 'Select';
94
  $manage_field_types['optgroups']['standard']['options'][] = 'Select (Multiple)';
95
- $manage_field_types['optgroups']['standard']['options'][] = 'Checkbox';
96
- $manage_field_types['optgroups']['standard']['options'][] = 'Radio';
97
  $manage_field_types['optgroups']['standard']['options'][] = 'HTML';
98
  $manage_field_types['optgroups']['standard']['options'][] = 'Upload';
99
- $manage_field_types['optgroups']['standard']['options'][] = 'Avatar';
100
 
101
  $manage_field_types['optgroups']['advanced']['options'][] = 'Phone';
102
  $manage_field_types['optgroups']['advanced']['options'][] = 'Select (Country)';
@@ -113,11 +124,12 @@ function wppb_populate_manage_fields(){
113
  $manage_field_types['optgroups']['other']['options'][] = 'Email';
114
  $manage_field_types['optgroups']['other']['options'][] = 'URL';
115
 
116
- $manage_field_types['optgroups']['other']['options'][] = 'Select2';
117
  $manage_field_types['optgroups']['other']['options'][] = 'Select2 (Multiple)';
118
 
119
  $manage_field_types['optgroups']['other']['options'][] = 'Honeypot';
120
- }
 
121
 
122
  $manage_field_types['optgroups']['other']['options'][] = 'Email Confirmation';
123
 
@@ -130,7 +142,42 @@ function wppb_populate_manage_fields(){
130
 
131
  $manage_field_types = apply_filters( 'wppb_all_manage_fields_types', $manage_field_types );
132
 
133
- //Free to Pro call to action on Manage Fields page
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  $field_description = __('Choose one of the supported field types','profile-builder');
135
  if( PROFILE_BUILDER == 'Profile Builder Free' ) {
136
  $field_description .= sprintf( __('. Extra Field Types are available in <a href="%s">Basic or PRO versions</a>.' , 'profile-builder'), esc_url( 'https://www.cozmoslabs.com/wordpress-profile-builder/?utm_source=wpbackend&utm_medium=clientsite&utm_content=manage-fields-link&utm_campaign=PBFree' ) );
53
  'standard' =>
54
  array(
55
  'label' => __('Standard', 'profile-builder'),
56
+ 'options' => array(
57
+ // since 3.8.1
58
+ 'Avatar',
59
+ 'Checkbox',
60
+ 'Heading',
61
+ 'Input',
62
+ 'Radio',
63
+ 'Select',
64
+ 'Textarea',
65
+ ),
66
  ),
67
  'advanced' =>
68
  array(
69
  'label' => __('Advanced', 'profile-builder'),
70
+ 'options' => array(
71
+ 'Select2' // since 3.8.1
72
+ ),
73
  ),
74
  'other' =>
75
  array(
94
  }
95
 
96
  if( PROFILE_BUILDER != 'Profile Builder Free' ) {
97
+ // $manage_field_types['optgroups']['standard']['options'][] = 'Heading';
98
+ // $manage_field_types['optgroups']['standard']['options'][] = 'Input';
99
  $manage_field_types['optgroups']['standard']['options'][] = 'Number';
100
  $manage_field_types['optgroups']['standard']['options'][] = 'Input (Hidden)';
101
  $manage_field_types['optgroups']['standard']['options'][] = 'Language';
102
+ // $manage_field_types['optgroups']['standard']['options'][] = 'Textarea';
103
  $manage_field_types['optgroups']['standard']['options'][] = 'WYSIWYG';
104
+ // $manage_field_types['optgroups']['standard']['options'][] = 'Select';
105
  $manage_field_types['optgroups']['standard']['options'][] = 'Select (Multiple)';
106
+ // $manage_field_types['optgroups']['standard']['options'][] = 'Checkbox';
107
+ // $manage_field_types['optgroups']['standard']['options'][] = 'Radio';
108
  $manage_field_types['optgroups']['standard']['options'][] = 'HTML';
109
  $manage_field_types['optgroups']['standard']['options'][] = 'Upload';
110
+ // $manage_field_types['optgroups']['standard']['options'][] = 'Avatar';
111
 
112
  $manage_field_types['optgroups']['advanced']['options'][] = 'Phone';
113
  $manage_field_types['optgroups']['advanced']['options'][] = 'Select (Country)';
124
  $manage_field_types['optgroups']['other']['options'][] = 'Email';
125
  $manage_field_types['optgroups']['other']['options'][] = 'URL';
126
 
127
+ // $manage_field_types['optgroups']['other']['options'][] = 'Select2';
128
  $manage_field_types['optgroups']['other']['options'][] = 'Select2 (Multiple)';
129
 
130
  $manage_field_types['optgroups']['other']['options'][] = 'Honeypot';
131
+
132
+ }
133
 
134
  $manage_field_types['optgroups']['other']['options'][] = 'Email Confirmation';
135
 
142
 
143
  $manage_field_types = apply_filters( 'wppb_all_manage_fields_types', $manage_field_types );
144
 
145
+ if( PROFILE_BUILDER == 'Profile Builder Free' ) {
146
+
147
+ $manage_field_types['optgroups']['standard']['options'][] = array( 'field_name' => 'Number', 'disabled' => true );
148
+ $manage_field_types['optgroups']['standard']['options'][] = array( 'field_name' => 'Input (Hidden)', 'disabled' => true );
149
+ $manage_field_types['optgroups']['standard']['options'][] = array( 'field_name' => 'Language', 'disabled' => true );
150
+ $manage_field_types['optgroups']['standard']['options'][] = array( 'field_name' => 'WYSIWYG', 'disabled' => true );
151
+ $manage_field_types['optgroups']['standard']['options'][] = array( 'field_name' => 'Select (Multiple)', 'disabled' => true );
152
+ $manage_field_types['optgroups']['standard']['options'][] = array( 'field_name' => 'HTML', 'disabled' => true );
153
+ $manage_field_types['optgroups']['standard']['options'][] = array( 'field_name' => 'Upload', 'disabled' => true );
154
+
155
+ $manage_field_types['optgroups']['advanced']['options'][] = array( 'field_name' => 'Phone', 'disabled' => true );
156
+ $manage_field_types['optgroups']['advanced']['options'][] = array( 'field_name' => 'Select (Country)', 'disabled' => true );
157
+ $manage_field_types['optgroups']['advanced']['options'][] = array( 'field_name' => 'Select (Timezone)', 'disabled' => true );
158
+ $manage_field_types['optgroups']['advanced']['options'][] = array( 'field_name' => 'Select (Currency)', 'disabled' => true );
159
+ $manage_field_types['optgroups']['advanced']['options'][] = array( 'field_name' => 'Select (CPT)', 'disabled' => true );
160
+ $manage_field_types['optgroups']['advanced']['options'][] = array( 'field_name' => 'Checkbox (Terms and Conditions)', 'disabled' => true );
161
+ $manage_field_types['optgroups']['advanced']['options'][] = array( 'field_name' => 'Datepicker', 'disabled' => true );
162
+ $manage_field_types['optgroups']['advanced']['options'][] = array( 'field_name' => 'Timepicker', 'disabled' => true );
163
+ $manage_field_types['optgroups']['advanced']['options'][] = array( 'field_name' => 'Colorpicker', 'disabled' => true );
164
+ $manage_field_types['optgroups']['advanced']['options'][] = array( 'field_name' => 'Validation', 'disabled' => true );
165
+ $manage_field_types['optgroups']['advanced']['options'][] = array( 'field_name' => 'Map', 'disabled' => true );
166
+
167
+ $manage_field_types['optgroups']['other']['options'][] = array( 'field_name' => 'WooCommerce Customer Billing Address', 'disabled' => true );
168
+ $manage_field_types['optgroups']['other']['options'][] = array( 'field_name' => 'WooCommerce Customer Shipping Address', 'disabled' => true );
169
+ $manage_field_types['optgroups']['other']['options'][] = array( 'field_name' => 'MailChimp Subscribe', 'disabled' => true );
170
+ $manage_field_types['optgroups']['other']['options'][] = array( 'field_name' => 'Email', 'disabled' => true );
171
+ $manage_field_types['optgroups']['other']['options'][] = array( 'field_name' => 'URL', 'disabled' => true );
172
+ $manage_field_types['optgroups']['other']['options'][] = array( 'field_name' => 'Select2 (Multiple)', 'disabled' => true );
173
+ $manage_field_types['optgroups']['other']['options'][] = array( 'field_name' => 'Honeypot', 'disabled' => true );
174
+
175
+ }
176
+
177
+ if( !is_plugin_active( 'paid-member-subscriptions/index.php' ) )
178
+ $manage_field_types['optgroups']['other']['options'][] = array( 'field_name' => 'Subscription Plans', 'disabled' => true );
179
+
180
+ // Free to Pro call to action on Manage Fields page
181
  $field_description = __('Choose one of the supported field types','profile-builder');
182
  if( PROFILE_BUILDER == 'Profile Builder Free' ) {
183
  $field_description .= sprintf( __('. Extra Field Types are available in <a href="%s">Basic or PRO versions</a>.' , 'profile-builder'), esc_url( 'https://www.cozmoslabs.com/wordpress-profile-builder/?utm_source=wpbackend&utm_medium=clientsite&utm_content=manage-fields-link&utm_campaign=PBFree' ) );
admin/register-version.php CHANGED
@@ -57,7 +57,7 @@ function wppb_serial_form(){
57
  <p><?php esc_html_e( 'If you register this version of Profile Builder, you\'ll receive information regarding upgrades, patches, and technical support.', 'profile-builder' ) ?></p>
58
 
59
  <div class="wppb-serial-wrap">
60
- <form method="post" action="options.php">
61
  <?php settings_fields( 'wppb_license_key' ); ?>
62
 
63
  <label for="wppb_license_key"><?php esc_html_e( 'License key', 'profile-builder' ); ?></label>
57
  <p><?php esc_html_e( 'If you register this version of Profile Builder, you\'ll receive information regarding upgrades, patches, and technical support.', 'profile-builder' ) ?></p>
58
 
59
  <div class="wppb-serial-wrap">
60
+ <form method="post" action="<?php echo !is_multisite() ? 'options.php' : 'edit.php'; ?>">
61
  <?php settings_fields( 'wppb_license_key' ); ?>
62
 
63
  <label for="wppb_license_key"><?php esc_html_e( 'License key', 'profile-builder' ); ?></label>
assets/lib/Mustache/Autoloader.php ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Mustache.php.
5
+ *
6
+ * (c) 2012 Justin Hileman
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * Mustache class autoloader.
14
+ */
15
+ class Mustache_Autoloader
16
+ {
17
+
18
+ private $baseDir;
19
+
20
+ /**
21
+ * Autoloader constructor.
22
+ *
23
+ * @param string $baseDir Mustache library base directory (default: dirname(__FILE__).'/..')
24
+ */
25
+ public function __construct($baseDir = null)
26
+ {
27
+ if ($baseDir === null) {
28
+ $baseDir = dirname(__FILE__).'/..';
29
+ }
30
+
31
+ // realpath doesn't always work, for example, with stream URIs
32
+ $realDir = realpath($baseDir);
33
+ if (is_dir($realDir)) {
34
+ $this->baseDir = $realDir;
35
+ } else {
36
+ $this->baseDir = $baseDir;
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Register a new instance as an SPL autoloader.
42
+ *
43
+ * @param string $baseDir Mustache library base directory (default: dirname(__FILE__).'/..')
44
+ *
45
+ * @return Mustache_Autoloader Registered Autoloader instance
46
+ */
47
+ public static function register($baseDir = null)
48
+ {
49
+ $loader = new self($baseDir);
50
+ spl_autoload_register(array($loader, 'autoload'));
51
+
52
+ return $loader;
53
+ }
54
+
55
+ /**
56
+ * Autoload Mustache classes.
57
+ *
58
+ * @param string $class
59
+ */
60
+ public function autoload($class)
61
+ {
62
+ if ($class[0] === '\\') {
63
+ $class = substr($class, 1);
64
+ }
65
+
66
+ if (strpos($class, 'Mustache') !== 0) {
67
+ return;
68
+ }
69
+
70
+ $file = sprintf('%s/%s.php', $this->baseDir, str_replace('_', '/', $class));
71
+ if (is_file($file)) {
72
+ require $file;
73
+ }
74
+ }
75
+ }
assets/lib/Mustache/Compiler.php ADDED
@@ -0,0 +1,476 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Mustache.php.
5
+ *
6
+ * (c) 2012 Justin Hileman
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * Mustache Compiler class.
14
+ *
15
+ * This class is responsible for turning a Mustache token parse tree into normal PHP source code.
16
+ */
17
+ class Mustache_Compiler
18
+ {
19
+
20
+ private $sections;
21
+ private $source;
22
+ private $indentNextLine;
23
+ private $customEscape;
24
+ private $entityFlags;
25
+ private $charset;
26
+ private $strictCallables;
27
+ private $pragmas;
28
+
29
+ /**
30
+ * Compile a Mustache token parse tree into PHP source code.
31
+ *
32
+ * @param string $source Mustache Template source code
33
+ * @param string $tree Parse tree of Mustache tokens
34
+ * @param string $name Mustache Template class name
35
+ * @param bool $customEscape (default: false)
36
+ * @param int $entityFlags (default: ENT_COMPAT)
37
+ * @param string $charset (default: 'UTF-8')
38
+ * @param bool $strictCallables (default: false)
39
+ *
40
+ * @return string Generated PHP source code
41
+ */
42
+ public function compile($source, array $tree, $name, $customEscape = false, $charset = 'UTF-8', $strictCallables = false, $entityFlags = ENT_COMPAT)
43
+ {
44
+ $this->pragmas = array();
45
+ $this->sections = array();
46
+ $this->source = $source;
47
+ $this->indentNextLine = true;
48
+ $this->customEscape = $customEscape;
49
+ $this->entityFlags = $entityFlags;
50
+ $this->charset = $charset;
51
+ $this->strictCallables = $strictCallables;
52
+
53
+ return $this->writeCode($tree, $name);
54
+ }
55
+
56
+ /**
57
+ * Helper function for walking the Mustache token parse tree.
58
+ *
59
+ * @throws Mustache_Exception_SyntaxException upon encountering unknown token types.
60
+ *
61
+ * @param array $tree Parse tree of Mustache tokens
62
+ * @param int $level (default: 0)
63
+ *
64
+ * @return string Generated PHP source code
65
+ */
66
+ private function walk(array $tree, $level = 0)
67
+ {
68
+ $code = '';
69
+ $level++;
70
+ foreach ($tree as $node) {
71
+ switch ($node[Mustache_Tokenizer::TYPE]) {
72
+ case Mustache_Tokenizer::T_PRAGMA:
73
+ $this->pragmas[$node[Mustache_Tokenizer::NAME]] = true;
74
+ break;
75
+
76
+ case Mustache_Tokenizer::T_SECTION:
77
+ $code .= $this->section(
78
+ $node[Mustache_Tokenizer::NODES],
79
+ $node[Mustache_Tokenizer::NAME],
80
+ $node[Mustache_Tokenizer::INDEX],
81
+ $node[Mustache_Tokenizer::END],
82
+ $node[Mustache_Tokenizer::OTAG],
83
+ $node[Mustache_Tokenizer::CTAG],
84
+ $level
85
+ );
86
+ break;
87
+
88
+ case Mustache_Tokenizer::T_INVERTED:
89
+ $code .= $this->invertedSection(
90
+ $node[Mustache_Tokenizer::NODES],
91
+ $node[Mustache_Tokenizer::NAME],
92
+ $level
93
+ );
94
+ break;
95
+
96
+ case Mustache_Tokenizer::T_PARTIAL:
97
+ case Mustache_Tokenizer::T_PARTIAL_2:
98
+ $code .= $this->partial(
99
+ $node[Mustache_Tokenizer::NAME],
100
+ isset($node[Mustache_Tokenizer::INDENT]) ? $node[Mustache_Tokenizer::INDENT] : '',
101
+ $level
102
+ );
103
+ break;
104
+
105
+ case Mustache_Tokenizer::T_UNESCAPED:
106
+ case Mustache_Tokenizer::T_UNESCAPED_2:
107
+ $code .= $this->variable($node[Mustache_Tokenizer::NAME], false, $level);
108
+ break;
109
+
110
+ case Mustache_Tokenizer::T_COMMENT:
111
+ break;
112
+
113
+ case Mustache_Tokenizer::T_ESCAPED:
114
+ $code .= $this->variable($node[Mustache_Tokenizer::NAME], true, $level);
115
+ break;
116
+
117
+ case Mustache_Tokenizer::T_TEXT:
118
+ $code .= $this->text($node[Mustache_Tokenizer::VALUE], $level);
119
+ break;
120
+
121
+ default:
122
+ throw new Mustache_Exception_SyntaxException(sprintf('Unknown token type: %s', $node[Mustache_Tokenizer::TYPE]), $node);
123
+ }
124
+ }
125
+
126
+ return $code;
127
+ }
128
+
129
+ const KLASS = '<?php
130
+
131
+ class %s extends Mustache_Template
132
+ {
133
+ private $lambdaHelper;%s
134
+
135
+ public function renderInternal(Mustache_Context $context, $indent = \'\')
136
+ {
137
+ $this->lambdaHelper = new Mustache_LambdaHelper($this->mustache, $context);
138
+ $buffer = \'\';
139
+ %s
140
+
141
+ return $buffer;
142
+ }
143
+ %s
144
+ }';
145
+
146
+ const KLASS_NO_LAMBDAS = '<?php
147
+
148
+ class %s extends Mustache_Template
149
+ {%s
150
+ public function renderInternal(Mustache_Context $context, $indent = \'\')
151
+ {
152
+ $buffer = \'\';
153
+ %s
154
+
155
+ return $buffer;
156
+ }
157
+ }';
158
+
159
+ const STRICT_CALLABLE = 'protected $strictCallables = true;';
160
+
161
+ /**
162
+ * Generate Mustache Template class PHP source.
163
+ *
164
+ * @param array $tree Parse tree of Mustache tokens
165
+ * @param string $name Mustache Template class name
166
+ *
167
+ * @return string Generated PHP source code
168
+ */
169
+ private function writeCode($tree, $name)
170
+ {
171
+ $code = $this->walk($tree);
172
+ $sections = implode("\n", $this->sections);
173
+ $klass = empty($this->sections) ? self::KLASS_NO_LAMBDAS : self::KLASS;
174
+ $callable = $this->strictCallables ? $this->prepare(self::STRICT_CALLABLE) : '';
175
+
176
+ return sprintf($this->prepare($klass, 0, false, true), $name, $callable, $code, $sections);
177
+ }
178
+
179
+ const SECTION_CALL = '
180
+ // %s section
181
+ $buffer .= $this->section%s($context, $indent, $context->%s(%s));
182
+ ';
183
+
184
+ const SECTION = '
185
+ private function section%s(Mustache_Context $context, $indent, $value)
186
+ {
187
+ $buffer = \'\';
188
+ if (%s) {
189
+ $source = %s;
190
+ $buffer .= $this->mustache
191
+ ->loadLambda((string) call_user_func($value, $source, $this->lambdaHelper)%s)
192
+ ->renderInternal($context);
193
+ } elseif (!empty($value)) {
194
+ $values = $this->isIterable($value) ? $value : array($value);
195
+ foreach ($values as $value) {
196
+ $context->push($value);%s
197
+ $context->pop();
198
+ }
199
+ }
200
+
201
+ return $buffer;
202
+ }';
203
+
204
+ /**
205
+ * Generate Mustache Template section PHP source.
206
+ *
207
+ * @param array $nodes Array of child tokens
208
+ * @param string $id Section name
209
+ * @param int $start Section start offset
210
+ * @param int $end Section end offset
211
+ * @param string $otag Current Mustache opening tag
212
+ * @param string $ctag Current Mustache closing tag
213
+ * @param int $level
214
+ *
215
+ * @return string Generated section PHP source code
216
+ */
217
+ private function section($nodes, $id, $start, $end, $otag, $ctag, $level)
218
+ {
219
+ $method = $this->getFindMethod($id);
220
+ $id = var_export($id, true);
221
+ $source = var_export(substr($this->source, $start, $end - $start), true);
222
+ $callable = $this->getCallable();
223
+
224
+ if ($otag !== '{{' || $ctag !== '}}') {
225
+ $delims = ', '.var_export(sprintf('{{= %s %s =}}', $otag, $ctag), true);
226
+ } else {
227
+ $delims = '';
228
+ }
229
+
230
+ $key = ucfirst(md5($delims."\n".$source));
231
+
232
+ if (!isset($this->sections[$key])) {
233
+ $this->sections[$key] = sprintf($this->prepare(self::SECTION), $key, $callable, $source, $delims, $this->walk($nodes, 2));
234
+ }
235
+
236
+ return sprintf($this->prepare(self::SECTION_CALL, $level), $id, $key, $method, $id);
237
+ }
238
+
239
+ const INVERTED_SECTION = '
240
+ // %s inverted section
241
+ $value = $context->%s(%s);
242
+ if (empty($value)) {
243
+ %s
244
+ }';
245
+
246
+ /**
247
+ * Generate Mustache Template inverted section PHP source.
248
+ *
249
+ * @param array $nodes Array of child tokens
250
+ * @param string $id Section name
251
+ * @param int $level
252
+ *
253
+ * @return string Generated inverted section PHP source code
254
+ */
255
+ private function invertedSection($nodes, $id, $level)
256
+ {
257
+ $method = $this->getFindMethod($id);
258
+ $id = var_export($id, true);
259
+
260
+ return sprintf($this->prepare(self::INVERTED_SECTION, $level), $id, $method, $id, $this->walk($nodes, $level));
261
+ }
262
+
263
+ const PARTIAL = '
264
+ if ($partial = $this->mustache->loadPartial(%s)) {
265
+ $buffer .= $partial->renderInternal($context, %s);
266
+ }
267
+ ';
268
+
269
+ /**
270
+ * Generate Mustache Template partial call PHP source.
271
+ *
272
+ * @param string $id Partial name
273
+ * @param string $indent Whitespace indent to apply to partial
274
+ * @param int $level
275
+ *
276
+ * @return string Generated partial call PHP source code
277
+ */
278
+ private function partial($id, $indent, $level)
279
+ {
280
+ return sprintf(
281
+ $this->prepare(self::PARTIAL, $level),
282
+ var_export($id, true),
283
+ var_export($indent, true)
284
+ );
285
+ }
286
+
287
+ const VARIABLE = '
288
+ $value = $this->resolveValue($context->%s(%s), $context, $indent);%s
289
+ $buffer .= %s%s;
290
+ ';
291
+
292
+ /**
293
+ * Generate Mustache Template variable interpolation PHP source.
294
+ *
295
+ * @param string $id Variable name
296
+ * @param boolean $escape Escape the variable value for output?
297
+ * @param int $level
298
+ *
299
+ * @return string Generated variable interpolation PHP source
300
+ */
301
+ private function variable($id, $escape, $level)
302
+ {
303
+ $filters = '';
304
+
305
+ if (isset($this->pragmas[Mustache_Engine::PRAGMA_FILTERS])) {
306
+ list($id, $filters) = $this->getFilters($id, $level);
307
+ }
308
+
309
+ $method = $this->getFindMethod($id);
310
+ $id = ($method !== 'last') ? var_export($id, true) : '';
311
+ $value = $escape ? $this->getEscape() : '$value';
312
+
313
+ return sprintf($this->prepare(self::VARIABLE, $level), $method, $id, $filters, $this->flushIndent(), $value);
314
+ }
315
+
316
+ /**
317
+ * Generate Mustache Template variable filtering PHP source.
318
+ *
319
+ * @param string $id Variable name
320
+ * @param int $level
321
+ *
322
+ * @return string Generated variable filtering PHP source
323
+ */
324
+ private function getFilters($id, $level)
325
+ {
326
+ $filters = array_map('trim', explode('|', $id));
327
+ $id = array_shift($filters);
328
+
329
+ return array($id, $this->getFilter($filters, $level));
330
+ }
331
+
332
+ const FILTER = '
333
+ $filter = $context->%s(%s);
334
+ if (!(%s)) {
335
+ throw new Mustache_Exception_UnknownFilterException(%s);
336
+ }
337
+ $value = call_user_func($filter, $value);%s
338
+ ';
339
+
340
+ /**
341
+ * Generate PHP source for a single filter.
342
+ *
343
+ * @param array $filters
344
+ * @param int $level
345
+ *
346
+ * @return string Generated filter PHP source
347
+ */
348
+ private function getFilter(array $filters, $level)
349
+ {
350
+ if (empty($filters)) {
351
+ return '';
352
+ }
353
+
354
+ $name = array_shift($filters);
355
+ $method = $this->getFindMethod($name);
356
+ $filter = ($method !== 'last') ? var_export($name, true) : '';
357
+ $callable = $this->getCallable('$filter');
358
+ $msg = var_export($name, true);
359
+
360
+ return sprintf($this->prepare(self::FILTER, $level), $method, $filter, $callable, $msg, $this->getFilter($filters, $level));
361
+ }
362
+
363
+ const LINE = '$buffer .= "\n";';
364
+ const TEXT = '$buffer .= %s%s;';
365
+
366
+ /**
367
+ * Generate Mustache Template output Buffer call PHP source.
368
+ *
369
+ * @param string $text
370
+ * @param int $level
371
+ *
372
+ * @return string Generated output Buffer call PHP source
373
+ */
374
+ private function text($text, $level)
375
+ {
376
+ $indentNextLine = (substr($text, -1) === "\n");
377
+ $code = sprintf($this->prepare(self::TEXT, $level), $this->flushIndent(), var_export($text, true));
378
+ $this->indentNextLine = $indentNextLine;
379
+
380
+ return $code;
381
+ }
382
+
383
+ /**
384
+ * Prepare PHP source code snippet for output.
385
+ *
386
+ * @param string $text
387
+ * @param int $bonus Additional indent level (default: 0)
388
+ * @param boolean $prependNewline Prepend a newline to the snippet? (default: true)
389
+ * @param boolean $appendNewline Append a newline to the snippet? (default: false)
390
+ *
391
+ * @return string PHP source code snippet
392
+ */
393
+ private function prepare($text, $bonus = 0, $prependNewline = true, $appendNewline = false)
394
+ {
395
+ $text = ($prependNewline ? "\n" : '').trim($text);
396
+ if ($prependNewline) {
397
+ $bonus++;
398
+ }
399
+ if ($appendNewline) {
400
+ $text .= "\n";
401
+ }
402
+
403
+ return preg_replace("/\n( {8})?/", "\n".str_repeat(" ", $bonus * 4), $text);
404
+ }
405
+
406
+ const DEFAULT_ESCAPE = 'htmlspecialchars(%s, %s, %s)';
407
+ const CUSTOM_ESCAPE = 'call_user_func($this->mustache->getEscape(), %s)';
408
+
409
+ /**
410
+ * Get the current escaper.
411
+ *
412
+ * @param string $value (default: '$value')
413
+ *
414
+ * @return string Either a custom callback, or an inline call to `htmlspecialchars`
415
+ */
416
+ private function getEscape($value = '$value')
417
+ {
418
+ if ($this->customEscape) {
419
+ return sprintf(self::CUSTOM_ESCAPE, $value);
420
+ } else {
421
+ return sprintf(self::DEFAULT_ESCAPE, $value, var_export($this->entityFlags, true), var_export($this->charset, true));
422
+ }
423
+ }
424
+
425
+ /**
426
+ * Select the appropriate Context `find` method for a given $id.
427
+ *
428
+ * The return value will be one of `find`, `findDot` or `last`.
429
+ *
430
+ * @see Mustache_Context::find
431
+ * @see Mustache_Context::findDot
432
+ * @see Mustache_Context::last
433
+ *
434
+ * @param string $id Variable name
435
+ *
436
+ * @return string `find` method name
437
+ */
438
+ private function getFindMethod($id)
439
+ {
440
+ if ($id === '.') {
441
+ return 'last';
442
+ } elseif (strpos($id, '.') === false) {
443
+ return 'find';
444
+ } else {
445
+ return 'findDot';
446
+ }
447
+ }
448
+
449
+ const IS_CALLABLE = '!is_string(%s) && is_callable(%s)';
450
+ const STRICT_IS_CALLABLE = 'is_object(%s) && is_callable(%s)';
451
+
452
+ private function getCallable($variable = '$value')
453
+ {
454
+ $tpl = $this->strictCallables ? self::STRICT_IS_CALLABLE : self::IS_CALLABLE;
455
+
456
+ return sprintf($tpl, $variable, $variable);
457
+ }
458
+
459
+ const LINE_INDENT = '$indent . ';
460
+
461
+ /**
462
+ * Get the current $indent prefix to write to the buffer.
463
+ *
464
+ * @return string "$indent . " or ""
465
+ */
466
+ private function flushIndent()
467
+ {
468
+ if (!$this->indentNextLine) {
469
+ return '';
470
+ }
471
+
472
+ $this->indentNextLine = false;
473
+
474
+ return self::LINE_INDENT;
475
+ }
476
+ }
assets/lib/Mustache/Context.php ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Mustache.php.
5
+ *
6
+ * (c) 2012 Justin Hileman
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * Mustache Template rendering Context.
14
+ */
15
+ class Mustache_Context
16
+ {
17
+ private $stack = array();
18
+
19
+ /**
20
+ * Mustache rendering Context constructor.
21
+ *
22
+ * @param mixed $context Default rendering context (default: null)
23
+ */
24
+ public function __construct($context = null)
25
+ {
26
+ if ($context !== null) {
27
+ $this->stack = array($context);
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Push a new Context frame onto the stack.
33
+ *
34
+ * @param mixed $value Object or array to use for context
35
+ */
36
+ public function push($value)
37
+ {
38
+ array_push($this->stack, $value);
39
+ }
40
+
41
+ /**
42
+ * Pop the last Context frame from the stack.
43
+ *
44
+ * @return mixed Last Context frame (object or array)
45
+ */
46
+ public function pop()
47
+ {
48
+ return array_pop($this->stack);
49
+ }
50
+
51
+ /**
52
+ * Get the last Context frame.
53
+ *
54
+ * @return mixed Last Context frame (object or array)
55
+ */
56
+ public function last()
57
+ {
58
+ return end($this->stack);
59
+ }
60
+
61
+ /**
62
+ * Find a variable in the Context stack.
63
+ *
64
+ * Starting with the last Context frame (the context of the innermost section), and working back to the top-level
65
+ * rendering context, look for a variable with the given name:
66
+ *
67
+ * * If the Context frame is an associative array which contains the key $id, returns the value of that element.
68
+ * * If the Context frame is an object, this will check first for a public method, then a public property named
69
+ * $id. Failing both of these, it will try `__isset` and `__get` magic methods.
70
+ * * If a value named $id is not found in any Context frame, returns an empty string.
71
+ *
72
+ * @param string $id Variable name
73
+ *
74
+ * @return mixed Variable value, or '' if not found
75
+ */
76
+ public function find($id)
77
+ {
78
+ return $this->findVariableInStack($id, $this->stack);
79
+ }
80
+
81
+ /**
82
+ * Find a 'dot notation' variable in the Context stack.
83
+ *
84
+ * Note that dot notation traversal bubbles through scope differently than the regular find method. After finding
85
+ * the initial chunk of the dotted name, each subsequent chunk is searched for only within the value of the previous
86
+ * result. For example, given the following context stack:
87
+ *
88
+ * $data = array(
89
+ * 'name' => 'Fred',
90
+ * 'child' => array(
91
+ * 'name' => 'Bob'
92
+ * ),
93
+ * );
94
+ *
95
+ * ... and the Mustache following template:
96
+ *
97
+ * {{ child.name }}
98
+ *
99
+ * ... the `name` value is only searched for within the `child` value of the global Context, not within parent
100
+ * Context frames.
101
+ *
102
+ * @param string $id Dotted variable selector
103
+ *
104
+ * @return mixed Variable value, or '' if not found
105
+ */
106
+ public function findDot($id)
107
+ {
108
+ $chunks = explode('.', $id);
109
+ $first = array_shift($chunks);
110
+ $value = $this->findVariableInStack($first, $this->stack);
111
+
112
+ foreach ($chunks as $chunk) {
113
+ if ($value === '') {
114
+ return $value;
115
+ }
116
+
117
+ $value = $this->findVariableInStack($chunk, array($value));
118
+ }
119
+
120
+ return $value;
121
+ }
122
+
123
+ /**
124
+ * Helper function to find a variable in the Context stack.
125
+ *
126
+ * @see Mustache_Context::find
127
+ *
128
+ * @param string $id Variable name
129
+ * @param array $stack Context stack
130
+ *
131
+ * @return mixed Variable value, or '' if not found
132
+ */
133
+ private function findVariableInStack($id, array $stack)
134
+ {
135
+ for ($i = count($stack) - 1; $i >= 0; $i--) {
136
+ if (is_object($stack[$i]) && !$stack[$i] instanceof Closure) {
137
+ if (method_exists($stack[$i], $id)) {
138
+ return $stack[$i]->$id();
139
+ } elseif (isset($stack[$i]->$id)) {
140
+ return $stack[$i]->$id;
141
+ }
142
+ } elseif (is_array($stack[$i]) && array_key_exists($id, $stack[$i])) {
143
+ return $stack[$i][$id];
144
+ }
145
+ }
146
+
147
+ return '';
148
+ }
149
+ }
assets/lib/Mustache/Engine.php ADDED
@@ -0,0 +1,748 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Mustache.php.
5
+ *
6
+ * (c) 2012 Justin Hileman
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * A Mustache implementation in PHP.
14
+ *
15
+ * {@link http://defunkt.github.com/mustache}
16
+ *
17
+ * Mustache is a framework-agnostic logic-less templating language. It enforces separation of view
18
+ * logic from template files. In fact, it is not even possible to embed logic in the template.
19
+ *
20
+ * This is very, very rad.
21
+ *
22
+ * @author Justin Hileman {@link http://justinhileman.com}
23
+ */
24
+ class Mustache_Engine
25
+ {
26
+ const VERSION = '2.4.1';
27
+ const SPEC_VERSION = '1.1.2';
28
+
29
+ const PRAGMA_FILTERS = 'FILTERS';
30
+
31
+ // Template cache
32
+ private $templates = array();
33
+
34
+ // Environment
35
+ private $templateClassPrefix = '__Mustache_';
36
+ private $cache = null;
37
+ private $cacheFileMode = null;
38
+ private $loader;
39
+ private $partialsLoader;
40
+ private $helpers;
41
+ private $escape;
42
+ private $entityFlags = ENT_COMPAT;
43
+ private $charset = 'UTF-8';
44
+ private $logger;
45
+ private $strictCallables = false;
46
+
47
+ /**
48
+ * Mustache class constructor.
49
+ *
50
+ * Passing an $options array allows overriding certain Mustache options during instantiation:
51
+ *
52
+ * $options = array(
53
+ * // The class prefix for compiled templates. Defaults to '__Mustache_'.
54
+ * 'template_class_prefix' => '__MyTemplates_',
55
+ *
56
+ * // A cache directory for compiled templates. Mustache will not cache templates unless this is set
57
+ * 'cache' => dirname(__FILE__).'/tmp/cache/mustache',
58
+ *
59
+ * // Override default permissions for cache files. Defaults to using the system-defined umask. It is
60
+ * // *strongly* recommended that you configure your umask properly rather than overriding permissions here.
61
+ * 'cache_file_mode' => 0666,
62
+ *
63
+ * // A Mustache template loader instance. Uses a StringLoader if not specified.
64
+ * 'loader' => new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/views'),
65
+ *
66
+ * // A Mustache loader instance for partials.
67
+ * 'partials_loader' => new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/views/partials'),
68
+ *
69
+ * // An array of Mustache partials. Useful for quick-and-dirty string template loading, but not as
70
+ * // efficient or lazy as a Filesystem (or database) loader.
71
+ * 'partials' => array('foo' => file_get_contents(dirname(__FILE__).'/views/partials/foo.mustache')),
72
+ *
73
+ * // An array of 'helpers'. Helpers can be global variables or objects, closures (e.g. for higher order
74
+ * // sections), or any other valid Mustache context value. They will be prepended to the context stack,
75
+ * // so they will be available in any template loaded by this Mustache instance.
76
+ * 'helpers' => array('i18n' => function($text) {
77
+ * // do something translatey here...
78
+ * }),
79
+ *
80
+ * // An 'escape' callback, responsible for escaping double-mustache variables.
81
+ * 'escape' => function($value) {
82
+ * return htmlspecialchars($buffer, ENT_COMPAT, 'UTF-8');
83
+ * },
84
+ *
85
+ * // Type argument for `htmlspecialchars`. Defaults to ENT_COMPAT. You may prefer ENT_QUOTES.
86
+ * 'entity_flags' => ENT_QUOTES,
87
+ *
88
+ * // Character set for `htmlspecialchars`. Defaults to 'UTF-8'. Use 'UTF-8'.
89
+ * 'charset' => 'ISO-8859-1',
90
+ *
91
+ * // A Mustache Logger instance. No logging will occur unless this is set. Using a PSR-3 compatible
92
+ * // logging library -- such as Monolog -- is highly recommended. A simple stream logger implementation is
93
+ * // available as well:
94
+ * 'logger' => new Mustache_Logger_StreamLogger('php://stderr'),
95
+ *
96
+ * // Only treat Closure instances and invokable classes as callable. If true, values like
97
+ * // `array('ClassName', 'methodName')` and `array($classInstance, 'methodName')`, which are traditionally
98
+ * // "callable" in PHP, are not called to resolve variables for interpolation or section contexts. This
99
+ * // helps protect against arbitrary code execution when user input is passed directly into the template.
100
+ * // This currently defaults to false, but will default to true in v3.0.
101
+ * 'strict_callables' => true,
102
+ * );
103
+ *
104
+ * @throws Mustache_Exception_InvalidArgumentException If `escape` option is not callable.
105
+ *
106
+ * @param array $options (default: array())
107
+ */
108
+ public function __construct(array $options = array())
109
+ {
110
+ if (isset($options['template_class_prefix'])) {
111
+ $this->templateClassPrefix = $options['template_class_prefix'];
112
+ }
113
+
114
+ if (isset($options['cache'])) {
115
+ $this->cache = $options['cache'];
116
+ }
117
+
118
+ if (isset($options['cache_file_mode'])) {
119
+ $this->cacheFileMode = $options['cache_file_mode'];
120
+ }
121
+
122
+ if (isset($options['loader'])) {
123
+ $this->setLoader($options['loader']);
124
+ }
125
+
126
+ if (isset($options['partials_loader'])) {
127
+ $this->setPartialsLoader($options['partials_loader']);
128
+ }
129
+
130
+ if (isset($options['partials'])) {
131
+ $this->setPartials($options['partials']);
132
+ }
133
+
134
+ if (isset($options['helpers'])) {
135
+ $this->setHelpers($options['helpers']);
136
+ }
137
+
138
+ if (isset($options['escape'])) {
139
+ if (!is_callable($options['escape'])) {
140
+ throw new Mustache_Exception_InvalidArgumentException('Mustache Constructor "escape" option must be callable');
141
+ }
142
+
143
+ $this->escape = $options['escape'];
144
+ }
145
+
146
+ if (isset($options['entity_flags'])) {
147
+ $this->entityFlags = $options['entity_flags'];
148
+ }
149
+
150
+ if (isset($options['charset'])) {
151
+ $this->charset = $options['charset'];
152
+ }
153
+
154
+ if (isset($options['logger'])) {
155
+ $this->setLogger($options['logger']);
156
+ }
157
+
158
+ if (isset($options['strict_callables'])) {
159
+ $this->strictCallables = $options['strict_callables'];
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Shortcut 'render' invocation.
165
+ *
166
+ * Equivalent to calling `$mustache->loadTemplate($template)->render($context);`
167
+ *
168
+ * @see Mustache_Engine::loadTemplate
169
+ * @see Mustache_Template::render
170
+ *
171
+ * @param string $template
172
+ * @param mixed $context (default: array())
173
+ *
174
+ * @return string Rendered template
175
+ */
176
+ public function render($template, $context = array())
177
+ {
178
+ return $this->loadTemplate($template)->render($context);
179
+ }
180
+
181
+ /**
182
+ * Get the current Mustache escape callback.
183
+ *
184
+ * @return mixed Callable or null
185
+ */
186
+ public function getEscape()
187
+ {
188
+ return $this->escape;
189
+ }
190
+
191
+ /**
192
+ * Get the current Mustache entitity type to escape.
193
+ *
194
+ * @return int
195
+ */
196
+ public function getEntityFlags()
197
+ {
198
+ return $this->entityFlags;
199
+ }
200
+
201
+ /**
202
+ * Get the current Mustache character set.
203
+ *
204
+ * @return string
205
+ */
206
+ public function getCharset()
207
+ {
208
+ return $this->charset;
209
+ }
210
+
211
+ /**
212
+ * Set the Mustache template Loader instance.
213
+ *
214
+ * @param Mustache_Loader $loader
215
+ */
216
+ public function setLoader(Mustache_Loader $loader)
217
+ {
218
+ $this->loader = $loader;
219
+ }
220
+
221
+ /**
222
+ * Get the current Mustache template Loader instance.
223
+ *
224
+ * If no Loader instance has been explicitly specified, this method will instantiate and return
225
+ * a StringLoader instance.
226
+ *
227
+ * @return Mustache_Loader
228
+ */
229
+ public function getLoader()
230
+ {
231
+ if (!isset($this->loader)) {
232
+ $this->loader = new Mustache_Loader_StringLoader;
233
+ }
234
+
235
+ return $this->loader;
236
+ }
237
+
238
+ /**
239
+ * Set the Mustache partials Loader instance.
240
+ *
241
+ * @param Mustache_Loader $partialsLoader
242
+ */
243
+ public function setPartialsLoader(Mustache_Loader $partialsLoader)
244
+ {
245
+ $this->partialsLoader = $partialsLoader;
246
+ }
247
+
248
+ /**
249
+ * Get the current Mustache partials Loader instance.
250
+ *
251
+ * If no Loader instance has been explicitly specified, this method will instantiate and return
252
+ * an ArrayLoader instance.
253
+ *
254
+ * @return Mustache_Loader
255
+ */
256
+ public function getPartialsLoader()
257
+ {
258
+ if (!isset($this->partialsLoader)) {
259
+ $this->partialsLoader = new Mustache_Loader_ArrayLoader;
260
+ }
261
+
262
+ return $this->partialsLoader;
263
+ }
264
+
265
+ /**
266
+ * Set partials for the current partials Loader instance.
267
+ *
268
+ * @throws Mustache_Exception_RuntimeException If the current Loader instance is immutable
269
+ *
270
+ * @param array $partials (default: array())
271
+ */
272
+ public function setPartials(array $partials = array())
273
+ {
274
+ if (!isset($this->partialsLoader)) {
275
+ $this->partialsLoader = new Mustache_Loader_ArrayLoader;
276
+ }
277
+
278
+ if (!$this->partialsLoader instanceof Mustache_Loader_MutableLoader) {
279
+ throw new Mustache_Exception_RuntimeException('Unable to set partials on an immutable Mustache Loader instance');
280
+ }
281
+
282
+ $this->partialsLoader->setTemplates($partials);
283
+ }
284
+
285
+ /**
286
+ * Set an array of Mustache helpers.
287
+ *
288
+ * An array of 'helpers'. Helpers can be global variables or objects, closures (e.g. for higher order sections), or
289
+ * any other valid Mustache context value. They will be prepended to the context stack, so they will be available in
290
+ * any template loaded by this Mustache instance.
291
+ *
292
+ * @throws Mustache_Exception_InvalidArgumentException if $helpers is not an array or Traversable
293
+ *
294
+ * @param array|Traversable $helpers
295
+ */
296
+ public function setHelpers($helpers)
297
+ {
298
+ if (!is_array($helpers) && !$helpers instanceof Traversable) {
299
+ throw new Mustache_Exception_InvalidArgumentException('setHelpers expects an array of helpers');
300
+ }
301
+
302
+ $this->getHelpers()->clear();
303
+
304
+ foreach ($helpers as $name => $helper) {
305
+ $this->addHelper($name, $helper);
306
+ }
307
+ }
308
+
309
+ /**
310
+ * Get the current set of Mustache helpers.
311
+ *
312
+ * @see Mustache_Engine::setHelpers
313
+ *
314
+ * @return Mustache_HelperCollection
315
+ */
316
+ public function getHelpers()
317
+ {
318
+ if (!isset($this->helpers)) {
319
+ $this->helpers = new Mustache_HelperCollection;
320
+ }
321
+
322
+ return $this->helpers;
323
+ }
324
+
325
+ /**
326
+ * Add a new Mustache helper.
327
+ *
328
+ * @see Mustache_Engine::setHelpers
329
+ *
330
+ * @param string $name
331
+ * @param mixed $helper
332
+ */
333
+ public function addHelper($name, $helper)
334
+ {
335
+ $this->getHelpers()->add($name, $helper);
336
+ }
337
+
338
+ /**
339
+ * Get a Mustache helper by name.
340
+ *
341
+ * @see Mustache_Engine::setHelpers
342
+ *
343
+ * @param string $name
344
+ *
345
+ * @return mixed Helper
346
+ */
347
+ public function getHelper($name)
348
+ {
349
+ return $this->getHelpers()->get($name);
350
+ }
351
+
352
+ /**
353
+ * Check whether this Mustache instance has a helper.
354
+ *
355
+ * @see Mustache_Engine::setHelpers
356
+ *
357
+ * @param string $name
358
+ *
359
+ * @return boolean True if the helper is present
360
+ */
361
+ public function hasHelper($name)
362
+ {
363
+ return $this->getHelpers()->has($name);
364
+ }
365
+
366
+ /**
367
+ * Remove a helper by name.
368
+ *
369
+ * @see Mustache_Engine::setHelpers
370
+ *
371
+ * @param string $name
372
+ */
373
+ public function removeHelper($name)
374
+ {
375
+ $this->getHelpers()->remove($name);
376
+ }
377
+
378
+ /**
379
+ * Set the Mustache Logger instance.
380
+ *
381
+ * @throws Mustache_Exception_InvalidArgumentException If logger is not an instance of Mustache_Logger or Psr\Log\LoggerInterface.
382
+ *
383
+ * @param Mustache_Logger|Psr\Log\LoggerInterface $logger
384
+ */
385
+ public function setLogger($logger = null)
386
+ {
387
+ if ($logger !== null && !($logger instanceof Mustache_Logger || is_a($logger, 'Psr\\Log\\LoggerInterface'))) {
388
+ throw new Mustache_Exception_InvalidArgumentException('Expected an instance of Mustache_Logger or Psr\\Log\\LoggerInterface.');
389
+ }
390
+
391
+ $this->logger = $logger;
392
+ }
393
+
394
+ /**
395
+ * Get the current Mustache Logger instance.
396
+ *
397
+ * @return Mustache_Logger|Psr\Log\LoggerInterface
398
+ */
399
+ public function getLogger()
400
+ {
401
+ return $this->logger;
402
+ }
403
+
404
+ /**
405
+ * Set the Mustache Tokenizer instance.
406
+ *
407
+ * @param Mustache_Tokenizer $tokenizer
408
+ */
409
+ public function setTokenizer(Mustache_Tokenizer $tokenizer)
410
+ {
411
+ $this->tokenizer = $tokenizer;
412
+ }
413
+
414
+ /**
415
+ * Get the current Mustache Tokenizer instance.
416
+ *
417
+ * If no Tokenizer instance has been explicitly specified, this method will instantiate and return a new one.
418
+ *
419
+ * @return Mustache_Tokenizer
420
+ */
421
+ public function getTokenizer()
422
+ {
423
+ if (!isset($this->tokenizer)) {
424
+ $this->tokenizer = new Mustache_Tokenizer;
425
+ }
426
+
427
+ return $this->tokenizer;
428
+ }
429
+
430
+ /**
431
+ * Set the Mustache Parser instance.
432
+ *
433
+ * @param Mustache_Parser $parser
434
+ */
435
+ public function setParser(Mustache_Parser $parser)
436
+ {
437
+ $this->parser = $parser;
438
+ }
439
+
440
+ /**
441
+ * Get the current Mustache Parser instance.
442
+ *
443
+ * If no Parser instance has been explicitly specified, this method will instantiate and return a new one.
444
+ *
445
+ * @return Mustache_Parser
446
+ */
447
+ public function getParser()
448
+ {
449
+ if (!isset($this->parser)) {
450
+ $this->parser = new Mustache_Parser;
451
+ }
452
+
453
+ return $this->parser;
454
+ }
455
+
456
+ /**
457
+ * Set the Mustache Compiler instance.
458
+ *
459
+ * @param Mustache_Compiler $compiler
460
+ */
461
+ public function setCompiler(Mustache_Compiler $compiler)
462
+ {
463
+ $this->compiler = $compiler;
464
+ }
465
+
466
+ /**
467
+ * Get the current Mustache Compiler instance.
468
+ *
469
+ * If no Compiler instance has been explicitly specified, this method will instantiate and return a new one.
470
+ *
471
+ * @return Mustache_Compiler
472
+ */
473
+ public function getCompiler()
474
+ {
475
+ if (!isset($this->compiler)) {
476
+ $this->compiler = new Mustache_Compiler;
477
+ }
478
+
479
+ return $this->compiler;
480
+ }
481
+
482
+ /**
483
+ * Helper method to generate a Mustache template class.
484
+ *
485
+ * @param string $source
486
+ *
487
+ * @return string Mustache Template class name
488
+ */
489
+ public function getTemplateClassName($source)
490
+ {
491
+ return $this->templateClassPrefix . md5(sprintf(
492
+ 'version:%s,escape:%s,entity_flags:%d,charset:%s,strict_callables:%s,source:%s',
493
+ self::VERSION,
494
+ isset($this->escape) ? 'custom' : 'default',
495
+ $this->entityFlags,
496
+ $this->charset,
497
+ $this->strictCallables ? 'true' : 'false',
498
+ $source
499
+ ));
500
+ }
501
+
502
+ /**
503
+ * Load a Mustache Template by name.
504
+ *
505
+ * @param string $name
506
+ *
507
+ * @return Mustache_Template
508
+ */
509
+ public function loadTemplate($name)
510
+ {
511
+ return $this->loadSource($this->getLoader()->load($name));
512
+ }
513
+
514
+ /**
515
+ * Load a Mustache partial Template by name.
516
+ *
517
+ * This is a helper method used internally by Template instances for loading partial templates. You can most likely
518
+ * ignore it completely.
519
+ *
520
+ * @param string $name
521
+ *
522
+ * @return Mustache_Template
523
+ */
524
+ public function loadPartial($name)
525
+ {
526
+ try {
527
+ if (isset($this->partialsLoader)) {
528
+ $loader = $this->partialsLoader;
529
+ } elseif (isset($this->loader) && !$this->loader instanceof Mustache_Loader_StringLoader) {
530
+ $loader = $this->loader;
531
+ } else {
532
+ throw new Mustache_Exception_UnknownTemplateException($name);
533
+ }
534
+
535
+ return $this->loadSource($loader->load($name));
536
+ } catch (Mustache_Exception_UnknownTemplateException $e) {
537
+ // If the named partial cannot be found, log then return null.
538
+ $this->log(
539
+ Mustache_Logger::WARNING,
540
+ 'Partial not found: "{name}"',
541
+ array('name' => $e->getTemplateName())
542
+ );
543
+ }
544
+ }
545
+
546
+ /**
547
+ * Load a Mustache lambda Template by source.
548
+ *
549
+ * This is a helper method used by Template instances to generate subtemplates for Lambda sections. You can most
550
+ * likely ignore it completely.
551
+ *
552
+ * @param string $source
553
+ * @param string $delims (default: null)
554
+ *
555
+ * @return Mustache_Template
556
+ */
557
+ public function loadLambda($source, $delims = null)
558
+ {
559
+ if ($delims !== null) {
560
+ $source = $delims . "\n" . $source;
561
+ }
562
+
563
+ return $this->loadSource($source);
564
+ }
565
+
566
+ /**
567
+ * Instantiate and return a Mustache Template instance by source.
568
+ *
569
+ * @see Mustache_Engine::loadTemplate
570
+ * @see Mustache_Engine::loadPartial
571
+ * @see Mustache_Engine::loadLambda
572
+ *
573
+ * @param string $source
574
+ *
575
+ * @return Mustache_Template
576
+ */
577
+ private function loadSource($source)
578
+ {
579
+ $className = $this->getTemplateClassName($source);
580
+
581
+ if (!isset($this->templates[$className])) {
582
+ if (!class_exists($className, false)) {
583
+ if ($fileName = $this->getCacheFilename($source)) {
584
+ if (!is_file($fileName)) {
585
+ $this->log(
586
+ Mustache_Logger::DEBUG,
587
+ 'Writing "{className}" class to template cache: "{fileName}"',
588
+ array('className' => $className, 'fileName' => $fileName)
589
+ );
590
+
591
+ $this->writeCacheFile($fileName, $this->compile($source));
592
+ }
593
+
594
+ require_once $fileName;
595
+ } else {
596
+ $this->log(
597
+ Mustache_Logger::WARNING,
598
+ 'Template cache disabled, evaluating "{className}" class at runtime',
599
+ array('className' => $className)
600
+ );
601
+
602
+ eval('?>'.$this->compile($source));
603
+ }
604
+ }
605
+
606
+ $this->log(
607
+ Mustache_Logger::DEBUG,
608
+ 'Instantiating template: "{className}"',
609
+ array('className' => $className)
610
+ );
611
+
612
+ $this->templates[$className] = new $className($this);
613
+ }
614
+
615
+ return $this->templates[$className];
616
+ }
617
+
618
+ /**
619
+ * Helper method to tokenize a Mustache template.
620
+ *
621
+ * @see Mustache_Tokenizer::scan
622
+ *
623
+ * @param string $source
624
+ *
625
+ * @return array Tokens
626
+ */
627
+ private function tokenize($source)
628
+ {
629
+ return $this->getTokenizer()->scan($source);
630
+ }
631
+
632
+ /**
633
+ * Helper method to parse a Mustache template.
634
+ *
635
+ * @see Mustache_Parser::parse
636
+ *
637
+ * @param string $source
638
+ *
639
+ * @return array Token tree
640
+ */
641
+ private function parse($source)
642
+ {
643
+ return $this->getParser()->parse($this->tokenize($source));
644
+ }
645
+
646
+ /**
647
+ * Helper method to compile a Mustache template.
648
+ *
649
+ * @see Mustache_Compiler::compile
650
+ *
651
+ * @param string $source
652
+ *
653
+ * @return string generated Mustache template class code
654
+ */
655
+ private function compile($source)
656
+ {
657
+ $tree = $this->parse($source);
658
+ $name = $this->getTemplateClassName($source);
659
+
660
+ $this->log(
661
+ Mustache_Logger::INFO,
662
+ 'Compiling template to "{className}" class',
663
+ array('className' => $name)
664
+ );
665
+
666
+ return $this->getCompiler()->compile($source, $tree, $name, isset($this->escape), $this->charset, $this->strictCallables, $this->entityFlags);
667
+ }
668
+
669
+ /**
670
+ * Helper method to generate a Mustache Template class cache filename.
671
+ *
672
+ * @param string $source
673
+ *
674
+ * @return string Mustache Template class cache filename
675
+ */
676
+ private function getCacheFilename($source)
677
+ {
678
+ if ($this->cache) {
679
+ return sprintf('%s/%s.php', $this->cache, $this->getTemplateClassName($source));
680
+ }
681
+ }
682
+
683
+ /**
684
+ * Helper method to dump a generated Mustache Template subclass to the file cache.
685
+ *
686
+ * @throws Mustache_Exception_RuntimeException if unable to create the cache directory or write to $fileName.
687
+ *
688
+ * @param string $fileName
689
+ * @param string $source
690
+ *
691
+ * @codeCoverageIgnore
692
+ */
693
+ private function writeCacheFile($fileName, $source)
694
+ {
695
+ $dirName = dirname($fileName);
696
+ if (!is_dir($dirName)) {
697
+ $this->log(
698
+ Mustache_Logger::INFO,
699
+ 'Creating Mustache template cache directory: "{dirName}"',
700
+ array('dirName' => $dirName)
701
+ );
702
+
703
+ @mkdir($dirName, 0777, true);
704
+ if (!is_dir($dirName)) {
705
+ throw new Mustache_Exception_RuntimeException(sprintf('Failed to create cache directory "%s".', $dirName));
706
+ }
707
+
708
+ }
709
+
710
+ $this->log(
711
+ Mustache_Logger::DEBUG,
712
+ 'Caching compiled template to "{fileName}"',
713
+ array('fileName' => $fileName)
714
+ );
715
+
716
+ $tempFile = tempnam($dirName, basename($fileName));
717
+ if (false !== @file_put_contents($tempFile, $source)) {
718
+ if (@rename($tempFile, $fileName)) {
719
+ $mode = isset($this->cacheFileMode) ? $this->cacheFileMode : (0666 & ~umask());
720
+ @chmod($fileName, $mode);
721
+
722
+ return;
723
+ }
724
+
725
+ $this->log(
726
+ Mustache_Logger::ERROR,
727
+ 'Unable to rename Mustache temp cache file: "{tempName}" -> "{fileName}"',
728
+ array('tempName' => $tempFile, 'fileName' => $fileName)
729
+ );
730
+ }
731
+
732
+ throw new Mustache_Exception_RuntimeException(sprintf('Failed to write cache file "%s".', $fileName));
733
+ }
734
+
735
+ /**
736
+ * Add a log record if logging is enabled.
737
+ *
738
+ * @param integer $level The logging level
739
+ * @param string $message The log message
740
+ * @param array $context The log context
741
+ */
742
+ private function log($level, $message, array $context = array())
743
+ {
744
+ if (isset($this->logger)) {
745
+ $this->logger->log($level, $message, $context);
746
+ }
747
+ }
748
+ }
assets/lib/Mustache/Exception.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Mustache.php.
5
+ *
6
+ * (c) 2013 Justin Hileman
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * A Mustache Exception interface.
14
+ */
15
+ interface Mustache_Exception
16
+ {
17
+ // This space intentionally left blank.
18
+ }
assets/lib/Mustache/Exception/InvalidArgumentException.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Mustache.php.
5
+ *
6
+ * (c) 2013 Justin Hileman
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * Invalid argument exception.
14
+ */
15
+ class Mustache_Exception_InvalidArgumentException extends InvalidArgumentException implements Mustache_Exception
16
+ {
17
+ // This space intentionally left blank.
18
+ }
assets/lib/Mustache/Exception/LogicException.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Mustache.php.
5
+ *
6
+ * (c) 2013 Justin Hileman
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * Logic exception.
14
+ */
15
+ class Mustache_Exception_LogicException extends LogicException implements Mustache_Exception
16
+ {
17
+ // This space intentionally left blank.
18
+ }
assets/lib/Mustache/Exception/RuntimeException.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Mustache.php.
5
+ *
6
+ * (c) 2013 Justin Hileman
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * Runtime exception.
14
+ */
15
+ class Mustache_Exception_RuntimeException extends RuntimeException implements Mustache_Exception
16
+ {
17
+ // This space intentionally left blank.
18
+ }
assets/lib/Mustache/Exception/SyntaxException.php ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Mustache.php.
5
+ *
6
+ * (c) 2013 Justin Hileman
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * Mustache syntax exception.
14
+ */
15
+ class Mustache_Exception_SyntaxException extends LogicException implements Mustache_Exception
16
+ {
17
+ protected $token;
18
+
19
+ public function __construct($msg, array $token)
20
+ {
21
+ $this->token = $token;
22
+ parent::__construct($msg);
23
+ }
24
+
25
+ public function getToken()
26
+ {
27
+ return $this->token;
28
+ }
29
+ }
assets/lib/Mustache/Exception/UnknownFilterException.php ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Mustache.php.
5
+ *
6
+ * (c) 2013 Justin Hileman
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * Unknown filter exception.
14
+ */
15
+ class Mustache_Exception_UnknownFilterException extends UnexpectedValueException implements Mustache_Exception
16
+ {
17
+ protected $filterName;
18
+
19
+ public function __construct($filterName)
20
+ {
21
+ $this->filterName = $filterName;
22
+ parent::__construct(sprintf('Unknown filter: %s', $filterName));
23
+ }
24
+
25
+ public function getFilterName()
26
+ {
27
+ return $this->filterName;
28
+ }
29
+ }
assets/lib/Mustache/Exception/UnknownHelperException.php ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Mustache.php.
5
+ *
6
+ * (c) 2013 Justin Hileman
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * Unknown helper exception.
14
+ */
15
+ class Mustache_Exception_UnknownHelperException extends InvalidArgumentException implements Mustache_Exception
16
+ {
17
+ protected $helperName;
18
+
19
+ public function __construct($helperName)
20
+ {
21
+ $this->helperName = $helperName;
22
+ parent::__construct(sprintf('Unknown helper: %s', $helperName));
23
+ }
24
+
25
+ public function getHelperName()
26
+ {
27
+ return $this->helperName;
28
+ }
29
+ }
assets/lib/Mustache/Exception/UnknownTemplateException.php ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Mustache.php.
5
+ *
6
+ * (c) 2013 Justin Hileman
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * Unknown template exception.
14
+ */
15
+ class Mustache_Exception_UnknownTemplateException extends InvalidArgumentException implements Mustache_Exception
16
+ {
17
+ protected $templateName;
18
+
19
+ public function __construct($templateName)
20
+ {
21
+ $this->templateName = $templateName;
22
+ parent::__construct(sprintf('Unknown template: %s', $templateName));
23
+ }
24
+
25
+ public function getTemplateName()
26
+ {
27
+ return $this->templateName;
28
+ }
29
+ }
assets/lib/Mustache/HelperCollection.php ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Mustache.php.
5
+ *
6
+ * (c) 2012 Justin Hileman
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * A collection of helpers for a Mustache instance.
14
+ */
15
+ class Mustache_HelperCollection
16
+ {
17
+ private $helpers = array();
18
+
19
+ /**
20
+ * Helper Collection constructor.
21
+ *
22
+ * Optionally accepts an array (or Traversable) of `$name => $helper` pairs.
23
+ *
24
+ * @throws Mustache_Exception_InvalidArgumentException if the $helpers argument isn't an array or Traversable
25
+ *
26
+ * @param array|Traversable $helpers (default: null)
27
+ */
28
+ public function __construct($helpers = null)
29
+ {
30
+ if ($helpers !== null) {
31
+ if (!is_array($helpers) && !$helpers instanceof Traversable) {
32
+ throw new Mustache_Exception_InvalidArgumentException('HelperCollection constructor expects an array of helpers');
33
+ }
34
+
35
+ foreach ($helpers as $name => $helper) {
36
+ $this->add($name, $helper);
37
+ }
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Magic mutator.
43
+ *
44
+ * @see Mustache_HelperCollection::add
45
+ *
46
+ * @param string $name
47
+ * @param mixed $helper
48
+ */
49
+ public function __set($name, $helper)
50
+ {
51
+ $this->add($name, $helper);
52
+ }
53
+
54
+ /**
55
+ * Add a helper to this collection.
56
+ *
57
+ * @param string $name
58
+ * @param mixed $helper
59
+ */
60
+ public function add($name, $helper)
61
+ {
62
+ $this->helpers[$name] = $helper;
63
+ }
64
+
65
+ /**
66
+ * Magic accessor.
67
+ *
68
+ * @see Mustache_HelperCollection::get
69
+ *
70
+ * @param string $name
71
+ *
72
+ * @return mixed Helper
73
+ */
74
+ public function __get($name)
75
+ {
76
+ return $this->get($name);
77
+ }
78
+
79
+ /**
80
+ * Get a helper by name.
81
+ *
82
+ * @throws Mustache_Exception_UnknownHelperException If helper does not exist.
83
+ *
84
+ * @param string $name
85
+ *
86
+ * @return mixed Helper
87
+ */
88
+ public function get($name)
89
+ {
90
+ if (!$this->has($name)) {
91
+ throw new Mustache_Exception_UnknownHelperException($name);
92
+ }
93
+
94
+ return $this->helpers[$name];
95
+ }
96
+
97
+ /**
98
+ * Magic isset().
99
+ *
100
+ * @see Mustache_HelperCollection::has
101
+ *
102
+ * @param string $name
103
+ *
104
+ * @return boolean True if helper is present
105
+ */
106
+ public function __isset($name)
107
+ {
108
+ return $this->has($name);
109
+ }
110
+
111
+ /**
112
+ * Check whether a given helper is present in the collection.
113
+ *
114
+ * @param string $name
115
+ *
116
+ * @return boolean True if helper is present
117
+ */
118
+ public function has($name)
119
+ {
120
+ return array_key_exists($name, $this->helpers);
121
+ }
122
+
123
+ /**
124
+ * Magic unset().
125
+ *
126
+ * @see Mustache_HelperCollection::remove
127
+ *
128
+ * @param string $name
129
+ */
130
+ public function __unset($name)
131
+ {
132
+ $this->remove($name);
133
+ }
134
+
135
+ /**
136
+ * Check whether a given helper is present in the collection.
137
+ *
138
+ * @throws Mustache_Exception_UnknownHelperException if the requested helper is not present.
139
+ *
140
+ * @param string $name
141
+ */
142
+ public function remove($name)
143
+ {
144
+ if (!$this->has($name)) {
145
+ throw new Mustache_Exception_UnknownHelperException($name);
146
+ }
147
+
148
+ unset($this->helpers[$name]);
149
+ }
150
+
151
+ /**
152
+ * Clear the helper collection.
153
+ *
154
+ * Removes all helpers from this collection
155
+ */
156
+ public function clear()
157
+ {
158
+ $this->helpers = array();
159
+ }
160
+
161
+ /**
162
+ * Check whether the helper collection is empty.
163
+ *
164
+ * @return boolean True if the collection is empty
165
+ */
166
+ public function isEmpty()
167
+ {
168
+ return empty($this->helpers);
169
+ }
170
+ }
assets/lib/Mustache/LambdaHelper.php ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Mustache.php.
5
+ *
6
+ * (c) 2012 Justin Hileman
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * Mustache Lambda Helper.
14
+ *
15
+ * Passed as the second argument to section lambdas (higher order sections),
16
+ * giving them access to a `render` method for rendering a string with the
17
+ * current context.
18
+ */
19
+ class Mustache_LambdaHelper
20
+ {
21
+ private $mustache;
22
+ private $context;
23
+
24
+ /**
25
+ * Mustache Lambda Helper constructor.
26
+ *
27
+ * @param Mustache_Engine $mustache Mustache engine instance.
28
+ * @param Mustache_Context $context Rendering context.
29
+ */
30
+ public function __construct(Mustache_Engine $mustache, Mustache_Context $context)
31
+ {
32
+ $this->mustache = $mustache;
33
+ $this->context = $context;
34
+ }
35
+
36
+ /**
37
+ * Render a string as a Mustache template with the current rendering context.
38
+ *
39
+ * @param string $string
40
+ *
41
+ * @return Rendered template.
42
+ */
43
+ public function render($string)
44
+ {
45
+ return $this->mustache
46
+ ->loadLambda((string) $string)
47
+ ->renderInternal($this->context);
48
+ }
49
+ }
assets/lib/Mustache/Loader.php ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Mustache.php.
5
+ *
6
+ * (c) 2012 Justin Hileman
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * Mustache Template Loader interface.
14
+ */
15
+ interface Mustache_Loader
16
+ {
17
+
18
+ /**
19
+ * Load a Template by name.
20
+ *
21
+ * @throws Mustache_Exception_UnknownTemplateException If a template file is not found.
22
+ *
23
+ * @param string $name
24
+ *
25
+ * @return string Mustache Template source
26
+ */
27
+ public function load($name);
28
+ }
assets/lib/Mustache/Loader/ArrayLoader.php ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Mustache.php.
5
+ *
6
+ * (c) 2012 Justin Hileman
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * Mustache Template array Loader implementation.
14
+ *
15
+ * An ArrayLoader instance loads Mustache Template source by name from an initial array:
16
+ *
17
+ * $loader = new ArrayLoader(
18
+ * 'foo' => '{{ bar }}',
19
+ * 'baz' => 'Hey {{ qux }}!'
20
+ * );
21
+ *
22
+ * $tpl = $loader->load('foo'); // '{{ bar }}'
23
+ *
24
+ * The ArrayLoader is used internally as a partials loader by Mustache_Engine instance when an array of partials
25
+ * is set. It can also be used as a quick-and-dirty Template loader.
26
+ */
27
+ class Mustache_Loader_ArrayLoader implements Mustache_Loader, Mustache_Loader_MutableLoader
28
+ {
29
+
30
+ /**
31
+ * ArrayLoader constructor.
32
+ *
33
+ * @param array $templates Associative array of Template source (default: array())
34
+ */
35
+ public function __construct(array $templates = array())
36
+ {
37
+ $this->templates = $templates;
38
+ }
39
+
40
+ /**
41
+ * Load a Template.
42
+ *
43
+ * @throws Mustache_Exception_UnknownTemplateException If a template file is not found.
44
+ *
45
+ * @param string $name
46
+ *
47
+ * @return string Mustache Template source
48
+ */
49
+ public function load($name)
50
+ {
51
+ if (!isset($this->templates[$name])) {
52
+ throw new Mustache_Exception_UnknownTemplateException($name);
53
+ }
54
+
55
+ return $this->templates[$name];
56
+ }
57
+
58
+ /**
59
+ * Set an associative array of Template sources for this loader.
60
+ *
61
+ * @param array $templates
62
+ */
63
+ public function setTemplates(array $templates)
64
+ {
65
+ $this->templates = $templates;
66
+ }
67
+
68
+ /**
69
+ * Set a Template source by name.
70
+ *
71
+ * @param string $name
72
+ * @param string $template Mustache Template source
73
+ */
74
+ public function setTemplate($name, $template)
75
+ {
76
+ $this->templates[$name] = $template;
77
+ }
78
+ }
assets/lib/Mustache/Loader/CascadingLoader.php ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Mustache.php.
5
+ *
6
+ * (c) 2013 Justin Hileman
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * A Mustache Template cascading loader implementation, which delegates to other
14
+ * Loader instances.
15
+ */
16
+ class Mustache_Loader_CascadingLoader implements Mustache_Loader
17
+ {
18
+ private $loaders;
19
+
20
+ /**
21
+ * Construct a CascadingLoader with an array of loaders:
22
+ *
23
+ * $loader = new Mustache_Loader_CascadingLoader(array(
24
+ * new Mustache_Loader_InlineLoader(__FILE__, __COMPILER_HALT_OFFSET__),
25
+ * new Mustache_Loader_FilesystemLoader(__DIR__.'/templates')
26
+ * ));
27
+ *
28
+ * @param array $loaders An array of Mustache Loader instances
29
+ */
30
+ public function __construct(array $loaders = array())
31
+ {
32
+ $this->loaders = array();
33
+ foreach ($loaders as $loader) {
34
+ $this->addLoader($loader);
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Add a Loader instance.
40
+ *
41
+ * @param Mustache_Loader $loader A Mustache Loader instance
42
+ */
43
+ public function addLoader(Mustache_Loader $loader)
44
+ {
45
+ $this->loaders[] = $loader;
46
+ }
47
+
48
+ /**
49
+ * Load a Template by name.
50
+ *
51
+ * @throws Mustache_Exception_UnknownTemplateException If a template file is not found.
52
+ *
53
+ * @param string $name
54
+ *
55
+ * @return string Mustache Template source
56
+ */
57
+ public function load($name)
58
+ {
59
+ foreach ($this->loaders as $loader) {
60
+ try {
61
+ return $loader->load($name);
62
+ } catch (Mustache_Exception_UnknownTemplateException $e) {
63
+ // do nothing, check the next loader.
64
+ }
65
+ }
66
+
67
+ throw new Mustache_Exception_UnknownTemplateException($name);
68
+ }
69
+ }
assets/lib/Mustache/Loader/FilesystemLoader.php ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Mustache.php.
5
+ *
6
+ * (c) 2012 Justin Hileman
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * Mustache Template filesystem Loader implementation.
14
+ *
15
+ * A FilesystemLoader instance loads Mustache Template source from the filesystem by name:
16
+ *
17
+ * $loader = new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/views');
18
+ * $tpl = $loader->load('foo'); // equivalent to `file_get_contents(dirname(__FILE__).'/views/foo.mustache');
19
+ *
20
+ * This is probably the most useful Mustache Loader implementation. It can be used for partials and normal Templates:
21
+ *
22
+ * $m = new Mustache(array(
23
+ * 'loader' => new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/views'),
24
+ * 'partials_loader' => new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/views/partials'),
25
+ * ));
26
+ */
27
+ class Mustache_Loader_FilesystemLoader implements Mustache_Loader
28
+ {
29
+ private $baseDir;
30
+ private $extension = '.mustache';
31
+ private $templates = array();
32
+
33
+ /**
34
+ * Mustache filesystem Loader constructor.
35
+ *
36
+ * Passing an $options array allows overriding certain Loader options during instantiation:
37
+ *
38
+ * $options = array(
39
+ * // The filename extension used for Mustache templates. Defaults to '.mustache'
40
+ * 'extension' => '.ms',
41
+ * );
42
+ *
43
+ * @throws Mustache_Exception_RuntimeException if $baseDir does not exist.
44
+ *
45
+ * @param string $baseDir Base directory containing Mustache template files.
46
+ * @param array $options Array of Loader options (default: array())
47
+ */
48
+ public function __construct($baseDir, array $options = array())
49
+ {
50
+ $this->baseDir = $baseDir;
51
+
52
+ if (strpos($this->baseDir, '://') === -1) {
53
+ $this->baseDir = realpath($this->baseDir);
54
+ }
55
+
56
+ if (!is_dir($this->baseDir)) {
57
+ throw new Mustache_Exception_RuntimeException(sprintf('FilesystemLoader baseDir must be a directory: %s', $baseDir));
58
+ }
59
+
60
+ if (array_key_exists('extension', $options)) {
61
+ if (empty($options['extension'])) {
62
+ $this->extension = '';
63
+ } else {
64
+ $this->extension = '.' . ltrim($options['extension'], '.');
65
+ }
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Load a Template by name.
71
+ *
72
+ * $loader = new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/views');
73
+ * $loader->load('admin/dashboard'); // loads "./views/admin/dashboard.mustache";
74
+ *
75
+ * @param string $name
76
+ *
77
+ * @return string Mustache Template source
78
+ */
79
+ public function load($name)
80
+ {
81
+ if (!isset($this->templates[$name])) {
82
+ $this->templates[$name] = $this->loadFile($name);
83
+ }
84
+
85
+ return $this->templates[$name];
86
+ }
87
+
88
+ /**
89
+ * Helper function for loading a Mustache file by name.
90
+ *
91
+ * @throws Mustache_Exception_UnknownTemplateException If a template file is not found.
92
+ *
93
+ * @param string $name
94
+ *
95
+ * @return string Mustache Template source
96
+ */
97
+ protected function loadFile($name)
98
+ {
99
+ $fileName = $this->getFileName($name);
100
+
101
+ if (!file_exists($fileName)) {
102
+ throw new Mustache_Exception_UnknownTemplateException($name);
103
+ }
104
+
105
+ return file_get_contents($fileName);
106
+ }
107
+
108
+ /**
109
+ * Helper function for getting a Mustache template file name.
110
+ *
111
+ * @param string $name
112
+ *
113
+ * @return string Template file name
114
+ */
115
+ protected function getFileName($name)
116
+ {
117
+ $fileName = $this->baseDir . '/' . $name;
118
+ if (substr($fileName, 0 - strlen($this->extension)) !== $this->extension) {
119
+ $fileName .= $this->extension;
120
+ }
121
+
122
+ return $fileName;
123
+ }
124
+ }
assets/lib/Mustache/Loader/InlineLoader.php ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Mustache.php.
5
+ *
6
+ * (c) 2013 Justin Hileman
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * A Mustache Template loader for inline templates.
14
+ *
15
+ * With the InlineLoader, templates can be defined at the end of any PHP source
16
+ * file:
17
+ *
18
+ * $loader = new Mustache_Loader_InlineLoader(__FILE__, __COMPILER_HALT_OFFSET__);
19
+ * $hello = $loader->load('hello');
20
+ * $goodbye = $loader->load('goodbye');
21
+ *
22
+ * __halt_compiler();
23
+ *
24
+ * @@ hello
25
+ * Hello, {{ planet }}!
26
+ *
27
+ * @@ goodbye
28
+ * Goodbye, cruel {{ planet }}
29
+ *
30
+ * Templates are deliniated by lines containing only `@@ name`.
31
+ *
32
+ * The InlineLoader is well-suited to micro-frameworks such as Silex:
33
+ *
34
+ * $app->register(new MustacheServiceProvider, array(
35
+ * 'mustache.loader' => new Mustache_Loader_InlineLoader(__FILE__, __COMPILER_HALT_OFFSET__)
36
+ * ));
37
+ *
38
+ * $app->get('/{name}', function($name) use ($app) {
39
+ * return $app['mustache']->render('hello', compact('name'));
40
+ * })
41
+ * ->value('name', 'world');
42
+ *
43
+ * // ...
44
+ *
45
+ * __halt_compiler();
46
+ *
47
+ * @@ hello
48
+ * Hello, {{ name }}!
49
+ *
50
+ */
51
+ class Mustache_Loader_InlineLoader implements Mustache_Loader
52
+ {
53
+ protected $fileName;
54
+ protected $offset;
55
+ protected $templates;
56
+
57
+ /**
58
+ * The InlineLoader requires a filename and offset to process templates.
59
+ * The magic constants `__FILE__` and `__COMPILER_HALT_OFFSET__` are usually
60
+ * perfectly suited to the job:
61
+ *
62
+ * $loader = new Mustache_Loader_InlineLoader(__FILE__, __COMPILER_HALT_OFFSET__);
63
+ *
64
+ * Note that this only works if the loader is instantiated inside the same
65
+ * file as the inline templates. If the templates are located in another
66
+ * file, it would be necessary to manually specify the filename and offset.
67
+ *
68
+ * @param string $fileName The file to parse for inline templates
69
+ * @param int $offset A string offset for the start of the templates.
70
+ * This usually coincides with the `__halt_compiler`
71
+ * call, and the `__COMPILER_HALT_OFFSET__`.
72
+ */
73
+ public function __construct($fileName, $offset)
74
+ {
75
+ if (!is_file($fileName)) {
76
+ throw new Mustache_Exception_InvalidArgumentException('InlineLoader expects a valid filename.');
77
+ }
78
+
79
+ if (!is_int($offset) || $offset < 0) {
80
+ throw new Mustache_Exception_InvalidArgumentException('InlineLoader expects a valid file offset.');
81
+ }
82
+
83
+ $this->fileName = $fileName;
84
+ $this->offset = $offset;
85
+ }
86
+
87
+ /**
88
+ * Load a Template by name.
89
+ *
90
+ * @throws Mustache_Exception_UnknownTemplateException If a template file is not found.
91
+ *
92
+ * @param string $name
93
+ *
94
+ * @return string Mustache Template source
95
+ */
96
+ public function load($name)
97
+ {
98
+ $this->loadTemplates();
99
+
100
+ if (!array_key_exists($name, $this->templates)) {
101
+ throw new Mustache_Exception_UnknownTemplateException($name);
102
+ }
103
+
104
+ return $this->templates[$name];
105
+ }
106
+
107
+ /**
108
+ * Parse and load templates from the end of a source file.
109
+ */
110
+ protected function loadTemplates()
111
+ {
112
+ if ($this->templates === null) {
113
+ $this->templates = array();
114
+ $data = file_get_contents($this->fileName, false, null, $this->offset);
115
+ foreach (preg_split("/^@@(?= [\w\d\.]+$)/m", $data, -1) as $chunk) {
116
+ if (trim($chunk)) {
117
+ list($name, $content) = explode("\n", $chunk, 2);
118
+ $this->templates[trim($name)] = trim($content);
119
+ }
120
+ }
121
+ }
122
+ }
123
+ }
assets/lib/Mustache/Loader/MutableLoader.php ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Mustache.php.
5
+ *
6
+ * (c) 2012 Justin Hileman
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * Mustache Template mutable Loader interface.
14
+ */
15
+ interface Mustache_Loader_MutableLoader
16
+ {
17
+
18
+ /**
19
+ * Set an associative array of Template sources for this loader.
20
+ *
21
+ * @param array $templates
22
+ */
23
+ public function setTemplates(array $templates);
24
+
25
+ /**
26
+ * Set a Template source by name.
27
+ *
28
+ * @param string $name
29
+ * @param string $template Mustache Template source
30
+ */
31
+ public function setTemplate($name, $template);
32
+ }
assets/lib/Mustache/Loader/StringLoader.php ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Mustache.php.
5
+ *
6
+ * (c) 2012 Justin Hileman
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * Mustache Template string Loader implementation.
14
+ *
15
+ * A StringLoader instance is essentially a noop. It simply passes the 'name' argument straight through:
16
+ *
17
+ * $loader = new StringLoader;
18
+ * $tpl = $loader->load('{{ foo }}'); // '{{ foo }}'
19
+ *
20
+ * This is the default Template Loader instance used by Mustache:
21
+ *
22
+ * $m = new Mustache;
23
+ * $tpl = $m->loadTemplate('{{ foo }}');
24
+ * echo $tpl->render(array('foo' => 'bar')); // "bar"
25
+ */
26
+ class Mustache_Loader_StringLoader implements Mustache_Loader
27
+ {
28
+
29
+ /**
30
+ * Load a Template by source.
31
+ *
32
+ * @param string $name Mustache Template source
33
+ *
34
+ * @return string Mustache Template source
35
+ */
36
+ public function load($name)
37
+ {
38
+ return $name;
39
+ }
40
+ }
assets/lib/Mustache/Logger.php ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Mustache.php.
5
+ *
6
+ * (c) 2012 Justin Hileman
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * Describes a Mustache logger instance
14
+ *
15
+ * This is identical to the Psr\Log\LoggerInterface.
16
+ *
17
+ * The message MUST be a string or object implementing __toString().
18
+ *
19
+ * The message MAY contain placeholders in the form: {foo} where foo
20
+ * will be replaced by the context data in key "foo".
21
+ *
22
+ * The context array can contain arbitrary data, the only assumption that
23
+ * can be made by implementors is that if an Exception instance is given
24
+ * to produce a stack trace, it MUST be in a key named "exception".
25
+ *
26
+ * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
27
+ * for the full interface specification.
28
+ */
29
+ interface Mustache_Logger
30
+ {
31
+ /**
32
+ * Psr\Log compatible log levels
33
+ */
34
+ const EMERGENCY = 'emergency';
35
+ const ALERT = 'alert';
36
+ const CRITICAL = 'critical';
37
+ const ERROR = 'error';
38
+ const WARNING = 'warning';
39
+ const NOTICE = 'notice';
40
+ const INFO = 'info';
41
+ const DEBUG = 'debug';
42
+
43
+ /**
44
+ * System is unusable.
45
+ *
46
+ * @param string $message
47
+ * @param array $context
48
+ * @return null
49
+ */
50
+ public function emergency($message, array $context = array());
51
+
52
+ /**
53
+ * Action must be taken immediately.
54
+ *
55
+ * Example: Entire website down, database unavailable, etc. This should
56
+ * trigger the SMS alerts and wake you up.
57
+ *
58
+ * @param string $message
59
+ * @param array $context
60
+ * @return null
61
+ */
62
+ public function alert($message, array $context = array());
63
+
64
+ /**
65
+ * Critical conditions.
66
+ *
67
+ * Example: Application component unavailable, unexpected exception.
68
+ *
69
+ * @param string $message
70
+ * @param array $context
71
+ * @return null
72
+ */
73
+ public function critical($message, array $context = array());
74
+
75
+ /**
76
+ * Runtime errors that do not require immediate action but should typically
77
+ * be logged and monitored.
78
+ *
79
+ * @param string $message
80
+ * @param array $context
81
+ * @return null
82
+ */
83
+ public function error($message, array $context = array());
84
+
85
+ /**
86
+ * Exceptional occurrences that are not errors.
87
+ *
88
+ * Example: Use of deprecated APIs, poor use of an API, undesirable things
89
+ * that are not necessarily wrong.
90
+ *
91
+ * @param string $message
92
+ * @param array $context
93
+ * @return null
94
+ */
95
+ public function warning($message, array $context = array());
96
+
97
+ /**
98
+ * Normal but significant events.
99
+ *
100
+ * @param string $message
101
+ * @param array $context
102
+ * @return null
103
+ */
104
+ public function notice($message, array $context = array());
105
+
106
+ /**
107
+ * Interesting events.
108
+ *
109
+ * Example: User logs in, SQL logs.
110
+ *
111
+ * @param string $message
112
+ * @param array $context
113
+ * @return null
114
+ */
115
+ public function info($message, array $context = array());
116
+
117
+ /**
118
+ * Detailed debug information.
119
+ *
120
+ * @param string $message
121
+ * @param array $context
122
+ * @return null
123
+ */
124
+ public function debug($message, array $context = array());
125
+
126
+ /**
127
+ * Logs with an arbitrary level.
128
+ *
129
+ * @param mixed $level
130
+ * @param string $message
131
+ * @param array $context
132
+ * @return null
133
+ */
134
+ public function log($level, $message, array $context = array());
135
+ }
assets/lib/Mustache/Logger/AbstractLogger.php ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Mustache.php.
5
+ *
6
+ * (c) 2012 Justin Hileman
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * This is a simple Logger implementation that other Loggers can inherit from.
14
+ *
15
+ * This is identical to the Psr\Log\AbstractLogger.
16
+ *
17
+ * It simply delegates all log-level-specific methods to the `log` method to
18
+ * reduce boilerplate code that a simple Logger that does the same thing with
19
+ * messages regardless of the error level has to implement.
20
+ */
21
+ abstract class Mustache_Logger_AbstractLogger implements Mustache_Logger
22
+ {
23
+ /**
24
+ * System is unusable.
25
+ *
26
+ * @param string $message
27
+ * @param array $context
28
+ */
29
+ public function emergency($message, array $context = array())
30
+ {
31
+ $this->log(Mustache_Logger::EMERGENCY, $message, $context);
32
+ }
33
+
34
+ /**
35
+ * Action must be taken immediately.
36
+ *
37
+ * Example: Entire website down, database unavailable, etc. This should
38
+ * trigger the SMS alerts and wake you up.
39
+ *
40
+ * @param string $message
41
+ * @param array $context
42
+ */
43
+ public function alert($message, array $context = array())
44
+ {
45
+ $this->log(Mustache_Logger::ALERT, $message, $context);
46
+ }
47
+
48
+ /**
49
+ * Critical conditions.
50
+ *
51
+ * Example: Application component unavailable, unexpected exception.
52
+ *
53
+ * @param string $message
54
+ * @param array $context
55
+ */
56
+ public function critical($message, array $context = array())
57
+ {
58
+ $this->log(Mustache_Logger::CRITICAL, $message, $context);
59
+ }
60
+
61
+ /**
62
+ * Runtime errors that do not require immediate action but should typically
63
+ * be logged and monitored.
64
+ *
65
+ * @param string $message
66
+ * @param array $context
67
+ */
68
+ public function error($message, array $context = array())
69
+ {
70
+ $this->log(Mustache_Logger::ERROR, $message, $context);
71
+ }
72
+
73
+ /**
74
+ * Exceptional occurrences that are not errors.
75
+ *
76
+ * Example: Use of deprecated APIs, poor use of an API, undesirable things
77
+ * that are not necessarily wrong.
78
+ *
79
+ * @param string $message
80
+ * @param array $context
81
+ */
82
+ public function warning($message, array $context = array())
83
+ {
84
+ $this->log(Mustache_Logger::WARNING, $message, $context);
85
+ }
86
+
87
+ /**
88
+ * Normal but significant events.
89
+ *
90
+ * @param string $message
91
+ * @param array $context
92
+ */
93
+ public function notice($message, array $context = array())
94
+ {
95
+ $this->log(Mustache_Logger::NOTICE, $message, $context);
96
+ }
97
+
98
+ /**
99
+ * Interesting events.
100
+ *
101
+ * Example: User logs in, SQL logs.
102
+ *
103
+ * @param string $message
104
+ * @param array $context
105
+ */
106
+ public function info($message, array $context = array())
107
+ {
108
+ $this->log(Mustache_Logger::INFO, $message, $context);
109
+ }
110
+
111
+ /**
112
+ * Detailed debug information.
113
+ *
114
+ * @param string $message
115
+ * @param array $context
116
+ */
117
+ public function debug($message, array $context = array())
118
+ {
119
+ $this->log(Mustache_Logger::DEBUG, $message, $context);
120
+ }
121
+ }
assets/lib/Mustache/Logger/StreamLogger.php ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Mustache.php.
5
+ *
6
+ * (c) 2012 Justin Hileman
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * A Mustache Stream Logger.
14
+ *
15
+ * The Stream Logger wraps a file resource instance (such as a stream) or a
16
+ * stream URL. All log messages over the threshold level will be appended to
17
+ * this stream.
18
+ *
19
+ * Hint: Try `php://stderr` for your stream URL.
20
+ */
21
+ class Mustache_Logger_StreamLogger extends Mustache_Logger_AbstractLogger
22
+ {
23
+ protected static $levels = array(
24
+ self::DEBUG => 100,
25
+ self::INFO => 200,
26
+ self::NOTICE => 250,
27
+ self::WARNING => 300,
28
+ self::ERROR => 400,
29
+ self::CRITICAL => 500,
30
+ self::ALERT => 550,
31
+ self::EMERGENCY => 600,
32
+ );
33
+
34
+ protected $stream = null;
35
+ protected $url = null;
36
+
37
+ /**
38
+ * @throws InvalidArgumentException if the logging level is unknown.
39
+ *
40
+ * @param string $stream Resource instance or URL
41
+ * @param integer $level The minimum logging level at which this handler will be triggered
42
+ */
43
+ public function __construct($stream, $level = Mustache_Logger::ERROR)
44
+ {
45
+ $this->setLevel($level);
46
+
47
+ if (is_resource($stream)) {
48
+ $this->stream = $stream;
49
+ } else {
50
+ $this->url = $stream;
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Close stream resources.
56
+ */
57
+ public function __destruct()
58
+ {
59
+ if (is_resource($this->stream)) {
60
+ fclose($this->stream);
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Set the minimum logging level.
66
+ *
67
+ * @throws Mustache_Exception_InvalidArgumentException if the logging level is unknown.
68
+ *
69
+ * @param integer $level The minimum logging level which will be written
70
+ */
71
+ public function setLevel($level)
72
+ {
73
+ if (!array_key_exists($level, self::$levels)) {
74
+ throw new Mustache_Exception_InvalidArgumentException(sprintf('Unexpected logging level: %s', $level));
75
+ }
76
+
77
+ $this->level = $level;
78
+ }
79
+
80
+ /**
81
+ * Get the current minimum logging level.
82
+ *
83
+ * @return integer
84
+ */
85
+ public function getLevel()
86
+ {
87
+ return $this->level;
88
+ }
89
+
90
+ /**
91
+ * Logs with an arbitrary level.
92
+ *
93
+ * @throws Mustache_Exception_InvalidArgumentException if the logging level is unknown.
94
+ *
95
+ * @param mixed $level
96
+ * @param string $message
97
+ * @param array $context
98
+ */
99
+ public function log($level, $message, array $context = array())
100
+ {
101
+ if (!array_key_exists($level, self::$levels)) {
102
+ throw new Mustache_Exception_InvalidArgumentException(sprintf('Unexpected logging level: %s', $level));
103
+ }
104
+
105
+ if (self::$levels[$level] >= self::$levels[$this->level]) {
106
+ $this->writeLog($level, $message, $context);
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Write a record to the log.
112
+ *
113
+ * @throws Mustache_Exception_LogicException If neither a stream resource nor url is present.
114
+ * @throws Mustache_Exception_RuntimeException If the stream url cannot be opened.
115
+ *
116
+ * @param integer $level The logging level
117
+ * @param string $message The log message
118
+ * @param array $context The log context
119
+ */
120
+ protected function writeLog($level, $message, array $context = array())
121
+ {
122
+ if (!is_resource($this->stream)) {
123
+ if (!isset($this->url)) {
124
+ throw new Mustache_Exception_LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().');
125
+ }
126
+
127
+ $this->stream = fopen($this->url, 'a');
128
+ if (!is_resource($this->stream)) {
129
+ // @codeCoverageIgnoreStart
130
+ throw new Mustache_Exception_RuntimeException(sprintf('The stream or file "%s" could not be opened.', $this->url));
131
+ // @codeCoverageIgnoreEnd
132
+ }
133
+ }
134
+
135
+ fwrite($this->stream, self::formatLine($level, $message, $context));
136
+ }
137
+
138
+ /**
139
+ * Gets the name of the logging level.
140
+ *
141
+ * @throws InvalidArgumentException if the logging level is unknown.
142
+ *
143
+ * @param integer $level
144
+ *
145
+ * @return string
146
+ */
147
+ protected static function getLevelName($level)
148
+ {
149
+ return strtoupper($level);
150
+ }
151
+
152
+ /**
153
+ * Format a log line for output.
154
+ *
155
+ * @param integer $level The logging level
156
+ * @param string $message The log message
157
+ * @param array $context The log context
158
+ *
159
+ * @return string
160
+ */
161
+ protected static function formatLine($level, $message, array $context = array())
162
+ {
163
+ return sprintf(
164
+ "%s: %s\n",
165
+ self::getLevelName($level),
166
+ self::interpolateMessage($message, $context)
167
+ );
168
+ }
169
+
170
+ /**
171
+ * Interpolate context values into the message placeholders.
172
+ *
173
+ * @param string $message
174
+ * @param array $context
175
+ *
176
+ * @return string
177
+ */
178
+ protected static function interpolateMessage($message, array $context = array())
179
+ {
180
+ if (strpos($message, '{') === false) {
181
+ return $message;
182
+ }
183
+
184
+ // build a replacement array with braces around the context keys
185
+ $replace = array();
186
+ foreach ($context as $key => $val) {
187
+ $replace['{' . $key . '}'] = $val;
188
+ }
189
+
190
+ // interpolate replacement values into the the message and return
191
+ return strtr($message, $replace);
192
+ }
193
+ }
assets/lib/Mustache/Parser.php ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Mustache.php.
5
+ *
6
+ * (c) 2012 Justin Hileman
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * Mustache Parser class.
14
+ *
15
+ * This class is responsible for turning a set of Mustache tokens into a parse tree.
16
+ */
17
+ class Mustache_Parser
18
+ {
19
+ private $lineNum;
20
+ private $lineTokens;
21
+
22
+ /**
23
+ * Process an array of Mustache tokens and convert them into a parse tree.
24
+ *
25
+ * @param array $tokens Set of Mustache tokens
26
+ *
27
+ * @return array Mustache token parse tree
28
+ */
29
+ public function parse(array $tokens = array())
30
+ {
31
+ $this->lineNum = -1;
32
+ $this->lineTokens = 0;
33
+
34
+ return $this->buildTree($tokens);
35
+ }
36
+
37
+ /**
38
+ * Helper method for recursively building a parse tree.
39
+ *
40
+ * @throws Mustache_Exception_SyntaxException when nesting errors or mismatched section tags are encountered.
41
+ *
42
+ * @param array &$tokens Set of Mustache tokens
43
+ * @param array $parent Parent token (default: null)
44
+ *
45
+ * @return array Mustache Token parse tree
46
+ */
47
+ private function buildTree(array &$tokens, array $parent = null)
48
+ {
49
+ $nodes = array();
50
+
51
+ while (!empty($tokens)) {
52
+ $token = array_shift($tokens);
53
+
54
+ if ($token[Mustache_Tokenizer::LINE] === $this->lineNum) {
55
+ $this->lineTokens++;
56
+ } else {
57
+ $this->lineNum = $token[Mustache_Tokenizer::LINE];
58
+ $this->lineTokens = 0;
59
+ }
60
+
61
+ switch ($token[Mustache_Tokenizer::TYPE]) {
62
+ case Mustache_Tokenizer::T_DELIM_CHANGE:
63
+ $this->clearStandaloneLines($nodes, $tokens);
64
+ break;
65
+
66
+ case Mustache_Tokenizer::T_SECTION:
67
+ case Mustache_Tokenizer::T_INVERTED:
68
+ $this->clearStandaloneLines($nodes, $tokens);
69
+ $nodes[] = $this->buildTree($tokens, $token);
70
+ break;
71
+
72
+ case Mustache_Tokenizer::T_END_SECTION:
73
+ if (!isset($parent)) {
74
+ $msg = sprintf('Unexpected closing tag: /%s', $token[Mustache_Tokenizer::NAME]);
75
+ throw new Mustache_Exception_SyntaxException($msg, $token);
76
+ }
77
+
78
+ if ($token[Mustache_Tokenizer::NAME] !== $parent[Mustache_Tokenizer::NAME]) {
79
+ $msg = sprintf('Nesting error: %s vs. %s', $parent[Mustache_Tokenizer::NAME], $token[Mustache_Tokenizer::NAME]);
80
+ throw new Mustache_Exception_SyntaxException($msg, $token);
81
+ }
82
+
83
+ $this->clearStandaloneLines($nodes, $tokens);
84
+ $parent[Mustache_Tokenizer::END] = $token[Mustache_Tokenizer::INDEX];
85
+ $parent[Mustache_Tokenizer::NODES] = $nodes;
86
+
87
+ return $parent;
88
+ break;
89
+
90
+ case Mustache_Tokenizer::T_PARTIAL:
91
+ case Mustache_Tokenizer::T_PARTIAL_2:
92
+ // store the whitespace prefix for laters!
93
+ if ($indent = $this->clearStandaloneLines($nodes, $tokens)) {
94
+ $token[Mustache_Tokenizer::INDENT] = $indent[Mustache_Tokenizer::VALUE];
95
+ }
96
+ $nodes[] = $token;
97
+ break;
98
+
99
+ case Mustache_Tokenizer::T_PRAGMA:
100
+ case Mustache_Tokenizer::T_COMMENT:
101
+ $this->clearStandaloneLines($nodes, $tokens);
102
+ $nodes[] = $token;
103
+ break;
104
+
105
+ default:
106
+ $nodes[] = $token;
107
+ break;
108
+ }
109
+ }
110
+
111
+ if (isset($parent)) {
112
+ $msg = sprintf('Missing closing tag: %s', $parent[Mustache_Tokenizer::NAME]);
113
+ throw new Mustache_Exception_SyntaxException($msg, $parent);
114
+ }
115
+
116
+ return $nodes;
117
+ }
118
+
119
+ /**
120
+ * Clear standalone line tokens.
121
+ *
122
+ * Returns a whitespace token for indenting partials, if applicable.
123
+ *
124
+ * @param array $nodes Parsed nodes.
125
+ * @param array $tokens Tokens to be parsed.
126
+ *
127
+ * @return array Resulting indent token, if any.
128
+ */
129
+ private function clearStandaloneLines(array &$nodes, array &$tokens)
130
+ {
131
+ if ($this->lineTokens > 1) {
132
+ // this is the third or later node on this line, so it can't be standalone
133
+ return;
134
+ }
135
+
136
+ $prev = null;
137
+ if ($this->lineTokens === 1) {
138
+ // this is the second node on this line, so it can't be standalone
139
+ // unless the previous node is whitespace.
140
+ if ($prev = end($nodes)) {
141
+ if (!$this->tokenIsWhitespace($prev)) {
142
+ return;
143
+ }
144
+ }
145
+ }
146
+
147
+ $next = null;
148
+ if ($next = reset($tokens)) {
149
+ // If we're on a new line, bail.
150
+ if ($next[Mustache_Tokenizer::LINE] !== $this->lineNum) {
151
+ return;
152
+ }
153
+
154
+ // If the next token isn't whitespace, bail.
155
+ if (!$this->tokenIsWhitespace($next)) {
156
+ return;
157
+ }
158
+
159
+ if (count($tokens) !== 1) {
160
+ // Unless it's the last token in the template, the next token
161
+ // must end in newline for this to be standalone.
162
+ if (substr($next[Mustache_Tokenizer::VALUE], -1) !== "\n") {
163
+ return;
164
+ }
165
+ }
166
+
167
+ // Discard the whitespace suffix
168
+ array_shift($tokens);
169
+ }
170
+
171
+ if ($prev) {
172
+ // Return the whitespace prefix, if any
173
+ return array_pop($nodes);
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Check whether token is a whitespace token.
179
+ *
180
+ * True if token type is T_TEXT and value is all whitespace characters.
181
+ *
182
+ * @param array $token
183
+ *
184
+ * @return boolean True if token is a whitespace token
185
+ */
186
+ private function tokenIsWhitespace(array $token)
187
+ {
188
+ if ($token[Mustache_Tokenizer::TYPE] == Mustache_Tokenizer::T_TEXT) {
189
+ return preg_match('/^\s*$/', $token[Mustache_Tokenizer::VALUE]);
190
+ }
191
+
192
+ return false;
193
+ }
194
+ }
assets/lib/Mustache/Template.php ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Mustache.php.
5
+ *
6
+ * (c) 2012 Justin Hileman
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * Abstract Mustache Template class.
14
+ *
15
+ * @abstract
16
+ */
17
+ abstract class Mustache_Template
18
+ {
19
+
20
+ /**
21
+ * @var Mustache_Engine
22
+ */
23
+ protected $mustache;
24
+
25
+ /**
26
+ * @var boolean
27
+ */
28
+ protected $strictCallables = false;
29
+
30
+ /**
31
+ * Mustache Template constructor.
32
+ *
33
+ * @param Mustache_Engine $mustache
34
+ */
35
+ public function __construct(Mustache_Engine $mustache)
36
+ {
37
+ $this->mustache = $mustache;
38
+ }
39
+
40
+ /**
41
+ * Mustache Template instances can be treated as a function and rendered by simply calling them:
42
+ *
43
+ * $m = new Mustache_Engine;
44
+ * $tpl = $m->loadTemplate('Hello, {{ name }}!');
45
+ * echo $tpl(array('name' => 'World')); // "Hello, World!"
46
+ *
47
+ * @see Mustache_Template::render
48
+ *
49
+ * @param mixed $context Array or object rendering context (default: array())
50
+ *
51
+ * @return string Rendered template
52
+ */
53
+ public function __invoke($context = array())
54
+ {
55
+ return $this->render($context);
56
+ }
57
+
58
+ /**
59
+ * Render this template given the rendering context.
60
+ *
61
+ * @param mixed $context Array or object rendering context (default: array())
62
+ *
63
+ * @return string Rendered template
64
+ */
65
+ public function render($context = array())
66
+ {
67
+ return $this->renderInternal($this->prepareContextStack($context));
68
+ }
69
+
70
+ /**
71
+ * Internal rendering method implemented by Mustache Template concrete subclasses.
72
+ *
73
+ * This is where the magic happens :)
74
+ *
75
+ * NOTE: This method is not part of the Mustache.php public API.
76
+ *
77
+ * @param Mustache_Context $context
78
+ * @param string $indent (default: '')
79
+ *
80
+ * @return string Rendered template
81
+ */
82
+ abstract public function renderInternal(Mustache_Context $context, $indent = '');
83
+
84
+ /**
85
+ * Tests whether a value should be iterated over (e.g. in a section context).
86
+ *
87
+ * In most languages there are two distinct array types: list and hash (or whatever you want to call them). Lists
88
+ * should be iterated, hashes should be treated as objects. Mustache follows this paradigm for Ruby, Javascript,
89
+ * Java, Python, etc.
90
+ *
91
+ * PHP, however, treats lists and hashes as one primitive type: array. So Mustache.php needs a way to distinguish
92
+ * between between a list of things (numeric, normalized array) and a set of variables to be used as section context
93
+ * (associative array). In other words, this will be iterated over:
94
+ *
95
+ * $items = array(
96
+ * array('name' => 'foo'),
97
+ * array('name' => 'bar'),
98
+ * array('name' => 'baz'),
99
+ * );
100
+ *
101
+ * ... but this will be used as a section context block:
102
+ *
103
+ * $items = array(
104
+ * 1 => array('name' => 'foo'),
105
+ * 'banana' => array('name' => 'bar'),
106
+ * 42 => array('name' => 'baz'),
107
+ * );
108
+ *
109
+ * @param mixed $value
110
+ *
111
+ * @return boolean True if the value is 'iterable'
112
+ */
113
+ protected function isIterable($value)
114
+ {
115
+ if (is_object($value)) {
116
+ return $value instanceof Traversable;
117
+ } elseif (is_array($value)) {
118
+ $i = 0;
119
+ foreach ($value as $k => $v) {
120
+ if ($k !== $i++) {
121
+ return false;
122
+ }
123
+ }
124
+
125
+ return true;
126
+ } else {
127
+ return false;
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Helper method to prepare the Context stack.
133
+ *
134
+ * Adds the Mustache HelperCollection to the stack's top context frame if helpers are present.
135
+ *
136
+ * @param mixed $context Optional first context frame (default: null)
137
+ *
138
+ * @return Mustache_Context
139
+ */
140
+ protected function prepareContextStack($context = null)
141
+ {
142
+ $stack = new Mustache_Context;
143
+
144
+ $helpers = $this->mustache->getHelpers();
145
+ if (!$helpers->isEmpty()) {
146
+ $stack->push($helpers);
147
+ }
148
+
149
+ if (!empty($context)) {
150
+ $stack->push($context);
151
+ }
152
+
153
+ return $stack;
154
+ }
155
+
156
+ /**
157
+ * Resolve a context value.
158
+ *
159
+ * Invoke the value if it is callable, otherwise return the value.
160
+ *
161
+ * @param mixed $value
162
+ * @param Mustache_Context $context
163
+ * @param string $indent
164
+ *
165
+ * @return string
166
+ */
167
+ protected function resolveValue($value, Mustache_Context $context, $indent = '')
168
+ {
169
+ if (($this->strictCallables ? is_object($value) : !is_string($value)) && is_callable($value)) {
170
+ return $this->mustache
171
+ ->loadLambda((string) call_user_func($value))
172
+ ->renderInternal($context, $indent);
173
+ }
174
+
175
+ return $value;
176
+ }
177
+ }
assets/lib/Mustache/Tokenizer.php ADDED
@@ -0,0 +1,278 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Mustache.php.
5
+ *
6
+ * (c) 2012 Justin Hileman
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * Mustache Tokenizer class.
14
+ *
15
+ * This class is responsible for turning raw template source into a set of Mustache tokens.
16
+ */
17
+ class Mustache_Tokenizer
18
+ {
19
+
20
+ // Finite state machine states
21
+ const IN_TEXT = 0;
22
+ const IN_TAG_TYPE = 1;
23
+ const IN_TAG = 2;
24
+
25
+ // Token types
26
+ const T_SECTION = '#';
27
+ const T_INVERTED = '^';
28
+ const T_END_SECTION = '/';
29
+ const T_COMMENT = '!';
30
+ const T_PARTIAL = '>';
31
+ const T_PARTIAL_2 = '<';
32
+ const T_DELIM_CHANGE = '=';
33
+ const T_ESCAPED = '_v';
34
+ const T_UNESCAPED = '{';
35
+ const T_UNESCAPED_2 = '&';
36
+ const T_TEXT = '_t';
37
+ const T_PRAGMA = '%';
38
+
39
+ // Valid token types
40
+ private static $tagTypes = array(
41
+ self::T_SECTION => true,
42
+ self::T_INVERTED => true,
43
+ self::T_END_SECTION => true,
44
+ self::T_COMMENT => true,
45
+ self::T_PARTIAL => true,
46
+ self::T_PARTIAL_2 => true,
47
+ self::T_DELIM_CHANGE => true,
48
+ self::T_ESCAPED => true,
49
+ self::T_UNESCAPED => true,
50
+ self::T_UNESCAPED_2 => true,
51
+ self::T_PRAGMA => true,
52
+ );
53
+
54
+ // Interpolated tags
55
+ private static $interpolatedTags = array(
56
+ self::T_ESCAPED => true,
57
+ self::T_UNESCAPED => true,
58
+ self::T_UNESCAPED_2 => true,
59
+ );
60
+
61
+ // Token properties
62
+ const TYPE = 'type';
63
+ const NAME = 'name';
64
+ const OTAG = 'otag';
65
+ const CTAG = 'ctag';
66
+ const LINE = 'line';
67
+ const INDEX = 'index';
68
+ const END = 'end';
69
+ const INDENT = 'indent';
70
+ const NODES = 'nodes';
71
+ const VALUE = 'value';
72
+
73
+ private $state;
74
+ private $tagType;
75
+ private $tag;
76
+ private $buffer;
77
+ private $tokens;
78
+ private $seenTag;
79
+ private $line;
80
+ private $otag;
81
+ private $ctag;
82
+
83
+ /**
84
+ * Scan and tokenize template source.
85
+ *
86
+ * @param string $text Mustache template source to tokenize
87
+ * @param string $delimiters Optionally, pass initial opening and closing delimiters (default: null)
88
+ *
89
+ * @return array Set of Mustache tokens
90
+ */
91
+ public function scan($text, $delimiters = null)
92
+ {
93
+ $this->reset();
94
+
95
+ if ($delimiters = trim($delimiters)) {
96
+ list($otag, $ctag) = explode(' ', $delimiters);
97
+ $this->otag = $otag;
98
+ $this->ctag = $ctag;
99
+ }
100
+
101
+ $len = strlen($text);
102
+ for ($i = 0; $i < $len; $i++) {
103
+ switch ($this->state) {
104
+ case self::IN_TEXT:
105
+ if ($this->tagChange($this->otag, $text, $i)) {
106
+ $i--;
107
+ $this->flushBuffer();
108
+ $this->state = self::IN_TAG_TYPE;
109
+ } else {
110
+ $char = substr($text, $i, 1);
111
+ $this->buffer .= $char;
112
+ if ($char == "\n") {
113
+ $this->flushBuffer();
114
+ $this->line++;
115
+ }
116
+ }
117
+ break;
118
+
119
+ case self::IN_TAG_TYPE:
120
+ $i += strlen($this->otag) - 1;
121
+ $char = substr($text, $i + 1, 1);
122
+ if (isset(self::$tagTypes[$char])) {
123
+ $tag = $char;
124
+ $this->tagType = $tag;
125
+ } else {
126
+ $tag = null;
127
+ $this->tagType = self::T_ESCAPED;
128
+ }
129
+
130
+ if ($this->tagType === self::T_DELIM_CHANGE) {
131
+ $i = $this->changeDelimiters($text, $i);
132
+ $this->state = self::IN_TEXT;
133
+ } elseif ($this->tagType === self::T_PRAGMA) {
134
+ $i = $this->addPragma($text, $i);
135
+ $this->state = self::IN_TEXT;
136
+ } else {
137
+ if ($tag !== null) {
138
+ $i++;
139
+ }
140
+ $this->state = self::IN_TAG;
141
+ }
142
+ $this->seenTag = $i;
143
+ break;
144
+
145
+ default:
146
+ if ($this->tagChange($this->ctag, $text, $i)) {
147
+ $this->tokens[] = array(
148
+ self::TYPE => $this->tagType,
149
+ self::NAME => trim($this->buffer),
150
+ self::OTAG => $this->otag,
151
+ self::CTAG => $this->ctag,
152
+ self::LINE => $this->line,
153
+ self::INDEX => ($this->tagType == self::T_END_SECTION) ? $this->seenTag - strlen($this->otag) : $i + strlen($this->ctag)
154
+ );
155
+
156
+ $this->buffer = '';
157
+ $i += strlen($this->ctag) - 1;
158
+ $this->state = self::IN_TEXT;
159
+ if ($this->tagType == self::T_UNESCAPED) {
160
+ if ($this->ctag == '}}') {
161
+ $i++;
162
+ } else {
163
+ // Clean up `{{{ tripleStache }}}` style tokens.
164
+ $lastName = $this->tokens[count($this->tokens) - 1][self::NAME];
165
+ if (substr($lastName, -1) === '}') {
166
+ $this->tokens[count($this->tokens) - 1][self::NAME] = trim(substr($lastName, 0, -1));
167
+ }
168
+ }
169
+ }
170
+ } else {
171
+ $this->buffer .= substr($text, $i, 1);
172
+ }
173
+ break;
174
+ }
175
+ }
176
+
177
+ $this->flushBuffer();
178
+
179
+ return $this->tokens;
180
+ }
181
+
182
+ /**
183
+ * Helper function to reset tokenizer internal state.
184
+ */
185
+ private function reset()
186
+ {
187
+ $this->state = self::IN_TEXT;
188
+ $this->tagType = null;
189
+ $this->tag = null;
190
+ $this->buffer = '';
191
+ $this->tokens = array();
192
+ $this->seenTag = false;
193
+ $this->line = 0;
194
+ $this->otag = '{{';
195
+ $this->ctag = '}}';
196
+ }
197
+
198
+ /**
199
+ * Flush the current buffer to a token.
200
+ */
201
+ private function flushBuffer()
202
+ {
203
+ if (!empty($this->buffer)) {
204
+ $this->tokens[] = array(
205
+ self::TYPE => self::T_TEXT,
206
+ self::LINE => $this->line,
207
+ self::VALUE => $this->buffer
208
+ );
209
+ $this->buffer = '';
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Change the current Mustache delimiters. Set new `otag` and `ctag` values.
215
+ *
216
+ * @param string $text Mustache template source
217
+ * @param int $index Current tokenizer index
218
+ *
219
+ * @return int New index value
220
+ */
221
+ private function changeDelimiters($text, $index)
222
+ {
223
+ $startIndex = strpos($text, '=', $index) + 1;
224
+ $close = '='.$this->ctag;
225
+ $closeIndex = strpos($text, $close, $index);
226
+
227
+ list($otag, $ctag) = explode(' ', trim(substr($text, $startIndex, $closeIndex - $startIndex)));
228
+ $this->otag = $otag;
229
+ $this->ctag = $ctag;
230
+
231
+ $this->tokens[] = array(
232
+ self::TYPE => self::T_DELIM_CHANGE,
233
+ self::LINE => $this->line,
234
+ );
235
+
236
+ return $closeIndex + strlen($close) - 1;
237
+ }
238
+
239
+ /**
240
+ * Add pragma token.
241
+ *
242
+ * Pragmas are hoisted to the front of the template, so all pragma tokens
243
+ * will appear at the front of the token list.
244
+ *
245
+ * @param string $text
246
+ * @param int $index
247
+ *
248
+ * @return int New index value
249
+ */
250
+ private function addPragma($text, $index)
251
+ {
252
+ $end = strpos($text, $this->ctag, $index);
253
+ $pragma = trim(substr($text, $index + 2, $end - $index - 2));
254
+
255
+ // Pragmas are hoisted to the front of the template.
256
+ array_unshift($this->tokens, array(
257
+ self::TYPE => self::T_PRAGMA,
258
+ self::NAME => $pragma,
259
+ self::LINE => 0,
260
+ ));
261
+
262
+ return $end + strlen($this->ctag) - 1;
263
+ }
264
+
265
+ /**
266
+ * Test whether it's time to change tags.
267
+ *
268
+ * @param string $tag Current tag name
269
+ * @param string $text Mustache template source
270
+ * @param int $index Current tokenizer index
271
+ *
272
+ * @return boolean True if this is a closing section tag
273
+ */
274
+ private function tagChange($tag, $text, $index)
275
+ {
276
+ return substr($text, $index, strlen($tag)) === $tag;
277
+ }
278
+ }
assets/lib/class-mustache-templates/class-mustache-templates.css ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Mustache Library for Creating */
2
+
3
+ /*--------------------------------------------------------------
4
+ >>> TABLE OF CONTENTS:
5
+ ----------------------------------------------------------------
6
+ 1.0 - Basic Structure
7
+ 2.0 - Textarea Codemirror Styling
8
+ 3.0 - Other Form Elements
9
+ 4.0 - Save Button
10
+ 5.0 - Media Queries
11
+ 6.0 - Other
12
+ --------------------------------------------------------------*/
13
+
14
+ /*--------------------------------------------------------------
15
+ 1.0 Basic Structure
16
+ --------------------------------------------------------------*/
17
+
18
+ .side .form-table th {
19
+ width: 20%;
20
+ font-weight: bold;
21
+ text-align: left;
22
+ padding-left: 0;
23
+ }
24
+
25
+ .wck-post-body{
26
+ clear: left;
27
+ float: left;
28
+ margin-right: -2000px;
29
+ width: 100%;
30
+ }
31
+
32
+ .metabox-holder .column-1 {
33
+ margin-right:300px;
34
+ }
35
+ .metabox-holder .column-2 {
36
+ float: right;
37
+ width: 280px;
38
+ clear:right;
39
+ position:relative;
40
+ }
41
+ .metabox-holder .column-3 {
42
+ clear: both;
43
+ margin-right:300px;
44
+ }
45
+ /*--------------------------------------------------------------
46
+ 2.0 Textarea Codemirror styling
47
+ --------------------------------------------------------------*/
48
+ .textarea .stp-extra{
49
+ border:1px solid #ccc;
50
+ border-left:0;
51
+ float: left;
52
+ overflow: auto;
53
+ width: 39%;
54
+ height:400px;
55
+ }
56
+ .textarea .stp-extra h4 {
57
+ height: 20px;
58
+ margin: 1px 0;
59
+ padding:10px 20px;
60
+ overflow: hidden;
61
+ cursor:pointer;
62
+ background:#F1F1F1;
63
+ }
64
+ .textarea .stp-extra h4:hover{
65
+ background:#FCFCFC;
66
+ }
67
+ .textarea .stp-extra pre {
68
+ display: block;
69
+ height: 253px;
70
+ margin: 12px 0;
71
+ max-height: 253px;
72
+ overflow: auto;
73
+ padding-left: 20px;
74
+ vertical-align: top;
75
+ width: auto;
76
+ }
77
+ .textarea label.wppb_mustache_label {
78
+ display: none;
79
+ }
80
+ .cm-s-default.CodeMirror {
81
+ border: 1px solid #CCCCCC;
82
+ float: left;
83
+ font-family: Monaco,Menlo,"Andale Mono","lucida console","Courier New",monospace !important;
84
+ line-height: 1.4em !important;
85
+ width: 60%;
86
+ }
87
+ .cm-s-default {
88
+ min-height: 400px;
89
+ }
90
+ .mustache-box{
91
+ table-layout:fixed;
92
+ }
93
+ /*--------------------------------------------------------------
94
+ 3.0 Other Form Elements
95
+ --------------------------------------------------------------*/
96
+ .text label.wppb_mustache_label {
97
+ display: inline-block;
98
+ min-width: 150px;
99
+ font-weight: bold;
100
+ }
101
+
102
+ .checkbox label.wppb_mustache_label {
103
+ font-weight: bold;
104
+ margin-right: 8px;
105
+ }
106
+
107
+ .mustache-box input[type="text"]{
108
+ width:400px;
109
+ min-width: auto;
110
+ }
111
+
112
+ .mustache-box .description{
113
+ display: block;
114
+ }
115
+
116
+ .mustache-box.form-table td.header{
117
+ padding-top: 0;
118
+ padding-bottom: 0;
119
+ }
120
+
121
+ /*--------------------------------------------------------------
122
+ 4.0 Save Button
123
+ --------------------------------------------------------------*/
124
+ .mustache-save{
125
+ width: 100%;
126
+ text-align: center;
127
+ }
128
+
129
+ #page-save-metabox .hndle{
130
+ background: #333333;
131
+ color: #fff;
132
+ }
133
+ #page-save-metabox .inside{
134
+ padding: 30px;
135
+ }
136
+
137
+ /*--------------------------------------------------------------
138
+ 5.0 Media Queries
139
+ --------------------------------------------------------------*/
140
+ @media screen and ( max-width: 878px ) {
141
+ .wck-post-body{
142
+ float:none;
143
+ margin-right: 0;
144
+ }
145
+
146
+ .metabox-holder .column-2{
147
+ float:none;
148
+ width:100%;
149
+ }
150
+
151
+ .metabox-holder .column-1{
152
+ margin-right:0;
153
+ }
154
+
155
+ .textarea .stp-extra{
156
+ float:none;
157
+ width:auto;
158
+ border:1px solid #ccc;
159
+ border-top:0;
160
+ }
161
+ .cm-s-default.CodeMirror{
162
+ float:none;
163
+ width: auto;
164
+ }
165
+ #page-save-metabox .inside{
166
+ text-align: right;
167
+ }
168
+ .mustache-save{
169
+ width: auto;
170
+ text-align: center;
171
+ }
172
+
173
+ }
174
+
175
+ /*--------------------------------------------------------------
176
+ 6.0 Other
177
+ --------------------------------------------------------------*/
178
+ /* show email toggle checkbox inline for User/Admin Email Customizers */
179
+ .profile-builder_page_user-email-customizer .wck-post-body .postbox:not(:first-child) tr:nth-child(1), .profile-builder_page_admin-email-customizer .wck-post-body .postbox:not(:first-child) tr:nth-child(1) {
180
+ width: 50%;
181
+ display: inline-block;
182
+ }
183
+
184
+ .profile-builder_page_user-email-customizer .wck-post-body .postbox:not(:first-child) tr:nth-child(2), .profile-builder_page_admin-email-customizer .wck-post-body .postbox:not(:first-child) tr:nth-child(2) {
185
+ text-align: left;
186
+ width: 50%;
187
+ display: inline-block;
188
+ }
189
+
190
+ .profile-builder_page_user-email-customizer .wck-post-body .postbox:not(:first-child) tr:nth-child(2) td, .profile-builder_page_admin-email-customizer .wck-post-body .postbox:not(:first-child) tr:nth-child(2) td {
191
+ float: right;
192
+ padding-right: 20px;
193
+ }
assets/lib/class-mustache-templates/class-mustache-templates.js ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ CodeMirror.defineMode("mustache", function(config, parserConfig) {
2
+ var mustacheOverlay = {
3
+ token: function(stream, state) {
4
+ var ch;
5
+ if (stream.match("{{")) {
6
+ while ((ch = stream.next()) != null)
7
+ if (ch == "}" && stream.next() == "}") break;
8
+ stream.eat("}");
9
+ return "mustache";
10
+ }
11
+ while (stream.next() != null && !stream.match("{{", false)) {}
12
+ return null;
13
+ }
14
+ };
15
+ return CodeMirror.overlayMode(CodeMirror.getMode(config, parserConfig.backdrop || "text/html"), mustacheOverlay);
16
+ });
17
+
18
+
19
+ jQuery(function(){
20
+ var wck_stp_textareas = ["wppb_mustache_template"];
21
+ var length = wck_stp_textareas.length;
22
+ element = null;
23
+
24
+ for ( var i=0; i < length; i++ ){
25
+ element = wck_stp_textareas[i];
26
+
27
+ if ( jQuery( 'textarea[class="' + element + '"]' ).length > 0 ){
28
+ jQuery( 'textarea[class|="' + element + '"]' ).each( function(){
29
+ var editor = CodeMirror.fromTextArea( this, {
30
+ mode: "mustache",
31
+ lineNumbers: true,
32
+ //lineWrapping:true,
33
+ extraKeys: {
34
+ "F11": function(cm) {
35
+ cm.setOption("fullScreen", !cm.getOption("fullScreen"));
36
+ },
37
+ "Esc": function(cm) {
38
+ if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false);
39
+ }
40
+ }
41
+ });
42
+ });
43
+ }
44
+ }
45
+ })
46
+
47
+ jQuery(function() {
48
+ jQuery( ".stp-extra" ).accordion();
49
+ });
50
+
51
+ //add hidden input with off value for checkboxes
52
+ jQuery(function() {
53
+
54
+ jQuery('form .wck-post-body input[type="checkbox"]').each( function() {
55
+
56
+ if ( !jQuery(this).is(':checked') ) {
57
+ var wppb_mustache_checkbox_off = document.createElement('input');
58
+ wppb_mustache_checkbox_off.type = 'hidden';
59
+ wppb_mustache_checkbox_off.value = 'off';
60
+ wppb_mustache_checkbox_off.name = jQuery(this).attr('name');
61
+
62
+ jQuery(this).after(wppb_mustache_checkbox_off);
63
+ }
64
+
65
+ });
66
+
67
+ jQuery('form .wck-post-body input[type="checkbox"]').on( 'change', function() {
68
+ var wppb_mustache_checkbox_off = document.createElement('input');
69
+ wppb_mustache_checkbox_off.type = 'hidden';
70
+ wppb_mustache_checkbox_off.value = 'off';
71
+ wppb_mustache_checkbox_off.name = jQuery(this).attr('name');
72
+
73
+ if ( jQuery(this).is(':checked') )
74
+ jQuery( '.' + jQuery(this).attr('name') + ' input[type="hidden"]' ).remove();
75
+ else
76
+ jQuery(this).after(wppb_mustache_checkbox_off);
77
+
78
+ });
79
+
80
+ });
assets/lib/class-mustache-templates/class-mustache-templates.php ADDED
@@ -0,0 +1,604 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @param array() $mustache_vars is the array containing the names of the variable that must be proccessed by mustache in the template. The array must be in this form:
4
+ * array( array( 'name' => '', 'type' => '' ) ... ), and for loop tags it also contains a 'children' element which contains other mustache_vars array( array( 'name' => 'users', 'type' => 'loop_tag', 'children' => $merge_tags )
5
+ * @param string $template the template that needs to be procesed
6
+ * @param array $extra_values in this array we can pass any variables that are required for that specific implementation of the mustache processing system
7
+ *
8
+ */
9
+ class PB_Mustache_Generate_Template{
10
+ var $mustache_vars = array( );
11
+ var $template = '';
12
+ var $extra_values = array( );
13
+
14
+ /**
15
+ * constructor for the class
16
+ *
17
+ * @param array $mustache_vars the array of variables
18
+ * @param string $template the html template
19
+ * @param $extra_values in this array we can pass any variables that are required for that specific implementation of the mustache processing system
20
+ *
21
+ * @since 2.0.0
22
+ *
23
+ */
24
+ function __construct( $mustache_vars, $template, $extra_values ){
25
+
26
+ // Include Mustache Templates
27
+ if( !class_exists( 'Mustache_Autoloader' ) ){
28
+
29
+ if( !defined( 'WPPB_PAID_PLUGIN_DIR' ) || ( defined( 'WPPB_PAID_PLUGIN_DIR' ) && defined( 'PROFILE_BUILDER_PAID_VERSION' ) ) ){
30
+ if( file_exists( WPPB_PLUGIN_DIR . '/assets/lib/Mustache/Autoloader.php' ) )
31
+ require_once( WPPB_PLUGIN_DIR.'/assets/lib/Mustache/Autoloader.php' );
32
+ } else if( defined( 'WPPB_PAID_PLUGIN_DIR' ) ){
33
+ if( file_exists( WPPB_PAID_PLUGIN_DIR . '/assets/lib/Mustache/Autoloader.php' ) )
34
+ require_once( WPPB_PAID_PLUGIN_DIR.'/assets/lib/Mustache/Autoloader.php' );
35
+ }
36
+
37
+ }
38
+
39
+ Mustache_Autoloader::register();
40
+
41
+ $this->mustache_vars = $mustache_vars;
42
+ $this->template = $template;
43
+ $this->extra_values = $extra_values;
44
+
45
+ $this->process_mustache_vars();
46
+ }
47
+
48
+ /**
49
+ * construct the mustache_vars_array from $mustache_vars through filters
50
+ *
51
+ * @since 2.0.0
52
+ *
53
+ */
54
+ function process_mustache_vars(){
55
+ if( !empty( $this->mustache_vars ) ){
56
+ foreach( $this->mustache_vars as $var ){
57
+ foreach( $var['variables'] as $variables ){
58
+ if( empty( $variables['children'] ) )
59
+ $variables['children'] = array();
60
+ $this->mustache_vars_array[ $variables['name'] ] = apply_filters( 'mustache_variable_'. $variables['type'], '', $variables['name'], $variables['children'], $this->extra_values );
61
+ }
62
+ }
63
+ }
64
+ }
65
+
66
+
67
+ /**
68
+ * Process the mustache template from the html template and the processed mustache variable array.
69
+ *
70
+ * @since 2.0.0
71
+ *
72
+ * @return $content the proccessed template ready for output.
73
+ */
74
+ function process_template(){
75
+ $m = new Mustache_Engine;
76
+ try {
77
+ if( !empty( $this->mustache_vars_array ) ){
78
+ foreach( $this->mustache_vars_array as $key => $value ){
79
+ if( is_string( $value ) ) {
80
+ $this->mustache_vars_array[$key] = str_replace('[', '&#91;', $value);
81
+ $this->mustache_vars_array[$key] = str_replace(']', '&#93;', $value);
82
+ }
83
+ }
84
+ }
85
+ $content = do_shortcode( $m->render( $this->template, $this->mustache_vars_array ) );
86
+ } catch (Exception $e) {
87
+ $content = $e->getMessage();
88
+ }
89
+
90
+ return apply_filters( 'wppb_mustache_template', $content );
91
+ }
92
+
93
+ /**
94
+ * Handle toString method
95
+ *
96
+ * @since 2.0.0
97
+ *
98
+ * @return string $html html for the template.
99
+ */
100
+ public function __toString() {
101
+ $html = $this->process_template();
102
+ return "{$html}";
103
+ }
104
+
105
+
106
+ }
107
+
108
+
109
+ class PB_Mustache_Generate_Admin_Box{
110
+
111
+ var $id; // string meta box id
112
+ var $title; // string title
113
+ var $page; // string|array post type to add meta box to
114
+ var $priority;
115
+ var $mustache_vars;
116
+ var $default_value;
117
+
118
+
119
+ /**
120
+ * constructor for the class
121
+ *
122
+ * @param string $id the meta box id
123
+ * @param string $title the title of the metabox
124
+ * @param string|array $page post type to add meta box to
125
+ * @param string $priority the priority within the context where the boxes should show ('high', 'core', 'default' or 'low')
126
+ * @param array $mustache_vars is the array containing the names of the variable that must be proccessed by mustache in the template. The array must be in this form:
127
+ * array( array( 'name' => '', 'type' => '' ) ... ), and for loop tags it also contains a 'children' element which contains other mustache_vars array( array( 'name' => 'users', 'type' => 'loop_tag', 'children' => $merge_tags )
128
+ * @param string $default_value the default template that populates the codemirror box.
129
+ *
130
+ * @since 2.0.0
131
+ *
132
+ */
133
+ function __construct( $id, $title, $page, $priority, $mustache_vars, $default_value = '', $fields = array() ){
134
+
135
+ $this->mustache_vars = $mustache_vars;
136
+
137
+ $this->id = $id;
138
+ $this->title = $title;
139
+ $this->page = $page;
140
+ $this->priority = $priority;
141
+ $this->default_value = $default_value;
142
+
143
+ if(!is_array($this->page)) {
144
+ $this->page = array($this->page);
145
+ }
146
+
147
+ if( empty( $fields ) ){
148
+ $this->fields = array(
149
+ array( // Textarea
150
+ 'label' => '', // <label>
151
+ 'desc' => '', // description
152
+ 'id' => $id, // field id and name
153
+ 'type' => 'textarea', // type of field
154
+ 'default' => $default_value, // type of field
155
+ )
156
+ );
157
+ }
158
+ else{
159
+ $this->fields = $fields;
160
+ }
161
+
162
+ $this->save_default_values();
163
+
164
+ if( defined( 'DOING_AJAX' ) && DOING_AJAX )
165
+ return;
166
+
167
+ add_action( 'add_meta_boxes', array( $this, 'add_box' ) );
168
+ add_action( 'save_post', array( $this, 'save_box' ), 11, 2);
169
+ add_action( 'wp_insert_post', array( $this, 'save_box' ), 11, 2);
170
+ add_action( 'admin_enqueue_scripts', array( $this, 'wppb_print_mustache_script' ) );
171
+ add_action( 'admin_head', array( $this, 'wppb_print_codemirror_script' ) );
172
+
173
+ add_action( 'wck_before_meta_boxes', array( $this, 'wppb_mustache_page_before' ) );
174
+ add_action( 'wck_after_meta_boxes', array( $this, 'wppb_mustache_page_after' ) );
175
+ }
176
+
177
+ /**
178
+ * Function that print the required scripts for the mustache templates
179
+ *
180
+ * @param string $hook the page hook
181
+ *
182
+ * @since 2.0.0
183
+ *
184
+ */
185
+ function wppb_print_mustache_script( $hook ){
186
+ if ( isset( $_GET['post_type'] ) || isset( $_GET['post'] ) || isset( $_GET['page'] ) ){
187
+ if ( isset( $_GET['post_type'] ) )
188
+ $post_type = sanitize_text_field( $_GET['post_type'] );
189
+
190
+ elseif ( isset( $_GET['post'] ) )
191
+ $post_type = get_post_type( absint( $_GET['post'] ) );
192
+ else if( isset( $_GET['page'] ) ){
193
+ $screen = get_current_screen();
194
+ $post_type = $screen->id;
195
+ }
196
+
197
+ if ( ( $this->page[0] == $post_type ) ){
198
+ if( file_exists( WPPB_PLUGIN_DIR . 'assets/lib/codemirror/lib/codemirror.css' ) )
199
+ wp_enqueue_style( 'codemirror-style', WPPB_PLUGIN_URL . 'assets/lib/codemirror/lib/codemirror.css', false, PROFILE_BUILDER_VERSION );
200
+ else if( defined( 'WPPB_PAID_PLUGIN_URL' ) )
201
+ wp_enqueue_style( 'codemirror-style', WPPB_PAID_PLUGIN_URL . 'assets/lib/codemirror/lib/codemirror.css', false, PROFILE_BUILDER_VERSION );
202
+
203
+ if( !wp_script_is( 'codemirror', 'registered' ) && !wp_script_is( 'codemirror', 'enqueued' ) ) {
204
+
205
+ if( file_exists( WPPB_PLUGIN_DIR . 'assets/lib/codemirror/lib/codemirror.js' ) ){
206
+ wp_enqueue_script('codemirror', WPPB_PLUGIN_URL . 'assets/lib/codemirror/lib/codemirror.js', array(), PROFILE_BUILDER_VERSION);
207
+ wp_enqueue_script('codemirror-mode-overlay-js', WPPB_PLUGIN_URL . 'assets/lib/codemirror/addon/mode/overlay.js', array(), '1.0');
208
+ wp_enqueue_script('codemirror-mode-xml-js', WPPB_PLUGIN_URL . 'assets/lib/codemirror/mode/xml/xml.js', array(), '1.0');
209
+ wp_enqueue_script('codemirror-fullscreen-js', WPPB_PLUGIN_URL . 'assets/lib/codemirror/addon/display/fullscreen.js', array(), '1.0');
210
+ } else if ( defined( 'WPPB_PAID_PLUGIN_URL' ) ){
211
+ wp_enqueue_script('codemirror', WPPB_PAID_PLUGIN_URL . 'assets/lib/codemirror/lib/codemirror.js', array(), PROFILE_BUILDER_VERSION);
212
+ wp_enqueue_script('codemirror-mode-overlay-js', WPPB_PAID_PLUGIN_URL . 'assets/lib/codemirror/addon/mode/overlay.js', array(), '1.0');
213
+ wp_enqueue_script('codemirror-mode-xml-js', WPPB_PAID_PLUGIN_URL . 'assets/lib/codemirror/mode/xml/xml.js', array(), '1.0');
214
+ wp_enqueue_script('codemirror-fullscreen-js', WPPB_PAID_PLUGIN_URL . 'assets/lib/codemirror/addon/display/fullscreen.js', array(), '1.0');
215
+ }
216
+
217
+ }
218
+
219
+ wp_enqueue_script('jquery-ui-accordion');
220
+ }
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Function that prints the codemirror initialization and the css
226
+ *
227
+ * @param string $hook the page hook
228
+ *
229
+ * @since 2.0.0
230
+ *
231
+ */
232
+ function wppb_print_codemirror_script(){
233
+ global $printed_codemirror_scripts;
234
+
235
+ if( $printed_codemirror_scripts )
236
+ return;
237
+
238
+ $post_type = NULL;
239
+
240
+ if ( isset( $_GET['post_type'] ) || isset( $_GET['post'] ) || isset( $_GET['page'] ) ){
241
+ if ( isset( $_GET['post_type'] ) )
242
+ $post_type = sanitize_text_field( $_GET['post_type'] );
243
+ elseif ( isset( $_GET['post'] ) )
244
+ $post_type = get_post_type( absint( $_GET['post'] ) );
245
+ else if( isset( $_GET['page'] ) ){
246
+ $screen = get_current_screen();
247
+ if( $screen !== null && is_object( $screen ) ) {
248
+ $post_type = $screen->id;
249
+ }
250
+ }
251
+
252
+ if ( ( $this->page[0] == $post_type ) ){
253
+ if( file_exists( WPPB_PLUGIN_DIR . 'assets/lib/class-mustache-templates/class-mustache-templates.css' ) )
254
+ wp_enqueue_style( 'class-mustache-css', WPPB_PLUGIN_URL . 'assets/lib/class-mustache-templates/class-mustache-templates.css', false, PROFILE_BUILDER_VERSION );
255
+ else if( defined( 'WPPB_PAID_PLUGIN_URL' ) )
256
+ wp_enqueue_style( 'class-mustache-css', WPPB_PAID_PLUGIN_URL . 'assets/lib/class-mustache-templates/class-mustache-templates.css', false, PROFILE_BUILDER_VERSION );
257
+
258
+ if( wp_script_is( 'codemirror-mode-overlay-js', 'enqueued' ) ) {
259
+ if( file_exists( WPPB_PLUGIN_DIR . 'assets/lib/class-mustache-templates/class-mustache-templates.css' ) )
260
+ wp_enqueue_script( 'class-mustache-js', WPPB_PLUGIN_URL . 'assets/lib/class-mustache-templates/class-mustache-templates.js', array("jquery"), PROFILE_BUILDER_VERSION );
261
+ else if( defined( 'WPPB_PAID_PLUGIN_URL' ) )
262
+ wp_enqueue_script( 'class-mustache-js', WPPB_PAID_PLUGIN_URL . 'assets/lib/class-mustache-templates/class-mustache-templates.js', array("jquery"), PROFILE_BUILDER_VERSION );
263
+ }
264
+
265
+ $printed_codemirror_scripts = true;
266
+ }
267
+ }
268
+ }
269
+
270
+ /**
271
+ * Function that adds the mustache metabox
272
+ *
273
+ * @since 2.0.0
274
+ *
275
+ */
276
+ function add_box() {
277
+ global $post_type;
278
+
279
+ foreach ( $this->page as $page ) {
280
+ add_meta_box( $this->id, $this->title, array( $this, 'meta_box_callback' ), $page, 'normal', $this->priority, array( 'post_type' => $post_type ) );
281
+
282
+ /* if it's not a post type add a side metabox with a save button */
283
+ if( isset( $_GET['page'] ) )
284
+ add_meta_box( 'page-save-metabox', __( 'Save', 'profile-builder' ), array( $this, 'page_save_meta_box' ), $page, 'side' );
285
+ }
286
+ }
287
+
288
+ /**
289
+ * Function output the content for the metabox
290
+ *
291
+ *
292
+ * @since 2.0.0
293
+ *
294
+ */
295
+ function meta_box_callback( $post, $metabox ) {
296
+ global $post;
297
+ if( isset( $_GET['page'] ) )
298
+ $post_type = NULL;
299
+ else
300
+ $post_type = $metabox['args']['post_type'];
301
+
302
+ /* save default values in the database as post meta for post types */
303
+ if( !empty( $this->fields ) && !empty( $post->ID ) ){
304
+ foreach( $this->fields as $field ){
305
+ if( !empty( $field['default'] ) ){
306
+ /* see if we have an option with this name, if we have don't do anything */
307
+ $field_saved_value = get_post_meta( $post->ID, $field['id'], true );
308
+ if( empty( $field_saved_value ) ){
309
+ add_post_meta( $post->ID, $field['id'], $field['default'], true );
310
+ }
311
+ }
312
+ }
313
+ }
314
+
315
+ // Use nonce for verification
316
+ echo '<input type="hidden" name="' . esc_attr( $post_type ) . '_meta_box_nonce" value="' . esc_attr( wp_create_nonce( basename( __FILE__) ) ) . '" />';
317
+
318
+ // Begin the field table and loop
319
+ echo '<table class="form-table meta_box mustache-box">';
320
+ foreach ( $this->fields as $field ) {
321
+
322
+ // get data for this field
323
+ extract( $field );
324
+ if ( !empty( $desc ) )
325
+ $desc = '<span class="description">' . $desc . '</span>';
326
+
327
+ // get value of this field if it exists for this post
328
+ if( isset( $_GET['page'] ) ){
329
+ $meta = get_option( $id );
330
+ }
331
+ else{
332
+ $meta = get_post_meta( $post->ID, $id, true );
333
+ }
334
+
335
+ //try to set a default value
336
+ if( empty( $meta ) && !empty( $default ) ){
337
+ $meta = $default;
338
+ }
339
+
340
+
341
+ // begin a table row with
342
+ echo '<tr>
343
+ <td class="' . esc_attr( $id ) . ' ' . esc_attr( $type ) . '">';
344
+ if( $type != 'header' && !empty( $label ) ){
345
+ echo '<label for="' . esc_attr( $id ) . '" class="wppb_mustache_label">' . esc_html( $label ) . '</label>';
346
+ }
347
+ switch( $type ) {
348
+ // text
349
+ case 'text':
350
+ echo '<input type="text" name="' . esc_attr( $id ) . '" id="' . esc_attr( $id ) . '" value="' . esc_attr( $meta ) . '" size="30" />
351
+ <br />' . wp_kses_post( $desc ) ;
352
+ break;
353
+ // textarea
354
+ case 'textarea':
355
+
356
+ echo '<textarea name="' . esc_attr( $id ) . '" id="' . esc_attr( $id ) . '" cols="220" rows="4" class="wppb_mustache_template">' . esc_textarea( $meta ) . '</textarea>';
357
+ echo wp_kses_post( $desc ) ;
358
+ break;
359
+ // checkbox
360
+ case 'checkbox':
361
+ echo '<input type="checkbox" name="' . esc_attr( $id ) . '" id="' . esc_attr( $id ) . '"' . checked( esc_attr( $meta ), 'on', false ) . ' value="on" />
362
+ <label for="' . esc_attr( $id ) . '">' . esc_html( $desc ) . '</label>';
363
+ break;
364
+ // select
365
+ case 'select':
366
+ echo '<select name="' . esc_attr( $id ) . '" id="' . esc_attr( $id ) . '">';
367
+ foreach ( $options as $option )
368
+ echo '<option' . selected( esc_attr( $meta ), $option['value'], false ) . ' value="' . esc_attr( $option['value'] ) . '">' . esc_html( $option['label'] ) . '</option>';
369
+ echo '</select><br />' . wp_kses_post( $desc );
370
+ break;
371
+ // radio
372
+ case 'radio':
373
+ foreach ( $options as $option )
374
+ echo '<input type="radio" name="' . esc_attr( $id ) . '" id="' . esc_attr( $id ) . '-' . esc_attr( $option['value'] ) . '" value="' . esc_attr( $option['value'] ) . '"' . checked( esc_attr( $meta ), $option['value'], false ) . ' />
375
+ <label for="' . esc_attr( $id ) . '-' . esc_attr( $option['value'] ) . '">' . esc_html( $option['label'] ) . '</label><br />';
376
+ echo '' . wp_kses_post( $desc );
377
+ break;
378
+ // checkbox_group
379
+ case 'checkbox_group':
380
+ foreach ( $options as $option )
381
+ echo '<input type="checkbox" value="' . esc_attr( $option['value'] ) . '" name="' . $id . '[]" id="' . $id . '-' . $option['value'] . '"' , is_array( $meta ) && in_array( $option['value'], $meta ) ? ' checked="checked"' : '' , ' />
382
+ <label for="' . esc_attr( $id ) . '-' . esc_attr( $option['value'] ) . '">' . esc_html( $option['label'] ) . '</label><br />';
383
+ echo '' . wp_kses_post( $desc );
384
+ break;
385
+ // text
386
+ case 'header':
387
+ echo '<h4>'. esc_html( $default ) .'</h4>';
388
+ break;
389
+ } //end switch
390
+
391
+ if( $type == 'textarea' ){
392
+ ?>
393
+ <div class="stp-extra">
394
+ <?php
395
+ if( !empty( $this->mustache_vars ) ){
396
+ foreach( $this->mustache_vars as $mustache_var_group ){
397
+ ?>
398
+ <h4><?php echo esc_html( $mustache_var_group['group-title'] ); ?></h4>
399
+ <pre><?php do_action( 'wppb_before_mustache_vars_display', $mustache_var_group, $id, $post_type ); $this->display_mustache_available_vars( $mustache_var_group['variables'], 0 ); ?></pre>
400
+ <?php
401
+ }
402
+ }
403
+ ?>
404
+ </div>
405
+ <?php
406
+ }
407
+ echo '</td></tr>';
408
+ } // end foreach
409
+ echo '</table>'; // end table
410
+ }
411
+
412
+ /**
413
+ * Function that ads a side metabox on pages ( not post types ) with a save button
414
+ *
415
+ *
416
+ * @since 2.0.0
417
+ *
418
+ */
419
+ function page_save_meta_box() {
420
+ ?>
421
+ <input type="submit" value="<?php esc_html_e( 'Save Changes', 'profile-builder' ); ?>" class="button button-primary button-large mustache-save">
422
+ <?php
423
+ }
424
+
425
+ /**
426
+ * Function that ads a form start on pages ( not post types ) and also the 'save_post' action
427
+ *
428
+ *
429
+ * @since 2.0.0
430
+ *
431
+ */
432
+ function wppb_mustache_page_before( $hook ){
433
+ global $started_mustache_form;
434
+
435
+ if( $started_mustache_form )
436
+ return;
437
+
438
+ if( isset( $_GET['page'] ) && $hook == $this->page[0] ){
439
+ /* if we are saving do the action 'save_post' */
440
+ if( isset( $_GET['mustache_action'] ) && $_GET['mustache_action'] == 'save' ) {
441
+ /* to avoid conflicts with other plugins we send an empty post object as the second parameter with a fake post type */
442
+ $post = new WP_Post( new stdClass() );
443
+ $post->post_type = 'wppb-mustache-settings-page';
444
+ remove_all_actions('save_post', 10 );//remove all actions that could run on the save_post hook on priority 10 than could expect a numeric post id instead of a string like $this->id. Ex: add_action( 'save_post', array( $this, 'refresh_post_word_count' ) ); in WPML translation management
445
+ remove_all_actions('save_post', PHP_INT_MAX );//same for PHP_INT_MAX priority: add_action( 'save_post', array( $this, 'queue_save_post_actions' ), PHP_INT_MAX, 2 );
446
+ do_action( 'save_post', $this->id, $post, false );
447
+ }
448
+
449
+ echo '<form action="'. esc_url( add_query_arg( array('mustache_action' => 'save') ) ) .'" method="post">';
450
+ $started_mustache_form = true;
451
+ }
452
+ }
453
+
454
+ /**
455
+ * Function that ads a form end on pages ( not post types )
456
+ *
457
+ *
458
+ * @since 2.0.0
459
+ *
460
+ */
461
+ function wppb_mustache_page_after( $hook ){
462
+ global $ended_mustache_form;
463
+ if( $ended_mustache_form )
464
+ return;
465
+
466
+ if( isset( $_GET['page'] ) && $hook == $this->page[0] ){
467
+ echo '</form>';
468
+ $ended_mustache_form = true;
469
+ }
470
+ }
471
+
472
+
473
+ /**
474
+ * Function that transforms and echoes the $mustache_vars into despayable forms. It takes care of nested levels and adds {{}} as well
475
+ *
476
+ * @param array $mustache_vars is the array containing the names of the variable that must be proccessed by mustache in the template. The array must be in this form:
477
+ * array( array( 'name' => '', 'type' => '' ) ... ), and for loop tags it also contains a 'children' element which contains other mustache_vars array( array( 'name' => 'users', 'type' => 'loop_tag', 'children' => $merge_tags )
478
+ * @param int $level the current level of the nested variables.
479
+ *
480
+ * @since 2.0.0
481
+ *
482
+ */
483
+ function display_mustache_available_vars( $mustache_vars, $level ){
484
+ if( !empty( $mustache_vars ) ){
485
+ foreach( $mustache_vars as $var ){
486
+ if ( $var[ 'name' ] != 'password' ) {
487
+ if ( empty( $var[ 'children' ] ) ) {
488
+ echo str_repeat( "&nbsp;&nbsp;", $level );//phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
489
+ if ( !empty( $var[ 'label' ] ) )
490
+ echo esc_html( apply_filters( 'wppb_variable_label', $var[ 'label' ] . ':' ) );
491
+ if ( !empty( $var[ 'unescaped' ] ) && $var[ 'unescaped' ] === true )
492
+ echo '{{{';
493
+ else
494
+ echo '{{';
495
+ echo esc_html( $var[ 'name' ] );
496
+ if ( !empty( $var[ 'unescaped' ] ) && $var[ 'unescaped' ] === true )
497
+ echo '}}}';
498
+ else
499
+ echo '}}';
500
+ echo PHP_EOL;
501
+ } else {
502
+ echo str_repeat( "&nbsp;&nbsp;", $level ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
503
+ echo '{{#' . esc_html( $var[ 'name' ] ) . '}}' . PHP_EOL;
504
+ $level++;
505
+ $this->display_mustache_available_vars( $var[ 'children' ], $level );
506
+ $level--;
507
+ echo str_repeat( "&nbsp;&nbsp;", $level ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
508
+ echo '{{/' . esc_html( $var[ 'name' ] ) . '}}' . PHP_EOL;
509
+ }
510
+ }
511
+ }
512
+ }
513
+ }
514
+
515
+ /**
516
+ * Function that saves the values from the metabox
517
+ *
518
+ * @param int $post_id the post id
519
+ * @param post object $post
520
+ *
521
+ * @since 2.0.0
522
+ *
523
+ */
524
+ function save_box( $post_id, $post ){
525
+ global $post_type;
526
+ /* addition to save as option if we are not on a post type */
527
+ if( !is_numeric( $post_id ) && isset( $_POST['_meta_box_nonce'] ) && @wp_verify_nonce( sanitize_text_field( $_POST['_meta_box_nonce'] ), basename( __FILE__ ) ) ){
528
+ foreach ( $this->fields as $field ) {
529
+
530
+ if ( isset( $_POST[$field['id']] ) ) {
531
+ update_option( $field['id'], wp_unslash( $_POST[$field['id']] ) );//phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
532
+ }
533
+
534
+ }
535
+ }
536
+
537
+ // verify nonce
538
+ if ( ! ( in_array( $post_type, $this->page ) && isset( $_POST[$post_type . '_meta_box_nonce'] ) && @wp_verify_nonce( sanitize_text_field( $_POST[$post_type . '_meta_box_nonce'] ), basename( __FILE__ ) ) ) )
539
+ return $post_id;
540
+ // check autosave
541
+ if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
542
+ return $post_id;
543
+ // check permissions
544
+ if ( !current_user_can( 'edit_page', $post_id ) )
545
+ return $post_id;
546
+
547
+ // loop through fields and save the data
548
+ foreach ( $this->fields as $field ) {
549
+ if( $field['type'] == 'tax_select' ) {
550
+ // save taxonomies
551
+ if ( isset( $_POST[$field['id']] ) )
552
+ $term = $_POST[$field['id']]; //phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
553
+ wp_set_object_terms( $post_id, $term, $field['id'] );
554
+ }
555
+ else {
556
+ // save the rest
557
+ $old = get_post_meta( $post_id, $field['id'], true );
558
+ if ( isset( $_POST[$field['id']] ) ) {
559
+ $new = $_POST[$field['id']]; /* phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized */
560
+ } else {
561
+ $new = '';
562
+ }
563
+
564
+ if ( $new && $new != $old ) {
565
+ if ( is_array( $new ) ) {
566
+ foreach ( $new as &$item ) {
567
+ //$item = esc_attr( $item );
568
+ }
569
+ unset( $item );
570
+ } else {
571
+ //$new = esc_attr( $new );
572
+ }
573
+ update_post_meta( $post_id, $field['id'], $new );
574
+ } elseif ( '' == $new && $old ) {
575
+ delete_post_meta( $post_id, $field['id'], $old );
576
+ }
577
+ }
578
+ } // end foreach
579
+ }
580
+
581
+
582
+ /**
583
+ * Function that saves the default values for each field in the database for options
584
+ *
585
+ *
586
+ * @since 2.0.0
587
+ *
588
+ */
589
+
590
+ function save_default_values(){
591
+ /* only do it on pages where we save as options and we have fields */
592
+ if( !empty( $this->fields ) && !post_type_exists($this->page[0]) ){
593
+ foreach( $this->fields as $field ){
594
+ if( !empty( $field['default'] ) ){
595
+ /* see if we have an option with this name, if we have don't do anything */
596
+ $field_saved_value = get_option( $field['id'] );
597
+ if( empty( $field_saved_value ) ) {
598
+ update_option( $field['id'], $field['default'] );
599
+ }
600
+ }
601
+ }
602
+ }
603
+ }
604
+ }
assets/lib/codemirror/LICENSE ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Copyright (C) 2013 by Marijn Haverbeke <marijnh@gmail.com> and others
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
assets/lib/codemirror/README.md ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ # CodeMirror
2
+ [![Build Status](https://secure.travis-ci.org/marijnh/CodeMirror.png?branch=master)](http://travis-ci.org/marijnh/CodeMirror)
3
+ [![NPM version](https://badge.fury.io/js/codemirror.png)](http://badge.fury.io/js/codemirror)
4
+
5
+ CodeMirror is a JavaScript component that provides a code editor in
6
+ the browser. When a mode is available for the language you are coding
7
+ in, it will color your code, and optionally help with indentation.
8
+
9
+ The project page is http://codemirror.net
10
+ The manual is at http://codemirror.net/doc/manual.html
11
+ The contributing guidelines are in [CONTRIBUTING.md](https://github.com/marijnh/CodeMirror/blob/master/CONTRIBUTING.md)
assets/lib/codemirror/addon/comment/comment.js ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function() {
2
+ "use strict";
3
+
4
+ var noOptions = {};
5
+ var nonWS = /[^\s\u00a0]/;
6
+ var Pos = CodeMirror.Pos;
7
+
8
+ function firstNonWS(str) {
9
+ var found = str.search(nonWS);
10
+ return found == -1 ? 0 : found;
11
+ }
12
+
13
+ CodeMirror.commands.toggleComment = function(cm) {
14
+ var from = cm.getCursor("start"), to = cm.getCursor("end");
15
+ cm.uncomment(from, to) || cm.lineComment(from, to);
16
+ };
17
+
18
+ CodeMirror.defineExtension("lineComment", function(from, to, options) {
19
+ if (!options) options = noOptions;
20
+ var self = this, mode = self.getModeAt(from);
21
+ var commentString = options.lineComment || mode.lineComment;
22
+ if (!commentString) {
23
+ if (options.blockCommentStart || mode.blockCommentStart) {
24
+ options.fullLines = true;
25
+ self.blockComment(from, to, options);
26
+ }
27
+ return;
28
+ }
29
+ var firstLine = self.getLine(from.line);
30
+ if (firstLine == null) return;
31
+ var end = Math.min(to.ch != 0 || to.line == from.line ? to.line + 1 : to.line, self.lastLine() + 1);
32
+ var pad = options.padding == null ? " " : options.padding;
33
+ var blankLines = options.commentBlankLines || from.line == to.line;
34
+
35
+ self.operation(function() {
36
+ if (options.indent) {
37
+ var baseString = firstLine.slice(0, firstNonWS(firstLine));
38
+ for (var i = from.line; i < end; ++i) {
39
+ var line = self.getLine(i), cut = baseString.length;
40
+ if (!blankLines && !nonWS.test(line)) continue;
41
+ if (line.slice(0, cut) != baseString) cut = firstNonWS(line);
42
+ self.replaceRange(baseString + commentString + pad, Pos(i, 0), Pos(i, cut));
43
+ }
44
+ } else {
45
+ for (var i = from.line; i < end; ++i) {
46
+ if (blankLines || nonWS.test(self.getLine(i)))
47
+ self.replaceRange(commentString + pad, Pos(i, 0));
48
+ }
49
+ }
50
+ });
51
+ });
52
+
53
+ CodeMirror.defineExtension("blockComment", function(from, to, options) {
54
+ if (!options) options = noOptions;
55
+ var self = this, mode = self.getModeAt(from);
56
+ var startString = options.blockCommentStart || mode.blockCommentStart;
57
+ var endString = options.blockCommentEnd || mode.blockCommentEnd;
58
+ if (!startString || !endString) {
59
+ if ((options.lineComment || mode.lineComment) && options.fullLines != false)
60
+ self.lineComment(from, to, options);
61
+ return;
62
+ }
63
+
64
+ var end = Math.min(to.line, self.lastLine());
65
+ if (end != from.line && to.ch == 0 && nonWS.test(self.getLine(end))) --end;
66
+
67
+ var pad = options.padding == null ? " " : options.padding;
68
+ if (from.line > end) return;
69
+
70
+ self.operation(function() {
71
+ if (options.fullLines != false) {
72
+ var lastLineHasText = nonWS.test(self.getLine(end));
73
+ self.replaceRange(pad + endString, Pos(end));
74
+ self.replaceRange(startString + pad, Pos(from.line, 0));
75
+ var lead = options.blockCommentLead || mode.blockCommentLead;
76
+ if (lead != null) for (var i = from.line + 1; i <= end; ++i)
77
+ if (i != end || lastLineHasText)
78
+ self.replaceRange(lead + pad, Pos(i, 0));
79
+ } else {
80
+ self.replaceRange(endString, to);
81
+ self.replaceRange(startString, from);
82
+ }
83
+ });
84
+ });
85
+
86
+ CodeMirror.defineExtension("uncomment", function(from, to, options) {
87
+ if (!options) options = noOptions;
88
+ var self = this, mode = self.getModeAt(from);
89
+ var end = Math.min(to.line, self.lastLine()), start = Math.min(from.line, end);
90
+
91
+ // Try finding line comments
92
+ var lineString = options.lineComment || mode.lineComment, lines = [];
93
+ var pad = options.padding == null ? " " : options.padding, didSomething;
94
+ lineComment: {
95
+ if (!lineString) break lineComment;
96
+ for (var i = start; i <= end; ++i) {
97
+ var line = self.getLine(i);
98
+ var found = line.indexOf(lineString);
99
+ if (found == -1 && (i != end || i == start) && nonWS.test(line)) break lineComment;
100
+ if (i != start && found > -1 && nonWS.test(line.slice(0, found))) break lineComment;
101
+ lines.push(line);
102
+ }
103
+ self.operation(function() {
104
+ for (var i = start; i <= end; ++i) {
105
+ var line = lines[i - start];
106
+ var pos = line.indexOf(lineString), endPos = pos + lineString.length;
107
+ if (pos < 0) continue;
108
+ if (line.slice(endPos, endPos + pad.length) == pad) endPos += pad.length;
109
+ didSomething = true;
110
+ self.replaceRange("", Pos(i, pos), Pos(i, endPos));
111
+ }
112
+ });
113
+ if (didSomething) return true;
114
+ }
115
+
116
+ // Try block comments
117
+ var startString = options.blockCommentStart || mode.blockCommentStart;
118
+ var endString = options.blockCommentEnd || mode.blockCommentEnd;
119
+ if (!startString || !endString) return false;
120
+ var lead = options.blockCommentLead || mode.blockCommentLead;
121
+ var startLine = self.getLine(start), endLine = end == start ? startLine : self.getLine(end);
122
+ var open = startLine.indexOf(startString), close = endLine.lastIndexOf(endString);
123
+ if (close == -1 && start != end) {
124
+ endLine = self.getLine(--end);
125
+ close = endLine.lastIndexOf(endString);
126
+ }
127
+ if (open == -1 || close == -1) return false;
128
+
129
+ self.operation(function() {
130
+ self.replaceRange("", Pos(end, close - (pad && endLine.slice(close - pad.length, close) == pad ? pad.length : 0)),
131
+ Pos(end, close + endString.length));
132
+ var openEnd = open + startString.length;
133
+ if (pad && startLine.slice(openEnd, openEnd + pad.length) == pad) openEnd += pad.length;
134
+ self.replaceRange("", Pos(start, open), Pos(start, openEnd));
135
+ if (lead) for (var i = start + 1; i <= end; ++i) {
136
+ var line = self.getLine(i), found = line.indexOf(lead);
137
+ if (found == -1 || nonWS.test(line.slice(0, found))) continue;
138
+ var foundEnd = found + lead.length;
139
+ if (pad && line.slice(foundEnd, foundEnd + pad.length) == pad) foundEnd += pad.length;
140
+ self.replaceRange("", Pos(i, found), Pos(i, foundEnd));
141
+ }
142
+ });
143
+ return true;
144
+ });
145
+ })();
assets/lib/codemirror/addon/comment/continuecomment.js ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function() {
2
+ var modes = ["clike", "css", "javascript"];
3
+ for (var i = 0; i < modes.length; ++i)
4
+ CodeMirror.extendMode(modes[i], {blockCommentContinue: " * "});
5
+
6
+ function continueComment(cm) {
7
+ var pos = cm.getCursor(), token = cm.getTokenAt(pos);
8
+ if (token.type != "comment") return CodeMirror.Pass;
9
+ var mode = CodeMirror.innerMode(cm.getMode(), token.state).mode;
10
+
11
+ var insert;
12
+ if (mode.blockCommentStart && mode.blockCommentContinue) {
13
+ var end = token.string.indexOf(mode.blockCommentEnd);
14
+ var full = cm.getRange(CodeMirror.Pos(pos.line, 0), CodeMirror.Pos(pos.line, token.end)), found;
15
+ if (end != -1 && end == token.string.length - mode.blockCommentEnd.length) {
16
+ // Comment ended, don't continue it
17
+ } else if (token.string.indexOf(mode.blockCommentStart) == 0) {
18
+ insert = full.slice(0, token.start);
19
+ if (!/^\s*$/.test(insert)) {
20
+ insert = "";
21
+ for (var i = 0; i < token.start; ++i) insert += " ";
22
+ }
23
+ } else if ((found = full.indexOf(mode.blockCommentContinue)) != -1 &&
24
+ found + mode.blockCommentContinue.length > token.start &&
25
+ /^\s*$/.test(full.slice(0, found))) {
26
+ insert = full.slice(0, found);
27
+ }
28
+ if (insert != null) insert += mode.blockCommentContinue;
29
+ }
30
+ if (insert == null && mode.lineComment) {
31
+ var line = cm.getLine(pos.line), found = line.indexOf(mode.lineComment);
32
+ if (found > -1) {
33
+ insert = line.slice(0, found);
34
+ if (/\S/.test(insert)) insert = null;
35
+ else insert += mode.lineComment + line.slice(found + mode.lineComment.length).match(/^\s*/)[0];
36
+ }
37
+ }
38
+
39
+ if (insert != null)
40
+ cm.replaceSelection("\n" + insert, "end");
41
+ else
42
+ return CodeMirror.Pass;
43
+ }
44
+
45
+ CodeMirror.defineOption("continueComments", null, function(cm, val, prev) {
46
+ if (prev && prev != CodeMirror.Init)
47
+ cm.removeKeyMap("continueComment");
48
+ if (val) {
49
+ var map = {name: "continueComment"};
50
+ map[typeof val == "string" ? val : "Enter"] = continueComment;
51
+ cm.addKeyMap(map);
52
+ }
53
+ });
54
+ })();
assets/lib/codemirror/addon/dialog/dialog.css ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .CodeMirror-dialog {
2
+ position: absolute;
3
+ left: 0; right: 0;
4
+ background: white;
5
+ z-index: 15;
6
+ padding: .1em .8em;
7
+ overflow: hidden;
8
+ color: #333;
9
+ }
10
+
11
+ .CodeMirror-dialog-top {
12
+ border-bottom: 1px solid #eee;
13
+ top: 0;
14
+ }
15
+
16
+ .CodeMirror-dialog-bottom {
17
+ border-top: 1px solid #eee;
18
+ bottom: 0;
19
+ }
20
+
21
+ .CodeMirror-dialog input {
22
+ border: none;
23
+ outline: none;
24
+ background: transparent;
25
+ width: 20em;
26
+ color: inherit;
27
+ font-family: monospace;
28
+ }
29
+
30
+ .CodeMirror-dialog button {
31
+ font-size: 70%;
32
+ }
assets/lib/codemirror/addon/dialog/dialog.js ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Open simple dialogs on top of an editor. Relies on dialog.css.
2
+
3
+ (function() {
4
+ function dialogDiv(cm, template, bottom) {
5
+ var wrap = cm.getWrapperElement();
6
+ var dialog;
7
+ dialog = wrap.appendChild(document.createElement("div"));
8
+ if (bottom) {
9
+ dialog.className = "CodeMirror-dialog CodeMirror-dialog-bottom";
10
+ } else {
11
+ dialog.className = "CodeMirror-dialog CodeMirror-dialog-top";
12
+ }
13
+ dialog.innerHTML = template;
14
+ return dialog;
15
+ }
16
+
17
+ CodeMirror.defineExtension("openDialog", function(template, callback, options) {
18
+ var dialog = dialogDiv(this, template, options && options.bottom);
19
+ var closed = false, me = this;
20
+ function close() {
21
+ if (closed) return;
22
+ closed = true;
23
+ dialog.parentNode.removeChild(dialog);
24
+ }
25
+ var inp = dialog.getElementsByTagName("input")[0], button;
26
+ if (inp) {
27
+ CodeMirror.on(inp, "keydown", function(e) {
28
+ if (options && options.onKeyDown && options.onKeyDown(e, inp.value, close)) { return; }
29
+ if (e.keyCode == 13 || e.keyCode == 27) {
30
+ CodeMirror.e_stop(e);
31
+ close();
32
+ me.focus();
33
+ if (e.keyCode == 13) callback(inp.value);
34
+ }
35
+ });
36
+ if (options && options.onKeyUp) {
37
+ CodeMirror.on(inp, "keyup", function(e) {options.onKeyUp(e, inp.value, close);});
38
+ }
39
+ if (options && options.value) inp.value = options.value;
40
+ inp.focus();
41
+ CodeMirror.on(inp, "blur", close);
42
+ } else if (button = dialog.getElementsByTagName("button")[0]) {
43
+ CodeMirror.on(button, "click", function() {
44
+ close();
45
+ me.focus();
46
+ });
47
+ button.focus();
48
+ CodeMirror.on(button, "blur", close);
49
+ }
50
+ return close;
51
+ });
52
+
53
+ CodeMirror.defineExtension("openConfirm", function(template, callbacks, options) {
54
+ var dialog = dialogDiv(this, template, options && options.bottom);
55
+ var buttons = dialog.getElementsByTagName("button");
56
+ var closed = false, me = this, blurring = 1;
57
+ function close() {
58
+ if (closed) return;
59
+ closed = true;
60
+ dialog.parentNode.removeChild(dialog);
61
+ me.focus();
62
+ }
63
+ buttons[0].focus();
64
+ for (var i = 0; i < buttons.length; ++i) {
65
+ var b = buttons[i];
66
+ (function(callback) {
67
+ CodeMirror.on(b, "click", function(e) {
68
+ CodeMirror.e_preventDefault(e);
69
+ close();
70
+ if (callback) callback(me);
71
+ });
72
+ })(callbacks[i]);
73
+ CodeMirror.on(b, "blur", function() {
74
+ --blurring;
75
+ setTimeout(function() { if (blurring <= 0) close(); }, 200);
76
+ });
77
+ CodeMirror.on(b, "focus", function() { ++blurring; });
78
+ }
79
+ });
80
+ })();
assets/lib/codemirror/addon/display/fullscreen.css ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ .CodeMirror-fullscreen {
2
+ position: fixed;
3
+ top: 0; left: 0; right: 0; bottom: 0;
4
+ height: auto;
5
+ z-index: 9;
6
+ }
assets/lib/codemirror/addon/display/fullscreen.js ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function() {
2
+ "use strict";
3
+
4
+ CodeMirror.defineOption("fullScreen", false, function(cm, val, old) {
5
+ if (old == CodeMirror.Init) old = false;
6
+ if (!old == !val) return;
7
+ if (val) setFullscreen(cm);
8
+ else setNormal(cm);
9
+ });
10
+
11
+ function setFullscreen(cm) {
12
+ var wrap = cm.getWrapperElement();
13
+ cm.state.fullScreenRestore = {scrollTop: window.pageYOffset, scrollLeft: window.pageXOffset,
14
+ width: wrap.style.width, height: wrap.style.height};
15
+ wrap.style.width = wrap.style.height = "";
16
+ wrap.className += " CodeMirror-fullscreen";
17
+ document.documentElement.style.overflow = "hidden";
18
+ cm.refresh();
19
+ }
20
+
21
+ function setNormal(cm) {
22
+ var wrap = cm.getWrapperElement();
23
+ wrap.className = wrap.className.replace(/\s*CodeMirror-fullscreen\b/, "");
24
+ document.documentElement.style.overflow = "";
25
+ var info = cm.state.fullScreenRestore;
26
+ wrap.style.width = info.width; wrap.style.height = info.height;
27
+ window.scrollTo(info.scrollLeft, info.scrollTop);
28
+ cm.refresh();
29
+ }
30
+ })();
assets/lib/codemirror/addon/display/placeholder.js ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function() {
2
+ CodeMirror.defineOption("placeholder", "", function(cm, val, old) {
3
+ var prev = old && old != CodeMirror.Init;
4
+ if (val && !prev) {
5
+ cm.on("focus", onFocus);
6
+ cm.on("blur", onBlur);
7
+ cm.on("change", onChange);
8
+ onChange(cm);
9
+ } else if (!val && prev) {
10
+ cm.off("focus", onFocus);
11
+ cm.off("blur", onBlur);
12
+ cm.off("change", onChange);
13
+ clearPlaceholder(cm);
14
+ var wrapper = cm.getWrapperElement();
15
+ wrapper.className = wrapper.className.replace(" CodeMirror-empty", "");
16
+ }
17
+
18
+ if (val && !cm.hasFocus()) onBlur(cm);
19
+ });
20
+
21
+ function clearPlaceholder(cm) {
22
+ if (cm.state.placeholder) {
23
+ cm.state.placeholder.parentNode.removeChild(cm.state.placeholder);
24
+ cm.state.placeholder = null;
25
+ }
26
+ }
27
+ function setPlaceholder(cm) {
28
+ clearPlaceholder(cm);
29
+ var elt = cm.state.placeholder = document.createElement("pre");
30
+ elt.style.cssText = "height: 0; overflow: visible";
31
+ elt.className = "CodeMirror-placeholder";
32
+ elt.appendChild(document.createTextNode(cm.getOption("placeholder")));
33
+ cm.display.lineSpace.insertBefore(elt, cm.display.lineSpace.firstChild);
34
+ }
35
+
36
+ function onFocus(cm) {
37
+ clearPlaceholder(cm);
38
+ }
39
+ function onBlur(cm) {
40
+ if (isEmpty(cm)) setPlaceholder(cm);
41
+ }
42
+ function onChange(cm) {
43
+ var wrapper = cm.getWrapperElement(), empty = isEmpty(cm);
44
+ wrapper.className = wrapper.className.replace(" CodeMirror-empty", "") + (empty ? " CodeMirror-empty" : "");
45
+
46
+ if (cm.hasFocus()) return;
47
+ if (empty) setPlaceholder(cm);
48
+ else clearPlaceholder(cm);
49
+ }
50
+
51
+ function isEmpty(cm) {
52
+ return (cm.lineCount() === 1) && (cm.getLine(0) === "");
53
+ }
54
+ })();
assets/lib/codemirror/addon/edit/closebrackets.js ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function() {
2
+ var DEFAULT_BRACKETS = "()[]{}''\"\"";
3
+ var DEFAULT_EXPLODE_ON_ENTER = "[]{}";
4
+ var SPACE_CHAR_REGEX = /\s/;
5
+
6
+ CodeMirror.defineOption("autoCloseBrackets", false, function(cm, val, old) {
7
+ if (old != CodeMirror.Init && old)
8
+ cm.removeKeyMap("autoCloseBrackets");
9
+ if (!val) return;
10
+ var pairs = DEFAULT_BRACKETS, explode = DEFAULT_EXPLODE_ON_ENTER;
11
+ if (typeof val == "string") pairs = val;
12
+ else if (typeof val == "object") {
13
+ if (val.pairs != null) pairs = val.pairs;
14
+ if (val.explode != null) explode = val.explode;
15
+ }
16
+ var map = buildKeymap(pairs);
17
+ if (explode) map.Enter = buildExplodeHandler(explode);
18
+ cm.addKeyMap(map);
19
+ });
20
+
21
+ function charsAround(cm, pos) {
22
+ var str = cm.getRange(CodeMirror.Pos(pos.line, pos.ch - 1),
23
+ CodeMirror.Pos(pos.line, pos.ch + 1));
24
+ return str.length == 2 ? str : null;
25
+ }
26
+
27
+ function buildKeymap(pairs) {
28
+ var map = {
29
+ name : "autoCloseBrackets",
30
+ Backspace: function(cm) {
31
+ if (cm.somethingSelected()) return CodeMirror.Pass;
32
+ var cur = cm.getCursor(), around = charsAround(cm, cur);
33
+ if (around && pairs.indexOf(around) % 2 == 0)
34
+ cm.replaceRange("", CodeMirror.Pos(cur.line, cur.ch - 1), CodeMirror.Pos(cur.line, cur.ch + 1));
35
+ else
36
+ return CodeMirror.Pass;
37
+ }
38
+ };
39
+ var closingBrackets = "";
40
+ for (var i = 0; i < pairs.length; i += 2) (function(left, right) {
41
+ if (left != right) closingBrackets += right;
42
+ function surround(cm) {
43
+ var selection = cm.getSelection();
44
+ cm.replaceSelection(left + selection + right);
45
+ }
46
+ function maybeOverwrite(cm) {
47
+ var cur = cm.getCursor(), ahead = cm.getRange(cur, CodeMirror.Pos(cur.line, cur.ch + 1));
48
+ if (ahead != right || cm.somethingSelected()) return CodeMirror.Pass;
49
+ else cm.execCommand("goCharRight");
50
+ }
51
+ map["'" + left + "'"] = function(cm) {
52
+ if (left == "'" && cm.getTokenAt(cm.getCursor()).type == "comment")
53
+ return CodeMirror.Pass;
54
+ if (cm.somethingSelected()) return surround(cm);
55
+ if (left == right && maybeOverwrite(cm) != CodeMirror.Pass) return;
56
+ var cur = cm.getCursor(), ahead = CodeMirror.Pos(cur.line, cur.ch + 1);
57
+ var line = cm.getLine(cur.line), nextChar = line.charAt(cur.ch), curChar = cur.ch > 0 ? line.charAt(cur.ch - 1) : "";
58
+ if (left == right && CodeMirror.isWordChar(curChar))
59
+ return CodeMirror.Pass;
60
+ if (line.length == cur.ch || closingBrackets.indexOf(nextChar) >= 0 || SPACE_CHAR_REGEX.test(nextChar))
61
+ cm.replaceSelection(left + right, {head: ahead, anchor: ahead});
62
+ else
63
+ return CodeMirror.Pass;
64
+ };
65
+ if (left != right) map["'" + right + "'"] = maybeOverwrite;
66
+ })(pairs.charAt(i), pairs.charAt(i + 1));
67
+ return map;
68
+ }
69
+
70
+ function buildExplodeHandler(pairs) {
71
+ return function(cm) {
72
+ var cur = cm.getCursor(), around = charsAround(cm, cur);
73
+ if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
74
+ cm.operation(function() {
75
+ var newPos = CodeMirror.Pos(cur.line + 1, 0);
76
+ cm.replaceSelection("\n\n", {anchor: newPos, head: newPos}, "+input");
77
+ cm.indentLine(cur.line + 1, null, true);
78
+ cm.indentLine(cur.line + 2, null, true);
79
+ });
80
+ };
81
+ }
82
+ })();
assets/lib/codemirror/addon/edit/closetag.js ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Tag-closer extension for CodeMirror.
3
+ *
4
+ * This extension adds an "autoCloseTags" option that can be set to
5
+ * either true to get the default behavior, or an object to further
6
+ * configure its behavior.
7
+ *
8
+ * These are supported options:
9
+ *
10
+ * `whenClosing` (default true)
11
+ * Whether to autoclose when the '/' of a closing tag is typed.
12
+ * `whenOpening` (default true)
13
+ * Whether to autoclose the tag when the final '>' of an opening
14
+ * tag is typed.
15
+ * `dontCloseTags` (default is empty tags for HTML, none for XML)
16
+ * An array of tag names that should not be autoclosed.
17
+ * `indentTags` (default is block tags for HTML, none for XML)
18
+ * An array of tag names that should, when opened, cause a
19
+ * blank line to be added inside the tag, and the blank line and
20
+ * closing line to be indented.
21
+ *
22
+ * See demos/closetag.html for a usage example.
23
+ */
24
+
25
+ (function() {
26
+ CodeMirror.defineOption("autoCloseTags", false, function(cm, val, old) {
27
+ if (val && (old == CodeMirror.Init || !old)) {
28
+ var map = {name: "autoCloseTags"};
29
+ if (typeof val != "object" || val.whenClosing)
30
+ map["'/'"] = function(cm) { return autoCloseSlash(cm); };
31
+ if (typeof val != "object" || val.whenOpening)
32
+ map["'>'"] = function(cm) { return autoCloseGT(cm); };
33
+ cm.addKeyMap(map);
34
+ } else if (!val && (old != CodeMirror.Init && old)) {
35
+ cm.removeKeyMap("autoCloseTags");
36
+ }
37
+ });
38
+
39
+ var htmlDontClose = ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param",
40
+ "source", "track", "wbr"];
41
+ var htmlIndent = ["applet", "blockquote", "body", "button", "div", "dl", "fieldset", "form", "frameset", "h1", "h2", "h3", "h4",
42
+ "h5", "h6", "head", "html", "iframe", "layer", "legend", "object", "ol", "p", "select", "table", "ul"];
43
+
44
+ function autoCloseGT(cm) {
45
+ var pos = cm.getCursor(), tok = cm.getTokenAt(pos);
46
+ var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state;
47
+ if (inner.mode.name != "xml" || !state.tagName) return CodeMirror.Pass;
48
+
49
+ var opt = cm.getOption("autoCloseTags"), html = inner.mode.configuration == "html";
50
+ var dontCloseTags = (typeof opt == "object" && opt.dontCloseTags) || (html && htmlDontClose);
51
+ var indentTags = (typeof opt == "object" && opt.indentTags) || (html && htmlIndent);
52
+
53
+ var tagName = state.tagName;
54
+ if (tok.end > pos.ch) tagName = tagName.slice(0, tagName.length - tok.end + pos.ch);
55
+ var lowerTagName = tagName.toLowerCase();
56
+ // Don't process the '>' at the end of an end-tag or self-closing tag
57
+ if (tok.type == "tag" && state.type == "closeTag" ||
58
+ tok.string.indexOf("/") == (tok.string.length - 1) || // match something like <someTagName />
59
+ dontCloseTags && indexOf(dontCloseTags, lowerTagName) > -1)
60
+ return CodeMirror.Pass;
61
+
62
+ var doIndent = indentTags && indexOf(indentTags, lowerTagName) > -1;
63
+ var curPos = doIndent ? CodeMirror.Pos(pos.line + 1, 0) : CodeMirror.Pos(pos.line, pos.ch + 1);
64
+ cm.replaceSelection(">" + (doIndent ? "\n\n" : "") + "</" + tagName + ">",
65
+ {head: curPos, anchor: curPos});
66
+ if (doIndent) {
67
+ cm.indentLine(pos.line + 1);
68
+ cm.indentLine(pos.line + 2);
69
+ }
70
+ }
71
+
72
+ function autoCloseSlash(cm) {
73
+ var pos = cm.getCursor(), tok = cm.getTokenAt(pos);
74
+ var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state;
75
+ if (tok.string.charAt(0) != "<" || inner.mode.name != "xml") return CodeMirror.Pass;
76
+
77
+ var tagName = state.context && state.context.tagName;
78
+ if (tagName) cm.replaceSelection("/" + tagName + ">", "end");
79
+ }
80
+
81
+ function indexOf(collection, elt) {
82
+ if (collection.indexOf) return collection.indexOf(elt);
83
+ for (var i = 0, e = collection.length; i < e; ++i)
84
+ if (collection[i] == elt) return i;
85
+ return -1;
86
+ }
87
+ })();
assets/lib/codemirror/addon/edit/continuelist.js ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function() {
2
+ 'use strict';
3
+
4
+ var listRE = /^(\s*)([*+-]|(\d+)\.)(\s*)/,
5
+ unorderedBullets = '*+-';
6
+
7
+ CodeMirror.commands.newlineAndIndentContinueMarkdownList = function(cm) {
8
+ var pos = cm.getCursor(),
9
+ inList = cm.getStateAfter(pos.line).list,
10
+ match;
11
+
12
+ if (!inList || !(match = cm.getLine(pos.line).match(listRE))) {
13
+ cm.execCommand('newlineAndIndent');
14
+ return;
15
+ }
16
+
17
+ var indent = match[1], after = match[4];
18
+ var bullet = unorderedBullets.indexOf(match[2]) >= 0
19
+ ? match[2]
20
+ : (parseInt(match[3], 10) + 1) + '.';
21
+
22
+ cm.replaceSelection('\n' + indent + bullet + after, 'end');
23
+ };
24
+
25
+ }());
assets/lib/codemirror/addon/edit/matchbrackets.js ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function() {
2
+ var ie_lt8 = /MSIE \d/.test(navigator.userAgent) &&
3
+ (document.documentMode == null || document.documentMode < 8);
4
+
5
+ var Pos = CodeMirror.Pos;
6
+
7
+ var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"};
8
+ function findMatchingBracket(cm, where, strict) {
9
+ var state = cm.state.matchBrackets;
10
+ var maxScanLen = (state && state.maxScanLineLength) || 10000;
11
+
12
+ var cur = where || cm.getCursor(), line = cm.getLineHandle(cur.line), pos = cur.ch - 1;
13
+ var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)];
14
+ if (!match) return null;
15
+ var forward = match.charAt(1) == ">", d = forward ? 1 : -1;
16
+ if (strict && forward != (pos == cur.ch)) return null;
17
+ var style = cm.getTokenTypeAt(Pos(cur.line, pos + 1));
18
+
19
+ var stack = [line.text.charAt(pos)], re = /[(){}[\]]/;
20
+ function scan(line, lineNo, start) {
21
+ if (!line.text) return;
22
+ var pos = forward ? 0 : line.text.length - 1, end = forward ? line.text.length : -1;
23
+ if (line.text.length > maxScanLen) return null;
24
+ if (start != null) pos = start + d;
25
+ for (; pos != end; pos += d) {
26
+ var ch = line.text.charAt(pos);
27
+ if (re.test(ch) && cm.getTokenTypeAt(Pos(lineNo, pos + 1)) == style) {
28
+ var match = matching[ch];
29
+ if (match.charAt(1) == ">" == forward) stack.push(ch);
30
+ else if (stack.pop() != match.charAt(0)) return {pos: pos, match: false};
31
+ else if (!stack.length) return {pos: pos, match: true};
32
+ }
33
+ }
34
+ }
35
+ for (var i = cur.line, found, e = forward ? Math.min(i + 100, cm.lineCount()) : Math.max(-1, i - 100); i != e; i+=d) {
36
+ if (i == cur.line) found = scan(line, i, pos);
37
+ else found = scan(cm.getLineHandle(i), i);
38
+ if (found) break;
39
+ }
40
+ return {from: Pos(cur.line, pos), to: found && Pos(i, found.pos),
41
+ match: found && found.match, forward: forward};
42
+ }
43
+
44
+ function matchBrackets(cm, autoclear) {
45
+ // Disable brace matching in long lines, since it'll cause hugely slow updates
46
+ var maxHighlightLen = cm.state.matchBrackets.maxHighlightLineLength || 1000;
47
+ var found = findMatchingBracket(cm);
48
+ if (!found || cm.getLine(found.from.line).length > maxHighlightLen ||
49
+ found.to && cm.getLine(found.to.line).length > maxHighlightLen)
50
+ return;
51
+
52
+ var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
53
+ var one = cm.markText(found.from, Pos(found.from.line, found.from.ch + 1), {className: style});
54
+ var two = found.to && cm.markText(found.to, Pos(found.to.line, found.to.ch + 1), {className: style});
55
+ // Kludge to work around the IE bug from issue #1193, where text
56
+ // input stops going to the textare whever this fires.
57
+ if (ie_lt8 && cm.state.focused) cm.display.input.focus();
58
+ var clear = function() {
59
+ cm.operation(function() { one.clear(); two && two.clear(); });
60
+ };
61
+ if (autoclear) setTimeout(clear, 800);
62
+ else return clear;
63
+ }
64
+
65
+ var currentlyHighlighted = null;
66
+ function doMatchBrackets(cm) {
67
+ cm.operation(function() {
68
+ if (currentlyHighlighted) {currentlyHighlighted(); currentlyHighlighted = null;}
69
+ if (!cm.somethingSelected()) currentlyHighlighted = matchBrackets(cm, false);
70
+ });
71
+ }
72
+
73
+ CodeMirror.defineOption("matchBrackets", false, function(cm, val, old) {
74
+ if (old && old != CodeMirror.Init)
75
+ cm.off("cursorActivity", doMatchBrackets);
76
+ if (val) {
77
+ cm.state.matchBrackets = typeof val == "object" ? val : {};
78
+ cm.on("cursorActivity", doMatchBrackets);
79
+ }
80
+ });
81
+
82
+ CodeMirror.defineExtension("matchBrackets", function() {matchBrackets(this, true);});
83
+ CodeMirror.defineExtension("findMatchingBracket", function(pos, strict){
84
+ return findMatchingBracket(this, pos, strict);
85
+ });
86
+ })();
assets/lib/codemirror/addon/edit/matchtags.js ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function() {
2
+ "use strict";
3
+
4
+ CodeMirror.defineOption("matchTags", false, function(cm, val, old) {
5
+ if (old && old != CodeMirror.Init) {
6
+ cm.off("cursorActivity", doMatchTags);
7
+ cm.off("viewportChange", maybeUpdateMatch);
8
+ clear(cm);
9
+ }
10
+ if (val) {
11
+ cm.state.matchBothTags = typeof val == "object" && val.bothTags;
12
+ cm.on("cursorActivity", doMatchTags);
13
+ cm.on("viewportChange", maybeUpdateMatch);
14
+ doMatchTags(cm);
15
+ }
16
+ });
17
+
18
+ function clear(cm) {
19
+ if (cm.state.tagHit) cm.state.tagHit.clear();
20
+ if (cm.state.tagOther) cm.state.tagOther.clear();
21
+ cm.state.tagHit = cm.state.tagOther = null;
22
+ }
23
+
24
+ function doMatchTags(cm) {
25
+ cm.state.failedTagMatch = false;
26
+ cm.operation(function() {
27
+ clear(cm);
28
+ if (cm.somethingSelected()) return;
29
+ var cur = cm.getCursor(), range = cm.getViewport();
30
+ range.from = Math.min(range.from, cur.line); range.to = Math.max(cur.line + 1, range.to);
31
+ var match = CodeMirror.findMatchingTag(cm, cur, range);
32
+ if (!match) return;
33
+ if (cm.state.matchBothTags) {
34
+ var hit = match.at == "open" ? match.open : match.close;
35
+ if (hit) cm.state.tagHit = cm.markText(hit.from, hit.to, {className: "CodeMirror-matchingtag"});
36
+ }
37
+ var other = match.at == "close" ? match.open : match.close;
38
+ if (other)
39
+ cm.state.tagOther = cm.markText(other.from, other.to, {className: "CodeMirror-matchingtag"});
40
+ else
41
+ cm.state.failedTagMatch = true;
42
+ });
43
+ }
44
+
45
+ function maybeUpdateMatch(cm) {
46
+ if (cm.state.failedTagMatch) doMatchTags(cm);
47
+ }
48
+
49
+ CodeMirror.commands.toMatchingTag = function(cm) {
50
+ var found = CodeMirror.findMatchingTag(cm, cm.getCursor());
51
+ if (found) {
52
+ var other = found.at == "close" ? found.open : found.close;
53
+ if (other) cm.setSelection(other.to, other.from);
54
+ }
55
+ };
56
+ })();
assets/lib/codemirror/addon/edit/trailingspace.js ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ CodeMirror.defineOption("showTrailingSpace", false, function(cm, val, prev) {
2
+ if (prev == CodeMirror.Init) prev = false;
3
+ if (prev && !val)
4
+ cm.removeOverlay("trailingspace");
5
+ else if (!prev && val)
6
+ cm.addOverlay({
7
+ token: function(stream) {
8
+ for (var l = stream.string.length, i = l; i && /\s/.test(stream.string.charAt(i - 1)); --i) {}
9
+ if (i > stream.pos) { stream.pos = i; return null; }
10
+ stream.pos = l;
11
+ return "trailingspace";
12
+ },
13
+ name: "trailingspace"
14
+ });
15
+ });
assets/lib/codemirror/addon/fold/brace-fold.js ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ CodeMirror.registerHelper("fold", "brace", function(cm, start) {
2
+ var line = start.line, lineText = cm.getLine(line);
3
+ var startCh, tokenType;
4
+
5
+ function findOpening(openCh) {
6
+ for (var at = start.ch, pass = 0;;) {
7
+ var found = at <= 0 ? -1 : lineText.lastIndexOf(openCh, at - 1);
8
+ if (found == -1) {
9
+ if (pass == 1) break;
10
+ pass = 1;
11
+ at = lineText.length;
12
+ continue;
13
+ }
14
+ if (pass == 1 && found < start.ch) break;
15
+ tokenType = cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1));
16
+ if (!/^(comment|string)/.test(tokenType)) return found + 1;
17
+ at = found - 1;
18
+ }
19
+ }
20
+
21
+ var startToken = "{", endToken = "}", startCh = findOpening("{");
22
+ if (startCh == null) {
23
+ startToken = "[", endToken = "]";
24
+ startCh = findOpening("[");
25
+ }
26
+
27
+ if (startCh == null) return;
28
+ var count = 1, lastLine = cm.lastLine(), end, endCh;
29
+ outer: for (var i = line; i <= lastLine; ++i) {
30
+ var text = cm.getLine(i), pos = i == line ? startCh : 0;
31
+ for (;;) {
32
+ var nextOpen = text.indexOf(startToken, pos), nextClose = text.indexOf(endToken, pos);
33
+ if (nextOpen < 0) nextOpen = text.length;
34
+ if (nextClose < 0) nextClose = text.length;
35
+ pos = Math.min(nextOpen, nextClose);
36
+ if (pos == text.length) break;
37
+ if (cm.getTokenTypeAt(CodeMirror.Pos(i, pos + 1)) == tokenType) {
38
+ if (pos == nextOpen) ++count;
39
+ else if (!--count) { end = i; endCh = pos; break outer; }
40
+ }
41
+ ++pos;
42
+ }
43
+ }
44
+ if (end == null || line == end && endCh == startCh) return;
45
+ return {from: CodeMirror.Pos(line, startCh),
46
+ to: CodeMirror.Pos(end, endCh)};
47
+ });
48
+ CodeMirror.braceRangeFinder = CodeMirror.fold.brace; // deprecated
49
+
50
+ CodeMirror.registerHelper("fold", "import", function(cm, start) {
51
+ function hasImport(line) {
52
+ if (line < cm.firstLine() || line > cm.lastLine()) return null;
53
+ var start = cm.getTokenAt(CodeMirror.Pos(line, 1));
54
+ if (!/\S/.test(start.string)) start = cm.getTokenAt(CodeMirror.Pos(line, start.end + 1));
55
+ if (start.type != "keyword" || start.string != "import") return null;
56
+ // Now find closing semicolon, return its position
57
+ for (var i = line, e = Math.min(cm.lastLine(), line + 10); i <= e; ++i) {
58
+ var text = cm.getLine(i), semi = text.indexOf(";");
59
+ if (semi != -1) return {startCh: start.end, end: CodeMirror.Pos(i, semi)};
60
+ }
61
+ }
62
+
63
+ var start = start.line, has = hasImport(start), prev;
64
+ if (!has || hasImport(start - 1) || ((prev = hasImport(start - 2)) && prev.end.line == start - 1))
65
+ return null;
66
+ for (var end = has.end;;) {
67
+ var next = hasImport(end.line + 1);
68
+ if (next == null) break;
69
+ end = next.end;
70
+ }
71
+ return {from: cm.clipPos(CodeMirror.Pos(start, has.startCh + 1)), to: end};
72
+ });
73
+ CodeMirror.importRangeFinder = CodeMirror.fold["import"]; // deprecated
74
+
75
+ CodeMirror.registerHelper("fold", "include", function(cm, start) {
76
+ function hasInclude(line) {
77
+ if (line < cm.firstLine() || line > cm.lastLine()) return null;
78
+ var start = cm.getTokenAt(CodeMirror.Pos(line, 1));
79
+ if (!/\S/.test(start.string)) start = cm.getTokenAt(CodeMirror.Pos(line, start.end + 1));
80
+ if (start.type == "meta" && start.string.slice(0, 8) == "#include") return start.start + 8;
81
+ }
82
+
83
+ var start = start.line, has = hasInclude(start);
84
+ if (has == null || hasInclude(start - 1) != null) return null;
85
+ for (var end = start;;) {
86
+ var next = hasInclude(end + 1);
87
+ if (next == null) break;
88
+ ++end;
89
+ }
90
+ return {from: CodeMirror.Pos(start, has + 1),
91
+ to: cm.clipPos(CodeMirror.Pos(end))};
92
+ });
93
+ CodeMirror.includeRangeFinder = CodeMirror.fold.include; // deprecated
assets/lib/codemirror/addon/fold/comment-fold.js ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ CodeMirror.registerHelper("fold", "comment", function(cm, start) {
2
+ var mode = cm.getModeAt(start), startToken = mode.blockCommentStart, endToken = mode.blockCommentEnd;
3
+ if (!startToken || !endToken) return;
4
+ var line = start.line, lineText = cm.getLine(line);
5
+
6
+ var startCh;
7
+ for (var at = start.ch, pass = 0;;) {
8
+ var found = at <= 0 ? -1 : lineText.lastIndexOf(startToken, at - 1);
9
+ if (found == -1) {
10
+ if (pass == 1) return;
11
+ pass = 1;
12
+ at = lineText.length;
13
+ continue;
14
+ }
15
+ if (pass == 1 && found < start.ch) return;
16
+ if (/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1)))) {
17
+ startCh = found + startToken.length;
18
+ break;
19
+ }
20
+ at = found - 1;
21
+ }
22
+
23
+ var depth = 1, lastLine = cm.lastLine(), end, endCh;
24
+ outer: for (var i = line; i <= lastLine; ++i) {
25
+ var text = cm.getLine(i), pos = i == line ? startCh : 0;
26
+ for (;;) {
27
+ var nextOpen = text.indexOf(startToken, pos), nextClose = text.indexOf(endToken, pos);
28
+ if (nextOpen < 0) nextOpen = text.length;
29
+ if (nextClose < 0) nextClose = text.length;
30
+ pos = Math.min(nextOpen, nextClose);
31
+ if (pos == text.length) break;
32
+ if (pos == nextOpen) ++depth;
33
+ else if (!--depth) { end = i; endCh = pos; break outer; }
34
+ ++pos;
35
+ }
36
+ }
37
+ if (end == null || line == end && endCh == startCh) return;
38
+ return {from: CodeMirror.Pos(line, startCh),
39
+ to: CodeMirror.Pos(end, endCh)};
40
+ });
assets/lib/codemirror/addon/fold/foldcode.js ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function() {
2
+ "use strict";
3
+
4
+ function doFold(cm, pos, options) {
5
+ var finder = options && (options.call ? options : options.rangeFinder);
6
+ if (!finder) finder = cm.getHelper(pos, "fold");
7
+ if (!finder) return;
8
+ if (typeof pos == "number") pos = CodeMirror.Pos(pos, 0);
9
+ var minSize = options && options.minFoldSize || 0;
10
+
11
+ function getRange(allowFolded) {
12
+ var range = finder(cm, pos);
13
+ if (!range || range.to.line - range.from.line < minSize) return null;
14
+ var marks = cm.findMarksAt(range.from);
15
+ for (var i = 0; i < marks.length; ++i) {
16
+ if (marks[i].__isFold) {
17
+ if (!allowFolded) return null;
18
+ range.cleared = true;
19
+ marks[i].clear();
20
+ }
21
+ }
22
+ return range;
23
+ }
24
+
25
+ var range = getRange(true);
26
+ if (options && options.scanUp) while (!range && pos.line > cm.firstLine()) {
27
+ pos = CodeMirror.Pos(pos.line - 1, 0);
28
+ range = getRange(false);
29
+ }
30
+ if (!range || range.cleared) return;
31
+
32
+ var myWidget = makeWidget(options);
33
+ CodeMirror.on(myWidget, "mousedown", function() { myRange.clear(); });
34
+ var myRange = cm.markText(range.from, range.to, {
35
+ replacedWith: myWidget,
36
+ clearOnEnter: true,
37
+ __isFold: true
38
+ });
39
+ myRange.on("clear", function(from, to) {
40
+ CodeMirror.signal(cm, "unfold", cm, from, to);
41
+ });
42
+ CodeMirror.signal(cm, "fold", cm, range.from, range.to);
43
+ }
44
+
45
+ function makeWidget(options) {
46
+ var widget = (options && options.widget) || "\u2194";
47
+ if (typeof widget == "string") {
48
+ var text = document.createTextNode(widget);
49
+ widget = document.createElement("span");
50
+ widget.appendChild(text);
51
+ widget.className = "CodeMirror-foldmarker";
52
+ }
53
+ return widget;
54
+ }
55
+
56
+ // Clumsy backwards-compatible interface
57
+ CodeMirror.newFoldFunction = function(rangeFinder, widget) {
58
+ return function(cm, pos) { doFold(cm, pos, {rangeFinder: rangeFinder, widget: widget}); };
59
+ };
60
+
61
+ // New-style interface
62
+ CodeMirror.defineExtension("foldCode", function(pos, options) { doFold(this, pos, options); });
63
+
64
+ CodeMirror.registerHelper("fold", "combine", function() {
65
+ var funcs = Array.prototype.slice.call(arguments, 0);
66
+ return function(cm, start) {
67
+ for (var i = 0; i < funcs.length; ++i) {
68
+ var found = funcs[i](cm, start);
69
+ if (found) return found;
70
+ }
71
+ };
72
+ });
73
+ })();
assets/lib/codemirror/addon/fold/foldgutter.js ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function() {
2
+ "use strict";
3
+
4
+ CodeMirror.defineOption("foldGutter", false, function(cm, val, old) {
5
+ if (old && old != CodeMirror.Init) {
6
+ cm.clearGutter(cm.state.foldGutter.options.gutter);
7
+ cm.state.foldGutter = null;
8
+ cm.off("gutterClick", onGutterClick);
9
+ cm.off("change", onChange);
10
+ cm.off("viewportChange", onViewportChange);
11
+ cm.off("fold", onFold);
12
+ cm.off("unfold", onFold);
13
+ }
14
+ if (val) {
15
+ cm.state.foldGutter = new State(parseOptions(val));
16
+ updateInViewport(cm);
17
+ cm.on("gutterClick", onGutterClick);
18
+ cm.on("change", onChange);
19
+ cm.on("viewportChange", onViewportChange);
20
+ cm.on("fold", onFold);
21
+ cm.on("unfold", onFold);
22
+ }
23
+ });
24
+
25
+ var Pos = CodeMirror.Pos;
26
+
27
+ function State(options) {
28
+ this.options = options;
29
+ this.from = this.to = 0;
30
+ }
31
+
32
+ function parseOptions(opts) {
33
+ if (opts === true) opts = {};
34
+ if (opts.gutter == null) opts.gutter = "CodeMirror-foldgutter";
35
+ if (opts.indicatorOpen == null) opts.indicatorOpen = "CodeMirror-foldgutter-open";
36
+ if (opts.indicatorFolded == null) opts.indicatorFolded = "CodeMirror-foldgutter-folded";
37
+ return opts;
38
+ }
39
+
40
+ function isFolded(cm, line) {
41
+ var marks = cm.findMarksAt(Pos(line));
42
+ for (var i = 0; i < marks.length; ++i)
43
+ if (marks[i].__isFold && marks[i].find().from.line == line) return true;
44
+ }
45
+
46
+ function marker(spec) {
47
+ if (typeof spec == "string") {
48
+ var elt = document.createElement("div");
49
+ elt.className = spec;
50
+ return elt;
51
+ } else {
52
+ return spec.cloneNode(true);
53
+ }
54
+ }
55
+
56
+ function updateFoldInfo(cm, from, to) {
57
+ var opts = cm.state.foldGutter.options, cur = from;
58
+ cm.eachLine(from, to, function(line) {
59
+ var mark = null;
60
+ if (isFolded(cm, cur)) {
61
+ mark = marker(opts.indicatorFolded);
62
+ } else {
63
+ var pos = Pos(cur, 0), func = opts.rangeFinder || cm.getHelper(pos, "fold");
64
+ var range = func && func(cm, pos);
65
+ if (range && range.from.line + 1 < range.to.line)
66
+ mark = marker(opts.indicatorOpen);
67
+ }
68
+ cm.setGutterMarker(line, opts.gutter, mark);
69
+ ++cur;
70
+ });
71
+ }
72
+
73
+ function updateInViewport(cm) {
74
+ var vp = cm.getViewport(), state = cm.state.foldGutter;
75
+ if (!state) return;
76
+ cm.operation(function() {
77
+ updateFoldInfo(cm, vp.from, vp.to);
78
+ });
79
+ state.from = vp.from; state.to = vp.to;
80
+ }
81
+
82
+ function onGutterClick(cm, line, gutter) {
83
+ var opts = cm.state.foldGutter.options;
84
+ if (gutter != opts.gutter) return;
85
+ cm.foldCode(Pos(line, 0), opts.rangeFinder);
86
+ }
87
+
88
+ function onChange(cm) {
89
+ var state = cm.state.foldGutter;
90
+ state.from = state.to = 0;
91
+ clearTimeout(state.changeUpdate);
92
+ state.changeUpdate = setTimeout(function() { updateInViewport(cm); }, 600);
93
+ }
94
+
95
+ function onViewportChange(cm) {
96
+ var state = cm.state.foldGutter;
97
+ clearTimeout(state.changeUpdate);
98
+ state.changeUpdate = setTimeout(function() {
99
+ var vp = cm.getViewport();
100
+ if (state.from == state.to || vp.from - state.to > 20 || state.from - vp.to > 20) {
101
+ updateInViewport(cm);
102
+ } else {
103
+ cm.operation(function() {
104
+ if (vp.from < state.from) {
105
+ updateFoldInfo(cm, vp.from, state.from);
106
+ state.from = vp.from;
107
+ }
108
+ if (vp.to > state.to) {
109
+ updateFoldInfo(cm, state.to, vp.to);
110
+ state.to = vp.to;
111
+ }
112
+ });
113
+ }
114
+ }, 400);
115
+ }
116
+
117
+ function onFold(cm, from) {
118
+ var state = cm.state.foldGutter, line = from.line;
119
+ if (line >= state.from && line < state.to)
120
+ updateFoldInfo(cm, line, line + 1);
121
+ }
122
+ })();
assets/lib/codemirror/addon/fold/indent-fold.js ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ CodeMirror.registerHelper("fold", "indent", function(cm, start) {
2
+ var tabSize = cm.getOption("tabSize"), firstLine = cm.getLine(start.line);
3
+ var myIndent = CodeMirror.countColumn(firstLine, null, tabSize);
4
+ for (var i = start.line + 1, end = cm.lineCount(); i < end; ++i) {
5
+ var curLine = cm.getLine(i);
6
+ if (CodeMirror.countColumn(curLine, null, tabSize) < myIndent &&
7
+ CodeMirror.countColumn(cm.getLine(i-1), null, tabSize) > myIndent)
8
+ return {from: CodeMirror.Pos(start.line, firstLine.length),
9
+ to: CodeMirror.Pos(i, curLine.length)};
10
+ }
11
+ });
12
+ CodeMirror.indentRangeFinder = CodeMirror.fold.indent; // deprecated
assets/lib/codemirror/addon/fold/xml-fold.js ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function() {
2
+ "use strict";
3
+
4
+ var Pos = CodeMirror.Pos;
5
+ function cmp(a, b) { return a.line - b.line || a.ch - b.ch; }
6
+
7
+ var nameStartChar = "A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD";
8
+ var nameChar = nameStartChar + "\-\:\.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040";
9
+ var xmlTagStart = new RegExp("<(/?)([" + nameStartChar + "][" + nameChar + "]*)", "g");
10
+
11
+ function Iter(cm, line, ch, range) {
12
+ this.line = line; this.ch = ch;
13
+ this.cm = cm; this.text = cm.getLine(line);
14
+ this.min = range ? range.from : cm.firstLine();
15
+ this.max = range ? range.to - 1 : cm.lastLine();
16
+ }
17
+
18
+ function tagAt(iter, ch) {
19
+ var type = iter.cm.getTokenTypeAt(Pos(iter.line, ch));
20
+ return type && /\btag\b/.test(type);
21
+ }
22
+
23
+ function nextLine(iter) {
24
+ if (iter.line >= iter.max) return;
25
+ iter.ch = 0;
26
+ iter.text = iter.cm.getLine(++iter.line);
27
+ return true;
28
+ }
29
+ function prevLine(iter) {
30
+ if (iter.line <= iter.min) return;
31
+ iter.text = iter.cm.getLine(--iter.line);
32
+ iter.ch = iter.text.length;
33
+ return true;
34
+ }
35
+
36
+ function toTagEnd(iter) {
37
+ for (;;) {
38
+ var gt = iter.text.indexOf(">", iter.ch);
39
+ if (gt == -1) { if (nextLine(iter)) continue; else return; }
40
+ if (!tagAt(iter, gt + 1)) { iter.ch = gt + 1; continue; }
41
+ var lastSlash = iter.text.lastIndexOf("/", gt);
42
+ var selfClose = lastSlash > -1 && !/\S/.test(iter.text.slice(lastSlash + 1, gt));
43
+ iter.ch = gt + 1;
44
+ return selfClose ? "selfClose" : "regular";
45
+ }
46
+ }
47
+ function toTagStart(iter) {
48
+ for (;;) {
49
+ var lt = iter.ch ? iter.text.lastIndexOf("<", iter.ch - 1) : -1;
50
+ if (lt == -1) { if (prevLine(iter)) continue; else return; }
51
+ if (!tagAt(iter, lt + 1)) { iter.ch = lt; continue; }
52
+ xmlTagStart.lastIndex = lt;
53
+ iter.ch = lt;
54
+ var match = xmlTagStart.exec(iter.text);
55
+ if (match && match.index == lt) return match;
56
+ }
57
+ }
58
+
59
+ function toNextTag(iter) {
60
+ for (;;) {
61
+ xmlTagStart.lastIndex = iter.ch;
62
+ var found = xmlTagStart.exec(iter.text);
63
+ if (!found) { if (nextLine(iter)) continue; else return; }
64
+ if (!tagAt(iter, found.index + 1)) { iter.ch = found.index + 1; continue; }
65
+ iter.ch = found.index + found[0].length;
66
+ return found;
67
+ }
68
+ }
69
+ function toPrevTag(iter) {
70
+ for (;;) {
71
+ var gt = iter.ch ? iter.text.lastIndexOf(">", iter.ch - 1) : -1;
72
+ if (gt == -1) { if (prevLine(iter)) continue; else return; }
73
+ if (!tagAt(iter, gt + 1)) { iter.ch = gt; continue; }
74
+ var lastSlash = iter.text.lastIndexOf("/", gt);
75
+ var selfClose = lastSlash > -1 && !/\S/.test(iter.text.slice(lastSlash + 1, gt));
76
+ iter.ch = gt + 1;
77
+ return selfClose ? "selfClose" : "regular";
78
+ }
79
+ }
80
+
81
+ function findMatchingClose(iter, tag) {
82
+ var stack = [];
83
+ for (;;) {
84
+ var next = toNextTag(iter), end, startLine = iter.line, startCh = iter.ch - (next ? next[0].length : 0);
85
+ if (!next || !(end = toTagEnd(iter))) return;
86
+ if (end == "selfClose") continue;
87
+ if (next[1]) { // closing tag
88
+ for (var i = stack.length - 1; i >= 0; --i) if (stack[i] == next[2]) {
89
+ stack.length = i;
90
+ break;
91
+ }
92
+ if (i < 0 && (!tag || tag == next[2])) return {
93
+ tag: next[2],
94
+ from: Pos(startLine, startCh),
95
+ to: Pos(iter.line, iter.ch)
96
+ };
97
+ } else { // opening tag
98
+ stack.push(next[2]);
99
+ }
100
+ }
101
+ }
102
+ function findMatchingOpen(iter, tag) {
103
+ var stack = [];
104
+ for (;;) {
105
+ var prev = toPrevTag(iter);
106
+ if (!prev) return;
107
+ if (prev == "selfClose") { toTagStart(iter); continue; }
108
+ var endLine = iter.line, endCh = iter.ch;
109
+ var start = toTagStart(iter);
110
+ if (!start) return;
111
+ if (start[1]) { // closing tag
112
+ stack.push(start[2]);
113
+ } else { // opening tag
114
+ for (var i = stack.length - 1; i >= 0; --i) if (stack[i] == start[2]) {
115
+ stack.length = i;
116
+ break;
117
+ }
118
+ if (i < 0 && (!tag || tag == start[2])) return {
119
+ tag: start[2],
120
+ from: Pos(iter.line, iter.ch),
121
+ to: Pos(endLine, endCh)
122
+ };
123
+ }
124
+ }
125
+ }
126
+
127
+ CodeMirror.registerHelper("fold", "xml", function(cm, start) {
128
+ var iter = new Iter(cm, start.line, 0);
129
+ for (;;) {
130
+ var openTag = toNextTag(iter), end;
131
+ if (!openTag || iter.line != start.line || !(end = toTagEnd(iter))) return;
132
+ if (!openTag[1] && end != "selfClose") {
133
+ var start = Pos(iter.line, iter.ch);
134
+ var close = findMatchingClose(iter, openTag[2]);
135
+ return close && {from: start, to: close.from};
136
+ }
137
+ }
138
+ });
139
+ CodeMirror.tagRangeFinder = CodeMirror.fold.xml; // deprecated
140
+
141
+ CodeMirror.findMatchingTag = function(cm, pos, range) {
142
+ var iter = new Iter(cm, pos.line, pos.ch, range);
143
+ if (iter.text.indexOf(">") == -1 && iter.text.indexOf("<") == -1) return;
144
+ var end = toTagEnd(iter), to = end && Pos(iter.line, iter.ch);
145
+ var start = end && toTagStart(iter);
146
+ if (!end || end == "selfClose" || !start || cmp(iter, pos) > 0) return;
147
+ var here = {from: Pos(iter.line, iter.ch), to: to, tag: start[2]};
148
+
149
+ if (start[1]) { // closing tag
150
+ return {open: findMatchingOpen(iter, start[2]), close: here, at: "close"};
151
+ } else { // opening tag
152
+ iter = new Iter(cm, to.line, to.ch, range);
153
+ return {open: here, close: findMatchingClose(iter, start[2]), at: "open"};
154
+ }
155
+ };
156
+
157
+ CodeMirror.findEnclosingTag = function(cm, pos, range) {
158
+ var iter = new Iter(cm, pos.line, pos.ch, range);
159
+ for (;;) {
160
+ var open = findMatchingOpen(iter);
161
+ if (!open) break;
162
+ var forward = new Iter(cm, pos.line, pos.ch, range);
163
+ var close = findMatchingClose(forward, open.tag);
164
+ if (close) return {open: open, close: close};
165
+ }
166
+ };
167
+ })();
assets/lib/codemirror/addon/hint/anyword-hint.js ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function() {
2
+ "use strict";
3
+
4
+ var WORD = /[\w$]+/, RANGE = 500;
5
+
6
+ CodeMirror.registerHelper("hint", "anyword", function(editor, options) {
7
+ var word = options && options.word || WORD;
8
+ var range = options && options.range || RANGE;
9
+ var cur = editor.getCursor(), curLine = editor.getLine(cur.line);
10
+ var start = cur.ch, end = start;
11
+ while (end < curLine.length && word.test(curLine.charAt(end))) ++end;
12
+ while (start && word.test(curLine.charAt(start - 1))) --start;
13
+ var curWord = start != end && curLine.slice(start, end);
14
+
15
+ var list = [], seen = {};
16
+ function scan(dir) {
17
+ var line = cur.line, end = Math.min(Math.max(line + dir * range, editor.firstLine()), editor.lastLine()) + dir;
18
+ for (; line != end; line += dir) {
19
+ var text = editor.getLine(line), m;
20
+ var re = new RegExp(word.source, "g");
21
+ while (m = re.exec(text)) {
22
+ if (line == cur.line && m[0] === curWord) continue;
23
+ if ((!curWord || m[0].indexOf(curWord) == 0) && !seen.hasOwnProperty(m[0])) {
24
+ seen[m[0]] = true;
25
+ list.push(m[0]);
26
+ }
27
+ }
28
+ }
29
+ }
30
+ scan(-1);
31
+ scan(1);
32
+ return {list: list, from: CodeMirror.Pos(cur.line, start), to: CodeMirror.Pos(cur.line, end)};
33
+ });
34
+ })();
assets/lib/codemirror/addon/hint/css-hint.js ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function () {
2
+ "use strict";
3
+
4
+ function getHints(cm) {
5
+ var cur = cm.getCursor(), token = cm.getTokenAt(cur);
6
+ var inner = CodeMirror.innerMode(cm.getMode(), token.state);
7
+ if (inner.mode.name != "css") return;
8
+
9
+ // If it's not a 'word-style' token, ignore the token.
10
+ if (!/^[\w$_-]*$/.test(token.string)) {
11
+ token = {
12
+ start: cur.ch, end: cur.ch, string: "", state: token.state,
13
+ type: null
14
+ };
15
+ var stack = token.state.stack;
16
+ var lastToken = stack && stack.length > 0 ? stack[stack.length - 1] : "";
17
+ if (token.string == ":" || lastToken.indexOf("property") == 0)
18
+ token.type = "variable";
19
+ else if (token.string == "{" || lastToken.indexOf("rule") == 0)
20
+ token.type = "property";
21
+ }
22
+
23
+ if (!token.type)
24
+ return;
25
+
26
+ var spec = CodeMirror.resolveMode("text/css");
27
+ var keywords = null;
28
+ if (token.type.indexOf("property") == 0)
29
+ keywords = spec.propertyKeywords;
30
+ else if (token.type.indexOf("variable") == 0)
31
+ keywords = spec.valueKeywords;
32
+
33
+ if (!keywords)
34
+ return;
35
+
36
+ var result = [];
37
+ for (var name in keywords) {
38
+ if (name.indexOf(token.string) == 0 /* > -1 */)
39
+ result.push(name);
40
+ }
41
+
42
+ return {
43
+ list: result,
44
+ from: CodeMirror.Pos(cur.line, token.start),
45
+ to: CodeMirror.Pos(cur.line, token.end)
46
+ };
47
+ }
48
+
49
+ CodeMirror.registerHelper("hint", "css", getHints);
50
+ })();
assets/lib/codemirror/addon/hint/html-hint.js ADDED
@@ -0,0 +1,337 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function () {
2
+ var langs = "ab aa af ak sq am ar an hy as av ae ay az bm ba eu be bn bh bi bs br bg my ca ch ce ny zh cv kw co cr hr cs da dv nl dz en eo et ee fo fj fi fr ff gl ka de el gn gu ht ha he hz hi ho hu ia id ie ga ig ik io is it iu ja jv kl kn kr ks kk km ki rw ky kv kg ko ku kj la lb lg li ln lo lt lu lv gv mk mg ms ml mt mi mr mh mn na nv nb nd ne ng nn no ii nr oc oj cu om or os pa pi fa pl ps pt qu rm rn ro ru sa sc sd se sm sg sr gd sn si sk sl so st es su sw ss sv ta te tg th ti bo tk tl tn to tr ts tt tw ty ug uk ur uz ve vi vo wa cy wo fy xh yi yo za zu".split(" ");
3
+ var targets = ["_blank", "_self", "_top", "_parent"];
4
+ var charsets = ["ascii", "utf-8", "utf-16", "latin1", "latin1"];
5
+ var methods = ["get", "post", "put", "delete"];
6
+ var encs = ["application/x-www-form-urlencoded", "multipart/form-data", "text/plain"];
7
+ var media = ["all", "screen", "print", "embossed", "braille", "handheld", "print", "projection", "screen", "tty", "tv", "speech",
8
+ "3d-glasses", "resolution [>][<][=] [X]", "device-aspect-ratio: X/Y", "orientation:portrait",
9
+ "orientation:landscape", "device-height: [X]", "device-width: [X]"];
10
+ var s = { attrs: {} }; // Simple tag, reused for a whole lot of tags
11
+
12
+ var data = {
13
+ a: {
14
+ attrs: {
15
+ href: null, ping: null, type: null,
16
+ media: media,
17
+ target: targets,
18
+ hreflang: langs
19
+ }
20
+ },
21
+ abbr: s,
22
+ acronym: s,
23
+ address: s,
24
+ applet: s,
25
+ area: {
26
+ attrs: {
27
+ alt: null, coords: null, href: null, target: null, ping: null,
28
+ media: media, hreflang: langs, type: null,
29
+ shape: ["default", "rect", "circle", "poly"]
30
+ }
31
+ },
32
+ article: s,
33
+ aside: s,
34
+ audio: {
35
+ attrs: {
36
+ src: null, mediagroup: null,
37
+ crossorigin: ["anonymous", "use-credentials"],
38
+ preload: ["none", "metadata", "auto"],
39
+ autoplay: ["", "autoplay"],
40
+ loop: ["", "loop"],
41
+ controls: ["", "controls"]
42
+ }
43
+ },
44
+ b: s,
45
+ base: { attrs: { href: null, target: targets } },
46
+ basefont: s,
47
+ bdi: s,
48
+ bdo: s,
49
+ big: s,
50
+ blockquote: { attrs: { cite: null } },
51
+ body: s,
52
+ br: s,
53
+ button: {
54
+ attrs: {
55
+ form: null, formaction: null, name: null, value: null,
56
+ autofocus: ["", "autofocus"],
57
+ disabled: ["", "autofocus"],
58
+ formenctype: encs,
59
+ formmethod: methods,
60
+ formnovalidate: ["", "novalidate"],
61
+ formtarget: targets,
62
+ type: ["submit", "reset", "button"]
63
+ }
64
+ },
65
+ canvas: { attrs: { width: null, height: null } },
66
+ caption: s,
67
+ center: s,
68
+ cite: s,
69
+ code: s,
70
+ col: { attrs: { span: null } },
71
+ colgroup: { attrs: { span: null } },
72
+ command: {
73
+ attrs: {
74
+ type: ["command", "checkbox", "radio"],
75
+ label: null, icon: null, radiogroup: null, command: null, title: null,
76
+ disabled: ["", "disabled"],
77
+ checked: ["", "checked"]
78
+ }
79
+ },
80
+ data: { attrs: { value: null } },
81
+ datagrid: { attrs: { disabled: ["", "disabled"], multiple: ["", "multiple"] } },
82
+ datalist: { attrs: { data: null } },
83
+ dd: s,
84
+ del: { attrs: { cite: null, datetime: null } },
85
+ details: { attrs: { open: ["", "open"] } },
86
+ dfn: s,
87
+ dir: s,
88
+ div: s,
89
+ dl: s,
90
+ dt: s,
91
+ em: s,
92
+ embed: { attrs: { src: null, type: null, width: null, height: null } },
93
+ eventsource: { attrs: { src: null } },
94
+ fieldset: { attrs: { disabled: ["", "disabled"], form: null, name: null } },
95
+ figcaption: s,
96
+ figure: s,
97
+ font: s,
98
+ footer: s,
99
+ form: {
100
+ attrs: {
101
+ action: null, name: null,
102
+ "accept-charset": charsets,
103
+ autocomplete: ["on", "off"],
104
+ enctype: encs,
105
+ method: methods,
106
+ novalidate: ["", "novalidate"],
107
+ target: targets
108
+ }
109
+ },
110
+ frame: s,
111
+ frameset: s,
112
+ h1: s, h2: s, h3: s, h4: s, h5: s, h6: s,
113
+ head: {
114
+ attrs: {},
115
+ children: ["title", "base", "link", "style", "meta", "script", "noscript", "command"]
116
+ },
117
+ header: s,
118
+ hgroup: s,
119
+ hr: s,
120
+ html: {
121
+ attrs: { manifest: null },
122
+ children: ["head", "body"]
123
+ },
124
+ i: s,
125
+ iframe: {
126
+ attrs: {
127
+ src: null, srcdoc: null, name: null, width: null, height: null,
128
+ sandbox: ["allow-top-navigation", "allow-same-origin", "allow-forms", "allow-scripts"],
129
+ seamless: ["", "seamless"]
130
+ }
131
+ },
132
+ img: {
133
+ attrs: {
134
+ alt: null, src: null, ismap: null, usemap: null, width: null, height: null,
135
+ crossorigin: ["anonymous", "use-credentials"]
136
+ }
137
+ },
138
+ input: {
139
+ attrs: {
140
+ alt: null, dirname: null, form: null, formaction: null,
141
+ height: null, list: null, max: null, maxlength: null, min: null,
142
+ name: null, pattern: null, placeholder: null, size: null, src: null,
143
+ step: null, value: null, width: null,
144
+ accept: ["audio/*", "video/*", "image/*"],
145
+ autocomplete: ["on", "off"],
146
+ autofocus: ["", "autofocus"],
147
+ checked: ["", "checked"],
148
+ disabled: ["", "disabled"],
149
+ formenctype: encs,
150
+ formmethod: methods,
151
+ formnovalidate: ["", "novalidate"],
152
+ formtarget: targets,
153
+ multiple: ["", "multiple"],
154
+ readonly: ["", "readonly"],
155
+ required: ["", "required"],
156
+ type: ["hidden", "text", "search", "tel", "url", "email", "password", "datetime", "date", "month",
157
+ "week", "time", "datetime-local", "number", "range", "color", "checkbox", "radio",
158
+ "file", "submit", "image", "reset", "button"]
159
+ }
160
+ },
161
+ ins: { attrs: { cite: null, datetime: null } },
162
+ kbd: s,
163
+ keygen: {
164
+ attrs: {
165
+ challenge: null, form: null, name: null,
166
+ autofocus: ["", "autofocus"],
167
+ disabled: ["", "disabled"],
168
+ keytype: ["RSA"]
169
+ }
170
+ },
171
+ label: { attrs: { "for": null, form: null } },
172
+ legend: s,
173
+ li: { attrs: { value: null } },
174
+ link: {
175
+ attrs: {
176
+ href: null, type: null,
177
+ hreflang: langs,
178
+ media: media,
179
+ sizes: ["all", "16x16", "16x16 32x32", "16x16 32x32 64x64"]
180
+ }
181
+ },
182
+ map: { attrs: { name: null } },
183
+ mark: s,
184
+ menu: { attrs: { label: null, type: ["list", "context", "toolbar"] } },
185
+ meta: {
186
+ attrs: {
187
+ content: null,
188
+ charset: charsets,
189
+ name: ["viewport", "application-name", "author", "description", "generator", "keywords"],
190
+ "http-equiv": ["content-language", "content-type", "default-style", "refresh"]
191
+ }
192
+ },
193
+ meter: { attrs: { value: null, min: null, low: null, high: null, max: null, optimum: null } },
194
+ nav: s,
195
+ noframes: s,
196
+ noscript: s,
197
+ object: {
198
+ attrs: {
199
+ data: null, type: null, name: null, usemap: null, form: null, width: null, height: null,
200
+ typemustmatch: ["", "typemustmatch"]
201
+ }
202
+ },
203
+ ol: { attrs: { reversed: ["", "reversed"], start: null, type: ["1", "a", "A", "i", "I"] } },
204
+ optgroup: { attrs: { disabled: ["", "disabled"], label: null } },
205
+ option: { attrs: { disabled: ["", "disabled"], label: null, selected: ["", "selected"], value: null } },
206
+ output: { attrs: { "for": null, form: null, name: null } },
207
+ p: s,
208
+ param: { attrs: { name: null, value: null } },
209
+ pre: s,
210
+ progress: { attrs: { value: null, max: null } },
211
+ q: { attrs: { cite: null } },
212
+ rp: s,
213
+ rt: s,
214
+ ruby: s,
215
+ s: s,
216
+ samp: s,
217
+ script: {
218
+ attrs: {
219
+ type: ["text/javascript"],
220
+ src: null,
221
+ async: ["", "async"],
222
+ defer: ["", "defer"],
223
+ charset: charsets
224
+ }
225
+ },
226
+ section: s,
227
+ select: {
228
+ attrs: {
229
+ form: null, name: null, size: null,
230
+ autofocus: ["", "autofocus"],
231
+ disabled: ["", "disabled"],
232
+ multiple: ["", "multiple"]
233
+ }
234
+ },
235
+ small: s,
236
+ source: { attrs: { src: null, type: null, media: null } },
237
+ span: s,
238
+ strike: s,
239
+ strong: s,
240
+ style: {
241
+ attrs: {
242
+ type: ["text/css"],
243
+ media: media,
244
+ scoped: null
245
+ }
246
+ },
247
+ sub: s,
248
+ summary: s,
249
+ sup: s,
250
+ table: s,
251
+ tbody: s,
252
+ td: { attrs: { colspan: null, rowspan: null, headers: null } },
253
+ textarea: {
254
+ attrs: {
255
+ dirname: null, form: null, maxlength: null, name: null, placeholder: null,
256
+ rows: null, cols: null,
257
+ autofocus: ["", "autofocus"],
258
+ disabled: ["", "disabled"],
259
+ readonly: ["", "readonly"],
260
+ required: ["", "required"],
261
+ wrap: ["soft", "hard"]
262
+ }
263
+ },
264
+ tfoot: s,
265
+ th: { attrs: { colspan: null, rowspan: null, headers: null, scope: ["row", "col", "rowgroup", "colgroup"] } },
266
+ thead: s,
267
+ time: { attrs: { datetime: null } },
268
+ title: s,
269
+ tr: s,
270
+ track: {
271
+ attrs: {
272
+ src: null, label: null, "default": null,
273
+ kind: ["subtitles", "captions", "descriptions", "chapters", "metadata"],
274
+ srclang: langs
275
+ }
276
+ },
277
+ tt: s,
278
+ u: s,
279
+ ul: s,
280
+ "var": s,
281
+ video: {
282
+ attrs: {
283
+ src: null, poster: null, width: null, height: null,
284
+ crossorigin: ["anonymous", "use-credentials"],
285
+ preload: ["auto", "metadata", "none"],
286
+ autoplay: ["", "autoplay"],
287
+ mediagroup: ["movie"],
288
+ muted: ["", "muted"],
289
+ controls: ["", "controls"]
290
+ }
291
+ },
292
+ wbr: s
293
+ };
294
+
295
+ var globalAttrs = {
296
+ accesskey: ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"],
297
+ "class": null,
298
+ contenteditable: ["true", "false"],
299
+ contextmenu: null,
300
+ dir: ["ltr", "rtl", "auto"],
301
+ draggable: ["true", "false", "auto"],
302
+ dropzone: ["copy", "move", "link", "string:", "file:"],
303
+ hidden: ["hidden"],
304
+ id: null,
305
+ inert: ["inert"],
306
+ itemid: null,
307
+ itemprop: null,
308
+ itemref: null,
309
+ itemscope: ["itemscope"],
310
+ itemtype: null,
311
+ lang: ["en", "es"],
312
+ spellcheck: ["true", "false"],
313
+ style: null,
314
+ tabindex: ["1", "2", "3", "4", "5", "6", "7", "8", "9"],
315
+ title: null,
316
+ translate: ["yes", "no"],
317
+ onclick: null,
318
+ rel: ["stylesheet", "alternate", "author", "bookmark", "help", "license", "next", "nofollow", "noreferrer", "prefetch", "prev", "search", "tag"]
319
+ };
320
+ function populate(obj) {
321
+ for (var attr in globalAttrs) if (globalAttrs.hasOwnProperty(attr))
322
+ obj.attrs[attr] = globalAttrs[attr];
323
+ }
324
+
325
+ populate(s);
326
+ for (var tag in data) if (data.hasOwnProperty(tag) && data[tag] != s)
327
+ populate(data[tag]);
328
+
329
+ CodeMirror.htmlSchema = data;
330
+ function htmlHint(cm, options) {
331
+ var local = {schemaInfo: data};
332
+ if (options) for (var opt in options) local[opt] = options[opt];
333
+ return CodeMirror.hint.xml(cm, local);
334
+ }
335
+ CodeMirror.htmlHint = htmlHint; // deprecated
336
+ CodeMirror.registerHelper("hint", "html", htmlHint);
337
+ })();
assets/lib/codemirror/addon/hint/javascript-hint.js ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function () {
2
+ var Pos = CodeMirror.Pos;
3
+
4
+ function forEach(arr, f) {
5
+ for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
6
+ }
7
+
8
+ function arrayContains(arr, item) {
9
+ if (!Array.prototype.indexOf) {
10
+ var i = arr.length;
11
+ while (i--) {
12
+ if (arr[i] === item) {
13
+ return true;
14
+ }
15
+ }
16
+ return false;
17
+ }
18
+ return arr.indexOf(item) != -1;
19
+ }
20
+
21
+ function scriptHint(editor, keywords, getToken, options) {
22
+ // Find the token at the cursor
23
+ var cur = editor.getCursor(), token = getToken(editor, cur), tprop = token;
24
+ token.state = CodeMirror.innerMode(editor.getMode(), token.state).state;
25
+
26
+ // If it's not a 'word-style' token, ignore the token.
27
+ if (!/^[\w$_]*$/.test(token.string)) {
28
+ token = tprop = {start: cur.ch, end: cur.ch, string: "", state: token.state,
29
+ type: token.string == "." ? "property" : null};
30
+ }
31
+ // If it is a property, find out what it is a property of.
32
+ while (tprop.type == "property") {
33
+ tprop = getToken(editor, Pos(cur.line, tprop.start));
34
+ if (tprop.string != ".") return;
35
+ tprop = getToken(editor, Pos(cur.line, tprop.start));
36
+ if (tprop.string == ')') {
37
+ var level = 1;
38
+ do {
39
+ tprop = getToken(editor, Pos(cur.line, tprop.start));
40
+ switch (tprop.string) {
41
+ case ')': level++; break;
42
+ case '(': level--; break;
43
+ default: break;
44
+ }
45
+ } while (level > 0);
46
+ tprop = getToken(editor, Pos(cur.line, tprop.start));
47
+ if (tprop.type.indexOf("variable") === 0)
48
+ tprop.type = "function";
49
+ else return; // no clue
50
+ }
51
+ if (!context) var context = [];
52
+ context.push(tprop);
53
+ }
54
+ return {list: getCompletions(token, context, keywords, options),
55
+ from: Pos(cur.line, token.start),
56
+ to: Pos(cur.line, token.end)};
57
+ }
58
+
59
+ function javascriptHint(editor, options) {
60
+ return scriptHint(editor, javascriptKeywords,
61
+ function (e, cur) {return e.getTokenAt(cur);},
62
+ options);
63
+ };
64
+ CodeMirror.javascriptHint = javascriptHint; // deprecated
65
+ CodeMirror.registerHelper("hint", "javascript", javascriptHint);
66
+
67
+ function getCoffeeScriptToken(editor, cur) {
68
+ // This getToken, it is for coffeescript, imitates the behavior of
69
+ // getTokenAt method in javascript.js, that is, returning "property"
70
+ // type and treat "." as indepenent token.
71
+ var token = editor.getTokenAt(cur);
72
+ if (cur.ch == token.start + 1 && token.string.charAt(0) == '.') {
73
+ token.end = token.start;
74
+ token.string = '.';
75
+ token.type = "property";
76
+ }
77
+ else if (/^\.[\w$_]*$/.test(token.string)) {
78
+ token.type = "property";
79
+ token.start++;
80
+ token.string = token.string.replace(/\./, '');
81
+ }
82
+ return token;
83
+ }
84
+
85
+ function coffeescriptHint(editor, options) {
86
+ return scriptHint(editor, coffeescriptKeywords, getCoffeeScriptToken, options);
87
+ }
88
+ CodeMirror.coffeescriptHint = coffeescriptHint; // deprecated
89
+ CodeMirror.registerHelper("hint", "coffeescript", coffeescriptHint);
90
+
91
+ var stringProps = ("charAt charCodeAt indexOf lastIndexOf substring substr slice trim trimLeft trimRight " +
92
+ "toUpperCase toLowerCase split concat match replace search").split(" ");
93
+ var arrayProps = ("length concat join splice push pop shift unshift slice reverse sort indexOf " +
94
+ "lastIndexOf every some filter forEach map reduce reduceRight ").split(" ");
95
+ var funcProps = "prototype apply call bind".split(" ");
96
+ var javascriptKeywords = ("break case catch continue debugger default delete do else false finally for function " +
97
+ "if in instanceof new null return switch throw true try typeof var void while with").split(" ");
98
+ var coffeescriptKeywords = ("and break catch class continue delete do else extends false finally for " +
99
+ "if in instanceof isnt new no not null of off on or return switch then throw true try typeof until void while with yes").split(" ");
100
+
101
+ function getCompletions(token, context, keywords, options) {
102
+ var found = [], start = token.string;
103
+ function maybeAdd(str) {
104
+ if (str.indexOf(start) == 0 && !arrayContains(found, str)) found.push(str);
105
+ }
106
+ function gatherCompletions(obj) {
107
+ if (typeof obj == "string") forEach(stringProps, maybeAdd);
108
+ else if (obj instanceof Array) forEach(arrayProps, maybeAdd);
109
+ else if (obj instanceof Function) forEach(funcProps, maybeAdd);
110
+ for (var name in obj) maybeAdd(name);
111
+ }
112
+
113
+ if (context) {
114
+ // If this is a property, see if it belongs to some object we can
115
+ // find in the current environment.
116
+ var obj = context.pop(), base;
117
+ if (obj.type.indexOf("variable") === 0) {
118
+ if (options && options.additionalContext)
119
+ base = options.additionalContext[obj.string];
120
+ base = base || window[obj.string];
121
+ } else if (obj.type == "string") {
122
+ base = "";
123
+ } else if (obj.type == "atom") {
124
+ base = 1;
125
+ } else if (obj.type == "function") {
126
+ if (window.jQuery != null && (obj.string == '$' || obj.string == 'jQuery') &&
127
+ (typeof window.jQuery == 'function'))
128
+ base = window.jQuery();
129
+ else if (window._ != null && (obj.string == '_') && (typeof window._ == 'function'))
130
+ base = window._();
131
+ }
132
+ while (base != null && context.length)
133
+ base = base[context.pop().string];
134
+ if (base != null) gatherCompletions(base);
135
+ }
136
+ else {
137
+ // If not, just look in the window object and any local scope
138
+ // (reading into JS mode internals to get at the local and global variables)
139
+ for (var v = token.state.localVars; v; v = v.next) maybeAdd(v.name);
140
+ for (var v = token.state.globalVars; v; v = v.next) maybeAdd(v.name);
141
+ gatherCompletions(window);
142
+ forEach(keywords, maybeAdd);
143
+ }
144
+ return found;
145
+ }
146
+ })();
assets/lib/codemirror/addon/hint/pig-hint.js ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function () {
2
+ function forEach(arr, f) {
3
+ for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
4
+ }
5
+
6
+ function arrayContains(arr, item) {
7
+ if (!Array.prototype.indexOf) {
8
+ var i = arr.length;
9
+ while (i--) {
10
+ if (arr[i] === item) {
11
+ return true;
12
+ }
13
+ }
14
+ return false;
15
+ }
16
+ return arr.indexOf(item) != -1;
17
+ }
18
+
19
+ function scriptHint(editor, _keywords, getToken) {
20
+ // Find the token at the cursor
21
+ var cur = editor.getCursor(), token = getToken(editor, cur), tprop = token;
22
+ // If it's not a 'word-style' token, ignore the token.
23
+
24
+ if (!/^[\w$_]*$/.test(token.string)) {
25
+ token = tprop = {start: cur.ch, end: cur.ch, string: "", state: token.state,
26
+ className: token.string == ":" ? "pig-type" : null};
27
+ }
28
+
29
+ if (!context) var context = [];
30
+ context.push(tprop);
31
+
32
+ var completionList = getCompletions(token, context);
33
+ completionList = completionList.sort();
34
+ //prevent autocomplete for last word, instead show dropdown with one word
35
+ if(completionList.length == 1) {
36
+ completionList.push(" ");
37
+ }
38
+
39
+ return {list: completionList,
40
+ from: CodeMirror.Pos(cur.line, token.start),
41
+ to: CodeMirror.Pos(cur.line, token.end)};
42
+ }
43
+
44
+ function pigHint(editor) {
45
+ return scriptHint(editor, pigKeywordsU, function (e, cur) {return e.getTokenAt(cur);});
46
+ }
47
+ CodeMirror.pigHint = pigHint; // deprecated
48
+ CodeMirror.registerHelper("hint", "pig", hinter);
49
+
50
+ var pigKeywords = "VOID IMPORT RETURNS DEFINE LOAD FILTER FOREACH ORDER CUBE DISTINCT COGROUP "
51
+ + "JOIN CROSS UNION SPLIT INTO IF OTHERWISE ALL AS BY USING INNER OUTER ONSCHEMA PARALLEL "
52
+ + "PARTITION GROUP AND OR NOT GENERATE FLATTEN ASC DESC IS STREAM THROUGH STORE MAPREDUCE "
53
+ + "SHIP CACHE INPUT OUTPUT STDERROR STDIN STDOUT LIMIT SAMPLE LEFT RIGHT FULL EQ GT LT GTE LTE "
54
+ + "NEQ MATCHES TRUE FALSE";
55
+ var pigKeywordsU = pigKeywords.split(" ");
56
+ var pigKeywordsL = pigKeywords.toLowerCase().split(" ");
57
+
58
+ var pigTypes = "BOOLEAN INT LONG FLOAT DOUBLE CHARARRAY BYTEARRAY BAG TUPLE MAP";
59
+ var pigTypesU = pigTypes.split(" ");
60
+ var pigTypesL = pigTypes.toLowerCase().split(" ");
61
+
62
+ var pigBuiltins = "ABS ACOS ARITY ASIN ATAN AVG BAGSIZE BINSTORAGE BLOOM BUILDBLOOM CBRT CEIL "
63
+ + "CONCAT COR COS COSH COUNT COUNT_STAR COV CONSTANTSIZE CUBEDIMENSIONS DIFF DISTINCT DOUBLEABS "
64
+ + "DOUBLEAVG DOUBLEBASE DOUBLEMAX DOUBLEMIN DOUBLEROUND DOUBLESUM EXP FLOOR FLOATABS FLOATAVG "
65
+ + "FLOATMAX FLOATMIN FLOATROUND FLOATSUM GENERICINVOKER INDEXOF INTABS INTAVG INTMAX INTMIN "
66
+ + "INTSUM INVOKEFORDOUBLE INVOKEFORFLOAT INVOKEFORINT INVOKEFORLONG INVOKEFORSTRING INVOKER "
67
+ + "ISEMPTY JSONLOADER JSONMETADATA JSONSTORAGE LAST_INDEX_OF LCFIRST LOG LOG10 LOWER LONGABS "
68
+ + "LONGAVG LONGMAX LONGMIN LONGSUM MAX MIN MAPSIZE MONITOREDUDF NONDETERMINISTIC OUTPUTSCHEMA "
69
+ + "PIGSTORAGE PIGSTREAMING RANDOM REGEX_EXTRACT REGEX_EXTRACT_ALL REPLACE ROUND SIN SINH SIZE "
70
+ + "SQRT STRSPLIT SUBSTRING SUM STRINGCONCAT STRINGMAX STRINGMIN STRINGSIZE TAN TANH TOBAG "
71
+ + "TOKENIZE TOMAP TOP TOTUPLE TRIM TEXTLOADER TUPLESIZE UCFIRST UPPER UTF8STORAGECONVERTER";
72
+ var pigBuiltinsU = pigBuiltins.split(" ").join("() ").split(" ");
73
+ var pigBuiltinsL = pigBuiltins.toLowerCase().split(" ").join("() ").split(" ");
74
+ var pigBuiltinsC = ("BagSize BinStorage Bloom BuildBloom ConstantSize CubeDimensions DoubleAbs "
75
+ + "DoubleAvg DoubleBase DoubleMax DoubleMin DoubleRound DoubleSum FloatAbs FloatAvg FloatMax "
76
+ + "FloatMin FloatRound FloatSum GenericInvoker IntAbs IntAvg IntMax IntMin IntSum "
77
+ + "InvokeForDouble InvokeForFloat InvokeForInt InvokeForLong InvokeForString Invoker "
78
+ + "IsEmpty JsonLoader JsonMetadata JsonStorage LongAbs LongAvg LongMax LongMin LongSum MapSize "
79
+ + "MonitoredUDF Nondeterministic OutputSchema PigStorage PigStreaming StringConcat StringMax "
80
+ + "StringMin StringSize TextLoader TupleSize Utf8StorageConverter").split(" ").join("() ").split(" ");
81
+
82
+ function getCompletions(token, context) {
83
+ var found = [], start = token.string;
84
+ function maybeAdd(str) {
85
+ if (str.indexOf(start) == 0 && !arrayContains(found, str)) found.push(str);
86
+ }
87
+
88
+ function gatherCompletions(obj) {
89
+ if(obj == ":") {
90
+ forEach(pigTypesL, maybeAdd);
91
+ }
92
+ else {
93
+ forEach(pigBuiltinsU, maybeAdd);
94
+ forEach(pigBuiltinsL, maybeAdd);
95
+ forEach(pigBuiltinsC, maybeAdd);
96
+ forEach(pigTypesU, maybeAdd);
97
+ forEach(pigTypesL, maybeAdd);
98
+ forEach(pigKeywordsU, maybeAdd);
99
+ forEach(pigKeywordsL, maybeAdd);
100
+ }
101
+ }
102
+
103
+ if (context) {
104
+ // If this is a property, see if it belongs to some object we can
105
+ // find in the current environment.
106
+ var obj = context.pop(), base;
107
+
108
+ if (obj.type == "variable")
109
+ base = obj.string;
110
+ else if(obj.type == "variable-3")
111
+ base = ":" + obj.string;
112
+
113
+ while (base != null && context.length)
114
+ base = base[context.pop().string];
115
+ if (base != null) gatherCompletions(base);
116
+ }
117
+ return found;
118
+ }
119
+ })();
assets/lib/codemirror/addon/hint/python-hint.js ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function () {
2
+ function forEach(arr, f) {
3
+ for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
4
+ }
5
+
6
+ function arrayContains(arr, item) {
7
+ if (!Array.prototype.indexOf) {
8
+ var i = arr.length;
9
+ while (i--) {
10
+ if (arr[i] === item) {
11
+ return true;
12
+ }
13
+ }
14
+ return false;
15
+ }
16
+ return arr.indexOf(item) != -1;
17
+ }
18
+
19
+ function scriptHint(editor, _keywords, getToken) {
20
+ // Find the token at the cursor
21
+ var cur = editor.getCursor(), token = getToken(editor, cur), tprop = token;
22
+ // If it's not a 'word-style' token, ignore the token.
23
+
24
+ if (!/^[\w$_]*$/.test(token.string)) {
25
+ token = tprop = {start: cur.ch, end: cur.ch, string: "", state: token.state,
26
+ className: token.string == ":" ? "python-type" : null};
27
+ }
28
+
29
+ if (!context) var context = [];
30
+ context.push(tprop);
31
+
32
+ var completionList = getCompletions(token, context);
33
+ completionList = completionList.sort();
34
+ //prevent autocomplete for last word, instead show dropdown with one word
35
+ if(completionList.length == 1) {
36
+ completionList.push(" ");
37
+ }
38
+
39
+ return {list: completionList,
40
+ from: CodeMirror.Pos(cur.line, token.start),
41
+ to: CodeMirror.Pos(cur.line, token.end)};
42
+ }
43
+
44
+ function pythonHint(editor) {
45
+ return scriptHint(editor, pythonKeywordsU, function (e, cur) {return e.getTokenAt(cur);});
46
+ }
47
+ CodeMirror.pythonHint = pythonHint; // deprecated
48
+ CodeMirror.registerHelper("hint", "python", pythonHint);
49
+
50
+ var pythonKeywords = "and del from not while as elif global or with assert else if pass yield"
51
+ + "break except import print class exec in raise continue finally is return def for lambda try";
52
+ var pythonKeywordsL = pythonKeywords.split(" ");
53
+ var pythonKeywordsU = pythonKeywords.toUpperCase().split(" ");
54
+
55
+ var pythonBuiltins = "abs divmod input open staticmethod all enumerate int ord str "
56
+ + "any eval isinstance pow sum basestring execfile issubclass print super"
57
+ + "bin file iter property tuple bool filter len range type"
58
+ + "bytearray float list raw_input unichr callable format locals reduce unicode"
59
+ + "chr frozenset long reload vars classmethod getattr map repr xrange"
60
+ + "cmp globals max reversed zip compile hasattr memoryview round __import__"
61
+ + "complex hash min set apply delattr help next setattr buffer"
62
+ + "dict hex object slice coerce dir id oct sorted intern ";
63
+ var pythonBuiltinsL = pythonBuiltins.split(" ").join("() ").split(" ");
64
+ var pythonBuiltinsU = pythonBuiltins.toUpperCase().split(" ").join("() ").split(" ");
65
+
66
+ function getCompletions(token, context) {
67
+ var found = [], start = token.string;
68
+ function maybeAdd(str) {
69
+ if (str.indexOf(start) == 0 && !arrayContains(found, str)) found.push(str);
70
+ }
71
+
72
+ function gatherCompletions(_obj) {
73
+ forEach(pythonBuiltinsL, maybeAdd);
74
+ forEach(pythonBuiltinsU, maybeAdd);
75
+ forEach(pythonKeywordsL, maybeAdd);
76
+ forEach(pythonKeywordsU, maybeAdd);
77
+ }
78
+
79
+ if (context) {
80
+ // If this is a property, see if it belongs to some object we can
81
+ // find in the current environment.
82
+ var obj = context.pop(), base;
83
+
84
+ if (obj.type == "variable")
85
+ base = obj.string;
86
+ else if(obj.type == "variable-3")
87
+ base = ":" + obj.string;
88
+
89
+ while (base != null && context.length)
90
+ base = base[context.pop().string];
91
+ if (base != null) gatherCompletions(base);
92
+ }
93
+ return found;
94
+ }
95
+ })();
assets/lib/codemirror/addon/hint/show-hint.css ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .CodeMirror-hints {
2
+ position: absolute;
3
+ z-index: 10;
4
+ overflow: hidden;
5
+ list-style: none;
6
+
7
+ margin: 0;
8
+ padding: 2px;
9
+
10
+ -webkit-box-shadow: 2px 3px 5px rgba(0,0,0,.2);
11
+ -moz-box-shadow: 2px 3px 5px rgba(0,0,0,.2);
12
+ box-shadow: 2px 3px 5px rgba(0,0,0,.2);
13
+ border-radius: 3px;
14
+ border: 1px solid silver;
15
+
16
+ background: white;
17
+ font-size: 90%;
18
+ font-family: monospace;
19
+
20
+ max-height: 20em;
21
+ overflow-y: auto;
22
+ }
23
+
24
+ .CodeMirror-hint {
25
+ margin: 0;
26
+ padding: 0 4px;
27
+ border-radius: 2px;
28
+ max-width: 19em;
29
+ overflow: hidden;
30
+ white-space: pre;
31
+ color: black;
32
+ cursor: pointer;
33
+ }
34
+
35
+ .CodeMirror-hint-active {
36
+ background: #08f;
37
+ color: white;
38
+ }
assets/lib/codemirror/addon/hint/show-hint.js ADDED
@@ -0,0 +1,274 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function() {
2
+ "use strict";
3
+
4
+ CodeMirror.showHint = function(cm, getHints, options) {
5
+ // We want a single cursor position.
6
+ if (cm.somethingSelected()) return;
7
+ if (getHints == null) getHints = cm.getHelper(cm.getCursor(), "hint");
8
+ if (getHints == null) return;
9
+
10
+ if (cm.state.completionActive) cm.state.completionActive.close();
11
+
12
+ var completion = cm.state.completionActive = new Completion(cm, getHints, options || {});
13
+ CodeMirror.signal(cm, "startCompletion", cm);
14
+ if (completion.options.async)
15
+ getHints(cm, function(hints) { completion.showHints(hints); }, completion.options);
16
+ else
17
+ return completion.showHints(getHints(cm, completion.options));
18
+ };
19
+
20
+ function Completion(cm, getHints, options) {
21
+ this.cm = cm;
22
+ this.getHints = getHints;
23
+ this.options = options;
24
+ this.widget = this.onClose = null;
25
+ }
26
+
27
+ Completion.prototype = {
28
+ close: function() {
29
+ if (!this.active()) return;
30
+ this.cm.state.completionActive = null;
31
+
32
+ if (this.widget) this.widget.close();
33
+ if (this.onClose) this.onClose();
34
+ CodeMirror.signal(this.cm, "endCompletion", this.cm);
35
+ },
36
+
37
+ active: function() {
38
+ return this.cm.state.completionActive == this;
39
+ },
40
+
41
+ pick: function(data, i) {
42
+ var completion = data.list[i];
43
+ if (completion.hint) completion.hint(this.cm, data, completion);
44
+ else this.cm.replaceRange(getText(completion), data.from, data.to);
45
+ this.close();
46
+ },
47
+
48
+ showHints: function(data) {
49
+ if (!data || !data.list.length || !this.active()) return this.close();
50
+
51
+ if (this.options.completeSingle != false && data.list.length == 1)
52
+ this.pick(data, 0);
53
+ else
54
+ this.showWidget(data);
55
+ },
56
+
57
+ showWidget: function(data) {
58
+ this.widget = new Widget(this, data);
59
+ CodeMirror.signal(data, "shown");
60
+
61
+ var debounce = null, completion = this, finished;
62
+ var closeOn = this.options.closeCharacters || /[\s()\[\]{};:>,]/;
63
+ var startPos = this.cm.getCursor(), startLen = this.cm.getLine(startPos.line).length;
64
+
65
+ function done() {
66
+ if (finished) return;
67
+ finished = true;
68
+ completion.close();
69
+ completion.cm.off("cursorActivity", activity);
70
+ if (data) CodeMirror.signal(data, "close");
71
+ }
72
+
73
+ function update() {
74
+ if (finished) return;
75
+ CodeMirror.signal(data, "update");
76
+ if (completion.options.async)
77
+ completion.getHints(completion.cm, finishUpdate, completion.options);
78
+ else
79
+ finishUpdate(completion.getHints(completion.cm, completion.options));
80
+ }
81
+ function finishUpdate(data_) {
82
+ data = data_;
83
+ if (finished) return;
84
+ if (!data || !data.list.length) return done();
85
+ completion.widget = new Widget(completion, data);
86
+ }
87
+
88
+ function activity() {
89
+ clearTimeout(debounce);
90
+ var pos = completion.cm.getCursor(), line = completion.cm.getLine(pos.line);
91
+ if (pos.line != startPos.line || line.length - pos.ch != startLen - startPos.ch ||
92
+ pos.ch < startPos.ch || completion.cm.somethingSelected() ||
93
+ (pos.ch && closeOn.test(line.charAt(pos.ch - 1)))) {
94
+ completion.close();
95
+ } else {
96
+ debounce = setTimeout(update, 170);
97
+ if (completion.widget) completion.widget.close();
98
+ }
99
+ }
100
+ this.cm.on("cursorActivity", activity);
101
+ this.onClose = done;
102
+ }
103
+ };
104
+
105
+ function getText(completion) {
106
+ if (typeof completion == "string") return completion;
107
+ else return completion.text;
108
+ }
109
+
110
+ function buildKeyMap(options, handle) {
111
+ var baseMap = {
112
+ Up: function() {handle.moveFocus(-1);},
113
+ Down: function() {handle.moveFocus(1);},
114
+ PageUp: function() {handle.moveFocus(-handle.menuSize() + 1, true);},
115
+ PageDown: function() {handle.moveFocus(handle.menuSize() - 1, true);},
116
+ Home: function() {handle.setFocus(0);},
117
+ End: function() {handle.setFocus(handle.length - 1);},
118
+ Enter: handle.pick,
119
+ Tab: handle.pick,
120
+ Esc: handle.close
121
+ };
122
+ var ourMap = options.customKeys ? {} : baseMap;
123
+ function addBinding(key, val) {
124
+ var bound;
125
+ if (typeof val != "string")
126
+ bound = function(cm) { return val(cm, handle); };
127
+ // This mechanism is deprecated
128
+ else if (baseMap.hasOwnProperty(val))
129
+ bound = baseMap[val];
130
+ else
131
+ bound = val;
132
+ ourMap[key] = bound;
133
+ }
134
+ if (options.customKeys)
135
+ for (var key in options.customKeys) if (options.customKeys.hasOwnProperty(key))
136
+ addBinding(key, options.customKeys[key]);
137
+ if (options.extraKeys)
138
+ for (var key in options.extraKeys) if (options.extraKeys.hasOwnProperty(key))
139
+ addBinding(key, options.extraKeys[key]);
140
+ return ourMap;
141
+ }
142
+
143
+ function Widget(completion, data) {
144
+ this.completion = completion;
145
+ this.data = data;
146
+ var widget = this, cm = completion.cm, options = completion.options;
147
+
148
+ var hints = this.hints = document.createElement("ul");
149
+ hints.className = "CodeMirror-hints";
150
+ this.selectedHint = 0;
151
+
152
+ var completions = data.list;
153
+ for (var i = 0; i < completions.length; ++i) {
154
+ var elt = hints.appendChild(document.createElement("li")), cur = completions[i];
155
+ var className = "CodeMirror-hint" + (i ? "" : " CodeMirror-hint-active");
156
+ if (cur.className != null) className = cur.className + " " + className;
157
+ elt.className = className;
158
+ if (cur.render) cur.render(elt, data, cur);
159
+ else elt.appendChild(document.createTextNode(cur.displayText || getText(cur)));
160
+ elt.hintId = i;
161
+ }
162
+
163
+ var pos = cm.cursorCoords(options.alignWithWord !== false ? data.from : null);
164
+ var left = pos.left, top = pos.bottom, below = true;
165
+ hints.style.left = left + "px";
166
+ hints.style.top = top + "px";
167
+ // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor.
168
+ var winW = window.innerWidth || Math.max(document.body.offsetWidth, document.documentElement.offsetWidth);
169
+ var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight);
170
+ (options.container || document.body).appendChild(hints);
171
+ var box = hints.getBoundingClientRect();
172
+ var overlapX = box.right - winW, overlapY = box.bottom - winH;
173
+ if (overlapX > 0) {
174
+ if (box.right - box.left > winW) {
175
+ hints.style.width = (winW - 5) + "px";
176
+ overlapX -= (box.right - box.left) - winW;
177
+ }
178
+ hints.style.left = (left = pos.left - overlapX) + "px";
179
+ }
180
+ if (overlapY > 0) {
181
+ var height = box.bottom - box.top;
182
+ if (box.top - (pos.bottom - pos.top) - height > 0) {
183
+ overlapY = height + (pos.bottom - pos.top);
184
+ below = false;
185
+ } else if (height > winH) {
186
+ hints.style.height = (winH - 5) + "px";
187
+ overlapY -= height - winH;
188
+ }
189
+ hints.style.top = (top = pos.bottom - overlapY) + "px";
190
+ }
191
+
192
+ cm.addKeyMap(this.keyMap = buildKeyMap(options, {
193
+ moveFocus: function(n, avoidWrap) { widget.changeActive(widget.selectedHint + n, avoidWrap); },
194
+ setFocus: function(n) { widget.changeActive(n); },
195
+ menuSize: function() { return widget.screenAmount(); },
196
+ length: completions.length,
197
+ close: function() { completion.close(); },
198
+ pick: function() { widget.pick(); }
199
+ }));
200
+
201
+ if (options.closeOnUnfocus !== false) {
202
+ var closingOnBlur;
203
+ cm.on("blur", this.onBlur = function() { closingOnBlur = setTimeout(function() { completion.close(); }, 100); });
204
+ cm.on("focus", this.onFocus = function() { clearTimeout(closingOnBlur); });
205
+ }
206
+
207
+ var startScroll = cm.getScrollInfo();
208
+ cm.on("scroll", this.onScroll = function() {
209
+ var curScroll = cm.getScrollInfo(), editor = cm.getWrapperElement().getBoundingClientRect();
210
+ var newTop = top + startScroll.top - curScroll.top;
211
+ var point = newTop - (window.pageYOffset || (document.documentElement || document.body).scrollTop);
212
+ if (!below) point += hints.offsetHeight;
213
+ if (point <= editor.top || point >= editor.bottom) return completion.close();
214
+ hints.style.top = newTop + "px";
215
+ hints.style.left = (left + startScroll.left - curScroll.left) + "px";
216
+ });
217
+
218
+ CodeMirror.on(hints, "dblclick", function(e) {
219
+ var t = e.target || e.srcElement;
220
+ if (t.hintId != null) {widget.changeActive(t.hintId); widget.pick();}
221
+ });
222
+ CodeMirror.on(hints, "click", function(e) {
223
+ var t = e.target || e.srcElement;
224
+ if (t.hintId != null) widget.changeActive(t.hintId);
225
+ });
226
+ CodeMirror.on(hints, "mousedown", function() {
227
+ setTimeout(function(){cm.focus();}, 20);
228
+ });
229
+
230
+ CodeMirror.signal(data, "select", completions[0], hints.firstChild);
231
+ return true;
232
+ }
233
+
234
+ Widget.prototype = {
235
+ close: function() {
236
+ if (this.completion.widget != this) return;
237
+ this.completion.widget = null;
238
+ this.hints.parentNode.removeChild(this.hints);
239
+ this.completion.cm.removeKeyMap(this.keyMap);
240
+
241
+ var cm = this.completion.cm;
242
+ if (this.completion.options.closeOnUnfocus !== false) {
243
+ cm.off("blur", this.onBlur);
244
+ cm.off("focus", this.onFocus);
245
+ }
246
+ cm.off("scroll", this.onScroll);
247
+ },
248
+
249
+ pick: function() {
250
+ this.completion.pick(this.data, this.selectedHint);
251
+ },
252
+
253
+ changeActive: function(i, avoidWrap) {
254
+ if (i >= this.data.list.length)
255
+ i = avoidWrap ? this.data.list.length - 1 : 0;
256
+ else if (i < 0)
257
+ i = avoidWrap ? 0 : this.data.list.length - 1;
258
+ if (this.selectedHint == i) return;
259
+ var node = this.hints.childNodes[this.selectedHint];
260
+ node.className = node.className.replace(" CodeMirror-hint-active", "");
261
+ node = this.hints.childNodes[this.selectedHint = i];
262
+ node.className += " CodeMirror-hint-active";
263
+ if (node.offsetTop < this.hints.scrollTop)
264
+ this.hints.scrollTop = node.offsetTop - 3;
265
+ else if (node.offsetTop + node.offsetHeight > this.hints.scrollTop + this.hints.clientHeight)
266
+ this.hints.scrollTop = node.offsetTop + node.offsetHeight - this.hints.clientHeight + 3;
267
+ CodeMirror.signal(this.data, "select", this.data.list[this.selectedHint], node);
268
+ },
269
+
270
+ screenAmount: function() {
271
+ return Math.floor(this.hints.clientHeight / this.hints.firstChild.offsetHeight) || 1;
272
+ }
273
+ };
274
+ })();
assets/lib/codemirror/addon/hint/xml-hint.js ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function() {
2
+ "use strict";
3
+
4
+ var Pos = CodeMirror.Pos;
5
+
6
+ function getHints(cm, options) {
7
+ var tags = options && options.schemaInfo;
8
+ var quote = (options && options.quoteChar) || '"';
9
+ if (!tags) return;
10
+ var cur = cm.getCursor(), token = cm.getTokenAt(cur);
11
+ var inner = CodeMirror.innerMode(cm.getMode(), token.state);
12
+ if (inner.mode.name != "xml") return;
13
+ var result = [], replaceToken = false, prefix;
14
+ var isTag = token.string.charAt(0) == "<";
15
+ if (!inner.state.tagName || isTag) { // Tag completion
16
+ if (isTag) {
17
+ prefix = token.string.slice(1);
18
+ replaceToken = true;
19
+ }
20
+ var cx = inner.state.context, curTag = cx && tags[cx.tagName];
21
+ var childList = cx ? curTag && curTag.children : tags["!top"];
22
+ if (childList) {
23
+ for (var i = 0; i < childList.length; ++i) if (!prefix || childList[i].indexOf(prefix) == 0)
24
+ result.push("<" + childList[i]);
25
+ } else {
26
+ for (var name in tags) if (tags.hasOwnProperty(name) && name != "!top" && (!prefix || name.indexOf(prefix) == 0))
27
+ result.push("<" + name);
28
+ }
29
+ if (cx && (!prefix || ("/" + cx.tagName).indexOf(prefix) == 0))
30
+ result.push("</" + cx.tagName + ">");
31
+ } else {
32
+ // Attribute completion
33
+ var curTag = tags[inner.state.tagName], attrs = curTag && curTag.attrs;
34
+ if (!attrs) return;
35
+ if (token.type == "string" || token.string == "=") { // A value
36
+ var before = cm.getRange(Pos(cur.line, Math.max(0, cur.ch - 60)),
37
+ Pos(cur.line, token.type == "string" ? token.start : token.end));
38
+ var atName = before.match(/([^\s\u00a0=<>\"\']+)=$/), atValues;
39
+ if (!atName || !attrs.hasOwnProperty(atName[1]) || !(atValues = attrs[atName[1]])) return;
40
+ if (token.type == "string") {
41
+ prefix = token.string;
42
+ if (/['"]/.test(token.string.charAt(0))) {
43
+ quote = token.string.charAt(0);
44
+ prefix = token.string.slice(1);
45
+ }
46
+ replaceToken = true;
47
+ }
48
+ for (var i = 0; i < atValues.length; ++i) if (!prefix || atValues[i].indexOf(prefix) == 0)
49
+ result.push(quote + atValues[i] + quote);
50
+ } else { // An attribute name
51
+ if (token.type == "attribute") {
52
+ prefix = token.string;
53
+ replaceToken = true;
54
+ }
55
+ for (var attr in attrs) if (attrs.hasOwnProperty(attr) && (!prefix || attr.indexOf(prefix) == 0))
56
+ result.push(attr);
57
+ }
58
+ }
59
+ return {
60
+ list: result,
61
+ from: replaceToken ? Pos(cur.line, token.start) : cur,
62
+ to: replaceToken ? Pos(cur.line, token.end) : cur
63
+ };
64
+ }
65
+
66
+ CodeMirror.xmlHint = getHints; // deprecated
67
+ CodeMirror.registerHelper("hint", "xml", getHints);
68
+ })();
assets/lib/codemirror/addon/lint/coffeescript-lint.js ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Depends on coffeelint.js from http://www.coffeelint.org/js/coffeelint.js
2
+
3
+ CodeMirror.registerHelper("lint", "coffeescript", function(text) {
4
+ var found = [];
5
+ var parseError = function(err) {
6
+ var loc = err.lineNumber;
7
+ found.push({from: CodeMirror.Pos(loc-1, 0),
8
+ to: CodeMirror.Pos(loc, 0),
9
+ severity: err.level,
10
+ message: err.message});
11
+ };
12
+ try {
13
+ var res = coffeelint.lint(text);
14
+ for(var i = 0; i < res.length; i++) {
15
+ parseError(res[i]);
16
+ }
17
+ } catch(e) {
18
+ found.push({from: CodeMirror.Pos(e.location.first_line, 0),
19
+ to: CodeMirror.Pos(e.location.last_line, e.location.last_column),
20
+ severity: 'error',
21
+ message: e.message});
22
+ }
23
+ return found;
24
+ });
25
+ CodeMirror.coffeeValidator = CodeMirror.lint.coffeescript; // deprecated
assets/lib/codemirror/addon/lint/css-lint.js ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Depends on csslint.js from https://github.com/stubbornella/csslint
2
+
3
+ CodeMirror.registerHelper("lint", "css", function(text) {
4
+ var found = [];
5
+ var results = CSSLint.verify(text), messages = results.messages, message = null;
6
+ for ( var i = 0; i < messages.length; i++) {
7
+ message = messages[i];
8
+ var startLine = message.line -1, endLine = message.line -1, startCol = message.col -1, endCol = message.col;
9
+ found.push({
10
+ from: CodeMirror.Pos(startLine, startCol),
11
+ to: CodeMirror.Pos(endLine, endCol),
12
+ message: message.message,
13
+ severity : message.type
14
+ });
15
+ }
16
+ return found;
17
+ });
assets/lib/codemirror/addon/lint/javascript-lint.js ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function() {
2
+
3
+ var bogus = [ "Dangerous comment" ];
4
+
5
+ var warnings = [ [ "Expected '{'",
6
+ "Statement body should be inside '{ }' braces." ] ];
7
+
8
+ var errors = [ "Missing semicolon", "Extra comma", "Missing property name",
9
+ "Unmatched ", " and instead saw", " is not defined",
10
+ "Unclosed string", "Stopping, unable to continue" ];
11
+
12
+ function validator(text, options) {
13
+ JSHINT(text, options);
14
+ var errors = JSHINT.data().errors, result = [];
15
+ if (errors) parseErrors(errors, result);
16
+ return result;
17
+ }
18
+
19
+ CodeMirror.registerHelper("lint", "javascript", validator);
20
+ CodeMirror.javascriptValidator = CodeMirror.lint.javascript; // deprecated
21
+
22
+ function cleanup(error) {
23
+ // All problems are warnings by default
24
+ fixWith(error, warnings, "warning", true);
25
+ fixWith(error, errors, "error");
26
+
27
+ return isBogus(error) ? null : error;
28
+ }
29
+
30
+ function fixWith(error, fixes, severity, force) {
31
+ var description, fix, find, replace, found;
32
+
33
+ description = error.description;
34
+
35
+ for ( var i = 0; i < fixes.length; i++) {
36
+ fix = fixes[i];
37
+ find = (typeof fix === "string" ? fix : fix[0]);
38
+ replace = (typeof fix === "string" ? null : fix[1]);
39
+ found = description.indexOf(find) !== -1;
40
+
41
+ if (force || found) {
42
+ error.severity = severity;
43
+ }
44
+ if (found && replace) {
45
+ error.description = replace;
46
+ }
47
+ }
48
+ }
49
+
50
+ function isBogus(error) {
51
+ var description = error.description;
52
+ for ( var i = 0; i < bogus.length; i++) {
53
+ if (description.indexOf(bogus[i]) !== -1) {
54
+ return true;
55
+ }
56
+ }
57
+ return false;
58
+ }
59
+
60
+ function parseErrors(errors, output) {
61
+ for ( var i = 0; i < errors.length; i++) {
62
+ var error = errors[i];
63
+ if (error) {
64
+ var linetabpositions, index;
65
+
66
+ linetabpositions = [];
67
+
68
+ // This next block is to fix a problem in jshint. Jshint
69
+ // replaces
70
+ // all tabs with spaces then performs some checks. The error
71
+ // positions (character/space) are then reported incorrectly,
72
+ // not taking the replacement step into account. Here we look
73
+ // at the evidence line and try to adjust the character position
74
+ // to the correct value.
75
+ if (error.evidence) {
76
+ // Tab positions are computed once per line and cached
77
+ var tabpositions = linetabpositions[error.line];
78
+ if (!tabpositions) {
79
+ var evidence = error.evidence;
80
+ tabpositions = [];
81
+ // ugggh phantomjs does not like this
82
+ // forEachChar(evidence, function(item, index) {
83
+ Array.prototype.forEach.call(evidence, function(item,
84
+ index) {
85
+ if (item === '\t') {
86
+ // First col is 1 (not 0) to match error
87
+ // positions
88
+ tabpositions.push(index + 1);
89
+ }
90
+ });
91
+ linetabpositions[error.line] = tabpositions;
92
+ }
93
+ if (tabpositions.length > 0) {
94
+ var pos = error.character;
95
+ tabpositions.forEach(function(tabposition) {
96
+ if (pos > tabposition) pos -= 1;
97
+ });
98
+ error.character = pos;
99
+ }
100
+ }
101
+
102
+ var start = error.character - 1, end = start + 1;
103
+ if (error.evidence) {
104
+ index = error.evidence.substring(start).search(/.\b/);
105
+ if (index > -1) {
106
+ end += index;
107
+ }
108
+ }
109
+
110
+ // Convert to format expected by validation service
111
+ error.description = error.reason;// + "(jshint)";
112
+ error.start = error.character;
113
+ error.end = end;
114
+ error = cleanup(error);
115
+
116
+ if (error)
117
+ output.push({message: error.description,
118
+ severity: error.severity,
119
+ from: CodeMirror.Pos(error.line - 1, start),
120
+ to: CodeMirror.Pos(error.line - 1, end)});
121
+ }
122
+ }
123
+ }
124
+ })();
assets/lib/codemirror/addon/lint/json-lint.js ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Depends on jsonlint.js from https://github.com/zaach/jsonlint
2
+
3
+ CodeMirror.registerHelper("lint", "json", function(text) {
4
+ var found = [];
5
+ jsonlint.parseError = function(str, hash) {
6
+ var loc = hash.loc;
7
+ found.push({from: CodeMirror.Pos(loc.first_line - 1, loc.first_column),
8
+ to: CodeMirror.Pos(loc.last_line - 1, loc.last_column),
9
+ message: str});
10
+ };
11
+ try { jsonlint.parse(text); }
12
+ catch(e) {}
13
+ return found;
14
+ });
15
+ CodeMirror.jsonValidator = CodeMirror.lint.json; // deprecated
assets/lib/codemirror/addon/lint/lint.css ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* The lint marker gutter */
2
+ .CodeMirror-lint-markers {
3
+ width: 16px;
4
+ }
5
+
6
+ .CodeMirror-lint-tooltip {
7
+ background-color: infobackground;
8
+ border: 1px solid black;
9
+ border-radius: 4px 4px 4px 4px;
10
+ color: infotext;
11
+ font-family: monospace;
12
+ font-size: 10pt;
13
+ overflow: hidden;
14
+ padding: 2px 5px;
15
+ position: fixed;
16
+ white-space: pre;
17
+ z-index: 100;
18
+ max-width: 600px;
19
+ opacity: 0;
20
+ transition: opacity .4s;
21
+ -moz-transition: opacity .4s;
22
+ -webkit-transition: opacity .4s;
23
+ -o-transition: opacity .4s;
24
+ -ms-transition: opacity .4s;
25
+ }
26
+
27
+ .CodeMirror-lint-mark-error, .CodeMirror-lint-mark-warning {
28
+ background-position: left bottom;
29
+ background-repeat: repeat-x;
30
+ }
31
+
32
+ .CodeMirror-lint-mark-error {
33
+ background-image:
34
+ url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJDw4cOCW1/KIAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg==")
35
+ ;
36
+ }
37
+
38
+ .CodeMirror-lint-mark-warning {
39
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII=");
40
+ }
41
+
42
+ .CodeMirror-lint-marker-error, .CodeMirror-lint-marker-warning {
43
+ background-position: center center;
44
+ background-repeat: no-repeat;
45
+ cursor: pointer;
46
+ display: inline-block;
47
+ height: 16px;
48
+ width: 16px;
49
+ vertical-align: middle;
50
+ position: relative;
51
+ }
52
+
53
+ .CodeMirror-lint-message-error, .CodeMirror-lint-message-warning {
54
+ padding-left: 18px;
55
+ background-position: top left;
56
+ background-repeat: no-repeat;
57
+ }
58
+
59
+ .CodeMirror-lint-marker-error, .CodeMirror-lint-message-error {
60
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAHlBMVEW7AAC7AACxAAC7AAC7AAAAAAC4AAC5AAD///+7AAAUdclpAAAABnRSTlMXnORSiwCK0ZKSAAAATUlEQVR42mWPOQ7AQAgDuQLx/z8csYRmPRIFIwRGnosRrpamvkKi0FTIiMASR3hhKW+hAN6/tIWhu9PDWiTGNEkTtIOucA5Oyr9ckPgAWm0GPBog6v4AAAAASUVORK5CYII=");
61
+ }
62
+
63
+ .CodeMirror-lint-marker-warning, .CodeMirror-lint-message-warning {
64
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAANlBMVEX/uwDvrwD/uwD/uwD/uwD/uwD/uwD/uwD/uwD6twD/uwAAAADurwD2tQD7uAD+ugAAAAD/uwDhmeTRAAAADHRSTlMJ8mN1EYcbmiixgACm7WbuAAAAVklEQVR42n3PUQqAIBBFUU1LLc3u/jdbOJoW1P08DA9Gba8+YWJ6gNJoNYIBzAA2chBth5kLmG9YUoG0NHAUwFXwO9LuBQL1giCQb8gC9Oro2vp5rncCIY8L8uEx5ZkAAAAASUVORK5CYII=");
65
+ }
66
+
67
+ .CodeMirror-lint-marker-multiple {
68
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAMAAADzjKfhAAAACVBMVEUAAAAAAAC/v7914kyHAAAAAXRSTlMAQObYZgAAACNJREFUeNo1ioEJAAAIwmz/H90iFFSGJgFMe3gaLZ0od+9/AQZ0ADosbYraAAAAAElFTkSuQmCC");
69
+ background-repeat: no-repeat;
70
+ background-position: right bottom;
71
+ width: 100%; height: 100%;
72
+ }
assets/lib/codemirror/addon/lint/lint.js ADDED
@@ -0,0 +1,203 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function() {
2
+ "use strict";
3
+ var GUTTER_ID = "CodeMirror-lint-markers";
4
+ var SEVERITIES = /^(?:error|warning)$/;
5
+
6
+ function showTooltip(e, content) {
7
+ var tt = document.createElement("div");
8
+ tt.className = "CodeMirror-lint-tooltip";
9
+ tt.appendChild(content.cloneNode(true));
10
+ document.body.appendChild(tt);
11
+
12
+ function position(e) {
13
+ if (!tt.parentNode) return CodeMirror.off(document, "mousemove", position);
14
+ tt.style.top = Math.max(0, e.clientY - tt.offsetHeight - 5) + "px";
15
+ tt.style.left = (e.clientX + 5) + "px";
16
+ }
17
+ CodeMirror.on(document, "mousemove", position);
18
+ position(e);
19
+ if (tt.style.opacity != null) tt.style.opacity = 1;
20
+ return tt;
21
+ }
22
+ function rm(elt) {
23
+ if (elt.parentNode) elt.parentNode.removeChild(elt);
24
+ }
25
+ function hideTooltip(tt) {
26
+ if (!tt.parentNode) return;
27
+ if (tt.style.opacity == null) rm(tt);
28
+ tt.style.opacity = 0;
29
+ setTimeout(function() { rm(tt); }, 600);
30
+ }
31
+
32
+ function showTooltipFor(e, content, node) {
33
+ var tooltip = showTooltip(e, content);
34
+ function hide() {
35
+ CodeMirror.off(node, "mouseout", hide);
36
+ if (tooltip) { hideTooltip(tooltip); tooltip = null; }
37
+ }
38
+ var poll = setInterval(function() {
39
+ if (tooltip) for (var n = node;; n = n.parentNode) {
40
+ if (n == document.body) return;
41
+ if (!n) { hide(); break; }
42
+ }
43
+ if (!tooltip) return clearInterval(poll);
44
+ }, 400);
45
+ CodeMirror.on(node, "mouseout", hide);
46
+ }
47
+
48
+ function LintState(cm, options, hasGutter) {
49
+ this.marked = [];
50
+ this.options = options;
51
+ this.timeout = null;
52
+ this.hasGutter = hasGutter;
53
+ this.onMouseOver = function(e) { onMouseOver(cm, e); };
54
+ }
55
+
56
+ function parseOptions(cm, options) {
57
+ if (options instanceof Function) return {getAnnotations: options};
58
+ if (!options || options === true) options = {};
59
+ if (!options.getAnnotations) options.getAnnotations = cm.getHelper(CodeMirror.Pos(0, 0), "lint");
60
+ if (!options.getAnnotations) throw new Error("Required option 'getAnnotations' missing (lint addon)");
61
+ return options;
62
+ }
63
+
64
+ function clearMarks(cm) {
65
+ var state = cm.state.lint;
66
+ if (state.hasGutter) cm.clearGutter(GUTTER_ID);
67
+ for (var i = 0; i < state.marked.length; ++i)
68
+ state.marked[i].clear();
69
+ state.marked.length = 0;
70
+ }
71
+
72
+ function makeMarker(labels, severity, multiple, tooltips) {
73
+ var marker = document.createElement("div"), inner = marker;
74
+ marker.className = "CodeMirror-lint-marker-" + severity;
75
+ if (multiple) {
76
+ inner = marker.appendChild(document.createElement("div"));
77
+ inner.className = "CodeMirror-lint-marker-multiple";
78
+ }
79
+
80
+ if (tooltips != false) CodeMirror.on(inner, "mouseover", function(e) {
81
+ showTooltipFor(e, labels, inner);
82
+ });
83
+
84
+ return marker;
85
+ }
86
+
87
+ function getMaxSeverity(a, b) {
88
+ if (a == "error") return a;
89
+ else return b;
90
+ }
91
+
92
+ function groupByLine(annotations) {
93
+ var lines = [];
94
+ for (var i = 0; i < annotations.length; ++i) {
95
+ var ann = annotations[i], line = ann.from.line;
96
+ (lines[line] || (lines[line] = [])).push(ann);
97
+ }
98
+ return lines;
99
+ }
100
+
101
+ function annotationTooltip(ann) {
102
+ var severity = ann.severity;
103
+ if (!SEVERITIES.test(severity)) severity = "error";
104
+ var tip = document.createElement("div");
105
+ tip.className = "CodeMirror-lint-message-" + severity;
106
+ tip.appendChild(document.createTextNode(ann.message));
107
+ return tip;
108
+ }
109
+
110
+ function startLinting(cm) {
111
+ var state = cm.state.lint, options = state.options;
112
+ if (options.async)
113
+ options.getAnnotations(cm, updateLinting, options);
114
+ else
115
+ updateLinting(cm, options.getAnnotations(cm.getValue(), options));
116
+ }
117
+
118
+ function updateLinting(cm, annotationsNotSorted) {
119
+ clearMarks(cm);
120
+ var state = cm.state.lint, options = state.options;
121
+
122
+ var annotations = groupByLine(annotationsNotSorted);
123
+
124
+ for (var line = 0; line < annotations.length; ++line) {
125
+ var anns = annotations[line];
126
+ if (!anns) continue;
127
+
128
+ var maxSeverity = null;
129
+ var tipLabel = state.hasGutter && document.createDocumentFragment();
130
+
131
+ for (var i = 0; i < anns.length; ++i) {
132
+ var ann = anns[i];
133
+ var severity = ann.severity;
134
+ if (!SEVERITIES.test(severity)) severity = "error";
135
+ maxSeverity = getMaxSeverity(maxSeverity, severity);
136
+
137
+ if (options.formatAnnotation) ann = options.formatAnnotation(ann);
138
+ if (state.hasGutter) tipLabel.appendChild(annotationTooltip(ann));
139
+
140
+ if (ann.to) state.marked.push(cm.markText(ann.from, ann.to, {
141
+ className: "CodeMirror-lint-mark-" + severity,
142
+ __annotation: ann
143
+ }));
144
+ }
145
+
146
+ if (state.hasGutter)
147
+ cm.setGutterMarker(line, GUTTER_ID, makeMarker(tipLabel, maxSeverity, anns.length > 1,
148
+ state.options.tooltips));
149
+ }
150
+ if (options.onUpdateLinting) options.onUpdateLinting(annotationsNotSorted, annotations, cm);
151
+ }
152
+
153
+ function onChange(cm) {
154
+ var state = cm.state.lint;
155
+ clearTimeout(state.timeout);
156
+ state.timeout = setTimeout(function(){startLinting(cm);}, state.options.delay || 500);
157
+ }
158
+
159
+ function popupSpanTooltip(ann, e) {
160
+ var target = e.target || e.srcElement;
161
+ showTooltipFor(e, annotationTooltip(ann), target);
162
+ }
163
+
164
+ // When the mouseover fires, the cursor might not actually be over
165
+ // the character itself yet. These pairs of x,y offsets are used to
166
+ // probe a few nearby points when no suitable marked range is found.
167
+ var nearby = [0, 0, 0, 5, 0, -5, 5, 0, -5, 0];
168
+
169
+ function onMouseOver(cm, e) {
170
+ if (!/\bCodeMirror-lint-mark-/.test((e.target || e.srcElement).className)) return;
171
+ for (var i = 0; i < nearby.length; i += 2) {
172
+ var spans = cm.findMarksAt(cm.coordsChar({left: e.clientX + nearby[i],
173
+ top: e.clientY + nearby[i + 1]}));
174
+ for (var j = 0; j < spans.length; ++j) {
175
+ var span = spans[j], ann = span.__annotation;
176
+ if (ann) return popupSpanTooltip(ann, e);
177
+ }
178
+ }
179
+ }
180
+
181
+ function optionHandler(cm, val, old) {
182
+ if (old && old != CodeMirror.Init) {
183
+ clearMarks(cm);
184
+ cm.off("change", onChange);
185
+ CodeMirror.off(cm.getWrapperElement(), "mouseover", cm.state.lint.onMouseOver);
186
+ delete cm.state.lint;
187
+ }
188
+
189
+ if (val) {
190
+ var gutters = cm.getOption("gutters"), hasLintGutter = false;
191
+ for (var i = 0; i < gutters.length; ++i) if (gutters[i] == GUTTER_ID) hasLintGutter = true;
192
+ var state = cm.state.lint = new LintState(cm, parseOptions(cm, val), hasLintGutter);
193
+ cm.on("change", onChange);
194
+ if (state.options.tooltips != false)
195
+ CodeMirror.on(cm.getWrapperElement(), "mouseover", state.onMouseOver);
196
+
197
+ startLinting(cm);
198
+ }
199
+ }
200
+
201
+ CodeMirror.defineOption("lintWith", false, optionHandler); // deprecated
202
+ CodeMirror.defineOption("lint", false, optionHandler); // deprecated
203
+ })();
assets/lib/codemirror/addon/merge/dep/diff_match_patch.js ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // From https://code.google.com/p/google-diff-match-patch/ , licensed under the Apache License 2.0
2
+ (function(){function diff_match_patch(){this.Diff_Timeout=1;this.Diff_EditCost=4;this.Match_Threshold=0.5;this.Match_Distance=1E3;this.Patch_DeleteThreshold=0.5;this.Patch_Margin=4;this.Match_MaxBits=32}
3
+ diff_match_patch.prototype.diff_main=function(a,b,c,d){"undefined"==typeof d&&(d=0>=this.Diff_Timeout?Number.MAX_VALUE:(new Date).getTime()+1E3*this.Diff_Timeout);if(null==a||null==b)throw Error("Null input. (diff_main)");if(a==b)return a?[[0,a]]:[];"undefined"==typeof c&&(c=!0);var e=c,f=this.diff_commonPrefix(a,b);c=a.substring(0,f);a=a.substring(f);b=b.substring(f);var f=this.diff_commonSuffix(a,b),g=a.substring(a.length-f);a=a.substring(0,a.length-f);b=b.substring(0,b.length-f);a=this.diff_compute_(a,
4
+ b,e,d);c&&a.unshift([0,c]);g&&a.push([0,g]);this.diff_cleanupMerge(a);return a};
5
+ diff_match_patch.prototype.diff_compute_=function(a,b,c,d){if(!a)return[[1,b]];if(!b)return[[-1,a]];var e=a.length>b.length?a:b,f=a.length>b.length?b:a,g=e.indexOf(f);return-1!=g?(c=[[1,e.substring(0,g)],[0,f],[1,e.substring(g+f.length)]],a.length>b.length&&(c[0][0]=c[2][0]=-1),c):1==f.length?[[-1,a],[1,b]]:(e=this.diff_halfMatch_(a,b))?(f=e[0],a=e[1],g=e[2],b=e[3],e=e[4],f=this.diff_main(f,g,c,d),c=this.diff_main(a,b,c,d),f.concat([[0,e]],c)):c&&100<a.length&&100<b.length?this.diff_lineMode_(a,b,
6
+ d):this.diff_bisect_(a,b,d)};
7
+ diff_match_patch.prototype.diff_lineMode_=function(a,b,c){var d=this.diff_linesToChars_(a,b);a=d.chars1;b=d.chars2;d=d.lineArray;a=this.diff_main(a,b,!1,c);this.diff_charsToLines_(a,d);this.diff_cleanupSemantic(a);a.push([0,""]);for(var e=d=b=0,f="",g="";b<a.length;){switch(a[b][0]){case 1:e++;g+=a[b][1];break;case -1:d++;f+=a[b][1];break;case 0:if(1<=d&&1<=e){a.splice(b-d-e,d+e);b=b-d-e;d=this.diff_main(f,g,!1,c);for(e=d.length-1;0<=e;e--)a.splice(b,0,d[e]);b+=d.length}d=e=0;g=f=""}b++}a.pop();return a};
8
+ diff_match_patch.prototype.diff_bisect_=function(a,b,c){for(var d=a.length,e=b.length,f=Math.ceil((d+e)/2),g=f,h=2*f,j=Array(h),i=Array(h),k=0;k<h;k++)j[k]=-1,i[k]=-1;j[g+1]=0;i[g+1]=0;for(var k=d-e,q=0!=k%2,r=0,t=0,p=0,w=0,v=0;v<f&&!((new Date).getTime()>c);v++){for(var n=-v+r;n<=v-t;n+=2){var l=g+n,m;m=n==-v||n!=v&&j[l-1]<j[l+1]?j[l+1]:j[l-1]+1;for(var s=m-n;m<d&&s<e&&a.charAt(m)==b.charAt(s);)m++,s++;j[l]=m;if(m>d)t+=2;else if(s>e)r+=2;else if(q&&(l=g+k-n,0<=l&&l<h&&-1!=i[l])){var u=d-i[l];if(m>=
9
+ u)return this.diff_bisectSplit_(a,b,m,s,c)}}for(n=-v+p;n<=v-w;n+=2){l=g+n;u=n==-v||n!=v&&i[l-1]<i[l+1]?i[l+1]:i[l-1]+1;for(m=u-n;u<d&&m<e&&a.charAt(d-u-1)==b.charAt(e-m-1);)u++,m++;i[l]=u;if(u>d)w+=2;else if(m>e)p+=2;else if(!q&&(l=g+k-n,0<=l&&(l<h&&-1!=j[l])&&(m=j[l],s=g+m-l,u=d-u,m>=u)))return this.diff_bisectSplit_(a,b,m,s,c)}}return[[-1,a],[1,b]]};
10
+ diff_match_patch.prototype.diff_bisectSplit_=function(a,b,c,d,e){var f=a.substring(0,c),g=b.substring(0,d);a=a.substring(c);b=b.substring(d);f=this.diff_main(f,g,!1,e);e=this.diff_main(a,b,!1,e);return f.concat(e)};
11
+ diff_match_patch.prototype.diff_linesToChars_=function(a,b){function c(a){for(var b="",c=0,f=-1,g=d.length;f<a.length-1;){f=a.indexOf("\n",c);-1==f&&(f=a.length-1);var r=a.substring(c,f+1),c=f+1;(e.hasOwnProperty?e.hasOwnProperty(r):void 0!==e[r])?b+=String.fromCharCode(e[r]):(b+=String.fromCharCode(g),e[r]=g,d[g++]=r)}return b}var d=[],e={};d[0]="";var f=c(a),g=c(b);return{chars1:f,chars2:g,lineArray:d}};
12
+ diff_match_patch.prototype.diff_charsToLines_=function(a,b){for(var c=0;c<a.length;c++){for(var d=a[c][1],e=[],f=0;f<d.length;f++)e[f]=b[d.charCodeAt(f)];a[c][1]=e.join("")}};diff_match_patch.prototype.diff_commonPrefix=function(a,b){if(!a||!b||a.charAt(0)!=b.charAt(0))return 0;for(var c=0,d=Math.min(a.length,b.length),e=d,f=0;c<e;)a.substring(f,e)==b.substring(f,e)?f=c=e:d=e,e=Math.floor((d-c)/2+c);return e};
13
+ diff_match_patch.prototype.diff_commonSuffix=function(a,b){if(!a||!b||a.charAt(a.length-1)!=b.charAt(b.length-1))return 0;for(var c=0,d=Math.min(a.length,b.length),e=d,f=0;c<e;)a.substring(a.length-e,a.length-f)==b.substring(b.length-e,b.length-f)?f=c=e:d=e,e=Math.floor((d-c)/2+c);return e};
14
+ diff_match_patch.prototype.diff_commonOverlap_=function(a,b){var c=a.length,d=b.length;if(0==c||0==d)return 0;c>d?a=a.substring(c-d):c<d&&(b=b.substring(0,c));c=Math.min(c,d);if(a==b)return c;for(var d=0,e=1;;){var f=a.substring(c-e),f=b.indexOf(f);if(-1==f)return d;e+=f;if(0==f||a.substring(c-e)==b.substring(0,e))d=e,e++}};
15
+ diff_match_patch.prototype.diff_halfMatch_=function(a,b){function c(a,b,c){for(var d=a.substring(c,c+Math.floor(a.length/4)),e=-1,g="",h,j,n,l;-1!=(e=b.indexOf(d,e+1));){var m=f.diff_commonPrefix(a.substring(c),b.substring(e)),s=f.diff_commonSuffix(a.substring(0,c),b.substring(0,e));g.length<s+m&&(g=b.substring(e-s,e)+b.substring(e,e+m),h=a.substring(0,c-s),j=a.substring(c+m),n=b.substring(0,e-s),l=b.substring(e+m))}return 2*g.length>=a.length?[h,j,n,l,g]:null}if(0>=this.Diff_Timeout)return null;
16
+ var d=a.length>b.length?a:b,e=a.length>b.length?b:a;if(4>d.length||2*e.length<d.length)return null;var f=this,g=c(d,e,Math.ceil(d.length/4)),d=c(d,e,Math.ceil(d.length/2)),h;if(!g&&!d)return null;h=d?g?g[4].length>d[4].length?g:d:d:g;var j;a.length>b.length?(g=h[0],d=h[1],e=h[2],j=h[3]):(e=h[0],j=h[1],g=h[2],d=h[3]);h=h[4];return[g,d,e,j,h]};
17
+ diff_match_patch.prototype.diff_cleanupSemantic=function(a){for(var b=!1,c=[],d=0,e=null,f=0,g=0,h=0,j=0,i=0;f<a.length;)0==a[f][0]?(c[d++]=f,g=j,h=i,i=j=0,e=a[f][1]):(1==a[f][0]?j+=a[f][1].length:i+=a[f][1].length,e&&(e.length<=Math.max(g,h)&&e.length<=Math.max(j,i))&&(a.splice(c[d-1],0,[-1,e]),a[c[d-1]+1][0]=1,d--,d--,f=0<d?c[d-1]:-1,i=j=h=g=0,e=null,b=!0)),f++;b&&this.diff_cleanupMerge(a);this.diff_cleanupSemanticLossless(a);for(f=1;f<a.length;){if(-1==a[f-1][0]&&1==a[f][0]){b=a[f-1][1];c=a[f][1];
18
+ d=this.diff_commonOverlap_(b,c);e=this.diff_commonOverlap_(c,b);if(d>=e){if(d>=b.length/2||d>=c.length/2)a.splice(f,0,[0,c.substring(0,d)]),a[f-1][1]=b.substring(0,b.length-d),a[f+1][1]=c.substring(d),f++}else if(e>=b.length/2||e>=c.length/2)a.splice(f,0,[0,b.substring(0,e)]),a[f-1][0]=1,a[f-1][1]=c.substring(0,c.length-e),a[f+1][0]=-1,a[f+1][1]=b.substring(e),f++;f++}f++}};
19
+ diff_match_patch.prototype.diff_cleanupSemanticLossless=function(a){function b(a,b){if(!a||!b)return 6;var c=a.charAt(a.length-1),d=b.charAt(0),e=c.match(diff_match_patch.nonAlphaNumericRegex_),f=d.match(diff_match_patch.nonAlphaNumericRegex_),g=e&&c.match(diff_match_patch.whitespaceRegex_),h=f&&d.match(diff_match_patch.whitespaceRegex_),c=g&&c.match(diff_match_patch.linebreakRegex_),d=h&&d.match(diff_match_patch.linebreakRegex_),i=c&&a.match(diff_match_patch.blanklineEndRegex_),j=d&&b.match(diff_match_patch.blanklineStartRegex_);
20
+ return i||j?5:c||d?4:e&&!g&&h?3:g||h?2:e||f?1:0}for(var c=1;c<a.length-1;){if(0==a[c-1][0]&&0==a[c+1][0]){var d=a[c-1][1],e=a[c][1],f=a[c+1][1],g=this.diff_commonSuffix(d,e);if(g)var h=e.substring(e.length-g),d=d.substring(0,d.length-g),e=h+e.substring(0,e.length-g),f=h+f;for(var g=d,h=e,j=f,i=b(d,e)+b(e,f);e.charAt(0)===f.charAt(0);){var d=d+e.charAt(0),e=e.substring(1)+f.charAt(0),f=f.substring(1),k=b(d,e)+b(e,f);k>=i&&(i=k,g=d,h=e,j=f)}a[c-1][1]!=g&&(g?a[c-1][1]=g:(a.splice(c-1,1),c--),a[c][1]=
21
+ h,j?a[c+1][1]=j:(a.splice(c+1,1),c--))}c++}};diff_match_patch.nonAlphaNumericRegex_=/[^a-zA-Z0-9]/;diff_match_patch.whitespaceRegex_=/\s/;diff_match_patch.linebreakRegex_=/[\r\n]/;diff_match_patch.blanklineEndRegex_=/\n\r?\n$/;diff_match_patch.blanklineStartRegex_=/^\r?\n\r?\n/;
22
+ diff_match_patch.prototype.diff_cleanupEfficiency=function(a){for(var b=!1,c=[],d=0,e=null,f=0,g=!1,h=!1,j=!1,i=!1;f<a.length;){if(0==a[f][0])a[f][1].length<this.Diff_EditCost&&(j||i)?(c[d++]=f,g=j,h=i,e=a[f][1]):(d=0,e=null),j=i=!1;else if(-1==a[f][0]?i=!0:j=!0,e&&(g&&h&&j&&i||e.length<this.Diff_EditCost/2&&3==g+h+j+i))a.splice(c[d-1],0,[-1,e]),a[c[d-1]+1][0]=1,d--,e=null,g&&h?(j=i=!0,d=0):(d--,f=0<d?c[d-1]:-1,j=i=!1),b=!0;f++}b&&this.diff_cleanupMerge(a)};
23
+ diff_match_patch.prototype.diff_cleanupMerge=function(a){a.push([0,""]);for(var b=0,c=0,d=0,e="",f="",g;b<a.length;)switch(a[b][0]){case 1:d++;f+=a[b][1];b++;break;case -1:c++;e+=a[b][1];b++;break;case 0:1<c+d?(0!==c&&0!==d&&(g=this.diff_commonPrefix(f,e),0!==g&&(0<b-c-d&&0==a[b-c-d-1][0]?a[b-c-d-1][1]+=f.substring(0,g):(a.splice(0,0,[0,f.substring(0,g)]),b++),f=f.substring(g),e=e.substring(g)),g=this.diff_commonSuffix(f,e),0!==g&&(a[b][1]=f.substring(f.length-g)+a[b][1],f=f.substring(0,f.length-
24
+ g),e=e.substring(0,e.length-g))),0===c?a.splice(b-d,c+d,[1,f]):0===d?a.splice(b-c,c+d,[-1,e]):a.splice(b-c-d,c+d,[-1,e],[1,f]),b=b-c-d+(c?1:0)+(d?1:0)+1):0!==b&&0==a[b-1][0]?(a[b-1][1]+=a[b][1],a.splice(b,1)):b++,c=d=0,f=e=""}""===a[a.length-1][1]&&a.pop();c=!1;for(b=1;b<a.length-1;)0==a[b-1][0]&&0==a[b+1][0]&&(a[b][1].substring(a[b][1].length-a[b-1][1].length)==a[b-1][1]?(a[b][1]=a[b-1][1]+a[b][1].substring(0,a[b][1].length-a[b-1][1].length),a[b+1][1]=a[b-1][1]+a[b+1][1],a.splice(b-1,1),c=!0):a[b][1].substring(0,
25
+ a[b+1][1].length)==a[b+1][1]&&(a[b-1][1]+=a[b+1][1],a[b][1]=a[b][1].substring(a[b+1][1].length)+a[b+1][1],a.splice(b+1,1),c=!0)),b++;c&&this.diff_cleanupMerge(a)};diff_match_patch.prototype.diff_xIndex=function(a,b){var c=0,d=0,e=0,f=0,g;for(g=0;g<a.length;g++){1!==a[g][0]&&(c+=a[g][1].length);-1!==a[g][0]&&(d+=a[g][1].length);if(c>b)break;e=c;f=d}return a.length!=g&&-1===a[g][0]?f:f+(b-e)};
26
+ diff_match_patch.prototype.diff_prettyHtml=function(a){for(var b=[],c=/&/g,d=/</g,e=/>/g,f=/\n/g,g=0;g<a.length;g++){var h=a[g][0],j=a[g][1],j=j.replace(c,"&amp;").replace(d,"&lt;").replace(e,"&gt;").replace(f,"&para;<br>");switch(h){case 1:b[g]='<ins style="background:#e6ffe6;">'+j+"</ins>";break;case -1:b[g]='<del style="background:#ffe6e6;">'+j+"</del>";break;case 0:b[g]="<span>"+j+"</span>"}}return b.join("")};
27
+ diff_match_patch.prototype.diff_text1=function(a){for(var b=[],c=0;c<a.length;c++)1!==a[c][0]&&(b[c]=a[c][1]);return b.join("")};diff_match_patch.prototype.diff_text2=function(a){for(var b=[],c=0;c<a.length;c++)-1!==a[c][0]&&(b[c]=a[c][1]);return b.join("")};diff_match_patch.prototype.diff_levenshtein=function(a){for(var b=0,c=0,d=0,e=0;e<a.length;e++){var f=a[e][0],g=a[e][1];switch(f){case 1:c+=g.length;break;case -1:d+=g.length;break;case 0:b+=Math.max(c,d),d=c=0}}return b+=Math.max(c,d)};
28
+ diff_match_patch.prototype.diff_toDelta=function(a){for(var b=[],c=0;c<a.length;c++)switch(a[c][0]){case 1:b[c]="+"+encodeURI(a[c][1]);break;case -1:b[c]="-"+a[c][1].length;break;case 0:b[c]="="+a[c][1].length}return b.join("\t").replace(/%20/g," ")};
29
+ diff_match_patch.prototype.diff_fromDelta=function(a,b){for(var c=[],d=0,e=0,f=b.split(/\t/g),g=0;g<f.length;g++){var h=f[g].substring(1);switch(f[g].charAt(0)){case "+":try{c[d++]=[1,decodeURI(h)]}catch(j){throw Error("Illegal escape in diff_fromDelta: "+h);}break;case "-":case "=":var i=parseInt(h,10);if(isNaN(i)||0>i)throw Error("Invalid number in diff_fromDelta: "+h);h=a.substring(e,e+=i);"="==f[g].charAt(0)?c[d++]=[0,h]:c[d++]=[-1,h];break;default:if(f[g])throw Error("Invalid diff operation in diff_fromDelta: "+
30
+ f[g]);}}if(e!=a.length)throw Error("Delta length ("+e+") does not equal source text length ("+a.length+").");return c};diff_match_patch.prototype.match_main=function(a,b,c){if(null==a||null==b||null==c)throw Error("Null input. (match_main)");c=Math.max(0,Math.min(c,a.length));return a==b?0:a.length?a.substring(c,c+b.length)==b?c:this.match_bitap_(a,b,c):-1};
31
+ diff_match_patch.prototype.match_bitap_=function(a,b,c){function d(a,d){var e=a/b.length,g=Math.abs(c-d);return!f.Match_Distance?g?1:e:e+g/f.Match_Distance}if(b.length>this.Match_MaxBits)throw Error("Pattern too long for this browser.");var e=this.match_alphabet_(b),f=this,g=this.Match_Threshold,h=a.indexOf(b,c);-1!=h&&(g=Math.min(d(0,h),g),h=a.lastIndexOf(b,c+b.length),-1!=h&&(g=Math.min(d(0,h),g)));for(var j=1<<b.length-1,h=-1,i,k,q=b.length+a.length,r,t=0;t<b.length;t++){i=0;for(k=q;i<k;)d(t,c+
32
+ k)<=g?i=k:q=k,k=Math.floor((q-i)/2+i);q=k;i=Math.max(1,c-k+1);var p=Math.min(c+k,a.length)+b.length;k=Array(p+2);for(k[p+1]=(1<<t)-1;p>=i;p--){var w=e[a.charAt(p-1)];k[p]=0===t?(k[p+1]<<1|1)&w:(k[p+1]<<1|1)&w|((r[p+1]|r[p])<<1|1)|r[p+1];if(k[p]&j&&(w=d(t,p-1),w<=g))if(g=w,h=p-1,h>c)i=Math.max(1,2*c-h);else break}if(d(t+1,c)>g)break;r=k}return h};
33
+ diff_match_patch.prototype.match_alphabet_=function(a){for(var b={},c=0;c<a.length;c++)b[a.charAt(c)]=0;for(c=0;c<a.length;c++)b[a.charAt(c)]|=1<<a.length-c-1;return b};
34
+ diff_match_patch.prototype.patch_addContext_=function(a,b){if(0!=b.length){for(var c=b.substring(a.start2,a.start2+a.length1),d=0;b.indexOf(c)!=b.lastIndexOf(c)&&c.length<this.Match_MaxBits-this.Patch_Margin-this.Patch_Margin;)d+=this.Patch_Margin,c=b.substring(a.start2-d,a.start2+a.length1+d);d+=this.Patch_Margin;(c=b.substring(a.start2-d,a.start2))&&a.diffs.unshift([0,c]);(d=b.substring(a.start2+a.length1,a.start2+a.length1+d))&&a.diffs.push([0,d]);a.start1-=c.length;a.start2-=c.length;a.length1+=
35
+ c.length+d.length;a.length2+=c.length+d.length}};
36
+ diff_match_patch.prototype.patch_make=function(a,b,c){var d;if("string"==typeof a&&"string"==typeof b&&"undefined"==typeof c)d=a,b=this.diff_main(d,b,!0),2<b.length&&(this.diff_cleanupSemantic(b),this.diff_cleanupEfficiency(b));else if(a&&"object"==typeof a&&"undefined"==typeof b&&"undefined"==typeof c)b=a,d=this.diff_text1(b);else if("string"==typeof a&&b&&"object"==typeof b&&"undefined"==typeof c)d=a;else if("string"==typeof a&&"string"==typeof b&&c&&"object"==typeof c)d=a,b=c;else throw Error("Unknown call format to patch_make.");
37
+ if(0===b.length)return[];c=[];a=new diff_match_patch.patch_obj;for(var e=0,f=0,g=0,h=d,j=0;j<b.length;j++){var i=b[j][0],k=b[j][1];!e&&0!==i&&(a.start1=f,a.start2=g);switch(i){case 1:a.diffs[e++]=b[j];a.length2+=k.length;d=d.substring(0,g)+k+d.substring(g);break;case -1:a.length1+=k.length;a.diffs[e++]=b[j];d=d.substring(0,g)+d.substring(g+k.length);break;case 0:k.length<=2*this.Patch_Margin&&e&&b.length!=j+1?(a.diffs[e++]=b[j],a.length1+=k.length,a.length2+=k.length):k.length>=2*this.Patch_Margin&&
38
+ e&&(this.patch_addContext_(a,h),c.push(a),a=new diff_match_patch.patch_obj,e=0,h=d,f=g)}1!==i&&(f+=k.length);-1!==i&&(g+=k.length)}e&&(this.patch_addContext_(a,h),c.push(a));return c};diff_match_patch.prototype.patch_deepCopy=function(a){for(var b=[],c=0;c<a.length;c++){var d=a[c],e=new diff_match_patch.patch_obj;e.diffs=[];for(var f=0;f<d.diffs.length;f++)e.diffs[f]=d.diffs[f].slice();e.start1=d.start1;e.start2=d.start2;e.length1=d.length1;e.length2=d.length2;b[c]=e}return b};
39
+ diff_match_patch.prototype.patch_apply=function(a,b){if(0==a.length)return[b,[]];a=this.patch_deepCopy(a);var c=this.patch_addPadding(a);b=c+b+c;this.patch_splitMax(a);for(var d=0,e=[],f=0;f<a.length;f++){var g=a[f].start2+d,h=this.diff_text1(a[f].diffs),j,i=-1;if(h.length>this.Match_MaxBits){if(j=this.match_main(b,h.substring(0,this.Match_MaxBits),g),-1!=j&&(i=this.match_main(b,h.substring(h.length-this.Match_MaxBits),g+h.length-this.Match_MaxBits),-1==i||j>=i))j=-1}else j=this.match_main(b,h,g);
40
+ if(-1==j)e[f]=!1,d-=a[f].length2-a[f].length1;else if(e[f]=!0,d=j-g,g=-1==i?b.substring(j,j+h.length):b.substring(j,i+this.Match_MaxBits),h==g)b=b.substring(0,j)+this.diff_text2(a[f].diffs)+b.substring(j+h.length);else if(g=this.diff_main(h,g,!1),h.length>this.Match_MaxBits&&this.diff_levenshtein(g)/h.length>this.Patch_DeleteThreshold)e[f]=!1;else{this.diff_cleanupSemanticLossless(g);for(var h=0,k,i=0;i<a[f].diffs.length;i++){var q=a[f].diffs[i];0!==q[0]&&(k=this.diff_xIndex(g,h));1===q[0]?b=b.substring(0,
41
+ j+k)+q[1]+b.substring(j+k):-1===q[0]&&(b=b.substring(0,j+k)+b.substring(j+this.diff_xIndex(g,h+q[1].length)));-1!==q[0]&&(h+=q[1].length)}}}b=b.substring(c.length,b.length-c.length);return[b,e]};
42
+ diff_match_patch.prototype.patch_addPadding=function(a){for(var b=this.Patch_Margin,c="",d=1;d<=b;d++)c+=String.fromCharCode(d);for(d=0;d<a.length;d++)a[d].start1+=b,a[d].start2+=b;var d=a[0],e=d.diffs;if(0==e.length||0!=e[0][0])e.unshift([0,c]),d.start1-=b,d.start2-=b,d.length1+=b,d.length2+=b;else if(b>e[0][1].length){var f=b-e[0][1].length;e[0][1]=c.substring(e[0][1].length)+e[0][1];d.start1-=f;d.start2-=f;d.length1+=f;d.length2+=f}d=a[a.length-1];e=d.diffs;0==e.length||0!=e[e.length-1][0]?(e.push([0,
43
+ c]),d.length1+=b,d.length2+=b):b>e[e.length-1][1].length&&(f=b-e[e.length-1][1].length,e[e.length-1][1]+=c.substring(0,f),d.length1+=f,d.length2+=f);return c};
44
+ diff_match_patch.prototype.patch_splitMax=function(a){for(var b=this.Match_MaxBits,c=0;c<a.length;c++)if(!(a[c].length1<=b)){var d=a[c];a.splice(c--,1);for(var e=d.start1,f=d.start2,g="";0!==d.diffs.length;){var h=new diff_match_patch.patch_obj,j=!0;h.start1=e-g.length;h.start2=f-g.length;""!==g&&(h.length1=h.length2=g.length,h.diffs.push([0,g]));for(;0!==d.diffs.length&&h.length1<b-this.Patch_Margin;){var g=d.diffs[0][0],i=d.diffs[0][1];1===g?(h.length2+=i.length,f+=i.length,h.diffs.push(d.diffs.shift()),
45
+ j=!1):-1===g&&1==h.diffs.length&&0==h.diffs[0][0]&&i.length>2*b?(h.length1+=i.length,e+=i.length,j=!1,h.diffs.push([g,i]),d.diffs.shift()):(i=i.substring(0,b-h.length1-this.Patch_Margin),h.length1+=i.length,e+=i.length,0===g?(h.length2+=i.length,f+=i.length):j=!1,h.diffs.push([g,i]),i==d.diffs[0][1]?d.diffs.shift():d.diffs[0][1]=d.diffs[0][1].substring(i.length))}g=this.diff_text2(h.diffs);g=g.substring(g.length-this.Patch_Margin);i=this.diff_text1(d.diffs).substring(0,this.Patch_Margin);""!==i&&
46
+ (h.length1+=i.length,h.length2+=i.length,0!==h.diffs.length&&0===h.diffs[h.diffs.length-1][0]?h.diffs[h.diffs.length-1][1]+=i:h.diffs.push([0,i]));j||a.splice(++c,0,h)}}};diff_match_patch.prototype.patch_toText=function(a){for(var b=[],c=0;c<a.length;c++)b[c]=a[c];return b.join("")};
47
+ diff_match_patch.prototype.patch_fromText=function(a){var b=[];if(!a)return b;a=a.split("\n");for(var c=0,d=/^@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@$/;c<a.length;){var e=a[c].match(d);if(!e)throw Error("Invalid patch string: "+a[c]);var f=new diff_match_patch.patch_obj;b.push(f);f.start1=parseInt(e[1],10);""===e[2]?(f.start1--,f.length1=1):"0"==e[2]?f.length1=0:(f.start1--,f.length1=parseInt(e[2],10));f.start2=parseInt(e[3],10);""===e[4]?(f.start2--,f.length2=1):"0"==e[4]?f.length2=0:(f.start2--,f.length2=
48
+ parseInt(e[4],10));for(c++;c<a.length;){e=a[c].charAt(0);try{var g=decodeURI(a[c].substring(1))}catch(h){throw Error("Illegal escape in patch_fromText: "+g);}if("-"==e)f.diffs.push([-1,g]);else if("+"==e)f.diffs.push([1,g]);else if(" "==e)f.diffs.push([0,g]);else if("@"==e)break;else if(""!==e)throw Error('Invalid patch mode "'+e+'" in: '+g);c++}}return b};diff_match_patch.patch_obj=function(){this.diffs=[];this.start2=this.start1=null;this.length2=this.length1=0};
49
+ diff_match_patch.patch_obj.prototype.toString=function(){var a,b;a=0===this.length1?this.start1+",0":1==this.length1?this.start1+1:this.start1+1+","+this.length1;b=0===this.length2?this.start2+",0":1==this.length2?this.start2+1:this.start2+1+","+this.length2;a=["@@ -"+a+" +"+b+" @@\n"];var c;for(b=0;b<this.diffs.length;b++){switch(this.diffs[b][0]){case 1:c="+";break;case -1:c="-";break;case 0:c=" "}a[b+1]=c+encodeURI(this.diffs[b][1])+"\n"}return a.join("").replace(/%20/g," ")};
50
+ this.diff_match_patch=diff_match_patch;this.DIFF_DELETE=-1;this.DIFF_INSERT=1;this.DIFF_EQUAL=0;})()
assets/lib/codemirror/addon/merge/merge.css ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .CodeMirror-merge {
2
+ position: relative;
3
+ border: 1px solid #ddd;
4
+ white-space: pre;
5
+ }
6
+
7
+ .CodeMirror-merge, .CodeMirror-merge .CodeMirror {
8
+ height: 350px;
9
+ }
10
+
11
+ .CodeMirror-merge-2pane .CodeMirror-merge-pane { width: 47%; }
12
+ .CodeMirror-merge-2pane .CodeMirror-merge-gap { width: 6%; }
13
+ .CodeMirror-merge-3pane .CodeMirror-merge-pane { width: 31%; }
14
+ .CodeMirror-merge-3pane .CodeMirror-merge-gap { width: 3.5%; }
15
+
16
+ .CodeMirror-merge-pane {
17
+ display: inline-block;
18
+ white-space: normal;
19
+ vertical-align: top;
20
+ }
21
+ .CodeMirror-merge-pane-rightmost {
22
+ position: absolute;
23
+ right: 0px;
24
+ z-index: 1;
25
+ }
26
+
27
+ .CodeMirror-merge-gap {
28
+ z-index: 2;
29
+ display: inline-block;
30
+ height: 100%;
31
+ -moz-box-sizing: border-box;
32
+ box-sizing: border-box;
33
+ overflow: hidden;
34
+ border-left: 1px solid #ddd;
35
+ border-right: 1px solid #ddd;
36
+ position: relative;
37
+ background: #f8f8f8;
38
+ }
39
+
40
+ .CodeMirror-merge-scrolllock-wrap {
41
+ position: absolute;
42
+ bottom: 0; left: 50%;
43
+ }
44
+ .CodeMirror-merge-scrolllock {
45
+ position: relative;
46
+ left: -50%;
47
+ cursor: pointer;
48
+ color: #555;
49
+ line-height: 1;
50
+ }
51
+
52
+ .CodeMirror-merge-copybuttons-left, .CodeMirror-merge-copybuttons-right {
53
+ position: absolute;
54
+ left: 0; top: 0;
55
+ right: 0; bottom: 0;
56
+ line-height: 1;
57
+ }
58
+
59
+ .CodeMirror-merge-copy {
60
+ position: absolute;
61
+ cursor: pointer;
62
+ color: #44c;
63
+ }
64
+
65
+ .CodeMirror-merge-copybuttons-left .CodeMirror-merge-copy { left: 2px; }
66
+ .CodeMirror-merge-copybuttons-right .CodeMirror-merge-copy { right: 2px; }
67
+
68
+ .CodeMirror-merge-r-inserted, .CodeMirror-merge-l-inserted {
69
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAACCAYAAACddGYaAAAAGUlEQVQI12MwuCXy3+CWyH8GBgYGJgYkAABZbAQ9ELXurwAAAABJRU5ErkJggg==);
70
+ background-position: bottom left;
71
+ background-repeat: repeat-x;
72
+ }
73
+
74
+ .CodeMirror-merge-r-deleted, .CodeMirror-merge-l-deleted {
75
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAACCAYAAACddGYaAAAAGUlEQVQI12M4Kyb2/6yY2H8GBgYGJgYkAABURgPz6Ks7wQAAAABJRU5ErkJggg==);
76
+ background-position: bottom left;
77
+ background-repeat: repeat-x;
78
+ }
79
+
80
+ .CodeMirror-merge-r-chunk { background: #ffffe0; }
81
+ .CodeMirror-merge-r-chunk-start { border-top: 1px solid #ee8; }
82
+ .CodeMirror-merge-r-chunk-end { border-bottom: 1px solid #ee8; }
83
+ .CodeMirror-merge-r-connect { fill: #ffffe0; stroke: #ee8; stroke-width: 1px; }
84
+
85
+ .CodeMirror-merge-l-chunk { background: #eef; }
86
+ .CodeMirror-merge-l-chunk-start { border-top: 1px solid #88e; }
87
+ .CodeMirror-merge-l-chunk-end { border-bottom: 1px solid #88e; }
88
+ .CodeMirror-merge-l-connect { fill: #eef; stroke: #88e; stroke-width: 1px; }
89
+
90
+ .CodeMirror-merge-l-chunk.CodeMirror-merge-r-chunk { background: #dfd; }
91
+ .CodeMirror-merge-l-chunk-start.CodeMirror-merge-r-chunk-start { border-top: 1px solid #4e4; }
92
+ .CodeMirror-merge-l-chunk-end.CodeMirror-merge-r-chunk-end { border-bottom: 1px solid #4e4; }
assets/lib/codemirror/addon/merge/merge.js ADDED
@@ -0,0 +1,473 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function() {
2
+ "use strict";
3
+
4
+ var Pos = CodeMirror.Pos;
5
+ var svgNS = "http://www.w3.org/2000/svg";
6
+
7
+ function DiffView(mv, type) {
8
+ this.mv = mv;
9
+ this.type = type;
10
+ this.classes = type == "left"
11
+ ? {chunk: "CodeMirror-merge-l-chunk",
12
+ start: "CodeMirror-merge-l-chunk-start",
13
+ end: "CodeMirror-merge-l-chunk-end",
14
+ insert: "CodeMirror-merge-l-inserted",
15
+ del: "CodeMirror-merge-l-deleted",
16
+ connect: "CodeMirror-merge-l-connect"}
17
+ : {chunk: "CodeMirror-merge-r-chunk",
18
+ start: "CodeMirror-merge-r-chunk-start",
19
+ end: "CodeMirror-merge-r-chunk-end",
20
+ insert: "CodeMirror-merge-r-inserted",
21
+ del: "CodeMirror-merge-r-deleted",
22
+ connect: "CodeMirror-merge-r-connect"};
23
+ }
24
+
25
+ DiffView.prototype = {
26
+ constructor: DiffView,
27
+ init: function(pane, orig, options) {
28
+ this.edit = this.mv.edit;
29
+ this.orig = CodeMirror(pane, copyObj({value: orig, readOnly: true}, copyObj(options)));
30
+
31
+ this.diff = getDiff(orig, options.value);
32
+ this.diffOutOfDate = false;
33
+
34
+ this.showDifferences = options.showDifferences !== false;
35
+ this.forceUpdate = registerUpdate(this);
36
+ setScrollLock(this, true, false);
37
+ registerScroll(this);
38
+ },
39
+ setShowDifferences: function(val) {
40
+ val = val !== false;
41
+ if (val != this.showDifferences) {
42
+ this.showDifferences = val;
43
+ this.forceUpdate("full");
44
+ }
45
+ }
46
+ };
47
+
48
+ function registerUpdate(dv) {
49
+ var edit = {from: 0, to: 0, marked: []};
50
+ var orig = {from: 0, to: 0, marked: []};
51
+ var debounceChange;
52
+ function update(mode) {
53
+ if (mode == "full") {
54
+ if (dv.svg) clear(dv.svg);
55
+ clear(dv.copyButtons);
56
+ clearMarks(dv.edit, edit.marked, dv.classes);
57
+ clearMarks(dv.orig, orig.marked, dv.classes);
58
+ edit.from = edit.to = orig.from = orig.to = 0;
59
+ }
60
+ if (dv.diffOutOfDate) {
61
+ dv.diff = getDiff(dv.orig.getValue(), dv.edit.getValue());
62
+ dv.diffOutOfDate = false;
63
+ CodeMirror.signal(dv.edit, "updateDiff", dv.diff);
64
+ }
65
+ if (dv.showDifferences) {
66
+ updateMarks(dv.edit, dv.diff, edit, DIFF_INSERT, dv.classes);
67
+ updateMarks(dv.orig, dv.diff, orig, DIFF_DELETE, dv.classes);
68
+ }
69
+ drawConnectors(dv);
70
+ }
71
+ function set(slow) {
72
+ clearTimeout(debounceChange);
73
+ debounceChange = setTimeout(update, slow == true ? 250 : 100);
74
+ }
75
+ function change() {
76
+ if (!dv.diffOutOfDate) {
77
+ dv.diffOutOfDate = true;
78
+ edit.from = edit.to = orig.from = orig.to = 0;
79
+ }
80
+ set(true);
81
+ }
82
+ dv.edit.on("change", change);
83
+ dv.orig.on("change", change);
84
+ dv.edit.on("viewportChange", set);
85
+ dv.orig.on("viewportChange", set);
86
+ update();
87
+ return update;
88
+ }
89
+
90
+ function registerScroll(dv) {
91
+ dv.edit.on("scroll", function() {
92
+ syncScroll(dv, DIFF_INSERT) && drawConnectors(dv);
93
+ });
94
+ dv.orig.on("scroll", function() {
95
+ syncScroll(dv, DIFF_DELETE) && drawConnectors(dv);
96
+ });
97
+ }
98
+
99
+ function syncScroll(dv, type) {
100
+ // Change handler will do a refresh after a timeout when diff is out of date
101
+ if (dv.diffOutOfDate) return false;
102
+ if (!dv.lockScroll) return true;
103
+ var editor, other, now = +new Date;
104
+ if (type == DIFF_INSERT) { editor = dv.edit; other = dv.orig; }
105
+ else { editor = dv.orig; other = dv.edit; }
106
+ // Don't take action if the position of this editor was recently set
107
+ // (to prevent feedback loops)
108
+ if (editor.state.scrollSetBy == dv && (editor.state.scrollSetAt || 0) + 50 > now) return false;
109
+
110
+ var sInfo = editor.getScrollInfo(), halfScreen = .5 * sInfo.clientHeight, midY = sInfo.top + halfScreen;
111
+ var mid = editor.lineAtHeight(midY, "local");
112
+ var around = chunkBoundariesAround(dv.diff, mid, type == DIFF_INSERT);
113
+ var off = getOffsets(editor, type == DIFF_INSERT ? around.edit : around.orig);
114
+ var offOther = getOffsets(other, type == DIFF_INSERT ? around.orig : around.edit);
115
+ var ratio = (midY - off.top) / (off.bot - off.top);
116
+ var targetPos = (offOther.top - halfScreen) + ratio * (offOther.bot - offOther.top);
117
+
118
+ var botDist, mix;
119
+ // Some careful tweaking to make sure no space is left out of view
120
+ // when scrolling to top or bottom.
121
+ if (targetPos > sInfo.top && (mix = sInfo.top / halfScreen) < 1) {
122
+ targetPos = targetPos * mix + sInfo.top * (1 - mix);
123
+ } else if ((botDist = sInfo.height - sInfo.clientHeight - sInfo.top) < halfScreen) {
124
+ var otherInfo = other.getScrollInfo();
125
+ var botDistOther = otherInfo.height - otherInfo.clientHeight - targetPos;
126
+ if (botDistOther > botDist && (mix = botDist / halfScreen) < 1)
127
+ targetPos = targetPos * mix + (otherInfo.height - otherInfo.clientHeight - botDist) * (1 - mix);
128
+ }
129
+
130
+ other.scrollTo(sInfo.left, targetPos);
131
+ other.state.scrollSetAt = now;
132
+ other.state.scrollSetBy = dv;
133
+ return true;
134
+ }
135
+
136
+ function getOffsets(editor, around) {
137
+ var bot = around.after;
138
+ if (bot == null) bot = editor.lastLine() + 1;
139
+ return {top: editor.heightAtLine(around.before || 0, "local"),
140
+ bot: editor.heightAtLine(bot, "local")};
141
+ }
142
+
143
+ function setScrollLock(dv, val, action) {
144
+ dv.lockScroll = val;
145
+ if (val && action != false) syncScroll(dv, DIFF_INSERT) && drawConnectors(dv);
146
+ dv.lockButton.innerHTML = val ? "\u21db\u21da" : "\u21db&nbsp;&nbsp;\u21da";
147
+ }
148
+
149
+ // Updating the marks for editor content
150
+
151
+ function clearMarks(editor, arr, classes) {
152
+ for (var i = 0; i < arr.length; ++i) {
153
+ var mark = arr[i];
154
+ if (mark instanceof CodeMirror.TextMarker) {
155
+ mark.clear();
156
+ } else {
157
+ editor.removeLineClass(mark, "background", classes.chunk);
158
+ editor.removeLineClass(mark, "background", classes.start);
159
+ editor.removeLineClass(mark, "background", classes.end);
160
+ }
161
+ }
162
+ arr.length = 0;
163
+ }
164
+
165
+ // FIXME maybe add a margin around viewport to prevent too many updates
166
+ function updateMarks(editor, diff, state, type, classes) {
167
+ var vp = editor.getViewport();
168
+ editor.operation(function() {
169
+ if (state.from == state.to || vp.from - state.to > 20 || state.from - vp.to > 20) {
170
+ clearMarks(editor, state.marked, classes);
171
+ markChanges(editor, diff, type, state.marked, vp.from, vp.to, classes);
172
+ state.from = vp.from; state.to = vp.to;
173
+ } else {
174
+ if (vp.from < state.from) {
175
+ markChanges(editor, diff, type, state.marked, vp.from, state.from, classes);
176
+ state.from = vp.from;
177
+ }
178
+ if (vp.to > state.to) {
179
+ markChanges(editor, diff, type, state.marked, state.to, vp.to, classes);
180
+ state.to = vp.to;
181
+ }
182
+ }
183
+ });
184
+ }
185
+
186
+ function markChanges(editor, diff, type, marks, from, to, classes) {
187
+ var pos = Pos(0, 0);
188
+ var top = Pos(from, 0), bot = editor.clipPos(Pos(to - 1));
189
+ var cls = type == DIFF_DELETE ? classes.del : classes.insert;
190
+ function markChunk(start, end) {
191
+ var bfrom = Math.max(from, start), bto = Math.min(to, end);
192
+ for (var i = bfrom; i < bto; ++i) {
193
+ var line = editor.addLineClass(i, "background", classes.chunk);
194
+ if (i == start) editor.addLineClass(line, "background", classes.start);
195
+ if (i == end - 1) editor.addLineClass(line, "background", classes.end);
196
+ marks.push(line);
197
+ }
198
+ // When the chunk is empty, make sure a horizontal line shows up
199
+ if (start == end && bfrom == end && bto == end) {
200
+ if (bfrom)
201
+ marks.push(editor.addLineClass(bfrom - 1, "background", classes.end));
202
+ else
203
+ marks.push(editor.addLineClass(bfrom, "background", classes.start));
204
+ }
205
+ }
206
+
207
+ var chunkStart = 0;
208
+ for (var i = 0; i < diff.length; ++i) {
209
+ var part = diff[i], tp = part[0], str = part[1];
210
+ if (tp == DIFF_EQUAL) {
211
+ var cleanFrom = pos.line + (startOfLineClean(diff, i) ? 0 : 1);
212
+ moveOver(pos, str);
213
+ var cleanTo = pos.line + (endOfLineClean(diff, i) ? 1 : 0);
214
+ if (cleanTo > cleanFrom) {
215
+ if (i) markChunk(chunkStart, cleanFrom);
216
+ chunkStart = cleanTo;
217
+ }
218
+ } else {
219
+ if (tp == type) {
220
+ var end = moveOver(pos, str, true);
221
+ var a = posMax(top, pos), b = posMin(bot, end);
222
+ if (!posEq(a, b))
223
+ marks.push(editor.markText(a, b, {className: cls}));
224
+ pos = end;
225
+ }
226
+ }
227
+ }
228
+ if (chunkStart <= pos.line) markChunk(chunkStart, pos.line + 1);
229
+ }
230
+
231
+ // Updating the gap between editor and original
232
+
233
+ function drawConnectors(dv) {
234
+ if (!dv.showDifferences) return;
235
+
236
+ if (dv.svg) {
237
+ clear(dv.svg);
238
+ var w = dv.gap.offsetWidth;
239
+ attrs(dv.svg, "width", w, "height", dv.gap.offsetHeight);
240
+ }
241
+ clear(dv.copyButtons);
242
+
243
+ var flip = dv.type == "left";
244
+ var vpEdit = dv.edit.getViewport(), vpOrig = dv.orig.getViewport();
245
+ var sTopEdit = dv.edit.getScrollInfo().top, sTopOrig = dv.orig.getScrollInfo().top;
246
+ iterateChunks(dv.diff, function(topOrig, botOrig, topEdit, botEdit) {
247
+ if (topEdit > vpEdit.to || botEdit < vpEdit.from ||
248
+ topOrig > vpOrig.to || botOrig < vpOrig.from)
249
+ return;
250
+ var topLpx = dv.orig.heightAtLine(topOrig, "local") - sTopOrig, top = topLpx;
251
+ if (dv.svg) {
252
+ var topRpx = dv.edit.heightAtLine(topEdit, "local") - sTopEdit;
253
+ if (flip) { var tmp = topLpx; topLpx = topRpx; topRpx = tmp; }
254
+ var botLpx = dv.orig.heightAtLine(botOrig, "local") - sTopOrig;
255
+ var botRpx = dv.edit.heightAtLine(botEdit, "local") - sTopEdit;
256
+ if (flip) { var tmp = botLpx; botLpx = botRpx; botRpx = tmp; }
257
+ var curveTop = " C " + w/2 + " " + topRpx + " " + w/2 + " " + topLpx + " " + (w + 2) + " " + topLpx;
258
+ var curveBot = " C " + w/2 + " " + botLpx + " " + w/2 + " " + botRpx + " -1 " + botRpx;
259
+ attrs(dv.svg.appendChild(document.createElementNS(svgNS, "path")),
260
+ "d", "M -1 " + topRpx + curveTop + " L " + (w + 2) + " " + botLpx + curveBot + " z",
261
+ "class", dv.classes.connect);
262
+ }
263
+ var copy = dv.copyButtons.appendChild(elt("div", dv.type == "left" ? "\u21dd" : "\u21dc",
264
+ "CodeMirror-merge-copy"));
265
+ copy.title = "Revert chunk";
266
+ copy.chunk = {topEdit: topEdit, botEdit: botEdit, topOrig: topOrig, botOrig: botOrig};
267
+ copy.style.top = top + "px";
268
+ });
269
+ }
270
+
271
+ function copyChunk(dv, chunk) {
272
+ if (dv.diffOutOfDate) return;
273
+ dv.edit.replaceRange(dv.orig.getRange(Pos(chunk.topOrig, 0), Pos(chunk.botOrig, 0)),
274
+ Pos(chunk.topEdit, 0), Pos(chunk.botEdit, 0));
275
+ }
276
+
277
+ // Merge view, containing 0, 1, or 2 diff views.
278
+
279
+ var MergeView = CodeMirror.MergeView = function(node, options) {
280
+ if (!(this instanceof MergeView)) return new MergeView(node, options);
281
+
282
+ var origLeft = options.origLeft, origRight = options.origRight == null ? options.orig : options.origRight;
283
+ var hasLeft = origLeft != null, hasRight = origRight != null;
284
+ var panes = 1 + (hasLeft ? 1 : 0) + (hasRight ? 1 : 0);
285
+ var wrap = [], left = this.left = null, right = this.right = null;
286
+
287
+ if (hasLeft) {
288
+ left = this.left = new DiffView(this, "left");
289
+ var leftPane = elt("div", null, "CodeMirror-merge-pane");
290
+ wrap.push(leftPane);
291
+ wrap.push(buildGap(left));
292
+ }
293
+
294
+ var editPane = elt("div", null, "CodeMirror-merge-pane");
295
+ wrap.push(editPane);
296
+
297
+ if (hasRight) {
298
+ right = this.right = new DiffView(this, "right");
299
+ wrap.push(buildGap(right));
300
+ var rightPane = elt("div", null, "CodeMirror-merge-pane");
301
+ wrap.push(rightPane);
302
+ }
303
+
304
+ (hasRight ? rightPane : editPane).className += " CodeMirror-merge-pane-rightmost";
305
+
306
+ wrap.push(elt("div", null, null, "height: 0; clear: both;"));
307
+ var wrapElt = this.wrap = node.appendChild(elt("div", wrap, "CodeMirror-merge CodeMirror-merge-" + panes + "pane"));
308
+ this.edit = CodeMirror(editPane, copyObj(options));
309
+
310
+ if (left) left.init(leftPane, origLeft, options);
311
+ if (right) right.init(rightPane, origRight, options);
312
+
313
+ var onResize = function() {
314
+ if (left) drawConnectors(left);
315
+ if (right) drawConnectors(right);
316
+ };
317
+ CodeMirror.on(window, "resize", onResize);
318
+ var resizeInterval = setInterval(function() {
319
+ for (var p = wrapElt.parentNode; p && p != document.body; p = p.parentNode) {}
320
+ if (!p) { clearInterval(resizeInterval); CodeMirror.off(window, "resize", onResize); }
321
+ }, 5000);
322
+ };
323
+
324
+ function buildGap(dv) {
325
+ var lock = dv.lockButton = elt("div", null, "CodeMirror-merge-scrolllock");
326
+ lock.title = "Toggle locked scrolling";
327
+ var lockWrap = elt("div", [lock], "CodeMirror-merge-scrolllock-wrap");
328
+ CodeMirror.on(lock, "click", function() { setScrollLock(dv, !dv.lockScroll); });
329
+ dv.copyButtons = elt("div", null, "CodeMirror-merge-copybuttons-" + dv.type);
330
+ CodeMirror.on(dv.copyButtons, "click", function(e) {
331
+ var node = e.target || e.srcElement;
332
+ if (node.chunk) copyChunk(dv, node.chunk);
333
+ });
334
+ var gapElts = [dv.copyButtons, lockWrap];
335
+ var svg = document.createElementNS && document.createElementNS(svgNS, "svg");
336
+ if (svg && !svg.createSVGRect) svg = null;
337
+ dv.svg = svg;
338
+ if (svg) gapElts.push(svg);
339
+
340
+ return dv.gap = elt("div", gapElts, "CodeMirror-merge-gap");
341
+ }
342
+
343
+ MergeView.prototype = {
344
+ constuctor: MergeView,
345
+ editor: function() { return this.edit; },
346
+ rightOriginal: function() { return this.right && this.right.orig; },
347
+ leftOriginal: function() { return this.left && this.left.orig; },
348
+ setShowDifferences: function(val) {
349
+ if (this.right) this.right.setShowDifferences(val);
350
+ if (this.left) this.left.setShowDifferences(val);
351
+ }
352
+ };
353
+
354
+ // Operations on diffs
355
+
356
+ var dmp = new diff_match_patch();
357
+ function getDiff(a, b) {
358
+ var diff = dmp.diff_main(a, b);
359
+ dmp.diff_cleanupSemantic(diff);
360
+ // The library sometimes leaves in empty parts, which confuse the algorithm
361
+ for (var i = 0; i < diff.length; ++i) {
362
+ var part = diff[i];
363
+ if (!part[1]) {
364
+ diff.splice(i--, 1);
365
+ } else if (i && diff[i - 1][0] == part[0]) {
366
+ diff.splice(i--, 1);
367
+ diff[i][1] += part[1];
368
+ }
369
+ }
370
+ return diff;
371
+ }
372
+
373
+ function iterateChunks(diff, f) {
374
+ var startEdit = 0, startOrig = 0;
375
+ var edit = Pos(0, 0), orig = Pos(0, 0);
376
+ for (var i = 0; i < diff.length; ++i) {
377
+ var part = diff[i], tp = part[0];
378
+ if (tp == DIFF_EQUAL) {
379
+ var startOff = startOfLineClean(diff, i) ? 0 : 1;
380
+ var cleanFromEdit = edit.line + startOff, cleanFromOrig = orig.line + startOff;
381
+ moveOver(edit, part[1], null, orig);
382
+ var endOff = endOfLineClean(diff, i) ? 1 : 0;
383
+ var cleanToEdit = edit.line + endOff, cleanToOrig = orig.line + endOff;
384
+ if (cleanToEdit > cleanFromEdit) {
385
+ if (i) f(startOrig, cleanFromOrig, startEdit, cleanFromEdit);
386
+ startEdit = cleanToEdit; startOrig = cleanToOrig;
387
+ }
388
+ } else {
389
+ moveOver(tp == DIFF_INSERT ? edit : orig, part[1]);
390
+ }
391
+ }
392
+ if (startEdit <= edit.line || startOrig <= orig.line)
393
+ f(startOrig, orig.line + 1, startEdit, edit.line + 1);
394
+ }
395
+
396
+ function endOfLineClean(diff, i) {
397
+ if (i == diff.length - 1) return true;
398
+ var next = diff[i + 1][1];
399
+ if (next.length == 1 || next.charCodeAt(0) != 10) return false;
400
+ if (i == diff.length - 2) return true;
401
+ next = diff[i + 2][1];
402
+ return next.length > 1 && next.charCodeAt(0) == 10;
403
+ }
404
+
405
+ function startOfLineClean(diff, i) {
406
+ if (i == 0) return true;
407
+ var last = diff[i - 1][1];
408
+ if (last.charCodeAt(last.length - 1) != 10) return false;
409
+ if (i == 1) return true;
410
+ last = diff[i - 2][1];
411
+ return last.charCodeAt(last.length - 1) == 10;
412
+ }
413
+
414
+ function chunkBoundariesAround(diff, n, nInEdit) {
415
+ var beforeE, afterE, beforeO, afterO;
416
+ iterateChunks(diff, function(fromOrig, toOrig, fromEdit, toEdit) {
417
+ var fromLocal = nInEdit ? fromEdit : fromOrig;
418
+ var toLocal = nInEdit ? toEdit : toOrig;
419
+ if (afterE == null) {
420
+ if (fromLocal > n) { afterE = fromEdit; afterO = fromOrig; }
421
+ else if (toLocal > n) { afterE = toEdit; afterO = toOrig; }
422
+ }
423
+ if (toLocal <= n) { beforeE = toEdit; beforeO = toOrig; }
424
+ else if (fromLocal <= n) { beforeE = fromEdit; beforeO = fromOrig; }
425
+ });
426
+ return {edit: {before: beforeE, after: afterE}, orig: {before: beforeO, after: afterO}};
427
+ }
428
+
429
+ // General utilities
430
+
431
+ function elt(tag, content, className, style) {
432
+ var e = document.createElement(tag);
433
+ if (className) e.className = className;
434
+ if (style) e.style.cssText = style;
435
+ if (typeof content == "string") e.appendChild(document.createTextNode(content));
436
+ else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]);
437
+ return e;
438
+ }
439
+
440
+ function clear(node) {
441
+ for (var count = node.childNodes.length; count > 0; --count)
442
+ node.removeChild(node.firstChild);
443
+ }
444
+
445
+ function attrs(elt) {
446
+ for (var i = 1; i < arguments.length; i += 2)
447
+ elt.setAttribute(arguments[i], arguments[i+1]);
448
+ }
449
+
450
+ function copyObj(obj, target) {
451
+ if (!target) target = {};
452
+ for (var prop in obj) if (obj.hasOwnProperty(prop)) target[prop] = obj[prop];
453
+ return target;
454
+ }
455
+
456
+ function moveOver(pos, str, copy, other) {
457
+ var out = copy ? Pos(pos.line, pos.ch) : pos, at = 0;
458
+ for (;;) {
459
+ var nl = str.indexOf("\n", at);
460
+ if (nl == -1) break;
461
+ ++out.line;
462
+ if (other) ++other.line;
463
+ at = nl + 1;
464
+ }
465
+ out.ch = (at ? 0 : out.ch) + (str.length - at);
466
+ if (other) other.ch = (at ? 0 : other.ch) + (str.length - at);
467
+ return out;
468
+ }
469
+
470
+ function posMin(a, b) { return (a.line - b.line || a.ch - b.ch) < 0 ? a : b; }
471
+ function posMax(a, b) { return (a.line - b.line || a.ch - b.ch) > 0 ? a : b; }
472
+ function posEq(a, b) { return a.line == b.line && a.ch == b.ch; }
473
+ })();
assets/lib/codemirror/addon/mode/loadmode.js ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function() {
2
+ if (!CodeMirror.modeURL) CodeMirror.modeURL = "../mode/%N/%N.js";
3
+
4
+ var loading = {};
5
+ function splitCallback(cont, n) {
6
+ var countDown = n;
7
+ return function() { if (--countDown == 0) cont(); };
8
+ }
9
+ function ensureDeps(mode, cont) {
10
+ var deps = CodeMirror.modes[mode].dependencies;
11
+ if (!deps) return cont();
12
+ var missing = [];
13
+ for (var i = 0; i < deps.length; ++i) {
14
+ if (!CodeMirror.modes.hasOwnProperty(deps[i]))
15
+ missing.push(deps[i]);
16
+ }
17
+ if (!missing.length) return cont();
18
+ var split = splitCallback(cont, missing.length);
19
+ for (var i = 0; i < missing.length; ++i)
20
+ CodeMirror.requireMode(missing[i], split);
21
+ }
22
+
23
+ CodeMirror.requireMode = function(mode, cont) {
24
+ if (typeof mode != "string") mode = mode.name;
25
+ if (CodeMirror.modes.hasOwnProperty(mode)) return ensureDeps(mode, cont);
26
+ if (loading.hasOwnProperty(mode)) return loading[mode].push(cont);
27
+
28
+ var script = document.createElement("script");
29
+ script.src = CodeMirror.modeURL.replace(/%N/g, mode);
30
+ var others = document.getElementsByTagName("script")[0];
31
+ others.parentNode.insertBefore(script, others);
32
+ var list = loading[mode] = [cont];
33
+ var count = 0, poll = setInterval(function() {
34
+ if (++count > 100) return clearInterval(poll);
35
+ if (CodeMirror.modes.hasOwnProperty(mode)) {
36
+ clearInterval(poll);
37
+ loading[mode] = null;
38
+ ensureDeps(mode, function() {
39
+ for (var i = 0; i < list.length; ++i) list[i]();
40
+ });
41
+ }
42
+ }, 200);
43
+ };
44
+
45
+ CodeMirror.autoLoadMode = function(instance, mode) {
46
+ if (!CodeMirror.modes.hasOwnProperty(mode))
47
+ CodeMirror.requireMode(mode, function() {
48
+ instance.setOption("mode", instance.getOption("mode"));
49
+ });
50
+ };
51
+ }());
assets/lib/codemirror/addon/mode/multiplex.js ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ CodeMirror.multiplexingMode = function(outer /*, others */) {
2
+ // Others should be {open, close, mode [, delimStyle] [, innerStyle]} objects
3
+ var others = Array.prototype.slice.call(arguments, 1);
4
+ var n_others = others.length;
5
+
6
+ function indexOf(string, pattern, from) {
7
+ if (typeof pattern == "string") return string.indexOf(pattern, from);
8
+ var m = pattern.exec(from ? string.slice(from) : string);
9
+ return m ? m.index + from : -1;
10
+ }
11
+
12
+ return {
13
+ startState: function() {
14
+ return {
15
+ outer: CodeMirror.startState(outer),
16
+ innerActive: null,
17
+ inner: null
18
+ };
19
+ },
20
+
21
+ copyState: function(state) {
22
+ return {
23
+ outer: CodeMirror.copyState(outer, state.outer),
24
+ innerActive: state.innerActive,
25
+ inner: state.innerActive && CodeMirror.copyState(state.innerActive.mode, state.inner)
26
+ };
27
+ },
28
+
29
+ token: function(stream, state) {
30
+ if (!state.innerActive) {
31
+ var cutOff = Infinity, oldContent = stream.string;
32
+ for (var i = 0; i < n_others; ++i) {
33
+ var other = others[i];
34
+ var found = indexOf(oldContent, other.open, stream.pos);
35
+ if (found == stream.pos) {
36
+ stream.match(other.open);
37
+ state.innerActive = other;
38
+ state.inner = CodeMirror.startState(other.mode, outer.indent ? outer.indent(state.outer, "") : 0);
39
+ return other.delimStyle;
40
+ } else if (found != -1 && found < cutOff) {
41
+ cutOff = found;
42
+ }
43
+ }
44
+ if (cutOff != Infinity) stream.string = oldContent.slice(0, cutOff);
45
+ var outerToken = outer.token(stream, state.outer);
46
+ if (cutOff != Infinity) stream.string = oldContent;
47
+ return outerToken;
48
+ } else {
49
+ var curInner = state.innerActive, oldContent = stream.string;
50
+ var found = indexOf(oldContent, curInner.close, stream.pos);
51
+ if (found == stream.pos) {
52
+ stream.match(curInner.close);
53
+ state.innerActive = state.inner = null;
54
+ return curInner.delimStyle;
55
+ }
56
+ if (found > -1) stream.string = oldContent.slice(0, found);
57
+ var innerToken = curInner.mode.token(stream, state.inner);
58
+ if (found > -1) stream.string = oldContent;
59
+ var cur = stream.current(), found = cur.indexOf(curInner.close);
60
+ if (found > -1) stream.backUp(cur.length - found);
61
+
62
+ if (curInner.innerStyle) {
63
+ if (innerToken) innerToken = innerToken + ' ' + curInner.innerStyle;
64
+ else innerToken = curInner.innerStyle;
65
+ }
66
+
67
+ return innerToken;
68
+ }
69
+ },
70
+
71
+ indent: function(state, textAfter) {
72
+ var mode = state.innerActive ? state.innerActive.mode : outer;
73
+ if (!mode.indent) return CodeMirror.Pass;
74
+ return mode.indent(state.innerActive ? state.inner : state.outer, textAfter);
75
+ },
76
+
77
+ blankLine: function(state) {
78
+ var mode = state.innerActive ? state.innerActive.mode : outer;
79
+ if (mode.blankLine) {
80
+ mode.blankLine(state.innerActive ? state.inner : state.outer);
81
+ }
82
+ if (!state.innerActive) {
83
+ for (var i = 0; i < n_others; ++i) {
84
+ var other = others[i];
85
+ if (other.open === "\n") {
86
+ state.innerActive = other;
87
+ state.inner = CodeMirror.startState(other.mode, mode.indent ? mode.indent(state.outer, "") : 0);
88
+ }
89
+ }
90
+ } else if (state.innerActive.close === "\n") {
91
+ state.innerActive = state.inner = null;
92
+ }
93
+ },
94
+
95
+ electricChars: outer.electricChars,
96
+
97
+ innerMode: function(state) {
98
+ return state.inner ? {state: state.inner, mode: state.innerActive.mode} : {state: state.outer, mode: outer};
99
+ }
100
+ };
101
+ };
assets/lib/codemirror/addon/mode/multiplex_test.js ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function() {
2
+ CodeMirror.defineMode("markdown_with_stex", function(){
3
+ var inner = CodeMirror.getMode({}, "stex");
4
+ var outer = CodeMirror.getMode({}, "markdown");
5
+
6
+ var innerOptions = {
7
+ open: '$',
8
+ close: '$',
9
+ mode: inner,
10
+ delimStyle: 'delim',
11
+ innerStyle: 'inner'
12
+ };
13
+
14
+ return CodeMirror.multiplexingMode(outer, innerOptions);
15
+ });
16
+
17
+ var mode = CodeMirror.getMode({}, "markdown_with_stex");
18
+
19
+ function MT(name) {
20
+ test.mode(
21
+ name,
22
+ mode,
23
+ Array.prototype.slice.call(arguments, 1),
24
+ 'multiplexing');
25
+ }
26
+
27
+ MT(
28
+ "stexInsideMarkdown",
29
+ "[strong **Equation:**] [delim $][inner&tag \\pi][delim $]");
30
+ })();
assets/lib/codemirror/addon/mode/overlay.js ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Utility function that allows modes to be combined. The mode given
2
+ // as the base argument takes care of most of the normal mode
3
+ // functionality, but a second (typically simple) mode is used, which
4
+ // can override the style of text. Both modes get to parse all of the
5
+ // text, but when both assign a non-null style to a piece of code, the
6
+ // overlay wins, unless the combine argument was true, in which case
7
+ // the styles are combined.
8
+
9
+ // overlayParser is the old, deprecated name
10
+ CodeMirror.overlayMode = CodeMirror.overlayParser = function(base, overlay, combine) {
11
+ return {
12
+ startState: function() {
13
+ return {
14
+ base: CodeMirror.startState(base),
15
+ overlay: CodeMirror.startState(overlay),
16
+ basePos: 0, baseCur: null,
17
+ overlayPos: 0, overlayCur: null
18
+ };
19
+ },
20
+ copyState: function(state) {
21
+ return {
22
+ base: CodeMirror.copyState(base, state.base),
23
+ overlay: CodeMirror.copyState(overlay, state.overlay),
24
+ basePos: state.basePos, baseCur: null,
25
+ overlayPos: state.overlayPos, overlayCur: null
26
+ };
27
+ },
28
+
29
+ token: function(stream, state) {
30
+ if (stream.start == state.basePos) {
31
+ state.baseCur = base.token(stream, state.base);
32
+ state.basePos = stream.pos;
33
+ }
34
+ if (stream.start == state.overlayPos) {
35
+ stream.pos = stream.start;
36
+ state.overlayCur = overlay.token(stream, state.overlay);
37
+ state.overlayPos = stream.pos;
38
+ }
39
+ stream.pos = Math.min(state.basePos, state.overlayPos);
40
+ if (stream.eol()) state.basePos = state.overlayPos = 0;
41
+
42
+ if (state.overlayCur == null) return state.baseCur;
43
+ if (state.baseCur != null && combine) return state.baseCur + " " + state.overlayCur;
44
+ else return state.overlayCur;
45
+ },
46
+
47
+ indent: base.indent && function(state, textAfter) {
48
+ return base.indent(state.base, textAfter);
49
+ },
50
+ electricChars: base.electricChars,
51
+
52
+ innerMode: function(state) { return {state: state.base, mode: base}; },
53
+
54
+ blankLine: function(state) {
55
+ if (base.blankLine) base.blankLine(state.base);
56
+ if (overlay.blankLine) overlay.blankLine(state.overlay);
57
+ }
58
+ };
59
+ };
assets/lib/codemirror/addon/runmode/colorize.js ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ CodeMirror.colorize = (function() {
2
+
3
+ var isBlock = /^(p|li|div|h\\d|pre|blockquote|td)$/;
4
+
5
+ function textContent(node, out) {
6
+ if (node.nodeType == 3) return out.push(node.nodeValue);
7
+ for (var ch = node.firstChild; ch; ch = ch.nextSibling) {
8
+ textContent(ch, out);
9
+ if (isBlock.test(node.nodeType)) out.push("\n");
10
+ }
11
+ }
12
+
13
+ return function(collection, defaultMode) {
14
+ if (!collection) collection = document.body.getElementsByTagName("pre");
15
+
16
+ for (var i = 0; i < collection.length; ++i) {
17
+ var node = collection[i];
18
+ var mode = node.getAttribute("data-lang") || defaultMode;
19
+ if (!mode) continue;
20
+
21
+ var text = [];
22
+ textContent(node, text);
23
+ node.innerHTML = "";
24
+ CodeMirror.runMode(text.join(""), mode, node);
25
+
26
+ node.className += " cm-s-default";
27
+ }
28
+ };
29
+ })();
assets/lib/codemirror/addon/runmode/runmode-standalone.js ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Just enough of CodeMirror to run runMode under node.js */
2
+
3
+ window.CodeMirror = {};
4
+
5
+ function splitLines(string){ return string.split(/\r?\n|\r/); };
6
+
7
+ function StringStream(string) {
8
+ this.pos = this.start = 0;
9
+ this.string = string;
10
+ }
11
+ StringStream.prototype = {
12
+ eol: function() {return this.pos >= this.string.length;},
13
+ sol: function() {return this.pos == 0;},
14
+ peek: function() {return this.string.charAt(this.pos) || null;},
15
+ next: function() {
16
+ if (this.pos < this.string.length)
17
+ return this.string.charAt(this.pos++);
18
+ },
19
+ eat: function(match) {
20
+ var ch = this.string.charAt(this.pos);
21
+ if (typeof match == "string") var ok = ch == match;
22
+ else var ok = ch && (match.test ? match.test(ch) : match(ch));
23
+ if (ok) {++this.pos; return ch;}
24
+ },
25
+ eatWhile: function(match) {
26
+ var start = this.pos;
27
+ while (this.eat(match)){}
28
+ return this.pos > start;
29
+ },
30
+ eatSpace: function() {
31
+ var start = this.pos;
32
+ while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos;
33
+ return this.pos > start;
34
+ },
35
+ skipToEnd: function() {this.pos = this.string.length;},
36
+ skipTo: function(ch) {
37
+ var found = this.string.indexOf(ch, this.pos);
38
+ if (found > -1) {this.pos = found; return true;}
39
+ },
40
+ backUp: function(n) {this.pos -= n;},
41
+ column: function() {return this.start;},
42
+ indentation: function() {return 0;},
43
+ match: function(pattern, consume, caseInsensitive) {
44
+ if (typeof pattern == "string") {
45
+ var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;};
46
+ var substr = this.string.substr(this.pos, pattern.length);
47
+ if (cased(substr) == cased(pattern)) {
48
+ if (consume !== false) this.pos += pattern.length;
49
+ return true;
50
+ }
51
+ } else {
52
+ var match = this.string.slice(this.pos).match(pattern);
53
+ if (match && match.index > 0) return null;
54
+ if (match && consume !== false) this.pos += match[0].length;
55
+ return match;
56
+ }
57
+ },
58
+ current: function(){return this.string.slice(this.start, this.pos);}
59
+ };
60
+ CodeMirror.StringStream = StringStream;
61
+
62
+ CodeMirror.startState = function (mode, a1, a2) {
63
+ return mode.startState ? mode.startState(a1, a2) : true;
64
+ };
65
+
66
+ var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {};
67
+ CodeMirror.defineMode = function (name, mode) { modes[name] = mode; };
68
+ CodeMirror.defineMIME = function (mime, spec) { mimeModes[mime] = spec; };
69
+ CodeMirror.getMode = function (options, spec) {
70
+ if (typeof spec == "string" && mimeModes.hasOwnProperty(spec))
71
+ spec = mimeModes[spec];
72
+ if (typeof spec == "string")
73
+ var mname = spec, config = {};
74
+ else if (spec != null)
75
+ var mname = spec.name, config = spec;
76
+ var mfactory = modes[mname];
77
+ if (!mfactory) throw new Error("Unknown mode: " + spec);
78
+ return mfactory(options, config || {});
79
+ };
80
+
81
+ CodeMirror.runMode = function (string, modespec, callback, options) {
82
+ var mode = CodeMirror.getMode({ indentUnit: 2 }, modespec);
83
+
84
+ if (callback.nodeType == 1) {
85
+ var tabSize = (options && options.tabSize) || 4;
86
+ var node = callback, col = 0;
87
+ node.innerHTML = "";
88
+ callback = function (text, style) {
89
+ if (text == "\n") {
90
+ node.appendChild(document.createElement("br"));
91
+ col = 0;
92
+ return;
93
+ }
94
+ var content = "";
95
+ // replace tabs
96
+ for (var pos = 0; ;) {
97
+ var idx = text.indexOf("\t", pos);
98
+ if (idx == -1) {
99
+ content += text.slice(pos);
100
+ col += text.length - pos;
101
+ break;
102
+ } else {
103
+ col += idx - pos;
104
+ content += text.slice(pos, idx);
105
+ var size = tabSize - col % tabSize;
106
+ col += size;
107
+ for (var i = 0; i < size; ++i) content += " ";
108
+ pos = idx + 1;
109
+ }
110
+ }
111
+
112
+ if (style) {
113
+ var sp = node.appendChild(document.createElement("span"));
114
+ sp.className = "cm-" + style.replace(/ +/g, " cm-");
115
+ sp.appendChild(document.createTextNode(content));
116
+ } else {
117
+ node.appendChild(document.createTextNode(content));
118
+ }
119
+ };
120
+ }
121
+
122
+ var lines = splitLines(string), state = CodeMirror.startState(mode);
123
+ for (var i = 0, e = lines.length; i < e; ++i) {
124
+ if (i) callback("\n");
125
+ var stream = new CodeMirror.StringStream(lines[i]);
126
+ while (!stream.eol()) {
127
+ var style = mode.token(stream, state);
128
+ callback(stream.current(), style, i, stream.start, state);
129
+ stream.start = stream.pos;
130
+ }
131
+ }
132
+ };
assets/lib/codemirror/addon/runmode/runmode.js ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ CodeMirror.runMode = function(string, modespec, callback, options) {
2
+ var mode = CodeMirror.getMode(CodeMirror.defaults, modespec);
3
+ var ie = /MSIE \d/.test(navigator.userAgent);
4
+ var ie_lt9 = ie && (document.documentMode == null || document.documentMode < 9);
5
+
6
+ if (callback.nodeType == 1) {
7
+ var tabSize = (options && options.tabSize) || CodeMirror.defaults.tabSize;
8
+ var node = callback, col = 0;
9
+ node.innerHTML = "";
10
+ callback = function(text, style) {
11
+ if (text == "\n") {
12
+ // Emitting LF or CRLF on IE8 or earlier results in an incorrect display.
13
+ // Emitting a carriage return makes everything ok.
14
+ node.appendChild(document.createTextNode(ie_lt9 ? '\r' : text));
15
+ col = 0;
16
+ return;
17
+ }
18
+ var content = "";
19
+ // replace tabs
20
+ for (var pos = 0;;) {
21
+ var idx = text.indexOf("\t", pos);
22
+ if (idx == -1) {
23
+ content += text.slice(pos);
24
+ col += text.length - pos;
25
+ break;
26
+ } else {
27
+ col += idx - pos;
28
+ content += text.slice(pos, idx);
29
+ var size = tabSize - col % tabSize;
30
+ col += size;
31
+ for (var i = 0; i < size; ++i) content += " ";
32
+ pos = idx + 1;
33
+ }
34
+ }
35
+
36
+ if (style) {
37
+ var sp = node.appendChild(document.createElement("span"));
38
+ sp.className = "cm-" + style.replace(/ +/g, " cm-");
39
+ sp.appendChild(document.createTextNode(content));
40
+ } else {
41
+ node.appendChild(document.createTextNode(content));
42
+ }
43
+ };
44
+ }
45
+
46
+ var lines = CodeMirror.splitLines(string), state = CodeMirror.startState(mode);
47
+ for (var i = 0, e = lines.length; i < e; ++i) {
48
+ if (i) callback("\n");
49
+ var stream = new CodeMirror.StringStream(lines[i]);
50
+ while (!stream.eol()) {
51
+ var style = mode.token(stream, state);
52
+ callback(stream.current(), style, i, stream.start, state);
53
+ stream.start = stream.pos;
54
+ }
55
+ }
56
+ };
assets/lib/codemirror/addon/runmode/runmode.node.js ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Just enough of CodeMirror to run runMode under node.js */
2
+
3
+ function splitLines(string){ return string.split(/\r?\n|\r/); };
4
+
5
+ function StringStream(string) {
6
+ this.pos = this.start = 0;
7
+ this.string = string;
8
+ }
9
+ StringStream.prototype = {
10
+ eol: function() {return this.pos >= this.string.length;},
11
+ sol: function() {return this.pos == 0;},
12
+ peek: function() {return this.string.charAt(this.pos) || null;},
13
+ next: function() {
14
+ if (this.pos < this.string.length)
15
+ return this.string.charAt(this.pos++);
16
+ },
17
+ eat: function(match) {
18
+ var ch = this.string.charAt(this.pos);
19
+ if (typeof match == "string") var ok = ch == match;
20
+ else var ok = ch && (match.test ? match.test(ch) : match(ch));
21
+ if (ok) {++this.pos; return ch;}
22
+ },
23
+ eatWhile: function(match) {
24
+ var start = this.pos;
25
+ while (this.eat(match)){}
26
+ return this.pos > start;
27
+ },
28
+ eatSpace: function() {
29
+ var start = this.pos;
30
+ while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos;
31
+ return this.pos > start;
32
+ },
33
+ skipToEnd: function() {this.pos = this.string.length;},
34
+ skipTo: function(ch) {
35
+ var found = this.string.indexOf(ch, this.pos);
36
+ if (found > -1) {this.pos = found; return true;}
37
+ },
38
+ backUp: function(n) {this.pos -= n;},
39
+ column: function() {return this.start;},
40
+ indentation: function() {return 0;},
41
+ match: function(pattern, consume, caseInsensitive) {
42
+ if (typeof pattern == "string") {
43
+ var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;};
44
+ var substr = this.string.substr(this.pos, pattern.length);
45
+ if (cased(substr) == cased(pattern)) {
46
+ if (consume !== false) this.pos += pattern.length;
47
+ return true;
48
+ }
49
+ } else {
50
+ var match = this.string.slice(this.pos).match(pattern);
51
+ if (match && match.index > 0) return null;
52
+ if (match && consume !== false) this.pos += match[0].length;
53
+ return match;
54
+ }
55
+ },
56
+ current: function(){return this.string.slice(this.start, this.pos);}
57
+ };
58
+ exports.StringStream = StringStream;
59
+
60
+ exports.startState = function(mode, a1, a2) {
61
+ return mode.startState ? mode.startState(a1, a2) : true;
62
+ };
63
+
64
+ var modes = exports.modes = {}, mimeModes = exports.mimeModes = {};
65
+ exports.defineMode = function(name, mode) {
66
+ if (arguments.length > 2) {
67
+ mode.dependencies = [];
68
+ for (var i = 2; i < arguments.length; ++i) mode.dependencies.push(arguments[i]);
69
+ }
70
+ modes[name] = mode;
71
+ };
72
+ exports.defineMIME = function(mime, spec) { mimeModes[mime] = spec; };
73
+
74
+ exports.defineMode("null", function() {
75
+ return {token: function(stream) {stream.skipToEnd();}};
76
+ });
77
+ exports.defineMIME("text/plain", "null");
78
+
79
+ exports.getMode = function(options, spec) {
80
+ if (typeof spec == "string" && mimeModes.hasOwnProperty(spec))
81
+ spec = mimeModes[spec];
82
+ if (typeof spec == "string")
83
+ var mname = spec, config = {};
84
+ else if (spec != null)
85
+ var mname = spec.name, config = spec;
86
+ var mfactory = modes[mname];
87
+ if (!mfactory) throw new Error("Unknown mode: " + spec);
88
+ return mfactory(options, config || {});
89
+ };
90
+
91
+ exports.runMode = function(string, modespec, callback) {
92
+ var mode = exports.getMode({indentUnit: 2}, modespec);
93
+ var lines = splitLines(string), state = exports.startState(mode);
94
+ for (var i = 0, e = lines.length; i < e; ++i) {
95
+ if (i) callback("\n");
96
+ var stream = new exports.StringStream(lines[i]);
97
+ while (!stream.eol()) {
98
+ var style = mode.token(stream, state);
99
+ callback(stream.current(), style, i, stream.start, state);
100
+ stream.start = stream.pos;
101
+ }
102
+ }
103
+ };
assets/lib/codemirror/addon/scroll/scrollpastend.js ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function() {
2
+ "use strict";
3
+
4
+ CodeMirror.defineOption("scrollPastEnd", false, function(cm, val, old) {
5
+ if (old && old != CodeMirror.Init) {
6
+ cm.off("change", onChange);
7
+ cm.display.lineSpace.parentNode.style.paddingBottom = "";
8
+ cm.state.scrollPastEndPadding = null;
9
+ }
10
+ if (val) {
11
+ cm.on("change", onChange);
12
+ updateBottomMargin(cm);
13
+ }
14
+ });
15
+
16
+ function onChange(cm, change) {
17
+ if (CodeMirror.changeEnd(change).line == cm.lastLine())
18
+ updateBottomMargin(cm);
19
+ }
20
+
21
+ function updateBottomMargin(cm) {
22
+ var padding = "";
23
+ if (cm.lineCount() > 1) {
24
+ var totalH = cm.display.scroller.clientHeight - 30,
25
+ lastLineH = cm.getLineHandle(cm.lastLine()).height;
26
+ padding = (totalH - lastLineH) + "px";
27
+ }
28
+ if (cm.state.scrollPastEndPadding != padding) {
29
+ cm.state.scrollPastEndPadding = padding;
30
+ cm.display.lineSpace.parentNode.style.paddingBottom = padding;
31
+ cm.setSize();
32
+ }
33
+ }
34
+ })();
assets/lib/codemirror/addon/search/match-highlighter.js ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Highlighting text that matches the selection
2
+ //
3
+ // Defines an option highlightSelectionMatches, which, when enabled,
4
+ // will style strings that match the selection throughout the
5
+ // document.
6
+ //
7
+ // The option can be set to true to simply enable it, or to a
8
+ // {minChars, style, showToken} object to explicitly configure it.
9
+ // minChars is the minimum amount of characters that should be
10
+ // selected for the behavior to occur, and style is the token style to
11
+ // apply to the matches. This will be prefixed by "cm-" to create an
12
+ // actual CSS class name. showToken, when enabled, will cause the
13
+ // current token to be highlighted when nothing is selected.
14
+
15
+ (function() {
16
+ var DEFAULT_MIN_CHARS = 2;
17
+ var DEFAULT_TOKEN_STYLE = "matchhighlight";
18
+ var DEFAULT_DELAY = 100;
19
+
20
+ function State(options) {
21
+ if (typeof options == "object") {
22
+ this.minChars = options.minChars;
23
+ this.style = options.style;
24
+ this.showToken = options.showToken;
25
+ this.delay = options.delay;
26
+ }
27
+ if (this.style == null) this.style = DEFAULT_TOKEN_STYLE;
28
+ if (this.minChars == null) this.minChars = DEFAULT_MIN_CHARS;
29
+ if (this.delay == null) this.delay = DEFAULT_DELAY;
30
+ this.overlay = this.timeout = null;
31
+ }
32
+
33
+ CodeMirror.defineOption("highlightSelectionMatches", false, function(cm, val, old) {
34
+ if (old && old != CodeMirror.Init) {
35
+ var over = cm.state.matchHighlighter.overlay;
36
+ if (over) cm.removeOverlay(over);
37
+ clearTimeout(cm.state.matchHighlighter.timeout);
38
+ cm.state.matchHighlighter = null;
39
+ cm.off("cursorActivity", cursorActivity);
40
+ }
41
+ if (val) {
42
+ cm.state.matchHighlighter = new State(val);
43
+ highlightMatches(cm);
44
+ cm.on("cursorActivity", cursorActivity);
45
+ }
46
+ });
47
+
48
+ function cursorActivity(cm) {
49
+ var state = cm.state.matchHighlighter;
50
+ clearTimeout(state.timeout);
51
+ state.timeout = setTimeout(function() {highlightMatches(cm);}, state.delay);
52
+ }
53
+
54
+ function highlightMatches(cm) {
55
+ cm.operation(function() {
56
+ var state = cm.state.matchHighlighter;
57
+ if (state.overlay) {
58
+ cm.removeOverlay(state.overlay);
59
+ state.overlay = null;
60
+ }
61
+ if (!cm.somethingSelected() && state.showToken) {
62
+ var re = state.showToken === true ? /[\w$]/ : state.showToken;
63
+ var cur = cm.getCursor(), line = cm.getLine(cur.line), start = cur.ch, end = start;
64
+ while (start && re.test(line.charAt(start - 1))) --start;
65
+ while (end < line.length && re.test(line.charAt(end))) ++end;
66
+ if (start < end)
67
+ cm.addOverlay(state.overlay = makeOverlay(line.slice(start, end), re, state.style));
68
+ return;
69
+ }
70
+ if (cm.getCursor("head").line != cm.getCursor("anchor").line) return;
71
+ var selection = cm.getSelection().replace(/^\s+|\s+$/g, "");
72
+ if (selection.length >= state.minChars)
73
+ cm.addOverlay(state.overlay = makeOverlay(selection, false, state.style));
74
+ });
75
+ }
76
+
77
+ function boundariesAround(stream, re) {
78
+ return (!stream.start || !re.test(stream.string.charAt(stream.start - 1))) &&
79
+ (stream.pos == stream.string.length || !re.test(stream.string.charAt(stream.pos)));
80
+ }
81
+
82
+ function makeOverlay(query, hasBoundary, style) {
83
+ return {token: function(stream) {
84
+ if (stream.match(query) &&
85
+ (!hasBoundary || boundariesAround(stream, hasBoundary)))
86
+ return style;
87
+ stream.next();
88
+ stream.skipTo(query.charAt(0)) || stream.skipToEnd();
89
+ }};
90
+ }
91
+ })();
assets/lib/codemirror/addon/search/search.js ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Define search commands. Depends on dialog.js or another
2
+ // implementation of the openDialog method.
3
+
4
+ // Replace works a little oddly -- it will do the replace on the next
5
+ // Ctrl-G (or whatever is bound to findNext) press. You prevent a
6
+ // replace by making sure the match is no longer selected when hitting
7
+ // Ctrl-G.
8
+
9
+ (function() {
10
+ function searchOverlay(query) {
11
+ if (typeof query == "string") return {token: function(stream) {
12
+ if (stream.match(query)) return "searching";
13
+ stream.next();
14
+ stream.skipTo(query.charAt(0)) || stream.skipToEnd();
15
+ }};
16
+ return {token: function(stream) {
17
+ if (stream.match(query)) return "searching";
18
+ while (!stream.eol()) {
19
+ stream.next();
20
+ if (stream.match(query, false)) break;
21
+ }
22
+ }};
23
+ }
24
+
25
+ function SearchState() {
26
+ this.posFrom = this.posTo = this.query = null;
27
+ this.overlay = null;
28
+ }
29
+ function getSearchState(cm) {
30
+ return cm.state.search || (cm.state.search = new SearchState());
31
+ }
32
+ function getSearchCursor(cm, query, pos) {
33
+ // Heuristic: if the query string is all lowercase, do a case insensitive search.
34
+ return cm.getSearchCursor(query, pos, typeof query == "string" && query == query.toLowerCase());
35
+ }
36
+ function dialog(cm, text, shortText, f) {
37
+ if (cm.openDialog) cm.openDialog(text, f);
38
+ else f(prompt(shortText, ""));
39
+ }
40
+ function confirmDialog(cm, text, shortText, fs) {
41
+ if (cm.openConfirm) cm.openConfirm(text, fs);
42
+ else if (confirm(shortText)) fs[0]();
43
+ }
44
+ function parseQuery(query) {
45
+ var isRE = query.match(/^\/(.*)\/([a-z]*)$/);
46
+ return isRE ? new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i") : query;
47
+ }
48
+ var queryDialog =
49
+ 'Search: <input type="text" style="width: 10em"/> <span style="color: #888">(Use /re/ syntax for regexp search)</span>';
50
+ function doSearch(cm, rev) {
51
+ var state = getSearchState(cm);
52
+ if (state.query) return findNext(cm, rev);
53
+ dialog(cm, queryDialog, "Search for:", function(query) {
54
+ cm.operation(function() {
55
+ if (!query || state.query) return;
56
+ state.query = parseQuery(query);
57
+ cm.removeOverlay(state.overlay);
58
+ state.overlay = searchOverlay(state.query);
59
+ cm.addOverlay(state.overlay);
60
+ state.posFrom = state.posTo = cm.getCursor();
61
+ findNext(cm, rev);
62
+ });
63
+ });
64
+ }
65
+ function findNext(cm, rev) {cm.operation(function() {
66
+ var state = getSearchState(cm);
67
+ var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo);
68
+ if (!cursor.find(rev)) {
69
+ cursor = getSearchCursor(cm, state.query, rev ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0));
70
+ if (!cursor.find(rev)) return;
71
+ }
72
+ cm.setSelection(cursor.from(), cursor.to());
73
+ state.posFrom = cursor.from(); state.posTo = cursor.to();
74
+ });}
75
+ function clearSearch(cm) {cm.operation(function() {
76
+ var state = getSearchState(cm);
77
+ if (!state.query) return;
78
+ state.query = null;
79
+ cm.removeOverlay(state.overlay);
80
+ });}
81
+
82
+ var replaceQueryDialog =
83
+ 'Replace: <input type="text" style="width: 10em"/> <span style="color: #888">(Use /re/ syntax for regexp search)</span>';
84
+ var replacementQueryDialog = 'With: <input type="text" style="width: 10em"/>';
85
+ var doReplaceConfirm = "Replace? <button>Yes</button> <button>No</button> <button>Stop</button>";
86
+ function replace(cm, all) {
87
+ dialog(cm, replaceQueryDialog, "Replace:", function(query) {
88
+ if (!query) return;
89
+ query = parseQuery(query);
90
+ dialog(cm, replacementQueryDialog, "Replace with:", function(text) {
91
+ if (all) {
92
+ cm.operation(function() {
93
+ for (var cursor = getSearchCursor(cm, query); cursor.findNext();) {
94
+ if (typeof query != "string") {
95
+ var match = cm.getRange(cursor.from(), cursor.to()).match(query);
96
+ cursor.replace(text.replace(/\$(\d)/, function(_, i) {return match[i];}));
97
+ } else cursor.replace(text);
98
+ }
99
+ });
100
+ } else {
101
+ clearSearch(cm);
102
+ var cursor = getSearchCursor(cm, query, cm.getCursor());
103
+ var advance = function() {
104
+ var start = cursor.from(), match;
105
+ if (!(match = cursor.findNext())) {
106
+ cursor = getSearchCursor(cm, query);
107
+ if (!(match = cursor.findNext()) ||
108
+ (start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return;
109
+ }
110
+ cm.setSelection(cursor.from(), cursor.to());
111
+ confirmDialog(cm, doReplaceConfirm, "Replace?",
112
+ [function() {doReplace(match);}, advance]);
113
+ };
114
+ var doReplace = function(match) {
115
+ cursor.replace(typeof query == "string" ? text :
116
+ text.replace(/\$(\d)/, function(_, i) {return match[i];}));
117
+ advance();
118
+ };
119
+ advance();
120
+ }
121
+ });
122
+ });
123
+ }
124
+
125
+ CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);};
126
+ CodeMirror.commands.findNext = doSearch;
127
+ CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);};
128
+ CodeMirror.commands.clearSearch = clearSearch;
129
+ CodeMirror.commands.replace = replace;
130
+ CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);};
131
+ })();
assets/lib/codemirror/addon/search/searchcursor.js ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function(){
2
+ var Pos = CodeMirror.Pos;
3
+
4
+ function SearchCursor(doc, query, pos, caseFold) {
5
+ this.atOccurrence = false; this.doc = doc;
6
+ if (caseFold == null && typeof query == "string") caseFold = false;
7
+
8
+ pos = pos ? doc.clipPos(pos) : Pos(0, 0);
9
+ this.pos = {from: pos, to: pos};
10
+
11
+ // The matches method is filled in based on the type of query.
12
+ // It takes a position and a direction, and returns an object
13
+ // describing the next occurrence of the query, or null if no
14
+ // more matches were found.
15
+ if (typeof query != "string") { // Regexp match
16
+ if (!query.global) query = new RegExp(query.source, query.ignoreCase ? "ig" : "g");
17
+ this.matches = function(reverse, pos) {
18
+ if (reverse) {
19
+ query.lastIndex = 0;
20
+ var line = doc.getLine(pos.line).slice(0, pos.ch), cutOff = 0, match, start;
21
+ for (;;) {
22
+ query.lastIndex = cutOff;
23
+ var newMatch = query.exec(line);
24
+ if (!newMatch) break;
25
+ match = newMatch;
26
+ start = match.index;
27
+ cutOff = match.index + (match[0].length || 1);
28
+ if (cutOff == line.length) break;
29
+ }
30
+ var matchLen = (match && match[0].length) || 0;
31
+ if (!matchLen) {
32
+ if (start == 0 && line.length == 0) {match = undefined;}
33
+ else if (start != doc.getLine(pos.line).length) {
34
+ matchLen++;
35
+ }
36
+ }
37
+ } else {
38
+ query.lastIndex = pos.ch;
39
+ var line = doc.getLine(pos.line), match = query.exec(line);
40
+ var matchLen = (match && match[0].length) || 0;
41
+ var start = match && match.index;
42
+ if (start + matchLen != line.length && !matchLen) matchLen = 1;
43
+ }
44
+ if (match && matchLen)
45
+ return {from: Pos(pos.line, start),
46
+ to: Pos(pos.line, start + matchLen),
47
+ match: match};
48
+ };
49
+ } else { // String query
50
+ if (caseFold) query = query.toLowerCase();
51
+ var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;};
52
+ var target = query.split("\n");
53
+ // Different methods for single-line and multi-line queries
54
+ if (target.length == 1) {
55
+ if (!query.length) {
56
+ // Empty string would match anything and never progress, so
57
+ // we define it to match nothing instead.
58
+ this.matches = function() {};
59
+ } else {
60
+ this.matches = function(reverse, pos) {
61
+ var line = fold(doc.getLine(pos.line)), len = query.length, match;
62
+ if (reverse ? (pos.ch >= len && (match = line.lastIndexOf(query, pos.ch - len)) != -1)
63
+ : (match = line.indexOf(query, pos.ch)) != -1)
64
+ return {from: Pos(pos.line, match),
65
+ to: Pos(pos.line, match + len)};
66
+ };
67
+ }
68
+ } else {
69
+ this.matches = function(reverse, pos) {
70
+ var ln = pos.line, idx = (reverse ? target.length - 1 : 0), match = target[idx], line = fold(doc.getLine(ln));
71
+ var offsetA = (reverse ? line.indexOf(match) + match.length : line.lastIndexOf(match));
72
+ if (reverse ? offsetA > pos.ch || offsetA != match.length
73
+ : offsetA < pos.ch || offsetA != line.length - match.length)
74
+ return;
75
+ for (;;) {
76
+ if (reverse ? !ln : ln == doc.lineCount() - 1) return;
77
+ line = fold(doc.getLine(ln += reverse ? -1 : 1));
78
+ match = target[reverse ? --idx : ++idx];
79
+ if (idx > 0 && idx < target.length - 1) {
80
+ if (line != match) return;
81
+ else continue;
82
+ }
83
+ var offsetB = (reverse ? line.lastIndexOf(match) : line.indexOf(match) + match.length);
84
+ if (reverse ? offsetB != line.length - match.length : offsetB != match.length)
85
+ return;
86
+ var start = Pos(pos.line, offsetA), end = Pos(ln, offsetB);
87
+ return {from: reverse ? end : start, to: reverse ? start : end};
88
+ }
89
+ };
90
+ }
91
+ }
92
+ }
93
+
94
+ SearchCursor.prototype = {
95
+ findNext: function() {return this.find(false);},
96
+ findPrevious: function() {return this.find(true);},
97
+
98
+ find: function(reverse) {
99
+ var self = this, pos = this.doc.clipPos(reverse ? this.pos.from : this.pos.to);
100
+ function savePosAndFail(line) {
101
+ var pos = Pos(line, 0);
102
+ self.pos = {from: pos, to: pos};
103
+ self.atOccurrence = false;
104
+ return false;
105
+ }
106
+
107
+ for (;;) {
108
+ if (this.pos = this.matches(reverse, pos)) {
109
+ if (!this.pos.from || !this.pos.to) { console.log(this.matches, this.pos); }
110
+ this.atOccurrence = true;
111
+ return this.pos.match || true;
112
+ }
113
+ if (reverse) {
114
+ if (!pos.line) return savePosAndFail(0);
115
+ pos = Pos(pos.line-1, this.doc.getLine(pos.line-1).length);
116
+ }
117
+ else {
118
+ var maxLine = this.doc.lineCount();
119
+ if (pos.line == maxLine - 1) return savePosAndFail(maxLine);
120
+ pos = Pos(pos.line + 1, 0);
121
+ }
122
+ }
123
+ },
124
+
125
+ from: function() {if (this.atOccurrence) return this.pos.from;},
126
+ to: function() {if (this.atOccurrence) return this.pos.to;},
127
+
128
+ replace: function(newText) {
129
+ if (!this.atOccurrence) return;
130
+ var lines = CodeMirror.splitLines(newText);
131
+ this.doc.replaceRange(lines, this.pos.from, this.pos.to);
132
+ this.pos.to = Pos(this.pos.from.line + lines.length - 1,
133
+ lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0));
134
+ }
135
+ };
136
+
137
+ CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) {
138
+ return new SearchCursor(this.doc, query, pos, caseFold);
139
+ });
140
+ CodeMirror.defineDocExtension("getSearchCursor", function(query, pos, caseFold) {
141
+ return new SearchCursor(this, query, pos, caseFold);
142
+ });
143
+ })();
assets/lib/codemirror/addon/selection/active-line.js ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Because sometimes you need to style the cursor's line.
2
+ //
3
+ // Adds an option 'styleActiveLine' which, when enabled, gives the
4
+ // active line's wrapping <div> the CSS class "CodeMirror-activeline",
5
+ // and gives its background <div> the class "CodeMirror-activeline-background".
6
+
7
+ (function() {
8
+ "use strict";
9
+ var WRAP_CLASS = "CodeMirror-activeline";
10
+ var BACK_CLASS = "CodeMirror-activeline-background";
11
+
12
+ CodeMirror.defineOption("styleActiveLine", false, function(cm, val, old) {
13
+ var prev = old && old != CodeMirror.Init;
14
+ if (val && !prev) {
15
+ updateActiveLine(cm);
16
+ cm.on("cursorActivity", updateActiveLine);
17
+ } else if (!val && prev) {
18
+ cm.off("cursorActivity", updateActiveLine);
19
+ clearActiveLine(cm);
20
+ delete cm.state.activeLine;
21
+ }
22
+ });
23
+
24
+ function clearActiveLine(cm) {
25
+ if ("activeLine" in cm.state) {
26
+ cm.removeLineClass(cm.state.activeLine, "wrap", WRAP_CLASS);
27
+ cm.removeLineClass(cm.state.activeLine, "background", BACK_CLASS);
28
+ }
29
+ }
30
+
31
+ function updateActiveLine(cm) {
32
+ var line = cm.getLineHandleVisualStart(cm.getCursor().line);
33
+ if (cm.state.activeLine == line) return;
34
+ clearActiveLine(cm);
35
+ cm.addLineClass(line, "wrap", WRAP_CLASS);
36
+ cm.addLineClass(line, "background", BACK_CLASS);
37
+ cm.state.activeLine = line;
38
+ }
39
+ })();
assets/lib/codemirror/addon/selection/mark-selection.js ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Because sometimes you need to mark the selected *text*.
2
+ //
3
+ // Adds an option 'styleSelectedText' which, when enabled, gives
4
+ // selected text the CSS class given as option value, or
5
+ // "CodeMirror-selectedtext" when the value is not a string.
6
+
7
+ (function() {
8
+ "use strict";
9
+
10
+ CodeMirror.defineOption("styleSelectedText", false, function(cm, val, old) {
11
+ var prev = old && old != CodeMirror.Init;
12
+ if (val && !prev) {
13
+ cm.state.markedSelection = [];
14
+ cm.state.markedSelectionStyle = typeof val == "string" ? val : "CodeMirror-selectedtext";
15
+ reset(cm);
16
+ cm.on("cursorActivity", onCursorActivity);
17
+ cm.on("change", onChange);
18
+ } else if (!val && prev) {
19
+ cm.off("cursorActivity", onCursorActivity);
20
+ cm.off("change", onChange);
21
+ clear(cm);
22
+ cm.state.markedSelection = cm.state.markedSelectionStyle = null;
23
+ }
24
+ });
25
+
26
+ function onCursorActivity(cm) {
27
+ cm.operation(function() { update(cm); });
28
+ }
29
+
30
+ function onChange(cm) {
31
+ if (cm.state.markedSelection.length)
32
+ cm.operation(function() { clear(cm); });
33
+ }
34
+
35
+ var CHUNK_SIZE = 8;
36
+ var Pos = CodeMirror.Pos;
37
+
38
+ function cmp(pos1, pos2) {
39
+ return pos1.line - pos2.line || pos1.ch - pos2.ch;
40
+ }
41
+
42
+ function coverRange(cm, from, to, addAt) {
43
+ if (cmp(from, to) == 0) return;
44
+ var array = cm.state.markedSelection;
45
+ var cls = cm.state.markedSelectionStyle;
46
+ for (var line = from.line;;) {
47
+ var start = line == from.line ? from : Pos(line, 0);
48
+ var endLine = line + CHUNK_SIZE, atEnd = endLine >= to.line;
49
+ var end = atEnd ? to : Pos(endLine, 0);
50
+ var mark = cm.markText(start, end, {className: cls});
51
+ if (addAt == null) array.push(mark);
52
+ else array.splice(addAt++, 0, mark);
53
+ if (atEnd) break;
54
+ line = endLine;
55
+ }
56
+ }
57
+
58
+ function clear(cm) {
59
+ var array = cm.state.markedSelection;
60
+ for (var i = 0; i < array.length; ++i) array[i].clear();
61
+ array.length = 0;
62
+ }
63
+
64
+ function reset(cm) {
65
+ clear(cm);
66
+ var from = cm.getCursor("start"), to = cm.getCursor("end");
67
+ coverRange(cm, from, to);
68
+ }
69
+
70
+ function update(cm) {
71
+ var from = cm.getCursor("start"), to = cm.getCursor("end");
72
+ if (cmp(from, to) == 0) return clear(cm);
73
+
74
+ var array = cm.state.markedSelection;
75
+ if (!array.length) return coverRange(cm, from, to);
76
+
77
+ var coverStart = array[0].find(), coverEnd = array[array.length - 1].find();
78
+ if (!coverStart || !coverEnd || to.line - from.line < CHUNK_SIZE ||
79
+ cmp(from, coverEnd.to) >= 0 || cmp(to, coverStart.from) <= 0)
80
+ return reset(cm);
81
+
82
+ while (cmp(from, coverStart.from) > 0) {
83
+ array.shift().clear();
84
+ coverStart = array[0].find();
85
+ }
86
+ if (cmp(from, coverStart.from) < 0) {
87
+ if (coverStart.to.line - from.line < CHUNK_SIZE) {
88
+ array.shift().clear();
89
+ coverRange(cm, from, coverStart.to, 0);
90
+ } else {
91
+ coverRange(cm, from, coverStart.from, 0);
92
+ }
93
+ }
94
+
95
+ while (cmp(to, coverEnd.to) < 0) {
96
+ array.pop().clear();
97
+ coverEnd = array[array.length - 1].find();
98
+ }
99
+ if (cmp(to, coverEnd.to) > 0) {
100
+ if (to.line - coverEnd.from.line < CHUNK_SIZE) {
101
+ array.pop().clear();
102
+ coverRange(cm, coverEnd.from, to);
103
+ } else {
104
+ coverRange(cm, coverEnd.to, to);
105
+ }
106
+ }
107
+ }
108
+ })();
assets/lib/codemirror/addon/tern/tern.css ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .CodeMirror-Tern-completion {
2
+ padding-left: 22px;
3
+ position: relative;
4
+ }
5
+ .CodeMirror-Tern-completion:before {
6
+ position: absolute;
7
+ left: 2px;
8
+ bottom: 2px;
9
+ border-radius: 50%;
10
+ font-size: 12px;
11
+ font-weight: bold;
12
+ height: 15px;
13
+ width: 15px;
14
+ line-height: 16px;
15
+ text-align: center;
16
+ color: white;
17
+ -moz-box-sizing: border-box;
18
+ box-sizing: border-box;
19
+ }
20
+ .CodeMirror-Tern-completion-unknown:before {
21
+ content: "?";
22
+ background: #4bb;
23
+ }
24
+ .CodeMirror-Tern-completion-object:before {
25
+ content: "O";
26
+ background: #77c;
27
+ }
28
+ .CodeMirror-Tern-completion-fn:before {
29
+ content: "F";
30
+ background: #7c7;
31
+ }
32
+ .CodeMirror-Tern-completion-array:before {
33
+ content: "A";
34
+ background: #c66;
35
+ }
36
+ .CodeMirror-Tern-completion-number:before {
37
+ content: "1";
38
+ background: #999;
39
+ }
40
+ .CodeMirror-Tern-completion-string:before {
41
+ content: "S";
42
+ background: #999;
43
+ }
44
+ .CodeMirror-Tern-completion-bool:before {
45
+ content: "B";
46
+ background: #999;
47
+ }
48
+
49
+ .CodeMirror-Tern-completion-guess {
50
+ color: #999;
51
+ }
52
+
53
+ .CodeMirror-Tern-tooltip {
54
+ border: 1px solid silver;
55
+ border-radius: 3px;
56
+ color: #444;
57
+ padding: 2px 5px;
58
+ font-size: 90%;
59
+ font-family: monospace;
60
+ background-color: white;
61
+ white-space: pre-wrap;
62
+
63
+ max-width: 40em;
64
+ position: absolute;
65
+ z-index: 10;
66
+ -webkit-box-shadow: 2px 3px 5px rgba(0,0,0,.2);
67
+ -moz-box-shadow: 2px 3px 5px rgba(0,0,0,.2);
68
+ box-shadow: 2px 3px 5px rgba(0,0,0,.2);
69
+
70
+ transition: opacity 1s;
71
+ -moz-transition: opacity 1s;
72
+ -webkit-transition: opacity 1s;
73
+ -o-transition: opacity 1s;
74
+ -ms-transition: opacity 1s;
75
+ }
76
+
77
+ .CodeMirror-Tern-hint-doc {
78
+ max-width: 25em;
79
+ }
80
+
81
+ .CodeMirror-Tern-fname { color: black; }
82
+ .CodeMirror-Tern-farg { color: #70a; }
83
+ .CodeMirror-Tern-farg-current { text-decoration: underline; }
84
+ .CodeMirror-Tern-type { color: #07c; }
85
+ .CodeMirror-Tern-fhint-guess { opacity: .7; }
assets/lib/codemirror/addon/tern/tern.js ADDED
@@ -0,0 +1,631 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Glue code between CodeMirror and Tern.
2
+ //
3
+ // Create a CodeMirror.TernServer to wrap an actual Tern server,
4
+ // register open documents (CodeMirror.Doc instances) with it, and
5
+ // call its methods to activate the assisting functions that Tern
6
+ // provides.
7
+ //
8
+ // Options supported (all optional):
9
+ // * defs: An array of JSON definition data structures.
10
+ // * plugins: An object mapping plugin names to configuration
11
+ // options.
12
+ // * getFile: A function(name, c) that can be used to access files in
13
+ // the project that haven't been loaded yet. Simply do c(null) to
14
+ // indicate that a file is not available.
15
+ // * fileFilter: A function(value, docName, doc) that will be applied
16
+ // to documents before passing them on to Tern.
17
+ // * switchToDoc: A function(name) that should, when providing a
18
+ // multi-file view, switch the view or focus to the named file.
19
+ // * showError: A function(editor, message) that can be used to
20
+ // override the way errors are displayed.
21
+ // * completionTip: Customize the content in tooltips for completions.
22
+ // Is passed a single argument—the completion's data as returned by
23
+ // Tern—and may return a string, DOM node, or null to indicate that
24
+ // no tip should be shown. By default the docstring is shown.
25
+ // * typeTip: Like completionTip, but for the tooltips shown for type
26
+ // queries.
27
+ // * responseFilter: A function(doc, query, request, error, data) that
28
+ // will be applied to the Tern responses before treating them
29
+ //
30
+ //
31
+ // It is possible to run the Tern server in a web worker by specifying
32
+ // these additional options:
33
+ // * useWorker: Set to true to enable web worker mode. You'll probably
34
+ // want to feature detect the actual value you use here, for example
35
+ // !!window.Worker.
36
+ // * workerScript: The main script of the worker. Point this to
37
+ // wherever you are hosting worker.js from this directory.
38
+ // * workerDeps: An array of paths pointing (relative to workerScript)
39
+ // to the Acorn and Tern libraries and any Tern plugins you want to
40
+ // load. Or, if you minified those into a single script and included
41
+ // them in the workerScript, simply leave this undefined.
42
+
43
+ (function() {
44
+ "use strict";
45
+
46
+ CodeMirror.TernServer = function(options) {
47
+ var self = this;
48
+ this.options = options || {};
49
+ var plugins = this.options.plugins || (this.options.plugins = {});
50
+ if (!plugins.doc_comment) plugins.doc_comment = true;
51
+ if (this.options.useWorker) {
52
+ this.server = new WorkerServer(this);
53
+ } else {
54
+ this.server = new tern.Server({
55
+ getFile: function(name, c) { return getFile(self, name, c); },
56
+ async: true,
57
+ defs: this.options.defs || [],
58
+ plugins: plugins
59
+ });
60
+ }
61
+ this.docs = Object.create(null);
62
+ this.trackChange = function(doc, change) { trackChange(self, doc, change); };
63
+
64
+ this.cachedArgHints = null;
65
+ this.activeArgHints = null;
66
+ this.jumpStack = [];
67
+ };
68
+
69
+ CodeMirror.TernServer.prototype = {
70
+ addDoc: function(name, doc) {
71
+ var data = {doc: doc, name: name, changed: null};
72
+ this.server.addFile(name, docValue(this, data));
73
+ CodeMirror.on(doc, "change", this.trackChange);
74
+ return this.docs[name] = data;
75
+ },
76
+
77
+ delDoc: function(name) {
78
+ var found = this.docs[name];
79
+ if (!found) return;
80
+ CodeMirror.off(found.doc, "change", this.trackChange);
81
+ delete this.docs[name];
82
+ this.server.delFile(name);
83
+ },
84
+
85
+ hideDoc: function(name) {
86
+ closeArgHints(this);
87
+ var found = this.docs[name];
88
+ if (found && found.changed) sendDoc(this, found);
89
+ },
90
+
91
+ complete: function(cm) {
92
+ var self = this;
93
+ CodeMirror.showHint(cm, function(cm, c) { return hint(self, cm, c); }, {async: true});
94
+ },
95
+
96
+ getHint: function(cm, c) { return hint(this, cm, c); },
97
+
98
+ showType: function(cm) { showType(this, cm); },
99
+
100
+ updateArgHints: function(cm) { updateArgHints(this, cm); },
101
+
102
+ jumpToDef: function(cm) { jumpToDef(this, cm); },
103
+
104
+ jumpBack: function(cm) { jumpBack(this, cm); },
105
+
106
+ rename: function(cm) { rename(this, cm); },
107
+
108
+ request: function (cm, query, c) {
109
+ var self = this;
110
+ var doc = findDoc(this, cm.getDoc());
111
+ var request = buildRequest(this, doc, query);
112
+
113
+ this.server.request(request, function (error, data) {
114
+ if (!error && self.options.responseFilter)
115
+ data = self.options.responseFilter(doc, query, request, error, data);
116
+ c(error, data);
117
+ });
118
+ }
119
+ };
120
+
121
+ var Pos = CodeMirror.Pos;
122
+ var cls = "CodeMirror-Tern-";
123
+ var bigDoc = 250;
124
+
125
+ function getFile(ts, name, c) {
126
+ var buf = ts.docs[name];
127
+ if (buf)
128
+ c(docValue(ts, buf));
129
+ else if (ts.options.getFile)
130
+ ts.options.getFile(name, c);
131
+ else
132
+ c(null);
133
+ }
134
+
135
+ function findDoc(ts, doc, name) {
136
+ for (var n in ts.docs) {
137
+ var cur = ts.docs[n];
138
+ if (cur.doc == doc) return cur;
139
+ }
140
+ if (!name) for (var i = 0;; ++i) {
141
+ n = "[doc" + (i || "") + "]";
142
+ if (!ts.docs[n]) { name = n; break; }
143
+ }
144
+ return ts.addDoc(name, doc);
145
+ }
146
+
147
+ function trackChange(ts, doc, change) {
148
+ var data = findDoc(ts, doc);
149
+
150
+ var argHints = ts.cachedArgHints;
151
+ if (argHints && argHints.doc == doc && cmpPos(argHints.start, change.to) <= 0)
152
+ ts.cachedArgHints = null;
153
+
154
+ var changed = data.changed;
155
+ if (changed == null)
156
+ data.changed = changed = {from: change.from.line, to: change.from.line};
157
+ var end = change.from.line + (change.text.length - 1);
158
+ if (change.from.line < changed.to) changed.to = changed.to - (change.to.line - end);
159
+ if (end >= changed.to) changed.to = end + 1;
160
+ if (changed.from > change.from.line) changed.from = change.from.line;
161
+
162
+ if (doc.lineCount() > bigDoc && change.to - changed.from > 100) setTimeout(function() {
163
+ if (data.changed && data.changed.to - data.changed.from > 100) sendDoc(ts, data);
164
+ }, 200);
165
+ }
166
+
167
+ function sendDoc(ts, doc) {
168
+ ts.server.request({files: [{type: "full", name: doc.name, text: docValue(ts, doc)}]}, function(error) {
169
+ if (error) console.error(error);
170
+ else doc.changed = null;
171
+ });
172
+ }
173
+
174
+ // Completion
175
+
176
+ function hint(ts, cm, c) {
177
+ ts.request(cm, {type: "completions", types: true, docs: true, urls: true}, function(error, data) {
178
+ if (error) return showError(ts, cm, error);
179
+ var completions = [], after = "";
180
+ var from = data.start, to = data.end;
181
+ if (cm.getRange(Pos(from.line, from.ch - 2), from) == "[\"" &&
182
+ cm.getRange(to, Pos(to.line, to.ch + 2)) != "\"]")
183
+ after = "\"]";
184
+
185
+ for (var i = 0; i < data.completions.length; ++i) {
186
+ var completion = data.completions[i], className = typeToIcon(completion.type);
187
+ if (data.guess) className += " " + cls + "guess";
188
+ completions.push({text: completion.name + after,
189
+ displayText: completion.name,
190
+ className: className,
191
+ data: completion});
192
+ }
193
+
194
+ var obj = {from: from, to: to, list: completions};
195
+ var tooltip = null;
196
+ CodeMirror.on(obj, "close", function() { remove(tooltip); });
197
+ CodeMirror.on(obj, "update", function() { remove(tooltip); });
198
+ CodeMirror.on(obj, "select", function(cur, node) {
199
+ remove(tooltip);
200
+ var content = ts.options.completionTip ? ts.options.completionTip(cur.data) : cur.data.doc;
201
+ if (content) {
202
+ tooltip = makeTooltip(node.parentNode.getBoundingClientRect().right + window.pageXOffset,
203
+ node.getBoundingClientRect().top + window.pageYOffset, content);
204
+ tooltip.className += " " + cls + "hint-doc";
205
+ }
206
+ });
207
+ c(obj);
208
+ });
209
+ }
210
+
211
+ function typeToIcon(type) {
212
+ var suffix;
213
+ if (type == "?") suffix = "unknown";
214
+ else if (type == "number" || type == "string" || type == "bool") suffix = type;
215
+ else if (/^fn\(/.test(type)) suffix = "fn";
216
+ else if (/^\[/.test(type)) suffix = "array";
217
+ else suffix = "object";
218
+ return cls + "completion " + cls + "completion-" + suffix;
219
+ }
220
+
221
+ // Type queries
222
+
223
+ function showType(ts, cm) {
224
+ ts.request(cm, "type", function(error, data) {
225
+ if (error) return showError(ts, cm, error);
226
+ if (ts.options.typeTip) {
227
+ var tip = ts.options.typeTip(data);
228
+ } else {
229
+ var tip = elt("span", null, elt("strong", null, data.type || "not found"));
230
+ if (data.doc)
231
+ tip.appendChild(document.createTextNode(" — " + data.doc));
232
+ if (data.url) {
233
+ tip.appendChild(document.createTextNode(" "));
234
+ tip.appendChild(elt("a", null, "[docs]")).href = data.url;
235
+ }
236
+ }
237
+ tempTooltip(cm, tip);
238
+ });
239
+ }
240
+
241
+ // Maintaining argument hints
242
+
243
+ function updateArgHints(ts, cm) {
244
+ closeArgHints(ts);
245
+
246
+ if (cm.somethingSelected()) return;
247
+ var state = cm.getTokenAt(cm.getCursor()).state;
248
+ var inner = CodeMirror.innerMode(cm.getMode(), state);
249
+ if (inner.mode.name != "javascript") return;
250
+ var lex = inner.state.lexical;
251
+ if (lex.info != "call") return;
252
+
253
+ var ch, pos = lex.pos || 0, tabSize = cm.getOption("tabSize");
254
+ for (var line = cm.getCursor().line, e = Math.max(0, line - 9), found = false; line >= e; --line) {
255
+ var str = cm.getLine(line), extra = 0;
256
+ for (var pos = 0;;) {
257
+ var tab = str.indexOf("\t", pos);
258
+ if (tab == -1) break;
259
+ extra += tabSize - (tab + extra) % tabSize - 1;
260
+ pos = tab + 1;
261
+ }
262
+ ch = lex.column - extra;
263
+ if (str.charAt(ch) == "(") {found = true; break;}
264
+ }
265
+ if (!found) return;
266
+
267
+ var start = Pos(line, ch);
268
+ var cache = ts.cachedArgHints;
269
+ if (cache && cache.doc == cm.getDoc() && cmpPos(start, cache.start) == 0)
270
+ return showArgHints(ts, cm, pos);
271
+
272
+ ts.request(cm, {type: "type", preferFunction: true, end: start}, function(error, data) {
273
+ if (error || !data.type || !(/^fn\(/).test(data.type)) return;
274
+ ts.cachedArgHints = {
275
+ start: pos,
276
+ type: parseFnType(data.type),
277
+ name: data.exprName || data.name || "fn",
278
+ guess: data.guess,
279
+ doc: cm.getDoc()
280
+ };
281
+ showArgHints(ts, cm, pos);
282
+ });
283
+ }
284
+
285
+ function showArgHints(ts, cm, pos) {
286
+ closeArgHints(ts);
287
+
288
+ var cache = ts.cachedArgHints, tp = cache.type;
289
+ var tip = elt("span", cache.guess ? cls + "fhint-guess" : null,
290
+ elt("span", cls + "fname", cache.name), "(");
291
+ for (var i = 0; i < tp.args.length; ++i) {
292
+ if (i) tip.appendChild(document.createTextNode(", "));
293
+ var arg = tp.args[i];
294
+ tip.appendChild(elt("span", cls + "farg" + (i == pos ? " " + cls + "farg-current" : ""), arg.name || "?"));
295
+ if (arg.type != "?") {
296
+ tip.appendChild(document.createTextNode(":\u00a0"));
297
+ tip.appendChild(elt("span", cls + "type", arg.type));
298
+ }
299
+ }
300
+ tip.appendChild(document.createTextNode(tp.rettype ? ") ->\u00a0" : ")"));
301
+ if (tp.rettype) tip.appendChild(elt("span", cls + "type", tp.rettype));
302
+ var place = cm.cursorCoords(null, "page");
303
+ ts.activeArgHints = makeTooltip(place.right + 1, place.bottom, tip);
304
+ }
305
+
306
+ function parseFnType(text) {
307
+ var args = [], pos = 3;
308
+
309
+ function skipMatching(upto) {
310
+ var depth = 0, start = pos;
311
+ for (;;) {
312
+ var next = text.charAt(pos);
313
+ if (upto.test(next) && !depth) return text.slice(start, pos);
314
+ if (/[{\[\(]/.test(next)) ++depth;
315
+ else if (/[}\]\)]/.test(next)) --depth;
316
+ ++pos;
317
+ }
318
+ }
319
+
320
+ // Parse arguments
321
+ if (text.charAt(pos) != ")") for (;;) {
322
+ var name = text.slice(pos).match(/^([^, \(\[\{]+): /);
323
+ if (name) {
324
+ pos += name[0].length;
325
+ name = name[1];
326
+ }
327
+ args.push({name: name, type: skipMatching(/[\),]/)});
328
+ if (text.charAt(pos) == ")") break;
329
+ pos += 2;
330
+ }
331
+
332
+ var rettype = text.slice(pos).match(/^\) -> (.*)$/);
333
+
334
+ return {args: args, rettype: rettype && rettype[1]};
335
+ }
336
+
337
+ // Moving to the definition of something
338
+
339
+ function jumpToDef(ts, cm) {
340
+ function inner(varName) {
341
+ var req = {type: "definition", variable: varName || null};
342
+ var doc = findDoc(ts, cm.getDoc());
343
+ ts.server.request(buildRequest(ts, doc, req), function(error, data) {
344
+ if (error) return showError(ts, cm, error);
345
+ if (!data.file && data.url) { window.open(data.url); return; }
346
+
347
+ if (data.file) {
348
+ var localDoc = ts.docs[data.file], found;
349
+ if (localDoc && (found = findContext(localDoc.doc, data))) {
350
+ ts.jumpStack.push({file: doc.name,
351
+ start: cm.getCursor("from"),
352
+ end: cm.getCursor("to")});
353
+ moveTo(ts, doc, localDoc, found.start, found.end);
354
+ return;
355
+ }
356
+ }
357
+ showError(ts, cm, "Could not find a definition.");
358
+ });
359
+ }
360
+
361
+ if (!atInterestingExpression(cm))
362
+ dialog(cm, "Jump to variable", function(name) { if (name) inner(name); });
363
+ else
364
+ inner();
365
+ }
366
+
367
+ function jumpBack(ts, cm) {
368
+ var pos = ts.jumpStack.pop(), doc = pos && ts.docs[pos.file];
369
+ if (!doc) return;
370
+ moveTo(ts, findDoc(ts, cm.getDoc()), doc, pos.start, pos.end);
371
+ }
372
+
373
+ function moveTo(ts, curDoc, doc, start, end) {
374
+ doc.doc.setSelection(end, start);
375
+ if (curDoc != doc && ts.options.switchToDoc) {
376
+ closeArgHints(ts);
377
+ ts.options.switchToDoc(doc.name);
378
+ }
379
+ }
380
+
381
+ // The {line,ch} representation of positions makes this rather awkward.
382
+ function findContext(doc, data) {
383
+ var before = data.context.slice(0, data.contextOffset).split("\n");
384
+ var startLine = data.start.line - (before.length - 1);
385
+ var start = Pos(startLine, (before.length == 1 ? data.start.ch : doc.getLine(startLine).length) - before[0].length);
386
+
387
+ var text = doc.getLine(startLine).slice(start.ch);
388
+ for (var cur = startLine + 1; cur < doc.lineCount() && text.length < data.context.length; ++cur)
389
+ text += "\n" + doc.getLine(cur);
390
+ if (text.slice(0, data.context.length) == data.context) return data;
391
+
392
+ var cursor = doc.getSearchCursor(data.context, 0, false);
393
+ var nearest, nearestDist = Infinity;
394
+ while (cursor.findNext()) {
395
+ var from = cursor.from(), dist = Math.abs(from.line - start.line) * 10000;
396
+ if (!dist) dist = Math.abs(from.ch - start.ch);
397
+ if (dist < nearestDist) { nearest = from; nearestDist = dist; }
398
+ }
399
+ if (!nearest) return null;
400
+
401
+ if (before.length == 1)
402
+ nearest.ch += before[0].length;
403
+ else
404
+ nearest = Pos(nearest.line + (before.length - 1), before[before.length - 1].length);
405
+ if (data.start.line == data.end.line)
406
+ var end = Pos(nearest.line, nearest.ch + (data.end.ch - data.start.ch));
407
+ else
408
+ var end = Pos(nearest.line + (data.end.line - data.start.line), data.end.ch);
409
+ return {start: nearest, end: end};
410
+ }
411
+
412
+ function atInterestingExpression(cm) {
413
+ var pos = cm.getCursor("end"), tok = cm.getTokenAt(pos);
414
+ if (tok.start < pos.ch && (tok.type == "comment" || tok.type == "string")) return false;
415
+ return /\w/.test(cm.getLine(pos.line).slice(Math.max(pos.ch - 1, 0), pos.ch + 1));
416
+ }
417
+
418
+ // Variable renaming
419
+
420
+ function rename(ts, cm) {
421
+ var token = cm.getTokenAt(cm.getCursor());
422
+ if (!/\w/.test(token.string)) showError(ts, cm, "Not at a variable");
423
+ dialog(cm, "New name for " + token.string, function(newName) {
424
+ ts.request(cm, {type: "rename", newName: newName, fullDocs: true}, function(error, data) {
425
+ if (error) return showError(ts, cm, error);
426
+ applyChanges(ts, data.changes);
427
+ });
428
+ });
429
+ }
430
+
431
+ var nextChangeOrig = 0;
432
+ function applyChanges(ts, changes) {
433
+ var perFile = Object.create(null);
434
+ for (var i = 0; i < changes.length; ++i) {
435
+ var ch = changes[i];
436
+ (perFile[ch.file] || (perFile[ch.file] = [])).push(ch);
437
+ }
438
+ for (var file in perFile) {
439
+ var known = ts.docs[file], chs = perFile[file];;
440
+ if (!known) continue;
441
+ chs.sort(function(a, b) { return cmpPos(b, a); });
442
+ var origin = "*rename" + (++nextChangeOrig);
443
+ for (var i = 0; i < chs.length; ++i) {
444
+ var ch = chs[i];
445
+ known.doc.replaceRange(ch.text, ch.start, ch.end, origin);
446
+ }
447
+ }
448
+ }
449
+
450
+ // Generic request-building helper
451
+
452
+ function buildRequest(ts, doc, query) {
453
+ var files = [], offsetLines = 0, allowFragments = !query.fullDocs;
454
+ if (!allowFragments) delete query.fullDocs;
455
+ if (typeof query == "string") query = {type: query};
456
+ query.lineCharPositions = true;
457
+ if (query.end == null) {
458
+ query.end = doc.doc.getCursor("end");
459
+ if (doc.doc.somethingSelected())
460
+ query.start = doc.doc.getCursor("start");
461
+ }
462
+ var startPos = query.start || query.end;
463
+
464
+ if (doc.changed) {
465
+ if (doc.doc.lineCount() > bigDoc && allowFragments !== false &&
466
+ doc.changed.to - doc.changed.from < 100 &&
467
+ doc.changed.from <= startPos.line && doc.changed.to > query.end.line) {
468
+ files.push(getFragmentAround(doc, startPos, query.end));
469
+ query.file = "#0";
470
+ var offsetLines = files[0].offsetLines;
471
+ if (query.start != null) query.start = Pos(query.start.line - -offsetLines, query.start.ch);
472
+ query.end = Pos(query.end.line - offsetLines, query.end.ch);
473
+ } else {
474
+ files.push({type: "full",
475
+ name: doc.name,
476
+ text: docValue(ts, doc)});
477
+ query.file = doc.name;
478
+ doc.changed = null;
479
+ }
480
+ } else {
481
+ query.file = doc.name;
482
+ }
483
+ for (var name in ts.docs) {
484
+ var cur = ts.docs[name];
485
+ if (cur.changed && cur != doc) {
486
+ files.push({type: "full", name: cur.name, text: docValue(ts, cur)});
487
+ cur.changed = null;
488
+ }
489
+ }
490
+
491
+ return {query: query, files: files};
492
+ }
493
+
494
+ function getFragmentAround(data, start, end) {
495
+ var doc = data.doc;
496
+ var minIndent = null, minLine = null, endLine, tabSize = 4;
497
+ for (var p = start.line - 1, min = Math.max(0, p - 50); p >= min; --p) {
498
+ var line = doc.getLine(p), fn = line.search(/\bfunction\b/);
499
+ if (fn < 0) continue;
500
+ var indent = CodeMirror.countColumn(line, null, tabSize);
501
+ if (minIndent != null && minIndent <= indent) continue;
502
+ minIndent = indent;
503
+ minLine = p;
504
+ }
505
+ if (minLine == null) minLine = min;
506
+ var max = Math.min(doc.lastLine(), end.line + 20);
507
+ if (minIndent == null || minIndent == CodeMirror.countColumn(doc.getLine(start.line), null, tabSize))
508
+ endLine = max;
509
+ else for (endLine = end.line + 1; endLine < max; ++endLine) {
510
+ var indent = CodeMirror.countColumn(doc.getLine(endLine), null, tabSize);
511
+ if (indent <= minIndent) break;
512
+ }
513
+ var from = Pos(minLine, 0);
514
+
515
+ return {type: "part",
516
+ name: data.name,
517
+ offsetLines: from.line,
518
+ text: doc.getRange(from, Pos(endLine, 0))};
519
+ }
520
+
521
+ // Generic utilities
522
+
523
+ function cmpPos(a, b) { return a.line - b.line || a.ch - b.ch; }
524
+
525
+ function elt(tagname, cls /*, ... elts*/) {
526
+ var e = document.createElement(tagname);
527
+ if (cls) e.className = cls;
528
+ for (var i = 2; i < arguments.length; ++i) {
529
+ var elt = arguments[i];
530
+ if (typeof elt == "string") elt = document.createTextNode(elt);
531
+ e.appendChild(elt);
532
+ }
533
+ return e;
534
+ }
535
+
536
+ function dialog(cm, text, f) {
537
+ if (cm.openDialog)
538
+ cm.openDialog(text + ": <input type=text>", f);
539
+ else
540
+ f(prompt(text, ""));
541
+ }
542
+
543
+ // Tooltips
544
+
545
+ function tempTooltip(cm, content) {
546
+ var where = cm.cursorCoords();
547
+ var tip = makeTooltip(where.right + 1, where.bottom, content);
548
+ function clear() {
549
+ if (!tip.parentNode) return;
550
+ cm.off("cursorActivity", clear);
551
+ fadeOut(tip);
552
+ }
553
+ setTimeout(clear, 1700);
554
+ cm.on("cursorActivity", clear);
555
+ }
556
+
557
+ function makeTooltip(x, y, content) {
558
+ var node = elt("div", cls + "tooltip", content);
559
+ node.style.left = x + "px";
560
+ node.style.top = y + "px";
561
+ document.body.appendChild(node);
562
+ return node;
563
+ }
564
+
565
+ function remove(node) {
566
+ var p = node && node.parentNode;
567
+ if (p) p.removeChild(node);
568
+ }
569
+
570
+ function fadeOut(tooltip) {
571
+ tooltip.style.opacity = "0";
572
+ setTimeout(function() { remove(tooltip); }, 1100);
573
+ }
574
+
575
+ function showError(ts, cm, msg) {
576
+ if (ts.options.showError)
577
+ ts.options.showError(cm, msg);
578
+ else
579
+ tempTooltip(cm, String(msg));
580
+ }
581
+
582
+ function closeArgHints(ts) {
583
+ if (ts.activeArgHints) { remove(ts.activeArgHints); ts.activeArgHints = null; }
584
+ }
585
+
586
+ function docValue(ts, doc) {
587
+ var val = doc.doc.getValue();
588
+ if (ts.options.fileFilter) val = ts.options.fileFilter(val, doc.name, doc.doc);
589
+ return val;
590
+ }
591
+
592
+ // Worker wrapper
593
+
594
+ function WorkerServer(ts) {
595
+ var worker = new Worker(ts.options.workerScript);
596
+ worker.postMessage({type: "init",
597
+ defs: ts.options.defs,
598
+ plugins: ts.options.plugins,
599
+ scripts: ts.options.workerDeps});
600
+ var msgId = 0, pending = {};
601
+
602
+ function send(data, c) {
603
+ if (c) {
604
+ data.id = ++msgId;
605
+ pending[msgId] = c;
606
+ }
607
+ worker.postMessage(data);
608
+ }
609
+ worker.onmessage = function(e) {
610
+ var data = e.data;
611
+ if (data.type == "getFile") {
612
+ getFile(ts, name, function(err, text) {
613
+ send({type: "getFile", err: String(err), text: text, id: data.id});
614
+ });
615
+ } else if (data.type == "debug") {
616
+ console.log(data.message);
617
+ } else if (data.id && pending[data.id]) {
618
+ pending[data.id](data.err, data.body);
619
+ delete pending[data.id];
620
+ }
621
+ };
622
+ worker.onerror = function(e) {
623
+ for (var id in pending) pending[id](e);
624
+ pending = {};
625
+ };
626
+
627
+ this.addFile = function(name, text) { send({type: "add", name: name, text: text}); };
628
+ this.delFile = function(name) { send({type: "del", name: name}); };
629
+ this.request = function(body, c) { send({type: "req", body: body}, c); };
630
+ }
631
+ })();
assets/lib/codemirror/addon/tern/worker.js ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var server;
2
+
3
+ this.onmessage = function(e) {
4
+ var data = e.data;
5
+ switch (data.type) {
6
+ case "init": return startServer(data.defs, data.plugins, data.scripts);
7
+ case "add": return server.addFile(data.name, data.text);
8
+ case "del": return server.delFile(data.name);
9
+ case "req": return server.request(data.body, function(err, reqData) {
10
+ postMessage({id: data.id, body: reqData, err: err && String(err)});
11
+ });
12
+ case "getFile":
13
+ var c = pending[data.id];
14
+ delete pending[data.id];
15
+ return c(data.err, data.text);
16
+ default: throw new Error("Unknown message type: " + data.type);
17
+ }
18
+ };
19
+
20
+ var nextId = 0, pending = {};
21
+ function getFile(file, c) {
22
+ postMessage({type: "getFile", name: file, id: ++nextId});
23
+ pending[nextId] = c;
24
+ }
25
+
26
+ function startServer(defs, plugins, scripts) {
27
+ if (scripts) importScripts.apply(null, scripts);
28
+
29
+ server = new tern.Server({
30
+ getFile: getFile,
31
+ async: true,
32
+ defs: defs,
33
+ plugins: plugins
34
+ });
35
+ }
36
+
37
+ var console = {
38
+ log: function(v) { postMessage({type: "debug", message: v}); }
39
+ };
assets/lib/codemirror/lib/codemirror.css ADDED
@@ -0,0 +1,273 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* BASICS */
2
+
3
+ .CodeMirror {
4
+ /* Set height, width, borders, and global font properties here */
5
+ font-family: monospace;
6
+ height: 300px;
7
+ }
8
+ .CodeMirror-scroll {
9
+ /* Set scrolling behaviour here */
10
+ overflow: auto;
11
+ }
12
+
13
+ /* PADDING */
14
+
15
+ .CodeMirror-lines {
16
+ padding: 4px 0; /* Vertical padding around content */
17
+ }
18
+ .CodeMirror pre {
19
+ padding: 0 4px; /* Horizontal padding of content */
20
+ }
21
+
22
+ .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
23
+ background-color: white; /* The little square between H and V scrollbars */
24
+ }
25
+
26
+ /* GUTTER */
27
+
28
+ .CodeMirror-gutters {
29
+ border-right: 1px solid #ddd;
30
+ background-color: #f7f7f7;
31
+ white-space: nowrap;
32
+ }
33
+ .CodeMirror-linenumbers {}
34
+ .CodeMirror-linenumber {
35
+ padding: 0 3px 0 5px;
36
+ min-width: 20px;
37
+ text-align: right;
38
+ color: #999;
39
+ }
40
+
41
+ /* CURSOR */
42
+
43
+ .CodeMirror div.CodeMirror-cursor {
44
+ border-left: 1px solid black;
45
+ z-index: 3;
46
+ }
47
+ /* Shown when moving in bi-directional text */
48
+ .CodeMirror div.CodeMirror-secondarycursor {
49
+ border-left: 1px solid silver;
50
+ }
51
+ .CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor {
52
+ width: auto;
53
+ border: 0;
54
+ background: #7e7;
55
+ z-index: 1;
56
+ }
57
+ /* Can style cursor different in overwrite (non-insert) mode */
58
+ .CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite {}
59
+
60
+ .cm-tab { display: inline-block; }
61
+
62
+ /* DEFAULT THEME */
63
+
64
+ .cm-s-default .cm-keyword {color: #708;}
65
+ .cm-s-default .cm-atom {color: #219;}
66
+ .cm-s-default .cm-number {color: #164;}
67
+ .cm-s-default .cm-def {color: #00f;}
68
+ .cm-s-default .cm-variable {color: black;}
69
+ .cm-s-default .cm-variable-2 {color: #05a;}
70
+ .cm-s-default .cm-variable-3 {color: #085;}
71
+ .cm-s-default .cm-property {color: black;}
72
+ .cm-s-default .cm-operator {color: black;}
73
+ .cm-s-default .cm-comment {color: #a50;}
74
+ .cm-s-default .cm-string {color: #a11;}
75
+ .cm-s-default .cm-string-2 {color: #f50;}
76
+ .cm-s-default .cm-meta {color: #555;}
77
+ .cm-s-default .cm-error {color: #f00;}
78
+ .cm-s-default .cm-qualifier {color: #555;}
79
+ .cm-s-default .cm-builtin {color: #30a;}
80
+ .cm-s-default .cm-bracket {color: #997;}
81
+ .cm-s-default .cm-tag {color: #170;}
82
+ .cm-s-default .cm-attribute {color: #00c;}
83
+ .cm-s-default .cm-header {color: blue;}
84
+ .cm-s-default .cm-quote {color: #090;}
85
+ .cm-s-default .cm-hr {color: #999;}
86
+ .cm-s-default .cm-link {color: #00c;}
87
+
88
+ .cm-negative {color: #d44;}
89
+ .cm-positive {color: #292;}
90
+ .cm-header, .cm-strong {font-weight: bold;}
91
+ .cm-em {font-style: italic;}
92
+ .cm-link {text-decoration: underline;}
93
+
94
+ .cm-invalidchar {color: #f00;}
95
+
96
+ div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
97
+ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
98
+ .CodeMirror-activeline-background {background: #e8f2ff;}
99
+
100
+ /* STOP */
101
+
102
+ /* The rest of this file contains styles related to the mechanics of
103
+ the editor. You probably shouldn't touch them. */
104
+
105
+ .CodeMirror {
106
+ line-height: 1;
107
+ position: relative;
108
+ overflow: hidden;
109
+ background: white;
110
+ color: black;
111
+ }
112
+
113
+ .CodeMirror-scroll {
114
+ /* 30px is the magic margin used to hide the element's real scrollbars */
115
+ /* See overflow: hidden in .CodeMirror */
116
+ margin-bottom: -30px; margin-right: -30px;
117
+ padding-bottom: 30px; padding-right: 30px;
118
+ height: 100%;
119
+ outline: none; /* Prevent dragging from highlighting the element */
120
+ position: relative;
121
+ -moz-box-sizing: content-box;
122
+ box-sizing: content-box;
123
+ }
124
+ .CodeMirror-sizer {
125
+ position: relative;
126
+ }
127
+
128
+ /* The fake, visible scrollbars. Used to force redraw during scrolling
129
+ before actuall scrolling happens, thus preventing shaking and
130
+ flickering artifacts. */
131
+ .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
132
+ position: absolute;
133
+ z-index: 6;
134
+ display: none;
135
+ }
136
+ .CodeMirror-vscrollbar {
137
+ right: 0; top: 0;
138
+ overflow-x: hidden;
139
+ overflow-y: scroll;
140
+ }
141
+ .CodeMirror-hscrollbar {
142
+ bottom: 0; left: 0;
143
+ overflow-y: hidden;
144
+ overflow-x: scroll;
145
+ }
146
+ .CodeMirror-scrollbar-filler {
147
+ right: 0; bottom: 0;
148
+ }
149
+ .CodeMirror-gutter-filler {
150
+ left: 0; bottom: 0;
151
+ }
152
+
153
+ .CodeMirror-gutters {
154
+ position: absolute; left: 0; top: 0;
155
+ padding-bottom: 30px;
156
+ z-index: 3;
157
+ }
158
+ .CodeMirror-gutter {
159
+ white-space: normal;
160
+ height: 100%;
161
+ -moz-box-sizing: content-box;
162
+ box-sizing: content-box;
163
+ padding-bottom: 30px;
164
+ margin-bottom: -32px;
165
+ display: inline-block;
166
+ /* Hack to make IE7 behave */
167
+ *zoom:1;
168
+ *display:inline;
169
+ }
170
+ .CodeMirror-gutter-elt {
171
+ position: absolute;
172
+ cursor: default;
173
+ z-index: 4;
174
+ }
175
+
176
+ .CodeMirror-lines {
177
+ cursor: text;
178
+ }
179
+ .CodeMirror pre {
180
+ /* Reset some styles that the rest of the page might have set */
181
+ -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
182
+ border-width: 0;
183
+ background: transparent;
184
+ font-family: inherit;
185
+ font-size: inherit;
186
+ margin: 0;
187
+ white-space: pre;
188
+ word-wrap: normal;
189
+ line-height: inherit;
190
+ color: inherit;
191
+ z-index: 2;
192
+ position: relative;
193
+ overflow: visible;
194
+ }
195
+ .CodeMirror-wrap pre {
196
+ word-wrap: break-word;
197
+ white-space: pre-wrap;
198
+ word-break: normal;
199
+ }
200
+ .CodeMirror-code pre {
201
+ border-right: 30px solid transparent;
202
+ width: -webkit-fit-content;
203
+ width: -moz-fit-content;
204
+ width: fit-content;
205
+ }
206
+ .CodeMirror-wrap .CodeMirror-code pre {
207
+ border-right: none;
208
+ width: auto;
209
+ }
210
+ .CodeMirror-linebackground {
211
+ position: absolute;
212
+ left: 0; right: 0; top: 0; bottom: 0;
213
+ z-index: 0;
214
+ }
215
+
216
+ .CodeMirror-linewidget {
217
+ position: relative;
218
+ z-index: 2;
219
+ overflow: auto;
220
+ }
221
+
222
+ .CodeMirror-widget {}
223
+
224
+ .CodeMirror-wrap .CodeMirror-scroll {
225
+ overflow-x: hidden;
226
+ }
227
+
228
+ .CodeMirror-measure {
229
+ position: absolute;
230
+ width: 100%;
231
+ height: 0;
232
+ overflow: hidden;
233
+ visibility: hidden;
234
+ }
235
+ .CodeMirror-measure pre { position: static; }
236
+
237
+ .CodeMirror div.CodeMirror-cursor {
238
+ position: absolute;
239
+ visibility: hidden;
240
+ border-right: none;
241
+ width: 0;
242
+ }
243
+ .CodeMirror-focused div.CodeMirror-cursor {
244
+ visibility: visible;
245
+ }
246
+
247
+ .CodeMirror-selected { background: #d9d9d9; }
248
+ .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
249
+
250
+ .cm-searching {
251
+ background: #ffa;
252
+ background: rgba(255, 255, 0, .4);
253
+ }
254
+
255
+ /* IE7 hack to prevent it from returning funny offsetTops on the spans */
256
+ .CodeMirror span { *vertical-align: text-bottom; }
257
+
258
+ @media print {
259
+ /* Hide the cursor when printing */
260
+ .CodeMirror div.CodeMirror-cursor {
261
+ visibility: hidden;
262
+ }
263
+ }
264
+
265
+
266
+ /* fullscreen adapted code */
267
+ .CodeMirror-fullscreen {
268
+ position: fixed;
269
+ top: 0; left: 0; right: 0; bottom: 0;
270
+ height: auto;
271
+ width:auto !important;
272
+ z-index: 999999;
273
+ }
assets/lib/codemirror/lib/codemirror.js ADDED
@@ -0,0 +1,5887 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // CodeMirror version 3.17
2
+ //
3
+ // CodeMirror is the only global var we claim
4
+ window.CodeMirror = (function() {
5
+ "use strict";
6
+
7
+ // BROWSER SNIFFING
8
+
9
+ // Crude, but necessary to handle a number of hard-to-feature-detect
10
+ // bugs and behavior differences.
11
+ var gecko = /gecko\/\d/i.test(navigator.userAgent);
12
+ var ie = /MSIE \d/.test(navigator.userAgent);
13
+ var ie_lt8 = ie && (document.documentMode == null || document.documentMode < 8);
14
+ var ie_lt9 = ie && (document.documentMode == null || document.documentMode < 9);
15
+ var webkit = /WebKit\//.test(navigator.userAgent);
16
+ var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent);
17
+ var chrome = /Chrome\//.test(navigator.userAgent);
18
+ var opera = /Opera\//.test(navigator.userAgent);
19
+ var safari = /Apple Computer/.test(navigator.vendor);
20
+ var khtml = /KHTML\//.test(navigator.userAgent);
21
+ var mac_geLion = /Mac OS X 1\d\D([7-9]|\d\d)\D/.test(navigator.userAgent);
22
+ var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent);
23
+ var phantom = /PhantomJS/.test(navigator.userAgent);
24
+
25
+ var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent);
26
+ // This is woefully incomplete. Suggestions for alternative methods welcome.
27
+ var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent);
28
+ var mac = ios || /Mac/.test(navigator.platform);
29
+ var windows = /win/i.test(navigator.platform);
30
+
31
+ var opera_version = opera && navigator.userAgent.match(/Version\/(\d*\.\d*)/);
32
+ if (opera_version) opera_version = Number(opera_version[1]);
33
+ if (opera_version && opera_version >= 15) { opera = false; webkit = true; }
34
+ // Some browsers use the wrong event properties to signal cmd/ctrl on OS X
35
+ var flipCtrlCmd = mac && (qtwebkit || opera && (opera_version == null || opera_version < 12.11));
36
+ var captureMiddleClick = gecko || (ie && !ie_lt9);
37
+
38
+ // Optimize some code when these features are not used
39
+ var sawReadOnlySpans = false, sawCollapsedSpans = false;
40
+
41
+ // CONSTRUCTOR
42
+
43
+ function CodeMirror(place, options) {
44
+ if (!(this instanceof CodeMirror)) return new CodeMirror(place, options);
45
+
46
+ this.options = options = options || {};
47
+ // Determine effective options based on given values and defaults.
48
+ for (var opt in defaults) if (!options.hasOwnProperty(opt) && defaults.hasOwnProperty(opt))
49
+ options[opt] = defaults[opt];
50
+ setGuttersForLineNumbers(options);
51
+
52
+ var docStart = typeof options.value == "string" ? 0 : options.value.first;
53
+ var display = this.display = makeDisplay(place, docStart);
54
+ display.wrapper.CodeMirror = this;
55
+ updateGutters(this);
56
+ if (options.autofocus && !mobile) focusInput(this);
57
+
58
+ this.state = {keyMaps: [],
59
+ overlays: [],
60
+ modeGen: 0,
61
+ overwrite: false, focused: false,
62
+ suppressEdits: false, pasteIncoming: false,
63
+ draggingText: false,
64
+ highlight: new Delayed()};
65
+
66
+ themeChanged(this);
67
+ if (options.lineWrapping)
68
+ this.display.wrapper.className += " CodeMirror-wrap";
69
+
70
+ var doc = options.value;
71
+ if (typeof doc == "string") doc = new Doc(options.value, options.mode);
72
+ operation(this, attachDoc)(this, doc);
73
+
74
+ // Override magic textarea content restore that IE sometimes does
75
+ // on our hidden textarea on reload
76
+ if (ie) setTimeout(bind(resetInput, this, true), 20);
77
+
78
+ registerEventHandlers(this);
79
+ // IE throws unspecified error in certain cases, when
80
+ // trying to access activeElement before onload
81
+ var hasFocus; try { hasFocus = (document.activeElement == display.input); } catch(e) { }
82
+ if (hasFocus || (options.autofocus && !mobile)) setTimeout(bind(onFocus, this), 20);
83
+ else onBlur(this);
84
+
85
+ operation(this, function() {
86
+ for (var opt in optionHandlers)
87
+ if (optionHandlers.propertyIsEnumerable(opt))
88
+ optionHandlers[opt](this, options[opt], Init);
89
+ for (var i = 0; i < initHooks.length; ++i) initHooks[i](this);
90
+ })();
91
+ }
92
+
93
+ // DISPLAY CONSTRUCTOR
94
+
95
+ function makeDisplay(place, docStart) {
96
+ var d = {};
97
+
98
+ var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none; font-size: 4px;");
99
+ if (webkit) input.style.width = "1000px";
100
+ else input.setAttribute("wrap", "off");
101
+ // if border: 0; -- iOS fails to open keyboard (issue #1287)
102
+ if (ios) input.style.border = "1px solid black";
103
+ input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off"); input.setAttribute("spellcheck", "false");
104
+
105
+ // Wraps and hides input textarea
106
+ d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
107
+ // The actual fake scrollbars.
108
+ d.scrollbarH = elt("div", [elt("div", null, null, "height: 1px")], "CodeMirror-hscrollbar");
109
+ d.scrollbarV = elt("div", [elt("div", null, null, "width: 1px")], "CodeMirror-vscrollbar");
110
+ d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler");
111
+ d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler");
112
+ // DIVs containing the selection and the actual code
113
+ d.lineDiv = elt("div", null, "CodeMirror-code");
114
+ d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1");
115
+ // Blinky cursor, and element used to ensure cursor fits at the end of a line
116
+ d.cursor = elt("div", "\u00a0", "CodeMirror-cursor");
117
+ // Secondary cursor, shown when on a 'jump' in bi-directional text
118
+ d.otherCursor = elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor");
119
+ // Used to measure text size
120
+ d.measure = elt("div", null, "CodeMirror-measure");
121
+ // Wraps everything that needs to exist inside the vertically-padded coordinate system
122
+ d.lineSpace = elt("div", [d.measure, d.selectionDiv, d.lineDiv, d.cursor, d.otherCursor],
123
+ null, "position: relative; outline: none");
124
+ // Moved around its parent to cover visible view
125
+ d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null, "position: relative");
126
+ // Set to the height of the text, causes scrolling
127
+ d.sizer = elt("div", [d.mover], "CodeMirror-sizer");
128
+ // D is needed because behavior of elts with overflow: auto and padding is inconsistent across browsers
129
+ d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerCutOff + "px; width: 1px;");
130
+ // Will contain the gutters, if any
131
+ d.gutters = elt("div", null, "CodeMirror-gutters");
132
+ d.lineGutter = null;
133
+ // Provides scrolling
134
+ d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll");
135
+ d.scroller.setAttribute("tabIndex", "-1");
136
+ // The element in which the editor lives.
137
+ d.wrapper = elt("div", [d.inputDiv, d.scrollbarH, d.scrollbarV,
138
+ d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror");
139
+ // Work around IE7 z-index bug
140
+ if (ie_lt8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; }
141
+ if (place.appendChild) place.appendChild(d.wrapper); else place(d.wrapper);
142
+
143
+ // Needed to hide big blue blinking cursor on Mobile Safari
144
+ if (ios) input.style.width = "0px";
145
+ if (!webkit) d.scroller.draggable = true;
146
+ // Needed to handle Tab key in KHTML
147
+ if (khtml) { d.inputDiv.style.height = "1px"; d.inputDiv.style.position = "absolute"; }
148
+ // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
149
+ else if (ie_lt8) d.scrollbarH.style.minWidth = d.scrollbarV.style.minWidth = "18px";
150
+
151
+ // Current visible range (may be bigger than the view window).
152
+ d.viewOffset = d.lastSizeC = 0;
153
+ d.showingFrom = d.showingTo = docStart;
154
+
155
+ // Used to only resize the line number gutter when necessary (when
156
+ // the amount of lines crosses a boundary that makes its width change)
157
+ d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null;
158
+ // See readInput and resetInput
159
+ d.prevInput = "";
160
+ // Set to true when a non-horizontal-scrolling widget is added. As
161
+ // an optimization, widget aligning is skipped when d is false.
162
+ d.alignWidgets = false;
163
+ // Flag that indicates whether we currently expect input to appear
164
+ // (after some event like 'keypress' or 'input') and are polling
165
+ // intensively.
166
+ d.pollingFast = false;
167
+ // Self-resetting timeout for the poller
168
+ d.poll = new Delayed();
169
+
170
+ d.cachedCharWidth = d.cachedTextHeight = null;
171
+ d.measureLineCache = [];
172
+ d.measureLineCachePos = 0;
173
+
174
+ // Tracks when resetInput has punted to just putting a short
175
+ // string instead of the (large) selection.
176
+ d.inaccurateSelection = false;
177
+
178
+ // Tracks the maximum line length so that the horizontal scrollbar
179
+ // can be kept static when scrolling.
180
+ d.maxLine = null;
181
+ d.maxLineLength = 0;
182
+ d.maxLineChanged = false;
183
+
184
+ // Used for measuring wheel scrolling granularity
185
+ d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null;
186
+
187
+ return d;
188
+ }
189
+
190
+ // STATE UPDATES
191
+
192
+ // Used to get the editor into a consistent state again when options change.
193
+
194
+ function loadMode(cm) {
195
+ cm.doc.mode = CodeMirror.getMode(cm.options, cm.doc.modeOption);
196
+ cm.doc.iter(function(line) {
197
+ if (line.stateAfter) line.stateAfter = null;
198
+ if (line.styles) line.styles = null;
199
+ });
200
+ cm.doc.frontier = cm.doc.first;
201
+ startWorker(cm, 100);
202
+ cm.state.modeGen++;
203
+ if (cm.curOp) regChange(cm);
204
+ }
205
+
206
+ function wrappingChanged(cm) {
207
+ if (cm.options.lineWrapping) {
208
+ cm.display.wrapper.className += " CodeMirror-wrap";
209
+ cm.display.sizer.style.minWidth = "";
210
+ } else {
211
+ cm.display.wrapper.className = cm.display.wrapper.className.replace(" CodeMirror-wrap", "");
212
+ computeMaxLength(cm);
213
+ }
214
+ estimateLineHeights(cm);
215
+ regChange(cm);
216
+ clearCaches(cm);
217
+ setTimeout(function(){updateScrollbars(cm);}, 100);
218
+ }
219
+
220
+ function estimateHeight(cm) {
221
+ var th = textHeight(cm.display), wrapping = cm.options.lineWrapping;
222
+ var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3);
223
+ return function(line) {
224
+ if (lineIsHidden(cm.doc, line))
225
+ return 0;
226
+ else if (wrapping)
227
+ return (Math.ceil(line.text.length / perLine) || 1) * th;
228
+ else
229
+ return th;
230
+ };
231
+ }
232
+
233
+ function estimateLineHeights(cm) {
234
+ var doc = cm.doc, est = estimateHeight(cm);
235
+ doc.iter(function(line) {
236
+ var estHeight = est(line);
237
+ if (estHeight != line.height) updateLineHeight(line, estHeight);
238
+ });
239
+ }
240
+
241
+ function keyMapChanged(cm) {
242
+ var map = keyMap[cm.options.keyMap], style = map.style;
243
+ cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-keymap-\S+/g, "") +
244
+ (style ? " cm-keymap-" + style : "");
245
+ cm.state.disableInput = map.disableInput;
246
+ }
247
+
248
+ function themeChanged(cm) {
249
+ cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") +
250
+ cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-");
251
+ clearCaches(cm);
252
+ }
253
+
254
+ function guttersChanged(cm) {
255
+ updateGutters(cm);
256
+ regChange(cm);
257
+ setTimeout(function(){alignHorizontally(cm);}, 20);
258
+ }
259
+
260
+ function updateGutters(cm) {
261
+ var gutters = cm.display.gutters, specs = cm.options.gutters;
262
+ removeChildren(gutters);
263
+ for (var i = 0; i < specs.length; ++i) {
264
+ var gutterClass = specs[i];
265
+ var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass));
266
+ if (gutterClass == "CodeMirror-linenumbers") {
267
+ cm.display.lineGutter = gElt;
268
+ gElt.style.width = (cm.display.lineNumWidth || 1) + "px";
269
+ }
270
+ }
271
+ gutters.style.display = i ? "" : "none";
272
+ }
273
+
274
+ function lineLength(doc, line) {
275
+ if (line.height == 0) return 0;
276
+ var len = line.text.length, merged, cur = line;
277
+ while (merged = collapsedSpanAtStart(cur)) {
278
+ var found = merged.find();
279
+ cur = getLine(doc, found.from.line);
280
+ len += found.from.ch - found.to.ch;
281
+ }
282
+ cur = line;
283
+ while (merged = collapsedSpanAtEnd(cur)) {
284
+ var found = merged.find();
285
+ len -= cur.text.length - found.from.ch;
286
+ cur = getLine(doc, found.to.line);
287
+ len += cur.text.length - found.to.ch;
288
+ }
289
+ return len;
290
+ }
291
+
292
+ function computeMaxLength(cm) {
293
+ var d = cm.display, doc = cm.doc;
294
+ d.maxLine = getLine(doc, doc.first);
295
+ d.maxLineLength = lineLength(doc, d.maxLine);
296
+ d.maxLineChanged = true;
297
+ doc.iter(function(line) {
298
+ var len = lineLength(doc, line);
299
+ if (len > d.maxLineLength) {
300
+ d.maxLineLength = len;
301
+ d.maxLine = line;
302
+ }
303
+ });
304
+ }
305
+
306
+ // Make sure the gutters options contains the element
307
+ // "CodeMirror-linenumbers" when the lineNumbers option is true.
308
+ function setGuttersForLineNumbers(options) {
309
+ var found = indexOf(options.gutters, "CodeMirror-linenumbers");
310
+ if (found == -1 && options.lineNumbers) {
311
+ options.gutters = options.gutters.concat(["CodeMirror-linenumbers"]);
312
+ } else if (found > -1 && !options.lineNumbers) {
313
+ options.gutters = options.gutters.slice(0);
314
+ options.gutters.splice(i, 1);
315
+ }
316
+ }
317
+
318
+ // SCROLLBARS
319
+
320
+ // Re-synchronize the fake scrollbars with the actual size of the
321
+ // content. Optionally force a scrollTop.
322
+ function updateScrollbars(cm) {
323
+ var d = cm.display, docHeight = cm.doc.height;
324
+ var totalHeight = docHeight + paddingVert(d);
325
+ d.sizer.style.minHeight = d.heightForcer.style.top = totalHeight + "px";
326
+ d.gutters.style.height = Math.max(totalHeight, d.scroller.clientHeight - scrollerCutOff) + "px";
327
+ var scrollHeight = Math.max(totalHeight, d.scroller.scrollHeight);
328
+ var needsH = d.scroller.scrollWidth > (d.scroller.clientWidth + 1);
329
+ var needsV = scrollHeight > (d.scroller.clientHeight + 1);
330
+ if (needsV) {
331
+ d.scrollbarV.style.display = "block";
332
+ d.scrollbarV.style.bottom = needsH ? scrollbarWidth(d.measure) + "px" : "0";
333
+ d.scrollbarV.firstChild.style.height =
334
+ (scrollHeight - d.scroller.clientHeight + d.scrollbarV.clientHeight) + "px";
335
+ } else {
336
+ d.scrollbarV.style.display = "";
337
+ d.scrollbarV.firstChild.style.height = "0";
338
+ }
339
+ if (needsH) {
340
+ d.scrollbarH.style.display = "block";
341
+ d.scrollbarH.style.right = needsV ? scrollbarWidth(d.measure) + "px" : "0";
342
+ d.scrollbarH.firstChild.style.width =
343
+ (d.scroller.scrollWidth - d.scroller.clientWidth + d.scrollbarH.clientWidth) + "px";
344
+ } else {
345
+ d.scrollbarH.style.display = "";
346
+ d.scrollbarH.firstChild.style.width = "0";
347
+ }
348
+ if (needsH && needsV) {
349
+ d.scrollbarFiller.style.display = "block";
350
+ d.scrollbarFiller.style.height = d.scrollbarFiller.style.width = scrollbarWidth(d.measure) + "px";
351
+ } else d.scrollbarFiller.style.display = "";
352
+ if (needsH && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) {
353
+ d.gutterFiller.style.display = "block";
354
+ d.gutterFiller.style.height = scrollbarWidth(d.measure) + "px";
355
+ d.gutterFiller.style.width = d.gutters.offsetWidth + "px";
356
+ } else d.gutterFiller.style.display = "";
357
+
358
+ if (mac_geLion && scrollbarWidth(d.measure) === 0)
359
+ d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = mac_geMountainLion ? "18px" : "12px";
360
+ }
361
+
362
+ function visibleLines(display, doc, viewPort) {
363
+ var top = display.scroller.scrollTop, height = display.wrapper.clientHeight;
364
+ if (typeof viewPort == "number") top = viewPort;
365
+ else if (viewPort) {top = viewPort.top; height = viewPort.bottom - viewPort.top;}
366
+ top = Math.floor(top - paddingTop(display));
367
+ var bottom = Math.ceil(top + height);
368
+ return {from: lineAtHeight(doc, top), to: lineAtHeight(doc, bottom)};
369
+ }
370
+
371
+ // LINE NUMBERS
372
+
373
+ function alignHorizontally(cm) {
374
+ var display = cm.display;
375
+ if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) return;
376
+ var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft;
377
+ var gutterW = display.gutters.offsetWidth, l = comp + "px";
378
+ for (var n = display.lineDiv.firstChild; n; n = n.nextSibling) if (n.alignable) {
379
+ for (var i = 0, a = n.alignable; i < a.length; ++i) a[i].style.left = l;
380
+ }
381
+ if (cm.options.fixedGutter)
382
+ display.gutters.style.left = (comp + gutterW) + "px";
383
+ }
384
+
385
+ function maybeUpdateLineNumberWidth(cm) {
386
+ if (!cm.options.lineNumbers) return false;
387
+ var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display;
388
+ if (last.length != display.lineNumChars) {
389
+ var test = display.measure.appendChild(elt("div", [elt("div", last)],
390
+ "CodeMirror-linenumber CodeMirror-gutter-elt"));
391
+ var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW;
392
+ display.lineGutter.style.width = "";
393
+ display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding);
394
+ display.lineNumWidth = display.lineNumInnerWidth + padding;
395
+ display.lineNumChars = display.lineNumInnerWidth ? last.length : -1;
396
+ display.lineGutter.style.width = display.lineNumWidth + "px";
397
+ return true;
398
+ }
399
+ return false;
400
+ }
401
+
402
+ function lineNumberFor(options, i) {
403
+ return String(options.lineNumberFormatter(i + options.firstLineNumber));
404
+ }
405
+ function compensateForHScroll(display) {
406
+ return getRect(display.scroller).left - getRect(display.sizer).left;
407
+ }
408
+
409
+ // DISPLAY DRAWING
410
+
411
+ function updateDisplay(cm, changes, viewPort, forced) {
412
+ var oldFrom = cm.display.showingFrom, oldTo = cm.display.showingTo, updated;
413
+ var visible = visibleLines(cm.display, cm.doc, viewPort);
414
+ for (var first = true;; first = false) {
415
+ var oldWidth = cm.display.scroller.clientWidth;
416
+ if (!updateDisplayInner(cm, changes, visible, forced)) break;
417
+ updated = true;
418
+ changes = [];
419
+ updateSelection(cm);
420
+ updateScrollbars(cm);
421
+ if (first && cm.options.lineWrapping && oldWidth != cm.display.scroller.clientWidth) {
422
+ forced = true;
423
+ continue;
424
+ }
425
+ forced = false;
426
+
427
+ // Clip forced viewport to actual scrollable area
428
+ if (viewPort)
429
+ viewPort = Math.min(cm.display.scroller.scrollHeight - cm.display.scroller.clientHeight,
430
+ typeof viewPort == "number" ? viewPort : viewPort.top);
431
+ visible = visibleLines(cm.display, cm.doc, viewPort);
432
+ if (visible.from >= cm.display.showingFrom && visible.to <= cm.display.showingTo)
433
+ break;
434
+ }
435
+
436
+ if (updated) {
437
+ signalLater(cm, "update", cm);
438
+ if (cm.display.showingFrom != oldFrom || cm.display.showingTo != oldTo)
439
+ signalLater(cm, "viewportChange", cm, cm.display.showingFrom, cm.display.showingTo);
440
+ }
441
+ return updated;
442
+ }
443
+
444
+ // Uses a set of changes plus the current scroll position to
445
+ // determine which DOM updates have to be made, and makes the
446
+ // updates.
447
+ function updateDisplayInner(cm, changes, visible, forced) {
448
+ var display = cm.display, doc = cm.doc;
449
+ if (!display.wrapper.clientWidth) {
450
+ display.showingFrom = display.showingTo = doc.first;
451
+ display.viewOffset = 0;
452
+ return;
453
+ }
454
+
455
+ // Bail out if the visible area is already rendered and nothing changed.
456
+ if (!forced && changes.length == 0 &&
457
+ visible.from > display.showingFrom && visible.to < display.showingTo)
458
+ return;
459
+
460
+ if (maybeUpdateLineNumberWidth(cm))
461
+ changes = [{from: doc.first, to: doc.first + doc.size}];
462
+ var gutterW = display.sizer.style.marginLeft = display.gutters.offsetWidth + "px";
463
+ display.scrollbarH.style.left = cm.options.fixedGutter ? gutterW : "0";
464
+
465
+ // Used to determine which lines need their line numbers updated
466
+ var positionsChangedFrom = Infinity;
467
+ if (cm.options.lineNumbers)
468
+ for (var i = 0; i < changes.length; ++i)
469
+ if (changes[i].diff && changes[i].from < positionsChangedFrom) { positionsChangedFrom = changes[i].from; }
470
+
471
+ var end = doc.first + doc.size;
472
+ var from = Math.max(visible.from - cm.options.viewportMargin, doc.first);
473
+ var to = Math.min(end, visible.to + cm.options.viewportMargin);
474
+ if (display.showingFrom < from && from - display.showingFrom < 20) from = Math.max(doc.first, display.showingFrom);
475
+ if (display.showingTo > to && display.showingTo - to < 20) to = Math.min(end, display.showingTo);
476
+ if (sawCollapsedSpans) {
477
+ from = lineNo(visualLine(doc, getLine(doc, from)));
478
+ while (to < end && lineIsHidden(doc, getLine(doc, to))) ++to;
479
+ }
480
+
481
+ // Create a range of theoretically intact lines, and punch holes
482
+ // in that using the change info.
483
+ var intact = [{from: Math.max(display.showingFrom, doc.first),
484
+ to: Math.min(display.showingTo, end)}];
485
+ if (intact[0].from >= intact[0].to) intact = [];
486
+ else intact = computeIntact(intact, changes);
487
+ // When merged lines are present, we might have to reduce the
488
+ // intact ranges because changes in continued fragments of the
489
+ // intact lines do require the lines to be redrawn.
490
+ if (sawCollapsedSpans)
491
+ for (var i = 0; i < intact.length; ++i) {
492
+ var range = intact[i], merged;
493
+ while (merged = collapsedSpanAtEnd(getLine(doc, range.to - 1))) {
494
+ var newTo = merged.find().from.line;
495
+ if (newTo > range.from) range.to = newTo;
496
+ else { intact.splice(i--, 1); break; }
497
+ }
498
+ }
499
+
500
+ // Clip off the parts that won't be visible
501
+ var intactLines = 0;
502
+ for (var i = 0; i < intact.length; ++i) {
503
+ var range = intact[i];
504
+ if (range.from < from) range.from = from;
505
+ if (range.to > to) range.to = to;
506
+ if (range.from >= range.to) intact.splice(i--, 1);
507
+ else intactLines += range.to - range.from;
508
+ }
509
+ if (!forced && intactLines == to - from && from == display.showingFrom && to == display.showingTo) {
510
+ updateViewOffset(cm);
511
+ return;
512
+ }
513
+ intact.sort(function(a, b) {return a.from - b.from;});
514
+
515
+ // Avoid crashing on IE's "unspecified error" when in iframes
516
+ try {
517
+ var focused = document.activeElement;
518
+ } catch(e) {}
519
+ if (intactLines < (to - from) * .7) display.lineDiv.style.display = "none";
520
+ patchDisplay(cm, from, to, intact, positionsChangedFrom);
521
+ display.lineDiv.style.display = "";
522
+ if (focused && document.activeElement != focused && focused.offsetHeight) focused.focus();
523
+
524
+ var different = from != display.showingFrom || to != display.showingTo ||
525
+ display.lastSizeC != display.wrapper.clientHeight;
526
+ // This is just a bogus formula that detects when the editor is
527
+ // resized or the font size changes.
528
+ if (different) {
529
+ display.lastSizeC = display.wrapper.clientHeight;
530
+ startWorker(cm, 400);
531
+ }
532
+ display.showingFrom = from; display.showingTo = to;
533
+
534
+ updateHeightsInViewport(cm);
535
+ updateViewOffset(cm);
536
+
537
+ return true;
538
+ }
539
+
540
+ function updateHeightsInViewport(cm) {
541
+ var display = cm.display;
542
+ var prevBottom = display.lineDiv.offsetTop;
543
+ for (var node = display.lineDiv.firstChild, height; node; node = node.nextSibling) if (node.lineObj) {
544
+ if (ie_lt8) {
545
+ var bot = node.offsetTop + node.offsetHeight;
546
+ height = bot - prevBottom;
547
+ prevBottom = bot;
548
+ } else {
549
+ var box = getRect(node);
550
+ height = box.bottom - box.top;
551
+ }
552
+ var diff = node.lineObj.height - height;
553
+ if (height < 2) height = textHeight(display);
554
+ if (diff > .001 || diff < -.001) {
555
+ updateLineHeight(node.lineObj, height);
556
+ var widgets = node.lineObj.widgets;
557
+ if (widgets) for (var i = 0; i < widgets.length; ++i)
558
+ widgets[i].height = widgets[i].node.offsetHeight;
559
+ }
560
+ }
561
+ }
562
+
563
+ function updateViewOffset(cm) {
564
+ var off = cm.display.viewOffset = heightAtLine(cm, getLine(cm.doc, cm.display.showingFrom));
565
+ // Position the mover div to align with the current virtual scroll position
566
+ cm.display.mover.style.top = off + "px";
567
+ }
568
+
569
+ function computeIntact(intact, changes) {
570
+ for (var i = 0, l = changes.length || 0; i < l; ++i) {
571
+ var change = changes[i], intact2 = [], diff = change.diff || 0;
572
+ for (var j = 0, l2 = intact.length; j < l2; ++j) {
573
+ var range = intact[j];
574
+ if (change.to <= range.from && change.diff) {
575
+ intact2.push({from: range.from + diff, to: range.to + diff});
576
+ } else if (change.to <= range.from || change.from >= range.to) {
577
+ intact2.push(range);
578
+ } else {
579
+ if (change.from > range.from)
580
+ intact2.push({from: range.from, to: change.from});
581
+ if (change.to < range.to)
582
+ intact2.push({from: change.to + diff, to: range.to + diff});
583
+ }
584
+ }
585
+ intact = intact2;
586
+ }
587
+ return intact;
588
+ }
589
+
590
+ function getDimensions(cm) {
591
+ var d = cm.display, left = {}, width = {};
592
+ for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) {
593
+ left[cm.options.gutters[i]] = n.offsetLeft;
594
+ width[cm.options.gutters[i]] = n.offsetWidth;
595
+ }
596
+ return {fixedPos: compensateForHScroll(d),
597
+ gutterTotalWidth: d.gutters.offsetWidth,
598
+ gutterLeft: left,
599
+ gutterWidth: width,
600
+ wrapperWidth: d.wrapper.clientWidth};
601
+ }
602
+
603
+ function patchDisplay(cm, from, to, intact, updateNumbersFrom) {
604
+ var dims = getDimensions(cm);
605
+ var display = cm.display, lineNumbers = cm.options.lineNumbers;
606
+ if (!intact.length && (!webkit || !cm.display.currentWheelTarget))
607
+ removeChildren(display.lineDiv);
608
+ var container = display.lineDiv, cur = container.firstChild;
609
+
610
+ function rm(node) {
611
+ var next = node.nextSibling;
612
+ if (webkit && mac && cm.display.currentWheelTarget == node) {
613
+ node.style.display = "none";
614
+ node.lineObj = null;
615
+ } else {
616
+ node.parentNode.removeChild(node);
617
+ }
618
+ return next;
619
+ }
620
+
621
+ var nextIntact = intact.shift(), lineN = from;
622
+ cm.doc.iter(from, to, function(line) {
623
+ if (nextIntact && nextIntact.to == lineN) nextIntact = intact.shift();
624
+ if (lineIsHidden(cm.doc, line)) {
625
+ if (line.height != 0) updateLineHeight(line, 0);
626
+ if (line.widgets && cur && cur.previousSibling) for (var i = 0; i < line.widgets.length; ++i) {
627
+ var w = line.widgets[i];
628
+ if (w.showIfHidden) {
629
+ var prev = cur.previousSibling;
630
+ if (/pre/i.test(prev.nodeName)) {
631
+ var wrap = elt("div", null, null, "position: relative");
632
+ prev.parentNode.replaceChild(wrap, prev);
633
+ wrap.appendChild(prev);
634
+ prev = wrap;
635
+ }
636
+ var wnode = prev.appendChild(elt("div", [w.node], "CodeMirror-linewidget"));
637
+ if (!w.handleMouseEvents) wnode.ignoreEvents = true;
638
+ positionLineWidget(w, wnode, prev, dims);
639
+ }
640
+ }
641
+ } else if (nextIntact && nextIntact.from <= lineN && nextIntact.to > lineN) {
642
+ // This line is intact. Skip to the actual node. Update its
643
+ // line number if needed.
644
+ while (cur.lineObj != line) cur = rm(cur);
645
+ if (lineNumbers && updateNumbersFrom <= lineN && cur.lineNumber)
646
+ setTextContent(cur.lineNumber, lineNumberFor(cm.options, lineN));
647
+ cur = cur.nextSibling;
648
+ } else {
649
+ // For lines with widgets, make an attempt to find and reuse
650
+ // the existing element, so that widgets aren't needlessly
651
+ // removed and re-inserted into the dom
652
+ if (line.widgets) for (var j = 0, search = cur, reuse; search && j < 20; ++j, search = search.nextSibling)
653
+ if (search.lineObj == line && /div/i.test(search.nodeName)) { reuse = search; break; }
654
+ // This line needs to be generated.
655
+ var lineNode = buildLineElement(cm, line, lineN, dims, reuse);
656
+ if (lineNode != reuse) {
657
+ container.insertBefore(lineNode, cur);
658
+ } else {
659
+ while (cur != reuse) cur = rm(cur);
660
+ cur = cur.nextSibling;
661
+ }
662
+
663
+ lineNode.lineObj = line;
664
+ }
665
+ ++lineN;
666
+ });
667
+ while (cur) cur = rm(cur);
668
+ }
669
+
670
+ function buildLineElement(cm, line, lineNo, dims, reuse) {
671
+ var built = buildLineContent(cm, line), lineElement = built.pre;
672
+ var markers = line.gutterMarkers, display = cm.display, wrap;
673
+
674
+ var bgClass = built.bgClass ? built.bgClass + " " + (line.bgClass || "") : line.bgClass;
675
+ if (!cm.options.lineNumbers && !markers && !bgClass && !line.wrapClass && !line.widgets)
676
+ return lineElement;
677
+
678
+ // Lines with gutter elements, widgets or a background class need
679
+ // to be wrapped again, and have the extra elements added to the
680
+ // wrapper div
681
+
682
+ if (reuse) {
683
+ reuse.alignable = null;
684
+ var isOk = true, widgetsSeen = 0, insertBefore = null;
685
+ for (var n = reuse.firstChild, next; n; n = next) {
686
+ next = n.nextSibling;
687
+ if (!/\bCodeMirror-linewidget\b/.test(n.className)) {
688
+ reuse.removeChild(n);
689
+ } else {
690
+ for (var i = 0; i < line.widgets.length; ++i) {
691
+ var widget = line.widgets[i];
692
+ if (widget.node == n.firstChild) {
693
+ if (!widget.above && !insertBefore) insertBefore = n;
694
+ positionLineWidget(widget, n, reuse, dims);
695
+ ++widgetsSeen;
696
+ break;
697
+ }
698
+ }
699
+ if (i == line.widgets.length) { isOk = false; break; }
700
+ }
701
+ }
702
+ reuse.insertBefore(lineElement, insertBefore);
703
+ if (isOk && widgetsSeen == line.widgets.length) {
704
+ wrap = reuse;
705
+ reuse.className = line.wrapClass || "";
706
+ }
707
+ }
708
+ if (!wrap) {
709
+ wrap = elt("div", null, line.wrapClass, "position: relative");
710
+ wrap.appendChild(lineElement);
711
+ }
712
+ // Kludge to make sure the styled element lies behind the selection (by z-index)
713
+ if (bgClass)
714
+ wrap.insertBefore(elt("div", null, bgClass + " CodeMirror-linebackground"), wrap.firstChild);
715
+ if (cm.options.lineNumbers || markers) {
716
+ var gutterWrap = wrap.insertBefore(elt("div", null, null, "position: absolute; left: " +
717
+ (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"),
718
+ wrap.firstChild);
719
+ if (cm.options.fixedGutter) (wrap.alignable || (wrap.alignable = [])).push(gutterWrap);
720
+ if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"]))
721
+ wrap.lineNumber = gutterWrap.appendChild(
722
+ elt("div", lineNumberFor(cm.options, lineNo),
723
+ "CodeMirror-linenumber CodeMirror-gutter-elt",
724
+ "left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width: "
725
+ + display.lineNumInnerWidth + "px"));
726
+ if (markers)
727
+ for (var k = 0; k < cm.options.gutters.length; ++k) {
728
+ var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id];
729
+ if (found)
730
+ gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "left: " +
731
+ dims.gutterLeft[id] + "px; width: " + dims.gutterWidth[id] + "px"));
732
+ }
733
+ }
734
+ if (ie_lt8) wrap.style.zIndex = 2;
735
+ if (line.widgets && wrap != reuse) for (var i = 0, ws = line.widgets; i < ws.length; ++i) {
736
+ var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget");
737
+ if (!widget.handleMouseEvents) node.ignoreEvents = true;
738
+ positionLineWidget(widget, node, wrap, dims);
739
+ if (widget.above)
740
+ wrap.insertBefore(node, cm.options.lineNumbers && line.height != 0 ? gutterWrap : lineElement);
741
+ else
742
+ wrap.appendChild(node);
743
+ signalLater(widget, "redraw");
744
+ }
745
+ return wrap;
746
+ }
747
+
748
+ function positionLineWidget(widget, node, wrap, dims) {
749
+ if (widget.noHScroll) {
750
+ (wrap.alignable || (wrap.alignable = [])).push(node);
751
+ var width = dims.wrapperWidth;
752
+ node.style.left = dims.fixedPos + "px";
753
+ if (!widget.coverGutter) {
754
+ width -= dims.gutterTotalWidth;
755
+ node.style.paddingLeft = dims.gutterTotalWidth + "px";
756
+ }
757
+ node.style.width = width + "px";
758
+ }
759
+ if (widget.coverGutter) {
760
+ node.style.zIndex = 5;
761
+ node.style.position = "relative";
762
+ if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px";
763
+ }
764
+ }
765
+
766
+ // SELECTION / CURSOR
767
+
768
+ function updateSelection(cm) {
769
+ var display = cm.display;
770
+ var collapsed = posEq(cm.doc.sel.from, cm.doc.sel.to);
771
+ if (collapsed || cm.options.showCursorWhenSelecting)
772
+ updateSelectionCursor(cm);
773
+ else
774
+ display.cursor.style.display = display.otherCursor.style.display = "none";
775
+ if (!collapsed)
776
+ updateSelectionRange(cm);
777
+ else
778
+ display.selectionDiv.style.display = "none";
779
+
780
+ // Move the hidden textarea near the cursor to prevent scrolling artifacts
781
+ if (cm.options.moveInputWithCursor) {
782
+ var headPos = cursorCoords(cm, cm.doc.sel.head, "div");
783
+ var wrapOff = getRect(display.wrapper), lineOff = getRect(display.lineDiv);
784
+ display.inputDiv.style.top = Math.max(0, Math.min(display.wrapper.clientHeight - 10,
785
+ headPos.top + lineOff.top - wrapOff.top)) + "px";
786
+ display.inputDiv.style.left = Math.max(0, Math.min(display.wrapper.clientWidth - 10,
787
+ headPos.left + lineOff.left - wrapOff.left)) + "px";
788
+ }
789
+ }
790
+
791
+ // No selection, plain cursor
792
+ function updateSelectionCursor(cm) {
793
+ var display = cm.display, pos = cursorCoords(cm, cm.doc.sel.head, "div");
794
+ display.cursor.style.left = pos.left + "px";
795
+ display.cursor.style.top = pos.top + "px";
796
+ display.cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px";
797
+ display.cursor.style.display = "";
798
+
799
+ if (pos.other) {
800
+ display.otherCursor.style.display = "";
801
+ display.otherCursor.style.left = pos.other.left + "px";
802
+ display.otherCursor.style.top = pos.other.top + "px";
803
+ display.otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px";
804
+ } else { display.otherCursor.style.display = "none"; }
805
+ }
806
+
807
+ // Highlight selection
808
+ function updateSelectionRange(cm) {
809
+ var display = cm.display, doc = cm.doc, sel = cm.doc.sel;
810
+ var fragment = document.createDocumentFragment();
811
+ var clientWidth = display.lineSpace.offsetWidth, pl = paddingLeft(cm.display);
812
+
813
+ function add(left, top, width, bottom) {
814
+ if (top < 0) top = 0;
815
+ fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left +
816
+ "px; top: " + top + "px; width: " + (width == null ? clientWidth - left : width) +
817
+ "px; height: " + (bottom - top) + "px"));
818
+ }
819
+
820
+ function drawForLine(line, fromArg, toArg) {
821
+ var lineObj = getLine(doc, line);
822
+ var lineLen = lineObj.text.length;
823
+ var start, end;
824
+ function coords(ch, bias) {
825
+ return charCoords(cm, Pos(line, ch), "div", lineObj, bias);
826
+ }
827
+
828
+ iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineLen : toArg, function(from, to, dir) {
829
+ var leftPos = coords(from, "left"), rightPos, left, right;
830
+ if (from == to) {
831
+ rightPos = leftPos;
832
+ left = right = leftPos.left;
833
+ } else {
834
+ rightPos = coords(to - 1, "right");
835
+ if (dir == "rtl") { var tmp = leftPos; leftPos = rightPos; rightPos = tmp; }
836
+ left = leftPos.left;
837
+ right = rightPos.right;
838
+ }
839
+ if (fromArg == null && from == 0) left = pl;
840
+ if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part
841
+ add(left, leftPos.top, null, leftPos.bottom);
842
+ left = pl;
843
+ if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rightPos.top);
844
+ }
845
+ if (toArg == null && to == lineLen) right = clientWidth;
846
+ if (!start || leftPos.top < start.top || leftPos.top == start.top && leftPos.left < start.left)
847
+ start = leftPos;
848
+ if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.bottom && rightPos.right > end.right)
849
+ end = rightPos;
850
+ if (left < pl + 1) left = pl;
851
+ add(left, rightPos.top, right - left, rightPos.bottom);
852
+ });
853
+ return {start: start, end: end};
854
+ }
855
+
856
+ if (sel.from.line == sel.to.line) {
857
+ drawForLine(sel.from.line, sel.from.ch, sel.to.ch);
858
+ } else {
859
+ var fromLine = getLine(doc, sel.from.line), toLine = getLine(doc, sel.to.line);
860
+ var singleVLine = visualLine(doc, fromLine) == visualLine(doc, toLine);
861
+ var leftEnd = drawForLine(sel.from.line, sel.from.ch, singleVLine ? fromLine.text.length : null).end;
862
+ var rightStart = drawForLine(sel.to.line, singleVLine ? 0 : null, sel.to.ch).start;
863
+ if (singleVLine) {
864
+ if (leftEnd.top < rightStart.top - 2) {
865
+ add(leftEnd.right, leftEnd.top, null, leftEnd.bottom);
866
+ add(pl, rightStart.top, rightStart.left, rightStart.bottom);
867
+ } else {
868
+ add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom);
869
+ }
870
+ }
871
+ if (leftEnd.bottom < rightStart.top)
872
+ add(pl, leftEnd.bottom, null, rightStart.top);
873
+ }
874
+
875
+ removeChildrenAndAdd(display.selectionDiv, fragment);
876
+ display.selectionDiv.style.display = "";
877
+ }
878
+
879
+ // Cursor-blinking
880
+ function restartBlink(cm) {
881
+ if (!cm.state.focused) return;
882
+ var display = cm.display;
883
+ clearInterval(display.blinker);
884
+ var on = true;
885
+ display.cursor.style.visibility = display.otherCursor.style.visibility = "";
886
+ if (cm.options.cursorBlinkRate > 0)
887
+ display.blinker = setInterval(function() {
888
+ display.cursor.style.visibility = display.otherCursor.style.visibility = (on = !on) ? "" : "hidden";
889
+ }, cm.options.cursorBlinkRate);
890
+ }
891
+
892
+ // HIGHLIGHT WORKER
893
+
894
+ function startWorker(cm, time) {
895
+ if (cm.doc.mode.startState && cm.doc.frontier < cm.display.showingTo)
896
+ cm.state.highlight.set(time, bind(highlightWorker, cm));
897
+ }
898
+
899
+ function highlightWorker(cm) {
900
+ var doc = cm.doc;
901
+ if (doc.frontier < doc.first) doc.frontier = doc.first;
902
+ if (doc.frontier >= cm.display.showingTo) return;
903
+ var end = +new Date + cm.options.workTime;
904
+ var state = copyState(doc.mode, getStateBefore(cm, doc.frontier));
905
+ var changed = [], prevChange;
906
+ doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.showingTo + 500), function(line) {
907
+ if (doc.frontier >= cm.display.showingFrom) { // Visible
908
+ var oldStyles = line.styles;
909
+ line.styles = highlightLine(cm, line, state);
910
+ var ischange = !oldStyles || oldStyles.length != line.styles.length;
911
+ for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i];
912
+ if (ischange) {
913
+ if (prevChange && prevChange.end == doc.frontier) prevChange.end++;
914
+ else changed.push(prevChange = {start: doc.frontier, end: doc.frontier + 1});
915
+ }
916
+ line.stateAfter = copyState(doc.mode, state);
917
+ } else {
918
+ processLine(cm, line, state);
919
+ line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null;
920
+ }
921
+ ++doc.frontier;
922
+ if (+new Date > end) {
923
+ startWorker(cm, cm.options.workDelay);
924
+ return true;
925
+ }
926
+ });
927
+ if (changed.length)
928
+ operation(cm, function() {
929
+ for (var i = 0; i < changed.length; ++i)
930
+ regChange(this, changed[i].start, changed[i].end);
931
+ })();
932
+ }
933
+
934
+ // Finds the line to start with when starting a parse. Tries to
935
+ // find a line with a stateAfter, so that it can start with a
936
+ // valid state. If that fails, it returns the line with the
937
+ // smallest indentation, which tends to need the least context to
938
+ // parse correctly.
939
+ function findStartLine(cm, n, precise) {
940
+ var minindent, minline, doc = cm.doc, maxScan = cm.doc.mode.innerMode ? 1000 : 100;
941
+ for (var search = n, lim = n - maxScan; search > lim; --search) {
942
+ if (search <= doc.first) return doc.first;
943
+ var line = getLine(doc, search - 1);
944
+ if (line.stateAfter && (!precise || search <= doc.frontier)) return search;
945
+ var indented = countColumn(line.text, null, cm.options.tabSize);
946
+ if (minline == null || minindent > indented) {
947
+ minline = search - 1;
948
+ minindent = indented;
949
+ }
950
+ }
951
+ return minline;
952
+ }
953
+
954
+ function getStateBefore(cm, n, precise) {
955
+ var doc = cm.doc, display = cm.display;
956
+ if (!doc.mode.startState) return true;
957
+ var pos = findStartLine(cm, n, precise), state = pos > doc.first && getLine(doc, pos-1).stateAfter;
958
+ if (!state) state = startState(doc.mode);
959
+ else state = copyState(doc.mode, state);
960
+ doc.iter(pos, n, function(line) {
961
+ processLine(cm, line, state);
962
+ var save = pos == n - 1 || pos % 5 == 0 || pos >= display.showingFrom && pos < display.showingTo;
963
+ line.stateAfter = save ? copyState(doc.mode, state) : null;
964
+ ++pos;
965
+ });
966
+ return state;
967
+ }
968
+
969
+ // POSITION MEASUREMENT
970
+
971
+ function paddingTop(display) {return display.lineSpace.offsetTop;}
972
+ function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight;}
973
+ function paddingLeft(display) {
974
+ var e = removeChildrenAndAdd(display.measure, elt("pre", null, null, "text-align: left")).appendChild(elt("span", "x"));
975
+ return e.offsetLeft;
976
+ }
977
+
978
+ function measureChar(cm, line, ch, data, bias) {
979
+ var dir = -1;
980
+ data = data || measureLine(cm, line);
981
+ if (data.crude) {
982
+ var left = data.left + ch * data.width;
983
+ return {left: left, right: left + data.width, top: data.top, bottom: data.bottom};
984
+ }
985
+
986
+ for (var pos = ch;; pos += dir) {
987
+ var r = data[pos];
988
+ if (r) break;
989
+ if (dir < 0 && pos == 0) dir = 1;
990
+ }
991
+ bias = pos > ch ? "left" : pos < ch ? "right" : bias;
992
+ if (bias == "left" && r.leftSide) r = r.leftSide;
993
+ else if (bias == "right" && r.rightSide) r = r.rightSide;
994
+ return {left: pos < ch ? r.right : r.left,
995
+ right: pos > ch ? r.left : r.right,
996
+ top: r.top,
997
+ bottom: r.bottom};
998
+ }
999
+
1000
+ function findCachedMeasurement(cm, line) {
1001
+ var cache = cm.display.measureLineCache;
1002
+ for (var i = 0; i < cache.length; ++i) {
1003
+ var memo = cache[i];
1004
+ if (memo.text == line.text && memo.markedSpans == line.markedSpans &&
1005
+ cm.display.scroller.clientWidth == memo.width &&
1006
+ memo.classes == line.textClass + "|" + line.wrapClass)
1007
+ return memo;
1008
+ }
1009
+ }
1010
+
1011
+ function clearCachedMeasurement(cm, line) {
1012
+ var exists = findCachedMeasurement(cm, line);
1013
+ if (exists) exists.text = exists.measure = exists.markedSpans = null;
1014
+ }
1015
+
1016
+ function measureLine(cm, line) {
1017
+ // First look in the cache
1018
+ var cached = findCachedMeasurement(cm, line);
1019
+ if (cached) return cached.measure;
1020
+
1021
+ // Failing that, recompute and store result in cache
1022
+ var measure = measureLineInner(cm, line);
1023
+ var cache = cm.display.measureLineCache;
1024
+ var memo = {text: line.text, width: cm.display.scroller.clientWidth,
1025
+ markedSpans: line.markedSpans, measure: measure,
1026
+ classes: line.textClass + "|" + line.wrapClass};
1027
+ if (cache.length == 16) cache[++cm.display.measureLineCachePos % 16] = memo;
1028
+ else cache.push(memo);
1029
+ return measure;
1030
+ }
1031
+
1032
+ function measureLineInner(cm, line) {
1033
+ if (!cm.options.lineWrapping && line.text.length >= cm.options.crudeMeasuringFrom)
1034
+ return crudelyMeasureLine(cm, line);
1035
+
1036
+ var display = cm.display, measure = emptyArray(line.text.length);
1037
+ var pre = buildLineContent(cm, line, measure, true).pre;
1038
+
1039
+ // IE does not cache element positions of inline elements between
1040
+ // calls to getBoundingClientRect. This makes the loop below,
1041
+ // which gathers the positions of all the characters on the line,
1042
+ // do an amount of layout work quadratic to the number of
1043
+ // characters. When line wrapping is off, we try to improve things
1044
+ // by first subdividing the line into a bunch of inline blocks, so
1045
+ // that IE can reuse most of the layout information from caches
1046
+ // for those blocks. This does interfere with line wrapping, so it
1047
+ // doesn't work when wrapping is on, but in that case the
1048
+ // situation is slightly better, since IE does cache line-wrapping
1049
+ // information and only recomputes per-line.
1050
+ if (ie && !ie_lt8 && !cm.options.lineWrapping && pre.childNodes.length > 100) {
1051
+ var fragment = document.createDocumentFragment();
1052
+ var chunk = 10, n = pre.childNodes.length;
1053
+ for (var i = 0, chunks = Math.ceil(n / chunk); i < chunks; ++i) {
1054
+ var wrap = elt("div", null, null, "display: inline-block");
1055
+ for (var j = 0; j < chunk && n; ++j) {
1056
+ wrap.appendChild(pre.firstChild);
1057
+ --n;
1058
+ }
1059
+ fragment.appendChild(wrap);
1060
+ }
1061
+ pre.appendChild(fragment);
1062
+ }
1063
+
1064
+ removeChildrenAndAdd(display.measure, pre);
1065
+
1066
+ var outer = getRect(display.lineDiv);
1067
+ var vranges = [], data = emptyArray(line.text.length), maxBot = pre.offsetHeight;
1068
+ // Work around an IE7/8 bug where it will sometimes have randomly
1069
+ // replaced our pre with a clone at this point.
1070
+ if (ie_lt9 && display.measure.first != pre)
1071
+ removeChildrenAndAdd(display.measure, pre);
1072
+
1073
+ function measureRect(rect) {
1074
+ var top = rect.top - outer.top, bot = rect.bottom - outer.top;
1075
+ if (bot > maxBot) bot = maxBot;
1076
+ if (top < 0) top = 0;
1077
+ for (var i = vranges.length - 2; i >= 0; i -= 2) {
1078
+ var rtop = vranges[i], rbot = vranges[i+1];
1079
+ if (rtop > bot || rbot < top) continue;
1080
+ if (rtop <= top && rbot >= bot ||
1081
+ top <= rtop && bot >= rbot ||
1082
+ Math.min(bot, rbot) - Math.max(top, rtop) >= (bot - top) >> 1) {
1083
+ vranges[i] = Math.min(top, rtop);
1084
+ vranges[i+1] = Math.max(bot, rbot);
1085
+ break;
1086
+ }
1087
+ }
1088
+ if (i < 0) { i = vranges.length; vranges.push(top, bot); }
1089
+ return {left: rect.left - outer.left,
1090
+ right: rect.right - outer.left,
1091
+ top: i, bottom: null};
1092
+ }
1093
+ function finishRect(rect) {
1094
+ rect.bottom = vranges[rect.top+1];
1095
+ rect.top = vranges[rect.top];
1096
+ }
1097
+
1098
+ for (var i = 0, cur; i < measure.length; ++i) if (cur = measure[i]) {
1099
+ var node = cur, rect = null;
1100
+ // A widget might wrap, needs special care
1101
+ if (/\bCodeMirror-widget\b/.test(cur.className) && cur.getClientRects) {
1102
+ if (cur.firstChild.nodeType == 1) node = cur.firstChild;
1103
+ var rects = node.getClientRects();
1104
+ if (rects.length > 1) {
1105
+ rect = data[i] = measureRect(rects[0]);
1106
+ rect.rightSide = measureRect(rects[rects.length - 1]);
1107
+ }
1108
+ }
1109
+ if (!rect) rect = data[i] = measureRect(getRect(node));
1110
+ if (cur.measureRight) rect.right = getRect(cur.measureRight).left;
1111
+ if (cur.leftSide) rect.leftSide = measureRect(getRect(cur.leftSide));
1112
+ }
1113
+ removeChildren(cm.display.measure);
1114
+ for (var i = 0, cur; i < data.length; ++i) if (cur = data[i]) {
1115
+ finishRect(cur);
1116
+ if (cur.leftSide) finishRect(cur.leftSide);
1117
+ if (cur.rightSide) finishRect(cur.rightSide);
1118
+ }
1119
+ return data;
1120
+ }
1121
+
1122
+ function crudelyMeasureLine(cm, line) {
1123
+ var copy = new Line(line.text.slice(0, 100), null);
1124
+ if (line.textClass) copy.textClass = line.textClass;
1125
+ var measure = measureLineInner(cm, copy);
1126
+ var left = measureChar(cm, copy, 0, measure, "left");
1127
+ var right = measureChar(cm, copy, 99, measure, "right");
1128
+ return {crude: true, top: left.top, left: left.left, bottom: left.bottom, width: (right.right - left.left) / 100};
1129
+ }
1130
+
1131
+ function measureLineWidth(cm, line) {
1132
+ var hasBadSpan = false;
1133
+ if (line.markedSpans) for (var i = 0; i < line.markedSpans; ++i) {
1134
+ var sp = line.markedSpans[i];
1135
+ if (sp.collapsed && (sp.to == null || sp.to == line.text.length)) hasBadSpan = true;
1136
+ }
1137
+ var cached = !hasBadSpan && findCachedMeasurement(cm, line);
1138
+ if (cached || line.text.length >= cm.options.crudeMeasuringFrom)
1139
+ return measureChar(cm, line, line.text.length, cached && cached.measure, "right").right;
1140
+
1141
+ var pre = buildLineContent(cm, line, null, true).pre;
1142
+ var end = pre.appendChild(zeroWidthElement(cm.display.measure));
1143
+ removeChildrenAndAdd(cm.display.measure, pre);
1144
+ return getRect(end).right - getRect(cm.display.lineDiv).left;
1145
+ }
1146
+
1147
+ function clearCaches(cm) {
1148
+ cm.display.measureLineCache.length = cm.display.measureLineCachePos = 0;
1149
+ cm.display.cachedCharWidth = cm.display.cachedTextHeight = null;
1150
+ if (!cm.options.lineWrapping) cm.display.maxLineChanged = true;
1151
+ cm.display.lineNumChars = null;
1152
+ }
1153
+
1154
+ function pageScrollX() { return window.pageXOffset || (document.documentElement || document.body).scrollLeft; }
1155
+ function pageScrollY() { return window.pageYOffset || (document.documentElement || document.body).scrollTop; }
1156
+
1157
+ // Context is one of "line", "div" (display.lineDiv), "local"/null (editor), or "page"
1158
+ function intoCoordSystem(cm, lineObj, rect, context) {
1159
+ if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) {
1160
+ var size = widgetHeight(lineObj.widgets[i]);
1161
+ rect.top += size; rect.bottom += size;
1162
+ }
1163
+ if (context == "line") return rect;
1164
+ if (!context) context = "local";
1165
+ var yOff = heightAtLine(cm, lineObj);
1166
+ if (context == "local") yOff += paddingTop(cm.display);
1167
+ else yOff -= cm.display.viewOffset;
1168
+ if (context == "page" || context == "window") {
1169
+ var lOff = getRect(cm.display.lineSpace);
1170
+ yOff += lOff.top + (context == "window" ? 0 : pageScrollY());
1171
+ var xOff = lOff.left + (context == "window" ? 0 : pageScrollX());
1172
+ rect.left += xOff; rect.right += xOff;
1173
+ }
1174
+ rect.top += yOff; rect.bottom += yOff;
1175
+ return rect;
1176
+ }
1177
+
1178
+ // Context may be "window", "page", "div", or "local"/null
1179
+ // Result is in "div" coords
1180
+ function fromCoordSystem(cm, coords, context) {
1181
+ if (context == "div") return coords;
1182
+ var left = coords.left, top = coords.top;
1183
+ // First move into "page" coordinate system
1184
+ if (context == "page") {
1185
+ left -= pageScrollX();
1186
+ top -= pageScrollY();
1187
+ } else if (context == "local" || !context) {
1188
+ var localBox = getRect(cm.display.sizer);
1189
+ left += localBox.left;
1190
+ top += localBox.top;
1191
+ }
1192
+
1193
+ var lineSpaceBox = getRect(cm.display.lineSpace);
1194
+ return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top};
1195
+ }
1196
+
1197
+ function charCoords(cm, pos, context, lineObj, bias) {
1198
+ if (!lineObj) lineObj = getLine(cm.doc, pos.line);
1199
+ return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, null, bias), context);
1200
+ }
1201
+
1202
+ function cursorCoords(cm, pos, context, lineObj, measurement) {
1203
+ lineObj = lineObj || getLine(cm.doc, pos.line);
1204
+ if (!measurement) measurement = measureLine(cm, lineObj);
1205
+ function get(ch, right) {
1206
+ var m = measureChar(cm, lineObj, ch, measurement, right ? "right" : "left");
1207
+ if (right) m.left = m.right; else m.right = m.left;
1208
+ return intoCoordSystem(cm, lineObj, m, context);
1209
+ }
1210
+ function getBidi(ch, partPos) {
1211
+ var part = order[partPos], right = part.level % 2;
1212
+ if (ch == bidiLeft(part) && partPos && part.level < order[partPos - 1].level) {
1213
+ part = order[--partPos];
1214
+ ch = bidiRight(part) - (part.level % 2 ? 0 : 1);
1215
+ right = true;
1216
+ } else if (ch == bidiRight(part) && partPos < order.length - 1 && part.level < order[partPos + 1].level) {
1217
+ part = order[++partPos];
1218
+ ch = bidiLeft(part) - part.level % 2;
1219
+ right = false;
1220
+ }
1221
+ if (right && ch == part.to && ch > part.from) return get(ch - 1);
1222
+ return get(ch, right);
1223
+ }
1224
+ var order = getOrder(lineObj), ch = pos.ch;
1225
+ if (!order) return get(ch);
1226
+ var partPos = getBidiPartAt(order, ch);
1227
+ var val = getBidi(ch, partPos);
1228
+ if (bidiOther != null) val.other = getBidi(ch, bidiOther);
1229
+ return val;
1230
+ }
1231
+
1232
+ function PosWithInfo(line, ch, outside, xRel) {
1233
+ var pos = new Pos(line, ch);
1234
+ pos.xRel = xRel;
1235
+ if (outside) pos.outside = true;
1236
+ return pos;
1237
+ }
1238
+
1239
+ // Coords must be lineSpace-local
1240
+ function coordsChar(cm, x, y) {
1241
+ var doc = cm.doc;
1242
+ y += cm.display.viewOffset;
1243
+ if (y < 0) return PosWithInfo(doc.first, 0, true, -1);
1244
+ var lineNo = lineAtHeight(doc, y), last = doc.first + doc.size - 1;
1245
+ if (lineNo > last)
1246
+ return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, true, 1);
1247
+ if (x < 0) x = 0;
1248
+
1249
+ for (;;) {
1250
+ var lineObj = getLine(doc, lineNo);
1251
+ var found = coordsCharInner(cm, lineObj, lineNo, x, y);
1252
+ var merged = collapsedSpanAtEnd(lineObj);
1253
+ var mergedPos = merged && merged.find();
1254
+ if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0))
1255
+ lineNo = mergedPos.to.line;
1256
+ else
1257
+ return found;
1258
+ }
1259
+ }
1260
+
1261
+ function coordsCharInner(cm, lineObj, lineNo, x, y) {
1262
+ var innerOff = y - heightAtLine(cm, lineObj);
1263
+ var wrongLine = false, adjust = 2 * cm.display.wrapper.clientWidth;
1264
+ var measurement = measureLine(cm, lineObj);
1265
+
1266
+ function getX(ch) {
1267
+ var sp = cursorCoords(cm, Pos(lineNo, ch), "line",
1268
+ lineObj, measurement);
1269
+ wrongLine = true;
1270
+ if (innerOff > sp.bottom) return sp.left - adjust;
1271
+ else if (innerOff < sp.top) return sp.left + adjust;
1272
+ else wrongLine = false;
1273
+ return sp.left;
1274
+ }
1275
+
1276
+ var bidi = getOrder(lineObj), dist = lineObj.text.length;
1277
+ var from = lineLeft(lineObj), to = lineRight(lineObj);
1278
+ var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside = wrongLine;
1279
+
1280
+ if (x > toX) return PosWithInfo(lineNo, to, toOutside, 1);
1281
+ // Do a binary search between these bounds.
1282
+ for (;;) {
1283
+ if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) {
1284
+ var ch = x < fromX || x - fromX <= toX - x ? from : to;
1285
+ var xDiff = x - (ch == from ? fromX : toX);
1286
+ while (isExtendingChar.test(lineObj.text.charAt(ch))) ++ch;
1287
+ var pos = PosWithInfo(lineNo, ch, ch == from ? fromOutside : toOutside,
1288
+ xDiff < 0 ? -1 : xDiff ? 1 : 0);
1289
+ return pos;
1290
+ }
1291
+ var step = Math.ceil(dist / 2), middle = from + step;
1292
+ if (bidi) {
1293
+ middle = from;
1294
+ for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1);
1295
+ }
1296
+ var middleX = getX(middle);
1297
+ if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) toX += 1000; dist = step;}
1298
+ else {from = middle; fromX = middleX; fromOutside = wrongLine; dist -= step;}
1299
+ }
1300
+ }
1301
+
1302
+ var measureText;
1303
+ function textHeight(display) {
1304
+ if (display.cachedTextHeight != null) return display.cachedTextHeight;
1305
+ if (measureText == null) {
1306
+ measureText = elt("pre");
1307
+ // Measure a bunch of lines, for browsers that compute
1308
+ // fractional heights.
1309
+ for (var i = 0; i < 49; ++i) {
1310
+ measureText.appendChild(document.createTextNode("x"));
1311
+ measureText.appendChild(elt("br"));
1312
+ }
1313
+ measureText.appendChild(document.createTextNode("x"));
1314
+ }
1315
+ removeChildrenAndAdd(display.measure, measureText);
1316
+ var height = measureText.offsetHeight / 50;
1317
+ if (height > 3) display.cachedTextHeight = height;
1318
+ removeChildren(display.measure);
1319
+ return height || 1;
1320
+ }
1321
+
1322
+ function charWidth(display) {
1323
+ if (display.cachedCharWidth != null) return display.cachedCharWidth;
1324
+ var anchor = elt("span", "x");
1325
+ var pre = elt("pre", [anchor]);
1326
+ removeChildrenAndAdd(display.measure, pre);
1327
+ var width = anchor.offsetWidth;
1328
+ if (width > 2) display.cachedCharWidth = width;
1329
+ return width || 10;
1330
+ }
1331
+
1332
+ // OPERATIONS
1333
+
1334
+ // Operations are used to wrap changes in such a way that each
1335
+ // change won't have to update the cursor and display (which would
1336
+ // be awkward, slow, and error-prone), but instead updates are
1337
+ // batched and then all combined and executed at once.
1338
+
1339
+ var nextOpId = 0;
1340
+ function startOperation(cm) {
1341
+ cm.curOp = {
1342
+ // An array of ranges of lines that have to be updated. See
1343
+ // updateDisplay.
1344
+ changes: [],
1345
+ forceUpdate: false,
1346
+ updateInput: null,
1347
+ userSelChange: null,
1348
+ textChanged: null,
1349
+ selectionChanged: false,
1350
+ cursorActivity: false,
1351
+ updateMaxLine: false,
1352
+ updateScrollPos: false,
1353
+ id: ++nextOpId
1354
+ };
1355
+ if (!delayedCallbackDepth++) delayedCallbacks = [];
1356
+ }
1357
+
1358
+ function endOperation(cm) {
1359
+ var op = cm.curOp, doc = cm.doc, display = cm.display;
1360
+ cm.curOp = null;
1361
+
1362
+ if (op.updateMaxLine) computeMaxLength(cm);
1363
+ if (display.maxLineChanged && !cm.options.lineWrapping && display.maxLine) {
1364
+ var width = measureLineWidth(cm, display.maxLine);
1365
+ display.sizer.style.minWidth = Math.max(0, width + 3 + scrollerCutOff) + "px";
1366
+ display.maxLineChanged = false;
1367
+ var maxScrollLeft = Math.max(0, display.sizer.offsetLeft + display.sizer.offsetWidth - display.scroller.clientWidth);
1368
+ if (maxScrollLeft < doc.scrollLeft && !op.updateScrollPos)
1369
+ setScrollLeft(cm, Math.min(display.scroller.scrollLeft, maxScrollLeft), true);
1370
+ }
1371
+ var newScrollPos, updated;
1372
+ if (op.updateScrollPos) {
1373
+ newScrollPos = op.updateScrollPos;
1374
+ } else if (op.selectionChanged && display.scroller.clientHeight) { // don't rescroll if not visible
1375
+ var coords = cursorCoords(cm, doc.sel.head);
1376
+ newScrollPos = calculateScrollPos(cm, coords.left, coords.top, coords.left, coords.bottom);
1377
+ }
1378
+ if (op.changes.length || op.forceUpdate || newScrollPos && newScrollPos.scrollTop != null) {
1379
+ updated = updateDisplay(cm, op.changes, newScrollPos && newScrollPos.scrollTop, op.forceUpdate);
1380
+ if (cm.display.scroller.offsetHeight) cm.doc.scrollTop = cm.display.scroller.scrollTop;
1381
+ }
1382
+ if (!updated && op.selectionChanged) updateSelection(cm);
1383
+ if (op.updateScrollPos) {
1384
+ display.scroller.scrollTop = display.scrollbarV.scrollTop = doc.scrollTop = newScrollPos.scrollTop;
1385
+ display.scroller.scrollLeft = display.scrollbarH.scrollLeft = doc.scrollLeft = newScrollPos.scrollLeft;
1386
+ alignHorizontally(cm);
1387
+ if (op.scrollToPos)
1388
+ scrollPosIntoView(cm, clipPos(cm.doc, op.scrollToPos), op.scrollToPosMargin);
1389
+ } else if (newScrollPos) {
1390
+ scrollCursorIntoView(cm);
1391
+ }
1392
+ if (op.selectionChanged) restartBlink(cm);
1393
+
1394
+ if (cm.state.focused && op.updateInput)
1395
+ resetInput(cm, op.userSelChange);
1396
+
1397
+ var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers;
1398
+ if (hidden) for (var i = 0; i < hidden.length; ++i)
1399
+ if (!hidden[i].lines.length) signal(hidden[i], "hide");
1400
+ if (unhidden) for (var i = 0; i < unhidden.length; ++i)
1401
+ if (unhidden[i].lines.length) signal(unhidden[i], "unhide");
1402
+
1403
+ var delayed;
1404
+ if (!--delayedCallbackDepth) {
1405
+ delayed = delayedCallbacks;
1406
+ delayedCallbacks = null;
1407
+ }
1408
+ if (op.textChanged)
1409
+ signal(cm, "change", cm, op.textChanged);
1410
+ if (op.cursorActivity) signal(cm, "cursorActivity", cm);
1411
+ if (delayed) for (var i = 0; i < delayed.length; ++i) delayed[i]();
1412
+ }
1413
+
1414
+ // Wraps a function in an operation. Returns the wrapped function.
1415
+ function operation(cm1, f) {
1416
+ return function() {
1417
+ var cm = cm1 || this, withOp = !cm.curOp;
1418
+ if (withOp) startOperation(cm);
1419
+ try { var result = f.apply(cm, arguments); }
1420
+ finally { if (withOp) endOperation(cm); }
1421
+ return result;
1422
+ };
1423
+ }
1424
+ function docOperation(f) {
1425
+ return function() {
1426
+ var withOp = this.cm && !this.cm.curOp, result;
1427
+ if (withOp) startOperation(this.cm);
1428
+ try { result = f.apply(this, arguments); }
1429
+ finally { if (withOp) endOperation(this.cm); }
1430
+ return result;
1431
+ };
1432
+ }
1433
+ function runInOp(cm, f) {
1434
+ var withOp = !cm.curOp, result;
1435
+ if (withOp) startOperation(cm);
1436
+ try { result = f(); }
1437
+ finally { if (withOp) endOperation(cm); }
1438
+ return result;
1439
+ }
1440
+
1441
+ function regChange(cm, from, to, lendiff) {
1442
+ if (from == null) from = cm.doc.first;
1443
+ if (to == null) to = cm.doc.first + cm.doc.size;
1444
+ cm.curOp.changes.push({from: from, to: to, diff: lendiff});
1445
+ }
1446
+
1447
+ // INPUT HANDLING
1448
+
1449
+ function slowPoll(cm) {
1450
+ if (cm.display.pollingFast) return;
1451
+ cm.display.poll.set(cm.options.pollInterval, function() {
1452
+ readInput(cm);
1453
+ if (cm.state.focused) slowPoll(cm);
1454
+ });
1455
+ }
1456
+
1457
+ function fastPoll(cm) {
1458
+ var missed = false;
1459
+ cm.display.pollingFast = true;
1460
+ function p() {
1461
+ var changed = readInput(cm);
1462
+ if (!changed && !missed) {missed = true; cm.display.poll.set(60, p);}
1463
+ else {cm.display.pollingFast = false; slowPoll(cm);}
1464
+ }
1465
+ cm.display.poll.set(20, p);
1466
+ }
1467
+
1468
+ // prevInput is a hack to work with IME. If we reset the textarea
1469
+ // on every change, that breaks IME. So we look for changes
1470
+ // compared to the previous content instead. (Modern browsers have
1471
+ // events that indicate IME taking place, but these are not widely
1472
+ // supported or compatible enough yet to rely on.)
1473
+ function readInput(cm) {
1474
+ var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc, sel = doc.sel;
1475
+ if (!cm.state.focused || hasSelection(input) || isReadOnly(cm) || cm.state.disableInput) return false;
1476
+ if (cm.state.pasteIncoming && cm.state.fakedLastChar) {
1477
+ input.value = input.value.substring(0, input.value.length - 1);
1478
+ cm.state.fakedLastChar = false;
1479
+ }
1480
+ var text = input.value;
1481
+ if (text == prevInput && posEq(sel.from, sel.to)) return false;
1482
+ if (ie && !ie_lt9 && cm.display.inputHasSelection === text) {
1483
+ resetInput(cm, true);
1484
+ return false;
1485
+ }
1486
+
1487
+ var withOp = !cm.curOp;
1488
+ if (withOp) startOperation(cm);
1489
+ sel.shift = false;
1490
+ var same = 0, l = Math.min(prevInput.length, text.length);
1491
+ while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same;
1492
+ var from = sel.from, to = sel.to;
1493
+ if (same < prevInput.length)
1494
+ from = Pos(from.line, from.ch - (prevInput.length - same));
1495
+ else if (cm.state.overwrite && posEq(from, to) && !cm.state.pasteIncoming)
1496
+ to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + (text.length - same)));
1497
+
1498
+ var updateInput = cm.curOp.updateInput;
1499
+ var changeEvent = {from: from, to: to, text: splitLines(text.slice(same)),
1500
+ origin: cm.state.pasteIncoming ? "paste" : "+input"};
1501
+ makeChange(cm.doc, changeEvent, "end");
1502
+ cm.curOp.updateInput = updateInput;
1503
+ signalLater(cm, "inputRead", cm, changeEvent);
1504
+
1505
+ if (text.length > 1000 || text.indexOf("\n") > -1) input.value = cm.display.prevInput = "";
1506
+ else cm.display.prevInput = text;
1507
+ if (withOp) endOperation(cm);
1508
+ cm.state.pasteIncoming = false;
1509
+ return true;
1510
+ }
1511
+
1512
+ function resetInput(cm, user) {
1513
+ var minimal, selected, doc = cm.doc;
1514
+ if (!posEq(doc.sel.from, doc.sel.to)) {
1515
+ cm.display.prevInput = "";
1516
+ minimal = hasCopyEvent &&
1517
+ (doc.sel.to.line - doc.sel.from.line > 100 || (selected = cm.getSelection()).length > 1000);
1518
+ var content = minimal ? "-" : selected || cm.getSelection();
1519
+ cm.display.input.value = content;
1520
+ if (cm.state.focused) selectInput(cm.display.input);
1521
+ if (ie && !ie_lt9) cm.display.inputHasSelection = content;
1522
+ } else if (user) {
1523
+ cm.display.prevInput = cm.display.input.value = "";
1524
+ if (ie && !ie_lt9) cm.display.inputHasSelection = null;
1525
+ }
1526
+ cm.display.inaccurateSelection = minimal;
1527
+ }
1528
+
1529
+ function focusInput(cm) {
1530
+ if (cm.options.readOnly != "nocursor" && (!mobile || document.activeElement != cm.display.input))
1531
+ cm.display.input.focus();
1532
+ }
1533
+
1534
+ function isReadOnly(cm) {
1535
+ return cm.options.readOnly || cm.doc.cantEdit;
1536
+ }
1537
+
1538
+ // EVENT HANDLERS
1539
+
1540
+ function registerEventHandlers(cm) {
1541
+ var d = cm.display;
1542
+ on(d.scroller, "mousedown", operation(cm, onMouseDown));
1543
+ if (ie)
1544
+ on(d.scroller, "dblclick", operation(cm, function(e) {
1545
+ if (signalDOMEvent(cm, e)) return;
1546
+ var pos = posFromMouse(cm, e);
1547
+ if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return;
1548
+ e_preventDefault(e);
1549
+ var word = findWordAt(getLine(cm.doc, pos.line).text, pos);
1550
+ extendSelection(cm.doc, word.from, word.to);
1551
+ }));
1552
+ else
1553
+ on(d.scroller, "dblclick", function(e) { signalDOMEvent(cm, e) || e_preventDefault(e); });
1554
+ on(d.lineSpace, "selectstart", function(e) {
1555
+ if (!eventInWidget(d, e)) e_preventDefault(e);
1556
+ });
1557
+ // Gecko browsers fire contextmenu *after* opening the menu, at
1558
+ // which point we can't mess with it anymore. Context menu is
1559
+ // handled in onMouseDown for Gecko.
1560
+ if (!captureMiddleClick) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);});
1561
+
1562
+ on(d.scroller, "scroll", function() {
1563
+ if (d.scroller.clientHeight) {
1564
+ setScrollTop(cm, d.scroller.scrollTop);
1565
+ setScrollLeft(cm, d.scroller.scrollLeft, true);
1566
+ signal(cm, "scroll", cm);
1567
+ }
1568
+ });
1569
+ on(d.scrollbarV, "scroll", function() {
1570
+ if (d.scroller.clientHeight) setScrollTop(cm, d.scrollbarV.scrollTop);
1571
+ });
1572
+ on(d.scrollbarH, "scroll", function() {
1573
+ if (d.scroller.clientHeight) setScrollLeft(cm, d.scrollbarH.scrollLeft);
1574
+ });
1575
+
1576
+ on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);});
1577
+ on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);});
1578
+
1579
+ function reFocus() { if (cm.state.focused) setTimeout(bind(focusInput, cm), 0); }
1580
+ on(d.scrollbarH, "mousedown", reFocus);
1581
+ on(d.scrollbarV, "mousedown", reFocus);
1582
+ // Prevent wrapper from ever scrolling
1583
+ on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; });
1584
+
1585
+ var resizeTimer;
1586
+ function onResize() {
1587
+ if (resizeTimer == null) resizeTimer = setTimeout(function() {
1588
+ resizeTimer = null;
1589
+ // Might be a text scaling operation, clear size caches.
1590
+ d.cachedCharWidth = d.cachedTextHeight = knownScrollbarWidth = null;
1591
+ clearCaches(cm);
1592
+ runInOp(cm, bind(regChange, cm));
1593
+ }, 100);
1594
+ }
1595
+ on(window, "resize", onResize);
1596
+ // Above handler holds on to the editor and its data structures.
1597
+ // Here we poll to unregister it when the editor is no longer in
1598
+ // the document, so that it can be garbage-collected.
1599
+ function unregister() {
1600
+ for (var p = d.wrapper.parentNode; p && p != document.body; p = p.parentNode) {}
1601
+ if (p) setTimeout(unregister, 5000);
1602
+ else off(window, "resize", onResize);
1603
+ }
1604
+ setTimeout(unregister, 5000);
1605
+
1606
+ on(d.input, "keyup", operation(cm, function(e) {
1607
+ if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
1608
+ if (e.keyCode == 16) cm.doc.sel.shift = false;
1609
+ }));
1610
+ on(d.input, "input", function() {
1611
+ if (ie && !ie_lt9 && cm.display.inputHasSelection) cm.display.inputHasSelection = null;
1612
+ fastPoll(cm);
1613
+ });
1614
+ on(d.input, "keydown", operation(cm, onKeyDown));
1615
+ on(d.input, "keypress", operation(cm, onKeyPress));
1616
+ on(d.input, "focus", bind(onFocus, cm));
1617
+ on(d.input, "blur", bind(onBlur, cm));
1618
+
1619
+ function drag_(e) {
1620
+ if (signalDOMEvent(cm, e) || cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))) return;
1621
+ e_stop(e);
1622
+ }
1623
+ if (cm.options.dragDrop) {
1624
+ on(d.scroller, "dragstart", function(e){onDragStart(cm, e);});
1625
+ on(d.scroller, "dragenter", drag_);
1626
+ on(d.scroller, "dragover", drag_);
1627
+ on(d.scroller, "drop", operation(cm, onDrop));
1628
+ }
1629
+ on(d.scroller, "paste", function(e) {
1630
+ if (eventInWidget(d, e)) return;
1631
+ focusInput(cm);
1632
+ fastPoll(cm);
1633
+ });
1634
+ on(d.input, "paste", function() {
1635
+ // Workaround for webkit bug https://bugs.webkit.org/show_bug.cgi?id=90206
1636
+ // Add a char to the end of textarea before paste occur so that
1637
+ // selection doesn't span to the end of textarea.
1638
+ if (webkit && !cm.state.fakedLastChar && !(new Date - cm.state.lastMiddleDown < 200)) {
1639
+ var start = d.input.selectionStart, end = d.input.selectionEnd;
1640
+ d.input.value += "$";
1641
+ d.input.selectionStart = start;
1642
+ d.input.selectionEnd = end;
1643
+ cm.state.fakedLastChar = true;
1644
+ }
1645
+ cm.state.pasteIncoming = true;
1646
+ fastPoll(cm);
1647
+ });
1648
+
1649
+ function prepareCopy() {
1650
+ if (d.inaccurateSelection) {
1651
+ d.prevInput = "";
1652
+ d.inaccurateSelection = false;
1653
+ d.input.value = cm.getSelection();
1654
+ selectInput(d.input);
1655
+ }
1656
+ }
1657
+ on(d.input, "cut", prepareCopy);
1658
+ on(d.input, "copy", prepareCopy);
1659
+
1660
+ // Needed to handle Tab key in KHTML
1661
+ if (khtml) on(d.sizer, "mouseup", function() {
1662
+ if (document.activeElement == d.input) d.input.blur();
1663
+ focusInput(cm);
1664
+ });
1665
+ }
1666
+
1667
+ function eventInWidget(display, e) {
1668
+ for (var n = e_target(e); n != display.wrapper; n = n.parentNode) {
1669
+ if (!n || n.ignoreEvents || n.parentNode == display.sizer && n != display.mover) return true;
1670
+ }
1671
+ }
1672
+
1673
+ function posFromMouse(cm, e, liberal) {
1674
+ var display = cm.display;
1675
+ if (!liberal) {
1676
+ var target = e_target(e);
1677
+ if (target == display.scrollbarH || target == display.scrollbarH.firstChild ||
1678
+ target == display.scrollbarV || target == display.scrollbarV.firstChild ||
1679
+ target == display.scrollbarFiller || target == display.gutterFiller) return null;
1680
+ }
1681
+ var x, y, space = getRect(display.lineSpace);
1682
+ // Fails unpredictably on IE[67] when mouse is dragged around quickly.
1683
+ try { x = e.clientX; y = e.clientY; } catch (e) { return null; }
1684
+ return coordsChar(cm, x - space.left, y - space.top);
1685
+ }
1686
+
1687
+ var lastClick, lastDoubleClick;
1688
+ function onMouseDown(e) {
1689
+ if (signalDOMEvent(this, e)) return;
1690
+ var cm = this, display = cm.display, doc = cm.doc, sel = doc.sel;
1691
+ sel.shift = e.shiftKey;
1692
+
1693
+ if (eventInWidget(display, e)) {
1694
+ if (!webkit) {
1695
+ display.scroller.draggable = false;
1696
+ setTimeout(function(){display.scroller.draggable = true;}, 100);
1697
+ }
1698
+ return;
1699
+ }
1700
+ if (clickInGutter(cm, e)) return;
1701
+ var start = posFromMouse(cm, e);
1702
+
1703
+ switch (e_button(e)) {
1704
+ case 3:
1705
+ if (captureMiddleClick) onContextMenu.call(cm, cm, e);
1706
+ return;
1707
+ case 2:
1708
+ if (webkit) cm.state.lastMiddleDown = +new Date;
1709
+ if (start) extendSelection(cm.doc, start);
1710
+ setTimeout(bind(focusInput, cm), 20);
1711
+ e_preventDefault(e);
1712
+ return;
1713
+ }
1714
+ // For button 1, if it was clicked inside the editor
1715
+ // (posFromMouse returning non-null), we have to adjust the
1716
+ // selection.
1717
+ if (!start) {if (e_target(e) == display.scroller) e_preventDefault(e); return;}
1718
+
1719
+ if (!cm.state.focused) onFocus(cm);
1720
+
1721
+ var now = +new Date, type = "single";
1722
+ if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) {
1723
+ type = "triple";
1724
+ e_preventDefault(e);
1725
+ setTimeout(bind(focusInput, cm), 20);
1726
+ selectLine(cm, start.line);
1727
+ } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) {
1728
+ type = "double";
1729
+ lastDoubleClick = {time: now, pos: start};
1730
+ e_preventDefault(e);
1731
+ var word = findWordAt(getLine(doc, start.line).text, start);
1732
+ extendSelection(cm.doc, word.from, word.to);
1733
+ } else { lastClick = {time: now, pos: start}; }
1734
+
1735
+ var last = start;
1736
+ if (cm.options.dragDrop && dragAndDrop && !isReadOnly(cm) && !posEq(sel.from, sel.to) &&
1737
+ !posLess(start, sel.from) && !posLess(sel.to, start) && type == "single") {
1738
+ var dragEnd = operation(cm, function(e2) {
1739
+ if (webkit) display.scroller.draggable = false;
1740
+ cm.state.draggingText = false;
1741
+ off(document, "mouseup", dragEnd);
1742
+ off(display.scroller, "drop", dragEnd);
1743
+ if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
1744
+ e_preventDefault(e2);
1745
+ extendSelection(cm.doc, start);
1746
+ focusInput(cm);
1747
+ }
1748
+ });
1749
+ // Let the drag handler handle this.
1750
+ if (webkit) display.scroller.draggable = true;
1751
+ cm.state.draggingText = dragEnd;
1752
+ // IE's approach to draggable
1753
+ if (display.scroller.dragDrop) display.scroller.dragDrop();
1754
+ on(document, "mouseup", dragEnd);
1755
+ on(display.scroller, "drop", dragEnd);
1756
+ return;
1757
+ }
1758
+ e_preventDefault(e);
1759
+ if (type == "single") extendSelection(cm.doc, clipPos(doc, start));
1760
+
1761
+ var startstart = sel.from, startend = sel.to, lastPos = start;
1762
+
1763
+ function doSelect(cur) {
1764
+ if (posEq(lastPos, cur)) return;
1765
+ lastPos = cur;
1766
+
1767
+ if (type == "single") {
1768
+ extendSelection(cm.doc, clipPos(doc, start), cur);
1769
+ return;
1770
+ }
1771
+
1772
+ startstart = clipPos(doc, startstart);
1773
+ startend = clipPos(doc, startend);
1774
+ if (type == "double") {
1775
+ var word = findWordAt(getLine(doc, cur.line).text, cur);
1776
+ if (posLess(cur, startstart)) extendSelection(cm.doc, word.from, startend);
1777
+ else extendSelection(cm.doc, startstart, word.to);
1778
+ } else if (type == "triple") {
1779
+ if (posLess(cur, startstart)) extendSelection(cm.doc, startend, clipPos(doc, Pos(cur.line, 0)));
1780
+ else extendSelection(cm.doc, startstart, clipPos(doc, Pos(cur.line + 1, 0)));
1781
+ }
1782
+ }
1783
+
1784
+ var editorSize = getRect(display.wrapper);
1785
+ // Used to ensure timeout re-tries don't fire when another extend
1786
+ // happened in the meantime (clearTimeout isn't reliable -- at
1787
+ // least on Chrome, the timeouts still happen even when cleared,
1788
+ // if the clear happens after their scheduled firing time).
1789
+ var counter = 0;
1790
+
1791
+ function extend(e) {
1792
+ var curCount = ++counter;
1793
+ var cur = posFromMouse(cm, e, true);
1794
+ if (!cur) return;
1795
+ if (!posEq(cur, last)) {
1796
+ if (!cm.state.focused) onFocus(cm);
1797
+ last = cur;
1798
+ doSelect(cur);
1799
+ var visible = visibleLines(display, doc);
1800
+ if (cur.line >= visible.to || cur.line < visible.from)
1801
+ setTimeout(operation(cm, function(){if (counter == curCount) extend(e);}), 150);
1802
+ } else {
1803
+ var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0;
1804
+ if (outside) setTimeout(operation(cm, function() {
1805
+ if (counter != curCount) return;
1806
+ display.scroller.scrollTop += outside;
1807
+ extend(e);
1808
+ }), 50);
1809
+ }
1810
+ }
1811
+
1812
+ function done(e) {
1813
+ counter = Infinity;
1814
+ e_preventDefault(e);
1815
+ focusInput(cm);
1816
+ off(document, "mousemove", move);
1817
+ off(document, "mouseup", up);
1818
+ }
1819
+
1820
+ var move = operation(cm, function(e) {
1821
+ if (!ie && !e_button(e)) done(e);
1822
+ else extend(e);
1823
+ });
1824
+ var up = operation(cm, done);
1825
+ on(document, "mousemove", move);
1826
+ on(document, "mouseup", up);
1827
+ }
1828
+
1829
+ function gutterEvent(cm, e, type, prevent, signalfn) {
1830
+ try { var mX = e.clientX, mY = e.clientY; }
1831
+ catch(e) { return false; }
1832
+ if (mX >= Math.floor(getRect(cm.display.gutters).right)) return false;
1833
+ if (prevent) e_preventDefault(e);
1834
+
1835
+ var display = cm.display;
1836
+ var lineBox = getRect(display.lineDiv);
1837
+
1838
+ if (mY > lineBox.bottom || !hasHandler(cm, type)) return e_defaultPrevented(e);
1839
+ mY -= lineBox.top - display.viewOffset;
1840
+
1841
+ for (var i = 0; i < cm.options.gutters.length; ++i) {
1842
+ var g = display.gutters.childNodes[i];
1843
+ if (g && getRect(g).right >= mX) {
1844
+ var line = lineAtHeight(cm.doc, mY);
1845
+ var gutter = cm.options.gutters[i];
1846
+ signalfn(cm, type, cm, line, gutter, e);
1847
+ return e_defaultPrevented(e);
1848
+ }
1849
+ }
1850
+ }
1851
+
1852
+ function contextMenuInGutter(cm, e) {
1853
+ if (!hasHandler(cm, "gutterContextMenu")) return false;
1854
+ return gutterEvent(cm, e, "gutterContextMenu", false, signal);
1855
+ }
1856
+
1857
+ function clickInGutter(cm, e) {
1858
+ return gutterEvent(cm, e, "gutterClick", true, signalLater);
1859
+ }
1860
+
1861
+ // Kludge to work around strange IE behavior where it'll sometimes
1862
+ // re-fire a series of drag-related events right after the drop (#1551)
1863
+ var lastDrop = 0;
1864
+
1865
+ function onDrop(e) {
1866
+ var cm = this;
1867
+ if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e) || (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))))
1868
+ return;
1869
+ e_preventDefault(e);
1870
+ if (ie) lastDrop = +new Date;
1871
+ var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files;
1872
+ if (!pos || isReadOnly(cm)) return;
1873
+ if (files && files.length && window.FileReader && window.File) {
1874
+ var n = files.length, text = Array(n), read = 0;
1875
+ var loadFile = function(file, i) {
1876
+ var reader = new FileReader;
1877
+ reader.onload = function() {
1878
+ text[i] = reader.result;
1879
+ if (++read == n) {
1880
+ pos = clipPos(cm.doc, pos);
1881
+ makeChange(cm.doc, {from: pos, to: pos, text: splitLines(text.join("\n")), origin: "paste"}, "around");
1882
+ }
1883
+ };
1884
+ reader.readAsText(file);
1885
+ };
1886
+ for (var i = 0; i < n; ++i) loadFile(files[i], i);
1887
+ } else {
1888
+ // Don't do a replace if the drop happened inside of the selected text.
1889
+ if (cm.state.draggingText && !(posLess(pos, cm.doc.sel.from) || posLess(cm.doc.sel.to, pos))) {
1890
+ cm.state.draggingText(e);
1891
+ // Ensure the editor is re-focused
1892
+ setTimeout(bind(focusInput, cm), 20);
1893
+ return;
1894
+ }
1895
+ try {
1896
+ var text = e.dataTransfer.getData("Text");
1897
+ if (text) {
1898
+ var curFrom = cm.doc.sel.from, curTo = cm.doc.sel.to;
1899
+ setSelection(cm.doc, pos, pos);
1900
+ if (cm.state.draggingText) replaceRange(cm.doc, "", curFrom, curTo, "paste");
1901
+ cm.replaceSelection(text, null, "paste");
1902
+ focusInput(cm);
1903
+ onFocus(cm);
1904
+ }
1905
+ }
1906
+ catch(e){}
1907
+ }
1908
+ }
1909
+
1910
+ function onDragStart(cm, e) {
1911
+ if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; }
1912
+ if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return;
1913
+
1914
+ var txt = cm.getSelection();
1915
+ e.dataTransfer.setData("Text", txt);
1916
+
1917
+ // Use dummy image instead of default browsers image.
1918
+ // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there.
1919
+ if (e.dataTransfer.setDragImage && !safari) {
1920
+ var img = elt("img", null, null, "position: fixed; left: 0; top: 0;");
1921
+ img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
1922
+ if (opera) {
1923
+ img.width = img.height = 1;
1924
+ cm.display.wrapper.appendChild(img);
1925
+ // Force a relayout, or Opera won't use our image for some obscure reason
1926
+ img._top = img.offsetTop;
1927
+ }
1928
+ e.dataTransfer.setDragImage(img, 0, 0);
1929
+ if (opera) img.parentNode.removeChild(img);
1930
+ }
1931
+ }
1932
+
1933
+ function setScrollTop(cm, val) {
1934
+ if (Math.abs(cm.doc.scrollTop - val) < 2) return;
1935
+ cm.doc.scrollTop = val;
1936
+ if (!gecko) updateDisplay(cm, [], val);
1937
+ if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val;
1938
+ if (cm.display.scrollbarV.scrollTop != val) cm.display.scrollbarV.scrollTop = val;
1939
+ if (gecko) updateDisplay(cm, []);
1940
+ startWorker(cm, 100);
1941
+ }
1942
+ function setScrollLeft(cm, val, isScroller) {
1943
+ if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) return;
1944
+ val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth);
1945
+ cm.doc.scrollLeft = val;
1946
+ alignHorizontally(cm);
1947
+ if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val;
1948
+ if (cm.display.scrollbarH.scrollLeft != val) cm.display.scrollbarH.scrollLeft = val;
1949
+ }
1950
+
1951
+ // Since the delta values reported on mouse wheel events are
1952
+ // unstandardized between browsers and even browser versions, and
1953
+ // generally horribly unpredictable, this code starts by measuring
1954
+ // the scroll effect that the first few mouse wheel events have,
1955
+ // and, from that, detects the way it can convert deltas to pixel
1956
+ // offsets afterwards.
1957
+ //
1958
+ // The reason we want to know the amount a wheel event will scroll
1959
+ // is that it gives us a chance to update the display before the
1960
+ // actual scrolling happens, reducing flickering.
1961
+
1962
+ var wheelSamples = 0, wheelPixelsPerUnit = null;
1963
+ // Fill in a browser-detected starting value on browsers where we
1964
+ // know one. These don't have to be accurate -- the result of them
1965
+ // being wrong would just be a slight flicker on the first wheel
1966
+ // scroll (if it is large enough).
1967
+ if (ie) wheelPixelsPerUnit = -.53;
1968
+ else if (gecko) wheelPixelsPerUnit = 15;
1969
+ else if (chrome) wheelPixelsPerUnit = -.7;
1970
+ else if (safari) wheelPixelsPerUnit = -1/3;
1971
+
1972
+ function onScrollWheel(cm, e) {
1973
+ var dx = e.wheelDeltaX, dy = e.wheelDeltaY;
1974
+ if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail;
1975
+ if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail;
1976
+ else if (dy == null) dy = e.wheelDelta;
1977
+
1978
+ var display = cm.display, scroll = display.scroller;
1979
+ // Quit if there's nothing to scroll here
1980
+ if (!(dx && scroll.scrollWidth > scroll.clientWidth ||
1981
+ dy && scroll.scrollHeight > scroll.clientHeight)) return;
1982
+
1983
+ // Webkit browsers on OS X abort momentum scrolls when the target
1984
+ // of the scroll event is removed from the scrollable element.
1985
+ // This hack (see related code in patchDisplay) makes sure the
1986
+ // element is kept around.
1987
+ if (dy && mac && webkit) {
1988
+ for (var cur = e.target; cur != scroll; cur = cur.parentNode) {
1989
+ if (cur.lineObj) {
1990
+ cm.display.currentWheelTarget = cur;
1991
+ break;
1992
+ }
1993
+ }
1994
+ }
1995
+
1996
+ // On some browsers, horizontal scrolling will cause redraws to
1997
+ // happen before the gutter has been realigned, causing it to
1998
+ // wriggle around in a most unseemly way. When we have an
1999
+ // estimated pixels/delta value, we just handle horizontal
2000
+ // scrolling entirely here. It'll be slightly off from native, but
2001
+ // better than glitching out.
2002
+ if (dx && !gecko && !opera && wheelPixelsPerUnit != null) {
2003
+ if (dy)
2004
+ setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight)));
2005
+ setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth)));
2006
+ e_preventDefault(e);
2007
+ display.wheelStartX = null; // Abort measurement, if in progress
2008
+ return;
2009
+ }
2010
+
2011
+ if (dy && wheelPixelsPerUnit != null) {
2012
+ var pixels = dy * wheelPixelsPerUnit;
2013
+ var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight;
2014
+ if (pixels < 0) top = Math.max(0, top + pixels - 50);
2015
+ else bot = Math.min(cm.doc.height, bot + pixels + 50);
2016
+ updateDisplay(cm, [], {top: top, bottom: bot});
2017
+ }
2018
+
2019
+ if (wheelSamples < 20) {
2020
+ if (display.wheelStartX == null) {
2021
+ display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop;
2022
+ display.wheelDX = dx; display.wheelDY = dy;
2023
+ setTimeout(function() {
2024
+ if (display.wheelStartX == null) return;
2025
+ var movedX = scroll.scrollLeft - display.wheelStartX;
2026
+ var movedY = scroll.scrollTop - display.wheelStartY;
2027
+ var sample = (movedY && display.wheelDY && movedY / display.wheelDY) ||
2028
+ (movedX && display.wheelDX && movedX / display.wheelDX);
2029
+ display.wheelStartX = display.wheelStartY = null;
2030
+ if (!sample) return;
2031
+ wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1);
2032
+ ++wheelSamples;
2033
+ }, 200);
2034
+ } else {
2035
+ display.wheelDX += dx; display.wheelDY += dy;
2036
+ }
2037
+ }
2038
+ }
2039
+
2040
+ function doHandleBinding(cm, bound, dropShift) {
2041
+ if (typeof bound == "string") {
2042
+ bound = commands[bound];
2043
+ if (!bound) return false;
2044
+ }
2045
+ // Ensure previous input has been read, so that the handler sees a
2046
+ // consistent view of the document
2047
+ if (cm.display.pollingFast && readInput(cm)) cm.display.pollingFast = false;
2048
+ var doc = cm.doc, prevShift = doc.sel.shift, done = false;
2049
+ try {
2050
+ if (isReadOnly(cm)) cm.state.suppressEdits = true;
2051
+ if (dropShift) doc.sel.shift = false;
2052
+ done = bound(cm) != Pass;
2053
+ } finally {
2054
+ doc.sel.shift = prevShift;
2055
+ cm.state.suppressEdits = false;
2056
+ }
2057
+ return done;
2058
+ }
2059
+
2060
+ function allKeyMaps(cm) {
2061
+ var maps = cm.state.keyMaps.slice(0);
2062
+ if (cm.options.extraKeys) maps.push(cm.options.extraKeys);
2063
+ maps.push(cm.options.keyMap);
2064
+ return maps;
2065
+ }
2066
+
2067
+ var maybeTransition;
2068
+ function handleKeyBinding(cm, e) {
2069
+ // Handle auto keymap transitions
2070
+ var startMap = getKeyMap(cm.options.keyMap), next = startMap.auto;
2071
+ clearTimeout(maybeTransition);
2072
+ if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() {
2073
+ if (getKeyMap(cm.options.keyMap) == startMap) {
2074
+ cm.options.keyMap = (next.call ? next.call(null, cm) : next);
2075
+ keyMapChanged(cm);
2076
+ }
2077
+ }, 50);
2078
+
2079
+ var name = keyName(e, true), handled = false;
2080
+ if (!name) return false;
2081
+ var keymaps = allKeyMaps(cm);
2082
+
2083
+ if (e.shiftKey) {
2084
+ // First try to resolve full name (including 'Shift-'). Failing
2085
+ // that, see if there is a cursor-motion command (starting with
2086
+ // 'go') bound to the keyname without 'Shift-'.
2087
+ handled = lookupKey("Shift-" + name, keymaps, function(b) {return doHandleBinding(cm, b, true);})
2088
+ || lookupKey(name, keymaps, function(b) {
2089
+ if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion)
2090
+ return doHandleBinding(cm, b);
2091
+ });
2092
+ } else {
2093
+ handled = lookupKey(name, keymaps, function(b) { return doHandleBinding(cm, b); });
2094
+ }
2095
+
2096
+ if (handled) {
2097
+ e_preventDefault(e);
2098
+ restartBlink(cm);
2099
+ if (ie_lt9) { e.oldKeyCode = e.keyCode; e.keyCode = 0; }
2100
+ signalLater(cm, "keyHandled", cm, name, e);
2101
+ }
2102
+ return handled;
2103
+ }
2104
+
2105
+ function handleCharBinding(cm, e, ch) {
2106
+ var handled = lookupKey("'" + ch + "'", allKeyMaps(cm),
2107
+ function(b) { return doHandleBinding(cm, b, true); });
2108
+ if (handled) {
2109
+ e_preventDefault(e);
2110
+ restartBlink(cm);
2111
+ signalLater(cm, "keyHandled", cm, "'" + ch + "'", e);
2112
+ }
2113
+ return handled;
2114
+ }
2115
+
2116
+ var lastStoppedKey = null;
2117
+ function onKeyDown(e) {
2118
+ var cm = this;
2119
+ if (!cm.state.focused) onFocus(cm);
2120
+ if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
2121
+ if (ie && e.keyCode == 27) e.returnValue = false;
2122
+ var code = e.keyCode;
2123
+ // IE does strange things with escape.
2124
+ cm.doc.sel.shift = code == 16 || e.shiftKey;
2125
+ // First give onKeyEvent option a chance to handle this.
2126
+ var handled = handleKeyBinding(cm, e);
2127
+ if (opera) {
2128
+ lastStoppedKey = handled ? code : null;
2129
+ // Opera has no cut event... we try to at least catch the key combo
2130
+ if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey))
2131
+ cm.replaceSelection("");
2132
+ }
2133
+ }
2134
+
2135
+ function onKeyPress(e) {
2136
+ var cm = this;
2137
+ if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
2138
+ var keyCode = e.keyCode, charCode = e.charCode;
2139
+ if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
2140
+ if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return;
2141
+ var ch = String.fromCharCode(charCode == null ? keyCode : charCode);
2142
+ if (this.options.electricChars && this.doc.mode.electricChars &&
2143
+ this.options.smartIndent && !isReadOnly(this) &&
2144
+ this.doc.mode.electricChars.indexOf(ch) > -1)
2145
+ setTimeout(operation(cm, function() {indentLine(cm, cm.doc.sel.to.line, "smart");}), 75);
2146
+ if (handleCharBinding(cm, e, ch)) return;
2147
+ if (ie && !ie_lt9) cm.display.inputHasSelection = null;
2148
+ fastPoll(cm);
2149
+ }
2150
+
2151
+ function onFocus(cm) {
2152
+ if (cm.options.readOnly == "nocursor") return;
2153
+ if (!cm.state.focused) {
2154
+ signal(cm, "focus", cm);
2155
+ cm.state.focused = true;
2156
+ if (cm.display.wrapper.className.search(/\bCodeMirror-focused\b/) == -1)
2157
+ cm.display.wrapper.className += " CodeMirror-focused";
2158
+ if (!cm.curOp) {
2159
+ resetInput(cm, true);
2160
+ if (webkit) setTimeout(bind(resetInput, cm, true), 0); // Issue #1730
2161
+ }
2162
+ }
2163
+ slowPoll(cm);
2164
+ restartBlink(cm);
2165
+ }
2166
+ function onBlur(cm) {
2167
+ if (cm.state.focused) {
2168
+ signal(cm, "blur", cm);
2169
+ cm.state.focused = false;
2170
+ cm.display.wrapper.className = cm.display.wrapper.className.replace(" CodeMirror-focused", "");
2171
+ }
2172
+ clearInterval(cm.display.blinker);
2173
+ setTimeout(function() {if (!cm.state.focused) cm.doc.sel.shift = false;}, 150);
2174
+ }
2175
+
2176
+ var detectingSelectAll;
2177
+ function onContextMenu(cm, e) {
2178
+ if (signalDOMEvent(cm, e, "contextmenu")) return;
2179
+ var display = cm.display, sel = cm.doc.sel;
2180
+ if (eventInWidget(display, e) || contextMenuInGutter(cm, e)) return;
2181
+
2182
+ var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop;
2183
+ if (!pos || opera) return; // Opera is difficult.
2184
+ if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to))
2185
+ operation(cm, setSelection)(cm.doc, pos, pos);
2186
+
2187
+ var oldCSS = display.input.style.cssText;
2188
+ display.inputDiv.style.position = "absolute";
2189
+ display.input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) +
2190
+ "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; outline: none;" +
2191
+ "border-width: 0; outline: none; overflow: hidden; opacity: .05; -ms-opacity: .05; filter: alpha(opacity=5);";
2192
+ focusInput(cm);
2193
+ resetInput(cm, true);
2194
+ // Adds "Select all" to context menu in FF
2195
+ if (posEq(sel.from, sel.to)) display.input.value = display.prevInput = " ";
2196
+
2197
+ function prepareSelectAllHack() {
2198
+ if (display.input.selectionStart != null) {
2199
+ var extval = display.input.value = "\u200b" + (posEq(sel.from, sel.to) ? "" : display.input.value);
2200
+ display.prevInput = "\u200b";
2201
+ display.input.selectionStart = 1; display.input.selectionEnd = extval.length;
2202
+ }
2203
+ }
2204
+ function rehide() {
2205
+ display.inputDiv.style.position = "relative";
2206
+ display.input.style.cssText = oldCSS;
2207
+ if (ie_lt9) display.scrollbarV.scrollTop = display.scroller.scrollTop = scrollPos;
2208
+ slowPoll(cm);
2209
+
2210
+ // Try to detect the user choosing select-all
2211
+ if (display.input.selectionStart != null) {
2212
+ if (!ie || ie_lt9) prepareSelectAllHack();
2213
+ clearTimeout(detectingSelectAll);
2214
+ var i = 0, poll = function(){
2215
+ if (display.prevInput == " " && display.input.selectionStart == 0)
2216
+ operation(cm, commands.selectAll)(cm);
2217
+ else if (i++ < 10) detectingSelectAll = setTimeout(poll, 500);
2218
+ else resetInput(cm);
2219
+ };
2220
+ detectingSelectAll = setTimeout(poll, 200);
2221
+ }
2222
+ }
2223
+
2224
+ if (ie && !ie_lt9) prepareSelectAllHack();
2225
+ if (captureMiddleClick) {
2226
+ e_stop(e);
2227
+ var mouseup = function() {
2228
+ off(window, "mouseup", mouseup);
2229
+ setTimeout(rehide, 20);
2230
+ };
2231
+ on(window, "mouseup", mouseup);
2232
+ } else {
2233
+ setTimeout(rehide, 50);
2234
+ }
2235
+ }
2236
+
2237
+ // UPDATING
2238
+
2239
+ var changeEnd = CodeMirror.changeEnd = function(change) {
2240
+ if (!change.text) return change.to;
2241
+ return Pos(change.from.line + change.text.length - 1,
2242
+ lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0));
2243
+ };
2244
+
2245
+ // Make sure a position will be valid after the given change.
2246
+ function clipPostChange(doc, change, pos) {
2247
+ if (!posLess(change.from, pos)) return clipPos(doc, pos);
2248
+ var diff = (change.text.length - 1) - (change.to.line - change.from.line);
2249
+ if (pos.line > change.to.line + diff) {
2250
+ var preLine = pos.line - diff, lastLine = doc.first + doc.size - 1;
2251
+ if (preLine > lastLine) return Pos(lastLine, getLine(doc, lastLine).text.length);
2252
+ return clipToLen(pos, getLine(doc, preLine).text.length);
2253
+ }
2254
+ if (pos.line == change.to.line + diff)
2255
+ return clipToLen(pos, lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0) +
2256
+ getLine(doc, change.to.line).text.length - change.to.ch);
2257
+ var inside = pos.line - change.from.line;
2258
+ return clipToLen(pos, change.text[inside].length + (inside ? 0 : change.from.ch));
2259
+ }
2260
+
2261
+ // Hint can be null|"end"|"start"|"around"|{anchor,head}
2262
+ function computeSelAfterChange(doc, change, hint) {
2263
+ if (hint && typeof hint == "object") // Assumed to be {anchor, head} object
2264
+ return {anchor: clipPostChange(doc, change, hint.anchor),
2265
+ head: clipPostChange(doc, change, hint.head)};
2266
+
2267
+ if (hint == "start") return {anchor: change.from, head: change.from};
2268
+
2269
+ var end = changeEnd(change);
2270
+ if (hint == "around") return {anchor: change.from, head: end};
2271
+ if (hint == "end") return {anchor: end, head: end};
2272
+
2273
+ // hint is null, leave the selection alone as much as possible
2274
+ var adjustPos = function(pos) {
2275
+ if (posLess(pos, change.from)) return pos;
2276
+ if (!posLess(change.to, pos)) return end;
2277
+
2278
+ var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch;
2279
+ if (pos.line == change.to.line) ch += end.ch - change.to.ch;
2280
+ return Pos(line, ch);
2281
+ };
2282
+ return {anchor: adjustPos(doc.sel.anchor), head: adjustPos(doc.sel.head)};
2283
+ }
2284
+
2285
+ function filterChange(doc, change, update) {
2286
+ var obj = {
2287
+ canceled: false,
2288
+ from: change.from,
2289
+ to: change.to,
2290
+ text: change.text,
2291
+ origin: change.origin,
2292
+ cancel: function() { this.canceled = true; }
2293
+ };
2294
+ if (update) obj.update = function(from, to, text, origin) {
2295
+ if (from) this.from = clipPos(doc, from);
2296
+ if (to) this.to = clipPos(doc, to);
2297
+ if (text) this.text = text;
2298
+ if (origin !== undefined) this.origin = origin;
2299
+ };
2300
+ signal(doc, "beforeChange", doc, obj);
2301
+ if (doc.cm) signal(doc.cm, "beforeChange", doc.cm, obj);
2302
+
2303
+ if (obj.canceled) return null;
2304
+ return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin};
2305
+ }
2306
+
2307
+ // Replace the range from from to to by the strings in replacement.
2308
+ // change is a {from, to, text [, origin]} object
2309
+ function makeChange(doc, change, selUpdate, ignoreReadOnly) {
2310
+ if (doc.cm) {
2311
+ if (!doc.cm.curOp) return operation(doc.cm, makeChange)(doc, change, selUpdate, ignoreReadOnly);
2312
+ if (doc.cm.state.suppressEdits) return;
2313
+ }
2314
+
2315
+ if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) {
2316
+ change = filterChange(doc, change, true);
2317
+ if (!change) return;
2318
+ }
2319
+
2320
+ // Possibly split or suppress the update based on the presence
2321
+ // of read-only spans in its range.
2322
+ var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to);
2323
+ if (split) {
2324
+ for (var i = split.length - 1; i >= 1; --i)
2325
+ makeChangeNoReadonly(doc, {from: split[i].from, to: split[i].to, text: [""]});
2326
+ if (split.length)
2327
+ makeChangeNoReadonly(doc, {from: split[0].from, to: split[0].to, text: change.text}, selUpdate);
2328
+ } else {
2329
+ makeChangeNoReadonly(doc, change, selUpdate);
2330
+ }
2331
+ }
2332
+
2333
+ function makeChangeNoReadonly(doc, change, selUpdate) {
2334
+ if (change.text.length == 1 && change.text[0] == "" && posEq(change.from, change.to)) return;
2335
+ var selAfter = computeSelAfterChange(doc, change, selUpdate);
2336
+ addToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN);
2337
+
2338
+ makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change));
2339
+ var rebased = [];
2340
+
2341
+ linkedDocs(doc, function(doc, sharedHist) {
2342
+ if (!sharedHist && indexOf(rebased, doc.history) == -1) {
2343
+ rebaseHist(doc.history, change);
2344
+ rebased.push(doc.history);
2345
+ }
2346
+ makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change));
2347
+ });
2348
+ }
2349
+
2350
+ function makeChangeFromHistory(doc, type) {
2351
+ if (doc.cm && doc.cm.state.suppressEdits) return;
2352
+
2353
+ var hist = doc.history;
2354
+ var event = (type == "undo" ? hist.done : hist.undone).pop();
2355
+ if (!event) return;
2356
+
2357
+ var anti = {changes: [], anchorBefore: event.anchorAfter, headBefore: event.headAfter,
2358
+ anchorAfter: event.anchorBefore, headAfter: event.headBefore,
2359
+ generation: hist.generation};
2360
+ (type == "undo" ? hist.undone : hist.done).push(anti);
2361
+ hist.generation = event.generation || ++hist.maxGeneration;
2362
+
2363
+ var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange");
2364
+
2365
+ for (var i = event.changes.length - 1; i >= 0; --i) {
2366
+ var change = event.changes[i];
2367
+ change.origin = type;
2368
+ if (filter && !filterChange(doc, change, false)) {
2369
+ (type == "undo" ? hist.done : hist.undone).length = 0;
2370
+ return;
2371
+ }
2372
+
2373
+ anti.changes.push(historyChangeFromChange(doc, change));
2374
+
2375
+ var after = i ? computeSelAfterChange(doc, change, null)
2376
+ : {anchor: event.anchorBefore, head: event.headBefore};
2377
+ makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change));
2378
+ var rebased = [];
2379
+
2380
+ linkedDocs(doc, function(doc, sharedHist) {
2381
+ if (!sharedHist && indexOf(rebased, doc.history) == -1) {
2382
+ rebaseHist(doc.history, change);
2383
+ rebased.push(doc.history);
2384
+ }
2385
+ makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change));
2386
+ });
2387
+ }
2388
+ }
2389
+
2390
+ function shiftDoc(doc, distance) {
2391
+ function shiftPos(pos) {return Pos(pos.line + distance, pos.ch);}
2392
+ doc.first += distance;
2393
+ if (doc.cm) regChange(doc.cm, doc.first, doc.first, distance);
2394
+ doc.sel.head = shiftPos(doc.sel.head); doc.sel.anchor = shiftPos(doc.sel.anchor);
2395
+ doc.sel.from = shiftPos(doc.sel.from); doc.sel.to = shiftPos(doc.sel.to);
2396
+ }
2397
+
2398
+ function makeChangeSingleDoc(doc, change, selAfter, spans) {
2399
+ if (doc.cm && !doc.cm.curOp)
2400
+ return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans);
2401
+
2402
+ if (change.to.line < doc.first) {
2403
+ shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line));
2404
+ return;
2405
+ }
2406
+ if (change.from.line > doc.lastLine()) return;
2407
+
2408
+ // Clip the change to the size of this doc
2409
+ if (change.from.line < doc.first) {
2410
+ var shift = change.text.length - 1 - (doc.first - change.from.line);
2411
+ shiftDoc(doc, shift);
2412
+ change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch),
2413
+ text: [lst(change.text)], origin: change.origin};
2414
+ }
2415
+ var last = doc.lastLine();
2416
+ if (change.to.line > last) {
2417
+ change = {from: change.from, to: Pos(last, getLine(doc, last).text.length),
2418
+ text: [change.text[0]], origin: change.origin};
2419
+ }
2420
+
2421
+ change.removed = getBetween(doc, change.from, change.to);
2422
+
2423
+ if (!selAfter) selAfter = computeSelAfterChange(doc, change, null);
2424
+ if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans, selAfter);
2425
+ else updateDoc(doc, change, spans, selAfter);
2426
+ }
2427
+
2428
+ function makeChangeSingleDocInEditor(cm, change, spans, selAfter) {
2429
+ var doc = cm.doc, display = cm.display, from = change.from, to = change.to;
2430
+
2431
+ var recomputeMaxLength = false, checkWidthStart = from.line;
2432
+ if (!cm.options.lineWrapping) {
2433
+ checkWidthStart = lineNo(visualLine(doc, getLine(doc, from.line)));
2434
+ doc.iter(checkWidthStart, to.line + 1, function(line) {
2435
+ if (line == display.maxLine) {
2436
+ recomputeMaxLength = true;
2437
+ return true;
2438
+ }
2439
+ });
2440
+ }
2441
+
2442
+ if (!posLess(doc.sel.head, change.from) && !posLess(change.to, doc.sel.head))
2443
+ cm.curOp.cursorActivity = true;
2444
+
2445
+ updateDoc(doc, change, spans, selAfter, estimateHeight(cm));
2446
+
2447
+ if (!cm.options.lineWrapping) {
2448
+ doc.iter(checkWidthStart, from.line + change.text.length, function(line) {
2449
+ var len = lineLength(doc, line);
2450
+ if (len > display.maxLineLength) {
2451
+ display.maxLine = line;
2452
+ display.maxLineLength = len;
2453
+ display.maxLineChanged = true;
2454
+ recomputeMaxLength = false;
2455
+ }
2456
+ });
2457
+ if (recomputeMaxLength) cm.curOp.updateMaxLine = true;
2458
+ }
2459
+
2460
+ // Adjust frontier, schedule worker
2461
+ doc.frontier = Math.min(doc.frontier, from.line);
2462
+ startWorker(cm, 400);
2463
+
2464
+ var lendiff = change.text.length - (to.line - from.line) - 1;
2465
+ // Remember that these lines changed, for updating the display
2466
+ regChange(cm, from.line, to.line + 1, lendiff);
2467
+
2468
+ if (hasHandler(cm, "change")) {
2469
+ var changeObj = {from: from, to: to,
2470
+ text: change.text,
2471
+ removed: change.removed,
2472
+ origin: change.origin};
2473
+ if (cm.curOp.textChanged) {
2474
+ for (var cur = cm.curOp.textChanged; cur.next; cur = cur.next) {}
2475
+ cur.next = changeObj;
2476
+ } else cm.curOp.textChanged = changeObj;
2477
+ }
2478
+ }
2479
+
2480
+ function replaceRange(doc, code, from, to, origin) {
2481
+ if (!to) to = from;
2482
+ if (posLess(to, from)) { var tmp = to; to = from; from = tmp; }
2483
+ if (typeof code == "string") code = splitLines(code);
2484
+ makeChange(doc, {from: from, to: to, text: code, origin: origin}, null);
2485
+ }
2486
+
2487
+ // POSITION OBJECT
2488
+
2489
+ function Pos(line, ch) {
2490
+ if (!(this instanceof Pos)) return new Pos(line, ch);
2491
+ this.line = line; this.ch = ch;
2492
+ }
2493
+ CodeMirror.Pos = Pos;
2494
+
2495
+ function posEq(a, b) {return a.line == b.line && a.ch == b.ch;}
2496
+ function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);}
2497
+ function copyPos(x) {return Pos(x.line, x.ch);}
2498
+
2499
+ // SELECTION
2500
+
2501
+ function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1));}
2502
+ function clipPos(doc, pos) {
2503
+ if (pos.line < doc.first) return Pos(doc.first, 0);
2504
+ var last = doc.first + doc.size - 1;
2505
+ if (pos.line > last) return Pos(last, getLine(doc, last).text.length);
2506
+ return clipToLen(pos, getLine(doc, pos.line).text.length);
2507
+ }
2508
+ function clipToLen(pos, linelen) {
2509
+ var ch = pos.ch;
2510
+ if (ch == null || ch > linelen) return Pos(pos.line, linelen);
2511
+ else if (ch < 0) return Pos(pos.line, 0);
2512
+ else return pos;
2513
+ }
2514
+ function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size;}
2515
+
2516
+ // If shift is held, this will move the selection anchor. Otherwise,
2517
+ // it'll set the whole selection.
2518
+ function extendSelection(doc, pos, other, bias) {
2519
+ if (doc.sel.shift || doc.sel.extend) {
2520
+ var anchor = doc.sel.anchor;
2521
+ if (other) {
2522
+ var posBefore = posLess(pos, anchor);
2523
+ if (posBefore != posLess(other, anchor)) {
2524
+ anchor = pos;
2525
+ pos = other;
2526
+ } else if (posBefore != posLess(pos, other)) {
2527
+ pos = other;
2528
+ }
2529
+ }
2530
+ setSelection(doc, anchor, pos, bias);
2531
+ } else {
2532
+ setSelection(doc, pos, other || pos, bias);
2533
+ }
2534
+ if (doc.cm) doc.cm.curOp.userSelChange = true;
2535
+ }
2536
+
2537
+ function filterSelectionChange(doc, anchor, head) {
2538
+ var obj = {anchor: anchor, head: head};
2539
+ signal(doc, "beforeSelectionChange", doc, obj);
2540
+ if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj);
2541
+ obj.anchor = clipPos(doc, obj.anchor); obj.head = clipPos(doc, obj.head);
2542
+ return obj;
2543
+ }
2544
+
2545
+ // Update the selection. Last two args are only used by
2546
+ // updateDoc, since they have to be expressed in the line
2547
+ // numbers before the update.
2548
+ function setSelection(doc, anchor, head, bias, checkAtomic) {
2549
+ if (!checkAtomic && hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) {
2550
+ var filtered = filterSelectionChange(doc, anchor, head);
2551
+ head = filtered.head;
2552
+ anchor = filtered.anchor;
2553
+ }
2554
+
2555
+ var sel = doc.sel;
2556
+ sel.goalColumn = null;
2557
+ if (bias == null) bias = posLess(head, sel.head) ? -1 : 1;
2558
+ // Skip over atomic spans.
2559
+ if (checkAtomic || !posEq(anchor, sel.anchor))
2560
+ anchor = skipAtomic(doc, anchor, bias, checkAtomic != "push");
2561
+ if (checkAtomic || !posEq(head, sel.head))
2562
+ head = skipAtomic(doc, head, bias, checkAtomic != "push");
2563
+
2564
+ if (posEq(sel.anchor, anchor) && posEq(sel.head, head)) return;
2565
+
2566
+ sel.anchor = anchor; sel.head = head;
2567
+ var inv = posLess(head, anchor);
2568
+ sel.from = inv ? head : anchor;
2569
+ sel.to = inv ? anchor : head;
2570
+
2571
+ if (doc.cm)
2572
+ doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged =
2573
+ doc.cm.curOp.cursorActivity = true;
2574
+
2575
+ signalLater(doc, "cursorActivity", doc);
2576
+ }
2577
+
2578
+ function reCheckSelection(cm) {
2579
+ setSelection(cm.doc, cm.doc.sel.from, cm.doc.sel.to, null, "push");
2580
+ }
2581
+
2582
+ function skipAtomic(doc, pos, bias, mayClear) {
2583
+ var flipped = false, curPos = pos;
2584
+ var dir = bias || 1;
2585
+ doc.cantEdit = false;
2586
+ search: for (;;) {
2587
+ var line = getLine(doc, curPos.line);
2588
+ if (line.markedSpans) {
2589
+ for (var i = 0; i < line.markedSpans.length; ++i) {
2590
+ var sp = line.markedSpans[i], m = sp.marker;
2591
+ if ((sp.from == null || (m.inclusiveLeft ? sp.from <= curPos.ch : sp.from < curPos.ch)) &&
2592
+ (sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to > curPos.ch))) {
2593
+ if (mayClear) {
2594
+ signal(m, "beforeCursorEnter");
2595
+ if (m.explicitlyCleared) {
2596
+ if (!line.markedSpans) break;
2597
+ else {--i; continue;}
2598
+ }
2599
+ }
2600
+ if (!m.atomic) continue;
2601
+ var newPos = m.find()[dir < 0 ? "from" : "to"];
2602
+ if (posEq(newPos, curPos)) {
2603
+ newPos.ch += dir;
2604
+ if (newPos.ch < 0) {
2605
+ if (newPos.line > doc.first) newPos = clipPos(doc, Pos(newPos.line - 1));
2606
+ else newPos = null;
2607
+ } else if (newPos.ch > line.text.length) {
2608
+ if (newPos.line < doc.first + doc.size - 1) newPos = Pos(newPos.line + 1, 0);
2609
+ else newPos = null;
2610
+ }
2611
+ if (!newPos) {
2612
+ if (flipped) {
2613
+ // Driven in a corner -- no valid cursor position found at all
2614
+ // -- try again *with* clearing, if we didn't already
2615
+ if (!mayClear) return skipAtomic(doc, pos, bias, true);
2616
+ // Otherwise, turn off editing until further notice, and return the start of the doc
2617
+ doc.cantEdit = true;
2618
+ return Pos(doc.first, 0);
2619
+ }
2620
+ flipped = true; newPos = pos; dir = -dir;
2621
+ }
2622
+ }
2623
+ curPos = newPos;
2624
+ continue search;
2625
+ }
2626
+ }
2627
+ }
2628
+ return curPos;
2629
+ }
2630
+ }
2631
+
2632
+ // SCROLLING
2633
+
2634
+ function scrollCursorIntoView(cm) {
2635
+ var coords = scrollPosIntoView(cm, cm.doc.sel.head, cm.options.cursorScrollMargin);
2636
+ if (!cm.state.focused) return;
2637
+ var display = cm.display, box = getRect(display.sizer), doScroll = null;
2638
+ if (coords.top + box.top < 0) doScroll = true;
2639
+ else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false;
2640
+ if (doScroll != null && !phantom) {
2641
+ var hidden = display.cursor.style.display == "none";
2642
+ if (hidden) {
2643
+ display.cursor.style.display = "";
2644
+ display.cursor.style.left = coords.left + "px";
2645
+ display.cursor.style.top = (coords.top - display.viewOffset) + "px";
2646
+ }
2647
+ display.cursor.scrollIntoView(doScroll);
2648
+ if (hidden) display.cursor.style.display = "none";
2649
+ }
2650
+ }
2651
+
2652
+ function scrollPosIntoView(cm, pos, margin) {
2653
+ if (margin == null) margin = 0;
2654
+ for (;;) {
2655
+ var changed = false, coords = cursorCoords(cm, pos);
2656
+ var scrollPos = calculateScrollPos(cm, coords.left, coords.top - margin, coords.left, coords.bottom + margin);
2657
+ var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft;
2658
+ if (scrollPos.scrollTop != null) {
2659
+ setScrollTop(cm, scrollPos.scrollTop);
2660
+ if (Math.abs(cm.doc.scrollTop - startTop) > 1) changed = true;
2661
+ }
2662
+ if (scrollPos.scrollLeft != null) {
2663
+ setScrollLeft(cm, scrollPos.scrollLeft);
2664
+ if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) changed = true;
2665
+ }
2666
+ if (!changed) return coords;
2667
+ }
2668
+ }
2669
+
2670
+ function scrollIntoView(cm, x1, y1, x2, y2) {
2671
+ var scrollPos = calculateScrollPos(cm, x1, y1, x2, y2);
2672
+ if (scrollPos.scrollTop != null) setScrollTop(cm, scrollPos.scrollTop);
2673
+ if (scrollPos.scrollLeft != null) setScrollLeft(cm, scrollPos.scrollLeft);
2674
+ }
2675
+
2676
+ function calculateScrollPos(cm, x1, y1, x2, y2) {
2677
+ var display = cm.display, snapMargin = textHeight(cm.display);
2678
+ if (y1 < 0) y1 = 0;
2679
+ var screen = display.scroller.clientHeight - scrollerCutOff, screentop = display.scroller.scrollTop, result = {};
2680
+ var docBottom = cm.doc.height + paddingVert(display);
2681
+ var atTop = y1 < snapMargin, atBottom = y2 > docBottom - snapMargin;
2682
+ if (y1 < screentop) {
2683
+ result.scrollTop = atTop ? 0 : y1;
2684
+ } else if (y2 > screentop + screen) {
2685
+ var newTop = Math.min(y1, (atBottom ? docBottom : y2) - screen);
2686
+ if (newTop != screentop) result.scrollTop = newTop;
2687
+ }
2688
+
2689
+ var screenw = display.scroller.clientWidth - scrollerCutOff, screenleft = display.scroller.scrollLeft;
2690
+ x1 += display.gutters.offsetWidth; x2 += display.gutters.offsetWidth;
2691
+ var gutterw = display.gutters.offsetWidth;
2692
+ var atLeft = x1 < gutterw + 10;
2693
+ if (x1 < screenleft + gutterw || atLeft) {
2694
+ if (atLeft) x1 = 0;
2695
+ result.scrollLeft = Math.max(0, x1 - 10 - gutterw);
2696
+ } else if (x2 > screenw + screenleft - 3) {
2697
+ result.scrollLeft = x2 + 10 - screenw;
2698
+ }
2699
+ return result;
2700
+ }
2701
+
2702
+ function updateScrollPos(cm, left, top) {
2703
+ cm.curOp.updateScrollPos = {scrollLeft: left == null ? cm.doc.scrollLeft : left,
2704
+ scrollTop: top == null ? cm.doc.scrollTop : top};
2705
+ }
2706
+
2707
+ function addToScrollPos(cm, left, top) {
2708
+ var pos = cm.curOp.updateScrollPos || (cm.curOp.updateScrollPos = {scrollLeft: cm.doc.scrollLeft, scrollTop: cm.doc.scrollTop});
2709
+ var scroll = cm.display.scroller;
2710
+ pos.scrollTop = Math.max(0, Math.min(scroll.scrollHeight - scroll.clientHeight, pos.scrollTop + top));
2711
+ pos.scrollLeft = Math.max(0, Math.min(scroll.scrollWidth - scroll.clientWidth, pos.scrollLeft + left));
2712
+ }
2713
+
2714
+ // API UTILITIES
2715
+
2716
+ function indentLine(cm, n, how, aggressive) {
2717
+ var doc = cm.doc;
2718
+ if (how == null) how = "add";
2719
+ if (how == "smart") {
2720
+ if (!cm.doc.mode.indent) how = "prev";
2721
+ else var state = getStateBefore(cm, n);
2722
+ }
2723
+
2724
+ var tabSize = cm.options.tabSize;
2725
+ var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize);
2726
+ var curSpaceString = line.text.match(/^\s*/)[0], indentation;
2727
+ if (how == "smart") {
2728
+ indentation = cm.doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text);
2729
+ if (indentation == Pass) {
2730
+ if (!aggressive) return;
2731
+ how = "prev";
2732
+ }
2733
+ }
2734
+ if (how == "prev") {
2735
+ if (n > doc.first) indentation = countColumn(getLine(doc, n-1).text, null, tabSize);
2736
+ else indentation = 0;
2737
+ } else if (how == "add") {
2738
+ indentation = curSpace + cm.options.indentUnit;
2739
+ } else if (how == "subtract") {
2740
+ indentation = curSpace - cm.options.indentUnit;
2741
+ } else if (typeof how == "number") {
2742
+ indentation = curSpace + how;
2743
+ }
2744
+ indentation = Math.max(0, indentation);
2745
+
2746
+ var indentString = "", pos = 0;
2747
+ if (cm.options.indentWithTabs)
2748
+ for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";}
2749
+ if (pos < indentation) indentString += spaceStr(indentation - pos);
2750
+
2751
+ if (indentString != curSpaceString)
2752
+ replaceRange(cm.doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input");
2753
+ line.stateAfter = null;
2754
+ }
2755
+
2756
+ function changeLine(cm, handle, op) {
2757
+ var no = handle, line = handle, doc = cm.doc;
2758
+ if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle));
2759
+ else no = lineNo(handle);
2760
+ if (no == null) return null;
2761
+ if (op(line, no)) regChange(cm, no, no + 1);
2762
+ else return null;
2763
+ return line;
2764
+ }
2765
+
2766
+ function findPosH(doc, pos, dir, unit, visually) {
2767
+ var line = pos.line, ch = pos.ch, origDir = dir;
2768
+ var lineObj = getLine(doc, line);
2769
+ var possible = true;
2770
+ function findNextLine() {
2771
+ var l = line + dir;
2772
+ if (l < doc.first || l >= doc.first + doc.size) return (possible = false);
2773
+ line = l;
2774
+ return lineObj = getLine(doc, l);
2775
+ }
2776
+ function moveOnce(boundToLine) {
2777
+ var next = (visually ? moveVisually : moveLogically)(lineObj, ch, dir, true);
2778
+ if (next == null) {
2779
+ if (!boundToLine && findNextLine()) {
2780
+ if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj);
2781
+ else ch = dir < 0 ? lineObj.text.length : 0;
2782
+ } else return (possible = false);
2783
+ } else ch = next;
2784
+ return true;
2785
+ }
2786
+
2787
+ if (unit == "char") moveOnce();
2788
+ else if (unit == "column") moveOnce(true);
2789
+ else if (unit == "word" || unit == "group") {
2790
+ var sawType = null, group = unit == "group";
2791
+ for (var first = true;; first = false) {
2792
+ if (dir < 0 && !moveOnce(!first)) break;
2793
+ var cur = lineObj.text.charAt(ch) || "\n";
2794
+ var type = isWordChar(cur) ? "w"
2795
+ : !group ? null
2796
+ : /\s/.test(cur) ? null
2797
+ : "p";
2798
+ if (sawType && sawType != type) {
2799
+ if (dir < 0) {dir = 1; moveOnce();}
2800
+ break;
2801
+ }
2802
+ if (type) sawType = type;
2803
+ if (dir > 0 && !moveOnce(!first)) break;
2804
+ }
2805
+ }
2806
+ var result = skipAtomic(doc, Pos(line, ch), origDir, true);
2807
+ if (!possible) result.hitSide = true;
2808
+ return result;
2809
+ }
2810
+
2811
+ function findPosV(cm, pos, dir, unit) {
2812
+ var doc = cm.doc, x = pos.left, y;
2813
+ if (unit == "page") {
2814
+ var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight);
2815
+ y = pos.top + dir * (pageSize - (dir < 0 ? 1.5 : .5) * textHeight(cm.display));
2816
+ } else if (unit == "line") {
2817
+ y = dir > 0 ? pos.bottom + 3 : pos.top - 3;
2818
+ }
2819
+ for (;;) {
2820
+ var target = coordsChar(cm, x, y);
2821
+ if (!target.outside) break;
2822
+ if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break; }
2823
+ y += dir * 5;
2824
+ }
2825
+ return target;
2826
+ }
2827
+
2828
+ function findWordAt(line, pos) {
2829
+ var start = pos.ch, end = pos.ch;
2830
+ if (line) {
2831
+ if ((pos.xRel < 0 || end == line.length) && start) --start; else ++end;
2832
+ var startChar = line.charAt(start);
2833
+ var check = isWordChar(startChar) ? isWordChar
2834
+ : /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);}
2835
+ : function(ch) {return !/\s/.test(ch) && !isWordChar(ch);};
2836
+ while (start > 0 && check(line.charAt(start - 1))) --start;
2837
+ while (end < line.length && check(line.charAt(end))) ++end;
2838
+ }
2839
+ return {from: Pos(pos.line, start), to: Pos(pos.line, end)};
2840
+ }
2841
+
2842
+ function selectLine(cm, line) {
2843
+ extendSelection(cm.doc, Pos(line, 0), clipPos(cm.doc, Pos(line + 1, 0)));
2844
+ }
2845
+
2846
+ // PROTOTYPE
2847
+
2848
+ // The publicly visible API. Note that operation(null, f) means
2849
+ // 'wrap f in an operation, performed on its `this` parameter'
2850
+
2851
+ CodeMirror.prototype = {
2852
+ constructor: CodeMirror,
2853
+ focus: function(){window.focus(); focusInput(this); onFocus(this); fastPoll(this);},
2854
+
2855
+ setOption: function(option, value) {
2856
+ var options = this.options, old = options[option];
2857
+ if (options[option] == value && option != "mode") return;
2858
+ options[option] = value;
2859
+ if (optionHandlers.hasOwnProperty(option))
2860
+ operation(this, optionHandlers[option])(this, value, old);
2861
+ },
2862
+
2863
+ getOption: function(option) {return this.options[option];},
2864
+ getDoc: function() {return this.doc;},
2865
+
2866
+ addKeyMap: function(map, bottom) {
2867
+ this.state.keyMaps[bottom ? "push" : "unshift"](map);
2868
+ },
2869
+ removeKeyMap: function(map) {
2870
+ var maps = this.state.keyMaps;
2871
+ for (var i = 0; i < maps.length; ++i)
2872
+ if (maps[i] == map || (typeof maps[i] != "string" && maps[i].name == map)) {
2873
+ maps.splice(i, 1);
2874
+ return true;
2875
+ }
2876
+ },
2877
+
2878
+ addOverlay: operation(null, function(spec, options) {
2879
+ var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec);
2880
+ if (mode.startState) throw new Error("Overlays may not be stateful.");
2881
+ this.state.overlays.push({mode: mode, modeSpec: spec, opaque: options && options.opaque});
2882
+ this.state.modeGen++;
2883
+ regChange(this);
2884
+ }),
2885
+ removeOverlay: operation(null, function(spec) {
2886
+ var overlays = this.state.overlays;
2887
+ for (var i = 0; i < overlays.length; ++i) {
2888
+ var cur = overlays[i].modeSpec;
2889
+ if (cur == spec || typeof spec == "string" && cur.name == spec) {
2890
+ overlays.splice(i, 1);
2891
+ this.state.modeGen++;
2892
+ regChange(this);
2893
+ return;
2894
+ }
2895
+ }
2896
+ }),
2897
+
2898
+ indentLine: operation(null, function(n, dir, aggressive) {
2899
+ if (typeof dir != "string" && typeof dir != "number") {
2900
+ if (dir == null) dir = this.options.smartIndent ? "smart" : "prev";
2901
+ else dir = dir ? "add" : "subtract";
2902
+ }
2903
+ if (isLine(this.doc, n)) indentLine(this, n, dir, aggressive);
2904
+ }),
2905
+ indentSelection: operation(null, function(how) {
2906
+ var sel = this.doc.sel;
2907
+ if (posEq(sel.from, sel.to)) return indentLine(this, sel.from.line, how);
2908
+ var e = sel.to.line - (sel.to.ch ? 0 : 1);
2909
+ for (var i = sel.from.line; i <= e; ++i) indentLine(this, i, how);
2910
+ }),
2911
+
2912
+ // Fetch the parser token for a given character. Useful for hacks
2913
+ // that want to inspect the mode state (say, for completion).
2914
+ getTokenAt: function(pos, precise) {
2915
+ var doc = this.doc;
2916
+ pos = clipPos(doc, pos);
2917
+ var state = getStateBefore(this, pos.line, precise), mode = this.doc.mode;
2918
+ var line = getLine(doc, pos.line);
2919
+ var stream = new StringStream(line.text, this.options.tabSize);
2920
+ while (stream.pos < pos.ch && !stream.eol()) {
2921
+ stream.start = stream.pos;
2922
+ var style = mode.token(stream, state);
2923
+ }
2924
+ return {start: stream.start,
2925
+ end: stream.pos,
2926
+ string: stream.current(),
2927
+ className: style || null, // Deprecated, use 'type' instead
2928
+ type: style || null,
2929
+ state: state};
2930
+ },
2931
+
2932
+ getTokenTypeAt: function(pos) {
2933
+ pos = clipPos(this.doc, pos);
2934
+ var styles = getLineStyles(this, getLine(this.doc, pos.line));
2935
+ var before = 0, after = (styles.length - 1) / 2, ch = pos.ch;
2936
+ if (ch == 0) return styles[2];
2937
+ for (;;) {
2938
+ var mid = (before + after) >> 1;
2939
+ if ((mid ? styles[mid * 2 - 1] : 0) >= ch) after = mid;
2940
+ else if (styles[mid * 2 + 1] < ch) before = mid + 1;
2941
+ else return styles[mid * 2 + 2];
2942
+ }
2943
+ },
2944
+
2945
+ getModeAt: function(pos) {
2946
+ var mode = this.doc.mode;
2947
+ if (!mode.innerMode) return mode;
2948
+ return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode;
2949
+ },
2950
+
2951
+ getHelper: function(pos, type) {
2952
+ if (!helpers.hasOwnProperty(type)) return;
2953
+ var help = helpers[type], mode = this.getModeAt(pos);
2954
+ return mode[type] && help[mode[type]] ||
2955
+ mode.helperType && help[mode.helperType] ||
2956
+ help[mode.name];
2957
+ },
2958
+
2959
+ getStateAfter: function(line, precise) {
2960
+ var doc = this.doc;
2961
+ line = clipLine(doc, line == null ? doc.first + doc.size - 1: line);
2962
+ return getStateBefore(this, line + 1, precise);
2963
+ },
2964
+
2965
+ cursorCoords: function(start, mode) {
2966
+ var pos, sel = this.doc.sel;
2967
+ if (start == null) pos = sel.head;
2968
+ else if (typeof start == "object") pos = clipPos(this.doc, start);
2969
+ else pos = start ? sel.from : sel.to;
2970
+ return cursorCoords(this, pos, mode || "page");
2971
+ },
2972
+
2973
+ charCoords: function(pos, mode) {
2974
+ return charCoords(this, clipPos(this.doc, pos), mode || "page");
2975
+ },
2976
+
2977
+ coordsChar: function(coords, mode) {
2978
+ coords = fromCoordSystem(this, coords, mode || "page");
2979
+ return coordsChar(this, coords.left, coords.top);
2980
+ },
2981
+
2982
+ lineAtHeight: function(height, mode) {
2983
+ height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top;
2984
+ return lineAtHeight(this.doc, height + this.display.viewOffset);
2985
+ },
2986
+ heightAtLine: function(line, mode) {
2987
+ var end = false, last = this.doc.first + this.doc.size - 1;
2988
+ if (line < this.doc.first) line = this.doc.first;
2989
+ else if (line > last) { line = last; end = true; }
2990
+ var lineObj = getLine(this.doc, line);
2991
+ return intoCoordSystem(this, getLine(this.doc, line), {top: 0, left: 0}, mode || "page").top +
2992
+ (end ? lineObj.height : 0);
2993
+ },
2994
+
2995
+ defaultTextHeight: function() { return textHeight(this.display); },
2996
+ defaultCharWidth: function() { return charWidth(this.display); },
2997
+
2998
+ setGutterMarker: operation(null, function(line, gutterID, value) {
2999
+ return changeLine(this, line, function(line) {
3000
+ var markers = line.gutterMarkers || (line.gutterMarkers = {});
3001
+ markers[gutterID] = value;
3002
+ if (!value && isEmpty(markers)) line.gutterMarkers = null;
3003
+ return true;
3004
+ });
3005
+ }),
3006
+
3007
+ clearGutter: operation(null, function(gutterID) {
3008
+ var cm = this, doc = cm.doc, i = doc.first;
3009
+ doc.iter(function(line) {
3010
+ if (line.gutterMarkers && line.gutterMarkers[gutterID]) {
3011
+ line.gutterMarkers[gutterID] = null;
3012
+ regChange(cm, i, i + 1);
3013
+ if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null;
3014
+ }
3015
+ ++i;
3016
+ });
3017
+ }),
3018
+
3019
+ addLineClass: operation(null, function(handle, where, cls) {
3020
+ return changeLine(this, handle, function(line) {
3021
+ var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass";
3022
+ if (!line[prop]) line[prop] = cls;
3023
+ else if (new RegExp("(?:^|\\s)" + cls + "(?:$|\\s)").test(line[prop])) return false;
3024
+ else line[prop] += " " + cls;
3025
+ return true;
3026
+ });
3027
+ }),
3028
+
3029
+ removeLineClass: operation(null, function(handle, where, cls) {
3030
+ return changeLine(this, handle, function(line) {
3031
+ var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass";
3032
+ var cur = line[prop];
3033
+ if (!cur) return false;
3034
+ else if (cls == null) line[prop] = null;
3035
+ else {
3036
+ var found = cur.match(new RegExp("(?:^|\\s+)" + cls + "(?:$|\\s+)"));
3037
+ if (!found) return false;
3038
+ var end = found.index + found[0].length;
3039
+ line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null;
3040
+ }
3041
+ return true;
3042
+ });
3043
+ }),
3044
+
3045
+ addLineWidget: operation(null, function(handle, node, options) {
3046
+ return addLineWidget(this, handle, node, options);
3047
+ }),
3048
+
3049
+ removeLineWidget: function(widget) { widget.clear(); },
3050
+
3051
+ lineInfo: function(line) {
3052
+ if (typeof line == "number") {
3053
+ if (!isLine(this.doc, line)) return null;
3054
+ var n = line;
3055
+ line = getLine(this.doc, line);
3056
+ if (!line) return null;
3057
+ } else {
3058
+ var n = lineNo(line);
3059
+ if (n == null) return null;
3060
+ }
3061
+ return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers,
3062
+ textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass,
3063
+ widgets: line.widgets};
3064
+ },
3065
+
3066
+ getViewport: function() { return {from: this.display.showingFrom, to: this.display.showingTo};},
3067
+
3068
+ addWidget: function(pos, node, scroll, vert, horiz) {
3069
+ var display = this.display;
3070
+ pos = cursorCoords(this, clipPos(this.doc, pos));
3071
+ var top = pos.bottom, left = pos.left;
3072
+ node.style.position = "absolute";
3073
+ display.sizer.appendChild(node);
3074
+ if (vert == "over") {
3075
+ top = pos.top;
3076
+ } else if (vert == "above" || vert == "near") {
3077
+ var vspace = Math.max(display.wrapper.clientHeight, this.doc.height),
3078
+ hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth);
3079
+ // Default to positioning above (if specified and possible); otherwise default to positioning below
3080
+ if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight)
3081
+ top = pos.top - node.offsetHeight;
3082
+ else if (pos.bottom + node.offsetHeight <= vspace)
3083
+ top = pos.bottom;
3084
+ if (left + node.offsetWidth > hspace)
3085
+ left = hspace - node.offsetWidth;
3086
+ }
3087
+ node.style.top = top + "px";
3088
+ node.style.left = node.style.right = "";
3089
+ if (horiz == "right") {
3090
+ left = display.sizer.clientWidth - node.offsetWidth;
3091
+ node.style.right = "0px";
3092
+ } else {
3093
+ if (horiz == "left") left = 0;
3094
+ else if (horiz == "middle") left = (display.sizer.clientWidth - node.offsetWidth) / 2;
3095
+ node.style.left = left + "px";
3096
+ }
3097
+ if (scroll)
3098
+ scrollIntoView(this, left, top, left + node.offsetWidth, top + node.offsetHeight);
3099
+ },
3100
+
3101
+ triggerOnKeyDown: operation(null, onKeyDown),
3102
+
3103
+ execCommand: function(cmd) {return commands[cmd](this);},
3104
+
3105
+ findPosH: function(from, amount, unit, visually) {
3106
+ var dir = 1;
3107
+ if (amount < 0) { dir = -1; amount = -amount; }
3108
+ for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) {
3109
+ cur = findPosH(this.doc, cur, dir, unit, visually);
3110
+ if (cur.hitSide) break;
3111
+ }
3112
+ return cur;
3113
+ },
3114
+
3115
+ moveH: operation(null, function(dir, unit) {
3116
+ var sel = this.doc.sel, pos;
3117
+ if (sel.shift || sel.extend || posEq(sel.from, sel.to))
3118
+ pos = findPosH(this.doc, sel.head, dir, unit, this.options.rtlMoveVisually);
3119
+ else
3120
+ pos = dir < 0 ? sel.from : sel.to;
3121
+ extendSelection(this.doc, pos, pos, dir);
3122
+ }),
3123
+
3124
+ deleteH: operation(null, function(dir, unit) {
3125
+ var sel = this.doc.sel;
3126
+ if (!posEq(sel.from, sel.to)) replaceRange(this.doc, "", sel.from, sel.to, "+delete");
3127
+ else replaceRange(this.doc, "", sel.from, findPosH(this.doc, sel.head, dir, unit, false), "+delete");
3128
+ this.curOp.userSelChange = true;
3129
+ }),
3130
+
3131
+ findPosV: function(from, amount, unit, goalColumn) {
3132
+ var dir = 1, x = goalColumn;
3133
+ if (amount < 0) { dir = -1; amount = -amount; }
3134
+ for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) {
3135
+ var coords = cursorCoords(this, cur, "div");
3136
+ if (x == null) x = coords.left;
3137
+ else coords.left = x;
3138
+ cur = findPosV(this, coords, dir, unit);
3139
+ if (cur.hitSide) break;
3140
+ }
3141
+ return cur;
3142
+ },
3143
+
3144
+ moveV: operation(null, function(dir, unit) {
3145
+ var sel = this.doc.sel;
3146
+ var pos = cursorCoords(this, sel.head, "div");
3147
+ if (sel.goalColumn != null) pos.left = sel.goalColumn;
3148
+ var target = findPosV(this, pos, dir, unit);
3149
+
3150
+ if (unit == "page") addToScrollPos(this, 0, charCoords(this, target, "div").top - pos.top);
3151
+ extendSelection(this.doc, target, target, dir);
3152
+ sel.goalColumn = pos.left;
3153
+ }),
3154
+
3155
+ toggleOverwrite: function(value) {
3156
+ if (value != null && value == this.state.overwrite) return;
3157
+ if (this.state.overwrite = !this.state.overwrite)
3158
+ this.display.cursor.className += " CodeMirror-overwrite";
3159
+ else
3160
+ this.display.cursor.className = this.display.cursor.className.replace(" CodeMirror-overwrite", "");
3161
+ },
3162
+ hasFocus: function() { return this.state.focused; },
3163
+
3164
+ scrollTo: operation(null, function(x, y) {
3165
+ updateScrollPos(this, x, y);
3166
+ }),
3167
+ getScrollInfo: function() {
3168
+ var scroller = this.display.scroller, co = scrollerCutOff;
3169
+ return {left: scroller.scrollLeft, top: scroller.scrollTop,
3170
+ height: scroller.scrollHeight - co, width: scroller.scrollWidth - co,
3171
+ clientHeight: scroller.clientHeight - co, clientWidth: scroller.clientWidth - co};
3172
+ },
3173
+
3174
+ scrollIntoView: operation(null, function(pos, margin) {
3175
+ if (typeof pos == "number") pos = Pos(pos, 0);
3176
+ if (!margin) margin = 0;
3177
+ var coords = pos;
3178
+
3179
+ if (!pos || pos.line != null) {
3180
+ this.curOp.scrollToPos = pos ? clipPos(this.doc, pos) : this.doc.sel.head;
3181
+ this.curOp.scrollToPosMargin = margin;
3182
+ coords = cursorCoords(this, this.curOp.scrollToPos);
3183
+ }
3184
+ var sPos = calculateScrollPos(this, coords.left, coords.top - margin, coords.right, coords.bottom + margin);
3185
+ updateScrollPos(this, sPos.scrollLeft, sPos.scrollTop);
3186
+ }),
3187
+
3188
+ setSize: operation(null, function(width, height) {
3189
+ function interpret(val) {
3190
+ return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val;
3191
+ }
3192
+ if (width != null) this.display.wrapper.style.width = interpret(width);
3193
+ if (height != null) this.display.wrapper.style.height = interpret(height);
3194
+ if (this.options.lineWrapping)
3195
+ this.display.measureLineCache.length = this.display.measureLineCachePos = 0;
3196
+ this.curOp.forceUpdate = true;
3197
+ }),
3198
+
3199
+ operation: function(f){return runInOp(this, f);},
3200
+
3201
+ refresh: operation(null, function() {
3202
+ var badHeight = this.display.cachedTextHeight == null;
3203
+ clearCaches(this);
3204
+ updateScrollPos(this, this.doc.scrollLeft, this.doc.scrollTop);
3205
+ regChange(this);
3206
+ if (badHeight) estimateLineHeights(this);
3207
+ }),
3208
+
3209
+ swapDoc: operation(null, function(doc) {
3210
+ var old = this.doc;
3211
+ old.cm = null;
3212
+ attachDoc(this, doc);
3213
+ clearCaches(this);
3214
+ resetInput(this, true);
3215
+ updateScrollPos(this, doc.scrollLeft, doc.scrollTop);
3216
+ return old;
3217
+ }),
3218
+
3219
+ getInputField: function(){return this.display.input;},
3220
+ getWrapperElement: function(){return this.display.wrapper;},
3221
+ getScrollerElement: function(){return this.display.scroller;},
3222
+ getGutterElement: function(){return this.display.gutters;}
3223
+ };
3224
+ eventMixin(CodeMirror);
3225
+
3226
+ // OPTION DEFAULTS
3227
+
3228
+ var optionHandlers = CodeMirror.optionHandlers = {};
3229
+
3230
+ // The default configuration options.
3231
+ var defaults = CodeMirror.defaults = {};
3232
+
3233
+ function option(name, deflt, handle, notOnInit) {
3234
+ CodeMirror.defaults[name] = deflt;
3235
+ if (handle) optionHandlers[name] =
3236
+ notOnInit ? function(cm, val, old) {if (old != Init) handle(cm, val, old);} : handle;
3237
+ }
3238
+
3239
+ var Init = CodeMirror.Init = {toString: function(){return "CodeMirror.Init";}};
3240
+
3241
+ // These two are, on init, called from the constructor because they
3242
+ // have to be initialized before the editor can start at all.
3243
+ option("value", "", function(cm, val) {
3244
+ cm.setValue(val);
3245
+ }, true);
3246
+ option("mode", null, function(cm, val) {
3247
+ cm.doc.modeOption = val;
3248
+ loadMode(cm);
3249
+ }, true);
3250
+
3251
+ option("indentUnit", 2, loadMode, true);
3252
+ option("indentWithTabs", false);
3253
+ option("smartIndent", true);
3254
+ option("tabSize", 4, function(cm) {
3255
+ loadMode(cm);
3256
+ clearCaches(cm);
3257
+ regChange(cm);
3258
+ }, true);
3259
+ option("electricChars", true);
3260
+ option("rtlMoveVisually", !windows);
3261
+
3262
+ option("theme", "default", function(cm) {
3263
+ themeChanged(cm);
3264
+ guttersChanged(cm);
3265
+ }, true);
3266
+ option("keyMap", "default", keyMapChanged);
3267
+ option("extraKeys", null);
3268
+
3269
+ option("onKeyEvent", null);
3270
+ option("onDragEvent", null);
3271
+
3272
+ option("lineWrapping", false, wrappingChanged, true);
3273
+ option("gutters", [], function(cm) {
3274
+ setGuttersForLineNumbers(cm.options);
3275
+ guttersChanged(cm);
3276
+ }, true);
3277
+ option("fixedGutter", true, function(cm, val) {
3278
+ cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0";
3279
+ cm.refresh();
3280
+ }, true);
3281
+ option("coverGutterNextToScrollbar", false, updateScrollbars, true);
3282
+ option("lineNumbers", false, function(cm) {
3283
+ setGuttersForLineNumbers(cm.options);
3284
+ guttersChanged(cm);
3285
+ }, true);
3286
+ option("firstLineNumber", 1, guttersChanged, true);
3287
+ option("lineNumberFormatter", function(integer) {return integer;}, guttersChanged, true);
3288
+ option("showCursorWhenSelecting", false, updateSelection, true);
3289
+
3290
+ option("readOnly", false, function(cm, val) {
3291
+ if (val == "nocursor") {onBlur(cm); cm.display.input.blur();}
3292
+ else if (!val) resetInput(cm, true);
3293
+ });
3294
+ option("dragDrop", true);
3295
+
3296
+ option("cursorBlinkRate", 530);
3297
+ option("cursorScrollMargin", 0);
3298
+ option("cursorHeight", 1);
3299
+ option("workTime", 100);
3300
+ option("workDelay", 100);
3301
+ option("flattenSpans", true);
3302
+ option("pollInterval", 100);
3303
+ option("undoDepth", 40, function(cm, val){cm.doc.history.undoDepth = val;});
3304
+ option("historyEventDelay", 500);
3305
+ option("viewportMargin", 10, function(cm){cm.refresh();}, true);
3306
+ option("maxHighlightLength", 10000, function(cm){loadMode(cm); cm.refresh();}, true);
3307
+ option("crudeMeasuringFrom", 10000);
3308
+ option("moveInputWithCursor", true, function(cm, val) {
3309
+ if (!val) cm.display.inputDiv.style.top = cm.display.inputDiv.style.left = 0;
3310
+ });
3311
+
3312
+ option("tabindex", null, function(cm, val) {
3313
+ cm.display.input.tabIndex = val || "";
3314
+ });
3315
+ option("autofocus", null);
3316
+
3317
+ // MODE DEFINITION AND QUERYING
3318
+
3319
+ // Known modes, by name and by MIME
3320
+ var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {};
3321
+
3322
+ CodeMirror.defineMode = function(name, mode) {
3323
+ if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name;
3324
+ if (arguments.length > 2) {
3325
+ mode.dependencies = [];
3326
+ for (var i = 2; i < arguments.length; ++i) mode.dependencies.push(arguments[i]);
3327
+ }
3328
+ modes[name] = mode;
3329
+ };
3330
+
3331
+ CodeMirror.defineMIME = function(mime, spec) {
3332
+ mimeModes[mime] = spec;
3333
+ };
3334
+
3335
+ CodeMirror.resolveMode = function(spec) {
3336
+ if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) {
3337
+ spec = mimeModes[spec];
3338
+ } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) {
3339
+ var found = mimeModes[spec.name];
3340
+ spec = createObj(found, spec);
3341
+ spec.name = found.name;
3342
+ } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) {
3343
+ return CodeMirror.resolveMode("application/xml");
3344
+ }
3345
+ if (typeof spec == "string") return {name: spec};
3346
+ else return spec || {name: "null"};
3347
+ };
3348
+
3349
+ CodeMirror.getMode = function(options, spec) {
3350
+ var spec = CodeMirror.resolveMode(spec);
3351
+ var mfactory = modes[spec.name];
3352
+ if (!mfactory) return CodeMirror.getMode(options, "text/plain");
3353
+ var modeObj = mfactory(options, spec);
3354
+ if (modeExtensions.hasOwnProperty(spec.name)) {
3355
+ var exts = modeExtensions[spec.name];
3356
+ for (var prop in exts) {
3357
+ if (!exts.hasOwnProperty(prop)) continue;
3358
+ if (modeObj.hasOwnProperty(prop)) modeObj["_" + prop] = modeObj[prop];
3359
+ modeObj[prop] = exts[prop];
3360
+ }
3361
+ }
3362
+ modeObj.name = spec.name;
3363
+
3364
+ return modeObj;
3365
+ };
3366
+
3367
+ CodeMirror.defineMode("null", function() {
3368
+ return {token: function(stream) {stream.skipToEnd();}};
3369
+ });
3370
+ CodeMirror.defineMIME("text/plain", "null");
3371
+
3372
+ var modeExtensions = CodeMirror.modeExtensions = {};
3373
+ CodeMirror.extendMode = function(mode, properties) {
3374
+ var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {});
3375
+ copyObj(properties, exts);
3376
+ };
3377
+
3378
+ // EXTENSIONS
3379
+
3380
+ CodeMirror.defineExtension = function(name, func) {
3381
+ CodeMirror.prototype[name] = func;
3382
+ };
3383
+ CodeMirror.defineDocExtension = function(name, func) {
3384
+ Doc.prototype[name] = func;
3385
+ };
3386
+ CodeMirror.defineOption = option;
3387
+
3388
+ var initHooks = [];
3389
+ CodeMirror.defineInitHook = function(f) {initHooks.push(f);};
3390
+
3391
+ var helpers = CodeMirror.helpers = {};
3392
+ CodeMirror.registerHelper = function(type, name, value) {
3393
+ if (!helpers.hasOwnProperty(type)) helpers[type] = CodeMirror[type] = {};
3394
+ helpers[type][name] = value;
3395
+ };
3396
+
3397
+ // UTILITIES
3398
+
3399
+ CodeMirror.isWordChar = isWordChar;
3400
+
3401
+ // MODE STATE HANDLING
3402
+
3403
+ // Utility functions for working with state. Exported because modes
3404
+ // sometimes need to do this.
3405
+ function copyState(mode, state) {
3406
+ if (state === true) return state;
3407
+ if (mode.copyState) return mode.copyState(state);
3408
+ var nstate = {};
3409
+ for (var n in state) {
3410
+ var val = state[n];
3411
+ if (val instanceof Array) val = val.concat([]);
3412
+ nstate[n] = val;
3413
+ }
3414
+ return nstate;
3415
+ }
3416
+ CodeMirror.copyState = copyState;
3417
+
3418
+ function startState(mode, a1, a2) {
3419
+ return mode.startState ? mode.startState(a1, a2) : true;
3420
+ }
3421
+ CodeMirror.startState = startState;
3422
+
3423
+ CodeMirror.innerMode = function(mode, state) {
3424
+ while (mode.innerMode) {
3425
+ var info = mode.innerMode(state);
3426
+ if (!info || info.mode == mode) break;
3427
+ state = info.state;
3428
+ mode = info.mode;
3429
+ }
3430
+ return info || {mode: mode, state: state};
3431
+ };
3432
+
3433
+ // STANDARD COMMANDS
3434
+
3435
+ var commands = CodeMirror.commands = {
3436
+ selectAll: function(cm) {cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()));},
3437
+ killLine: function(cm) {
3438
+ var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
3439
+ if (!sel && cm.getLine(from.line).length == from.ch)
3440
+ cm.replaceRange("", from, Pos(from.line + 1, 0), "+delete");
3441
+ else cm.replaceRange("", from, sel ? to : Pos(from.line), "+delete");
3442
+ },
3443
+ deleteLine: function(cm) {
3444
+ var l = cm.getCursor().line;
3445
+ cm.replaceRange("", Pos(l, 0), Pos(l), "+delete");
3446
+ },
3447
+ delLineLeft: function(cm) {
3448
+ var cur = cm.getCursor();
3449
+ cm.replaceRange("", Pos(cur.line, 0), cur, "+delete");
3450
+ },
3451
+ undo: function(cm) {cm.undo();},
3452
+ redo: function(cm) {cm.redo();},
3453
+ goDocStart: function(cm) {cm.extendSelection(Pos(cm.firstLine(), 0));},
3454
+ goDocEnd: function(cm) {cm.extendSelection(Pos(cm.lastLine()));},
3455
+ goLineStart: function(cm) {
3456
+ cm.extendSelection(lineStart(cm, cm.getCursor().line));
3457
+ },
3458
+ goLineStartSmart: function(cm) {
3459
+ var cur = cm.getCursor(), start = lineStart(cm, cur.line);
3460
+ var line = cm.getLineHandle(start.line);
3461
+ var order = getOrder(line);
3462
+ if (!order || order[0].level == 0) {
3463
+ var firstNonWS = Math.max(0, line.text.search(/\S/));
3464
+ var inWS = cur.line == start.line && cur.ch <= firstNonWS && cur.ch;
3465
+ cm.extendSelection(Pos(start.line, inWS ? 0 : firstNonWS));
3466
+ } else cm.extendSelection(start);
3467
+ },
3468
+ goLineEnd: function(cm) {
3469
+ cm.extendSelection(lineEnd(cm, cm.getCursor().line));
3470
+ },
3471
+ goLineRight: function(cm) {
3472
+ var top = cm.charCoords(cm.getCursor(), "div").top + 5;
3473
+ cm.extendSelection(cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div"));
3474
+ },
3475
+ goLineLeft: function(cm) {
3476
+ var top = cm.charCoords(cm.getCursor(), "div").top + 5;
3477
+ cm.extendSelection(cm.coordsChar({left: 0, top: top}, "div"));
3478
+ },
3479
+ goLineUp: function(cm) {cm.moveV(-1, "line");},
3480
+ goLineDown: function(cm) {cm.moveV(1, "line");},
3481
+ goPageUp: function(cm) {cm.moveV(-1, "page");},
3482
+ goPageDown: function(cm) {cm.moveV(1, "page");},
3483
+ goCharLeft: function(cm) {cm.moveH(-1, "char");},
3484
+ goCharRight: function(cm) {cm.moveH(1, "char");},
3485
+ goColumnLeft: function(cm) {cm.moveH(-1, "column");},
3486
+ goColumnRight: function(cm) {cm.moveH(1, "column");},
3487
+ goWordLeft: function(cm) {cm.moveH(-1, "word");},
3488
+ goGroupRight: function(cm) {cm.moveH(1, "group");},
3489
+ goGroupLeft: function(cm) {cm.moveH(-1, "group");},
3490
+ goWordRight: function(cm) {cm.moveH(1, "word");},
3491
+ delCharBefore: function(cm) {cm.deleteH(-1, "char");},
3492
+ delCharAfter: function(cm) {cm.deleteH(1, "char");},
3493
+ delWordBefore: function(cm) {cm.deleteH(-1, "word");},
3494
+ delWordAfter: function(cm) {cm.deleteH(1, "word");},
3495
+ delGroupBefore: function(cm) {cm.deleteH(-1, "group");},
3496
+ delGroupAfter: function(cm) {cm.deleteH(1, "group");},
3497
+ indentAuto: function(cm) {cm.indentSelection("smart");},
3498
+ indentMore: function(cm) {cm.indentSelection("add");},
3499
+ indentLess: function(cm) {cm.indentSelection("subtract");},
3500
+ insertTab: function(cm) {cm.replaceSelection("\t", "end", "+input");},
3501
+ defaultTab: function(cm) {
3502
+ if (cm.somethingSelected()) cm.indentSelection("add");
3503
+ else cm.replaceSelection("\t", "end", "+input");
3504
+ },
3505
+ transposeChars: function(cm) {
3506
+ var cur = cm.getCursor(), line = cm.getLine(cur.line);
3507
+ if (cur.ch > 0 && cur.ch < line.length - 1)
3508
+ cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1),
3509
+ Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1));
3510
+ },
3511
+ newlineAndIndent: function(cm) {
3512
+ operation(cm, function() {
3513
+ cm.replaceSelection("\n", "end", "+input");
3514
+ cm.indentLine(cm.getCursor().line, null, true);
3515
+ })();
3516
+ },
3517
+ toggleOverwrite: function(cm) {cm.toggleOverwrite();}
3518
+ };
3519
+
3520
+ // STANDARD KEYMAPS
3521
+
3522
+ var keyMap = CodeMirror.keyMap = {};
3523
+ keyMap.basic = {
3524
+ "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown",
3525
+ "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown",
3526
+ "Delete": "delCharAfter", "Backspace": "delCharBefore", "Tab": "defaultTab", "Shift-Tab": "indentAuto",
3527
+ "Enter": "newlineAndIndent", "Insert": "toggleOverwrite"
3528
+ };
3529
+ // Note that the save and find-related commands aren't defined by
3530
+ // default. Unknown commands are simply ignored.
3531
+ keyMap.pcDefault = {
3532
+ "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo",
3533
+ "Ctrl-Home": "goDocStart", "Alt-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd",
3534
+ "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd",
3535
+ "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find",
3536
+ "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll",
3537
+ "Ctrl-[": "indentLess", "Ctrl-]": "indentMore",
3538
+ fallthrough: "basic"
3539
+ };
3540
+ keyMap.macDefault = {
3541
+ "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo",
3542
+ "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft",
3543
+ "Alt-Right": "goGroupRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delGroupBefore",
3544
+ "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find",
3545
+ "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll",
3546
+ "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delLineLeft",
3547
+ fallthrough: ["basic", "emacsy"]
3548
+ };
3549
+ keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault;
3550
+ keyMap.emacsy = {
3551
+ "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown",
3552
+ "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd",
3553
+ "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore",
3554
+ "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars"
3555
+ };
3556
+
3557
+ // KEYMAP DISPATCH
3558
+
3559
+ function getKeyMap(val) {
3560
+ if (typeof val == "string") return keyMap[val];
3561
+ else return val;
3562
+ }
3563
+
3564
+ function lookupKey(name, maps, handle) {
3565
+ function lookup(map) {
3566
+ map = getKeyMap(map);
3567
+ var found = map[name];
3568
+ if (found === false) return "stop";
3569
+ if (found != null && handle(found)) return true;
3570
+ if (map.nofallthrough) return "stop";
3571
+
3572
+ var fallthrough = map.fallthrough;
3573
+ if (fallthrough == null) return false;
3574
+ if (Object.prototype.toString.call(fallthrough) != "[object Array]")
3575
+ return lookup(fallthrough);
3576
+ for (var i = 0, e = fallthrough.length; i < e; ++i) {
3577
+ var done = lookup(fallthrough[i]);
3578
+ if (done) return done;
3579
+ }
3580
+ return false;
3581
+ }
3582
+
3583
+ for (var i = 0; i < maps.length; ++i) {
3584
+ var done = lookup(maps[i]);
3585
+ if (done) return done != "stop";
3586
+ }
3587
+ }
3588
+ function isModifierKey(event) {
3589
+ var name = keyNames[event.keyCode];
3590
+ return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod";
3591
+ }
3592
+ function keyName(event, noShift) {
3593
+ if (opera && event.keyCode == 34 && event["char"]) return false;
3594
+ var name = keyNames[event.keyCode];
3595
+ if (name == null || event.altGraphKey) return false;
3596
+ if (event.altKey) name = "Alt-" + name;
3597
+ if (flipCtrlCmd ? event.metaKey : event.ctrlKey) name = "Ctrl-" + name;
3598
+ if (flipCtrlCmd ? event.ctrlKey : event.metaKey) name = "Cmd-" + name;
3599
+ if (!noShift && event.shiftKey) name = "Shift-" + name;
3600
+ return name;
3601
+ }
3602
+ CodeMirror.lookupKey = lookupKey;
3603
+ CodeMirror.isModifierKey = isModifierKey;
3604
+ CodeMirror.keyName = keyName;
3605
+
3606
+ // FROMTEXTAREA
3607
+
3608
+ CodeMirror.fromTextArea = function(textarea, options) {
3609
+ if (!options) options = {};
3610
+ options.value = textarea.value;
3611
+ if (!options.tabindex && textarea.tabindex)
3612
+ options.tabindex = textarea.tabindex;
3613
+ if (!options.placeholder && textarea.placeholder)
3614
+ options.placeholder = textarea.placeholder;
3615
+ // Set autofocus to true if this textarea is focused, or if it has
3616
+ // autofocus and no other element is focused.
3617
+ if (options.autofocus == null) {
3618
+ var hasFocus = document.body;
3619
+ // doc.activeElement occasionally throws on IE
3620
+ try { hasFocus = document.activeElement; } catch(e) {}
3621
+ options.autofocus = hasFocus == textarea ||
3622
+ textarea.getAttribute("autofocus") != null && hasFocus == document.body;
3623
+ }
3624
+
3625
+ function save() {textarea.value = cm.getValue();}
3626
+ if (textarea.form) {
3627
+ on(textarea.form, "submit", save);
3628
+ // Deplorable hack to make the submit method do the right thing.
3629
+ if (!options.leaveSubmitMethodAlone) {
3630
+ var form = textarea.form, realSubmit = form.submit;
3631
+ try {
3632
+ var wrappedSubmit = form.submit = function() {
3633
+ save();
3634
+ form.submit = realSubmit;
3635
+ form.submit();
3636
+ form.submit = wrappedSubmit;
3637
+ };
3638
+ } catch(e) {}
3639
+ }
3640
+ }
3641
+
3642
+ textarea.style.display = "none";
3643
+ var cm = CodeMirror(function(node) {
3644
+ textarea.parentNode.insertBefore(node, textarea.nextSibling);
3645
+ }, options);
3646
+ cm.save = save;
3647
+ cm.getTextArea = function() { return textarea; };
3648
+ cm.toTextArea = function() {
3649
+ save();
3650
+ textarea.parentNode.removeChild(cm.getWrapperElement());
3651
+ textarea.style.display = "";
3652
+ if (textarea.form) {
3653
+ off(textarea.form, "submit", save);
3654
+ if (typeof textarea.form.submit == "function")
3655
+ textarea.form.submit = realSubmit;
3656
+ }
3657
+ };
3658
+ return cm;
3659
+ };
3660
+
3661
+ // STRING STREAM
3662
+
3663
+ // Fed to the mode parsers, provides helper functions to make
3664
+ // parsers more succinct.
3665
+
3666
+ // The character stream used by a mode's parser.
3667
+ function StringStream(string, tabSize) {
3668
+ this.pos = this.start = 0;
3669
+ this.string = string;
3670
+ this.tabSize = tabSize || 8;
3671
+ this.lastColumnPos = this.lastColumnValue = 0;
3672
+ }
3673
+
3674
+ StringStream.prototype = {
3675
+ eol: function() {return this.pos >= this.string.length;},
3676
+ sol: function() {return this.pos == 0;},
3677
+ peek: function() {return this.string.charAt(this.pos) || undefined;},
3678
+ next: function() {
3679
+ if (this.pos < this.string.length)
3680
+ return this.string.charAt(this.pos++);
3681
+ },
3682
+ eat: function(match) {
3683
+ var ch = this.string.charAt(this.pos);
3684
+ if (typeof match == "string") var ok = ch == match;
3685
+ else var ok = ch && (match.test ? match.test(ch) : match(ch));
3686
+ if (ok) {++this.pos; return ch;}
3687
+ },
3688
+ eatWhile: function(match) {
3689
+ var start = this.pos;
3690
+ while (this.eat(match)){}
3691
+ return this.pos > start;
3692
+ },
3693
+ eatSpace: function() {
3694
+ var start = this.pos;
3695
+ while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos;
3696
+ return this.pos > start;
3697
+ },
3698
+ skipToEnd: function() {this.pos = this.string.length;},
3699
+ skipTo: function(ch) {
3700
+ var found = this.string.indexOf(ch, this.pos);
3701
+ if (found > -1) {this.pos = found; return true;}
3702
+ },
3703
+ backUp: function(n) {this.pos -= n;},
3704
+ column: function() {
3705
+ if (this.lastColumnPos < this.start) {
3706
+ this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue);
3707
+ this.lastColumnPos = this.start;
3708
+ }
3709
+ return this.lastColumnValue;
3710
+ },
3711
+ indentation: function() {return countColumn(this.string, null, this.tabSize);},
3712
+ match: function(pattern, consume, caseInsensitive) {
3713
+ if (typeof pattern == "string") {
3714
+ var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;};
3715
+ var substr = this.string.substr(this.pos, pattern.length);
3716
+ if (cased(substr) == cased(pattern)) {
3717
+ if (consume !== false) this.pos += pattern.length;
3718
+ return true;
3719
+ }
3720
+ } else {
3721
+ var match = this.string.slice(this.pos).match(pattern);
3722
+ if (match && match.index > 0) return null;
3723
+ if (match && consume !== false) this.pos += match[0].length;
3724
+ return match;
3725
+ }
3726
+ },
3727
+ current: function(){return this.string.slice(this.start, this.pos);}
3728
+ };
3729
+ CodeMirror.StringStream = StringStream;
3730
+
3731
+ // TEXTMARKERS
3732
+
3733
+ function TextMarker(doc, type) {
3734
+ this.lines = [];
3735
+ this.type = type;
3736
+ this.doc = doc;
3737
+ }
3738
+ CodeMirror.TextMarker = TextMarker;
3739
+ eventMixin(TextMarker);
3740
+
3741
+ TextMarker.prototype.clear = function() {
3742
+ if (this.explicitlyCleared) return;
3743
+ var cm = this.doc.cm, withOp = cm && !cm.curOp;
3744
+ if (withOp) startOperation(cm);
3745
+ if (hasHandler(this, "clear")) {
3746
+ var found = this.find();
3747
+ if (found) signalLater(this, "clear", found.from, found.to);
3748
+ }
3749
+ var min = null, max = null;
3750
+ for (var i = 0; i < this.lines.length; ++i) {
3751
+ var line = this.lines[i];
3752
+ var span = getMarkedSpanFor(line.markedSpans, this);
3753
+ if (span.to != null) max = lineNo(line);
3754
+ line.markedSpans = removeMarkedSpan(line.markedSpans, span);
3755
+ if (span.from != null)
3756
+ min = lineNo(line);
3757
+ else if (this.collapsed && !lineIsHidden(this.doc, line) && cm)
3758
+ updateLineHeight(line, textHeight(cm.display));
3759
+ }
3760
+ if (cm && this.collapsed && !cm.options.lineWrapping) for (var i = 0; i < this.lines.length; ++i) {
3761
+ var visual = visualLine(cm.doc, this.lines[i]), len = lineLength(cm.doc, visual);
3762
+ if (len > cm.display.maxLineLength) {
3763
+ cm.display.maxLine = visual;
3764
+ cm.display.maxLineLength = len;
3765
+ cm.display.maxLineChanged = true;
3766
+ }
3767
+ }
3768
+
3769
+ if (min != null && cm) regChange(cm, min, max + 1);
3770
+ this.lines.length = 0;
3771
+ this.explicitlyCleared = true;
3772
+ if (this.atomic && this.doc.cantEdit) {
3773
+ this.doc.cantEdit = false;
3774
+ if (cm) reCheckSelection(cm);
3775
+ }
3776
+ if (withOp) endOperation(cm);
3777
+ };
3778
+
3779
+ TextMarker.prototype.find = function() {
3780
+ var from, to;
3781
+ for (var i = 0; i < this.lines.length; ++i) {
3782
+ var line = this.lines[i];
3783
+ var span = getMarkedSpanFor(line.markedSpans, this);
3784
+ if (span.from != null || span.to != null) {
3785
+ var found = lineNo(line);
3786
+ if (span.from != null) from = Pos(found, span.from);
3787
+ if (span.to != null) to = Pos(found, span.to);
3788
+ }
3789
+ }
3790
+ if (this.type == "bookmark") return from;
3791
+ return from && {from: from, to: to};
3792
+ };
3793
+
3794
+ TextMarker.prototype.changed = function() {
3795
+ var pos = this.find(), cm = this.doc.cm;
3796
+ if (!pos || !cm) return;
3797
+ if (this.type != "bookmark") pos = pos.from;
3798
+ var line = getLine(this.doc, pos.line);
3799
+ clearCachedMeasurement(cm, line);
3800
+ if (pos.line >= cm.display.showingFrom && pos.line < cm.display.showingTo) {
3801
+ for (var node = cm.display.lineDiv.firstChild; node; node = node.nextSibling) if (node.lineObj == line) {
3802
+ if (node.offsetHeight != line.height) updateLineHeight(line, node.offsetHeight);
3803
+ break;
3804
+ }
3805
+ runInOp(cm, function() {
3806
+ cm.curOp.selectionChanged = cm.curOp.forceUpdate = cm.curOp.updateMaxLine = true;
3807
+ });
3808
+ }
3809
+ };
3810
+
3811
+ TextMarker.prototype.attachLine = function(line) {
3812
+ if (!this.lines.length && this.doc.cm) {
3813
+ var op = this.doc.cm.curOp;
3814
+ if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1)
3815
+ (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this);
3816
+ }
3817
+ this.lines.push(line);
3818
+ };
3819
+ TextMarker.prototype.detachLine = function(line) {
3820
+ this.lines.splice(indexOf(this.lines, line), 1);
3821
+ if (!this.lines.length && this.doc.cm) {
3822
+ var op = this.doc.cm.curOp;
3823
+ (op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this);
3824
+ }
3825
+ };
3826
+
3827
+ function markText(doc, from, to, options, type) {
3828
+ if (options && options.shared) return markTextShared(doc, from, to, options, type);
3829
+ if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, to, options, type);
3830
+
3831
+ var marker = new TextMarker(doc, type);
3832
+ if (type == "range" && !posLess(from, to)) return marker;
3833
+ if (options) copyObj(options, marker);
3834
+ if (marker.replacedWith) {
3835
+ marker.collapsed = true;
3836
+ marker.replacedWith = elt("span", [marker.replacedWith], "CodeMirror-widget");
3837
+ if (!options.handleMouseEvents) marker.replacedWith.ignoreEvents = true;
3838
+ }
3839
+ if (marker.collapsed) sawCollapsedSpans = true;
3840
+
3841
+ if (marker.addToHistory)
3842
+ addToHistory(doc, {from: from, to: to, origin: "markText"},
3843
+ {head: doc.sel.head, anchor: doc.sel.anchor}, NaN);
3844
+
3845
+ var curLine = from.line, size = 0, collapsedAtStart, collapsedAtEnd, cm = doc.cm, updateMaxLine;
3846
+ doc.iter(curLine, to.line + 1, function(line) {
3847
+ if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(doc, line) == cm.display.maxLine)
3848
+ updateMaxLine = true;
3849
+ var span = {from: null, to: null, marker: marker};
3850
+ size += line.text.length;
3851
+ if (curLine == from.line) {span.from = from.ch; size -= from.ch;}
3852
+ if (curLine == to.line) {span.to = to.ch; size -= line.text.length - to.ch;}
3853
+ if (marker.collapsed) {
3854
+ if (curLine == to.line) collapsedAtEnd = collapsedSpanAt(line, to.ch);
3855
+ if (curLine == from.line) collapsedAtStart = collapsedSpanAt(line, from.ch);
3856
+ else updateLineHeight(line, 0);
3857
+ }
3858
+ addMarkedSpan(line, span);
3859
+ ++curLine;
3860
+ });
3861
+ if (marker.collapsed) doc.iter(from.line, to.line + 1, function(line) {
3862
+ if (lineIsHidden(doc, line)) updateLineHeight(line, 0);
3863
+ });
3864
+
3865
+ if (marker.clearOnEnter) on(marker, "beforeCursorEnter", function() { marker.clear(); });
3866
+
3867
+ if (marker.readOnly) {
3868
+ sawReadOnlySpans = true;
3869
+ if (doc.history.done.length || doc.history.undone.length)
3870
+ doc.clearHistory();
3871
+ }
3872
+ if (marker.collapsed) {
3873
+ if (collapsedAtStart != collapsedAtEnd)
3874
+ throw new Error("Inserting collapsed marker overlapping an existing one");
3875
+ marker.size = size;
3876
+ marker.atomic = true;
3877
+ }
3878
+ if (cm) {
3879
+ if (updateMaxLine) cm.curOp.updateMaxLine = true;
3880
+ if (marker.className || marker.title || marker.startStyle || marker.endStyle || marker.collapsed)
3881
+ regChange(cm, from.line, to.line + 1);
3882
+ if (marker.atomic) reCheckSelection(cm);
3883
+ }
3884
+ return marker;
3885
+ }
3886
+
3887
+ // SHARED TEXTMARKERS
3888
+
3889
+ function SharedTextMarker(markers, primary) {
3890
+ this.markers = markers;
3891
+ this.primary = primary;
3892
+ for (var i = 0, me = this; i < markers.length; ++i) {
3893
+ markers[i].parent = this;
3894
+ on(markers[i], "clear", function(){me.clear();});
3895
+ }
3896
+ }
3897
+ CodeMirror.SharedTextMarker = SharedTextMarker;
3898
+ eventMixin(SharedTextMarker);
3899
+
3900
+ SharedTextMarker.prototype.clear = function() {
3901
+ if (this.explicitlyCleared) return;
3902
+ this.explicitlyCleared = true;
3903
+ for (var i = 0; i < this.markers.length; ++i)
3904
+ this.markers[i].clear();
3905
+ signalLater(this, "clear");
3906
+ };
3907
+ SharedTextMarker.prototype.find = function() {
3908
+ return this.primary.find();
3909
+ };
3910
+
3911
+ function markTextShared(doc, from, to, options, type) {
3912
+ options = copyObj(options);
3913
+ options.shared = false;
3914
+ var markers = [markText(doc, from, to, options, type)], primary = markers[0];
3915
+ var widget = options.replacedWith;
3916
+ linkedDocs(doc, function(doc) {
3917
+ if (widget) options.replacedWith = widget.cloneNode(true);
3918
+ markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type));
3919
+ for (var i = 0; i < doc.linked.length; ++i)
3920
+ if (doc.linked[i].isParent) return;
3921
+ primary = lst(markers);
3922
+ });
3923
+ return new SharedTextMarker(markers, primary);
3924
+ }
3925
+
3926
+ // TEXTMARKER SPANS
3927
+
3928
+ function getMarkedSpanFor(spans, marker) {
3929
+ if (spans) for (var i = 0; i < spans.length; ++i) {
3930
+ var span = spans[i];
3931
+ if (span.marker == marker) return span;
3932
+ }
3933
+ }
3934
+ function removeMarkedSpan(spans, span) {
3935
+ for (var r, i = 0; i < spans.length; ++i)
3936
+ if (spans[i] != span) (r || (r = [])).push(spans[i]);
3937
+ return r;
3938
+ }
3939
+ function addMarkedSpan(line, span) {
3940
+ line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span];
3941
+ span.marker.attachLine(line);
3942
+ }
3943
+
3944
+ function markedSpansBefore(old, startCh, isInsert) {
3945
+ if (old) for (var i = 0, nw; i < old.length; ++i) {
3946
+ var span = old[i], marker = span.marker;
3947
+ var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh);
3948
+ if (startsBefore || marker.type == "bookmark" && span.from == startCh && (!isInsert || !span.marker.insertLeft)) {
3949
+ var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh);
3950
+ (nw || (nw = [])).push({from: span.from,
3951
+ to: endsAfter ? null : span.to,
3952
+ marker: marker});
3953
+ }
3954
+ }
3955
+ return nw;
3956
+ }
3957
+
3958
+ function markedSpansAfter(old, endCh, isInsert) {
3959
+ if (old) for (var i = 0, nw; i < old.length; ++i) {
3960
+ var span = old[i], marker = span.marker;
3961
+ var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh);
3962
+ if (endsAfter || marker.type == "bookmark" && span.from == endCh && (!isInsert || span.marker.insertLeft)) {
3963
+ var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh);
3964
+ (nw || (nw = [])).push({from: startsBefore ? null : span.from - endCh,
3965
+ to: span.to == null ? null : span.to - endCh,
3966
+ marker: marker});
3967
+ }
3968
+ }
3969
+ return nw;
3970
+ }
3971
+
3972
+ function stretchSpansOverChange(doc, change) {
3973
+ var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans;
3974
+ var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans;
3975
+ if (!oldFirst && !oldLast) return null;
3976
+
3977
+ var startCh = change.from.ch, endCh = change.to.ch, isInsert = posEq(change.from, change.to);
3978
+ // Get the spans that 'stick out' on both sides
3979
+ var first = markedSpansBefore(oldFirst, startCh, isInsert);
3980
+ var last = markedSpansAfter(oldLast, endCh, isInsert);
3981
+
3982
+ // Next, merge those two ends
3983
+ var sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0);
3984
+ if (first) {
3985
+ // Fix up .to properties of first
3986
+ for (var i = 0; i < first.length; ++i) {
3987
+ var span = first[i];
3988
+ if (span.to == null) {
3989
+ var found = getMarkedSpanFor(last, span.marker);
3990
+ if (!found) span.to = startCh;
3991
+ else if (sameLine) span.to = found.to == null ? null : found.to + offset;
3992
+ }
3993
+ }
3994
+ }
3995
+ if (last) {
3996
+ // Fix up .from in last (or move them into first in case of sameLine)
3997
+ for (var i = 0; i < last.length; ++i) {
3998
+ var span = last[i];
3999
+ if (span.to != null) span.to += offset;
4000
+ if (span.from == null) {
4001
+ var found = getMarkedSpanFor(first, span.marker);
4002
+ if (!found) {
4003
+ span.from = offset;
4004
+ if (sameLine) (first || (first = [])).push(span);
4005
+ }
4006
+ } else {
4007
+ span.from += offset;
4008
+ if (sameLine) (first || (first = [])).push(span);
4009
+ }
4010
+ }
4011
+ }
4012
+ if (sameLine && first) {
4013
+ // Make sure we didn't create any zero-length spans
4014
+ for (var i = 0; i < first.length; ++i)
4015
+ if (first[i].from != null && first[i].from == first[i].to && first[i].marker.type != "bookmark")
4016
+ first.splice(i--, 1);
4017
+ if (!first.length) first = null;
4018
+ }
4019
+
4020
+ var newMarkers = [first];
4021
+ if (!sameLine) {
4022
+ // Fill gap with whole-line-spans
4023
+ var gap = change.text.length - 2, gapMarkers;
4024
+ if (gap > 0 && first)
4025
+ for (var i = 0; i < first.length; ++i)
4026
+ if (first[i].to == null)
4027
+ (gapMarkers || (gapMarkers = [])).push({from: null, to: null, marker: first[i].marker});
4028
+ for (var i = 0; i < gap; ++i)
4029
+ newMarkers.push(gapMarkers);
4030
+ newMarkers.push(last);
4031
+ }
4032
+ return newMarkers;
4033
+ }
4034
+
4035
+ function mergeOldSpans(doc, change) {
4036
+ var old = getOldSpans(doc, change);
4037
+ var stretched = stretchSpansOverChange(doc, change);
4038
+ if (!old) return stretched;
4039
+ if (!stretched) return old;
4040
+
4041
+ for (var i = 0; i < old.length; ++i) {
4042
+ var oldCur = old[i], stretchCur = stretched[i];
4043
+ if (oldCur && stretchCur) {
4044
+ spans: for (var j = 0; j < stretchCur.length; ++j) {
4045
+ var span = stretchCur[j];
4046
+ for (var k = 0; k < oldCur.length; ++k)
4047
+ if (oldCur[k].marker == span.marker) continue spans;
4048
+ oldCur.push(span);
4049
+ }
4050
+ } else if (stretchCur) {
4051
+ old[i] = stretchCur;
4052
+ }
4053
+ }
4054
+ return old;
4055
+ }
4056
+
4057
+ function removeReadOnlyRanges(doc, from, to) {
4058
+ var markers = null;
4059
+ doc.iter(from.line, to.line + 1, function(line) {
4060
+ if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) {
4061
+ var mark = line.markedSpans[i].marker;
4062
+ if (mark.readOnly && (!markers || indexOf(markers, mark) == -1))
4063
+ (markers || (markers = [])).push(mark);
4064
+ }
4065
+ });
4066
+ if (!markers) return null;
4067
+ var parts = [{from: from, to: to}];
4068
+ for (var i = 0; i < markers.length; ++i) {
4069
+ var mk = markers[i], m = mk.find();
4070
+ for (var j = 0; j < parts.length; ++j) {
4071
+ var p = parts[j];
4072
+ if (posLess(p.to, m.from) || posLess(m.to, p.from)) continue;
4073
+ var newParts = [j, 1];
4074
+ if (posLess(p.from, m.from) || !mk.inclusiveLeft && posEq(p.from, m.from))
4075
+ newParts.push({from: p.from, to: m.from});
4076
+ if (posLess(m.to, p.to) || !mk.inclusiveRight && posEq(p.to, m.to))
4077
+ newParts.push({from: m.to, to: p.to});
4078
+ parts.splice.apply(parts, newParts);
4079
+ j += newParts.length - 1;
4080
+ }
4081
+ }
4082
+ return parts;
4083
+ }
4084
+
4085
+ function collapsedSpanAt(line, ch) {
4086
+ var sps = sawCollapsedSpans && line.markedSpans, found;
4087
+ if (sps) for (var sp, i = 0; i < sps.length; ++i) {
4088
+ sp = sps[i];
4089
+ if (!sp.marker.collapsed) continue;
4090
+ if ((sp.from == null || sp.from < ch) &&
4091
+ (sp.to == null || sp.to > ch) &&
4092
+ (!found || found.width < sp.marker.width))
4093
+ found = sp.marker;
4094
+ }
4095
+ return found;
4096
+ }
4097
+ function collapsedSpanAtStart(line) { return collapsedSpanAt(line, -1); }
4098
+ function collapsedSpanAtEnd(line) { return collapsedSpanAt(line, line.text.length + 1); }
4099
+
4100
+ function visualLine(doc, line) {
4101
+ var merged;
4102
+ while (merged = collapsedSpanAtStart(line))
4103
+ line = getLine(doc, merged.find().from.line);
4104
+ return line;
4105
+ }
4106
+
4107
+ function lineIsHidden(doc, line) {
4108
+ var sps = sawCollapsedSpans && line.markedSpans;
4109
+ if (sps) for (var sp, i = 0; i < sps.length; ++i) {
4110
+ sp = sps[i];
4111
+ if (!sp.marker.collapsed) continue;
4112
+ if (sp.from == null) return true;
4113
+ if (sp.marker.replacedWith) continue;
4114
+ if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp))
4115
+ return true;
4116
+ }
4117
+ }
4118
+ function lineIsHiddenInner(doc, line, span) {
4119
+ if (span.to == null) {
4120
+ var end = span.marker.find().to, endLine = getLine(doc, end.line);
4121
+ return lineIsHiddenInner(doc, endLine, getMarkedSpanFor(endLine.markedSpans, span.marker));
4122
+ }
4123
+ if (span.marker.inclusiveRight && span.to == line.text.length)
4124
+ return true;
4125
+ for (var sp, i = 0; i < line.markedSpans.length; ++i) {
4126
+ sp = line.markedSpans[i];
4127
+ if (sp.marker.collapsed && !sp.marker.replacedWith && sp.from == span.to &&
4128
+ (sp.marker.inclusiveLeft || span.marker.inclusiveRight) &&
4129
+ lineIsHiddenInner(doc, line, sp)) return true;
4130
+ }
4131
+ }
4132
+
4133
+ function detachMarkedSpans(line) {
4134
+ var spans = line.markedSpans;
4135
+ if (!spans) return;
4136
+ for (var i = 0; i < spans.length; ++i)
4137
+ spans[i].marker.detachLine(line);
4138
+ line.markedSpans = null;
4139
+ }
4140
+
4141
+ function attachMarkedSpans(line, spans) {
4142
+ if (!spans) return;
4143
+ for (var i = 0; i < spans.length; ++i)
4144
+ spans[i].marker.attachLine(line);
4145
+ line.markedSpans = spans;
4146
+ }
4147
+
4148
+ // LINE WIDGETS
4149
+
4150
+ var LineWidget = CodeMirror.LineWidget = function(cm, node, options) {
4151
+ if (options) for (var opt in options) if (options.hasOwnProperty(opt))
4152
+ this[opt] = options[opt];
4153
+ this.cm = cm;
4154
+ this.node = node;
4155
+ };
4156
+ eventMixin(LineWidget);
4157
+ function widgetOperation(f) {
4158
+ return function() {
4159
+ var withOp = !this.cm.curOp;
4160
+ if (withOp) startOperation(this.cm);
4161
+ try {var result = f.apply(this, arguments);}
4162
+ finally {if (withOp) endOperation(this.cm);}
4163
+ return result;
4164
+ };
4165
+ }
4166
+ LineWidget.prototype.clear = widgetOperation(function() {
4167
+ var ws = this.line.widgets, no = lineNo(this.line);
4168
+ if (no == null || !ws) return;
4169
+ for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1);
4170
+ if (!ws.length) this.line.widgets = null;
4171
+ var aboveVisible = heightAtLine(this.cm, this.line) < this.cm.doc.scrollTop;
4172
+ updateLineHeight(this.line, Math.max(0, this.line.height - widgetHeight(this)));
4173
+ if (aboveVisible) addToScrollPos(this.cm, 0, -this.height);
4174
+ regChange(this.cm, no, no + 1);
4175
+ });
4176
+ LineWidget.prototype.changed = widgetOperation(function() {
4177
+ var oldH = this.height;
4178
+ this.height = null;
4179
+ var diff = widgetHeight(this) - oldH;
4180
+ if (!diff) return;
4181
+ updateLineHeight(this.line, this.line.height + diff);
4182
+ var no = lineNo(this.line);
4183
+ regChange(this.cm, no, no + 1);
4184
+ });
4185
+
4186
+ function widgetHeight(widget) {
4187
+ if (widget.height != null) return widget.height;
4188
+ if (!widget.node.parentNode || widget.node.parentNode.nodeType != 1)
4189
+ removeChildrenAndAdd(widget.cm.display.measure, elt("div", [widget.node], null, "position: relative"));
4190
+ return widget.height = widget.node.offsetHeight;
4191
+ }
4192
+
4193
+ function addLineWidget(cm, handle, node, options) {
4194
+ var widget = new LineWidget(cm, node, options);
4195
+ if (widget.noHScroll) cm.display.alignWidgets = true;
4196
+ changeLine(cm, handle, function(line) {
4197
+ var widgets = line.widgets || (line.widgets = []);
4198
+ if (widget.insertAt == null) widgets.push(widget);
4199
+ else widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget);
4200
+ widget.line = line;
4201
+ if (!lineIsHidden(cm.doc, line) || widget.showIfHidden) {
4202
+ var aboveVisible = heightAtLine(cm, line) < cm.doc.scrollTop;
4203
+ updateLineHeight(line, line.height + widgetHeight(widget));
4204
+ if (aboveVisible) addToScrollPos(cm, 0, widget.height);
4205
+ }
4206
+ return true;
4207
+ });
4208
+ return widget;
4209
+ }
4210
+
4211
+ // LINE DATA STRUCTURE
4212
+
4213
+ // Line objects. These hold state related to a line, including
4214
+ // highlighting info (the styles array).
4215
+ var Line = CodeMirror.Line = function(text, markedSpans, estimateHeight) {
4216
+ this.text = text;
4217
+ attachMarkedSpans(this, markedSpans);
4218
+ this.height = estimateHeight ? estimateHeight(this) : 1;
4219
+ };
4220
+ eventMixin(Line);
4221
+
4222
+ function updateLine(line, text, markedSpans, estimateHeight) {
4223
+ line.text = text;
4224
+ if (line.stateAfter) line.stateAfter = null;
4225
+ if (line.styles) line.styles = null;
4226
+ if (line.order != null) line.order = null;
4227
+ detachMarkedSpans(line);
4228
+ attachMarkedSpans(line, markedSpans);
4229
+ var estHeight = estimateHeight ? estimateHeight(line) : 1;
4230
+ if (estHeight != line.height) updateLineHeight(line, estHeight);
4231
+ }
4232
+
4233
+ function cleanUpLine(line) {
4234
+ line.parent = null;
4235
+ detachMarkedSpans(line);
4236
+ }
4237
+
4238
+ // Run the given mode's parser over a line, update the styles
4239
+ // array, which contains alternating fragments of text and CSS
4240
+ // classes.
4241
+ function runMode(cm, text, mode, state, f) {
4242
+ var flattenSpans = mode.flattenSpans;
4243
+ if (flattenSpans == null) flattenSpans = cm.options.flattenSpans;
4244
+ var curStart = 0, curStyle = null;
4245
+ var stream = new StringStream(text, cm.options.tabSize), style;
4246
+ if (text == "" && mode.blankLine) mode.blankLine(state);
4247
+ while (!stream.eol()) {
4248
+ if (stream.pos > cm.options.maxHighlightLength) {
4249
+ flattenSpans = false;
4250
+ stream.pos = text.length;
4251
+ style = null;
4252
+ } else {
4253
+ style = mode.token(stream, state);
4254
+ }
4255
+ if (!flattenSpans || curStyle != style) {
4256
+ if (curStart < stream.start) f(stream.start, curStyle);
4257
+ curStart = stream.start; curStyle = style;
4258
+ }
4259
+ stream.start = stream.pos;
4260
+ }
4261
+ while (curStart < stream.pos) {
4262
+ // Webkit seems to refuse to render text nodes longer than 57444 characters
4263
+ var pos = Math.min(stream.pos, curStart + 50000);
4264
+ f(pos, curStyle);
4265
+ curStart = pos;
4266
+ }
4267
+ }
4268
+
4269
+ function highlightLine(cm, line, state) {
4270
+ // A styles array always starts with a number identifying the
4271
+ // mode/overlays that it is based on (for easy invalidation).
4272
+ var st = [cm.state.modeGen];
4273
+ // Compute the base array of styles
4274
+ runMode(cm, line.text, cm.doc.mode, state, function(end, style) {st.push(end, style);});
4275
+
4276
+ // Run overlays, adjust style array.
4277
+ for (var o = 0; o < cm.state.overlays.length; ++o) {
4278
+ var overlay = cm.state.overlays[o], i = 1, at = 0;
4279
+ runMode(cm, line.text, overlay.mode, true, function(end, style) {
4280
+ var start = i;
4281
+ // Ensure there's a token end at the current position, and that i points at it
4282
+ while (at < end) {
4283
+ var i_end = st[i];
4284
+ if (i_end > end)
4285
+ st.splice(i, 1, end, st[i+1], i_end);
4286
+ i += 2;
4287
+ at = Math.min(end, i_end);
4288
+ }
4289
+ if (!style) return;
4290
+ if (overlay.opaque) {
4291
+ st.splice(start, i - start, end, style);
4292
+ i = start + 2;
4293
+ } else {
4294
+ for (; start < i; start += 2) {
4295
+ var cur = st[start+1];
4296
+ st[start+1] = cur ? cur + " " + style : style;
4297
+ }
4298
+ }
4299
+ });
4300
+ }
4301
+
4302
+ return st;
4303
+ }
4304
+
4305
+ function getLineStyles(cm, line) {
4306
+ if (!line.styles || line.styles[0] != cm.state.modeGen)
4307
+ line.styles = highlightLine(cm, line, line.stateAfter = getStateBefore(cm, lineNo(line)));
4308
+ return line.styles;
4309
+ }
4310
+
4311
+ // Lightweight form of highlight -- proceed over this line and
4312
+ // update state, but don't save a style array.
4313
+ function processLine(cm, line, state) {
4314
+ var mode = cm.doc.mode;
4315
+ var stream = new StringStream(line.text, cm.options.tabSize);
4316
+ if (line.text == "" && mode.blankLine) mode.blankLine(state);
4317
+ while (!stream.eol() && stream.pos <= cm.options.maxHighlightLength) {
4318
+ mode.token(stream, state);
4319
+ stream.start = stream.pos;
4320
+ }
4321
+ }
4322
+
4323
+ var styleToClassCache = {};
4324
+ function interpretTokenStyle(style, builder) {
4325
+ if (!style) return null;
4326
+ for (;;) {
4327
+ var lineClass = style.match(/(?:^|\s)line-(background-)?(\S+)/);
4328
+ if (!lineClass) break;
4329
+ style = style.slice(0, lineClass.index) + style.slice(lineClass.index + lineClass[0].length);
4330
+ var prop = lineClass[1] ? "bgClass" : "textClass";
4331
+ if (builder[prop] == null)
4332
+ builder[prop] = lineClass[2];
4333
+ else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(builder[prop]))
4334
+ builder[prop] += " " + lineClass[2];
4335
+ }
4336
+ return styleToClassCache[style] ||
4337
+ (styleToClassCache[style] = "cm-" + style.replace(/ +/g, " cm-"));
4338
+ }
4339
+
4340
+ function buildLineContent(cm, realLine, measure, copyWidgets) {
4341
+ var merged, line = realLine, empty = true;
4342
+ while (merged = collapsedSpanAtStart(line))
4343
+ line = getLine(cm.doc, merged.find().from.line);
4344
+
4345
+ var builder = {pre: elt("pre"), col: 0, pos: 0,
4346
+ measure: null, measuredSomething: false, cm: cm,
4347
+ copyWidgets: copyWidgets};
4348
+
4349
+ do {
4350
+ if (line.text) empty = false;
4351
+ builder.measure = line == realLine && measure;
4352
+ builder.pos = 0;
4353
+ builder.addToken = builder.measure ? buildTokenMeasure : buildToken;
4354
+ if ((ie || webkit) && cm.getOption("lineWrapping"))
4355
+ builder.addToken = buildTokenSplitSpaces(builder.addToken);
4356
+ var next = insertLineContent(line, builder, getLineStyles(cm, line));
4357
+ if (measure && line == realLine && !builder.measuredSomething) {
4358
+ measure[0] = builder.pre.appendChild(zeroWidthElement(cm.display.measure));
4359
+ builder.measuredSomething = true;
4360
+ }
4361
+ if (next) line = getLine(cm.doc, next.to.line);
4362
+ } while (next);
4363
+
4364
+ if (measure && !builder.measuredSomething && !measure[0])
4365
+ measure[0] = builder.pre.appendChild(empty ? elt("span", "\u00a0") : zeroWidthElement(cm.display.measure));
4366
+ if (!builder.pre.firstChild && !lineIsHidden(cm.doc, realLine))
4367
+ builder.pre.appendChild(document.createTextNode("\u00a0"));
4368
+
4369
+ var order;
4370
+ // Work around problem with the reported dimensions of single-char
4371
+ // direction spans on IE (issue #1129). See also the comment in
4372
+ // cursorCoords.
4373
+ if (measure && ie && (order = getOrder(line))) {
4374
+ var l = order.length - 1;
4375
+ if (order[l].from == order[l].to) --l;
4376
+ var last = order[l], prev = order[l - 1];
4377
+ if (last.from + 1 == last.to && prev && last.level < prev.level) {
4378
+ var span = measure[builder.pos - 1];
4379
+ if (span) span.parentNode.insertBefore(span.measureRight = zeroWidthElement(cm.display.measure),
4380
+ span.nextSibling);
4381
+ }
4382
+ }
4383
+
4384
+ var textClass = builder.textClass ? builder.textClass + " " + (realLine.textClass || "") : realLine.textClass;
4385
+ if (textClass) builder.pre.className = textClass;
4386
+
4387
+ signal(cm, "renderLine", cm, realLine, builder.pre);
4388
+ return builder;
4389
+ }
4390
+
4391
+ var tokenSpecialChars = /[\t\u0000-\u0019\u00ad\u200b\u2028\u2029\uFEFF]/g;
4392
+ function buildToken(builder, text, style, startStyle, endStyle, title) {
4393
+ if (!text) return;
4394
+ if (!tokenSpecialChars.test(text)) {
4395
+ builder.col += text.length;
4396
+ var content = document.createTextNode(text);
4397
+ } else {
4398
+ var content = document.createDocumentFragment(), pos = 0;
4399
+ while (true) {
4400
+ tokenSpecialChars.lastIndex = pos;
4401
+ var m = tokenSpecialChars.exec(text);
4402
+ var skipped = m ? m.index - pos : text.length - pos;
4403
+ if (skipped) {
4404
+ content.appendChild(document.createTextNode(text.slice(pos, pos + skipped)));
4405
+ builder.col += skipped;
4406
+ }
4407
+ if (!m) break;
4408
+ pos += skipped + 1;
4409
+ if (m[0] == "\t") {
4410
+ var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize;
4411
+ content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab"));
4412
+ builder.col += tabWidth;
4413
+ } else {
4414
+ var token = elt("span", "\u2022", "cm-invalidchar");
4415
+ token.title = "\\u" + m[0].charCodeAt(0).toString(16);
4416
+ content.appendChild(token);
4417
+ builder.col += 1;
4418
+ }
4419
+ }
4420
+ }
4421
+ if (style || startStyle || endStyle || builder.measure) {
4422
+ var fullStyle = style || "";
4423
+ if (startStyle) fullStyle += startStyle;
4424
+ if (endStyle) fullStyle += endStyle;
4425
+ var token = elt("span", [content], fullStyle);
4426
+ if (title) token.title = title;
4427
+ return builder.pre.appendChild(token);
4428
+ }
4429
+ builder.pre.appendChild(content);
4430
+ }
4431
+
4432
+ function buildTokenMeasure(builder, text, style, startStyle, endStyle) {
4433
+ var wrapping = builder.cm.options.lineWrapping;
4434
+ for (var i = 0; i < text.length; ++i) {
4435
+ var ch = text.charAt(i), start = i == 0;
4436
+ if (ch >= "\ud800" && ch < "\udbff" && i < text.length - 1) {
4437
+ ch = text.slice(i, i + 2);
4438
+ ++i;
4439
+ } else if (i && wrapping && spanAffectsWrapping(text, i)) {
4440
+ builder.pre.appendChild(elt("wbr"));
4441
+ }
4442
+ var old = builder.measure[builder.pos];
4443
+ var span = builder.measure[builder.pos] =
4444
+ buildToken(builder, ch, style,
4445
+ start && startStyle, i == text.length - 1 && endStyle);
4446
+ if (old) span.leftSide = old.leftSide || old;
4447
+ // In IE single-space nodes wrap differently than spaces
4448
+ // embedded in larger text nodes, except when set to
4449
+ // white-space: normal (issue #1268).
4450
+ if (ie && wrapping && ch == " " && i && !/\s/.test(text.charAt(i - 1)) &&
4451
+ i < text.length - 1 && !/\s/.test(text.charAt(i + 1)))
4452
+ span.style.whiteSpace = "normal";
4453
+ builder.pos += ch.length;
4454
+ }
4455
+ if (text.length) builder.measuredSomething = true;
4456
+ }
4457
+
4458
+ function buildTokenSplitSpaces(inner) {
4459
+ function split(old) {
4460
+ var out = " ";
4461
+ for (var i = 0; i < old.length - 2; ++i) out += i % 2 ? " " : "\u00a0";
4462
+ out += " ";
4463
+ return out;
4464
+ }
4465
+ return function(builder, text, style, startStyle, endStyle, title) {
4466
+ return inner(builder, text.replace(/ {3,}/, split), style, startStyle, endStyle, title);
4467
+ };
4468
+ }
4469
+
4470
+ function buildCollapsedSpan(builder, size, marker, ignoreWidget) {
4471
+ var widget = !ignoreWidget && marker.replacedWith;
4472
+ if (widget) {
4473
+ if (builder.copyWidgets) widget = widget.cloneNode(true);
4474
+ builder.pre.appendChild(widget);
4475
+ if (builder.measure) {
4476
+ if (size) {
4477
+ builder.measure[builder.pos] = widget;
4478
+ } else {
4479
+ var elt = zeroWidthElement(builder.cm.display.measure);
4480
+ if (marker.type == "bookmark" && !marker.insertLeft)
4481
+ builder.measure[builder.pos] = builder.pre.appendChild(elt);
4482
+ else if (builder.measure[builder.pos])
4483
+ return;
4484
+ else
4485
+ builder.measure[builder.pos] = builder.pre.insertBefore(elt, widget);
4486
+ }
4487
+ builder.measuredSomething = true;
4488
+ }
4489
+ }
4490
+ builder.pos += size;
4491
+ }
4492
+
4493
+ // Outputs a number of spans to make up a line, taking highlighting
4494
+ // and marked text into account.
4495
+ function insertLineContent(line, builder, styles) {
4496
+ var spans = line.markedSpans, allText = line.text, at = 0;
4497
+ if (!spans) {
4498
+ for (var i = 1; i < styles.length; i+=2)
4499
+ builder.addToken(builder, allText.slice(at, at = styles[i]), interpretTokenStyle(styles[i+1], builder));
4500
+ return;
4501
+ }
4502
+
4503
+ var len = allText.length, pos = 0, i = 1, text = "", style;
4504
+ var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapsed;
4505
+ for (;;) {
4506
+ if (nextChange == pos) { // Update current marker set
4507
+ spanStyle = spanEndStyle = spanStartStyle = title = "";
4508
+ collapsed = null; nextChange = Infinity;
4509
+ var foundBookmarks = [];
4510
+ for (var j = 0; j < spans.length; ++j) {
4511
+ var sp = spans[j], m = sp.marker;
4512
+ if (sp.from <= pos && (sp.to == null || sp.to > pos)) {
4513
+ if (sp.to != null && nextChange > sp.to) { nextChange = sp.to; spanEndStyle = ""; }
4514
+ if (m.className) spanStyle += " " + m.className;
4515
+ if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle;
4516
+ if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endStyle;
4517
+ if (m.title && !title) title = m.title;
4518
+ if (m.collapsed && (!collapsed || collapsed.marker.size < m.size))
4519
+ collapsed = sp;
4520
+ } else if (sp.from > pos && nextChange > sp.from) {
4521
+ nextChange = sp.from;
4522
+ }
4523
+ if (m.type == "bookmark" && sp.from == pos && m.replacedWith) foundBookmarks.push(m);
4524
+ }
4525
+ if (collapsed && (collapsed.from || 0) == pos) {
4526
+ buildCollapsedSpan(builder, (collapsed.to == null ? len : collapsed.to) - pos,
4527
+ collapsed.marker, collapsed.from == null);
4528
+ if (collapsed.to == null) return collapsed.marker.find();
4529
+ }
4530
+ if (!collapsed && foundBookmarks.length) for (var j = 0; j < foundBookmarks.length; ++j)
4531
+ buildCollapsedSpan(builder, 0, foundBookmarks[j]);
4532
+ }
4533
+ if (pos >= len) break;
4534
+
4535
+ var upto = Math.min(len, nextChange);
4536
+ while (true) {
4537
+ if (text) {
4538
+ var end = pos + text.length;
4539
+ if (!collapsed) {
4540
+ var tokenText = end > upto ? text.slice(0, upto - pos) : text;
4541
+ builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle,
4542
+ spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", title);
4543
+ }
4544
+ if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}
4545
+ pos = end;
4546
+ spanStartStyle = "";
4547
+ }
4548
+ text = allText.slice(at, at = styles[i++]);
4549
+ style = interpretTokenStyle(styles[i++], builder);
4550
+ }
4551
+ }
4552
+ }
4553
+
4554
+ // DOCUMENT DATA STRUCTURE
4555
+
4556
+ function updateDoc(doc, change, markedSpans, selAfter, estimateHeight) {
4557
+ function spansFor(n) {return markedSpans ? markedSpans[n] : null;}
4558
+ function update(line, text, spans) {
4559
+ updateLine(line, text, spans, estimateHeight);
4560
+ signalLater(line, "change", line, change);
4561
+ }
4562
+
4563
+ var from = change.from, to = change.to, text = change.text;
4564
+ var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line);
4565
+ var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line;
4566
+
4567
+ // First adjust the line structure
4568
+ if (from.ch == 0 && to.ch == 0 && lastText == "") {
4569
+ // This is a whole-line replace. Treated specially to make
4570
+ // sure line objects move the way they are supposed to.
4571
+ for (var i = 0, e = text.length - 1, added = []; i < e; ++i)
4572
+ added.push(new Line(text[i], spansFor(i), estimateHeight));
4573
+ update(lastLine, lastLine.text, lastSpans);
4574
+ if (nlines) doc.remove(from.line, nlines);
4575
+ if (added.length) doc.insert(from.line, added);
4576
+ } else if (firstLine == lastLine) {
4577
+ if (text.length == 1) {
4578
+ update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans);
4579
+ } else {
4580
+ for (var added = [], i = 1, e = text.length - 1; i < e; ++i)
4581
+ added.push(new Line(text[i], spansFor(i), estimateHeight));
4582
+ added.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight));
4583
+ update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0));
4584
+ doc.insert(from.line + 1, added);
4585
+ }
4586
+ } else if (text.length == 1) {
4587
+ update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0));
4588
+ doc.remove(from.line + 1, nlines);
4589
+ } else {
4590
+ update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0));
4591
+ update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans);
4592
+ for (var i = 1, e = text.length - 1, added = []; i < e; ++i)
4593
+ added.push(new Line(text[i], spansFor(i), estimateHeight));
4594
+ if (nlines > 1) doc.remove(from.line + 1, nlines - 1);
4595
+ doc.insert(from.line + 1, added);
4596
+ }
4597
+
4598
+ signalLater(doc, "change", doc, change);
4599
+ setSelection(doc, selAfter.anchor, selAfter.head, null, true);
4600
+ }
4601
+
4602
+ function LeafChunk(lines) {
4603
+ this.lines = lines;
4604
+ this.parent = null;
4605
+ for (var i = 0, e = lines.length, height = 0; i < e; ++i) {
4606
+ lines[i].parent = this;
4607
+ height += lines[i].height;
4608
+ }
4609
+ this.height = height;
4610
+ }
4611
+
4612
+ LeafChunk.prototype = {
4613
+ chunkSize: function() { return this.lines.length; },
4614
+ removeInner: function(at, n) {
4615
+ for (var i = at, e = at + n; i < e; ++i) {
4616
+ var line = this.lines[i];
4617
+ this.height -= line.height;
4618
+ cleanUpLine(line);
4619
+ signalLater(line, "delete");
4620
+ }
4621
+ this.lines.splice(at, n);
4622
+ },
4623
+ collapse: function(lines) {
4624
+ lines.splice.apply(lines, [lines.length, 0].concat(this.lines));
4625
+ },
4626
+ insertInner: function(at, lines, height) {
4627
+ this.height += height;
4628
+ this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at));
4629
+ for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this;
4630
+ },
4631
+ iterN: function(at, n, op) {
4632
+ for (var e = at + n; at < e; ++at)
4633
+ if (op(this.lines[at])) return true;
4634
+ }
4635
+ };
4636
+
4637
+ function BranchChunk(children) {
4638
+ this.children = children;
4639
+ var size = 0, height = 0;
4640
+ for (var i = 0, e = children.length; i < e; ++i) {
4641
+ var ch = children[i];
4642
+ size += ch.chunkSize(); height += ch.height;
4643
+ ch.parent = this;
4644
+ }
4645
+ this.size = size;
4646
+ this.height = height;
4647
+ this.parent = null;
4648
+ }
4649
+
4650
+ BranchChunk.prototype = {
4651
+ chunkSize: function() { return this.size; },
4652
+ removeInner: function(at, n) {
4653
+ this.size -= n;
4654
+ for (var i = 0; i < this.children.length; ++i) {
4655
+ var child = this.children[i], sz = child.chunkSize();
4656
+ if (at < sz) {
4657
+ var rm = Math.min(n, sz - at), oldHeight = child.height;
4658
+ child.removeInner(at, rm);
4659
+ this.height -= oldHeight - child.height;
4660
+ if (sz == rm) { this.children.splice(i--, 1); child.parent = null; }
4661
+ if ((n -= rm) == 0) break;
4662
+ at = 0;
4663
+ } else at -= sz;
4664
+ }
4665
+ if (this.size - n < 25) {
4666
+ var lines = [];
4667
+ this.collapse(lines);
4668
+ this.children = [new LeafChunk(lines)];
4669
+ this.children[0].parent = this;
4670
+ }
4671
+ },
4672
+ collapse: function(lines) {
4673
+ for (var i = 0, e = this.children.length; i < e; ++i) this.children[i].collapse(lines);
4674
+ },
4675
+ insertInner: function(at, lines, height) {
4676
+ this.size += lines.length;
4677
+ this.height += height;
4678
+ for (var i = 0, e = this.children.length; i < e; ++i) {
4679
+ var child = this.children[i], sz = child.chunkSize();
4680
+ if (at <= sz) {
4681
+ child.insertInner(at, lines, height);
4682
+ if (child.lines && child.lines.length > 50) {
4683
+ while (child.lines.length > 50) {
4684
+ var spilled = child.lines.splice(child.lines.length - 25, 25);
4685
+ var newleaf = new LeafChunk(spilled);
4686
+ child.height -= newleaf.height;
4687
+ this.children.splice(i + 1, 0, newleaf);
4688
+ newleaf.parent = this;
4689
+ }
4690
+ this.maybeSpill();
4691
+ }
4692
+ break;
4693
+ }
4694
+ at -= sz;
4695
+ }
4696
+ },
4697
+ maybeSpill: function() {
4698
+ if (this.children.length <= 10) return;
4699
+ var me = this;
4700
+ do {
4701
+ var spilled = me.children.splice(me.children.length - 5, 5);
4702
+ var sibling = new BranchChunk(spilled);
4703
+ if (!me.parent) { // Become the parent node
4704
+ var copy = new BranchChunk(me.children);
4705
+ copy.parent = me;
4706
+ me.children = [copy, sibling];
4707
+ me = copy;
4708
+ } else {
4709
+ me.size -= sibling.size;
4710
+ me.height -= sibling.height;
4711
+ var myIndex = indexOf(me.parent.children, me);
4712
+ me.parent.children.splice(myIndex + 1, 0, sibling);
4713
+ }
4714
+ sibling.parent = me.parent;
4715
+ } while (me.children.length > 10);
4716
+ me.parent.maybeSpill();
4717
+ },
4718
+ iterN: function(at, n, op) {
4719
+ for (var i = 0, e = this.children.length; i < e; ++i) {
4720
+ var child = this.children[i], sz = child.chunkSize();
4721
+ if (at < sz) {
4722
+ var used = Math.min(n, sz - at);
4723
+ if (child.iterN(at, used, op)) return true;
4724
+ if ((n -= used) == 0) break;
4725
+ at = 0;
4726
+ } else at -= sz;
4727
+ }
4728
+ }
4729
+ };
4730
+
4731
+ var nextDocId = 0;
4732
+ var Doc = CodeMirror.Doc = function(text, mode, firstLine) {
4733
+ if (!(this instanceof Doc)) return new Doc(text, mode, firstLine);
4734
+ if (firstLine == null) firstLine = 0;
4735
+
4736
+ BranchChunk.call(this, [new LeafChunk([new Line("", null)])]);
4737
+ this.first = firstLine;
4738
+ this.scrollTop = this.scrollLeft = 0;
4739
+ this.cantEdit = false;
4740
+ this.history = makeHistory();
4741
+ this.cleanGeneration = 1;
4742
+ this.frontier = firstLine;
4743
+ var start = Pos(firstLine, 0);
4744
+ this.sel = {from: start, to: start, head: start, anchor: start, shift: false, extend: false, goalColumn: null};
4745
+ this.id = ++nextDocId;
4746
+ this.modeOption = mode;
4747
+
4748
+ if (typeof text == "string") text = splitLines(text);
4749
+ updateDoc(this, {from: start, to: start, text: text}, null, {head: start, anchor: start});
4750
+ };
4751
+
4752
+ Doc.prototype = createObj(BranchChunk.prototype, {
4753
+ constructor: Doc,
4754
+ iter: function(from, to, op) {
4755
+ if (op) this.iterN(from - this.first, to - from, op);
4756
+ else this.iterN(this.first, this.first + this.size, from);
4757
+ },
4758
+
4759
+ insert: function(at, lines) {
4760
+ var height = 0;
4761
+ for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height;
4762
+ this.insertInner(at - this.first, lines, height);
4763
+ },
4764
+ remove: function(at, n) { this.removeInner(at - this.first, n); },
4765
+
4766
+ getValue: function(lineSep) {
4767
+ var lines = getLines(this, this.first, this.first + this.size);
4768
+ if (lineSep === false) return lines;
4769
+ return lines.join(lineSep || "\n");
4770
+ },
4771
+ setValue: function(code) {
4772
+ var top = Pos(this.first, 0), last = this.first + this.size - 1;
4773
+ makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length),
4774
+ text: splitLines(code), origin: "setValue"},
4775
+ {head: top, anchor: top}, true);
4776
+ },
4777
+ replaceRange: function(code, from, to, origin) {
4778
+ from = clipPos(this, from);
4779
+ to = to ? clipPos(this, to) : from;
4780
+ replaceRange(this, code, from, to, origin);
4781
+ },
4782
+ getRange: function(from, to, lineSep) {
4783
+ var lines = getBetween(this, clipPos(this, from), clipPos(this, to));
4784
+ if (lineSep === false) return lines;
4785
+ return lines.join(lineSep || "\n");
4786
+ },
4787
+
4788
+ getLine: function(line) {var l = this.getLineHandle(line); return l && l.text;},
4789
+ setLine: function(line, text) {
4790
+ if (isLine(this, line))
4791
+ replaceRange(this, text, Pos(line, 0), clipPos(this, Pos(line)));
4792
+ },
4793
+ removeLine: function(line) {
4794
+ if (line) replaceRange(this, "", clipPos(this, Pos(line - 1)), clipPos(this, Pos(line)));
4795
+ else replaceRange(this, "", Pos(0, 0), clipPos(this, Pos(1, 0)));
4796
+ },
4797
+
4798
+ getLineHandle: function(line) {if (isLine(this, line)) return getLine(this, line);},
4799
+ getLineNumber: function(line) {return lineNo(line);},
4800
+
4801
+ getLineHandleVisualStart: function(line) {
4802
+ if (typeof line == "number") line = getLine(this, line);
4803
+ return visualLine(this, line);
4804
+ },
4805
+
4806
+ lineCount: function() {return this.size;},
4807
+ firstLine: function() {return this.first;},
4808
+ lastLine: function() {return this.first + this.size - 1;},
4809
+
4810
+ clipPos: function(pos) {return clipPos(this, pos);},
4811
+
4812
+ getCursor: function(start) {
4813
+ var sel = this.sel, pos;
4814
+ if (start == null || start == "head") pos = sel.head;
4815
+ else if (start == "anchor") pos = sel.anchor;
4816
+ else if (start == "end" || start === false) pos = sel.to;
4817
+ else pos = sel.from;
4818
+ return copyPos(pos);
4819
+ },
4820
+ somethingSelected: function() {return !posEq(this.sel.head, this.sel.anchor);},
4821
+
4822
+ setCursor: docOperation(function(line, ch, extend) {
4823
+ var pos = clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line);
4824
+ if (extend) extendSelection(this, pos);
4825
+ else setSelection(this, pos, pos);
4826
+ }),
4827
+ setSelection: docOperation(function(anchor, head, bias) {
4828
+ setSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), bias);
4829
+ }),
4830
+ extendSelection: docOperation(function(from, to, bias) {
4831
+ extendSelection(this, clipPos(this, from), to && clipPos(this, to), bias);
4832
+ }),
4833
+
4834
+ getSelection: function(lineSep) {return this.getRange(this.sel.from, this.sel.to, lineSep);},
4835
+ replaceSelection: function(code, collapse, origin) {
4836
+ makeChange(this, {from: this.sel.from, to: this.sel.to, text: splitLines(code), origin: origin}, collapse || "around");
4837
+ },
4838
+ undo: docOperation(function() {makeChangeFromHistory(this, "undo");}),
4839
+ redo: docOperation(function() {makeChangeFromHistory(this, "redo");}),
4840
+
4841
+ setExtending: function(val) {this.sel.extend = val;},
4842
+
4843
+ historySize: function() {
4844
+ var hist = this.history;
4845
+ return {undo: hist.done.length, redo: hist.undone.length};
4846
+ },
4847
+ clearHistory: function() {this.history = makeHistory(this.history.maxGeneration);},
4848
+
4849
+ markClean: function() {
4850
+ this.cleanGeneration = this.changeGeneration();
4851
+ },
4852
+ changeGeneration: function() {
4853
+ this.history.lastOp = this.history.lastOrigin = null;
4854
+ return this.history.generation;
4855
+ },
4856
+ isClean: function (gen) {
4857
+ return this.history.generation == (gen || this.cleanGeneration);
4858
+ },
4859
+
4860
+ getHistory: function() {
4861
+ return {done: copyHistoryArray(this.history.done),
4862
+ undone: copyHistoryArray(this.history.undone)};
4863
+ },
4864
+ setHistory: function(histData) {
4865
+ var hist = this.history = makeHistory(this.history.maxGeneration);
4866
+ hist.done = histData.done.slice(0);
4867
+ hist.undone = histData.undone.slice(0);
4868
+ },
4869
+
4870
+ markText: function(from, to, options) {
4871
+ return markText(this, clipPos(this, from), clipPos(this, to), options, "range");
4872
+ },
4873
+ setBookmark: function(pos, options) {
4874
+ var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options),
4875
+ insertLeft: options && options.insertLeft};
4876
+ pos = clipPos(this, pos);
4877
+ return markText(this, pos, pos, realOpts, "bookmark");
4878
+ },
4879
+ findMarksAt: function(pos) {
4880
+ pos = clipPos(this, pos);
4881
+ var markers = [], spans = getLine(this, pos.line).markedSpans;
4882
+ if (spans) for (var i = 0; i < spans.length; ++i) {
4883
+ var span = spans[i];
4884
+ if ((span.from == null || span.from <= pos.ch) &&
4885
+ (span.to == null || span.to >= pos.ch))
4886
+ markers.push(span.marker.parent || span.marker);
4887
+ }
4888
+ return markers;
4889
+ },
4890
+ getAllMarks: function() {
4891
+ var markers = [];
4892
+ this.iter(function(line) {
4893
+ var sps = line.markedSpans;
4894
+ if (sps) for (var i = 0; i < sps.length; ++i)
4895
+ if (sps[i].from != null) markers.push(sps[i].marker);
4896
+ });
4897
+ return markers;
4898
+ },
4899
+
4900
+ posFromIndex: function(off) {
4901
+ var ch, lineNo = this.first;
4902
+ this.iter(function(line) {
4903
+ var sz = line.text.length + 1;
4904
+ if (sz > off) { ch = off; return true; }
4905
+ off -= sz;
4906
+ ++lineNo;
4907
+ });
4908
+ return clipPos(this, Pos(lineNo, ch));
4909
+ },
4910
+ indexFromPos: function (coords) {
4911
+ coords = clipPos(this, coords);
4912
+ var index = coords.ch;
4913
+ if (coords.line < this.first || coords.ch < 0) return 0;
4914
+ this.iter(this.first, coords.line, function (line) {
4915
+ index += line.text.length + 1;
4916
+ });
4917
+ return index;
4918
+ },
4919
+
4920
+ copy: function(copyHistory) {
4921
+ var doc = new Doc(getLines(this, this.first, this.first + this.size), this.modeOption, this.first);
4922
+ doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft;
4923
+ doc.sel = {from: this.sel.from, to: this.sel.to, head: this.sel.head, anchor: this.sel.anchor,
4924
+ shift: this.sel.shift, extend: false, goalColumn: this.sel.goalColumn};
4925
+ if (copyHistory) {
4926
+ doc.history.undoDepth = this.history.undoDepth;
4927
+ doc.setHistory(this.getHistory());
4928
+ }
4929
+ return doc;
4930
+ },
4931
+
4932
+ linkedDoc: function(options) {
4933
+ if (!options) options = {};
4934
+ var from = this.first, to = this.first + this.size;
4935
+ if (options.from != null && options.from > from) from = options.from;
4936
+ if (options.to != null && options.to < to) to = options.to;
4937
+ var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from);
4938
+ if (options.sharedHist) copy.history = this.history;
4939
+ (this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist});
4940
+ copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}];
4941
+ return copy;
4942
+ },
4943
+ unlinkDoc: function(other) {
4944
+ if (other instanceof CodeMirror) other = other.doc;
4945
+ if (this.linked) for (var i = 0; i < this.linked.length; ++i) {
4946
+ var link = this.linked[i];
4947
+ if (link.doc != other) continue;
4948
+ this.linked.splice(i, 1);
4949
+ other.unlinkDoc(this);
4950
+ break;
4951
+ }
4952
+ // If the histories were shared, split them again
4953
+ if (other.history == this.history) {
4954
+ var splitIds = [other.id];
4955
+ linkedDocs(other, function(doc) {splitIds.push(doc.id);}, true);
4956
+ other.history = makeHistory();
4957
+ other.history.done = copyHistoryArray(this.history.done, splitIds);
4958
+ other.history.undone = copyHistoryArray(this.history.undone, splitIds);
4959
+ }
4960
+ },
4961
+ iterLinkedDocs: function(f) {linkedDocs(this, f);},
4962
+
4963
+ getMode: function() {return this.mode;},
4964
+ getEditor: function() {return this.cm;}
4965
+ });
4966
+
4967
+ Doc.prototype.eachLine = Doc.prototype.iter;
4968
+
4969
+ // The Doc methods that should be available on CodeMirror instances
4970
+ var dontDelegate = "iter insert remove copy getEditor".split(" ");
4971
+ for (var prop in Doc.prototype) if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0)
4972
+ CodeMirror.prototype[prop] = (function(method) {
4973
+ return function() {return method.apply(this.doc, arguments);};
4974
+ })(Doc.prototype[prop]);
4975
+
4976
+ eventMixin(Doc);
4977
+
4978
+ function linkedDocs(doc, f, sharedHistOnly) {
4979
+ function propagate(doc, skip, sharedHist) {
4980
+ if (doc.linked) for (var i = 0; i < doc.linked.length; ++i) {
4981
+ var rel = doc.linked[i];
4982
+ if (rel.doc == skip) continue;
4983
+ var shared = sharedHist && rel.sharedHist;
4984
+ if (sharedHistOnly && !shared) continue;
4985
+ f(rel.doc, shared);
4986
+ propagate(rel.doc, doc, shared);
4987
+ }
4988
+ }
4989
+ propagate(doc, null, true);
4990
+ }
4991
+
4992
+ function attachDoc(cm, doc) {
4993
+ if (doc.cm) throw new Error("This document is already in use.");
4994
+ cm.doc = doc;
4995
+ doc.cm = cm;
4996
+ estimateLineHeights(cm);
4997
+ loadMode(cm);
4998
+ if (!cm.options.lineWrapping) computeMaxLength(cm);
4999
+ cm.options.mode = doc.modeOption;
5000
+ regChange(cm);
5001
+ }
5002
+
5003
+ // LINE UTILITIES
5004
+
5005
+ function getLine(chunk, n) {
5006
+ n -= chunk.first;
5007
+ while (!chunk.lines) {
5008
+ for (var i = 0;; ++i) {
5009
+ var child = chunk.children[i], sz = child.chunkSize();
5010
+ if (n < sz) { chunk = child; break; }
5011
+ n -= sz;
5012
+ }
5013
+ }
5014
+ return chunk.lines[n];
5015
+ }
5016
+
5017
+ function getBetween(doc, start, end) {
5018
+ var out = [], n = start.line;
5019
+ doc.iter(start.line, end.line + 1, function(line) {
5020
+ var text = line.text;
5021
+ if (n == end.line) text = text.slice(0, end.ch);
5022
+ if (n == start.line) text = text.slice(start.ch);
5023
+ out.push(text);
5024
+ ++n;
5025
+ });
5026
+ return out;
5027
+ }
5028
+ function getLines(doc, from, to) {
5029
+ var out = [];
5030
+ doc.iter(from, to, function(line) { out.push(line.text); });
5031
+ return out;
5032
+ }
5033
+
5034
+ function updateLineHeight(line, height) {
5035
+ var diff = height - line.height;
5036
+ for (var n = line; n; n = n.parent) n.height += diff;
5037
+ }
5038
+
5039
+ function lineNo(line) {
5040
+ if (line.parent == null) return null;
5041
+ var cur = line.parent, no = indexOf(cur.lines, line);
5042
+ for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) {
5043
+ for (var i = 0;; ++i) {
5044
+ if (chunk.children[i] == cur) break;
5045
+ no += chunk.children[i].chunkSize();
5046
+ }
5047
+ }
5048
+ return no + cur.first;
5049
+ }
5050
+
5051
+ function lineAtHeight(chunk, h) {
5052
+ var n = chunk.first;
5053
+ outer: do {
5054
+ for (var i = 0, e = chunk.children.length; i < e; ++i) {
5055
+ var child = chunk.children[i], ch = child.height;
5056
+ if (h < ch) { chunk = child; continue outer; }
5057
+ h -= ch;
5058
+ n += child.chunkSize();
5059
+ }
5060
+ return n;
5061
+ } while (!chunk.lines);
5062
+ for (var i = 0, e = chunk.lines.length; i < e; ++i) {
5063
+ var line = chunk.lines[i], lh = line.height;
5064
+ if (h < lh) break;
5065
+ h -= lh;
5066
+ }
5067
+ return n + i;
5068
+ }
5069
+
5070
+ function heightAtLine(cm, lineObj) {
5071
+ lineObj = visualLine(cm.doc, lineObj);
5072
+
5073
+ var h = 0, chunk = lineObj.parent;
5074
+ for (var i = 0; i < chunk.lines.length; ++i) {
5075
+ var line = chunk.lines[i];
5076
+ if (line == lineObj) break;
5077
+ else h += line.height;
5078
+ }
5079
+ for (var p = chunk.parent; p; chunk = p, p = chunk.parent) {
5080
+ for (var i = 0; i < p.children.length; ++i) {
5081
+ var cur = p.children[i];
5082
+ if (cur == chunk) break;
5083
+ else h += cur.height;
5084
+ }
5085
+ }
5086
+ return h;
5087
+ }
5088
+
5089
+ function getOrder(line) {
5090
+ var order = line.order;
5091
+ if (order == null) order = line.order = bidiOrdering(line.text);
5092
+ return order;
5093
+ }
5094
+
5095
+ // HISTORY
5096
+
5097
+ function makeHistory(startGen) {
5098
+ return {
5099
+ // Arrays of history events. Doing something adds an event to
5100
+ // done and clears undo. Undoing moves events from done to
5101
+ // undone, redoing moves them in the other direction.
5102
+ done: [], undone: [], undoDepth: Infinity,
5103
+ // Used to track when changes can be merged into a single undo
5104
+ // event
5105
+ lastTime: 0, lastOp: null, lastOrigin: null,
5106
+ // Used by the isClean() method
5107
+ generation: startGen || 1, maxGeneration: startGen || 1
5108
+ };
5109
+ }
5110
+
5111
+ function attachLocalSpans(doc, change, from, to) {
5112
+ var existing = change["spans_" + doc.id], n = 0;
5113
+ doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function(line) {
5114
+ if (line.markedSpans)
5115
+ (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans;
5116
+ ++n;
5117
+ });
5118
+ }
5119
+
5120
+ function historyChangeFromChange(doc, change) {
5121
+ var from = { line: change.from.line, ch: change.from.ch };
5122
+ var histChange = {from: from, to: changeEnd(change), text: getBetween(doc, change.from, change.to)};
5123
+ attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);
5124
+ linkedDocs(doc, function(doc) {attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);}, true);
5125
+ return histChange;
5126
+ }
5127
+
5128
+ function addToHistory(doc, change, selAfter, opId) {
5129
+ var hist = doc.history;
5130
+ hist.undone.length = 0;
5131
+ var time = +new Date, cur = lst(hist.done);
5132
+
5133
+ if (cur &&
5134
+ (hist.lastOp == opId ||
5135
+ hist.lastOrigin == change.origin && change.origin &&
5136
+ ((change.origin.charAt(0) == "+" && doc.cm && hist.lastTime > time - doc.cm.options.historyEventDelay) ||
5137
+ change.origin.charAt(0) == "*"))) {
5138
+ // Merge this change into the last event
5139
+ var last = lst(cur.changes);
5140
+ if (posEq(change.from, change.to) && posEq(change.from, last.to)) {
5141
+ // Optimized case for simple insertion -- don't want to add
5142
+ // new changesets for every character typed
5143
+ last.to = changeEnd(change);
5144
+ } else {
5145
+ // Add new sub-event
5146
+ cur.changes.push(historyChangeFromChange(doc, change));
5147
+ }
5148
+ cur.anchorAfter = selAfter.anchor; cur.headAfter = selAfter.head;
5149
+ } else {
5150
+ // Can not be merged, start a new event.
5151
+ cur = {changes: [historyChangeFromChange(doc, change)],
5152
+ generation: hist.generation,
5153
+ anchorBefore: doc.sel.anchor, headBefore: doc.sel.head,
5154
+ anchorAfter: selAfter.anchor, headAfter: selAfter.head};
5155
+ hist.done.push(cur);
5156
+ hist.generation = ++hist.maxGeneration;
5157
+ while (hist.done.length > hist.undoDepth)
5158
+ hist.done.shift();
5159
+ }
5160
+ hist.lastTime = time;
5161
+ hist.lastOp = opId;
5162
+ hist.lastOrigin = change.origin;
5163
+ }
5164
+
5165
+ function removeClearedSpans(spans) {
5166
+ if (!spans) return null;
5167
+ for (var i = 0, out; i < spans.length; ++i) {
5168
+ if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i); }
5169
+ else if (out) out.push(spans[i]);
5170
+ }
5171
+ return !out ? spans : out.length ? out : null;
5172
+ }
5173
+
5174
+ function getOldSpans(doc, change) {
5175
+ var found = change["spans_" + doc.id];
5176
+ if (!found) return null;
5177
+ for (var i = 0, nw = []; i < change.text.length; ++i)
5178
+ nw.push(removeClearedSpans(found[i]));
5179
+ return nw;
5180
+ }
5181
+
5182
+ // Used both to provide a JSON-safe object in .getHistory, and, when
5183
+ // detaching a document, to split the history in two
5184
+ function copyHistoryArray(events, newGroup) {
5185
+ for (var i = 0, copy = []; i < events.length; ++i) {
5186
+ var event = events[i], changes = event.changes, newChanges = [];
5187
+ copy.push({changes: newChanges, anchorBefore: event.anchorBefore, headBefore: event.headBefore,
5188
+ anchorAfter: event.anchorAfter, headAfter: event.headAfter});
5189
+ for (var j = 0; j < changes.length; ++j) {
5190
+ var change = changes[j], m;
5191
+ newChanges.push({from: change.from, to: change.to, text: change.text});
5192
+ if (newGroup) for (var prop in change) if (m = prop.match(/^spans_(\d+)$/)) {
5193
+ if (indexOf(newGroup, Number(m[1])) > -1) {
5194
+ lst(newChanges)[prop] = change[prop];
5195
+ delete change[prop];
5196
+ }
5197
+ }
5198
+ }
5199
+ }
5200
+ return copy;
5201
+ }
5202
+
5203
+ // Rebasing/resetting history to deal with externally-sourced changes
5204
+
5205
+ function rebaseHistSel(pos, from, to, diff) {
5206
+ if (to < pos.line) {
5207
+ pos.line += diff;
5208
+ } else if (from < pos.line) {
5209
+ pos.line = from;
5210
+ pos.ch = 0;
5211
+ }
5212
+ }
5213
+
5214
+ // Tries to rebase an array of history events given a change in the
5215
+ // document. If the change touches the same lines as the event, the
5216
+ // event, and everything 'behind' it, is discarded. If the change is
5217
+ // before the event, the event's positions are updated. Uses a
5218
+ // copy-on-write scheme for the positions, to avoid having to
5219
+ // reallocate them all on every rebase, but also avoid problems with
5220
+ // shared position objects being unsafely updated.
5221
+ function rebaseHistArray(array, from, to, diff) {
5222
+ for (var i = 0; i < array.length; ++i) {
5223
+ var sub = array[i], ok = true;
5224
+ for (var j = 0; j < sub.changes.length; ++j) {
5225
+ var cur = sub.changes[j];
5226
+ if (!sub.copied) { cur.from = copyPos(cur.from); cur.to = copyPos(cur.to); }
5227
+ if (to < cur.from.line) {
5228
+ cur.from.line += diff;
5229
+ cur.to.line += diff;
5230
+ } else if (from <= cur.to.line) {
5231
+ ok = false;
5232
+ break;
5233
+ }
5234
+ }
5235
+ if (!sub.copied) {
5236
+ sub.anchorBefore = copyPos(sub.anchorBefore); sub.headBefore = copyPos(sub.headBefore);
5237
+ sub.anchorAfter = copyPos(sub.anchorAfter); sub.readAfter = copyPos(sub.headAfter);
5238
+ sub.copied = true;
5239
+ }
5240
+ if (!ok) {
5241
+ array.splice(0, i + 1);
5242
+ i = 0;
5243
+ } else {
5244
+ rebaseHistSel(sub.anchorBefore); rebaseHistSel(sub.headBefore);
5245
+ rebaseHistSel(sub.anchorAfter); rebaseHistSel(sub.headAfter);
5246
+ }
5247
+ }
5248
+ }
5249
+
5250
+ function rebaseHist(hist, change) {
5251
+ var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1;
5252
+ rebaseHistArray(hist.done, from, to, diff);
5253
+ rebaseHistArray(hist.undone, from, to, diff);
5254
+ }
5255
+
5256
+ // EVENT OPERATORS
5257
+
5258
+ function stopMethod() {e_stop(this);}
5259
+ // Ensure an event has a stop method.
5260
+ function addStop(event) {
5261
+ if (!event.stop) event.stop = stopMethod;
5262
+ return event;
5263
+ }
5264
+
5265
+ function e_preventDefault(e) {
5266
+ if (e.preventDefault) e.preventDefault();
5267
+ else e.returnValue = false;
5268
+ }
5269
+ function e_stopPropagation(e) {
5270
+ if (e.stopPropagation) e.stopPropagation();
5271
+ else e.cancelBubble = true;
5272
+ }
5273
+ function e_defaultPrevented(e) {
5274
+ return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false;
5275
+ }
5276
+ function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);}
5277
+ CodeMirror.e_stop = e_stop;
5278
+ CodeMirror.e_preventDefault = e_preventDefault;
5279
+ CodeMirror.e_stopPropagation = e_stopPropagation;
5280
+
5281
+ function e_target(e) {return e.target || e.srcElement;}
5282
+ function e_button(e) {
5283
+ var b = e.which;
5284
+ if (b == null) {
5285
+ if (e.button & 1) b = 1;
5286
+ else if (e.button & 2) b = 3;
5287
+ else if (e.button & 4) b = 2;
5288
+ }
5289
+ if (mac && e.ctrlKey && b == 1) b = 3;
5290
+ return b;
5291
+ }
5292
+
5293
+ // EVENT HANDLING
5294
+
5295
+ function on(emitter, type, f) {
5296
+ if (emitter.addEventListener)
5297
+ emitter.addEventListener(type, f, false);
5298
+ else if (emitter.attachEvent)
5299
+ emitter.attachEvent("on" + type, f);
5300
+ else {
5301
+ var map = emitter._handlers || (emitter._handlers = {});
5302
+ var arr = map[type] || (map[type] = []);
5303
+ arr.push(f);
5304
+ }
5305
+ }
5306
+
5307
+ function off(emitter, type, f) {
5308
+ if (emitter.removeEventListener)
5309
+ emitter.removeEventListener(type, f, false);
5310
+ else if (emitter.detachEvent)
5311
+ emitter.detachEvent("on" + type, f);
5312
+ else {
5313
+ var arr = emitter._handlers && emitter._handlers[type];
5314
+ if (!arr) return;
5315
+ for (var i = 0; i < arr.length; ++i)
5316
+ if (arr[i] == f) { arr.splice(i, 1); break; }
5317
+ }
5318
+ }
5319
+
5320
+ function signal(emitter, type /*, values...*/) {
5321
+ var arr = emitter._handlers && emitter._handlers[type];
5322
+ if (!arr) return;
5323
+ var args = Array.prototype.slice.call(arguments, 2);
5324
+ for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args);
5325
+ }
5326
+
5327
+ var delayedCallbacks, delayedCallbackDepth = 0;
5328
+ function signalLater(emitter, type /*, values...*/) {
5329
+ var arr = emitter._handlers && emitter._handlers[type];
5330
+ if (!arr) return;
5331
+ var args = Array.prototype.slice.call(arguments, 2);
5332
+ if (!delayedCallbacks) {
5333
+ ++delayedCallbackDepth;
5334
+ delayedCallbacks = [];
5335
+ setTimeout(fireDelayed, 0);
5336
+ }
5337
+ function bnd(f) {return function(){f.apply(null, args);};};
5338
+ for (var i = 0; i < arr.length; ++i)
5339
+ delayedCallbacks.push(bnd(arr[i]));
5340
+ }
5341
+
5342
+ function signalDOMEvent(cm, e, override) {
5343
+ signal(cm, override || e.type, cm, e);
5344
+ return e_defaultPrevented(e) || e.codemirrorIgnore;
5345
+ }
5346
+
5347
+ function fireDelayed() {
5348
+ --delayedCallbackDepth;
5349
+ var delayed = delayedCallbacks;
5350
+ delayedCallbacks = null;
5351
+ for (var i = 0; i < delayed.length; ++i) delayed[i]();
5352
+ }
5353
+
5354
+ function hasHandler(emitter, type) {
5355
+ var arr = emitter._handlers && emitter._handlers[type];
5356
+ return arr && arr.length > 0;
5357
+ }
5358
+
5359
+ CodeMirror.on = on; CodeMirror.off = off; CodeMirror.signal = signal;
5360
+
5361
+ function eventMixin(ctor) {
5362
+ ctor.prototype.on = function(type, f) {on(this, type, f);};
5363
+ ctor.prototype.off = function(type, f) {off(this, type, f);};
5364
+ }
5365
+
5366
+ // MISC UTILITIES
5367
+
5368
+ // Number of pixels added to scroller and sizer to hide scrollbar
5369
+ var scrollerCutOff = 30;
5370
+
5371
+ // Returned or thrown by various protocols to signal 'I'm not
5372
+ // handling this'.
5373
+ var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}};
5374
+
5375
+ function Delayed() {this.id = null;}
5376
+ Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}};
5377
+
5378
+ // Counts the column offset in a string, taking tabs into account.
5379
+ // Used mostly to find indentation.
5380
+ function countColumn(string, end, tabSize, startIndex, startValue) {
5381
+ if (end == null) {
5382
+ end = string.search(/[^\s\u00a0]/);
5383
+ if (end == -1) end = string.length;
5384
+ }
5385
+ for (var i = startIndex || 0, n = startValue || 0; i < end; ++i) {
5386
+ if (string.charAt(i) == "\t") n += tabSize - (n % tabSize);
5387
+ else ++n;
5388
+ }
5389
+ return n;
5390
+ }
5391
+ CodeMirror.countColumn = countColumn;
5392
+
5393
+ var spaceStrs = [""];
5394
+ function spaceStr(n) {
5395
+ while (spaceStrs.length <= n)
5396
+ spaceStrs.push(lst(spaceStrs) + " ");
5397
+ return spaceStrs[n];
5398
+ }
5399
+
5400
+ function lst(arr) { return arr[arr.length-1]; }
5401
+
5402
+ function selectInput(node) {
5403
+ if (ios) { // Mobile Safari apparently has a bug where select() is broken.
5404
+ node.selectionStart = 0;
5405
+ node.selectionEnd = node.value.length;
5406
+ } else {
5407
+ // Suppress mysterious IE10 errors
5408
+ try { node.select(); }
5409
+ catch(_e) {}
5410
+ }
5411
+ }
5412
+
5413
+ function indexOf(collection, elt) {
5414
+ if (collection.indexOf) return collection.indexOf(elt);
5415
+ for (var i = 0, e = collection.length; i < e; ++i)
5416
+ if (collection[i] == elt) return i;
5417
+ return -1;
5418
+ }
5419
+
5420
+ function createObj(base, props) {
5421
+ function Obj() {}
5422
+ Obj.prototype = base;
5423
+ var inst = new Obj();
5424
+ if (props) copyObj(props, inst);
5425
+ return inst;
5426
+ }
5427
+
5428
+ function copyObj(obj, target) {
5429
+ if (!target) target = {};
5430
+ for (var prop in obj) if (obj.hasOwnProperty(prop)) target[prop] = obj[prop];
5431
+ return target;
5432
+ }
5433
+
5434
+ function emptyArray(size) {
5435
+ for (var a = [], i = 0; i < size; ++i) a.push(undefined);
5436
+ return a;
5437
+ }
5438
+
5439
+ function bind(f) {
5440
+ var args = Array.prototype.slice.call(arguments, 1);
5441
+ return function(){return f.apply(null, args);};
5442
+ }
5443
+
5444
+ var nonASCIISingleCaseWordChar = /[\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/;
5445
+ function isWordChar(ch) {
5446
+ return /\w/.test(ch) || ch > "\x80" &&
5447
+ (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch));
5448
+ }
5449
+
5450
+ function isEmpty(obj) {
5451
+ for (var n in obj) if (obj.hasOwnProperty(n) && obj[n]) return false;
5452
+ return true;
5453
+ }
5454
+
5455
+ var isExtendingChar = /[\u0300-\u036F\u0483-\u0487\u0488-\u0489\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\uA66F\uA670-\uA672\uA674-\uA67D\uA69F\udc00-\udfff]/;
5456
+
5457
+ // DOM UTILITIES
5458
+
5459
+ function elt(tag, content, className, style) {
5460
+ var e = document.createElement(tag);
5461
+ if (className) e.className = className;
5462
+ if (style) e.style.cssText = style;
5463
+ if (typeof content == "string") setTextContent(e, content);
5464
+ else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]);
5465
+ return e;
5466
+ }
5467
+
5468
+ function removeChildren(e) {
5469
+ for (var count = e.childNodes.length; count > 0; --count)
5470
+ e.removeChild(e.firstChild);
5471
+ return e;
5472
+ }
5473
+
5474
+ function removeChildrenAndAdd(parent, e) {
5475
+ return removeChildren(parent).appendChild(e);
5476
+ }
5477
+
5478
+ function setTextContent(e, str) {
5479
+ if (ie_lt9) {
5480
+ e.innerHTML = "";
5481
+ e.appendChild(document.createTextNode(str));
5482
+ } else e.textContent = str;
5483
+ }
5484
+
5485
+ function getRect(node) {
5486
+ return node.getBoundingClientRect();
5487
+ }
5488
+ CodeMirror.replaceGetRect = function(f) { getRect = f; };
5489
+
5490
+ // FEATURE DETECTION
5491
+
5492
+ // Detect drag-and-drop
5493
+ var dragAndDrop = function() {
5494
+ // There is *some* kind of drag-and-drop support in IE6-8, but I
5495
+ // couldn't get it to work yet.
5496
+ if (ie_lt9) return false;
5497
+ var div = elt('div');
5498
+ return "draggable" in div || "dragDrop" in div;
5499
+ }();
5500
+
5501
+ // For a reason I have yet to figure out, some browsers disallow
5502
+ // word wrapping between certain characters *only* if a new inline
5503
+ // element is started between them. This makes it hard to reliably
5504
+ // measure the position of things, since that requires inserting an
5505
+ // extra span. This terribly fragile set of tests matches the
5506
+ // character combinations that suffer from this phenomenon on the
5507
+ // various browsers.
5508
+ function spanAffectsWrapping() { return false; }
5509
+ if (gecko) // Only for "$'"
5510
+ spanAffectsWrapping = function(str, i) {
5511
+ return str.charCodeAt(i - 1) == 36 && str.charCodeAt(i) == 39;
5512
+ };
5513
+ else if (safari && !/Version\/([6-9]|\d\d)\b/.test(navigator.userAgent))
5514
+ spanAffectsWrapping = function(str, i) {
5515
+ return /\-[^ \-?]|\?[^ !\'\"\),.\-\/:;\?\]\}]/.test(str.slice(i - 1, i + 1));
5516
+ };
5517
+ else if (webkit && /Chrome\/(?:29|[3-9]\d|\d\d\d)\./.test(navigator.userAgent))
5518
+ spanAffectsWrapping = function(str, i) {
5519
+ var code = str.charCodeAt(i - 1);
5520
+ return code >= 8208 && code <= 8212;
5521
+ };
5522
+ else if (webkit)
5523
+ spanAffectsWrapping = function(str, i) {
5524
+ if (i > 1 && str.charCodeAt(i - 1) == 45) {
5525
+ if (/\w/.test(str.charAt(i - 2)) && /[^\-?\.]/.test(str.charAt(i))) return true;
5526
+ if (i > 2 && /[\d\.,]/.test(str.charAt(i - 2)) && /[\d\.,]/.test(str.charAt(i))) return false;
5527
+ }
5528
+ return /[~!#%&*)=+}\]\\|\"\.>,:;][({[<]|-[^\-?\.\u2010-\u201f\u2026]|\?[\w~`@#$%\^&*(_=+{[|><]|…[\w~`@#$%\^&*(_=+{[><]/.test(str.slice(i - 1, i + 1));
5529
+ };
5530
+
5531
+ var knownScrollbarWidth;
5532
+ function scrollbarWidth(measure) {
5533
+ if (knownScrollbarWidth != null) return knownScrollbarWidth;
5534
+ var test = elt("div", null, null, "width: 50px; height: 50px; overflow-x: scroll");
5535
+ removeChildrenAndAdd(measure, test);
5536
+ if (test.offsetWidth)
5537
+ knownScrollbarWidth = test.offsetHeight - test.clientHeight;
5538
+ return knownScrollbarWidth || 0;
5539
+ }
5540
+
5541
+ var zwspSupported;
5542
+ function zeroWidthElement(measure) {
5543
+ if (zwspSupported == null) {
5544
+ var test = elt("span", "\u200b");
5545
+ removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")]));
5546
+ if (measure.firstChild.offsetHeight != 0)
5547
+ zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !ie_lt8;
5548
+ }
5549
+ if (zwspSupported) return elt("span", "\u200b");
5550
+ else return elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px");
5551
+ }
5552
+
5553
+ // See if "".split is the broken IE version, if so, provide an
5554
+ // alternative way to split lines.
5555
+ var splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) {
5556
+ var pos = 0, result = [], l = string.length;
5557
+ while (pos <= l) {
5558
+ var nl = string.indexOf("\n", pos);
5559
+ if (nl == -1) nl = string.length;
5560
+ var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl);
5561
+ var rt = line.indexOf("\r");
5562
+ if (rt != -1) {
5563
+ result.push(line.slice(0, rt));
5564
+ pos += rt + 1;
5565
+ } else {
5566
+ result.push(line);
5567
+ pos = nl + 1;
5568
+ }
5569
+ }
5570
+ return result;
5571
+ } : function(string){return string.split(/\r\n?|\n/);};
5572
+ CodeMirror.splitLines = splitLines;
5573
+
5574
+ var hasSelection = window.getSelection ? function(te) {
5575
+ try { return te.selectionStart != te.selectionEnd; }
5576
+ catch(e) { return false; }
5577
+ } : function(te) {
5578
+ try {var range = te.ownerDocument.selection.createRange();}
5579
+ catch(e) {}
5580
+ if (!range || range.parentElement() != te) return false;
5581
+ return range.compareEndPoints("StartToEnd", range) != 0;
5582
+ };
5583
+
5584
+ var hasCopyEvent = (function() {
5585
+ var e = elt("div");
5586
+ if ("oncopy" in e) return true;
5587
+ e.setAttribute("oncopy", "return;");
5588
+ return typeof e.oncopy == 'function';
5589
+ })();
5590
+
5591
+ // KEY NAMING
5592
+
5593
+ var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt",
5594
+ 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End",
5595
+ 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert",
5596
+ 46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 109: "-", 107: "=", 127: "Delete",
5597
+ 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\",
5598
+ 221: "]", 222: "'", 63276: "PageUp", 63277: "PageDown", 63275: "End", 63273: "Home",
5599
+ 63234: "Left", 63232: "Up", 63235: "Right", 63233: "Down", 63302: "Insert", 63272: "Delete"};
5600
+ CodeMirror.keyNames = keyNames;
5601
+ (function() {
5602
+ // Number keys
5603
+ for (var i = 0; i < 10; i++) keyNames[i + 48] = String(i);
5604
+ // Alphabetic keys
5605
+ for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i);
5606
+ // Function keys
5607
+ for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i;
5608
+ })();
5609
+
5610
+ // BIDI HELPERS
5611
+
5612
+ function iterateBidiSections(order, from, to, f) {
5613
+ if (!order) return f(from, to, "ltr");
5614
+ var found = false;
5615
+ for (var i = 0; i < order.length; ++i) {
5616
+ var part = order[i];
5617
+ if (part.from < to && part.to > from || from == to && part.to == from) {
5618
+ f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr");
5619
+ found = true;
5620
+ }
5621
+ }
5622
+ if (!found) f(from, to, "ltr");
5623
+ }
5624
+
5625
+ function bidiLeft(part) { return part.level % 2 ? part.to : part.from; }
5626
+ function bidiRight(part) { return part.level % 2 ? part.from : part.to; }
5627
+
5628
+ function lineLeft(line) { var order = getOrder(line); return order ? bidiLeft(order[0]) : 0; }
5629
+ function lineRight(line) {
5630
+ var order = getOrder(line);
5631
+ if (!order) return line.text.length;
5632
+ return bidiRight(lst(order));
5633
+ }
5634
+
5635
+ function lineStart(cm, lineN) {
5636
+ var line = getLine(cm.doc, lineN);
5637
+ var visual = visualLine(cm.doc, line);
5638
+ if (visual != line) lineN = lineNo(visual);
5639
+ var order = getOrder(visual);
5640
+ var ch = !order ? 0 : order[0].level % 2 ? lineRight(visual) : lineLeft(visual);
5641
+ return Pos(lineN, ch);
5642
+ }
5643
+ function lineEnd(cm, lineN) {
5644
+ var merged, line;
5645
+ while (merged = collapsedSpanAtEnd(line = getLine(cm.doc, lineN)))
5646
+ lineN = merged.find().to.line;
5647
+ var order = getOrder(line);
5648
+ var ch = !order ? line.text.length : order[0].level % 2 ? lineLeft(line) : lineRight(line);
5649
+ return Pos(lineN, ch);
5650
+ }
5651
+
5652
+ function compareBidiLevel(order, a, b) {
5653
+ var linedir = order[0].level;
5654
+ if (a == linedir) return true;
5655
+ if (b == linedir) return false;
5656
+ return a < b;
5657
+ }
5658
+ var bidiOther;
5659
+ function getBidiPartAt(order, pos) {
5660
+ for (var i = 0, found; i < order.length; ++i) {
5661
+ var cur = order[i];
5662
+ if (cur.from < pos && cur.to > pos) { bidiOther = null; return i; }
5663
+ if (cur.from == pos || cur.to == pos) {
5664
+ if (found == null) {
5665
+ found = i;
5666
+ } else if (compareBidiLevel(order, cur.level, order[found].level)) {
5667
+ bidiOther = found;
5668
+ return i;
5669
+ } else {
5670
+ bidiOther = i;
5671
+ return found;
5672
+ }
5673
+ }
5674
+ }
5675
+ bidiOther = null;
5676
+ return found;
5677
+ }
5678
+
5679
+ function moveInLine(line, pos, dir, byUnit) {
5680
+ if (!byUnit) return pos + dir;
5681
+ do pos += dir;
5682
+ while (pos > 0 && isExtendingChar.test(line.text.charAt(pos)));
5683
+ return pos;
5684
+ }
5685
+
5686
+ // This is somewhat involved. It is needed in order to move
5687
+ // 'visually' through bi-directional text -- i.e., pressing left
5688
+ // should make the cursor go left, even when in RTL text. The
5689
+ // tricky part is the 'jumps', where RTL and LTR text touch each
5690
+ // other. This often requires the cursor offset to move more than
5691
+ // one unit, in order to visually move one unit.
5692
+ function moveVisually(line, start, dir, byUnit) {
5693
+ var bidi = getOrder(line);
5694
+ if (!bidi) return moveLogically(line, start, dir, byUnit);
5695
+ var pos = getBidiPartAt(bidi, start), part = bidi[pos];
5696
+ var target = moveInLine(line, start, part.level % 2 ? -dir : dir, byUnit);
5697
+
5698
+ for (;;) {
5699
+ if (target > part.from && target < part.to) return target;
5700
+ if (target == part.from || target == part.to) {
5701
+ if (getBidiPartAt(bidi, target) == pos) return target;
5702
+ part = bidi[pos += dir];
5703
+ return (dir > 0) == part.level % 2 ? part.to : part.from;
5704
+ } else {
5705
+ part = bidi[pos += dir];
5706
+ if (!part) return null;
5707
+ if ((dir > 0) == part.level % 2)
5708
+ target = moveInLine(line, part.to, -1, byUnit);
5709
+ else
5710
+ target = moveInLine(line, part.from, 1, byUnit);
5711
+ }
5712
+ }
5713
+ }
5714
+
5715
+ function moveLogically(line, start, dir, byUnit) {
5716
+ var target = start + dir;
5717
+ if (byUnit) while (target > 0 && isExtendingChar.test(line.text.charAt(target))) target += dir;
5718
+ return target < 0 || target > line.text.length ? null : target;
5719
+ }
5720
+
5721
+ // Bidirectional ordering algorithm
5722
+ // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm
5723
+ // that this (partially) implements.
5724
+
5725
+ // One-char codes used for character types:
5726
+ // L (L): Left-to-Right
5727
+ // R (R): Right-to-Left
5728
+ // r (AL): Right-to-Left Arabic
5729
+ // 1 (EN): European Number
5730
+ // + (ES): European Number Separator
5731
+ // % (ET): European Number Terminator
5732
+ // n (AN): Arabic Number
5733
+ // , (CS): Common Number Separator
5734
+ // m (NSM): Non-Spacing Mark
5735
+ // b (BN): Boundary Neutral
5736
+ // s (B): Paragraph Separator
5737
+ // t (S): Segment Separator
5738
+ // w (WS): Whitespace
5739
+ // N (ON): Other Neutrals
5740
+
5741
+ // Returns null if characters are ordered as they appear
5742
+ // (left-to-right), or an array of sections ({from, to, level}
5743
+ // objects) in the order in which they occur visually.
5744
+ var bidiOrdering = (function() {
5745
+ // Character types for codepoints 0 to 0xff
5746
+ var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLL";
5747
+ // Character types for codepoints 0x600 to 0x6ff
5748
+ var arabicTypes = "rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmmrrrrrrrrrrrrrrrrrr";
5749
+ function charType(code) {
5750
+ if (code <= 0xff) return lowTypes.charAt(code);
5751
+ else if (0x590 <= code && code <= 0x5f4) return "R";
5752
+ else if (0x600 <= code && code <= 0x6ff) return arabicTypes.charAt(code - 0x600);
5753
+ else if (0x700 <= code && code <= 0x8ac) return "r";
5754
+ else return "L";
5755
+ }
5756
+
5757
+ var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;
5758
+ var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/;
5759
+ // Browsers seem to always treat the boundaries of block elements as being L.
5760
+ var outerType = "L";
5761
+
5762
+ return function(str) {
5763
+ if (!bidiRE.test(str)) return false;
5764
+ var len = str.length, types = [];
5765
+ for (var i = 0, type; i < len; ++i)
5766
+ types.push(type = charType(str.charCodeAt(i)));
5767
+
5768
+ // W1. Examine each non-spacing mark (NSM) in the level run, and
5769
+ // change the type of the NSM to the type of the previous
5770
+ // character. If the NSM is at the start of the level run, it will
5771
+ // get the type of sor.
5772
+ for (var i = 0, prev = outerType; i < len; ++i) {
5773
+ var type = types[i];
5774
+ if (type == "m") types[i] = prev;
5775
+ else prev = type;
5776
+ }
5777
+
5778
+ // W2. Search backwards from each instance of a European number
5779
+ // until the first strong type (R, L, AL, or sor) is found. If an
5780
+ // AL is found, change the type of the European number to Arabic
5781
+ // number.
5782
+ // W3. Change all ALs to R.
5783
+ for (var i = 0, cur = outerType; i < len; ++i) {
5784
+ var type = types[i];
5785
+ if (type == "1" && cur == "r") types[i] = "n";
5786
+ else if (isStrong.test(type)) { cur = type; if (type == "r") types[i] = "R"; }
5787
+ }
5788
+
5789
+ // W4. A single European separator between two European numbers
5790
+ // changes to a European number. A single common separator between
5791
+ // two numbers of the same type changes to that type.
5792
+ for (var i = 1, prev = types[0]; i < len - 1; ++i) {
5793
+ var type = types[i];
5794
+ if (type == "+" && prev == "1" && types[i+1] == "1") types[i] = "1";
5795
+ else if (type == "," && prev == types[i+1] &&
5796
+ (prev == "1" || prev == "n")) types[i] = prev;
5797
+ prev = type;
5798
+ }
5799
+
5800
+ // W5. A sequence of European terminators adjacent to European
5801
+ // numbers changes to all European numbers.
5802
+ // W6. Otherwise, separators and terminators change to Other
5803
+ // Neutral.
5804
+ for (var i = 0; i < len; ++i) {
5805
+ var type = types[i];
5806
+ if (type == ",") types[i] = "N";
5807
+ else if (type == "%") {
5808
+ for (var end = i + 1; end < len && types[end] == "%"; ++end) {}
5809
+ var replace = (i && types[i-1] == "!") || (end < len - 1 && types[end] == "1") ? "1" : "N";
5810
+ for (var j = i; j < end; ++j) types[j] = replace;
5811
+ i = end - 1;
5812
+ }
5813
+ }
5814
+
5815
+ // W7. Search backwards from each instance of a European number
5816
+ // until the first strong type (R, L, or sor) is found. If an L is
5817
+ // found, then change the type of the European number to L.
5818
+ for (var i = 0, cur = outerType; i < len; ++i) {
5819
+ var type = types[i];
5820
+ if (cur == "L" && type == "1") types[i] = "L";
5821
+ else if (isStrong.test(type)) cur = type;
5822
+ }
5823
+
5824
+ // N1. A sequence of neutrals takes the direction of the
5825
+ // surrounding strong text if the text on both sides has the same
5826
+ // direction. European and Arabic numbers act as if they were R in
5827
+ // terms of their influence on neutrals. Start-of-level-run (sor)
5828
+ // and end-of-level-run (eor) are used at level run boundaries.
5829
+ // N2. Any remaining neutrals take the embedding direction.
5830
+ for (var i = 0; i < len; ++i) {
5831
+ if (isNeutral.test(types[i])) {
5832
+ for (var end = i + 1; end < len && isNeutral.test(types[end]); ++end) {}
5833
+ var before = (i ? types[i-1] : outerType) == "L";
5834
+ var after = (end < len - 1 ? types[end] : outerType) == "L";
5835
+ var replace = before || after ? "L" : "R";
5836
+ for (var j = i; j < end; ++j) types[j] = replace;
5837
+ i = end - 1;
5838
+ }
5839
+ }
5840
+
5841
+ // Here we depart from the documented algorithm, in order to avoid
5842
+ // building up an actual levels array. Since there are only three
5843
+ // levels (0, 1, 2) in an implementation that doesn't take
5844
+ // explicit embedding into account, we can build up the order on
5845
+ // the fly, without following the level-based algorithm.
5846
+ var order = [], m;
5847
+ for (var i = 0; i < len;) {
5848
+ if (countsAsLeft.test(types[i])) {
5849
+ var start = i;
5850
+ for (++i; i < len && countsAsLeft.test(types[i]); ++i) {}
5851
+ order.push({from: start, to: i, level: 0});
5852
+ } else {
5853
+ var pos = i, at = order.length;
5854
+ for (++i; i < len && types[i] != "L"; ++i) {}
5855
+ for (var j = pos; j < i;) {
5856
+ if (countsAsNum.test(types[j])) {
5857
+ if (pos < j) order.splice(at, 0, {from: pos, to: j, level: 1});
5858
+ var nstart = j;
5859
+ for (++j; j < i && countsAsNum.test(types[j]); ++j) {}
5860
+ order.splice(at, 0, {from: nstart, to: j, level: 2});
5861
+ pos = j;
5862
+ } else ++j;
5863
+ }
5864
+ if (pos < i) order.splice(at, 0, {from: pos, to: i, level: 1});
5865
+ }
5866
+ }
5867
+ if (order[0].level == 1 && (m = str.match(/^\s+/))) {
5868
+ order[0].from = m[0].length;
5869
+ order.unshift({from: 0, to: m[0].length, level: 0});
5870
+ }
5871
+ if (lst(order).level == 1 && (m = str.match(/\s+$/))) {
5872
+ lst(order).to -= m[0].length;
5873
+ order.push({from: len - m[0].length, to: len, level: 0});
5874
+ }
5875
+ if (order[0].level != lst(order).level)
5876
+ order.push({from: len, to: len, level: order[0].level});
5877
+
5878
+ return order;
5879
+ };
5880
+ })();
5881
+
5882
+ // THE END
5883
+
5884
+ CodeMirror.version = "3.17.0";
5885
+
5886
+ return CodeMirror;
5887
+ })();
assets/lib/codemirror/mode/css/css.js ADDED
@@ -0,0 +1,627 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ CodeMirror.defineMode("css", function(config, parserConfig) {
2
+ "use strict";
3
+
4
+ if (!parserConfig.propertyKeywords) parserConfig = CodeMirror.resolveMode("text/css");
5
+
6
+ var indentUnit = config.indentUnit,
7
+ hooks = parserConfig.hooks || {},
8
+ atMediaTypes = parserConfig.atMediaTypes || {},
9
+ atMediaFeatures = parserConfig.atMediaFeatures || {},
10
+ propertyKeywords = parserConfig.propertyKeywords || {},
11
+ colorKeywords = parserConfig.colorKeywords || {},
12
+ valueKeywords = parserConfig.valueKeywords || {},
13
+ allowNested = !!parserConfig.allowNested,
14
+ type = null;
15
+
16
+ function ret(style, tp) { type = tp; return style; }
17
+
18
+ function tokenBase(stream, state) {
19
+ var ch = stream.next();
20
+ if (hooks[ch]) {
21
+ // result[0] is style and result[1] is type
22
+ var result = hooks[ch](stream, state);
23
+ if (result !== false) return result;
24
+ }
25
+ if (ch == "@") {stream.eatWhile(/[\w\\\-]/); return ret("def", stream.current());}
26
+ else if (ch == "=") ret(null, "compare");
27
+ else if ((ch == "~" || ch == "|") && stream.eat("=")) return ret(null, "compare");
28
+ else if (ch == "\"" || ch == "'") {
29
+ state.tokenize = tokenString(ch);
30
+ return state.tokenize(stream, state);
31
+ }
32
+ else if (ch == "#") {
33
+ stream.eatWhile(/[\w\\\-]/);
34
+ return ret("atom", "hash");
35
+ }
36
+ else if (ch == "!") {
37
+ stream.match(/^\s*\w*/);
38
+ return ret("keyword", "important");
39
+ }
40
+ else if (/\d/.test(ch) || ch == "." && stream.eat(/\d/)) {
41
+ stream.eatWhile(/[\w.%]/);
42
+ return ret("number", "unit");
43
+ }
44
+ else if (ch === "-") {
45
+ if (/\d/.test(stream.peek())) {
46
+ stream.eatWhile(/[\w.%]/);
47
+ return ret("number", "unit");
48
+ } else if (stream.match(/^[^-]+-/)) {
49
+ return ret("meta", "meta");
50
+ }
51
+ }
52
+ else if (/[,+>*\/]/.test(ch)) {
53
+ return ret(null, "select-op");
54
+ }
55
+ else if (ch == "." && stream.match(/^-?[_a-z][_a-z0-9-]*/i)) {
56
+ return ret("qualifier", "qualifier");
57
+ }
58
+ else if (ch == ":") {
59
+ return ret("operator", ch);
60
+ }
61
+ else if (/[;{}\[\]\(\)]/.test(ch)) {
62
+ return ret(null, ch);
63
+ }
64
+ else if (ch == "u" && stream.match("rl(")) {
65
+ stream.backUp(1);
66
+ state.tokenize = tokenParenthesized;
67
+ return ret("property", "variable");
68
+ }
69
+ else {
70
+ stream.eatWhile(/[\w\\\-]/);
71
+ return ret("property", "variable");
72
+ }
73
+ }
74
+
75
+ function tokenString(quote, nonInclusive) {
76
+ return function(stream, state) {
77
+ var escaped = false, ch;
78
+ while ((ch = stream.next()) != null) {
79
+ if (ch == quote && !escaped)
80
+ break;
81
+ escaped = !escaped && ch == "\\";
82
+ }
83
+ if (!escaped) {
84
+ if (nonInclusive) stream.backUp(1);
85
+ state.tokenize = tokenBase;
86
+ }
87
+ return ret("string", "string");
88
+ };
89
+ }
90
+
91
+ function tokenParenthesized(stream, state) {
92
+ stream.next(); // Must be '('
93
+ if (!stream.match(/\s*[\"\']/, false))
94
+ state.tokenize = tokenString(")", true);
95
+ else
96
+ state.tokenize = tokenBase;
97
+ return ret(null, "(");
98
+ }
99
+
100
+ return {
101
+ startState: function(base) {
102
+ return {tokenize: tokenBase,
103
+ baseIndent: base || 0,
104
+ stack: [],
105
+ lastToken: null};
106
+ },
107
+
108
+ token: function(stream, state) {
109
+
110
+ // Use these terms when applicable (see http://www.xanthir.com/blog/b4E50)
111
+ //
112
+ // rule** or **ruleset:
113
+ // A selector + braces combo, or an at-rule.
114
+ //
115
+ // declaration block:
116
+ // A sequence of declarations.
117
+ //
118
+ // declaration:
119
+ // A property + colon + value combo.
120
+ //
121
+ // property value:
122
+ // The entire value of a property.
123
+ //
124
+ // component value:
125
+ // A single piece of a property value. Like the 5px in
126
+ // text-shadow: 0 0 5px blue;. Can also refer to things that are
127
+ // multiple terms, like the 1-4 terms that make up the background-size
128
+ // portion of the background shorthand.
129
+ //
130
+ // term:
131
+ // The basic unit of author-facing CSS, like a single number (5),
132
+ // dimension (5px), string ("foo"), or function. Officially defined
133
+ // by the CSS 2.1 grammar (look for the 'term' production)
134
+ //
135
+ //
136
+ // simple selector:
137
+ // A single atomic selector, like a type selector, an attr selector, a
138
+ // class selector, etc.
139
+ //
140
+ // compound selector:
141
+ // One or more simple selectors without a combinator. div.example is
142
+ // compound, div > .example is not.
143
+ //
144
+ // complex selector:
145
+ // One or more compound selectors chained with combinators.
146
+ //
147
+ // combinator:
148
+ // The parts of selectors that express relationships. There are four
149
+ // currently - the space (descendant combinator), the greater-than
150
+ // bracket (child combinator), the plus sign (next sibling combinator),
151
+ // and the tilda (following sibling combinator).
152
+ //
153
+ // sequence of selectors:
154
+ // One or more of the named type of selector chained with commas.
155
+
156
+ state.tokenize = state.tokenize || tokenBase;
157
+ if (state.tokenize == tokenBase && stream.eatSpace()) return null;
158
+ var style = state.tokenize(stream, state);
159
+ if (style && typeof style != "string") style = ret(style[0], style[1]);
160
+
161
+ // Changing style returned based on context
162
+ var context = state.stack[state.stack.length-1];
163
+ if (style == "variable") {
164
+ if (type == "variable-definition") state.stack.push("propertyValue");
165
+ return state.lastToken = "variable-2";
166
+ } else if (style == "property") {
167
+ var word = stream.current().toLowerCase();
168
+ if (context == "propertyValue") {
169
+ if (valueKeywords.hasOwnProperty(word)) {
170
+ style = "string-2";
171
+ } else if (colorKeywords.hasOwnProperty(word)) {
172
+ style = "keyword";
173
+ } else {
174
+ style = "variable-2";
175
+ }
176
+ } else if (context == "rule") {
177
+ if (!propertyKeywords.hasOwnProperty(word)) {
178
+ style += " error";
179
+ }
180
+ } else if (context == "block") {
181
+ // if a value is present in both property, value, or color, the order
182
+ // of preference is property -> color -> value
183
+ if (propertyKeywords.hasOwnProperty(word)) {
184
+ style = "property";
185
+ } else if (colorKeywords.hasOwnProperty(word)) {
186
+ style = "keyword";
187
+ } else if (valueKeywords.hasOwnProperty(word)) {
188
+ style = "string-2";
189
+ } else {
190
+ style = "tag";
191
+ }
192
+ } else if (!context || context == "@media{") {
193
+ style = "tag";
194
+ } else if (context == "@media") {
195
+ if (atMediaTypes[stream.current()]) {
196
+ style = "attribute"; // Known attribute
197
+ } else if (/^(only|not)$/.test(word)) {
198
+ style = "keyword";
199
+ } else if (word == "and") {
200
+ style = "error"; // "and" is only allowed in @mediaType
201
+ } else if (atMediaFeatures.hasOwnProperty(word)) {
202
+ style = "error"; // Known property, should be in @mediaType(
203
+ } else {
204
+ // Unknown, expecting keyword or attribute, assuming attribute
205
+ style = "attribute error";
206
+ }
207
+ } else if (context == "@mediaType") {
208
+ if (atMediaTypes.hasOwnProperty(word)) {
209
+ style = "attribute";
210
+ } else if (word == "and") {
211
+ style = "operator";
212
+ } else if (/^(only|not)$/.test(word)) {
213
+ style = "error"; // Only allowed in @media
214
+ } else {
215
+ // Unknown attribute or property, but expecting property (preceded
216
+ // by "and"). Should be in parentheses
217
+ style = "error";
218
+ }
219
+ } else if (context == "@mediaType(") {
220
+ if (propertyKeywords.hasOwnProperty(word)) {
221
+ // do nothing, remains "property"
222
+ } else if (atMediaTypes.hasOwnProperty(word)) {
223
+ style = "error"; // Known property, should be in parentheses
224
+ } else if (word == "and") {
225
+ style = "operator";
226
+ } else if (/^(only|not)$/.test(word)) {
227
+ style = "error"; // Only allowed in @media
228
+ } else {
229
+ style += " error";
230
+ }
231
+ } else if (context == "@import") {
232
+ style = "tag";
233
+ } else {
234
+ style = "error";
235
+ }
236
+ } else if (style == "atom") {
237
+ if(!context || context == "@media{" || context == "block") {
238
+ style = "builtin";
239
+ } else if (context == "propertyValue") {
240
+ if (!/^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/.test(stream.current())) {
241
+ style += " error";
242
+ }
243
+ } else {
244
+ style = "error";
245
+ }
246
+ } else if (context == "@media" && type == "{") {
247
+ style = "error";
248
+ }
249
+
250
+ // Push/pop context stack
251
+ if (type == "{") {
252
+ if (context == "@media" || context == "@mediaType") {
253
+ state.stack[state.stack.length-1] = "@media{";
254
+ }
255
+ else {
256
+ var newContext = allowNested ? "block" : "rule";
257
+ state.stack.push(newContext);
258
+ }
259
+ }
260
+ else if (type == "}") {
261
+ if (context == "interpolation") style = "operator";
262
+ state.stack.pop();
263
+ if (context == "propertyValue") state.stack.pop();
264
+ }
265
+ else if (type == "interpolation") state.stack.push("interpolation");
266
+ else if (type == "@media") state.stack.push("@media");
267
+ else if (type == "@import") state.stack.push("@import");
268
+ else if (context == "@media" && /\b(keyword|attribute)\b/.test(style))
269
+ state.stack[state.stack.length-1] = "@mediaType";
270
+ else if (context == "@mediaType" && stream.current() == ",")
271
+ state.stack[state.stack.length-1] = "@media";
272
+ else if (type == "(") {
273
+ if (context == "@media" || context == "@mediaType") {
274
+ // Make sure @mediaType is used to avoid error on {
275
+ state.stack[state.stack.length-1] = "@mediaType";
276
+ state.stack.push("@mediaType(");
277
+ }
278
+ else state.stack.push("(");
279
+ }
280
+ else if (type == ")") {
281
+ if (context == "propertyValue") {
282
+ // In @mediaType( without closing ; after propertyValue
283
+ state.stack.pop();
284
+ }
285
+ state.stack.pop();
286
+ }
287
+ else if (type == ":" && state.lastToken == "property") state.stack.push("propertyValue");
288
+ else if (context == "propertyValue" && type == ";") state.stack.pop();
289
+ else if (context == "@import" && type == ";") state.stack.pop();
290
+
291
+ return state.lastToken = style;
292
+ },
293
+
294
+ indent: function(state, textAfter) {
295
+ var n = state.stack.length;
296
+ if (/^\}/.test(textAfter))
297
+ n -= state.stack[n-1] == "propertyValue" ? 2 : 1;
298
+ return state.baseIndent + n * indentUnit;
299
+ },
300
+
301
+ electricChars: "}",
302
+ blockCommentStart: "/*",
303
+ blockCommentEnd: "*/",
304
+ fold: "brace"
305
+ };
306
+ });
307
+
308
+ (function() {
309
+ function keySet(array) {
310
+ var keys = {};
311
+ for (var i = 0; i < array.length; ++i) {
312
+ keys[array[i]] = true;
313
+ }
314
+ return keys;
315
+ }
316
+
317
+ var atMediaTypes = keySet([
318
+ "all", "aural", "braille", "handheld", "print", "projection", "screen",
319
+ "tty", "tv", "embossed"
320
+ ]);
321
+
322
+ var atMediaFeatures = keySet([
323
+ "width", "min-width", "max-width", "height", "min-height", "max-height",
324
+ "device-width", "min-device-width", "max-device-width", "device-height",
325
+ "min-device-height", "max-device-height", "aspect-ratio",
326
+ "min-aspect-ratio", "max-aspect-ratio", "device-aspect-ratio",
327
+ "min-device-aspect-ratio", "max-device-aspect-ratio", "color", "min-color",
328
+ "max-color", "color-index", "min-color-index", "max-color-index",
329
+ "monochrome", "min-monochrome", "max-monochrome", "resolution",
330
+ "min-resolution", "max-resolution", "scan", "grid"
331
+ ]);
332
+
333
+ var propertyKeywords = keySet([
334
+ "align-content", "align-items", "align-self", "alignment-adjust",
335
+ "alignment-baseline", "anchor-point", "animation", "animation-delay",
336
+ "animation-direction", "animation-duration", "animation-iteration-count",
337
+ "animation-name", "animation-play-state", "animation-timing-function",
338
+ "appearance", "azimuth", "backface-visibility", "background",
339
+ "background-attachment", "background-clip", "background-color",
340
+ "background-image", "background-origin", "background-position",
341
+ "background-repeat", "background-size", "baseline-shift", "binding",
342
+ "bleed", "bookmark-label", "bookmark-level", "bookmark-state",
343
+ "bookmark-target", "border", "border-bottom", "border-bottom-color",
344
+ "border-bottom-left-radius", "border-bottom-right-radius",
345
+ "border-bottom-style", "border-bottom-width", "border-collapse",
346
+ "border-color", "border-image", "border-image-outset",
347
+ "border-image-repeat", "border-image-slice", "border-image-source",
348
+ "border-image-width", "border-left", "border-left-color",
349
+ "border-left-style", "border-left-width", "border-radius", "border-right",
350
+ "border-right-color", "border-right-style", "border-right-width",
351
+ "border-spacing", "border-style", "border-top", "border-top-color",
352
+ "border-top-left-radius", "border-top-right-radius", "border-top-style",
353
+ "border-top-width", "border-width", "bottom", "box-decoration-break",
354
+ "box-shadow", "box-sizing", "break-after", "break-before", "break-inside",
355
+ "caption-side", "clear", "clip", "color", "color-profile", "column-count",
356
+ "column-fill", "column-gap", "column-rule", "column-rule-color",
357
+ "column-rule-style", "column-rule-width", "column-span", "column-width",
358
+ "columns", "content", "counter-increment", "counter-reset", "crop", "cue",
359
+ "cue-after", "cue-before", "cursor", "direction", "display",
360
+ "dominant-baseline", "drop-initial-after-adjust",
361
+ "drop-initial-after-align", "drop-initial-before-adjust",
362
+ "drop-initial-before-align", "drop-initial-size", "drop-initial-value",
363
+ "elevation", "empty-cells", "fit", "fit-position", "flex", "flex-basis",
364
+ "flex-direction", "flex-flow", "flex-grow", "flex-shrink", "flex-wrap",
365
+ "float", "float-offset", "flow-from", "flow-into", "font", "font-feature-settings",
366
+ "font-family", "font-kerning", "font-language-override", "font-size", "font-size-adjust",
367
+ "font-stretch", "font-style", "font-synthesis", "font-variant",
368
+ "font-variant-alternates", "font-variant-caps", "font-variant-east-asian",
369
+ "font-variant-ligatures", "font-variant-numeric", "font-variant-position",
370
+ "font-weight", "grid-cell", "grid-column", "grid-column-align",
371
+ "grid-column-sizing", "grid-column-span", "grid-columns", "grid-flow",
372
+ "grid-row", "grid-row-align", "grid-row-sizing", "grid-row-span",
373
+ "grid-rows", "grid-template", "hanging-punctuation", "height", "hyphens",
374
+ "icon", "image-orientation", "image-rendering", "image-resolution",
375
+ "inline-box-align", "justify-content", "left", "letter-spacing",
376
+ "line-break", "line-height", "line-stacking", "line-stacking-ruby",
377
+ "line-stacking-shift", "line-stacking-strategy", "list-style",
378
+ "list-style-image", "list-style-position", "list-style-type", "margin",
379
+ "margin-bottom", "margin-left", "margin-right", "margin-top",
380
+ "marker-offset", "marks", "marquee-direction", "marquee-loop",
381
+ "marquee-play-count", "marquee-speed", "marquee-style", "max-height",
382
+ "max-width", "min-height", "min-width", "move-to", "nav-down", "nav-index",
383
+ "nav-left", "nav-right", "nav-up", "opacity", "order", "orphans", "outline",
384
+ "outline-color", "outline-offset", "outline-style", "outline-width",
385
+ "overflow", "overflow-style", "overflow-wrap", "overflow-x", "overflow-y",
386
+ "padding", "padding-bottom", "padding-left", "padding-right", "padding-top",
387
+ "page", "page-break-after", "page-break-before", "page-break-inside",
388
+ "page-policy", "pause", "pause-after", "pause-before", "perspective",
389
+ "perspective-origin", "pitch", "pitch-range", "play-during", "position",
390
+ "presentation-level", "punctuation-trim", "quotes", "region-break-after",
391
+ "region-break-before", "region-break-inside", "region-fragment",
392
+ "rendering-intent", "resize", "rest", "rest-after", "rest-before", "richness",
393
+ "right", "rotation", "rotation-point", "ruby-align", "ruby-overhang",
394
+ "ruby-position", "ruby-span", "shape-inside", "shape-outside", "size",
395
+ "speak", "speak-as", "speak-header",
396
+ "speak-numeral", "speak-punctuation", "speech-rate", "stress", "string-set",
397
+ "tab-size", "table-layout", "target", "target-name", "target-new",
398
+ "target-position", "text-align", "text-align-last", "text-decoration",
399
+ "text-decoration-color", "text-decoration-line", "text-decoration-skip",
400
+ "text-decoration-style", "text-emphasis", "text-emphasis-color",
401
+ "text-emphasis-position", "text-emphasis-style", "text-height",
402
+ "text-indent", "text-justify", "text-outline", "text-overflow", "text-shadow",
403
+ "text-size-adjust", "text-space-collapse", "text-transform", "text-underline-position",
404
+ "text-wrap", "top", "transform", "transform-origin", "transform-style",
405
+ "transition", "transition-delay", "transition-duration",
406
+ "transition-property", "transition-timing-function", "unicode-bidi",
407
+ "vertical-align", "visibility", "voice-balance", "voice-duration",
408
+ "voice-family", "voice-pitch", "voice-range", "voice-rate", "voice-stress",
409
+ "voice-volume", "volume", "white-space", "widows", "width", "word-break",
410
+ "word-spacing", "word-wrap", "z-index", "zoom",
411
+ // SVG-specific
412
+ "clip-path", "clip-rule", "mask", "enable-background", "filter", "flood-color",
413
+ "flood-opacity", "lighting-color", "stop-color", "stop-opacity", "pointer-events",
414
+ "color-interpolation", "color-interpolation-filters", "color-profile",
415
+ "color-rendering", "fill", "fill-opacity", "fill-rule", "image-rendering",
416
+ "marker", "marker-end", "marker-mid", "marker-start", "shape-rendering", "stroke",
417
+ "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin",
418
+ "stroke-miterlimit", "stroke-opacity", "stroke-width", "text-rendering",
419
+ "baseline-shift", "dominant-baseline", "glyph-orientation-horizontal",
420
+ "glyph-orientation-vertical", "kerning", "text-anchor", "writing-mode"
421
+ ]);
422
+
423
+ var colorKeywords = keySet([
424
+ "aliceblue", "antiquewhite", "aqua", "aquamarine", "azure", "beige",
425
+ "bisque", "black", "blanchedalmond", "blue", "blueviolet", "brown",
426
+ "burlywood", "cadetblue", "chartreuse", "chocolate", "coral", "cornflowerblue",
427
+ "cornsilk", "crimson", "cyan", "darkblue", "darkcyan", "darkgoldenrod",
428
+ "darkgray", "darkgreen", "darkkhaki", "darkmagenta", "darkolivegreen",
429
+ "darkorange", "darkorchid", "darkred", "darksalmon", "darkseagreen",
430
+ "darkslateblue", "darkslategray", "darkturquoise", "darkviolet",
431
+ "deeppink", "deepskyblue", "dimgray", "dodgerblue", "firebrick",
432
+ "floralwhite", "forestgreen", "fuchsia", "gainsboro", "ghostwhite",
433
+ "gold", "goldenrod", "gray", "grey", "green", "greenyellow", "honeydew",
434
+ "hotpink", "indianred", "indigo", "ivory", "khaki", "lavender",
435
+ "lavenderblush", "lawngreen", "lemonchiffon", "lightblue", "lightcoral",
436
+ "lightcyan", "lightgoldenrodyellow", "lightgray", "lightgreen", "lightpink",
437
+ "lightsalmon", "lightseagreen", "lightskyblue", "lightslategray",
438
+ "lightsteelblue", "lightyellow", "lime", "limegreen", "linen", "magenta",
439
+ "maroon", "mediumaquamarine", "mediumblue", "mediumorchid", "mediumpurple",
440
+ "mediumseagreen", "mediumslateblue", "mediumspringgreen", "mediumturquoise",
441
+ "mediumvioletred", "midnightblue", "mintcream", "mistyrose", "moccasin",
442
+ "navajowhite", "navy", "oldlace", "olive", "olivedrab", "orange", "orangered",
443
+ "orchid", "palegoldenrod", "palegreen", "paleturquoise", "palevioletred",
444
+ "papayawhip", "peachpuff", "peru", "pink", "plum", "powderblue",
445
+ "purple", "red", "rosybrown", "royalblue", "saddlebrown", "salmon",
446
+ "sandybrown", "seagreen", "seashell", "sienna", "silver", "skyblue",
447
+ "slateblue", "slategray", "snow", "springgreen", "steelblue", "tan",
448
+ "teal", "thistle", "tomato", "turquoise", "violet", "wheat", "white",
449
+ "whitesmoke", "yellow", "yellowgreen"
450
+ ]);
451
+
452
+ var valueKeywords = keySet([
453
+ "above", "absolute", "activeborder", "activecaption", "afar",
454
+ "after-white-space", "ahead", "alias", "all", "all-scroll", "alternate",
455
+ "always", "amharic", "amharic-abegede", "antialiased", "appworkspace",
456
+ "arabic-indic", "armenian", "asterisks", "auto", "avoid", "avoid-column", "avoid-page",
457
+ "avoid-region", "background", "backwards", "baseline", "below", "bidi-override", "binary",
458
+ "bengali", "blink", "block", "block-axis", "bold", "bolder", "border", "border-box",
459
+ "both", "bottom", "break", "break-all", "break-word", "button", "button-bevel",
460
+ "buttonface", "buttonhighlight", "buttonshadow", "buttontext", "cambodian",
461
+ "capitalize", "caps-lock-indicator", "caption", "captiontext", "caret",
462
+ "cell", "center", "checkbox", "circle", "cjk-earthly-branch",
463
+ "cjk-heavenly-stem", "cjk-ideographic", "clear", "clip", "close-quote",
464
+ "col-resize", "collapse", "column", "compact", "condensed", "contain", "content",
465
+ "content-box", "context-menu", "continuous", "copy", "cover", "crop",
466
+ "cross", "crosshair", "currentcolor", "cursive", "dashed", "decimal",
467
+ "decimal-leading-zero", "default", "default-button", "destination-atop",
468
+ "destination-in", "destination-out", "destination-over", "devanagari",
469
+ "disc", "discard", "document", "dot-dash", "dot-dot-dash", "dotted",
470
+ "double", "down", "e-resize", "ease", "ease-in", "ease-in-out", "ease-out",
471
+ "element", "ellipse", "ellipsis", "embed", "end", "ethiopic", "ethiopic-abegede",
472
+ "ethiopic-abegede-am-et", "ethiopic-abegede-gez", "ethiopic-abegede-ti-er",
473
+ "ethiopic-abegede-ti-et", "ethiopic-halehame-aa-er",
474
+ "ethiopic-halehame-aa-et", "ethiopic-halehame-am-et",
475
+ "ethiopic-halehame-gez", "ethiopic-halehame-om-et",
476
+ "ethiopic-halehame-sid-et", "ethiopic-halehame-so-et",
477
+ "ethiopic-halehame-ti-er", "ethiopic-halehame-ti-et",
478
+ "ethiopic-halehame-tig", "ew-resize", "expanded", "extra-condensed",
479
+ "extra-expanded", "fantasy", "fast", "fill", "fixed", "flat", "footnotes",
480
+ "forwards", "from", "geometricPrecision", "georgian", "graytext", "groove",
481
+ "gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hebrew",
482
+ "help", "hidden", "hide", "higher", "highlight", "highlighttext",
483
+ "hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "icon", "ignore",
484
+ "inactiveborder", "inactivecaption", "inactivecaptiontext", "infinite",
485
+ "infobackground", "infotext", "inherit", "initial", "inline", "inline-axis",
486
+ "inline-block", "inline-table", "inset", "inside", "intrinsic", "invert",
487
+ "italic", "justify", "kannada", "katakana", "katakana-iroha", "keep-all", "khmer",
488
+ "landscape", "lao", "large", "larger", "left", "level", "lighter",
489
+ "line-through", "linear", "lines", "list-item", "listbox", "listitem",
490
+ "local", "logical", "loud", "lower", "lower-alpha", "lower-armenian",
491
+ "lower-greek", "lower-hexadecimal", "lower-latin", "lower-norwegian",
492
+ "lower-roman", "lowercase", "ltr", "malayalam", "match",
493
+ "media-controls-background", "media-current-time-display",
494
+ "media-fullscreen-button", "media-mute-button", "media-play-button",
495
+ "media-return-to-realtime-button", "media-rewind-button",
496
+ "media-seek-back-button", "media-seek-forward-button", "media-slider",
497
+ "media-sliderthumb", "media-time-remaining-display", "media-volume-slider",
498
+ "media-volume-slider-container", "media-volume-sliderthumb", "medium",
499
+ "menu", "menulist", "menulist-button", "menulist-text",
500
+ "menulist-textfield", "menutext", "message-box", "middle", "min-intrinsic",
501
+ "mix", "mongolian", "monospace", "move", "multiple", "myanmar", "n-resize",
502
+ "narrower", "ne-resize", "nesw-resize", "no-close-quote", "no-drop",
503
+ "no-open-quote", "no-repeat", "none", "normal", "not-allowed", "nowrap",
504
+ "ns-resize", "nw-resize", "nwse-resize", "oblique", "octal", "open-quote",
505
+ "optimizeLegibility", "optimizeSpeed", "oriya", "oromo", "outset",
506
+ "outside", "outside-shape", "overlay", "overline", "padding", "padding-box",
507
+ "painted", "page", "paused", "persian", "plus-darker", "plus-lighter", "pointer",
508
+ "polygon", "portrait", "pre", "pre-line", "pre-wrap", "preserve-3d", "progress", "push-button",
509
+ "radio", "read-only", "read-write", "read-write-plaintext-only", "rectangle", "region",
510
+ "relative", "repeat", "repeat-x", "repeat-y", "reset", "reverse", "rgb", "rgba",
511
+ "ridge", "right", "round", "row-resize", "rtl", "run-in", "running",
512
+ "s-resize", "sans-serif", "scroll", "scrollbar", "se-resize", "searchfield",
513
+ "searchfield-cancel-button", "searchfield-decoration",
514
+ "searchfield-results-button", "searchfield-results-decoration",
515
+ "semi-condensed", "semi-expanded", "separate", "serif", "show", "sidama",
516
+ "single", "skip-white-space", "slide", "slider-horizontal",
517
+ "slider-vertical", "sliderthumb-horizontal", "sliderthumb-vertical", "slow",
518
+ "small", "small-caps", "small-caption", "smaller", "solid", "somali",
519
+ "source-atop", "source-in", "source-out", "source-over", "space", "square",
520
+ "square-button", "start", "static", "status-bar", "stretch", "stroke",
521
+ "sub", "subpixel-antialiased", "super", "sw-resize", "table",
522
+ "table-caption", "table-cell", "table-column", "table-column-group",
523
+ "table-footer-group", "table-header-group", "table-row", "table-row-group",
524
+ "telugu", "text", "text-bottom", "text-top", "textarea", "textfield", "thai",
525
+ "thick", "thin", "threeddarkshadow", "threedface", "threedhighlight",
526
+ "threedlightshadow", "threedshadow", "tibetan", "tigre", "tigrinya-er",
527
+ "tigrinya-er-abegede", "tigrinya-et", "tigrinya-et-abegede", "to", "top",
528
+ "transparent", "ultra-condensed", "ultra-expanded", "underline", "up",
529
+ "upper-alpha", "upper-armenian", "upper-greek", "upper-hexadecimal",
530
+ "upper-latin", "upper-norwegian", "upper-roman", "uppercase", "urdu", "url",
531
+ "vertical", "vertical-text", "visible", "visibleFill", "visiblePainted",
532
+ "visibleStroke", "visual", "w-resize", "wait", "wave", "wider",
533
+ "window", "windowframe", "windowtext", "x-large", "x-small", "xor",
534
+ "xx-large", "xx-small"
535
+ ]);
536
+
537
+ function tokenCComment(stream, state) {
538
+ var maybeEnd = false, ch;
539
+ while ((ch = stream.next()) != null) {
540
+ if (maybeEnd && ch == "/") {
541
+ state.tokenize = null;
542
+ break;
543
+ }
544
+ maybeEnd = (ch == "*");
545
+ }
546
+ return ["comment", "comment"];
547
+ }
548
+
549
+ CodeMirror.defineMIME("text/css", {
550
+ atMediaTypes: atMediaTypes,
551
+ atMediaFeatures: atMediaFeatures,
552
+ propertyKeywords: propertyKeywords,
553
+ colorKeywords: colorKeywords,
554
+ valueKeywords: valueKeywords,
555
+ hooks: {
556
+ "<": function(stream, state) {
557
+ function tokenSGMLComment(stream, state) {
558
+ var dashes = 0, ch;
559
+ while ((ch = stream.next()) != null) {
560
+ if (dashes >= 2 && ch == ">") {
561
+ state.tokenize = null;
562
+ break;
563
+ }
564
+ dashes = (ch == "-") ? dashes + 1 : 0;
565
+ }
566
+ return ["comment", "comment"];
567
+ }
568
+ if (stream.eat("!")) {
569
+ state.tokenize = tokenSGMLComment;
570
+ return tokenSGMLComment(stream, state);
571
+ }
572
+ },
573
+ "/": function(stream, state) {
574
+ if (stream.eat("*")) {
575
+ state.tokenize = tokenCComment;
576
+ return tokenCComment(stream, state);
577
+ }
578
+ return false;
579
+ }
580
+ },
581
+ name: "css"
582
+ });
583
+
584
+ CodeMirror.defineMIME("text/x-scss", {
585
+ atMediaTypes: atMediaTypes,
586
+ atMediaFeatures: atMediaFeatures,
587
+ propertyKeywords: propertyKeywords,
588
+ colorKeywords: colorKeywords,
589
+ valueKeywords: valueKeywords,
590
+ allowNested: true,
591
+ hooks: {
592
+ ":": function(stream) {
593
+ if (stream.match(/\s*{/)) {
594
+ return [null, "{"];
595
+ }
596
+ return false;
597
+ },
598
+ "$": function(stream) {
599
+ stream.match(/^[\w-]+/);
600
+ if (stream.peek() == ":") {
601
+ return ["variable", "variable-definition"];
602
+ }
603
+ return ["variable", "variable"];
604
+ },
605
+ "/": function(stream, state) {
606
+ if (stream.eat("/")) {
607
+ stream.skipToEnd();
608
+ return ["comment", "comment"];
609
+ } else if (stream.eat("*")) {
610
+ state.tokenize = tokenCComment;
611
+ return tokenCComment(stream, state);
612
+ } else {
613
+ return ["operator", "operator"];
614
+ }
615
+ },
616
+ "#": function(stream) {
617
+ if (stream.eat("{")) {
618
+ return ["operator", "interpolation"];
619
+ } else {
620
+ stream.eatWhile(/[\w\\\-]/);
621
+ return ["atom", "hash"];
622
+ }
623
+ }
624
+ },
625
+ name: "css"
626
+ });
627
+ })();
assets/lib/codemirror/mode/css/index.html ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+
3
+ <title>CodeMirror: CSS mode</title>
4
+ <meta charset="utf-8"/>
5
+ <link rel=stylesheet href="../../doc/docs.css">
6
+
7
+ <link rel="stylesheet" href="../../lib/codemirror.css">
8
+ <script src="../../lib/codemirror.js"></script>
9
+ <script src="css.js"></script>
10
+ <style>.CodeMirror {background: #f8f8f8;}</style>
11
+ <div id=nav>
12
+ <a href="http://codemirror.net"><img id=logo src="../../doc/logo.png"></a>
13
+
14
+ <ul>
15
+ <li><a href="../../index.html">Home</a>
16
+ <li><a href="../../doc/manual.html">Manual</a>
17
+ <li><a href="https://github.com/marijnh/codemirror">Code</a>
18
+ </ul>
19
+ <ul>
20
+ <li><a href="../index.html">Language modes</a>
21
+ <li><a class=active href="#">CSS</a>
22
+ </ul>
23
+ </div>
24
+
25
+ <article>
26
+ <h2>CSS mode</h2>
27
+ <form><textarea id="code" name="code">
28
+ /* Some example CSS */
29
+
30
+ @import url("something.css");
31
+
32
+ body {
33
+ margin: 0;
34
+ padding: 3em 6em;
35
+ font-family: tahoma, arial, sans-serif;
36
+ color: #000;
37
+ }
38
+
39
+ #navigation a {
40
+ font-weight: bold;
41
+ text-decoration: none !important;
42
+ }
43
+
44
+ h1 {
45
+ font-size: 2.5em;
46
+ }
47
+
48
+ h2 {
49
+ font-size: 1.7em;
50
+ }
51
+
52
+ h1:before, h2:before {
53
+ content: "::";
54
+ }
55
+
56
+ code {
57
+ font-family: courier, monospace;
58
+ font-size: 80%;
59
+ color: #418A8A;
60
+ }
61
+ </textarea></form>
62
+ <script>
63
+ var editor = CodeMirror.fromTextArea(document.getElementById("code"), {});
64
+ </script>
65
+
66
+ <p><strong>MIME types defined:</strong> <code>text/css</code>.</p>
67
+
68
+ <p><strong>Parsing/Highlighting Tests:</strong> <a href="../../test/index.html#css_*">normal</a>, <a href="../../test/index.html#verbose,css_*">verbose</a>.</p>
69
+
70
+ </article>
assets/lib/codemirror/mode/css/scss.html ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+
3
+ <title>CodeMirror: SCSS mode</title>
4
+ <meta charset="utf-8"/>
5
+ <link rel=stylesheet href="../../doc/docs.css">
6
+
7
+ <link rel="stylesheet" href="../../lib/codemirror.css">
8
+ <script src="../../lib/codemirror.js"></script>
9
+ <script src="css.js"></script>
10
+ <style>.CodeMirror {background: #f8f8f8;}</style>
11
+ <div id=nav>
12
+ <a href="http://codemirror.net"><img id=logo src="../../doc/logo.png"></a>
13
+
14
+ <ul>
15
+ <li><a href="../../index.html">Home</a>
16
+ <li><a href="../../doc/manual.html">Manual</a>
17
+ <li><a href="https://github.com/marijnh/codemirror">Code</a>
18
+ </ul>
19
+ <ul>
20
+ <li><a href="../index.html">Language modes</a>
21
+ <li><a class=active href="#">SCSS</a>
22
+ </ul>
23
+ </div>
24
+
25
+ <article>
26
+ <h2>SCSS mode</h2>
27
+ <form><textarea id="code" name="code">
28
+ /* Some example SCSS */
29
+
30
+ @import "compass/css3";
31
+ $variable: #333;
32
+
33
+ $blue: #3bbfce;
34
+ $margin: 16px;
35
+
36
+ .content-navigation {
37
+ #nested {
38
+ background-color: black;
39
+ }
40
+ border-color: $blue;
41
+ color:
42
+ darken($blue, 9%);
43
+ }
44
+
45
+ .border {
46
+ padding: $margin / 2;
47
+ margin: $margin / 2;
48
+ border-color: $blue;
49
+ }
50
+
51
+ @mixin table-base {
52
+ th {
53
+ text-align: center;
54
+ font-weight: bold;
55
+ }
56
+ td, th {padding: 2px}
57
+ }
58
+
59
+ table.hl {
60
+ margin: 2em 0;
61
+ td.ln {
62
+ text-align: right;
63
+ }
64
+ }
65
+
66
+ li {
67
+ font: {
68
+ family: serif;
69
+ weight: bold;
70
+ size: 1.2em;
71
+ }
72
+ }
73
+
74
+ @mixin left($dist) {
75
+ float: left;
76
+ margin-left: $dist;
77
+ }
78
+
79
+ #data {
80
+ @include left(10px);
81
+ @include table-base;
82
+ }
83
+
84
+ .source {
85
+ @include flow-into(target);
86
+ border: 10px solid green;
87
+ margin: 20px;
88
+ width: 200px; }
89
+
90
+ .new-container {
91
+ @include flow-from(target);
92
+ border: 10px solid red;
93
+ margin: 20px;
94
+ width: 200px; }
95
+
96
+ body {
97
+ margin: 0;
98
+ padding: 3em 6em;
99
+ font-family: tahoma, arial, sans-serif;
100
+ color: #000;
101
+ }
102
+
103
+ @mixin yellow() {
104
+ background: yellow;
105
+ }
106
+
107
+ .big {
108
+ font-size: 14px;
109
+ }
110
+
111
+ .nested {
112
+ @include border-radius(3px);
113
+ @extend .big;
114
+ p {
115
+ background: whitesmoke;
116
+ a {
117
+ color: red;
118
+ }
119
+ }
120
+ }
121
+
122
+ #navigation a {
123
+ font-weight: bold;
124
+ text-decoration: none !important;
125
+ }
126
+
127
+ h1 {
128
+ font-size: 2.5em;
129
+ }
130
+
131
+ h2 {
132
+ font-size: 1.7em;
133
+ }
134
+
135
+ h1:before, h2:before {
136
+ content: "::";
137
+ }
138
+
139
+ code {
140
+ font-family: courier, monospace;
141
+ font-size: 80%;
142
+ color: #418A8A;
143
+ }
144
+ </textarea></form>
145
+ <script>
146
+ var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
147
+ lineNumbers: true,
148
+ matchBrackets: true,
149
+ mode: "text/x-scss"
150
+ });
151
+ </script>
152
+
153
+ <p><strong>MIME types defined:</strong> <code>text/scss</code>.</p>
154
+
155
+ <p><strong>Parsing/Highlighting Tests:</strong> <a href="../../test/index.html#scss_*">normal</a>, <a href="../../test/index.html#verbose,scss_*">verbose</a>.</p>
156
+
157
+ </article>
assets/lib/codemirror/mode/css/scss_test.js ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function() {
2
+ var mode = CodeMirror.getMode({tabSize: 4}, "text/x-scss");
3
+ function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1), "scss"); }
4
+
5
+ MT('url_with_quotation',
6
+ "[tag foo] { [property background][operator :][string-2 url]([string test.jpg]) }");
7
+
8
+ MT('url_with_double_quotes',
9
+ "[tag foo] { [property background][operator :][string-2 url]([string \"test.jpg\"]) }");
10
+
11
+ MT('url_with_single_quotes',
12
+ "[tag foo] { [property background][operator :][string-2 url]([string \'test.jpg\']) }");
13
+
14
+ MT('string',
15
+ "[def @import] [string \"compass/css3\"]");
16
+
17
+ MT('important_keyword',
18
+ "[tag foo] { [property background][operator :][string-2 url]([string \'test.jpg\']) [keyword !important] }");
19
+
20
+ MT('variable',
21
+ "[variable-2 $blue][operator :][atom #333]");
22
+
23
+ MT('variable_as_attribute',
24
+ "[tag foo] { [property color][operator :][variable-2 $blue] }");
25
+
26
+ MT('numbers',
27
+ "[tag foo] { [property padding][operator :][number 10px] [number 10] [number 10em] [number 8in] }");
28
+
29
+ MT('number_percentage',
30
+ "[tag foo] { [property width][operator :][number 80%] }");
31
+
32
+ MT('selector',
33
+ "[builtin #hello][qualifier .world]{}");
34
+
35
+ MT('singleline_comment',
36
+ "[comment // this is a comment]");
37
+
38
+ MT('multiline_comment',
39
+ "[comment /*foobar*/]");
40
+
41
+ MT('attribute_with_hyphen',
42
+ "[tag foo] { [property font-size][operator :][number 10px] }");
43
+
44
+ MT('string_after_attribute',
45
+ "[tag foo] { [property content][operator :][string \"::\"] }");
46
+
47
+ MT('directives',
48
+ "[def @include] [qualifier .mixin]");
49
+
50
+ MT('basic_structure',
51
+ "[tag p] { [property background][operator :][keyword red]; }");
52
+
53
+ MT('nested_structure',
54
+ "[tag p] { [tag a] { [property color][operator :][keyword red]; } }");
55
+
56
+ MT('mixin',
57
+ "[def @mixin] [tag table-base] {}");
58
+
59
+ MT('number_without_semicolon',
60
+ "[tag p] {[property width][operator :][number 12]}",
61
+ "[tag a] {[property color][operator :][keyword red];}");
62
+
63
+ MT('atom_in_nested_block',
64
+ "[tag p] { [tag a] { [property color][operator :][atom #000]; } }");
65
+
66
+ MT('interpolation_in_property',
67
+ "[tag foo] { [operator #{][variable-2 $hello][operator }:][number 2]; }");
68
+
69
+ MT('interpolation_in_selector',
70
+ "[tag foo][operator #{][variable-2 $hello][operator }] { [property color][operator :][atom #000]; }");
71
+
72
+ MT('interpolation_error',
73
+ "[tag foo][operator #{][error foo][operator }] { [property color][operator :][atom #000]; }");
74
+
75
+ MT("divide_operator",
76
+ "[tag foo] { [property width][operator :][number 4] [operator /] [number 2] }");
77
+
78
+ MT('nested_structure_with_id_selector',
79
+ "[tag p] { [builtin #hello] { [property color][operator :][keyword red]; } }");
80
+ })();
assets/lib/codemirror/mode/css/test.js ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function() {
2
+ var mode = CodeMirror.getMode({tabSize: 4}, "css");
3
+ function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); }
4
+
5
+ // Requires at least one media query
6
+ MT("atMediaEmpty",
7
+ "[def @media] [error {] }");
8
+
9
+ MT("atMediaMultiple",
10
+ "[def @media] [keyword not] [attribute screen] [operator and] ([property color]), [keyword not] [attribute print] [operator and] ([property color]) { }");
11
+
12
+ MT("atMediaCheckStack",
13
+ "[def @media] [attribute screen] { } [tag foo] { }");
14
+
15
+ MT("atMediaCheckStack",
16
+ "[def @media] [attribute screen] ([property color]) { } [tag foo] { }");
17
+
18
+ MT("atMediaPropertyOnly",
19
+ "[def @media] ([property color]) { } [tag foo] { }");
20
+
21
+ MT("atMediaCheckStackInvalidAttribute",
22
+ "[def @media] [attribute&error foobarhello] { [tag foo] { } }");
23
+
24
+ MT("atMediaCheckStackInvalidAttribute",
25
+ "[def @media] [attribute&error foobarhello] { } [tag foo] { }");
26
+
27
+ // Error, because "and" is only allowed immediately preceding a media expression
28
+ MT("atMediaInvalidAttribute",
29
+ "[def @media] [attribute&error foobarhello] { }");
30
+
31
+ // Error, because "and" is only allowed immediately preceding a media expression
32
+ MT("atMediaInvalidAnd",
33
+ "[def @media] [error and] [attribute screen] { }");
34
+
35
+ // Error, because "not" is only allowed as the first item in each media query
36
+ MT("atMediaInvalidNot",
37
+ "[def @media] [attribute screen] [error not] ([error not]) { }");
38
+
39
+ // Error, because "only" is only allowed as the first item in each media query
40
+ MT("atMediaInvalidOnly",
41
+ "[def @media] [attribute screen] [error only] ([error only]) { }");
42
+
43
+ // Error, because "foobarhello" is neither a known type or property, but
44
+ // property was expected (after "and"), and it should be in parenthese.
45
+ MT("atMediaUnknownType",
46
+ "[def @media] [attribute screen] [operator and] [error foobarhello] { }");
47
+
48
+ // Error, because "color" is not a known type, but is a known property, and
49
+ // should be in parentheses.
50
+ MT("atMediaInvalidType",
51
+ "[def @media] [attribute screen] [operator and] [error color] { }");
52
+
53
+ // Error, because "print" is not a known property, but is a known type,
54
+ // and should not be in parenthese.
55
+ MT("atMediaInvalidProperty",
56
+ "[def @media] [attribute screen] [operator and] ([error print]) { }");
57
+
58
+ // Soft error, because "foobarhello" is not a known property or type.
59
+ MT("atMediaUnknownProperty",
60
+ "[def @media] [attribute screen] [operator and] ([property&error foobarhello]) { }");
61
+
62
+ // Make sure nesting works with media queries
63
+ MT("atMediaMaxWidthNested",
64
+ "[def @media] [attribute screen] [operator and] ([property max-width][operator :] [number 25px]) { [tag foo] { } }");
65
+
66
+ MT("tagSelector",
67
+ "[tag foo] { }");
68
+
69
+ MT("classSelector",
70
+ "[qualifier .foo-bar_hello] { }");
71
+
72
+ MT("idSelector",
73
+ "[builtin #foo] { [error #foo] }");
74
+
75
+ MT("tagSelectorUnclosed",
76
+ "[tag foo] { [property margin][operator :] [number 0] } [tag bar] { }");
77
+
78
+ MT("tagStringNoQuotes",
79
+ "[tag foo] { [property font-family][operator :] [variable-2 hello] [variable-2 world]; }");
80
+
81
+ MT("tagStringDouble",
82
+ "[tag foo] { [property font-family][operator :] [string \"hello world\"]; }");
83
+
84
+ MT("tagStringSingle",
85
+ "[tag foo] { [property font-family][operator :] [string 'hello world']; }");
86
+
87
+ MT("tagColorKeyword",
88
+ "[tag foo] {" +
89
+ "[property color][operator :] [keyword black];" +
90
+ "[property color][operator :] [keyword navy];" +
91
+ "[property color][operator :] [keyword yellow];" +
92
+ "}");
93
+
94
+ MT("tagColorHex3",
95
+ "[tag foo] { [property background][operator :] [atom #fff]; }");
96
+
97
+ MT("tagColorHex6",
98
+ "[tag foo] { [property background][operator :] [atom #ffffff]; }");
99
+
100
+ MT("tagColorHex4",
101
+ "[tag foo] { [property background][operator :] [atom&error #ffff]; }");
102
+
103
+ MT("tagColorHexInvalid",
104
+ "[tag foo] { [property background][operator :] [atom&error #ffg]; }");
105
+
106
+ MT("tagNegativeNumber",
107
+ "[tag foo] { [property margin][operator :] [number -5px]; }");
108
+
109
+ MT("tagPositiveNumber",
110
+ "[tag foo] { [property padding][operator :] [number 5px]; }");
111
+
112
+ MT("tagVendor",
113
+ "[tag foo] { [meta -foo-][property box-sizing][operator :] [meta -foo-][string-2 border-box]; }");
114
+
115
+ MT("tagBogusProperty",
116
+ "[tag foo] { [property&error barhelloworld][operator :] [number 0]; }");
117
+
118
+ MT("tagTwoProperties",
119
+ "[tag foo] { [property margin][operator :] [number 0]; [property padding][operator :] [number 0]; }");
120
+
121
+ MT("tagTwoPropertiesURL",
122
+ "[tag foo] { [property background][operator :] [string-2 url]([string //example.com/foo.png]); [property padding][operator :] [number 0]; }");
123
+
124
+ MT("commentSGML",
125
+ "[comment <!--comment-->]");
126
+ })();
assets/lib/codemirror/mode/htmlembedded/htmlembedded.js ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ CodeMirror.defineMode("htmlembedded", function(config, parserConfig) {
2
+
3
+ //config settings
4
+ var scriptStartRegex = parserConfig.scriptStartRegex || /^<%/i,
5
+ scriptEndRegex = parserConfig.scriptEndRegex || /^%>/i;
6
+
7
+ //inner modes
8
+ var scriptingMode, htmlMixedMode;
9
+
10
+ //tokenizer when in html mode
11
+ function htmlDispatch(stream, state) {
12
+ if (stream.match(scriptStartRegex, false)) {
13
+ state.token=scriptingDispatch;
14
+ return scriptingMode.token(stream, state.scriptState);
15
+ }
16
+ else
17
+ return htmlMixedMode.token(stream, state.htmlState);
18
+ }
19
+
20
+ //tokenizer when in scripting mode
21
+ function scriptingDispatch(stream, state) {
22
+ if (stream.match(scriptEndRegex, false)) {
23
+ state.token=htmlDispatch;
24
+ return htmlMixedMode.token(stream, state.htmlState);
25
+ }
26
+ else
27
+ return scriptingMode.token(stream, state.scriptState);
28
+ }
29
+
30
+
31
+ return {
32
+ startState: function() {
33
+ scriptingMode = scriptingMode || CodeMirror.getMode(config, parserConfig.scriptingModeSpec);
34
+ htmlMixedMode = htmlMixedMode || CodeMirror.getMode(config, "htmlmixed");
35
+ return {
36
+ token : parserConfig.startOpen ? scriptingDispatch : htmlDispatch,
37
+ htmlState : CodeMirror.startState(htmlMixedMode),
38
+ scriptState : CodeMirror.startState(scriptingMode)
39
+ };
40
+ },
41
+
42
+ token: function(stream, state) {
43
+ return state.token(stream, state);
44
+ },
45
+
46
+ indent: function(state, textAfter) {
47
+ if (state.token == htmlDispatch)
48
+ return htmlMixedMode.indent(state.htmlState, textAfter);
49
+ else if (scriptingMode.indent)
50
+ return scriptingMode.indent(state.scriptState, textAfter);
51
+ },
52
+
53
+ copyState: function(state) {
54
+ return {
55
+ token : state.token,
56
+ htmlState : CodeMirror.copyState(htmlMixedMode, state.htmlState),
57
+ scriptState : CodeMirror.copyState(scriptingMode, state.scriptState)
58
+ };
59
+ },
60
+
61
+ electricChars: "/{}:",
62
+
63
+ innerMode: function(state) {
64
+ if (state.token == scriptingDispatch) return {state: state.scriptState, mode: scriptingMode};
65
+ else return {state: state.htmlState, mode: htmlMixedMode};
66
+ }
67
+ };
68
+ }, "htmlmixed");
69
+
70
+ CodeMirror.defineMIME("application/x-ejs", { name: "htmlembedded", scriptingModeSpec:"javascript"});
71
+ CodeMirror.defineMIME("application/x-aspx", { name: "htmlembedded", scriptingModeSpec:"text/x-csharp"});
72
+ CodeMirror.defineMIME("application/x-jsp", { name: "htmlembedded", scriptingModeSpec:"text/x-java"});
73
+ CodeMirror.defineMIME("application/x-erb", { name: "htmlembedded", scriptingModeSpec:"ruby"});
assets/lib/codemirror/mode/htmlembedded/index.html ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+
3
+ <title>CodeMirror: Html Embedded Scripts mode</title>
4
+ <meta charset="utf-8"/>
5
+ <link rel=stylesheet href="../../doc/docs.css">
6
+
7
+ <link rel="stylesheet" href="../../lib/codemirror.css">
8
+ <script src="../../lib/codemirror.js"></script>
9
+ <script src="../xml/xml.js"></script>
10
+ <script src="../javascript/javascript.js"></script>
11
+ <script src="../css/css.js"></script>
12
+ <script src="../htmlmixed/htmlmixed.js"></script>
13
+ <script src="htmlembedded.js"></script>
14
+ <style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
15
+ <div id=nav>
16
+ <a href="http://codemirror.net"><img id=logo src="../../doc/logo.png"></a>
17
+
18
+ <ul>
19
+ <li><a href="../../index.html">Home</a>
20
+ <li><a href="../../doc/manual.html">Manual</a>
21
+ <li><a href="https://github.com/marijnh/codemirror">Code</a>
22
+ </ul>
23
+ <ul>
24
+ <li><a href="../index.html">Language modes</a>
25
+ <li><a class=active href="#">Html Embedded Scripts</a>
26
+ </ul>
27
+ </div>
28
+
29
+ <article>
30
+ <h2>Html Embedded Scripts mode</h2>
31
+ <form><textarea id="code" name="code">
32
+ <%
33
+ function hello(who) {
34
+ return "Hello " + who;
35
+ }
36
+ %>
37
+ This is an example of EJS (embedded javascript)
38
+ <p>The program says <%= hello("world") %>.</p>
39
+ <script>
40
+ alert("And here is some normal JS code"); // also colored
41
+ </script>
42
+ </textarea></form>
43
+
44
+ <script>
45
+ var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
46
+ lineNumbers: true,
47
+ mode: "application/x-ejs",
48
+ indentUnit: 4,
49
+ indentWithTabs: true,
50
+ enterMode: "keep",
51
+ tabMode: "shift"
52
+ });
53
+ </script>
54
+
55
+ <p>Mode for html embedded scripts like JSP and ASP.NET. Depends on HtmlMixed which in turn depends on
56
+ JavaScript, CSS and XML.<br />Other dependancies include those of the scriping language chosen.</p>
57
+
58
+ <p><strong>MIME types defined:</strong> <code>application/x-aspx</code> (ASP.NET),
59
+ <code>application/x-ejs</code> (Embedded Javascript), <code>application/x-jsp</code> (JavaServer Pages)</p>
60
+ </article>
assets/lib/codemirror/mode/htmlmixed/htmlmixed.js ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ CodeMirror.defineMode("htmlmixed", function(config, parserConfig) {
2
+ var htmlMode = CodeMirror.getMode(config, {name: "xml", htmlMode: true});
3
+ var cssMode = CodeMirror.getMode(config, "css");
4
+
5
+ var scriptTypes = [], scriptTypesConf = parserConfig && parserConfig.scriptTypes;
6
+ scriptTypes.push({matches: /^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^$/i,
7
+ mode: CodeMirror.getMode(config, "javascript")});
8
+ if (scriptTypesConf) for (var i = 0; i < scriptTypesConf.length; ++i) {
9
+ var conf = scriptTypesConf[i];
10
+ scriptTypes.push({matches: conf.matches, mode: conf.mode && CodeMirror.getMode(config, conf.mode)});
11
+ }
12
+ scriptTypes.push({matches: /./,
13
+ mode: CodeMirror.getMode(config, "text/plain")});
14
+
15
+ function html(stream, state) {
16
+ var tagName = state.htmlState.tagName;
17
+ var style = htmlMode.token(stream, state.htmlState);
18
+ if (tagName == "script" && /\btag\b/.test(style) && stream.current() == ">") {
19
+ // Script block: mode to change to depends on type attribute
20
+ var scriptType = stream.string.slice(Math.max(0, stream.pos - 100), stream.pos).match(/\btype\s*=\s*("[^"]+"|'[^']+'|\S+)[^<]*$/i);
21
+ scriptType = scriptType ? scriptType[1] : "";
22
+ if (scriptType && /[\"\']/.test(scriptType.charAt(0))) scriptType = scriptType.slice(1, scriptType.length - 1);
23
+ for (var i = 0; i < scriptTypes.length; ++i) {
24
+ var tp = scriptTypes[i];
25
+ if (typeof tp.matches == "string" ? scriptType == tp.matches : tp.matches.test(scriptType)) {
26
+ if (tp.mode) {
27
+ state.token = script;
28
+ state.localMode = tp.mode;
29
+ state.localState = tp.mode.startState && tp.mode.startState(htmlMode.indent(state.htmlState, ""));
30
+ }
31
+ break;
32
+ }
33
+ }
34
+ } else if (tagName == "style" && /\btag\b/.test(style) && stream.current() == ">") {
35
+ state.token = css;
36
+ state.localMode = cssMode;
37
+ state.localState = cssMode.startState(htmlMode.indent(state.htmlState, ""));
38
+ }
39
+ return style;
40
+ }
41
+ function maybeBackup(stream, pat, style) {
42
+ var cur = stream.current();
43
+ var close = cur.search(pat), m;
44
+ if (close > -1) stream.backUp(cur.length - close);
45
+ else if (m = cur.match(/<\/?$/)) {
46
+ stream.backUp(cur.length);
47
+ if (!stream.match(pat, false)) stream.match(cur[0]);
48
+ }
49
+ return style;
50
+ }
51
+ function script(stream, state) {
52
+ if (stream.match(/^<\/\s*script\s*>/i, false)) {
53
+ state.token = html;
54
+ state.localState = state.localMode = null;
55
+ return html(stream, state);
56
+ }
57
+ return maybeBackup(stream, /<\/\s*script\s*>/,
58
+ state.localMode.token(stream, state.localState));
59
+ }
60
+ function css(stream, state) {
61
+ if (stream.match(/^<\/\s*style\s*>/i, false)) {
62
+ state.token = html;
63
+ state.localState = state.localMode = null;
64
+ return html(stream, state);
65
+ }
66
+ return maybeBackup(stream, /<\/\s*style\s*>/,
67
+ cssMode.token(stream, state.localState));
68
+ }
69
+
70
+ return {
71
+ startState: function() {
72
+ var state = htmlMode.startState();
73
+ return {token: html, localMode: null, localState: null, htmlState: state};
74
+ },
75
+
76
+ copyState: function(state) {
77
+ if (state.localState)
78
+ var local = CodeMirror.copyState(state.localMode, state.localState);
79
+ return {token: state.token, localMode: state.localMode, localState: local,
80
+ htmlState: CodeMirror.copyState(htmlMode, state.htmlState)};
81
+ },
82
+
83
+ token: function(stream, state) {
84
+ return state.token(stream, state);
85
+ },
86
+
87
+ indent: function(state, textAfter) {
88
+ if (!state.localMode || /^\s*<\//.test(textAfter))
89
+ return htmlMode.indent(state.htmlState, textAfter);
90
+ else if (state.localMode.indent)
91
+ return state.localMode.indent(state.localState, textAfter);
92
+ else
93
+ return CodeMirror.Pass;
94
+ },
95
+
96
+ electricChars: "/{}:",
97
+
98
+ innerMode: function(state) {
99
+ return {state: state.localState || state.htmlState, mode: state.localMode || htmlMode};
100
+ }
101
+ };
102
+ }, "xml", "javascript", "css");
103
+
104
+ CodeMirror.defineMIME("text/html", "htmlmixed");
assets/lib/codemirror/mode/htmlmixed/index.html ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+
3
+ <title>CodeMirror: HTML mixed mode</title>
4
+ <meta charset="utf-8"/>
5
+ <link rel=stylesheet href="../../doc/docs.css">
6
+
7
+ <link rel="stylesheet" href="../../lib/codemirror.css">
8
+ <script src="../../lib/codemirror.js"></script>
9
+ <script src="../xml/xml.js"></script>
10
+ <script src="../javascript/javascript.js"></script>
11
+ <script src="../css/css.js"></script>
12
+ <script src="../vbscript/vbscript.js"></script>
13
+ <script src="htmlmixed.js"></script>
14
+ <style>.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
15
+ <div id=nav>
16
+ <a href="http://codemirror.net"><img id=logo src="../../doc/logo.png"></a>
17
+
18
+ <ul>
19
+ <li><a href="../../index.html">Home</a>
20
+ <li><a href="../../doc/manual.html">Manual</a>
21
+ <li><a href="https://github.com/marijnh/codemirror">Code</a>
22
+ </ul>
23
+ <ul>
24
+ <li><a href="../index.html">Language modes</a>
25
+ <li><a class=active href="#">HTML mixed</a>
26
+ </ul>
27
+ </div>
28
+
29
+ <article>
30
+ <h2>HTML mixed mode</h2>
31
+ <form><textarea id="code" name="code">
32
+ <html style="color: green">
33
+ <!-- this is a comment -->
34
+ <head>
35
+ <title>Mixed HTML Example</title>
36
+ <style type="text/css">
37
+ h1 {font-family: comic sans; color: #f0f;}
38
+ div {background: yellow !important;}
39
+ body {
40
+ max-width: 50em;
41
+ margin: 1em 2em 1em 5em;
42
+ }
43
+ </style>
44
+ </head>
45
+ <body>
46
+ <h1>Mixed HTML Example</h1>
47
+ <script>
48
+ function jsFunc(arg1, arg2) {
49
+ if (arg1 && arg2) document.body.innerHTML = "achoo";
50
+ }
51
+ </script>
52
+ </body>
53
+ </html>
54
+ </textarea></form>
55
+ <script>
56
+ // Define an extended mixed-mode that understands vbscript and
57
+ // leaves mustache/handlebars embedded templates in html mode
58
+ var mixedMode = {
59
+ name: "htmlmixed",
60
+ scriptTypes: [{matches: /\/x-handlebars-template|\/x-mustache/i,
61
+ mode: null},
62
+ {matches: /(text|application)\/(x-)?vb(a|script)/i,
63
+ mode: "vbscript"}]
64
+ };
65
+ var editor = CodeMirror.fromTextArea(document.getElementById("code"), {mode: mixedMode, tabMode: "indent"});
66
+ </script>
67
+
68
+ <p>The HTML mixed mode depends on the XML, JavaScript, and CSS modes.</p>
69
+
70
+ <p>It takes an optional mode configuration
71
+ option, <code>scriptTypes</code>, which can be used to add custom
72
+ behavior for specific <code>&lt;script type="..."></code> tags. If
73
+ given, it should hold an array of <code>{matches, mode}</code>
74
+ objects, where <code>matches</code> is a string or regexp that
75
+ matches the script type, and <code>mode</code> is
76
+ either <code>null</code>, for script types that should stay in
77
+ HTML mode, or a <a href="../../doc/manual.html#option_mode">mode
78
+ spec</a> corresponding to the mode that should be used for the
79
+ script.</p>
80
+
81
+ <p><strong>MIME types defined:</strong> <code>text/html</code>
82
+ (redefined, only takes effect if you load this parser after the
83
+ XML parser).</p>
84
+
85
+ </article>
assets/lib/codemirror/mode/http/http.js ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ CodeMirror.defineMode("http", function() {
2
+ function failFirstLine(stream, state) {
3
+ stream.skipToEnd();
4
+ state.cur = header;
5
+ return "error";
6
+ }
7
+
8
+ function start(stream, state) {
9
+ if (stream.match(/^HTTP\/\d\.\d/)) {
10
+ state.cur = responseStatusCode;
11
+ return "keyword";
12
+ } else if (stream.match(/^[A-Z]+/) && /[ \t]/.test(stream.peek())) {
13
+ state.cur = requestPath;
14
+ return "keyword";
15
+ } else {
16
+ return failFirstLine(stream, state);
17
+ }
18
+ }
19
+
20
+ function responseStatusCode(stream, state) {
21
+ var code = stream.match(/^\d+/);
22
+ if (!code) return failFirstLine(stream, state);
23
+
24
+ state.cur = responseStatusText;
25
+ var status = Number(code[0]);
26
+ if (status >= 100 && status < 200) {
27
+ return "positive informational";
28
+ } else if (status >= 200 && status < 300) {
29
+ return "positive success";
30
+ } else if (status >= 300 && status < 400) {
31
+ return "positive redirect";
32
+ } else if (status >= 400 && status < 500) {
33
+ return "negative client-error";
34
+ } else if (status >= 500 && status < 600) {
35
+ return "negative server-error";
36
+ } else {
37
+ return "error";
38
+ }
39
+ }
40
+
41
+ function responseStatusText(stream, state) {
42
+ stream.skipToEnd();
43
+ state.cur = header;
44
+ return null;
45
+ }
46
+
47
+ function requestPath(stream, state) {
48
+ stream.eatWhile(/\S/);
49
+ state.cur = requestProtocol;
50
+ return "string-2";
51
+ }
52
+
53
+ function requestProtocol(stream, state) {
54
+ if (stream.match(/^HTTP\/\d\.\d$/)) {
55
+ state.cur = header;
56
+ return "keyword";
57
+ } else {
58
+ return failFirstLine(stream, state);
59
+ }
60
+ }
61
+
62
+ function header(stream) {
63
+ if (stream.sol() && !stream.eat(/[ \t]/)) {
64
+ if (stream.match(/^.*?:/)) {
65
+ return "atom";
66
+ } else {
67
+ stream.skipToEnd();
68
+ return "error";
69
+ }
70
+ } else {
71
+ stream.skipToEnd();
72
+ return "string";
73
+ }
74
+ }
75
+
76
+ function body(stream) {
77
+ stream.skipToEnd();
78
+ return null;
79
+ }
80
+
81
+ return {
82
+ token: function(stream, state) {
83
+ var cur = state.cur;
84
+ if (cur != header && cur != body && stream.eatSpace()) return null;
85
+ return cur(stream, state);
86
+ },
87
+
88
+ blankLine: function(state) {
89
+ state.cur = body;
90
+ },
91
+
92
+ startState: function() {
93
+ return {cur: start};
94
+ }
95
+ };
96
+ });
97
+
98
+ CodeMirror.defineMIME("message/http", "http");
assets/lib/codemirror/mode/http/index.html ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+
3
+ <title>CodeMirror: HTTP mode</title>
4
+ <meta charset="utf-8"/>
5
+ <link rel=stylesheet href="../../doc/docs.css">
6
+
7
+ <link rel="stylesheet" href="../../lib/codemirror.css">
8
+ <script src="../../lib/codemirror.js"></script>
9
+ <script src="http.js"></script>
10
+ <style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
11
+ <div id=nav>
12
+ <a href="http://codemirror.net"><img id=logo src="../../doc/logo.png"></a>
13
+
14
+ <ul>
15
+ <li><a href="../../index.html">Home</a>
16
+ <li><a href="../../doc/manual.html">Manual</a>
17
+ <li><a href="https://github.com/marijnh/codemirror">Code</a>
18
+ </ul>
19
+ <ul>
20
+ <li><a href="../index.html">Language modes</a>
21
+ <li><a class=active href="#">HTTP</a>
22
+ </ul>
23
+ </div>
24
+
25
+ <article>
26
+ <h2>HTTP mode</h2>
27
+
28
+
29
+ <div><textarea id="code" name="code">
30
+ POST /somewhere HTTP/1.1
31
+ Host: example.com
32
+ If-Modified-Since: Sat, 29 Oct 1994 19:43:31 GMT
33
+ Content-Type: application/x-www-form-urlencoded;
34
+ charset=utf-8
35
+ User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.11 (KHTML, like Gecko) Ubuntu/12.04 Chromium/20.0.1132.47 Chrome/20.0.1132.47 Safari/536.11
36
+
37
+ This is the request body!
38
+ </textarea></div>
39
+
40
+ <script>
41
+ var editor = CodeMirror.fromTextArea(document.getElementById("code"), {});
42
+ </script>
43
+
44
+ <p><strong>MIME types defined:</strong> <code>message/http</code>.</p>
45
+ </article>
assets/lib/codemirror/mode/index.html ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+
3
+ <title>CodeMirror: Language Modes</title>
4
+ <meta charset="utf-8"/>
5
+ <link rel=stylesheet href="../doc/docs.css">
6
+
7
+ <div id=nav>
8
+ <a href="http://codemirror.net"><img id=logo src="../doc/logo.png"></a>
9
+
10
+ <ul>
11
+ <li><a href="../index.html">Home</a>
12
+ <li><a href="../doc/manual.html">Manual</a>
13
+ <li><a href="https://github.com/marijnh/codemirror">Code</a>
14
+ </ul>
15
+ <ul>
16
+ <li><a class=active href="#">Language modes</a>
17
+ </ul>
18
+ </div>
19
+
20
+ <article>
21
+
22
+ <h2>Language modes</h2>
23
+
24
+ <p>This is a list of every mode in the distribution. Each mode lives
25
+ in a subdirectory of the <code>mode/</code> directory, and typically
26
+ defines a single JavaScript file that implements the mode. Loading
27
+ such file will make the language available to CodeMirror, through
28
+ the <a href="manual.html#option_mode"><code>mode</code></a>
29
+ option.</p>
30
+
31
+ <div style="-webkit-columns: 100px 2; -moz-columns: 100px 2; columns: 100px 2;">
32
+ <ul>
33
+ <li><a href="apl/index.html">APL</a></li>
34
+ <li><a href="asterisk/index.html">Asterisk dialplan</a></li>
35
+ <li><a href="clike/index.html">C, C++, C#</a></li>
36
+ <li><a href="clojure/index.html">Clojure</a></li>
37
+ <li><a href="cobol/index.html">COBOL</a></li>
38
+ <li><a href="coffeescript/index.html">CoffeeScript</a></li>
39
+ <li><a href="commonlisp/index.html">Common Lisp</a></li>
40
+ <li><a href="css/index.html">CSS</a></li>
41
+ <li><a href="python/index.html">Cython</a></li>
42
+ <li><a href="d/index.html">D</a></li>
43
+ <li><a href="diff/index.html">diff</a></li>
44
+ <li><a href="dtd/index.html">DTD</a></li>
45
+ <li><a href="ecl/index.html">ECL</a></li>
46
+ <li><a href="erlang/index.html">Erlang</a></li>
47
+ <li><a href="fortran/index.html">Fortran</a></li>
48
+ <li><a href="gas/index.html">Gas</a> (AT&amp;T-style assembly)</li>
49
+ <li><a href="go/index.html">Go</a></li>
50
+ <li><a href="groovy/index.html">Groovy</a></li>
51
+ <li><a href="haml/index.html">HAML</a></li>
52
+ <li><a href="haskell/index.html">Haskell</a></li>
53
+ <li><a href="haxe/index.html">Haxe</a></li>
54
+ <li><a href="htmlembedded/index.html">HTML embedded scripts</a></li>
55
+ <li><a href="htmlmixed/index.html">HTML mixed-mode</a></li>
56
+ <li><a href="http/index.html">HTTP</a></li>
57
+ <li><a href="clike/index.html">Java</a></li>
58
+ <li><a href="jade/index.html">Jade</a></li>
59
+ <li><a href="javascript/index.html">JavaScript</a></li>
60
+ <li><a href="jinja2/index.html">Jinja2</a></li>
61
+ <li><a href="less/index.html">LESS</a></li>
62
+ <li><a href="livescript/index.html">LiveScript</a></li>
63
+ <li><a href="lua/index.html">Lua</a></li>
64
+ <li><a href="markdown/index.html">Markdown</a> (<a href="gfm/index.html">GitHub-flavour</a>)</li>
65
+ <li><a href="mirc/index.html">mIRC</a></li>
66
+ <li><a href="nginx/index.html">Nginx</a></li>
67
+ <li><a href="ntriples/index.html">NTriples</a></li>
68
+ <li><a href="ocaml/index.html">OCaml</a></li>
69
+ <li><a href="octave/index.html">Octave</a> (MATLAB)</li>
70
+ <li><a href="pascal/index.html">Pascal</a></li>
71
+ <li><a href="perl/index.html">Perl</a></li>
72
+ <li><a href="php/index.html">PHP</a></li>
73
+ <li><a href="pig/index.html">Pig Latin</a></li>
74
+ <li><a href="properties/index.html">Properties files</a></li>
75
+ <li><a href="python/index.html">Python</a></li>
76
+ <li><a href="q/index.html">Q</a></li>
77
+ <li><a href="r/index.html">R</a></li>
78
+ <li>RPM <a href="rpm/spec/index.html">spec</a> and <a href="rpm/changes/index.html">changelog</a></li>
79
+ <li><a href="rst/index.html">reStructuredText</a></li>
80
+ <li><a href="ruby/index.html">Ruby</a></li>
81
+ <li><a href="rust/index.html">Rust</a></li>
82
+ <li><a href="sass/index.html">Sass</a></li>
83
+ <li><a href="clike/scala.html">Scala</a></li>
84
+ <li><a href="scheme/index.html">Scheme</a></li>
85
+ <li><a href="css/scss.html">SCSS</a></li>
86
+ <li><a href="shell/index.html">Shell</a></li>
87
+ <li><a href="sieve/index.html">Sieve</a></li>
88
+ <li><a href="smalltalk/index.html">Smalltalk</a></li>
89
+ <li><a href="smarty/index.html">Smarty</a></li>
90
+ <li><a href="smartymixed/index.html">Smarty/HTML mixed</a></li>
91
+ <li><a href="sql/index.html">SQL</a> (several dialects)</li>
92
+ <li><a href="sparql/index.html">SPARQL</a></li>
93
+ <li><a href="stex/index.html">sTeX, LaTeX</a></li>
94
+ <li><a href="tcl/index.html">Tcl</a></li>
95
+ <li><a href="tiddlywiki/index.html">Tiddlywiki</a></li>
96
+ <li><a href="tiki/index.html">Tiki wiki</a></li>
97
+ <li><a href="toml/index.html">TOML</a></li>
98
+ <li><a href="turtle/index.html">Turtle</a></li>
99
+ <li><a href="vb/index.html">VB.NET</a></li>
100
+ <li><a href="vbscript/index.html">VBScript</a></li>
101
+ <li><a href="velocity/index.html">Velocity</a></li>
102
+ <li><a href="verilog/index.html">Verilog</a></li>
103
+ <li><a href="xml/index.html">XML/HTML</a></li>
104
+ <li><a href="xquery/index.html">XQuery</a></li>
105
+ <li><a href="yaml/index.html">YAML</a></li>
106
+ <li><a href="z80/index.html">Z80</a></li>
107
+ </ul>
108
+ </div>
109
+
110
+ </article>
assets/lib/codemirror/mode/javascript/index.html ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+
3
+ <title>CodeMirror: JavaScript mode</title>
4
+ <meta charset="utf-8"/>
5
+ <link rel=stylesheet href="../../doc/docs.css">
6
+
7
+ <link rel="stylesheet" href="../../lib/codemirror.css">
8
+ <script src="../../lib/codemirror.js"></script>
9
+ <script src="../../addon/edit/matchbrackets.js"></script>
10
+ <script src="../../addon/comment/continuecomment.js"></script>
11
+ <script src="../../addon/comment/comment.js"></script>
12
+ <script src="javascript.js"></script>
13
+ <style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
14
+ <div id=nav>
15
+ <a href="http://codemirror.net"><img id=logo src="../../doc/logo.png"></a>
16
+
17
+ <ul>
18
+ <li><a href="../../index.html">Home</a>
19
+ <li><a href="../../doc/manual.html">Manual</a>
20
+ <li><a href="https://github.com/marijnh/codemirror">Code</a>
21
+ </ul>
22
+ <ul>
23
+ <li><a href="../index.html">Language modes</a>
24
+ <li><a class=active href="#">JavaScript</a>
25
+ </ul>
26
+ </div>
27
+
28
+ <article>
29
+ <h2>JavaScript mode</h2>
30
+
31
+
32
+ <div><textarea id="code" name="code">
33
+ // Demo code (the actual new parser character stream implementation)
34
+
35
+ function StringStream(string) {
36
+ this.pos = 0;
37
+ this.string = string;
38
+ }
39
+
40
+ StringStream.prototype = {
41
+ done: function() {return this.pos >= this.string.length;},
42
+ peek: function() {return this.string.charAt(this.pos);},
43
+ next: function() {
44
+ if (this.pos &lt; this.string.length)
45
+ return this.string.charAt(this.pos++);
46
+ },
47
+ eat: function(match) {
48
+ var ch = this.string.charAt(this.pos);
49
+ if (typeof match == "string") var ok = ch == match;
50
+ else var ok = ch &amp;&amp; match.test ? match.test(ch) : match(ch);
51
+ if (ok) {this.pos++; return ch;}
52
+ },
53
+ eatWhile: function(match) {
54
+ var start = this.pos;
55
+ while (this.eat(match));
56
+ if (this.pos > start) return this.string.slice(start, this.pos);
57
+ },
58
+ backUp: function(n) {this.pos -= n;},
59
+ column: function() {return this.pos;},
60
+ eatSpace: function() {
61
+ var start = this.pos;
62
+ while (/\s/.test(this.string.charAt(this.pos))) this.pos++;
63
+ return this.pos - start;
64
+ },
65
+ match: function(pattern, consume, caseInsensitive) {
66
+ if (typeof pattern == "string") {
67
+ function cased(str) {return caseInsensitive ? str.toLowerCase() : str;}
68
+ if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) {
69
+ if (consume !== false) this.pos += str.length;
70
+ return true;
71
+ }
72
+ }
73
+ else {
74
+ var match = this.string.slice(this.pos).match(pattern);
75
+ if (match &amp;&amp; consume !== false) this.pos += match[0].length;
76
+ return match;
77
+ }
78
+ }
79
+ };
80
+ </textarea></div>
81
+
82
+ <script>
83
+ var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
84
+ lineNumbers: true,
85
+ matchBrackets: true,
86
+ continueComments: "Enter",
87
+ extraKeys: {"Ctrl-Q": "toggleComment"}
88
+ });
89
+ </script>
90
+
91
+ <p>
92
+ JavaScript mode supports a two configuration
93
+ options:
94
+ <ul>
95
+ <li><code>json</code> which will set the mode to expect JSON
96
+ data rather than a JavaScript program.</li>
97
+ <li><code>typescript</code> which will activate additional
98
+ syntax highlighting and some other things for TypeScript code
99
+ (<a href="typescript.html">demo</a>).</li>
100
+ <li><code>statementIndent</code> which (given a number) will
101
+ determine the amount of indentation to use for statements
102
+ continued on a new line.</li>
103
+ </ul>
104
+ </p>
105
+
106
+ <p><strong>MIME types defined:</strong> <code>text/javascript</code>, <code>application/json</code>, <code>text/typescript</code>, <code>application/typescript</code>.</p>
107
+ </article>
assets/lib/codemirror/mode/javascript/javascript.js ADDED
@@ -0,0 +1,480 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // TODO actually recognize syntax of TypeScript constructs
2
+
3
+ CodeMirror.defineMode("javascript", function(config, parserConfig) {
4
+ var indentUnit = config.indentUnit;
5
+ var statementIndent = parserConfig.statementIndent;
6
+ var jsonMode = parserConfig.json;
7
+ var isTS = parserConfig.typescript;
8
+
9
+ // Tokenizer
10
+
11
+ var keywords = function(){
12
+ function kw(type) {return {type: type, style: "keyword"};}
13
+ var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
14
+ var operator = kw("operator"), atom = {type: "atom", style: "atom"};
15
+
16
+ var jsKeywords = {
17
+ "if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
18
+ "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C,
19
+ "var": kw("var"), "const": kw("var"), "let": kw("var"),
20
+ "function": kw("function"), "catch": kw("catch"),
21
+ "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
22
+ "in": operator, "typeof": operator, "instanceof": operator,
23
+ "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
24
+ "this": kw("this")
25
+ };
26
+
27
+ // Extend the 'normal' keywords with the TypeScript language extensions
28
+ if (isTS) {
29
+ var type = {type: "variable", style: "variable-3"};
30
+ var tsKeywords = {
31
+ // object-like things
32
+ "interface": kw("interface"),
33
+ "class": kw("class"),
34
+ "extends": kw("extends"),
35
+ "constructor": kw("constructor"),
36
+
37
+ // scope modifiers
38
+ "public": kw("public"),
39
+ "private": kw("private"),
40
+ "protected": kw("protected"),
41
+ "static": kw("static"),
42
+
43
+ "super": kw("super"),
44
+
45
+ // types
46
+ "string": type, "number": type, "bool": type, "any": type
47
+ };
48
+
49
+ for (var attr in tsKeywords) {
50
+ jsKeywords[attr] = tsKeywords[attr];
51
+ }
52
+ }
53
+
54
+ return jsKeywords;
55
+ }();
56
+
57
+ var isOperatorChar = /[+\-*&%=<>!?|~^]/;
58
+
59
+ function chain(stream, state, f) {
60
+ state.tokenize = f;
61
+ return f(stream, state);
62
+ }
63
+
64
+ function nextUntilUnescaped(stream, end) {
65
+ var escaped = false, next;
66
+ while ((next = stream.next()) != null) {
67
+ if (next == end && !escaped)
68
+ return false;
69
+ escaped = !escaped && next == "\\";
70
+ }
71
+ return escaped;
72
+ }
73
+
74
+ // Used as scratch variables to communicate multiple values without
75
+ // consing up tons of objects.
76
+ var type, content;
77
+ function ret(tp, style, cont) {
78
+ type = tp; content = cont;
79
+ return style;
80
+ }
81
+ function jsTokenBase(stream, state) {
82
+ var ch = stream.next();
83
+ if (ch == '"' || ch == "'")
84
+ return chain(stream, state, jsTokenString(ch));
85
+ else if (ch == "." && stream.match(/^\d+(?:[eE][+\-]?\d+)?/))
86
+ return ret("number", "number");
87
+ else if (/[\[\]{}\(\),;\:\.]/.test(ch))
88
+ return ret(ch);
89
+ else if (ch == "0" && stream.eat(/x/i)) {
90
+ stream.eatWhile(/[\da-f]/i);
91
+ return ret("number", "number");
92
+ }
93
+ else if (/\d/.test(ch)) {
94
+ stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
95
+ return ret("number", "number");
96
+ }
97
+ else if (ch == "/") {
98
+ if (stream.eat("*")) {
99
+ return chain(stream, state, jsTokenComment);
100
+ }
101
+ else if (stream.eat("/")) {
102
+ stream.skipToEnd();
103
+ return ret("comment", "comment");
104
+ }
105
+ else if (state.lastType == "operator" || state.lastType == "keyword c" ||
106
+ /^[\[{}\(,;:]$/.test(state.lastType)) {
107
+ nextUntilUnescaped(stream, "/");
108
+ stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla
109
+ return ret("regexp", "string-2");
110
+ }
111
+ else {
112
+ stream.eatWhile(isOperatorChar);
113
+ return ret("operator", null, stream.current());
114
+ }
115
+ }
116
+ else if (ch == "#") {
117
+ stream.skipToEnd();
118
+ return ret("error", "error");
119
+ }
120
+ else if (isOperatorChar.test(ch)) {
121
+ stream.eatWhile(isOperatorChar);
122
+ return ret("operator", null, stream.current());
123
+ }
124
+ else {
125
+ stream.eatWhile(/[\w\$_]/);
126
+ var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
127
+ return (known && state.lastType != ".") ? ret(known.type, known.style, word) :
128
+ ret("variable", "variable", word);
129
+ }
130
+ }
131
+
132
+ function jsTokenString(quote) {
133
+ return function(stream, state) {
134
+ if (!nextUntilUnescaped(stream, quote))
135
+ state.tokenize = jsTokenBase;
136
+ return ret("string", "string");
137
+ };
138
+ }
139
+
140
+ function jsTokenComment(stream, state) {
141
+ var maybeEnd = false, ch;
142
+ while (ch = stream.next()) {
143
+ if (ch == "/" && maybeEnd) {
144
+ state.tokenize = jsTokenBase;
145
+ break;
146
+ }
147
+ maybeEnd = (ch == "*");
148
+ }
149
+ return ret("comment", "comment");
150
+ }
151
+
152
+ // Parser
153
+
154
+ var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true};
155
+
156
+ function JSLexical(indented, column, type, align, prev, info) {
157
+ this.indented = indented;
158
+ this.column = column;
159
+ this.type = type;
160
+ this.prev = prev;
161
+ this.info = info;
162
+ if (align != null) this.align = align;
163
+ }
164
+
165
+ function inScope(state, varname) {
166
+ for (var v = state.localVars; v; v = v.next)
167
+ if (v.name == varname) return true;
168
+ }
169
+
170
+ function parseJS(state, style, type, content, stream) {
171
+ var cc = state.cc;
172
+ // Communicate our context to the combinators.
173
+ // (Less wasteful than consing up a hundred closures on every call.)
174
+ cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc;
175
+
176
+ if (!state.lexical.hasOwnProperty("align"))
177
+ state.lexical.align = true;
178
+
179
+ while(true) {
180
+ var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
181
+ if (combinator(type, content)) {
182
+ while(cc.length && cc[cc.length - 1].lex)
183
+ cc.pop()();
184
+ if (cx.marked) return cx.marked;
185
+ if (type == "variable" && inScope(state, content)) return "variable-2";
186
+ return style;
187
+ }
188
+ }
189
+ }
190
+
191
+ // Combinator utils
192
+
193
+ var cx = {state: null, column: null, marked: null, cc: null};
194
+ function pass() {
195
+ for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
196
+ }
197
+ function cont() {
198
+ pass.apply(null, arguments);
199
+ return true;
200
+ }
201
+ function register(varname) {
202
+ function inList(list) {
203
+ for (var v = list; v; v = v.next)
204
+ if (v.name == varname) return true;
205
+ return false;
206
+ }
207
+ var state = cx.state;
208
+ if (state.context) {
209
+ cx.marked = "def";
210
+ if (inList(state.localVars)) return;
211
+ state.localVars = {name: varname, next: state.localVars};
212
+ } else {
213
+ if (inList(state.globalVars)) return;
214
+ state.globalVars = {name: varname, next: state.globalVars};
215
+ }
216
+ }
217
+
218
+ // Combinators
219
+
220
+ var defaultVars = {name: "this", next: {name: "arguments"}};
221
+ function pushcontext() {
222
+ cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
223
+ cx.state.localVars = defaultVars;
224
+ }
225
+ function popcontext() {
226
+ cx.state.localVars = cx.state.context.vars;
227
+ cx.state.context = cx.state.context.prev;
228
+ }
229
+ function pushlex(type, info) {
230
+ var result = function() {
231
+ var state = cx.state, indent = state.indented;
232
+ if (state.lexical.type == "stat") indent = state.lexical.indented;
233
+ state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info);
234
+ };
235
+ result.lex = true;
236
+ return result;
237
+ }
238
+ function poplex() {
239
+ var state = cx.state;
240
+ if (state.lexical.prev) {
241
+ if (state.lexical.type == ")")
242
+ state.indented = state.lexical.indented;
243
+ state.lexical = state.lexical.prev;
244
+ }
245
+ }
246
+ poplex.lex = true;
247
+
248
+ function expect(wanted) {
249
+ return function(type) {
250
+ if (type == wanted) return cont();
251
+ else if (wanted == ";") return pass();
252
+ else return cont(arguments.callee);
253
+ };
254
+ }
255
+
256
+ function statement(type) {
257
+ if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex);
258
+ if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex);
259
+ if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
260
+ if (type == "{") return cont(pushlex("}"), block, poplex);
261
+ if (type == ";") return cont();
262
+ if (type == "if") return cont(pushlex("form"), expression, statement, poplex, maybeelse);
263
+ if (type == "function") return cont(functiondef);
264
+ if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"),
265
+ poplex, statement, poplex);
266
+ if (type == "variable") return cont(pushlex("stat"), maybelabel);
267
+ if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
268
+ block, poplex, poplex);
269
+ if (type == "case") return cont(expression, expect(":"));
270
+ if (type == "default") return cont(expect(":"));
271
+ if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
272
+ statement, poplex, popcontext);
273
+ return pass(pushlex("stat"), expression, expect(";"), poplex);
274
+ }
275
+ function expression(type) {
276
+ return expressionInner(type, false);
277
+ }
278
+ function expressionNoComma(type) {
279
+ return expressionInner(type, true);
280
+ }
281
+ function expressionInner(type, noComma) {
282
+ var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
283
+ if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
284
+ if (type == "function") return cont(functiondef);
285
+ if (type == "keyword c") return cont(noComma ? maybeexpressionNoComma : maybeexpression);
286
+ if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop);
287
+ if (type == "operator") return cont(noComma ? expressionNoComma : expression);
288
+ if (type == "[") return cont(pushlex("]"), commasep(expressionNoComma, "]"), poplex, maybeop);
289
+ if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeop);
290
+ return cont();
291
+ }
292
+ function maybeexpression(type) {
293
+ if (type.match(/[;\}\)\],]/)) return pass();
294
+ return pass(expression);
295
+ }
296
+ function maybeexpressionNoComma(type) {
297
+ if (type.match(/[;\}\)\],]/)) return pass();
298
+ return pass(expressionNoComma);
299
+ }
300
+
301
+ function maybeoperatorComma(type, value) {
302
+ if (type == ",") return cont(expression);
303
+ return maybeoperatorNoComma(type, value, false);
304
+ }
305
+ function maybeoperatorNoComma(type, value, noComma) {
306
+ var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma;
307
+ var expr = noComma == false ? expression : expressionNoComma;
308
+ if (type == "operator") {
309
+ if (/\+\+|--/.test(value)) return cont(me);
310
+ if (value == "?") return cont(expression, expect(":"), expr);
311
+ return cont(expr);
312
+ }
313
+ if (type == ";") return;
314
+ if (type == "(") return cont(pushlex(")", "call"), commasep(expressionNoComma, ")"), poplex, me);
315
+ if (type == ".") return cont(property, me);
316
+ if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
317
+ }
318
+ function maybelabel(type) {
319
+ if (type == ":") return cont(poplex, statement);
320
+ return pass(maybeoperatorComma, expect(";"), poplex);
321
+ }
322
+ function property(type) {
323
+ if (type == "variable") {cx.marked = "property"; return cont();}
324
+ }
325
+ function objprop(type, value) {
326
+ if (type == "variable") {
327
+ cx.marked = "property";
328
+ if (value == "get" || value == "set") return cont(getterSetter);
329
+ } else if (type == "number" || type == "string") {
330
+ cx.marked = type + " property";
331
+ }
332
+ if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expressionNoComma);
333
+ }
334
+ function getterSetter(type) {
335
+ if (type == ":") return cont(expression);
336
+ if (type != "variable") return cont(expect(":"), expression);
337
+ cx.marked = "property";
338
+ return cont(functiondef);
339
+ }
340
+ function commasep(what, end) {
341
+ function proceed(type) {
342
+ if (type == ",") {
343
+ var lex = cx.state.lexical;
344
+ if (lex.info == "call") lex.pos = (lex.pos || 0) + 1;
345
+ return cont(what, proceed);
346
+ }
347
+ if (type == end) return cont();
348
+ return cont(expect(end));
349
+ }
350
+ return function(type) {
351
+ if (type == end) return cont();
352
+ else return pass(what, proceed);
353
+ };
354
+ }
355
+ function block(type) {
356
+ if (type == "}") return cont();
357
+ return pass(statement, block);
358
+ }
359
+ function maybetype(type) {
360
+ if (type == ":") return cont(typedef);
361
+ return pass();
362
+ }
363
+ function typedef(type) {
364
+ if (type == "variable"){cx.marked = "variable-3"; return cont();}
365
+ return pass();
366
+ }
367
+ function vardef1(type, value) {
368
+ if (type == "variable") {
369
+ register(value);
370
+ return isTS ? cont(maybetype, vardef2) : cont(vardef2);
371
+ }
372
+ return pass();
373
+ }
374
+ function vardef2(type, value) {
375
+ if (value == "=") return cont(expressionNoComma, vardef2);
376
+ if (type == ",") return cont(vardef1);
377
+ }
378
+ function maybeelse(type, value) {
379
+ if (type == "keyword b" && value == "else") return cont(pushlex("form"), statement, poplex);
380
+ }
381
+ function forspec1(type) {
382
+ if (type == "var") return cont(vardef1, expect(";"), forspec2);
383
+ if (type == ";") return cont(forspec2);
384
+ if (type == "variable") return cont(formaybein);
385
+ return pass(expression, expect(";"), forspec2);
386
+ }
387
+ function formaybein(_type, value) {
388
+ if (value == "in") return cont(expression);
389
+ return cont(maybeoperatorComma, forspec2);
390
+ }
391
+ function forspec2(type, value) {
392
+ if (type == ";") return cont(forspec3);
393
+ if (value == "in") return cont(expression);
394
+ return pass(expression, expect(";"), forspec3);
395
+ }
396
+ function forspec3(type) {
397
+ if (type != ")") cont(expression);
398
+ }
399
+ function functiondef(type, value) {
400
+ if (type == "variable") {register(value); return cont(functiondef);}
401
+ if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext);
402
+ }
403
+ function funarg(type, value) {
404
+ if (type == "variable") {register(value); return isTS ? cont(maybetype) : cont();}
405
+ }
406
+
407
+ // Interface
408
+
409
+ return {
410
+ startState: function(basecolumn) {
411
+ return {
412
+ tokenize: jsTokenBase,
413
+ lastType: null,
414
+ cc: [],
415
+ lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
416
+ localVars: parserConfig.localVars,
417
+ globalVars: parserConfig.globalVars,
418
+ context: parserConfig.localVars && {vars: parserConfig.localVars},
419
+ indented: 0
420
+ };
421
+ },
422
+
423
+ token: function(stream, state) {
424
+ if (stream.sol()) {
425
+ if (!state.lexical.hasOwnProperty("align"))
426
+ state.lexical.align = false;
427
+ state.indented = stream.indentation();
428
+ }
429
+ if (state.tokenize != jsTokenComment && stream.eatSpace()) return null;
430
+ var style = state.tokenize(stream, state);
431
+ if (type == "comment") return style;
432
+ state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type;
433
+ return parseJS(state, style, type, content, stream);
434
+ },
435
+
436
+ indent: function(state, textAfter) {
437
+ if (state.tokenize == jsTokenComment) return CodeMirror.Pass;
438
+ if (state.tokenize != jsTokenBase) return 0;
439
+ var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical;
440
+ // Kludge to prevent 'maybelse' from blocking lexical scope pops
441
+ for (var i = state.cc.length - 1; i >= 0; --i) {
442
+ var c = state.cc[i];
443
+ if (c == poplex) lexical = lexical.prev;
444
+ else if (c != maybeelse || /^else\b/.test(textAfter)) break;
445
+ }
446
+ if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev;
447
+ if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat")
448
+ lexical = lexical.prev;
449
+ var type = lexical.type, closing = firstChar == type;
450
+
451
+ if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? 4 : 0);
452
+ else if (type == "form" && firstChar == "{") return lexical.indented;
453
+ else if (type == "form") return lexical.indented + indentUnit;
454
+ else if (type == "stat")
455
+ return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? statementIndent || indentUnit : 0);
456
+ else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false)
457
+ return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
458
+ else if (lexical.align) return lexical.column + (closing ? 0 : 1);
459
+ else return lexical.indented + (closing ? 0 : indentUnit);
460
+ },
461
+
462
+ electricChars: ":{}",
463
+ blockCommentStart: jsonMode ? null : "/*",
464
+ blockCommentEnd: jsonMode ? null : "*/",
465
+ lineComment: jsonMode ? null : "//",
466
+ fold: "brace",
467
+
468
+ helperType: jsonMode ? "json" : "javascript",
469
+ jsonMode: jsonMode
470
+ };
471
+ });
472
+
473
+ CodeMirror.defineMIME("text/javascript", "javascript");
474
+ CodeMirror.defineMIME("text/ecmascript", "javascript");
475
+ CodeMirror.defineMIME("application/javascript", "javascript");
476
+ CodeMirror.defineMIME("application/ecmascript", "javascript");
477
+ CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
478
+ CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true});
479
+ CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
480
+ CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });
assets/lib/codemirror/mode/javascript/test.js ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ (function() {
2
+ var mode = CodeMirror.getMode({indentUnit: 2}, "javascript");
3
+ function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); }
4
+
5
+ MT("locals",
6
+ "[keyword function] [variable foo]([def a], [def b]) { [keyword var] [def c] = [number 10]; [keyword return] [variable-2 a] + [variable-2 c] + [variable d]; }");
7
+
8
+ MT("comma-and-binop",
9
+ "[keyword function](){ [keyword var] [def x] = [number 1] + [number 2], [def y]; }");
10
+ })();
assets/lib/codemirror/mode/javascript/typescript.html ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+
3
+ <title>CodeMirror: TypeScript mode</title>
4
+ <meta charset="utf-8"/>
5
+ <link rel=stylesheet href="../../doc/docs.css">
6
+
7
+ <link rel="stylesheet" href="../../lib/codemirror.css">
8
+ <script src="../../lib/codemirror.js"></script>
9
+ <script src="javascript.js"></script>
10
+ <style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
11
+ <div id=nav>
12
+ <a href="http://codemirror.net"><img id=logo src="../../doc/logo.png"></a>
13
+
14
+ <ul>
15
+ <li><a href="../../index.html">Home</a>
16
+ <li><a href="../../doc/manual.html">Manual</a>
17
+ <li><a href="https://github.com/marijnh/codemirror">Code</a>
18
+ </ul>
19
+ <ul>
20
+ <li><a href="../index.html">Language modes</a>
21
+ <li><a class=active href="#">TypeScript</a>
22
+ </ul>
23
+ </div>
24
+
25
+ <article>
26
+ <h2>TypeScript mode</h2>
27
+
28
+
29
+ <div><textarea id="code" name="code">
30
+ class Greeter {
31
+ greeting: string;
32
+ constructor (message: string) {
33
+ this.greeting = message;
34
+ }
35
+ greet() {
36
+ return "Hello, " + this.greeting;
37
+ }
38
+ }
39
+
40
+ var greeter = new Greeter("world");
41
+
42
+ var button = document.createElement('button')
43
+ button.innerText = "Say Hello"
44
+ button.onclick = function() {
45
+ alert(greeter.greet())
46
+ }
47
+
48
+ document.body.appendChild(button)
49
+
50
+ </textarea></div>
51
+
52
+ <script>
53
+ var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
54
+ lineNumbers: true,
55
+ matchBrackets: true,
56
+ mode: "text/typescript"
57
+ });
58
+ </script>
59
+
60
+ <p>This is a specialization of the <a href="index.html">JavaScript mode</a>.</p>
61
+ </article>
assets/lib/codemirror/mode/meta.js ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ CodeMirror.modeInfo = [
2
+ {name: 'APL', mime: 'text/apl', mode: 'apl'},
3
+ {name: 'Asterisk', mime: 'text/x-asterisk', mode: 'asterisk'},
4
+ {name: 'C', mime: 'text/x-csrc', mode: 'clike'},
5
+ {name: 'C++', mime: 'text/x-c++src', mode: 'clike'},
6
+ {name: 'Cobol', mime: 'text/x-cobol', mode: 'cobol'},
7
+ {name: 'Java', mime: 'text/x-java', mode: 'clike'},
8
+ {name: 'C#', mime: 'text/x-csharp', mode: 'clike'},
9
+ {name: 'Scala', mime: 'text/x-scala', mode: 'clike'},
10
+ {name: 'Clojure', mime: 'text/x-clojure', mode: 'clojure'},
11
+ {name: 'CoffeeScript', mime: 'text/x-coffeescript', mode: 'coffeescript'},
12
+ {name: 'Common Lisp', mime: 'text/x-common-lisp', mode: 'commonlisp'},
13
+ {name: 'CSS', mime: 'text/css', mode: 'css'},
14
+ {name: 'D', mime: 'text/x-d', mode: 'd'},
15
+ {name: 'diff', mime: 'text/x-diff', mode: 'diff'},
16
+ {name: 'DTD', mime: 'application/xml-dtd', mode: 'dtd'},
17
+ {name: 'ECL', mime: 'text/x-ecl', mode: 'ecl'},
18
+ {name: 'Erlang', mime: 'text/x-erlang', mode: 'erlang'},
19
+ {name: 'Fortran', mime: 'text/x-fortran', mode: 'fortran'},
20
+ {name: 'Gas', mime: 'text/x-gas', mode: 'gas'},
21
+ {name: 'GitHub Flavored Markdown', mime: 'text/x-gfm', mode: 'gfm'},
22
+ {name: 'GO', mime: 'text/x-go', mode: 'go'},
23
+ {name: 'Groovy', mime: 'text/x-groovy', mode: 'groovy'},
24
+ {name: 'HAML', mime: 'text/x-haml', mode: 'haml'},
25
+ {name: 'Haskell', mime: 'text/x-haskell', mode: 'haskell'},
26
+ {name: 'Haxe', mime: 'text/x-haxe', mode: 'haxe'},
27
+ {name: 'ASP.NET', mime: 'application/x-aspx', mode: 'htmlembedded'},
28
+ {name: 'Embedded Javascript', mime: 'application/x-ejs', mode: 'htmlembedded'},
29
+ {name: 'JavaServer Pages', mime: 'application/x-jsp', mode: 'htmlembedded'},
30
+ {name: 'HTML', mime: 'text/html', mode: 'htmlmixed'},
31
+ {name: 'HTTP', mime: 'message/http', mode: 'http'},
32
+ {name: 'Jade', mime: 'text/x-jade', mode: 'jade'},
33
+ {name: 'JavaScript', mime: 'text/javascript', mode: 'javascript'},
34
+ {name: 'JSON', mime: 'application/x-json', mode: 'javascript'},
35
+ {name: 'JSON', mime: 'application/json', mode: 'javascript'},
36
+ {name: 'TypeScript', mime: 'application/typescript', mode: 'javascript'},
37
+ {name: 'Jinja2', mime: 'jinja2', mode: 'jinja2'},
38
+ {name: 'LESS', mime: 'text/x-less', mode: 'less'},
39
+ {name: 'LiveScript', mime: 'text/x-livescript', mode: 'livescript'},
40
+ {name: 'Lua', mime: 'text/x-lua', mode: 'lua'},
41
+ {name: 'Markdown (GitHub-flavour)', mime: 'text/x-markdown', mode: 'markdown'},
42
+ {name: 'mIRC', mime: 'text/mirc', mode: 'mirc'},
43
+ {name: 'Nginx', mime: 'text/x-nginx-conf', mode: 'nginx'},
44
+ {name: 'NTriples', mime: 'text/n-triples', mode: 'ntriples'},
45
+ {name: 'OCaml', mime: 'text/x-ocaml', mode: 'ocaml'},
46
+ {name: 'Octave', mime: 'text/x-octave', mode: 'octave'},
47
+ {name: 'Pascal', mime: 'text/x-pascal', mode: 'pascal'},
48
+ {name: 'Perl', mime: 'text/x-perl', mode: 'perl'},
49
+ {name: 'PHP', mime: 'text/x-php', mode: 'php'},
50
+ {name: 'PHP(HTML)', mime: 'application/x-httpd-php', mode: 'php'},
51
+ {name: 'Pig', mime: 'text/x-pig', mode: 'pig'},
52
+ {name: 'Plain Text', mime: 'text/plain', mode: 'null'},
53
+ {name: 'Properties files', mime: 'text/x-properties', mode: 'properties'},
54
+ {name: 'Python', mime: 'text/x-python', mode: 'python'},
55
+ {name: 'Cython', mime: 'text/x-cython', mode: 'python'},
56
+ {name: 'R', mime: 'text/x-rsrc', mode: 'r'},
57
+ {name: 'reStructuredText', mime: 'text/x-rst', mode: 'rst'},
58
+ {name: 'Ruby', mime: 'text/x-ruby', mode: 'ruby'},
59
+ {name: 'Rust', mime: 'text/x-rustsrc', mode: 'rust'},
60
+ {name: 'Sass', mime: 'text/x-sass', mode: 'sass'},
61
+ {name: 'Scheme', mime: 'text/x-scheme', mode: 'scheme'},
62
+ {name: 'SCSS', mime: 'text/x-scss', mode: 'css'},
63
+ {name: 'Shell', mime: 'text/x-sh', mode: 'shell'},
64
+ {name: 'Sieve', mime: 'application/sieve', mode: 'sieve'},
65
+ {name: 'Smalltalk', mime: 'text/x-stsrc', mode: 'smalltalk'},
66
+ {name: 'Smarty', mime: 'text/x-smarty', mode: 'smarty'},
67
+ {name: 'SmartyMixed', mime: 'text/x-smarty', mode: 'smartymixed'},
68
+ {name: 'SPARQL', mime: 'application/x-sparql-query', mode: 'sparql'},
69
+ {name: 'SQL', mime: 'text/x-sql', mode: 'sql'},
70
+ {name: 'MariaDB', mime: 'text/x-mariadb', mode: 'sql'},
71
+ {name: 'sTeX', mime: 'text/x-stex', mode: 'stex'},
72
+ {name: 'LaTeX', mime: 'text/x-latex', mode: 'stex'},
73
+ {name: 'Tcl', mime: 'text/x-tcl', mode: 'tcl'},
74
+ {name: 'TiddlyWiki ', mime: 'text/x-tiddlywiki', mode: 'tiddlywiki'},
75
+ {name: 'Tiki wiki', mime: 'text/tiki', mode: 'tiki'},
76
+ {name: 'TOML', mime: 'text/x-toml', mode: 'toml'},
77
+ {name: 'Turtle', mime: 'text/turtle', mode: 'turtle'},
78
+ {name: 'VB.NET', mime: 'text/x-vb', mode: 'vb'},
79
+ {name: 'VBScript', mime: 'text/vbscript', mode: 'vbscript'},
80
+ {name: 'Velocity', mime: 'text/velocity', mode: 'velocity'},
81
+ {name: 'Verilog', mime: 'text/x-verilog', mode: 'verilog'},
82
+ {name: 'XML', mime: 'application/xml', mode: 'xml'},
83
+ {name: 'HTML', mime: 'text/html', mode: 'xml'},
84
+ {name: 'XQuery', mime: 'application/xquery', mode: 'xquery'},
85
+ {name: 'YAML', mime: 'text/x-yaml', mode: 'yaml'},
86
+ {name: 'Z80', mime: 'text/x-z80', mode: 'z80'}
87
+ ];
assets/lib/codemirror/mode/php/index.html ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+
3
+ <title>CodeMirror: PHP mode</title>
4
+ <meta charset="utf-8"/>
5
+ <link rel=stylesheet href="../../doc/docs.css">
6
+
7
+ <link rel="stylesheet" href="../../lib/codemirror.css">
8
+ <script src="../../lib/codemirror.js"></script>
9
+ <script src="../../addon/edit/matchbrackets.js"></script>
10
+ <script src="../htmlmixed/htmlmixed.js"></script>
11
+ <script src="../xml/xml.js"></script>
12
+ <script src="../javascript/javascript.js"></script>
13
+ <script src="../css/css.js"></script>
14
+ <script src="../clike/clike.js"></script>
15
+ <script src="php.js"></script>
16
+ <style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
17
+ <div id=nav>
18
+ <a href="http://codemirror.net"><img id=logo src="../../doc/logo.png"></a>
19
+
20
+ <ul>
21
+ <li><a href="../../index.html">Home</a>
22
+ <li><a href="../../doc/manual.html">Manual</a>
23
+ <li><a href="https://github.com/marijnh/codemirror">Code</a>
24
+ </ul>
25
+ <ul>
26
+ <li><a href="../index.html">Language modes</a>
27
+ <li><a class=active href="#">PHP</a>
28
+ </ul>
29
+ </div>
30
+
31
+ <article>
32
+ <h2>PHP mode</h2>
33
+ <form><textarea id="code" name="code">
34
+ <?php
35
+ function hello($who) {
36
+ return "Hello " . $who;
37
+ }
38
+ ?>
39
+ <p>The program says <?= hello("World") ?>.</p>
40
+ <script>
41
+ alert("And here is some JS code"); // also colored
42
+ </script>
43
+ </textarea></form>
44
+
45
+ <script>
46
+ var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
47
+ lineNumbers: true,
48
+ matchBrackets: true,
49
+ mode: "application/x-httpd-php",
50
+ indentUnit: 4,
51
+ indentWithTabs: true,
52
+ enterMode: "keep",
53
+ tabMode: "shift"
54
+ });
55
+ </script>
56
+
57
+ <p>Simple HTML/PHP mode based on
58
+ the <a href="../clike/">C-like</a> mode. Depends on XML,
59
+ JavaScript, CSS, HTMLMixed, and C-like modes.</p>
60
+
61
+ <p><strong>MIME types defined:</strong> <code>application/x-httpd-php</code> (HTML with PHP code), <code>text/x-php</code> (plain, non-wrapped PHP code).</p>
62
+ </article>
assets/lib/codemirror/mode/php/php.js ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function() {
2
+ function keywords(str) {
3
+ var obj = {}, words = str.split(" ");
4
+ for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
5
+ return obj;
6
+ }
7
+ function heredoc(delim) {
8
+ return function(stream, state) {
9
+ if (stream.match(delim)) state.tokenize = null;
10
+ else stream.skipToEnd();
11
+ return "string";
12
+ };
13
+ }
14
+ var phpConfig = {
15
+ name: "clike",
16
+ keywords: keywords("abstract and array as break case catch class clone const continue declare default " +
17
+ "do else elseif enddeclare endfor endforeach endif endswitch endwhile extends final " +
18
+ "for foreach function global goto if implements interface instanceof namespace " +
19
+ "new or private protected public static switch throw trait try use var while xor " +
20
+ "die echo empty exit eval include include_once isset list require require_once return " +
21
+ "print unset __halt_compiler self static parent"),
22
+ blockKeywords: keywords("catch do else elseif for foreach if switch try while"),
23
+ atoms: keywords("true false null TRUE FALSE NULL __CLASS__ __DIR__ __FILE__ __LINE__ __METHOD__ __FUNCTION__ __NAMESPACE__"),
24
+ builtin: keywords("func_num_args func_get_arg func_get_args strlen strcmp strncmp strcasecmp strncasecmp each error_reporting define defined trigger_error user_error set_error_handler restore_error_handler get_declared_classes get_loaded_extensions extension_loaded get_extension_funcs debug_backtrace constant bin2hex sleep usleep time mktime gmmktime strftime gmstrftime strtotime date gmdate getdate localtime checkdate flush wordwrap htmlspecialchars htmlentities html_entity_decode md5 md5_file crc32 getimagesize image_type_to_mime_type phpinfo phpversion phpcredits strnatcmp strnatcasecmp substr_count strspn strcspn strtok strtoupper strtolower strpos strrpos strrev hebrev hebrevc nl2br basename dirname pathinfo stripslashes stripcslashes strstr stristr strrchr str_shuffle str_word_count strcoll substr substr_replace quotemeta ucfirst ucwords strtr addslashes addcslashes rtrim str_replace str_repeat count_chars chunk_split trim ltrim strip_tags similar_text explode implode setlocale localeconv parse_str str_pad chop strchr sprintf printf vprintf vsprintf sscanf fscanf parse_url urlencode urldecode rawurlencode rawurldecode readlink linkinfo link unlink exec system escapeshellcmd escapeshellarg passthru shell_exec proc_open proc_close rand srand getrandmax mt_rand mt_srand mt_getrandmax base64_decode base64_encode abs ceil floor round is_finite is_nan is_infinite bindec hexdec octdec decbin decoct dechex base_convert number_format fmod ip2long long2ip getenv putenv getopt microtime gettimeofday getrusage uniqid quoted_printable_decode set_time_limit get_cfg_var magic_quotes_runtime set_magic_quotes_runtime get_magic_quotes_gpc get_magic_quotes_runtime import_request_variables error_log serialize unserialize memory_get_usage var_dump var_export debug_zval_dump print_r highlight_file show_source highlight_string ini_get ini_get_all ini_set ini_alter ini_restore get_include_path set_include_path restore_include_path setcookie header headers_sent connection_aborted connection_status ignore_user_abort parse_ini_file is_uploaded_file move_uploaded_file intval floatval doubleval strval gettype settype is_null is_resource is_bool is_long is_float is_int is_integer is_double is_real is_numeric is_string is_array is_object is_scalar ereg ereg_replace eregi eregi_replace split spliti join sql_regcase dl pclose popen readfile rewind rmdir umask fclose feof fgetc fgets fgetss fread fopen fpassthru ftruncate fstat fseek ftell fflush fwrite fputs mkdir rename copy tempnam tmpfile file file_get_contents stream_select stream_context_create stream_context_set_params stream_context_set_option stream_context_get_options stream_filter_prepend stream_filter_append fgetcsv flock get_meta_tags stream_set_write_buffer set_file_buffer set_socket_blocking stream_set_blocking socket_set_blocking stream_get_meta_data stream_register_wrapper stream_wrapper_register stream_set_timeout socket_set_timeout socket_get_status realpath fnmatch fsockopen pfsockopen pack unpack get_browser crypt opendir closedir chdir getcwd rewinddir readdir dir glob fileatime filectime filegroup fileinode filemtime fileowner fileperms filesize filetype file_exists is_writable is_writeable is_readable is_executable is_file is_dir is_link stat lstat chown touch clearstatcache mail ob_start ob_flush ob_clean ob_end_flush ob_end_clean ob_get_flush ob_get_clean ob_get_length ob_get_level ob_get_status ob_get_contents ob_implicit_flush ob_list_handlers ksort krsort natsort natcasesort asort arsort sort rsort usort uasort uksort shuffle array_walk count end prev next reset current key min max in_array array_search extract compact array_fill range array_multisort array_push array_pop array_shift array_unshift array_splice array_slice array_merge array_merge_recursive array_keys array_values array_count_values array_reverse array_reduce array_pad array_flip array_change_key_case array_rand array_unique array_intersect array_intersect_assoc array_diff array_diff_assoc array_sum array_filter array_map array_chunk array_key_exists pos sizeof key_exists assert assert_options version_compare ftok str_rot13 aggregate session_name session_module_name session_save_path session_id session_regenerate_id session_decode session_register session_unregister session_is_registered session_encode session_start session_destroy session_unset session_set_save_handler session_cache_limiter session_cache_expire session_set_cookie_params session_get_cookie_params session_write_close preg_match preg_match_all preg_replace preg_replace_callback preg_split preg_quote preg_grep overload ctype_alnum ctype_alpha ctype_cntrl ctype_digit ctype_lower ctype_graph ctype_print ctype_punct ctype_space ctype_upper ctype_xdigit virtual apache_request_headers apache_note apache_lookup_uri apache_child_terminate apache_setenv apache_response_headers apache_get_version getallheaders mysql_connect mysql_pconnect mysql_close mysql_select_db mysql_create_db mysql_drop_db mysql_query mysql_unbuffered_query mysql_db_query mysql_list_dbs mysql_list_tables mysql_list_fields mysql_list_processes mysql_error mysql_errno mysql_affected_rows mysql_insert_id mysql_result mysql_num_rows mysql_num_fields mysql_fetch_row mysql_fetch_array mysql_fetch_assoc mysql_fetch_object mysql_data_seek mysql_fetch_lengths mysql_fetch_field mysql_field_seek mysql_free_result mysql_field_name mysql_field_table mysql_field_len mysql_field_type mysql_field_flags mysql_escape_string mysql_real_escape_string mysql_stat mysql_thread_id mysql_client_encoding mysql_get_client_info mysql_get_host_info mysql_get_proto_info mysql_get_server_info mysql_info mysql mysql_fieldname mysql_fieldtable mysql_fieldlen mysql_fieldtype mysql_fieldflags mysql_selectdb mysql_createdb mysql_dropdb mysql_freeresult mysql_numfields mysql_numrows mysql_listdbs mysql_listtables mysql_listfields mysql_db_name mysql_dbname mysql_tablename mysql_table_name pg_connect pg_pconnect pg_close pg_connection_status pg_connection_busy pg_connection_reset pg_host pg_dbname pg_port pg_tty pg_options pg_ping pg_query pg_send_query pg_cancel_query pg_fetch_result pg_fetch_row pg_fetch_assoc pg_fetch_array pg_fetch_object pg_fetch_all pg_affected_rows pg_get_result pg_result_seek pg_result_status pg_free_result pg_last_oid pg_num_rows pg_num_fields pg_field_name pg_field_num pg_field_size pg_field_type pg_field_prtlen pg_field_is_null pg_get_notify pg_get_pid pg_result_error pg_last_error pg_last_notice pg_put_line pg_end_copy pg_copy_to pg_copy_from pg_trace pg_untrace pg_lo_create pg_lo_unlink pg_lo_open pg_lo_close pg_lo_read pg_lo_write pg_lo_read_all pg_lo_import pg_lo_export pg_lo_seek pg_lo_tell pg_escape_string pg_escape_bytea pg_unescape_bytea pg_client_encoding pg_set_client_encoding pg_meta_data pg_convert pg_insert pg_update pg_delete pg_select pg_exec pg_getlastoid pg_cmdtuples pg_errormessage pg_numrows pg_numfields pg_fieldname pg_fieldsize pg_fieldtype pg_fieldnum pg_fieldprtlen pg_fieldisnull pg_freeresult pg_result pg_loreadall pg_locreate pg_lounlink pg_loopen pg_loclose pg_loread pg_lowrite pg_loimport pg_loexport echo print global static exit array empty eval isset unset die include require include_once require_once"),
25
+ multiLineStrings: true,
26
+ hooks: {
27
+ "$": function(stream) {
28
+ stream.eatWhile(/[\w\$_]/);
29
+ return "variable-2";
30
+ },
31
+ "<": function(stream, state) {
32
+ if (stream.match(/<</)) {
33
+ stream.eatWhile(/[\w\.]/);
34
+ state.tokenize = heredoc(stream.current().slice(3));
35
+ return state.tokenize(stream, state);
36
+ }
37
+ return false;
38
+ },
39
+ "#": function(stream) {
40
+ while (!stream.eol() && !stream.match("?>", false)) stream.next();
41
+ return "comment";
42
+ },
43
+ "/": function(stream) {
44
+ if (stream.eat("/")) {
45
+ while (!stream.eol() && !stream.match("?>", false)) stream.next();
46
+ return "comment";
47
+ }
48
+ return false;
49
+ }
50
+ }
51
+ };
52
+
53
+ CodeMirror.defineMode("php", function(config, parserConfig) {
54
+ var htmlMode = CodeMirror.getMode(config, "text/html");
55
+ var phpMode = CodeMirror.getMode(config, phpConfig);
56
+
57
+ function dispatch(stream, state) {
58
+ var isPHP = state.curMode == phpMode;
59
+ if (stream.sol() && state.pending != '"') state.pending = null;
60
+ if (!isPHP) {
61
+ if (stream.match(/^<\?\w*/)) {
62
+ state.curMode = phpMode;
63
+ state.curState = state.php;
64
+ return "meta";
65
+ }
66
+ if (state.pending == '"') {
67
+ while (!stream.eol() && stream.next() != '"') {}
68
+ var style = "string";
69
+ } else if (state.pending && stream.pos < state.pending.end) {
70
+ stream.pos = state.pending.end;
71
+ var style = state.pending.style;
72
+ } else {
73
+ var style = htmlMode.token(stream, state.curState);
74
+ }
75
+ state.pending = null;
76
+ var cur = stream.current(), openPHP = cur.search(/<\?/);
77
+ if (openPHP != -1) {
78
+ if (style == "string" && /\"$/.test(cur) && !/\?>/.test(cur)) state.pending = '"';
79
+ else state.pending = {end: stream.pos, style: style};
80
+ stream.backUp(cur.length - openPHP);
81
+ }
82
+ return style;
83
+ } else if (isPHP && state.php.tokenize == null && stream.match("?>")) {
84
+ state.curMode = htmlMode;
85
+ state.curState = state.html;
86
+ return "meta";
87
+ } else {
88
+ return phpMode.token(stream, state.curState);
89
+ }
90
+ }
91
+
92
+ return {
93
+ startState: function() {
94
+ var html = CodeMirror.startState(htmlMode), php = CodeMirror.startState(phpMode);
95
+ return {html: html,
96
+ php: php,
97
+ curMode: parserConfig.startOpen ? phpMode : htmlMode,
98
+ curState: parserConfig.startOpen ? php : html,
99
+ pending: null};
100
+ },
101
+
102
+ copyState: function(state) {
103
+ var html = state.html, htmlNew = CodeMirror.copyState(htmlMode, html),
104
+ php = state.php, phpNew = CodeMirror.copyState(phpMode, php), cur;
105
+ if (state.curMode == htmlMode) cur = htmlNew;
106
+ else cur = phpNew;
107
+ return {html: htmlNew, php: phpNew, curMode: state.curMode, curState: cur,
108
+ pending: state.pending};
109
+ },
110
+
111
+ token: dispatch,
112
+
113
+ indent: function(state, textAfter) {
114
+ if ((state.curMode != phpMode && /^\s*<\//.test(textAfter)) ||
115
+ (state.curMode == phpMode && /^\?>/.test(textAfter)))
116
+ return htmlMode.indent(state.html, textAfter);
117
+ return state.curMode.indent(state.curState, textAfter);
118
+ },
119
+
120
+ electricChars: "/{}:",
121
+ blockCommentStart: "/*",
122
+ blockCommentEnd: "*/",
123
+ lineComment: "//",
124
+
125
+ innerMode: function(state) { return {state: state.curState, mode: state.curMode}; }
126
+ };
127
+ }, "htmlmixed", "clike");
128
+
129
+ CodeMirror.defineMIME("application/x-httpd-php", "php");
130
+ CodeMirror.defineMIME("application/x-httpd-php-open", {name: "php", startOpen: true});
131
+ CodeMirror.defineMIME("text/x-php", phpConfig);
132
+ })();
assets/lib/codemirror/mode/xml/index.html ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+
3
+ <title>CodeMirror: XML mode</title>
4
+ <meta charset="utf-8"/>
5
+ <link rel=stylesheet href="../../doc/docs.css">
6
+
7
+ <link rel="stylesheet" href="../../lib/codemirror.css">
8
+ <script src="../../lib/codemirror.js"></script>
9
+ <script src="xml.js"></script>
10
+ <style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
11
+ <div id=nav>
12
+ <a href="http://codemirror.net"><img id=logo src="../../doc/logo.png"></a>
13
+
14
+ <ul>
15
+ <li><a href="../../index.html">Home</a>
16
+ <li><a href="../../doc/manual.html">Manual</a>
17
+ <li><a href="https://github.com/marijnh/codemirror">Code</a>
18
+ </ul>
19
+ <ul>
20
+ <li><a href="../index.html">Language modes</a>
21
+ <li><a class=active href="#">XML</a>
22
+ </ul>
23
+ </div>
24
+
25
+ <article>
26
+ <h2>XML mode</h2>
27
+ <form><textarea id="code" name="code">
28
+ &lt;html style="color: green"&gt;
29
+ &lt;!-- this is a comment --&gt;
30
+ &lt;head&gt;
31
+ &lt;title&gt;HTML Example&lt;/title&gt;
32
+ &lt;/head&gt;
33
+ &lt;body&gt;
34
+ The indentation tries to be &lt;em&gt;somewhat &amp;quot;do what
35
+ I mean&amp;quot;&lt;/em&gt;... but might not match your style.
36
+ &lt;/body&gt;
37
+ &lt;/html&gt;
38
+ </textarea></form>
39
+ <script>
40
+ var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
41
+ mode: {name: "xml", alignCDATA: true},
42
+ lineNumbers: true
43
+ });
44
+ </script>
45
+ <p>The XML mode supports two configuration parameters:</p>
46
+ <dl>
47
+ <dt><code>htmlMode (boolean)</code></dt>
48
+ <dd>This switches the mode to parse HTML instead of XML. This
49
+ means attributes do not have to be quoted, and some elements
50
+ (such as <code>br</code>) do not require a closing tag.</dd>
51
+ <dt><code>alignCDATA (boolean)</code></dt>
52
+ <dd>Setting this to true will force the opening tag of CDATA
53
+ blocks to not be indented.</dd>
54
+ </dl>
55
+
56
+ <p><strong>MIME types defined:</strong> <code>application/xml</code>, <code>text/html</code>.</p>
57
+ </article>
assets/lib/codemirror/mode/xml/xml.js ADDED
@@ -0,0 +1,341 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ CodeMirror.defineMode("xml", function(config, parserConfig) {
2
+ var indentUnit = config.indentUnit;
3
+ var multilineTagIndentFactor = parserConfig.multilineTagIndentFactor || 1;
4
+ var multilineTagIndentPastTag = parserConfig.multilineTagIndentPastTag || true;
5
+
6
+ var Kludges = parserConfig.htmlMode ? {
7
+ autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true,
8
+ 'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true,
9
+ 'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true,
10
+ 'track': true, 'wbr': true},
11
+ implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true,
12
+ 'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true,
13
+ 'th': true, 'tr': true},
14
+ contextGrabbers: {
15
+ 'dd': {'dd': true, 'dt': true},
16
+ 'dt': {'dd': true, 'dt': true},
17
+ 'li': {'li': true},
18
+ 'option': {'option': true, 'optgroup': true},
19
+ 'optgroup': {'optgroup': true},
20
+ 'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true,
21
+ 'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true,
22
+ 'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true,
23
+ 'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true,
24
+ 'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true},
25
+ 'rp': {'rp': true, 'rt': true},
26
+ 'rt': {'rp': true, 'rt': true},
27
+ 'tbody': {'tbody': true, 'tfoot': true},
28
+ 'td': {'td': true, 'th': true},
29
+ 'tfoot': {'tbody': true},
30
+ 'th': {'td': true, 'th': true},
31
+ 'thead': {'tbody': true, 'tfoot': true},
32
+ 'tr': {'tr': true}
33
+ },
34
+ doNotIndent: {"pre": true},
35
+ allowUnquoted: true,
36
+ allowMissing: true
37
+ } : {
38
+ autoSelfClosers: {},
39
+ implicitlyClosed: {},
40
+ contextGrabbers: {},
41
+ doNotIndent: {},
42
+ allowUnquoted: false,
43
+ allowMissing: false
44
+ };
45
+ var alignCDATA = parserConfig.alignCDATA;
46
+
47
+ // Return variables for tokenizers
48
+ var tagName, type;
49
+
50
+ function inText(stream, state) {
51
+ function chain(parser) {
52
+ state.tokenize = parser;
53
+ return parser(stream, state);
54
+ }
55
+
56
+ var ch = stream.next();
57
+ if (ch == "<") {
58
+ if (stream.eat("!")) {
59
+ if (stream.eat("[")) {
60
+ if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>"));
61
+ else return null;
62
+ } else if (stream.match("--")) {
63
+ return chain(inBlock("comment", "-->"));
64
+ } else if (stream.match("DOCTYPE", true, true)) {
65
+ stream.eatWhile(/[\w\._\-]/);
66
+ return chain(doctype(1));
67
+ } else {
68
+ return null;
69
+ }
70
+ } else if (stream.eat("?")) {
71
+ stream.eatWhile(/[\w\._\-]/);
72
+ state.tokenize = inBlock("meta", "?>");
73
+ return "meta";
74
+ } else {
75
+ var isClose = stream.eat("/");
76
+ tagName = "";
77
+ var c;
78
+ while ((c = stream.eat(/[^\s\u00a0=<>\"\'\/?]/))) tagName += c;
79
+ if (!tagName) return "error";
80
+ type = isClose ? "closeTag" : "openTag";
81
+ state.tokenize = inTag;
82
+ return "tag";
83
+ }
84
+ } else if (ch == "&") {
85
+ var ok;
86
+ if (stream.eat("#")) {
87
+ if (stream.eat("x")) {
88
+ ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";");
89
+ } else {
90
+ ok = stream.eatWhile(/[\d]/) && stream.eat(";");
91
+ }
92
+ } else {
93
+ ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";");
94
+ }
95
+ return ok ? "atom" : "error";
96
+ } else {
97
+ stream.eatWhile(/[^&<]/);
98
+ return null;
99
+ }
100
+ }
101
+
102
+ function inTag(stream, state) {
103
+ var ch = stream.next();
104
+ if (ch == ">" || (ch == "/" && stream.eat(">"))) {
105
+ state.tokenize = inText;
106
+ type = ch == ">" ? "endTag" : "selfcloseTag";
107
+ return "tag";
108
+ } else if (ch == "=") {
109
+ type = "equals";
110
+ return null;
111
+ } else if (ch == "<") {
112
+ return "error";
113
+ } else if (/[\'\"]/.test(ch)) {
114
+ state.tokenize = inAttribute(ch);
115
+ state.stringStartCol = stream.column();
116
+ return state.tokenize(stream, state);
117
+ } else {
118
+ stream.eatWhile(/[^\s\u00a0=<>\"\']/);
119
+ return "word";
120
+ }
121
+ }
122
+
123
+ function inAttribute(quote) {
124
+ var closure = function(stream, state) {
125
+ while (!stream.eol()) {
126
+ if (stream.next() == quote) {
127
+ state.tokenize = inTag;
128
+ break;
129
+ }
130
+ }
131
+ return "string";
132
+ };
133
+ closure.isInAttribute = true;
134
+ return closure;
135
+ }
136
+
137
+ function inBlock(style, terminator) {
138
+ return function(stream, state) {
139
+ while (!stream.eol()) {
140
+ if (stream.match(terminator)) {
141
+ state.tokenize = inText;
142
+ break;
143
+ }
144
+ stream.next();
145
+ }
146
+ return style;
147
+ };
148
+ }
149
+ function doctype(depth) {
150
+ return function(stream, state) {
151
+ var ch;
152
+ while ((ch = stream.next()) != null) {
153
+ if (ch == "<") {
154
+ state.tokenize = doctype(depth + 1);
155
+ return state.tokenize(stream, state);
156
+ } else if (ch == ">") {
157
+ if (depth == 1) {
158
+ state.tokenize = inText;
159
+ break;
160
+ } else {
161
+ state.tokenize = doctype(depth - 1);
162
+ return state.tokenize(stream, state);
163
+ }
164
+ }
165
+ }
166
+ return "meta";
167
+ };
168
+ }
169
+
170
+ var curState, curStream, setStyle;
171
+ function pass() {
172
+ for (var i = arguments.length - 1; i >= 0; i--) curState.cc.push(arguments[i]);
173
+ }
174
+ function cont() {
175
+ pass.apply(null, arguments);
176
+ return true;
177
+ }
178
+
179
+ function pushContext(tagName, startOfLine) {
180
+ var noIndent = Kludges.doNotIndent.hasOwnProperty(tagName) || (curState.context && curState.context.noIndent);
181
+ curState.context = {
182
+ prev: curState.context,
183
+ tagName: tagName,
184
+ indent: curState.indented,
185
+ startOfLine: startOfLine,
186
+ noIndent: noIndent
187
+ };
188
+ }
189
+ function popContext() {
190
+ if (curState.context) curState.context = curState.context.prev;
191
+ }
192
+
193
+ function element(type) {
194
+ if (type == "openTag") {
195
+ curState.tagName = tagName;
196
+ curState.tagStart = curStream.column();
197
+ return cont(attributes, endtag(curState.startOfLine));
198
+ } else if (type == "closeTag") {
199
+ var err = false;
200
+ if (curState.context) {
201
+ if (curState.context.tagName != tagName) {
202
+ if (Kludges.implicitlyClosed.hasOwnProperty(curState.context.tagName.toLowerCase())) {
203
+ popContext();
204
+ }
205
+ err = !curState.context || curState.context.tagName != tagName;
206
+ }
207
+ } else {
208
+ err = true;
209
+ }
210
+ if (err) setStyle = "error";
211
+ return cont(endclosetag(err));
212
+ }
213
+ return cont();
214
+ }
215
+ function endtag(startOfLine) {
216
+ return function(type) {
217
+ var tagName = curState.tagName;
218
+ curState.tagName = curState.tagStart = null;
219
+ if (type == "selfcloseTag" ||
220
+ (type == "endTag" && Kludges.autoSelfClosers.hasOwnProperty(tagName.toLowerCase()))) {
221
+ maybePopContext(tagName.toLowerCase());
222
+ return cont();
223
+ }
224
+ if (type == "endTag") {
225
+ maybePopContext(tagName.toLowerCase());
226
+ pushContext(tagName, startOfLine);
227
+ return cont();
228
+ }
229
+ return cont();
230
+ };
231
+ }
232
+ function endclosetag(err) {
233
+ return function(type) {
234
+ if (err) setStyle = "error";
235
+ if (type == "endTag") { popContext(); return cont(); }
236
+ setStyle = "error";
237
+ return cont(arguments.callee);
238
+ };
239
+ }
240
+ function maybePopContext(nextTagName) {
241
+ var parentTagName;
242
+ while (true) {
243
+ if (!curState.context) {
244
+ return;
245
+ }
246
+ parentTagName = curState.context.tagName.toLowerCase();
247
+ if (!Kludges.contextGrabbers.hasOwnProperty(parentTagName) ||
248
+ !Kludges.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) {
249
+ return;
250
+ }
251
+ popContext();
252
+ }
253
+ }
254
+
255
+ function attributes(type) {
256
+ if (type == "word") {setStyle = "attribute"; return cont(attribute, attributes);}
257
+ if (type == "endTag" || type == "selfcloseTag") return pass();
258
+ setStyle = "error";
259
+ return cont(attributes);
260
+ }
261
+ function attribute(type) {
262
+ if (type == "equals") return cont(attvalue, attributes);
263
+ if (!Kludges.allowMissing) setStyle = "error";
264
+ else if (type == "word") {setStyle = "attribute"; return cont(attribute, attributes);}
265
+ return (type == "endTag" || type == "selfcloseTag") ? pass() : cont();
266
+ }
267
+ function attvalue(type) {
268
+ if (type == "string") return cont(attvaluemaybe);
269
+ if (type == "word" && Kludges.allowUnquoted) {setStyle = "string"; return cont();}
270
+ setStyle = "error";
271
+ return (type == "endTag" || type == "selfCloseTag") ? pass() : cont();
272
+ }
273
+ function attvaluemaybe(type) {
274
+ if (type == "string") return cont(attvaluemaybe);
275
+ else return pass();
276
+ }
277
+
278
+ return {
279
+ startState: function() {
280
+ return {tokenize: inText, cc: [], indented: 0, startOfLine: true, tagName: null, tagStart: null, context: null};
281
+ },
282
+
283
+ token: function(stream, state) {
284
+ if (!state.tagName && stream.sol()) {
285
+ state.startOfLine = true;
286
+ state.indented = stream.indentation();
287
+ }
288
+ if (stream.eatSpace()) return null;
289
+
290
+ setStyle = type = tagName = null;
291
+ var style = state.tokenize(stream, state);
292
+ state.type = type;
293
+ if ((style || type) && style != "comment") {
294
+ curState = state; curStream = stream;
295
+ while (true) {
296
+ var comb = state.cc.pop() || element;
297
+ if (comb(type || style)) break;
298
+ }
299
+ }
300
+ state.startOfLine = false;
301
+ return setStyle || style;
302
+ },
303
+
304
+ indent: function(state, textAfter, fullLine) {
305
+ var context = state.context;
306
+ // Indent multi-line strings (e.g. css).
307
+ if (state.tokenize.isInAttribute) {
308
+ return state.stringStartCol + 1;
309
+ }
310
+ if ((state.tokenize != inTag && state.tokenize != inText) ||
311
+ context && context.noIndent)
312
+ return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0;
313
+ // Indent the starts of attribute names.
314
+ if (state.tagName) {
315
+ if (multilineTagIndentPastTag)
316
+ return state.tagStart + state.tagName.length + 2;
317
+ else
318
+ return state.tagStart + indentUnit * multilineTagIndentFactor;
319
+ }
320
+ if (alignCDATA && /<!\[CDATA\[/.test(textAfter)) return 0;
321
+ if (context && /^<\//.test(textAfter))
322
+ context = context.prev;
323
+ while (context && !context.startOfLine)
324
+ context = context.prev;
325
+ if (context) return context.indent + indentUnit;
326
+ else return 0;
327
+ },
328
+
329
+ electricChars: "/",
330
+ blockCommentStart: "<!--",
331
+ blockCommentEnd: "-->",
332
+
333
+ configuration: parserConfig.htmlMode ? "html" : "xml",
334
+ helperType: parserConfig.htmlMode ? "html" : "xml"
335
+ };
336
+ });
337
+
338
+ CodeMirror.defineMIME("text/xml", "xml");
339
+ CodeMirror.defineMIME("application/xml", "xml");
340
+ if (!CodeMirror.mimeModes.hasOwnProperty("text/html"))
341
+ CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true});
assets/lib/wck-api/wordpress-creation-kit.php CHANGED
@@ -570,7 +570,10 @@ class Wordpress_Creation_Kit_PB{
570
  $select_options = array();
571
  foreach($field_details['options']['optgroups'] as $optgroup) {
572
  foreach($optgroup['options'] as $group_option ){
573
- $select_options[] = $group_option;
 
 
 
574
  }
575
  }
576
 
@@ -578,7 +581,13 @@ class Wordpress_Creation_Kit_PB{
578
  }
579
 
580
  foreach( $field_details['options'] as $option ){
581
- if ( strpos( $option, $value ) !== false ){
 
 
 
 
 
 
582
  if( strpos( $option, '%' ) === false ){
583
  return $value;
584
  } else {
@@ -591,7 +600,9 @@ class Wordpress_Creation_Kit_PB{
591
  }
592
  }
593
  }
 
594
  }
 
595
  //this was added for select-2 custom values that do not exist in the defined options
596
  return $value;
597
  }
@@ -1322,14 +1333,31 @@ class Wordpress_Creation_Kit_PB{
1322
  }
1323
 
1324
  static function wck_generate_select_option($option, $values, $i, $current_value){
1325
- if( strpos( $option, '%' ) === false ){
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1326
  $label = $option;
1327
  if( !empty( $values ) )
1328
  $value_attr = $values[$i];
1329
  else
1330
  $value_attr = $option;
1331
- }
1332
- else{
1333
  $option_parts = explode( '%', $option );
1334
  if( !empty( $option_parts ) ){
1335
  if( empty( $option_parts[0] ) && count( $option_parts ) == 3 ){
@@ -1349,7 +1377,15 @@ class Wordpress_Creation_Kit_PB{
1349
  }
1350
  }
1351
 
1352
- $optionOutput = '<option value="'. esc_attr( $value_attr ) .'" '. selected( $value_attr, $current_value, false ) .' >'. esc_html( $label ) .'</option>';
 
 
 
 
 
 
 
 
1353
  return $optionOutput;
1354
  }
1355
 
570
  $select_options = array();
571
  foreach($field_details['options']['optgroups'] as $optgroup) {
572
  foreach($optgroup['options'] as $group_option ){
573
+
574
+ if( !is_array( $group_option ) )
575
+ $select_options[] = $group_option;
576
+
577
  }
578
  }
579
 
581
  }
582
 
583
  foreach( $field_details['options'] as $option ){
584
+
585
+ if( is_array( $option ) ){
586
+
587
+ if( !empty( $option['field_name'] ) )
588
+ return $option['field_name'];
589
+
590
+ } elseif ( strpos( $option, $value ) !== false ){
591
  if( strpos( $option, '%' ) === false ){
592
  return $value;
593
  } else {
600
  }
601
  }
602
  }
603
+
604
  }
605
+
606
  //this was added for select-2 custom values that do not exist in the defined options
607
  return $value;
608
  }
1333
  }
1334
 
1335
  static function wck_generate_select_option($option, $values, $i, $current_value){
1336
+
1337
+ $disabled = '';
1338
+
1339
+ if( is_array( $option ) ){
1340
+
1341
+ if( !empty( $option['field_name'] ) ){
1342
+ $label = $option['field_name'];
1343
+
1344
+ if( !empty( $values ) )
1345
+ $value_attr = $values[$i];
1346
+ else
1347
+ $value_attr = $option;
1348
+
1349
+ if( !empty( $option['disabled'] ) && $option['disabled'] == true ){
1350
+ $disabled = ' disabled';
1351
+ }
1352
+ }
1353
+
1354
+ } elseif( strpos( $option, '%' ) === false ){
1355
  $label = $option;
1356
  if( !empty( $values ) )
1357
  $value_attr = $values[$i];
1358
  else
1359
  $value_attr = $option;
1360
+ } else {
 
1361
  $option_parts = explode( '%', $option );
1362
  if( !empty( $option_parts ) ){
1363
  if( empty( $option_parts[0] ) && count( $option_parts ) == 3 ){
1377
  }
1378
  }
1379
 
1380
+ // title is set only for disabled options to let users know those fields are available in a paid version
1381
+ if( !empty( $disabled ) ){
1382
+ if( isset( $value_attr['field_name'] ) && $value_attr['field_name'] == 'Subscription Plans' )
1383
+ $title = esc_attr( __( 'Install the free Paid Member Subscriptions plugin to get access this field.', 'profile-builder' ) );
1384
+ else
1385
+ $title = esc_attr( __( 'This field is available in our paid plans.', 'profile-builder' ) );
1386
+ }
1387
+
1388
+ $optionOutput = '<option value="'. esc_attr( $value_attr ) .'" '. selected( $value_attr, $current_value, false ) . esc_attr( $disabled ) . ( !empty( $disabled ) ? ' title="'. $title .'"' : '' ) . ' >'. esc_html( $label ) .'</option>';
1389
  return $optionOutput;
1390
  }
1391
 
features/email-customizer/admin-email-customizer.php ADDED
@@ -0,0 +1,205 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Function that creates the Admin Email Customizer menu
4
+ *
5
+ * @since v.2.0
6
+ *
7
+ * @return void
8
+ */
9
+ function wppb_admin_email_customizer_submenu(){
10
+ $args = array(
11
+ 'menu_title' => __( 'Admin Email Customizer', 'profile-builder' ),
12
+ 'page_title' => __( 'Admin Email Customizer Settings', 'profile-builder' ),
13
+ 'menu_slug' => 'admin-email-customizer',
14
+ 'page_type' => 'submenu_page',
15
+ 'capability' => 'manage_options',
16
+ 'priority' => 10,
17
+ 'parent_slug' => 'profile-builder'
18
+ );
19
+
20
+ new WCK_Page_Creator_PB( $args );
21
+ }
22
+ add_action( 'admin_menu', 'wppb_admin_email_customizer_submenu', 1 );
23
+
24
+ add_action( 'wck_before_meta_boxes', 'wppb_add_tabs_on_top_of_admin_email_page' );
25
+ function wppb_add_tabs_on_top_of_admin_email_page( $hookname ){
26
+ if( $hookname == 'profile-builder_page_admin-email-customizer' ){
27
+
28
+ if( isset( $_GET['mustache_action'] ) && $_GET['mustache_action'] == 'save' ) { ?>
29
+ <div id="setting-error-settings_updated" class="updated settings-error notice">
30
+ <p><strong><?php esc_html_e( "Settings saved.", 'profile-builder' ); ?></strong></p>
31
+ </div>
32
+ <?php
33
+ }
34
+
35
+ wppb_generate_settings_tabs();
36
+ }
37
+ }
38
+
39
+ /* on the init hook add the mustache boxes */
40
+ add_action( 'init', 'wppb_admin_email_customizer_add_mustache_in_backend', 11 );
41
+ /**
42
+ * Function that ads the mustache boxes in the backend for admin email customizer
43
+ *
44
+ * @since v.2.0
45
+ */
46
+ function wppb_admin_email_customizer_add_mustache_in_backend(){
47
+ if( defined( 'WPPB_PAID_PLUGIN_DIR' ) && file_exists( WPPB_PAID_PLUGIN_DIR . '/assets/lib/class-mustache-templates/class-mustache-templates.php' ) )
48
+ require_once( WPPB_PAID_PLUGIN_DIR . '/assets/lib/class-mustache-templates/class-mustache-templates.php' );
49
+ elseif( file_exists( WPPB_PLUGIN_DIR . '/assets/lib/class-mustache-templates/class-mustache-templates.php' ) )
50
+ require_once( WPPB_PLUGIN_DIR . '/assets/lib/class-mustache-templates/class-mustache-templates.php' );
51
+
52
+ $fields = array(
53
+ array(
54
+ 'id' => 'wppb_admin_emailc_common_settings_header', // field id and name
55
+ 'type' => 'header', // type of field
56
+ 'default' => __( 'These settings are also replicated in the "User Email Customizer" settings-page upon save.', 'profile-builder' ).' '.__( 'Valid tags {{reply_to}} and {{site_name}}', 'profile-builder'), // type of field
57
+ ),
58
+ array(
59
+ 'label' => __( 'From (name)', 'profile-builder' ), // <label>
60
+ 'desc' => '', // description
61
+ 'id' => 'wppb_emailc_common_settings_from_name', // field id and name
62
+ 'type' => 'text', // type of field
63
+ 'default' => '{{site_name}}', // type of field
64
+ 'desc' => '',
65
+ ),
66
+ array(
67
+ 'label' => __( 'From (reply-to email)', 'profile-builder' ), // <label>
68
+ 'desc' => '', // description
69
+ 'id' => 'wppb_emailc_common_settings_from_reply_to_email', // field id and name
70
+ 'type' => 'text', // type of field
71
+ 'default' => '{{reply_to}}', // type of field
72
+ 'desc' => __( 'Must be a valid email address or the tag {{reply_to}} which defaults to the administrator email', 'profile-builder' ),
73
+ ),
74
+ );
75
+ new PB_Mustache_Generate_Admin_Box( 'aec_common_settings', __( 'Common Settings', 'profile-builder' ), 'profile-builder_page_admin-email-customizer', 'core', '', '', $fields );
76
+
77
+
78
+ $registration_default_email_content = __( "<p>New subscriber on {{site_name}}.</p>\n<p>Username:{{username}}</p>\n<p>Email:{{user_email}}</p>\n", 'profile-builder' );
79
+ $mustache_vars = wppb_email_customizer_generate_merge_tags();
80
+
81
+ $fields = array(
82
+ array(
83
+ 'label' => __( 'Email Subject', 'profile-builder' ), // <label>
84
+ 'desc' => '', // description
85
+ 'id' => 'wppb_admin_emailc_default_registration_email_subject', // field id and name
86
+ 'type' => 'text', // type of field
87
+ 'default' => '[{{site_name}}] '.__( 'A new subscriber has (been) registered!', 'profile-builder' ), // type of field
88
+ ),
89
+ array(
90
+ 'label' => __( 'Enable email', 'profile-builder' ), // <label>
91
+ 'desc' => '', // description
92
+ 'id' => 'wppb_admin_emailc_default_registration_email_enabled', // field id and name
93
+ 'type' => 'checkbox', // type of field
94
+ 'default' => 'on',
95
+ ),
96
+ array( // Textarea
97
+ 'label' => '', // <label>
98
+ 'desc' => '', // description
99
+ 'id' => 'wppb_admin_emailc_default_registration_email_content', // field id and name
100
+ 'type' => 'textarea', // type of field
101
+ 'default' => $registration_default_email_content, // type of field
102
+ )
103
+ );
104
+
105
+ new PB_Mustache_Generate_Admin_Box( 'aec_default_registration', __( 'Default Registration & Registration with Email Confirmation', 'profile-builder' ), 'profile-builder_page_admin-email-customizer', 'core', $mustache_vars, '', $fields);
106
+
107
+ if( PROFILE_BUILDER != 'Profile Builder Free' ){
108
+ $registration_admin_approval_email_content = __( "<p>New subscriber on {{site_name}}.</p>\n<p>Username:{{username}}</p>\n<p>Email:{{user_email}}</p>\n<p>The Admin Approval feature was activated at the time of registration,\nso please remember that you need to approve this user before he/she can log in!</p>", 'profile-builder' );
109
+
110
+ $mustache_vars = wppb_email_customizer_generate_merge_tags( 'admin_approval' );
111
+
112
+ $fields = array(
113
+ array(
114
+ 'label' => __( 'Email Subject', 'profile-builder' ), // <label>
115
+ 'desc' => '', // description
116
+ 'id' => 'wppb_admin_emailc_registration_with_admin_approval_email_subject', // field id and name
117
+ 'type' => 'text', // type of field
118
+ 'default' => '[{{site_name}}] A new subscriber has (been) registered!', // type of field
119
+ ),
120
+ array(
121
+ 'label' => __( 'Enable email', 'profile-builder' ), // <label>
122
+ 'desc' => '', // description
123
+ 'id' => 'wppb_admin_emailc_registration_with_admin_approval_email_enabled', // field id and name
124
+ 'type' => 'checkbox', // type of field
125
+ 'default' => 'on',
126
+ ),
127
+ array( // Textarea
128
+ 'label' => '', // <label>
129
+ 'desc' => '', // description
130
+ 'id' => 'wppb_admin_emailc_registration_with_admin_approval_email_content', // field id and name
131
+ 'type' => 'textarea', // type of field
132
+ 'default' => $registration_admin_approval_email_content , // type of field
133
+ )
134
+ );
135
+
136
+ new PB_Mustache_Generate_Admin_Box( 'aec_reg_with_admin_approval', __( 'Registration with Admin Approval', 'profile-builder' ), 'profile-builder_page_admin-email-customizer', 'core', $mustache_vars, '', $fields );
137
+ }
138
+
139
+ $user_password_reset_email_content = __( "<p>{{username}} has requested a password change via the password reset feature.</p>\n", 'profile-builder' );
140
+
141
+ $mustache_vars = wppb_email_customizer_generate_merge_tags();
142
+
143
+ $fields = array(
144
+ array(
145
+ 'label' => __( 'Email Subject', 'profile-builder' ), // <label>
146
+ 'desc' => '', // description
147
+ 'id' => 'wppb_admin_emailc_user_password_reset_email_subject', // field id and name
148
+ 'type' => 'text', // type of field
149
+ 'default' => '[{{site_name}}] Password Successfully Reset for {{username}}', // type of field
150
+ ),
151
+ array(
152
+ 'label' => __( 'Enable email', 'profile-builder' ), // <label>
153
+ 'desc' => '', // description
154
+ 'id' => 'wppb_admin_emailc_user_password_reset_email_enabled', // field id and name
155
+ 'type' => 'checkbox', // type of field
156
+ 'default' => 'on',
157
+ ),
158
+ array( // Textarea
159
+ 'label' => '', // <label>
160
+ 'desc' => '', // description
161
+ 'id' => 'wppb_admin_emailc_user_password_reset_email_content', // field id and name
162
+ 'type' => 'textarea', // type of field
163
+ 'default' => $user_password_reset_email_content , // type of field
164
+ )
165
+ );
166
+
167
+ new PB_Mustache_Generate_Admin_Box( 'aec_user_password_reset', __( 'Admin Notification for User Password Reset', 'profile-builder' ), 'profile-builder_page_admin-email-customizer', 'core', $mustache_vars, '', $fields );
168
+
169
+
170
+ /*
171
+ * Admin Notification for Edit Profile Approved by Admin
172
+ */
173
+
174
+ // check if the Edit Profile Approved by Admin add-on is active to see if the email should be displayed
175
+ if( function_exists( 'wppb_in_init_edit_profile_approval' ) ) {
176
+ // we format the var like this for proper line breaks.
177
+ $aec_epaa_notification = __("<p>The user {{username}} has updated their profile and some of the fields require admin approval:</p>\n<br>\n{{modified_fields}}\n<br>\n<p>Access this link to approve changes: {{approval_url}}</p>\n", 'profile-builder');
178
+ $mustache_vars = wppb_email_customizer_generate_merge_tags('epaa_notification_admin');
179
+ $fields = array(
180
+ array(
181
+ 'label' => __('Email Subject', 'profile-builder'), // <label>
182
+ 'desc' => '', // description
183
+ 'id' => 'wppb_admin_emailc_epaa_notification_subject', // field id and name
184
+ 'type' => 'text', // type of field
185
+ 'default' => __('[{{site_name}}] A user has updated their profile. Some fields need approval', 'profile-builder'), // type of field
186
+ ),
187
+ array(
188
+ 'label' => __('Enable email', 'profile-builder'), // <label>
189
+ 'desc' => '', // description
190
+ 'id' => 'wppb_admin_emailc_epaa_notification_enabled', // field id and name
191
+ 'type' => 'checkbox', // type of field
192
+ 'default' => 'on',
193
+ ),
194
+ array( // Textarea
195
+ 'label' => '', // <label>
196
+ 'desc' => '', // description
197
+ 'id' => 'wppb_admin_emailc_epaa_notification_content', // field id and name
198
+ 'type' => 'textarea', // type of field
199
+ 'default' => $aec_epaa_notification, // type of field
200
+ )
201
+ );
202
+
203
+ new PB_Mustache_Generate_Admin_Box('aec_epaa_notification', __('Admin Notification for Edit Profile Approved by Admin', 'profile-builder'), 'profile-builder_page_admin-email-customizer', 'core', $mustache_vars, '', $fields);
204
+ }
205
+ }
features/email-customizer/email-customizer.php ADDED
@@ -0,0 +1,1286 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ function wppb_email_customizer_generate_merge_tags( $special_merge_tags = null ){
4
+
5
+ $mustache_vars = array(
6
+ array(
7
+ 'group-title' => __( 'Available Tags', 'profile-builder' ),
8
+ 'variables' => wppb_email_customizer_generate_default_meta_merge_tags( $special_merge_tags )
9
+ ),
10
+ array(
11
+ 'group-title' => __( 'User Fields Tags', 'profile-builder' ),
12
+ 'variables' => wppb_email_customizer_generate_meta_merge_tags()
13
+ )
14
+ );
15
+
16
+ return $mustache_vars;
17
+ }
18
+
19
+
20
+ function wppb_email_customizer_generate_default_meta_merge_tags( $special_merge_tags ){
21
+ $merge_tags[] = array( 'name' => 'site_url', 'type' => 'ec_site_url', 'label' => __( 'Site Url', 'profile-builder' ) );
22
+ $merge_tags[] = array( 'name' => 'site_name', 'type' => 'ec_site_name', 'label' => __( 'Site Name', 'profile-builder' ) );
23
+
24
+ if ( $special_merge_tags != 'email_confirmation' ){
25
+ $merge_tags[] = array( 'name' => 'user_id', 'type' => 'ec_user_id', 'label' => __( 'User Id', 'profile-builder' ) );
26
+ }
27
+
28
+ $merge_tags[] = array( 'name' => 'username', 'type' => 'ec_username', 'label' => __( 'Username', 'profile-builder' ) );
29
+ $merge_tags[] = array( 'name' => 'user_email', 'type' => 'ec_user_email', 'label' => __( 'Email', 'profile-builder' ) );
30
+ $merge_tags[] = array( 'name' => 'password', 'type' => 'ec_password', 'label' => __( 'Password', 'profile-builder' ) );
31
+ $merge_tags[] = array( 'name' => 'role', 'type' => 'ec_role', 'label' => __( 'User Role', 'profile-builder' ) );
32
+ $merge_tags[] = array( 'name' => 'role_label', 'type' => 'ec_role_label', 'label' => __( 'User Role Label', 'profile-builder' ) );
33
+ $merge_tags[] = array( 'name' => 'website', 'type' => 'ec_website', 'label' => __( 'Website', 'profile-builder' ) );
34
+ $merge_tags[] = array( 'name' => 'reply_to', 'type' => 'ec_reply_to', 'label' => __( 'Reply To', 'profile-builder' ) );
35
+
36
+ if ( wppb_can_users_signup_blog() ) {
37
+ $merge_tags[] = array('name' => 'blog_url', 'type' => 'ec_blog_url', 'label' => __('Blog URL', 'profile-builder'));
38
+ }
39
+
40
+ if ( $special_merge_tags == 'email_confirmation' ){
41
+ $merge_tags[] = array( 'name' => 'activation_key', 'type' => 'ec_activation_key', 'label' => __( 'Activation Key', 'profile-builder' ) );
42
+ $merge_tags[] = array( 'name' => 'activation_url', 'type' => 'ec_activation_url', 'label' => __( 'Activation Url', 'profile-builder' ) );
43
+ $merge_tags[] = array( 'name' => 'activation_link', 'type' => 'ec_activation_link', 'unescaped' => true, 'label' => __( 'Activation Link', 'profile-builder' ) );
44
+ }
45
+
46
+ if ( $special_merge_tags == 'password_reset' ){
47
+ $merge_tags[] = array( 'name' => 'reset_key', 'type' => 'ec_reset_key', 'label' => __( 'Reset Key', 'profile-builder' ) );
48
+ $merge_tags[] = array( 'name' => 'reset_url', 'type' => 'ec_reset_url', 'label' => __( 'Reset Url', 'profile-builder' ) );
49
+ $merge_tags[] = array( 'name' => 'reset_link', 'type' => 'ec_reset_link', 'unescaped' => true, 'label' => __( 'Reset Link', 'profile-builder' ) );
50
+ }
51
+
52
+ if ( $special_merge_tags == 'change_email_address_request' ){
53
+ $merge_tags[] = array( 'name' => 'user_email_change_link', 'type' => 'ec_user_email_change_link', 'unescaped' => true, 'label' => __( 'Change Email Address Link', 'profile-builder' ) );
54
+ }
55
+
56
+ if ( $special_merge_tags == 'admin_approval' ){
57
+ $merge_tags[] = array( 'name' => 'approve_url', 'type' => 'ec_approve_url', 'label' => __( 'Approve User Url', 'profile-builder' ) );
58
+ $merge_tags[] = array( 'name' => 'approve_link', 'type' => 'ec_approve_link', 'unescaped' => true, 'label' => __( 'Approve User Link', 'profile-builder' ) );
59
+ $merge_tags[] = array( 'name' => 'unapprove_url', 'type' => 'ec_unapprove_url', 'label' => __( 'Unapprove User Url', 'profile-builder' ) );
60
+ $merge_tags[] = array( 'name' => 'unapprove_link', 'type' => 'ec_unapprove_link', 'unescaped' => true, 'label' => __( 'Unapprove User Link', 'profile-builder' ) );
61
+ }
62
+
63
+ if ( $special_merge_tags == 'epaa_notification' ){
64
+ $merge_tags[] = array( 'name' => 'approved_fields', 'type' => 'ec_epaa_approved_fields', 'label' => __( 'Approved Fields', 'profile-builder' ) );
65
+ $merge_tags[] = array( 'name' => 'unapproved_fields', 'type' => 'ec_epaa_unapproved_fields', 'label' => __( 'Unapproved Fields', 'profile-builder' ) );
66
+ }
67
+
68
+ if ( $special_merge_tags == 'epaa_notification_admin' ){
69
+ $merge_tags[] = array( 'name' => 'modified_fields', 'type' => 'ec_epaa_modified_fields', 'label' => __( 'Modified Fields', 'profile-builder' ) );
70
+ $merge_tags[] = array( 'name' => 'approval_url', 'type' => 'ec_epaa_approval_url', 'label' => __( 'Approval URL', 'profile-builder' ) );
71
+ }
72
+
73
+ return $merge_tags;
74
+ }
75
+
76
+
77
+ function wppb_email_customizer_generate_meta_merge_tags(){
78
+ $wppb_manage_fields = apply_filters( 'wppb_email_customizer_get_fields' , get_option( 'wppb_manage_fields', 'not_found' ) );
79
+ $merge_tags = array();
80
+
81
+ if ( $wppb_manage_fields != 'not_found' ){
82
+ foreach( $wppb_manage_fields as $key => $value ){
83
+ if( !empty( $value['meta-name'] ) ){
84
+
85
+ // Skip all map fields
86
+ if( $value['field'] == 'Map' || $value['field'] == 'HTML' )
87
+ continue;
88
+
89
+ $merge_tags[] = array( 'name' => $value['meta-name'], 'type' => 'ec_user_meta', 'label' => $value['field-title'] );
90
+ if( $value['field'] == 'Select' || $value['field'] == 'Select (Multiple)' || $value['field'] == 'Select (Country)' || $value['field'] == 'Select (Currency)' || $value['field'] == 'Checkbox' || $value['field'] == 'Radio' )
91
+ $merge_tags[] = array( 'name' => $value['meta-name'] . '_labels', 'type' => 'ec_user_meta_labels', 'label' => $value['field-title'] . ' Labels' );
92
+ }
93
+ }
94
+ }
95
+
96
+ return apply_filters( 'wppb_email_customizer_get_merge_tags', $merge_tags );
97
+ }
98
+
99
+ function wppb_email_customizer_admin_approval_new_user_signup_filter_handler( $default_string, $user_email, $user_password, $email_sent_from, $option_name ){
100
+ $email_customizer_option = get_option( $option_name, 'not_found' );
101
+ if ( $email_customizer_option != 'not_found' ){
102
+ $user_data = get_user_by( 'email', $user_email );
103
+ $special_merge_tags = ( current_filter() == 'wppb_register_admin_email_message_with_admin_approval' ) ? 'admin_approval' : '';
104
+ wppb_change_email_from_headers();
105
+ return (string) new PB_Mustache_Generate_Template( wppb_email_customizer_generate_merge_tags($special_merge_tags), $email_customizer_option, array( 'user_id' => $user_data->ID, 'user_login' => $user_data->user_login, 'user_email' => $user_email, 'user_password' => $user_password, 'user_data' => $user_data, 'email_sent_from' => $email_sent_from ) );
106
+ }
107
+
108
+ return $default_string;
109
+ }
110
+
111
+ function wppb_email_customizer_admin_approval_new_user_signup_subject_filter_handler( $default_string, $user_email, $user_password, $email_sent_from, $option_name ){
112
+ if( current_user_can( 'delete_users') ) {
113
+ $email_customizer_option = get_option( 'wppb_user_emailc_admin_approval_notif_approved_email_subject', 'not_found' );
114
+ } else {
115
+ $email_customizer_option = get_option( $option_name, 'not_found' );
116
+ }
117
+
118
+ if ( $email_customizer_option != 'not_found' ){
119
+ $user_data = get_user_by( 'email', $user_email );
120
+ wppb_change_email_from_headers();
121
+ return (string) new PB_Mustache_Generate_Template( wppb_email_customizer_generate_merge_tags(), $email_customizer_option, array( 'user_id' => $user_data->ID, 'user_login' => $user_data->user_login, 'user_email' => $user_email, 'user_password' => $user_password, 'user_data' => $user_data, 'email_sent_from' => $email_sent_from ) );
122
+ }
123
+
124
+ return $default_string;
125
+ }
126
+
127
+ function wppb_email_customizer_admin_approval_new_user_signup_message_filter_handler( $default_string, $user_email, $user_password, $email_sent_from, $option_name ){
128
+ if( current_user_can( 'delete_users') ) {
129
+ $email_customizer_option = get_option( 'wppb_user_emailc_admin_approval_notif_approved_email_content', 'not_found' );
130
+ } else {
131
+ $email_customizer_option = get_option( $option_name, 'not_found' );
132
+ }
133
+
134
+ if ( $email_customizer_option != 'not_found' ){
135
+ $user_data = get_user_by( 'email', $user_email );
136
+ wppb_change_email_from_headers();
137
+ return (string) new PB_Mustache_Generate_Template( wppb_email_customizer_generate_merge_tags(), $email_customizer_option, array( 'user_id' => $user_data->ID, 'user_login' => $user_data->user_login, 'user_email' => $user_email, 'user_password' => $user_password, 'user_data' => $user_data, 'email_sent_from' => $email_sent_from ) );
138
+ }
139
+
140
+ return $default_string;
141
+ }
142
+
143
+ function wppb_email_customizer_admin_approval_new_user_status_filter_handler( $default_string, $user_data, $new_status, $email_sent_from, $option_name ){
144
+ $email_customizer_option = get_option( $option_name, 'not_found' );
145
+
146
+ if ( $email_customizer_option != 'not_found' ){
147
+ if( !empty( $user_data ) ){
148
+ wppb_change_email_from_headers();
149
+ return (string) new PB_Mustache_Generate_Template( wppb_email_customizer_generate_merge_tags(), $email_customizer_option, array( 'user_id' => $user_data->ID, 'user_login' => $user_data->user_login, 'user_email' => $user_data->user_email, 'user_password' => $user_data->user_pass, 'user_data' => $user_data, 'email_sent_from' => $email_sent_from ) );
150
+ }
151
+ }
152
+
153
+ return $default_string;
154
+ }
155
+
156
+ function wppb_email_customizer_email_confirmation_filter_handler( $default_string, $user_email, $username, $key, $activation_url, $serialized_data, $email_sent_from, $option_name ){
157
+ $email_customizer_option = get_option( $option_name, 'not_found' );
158
+ if ( $email_customizer_option != 'not_found' ){
159
+ $unserialized_data = unserialize( $serialized_data );
160
+ if( !empty( $unserialized_data ) ){
161
+ wppb_change_email_from_headers();
162
+ return (string) new PB_Mustache_Generate_Template( wppb_email_customizer_generate_merge_tags( 'email_confirmation' ), $email_customizer_option, array( 'user_login' => $unserialized_data['user_login'], 'user_email' => $user_email, 'user_password' => $unserialized_data['user_pass'], 'email_confirmation_unserialized_data' => $unserialized_data, 'email_confirmation_key' => $key, 'email_confirmation_url' => $activation_url, 'email_sent_from' => $email_sent_from ) );
163
+ }
164
+ }
165
+
166
+ return $default_string;
167
+ }
168
+
169
+ function wppb_email_customizer_password_reset_content_filter_handler( $default_string, $user_id, $user_login, $user_email ) {
170
+ $email_customizer_option = get_option( 'wppb_user_emailc_reset_email_content', 'not_found' );
171
+ $key = wppb_retrieve_activation_key( $user_login );
172
+ $url = add_query_arg( array( 'key' => $key ), wppb_curpageurl() );
173
+
174
+ if( $email_customizer_option != 'not_found' ) {
175
+ wppb_change_email_from_headers();
176
+ return (string) new PB_Mustache_Generate_Template( wppb_email_customizer_generate_merge_tags( 'password_reset' ), $email_customizer_option, array( 'user_login' => $user_login, 'reset_key' => $key, 'reset_url' => $url, 'user_email' => $user_email, 'user_id' => $user_id ) );
177
+ }
178
+
179
+ return $default_string;
180
+ }
181
+
182
+ function wppb_email_customizer_password_reset_title_filter_handler( $default_string, $username ) {
183
+ $email_customizer_option = get_option( 'wppb_user_emailc_reset_email_subject', 'not_found' );
184
+
185
+ if( $email_customizer_option != 'not_found' ) {
186
+ wppb_change_email_from_headers();
187
+ $user_info = get_user_by( 'login', $username );
188
+ if( !empty( $user_info->data->user_email ) )
189
+ $user_email = $user_info->data->user_email;
190
+ else
191
+ $user_email = '';
192
+ return (string) new PB_Mustache_Generate_Template( wppb_email_customizer_generate_merge_tags( 'password_reset' ), $email_customizer_option, array( 'user_login' => $username, 'user_email' => $user_email, 'user_id' => $user_info->data->ID ) );
193
+ }
194
+
195
+ return $default_string;
196
+ }
197
+
198
+ function wppb_email_customizer_password_reset_success_content_filter_handler( $default_string, $username, $new_password, $userID ) {
199
+ $email_customizer_option = get_option( 'wppb_user_emailc_reset_success_email_content', 'not_found' );
200
+
201
+ if( $email_customizer_option != 'not_found' ) {
202
+ $user_info = get_userdata( $userID );
203
+ wppb_change_email_from_headers();
204
+ return (string) new PB_Mustache_Generate_Template( wppb_email_customizer_generate_merge_tags(), $email_customizer_option, array( 'user_login' => $username, 'user_password' => $new_password, 'user_id' => $userID, 'user_email' => $user_info->user_email ) );
205
+ }
206
+
207
+ return $default_string;
208
+ }
209
+
210
+ function wppb_email_customizer_password_reset_success_title_filter_handler( $default_string, $username ) {
211
+ $email_customizer_option = get_option( 'wppb_user_emailc_reset_success_email_subject', 'not_found' );
212
+
213
+ if( $email_customizer_option != 'not_found' ) {
214
+ $user_info = get_user_by( 'login', $username );
215
+ /* we could have email instead of username in $username */
216
+ if( !$user_info )
217
+ $user_info = get_user_by( 'email', $username );
218
+
219
+ if( !empty( $user_info->data->user_email ) )
220
+ $user_email = $user_info->data->user_email;
221
+ else
222
+ $user_email = '';
223
+ wppb_change_email_from_headers();
224
+ return (string) new PB_Mustache_Generate_Template( wppb_email_customizer_generate_merge_tags(), $email_customizer_option, array( 'user_login' => $username, 'user_email' => $user_email, 'user_id' => $user_info->data->ID ) );
225
+ }
226
+
227
+ return $default_string;
228
+ }
229
+
230
+ function wppb_admin_email_customizer_password_reset_content_filter_handler( $default_string, $username, $new_password, $userID ) {
231
+ $email_customizer_option = get_option( 'wppb_admin_emailc_user_password_reset_email_content', 'not_found' );
232
+
233
+ if( $email_customizer_option != 'not_found' ) {
234
+ $user_info = get_userdata( $userID );
235
+ wppb_change_email_from_headers();
236
+ return (string) new PB_Mustache_Generate_Template( wppb_email_customizer_generate_merge_tags(), $email_customizer_option, array( 'user_login' => $username, 'user_password' => $new_password, 'user_id' => $userID, 'user_email' => $user_info->user_email ) );
237
+ }
238
+
239
+ return $default_string;
240
+ }
241
+
242
+ function wppb_admin_email_customizer_password_reset_title_filter_handler( $default_string, $username ) {
243
+ $email_customizer_option = get_option( 'wppb_admin_emailc_user_password_reset_email_subject', 'not_found' );
244
+
245
+ if( $email_customizer_option != 'not_found' ) {
246
+ $user_info = get_user_by( 'login', $username );
247
+ /* we could have email instead of username in $username */
248
+ if( !$user_info )
249
+ $user_info = get_user_by( 'email', $username );
250
+
251
+ if( !empty( $user_info->data->user_email ) )
252
+ $user_email = $user_info->data->user_email;
253
+ else
254
+ $user_email = '';
255
+ wppb_change_email_from_headers();
256
+ return (string) new PB_Mustache_Generate_Template( wppb_email_customizer_generate_merge_tags(), $email_customizer_option, array( 'user_login' => $username, 'user_email' => $user_email, 'user_id' => $user_info->data->ID ) );
257
+ }
258
+
259
+ return $default_string;
260
+ }
261
+
262
+ function wppb_email_customizer_user_email_change_request_subject_filter_handler($default_string, $user) {
263
+ $email_customizer_option = get_option( 'wppb_user_emailc_change_email_address_request_subject', 'not_found' );
264
+
265
+ if( $email_customizer_option != 'not_found' && class_exists( 'PB_Mustache_Generate_Template' ) ) {
266
+ wppb_change_email_from_headers();
267
+ return (string) new PB_Mustache_Generate_Template( wppb_email_customizer_generate_merge_tags( 'change_email_address_request' ), $email_customizer_option, array( 'user_login' => $user->user_login, 'user_email' => $user->user_email, 'user_id' => $user->ID ));
268
+ }
269
+
270
+ return $default_string;
271
+ }
272
+
273
+ function wppb_email_customizer_user_email_change_request_content_filter_handler($default_string, $user, $email_change_request_url) {
274
+ $email_customizer_option = get_option( 'wppb_user_emailc_change_email_address_request_content', 'not_found' );
275
+
276
+ if( $email_customizer_option != 'not_found' && class_exists( 'PB_Mustache_Generate_Template' ) ) {
277
+ wppb_change_email_from_headers();
278
+
279
+ return (string) new PB_Mustache_Generate_Template( wppb_email_customizer_generate_merge_tags( 'change_email_address_request' ), $email_customizer_option, array( 'user_login' => $user->user_login, 'user_email' => $user->user_email, 'user_id' => $user->ID, 'change_email_url' => $email_change_request_url ));
280
+ }
281
+
282
+ return $default_string;
283
+ }
284
+
285
+ function wppb_email_customizer_change_email_address_title_filter_handler ( $default, $old_user_data, $new_user_data ){
286
+ $email_customizer_option = get_option( 'wppb_user_emailc_change_email_address_subject', 'not_found' );
287
+
288
+ if( $email_customizer_option != 'not_found' && class_exists( 'PB_Mustache_Generate_Template' ) ) {
289
+ $email_change_email = $default;
290
+ $subject = (string) new PB_Mustache_Generate_Template( wppb_email_customizer_generate_merge_tags(), $email_customizer_option, array( 'user_login' => $old_user_data['user_login'], 'user_email' => $old_user_data['user_email'], 'user_id' => $old_user_data['ID'] ) );
291
+ $email_change_email['subject'] = html_entity_decode( htmlspecialchars_decode( $subject, ENT_QUOTES ), ENT_QUOTES );
292
+
293
+ wppb_change_email_from_headers();
294
+ return $email_change_email;
295
+ }
296
+
297
+ return $default;
298
+ }
299
+
300
+ function wppb_email_customizer_change_email_address_header_filter_handler( $email_change_email, $user, $userdata ){
301
+ $email_change_email['headers'] = array( 'Content-Type: text/html; charset=UTF-8' );
302
+ return $email_change_email;
303
+ }
304
+
305
+ function wppb_email_customizer_change_email_address_content_filter_handler ( $default, $old_user_data, $new_user_data ){
306
+ $email_customizer_option = get_option( 'wppb_user_emailc_change_email_address_content', 'not_found' );
307
+
308
+ if( $email_customizer_option != 'not_found' && class_exists( 'PB_Mustache_Generate_Template' ) ) {
309
+
310
+ $email_change_email = $default;
311
+ $email_content = (string) new PB_Mustache_Generate_Template( wppb_email_customizer_generate_merge_tags(), $email_customizer_option, array( 'user_login' => $old_user_data['user_login'], 'user_email' => $old_user_data['user_email'], 'user_id' => $old_user_data['ID'] ) );
312
+ $email_change_email['message'] = $email_content;
313
+
314
+ wppb_change_email_from_headers();
315
+ return $email_change_email;
316
+ }
317
+
318
+ return $default;
319
+ }
320
+
321
+ function wppb_email_customizer_epaa_title_filter_handler ( $default, $user_id, $approved_field_names, $unapproved_field_names ){
322
+ $email_customizer_option = get_option( 'wppb_user_emailc_epaa_notification_subject', 'not_found' );
323
+
324
+ if( $email_customizer_option != 'not_found' && class_exists( 'PB_Mustache_Generate_Template' ) ) {
325
+ $user_info = get_user_by( 'id', $user_id );
326
+ $subject = (string) new PB_Mustache_Generate_Template( wppb_email_customizer_generate_merge_tags( 'epaa_notification' ), $email_customizer_option, array( 'user_login' => $user_info->data->user_login, 'user_email' => $user_info->data->user_email, 'user_id' => $user_info->data->ID, 'approved_field_names' => $approved_field_names, 'unapproved_field_names' => $unapproved_field_names ) );
327
+
328
+ wppb_change_email_from_headers();
329
+ return $subject;
330
+ }
331
+
332
+ return $default;
333
+ }
334
+
335
+ function wppb_email_customizer_epaa_content_filter_handler ( $default, $user_id, $approved_field_names, $unapproved_field_names ){
336
+ $email_customizer_option = get_option( 'wppb_user_emailc_epaa_notification_content', 'not_found' );
337
+
338
+ if( $email_customizer_option != 'not_found' && class_exists( 'PB_Mustache_Generate_Template' ) ) {
339
+ $user_info = get_user_by( 'id', $user_id );
340
+ $email_content = (string) new PB_Mustache_Generate_Template( wppb_email_customizer_generate_merge_tags( 'epaa_notification' ), $email_customizer_option, array( 'user_login' => $user_info->data->user_login, 'user_email' => $user_info->data->user_email, 'user_id' => $user_info->data->ID, 'approved_field_names' => $approved_field_names, 'unapproved_field_names' => $unapproved_field_names ) );
341
+
342
+ wppb_change_email_from_headers();
343
+ return $email_content;
344
+ }
345
+
346
+ return $default;
347
+ }
348
+
349
+ function wppb_admin_email_customizer_epaa_title_filter_handler ( $default, $user_id, $fields, $approve_url ){
350
+ $email_customizer_option = get_option( 'wppb_admin_emailc_epaa_notification_subject', 'not_found' );
351
+
352
+ if( $email_customizer_option != 'not_found' && class_exists( 'PB_Mustache_Generate_Template' ) ) {
353
+ $user_info = get_user_by( 'id', $user_id );
354
+ $subject = (string) new PB_Mustache_Generate_Template( wppb_email_customizer_generate_merge_tags( 'epaa_notification_admin' ), $email_customizer_option, array( 'user_login' => $user_info->data->user_login, 'user_email' => $user_info->data->user_email, 'user_id' => $user_info->data->ID, 'modified_fields' => $fields, 'approval_url' => $approve_url ) );
355
+
356
+ wppb_change_email_from_headers();
357
+ return $subject;
358
+ }
359
+
360
+ return $default;
361
+ }
362
+
363
+ function wppb_admin_email_customizer_epaa_content_filter_handler ( $default, $user_id, $fields, $approve_url ){
364
+ $email_customizer_option = get_option( 'wppb_admin_emailc_epaa_notification_content', 'not_found' );
365
+
366
+ if( $email_customizer_option != 'not_found' && class_exists( 'PB_Mustache_Generate_Template' ) ) {
367
+ $user_info = get_user_by( 'id', $user_id );
368
+ $email_content = (string) new PB_Mustache_Generate_Template( wppb_email_customizer_generate_merge_tags( 'epaa_notification_admin' ), $email_customizer_option, array( 'user_login' => $user_info->data->user_login, 'user_email' => $user_info->data->user_email, 'user_id' => $user_info->data->ID, 'modified_fields' => $fields, 'approval_url' => $approve_url ) );
369
+
370
+ wppb_change_email_from_headers();
371
+ return html_entity_decode( htmlspecialchars_decode( $email_content, ENT_QUOTES ), ENT_QUOTES );
372
+ }
373
+
374
+ return $default;
375
+ }
376
+
377
+
378
+ $wppb_addonOptions = get_option( 'wppb_module_settings' );
379
+ $wppb_email_customizer_activate = 'hide';
380
+ if ( ( !empty( $wppb_addonOptions['wppb_emailCustomizer'] ) && $wppb_addonOptions['wppb_emailCustomizer'] == 'show' ) || ( !empty( $wppb_addonOptions['wppb_emailCustomizerAdmin'] ) && $wppb_addonOptions['wppb_emailCustomizerAdmin'] == 'show' ) ) {
381
+ $wppb_email_customizer_activate = 'show';
382
+ }
383
+ // using filters, we overwrite the old content with the new one from the email customizer (for the user)
384
+ if ( $wppb_email_customizer_activate == 'show' ){
385
+ add_filter ( 'wppb_register_user_email_subject_without_admin_approval', 'wppb_email_customizer_admin_approval_new_user_signup_filter_handler', 10, 5 );
386
+ add_filter ( 'wppb_register_user_email_message_without_admin_approval', 'wppb_email_customizer_admin_approval_new_user_signup_filter_handler', 10, 5 );
387
+
388
+ add_filter ( 'wppb_register_user_email_subject_with_admin_approval', 'wppb_email_customizer_admin_approval_new_user_signup_subject_filter_handler', 10, 5 );
389
+ add_filter ( 'wppb_register_user_email_message_with_admin_approval', 'wppb_email_customizer_admin_approval_new_user_signup_message_filter_handler', 10, 5 );
390
+
391
+ add_filter ( 'wppb_new_user_status_subject_approved', 'wppb_email_customizer_admin_approval_new_user_status_filter_handler', 10, 5 );
392
+ add_filter ( 'wppb_new_user_status_message_approved', 'wppb_email_customizer_admin_approval_new_user_status_filter_handler', 10, 5 );
393
+
394
+ add_filter ( 'wppb_new_user_status_subject_unapproved', 'wppb_email_customizer_admin_approval_new_user_status_filter_handler', 10, 5 );
395
+ add_filter ( 'wppb_new_user_status_message_unapproved', 'wppb_email_customizer_admin_approval_new_user_status_filter_handler', 10, 5 );
396
+
397
+ add_filter ( 'wppb_signup_user_notification_email_subject', 'wppb_email_customizer_email_confirmation_filter_handler', 10, 8 );
398
+ add_filter ( 'wppb_signup_user_notification_email_content', 'wppb_email_customizer_email_confirmation_filter_handler', 10, 8 );
399
+
400
+ add_filter ( 'wppb_recover_password_message_content_sent_to_user1', 'wppb_email_customizer_password_reset_content_filter_handler', 10, 4 );
401
+ add_filter ( 'wppb_recover_password_message_title_sent_to_user1', 'wppb_email_customizer_password_reset_title_filter_handler', 10, 2 );
402
+
403
+ add_filter ( 'wppb_recover_password_message_content_sent_to_user2', 'wppb_email_customizer_password_reset_success_content_filter_handler', 10, 4 );
404
+ add_filter ( 'wppb_recover_password_message_title_sent_to_user2', 'wppb_email_customizer_password_reset_success_title_filter_handler', 10, 2 );
405
+
406
+ add_filter ( 'wppb_user_email_change_request_notification_subject', 'wppb_email_customizer_user_email_change_request_subject_filter_handler', 10, 3);
407
+ add_filter ( 'wppb_user_email_change_request_notification_content', 'wppb_email_customizer_user_email_change_request_content_filter_handler', 10, 3);
408
+
409
+ add_filter ( 'email_change_email', 'wppb_email_customizer_change_email_address_content_filter_handler', 10, 3);
410
+ add_filter ( 'email_change_email', 'wppb_email_customizer_change_email_address_title_filter_handler', 10, 3);
411
+ add_filter ( 'email_change_email', 'wppb_email_customizer_change_email_address_header_filter_handler', 10, 3);
412
+
413
+ if( function_exists( 'wppb_in_init_edit_profile_approval' ) ) {
414
+ add_filter('wppb_epaa_user_email_content', 'wppb_email_customizer_epaa_content_filter_handler', 10, 4);
415
+ add_filter('wppb_epaa_user_email_subject', 'wppb_email_customizer_epaa_title_filter_handler', 10, 4);
416
+ }
417
+
418
+ }
419
+
420
+ // using filters, we overwrite the old content with the new one from the email customizer (for the admin)
421
+ if ( $wppb_email_customizer_activate == 'show' ){
422
+ add_filter ( 'wppb_register_admin_email_subject_without_admin_approval', 'wppb_email_customizer_admin_approval_new_user_signup_filter_handler', 10, 5 );
423
+ add_filter ( 'wppb_register_admin_email_message_without_admin_approval', 'wppb_email_customizer_admin_approval_new_user_signup_filter_handler', 10, 5 );
424
+
425
+ add_filter ( 'wppb_register_admin_email_subject_with_admin_approval', 'wppb_email_customizer_admin_approval_new_user_signup_filter_handler', 10, 5 );
426
+ add_filter ( 'wppb_register_admin_email_message_with_admin_approval', 'wppb_email_customizer_admin_approval_new_user_signup_filter_handler', 10, 5 );
427
+
428
+ add_filter ( 'wppb_recover_password_message_content_sent_to_admin', 'wppb_admin_email_customizer_password_reset_content_filter_handler', 10, 4 );
429
+ add_filter ( 'wppb_recover_password_message_title_sent_to_admin', 'wppb_admin_email_customizer_password_reset_title_filter_handler', 10, 2 );
430
+
431
+ if( function_exists( 'wppb_in_init_edit_profile_approval' ) ) {
432
+ add_filter('wppb_epaa_admin_email_content', 'wppb_admin_email_customizer_epaa_content_filter_handler', 10, 4);
433
+ add_filter('wppb_epaa_admin_email_subject', 'wppb_admin_email_customizer_epaa_title_filter_handler', 10, 4);
434
+ }
435
+
436
+ }
437
+
438
+
439
+ // Mustache variables
440
+
441
+ /**
442
+ * Function that overwrites the default reply-to with the one set in the Email Customizer
443
+ *
444
+ * @since v.2.0
445
+ *
446
+ * @param string $sender_email
447
+ *
448
+ * @return string
449
+ */
450
+ function wppb_ec_website_email( $value, $merge_tag_name, $merge_tag, $extra_data ){
451
+ $admin_email_address = get_bloginfo( 'admin_email' );
452
+
453
+ $emailc_common_settings_from_reply_to_email = get_option( 'wppb_emailc_common_settings_from_reply_to_email', 'not_found' );
454
+ if ( $emailc_common_settings_from_reply_to_email != 'not_found' ) {
455
+ if( strpos( $emailc_common_settings_from_reply_to_email, '{{reply_to}}' ) === false ) {
456
+ if( is_email( trim( $emailc_common_settings_from_reply_to_email ) ) )
457
+ $admin_email_address = trim( $emailc_common_settings_from_reply_to_email );
458
+ }
459
+ }
460
+
461
+ return apply_filters( 'wppb_ec_sender_email_filter', trim( $admin_email_address ) );
462
+ }
463
+ add_filter( 'mustache_variable_ec_reply_to', 'wppb_ec_website_email', 10, 4 );
464
+
465
+
466
+ /**
467
+ * Function that filters and returns the site_url in the Email Customizer
468
+ *
469
+ * @since v.2.0
470
+ *
471
+ * @param string $value
472
+ * @param string $merge_tag_name
473
+ * @param string $merge_tag
474
+ * @param array $extra_data
475
+ *
476
+ * @return string
477
+ */
478
+ function wppb_ec_replace_site_url( $value, $merge_tag_name, $merge_tag, $extra_data ){
479
+ return apply_filters( 'wppb_ec_site_url_filter', get_bloginfo( 'url' ) );
480
+ }
481
+ add_filter( 'mustache_variable_ec_site_url', 'wppb_ec_replace_site_url', 10, 4 );
482
+
483
+
484
+ /**
485
+ * Function that filters and returns the site_name in the Email Customizer
486
+ *
487
+ * @since v.2.0
488
+ *
489
+ * @param string $value
490
+ * @param string $merge_tag_name
491
+ * @param string $merge_tag
492
+ * @param array $extra_data
493
+ *
494
+ * @return string
495
+ */
496
+ function wppb_ec_replace_site_name( $value, $merge_tag_name, $merge_tag, $extra_data ){
497
+ return apply_filters( 'wppb_ec_site_name_filter', html_entity_decode( htmlspecialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ), ENT_QUOTES ) );
498
+ }
499
+ add_filter( 'mustache_variable_ec_site_name', 'wppb_ec_replace_site_name', 10, 4 );
500
+
501
+
502
+ /**
503
+ * Function that filters and returns the user_id in the Email Customizer
504
+ *
505
+ * @since v.2.0
506
+ *
507
+ * @param string $value
508
+ * @param string $merge_tag_name
509
+ * @param string $merge_tag
510
+ * @param array $extra_data
511
+ *
512
+ * @return string
513
+ */
514
+ function wppb_ec_replace_user_id( $value, $merge_tag_name, $merge_tag, $extra_data ){
515
+ if( !empty( $extra_data['user_id'] ) )
516
+ return $extra_data['user_id'];
517
+ else
518
+ return '';
519
+ }
520
+ add_filter( 'mustache_variable_ec_user_id', 'wppb_ec_replace_user_id', 10, 4 );
521
+
522
+
523
+ /**
524
+ * Function that filters and returns the username in the Email Customizer
525
+ *
526
+ * @since v.2.0
527
+ *
528
+ * @param string $value
529
+ * @param string $merge_tag_name
530
+ * @param string $merge_tag
531
+ * @param array $extra_data
532
+ *
533
+ * @return string
534
+ */
535
+ function wppb_ec_replace_username( $value, $merge_tag_name, $merge_tag, $extra_data ){
536
+ $wppb_general_settings = get_option( 'wppb_general_settings' );
537
+
538
+ if ( isset( $extra_data['email_confirmation_unserialized_data']['user_login'] ) ){
539
+ if( isset( $wppb_general_settings['loginWith'] ) && ( $wppb_general_settings['loginWith'] == 'email' ) )
540
+ return $extra_data['email_confirmation_unserialized_data']['user_email'];
541
+ else
542
+ return $extra_data['email_confirmation_unserialized_data']['user_login'];
543
+ }
544
+
545
+ if( isset( $wppb_general_settings['loginWith'] ) && ( $wppb_general_settings['loginWith'] == 'email' ) )
546
+ return $extra_data['user_email'];
547
+ else
548
+ return $extra_data['user_login'];
549
+
550
+ }
551
+ add_filter( 'mustache_variable_ec_username', 'wppb_ec_replace_username', 10, 4 );
552
+
553
+
554
+ /**
555
+ * Function that filters and returns the user_email in the Email Customizer
556
+ *
557
+ * @since v.2.0
558
+ *
559
+ * @param string $value
560
+ * @param string $merge_tag_name
561
+ * @param string $merge_tag
562
+ * @param array $extra_data
563
+ *
564
+ * @return string
565
+ */
566
+ function wppb_ec_replace_user_email( $value, $merge_tag_name, $merge_tag, $extra_data ){
567
+ if ( isset( $extra_data['email_confirmation_unserialized_data']['user_email'] ) )
568
+ return $extra_data['email_confirmation_unserialized_data']['user_email'];
569
+
570
+ if( !empty( $extra_data['user_email'] ) )
571
+ return $extra_data['user_email'];
572
+ else
573
+ return '';
574
+ }
575
+ add_filter( 'mustache_variable_ec_user_email', 'wppb_ec_replace_user_email', 10, 4 );
576
+
577
+ /**
578
+ * Function that filters and returns the users password in the Email Customizer
579
+ *
580
+ * @since v.2.0
581
+ *
582
+ * @param string $value
583
+ * @param string $merge_tag_name
584
+ * @param string $merge_tag
585
+ * @param array $extra_data
586
+ *
587
+ * @return string
588
+ */
589
+ function wppb_ec_replace_password( $value, $merge_tag_name, $merge_tag, $extra_data ){
590
+ /* don't send the password to the admin */
591
+ global $wp_current_filter;
592
+ if( !empty( $wp_current_filter ) && is_array( $wp_current_filter ) ){
593
+ foreach( $wp_current_filter as $filter ){
594
+ if( $filter == 'wppb_register_admin_email_subject_without_admin_approval' ||
595
+ $filter == 'wppb_register_admin_email_message_without_admin_approval' ||
596
+ $filter == 'wppb_register_admin_email_subject_with_admin_approval' ||
597
+ $filter == 'wppb_register_admin_email_message_with_admin_approval' ||
598
+ $filter == 'wppb_recover_password_message_content_sent_to_admin' ||
599
+ $filter == 'wppb_recover_password_message_title_sent_to_admin'
600
+ ){
601
+ return __( 'The users selected password at signup', 'profile-builder' );
602
+ }
603
+ }
604
+ }
605
+
606
+ $wppb_general_settings = get_option( 'wppb_general_settings' );
607
+
608
+ if( empty( $extra_data['email_confirmation_unserialized_data']['user_pass'] ) && empty( $extra_data['user_password'] ) ){
609
+ return __( 'Your selected password at signup', 'profile-builder' );
610
+ }
611
+
612
+ if ( isset( $extra_data['email_confirmation_unserialized_data']['user_pass'] ) ) {
613
+ if( base64_encode( base64_decode($extra_data['email_confirmation_unserialized_data']['user_pass'] ) ) === $extra_data['email_confirmation_unserialized_data']['user_pass'] )
614
+ return base64_decode($extra_data['email_confirmation_unserialized_data']['user_pass']);
615
+ else
616
+ return __( 'Your selected password at signup', 'profile-builder' );
617
+ }
618
+
619
+ if( wppb_get_admin_approval_option_value() === 'yes' ){
620
+ if ( isset( $extra_data['user_password'] ) ) {
621
+ global $wpdb;
622
+ $search_for_password = $wpdb->get_results(
623
+ "
624
+ SELECT user_pass
625
+ FROM $wpdb->users
626
+ WHERE user_pass = '".$extra_data['user_password']."'
627
+ "
628
+ );
629
+ if( !empty( $search_for_password ) )
630
+ return __( 'Your selected password at signup', 'profile-builder' );
631
+ }
632
+ }
633
+
634
+ return $extra_data['user_password'];
635
+ }
636
+ add_filter( 'mustache_variable_ec_password', 'wppb_ec_replace_password', 10, 4 );
637
+
638
+
639
+ /**
640
+ * Function that filters and returns the users website in the Email Customizer
641
+ *
642
+ * @since v.2.0
643
+ *
644
+ * @param string $value
645
+ * @param string $merge_tag_name
646
+ * @param string $merge_tag
647
+ * @param array $extra_data
648
+ *
649
+ * @return string
650
+ */
651
+ function wppb_ec_replace_website( $value, $merge_tag_name, $merge_tag, $extra_data ){
652
+ if ( isset( $extra_data['email_confirmation_unserialized_data']['user_url'] ) )
653
+ return $extra_data['email_confirmation_unserialized_data']['user_url'];
654
+ if( !empty( $extra_data['user_data'] ) )
655
+ return $extra_data['user_data']->user_url;
656
+ }
657
+ add_filter( 'mustache_variable_ec_website', 'wppb_ec_replace_website', 10, 4 );
658
+
659
+
660
+ /**
661
+ * Function that filters and returns the users blog url in the Email Customizer
662
+ *
663
+ * @since v.2.5.5
664
+ *
665
+ * @param string $value
666
+ * @param string $merge_tag_name
667
+ * @param string $merge_tag
668
+ * @param array $extra_data
669
+ *
670
+ * @return string
671
+ */
672
+ function wppb_ec_replace_blog_url( $value, $merge_tag_name, $merge_tag, $extra_data ){
673
+ if ( isset( $extra_data['email_confirmation_unserialized_data']['wppb_create_new_site_checkbox'] ) && $extra_data['email_confirmation_unserialized_data']['wppb_create_new_site_checkbox'] == 'yes' ) {
674
+ $blog_details = wpmu_validate_blog_signup( $extra_data['email_confirmation_unserialized_data']['wppb_blog_url'], $extra_data['email_confirmation_unserialized_data']['wppb_blog_title'] );
675
+ if ( empty($blog_details['errors']->errors['blogname']) && empty($blog_details['errors']->errors['blog_title']))
676
+ return $blog_details['domain'] . $blog_details['path'];
677
+ }
678
+
679
+ if( !empty( $extra_data['user_data'] ) ) {
680
+ return wppb_get_blog_url_of_user_id($extra_data['user_data']->ID);
681
+ }
682
+ }
683
+ add_filter( 'mustache_variable_ec_blog_url', 'wppb_ec_replace_blog_url', 10, 4 );
684
+
685
+
686
+ /**
687
+ * Function that filters and returns the users role in the Email Customizer
688
+ *
689
+ * @since v.2.0
690
+ *
691
+ * @param string $value
692
+ * @param string $merge_tag_name
693
+ * @param string $merge_tag
694
+ * @param array $extra_data
695
+ *
696
+ * @return string
697
+ */
698
+ function wppb_ec_replace_role( $value, $merge_tag_name, $merge_tag, $extra_data ){
699
+ if( isset( $extra_data['email_confirmation_unserialized_data']['role'] ) ) {
700
+ return $extra_data['email_confirmation_unserialized_data']['role'];
701
+ }
702
+
703
+ if( ! empty( $extra_data['user_data'] ) ) {
704
+ $user_role = implode( ', ', $extra_data['user_data']->roles );
705
+ return $user_role;
706
+ }
707
+
708
+ if( ! empty ( $extra_data['user_id'] ) ) {
709
+ $user_data = get_user_by( 'id', $extra_data['user_id'] );
710
+ $user_role = implode( ', ', $user_data->roles );
711
+ return $user_role;
712
+ }
713
+ }
714
+ add_filter( 'mustache_variable_ec_role', 'wppb_ec_replace_role', 10, 4 );
715
+
716
+ /**
717
+ * Function that filters and returns the users role label in the Email Customizer
718
+ *
719
+ * @since v.2.7.1
720
+ *
721
+ * @param string $value
722
+ * @param string $merge_tag_name
723
+ * @param string $merge_tag
724
+ * @param array $extra_data
725
+ *
726
+ * @return string
727
+ */
728
+ function wppb_ec_replace_role_label( $value, $merge_tag_name, $merge_tag, $extra_data ){
729
+ $roles = explode(', ', wppb_ec_replace_role( $value, $merge_tag_name, $merge_tag, $extra_data ) );
730
+ $role_labels = '';
731
+
732
+ foreach ($roles as $role) {
733
+ $role_labels .= wppb_get_role_name( $role ) . ', ';
734
+ }
735
+
736
+ return rtrim($role_labels, ', ');
737
+ }
738
+ add_filter( 'mustache_variable_ec_role_label', 'wppb_ec_replace_role_label', 10, 4 );
739
+
740
+ /**
741
+ * Function that filters and returns the users activation_key in the Email Customizer
742
+ *
743
+ * @since v.2.0
744
+ *
745
+ * @param string $value
746
+ * @param string $merge_tag_name
747
+ * @param string $merge_tag
748
+ * @param array $extra_data
749
+ *
750
+ * @return string
751
+ */
752
+ function wppb_ec_replace_activation_key( $value, $merge_tag_name, $merge_tag, $extra_data ){
753
+ if ( isset( $extra_data['email_confirmation_key'] ) )
754
+ return $extra_data['email_confirmation_key'];
755
+ }
756
+ add_filter( 'mustache_variable_ec_activation_key', 'wppb_ec_replace_activation_key', 10, 4 );
757
+
758
+
759
+ /**
760
+ * Function that filters and returns the users activation_url in the Email Customizer
761
+ *
762
+ * @since v.2.0
763
+ *
764
+ * @param string $value
765
+ * @param string $merge_tag_name
766
+ * @param string $merge_tag
767
+ * @param array $extra_data
768
+ *
769
+ * @return string
770
+ */
771
+ function wppb_ec_replace_activation_url( $value, $merge_tag_name, $merge_tag, $extra_data ){
772
+ if ( isset( $extra_data['email_confirmation_url'] ) )
773
+ return $extra_data['email_confirmation_url'];
774
+ }
775
+ add_filter( 'mustache_variable_ec_activation_url', 'wppb_ec_replace_activation_url', 10, 4 );
776
+
777
+
778
+ /**
779
+ * Function that filters and returns the users activation_link in the Email Customizer
780
+ *
781
+ * @since v.2.0
782
+ *
783
+ * @param string $value
784
+ * @param string $merge_tag_name
785
+ * @param string $merge_tag
786
+ * @param array $extra_data
787
+ *
788
+ * @return string
789
+ */
790
+ function wppb_ec_replace_activation_link( $value, $merge_tag_name, $merge_tag, $extra_data ){
791
+ if ( isset( $extra_data['email_confirmation_url'] ) )
792
+ return '<a href="'.$extra_data['email_confirmation_url'].'" target="_blank">'.$extra_data['email_confirmation_url'].'</a>';
793
+ }
794
+ add_filter( 'mustache_variable_ec_activation_link', 'wppb_ec_replace_activation_link', 10, 4 );
795
+
796
+
797
+ /**
798
+ * Function that filters and returns the users reset_key in the Email Customizer
799
+ *
800
+ * @since v.2.0
801
+ *
802
+ * @param string $value
803
+ * @param string $merge_tag_name
804
+ * @param string $merge_tag
805
+ * @param array $extra_data
806
+ *
807
+ * @return string
808
+ */
809
+ function wppb_ec_replace_reset_key( $value, $merge_tag_name, $merge_tag, $extra_data ){
810
+ if ( isset( $extra_data['reset_key'] ) )
811
+ return $extra_data['reset_key'];
812
+ }
813
+ add_filter( 'mustache_variable_ec_reset_key', 'wppb_ec_replace_reset_key', 10, 4 );
814
+
815
+
816
+ /**
817
+ * Function that filters and returns the users reset_url in the Email Customizer
818
+ *
819
+ * @since v.2.0
820
+ *
821
+ * @param string $value
822
+ * @param string $merge_tag_name
823
+ * @param string $merge_tag
824
+ * @param array $extra_data
825
+ *
826
+ * @return string
827
+ */
828
+ function wppb_ec_replace_reset_url( $value, $merge_tag_name, $merge_tag, $extra_data ){
829
+ if ( isset( $extra_data['reset_url'] ) )
830
+ return $extra_data['reset_url'];
831
+ }
832
+ add_filter( 'mustache_variable_ec_reset_url', 'wppb_ec_replace_reset_url', 10, 4 );
833
+
834
+
835
+ /**
836
+ * Function that filters and returns the users reset_link in the Email Customizer
837
+ *
838
+ * @since v.2.0
839
+ *
840
+ * @param string $value
841
+ * @param string $merge_tag_name
842
+ * @param string $merge_tag
843
+ * @param array $extra_data
844
+ *
845
+ * @return string
846
+ */
847
+ function wppb_ec_replace_reset_link( $value, $merge_tag_name, $merge_tag, $extra_data ){
848
+ if ( isset( $extra_data['reset_url'] ) )
849
+ return '<a href="'.$extra_data['reset_url'].'" target="_blank">'.$extra_data['reset_url'].'</a>';
850
+ }
851
+ add_filter( 'mustache_variable_ec_reset_link', 'wppb_ec_replace_reset_link', 10, 4 );
852
+
853
+
854
+ /**
855
+ * Function that filters and returns the users user_email_change_link in the Email Customizer
856
+ *
857
+ * @since v.
858
+ *
859
+ * @param string $value
860
+ * @param string $merge_tag_name
861
+ * @param string $merge_tag
862
+ * @param array $extra_data
863
+ *
864
+ * @return string
865
+ */
866
+ function wppb_ec_generate_user_email_change_link( $value, $merge_tag_name, $merge_tag, $extra_data ){
867
+ if ( isset( $extra_data['change_email_url'] ) )
868
+ return $extra_data['change_email_url'];
869
+ }
870
+ add_filter( 'mustache_variable_ec_user_email_change_link', 'wppb_ec_generate_user_email_change_link', 10, 4 );
871
+
872
+
873
+ /**
874
+ * Function that filters and returns the approve user link in the (Admin) Email Customizer.
875
+ * This allows the admin to approve directly from his email a newly registered user.
876
+ *
877
+ * @since v.2.7.3
878
+ *
879
+ * @param string $value
880
+ * @param string $merge_tag_name
881
+ * @param string $merge_tag
882
+ * @param array $extra_data
883
+ *
884
+ * @return string
885
+ */
886
+ function wppb_ec_replace_approve_user_link( $value, $merge_tag_name, $merge_tag, $extra_data ){
887
+
888
+ if( ! empty ( $extra_data['user_id'] ) ) {
889
+
890
+ $approve_url = add_query_arg( 'pbaction', 'approve', wppb_ec_get_approve_url( $extra_data['user_id'] ) );
891
+
892
+ if ( !empty($approve_url) )
893
+
894
+ return '<a href="' . $approve_url . '" target="_blank">' . $approve_url . '</a>';
895
+ }
896
+ }
897
+ add_filter( 'mustache_variable_ec_approve_link', 'wppb_ec_replace_approve_user_link', 10, 4 );
898
+
899
+
900
+ /**
901
+ * Function that filters and returns the approve user url in the (Admin) Email Customizer.
902
+ *
903
+ * @since v.2.7.3
904
+ *
905
+ * @param string $value
906
+ * @param string $merge_tag_name
907
+ * @param string $merge_tag
908
+ * @param array $extra_data
909
+ *
910
+ * @return string
911
+ */
912
+ function wppb_ec_replace_approve_user_url( $value, $merge_tag_name, $merge_tag, $extra_data ){
913
+
914
+ if( ! empty ( $extra_data['user_id'] ) ) {
915
+
916
+ $approve_url = add_query_arg( 'pbaction', 'approve', wppb_ec_get_approve_url( $extra_data['user_id'] ) );
917
+
918
+ if ( !empty( $approve_url ) )
919
+ return $approve_url;
920
+
921
+ }
922
+ }
923
+ add_filter( 'mustache_variable_ec_approve_url', 'wppb_ec_replace_approve_user_url', 10, 4 );
924
+
925
+ /**
926
+ * Function that filters and returns the approve user link in the (Admin) Email Customizer.
927
+ * This allows the admin to approve directly from his email a newly registered user.
928
+ *
929
+ * @since v.3.4.7
930
+ *
931
+ * @param string $value
932
+ * @param string $merge_tag_name
933
+ * @param string $merge_tag
934
+ * @param array $extra_data
935
+ *
936
+ * @return string
937
+ */
938
+ function wppb_ec_replace_unapprove_user_link( $value, $merge_tag_name, $merge_tag, $extra_data ){
939
+
940
+ if( ! empty ( $extra_data['user_id'] ) ) {
941
+
942
+ $approve_url = add_query_arg( 'pbaction', 'unapprove', wppb_ec_get_approve_url( $extra_data['user_id'] ) );
943
+
944
+ if ( !empty($approve_url) )
945
+
946
+ return '<a href="' . $approve_url . '" target="_blank">' . $approve_url . '</a>';
947
+ }
948
+ }
949
+ add_filter( 'mustache_variable_ec_unapprove_link', 'wppb_ec_replace_unapprove_user_link', 10, 4 );
950
+
951
+
952
+ /**
953
+ * Function that filters and returns the approve user url in the (Admin) Email Customizer.
954
+ *
955
+ * @since v.3.4.7
956
+ *
957
+ * @param string $value
958
+ * @param string $merge_tag_name
959
+ * @param string $merge_tag
960
+ * @param array $extra_data
961
+ *
962
+ * @return string
963
+ */
964
+ function wppb_ec_replace_unapprove_user_url( $value, $merge_tag_name, $merge_tag, $extra_data ){
965
+
966
+ if( ! empty ( $extra_data['user_id'] ) ) {
967
+
968
+ $approve_url = add_query_arg( 'pbaction', 'unapprove', wppb_ec_get_approve_url( $extra_data['user_id'] ) );
969
+
970
+ if ( !empty( $approve_url ) )
971
+ return $approve_url;
972
+
973
+ }
974
+ }
975
+ add_filter( 'mustache_variable_ec_unapprove_url', 'wppb_ec_replace_unapprove_user_url', 10, 4 );
976
+
977
+ /**
978
+ * Function that returns the approve user url to include in the admin email
979
+ *
980
+ * @since v.2.7.3
981
+ *
982
+ * @param $user_id The ID of the user that is pending approval
983
+ *
984
+ * @return string Approve URL
985
+ */
986
+ function wppb_ec_get_approve_url ( $user_id ){
987
+
988
+ $approve_url = '';
989
+
990
+ // If the user is "unaproved" the user meta below will contain a (hashed) value that we need to attach to the admin approval email link for validating it
991
+ $approve_url_param = get_user_meta( $user_id, '_wppb_admin_approval_link_param', true );
992
+
993
+ if ( !empty( $approve_url_param ) ) {
994
+ $approve_url = esc_url( add_query_arg( 'pbapprove', $approve_url_param, get_site_url() ) );
995
+ }
996
+
997
+ return $approve_url;
998
+ }
999
+
1000
+
1001
+ /**
1002
+ * Function that filters and returns the approved fields for Edit Profile Approved by Admin.
1003
+ *
1004
+ * @since v.2.7.3
1005
+ *
1006
+ * @param string $value
1007
+ * @param string $merge_tag_name
1008
+ * @param string $merge_tag
1009
+ * @param array $extra_data
1010
+ *
1011
+ * @return string
1012
+ */
1013
+ function wppb_epaa_replace_approved_fields( $value, $merge_tag_name, $merge_tag, $extra_data ){
1014
+
1015
+ if( ! empty ( $extra_data['approved_field_names'] ) ) {
1016
+ return $extra_data['approved_field_names'];
1017
+ }
1018
+
1019
+ return '';
1020
+ }
1021
+ add_filter( 'mustache_variable_ec_epaa_approved_fields', 'wppb_epaa_replace_approved_fields', 10, 4 );
1022
+
1023
+
1024
+ /**
1025
+ * Function that filters and returns the unapproved fields for Edit Profile Approved by Admin.
1026
+ *
1027
+ * @since v.2.7.3
1028
+ *
1029
+ * @param string $value
1030
+ * @param string $merge_tag_name
1031
+ * @param string $merge_tag
1032
+ * @param array $extra_data
1033
+ *
1034
+ * @return string
1035
+ */
1036
+ function wppb_epaa_replace_unapproved_fields( $value, $merge_tag_name, $merge_tag, $extra_data ){
1037
+
1038
+ if( ! empty ( $extra_data['unapproved_field_names'] ) ) {
1039
+ return $extra_data['unapproved_field_names'];
1040
+ }
1041
+
1042
+ return '';
1043
+ }
1044
+ add_filter( 'mustache_variable_ec_epaa_unapproved_fields', 'wppb_epaa_replace_unapproved_fields', 10, 4 );
1045
+
1046
+
1047
+ /**
1048
+ * Function that filters and returns the modified fields for Edit Profile Approved by Admin.
1049
+ *
1050
+ * @since v.2.7.3
1051
+ *
1052
+ * @param string $value
1053
+ * @param string $merge_tag_name
1054
+ * @param string $merge_tag
1055
+ * @param array $extra_data
1056
+ *
1057
+ * @return string
1058
+ */
1059
+ function wppb_epaa_replace_modified_fields( $value, $merge_tag_name, $merge_tag, $extra_data ){
1060
+
1061
+ if( ! empty ( $extra_data['modified_fields'] ) ) {
1062
+ return $extra_data['modified_fields'];
1063
+ }
1064
+
1065
+ return '';
1066
+ }
1067
+ add_filter( 'mustache_variable_ec_epaa_modified_fields', 'wppb_epaa_replace_modified_fields', 10, 4 );
1068
+
1069
+
1070
+ /**
1071
+ * Function that filters and returns the approval URL for Edit Profile Approved by Admin.
1072
+ *
1073
+ * @since v.2.7.3
1074
+ *
1075
+ * @param string $value
1076
+ * @param string $merge_tag_name
1077
+ * @param string $merge_tag
1078
+ * @param array $extra_data
1079
+ *
1080
+ * @return string
1081
+ */
1082
+ function wppb_epaa_replace_approval_url( $value, $merge_tag_name, $merge_tag, $extra_data ){
1083
+
1084
+ if( ! empty ( $extra_data['approval_url'] ) ) {
1085
+ return $extra_data['approval_url'];
1086
+ }
1087
+
1088
+ return '';
1089
+ }
1090
+ add_filter( 'mustache_variable_ec_epaa_approval_url', 'wppb_epaa_replace_approval_url', 10, 4 );
1091
+
1092
+
1093
+ /**
1094
+ * Function that filters and returns the users user_meta values in the Email Customizer
1095
+ *
1096
+ * @since v.2.0
1097
+ *
1098
+ * @param string $value
1099
+ * @param string $merge_tag_name
1100
+ * @param string $merge_tag
1101
+ * @param array $extra_data
1102
+ *
1103
+ * @return string
1104
+ */
1105
+ function wppb_ec_replace_user_meta( $value, $merge_tag_name, $merge_tag, $extra_data ){
1106
+ if ( isset( $extra_data['email_confirmation_unserialized_data'][$merge_tag_name] ) )
1107
+ $value = $extra_data['email_confirmation_unserialized_data'][$merge_tag_name];
1108
+ elseif( !empty( $extra_data['user_id'] ) )
1109
+ $value = get_user_meta( $extra_data['user_id'], $merge_tag_name, true );
1110
+
1111
+ /* value should be a string, but in case it is not */
1112
+ if( is_array( $value ) )
1113
+ $value = implode( ' ', $value );
1114
+
1115
+ return apply_filters( 'wppb_ec_filter_user_meta', $value, $merge_tag_name, $extra_data );
1116
+ }
1117
+ add_filter( 'mustache_variable_ec_user_meta', 'wppb_ec_replace_user_meta', 10, 4 );
1118
+
1119
+
1120
+ add_filter( 'wppb_ec_filter_user_meta', 'wppb_ec_change_user_meta_by_type', 10, 3 );
1121
+ function wppb_ec_change_user_meta_by_type( $value, $meta_name, $extra_data = '' ){
1122
+ $fields = apply_filters( 'wppb_form_fields', get_option( 'wppb_manage_fields', 'not_found' ), array( 'user_id' => ( empty( $extra_data['user_id'] ) ) ? '' : $extra_data['user_id'], 'meta' => ( empty( $extra_data['email_confirmation_unserialized_data'] ) ) ? '' : $extra_data['email_confirmation_unserialized_data'], 'context' => 'mustache_variable' ) );
1123
+ if( !empty( $fields ) ) {
1124
+ foreach ($fields as $field) {
1125
+ if ($field['meta-name'] == $meta_name && ($field['field'] == 'Avatar' || $field['field'] == 'Upload' ) ) {
1126
+ if( is_numeric( $value ) )
1127
+ $value = wp_get_attachment_url( $value );
1128
+ }
1129
+ }
1130
+ }
1131
+ return $value;
1132
+ }
1133
+
1134
+ /**
1135
+ * Function that filters and returns the users user_meta labels in the Email Customizer
1136
+ *
1137
+ * @since v.2.0
1138
+ *
1139
+ * @param string $value
1140
+ * @param string $merge_tag_name
1141
+ * @param string $merge_tag
1142
+ * @param array $extra_data
1143
+ *
1144
+ * @return string
1145
+ */
1146
+ function wppb_ec_replace_user_meta_labels( $value, $merge_tag_name, $merge_tag, $extra_data ){
1147
+ /* we remove _labels from end so we get the meta name */
1148
+ $merge_tag_name = preg_replace( '/_labels$/', '', $merge_tag_name, 1 );
1149
+
1150
+ if ( isset( $extra_data['email_confirmation_unserialized_data'][$merge_tag_name] ) )
1151
+ $value = $extra_data['email_confirmation_unserialized_data'][$merge_tag_name];
1152
+ if( !empty( $extra_data['user_id'] ) )
1153
+ $value = get_user_meta( $extra_data['user_id'], $merge_tag_name, true );
1154
+
1155
+ /* get label from value */
1156
+ /* get manage fields */
1157
+ $fields = apply_filters( 'wppb_form_fields', get_option( 'wppb_manage_fields', 'not_found' ), array( 'user_id' => ( empty( $extra_data['user_id'] ) ) ? '' : $extra_data['user_id'], 'meta' => ( empty( $extra_data['email_confirmation_unserialized_data'] ) ) ? '' : $extra_data['email_confirmation_unserialized_data'], 'context' => 'mustache_variable' ) );
1158
+ if( !empty( $fields ) ) {
1159
+ foreach ($fields as $field) {
1160
+ if( $field['meta-name'] == $merge_tag_name ){
1161
+ if( $field['field'] == 'Select (Country)' ){
1162
+ if( function_exists( 'wppb_country_select_options' ) ) {
1163
+ $all_countries = wppb_country_select_options('email_customizer');
1164
+ if( !empty( $all_countries[$value] ) )
1165
+ return $all_countries[$value];
1166
+ else
1167
+ return $value;
1168
+ }
1169
+ else{
1170
+ return $value;
1171
+ }
1172
+ }
1173
+ else if( $field['field'] == 'Select (Currency)' ){
1174
+ if( function_exists( 'wppb_get_currencies' ) ) {
1175
+ $all_currencies = wppb_get_currencies('email_customizer');
1176
+ if( !empty( $all_currencies[$value] ) )
1177
+ return $all_currencies[$value];
1178
+ else
1179
+ return $value;
1180
+ }
1181
+ else{
1182
+ return $value;
1183
+ }
1184
+ }
1185
+ else {
1186
+ /* get label corresponding to value. the values and labels in the backend settings are comma separated so we assume that as well here ? */
1187
+ $saved_values = array_map('trim', explode(',', $value));
1188
+ $field['options'] = array_map('trim', explode(',', $field['options']));
1189
+ $field['labels'] = array_map('trim', explode(',', $field['labels']));
1190
+ /* get the position for each value */
1191
+ $key_array = array();
1192
+ if (!empty($field['options'])) {
1193
+ foreach ($field['options'] as $key => $option) {
1194
+ if (in_array($option, $saved_values))
1195
+ $key_array[] = $key;
1196
+ }
1197
+ }
1198
+
1199
+ $show_values = array();
1200
+ if (!empty($key_array)) {
1201
+ foreach ($key_array as $key) {
1202
+ if (!empty($field['labels'][$key]))
1203
+ $show_values[] = $field['labels'][$key];
1204
+ else
1205
+ $show_values[] = $field['options'][$key];
1206
+ }
1207
+ }
1208
+
1209
+ return implode(',', $show_values);
1210
+ }
1211
+ }
1212
+ }
1213
+ }
1214
+ }
1215
+ add_filter( 'mustache_variable_ec_user_meta_labels', 'wppb_ec_replace_user_meta_labels', 10, 4 );
1216
+
1217
+ // function that filters the From email address
1218
+ function wppb_website_email($sender_email){
1219
+ $wppb_addonOptions = get_option( 'wppb_module_settings' );
1220
+
1221
+ if ( ( $wppb_addonOptions['wppb_emailCustomizer'] == 'show' ) || ( isset( $wppb_addonOptions['wppb_emailCustomizerAdmin'] ) && $wppb_addonOptions['wppb_emailCustomizerAdmin'] == 'show' ) ){
1222
+ $reply_to_email = get_option( 'wppb_emailc_common_settings_from_reply_to_email', 'not_found' );
1223
+ if ( $reply_to_email != 'not_found' ) {
1224
+ $reply_to_email = str_replace('{{reply_to}}', get_bloginfo('admin_email'), $reply_to_email );
1225
+ if( is_email( $reply_to_email ) )
1226
+ $sender_email = $reply_to_email;
1227
+ }
1228
+ }
1229
+
1230
+ return $sender_email;
1231
+ }
1232
+
1233
+ // function that filters the From name
1234
+ function wppb_website_name($site_name){
1235
+ $wppb_addonOptions = get_option( 'wppb_module_settings' );
1236
+
1237
+ if ( ( $wppb_addonOptions['wppb_emailCustomizer'] == 'show' ) || ( isset( $wppb_addonOptions['wppb_emailCustomizerAdmin'] ) && $wppb_addonOptions['wppb_emailCustomizerAdmin'] == 'show' ) ){
1238
+ $email_from_name = get_option( 'wppb_emailc_common_settings_from_name', 'not_found' );
1239
+ if ( $email_from_name != 'not_found' ) {
1240
+ $email_from_name = str_replace('{{site_name}}', get_bloginfo('name'), $email_from_name );
1241
+ $site_name = $email_from_name;
1242
+ }
1243
+ }
1244
+
1245
+ return html_entity_decode( htmlspecialchars_decode( $site_name, ENT_QUOTES ), ENT_QUOTES );
1246
+ }
1247
+
1248
+ // Function that changes email headers only when they are needed.
1249
+ function wppb_change_email_from_headers(){
1250
+ add_filter('wp_mail_from_name','wppb_website_name', 20, 1);
1251
+ add_filter('wp_mail_from','wppb_website_email', 20, 1);
1252
+ }
1253
+
1254
+ add_filter( 'wppb_send_email', 'wppb_disable_emails', 20, 5 );
1255
+ function wppb_disable_emails( $send_email, $to, $subject, $message, $context ) {
1256
+ $actions = array(
1257
+ 'email_user_account_info' => 'wppb_user_emailc_default_registration_email_enabled',
1258
+ 'email_user_activate' => 'wppb_user_emailc_registr_w_email_confirm_email_enabled',
1259
+ 'email_user_need_approval' => 'wppb_user_emailc_registration_with_admin_approval_email_enabled',
1260
+ 'email_user_approved' => 'wppb_user_emailc_admin_approval_notif_approved_email_enabled',
1261
+ 'email_user_unapproved' => 'wppb_user_emailc_admin_approval_notif_unapproved_email_enabled',
1262
+ 'email_user_recover' => 'wppb_user_emailc_reset_email_enabled',
1263
+ 'email_user_recover_success' => 'wppb_user_emailc_reset_success_email_enabled',
1264
+ 'email_user_update_email' => 'wppb_user_emailc_change_email_address_request_enabled',
1265
+ 'wppb_epaa_user_email' => 'wppb_user_emailc_epaa_notification_enabled',
1266
+ 'email_admin_new_subscriber' => 'wppb_admin_emailc_default_registration_email_enabled',
1267
+ 'email_admin_approve' => 'wppb_admin_emailc_registration_with_admin_approval_email_enabled',
1268
+ 'email_admin_recover_success' => 'wppb_admin_emailc_user_password_reset_email_enabled',
1269
+ 'wppb_epaa_admin_email' => 'wppb_admin_emailc_epaa_notification_enabled',
1270
+ );
1271
+
1272
+ if ( !empty( $actions[$context] ) )
1273
+ $option_name = $actions[$context];
1274
+ else
1275
+ return $send_email;
1276
+
1277
+ $option = get_option( $option_name );
1278
+
1279
+ if ( $option == 'off' )
1280
+ return false;
1281
+
1282
+ return $send_email;
1283
+ }
1284
+
1285
+ if ( get_option( 'wppb_user_emailc_change_email_address_enabled' ) == 'off' )
1286
+ add_filter( 'send_email_change_email', '__return_false' );
features/email-customizer/user-email-customizer.php ADDED
@@ -0,0 +1,409 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Function that creates the User Email Customizer menu
4
+ *
5
+ * @since v.2.0
6
+ *
7
+ * @return void
8
+ */
9
+ function wppb_user_email_customizer_submenu(){
10
+ $args = array(
11
+ 'menu_title' => __( 'User Email Customizer', 'profile-builder' ),
12
+ 'page_title' => __( 'User Email Customizer Settings', 'profile-builder' ),
13
+ 'menu_slug' => 'user-email-customizer',
14
+ 'page_type' => 'submenu_page',
15
+ 'capability' => 'manage_options',
16
+ 'priority' => 10,
17
+ 'parent_slug' => 'profile-builder'
18
+ );
19
+
20
+ new WCK_Page_Creator_PB( $args );
21
+ }
22
+ add_action( 'admin_menu', 'wppb_user_email_customizer_submenu', 1 );
23
+
24
+ add_action( 'wck_before_meta_boxes', 'wppb_add_tabs_on_top_of_user_email_page' );
25
+ function wppb_add_tabs_on_top_of_user_email_page( $hookname ){
26
+ if( $hookname == 'profile-builder_page_user-email-customizer' ){
27
+
28
+ if( isset( $_GET['mustache_action'] ) && $_GET['mustache_action'] == 'save' ) { ?>
29
+ <div id="setting-error-settings_updated" class="updated settings-error notice">
30
+ <p><strong><?php esc_html_e( "Settings saved.", 'profile-builder' ); ?></strong></p>
31
+ </div>
32
+ <?php
33
+ }
34
+
35
+ wppb_generate_settings_tabs();
36
+ }
37
+ }
38
+
39
+ /* on the init hook add the mustache boxes */
40
+ add_action( 'init', 'wppb_user_email_customizer_add_mustache_in_backend', 11 );
41
+ /**
42
+ * Function that ads the mustache boxes in the backend for user email customizer
43
+ *
44
+ * @since v.2.0
45
+ */
46
+ function wppb_user_email_customizer_add_mustache_in_backend(){
47
+ if( defined( 'WPPB_PAID_PLUGIN_DIR' ) && file_exists( WPPB_PAID_PLUGIN_DIR . '/assets/lib/class-mustache-templates/class-mustache-templates.php' ) )
48
+ require_once( WPPB_PAID_PLUGIN_DIR . '/assets/lib/class-mustache-templates/class-mustache-templates.php' );
49
+ elseif( file_exists( WPPB_PLUGIN_DIR . '/assets/lib/class-mustache-templates/class-mustache-templates.php' ) )
50
+ require_once( WPPB_PLUGIN_DIR . '/assets/lib/class-mustache-templates/class-mustache-templates.php' );
51
+
52
+ $fields = array(
53
+ array(
54
+ 'id' => 'wppb_admin_emailc_common_settings_header', // field id and name
55
+ 'type' => 'header', // type of field
56
+ 'default' => __( 'These settings are also replicated in the "Admin Email Customizer" settings-page upon save.', 'profile-builder' ).' '.__( 'Valid tags {{reply_to}} and {{site_name}}', 'profile-builder'), // type of field
57
+ ),
58
+ array(
59
+ 'label' => __( 'From (name)', 'profile-builder' ), // <label>
60
+ 'desc' => '', // description
61
+ 'id' => 'wppb_emailc_common_settings_from_name', // field id and name
62
+ 'type' => 'text', // type of field
63
+ 'default' => '{{site_name}}', // type of field
64
+ 'desc' => '',
65
+ ),
66
+ array(
67
+ 'label' => __( 'From (reply-to email)', 'profile-builder' ), // <label>
68
+ 'desc' => '', // description
69
+ 'id' => 'wppb_emailc_common_settings_from_reply_to_email', // field id and name
70
+ 'type' => 'text', // type of field
71
+ 'default' => '{{reply_to}}', // type of field
72
+ 'desc' => __( 'Must be a valid email address or the tag {{reply_to}} which defaults to the administrator email', 'profile-builder' ),
73
+ ),
74
+ );
75
+ new PB_Mustache_Generate_Admin_Box( 'uec_common_settings', __( 'Common Settings', 'profile-builder' ), 'profile-builder_page_user-email-customizer', 'core', '', '', $fields );
76
+
77
+ /*
78
+ * Default Registration
79
+ */
80
+
81
+ // we format the var like this for proper line breaks.
82
+ $uec_default_registration = __("<h3>Welcome to {{site_name}}!</h3>\n<p>Your username is: {{username}}</p>\n", 'profile-builder' );
83
+ $mustache_vars = wppb_email_customizer_generate_merge_tags();
84
+ $fields = array(
85
+ array(
86
+ 'label' => __( 'Email Subject', 'profile-builder' ), // <label>
87
+ 'desc' => '', // description
88
+ 'id' => 'wppb_user_emailc_default_registration_email_subject', // field id and name
89
+ 'type' => 'text', // type of field
90
+ 'default' => 'A new account has been created for you on {{site_name}}', // type of field
91
+ ),
92
+ array(
93
+ 'label' => __( 'Enable email', 'profile-builder' ), // <label>
94
+ 'desc' => '', // description
95
+ 'id' => 'wppb_user_emailc_default_registration_email_enabled', // field id and name
96
+ 'type' => 'checkbox', // type of field
97
+ 'default' => 'on',
98
+ ),
99
+ array( // Textarea
100
+ 'label' => '', // <label>
101
+ 'desc' => '', // description
102
+ 'id' => 'wppb_user_emailc_default_registration_email_content', // field id and name
103
+ 'type' => 'textarea', // type of field
104
+ 'default' => $uec_default_registration, // type of field
105
+ )
106
+ );
107
+ new PB_Mustache_Generate_Admin_Box( 'uec_default_registration', __( 'Default Registration', 'profile-builder' ), 'profile-builder_page_user-email-customizer', 'core', $mustache_vars, '', $fields );
108
+
109
+ /*
110
+ * Registration with Email Confirmation
111
+ */
112
+ // we format the var like this for proper line breaks.
113
+ $uec_reg_with_email_confirm = __( "<p>To activate your user, please click the following link:<br/>\n{{{activation_link}}}</p>\n<p>After you activate, you will receive another email with your credentials.</p>\n", 'profile-builder' );
114
+ $mustache_vars = wppb_email_customizer_generate_merge_tags( 'email_confirmation' );
115
+ $fields = array(
116
+ array(
117
+ 'label' => __( 'Email Subject', 'profile-builder' ), // <label>
118
+ 'desc' => '', // description
119
+ 'id' => 'wppb_user_emailc_registr_w_email_confirm_email_subject', // field id and name
120
+ 'type' => 'text', // type of field
121
+ 'default' => __( '[{{site_name}}] Activate {{username}}', 'profile-builder' ), // type of field
122
+ ),
123
+ array(
124
+ 'label' => __( 'Enable email', 'profile-builder' ), // <label>
125
+ 'desc' => '', // description
126
+ 'id' => 'wppb_user_emailc_registr_w_email_confirm_email_enabled', // field id and name
127
+ 'type' => 'checkbox', // type of field
128
+ 'default' => 'on',
129
+ ),
130
+ array( // Textarea
131
+ 'label' => '', // <label>
132
+ 'desc' => '', // description
133
+ 'id' => 'wppb_user_emailc_registr_w_email_confirm_email_content', // field id and name
134
+ 'type' => 'textarea', // type of field
135
+ 'default' => $uec_reg_with_email_confirm, // type of field
136
+ )
137
+ );
138
+
139
+ new PB_Mustache_Generate_Admin_Box( 'uec_reg_with_email_confirmation', __( 'Registration with Email Confirmation', 'profile-builder' ), 'profile-builder_page_user-email-customizer', 'core', $mustache_vars, '', $fields );
140
+
141
+ if( PROFILE_BUILDER != 'Profile Builder Free' ){
142
+
143
+ /*
144
+ * Registration with Admin Approval
145
+ */
146
+
147
+ $uec_reg_with_admin_approval = __( "<h3>Welcome to {{site_name}}!</h3>\n<p>Your username is: {{username}}</p>\n<p>Before you can access your account, an administrator needs to approve it. You will be notified via email.</p>\n", 'profile-builder' );
148
+ $mustache_vars = wppb_email_customizer_generate_merge_tags();
149
+ $fields = array(
150
+ array(
151
+ 'label' => __( 'Email Subject', 'profile-builder' ), // <label>
152
+ 'desc' => '', // description
153
+ 'id' => 'wppb_user_emailc_registration_with_admin_approval_email_subject', // field id and name
154
+ 'type' => 'text', // type of field
155
+ 'default' => __( 'A new account has been created for you on {{site_name}}', 'profile-builder' ), // type of field
156
+ ),
157
+ array(
158
+ 'label' => __( 'Enable email', 'profile-builder' ), // <label>
159
+ 'desc' => '', // description
160
+ 'id' => 'wppb_user_emailc_registration_with_admin_approval_email_enabled', // field id and name
161
+ 'type' => 'checkbox', // type of field
162
+ 'default' => 'on',
163
+ ),
164
+ array( // Textarea
165
+ 'label' => '', // <label>
166
+ 'desc' => '', // description
167
+ 'id' => 'wppb_user_emailc_registration_with_admin_approval_email_content', // field id and name
168
+ 'type' => 'textarea', // type of field
169
+ 'default' => $uec_reg_with_admin_approval, // type of field
170
+ )
171
+ );
172
+
173
+ new PB_Mustache_Generate_Admin_Box( 'uec_reg_with_admin_approval', __( 'Registration with Admin Approval', 'profile-builder' ), 'profile-builder_page_user-email-customizer', 'core', $mustache_vars, '', $fields );
174
+
175
+ /*
176
+ * Admin Approval Notifications ( on user approval )
177
+ */
178
+ $uec_notif_approved_email = __( "<h3>Good News!</h3>\n<p>An administrator has just approved your account: {{username}} on {{site_name}}.</p>\n", 'profile-builder' );
179
+ $mustache_vars = wppb_email_customizer_generate_merge_tags();
180
+ $fields = array(
181
+ array(
182
+ 'label' => __( 'Email Subject', 'profile-builder' ), // <label>
183
+ 'desc' => '', // description
184
+ 'id' => 'wppb_user_emailc_admin_approval_notif_approved_email_subject', // field id and name
185
+ 'type' => 'text', // type of field
186
+ 'default' => __( 'Your account on {{site_name}} has been approved!', 'profile-builder' ), // type of field
187
+ ),
188
+ array(
189
+ 'label' => __( 'Enable email', 'profile-builder' ), // <label>
190
+ 'desc' => '', // description
191
+ 'id' => 'wppb_user_emailc_admin_approval_notif_approved_email_enabled', // field id and name
192
+ 'type' => 'checkbox', // type of field
193
+ 'default' => 'on',
194
+ ),
195
+ array( // Textarea
196
+ 'label' => '', // <label>
197
+ 'desc' => '', // description
198
+ 'id' => 'wppb_user_emailc_admin_approval_notif_approved_email_content', // field id and name
199
+ 'type' => 'textarea', // type of field
200
+ 'default' => $uec_notif_approved_email, // type of field
201
+ )
202
+ );
203
+
204
+ new PB_Mustache_Generate_Admin_Box( 'uec_notif_approved_email', __( 'User Approval Notification', 'profile-builder' ), 'profile-builder_page_user-email-customizer', 'core', $mustache_vars, '', $fields );
205
+
206
+ /*
207
+ * Admin Approval Notifications ( on user unapproval )
208
+ */
209
+ $uec_notif_unapproved_email = __( "<h3>Hello,</h3>\n<p>Unfortunatelly an administrator has just unapproved your account: {{username}} on {{site_name}}.</p>\n", 'profile-builder' );
210
+ $mustache_vars = wppb_email_customizer_generate_merge_tags();
211
+ $fields = array(
212
+ array(
213
+ 'label' => __( 'Email Subject', 'profile-builder' ), // <label>
214
+ 'desc' => '', // description
215
+ 'id' => 'wppb_user_emailc_admin_approval_notif_unapproved_email_subject', // field id and name
216
+ 'type' => 'text', // type of field
217
+ 'default' => __( 'Your account on {{site_name}} has been unapproved!', 'profile-builder' ), // type of field
218
+ ),
219
+ array(
220
+ 'label' => __( 'Enable email', 'profile-builder' ), // <label>
221
+ 'desc' => '', // description
222
+ 'id' => 'wppb_user_emailc_admin_approval_notif_unapproved_email_enabled', // field id and name
223
+ 'type' => 'checkbox', // type of field
224
+ 'default' => 'on',
225
+ ),
226
+ array( // Textarea
227
+ 'label' => '', // <label>
228
+ 'desc' => '', // description
229
+ 'id' => 'wppb_user_emailc_admin_approval_notif_unapproved_email_content', // field id and name
230
+ 'type' => 'textarea', // type of field
231
+ 'default' => $uec_notif_unapproved_email , // type of field
232
+ )
233
+ );
234
+
235
+ new PB_Mustache_Generate_Admin_Box( 'uec_notif_unapproved_email', __( 'Unapproved User Notification', 'profile-builder' ), 'profile-builder_page_user-email-customizer', 'core', $mustache_vars, '', $fields );
236
+
237
+ }
238
+
239
+
240
+ /*
241
+ * Password Reset Email
242
+ */
243
+ // we format the var like this for proper line breaks.
244
+ $uec_reset = __( "<p>Someone requested that the password be reset for the following account: {{site_name}}<br/>\nUsername: {{username}}</p>\n<p>If this was a mistake, just ignore this email and nothing will happen.</p>\n<p>To reset your password, visit the following address:<br/>\n{{{reset_link}}}</p>\n", 'profile-builder' );
245
+ $mustache_vars = wppb_email_customizer_generate_merge_tags( 'password_reset' );
246
+ $fields = array(
247
+ array(
248
+ 'label' => __( 'Email Subject', 'profile-builder' ), // <label>
249
+ 'desc' => '', // description
250
+ 'id' => 'wppb_user_emailc_reset_email_subject', // field id and name
251
+ 'type' => 'text', // type of field
252
+ 'default' => __( '[{{site_name}}] Password Reset', 'profile-builder' ), // type of field
253
+ ),
254
+ array(
255
+ 'label' => __( 'Enable email', 'profile-builder' ), // <label>
256
+ 'desc' => '', // description
257
+ 'id' => 'wppb_user_emailc_reset_email_enabled', // field id and name
258
+ 'type' => 'checkbox', // type of field
259
+ 'default' => 'on',
260
+ ),
261
+ array( // Textarea
262
+ 'label' => '', // <label>
263
+ 'desc' => '', // description
264
+ 'id' => 'wppb_user_emailc_reset_email_content', // field id and name
265
+ 'type' => 'textarea', // type of field
266
+ 'default' => $uec_reset, // type of field
267
+ )
268
+ );
269
+
270
+ new PB_Mustache_Generate_Admin_Box( 'uec_reset', __( 'Password Reset Email', 'profile-builder' ), 'profile-builder_page_user-email-customizer', 'core', $mustache_vars, '', $fields );
271
+
272
+ /*
273
+ * Password Reset Success Email
274
+ */
275
+ // we format the var like this for proper line breaks.
276
+ $uec_reset_success = __( "<p>You have successfully reset your password.</p>\n", 'profile-builder' );
277
+ $mustache_vars = wppb_email_customizer_generate_merge_tags( 'password_reset_success' );
278
+ $fields = array(
279
+ array(
280
+ 'label' => __( 'Email Subject', 'profile-builder' ), // <label>
281
+ 'desc' => '', // description
282
+ 'id' => 'wppb_user_emailc_reset_success_email_subject', // field id and name
283
+ 'type' => 'text', // type of field
284
+ 'default' => __( '[{{site_name}}] Password Reset Successfully', 'profile-builder' ), // type of field
285
+ ),
286
+ array(
287
+ 'label' => __( 'Enable email', 'profile-builder' ), // <label>
288
+ 'desc' => '', // description
289
+ 'id' => 'wppb_user_emailc_reset_success_email_enabled', // field id and name
290
+ 'type' => 'checkbox', // type of field
291
+ 'default' => 'on',
292
+ ),
293
+ array( // Textarea
294
+ 'label' => '', // <label>
295
+ 'desc' => '', // description
296
+ 'id' => 'wppb_user_emailc_reset_success_email_content', // field id and name
297
+ 'type' => 'textarea', // type of field
298
+ 'default' => $uec_reset_success, // type of field
299
+ )
300
+ );
301
+
302
+ new PB_Mustache_Generate_Admin_Box( 'uec_reset_success', __( 'Password Reset Success Email', 'profile-builder' ), 'profile-builder_page_user-email-customizer', 'core', $mustache_vars, '', $fields );
303
+
304
+ /*
305
+ * Change Email Address Request Notification
306
+ */
307
+ $admin_email = get_option('admin_email');
308
+
309
+ // we format the var like this for proper line breaks.
310
+ $uec_change_email_request = sprintf( __( "<h3>Hi {{username}},</h3>\n<p>There is an email address change request on {{site_name}}.</p>\n<p>To change your email address click on: {{{user_email_change_link}}}</p>\n<p>Regards,<br>\nAll at {{site_name}}<br>\n<a href=\"{{site_url}}\">{{site_url}}</a></p>", 'profile-builder' ), $admin_email );
311
+ $mustache_vars = wppb_email_customizer_generate_merge_tags( 'change_email_address_request' );
312
+ $fields = array(
313
+ array(
314
+ 'label' => __( 'Email Subject', 'profile-builder' ), // <label>
315
+ 'desc' => '', // description
316
+ 'id' => 'wppb_user_emailc_change_email_address_request_subject', // field id and name
317
+ 'type' => 'text', // type of field
318
+ 'default' => __( '[{{site_name}}] Notice of Email Change Request', 'profile-builder' ), // type of field
319
+ ),
320
+ array(
321
+ 'label' => __( 'Enable email', 'profile-builder' ), // <label>
322
+ 'desc' => '', // description
323
+ 'id' => 'wppb_user_emailc_change_email_address_request_enabled', // field id and name
324
+ 'type' => 'checkbox', // type of field
325
+ 'default' => 'on',
326
+ ),
327
+ array( // Textarea
328
+ 'label' => '', // <label>
329
+ 'desc' => '', // description
330
+ 'id' => 'wppb_user_emailc_change_email_address_request_content', // field id and name
331
+ 'type' => 'textarea', // type of field
332
+ 'default' => $uec_change_email_request, // type of field
333
+ )
334
+ );
335
+
336
+ new PB_Mustache_Generate_Admin_Box( 'uec_change_email_request', __( 'Change Email Address Request Notification', 'profile-builder' ), 'profile-builder_page_user-email-customizer', 'core', $mustache_vars, '', $fields );
337
+
338
+ /*
339
+ * Change Email Address Notification
340
+ */
341
+ $admin_email = get_option('admin_email');
342
+
343
+ // we format the var like this for proper line breaks.
344
+ $uec_change_email = sprintf( __( "<h3>Hi {{username}},</h3>\n<p>This notice confirms that your email was changed on {{site_name}}.</p>\n<p>If you did not change your email, please contact the Site Administrator at %s</p>\n<p>This email has been sent to {{user_email}}</p>\n<p>Regards,<br>\nAll at {{site_name}}<br>\n<a href=\"{{site_url}}\">{{site_url}}</a></p>", 'profile-builder' ), $admin_email );
345
+ $mustache_vars = wppb_email_customizer_generate_merge_tags( 'change_email_address' );
346
+ $fields = array(
347
+ array(
348
+ 'label' => __( 'Email Subject', 'profile-builder' ), // <label>
349
+ 'desc' => '', // description
350
+ 'id' => 'wppb_user_emailc_change_email_address_subject', // field id and name
351
+ 'type' => 'text', // type of field
352
+ 'default' => __( '[{{site_name}}] Notice of Email Change', 'profile-builder' ), // type of field
353
+ ),
354
+ array(
355
+ 'label' => __( 'Enable email', 'profile-builder' ), // <label>
356
+ 'desc' => '', // description
357
+ 'id' => 'wppb_user_emailc_change_email_address_enabled', // field id and name
358
+ 'type' => 'checkbox', // type of field
359
+ 'default' => 'on',
360
+ ),
361
+ array( // Textarea
362
+ 'label' => '', // <label>
363
+ 'desc' => '', // description
364
+ 'id' => 'wppb_user_emailc_change_email_address_content', // field id and name
365
+ 'type' => 'textarea', // type of field
366
+ 'default' => $uec_change_email, // type of field
367
+ )
368
+ );
369
+
370
+ new PB_Mustache_Generate_Admin_Box( 'uec_change_email', __( 'Changed Email Address Notification', 'profile-builder' ), 'profile-builder_page_user-email-customizer', 'core', $mustache_vars, '', $fields );
371
+
372
+ /*
373
+ * User Notification for Edit Profile Approved by Admin
374
+ */
375
+
376
+ // check if the Edit Profile Approved by Admin add-on is active to see if the email should be displayed
377
+ if( function_exists( 'wppb_in_init_edit_profile_approval' ) ) {
378
+
379
+ // we format the var like this for proper line breaks.
380
+ $uec_epaa_notification = __("<p>Your profile has been reviewed by an administrator:</p>\n<br>\n<p>Approved Fields: {{approved_fields}}</p>\n<p>Unapproved Fields: {{unapproved_fields}}</p>\n", 'profile-builder');
381
+ $mustache_vars = wppb_email_customizer_generate_merge_tags('epaa_notification');
382
+ $fields = array(
383
+ array(
384
+ 'label' => __('Email Subject', 'profile-builder'), // <label>
385
+ 'desc' => '', // description
386
+ 'id' => 'wppb_user_emailc_epaa_notification_subject', // field id and name
387
+ 'type' => 'text', // type of field
388
+ 'default' => __('[{{site_name}}] Your profile has been reviewed by an administrator', 'profile-builder'), // type of field
389
+ ),
390
+ array(
391
+ 'label' => __('Enable email', 'profile-builder'), // <label>
392
+ 'desc' => '', // description
393
+ 'id' => 'wppb_user_emailc_epaa_notification_enabled', // field id and name
394
+ 'type' => 'checkbox', // type of field
395
+ 'default' => 'on',
396
+ ),
397
+ array( // Textarea
398
+ 'label' => '', // <label>
399
+ 'desc' => '', // description
400
+ 'id' => 'wppb_user_emailc_epaa_notification_content', // field id and name
401
+ 'type' => 'textarea', // type of field
402
+ 'default' => $uec_epaa_notification, // type of field
403
+ )
404
+ );
405
+
406
+ new PB_Mustache_Generate_Admin_Box('uec_epaa_notification', __('User Notification for Edit Profile Approved by Admin', 'profile-builder'), 'profile-builder_page_user-email-customizer', 'core', $mustache_vars, '', $fields);
407
+
408
+ }
409
+ }
features/functions.php CHANGED
@@ -166,24 +166,23 @@ if ( is_admin() ){
166
  add_action( 'admin_init', 'wppb_register_settings' );
167
 
168
  // display the same extra profile fields in the admin panel also
169
- if ( defined( 'WPPB_PAID_PLUGIN_DIR' ) && file_exists ( WPPB_PAID_PLUGIN_DIR.'/front-end/extra-fields/extra-fields.php' ) ){
170
- require_once( WPPB_PAID_PLUGIN_DIR.'/front-end/extra-fields/extra-fields.php' );
171
 
172
- add_action( 'show_user_profile', 'display_profile_extra_fields_in_admin', 10 );
173
- add_action( 'edit_user_profile', 'display_profile_extra_fields_in_admin', 10 );
174
  global $pagenow;
175
  if( $pagenow != 'user-new.php' )
176
- add_action( 'user_profile_update_errors', 'wppb_validate_backend_fields', 10, 3 );
177
- add_action( 'personal_options_update', 'save_profile_extra_fields_in_admin', 10 );
178
- add_action( 'edit_user_profile_update', 'save_profile_extra_fields_in_admin', 10 );
179
  }
180
 
181
  /* we need to include the fields here for conditional fields when they run through ajax, the extra-fields were already included above for backend forms */
182
- $wppb_generalSettings = get_option( 'wppb_general_settings' );
183
- if( wp_doing_ajax() && wppb_conditional_fields_exists() && isset( $wppb_generalSettings['conditional_fields_ajax'] ) && $wppb_generalSettings['conditional_fields_ajax'] === 'yes' ) {
184
- if (file_exists(WPPB_PLUGIN_DIR . '/front-end/default-fields/default-fields.php'))
185
- require_once(WPPB_PLUGIN_DIR . '/front-end/default-fields/default-fields.php');
186
- }
187
 
188
  }else if ( !is_admin() ){
189
  // include the stylesheet
@@ -376,6 +375,7 @@ function wppb_print_cpt_script( $hook ){
376
  ( strpos( $hook, 'profile-builder_page_' ) === 0 ) ||
377
  ( $hook == 'edit.php' && ( isset( $_GET['post_type'] ) && $_GET['post_type'] === 'wppb-roles-editor' ) ) ||
378
  ( $hook == 'admin_page_profile-builder-pms-promo') ||
 
379
  ( $hook == 'admin_page_profile-builder-private-website') ) {
380
  wp_enqueue_style( 'wppb-back-end-style', WPPB_PLUGIN_URL . 'assets/css/style-back-end.css', false, PROFILE_BUILDER_VERSION );
381
  }
166
  add_action( 'admin_init', 'wppb_register_settings' );
167
 
168
  // display the same extra profile fields in the admin panel also
169
+ if ( file_exists ( WPPB_PLUGIN_DIR.'/front-end/default-fields/fields-functions.php' ) ){
170
+ require_once( WPPB_PLUGIN_DIR.'/front-end/default-fields/fields-functions.php' );
171
 
172
+ add_action( 'show_user_profile', 'wppb_display_fields_in_admin', 10 );
173
+ add_action( 'edit_user_profile', 'wppb_display_fields_in_admin', 10 );
174
  global $pagenow;
175
  if( $pagenow != 'user-new.php' )
176
+ add_action( 'user_profile_update_errors', 'wppb_validate_fields_in_admin', 10, 3 );
177
+ add_action( 'personal_options_update', 'wppb_save_fields_in_admin', 10 );
178
+ add_action( 'edit_user_profile_update', 'wppb_save_fields_in_admin', 10 );
179
  }
180
 
181
  /* we need to include the fields here for conditional fields when they run through ajax, the extra-fields were already included above for backend forms */
182
+ // now they are loaded all the time so the simple upload functionality works correctly as well, since 3.8.1
183
+ if (file_exists(WPPB_PLUGIN_DIR . '/front-end/default-fields/default-fields.php'))
184
+ require_once(WPPB_PLUGIN_DIR . '/front-end/default-fields/default-fields.php');
185
+
 
186
 
187
  }else if ( !is_admin() ){
188
  // include the stylesheet
375
  ( strpos( $hook, 'profile-builder_page_' ) === 0 ) ||
376
  ( $hook == 'edit.php' && ( isset( $_GET['post_type'] ) && $_GET['post_type'] === 'wppb-roles-editor' ) ) ||
377
  ( $hook == 'admin_page_profile-builder-pms-promo') ||
378
+ ( $hook == 'toplevel_page_profile-builder-register') || //multisite register version page
379
  ( $hook == 'admin_page_profile-builder-private-website') ) {
380
  wp_enqueue_style( 'wppb-back-end-style', WPPB_PLUGIN_URL . 'assets/css/style-back-end.css', false, PROFILE_BUILDER_VERSION );
381
  }
front-end/default-fields/avatar/avatar.php ADDED
@@ -0,0 +1,263 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /* the avatar field relies on the upload field */
3
+
4
+ /* handle field output */
5
+ function wppb_avatar_handler( $output, $form_location, $field, $user_id, $field_check_errors, $request_data ){
6
+ if ( $field['field'] == 'Avatar' ){
7
+
8
+ $field['meta-name'] = Wordpress_Creation_Kit_PB::wck_generate_slug( $field['meta-name'] );
9
+
10
+ /* media upload add here, this should be added just once even if called multiple times */
11
+ wp_enqueue_media();
12
+ /* propper way to dequeue. add to functions file in theme or custom plugin
13
+ function wppb_dequeue_script() {
14
+ wp_script_is( 'wppb-upload-script', 'enqueued' ); //true
15
+ wp_dequeue_script( 'wppb-upload-script' );
16
+ }
17
+ add_action( 'get_footer', 'wppb_dequeue_script' );
18
+ */
19
+ $upload_script_vars = array(
20
+ 'nonce' => wp_create_nonce( 'wppb_woo_simple_upload' ),
21
+ 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
22
+ 'remove_link_text' => __( 'Remove', 'profile-builder' )
23
+ );
24
+
25
+ wp_enqueue_script( 'wppb-upload-script', WPPB_PLUGIN_URL.'front-end/default-fields/upload/upload.js', array('jquery'), PROFILE_BUILDER_VERSION, true );
26
+ wp_localize_script( 'wppb-upload-script', 'wppb_upload_script_vars', $upload_script_vars );
27
+
28
+ $wppb_generalSettings = get_option( 'wppb_general_settings' );
29
+
30
+ if ( ( isset( $wppb_generalSettings['extraFieldsLayout'] ) && ( $wppb_generalSettings['extraFieldsLayout'] == 'default' ) ) )
31
+ wp_enqueue_style( 'profile-builder-upload-css', WPPB_PLUGIN_URL.'front-end/default-fields/upload/upload.css', false, PROFILE_BUILDER_VERSION );
32
+
33
+ $item_title = apply_filters( 'wppb_'.$form_location.'_avatar_custom_field_'.$field['id'].'_item_title', wppb_icl_t( 'plugin profile-builder-pro', 'custom_field_'.$field['id'].'_title_translation', $field['field-title'], true ) );
34
+ $item_description = wppb_icl_t( 'plugin profile-builder-pro', 'custom_field_'.$field['id'].'_description_translation', $field['description'], true );
35
+
36
+ if( $form_location != 'register' ) {
37
+ if( empty( $request_data[wppb_handle_meta_name( $field['meta-name'] )] ) )
38
+ $input_value = ( (wppb_user_meta_exists($user_id, $field['meta-name']) != null) ? get_user_meta($user_id, $field['meta-name'], true) : '');
39
+ else
40
+ $input_value = $request_data[wppb_handle_meta_name( $field['meta-name'] )];
41
+
42
+ if( !empty( $input_value ) && !is_numeric( $input_value ) ){
43
+ /* we have a file url and we need to change it into an attachment */
44
+ // Check the type of file. We'll use this as the 'post_mime_type'.
45
+ $wp_upload_dir = wp_upload_dir();
46
+ $file_path = str_replace( $wp_upload_dir['baseurl'], $wp_upload_dir["basedir"], $input_value );
47
+ //on windows os we might have \ instead of / so change them
48
+ $file_path = str_replace( "\\", "/", $file_path );
49
+ $file_type = wp_check_filetype( basename( $input_value ), null );
50
+ $attachment = array(
51
+ 'guid' => $input_value,
52
+ 'post_mime_type' => $file_type['type'],
53
+ 'post_title' => preg_replace( '/\.[^.]+$/', '', basename( $input_value ) ),
54
+ 'post_content' => '',
55
+ 'post_status' => 'inherit'
56
+ );
57
+
58
+ // Insert the attachment.
59
+ $input_value = wp_insert_attachment( $attachment, $input_value, 0 );
60
+ if( !empty( $input_value ) ) {
61
+ // Make sure that this file is included, as wp_generate_attachment_metadata() depends on it.
62
+ require_once(ABSPATH . 'wp-admin/includes/image.php');
63
+ // Generate the metadata for the attachment, and update the database record.
64
+ $attach_data = wp_generate_attachment_metadata($input_value, $file_path);
65
+ wp_update_attachment_metadata($input_value, $attach_data);
66
+ /* save the new attachment instead of the url */
67
+ update_user_meta( $user_id, $field['meta-name'], $input_value );
68
+ }
69
+ }
70
+ }
71
+ else
72
+ $input_value = !empty( $_POST[$field['meta-name']] ) ? sanitize_text_field( $_POST[$field['meta-name']] ) : '';
73
+
74
+ if ( $form_location != 'back_end' ){
75
+ $error_mark = ( ( $field['required'] == 'Yes' ) ? '<span class="wppb-required" title="'.wppb_required_field_error($field["field-title"]).'">*</span>' : '' );
76
+
77
+ if ( array_key_exists( $field['id'], $field_check_errors ) )
78
+ $error_mark = '<img src="'.WPPB_PLUGIN_URL.'assets/images/pencil_delete.png" title="'.wppb_required_field_error($field["field-title"]).'"/>';
79
+
80
+ $extra_attr = apply_filters( 'wppb_extra_attribute', '', $field, $form_location );
81
+
82
+ $output = '<label for="'.$field['meta-name'].'">'.$item_title.$error_mark.'</label>';
83
+ $output .= wppb_make_upload_button( $field, $input_value, $extra_attr );
84
+ if( !empty( $item_description ) )
85
+ $output .= '<span class="wppb-description-delimiter">'.$item_description.'</span>';
86
+ }else{
87
+ $item_title = ( ( $field['required'] == 'Yes' ) ? $item_title .' <span class="description">('. __( 'required', 'profile-builder' ) .')</span>' : $item_title );
88
+ $output = '
89
+ <table class="form-table">
90
+ <tr>
91
+ <th><label for="'.$field['meta-name'].'">'.$item_title.'</label></th>
92
+ <td>';
93
+ $output .= wppb_make_upload_button( $field, $input_value );
94
+ $output .='<br/><span class="wppb-description-delimiter">'.$item_description;
95
+ $output .= '
96
+ </td>
97
+ </tr>
98
+ </table>';
99
+ }
100
+
101
+ return apply_filters( 'wppb_'.$form_location.'_avatar_custom_field_'.$field['id'], $output, $form_location, $field, $user_id, $field_check_errors, $request_data, $input_value );
102
+ }
103
+ }
104
+ add_filter( 'wppb_output_form_field_avatar', 'wppb_avatar_handler', 10, 6 );
105
+ add_filter( 'wppb_admin_output_form_field_avatar', 'wppb_avatar_handler', 10, 6 );
106
+
107
+
108
+ /* handle field save */
109
+ function wppb_save_avatar_value( $field, $user_id, $request_data, $form_location ){
110
+ if( $field['field'] == 'Avatar' ){
111
+ $field['meta-name'] = Wordpress_Creation_Kit_PB::wck_generate_slug( $field['meta-name'] );
112
+ if ( isset( $field[ 'simple-upload' ] ) && $field[ 'simple-upload' ] == 'yes' && $field[ 'woocommerce-checkout-field' ] !== 'Yes' ) {
113
+ //Save data in the case the simple upload field is used
114
+ $field_name = 'simple_upload_' . wppb_handle_meta_name( $field[ 'meta-name' ] );
115
+ if( isset( $_FILES[ $field_name ] ) ) {
116
+ if ( !( isset( $field[ 'conditional-logic-enabled' ] ) && $field[ 'conditional-logic-enabled' ] == 'yes' && !isset( $request_data[ wppb_handle_meta_name( $field[ 'meta-name' ] ) ] ) ) ){
117
+ if ( isset( $_FILES[ $field_name ][ 'size' ] ) && $_FILES[ $field_name ][ 'size' ] == 0 ){
118
+ if ( isset( $request_data[ wppb_handle_meta_name( $field[ 'meta-name' ] ) ] ) ){
119
+ update_user_meta( $user_id, $field[ 'meta-name' ], sanitize_text_field( $request_data[ wppb_handle_meta_name( $field[ 'meta-name' ] ) ] ) );
120
+ }
121
+ }
122
+ else{
123
+ $attachment_id = $request_data[ $field[ 'meta-name' ] ];
124
+ update_user_meta( $user_id, $field[ 'meta-name' ], absint( $attachment_id ) );
125
+ if ( $attachment_id !== '' ) {
126
+ wp_update_post(array(
127
+ 'ID' => absint(trim($attachment_id)),
128
+ 'post_author' => $user_id
129
+ ));
130
+ }
131
+ }
132
+ }
133
+ }
134
+ }
135
+ else{
136
+ //Save data in the case the WordPress upload is used
137
+ if ( isset( $request_data[wppb_handle_meta_name( $field['meta-name'] )] ) ){
138
+ update_user_meta( $user_id, $field['meta-name'], sanitize_text_field( $request_data[wppb_handle_meta_name( $field['meta-name'] )] ) );
139
+ }
140
+ }
141
+ }
142
+ }
143
+ add_action( 'wppb_save_form_field', 'wppb_save_avatar_value', 10, 4 );
144
+ add_action( 'wppb_backend_save_form_field', 'wppb_save_avatar_value', 10, 4 );
145
+
146
+ /**
147
+ * Function that saves an attachment from the simple upload version of the Avatar field
148
+ * @param $field_name
149
+ * @return string|WP_Error
150
+ */
151
+ function wppb_avatar_save_simple_upload_file ( $field_name ){
152
+
153
+ require_once( ABSPATH . 'wp-admin/includes/file.php' );
154
+ $upload_overrides = array( 'test_form' => false );
155
+
156
+ if( isset( $_FILES[$field_name] ) )
157
+ $file = wp_handle_upload( $_FILES[$field_name], $upload_overrides );
158
+
159
+ if ( isset( $file[ 'error' ] ) ) {
160
+ return new WP_Error( 'upload_error', $file[ 'error' ] );
161
+ }
162
+ $filename = isset( $_FILES[ $field_name ][ 'name' ] ) ? sanitize_text_field( $_FILES[ $field_name ][ 'name' ] ) : '';
163
+ $wp_filetype = wp_check_filetype( $filename, null );
164
+ $attachment = array(
165
+ 'post_mime_type' => $wp_filetype[ 'type' ],
166
+ 'post_title' => $filename,
167
+ 'post_content' => '',
168
+ 'post_status' => 'inherit'
169
+ );
170
+
171
+ $attachment_id = wp_insert_attachment( $attachment, $file[ 'file' ] );
172
+
173
+ if (!is_wp_error($attachment_id) && is_numeric($attachment_id)) {
174
+ require_once(ABSPATH . 'wp-admin/includes/image.php');
175
+ $attachment_data = wp_generate_attachment_metadata($attachment_id, $file['file']);
176
+ wp_update_attachment_metadata($attachment_id, $attachment_data);
177
+ return trim($attachment_id);
178
+ } else {
179
+ return '';
180
+ }
181
+ }
182
+
183
+ /* save file when ec is enabled */
184
+ function wppb_avatar_add_upload_for_user_signup( $field_value, $field, $request_data ){
185
+
186
+ // Save the uploaded file
187
+ // It will have no author until the user's email is confirmed
188
+ if( $field['field'] == 'Avatar' ) {
189
+ if( isset( $field[ 'simple-upload' ] ) && $field[ 'simple-upload' ] === 'yes' && $field[ 'woocommerce-checkout-field' ] !== 'Yes' ) {
190
+ $field_name = 'simple_upload_' . $field['meta-name'];
191
+
192
+ if (isset($_FILES[$field_name]) &&
193
+ isset($_FILES[$field_name]['size']) && $_FILES[$field_name]['size'] !== 0 &&
194
+ !(wppb_belongs_to_repeater_with_conditional_logic($field) && !isset($request_data[wppb_handle_meta_name($field['meta-name'])])) &&
195
+ !(isset($field['conditional-logic-enabled']) && $field['conditional-logic-enabled'] == 'yes' && !isset($request_data[wppb_handle_meta_name($field['meta-name'])])) &&
196
+ wppb_valid_simple_upload($field, $_FILES[$field_name])) { /* phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized */ /* no need here */
197
+ return wppb_save_simple_upload_file($field_name);
198
+ }
199
+ } else {
200
+ $attachment_id = $request_data[wppb_handle_meta_name( $field['meta-name'] )];
201
+ if ( isset( $attachment_id ) ) {
202
+ return absint( trim( $attachment_id ) );
203
+ }
204
+ }
205
+ }
206
+
207
+ return '';
208
+ }
209
+ add_filter( 'wppb_add_to_user_signup_form_field_avatar', 'wppb_avatar_add_upload_for_user_signup', 10, 3 );
210
+
211
+ /* handle simple upload at the WooCommerce Checkout */
212
+ function wppb_woo_simple_avatar(){
213
+ check_ajax_referer( 'wppb_woo_simple_upload', 'nonce' );
214
+ if ( isset($_POST["name"]) ) {
215
+ echo json_encode( wppb_avatar_save_simple_upload_file( sanitize_text_field( $_POST["name"] ) ) );
216
+ }
217
+ wp_die();
218
+ }
219
+ add_action( 'wp_ajax_nopriv_wppb_woo_simple_avatar', 'wppb_woo_simple_avatar' );
220
+ add_action( 'wp_ajax_wppb_woo_simple_avatar', 'wppb_woo_simple_avatar' );
221
+
222
+ /* handle field validation */
223
+ function wppb_check_avatar_value( $message, $field, $request_data, $form_location ){
224
+ if( $field['field'] == 'Avatar' ){
225
+ if( $field['required'] == 'Yes' ){
226
+ $field['meta-name'] = Wordpress_Creation_Kit_PB::wck_generate_slug( $field['meta-name'] );
227
+ if ( isset( $field[ 'simple-upload' ] ) && $field[ 'simple-upload' ] == 'yes' && $field[ 'woocommerce-checkout-field' ] !== 'Yes' ) {
228
+ //Check the required field in case simple upload is used
229
+ $field_name = 'simple_upload_' . wppb_handle_meta_name( $field[ 'meta-name' ] );
230
+ if ( (!isset( $_FILES[ $field_name ] ) || ( isset( $_FILES[ $field_name ] ) && isset( $_FILES[ $field_name ][ 'size' ] ) && $_FILES[ $field_name ][ 'size' ] == 0 ) || !wppb_valid_simple_upload( $field, $_FILES[ $field_name ] ) ) && isset( $request_data[ $field[ 'meta-name' ] ] ) && empty( $request_data[ $field[ 'meta-name' ] ] ) ){ /* phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized */ /* no need here for wppb_valid_simple_upload() */
231
+ return wppb_required_field_error( $field[ 'field-title' ] );
232
+ }
233
+ }
234
+ else{
235
+ //Check the required field in case the WordPress upload is used
236
+ if ( ( isset( $request_data[wppb_handle_meta_name( $field['meta-name'] )] ) && ( trim( $request_data[wppb_handle_meta_name( $field['meta-name'] )] ) == '' ) ) || !isset( $request_data[wppb_handle_meta_name( $field['meta-name'] )] ) ){
237
+ return wppb_required_field_error($field["field-title"]);
238
+ }
239
+ }
240
+ }
241
+ }
242
+ return $message;
243
+ }
244
+ add_filter( 'wppb_check_form_field_avatar', 'wppb_check_avatar_value', 10, 4 );
245
+
246
+
247
+ /* register image size defined in avatar field */
248
+ add_action( 'after_setup_theme', 'wppb_add_avatar_image_sizes' );
249
+ function wppb_add_avatar_image_sizes() {
250
+ if ( isset($_REQUEST['action']) && ( ( 'upload-attachment' == $_REQUEST['action'] && isset($_REQUEST['wppb_upload']) && 'true' == $_REQUEST['wppb_upload'] ) || 'wppb_woo_simple_avatar' == $_REQUEST['action'] ) ) {
251
+
252
+ $all_fields = get_option('wppb_manage_fields');
253
+ if( !empty( $all_fields ) ) {
254
+ foreach ($all_fields as $field) {
255
+ if( $field['field'] == 'Avatar' ) {
256
+ wppb_add_avatar_sizes( $field );
257
+ }
258
+ }
259
+ }
260
+
261
+ wppb_userlisting_avatar();
262
+ }
263
+ }
front-end/default-fields/checkbox/checkbox.php ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /* handle field output */
3
+ function wppb_checkbox_handler( $output, $form_location, $field, $user_id, $field_check_errors, $request_data ){
4
+ if ( $field['field'] == 'Checkbox' ){
5
+ $item_title = apply_filters( 'wppb_'.$form_location.'_checkbox_custom_field_'.$field['id'].'_item_title', wppb_icl_t( 'plugin profile-builder-pro', 'custom_field_'.$field['id'].'_title_translation', $field['field-title'], true ) );
6
+ $item_description = wppb_icl_t( 'plugin profile-builder-pro', 'custom_field_'.$field['id'].'_description_translation', $field['description'], true );
7
+ $item_option_labels = wppb_icl_t( 'plugin profile-builder-pro', 'custom_field_'.$field['id'].'_labels_translation', $field['labels'], true );
8
+
9
+ $checkbox_labels = apply_filters( 'wppb_checkbox_labels_array', explode( ',', $item_option_labels ), $field, $form_location, $user_id, $request_data );
10
+ $checkbox_values = apply_filters( 'wppb_checkbox_options_array', explode( ',', $field['options'] ), $field, $form_location, $user_id, $request_data );
11
+
12
+ if( $form_location != 'register' )
13
+ $input_value = ( ( wppb_user_meta_exists ( $user_id, $field['meta-name'] ) != null ) ? array_map( 'trim', explode( ',', stripslashes(get_user_meta( $user_id, $field['meta-name'], true )) ) ) : array_map( 'trim', explode( ',', $field['default-options'] ) ) );
14
+ else
15
+ $input_value = ( !empty( $field['default-options'] ) ? array_map( 'trim', explode( ',', $field['default-options'] ) ) : array() );
16
+
17
+ if( isset( $request_data[ wppb_handle_meta_name( $field['meta-name'] ) ] ) && !empty( $request_data[ wppb_handle_meta_name( $field['meta-name'] ) ] ) )
18
+ $input_value = $request_data[ wppb_handle_meta_name( $field['meta-name'] ) ];
19
+
20
+ $extra_attr = apply_filters( 'wppb_extra_attribute', '', $field, $form_location );
21
+
22
+ if ( $form_location != 'back_end' ){
23
+ $error_mark = ( ( $field['required'] == 'Yes' ) ? '<span class="wppb-required" title="'.wppb_required_field_error($field["field-title"]).'">*</span>' : '' );
24
+
25
+ if ( array_key_exists( $field['id'], $field_check_errors ) )
26
+ $error_mark = '<img src="'.WPPB_PLUGIN_URL.'assets/images/pencil_delete.png" title="'.wppb_required_field_error($field["field-title"]).'"/>';
27
+
28
+ $output = '
29
+ <label for="'.$field['meta-name'].'">'.$item_title.$error_mark.'</label>';
30
+ $output .= '<ul class="wppb-checkboxes">';
31
+ $output .= '<li class="wppb-hidden"><input type="hidden" value="" name="' . $field['meta-name'] . '"></li>';
32
+ foreach( $checkbox_values as $key => $value ){
33
+ $output .= '<li><input value="'.esc_attr( trim( $value ) ).'" class="custom_field_checkbox" name="' . $field['meta-name'] . '[]" id="'.Wordpress_Creation_Kit_PB::wck_generate_slug( trim( $value ) ).'_'.$field['id'].'" type="checkbox" '. $extra_attr .' ';
34
+
35
+ if ( in_array( trim( $value ), $input_value ) )
36
+ $output .= ' checked';
37
+
38
+ $output .= ' /><label for="'.Wordpress_Creation_Kit_PB::wck_generate_slug( trim( $value ) ).'_'.$field['id'].'" class="wppb-rc-value">'.( ( !isset( $checkbox_labels[$key] ) || !$checkbox_labels[$key] ) ? trim( $checkbox_values[$key] ) : trim( $checkbox_labels[$key] ) ).'</label></li>';
39
+ }
40
+ $output .= '</ul>';
41
+ if( !empty( $item_description ) )
42
+ $output .= '<span class="wppb-description-delimiter">'.$item_description.'</span>';
43
+
44
+ }else{
45
+ $item_title = ( ( $field['required'] == 'Yes' ) ? $item_title .' <span class="description">('. __( 'required', 'profile-builder' ) .')</span>' : $item_title );
46
+ $output = '
47
+ <table class="form-table">
48
+ <tr>
49
+ <th><label for="'.$field['meta-name'].'">'.$item_title.'</label></th>
50
+ <td>';
51
+ $output .= '<ul class="wppb-checkboxes">';
52
+ $output .= '<li class="wppb-hidden"><input type="hidden" value="" name="' . $field['meta-name'] . '"></li>';
53
+ foreach( $checkbox_values as $key => $value ){
54
+ $output .= '<li><input value="'.esc_attr( trim( $value ) ).'" class="custom_field_checkbox '. apply_filters( 'wppb_fields_extra_css_class', '', $field ) .'" name="' . $field['meta-name'] . '[]" id="'.Wordpress_Creation_Kit_PB::wck_generate_slug( trim( $value ) ).'_'.$field['id'].'" type="checkbox"';
55
+
56
+ if ( in_array( trim( $value ), $input_value ) )
57
+ $output .= ' checked';
58
+
59
+ $output .= ' /><label for="'.Wordpress_Creation_Kit_PB::wck_generate_slug( trim( $value ) ).'_'.$field['id'].'" class="wppb-rc-value">'.( ( !isset( $checkbox_labels[$key] ) || !$checkbox_labels[$key] ) ? trim( $checkbox_values[$key] ) : trim( $checkbox_labels[$key] ) ).'</label></li>';
60
+ }
61
+
62
+ $output .= '</ul>
63
+ <span class="wppb-description-delimiter">'.$item_description.'</span>
64
+ </td>
65
+ </tr>
66
+ </table>';
67
+ }
68
+
69
+ return apply_filters( 'wppb_'.$form_location.'_checkbox_custom_field_'.$field['id'], $output, $form_location, $field, $user_id, $field_check_errors, $request_data, $input_value );
70
+ }
71
+ }
72
+ add_filter( 'wppb_output_form_field_checkbox', 'wppb_checkbox_handler', 10, 6 );
73
+ add_filter( 'wppb_admin_output_form_field_checkbox', 'wppb_checkbox_handler', 10, 6 );
74
+
75
+
76
+ /* handle field save */
77
+ function wppb_save_checkbox_value( $field, $user_id, $request_data, $form_location ){
78
+ if( $field['field'] == 'Checkbox' ){
79
+ if( isset( $request_data[ wppb_handle_meta_name( $field['meta-name'] ) ] ) ) {
80
+ $checkbox_values = wppb_process_checkbox_value($field, $request_data);
81
+ update_user_meta($user_id, $field['meta-name'], $checkbox_values);
82
+ }
83
+ }
84
+ }
85
+ add_action( 'wppb_save_form_field', 'wppb_save_checkbox_value', 10, 4 );
86
+ add_action( 'wppb_backend_save_form_field', 'wppb_save_checkbox_value', 10, 4 );
87
+
88
+
89
+ function wppb_process_checkbox_value( $field, $request_data ){
90
+ $checkbox_values = '';
91
+
92
+ if( isset( $request_data[ wppb_handle_meta_name( $field['meta-name'] ) ] ) && is_array( $request_data[ wppb_handle_meta_name( $field['meta-name'] ) ] ) )
93
+ $checkbox_values = implode( ',', $request_data[ wppb_handle_meta_name( $field['meta-name'] ) ] );
94
+
95
+ return trim( $checkbox_values, ',' );
96
+ }
97
+
98
+
99
+ function wppb_add_checkbox_for_user_signup( $field_value, $field, $request_data ){
100
+ return wppb_process_checkbox_value( $field, $request_data );
101
+ }
102
+ add_filter( 'wppb_add_to_user_signup_form_field_checkbox', 'wppb_add_checkbox_for_user_signup', 10, 3 );
103
+
104
+
105
+ /* handle field validation */
106
+ function wppb_check_checkbox_value( $message, $field, $request_data, $form_location ){
107
+
108
+ if( $field['field'] == 'Checkbox' ){
109
+ $checked_values = '';
110
+
111
+ if( isset( $request_data[ wppb_handle_meta_name( $field['meta-name'] ) ] ) && is_array( $request_data[ wppb_handle_meta_name( $field['meta-name'] ) ] ) )
112
+ $checked_values = implode( ',', $request_data[ wppb_handle_meta_name( $field['meta-name'] ) ] );
113
+
114
+ if ( ( $field['required'] == 'Yes' ) && empty( $checked_values ) ){
115
+ return wppb_required_field_error($field["field-title"]);
116
+ }
117
+
118
+ }
119
+
120
+ return $message;
121
+ }
122
+ add_filter( 'wppb_check_form_field_checkbox', 'wppb_check_checkbox_value', 10, 4 );
front-end/default-fields/default-fields.php CHANGED
@@ -50,5 +50,20 @@ function wppb_include_default_fields_files() {
50
 
51
  /* added email-confirmation field in main plugin since version 3.3.4 */
52
  include_once( WPPB_PLUGIN_DIR.'/front-end/default-fields/email-confirmation/email-confirmation.php' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  }
54
- wppb_include_default_fields_files();
50
 
51
  /* added email-confirmation field in main plugin since version 3.3.4 */
52
  include_once( WPPB_PLUGIN_DIR.'/front-end/default-fields/email-confirmation/email-confirmation.php' );
53
+
54
+ // added extra fields since version 3.8.1
55
+ if( !defined( 'WPPB_PAID_PLUGIN_DIR' ) || ( defined( 'WPPB_PAID_PLUGIN_DIR' ) && defined( 'PROFILE_BUILDER_PAID_VERSION' ) ) ){
56
+
57
+ include_once( WPPB_PLUGIN_DIR.'/front-end/default-fields/avatar/avatar.php' );
58
+ include_once( WPPB_PLUGIN_DIR.'/front-end/default-fields/checkbox/checkbox.php' );
59
+ include_once( WPPB_PLUGIN_DIR.'/front-end/default-fields/heading/heading.php' );
60
+ include_once( WPPB_PLUGIN_DIR.'/front-end/default-fields/input/input.php' );
61
+ include_once( WPPB_PLUGIN_DIR.'/front-end/default-fields/radio/radio.php' );
62
+ include_once( WPPB_PLUGIN_DIR.'/front-end/default-fields/select/select.php' );
63
+ include_once( WPPB_PLUGIN_DIR.'/front-end/default-fields/select2/select2.php' );
64
+ include_once( WPPB_PLUGIN_DIR.'/front-end/default-fields/textarea/textarea.php' );
65
+
66
+ }
67
+
68
  }
69
+ wppb_include_default_fields_files();
front-end/default-fields/fields-functions.php ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
3
+
4
+ // the function to display the custom fields in the back-end
5
+ function wppb_display_fields_in_admin( $user ){
6
+ $admin_fields = '';
7
+ ?>
8
+ <script type="text/javascript">
9
+ var form = document.getElementById('your-profile');
10
+ form.encoding = "multipart/form-data"; //IE5.5
11
+ form.setAttribute('enctype', 'multipart/form-data'); //required for IE6 (is interpreted into "encType")
12
+
13
+ jQuery(function(){
14
+ //hover states on the static widgets
15
+ jQuery('#dialog_link, ul#icons li').on('mouseenter', function() { jQuery(this).addClass('ui-state-hover'); });
16
+ jQuery('#dialog_link, ul#icons li').on('mouseleave', function() { jQuery(this).removeClass('ui-state-hover'); });
17
+ });
18
+ </script>
19
+ <?php
20
+
21
+ $all_data = get_option( 'wppb_manage_fields' );
22
+ if ( is_array( $all_data ) ){
23
+ foreach ( $all_data as $value ) {
24
+
25
+ $display_field = apply_filters( 'wppb_output_display_form_field', true, $value, 'back_end', 'all', $user->ID );
26
+
27
+ if( $display_field == false )
28
+ continue;
29
+
30
+ $admin_fields .= apply_filters( 'wppb_admin_output_form_field_'.Wordpress_Creation_Kit_PB::wck_generate_slug( $value['field'] ), '', 'back_end', $value, $user->ID, '', $_REQUEST );
31
+ }
32
+
33
+ }
34
+
35
+ echo $admin_fields; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
36
+ }
37
+
38
+ // the function to save the values from the custom fields in the back-end
39
+ function wppb_save_fields_in_admin( $user_id ){
40
+ $global_request = $_REQUEST;
41
+ $all_data = apply_filters( 'wppb_form_fields', get_option( 'wppb_manage_fields' ), array( 'context' => 'validate_backend' ) );
42
+ if ( is_array( $all_data ) ){
43
+ foreach ( $all_data as $field ){
44
+ /* check to see if we have any error for the field. if we do don't save it */
45
+ $error_for_field = apply_filters( 'wppb_check_form_field_'.Wordpress_Creation_Kit_PB::wck_generate_slug( $field['field'] ), '', $field, $global_request, 'back_end', '', $user_id );
46
+ if( empty( $error_for_field ) )
47
+ do_action( 'wppb_backend_save_form_field', $field, $user_id, $global_request, 'backend-form' );
48
+ }
49
+ }
50
+ }
51
+
52
+ /* the function that checks for field error in the backend */
53
+ function wppb_validate_fields_in_admin( &$errors, $update, &$user ){
54
+
55
+ $all_data = apply_filters( 'wppb_form_fields', get_option( 'wppb_manage_fields' ), array( 'context' => 'validate_backend' ) );
56
+ $global_request = $_REQUEST;
57
+ if ( is_array( $all_data ) ){
58
+ foreach ( $all_data as $field ){
59
+ $error_for_field = apply_filters( 'wppb_check_form_field_'.Wordpress_Creation_Kit_PB::wck_generate_slug( $field['field'] ), '', $field, $global_request, 'back_end', '', get_current_user_id() );
60
+
61
+ if( !empty( $error_for_field ) ){
62
+ $errors->add( $field['id'], '<strong>'. __( 'ERROR', 'profile-builder' ).'</strong> '.$field['field-title'].':'.$error_for_field);
63
+ }
64
+ }
65
+ }
66
+ }
front-end/default-fields/heading/heading.php ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /* handle field output */
3
+ function wppb_heading_handler( $output, $form_location, $field, $user_id, $field_check_errors, $request_data ){
4
+ if ( $field['field'] == 'Heading' ){
5
+ $item_title = apply_filters( 'wppb_'.$form_location.'_heading_custom_field_'.$field['id'].'_item_title', wppb_icl_t( 'plugin profile-builder-pro', 'custom_field_'.$field['id'].'_title_translation', $field['field-title'], true ) );
6
+ $item_description = wppb_icl_t( 'plugin profile-builder-pro', 'custom_field_'.$field['id'].'_description_translation', $field['description'], true );
7
+
8
+ if( isset( $field['heading-tag'] ) ) {
9
+ $heading_tag = $field['heading-tag'];
10
+ } else {
11
+ $heading_tag = 'h4';
12
+ }
13
+
14
+ $heading_element1 = ( ( $form_location == 'back_end' ) ? '<h3>' : '<'. $heading_tag .' class="extra_field_heading">' );
15
+ $heading_element2 = ( ( $form_location == 'back_end' ) ? '</h3>' : '</'. $heading_tag .'>' );
16
+
17
+ $output = $heading_element1 . $item_title . $heading_element2 . '<span class="wppb-description-delimiter">'.$item_description.'</span>';
18
+
19
+ return apply_filters( 'wppb_'.$form_location.'_heading_custom_field_'.$field['id'], $output, $form_location, $field, $user_id, $field_check_errors, $request_data );
20
+ }
21
+ }
22
+ add_filter( 'wppb_output_form_field_heading', 'wppb_heading_handler', 10, 6 );
23
+ add_filter( 'wppb_admin_output_form_field_heading', 'wppb_heading_handler', 10, 6 );
front-end/default-fields/input/input.php ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /* handle field output */
3
+ function wppb_input_handler( $output, $form_location, $field, $user_id, $field_check_errors, $request_data ){
4
+ if ( $field['field'] == 'Input' ){
5
+ $item_title = apply_filters( 'wppb_'.$form_location.'_input_custom_field_'.$field['id'].'_item_title', wppb_icl_t( 'plugin profile-builder-pro', 'custom_field_'.$field['id'].'_title_translation', $field['field-title'], true ) );
6
+ $item_description = wppb_icl_t( 'plugin profile-builder-pro', 'custom_field_'.$field['id'].'_description_translation', $field['description'], true );
7
+
8
+ $extra_attr = apply_filters( 'wppb_extra_attribute', '', $field, $form_location );
9
+
10
+ if( $form_location != 'register' )
11
+ $input_value = ( ( wppb_user_meta_exists ( $user_id, $field['meta-name'] ) != null ) ? get_user_meta( $user_id, $field['meta-name'], true ) : $field['default-value'] );
12
+ else
13
+ $input_value = ( isset( $field['default-value'] ) ? trim( $field['default-value'] ) : '' );
14
+
15
+ $input_value = ( isset( $request_data[wppb_handle_meta_name( $field['meta-name'] )] ) ? trim( $request_data[wppb_handle_meta_name( $field['meta-name'] )] ) : $input_value );
16
+
17
+ if ( $form_location != 'back_end' ){
18
+ $error_mark = ( ( $field['required'] == 'Yes' ) ? '<span class="wppb-required" title="'.wppb_required_field_error($field["field-title"]).'">*</span>' : '' );
19
+
20
+ if ( array_key_exists( $field['id'], $field_check_errors ) )
21
+ $error_mark = '<img src="'.WPPB_PLUGIN_URL.'assets/images/pencil_delete.png" title="'.wppb_required_field_error($field["field-title"]).'"/>';
22
+
23
+ $output = '
24
+ <label for="'.$field['meta-name'].'">'.$item_title.$error_mark.'</label>
25
+ <input class="extra_field_input '. apply_filters( 'wppb_fields_extra_css_class', '', $field ) .'" name="'.$field['meta-name'].'" maxlength="'. apply_filters( 'wppb_maximum_character_length', 250, $field ) .'" type="text" id="'.$field['meta-name'].'" value="'. esc_attr( wp_unslash( $input_value ) ) .'" '. $extra_attr .'/>';
26
+ if( !empty( $item_description ) )
27
+ $output .= '<span class="wppb-description-delimiter">'.$item_description.'</span>';
28
+
29
+ }else{
30
+ $item_title = ( ( $field['required'] == 'Yes' ) ? $item_title .' <span class="description">('. __( 'required', 'profile-builder' ) .')</span>' : $item_title );
31
+ $output = '
32
+ <table class="form-table">
33
+ <tr>
34
+ <th><label for="'.$field['meta-name'].'">'.$item_title.'</label></th>
35
+ <td>
36
+ <input class="custom_field_input" size="45" name="'.$field['meta-name'].'" maxlength="'. apply_filters( 'wppb_maximum_character_length', 250, $field ) .'" type="text" id="'.$field['meta-name'].'" value="'. esc_attr( $input_value ) .'" '. $extra_attr .'/>
37
+ <span class="description">'.$item_description.'</span>
38
+ </td>
39
+ </tr>
40
+ </table>';
41
+ }
42
+
43
+ return apply_filters( 'wppb_'.$form_location.'_input_custom_field_'.$field['id'], $output, $form_location, $field, $user_id, $field_check_errors, $request_data, $input_value );
44
+ }
45
+ }
46
+ add_filter( 'wppb_output_form_field_input', 'wppb_input_handler', 10, 6 );
47
+ add_filter( 'wppb_admin_output_form_field_input', 'wppb_input_handler', 10, 6 );
48
+
49
+ /* handle field save */
50
+ function wppb_save_input_value( $field, $user_id, $request_data, $form_location ){
51
+ if( $field['field'] == 'Input' ){
52
+ if ( isset( $request_data[wppb_handle_meta_name( $field['meta-name'] )] ) )
53
+ update_user_meta( $user_id, $field['meta-name'], $request_data[wppb_handle_meta_name( $field['meta-name'] )] );
54
+ }
55
+ }
56
+ add_action( 'wppb_save_form_field', 'wppb_save_input_value', 10, 4 );
57
+ add_action( 'wppb_backend_save_form_field', 'wppb_save_input_value', 10, 4 );
58
+
59
+ /* handle field validation */
60
+ function wppb_check_input_value( $message, $field, $request_data, $form_location ){
61
+ if( $field['field'] == 'Input' ){
62
+ if( $field['required'] == 'Yes' ){
63
+ if ( ( isset( $request_data[wppb_handle_meta_name( $field['meta-name'] )] ) && ( trim( $request_data[wppb_handle_meta_name( $field['meta-name'] )] ) == '' ) ) || !isset( $request_data[wppb_handle_meta_name( $field['meta-name'] )] ) ){
64
+ return wppb_required_field_error($field["field-title"]);
65
+ }
66
+ }
67
+ }
68
+
69
+ return $message;
70
+ }
71
+ add_filter( 'wppb_check_form_field_input', 'wppb_check_input_value', 10, 4 );
front-end/default-fields/radio/radio.php ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /* handle field output */
3
+ function wppb_radio_handler( $output, $form_location, $field, $user_id, $field_check_errors, $request_data ){
4
+ if ( $field['field'] == 'Radio' ){
5
+ $item_title = apply_filters( 'wppb_'.$form_location.'_radio_custom_field_'.$field['id'].'_item_title', wppb_icl_t( 'plugin profile-builder-pro', 'custom_field_'.$field['id'].'_title_translation', $field['field-title'], true ) );
6
+ $item_description = wppb_icl_t( 'plugin profile-builder-pro', 'custom_field_'.$field['id'].'_description_translation', $field['description'], true );
7
+ $item_option_labels = wppb_icl_t( 'plugin profile-builder-pro', 'custom_field_'.$field['id'].'_labels_translation', $field['labels'], true );
8
+
9
+ $radio_labels = explode( ',', $item_option_labels );
10
+ $radio_values = explode( ',', $field['options'] );
11
+
12
+ if( $form_location != 'register' )
13
+ $input_value = ( ( wppb_user_meta_exists ( $user_id, $field['meta-name'] ) != null ) ? stripslashes(get_user_meta( $user_id, $field['meta-name'], true )) : $field['default-option'] );
14
+ else
15
+ $input_value = ( isset( $field['default-option'] ) ? trim( $field['default-option'] ) : '' );
16
+
17
+ $input_value = ( isset( $request_data[wppb_handle_meta_name( $field['meta-name'] )] ) ? trim( stripslashes( $request_data[wppb_handle_meta_name( $field['meta-name'] )] )) : $input_value );
18
+
19
+ $extra_attr = apply_filters( 'wppb_extra_attribute', '', $field, $form_location );
20
+
21
+ if ( $form_location != 'back_end' ){
22
+ $error_mark = ( ( $field['required'] == 'Yes' ) ? '<span class="wppb-required" title="'.wppb_required_field_error($field["field-title"]).'">*</span>' : '' );
23
+
24
+ if ( array_key_exists( $field['id'], $field_check_errors ) )
25
+ $error_mark = '<img src="'.WPPB_PLUGIN_URL.'assets/images/pencil_delete.png" title="'.wppb_required_field_error($field["field-title"]).'"/>';
26
+
27
+ $output = '
28
+ <label for="'.$field['meta-name'].'">'.$item_title.$error_mark.'</label>';
29
+ $output .= '<ul class="wppb-radios">';
30
+ foreach( $radio_values as $key => $value){
31
+ $output .= '<li><input value="'.esc_attr( trim( $value ) ).'" class="custom_field_radio '. apply_filters( 'wppb_fields_extra_css_class', '', $field ) .'" id="'.Wordpress_Creation_Kit_PB::wck_generate_slug( trim( $value ) ).'_'.$field['id'].'" name="'.$field['meta-name'].'" type="radio" '. $extra_attr .' ';
32
+
33
+ if ( $input_value === trim( $value ) )
34
+ $output .= ' checked';
35
+
36
+ $output .= ' /><label for="'.Wordpress_Creation_Kit_PB::wck_generate_slug( trim( $value ) ).'_'.$field['id'].'" class="wppb-rc-value">'.( ( !isset( $radio_labels[$key] ) || !$radio_labels[$key] ) ? trim( $radio_values[$key] ) : trim( $radio_labels[$key] ) ).'</label></li>';
37
+ }
38
+ $output .= '</ul>';
39
+
40
+ if( !empty( $item_description ) )
41
+ $output .= '<span class="wppb-description-delimiter">'.$item_description.'</span>';
42
+
43
+ }else{
44
+ $item_title = ( ( $field['required'] == 'Yes' ) ? $item_title .' <span class="description">('. __( 'required', 'profile-builder' ) .')</span>' : $item_title );
45
+ $output = '
46
+ <table class="form-table">
47
+ <tr>
48
+ <th><label for="'.$field['meta-name'].'">'.$item_title.'</label></th>
49
+ <td>';
50
+
51
+ foreach( $radio_values as $key => $value ){
52
+ $output .= '<li><input value="'.esc_attr( trim( $value ) ).'" id="'.Wordpress_Creation_Kit_PB::wck_generate_slug( trim( $value ) ).'_'.$field['id'].'" class="custom_field_radio" name="'.$field['meta-name'].'" type="radio" '. $extra_attr .' ';
53
+
54
+ if ( $input_value === trim( $value ) )
55
+ $output .= ' checked';
56
+
57
+ $output .= ' /><label for="'.Wordpress_Creation_Kit_PB::wck_generate_slug( trim( $value ) ).'_'.$field['id'].'" class="wppb-rc-value">'.( ( !isset( $radio_labels[$key] ) || !$radio_labels[$key] ) ? trim( $radio_values[$key] ) : trim( $radio_labels[$key] ) ).'</label></li>';
58
+ }
59
+
60
+ $output .= '
61
+ <span class="description">'.$item_description.'</span>
62
+ </td>
63
+ </tr>
64
+ </table>';
65
+ }
66
+
67
+ return apply_filters( 'wppb_'.$form_location.'_radio_custom_field_'.$field['id'], $output, $form_location, $field, $user_id, $field_check_errors, $request_data, $input_value );
68
+ }
69
+ }
70
+ add_filter( 'wppb_output_form_field_radio', 'wppb_radio_handler', 10, 6 );
71
+ add_filter( 'wppb_admin_output_form_field_radio', 'wppb_radio_handler', 10, 6 );
72
+
73
+
74
+ /* handle field save */
75
+ function wppb_save_radio_select_value( $field, $user_id, $request_data, $form_location ){
76
+ if( $field['field'] == 'Radio' ){
77
+ if ( isset( $request_data[wppb_handle_meta_name( $field['meta-name'] )] ) )
78
+ update_user_meta( $user_id, $field['meta-name'], $request_data[wppb_handle_meta_name( $field['meta-name'] )] );
79
+ }
80
+ }
81
+ add_action( 'wppb_save_form_field', 'wppb_save_radio_select_value', 10, 4 );
82
+ add_action( 'wppb_backend_save_form_field', 'wppb_save_radio_select_value', 10, 4 );
83
+
84
+
85
+ /* handle field validation */
86
+ function wppb_check_radio_value( $message, $field, $request_data, $form_location ){
87
+ if( $field['field'] == 'Radio' ){
88
+ if ( !isset( $request_data[wppb_handle_meta_name( $field['meta-name'] )] ) && ( $field['required'] == 'Yes' ) ){
89
+ return wppb_required_field_error($field["field-title"]);
90
+ }
91
+ }
92
+
93
+ return $message;
94
+ }
95
+ add_filter( 'wppb_check_form_field_radio', 'wppb_check_radio_value', 10, 4 );
front-end/default-fields/select/select.php ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /* handle field output */
3
+ function wppb_select_handler( $output, $form_location, $field, $user_id, $field_check_errors, $request_data ){
4
+ if ( $field['field'] == 'Select' ){
5
+ $item_title = apply_filters( 'wppb_'.$form_location.'_select_custom_field_'.$field['id'].'_item_title', wppb_icl_t( 'plugin profile-builder-pro', 'custom_field_'.$field['id'].'_title_translation', $field['field-title'], true ) );
6
+ $item_description = wppb_icl_t( 'plugin profile-builder-pro', 'custom_field_'.$field['id'].'_description_translation', $field['description'], true );
7
+ $item_option_labels = wppb_icl_t( 'plugin profile-builder-pro', 'custom_field_'.$field['id'].'_labels_translation', $field['labels'], true );
8
+
9
+ $select_labels = apply_filters( 'wppb_select_labels_array', explode( ',', $item_option_labels ), $field, $form_location, $user_id, $request_data );
10
+ $select_values = apply_filters( 'wppb_select_options_array', explode( ',', $field['options'] ), $field, $form_location, $user_id, $request_data );
11
+
12
+ $extra_attr = apply_filters( 'wppb_extra_attribute', '', $field, $form_location );
13
+
14
+ if( $form_location != 'register' )
15
+ $input_value = ( ( wppb_user_meta_exists ( $user_id, $field['meta-name'] ) != null ) ? stripslashes( get_user_meta( $user_id, $field['meta-name'], true ) ) : $field['default-option'] );
16
+ else
17
+ $input_value = ( isset( $field['default-option'] ) ? trim( $field['default-option'] ) : '' );
18
+
19
+ $input_value = ( isset( $request_data[wppb_handle_meta_name( $field['meta-name'] )] ) ? stripslashes( trim( $request_data[wppb_handle_meta_name( $field['meta-name'] )] ) ) : $input_value );
20
+
21
+ if ( $form_location != 'back_end' ){
22
+ $error_mark = ( ( $field['required'] == 'Yes' ) ? '<span class="wppb-required" title="'.wppb_required_field_error($field["field-title"]).'">*</span>' : '' );
23
+
24
+ if ( array_key_exists( $field['id'], $field_check_errors ) )
25
+ $error_mark = '<img src="'.WPPB_PLUGIN_URL.'assets/images/pencil_delete.png" title="'.wppb_required_field_error($field["field-title"]).'"/>';
26
+
27
+ $output = '
28
+ <label for="'.$field['meta-name'].'">'.$item_title.$error_mark.'</label>
29
+ <select name="'.$field['meta-name'].'" id="'.$field['meta-name'].'" class="custom_field_select" '. $extra_attr .'>';
30
+
31
+ $extra_select_option = apply_filters( 'wppb_extra_select_option', '', $field, $item_title );
32
+ if( ! empty( $extra_select_option ) ) {
33
+ $output .= $extra_select_option;
34
+ }
35
+
36
+ foreach( $select_values as $key => $value){
37
+ $output .= '<option value="'.esc_attr( trim( $value ) ).'" class="custom_field_select_option '. apply_filters( 'wppb_fields_extra_css_class', '', $field ) .'" ';
38
+
39
+ if ( $input_value === trim( $value ) )
40
+ $output .= ' selected';
41
+
42
+ $output .= '>'.( ( !isset( $select_labels[$key] ) || !$select_labels[$key] ) ? trim( $select_values[$key] ) : trim( $select_labels[$key] ) ).'</option>';
43
+ }
44
+
45
+ $output .= '
46
+ </select>';
47
+ if( !empty( $item_description ) )
48
+ $output .= '<span class="wppb-description-delimiter">'.$item_description.'</span>';
49
+
50
+ }else{
51
+ $item_title = ( ( $field['required'] == 'Yes' ) ? $item_title .' <span class="description">('. __( 'required', 'profile-builder' ) .')</span>' : $item_title );
52
+ $output = '
53
+ <table class="form-table">
54
+ <tr>
55
+ <th><label for="'.$field['meta-name'].'">'.$item_title.'</label></th>
56
+ <td>
57
+ <select name="'.$field['meta-name'].'" class="custom_field_select" id="'.$field['meta-name'].'" '. $extra_attr .'>';
58
+
59
+ foreach( $select_values as $key => $value){
60
+ $output .= '<option value="'.esc_attr( trim( $value ) ).'" class="custom_field_select_option" ';
61
+
62
+ if ( $input_value === trim( $value ) )
63
+ $output .= ' selected';
64
+
65
+ $output .= '>'.( ( !isset( $select_labels[$key] ) || !$select_labels[$key] ) ? trim( $select_values[$key] ) : trim( $select_labels[$key] ) ).'</option>';
66
+ }
67
+
68
+ $output .= '</select>
69
+ <span class="description">'.$item_description.'</span>
70
+ </td>
71
+ </tr>
72
+ </table>';
73
+ }
74
+
75
+ return apply_filters( 'wppb_'.$form_location.'_select_custom_field_'.$field['id'], $output, $form_location, $field, $user_id, $field_check_errors, $request_data, $input_value );
76
+ }
77
+ }
78
+ add_filter( 'wppb_output_form_field_select', 'wppb_select_handler', 10, 6 );
79
+ add_filter( 'wppb_admin_output_form_field_select', 'wppb_select_handler', 10, 6 );
80
+
81
+
82
+ /* handle field save */
83
+ function wppb_save_select_value( $field, $user_id, $request_data, $form_location ){
84
+ if( $field['field'] == 'Select' ){
85
+ if ( isset( $request_data[wppb_handle_meta_name( $field['meta-name'] )] ) )
86
+ update_user_meta( $user_id, $field['meta-name'], $request_data[wppb_handle_meta_name( $field['meta-name'] )] );
87
+ }
88
+ }
89
+ add_action( 'wppb_save_form_field', 'wppb_save_select_value', 10, 4 );
90
+ add_action( 'wppb_backend_save_form_field', 'wppb_save_select_value', 10, 4 );
91
+
92
+
93
+ /* handle field validation */
94
+ function wppb_check_select_value( $message, $field, $request_data, $form_location ){
95
+ if( $field['field'] == 'Select' ){
96
+ if( $field['required'] == 'Yes' ){
97
+ if ( ( isset( $request_data[wppb_handle_meta_name( $field['meta-name'] )] ) && ( trim( $request_data[wppb_handle_meta_name( $field['meta-name'] )] ) == '' ) ) || !isset( $request_data[wppb_handle_meta_name( $field['meta-name'] )] ) ){
98
+ return wppb_required_field_error($field["field-title"]);
99
+ }
100
+ }
101
+ }
102
+
103
+ return $message;
104
+ }
105
+ add_filter( 'wppb_check_form_field_select', 'wppb_check_select_value', 10, 4 );
front-end/default-fields/select2/select2.css ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Select 2 Field */
2
+ .wppb-select2 span.select2-container{
3
+ clear: none;
4
+ width: 69.9% !important;
5
+ }
6
+
7
+ .wppb-select2-multiple span.select2-container{
8
+ clear: none;
9
+ width: 69.9% !important;
10
+ }
11
+
12
+ @media screen and (max-width: 400px){
13
+ .wppb-select2 span.select2-container{
14
+ clear: none;
15
+ width: 100% !important;
16
+ }
17
+ .wppb-select2-multiple span.select2-container{
18
+ clear: none;
19
+ width: 100% !important;
20
+ }
21
+ }
front-end/default-fields/select2/select2.js ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ /**
3
+ * Function that initializes select2 on fields
4
+ *
5
+ * @since v.3.3.4
6
+ */
7
+ function wppb_select2_initialize() {
8
+ jQuery ( '.custom_field_select2' ).each( function(){
9
+ var selectElement = jQuery( this );
10
+ var arguments = selectElement.attr('data-wppb-select2-arguments');
11
+ arguments = JSON.parse( arguments );
12
+ selectElement.select2( arguments ).on('select2:open', function(){
13
+ // compatibility with Divi Overlay
14
+ if( jQuery(selectElement).parents( '.overlay-container' ).length ){
15
+ jQuery(selectElement).data('select2').dropdown.$dropdownContainer.css( 'z-index', '99999999' );
16
+ }
17
+ });
18
+ });
19
+ }
20
+
21
+ jQuery( document ).ready(function() {
22
+ wppb_select2_initialize();
23
+ });
front-end/default-fields/select2/select2.php ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * Handle field output.
5
+ *
6
+ * @param string $output Contains the HTML to display the select2 field.
7
+ * @param string $form_location
8
+ * @param array $field
9
+ * @param integer $user_id
10
+ * @param array $field_check_errors
11
+ * @param array $request_data
12
+ * @return string Filtered output of the HTML to display the select2 field.
13
+ */
14
+ function wppb_select2_display_handler($output, $form_location, $field, $user_id, $field_check_errors, $request_data)
15
+ {
16
+ if ($field['field'] == 'Select2') {
17
+ wp_enqueue_script('wppb_sl2_lib_js', WPPB_PLUGIN_URL . 'assets/js/select2/select2.min.js', array('jquery'));
18
+ wp_enqueue_style('wppb_sl2_lib_css', WPPB_PLUGIN_URL . 'assets/css/select2/select2.min.css');
19
+
20
+ wp_enqueue_script( 'wppb_sl2_js', WPPB_PLUGIN_URL.'front-end/default-fields/select2/select2.js', array('jquery'), PROFILE_BUILDER_VERSION, true );
21
+ wp_enqueue_style( 'wppb_sl2_css', WPPB_PLUGIN_URL.'front-end/default-fields/select2/select2.css', false, PROFILE_BUILDER_VERSION );
22
+
23
+
24
+ $field['labels'] = apply_filters('wppb_select2_labels', $field['labels'], $form_location, $field, $user_id, $field_check_errors, $request_data);
25
+ $field['options'] = apply_filters('wppb_select2_options', $field['options'], $form_location, $field, $user_id, $field_check_errors, $request_data);
26
+ $arguments = apply_filters('wppb_select2_arguments', array(), $form_location, $field);
27
+
28
+ $item_title = apply_filters('wppb_' . $form_location . '_select2_custom_field_' . $field['id'] . '_item_title', wppb_icl_t('plugin profile-builder-pro', 'custom_field_' . $field['id'] . '_title_translation', $field['field-title'], true));
29
+ $item_description = wppb_icl_t('plugin profile-builder-pro', 'custom_field_' . $field['id'] . '_description_translation', $field['description'], true);
30
+ $item_option_labels = wppb_icl_t('plugin profile-builder-pro', 'custom_field_' . $field['id'] . '_labels_translation', $field['labels'], true);
31
+
32
+ $select2_labels = apply_filters('wppb_select2_labels_array', explode(',', $item_option_labels));
33
+ $select2_values = apply_filters('wppb_select2_options_array', explode(',', $field['options']));
34
+
35
+ $extra_attr = apply_filters('wppb_extra_attribute', '', $field, $form_location);
36
+
37
+ if ($form_location != 'register')
38
+ $input_value = ((wppb_user_meta_exists($user_id, $field['meta-name']) != null) ? esc_attr(stripslashes(get_user_meta($user_id, $field['meta-name'], true))) : $field['default-option']);
39
+ else
40
+ $input_value = (!empty($field['default-option']) ? esc_attr(trim($field['default-option'])) : '');
41
+
42
+ $input_value = (isset($request_data[wppb_handle_meta_name($field['meta-name'])]) ? esc_attr(stripslashes(trim($request_data[wppb_handle_meta_name($field['meta-name'])]))) : $input_value);
43
+
44
+ if ($form_location != 'back_end') {
45
+ $error_mark = (($field['required'] == 'Yes') ? '<span class="wppb-required" title="' . wppb_required_field_error($field["field-title"]) . '">*</span>' : '');
46
+
47
+ if (array_key_exists($field['id'], $field_check_errors))
48
+ $error_mark = '<img src="' . WPPB_PLUGIN_URL . 'assets/images/pencil_delete.png" title="' . wppb_required_field_error($field["field-title"]) . '"/>';
49
+
50
+ $output = '
51
+ <label for="' . esc_attr($field['meta-name']) . '">' . $item_title . $error_mark . '</label>
52
+ <select name="' . esc_attr($field['meta-name']) . '" id="' . esc_attr($field['meta-name']) . '" class="custom_field_select2" ' . $extra_attr . ' data-wppb-select2-arguments=\'' . json_encode($arguments) . '\'>';
53
+
54
+ $extra_select_option = apply_filters('wppb_extra_select_option', '', $field, $item_title);
55
+ if (!empty($extra_select_option)) {
56
+ $output .= $extra_select_option;
57
+ }
58
+
59
+ foreach ($select2_values as $key => $value) {
60
+ $output .= '<option value="' . esc_attr(trim($value)) . '" class="custom_field_select2_option ' . apply_filters('wppb_fields_extra_css_class', '', $field) . '" ';
61
+
62
+ if ($input_value === trim($value))
63
+ $output .= ' selected';
64
+
65
+ $output .= '>' . ((!isset($select2_labels[$key]) || !$select2_labels[$key]) ? esc_attr(trim($select2_values[$key])) : esc_attr(trim($select2_labels[$key]))) . '</option>';
66
+ }
67
+
68
+ $output .= '
69
+ </select>';
70
+ if (!empty($item_description))
71
+ $output .= '<span class="wppb-description-delimiter">' . $item_description . '</span>';
72
+
73
+ } else {
74
+ $item_title = (($field['required'] == 'Yes') ? $item_title . ' <span class="description">(' . __('required', 'profile-builder') . ')</span>' : $item_title);
75
+ $output = '
76
+ <table class="form-table wppb-select2">
77
+ <tr>
78
+ <th><label for="' . esc_attr($field['meta-name']) . '">' . $item_title . '</label></th>
79
+ <td>
80
+ <select name="' . esc_attr($field['meta-name']) . '" class="custom_field_select2" id="' . esc_attr($field['meta-name']) . '" ' . $extra_attr . ' data-wppb-select2-arguments=\'' . json_encode($arguments) . '\'>';
81
+
82
+ foreach ($select2_values as $key => $value) {
83
+ $output .= '<option value="' . esc_attr(trim($value)) . '" class="custom_field_select2_option" ';
84
+
85
+ if ($input_value === trim($value))
86
+ $output .= ' selected';
87
+
88
+ $output .= '>' . ((!isset($select2_labels[$key]) || !$select2_labels[$key]) ? esc_attr(trim($select2_values[$key])) : esc_attr(trim($select2_labels[$key]))) . '</option>';
89
+ }
90
+
91
+ $output .= '</select>
92
+ <span class="description">' . $item_description . '</span>
93
+ </td>
94
+ </tr>
95
+ </table>';
96
+ }
97
+
98
+ return apply_filters('wppb_' . $form_location . '_select2_custom_field_' . $field['id'], $output, $form_location, $field, $user_id, $field_check_errors, $request_data, $input_value);
99
+ }
100
+ }
101
+
102
+ add_filter('wppb_output_form_field_select2', 'wppb_select2_display_handler', 10, 6);
103
+ add_filter('wppb_admin_output_form_field_select2', 'wppb_select2_display_handler', 10, 6);
104
+
105
+ /*
106
+ * Handle field save.
107
+ *
108
+ * @since 1.0.0
109
+ *
110
+ * @param array $field
111
+ * @param integer $user_id
112
+ * @param array $request_data
113
+ * @param string $form_location
114
+ */
115
+ function wppb_select2_save_value($field, $user_id, $request_data, $form_location)
116
+ {
117
+ if ($field['field'] == 'Select2') {
118
+ if (isset($request_data[wppb_handle_meta_name($field['meta-name'])]))
119
+ update_user_meta($user_id, sanitize_text_field($field['meta-name']), sanitize_text_field($request_data[wppb_handle_meta_name($field['meta-name'])]));
120
+ }
121
+ }
122
+
123
+ add_action('wppb_save_form_field', 'wppb_select2_save_value', 10, 4);
124
+ add_action('wppb_backend_save_form_field', 'wppb_select2_save_value', 10, 4);
125
+
126
+ /*
127
+ * Handle field validation.
128
+ *
129
+ * @since 1.0.0
130
+ *
131
+ * @param string $message
132
+ * @param array $field
133
+ * @param array $request_data
134
+ * @param $form_location
135
+ * @return string Message to display on field validation
136
+ */
137
+ function wppb_select2_check_value($message, $field, $request_data, $form_location)
138
+ {
139
+ if ($field['field'] == 'Select2') {
140
+ if ($field['required'] == 'Yes') {
141
+ if ((isset($request_data[wppb_handle_meta_name($field['meta-name'])]) && (trim($request_data[wppb_handle_meta_name($field['meta-name'])]) == '')) || !isset($request_data[wppb_handle_meta_name($field['meta-name'])])) {
142
+ return wppb_required_field_error($field["field-title"]);
143
+ }
144
+ }
145
+ }
146
+
147
+ return $message;
148
+ }
149
+
150
+ add_filter('wppb_check_form_field_select2', 'wppb_select2_check_value', 10, 4);
151
+
152
+
front-end/default-fields/textarea/textarea.php ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /* handle field output */
3
+ function wppb_textarea_handler( $output, $form_location, $field, $user_id, $field_check_errors, $request_data ){
4
+ if ( $field['field'] == 'Textarea' ){
5
+ $item_title = apply_filters( 'wppb_'.$form_location.'_textarea_custom_field_'.$field['id'].'_item_title', wppb_icl_t( 'plugin profile-builder-pro', 'custom_field_'.$field['id'].'_title_translation', $field['field-title'], true ) );
6
+ $item_description = wppb_icl_t( 'plugin profile-builder-pro', 'custom_field_'.$field['id'].'_description_translation', $field['description'], true );
7
+
8
+ $extra_attr = apply_filters( 'wppb_extra_attribute', '', $field, $form_location );
9
+
10
+ if( $form_location != 'register' )
11
+ $input_value = ( ( wppb_user_meta_exists ( $user_id, $field['meta-name'] ) != null ) ? get_user_meta( $user_id, $field['meta-name'], true ) : $field['default-content'] );
12
+ else
13
+ $input_value = ( isset( $field['default-content'] ) ? trim( $field['default-content'] ) : '' );
14
+
15
+ $input_value = ( isset( $request_data[wppb_handle_meta_name( $field['meta-name'] )] ) ? trim( $request_data[wppb_handle_meta_name( $field['meta-name'] )] ) : $input_value );
16
+
17
+ if ( $form_location != 'back_end' ){
18
+ $error_mark = ( ( $field['required'] == 'Yes' ) ? '<span class="wppb-required" title="'.wppb_required_field_error($field["field-title"]).'">*</span>' : '' );
19
+
20
+ if ( array_key_exists( $field['id'], $field_check_errors ) )
21
+ $error_mark = '<img src="'.WPPB_PLUGIN_URL.'assets/images/pencil_delete.png" title="'.wppb_required_field_error($field["field-title"]).'"/>';
22
+
23
+ $output = '
24
+ <label for="'.$field['meta-name'].'">'.$item_title.$error_mark.'</label>
25
+ <textarea rows="'.$field['row-count'].'" name="'.$field['meta-name'].'" maxlength="'. apply_filters( 'wppb_maximum_character_length', '', $field ) .'" class="custom_field_textarea '. apply_filters( 'wppb_fields_extra_css_class', '', $field ) .'" id="'.$field['meta-name'].'" wrap="virtual" '. $extra_attr .'>'. esc_textarea( wp_unslash( $input_value ) ) .'</textarea>';
26
+ if( !empty( $item_description ) )
27
+ $output .= '<span class="wppb-description-delimiter">'.$item_description.'</span>';
28
+
29
+ }else{
30
+ $item_title = ( ( $field['required'] == 'Yes' ) ? $item_title .' <span class="description">('. __( 'required', 'profile-builder' ) .')</span>' : $item_title );
31
+ $output = '
32
+ <table class="form-table">
33
+ <tr>
34
+ <th><label for="'.$field['meta-name'].'">'.$item_title.'</label></th>
35
+ <td>
36
+ <textarea rows="'.$field['row-count'].'" name="'.$field['meta-name'].'" maxlength="'. apply_filters( 'wppb_maximum_character_length', '', $field ) .'" class="custom_field_textarea" id="'.$field['meta-name'].'" wrap="virtual" '. $extra_attr .'>'. esc_textarea( $input_value ) .'</textarea>
37
+ <span class="description">'.$item_description.'</span>
38
+ </td>
39
+ </tr>
40
+ </table>';
41
+ }
42
+
43
+ $filter_types = array( $field['meta-name'], $field['id'] );
44
+ foreach( $filter_types as $filter_type ){
45
+ $output = apply_filters( 'wppb_'.$form_location.'_textarea_custom_field_'.$filter_type, $output, $form_location, $field, $user_id, $field_check_errors, $request_data, $input_value );
46
+ }
47
+
48
+ return $output;
49
+ }
50
+ }
51
+ add_filter( 'wppb_output_form_field_textarea', 'wppb_textarea_handler', 10, 6 );
52
+ add_filter( 'wppb_admin_output_form_field_textarea', 'wppb_textarea_handler', 10, 6 );
53
+
54
+
55
+ /* handle field save */
56
+ function wppb_save_textarea_value( $field, $user_id, $request_data, $form_location ){
57
+ if( $field['field'] == 'Textarea' ){
58
+ if ( isset( $request_data[wppb_handle_meta_name( $field['meta-name'] )] ) )
59
+ update_user_meta( $user_id, $field['meta-name'], $request_data[wppb_handle_meta_name( $field['meta-name'] )] );
60
+ }
61
+ }
62
+ add_action( 'wppb_save_form_field', 'wppb_save_textarea_value', 10, 4 );
63
+ add_action( 'wppb_backend_save_form_field', 'wppb_save_textarea_value', 10, 4 );
64
+
65
+
66
+ /* handle field validation */
67
+ function wppb_check_textarea_value( $message, $field, $request_data, $form_location ){
68
+ if( $field['field'] == 'Textarea' ){
69
+ if( $field['required'] == 'Yes' ){
70
+ if ( ( isset( $request_data[wppb_handle_meta_name( $field['meta-name'] )] ) && ( trim( $request_data[wppb_handle_meta_name( $field['meta-name'] )] ) == '' ) ) || !isset( $request_data[wppb_handle_meta_name( $field['meta-name'] )] ) ){
71
+ return wppb_required_field_error($field["field-title"]);
72
+ }
73
+ }
74
+ }
75
+
76
+ return $message;
77
+ }
78
+ add_filter( 'wppb_check_form_field_textarea', 'wppb_check_textarea_value', 10, 4 );
front-end/default-fields/upload/upload.css ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*--------------------------------------------------------------
2
+ Upload Field
3
+ --------------------------------------------------------------*/
4
+ .wppb-remove-upload{
5
+ color:#BC0B0B;
6
+ cursor:pointer;
7
+ }
8
+
9
+ .wppb-remove-upload:hover{
10
+ color:#FF0000;
11
+ }
12
+
13
+ .wppb-remove-upload:focus{
14
+ border:1px dotted blue;
15
+ }
16
+
17
+ /* upload field */
18
+ .upload-field-details{
19
+ display:inline-block;
20
+ vertical-align:middle;
21
+ min-height:24px;
22
+ padding:2px 0;
23
+ }
24
+
25
+ .upload-field-details p{
26
+ margin:2px 0 0;
27
+ }
28
+
29
+ .upload-field-details img{
30
+ margin:0 5px 0 0;
31
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
32
+ }
33
+
34
+ .upload-field-details > *{
35
+ float:left;
36
+ }
37
+
38
+ .upload-field-details span{
39
+ display:block;
40
+ }
41
+
42
+ .upload-field-details .file-name{
43
+ font-weight:bold;
44
+ color:#21759B;
45
+ }
46
+
47
+ .upload-field-details .file-type {
48
+ font-size: 11px;
49
+ }
50
+
51
+ .wppb_upload_button {
52
+ background: none repeat scroll 0 0 #f7f7f7;
53
+ border: 1px solid #ccc !important;
54
+ color: #555;
55
+ cursor: pointer;
56
+ display: inline-block;
57
+ font-size: 12px;
58
+ line-height: 26px;
59
+ padding: 0 15px;
60
+ text-decoration: none;
61
+ white-space: nowrap;
62
+ }
63
+
64
+ .wppb_upload_button:hover {
65
+ background: none repeat scroll 0 0 #fafafa;
66
+ border: 1px solid #999 !important;
67
+ color: #222;
68
+ text-decoration: none;
69
+ }
front-end/default-fields/upload/upload.js ADDED
@@ -0,0 +1,242 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ function validate_simple_upload(){
2
+ jQuery("input:file").on('change', function(e){
3
+ //handle simple upload
4
+ var uploadInputName = jQuery(this).attr('name');
5
+ var uploadButton = jQuery(this);
6
+ var oFReader = new FileReader();
7
+ var attachment = e.target.files[0];
8
+ oFReader.readAsDataURL(attachment);
9
+ oFReader.onload = function (oFREvent) {
10
+ var src = oFREvent.target.result;
11
+ var error = '';
12
+ jQuery("#p_" + uploadInputName).text(error);
13
+ uploadInputId = uploadInputName.split('-').join('_');
14
+ //Check allowed upload type by wordpress
15
+ allowed_mime_types = wppb_allowed_wordpress_formats.allowed_wordpress_formats;
16
+ allowed_by_wordpress = false;
17
+ for (var key in allowed_mime_types){
18
+ allowed_type = allowed_mime_types[key];
19
+ if (attachment.type === allowed_type){
20
+ allowed_by_wordpress = true;
21
+ possible_extensions = key.split('|');
22
+ }
23
+ }
24
+ if (allowed_by_wordpress) {
25
+ //Check allowed extensions
26
+ allowed_extensions = jQuery("#allowed_extensions_" + uploadInputId).val();
27
+ if (allowed_extensions !== '') {
28
+ var allowed = false;
29
+ allowed_extensions = allowed_extensions.split(',');
30
+ for (var i = 0; i < allowed_extensions.length; i++) {
31
+ if (possible_extensions.includes(allowed_extensions[i])) {
32
+ allowed = true;
33
+ }
34
+ }
35
+ if (allowed == false) {
36
+ error = wppb_error_messages.upload_type_error_message;
37
+ }
38
+ }
39
+ //Check size limit
40
+ allowed_size_limit = wppb_limit.size_limit;
41
+ if (attachment.size > allowed_size_limit) {
42
+ var limit = allowed_size_limit / (1024 * 1024) + 1;
43
+ error = wppb_error_messages.limit_error_message + limit + 'MB.';
44
+ }
45
+ }
46
+ else{
47
+ error = wppb_error_messages.upload_type_error_message;
48
+ }
49
+ if (error !== '') {
50
+ jQuery("#p_" + uploadInputName).text(error);
51
+ uploadButton.val('');
52
+ } else {
53
+ var fieldName = uploadInputName.replace(/^(simple_upload_)/,'');
54
+ var formData = new FormData();
55
+ if (uploadButton.closest('.wppb-upload').length > 0) {
56
+ formData.append('action', 'wppb_woo_simple_upload');
57
+ formData.append(fieldName, jQuery(e.target).prop('files')[0]);
58
+ } else {
59
+ formData.append('action', 'wppb_woo_simple_avatar');
60
+ formData.append(fieldName, jQuery(e.target).prop('files')[0]);
61
+ }
62
+ formData.append('nonce', wppb_upload_script_vars.nonce);
63
+ formData.append('name', fieldName);
64
+
65
+ var alreadyDisabled = false;
66
+ if ( jQuery( 'p.form-submit .submit.button' ).prop( 'disabled' ) ) {
67
+ alreadyDisabled = true;
68
+ } else {
69
+ jQuery( "p.form-submit .submit.button" ).prop( 'disabled', true );
70
+ }
71
+
72
+ jQuery.ajax({
73
+ url: wppb_upload_script_vars.ajaxUrl,
74
+ type: 'POST',
75
+ contentType: false,
76
+ processData: false,
77
+ data: formData,
78
+ success: function(response){
79
+ jQuery("input#"+fieldName).val(JSON.parse(response));
80
+ if ( !alreadyDisabled ) {
81
+ jQuery("p.form-submit .submit.button").prop('disabled', false);
82
+ }
83
+ },
84
+ });
85
+ }
86
+ };
87
+ });
88
+ }
89
+ jQuery(document).ready(function(){
90
+ validate_simple_upload();
91
+
92
+ jQuery(document).on( 'click', '.wppb-rpf-add', function( event ){
93
+ validate_simple_upload();
94
+ });
95
+
96
+ if( typeof wp.media != "undefined" ){
97
+ // Uploading files
98
+ var wp_media_post_id = wp.media.model.settings.post.id; // Store the old id
99
+
100
+ jQuery(document).on( 'click', '.wppb_upload_button', function( event ){
101
+ event.preventDefault();
102
+
103
+ var set_to_post_id = ''; // Set this
104
+
105
+ var file_frame;
106
+ var uploadInputId = jQuery( this ).data( 'upload_input' );
107
+ var uploadButton = jQuery( this );
108
+
109
+ /* set default tab to upload file */
110
+ wp.media.controller.Library.prototype.defaults.contentUserSetting = false;
111
+ wp.media.controller.Library.prototype.defaults.router = false;
112
+ wp.media.controller.Library.prototype.defaults.searchable = true;
113
+ wp.media.controller.Library.prototype.defaults.sortable = false;
114
+ wp.media.controller.Library.prototype.defaults.date = false;
115
+
116
+ // If the media frame already exists, reopen it.
117
+ if ( file_frame ) {
118
+ // Set the post ID to what we want
119
+ file_frame.uploader.uploader.param( 'post_id', set_to_post_id );
120
+ // Open frame
121
+ file_frame.open();
122
+ return;
123
+ } else {
124
+ // Set the wp.media post id so the uploader grabs the ID we want when initialised
125
+ wp.media.model.settings.post.id = set_to_post_id;
126
+ }
127
+
128
+ // Create the media frame.
129
+ file_frame = wp.media.frames.file_frame = wp.media({
130
+ title: jQuery( this ).data( 'uploader_title' ),
131
+ button: {
132
+ text: jQuery( this ).data( 'uploader_button_text' )
133
+ },
134
+ multiple: false // Set to true to allow multiple files to be selected
135
+ });
136
+
137
+ /* send the meta_name of the field */
138
+ file_frame.uploader.options.uploader['params']['wppb_upload'] = 'true';
139
+ file_frame.uploader.options.uploader['params']['meta_name'] = jQuery( this ).data( 'upload_mn' );
140
+
141
+ // When an image is selected, run a callback.
142
+ file_frame.on( 'select', function() {
143
+ // We set multiple to false so only get one image from the uploader
144
+ attachments = file_frame.state().get('selection').toJSON();
145
+ var attids = [];
146
+
147
+ for( var i=0;i < attachments.length; i++ ){
148
+ // Do something with attachment.id and/or attachment.url here
149
+ attids.push( attachments[i].id );
150
+ result = '<div class="upload-field-details" id="'+ uploadInputId +'_info_container" data-attachment_id="'+ attachments[i].id +'">';
151
+ if( attachments[i].sizes != undefined ){
152
+ if( attachments[i].sizes.thumbnail != undefined )
153
+ thumb = attachments[i].sizes.thumbnail;
154
+ else
155
+ thumb = attachments[i].sizes.full;
156
+ thumbnailUrl = thumb.url;
157
+ }
158
+ else{
159
+ thumbnailUrl = attachments[i].icon;
160
+ }
161
+
162
+ result += '<div class="file-thumb">';
163
+ result += '<a href="'+ attachments[i].url +'" target="_blank" class="wppb-attachment-link">';
164
+ result += '<img width="80" height="80" src="'+ thumbnailUrl +'"/>';
165
+ result += '</a>';
166
+ result += '</div>';
167
+ result += '<p><span class="file-name">'+attachments[i].filename+'</span><span class="file-type">'+attachments[i].mime +'</span><span class="wppb-remove-upload" tabindex="0">'+wppb_upload_script_vars.remove_link_text+'</span></p></div>';
168
+
169
+ // if multiple upload false remove previous upload details
170
+ if( uploadButton.data( 'multiple_upload' ) == false ){
171
+ jQuery( '.upload-field-details', uploadButton.parent() ).remove();
172
+ }
173
+
174
+ uploadButton.before( result );
175
+ uploadButton.hide();
176
+
177
+ }
178
+ // turn into comma separated string
179
+ attids = attids.join(',');
180
+ jQuery( 'input[id="'+uploadInputId+'"]', uploadButton.parent() ).val( attids );
181
+
182
+ // Restore the main post ID
183
+ wp.media.model.settings.post.id = wp_media_post_id;
184
+
185
+ jQuery(document).trigger( 'wppb_file_uploaded' )
186
+
187
+ });
188
+
189
+ // Finally, open the modal
190
+ file_frame.open();
191
+ // remove tabs from the top ( this is done higher in the code when setting router to false )
192
+ //jQuery('.media-frame-router').remove();
193
+
194
+ jQuery('.media-frame-title').append('<style type="text/css">label.setting, .edit-attachment{display:none !important;}</style>');
195
+ });
196
+
197
+ // Restore the main ID when the add media button is pressed
198
+ jQuery('a.add_media').on('click', function() {
199
+ wp.media.model.settings.post.id = wp_media_post_id;
200
+ });
201
+
202
+ jQuery(document).on('keypress', '.wppb-remove-upload', function(e){
203
+ if(e.which == 13) {
204
+ jQuery(this).trigger('click');
205
+ }
206
+ });
207
+
208
+ jQuery(document).on('click', '.wppb-remove-upload', function(e){
209
+ if( confirm( 'Are you sure ?' ) ){
210
+ /* update hidden input */
211
+ simple_upload_button = jQuery(this).closest('li, td, p.form-row, div.form-row').find('input[type="file"]');
212
+ if (simple_upload_button.attr('name')) {
213
+ upload_input_id = simple_upload_button.attr('name').split('_').slice(2).join('_');
214
+ upload_input_id = upload_input_id.split('-').join('_');
215
+ upload_input = jQuery("#" + upload_input_id);
216
+ }
217
+ else{
218
+ upload_input = jQuery(this).closest('li, td, p.form-row, div.form-row').find('input[type="hidden"]');
219
+ }
220
+ removedAttachement = jQuery(this).parent().parent('.upload-field-details').data('attachment_id');
221
+ uploadAttachemnts = upload_input.val();
222
+ uploadAttachemntsArray = uploadAttachemnts.split(',');
223
+ newuploadAttachments = [];
224
+ for (var i = 0; i < uploadAttachemntsArray.length; i++) {
225
+ if (uploadAttachemntsArray[i] != removedAttachement)
226
+ newuploadAttachments.push(uploadAttachemntsArray[i]);
227
+ }
228
+ newuploadAttachments = newuploadAttachments.join(',');
229
+ upload_input.val(newuploadAttachments);
230
+
231
+ /* remove the attachment details */
232
+ jQuery(this).parent().parent('.upload-field-details').next('a').show();
233
+ simple_upload_button.show();
234
+ simple_upload_button.val('');
235
+ jQuery(this).parent().parent('.upload-field-details').remove();
236
+
237
+ jQuery(document).trigger( 'wppb_removed_uploaded_file' )
238
+
239
+ }
240
+ });
241
+ }
242
+ });
front-end/default-fields/upload/upload_helper_functions.php ADDED
@@ -0,0 +1,380 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /* Set up upload field for frontend */
3
+ /* overwrite the two functions for when an upload is made from the frontend so they don't check for a logged in user */
4
+ if( strpos( wp_get_referer(), 'wp-admin' ) === false && isset( $_REQUEST['action'] ) && 'upload-attachment' == $_REQUEST['action'] ){
5
+ if( !function_exists( 'check_ajax_referer' ) ){
6
+ function check_ajax_referer( ) {
7
+ return true;
8
+ }
9
+ }
10
+
11
+ if( !function_exists( 'auth_redirect' ) ){
12
+ function auth_redirect() {
13
+ return true;
14
+ }
15
+ }
16
+ }
17
+
18
+ /* create a fake user with the "upload_posts" capability and assign him to the global $current_user. this is used to bypass the checks for current_user_can('upload_files') in async-upload.php */
19
+ add_action( 'current_screen', 'wppb_create_fake_user_when_uploading_and_not_logged_in' );
20
+ if( !function_exists( 'wppb_create_fake_user_when_uploading_and_not_logged_in' ) ) {
21
+ function wppb_create_fake_user_when_uploading_and_not_logged_in()
22
+ {
23
+ if ( isset($_REQUEST['action']) && 'upload-attachment' == $_REQUEST['action'] && isset($_REQUEST['wppb_upload']) && 'true' == $_REQUEST['wppb_upload'] ) {
24
+ if (!is_user_logged_in() || !current_user_can('upload_files') || !current_user_can('edit_posts')) {
25
+ global $current_user;
26
+ $current_user = new WP_User(0, 'frontend_uploader');
27
+ $current_user->allcaps = array("upload_files" => true, "edit_posts" => true, "edit_others_posts" => true, "edit_pages" => true, "edit_others_pages" => true);
28
+ }
29
+ }
30
+ }
31
+ }
32
+
33
+ /* for a request of a upload from the frontend and no user is logged in don't query for attachments */
34
+ add_action( 'after_setup_theme', 'wppb_modify_query_attachements_when_not_logged_in' );
35
+ if( !function_exists( 'wppb_modify_query_attachements_when_not_logged_in' ) ) {
36
+ function wppb_modify_query_attachements_when_not_logged_in()
37
+ {
38
+ if (strpos(wp_get_referer(), 'wp-admin') === false && !is_user_logged_in()) {
39
+ add_action('wp_ajax_query-attachments', 'wppb_wp_ajax_not_loggedin_query_attachments', 0);
40
+ add_action('wp_ajax_nopriv_query-attachments', 'wppb_wp_ajax_not_loggedin_query_attachments', 0);
41
+ function wppb_wp_ajax_not_loggedin_query_attachments()
42
+ {
43
+ wp_send_json_success();
44
+ }
45
+ }
46
+ }
47
+ }
48
+
49
+ /* restrict file types of the upload field functionality */
50
+ add_filter('wp_handle_upload_prefilter', 'wppb_upload_file_type');
51
+ if( !function_exists( 'wppb_upload_file_type' ) ) {
52
+ function wppb_upload_file_type($file)
53
+ {
54
+ if( isset( $_POST['wppb_upload'] ) && $_POST['wppb_upload'] == 'true' ) {
55
+
56
+ // file size limits.
57
+ $size = $file['size'];
58
+ $limit = apply_filters('wppb_server_max_upload_size_byte_constant', wppb_return_bytes(ini_get('upload_max_filesize')));
59
+ if ( $size > $limit ) {
60
+ $limit = $limit / ( 1024 * 1024 ) ;
61
+ $file['error'] = __("Files must be smaller than ", "profile-builder") . $limit . 'MB';
62
+ }
63
+
64
+ if (isset($_POST['meta_name']) && !empty($_POST['meta_name'])) {
65
+ $meta_name = sanitize_text_field( $_POST['meta_name'] );
66
+ /*let's get the field details so we can see if we have any file restrictions */
67
+ $all_fields = apply_filters( 'wppb_form_fields', get_option('wppb_manage_fields'), array( 'context' => 'upload_helper', 'upload_meta_name' => $meta_name ) );
68
+ if (!empty($all_fields)) {
69
+ foreach ($all_fields as $field) {
70
+ if ($field['meta-name'] == $meta_name) {
71
+ $allowed_upload_extensions = '';
72
+ if ($field['field'] == 'Upload' && !empty($field['allowed-upload-extensions']))
73
+ $allowed_upload_extensions = $field['allowed-upload-extensions'];
74
+ if ($field['field'] == 'Avatar' && !empty($field['allowed-image-extensions'])) {
75
+ if (trim($field['allowed-image-extensions']) == '.*')
76
+ $allowed_upload_extensions = '.jpg,.jpeg,.gif,.png,.ico';
77
+ else
78
+ $allowed_upload_extensions = $field['allowed-image-extensions'];
79
+ }
80
+
81
+ $ext = strtolower( substr(strrchr($file['name'], '.'), 1) );
82
+
83
+ if (!empty($allowed_upload_extensions) && $allowed_upload_extensions != '.*') {
84
+ $allowed = str_replace('.', '', array_map('trim', explode(",", strtolower( $allowed_upload_extensions))));
85
+ //first check if the user uploaded the right type
86
+ if (!in_array($ext, (array)$allowed)) {
87
+ $file['error'] = __("Sorry, you cannot upload this file type for this field.", 'profile-builder');
88
+ return $file;
89
+ }
90
+ }
91
+ //check if the type is allowed at all by WordPress
92
+ foreach (get_allowed_mime_types() as $key => $value) {
93
+ if (strpos($key, $ext) !== false || $key == $ext)
94
+ return $file;
95
+ }
96
+ $file['error'] = __("Sorry, you cannot upload this file type for this field.", 'profile-builder');
97
+ }
98
+ }
99
+ }
100
+ }
101
+
102
+ if (empty($_POST['meta_name']))
103
+ $file['error'] = __("An error occurred, please try again later.", 'profile-builder');
104
+ }
105
+
106
+ return $file;
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Function that performs validation for the simple upload field
112
+ *
113
+ * @param $field - simple upload field
114
+ * @param $upload - data to be uploaded
115
+ *
116
+ * @return bool
117
+ */
118
+ function wppb_valid_simple_upload( $field, $upload ){
119
+ $limit = apply_filters( 'wppb_server_max_upload_size_byte_constant', wppb_return_bytes( ini_get( 'upload_max_filesize' ) ) );
120
+ $allowed_mime_types = get_allowed_mime_types();
121
+ $all_fields = apply_filters( 'wppb_form_fields', get_option( 'wppb_manage_fields' ), array( 'context' => 'upload_helper', 'upload_meta_name' => $field[ 'meta-name' ] ) );
122
+ if ( !empty( $all_fields ) ) {
123
+ foreach ( $all_fields as $form_field ) {
124
+ if ($form_field[ 'meta-name' ] == $field[ 'meta-name' ] ) {
125
+ $allowed_upload_extensions = '';
126
+ if ( $form_field[ 'field' ] == 'Upload' && !empty( $form_field[ 'allowed-upload-extensions' ] ) ) {
127
+ $allowed_upload_extensions = $form_field[ 'allowed-upload-extensions' ];
128
+ }
129
+ if ( $form_field[ 'field' ] == 'Avatar' ) {
130
+ if ( trim( $field[ 'allowed-image-extensions' ] ) == '.*' || trim( $field[ 'allowed-image-extensions' ] ) == '' ) {
131
+ $allowed_upload_extensions = '.jpg,.jpeg,.gif,.png';
132
+ }
133
+ else {
134
+ $allowed_upload_extensions = $form_field[ 'allowed-image-extensions' ];
135
+ }
136
+ }
137
+ if ( !empty( $allowed_upload_extensions ) && $allowed_upload_extensions != '.*' ) {
138
+ $allowed_upload_extensions = str_replace( '.', '', array_map( 'trim', explode( ",", strtolower( $allowed_upload_extensions ) ) ) );
139
+ } else {
140
+ $allowed = true;
141
+ }
142
+ $allowed_by_wordpress = false;
143
+ foreach ( $allowed_mime_types as $key => $val ){
144
+ if ( $val == $upload[ 'type' ] ){
145
+ $possible_extensions = explode( '|', $key );
146
+ $allowed_by_wordpress = true;
147
+ }
148
+ }
149
+ if ( isset( $possible_extensions ) && $allowed_by_wordpress == true ){
150
+ if ( !isset( $allowed ) ){
151
+ $allowed = false;
152
+ foreach ( $allowed_upload_extensions as $extension ){
153
+ if ( in_array( $extension, $possible_extensions ) ){
154
+ $allowed = true;
155
+ }
156
+ }
157
+ }
158
+ if ( $upload[ 'size' ] > $limit ){
159
+ $allowed = false;
160
+ }
161
+ return $allowed;
162
+ }
163
+ else{
164
+ return false;
165
+ }
166
+ }
167
+ }
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Function that registers intermediate avatar sizes
173
+ *
174
+ * @param $field - avatar field
175
+ *
176
+ */
177
+ function wppb_add_avatar_sizes( $field ){
178
+ if( !empty( $field['avatar-size'] ) )
179
+ add_image_size( 'wppb-avatar-size-'.$field['avatar-size'], $field['avatar-size'], $field['avatar-size'], true );
180
+ else
181
+ add_image_size( 'wppb-avatar-size-100', 100, 100, true );
182
+
183
+ add_image_size( 'wppb-avatar-size-64', 64, 64, true );
184
+ add_image_size( 'wppb-avatar-size-26', 26, 26, true );
185
+ }
186
+
187
+ //Function that registers avatar sizes for userlisting
188
+ function wppb_userlisting_avatar(){
189
+ $userlisting_posts = get_posts( array( 'posts_per_page' => -1, 'post_status' =>'publish', 'post_type' => 'wppb-ul-cpt', 'orderby' => 'post_date', 'order' => 'ASC' ) );
190
+ if( !empty( $userlisting_posts ) ){
191
+ foreach ( $userlisting_posts as $post ){
192
+ $this_form_settings = get_post_meta( $post->ID, 'wppb_ul_page_settings', true );
193
+ $all_userlisting_avatar_size = apply_filters( 'all_userlisting_avatar_size', ( isset( $this_form_settings[0]['avatar-size-all-userlisting'] ) ? (int)$this_form_settings[0]['avatar-size-all-userlisting'] : 100 ) );
194
+ $single_userlisting_avatar_size = apply_filters( 'single_userlisting_avatar_size', ( isset( $this_form_settings[0]['avatar-size-single-userlisting'] ) ? (int)$this_form_settings[0]['avatar-size-single-userlisting'] : 100 ) );
195
+
196
+ add_image_size( 'wppb-avatar-size-'.$all_userlisting_avatar_size, $all_userlisting_avatar_size, $all_userlisting_avatar_size, true );
197
+ add_image_size( 'wppb-avatar-size-'.$single_userlisting_avatar_size, $single_userlisting_avatar_size, $single_userlisting_avatar_size, true );
198
+ }
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Function that checks if the simple upload field belongs to a repeater field with conditional logic enabled
204
+ *
205
+ * @param $field - simple upload field
206
+ *
207
+ * @return bool
208
+ */
209
+ function wppb_belongs_to_repeater_with_conditional_logic( $field ){
210
+ $all_fields = apply_filters( 'wppb_form_fields', get_option( 'wppb_manage_fields' ), array( 'context' => 'upload_helper', 'upload_meta_name' => $field[ 'meta-name' ] ) );
211
+ if ( !empty( $all_fields ) ) {
212
+ foreach ( $all_fields as $form_field ) {
213
+ if ( $form_field[ 'field' ] == 'Repeater' && isset( $form_field[ 'conditional-logic-enabled' ] ) && $form_field[ 'conditional-logic-enabled' ] == 'yes' ) {
214
+ $repeater_group = get_option( $form_field[ 'meta-name' ], 'not_set' );
215
+ if ( $repeater_group == 'not_set' ) {
216
+ continue;
217
+ }
218
+ else{
219
+ $repeater_count = count( $repeater_group );
220
+ for ( $i = 0; $i < $repeater_count; $i++ ){
221
+ if ( $repeater_group[ $i ][ 'field' ] == 'Upload' && isset( $repeater_group[ $i ][ 'simple-upload' ] ) && $repeater_group[ $i ][ 'simple-upload' ] == 'yes' && isset( $_REQUEST[ $form_field[ 'meta-name' ] . '_extra_groups_count' ] ) ){
222
+ $groups = absint( $_REQUEST[ $form_field[ 'meta-name' ] . '_extra_groups_count' ] );
223
+ for ( $j = 0; $j <= $groups; $j++ ){
224
+ $name = $repeater_group[ $i ][ 'meta-name' ];
225
+ if ( $j != 0 ){
226
+ $name .= '_' . $j;
227
+ }
228
+ if ( $field[ 'meta-name' ] == $name ){
229
+ return true;
230
+ }
231
+ }
232
+ }
233
+ }
234
+ }
235
+ }
236
+ }
237
+ }
238
+ return false;
239
+ }
240
+
241
+ function wppb_make_upload_button( $field, $input_value, $extra_attr = '' ){
242
+ // change the upload limit. This is not functional.
243
+ // just for display in the upload window. see upload_helper_functions.php for the actual restriction.
244
+ add_filter('upload_size_limit', function($limit, $u, $p){
245
+ return apply_filters('wppb_server_max_upload_size_byte_constant', wppb_return_bytes(ini_get('upload_max_filesize')));
246
+ }, 10, 3);
247
+
248
+ $upload_button = '';
249
+ $upload_input_id = str_replace( '-', '_', Wordpress_Creation_Kit_PB::wck_generate_slug( $field['meta-name'] ) );
250
+
251
+ /* container for the image preview (or file ico) and name and file type */
252
+ if( !empty( $input_value ) ){
253
+ /* it can hold multiple attachments separated by comma */
254
+ $values = explode( ',', $input_value );
255
+ foreach( $values as $value ) {
256
+ if( !empty( $value ) && is_numeric( $value ) ){
257
+ $thumbnail = wp_get_attachment_image($value, array(80, 80), true);
258
+ $file_name = get_the_title($value);
259
+ $file_type = get_post_mime_type($value);
260
+ $attachment_url = wp_get_attachment_url($value);
261
+ $upload_button .= '<div id="' . esc_attr($upload_input_id) . '_info_container" class="upload-field-details" data-attachment_id="' . $value . '">';
262
+ $upload_button .= '<div class="file-thumb">';
263
+ $upload_button .= "<a href='{$attachment_url}' target='_blank' class='wppb-attachment-link'>" . $thumbnail . "</a>";
264
+ $upload_button .= '</div>';
265
+ $upload_button .= '<p><span class="file-name">';
266
+ $upload_button .= $file_name;
267
+ $upload_button .= '</span><span class="file-type">';
268
+ $upload_button .= $file_type;
269
+ $upload_button .= '</span>';
270
+ $upload_button .= '<span class="wppb-remove-upload" tabindex="0">' . apply_filters( 'wppb_upload_button_remove_label', __( 'Remove', 'profile-builder' ) ) . '</span>';
271
+ $upload_button .= '</p></div>';
272
+ }
273
+ }
274
+ $hide_upload_button = ' style="display:none;"';
275
+ }
276
+ else{
277
+ $hide_upload_button = '';
278
+ }
279
+
280
+ if ( isset( $field[ 'simple-upload' ] ) && $field[ 'simple-upload' ] == 'yes' ){
281
+ //If selected accordingly in form fields, generate a simple upload button
282
+ $upload_button .= '<input type="file" id="upload_' . esc_attr(Wordpress_Creation_Kit_PB::wck_generate_slug($field['meta-name'], $field)) . '_button" name="simple_upload_'. esc_attr( Wordpress_Creation_Kit_PB::wck_generate_slug( $field['meta-name'], $field ) ) .'"';
283
+ $upload_button .= $hide_upload_button . '>';
284
+ $upload_button .= '<p id="p_simple_upload_'. esc_attr(Wordpress_Creation_Kit_PB::wck_generate_slug($field['meta-name'], $field)) .'"></p>';
285
+ $limit = apply_filters( 'wppb_server_max_upload_size_byte_constant', wppb_return_bytes( ini_get( 'upload_max_filesize' ) ) );
286
+ $all_fields = apply_filters( 'wppb_form_fields', get_option( 'wppb_manage_fields' ), array( 'context' => 'upload_helper', 'upload_meta_name' => $field[ 'meta-name' ] ) );
287
+ if ( !empty( $all_fields ) ) {
288
+ foreach ( $all_fields as $form_field ) {
289
+ if ($form_field[ 'meta-name' ] == $field[ 'meta-name' ] ) {
290
+ $allowed_upload_extensions = '';
291
+ if ( $form_field[ 'field' ] == 'Upload' && !empty( $form_field[ 'allowed-upload-extensions' ] ) ) {
292
+ $allowed_upload_extensions = $form_field[ 'allowed-upload-extensions' ];
293
+ }
294
+ if ( $form_field[ 'field' ] == 'Avatar' ) {
295
+ if ( trim( $field[ 'allowed-image-extensions' ] ) == '.*' || trim( $field[ 'allowed-image-extensions' ] ) == '' ) {
296
+ $allowed_upload_extensions = '.jpg,.jpeg,.gif,.png';
297
+ }
298
+ else {
299
+ $allowed_upload_extensions = $form_field[ 'allowed-image-extensions' ];
300
+ }
301
+ }
302
+ }
303
+ if ( !empty( $allowed_upload_extensions ) && $allowed_upload_extensions != '.*' ) {
304
+ $allowed_extensions = str_replace( '.', '', array_map( 'trim', explode( ",", strtolower( $allowed_upload_extensions ) ) ) );
305
+ $allowed_extensions = implode( ',', $allowed_extensions );
306
+ } else {
307
+ $allowed_extensions = '';
308
+ }
309
+ }
310
+ }
311
+ $upload_button .= '<input id="allowed_extensions_simple_upload_'. esc_attr( $upload_input_id ) .'" type="hidden" size="36" name="allowed_extensions_simple_upload_'. esc_attr( Wordpress_Creation_Kit_PB::wck_generate_slug( $field['meta-name'], $field ) ) .'" value="'. $allowed_extensions .'"/>';
312
+ $allowed_mime_types = get_allowed_mime_types();
313
+ $allowed_types = '';
314
+ if ( !empty( $allowed_mime_types ) ) {
315
+ foreach ($allowed_mime_types as $key => $val){
316
+ $allowed_types .= $key . '=>' . $val . ',';
317
+ }
318
+ }
319
+ $error_messages = array(
320
+ 'limit_error_message' => __( 'Files must be smaller than ', 'profile-builder' ),
321
+ 'upload_type_error_message' => __( 'Sorry, you cannot upload this file type for this field.', 'profile-builder' ),
322
+ );
323
+ $size_limit = array(
324
+ 'size_limit' => $limit
325
+ );
326
+ $allowed_wordpress_formats = array(
327
+ 'allowed_wordpress_formats' => $allowed_mime_types
328
+ );
329
+ wp_localize_script( 'wppb-upload-script', 'wppb_error_messages', $error_messages );
330
+ wp_localize_script( 'wppb-upload-script', 'wppb_limit', $size_limit );
331
+ wp_localize_script( 'wppb-upload-script', 'wppb_allowed_wordpress_formats', $allowed_wordpress_formats );
332
+ }
333
+ else{
334
+ //Otherwise, generate the WordPress upload button
335
+ $upload_button .= '<a href="#" class="button wppb_upload_button" id="upload_' . esc_attr(Wordpress_Creation_Kit_PB::wck_generate_slug($field['meta-name'], $field)) . '_button" '.$hide_upload_button.' data-uploader_title="' . $field["field-title"] . '" data-uploader_button_text="'. __( 'Select File', 'profile-builder' ) .'" data-upload_mn="'. $field['meta-name'] .'" data-upload_input="' . esc_attr($upload_input_id) . '"';
336
+
337
+ if (is_user_logged_in())
338
+ $upload_button .= ' data-uploader_logged_in="true"';
339
+ $upload_button .= ' data-multiple_upload="false"';
340
+
341
+ $upload_button .= '>' . apply_filters( 'wppb_upload_button_select_label', __( 'Upload ', 'profile-builder' ) ) . '</a>';
342
+ }
343
+
344
+ $upload_button .= '<input id="'. esc_attr( $upload_input_id ) .'" type="hidden" size="36" name="'. esc_attr( Wordpress_Creation_Kit_PB::wck_generate_slug( $field['meta-name'], $field ) ) .'" value="'. $input_value .'"/>';
345
+ return $upload_button;
346
+ }
347
+
348
+ /**
349
+ * Function to save an attachment from the simple upload field
350
+ * @param $field_name
351
+ * @return string|WP_Error
352
+ */
353
+ function wppb_save_simple_upload_file ( $field_name ){
354
+ require_once(ABSPATH . 'wp-admin/includes/file.php');
355
+ $upload_overrides = array('test_form' => false);
356
+
357
+ if( isset( $_FILES[$field_name] ) )
358
+ $file = wp_handle_upload($_FILES[$field_name], $upload_overrides);
359
+
360
+ if (isset($file['error'])) {
361
+ return new WP_Error('upload_error', $file['error']);
362
+ }
363
+ $filename = isset( $_FILES[$field_name]['name'] ) ? sanitize_text_field( $_FILES[$field_name]['name'] ) : '';
364
+ $wp_filetype = wp_check_filetype($filename, null);
365
+ $attachment = array(
366
+ 'post_mime_type' => $wp_filetype['type'],
367
+ 'post_title' => $filename,
368
+ 'post_content' => '',
369
+ 'post_status' => 'inherit'
370
+ );
371
+ $attachment_id = wp_insert_attachment($attachment, $file['file']);
372
+ if (!is_wp_error($attachment_id) && is_numeric($attachment_id)) {
373
+ require_once(ABSPATH . 'wp-admin/includes/image.php');
374
+ $attachment_data = wp_generate_attachment_metadata($attachment_id, $file['file']);
375
+ wp_update_attachment_metadata($attachment_id, $attachment_data);
376
+ return trim($attachment_id);
377
+ } else {
378
+ return '';
379
+ }
380
+ }
front-end/login.php CHANGED
@@ -270,8 +270,8 @@ function wppb_resend_confirmation_email() {
270
 
271
  include_once(plugin_dir_path(__FILE__) . '../features/email-confirmation/email-confirmation.php');
272
 
273
- if ( !class_exists('PB_Mustache_Generate_Template') && defined( 'WPPB_PAID_PLUGIN_DIR' ) && file_exists( WPPB_PAID_PLUGIN_DIR . '/assets/lib/class-mustache-templates/class-mustache-templates.php' ) )
274
- include_once( WPPB_PAID_PLUGIN_DIR . '/assets/lib/class-mustache-templates/class-mustache-templates.php' );
275
 
276
  global $wpdb;
277
  $sql_result = $wpdb->get_row( $wpdb->prepare("SELECT * FROM " . $wpdb->base_prefix . "signups WHERE user_email = %s", sanitize_email( $_GET['email'] )), ARRAY_A );
@@ -579,6 +579,10 @@ function wppb_front_end_login( $atts ){
579
  }
580
  // build our form
581
  $login_form .= '<div id="wppb-login-wrap" class="wppb-user-forms">';
 
 
 
 
582
  $form_args['lostpassword_url'] = $lostpassword_url;
583
  $login_form .= wppb_login_form( apply_filters( 'wppb_login_form_args', $form_args ) );
584
 
270
 
271
  include_once(plugin_dir_path(__FILE__) . '../features/email-confirmation/email-confirmation.php');
272
 
273
+ if ( file_exists( WPPB_PLUGIN_DIR . '/assets/lib/class-mustache-templates/class-mustache-templates.php' ) )
274
+ include_once( WPPB_PLUGIN_DIR . '/assets/lib/class-mustache-templates/class-mustache-templates.php' );
275
 
276
  global $wpdb;
277
  $sql_result = $wpdb->get_row( $wpdb->prepare("SELECT * FROM " . $wpdb->base_prefix . "signups WHERE user_email = %s", sanitize_email( $_GET['email'] )), ARRAY_A );
579
  }
580
  // build our form
581
  $login_form .= '<div id="wppb-login-wrap" class="wppb-user-forms">';
582
+
583
+ if ( empty( $lostpassword_url ) )
584
+ $lostpassword_url = ( !empty( $wppb_generalSettings['lost_password_page'] ) ) ? $wppb_generalSettings['lost_password_page'] : '';
585
+
586
  $form_args['lostpassword_url'] = $lostpassword_url;
587
  $login_form .= wppb_login_form( apply_filters( 'wppb_login_form_args', $form_args ) );
588
 
index.php CHANGED
@@ -3,14 +3,14 @@
3
  * Plugin Name: Profile Builder
4
  * Plugin URI: https://www.cozmoslabs.com/wordpress-profile-builder/
5
  * Description: Login, registration and edit profile shortcodes for the front-end. Also you can choose what fields should be displayed or add new (custom) ones both in the front-end and in the dashboard.
6
- * Version: 3.8.0
7
  * Author: Cozmoslabs
8
  * Author URI: https://www.cozmoslabs.com/
9
  * Text Domain: profile-builder
10
  * Domain Path: /translation
11
  * License: GPL2
12
- * Elementor tested up to: 3.7.7
13
- * Elementor Pro tested up to: 3.7.8
14
  *
15
  * == Copyright ==
16
  * Copyright 2014 Cozmoslabs (www.cozmoslabs.com)
@@ -144,7 +144,6 @@ function wppb_plugin_init() {
144
  include_once(WPPB_PAID_PLUGIN_DIR . '/add-ons/add-ons.php');
145
  include_once(WPPB_PAID_PLUGIN_DIR . '/add-ons/repeater-field/repeater-module.php');
146
  include_once(WPPB_PAID_PLUGIN_DIR . '/add-ons/custom-redirects/custom-redirects.php');
147
- include_once(WPPB_PAID_PLUGIN_DIR . '/add-ons/email-customizer/email-customizer.php');
148
  include_once(WPPB_PAID_PLUGIN_DIR . '/add-ons/multiple-forms/multiple-forms.php');
149
  include_once(WPPB_PAID_PLUGIN_DIR . '/add-ons/user-listing/userlisting.php');
150
 
@@ -154,16 +153,28 @@ function wppb_plugin_init() {
154
  } else
155
  add_shortcode('wppb-list-users', 'wppb_list_all_users_display_error');
156
 
157
- $wppb_email_customizer_activate = 'hide';
158
- if ( ( !empty( $wppb_module_settings['wppb_emailCustomizer'] ) && $wppb_module_settings['wppb_emailCustomizer'] == 'show' ) || ( !empty( $wppb_module_settings['wppb_emailCustomizerAdmin'] ) && $wppb_module_settings['wppb_emailCustomizerAdmin'] == 'show' ) )
159
- $wppb_email_customizer_activate = 'show';
160
 
161
- if ( $wppb_email_customizer_activate == 'show' )
162
- include_once(WPPB_PAID_PLUGIN_DIR . '/add-ons/email-customizer/admin-email-customizer.php');
163
 
164
- if ( $wppb_email_customizer_activate == 'show' )
165
- include_once(WPPB_PAID_PLUGIN_DIR . '/add-ons/email-customizer/user-email-customizer.php');
 
166
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  }
168
 
169
  include_once(WPPB_PLUGIN_DIR . '/admin/add-ons.php');
@@ -378,7 +389,7 @@ add_action( 'plugins_loaded', 'wppb_plugin_init' );
378
  *
379
  *
380
  */
381
- define('PROFILE_BUILDER_VERSION', '3.8.0' );
382
  define('WPPB_PLUGIN_DIR', plugin_dir_path(__FILE__));
383
  define('WPPB_PLUGIN_URL', plugin_dir_url(__FILE__));
384
  define('WPPB_PLUGIN_BASENAME', plugin_basename(__FILE__));
@@ -398,8 +409,8 @@ if ( in_array( 'profile-builder-pro/index.php', $active_plugins ) || isset( $act
398
  } elseif ( in_array( 'profile-builder-dev/index.php', $active_plugins ) || isset( $active_network_plugins['profile-builder-dev/index.php'] ) ){
399
 
400
  define('PROFILE_BUILDER', 'Profile Builder Pro');
401
- define('WPPB_PAID_PLUGIN_DIR', WPPB_PLUGIN_DIR );
402
- define('WPPB_PAID_PLUGIN_URL', WPPB_PLUGIN_URL );
403
 
404
  } elseif ( in_array( 'profile-builder-elite/index.php', $active_plugins ) || isset( $active_network_plugins['profile-builder-elite/index.php'] ) ){
405
 
@@ -425,6 +436,8 @@ if ( in_array( 'profile-builder-pro/index.php', $active_plugins ) || isset( $act
425
  // This needs to be loaded here since we try to plug some functions, not suited for plugins_loaded hook
426
  if ( defined( 'WPPB_PAID_PLUGIN_DIR' ) && file_exists( WPPB_PAID_PLUGIN_DIR . '/front-end/extra-fields/upload/upload_helper_functions.php'))
427
  include_once( WPPB_PAID_PLUGIN_DIR . '/front-end/extra-fields/upload/upload_helper_functions.php');
 
 
428
 
429
  /* add a redirect when plugin is activated */
430
  if( !function_exists( 'wppb_activate_plugin_redirect' ) ){
3
  * Plugin Name: Profile Builder
4
  * Plugin URI: https://www.cozmoslabs.com/wordpress-profile-builder/
5
  * Description: Login, registration and edit profile shortcodes for the front-end. Also you can choose what fields should be displayed or add new (custom) ones both in the front-end and in the dashboard.
6
+ * Version: 3.8.1
7
  * Author: Cozmoslabs
8
  * Author URI: https://www.cozmoslabs.com/
9
  * Text Domain: profile-builder
10
  * Domain Path: /translation
11
  * License: GPL2
12
+ * Elementor tested up to: 3.7.8
13
+ * Elementor Pro tested up to: 3.7.7
14
  *
15
  * == Copyright ==
16
  * Copyright 2014 Cozmoslabs (www.cozmoslabs.com)
144
  include_once(WPPB_PAID_PLUGIN_DIR . '/add-ons/add-ons.php');
145
  include_once(WPPB_PAID_PLUGIN_DIR . '/add-ons/repeater-field/repeater-module.php');
146
  include_once(WPPB_PAID_PLUGIN_DIR . '/add-ons/custom-redirects/custom-redirects.php');
 
147
  include_once(WPPB_PAID_PLUGIN_DIR . '/add-ons/multiple-forms/multiple-forms.php');
148
  include_once(WPPB_PAID_PLUGIN_DIR . '/add-ons/user-listing/userlisting.php');
149
 
153
  } else
154
  add_shortcode('wppb-list-users', 'wppb_list_all_users_display_error');
155
 
156
+ if( !file_exists( WPPB_PLUGIN_DIR . '/features/email-customizer/email-customizer.php' ) ){
 
 
157
 
158
+ include_once( WPPB_PAID_PLUGIN_DIR . '/add-ons/email-customizer/email-customizer.php' );
 
159
 
160
+ $wppb_email_customizer_activate = 'hide';
161
+ if ( ( !empty( $wppb_module_settings['wppb_emailCustomizer'] ) && $wppb_module_settings['wppb_emailCustomizer'] == 'show' ) || ( !empty( $wppb_module_settings['wppb_emailCustomizerAdmin'] ) && $wppb_module_settings['wppb_emailCustomizerAdmin'] == 'show' ) )
162
+ $wppb_email_customizer_activate = 'show';
163
 
164
+ if ( $wppb_email_customizer_activate == 'show' )
165
+ include_once(WPPB_PAID_PLUGIN_DIR . '/add-ons/email-customizer/admin-email-customizer.php');
166
+
167
+ if ( $wppb_email_customizer_activate == 'show' )
168
+ include_once(WPPB_PAID_PLUGIN_DIR . '/add-ons/email-customizer/user-email-customizer.php');
169
+ }
170
+
171
+ }
172
+
173
+ // Email Customizer is in free since 3.8.1
174
+ if( file_exists( WPPB_PLUGIN_DIR . '/features/email-customizer/email-customizer.php' ) ){
175
+ include_once( WPPB_PLUGIN_DIR . '/features/email-customizer/email-customizer.php' );
176
+ include_once( WPPB_PLUGIN_DIR . '/features/email-customizer/admin-email-customizer.php' );
177
+ include_once( WPPB_PLUGIN_DIR . '/features/email-customizer/user-email-customizer.php' );
178
  }
179
 
180
  include_once(WPPB_PLUGIN_DIR . '/admin/add-ons.php');
389
  *
390
  *
391
  */
392
+ define('PROFILE_BUILDER_VERSION', '3.8.1' );
393
  define('WPPB_PLUGIN_DIR', plugin_dir_path(__FILE__));
394
  define('WPPB_PLUGIN_URL', plugin_dir_url(__FILE__));
395
  define('WPPB_PLUGIN_BASENAME', plugin_basename(__FILE__));
409
  } elseif ( in_array( 'profile-builder-dev/index.php', $active_plugins ) || isset( $active_network_plugins['profile-builder-dev/index.php'] ) ){
410
 
411
  define('PROFILE_BUILDER', 'Profile Builder Pro');
412
+ define('WPPB_PAID_PLUGIN_DIR', WP_PLUGIN_DIR . '/profile-builder-dev' );
413
+ define('WPPB_PAID_PLUGIN_URL', plugins_url() . '/profile-builder-dev/' );
414
 
415
  } elseif ( in_array( 'profile-builder-elite/index.php', $active_plugins ) || isset( $active_network_plugins['profile-builder-elite/index.php'] ) ){
416
 
436
  // This needs to be loaded here since we try to plug some functions, not suited for plugins_loaded hook
437
  if ( defined( 'WPPB_PAID_PLUGIN_DIR' ) && file_exists( WPPB_PAID_PLUGIN_DIR . '/front-end/extra-fields/upload/upload_helper_functions.php'))
438
  include_once( WPPB_PAID_PLUGIN_DIR . '/front-end/extra-fields/upload/upload_helper_functions.php');
439
+ else if ( file_exists( WPPB_PLUGIN_DIR . '/front-end/default-fields/upload/upload_helper_functions.php' ) )
440
+ include_once( WPPB_PLUGIN_DIR . '/front-end/default-fields/upload/upload_helper_functions.php');
441
 
442
  /* add a redirect when plugin is activated */
443
  if( !function_exists( 'wppb_activate_plugin_redirect' ) ){
readme.txt CHANGED
@@ -4,7 +4,7 @@ Donate link: http://www.cozmoslabs.com/wordpress-profile-builder/
4
  Tags: user registration, user profile, registration, profile, user registration form, user fields, edit profile, user custom fields, front-end login, front-end edit profile, front-end user registration, email confirmation, login form, content restriction, restrict content
5
  Requires at least: 3.1
6
  Tested up to: 6.0
7
- Stable tag: 3.8.0
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
 
@@ -38,7 +38,9 @@ You can use the following shortcode list to display the forms:
38
  == Profile Builder Features ==
39
 
40
  * drag & drop to reorder user profile fields
 
41
  * enable **Email Confirmation** (on registration users will receive a notification to [confirm their email address](https://www.cozmoslabs.com/14722-wordpress-email-confirmation/))
 
42
  * choose between login with **only Username, Email** or **both**
43
  * enforce [WordPress password requirements](https://www.cozmoslabs.com/28998-wordpress-minimum-password-length-strength/) by setting up a **minimum password length** and **minimum password strength** (using the default WordPress password strength meter)
44
  * assign users a specific role at registration (using **[wppb-register role="desired_role"]** shortcode argument for the register form)
@@ -62,8 +64,7 @@ You can use the following shortcode list to display the forms:
62
 
63
  The [Pro version](http://www.cozmoslabs.com/wordpress-profile-builder/?utm_source=wp.org&utm_medium=pb-description-page&utm_campaign=PBFree) has the following extra features:
64
 
65
- * Create Extra User Fields (Heading, Input, Hidden Input, Number, Checkbox, Agree to Terms Checkbox, Radio Buttons, , Textarea, WYSIWYG, reCAPTCHA, Upload fields, Selects, User Role Select, Country Select, Timezone Select, Avatar Upload, Map, HTML, Phone, Datepicker, Timepicker, Colorpicker, Custom Validation field, Currency Select, CPT Select)
66
- * Add Avatar Upload for users
67
  * Support for Conditional Fields
68
  * Front-end User Listing (fully customizable, sorting included)
69
  * Create Multiple User Listings
@@ -72,7 +73,6 @@ The [Pro version](http://www.cozmoslabs.com/wordpress-profile-builder/?utm_sourc
72
  * Multiple Registration Forms (set up [multiple registration forms](https://www.cozmoslabs.com/docs/profile-builder-2/add-ons/multiple-registration-forms/?utm_source=wp.org&utm_medium=pb-description-page&utm_campaign=PBFree) with different profile fields for certain user roles)
73
  * Multiple Edit Profile Forms
74
  * Admin Approval ([approve new users from dashboard or via email](https://www.cozmoslabs.com/112321-approve-users-from-admin-email-using-profile-builder/))
75
- * Email Customizer (Personalize all emails sent to your users or admins; customize default WordPress registration email)
76
  * Advanced Add-ons (e.g. custom redirects, user listing, multiple registration forms etc.)
77
  * Access to support and documentation
78
  * 1 Year of Updates / Priority Support
@@ -177,6 +177,12 @@ This plugin adds/removes user fields in the front-end. Both default and extra pr
177
  15. Edit or Add New User Role
178
 
179
  == Changelog ==
 
 
 
 
 
 
180
  = 3.8.0 =
181
  * Feature: added the possibility to restrict the purchase of WooCommerce Products based on User Roles
182
  * Misc: fix some cases where HTML was displaying in the back-end
4
  Tags: user registration, user profile, registration, profile, user registration form, user fields, edit profile, user custom fields, front-end login, front-end edit profile, front-end user registration, email confirmation, login form, content restriction, restrict content
5
  Requires at least: 3.1
6
  Tested up to: 6.0
7
+ Stable tag: 3.8.1
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
 
38
  == Profile Builder Features ==
39
 
40
  * drag & drop to reorder user profile fields
41
+ * add Avatar Upload field for users to manage their avatar on your website
42
  * enable **Email Confirmation** (on registration users will receive a notification to [confirm their email address](https://www.cozmoslabs.com/14722-wordpress-email-confirmation/))
43
+ * Email Customizer - Personalize all emails sent to your users or admins; customize default WordPress registration email
44
  * choose between login with **only Username, Email** or **both**
45
  * enforce [WordPress password requirements](https://www.cozmoslabs.com/28998-wordpress-minimum-password-length-strength/) by setting up a **minimum password length** and **minimum password strength** (using the default WordPress password strength meter)
46
  * assign users a specific role at registration (using **[wppb-register role="desired_role"]** shortcode argument for the register form)
64
 
65
  The [Pro version](http://www.cozmoslabs.com/wordpress-profile-builder/?utm_source=wp.org&utm_medium=pb-description-page&utm_campaign=PBFree) has the following extra features:
66
 
67
+ * Create Extra User Fields (Hidden Input, Agree to Terms Checkbox WYSIWYG, Upload fields, User Role Select, Country Select, Timezone Select Upload, Map, HTML, Phone, Datepicker, Timepicker, Colorpicker, Custom Validation field, Currency Select, CPT Select)
 
68
  * Support for Conditional Fields
69
  * Front-end User Listing (fully customizable, sorting included)
70
  * Create Multiple User Listings
73
  * Multiple Registration Forms (set up [multiple registration forms](https://www.cozmoslabs.com/docs/profile-builder-2/add-ons/multiple-registration-forms/?utm_source=wp.org&utm_medium=pb-description-page&utm_campaign=PBFree) with different profile fields for certain user roles)
74
  * Multiple Edit Profile Forms
75
  * Admin Approval ([approve new users from dashboard or via email](https://www.cozmoslabs.com/112321-approve-users-from-admin-email-using-profile-builder/))
 
76
  * Advanced Add-ons (e.g. custom redirects, user listing, multiple registration forms etc.)
77
  * Access to support and documentation
78
  * 1 Year of Updates / Priority Support
177
  15. Edit or Add New User Role
178
 
179
  == Changelog ==
180
+ = 3.8.1 =
181
+ * Feature: added new field types: Heading, Input, Textarea, Select, Select2, Checkbox, Radio
182
+ * Feature: added Avatar field which lets your users manage their avatar on your website from the Profile Builder forms
183
+ * Feature: added Email Customizer feature which lets you edit all the emails that the plugin sends with custom content and tags for the defined fields
184
+ * Fix: issue with licence activation not working on multisite
185
+
186
  = 3.8.0 =
187
  * Feature: added the possibility to restrict the purchase of WooCommerce Products based on User Roles
188
  * Misc: fix some cases where HTML was displaying in the back-end
translation/profile-builder.catalog.php CHANGED
@@ -13,8 +13,6 @@
13
  <?php __("Allow different user roles to edit their specific information. Set up multiple edit-profile forms with different fields for certain user roles.", "profile-builder"); ?>
14
  <?php __("User Listing", "profile-builder"); ?>
15
  <?php __("Easy to edit templates for listing your users as well as creating single user pages.", "profile-builder"); ?>
16
- <?php __("Email Customizer", "profile-builder"); ?>
17
- <?php __("Simple to use customization of the WordPress Registration Emails", "profile-builder"); ?>
18
  <?php __("Custom Redirects", "profile-builder"); ?>
19
  <?php __("Redirect users after login, after they first register or when they try to access the default WordPress dashboard, login, lost password and registration forms.", "profile-builder"); ?>
20
  <?php __("Repeater Fields", "profile-builder"); ?>
@@ -120,6 +118,8 @@
120
  <?php __("Make sure users sign up with genuine emails. On registration users will receive a notification to confirm their email address.", "profile-builder"); ?>
121
  <?php __("Content Restriction", "profile-builder"); ?>
122
  <?php __("Restrict users from accessing certain pages, posts or custom post types based on user role or logged-in status.", "profile-builder"); ?>
 
 
123
  <?php __("Minimum Password Length and Strength Meter", "profile-builder"); ?>
124
  <?php __("Eliminate weak passwords altogether by setting a minimum password length and enforcing a certain password strength.", "profile-builder"); ?>
125
  <?php __("Login with Email or Username", "profile-builder"); ?>
@@ -130,24 +130,21 @@
130
  <?php __("With Extra Profile Fields you can create the exact registration form your project needs.", "profile-builder"); ?>
131
  <?php __("Get started with extra fields", "profile-builder"); ?>
132
  <?php __("Extra Profile Fields are available in Basic or PRO versions", "profile-builder"); ?>
133
- <?php __("Avatar Upload", "profile-builder"); ?>
134
  <?php __("Generic Uploads", "profile-builder"); ?>
135
  <?php __("Agree To Terms Checkbox", "profile-builder"); ?>
136
  <?php __("Datepicker", "profile-builder"); ?>
137
  <?php __("Timepicker", "profile-builder"); ?>
138
  <?php __("Colorpicker", "profile-builder"); ?>
139
- <?php __("reCAPTCHA", "profile-builder"); ?>
140
  <?php __("Country Select", "profile-builder"); ?>
141
  <?php __("Currency Select", "profile-builder"); ?>
142
  <?php __("Timezone Select", "profile-builder"); ?>
143
- <?php __("Input / Hidden Input", "profile-builder"); ?>
 
 
 
144
  <?php __("Number", "profile-builder"); ?>
145
- <?php __("Checkbox", "profile-builder"); ?>
146
- <?php __("Select", "profile-builder"); ?>
147
- <?php __("Radio Buttons", "profile-builder"); ?>
148
- <?php __("Textarea", "profile-builder"); ?>
149
  <?php __("Validation", "profile-builder"); ?>
150
- <?php __("Map", "profile-builder"); ?>
151
  <?php __("HTML", "profile-builder"); ?>
152
  <?php __("Powerful Add-ons (**)", "profile-builder"); ?>
153
  <?php __("Everything you will need to manage your users is probably already available using the Pro Add-ons.", "profile-builder"); ?>
@@ -155,7 +152,6 @@
155
  <?php __("Find out more about PRO Modules", "profile-builder"); ?>
156
  <?php __("To create a page containing the users registered to this current site/blog, insert the following shortcode in a page of your chosing: %s.", "profile-builder"); ?>
157
  <?php __("Easy to edit templates for listing your website users as well as creating single user pages. Shortcode based, offering many options to customize your listings.", "profile-builder"); ?>
158
- <?php __("Personalize all emails sent to your users or admins. On registration, email confirmation, admin approval / un-approval.", "profile-builder"); ?>
159
  <?php __("Keep your users out of the WordPress dashboard, redirect them to the front-page after login or registration, everything is just a few clicks away.", "profile-builder"); ?>
160
  <?php __("Set up multiple registration forms with different fields for certain user roles. Capture different information from different types of users.", "profile-builder"); ?>
161
  <?php __("Multiple Edit-profile Forms", "profile-builder"); ?>
@@ -220,6 +216,9 @@
220
  <?php __("Enter the minimum characters the password should have. Leave empty for no minimum limit", "profile-builder"); ?>
221
  <?php __("Minimum Password Strength:", "profile-builder"); ?>
222
  <?php __("Disabled", "profile-builder"); ?>
 
 
 
223
  <?php __("Form Fields", "profile-builder"); ?>
224
  <?php __("Manage Form Fields", "profile-builder"); ?>
225
  <?php __("Standard", "profile-builder"); ?>
@@ -1125,7 +1124,6 @@
1125
  <?php __("You do not have permission to view this user list.", "profile-builder"); ?>
1126
  <?php __("You do not have the required user role to view this user list.", "profile-builder"); ?>
1127
  <?php __("User not found", "profile-builder"); ?>
1128
- <?php __("None", "profile-builder"); ?>
1129
  <?php __("Jabber", "profile-builder"); ?>
1130
  <?php __("Yim", "profile-builder"); ?>
1131
  <?php __("Aim", "profile-builder"); ?>
@@ -1366,7 +1364,6 @@
1366
  <?php __("State / County", "profile-builder"); ?>
1367
  <?php __("Postcode / Zip", "profile-builder"); ?>
1368
  <?php __("Email Address", "profile-builder"); ?>
1369
- <?php __("Phone", "profile-builder"); ?>
1370
  <?php __("Ship to a different address?", "profile-builder"); ?>
1371
  <?php __("WooCommerce needs to be installed and activated for Profile Builder - WooCommerce Sync Add-on to work!", "profile-builder"); ?>
1372
  <?php __("Address line 2", "profile-builder"); ?>
@@ -1396,7 +1393,7 @@
1396
  <?php __("Displays customer shipping fields in front-end. ", "profile-builder"); ?>
1397
  <?php __("Choose Register form to display on My Account page:", "profile-builder"); ?>
1398
  <?php __("Default Register", "profile-builder"); ?>
1399
- <?php __("Select which Profile Builder Register form to display on My Account page from WooCommerce. <br/> This will also add the Profile Builder Login form to MyAccount page.", "profile-builder"); ?>
1400
  <?php __("Choose Edit Profile form to display on My Account page:", "profile-builder"); ?>
1401
  <?php __("Select which Profile Builder Edit-profile form to display on My Account page from WooCommerce.", "profile-builder"); ?>
1402
  <?php __("CSS Class", "profile-builder"); ?>
@@ -1813,7 +1810,10 @@
1813
  <?php __("Add Entry", "profile-builder"); ?>
1814
  <?php __("Delete this item", "profile-builder"); ?>
1815
  <?php __("Please enter a value for the required field ", "profile-builder"); ?>
 
 
1816
  <?php __("Profile Builder Forms", "profile-builder"); ?>
 
1817
  <?php __("Yes, I'd like to create a new site", "profile-builder"); ?>
1818
  <?php __("Your site url will look like this:<br>", "profile-builder"); ?>
1819
  <?php __("Site URL slug", "profile-builder"); ?>
@@ -1834,6 +1834,11 @@
1834
  <?php __("For security reasons, you must pass the remote ip to reCAPTCHA!", "profile-builder"); ?>
1835
  <?php __("To use reCAPTCHA you must get an API public key from:", "profile-builder"); ?>
1836
  <?php __("Click the BACK button on your browser, and try again.", "profile-builder"); ?>
 
 
 
 
 
1837
  <?php __("As an administrator you cannot change your role.", "profile-builder"); ?>
1838
  <?php __("Only administrators can see this field on edit profile forms.", "profile-builder"); ?>
1839
  <?php __("You cannot register this user role", "profile-builder"); ?>
@@ -1841,7 +1846,6 @@
1841
  <?php __("This username is invalid because it uses illegal characters.", "profile-builder"); ?>
1842
  <?php __("Please enter a valid username.", "profile-builder"); ?>
1843
  <?php __("This username is already reserved to be used soon.", "profile-builder"); ?>
1844
- <?php __("Remove", "profile-builder"); ?>
1845
  <?php __("The hidden Honeypot field must be empty.", "profile-builder"); ?>
1846
  <?php __("You must enter a valid URL.", "profile-builder"); ?>
1847
  <?php __("Please add the Google Maps API key for this field.", "profile-builder"); ?>
@@ -1852,11 +1856,6 @@
1852
  <?php __('Value must be less than or equal to %1$s', 'profile-builder' ); ?>
1853
  <?php __("Required phone number format: ", "profile-builder"); ?>
1854
  <?php __("...Choose", "profile-builder"); ?>
1855
- <?php __("Select File", "profile-builder"); ?>
1856
- <?php __("Upload ", "profile-builder"); ?>
1857
- <?php __("Files must be smaller than ", "profile-builder"); ?>
1858
- <?php __("Sorry, you cannot upload this file type for this field.", "profile-builder"); ?>
1859
- <?php __("An error occurred, please try again later.", "profile-builder"); ?>
1860
  <?php __("This display name is already in use. Please choose another one.", "profile-builder"); ?>
1861
  <?php __("Email address change request for %s", "profile-builder"); ?>
1862
  <?php __('Someone requested to change the email address for your account.<br/>If this was a mistake, just ignore this email and nothing will happen.<br/>To update your account email address to the one requested (%1$s), visit the following link: %2$s', 'profile-builder' ); ?>
13
  <?php __("Allow different user roles to edit their specific information. Set up multiple edit-profile forms with different fields for certain user roles.", "profile-builder"); ?>
14
  <?php __("User Listing", "profile-builder"); ?>
15
  <?php __("Easy to edit templates for listing your users as well as creating single user pages.", "profile-builder"); ?>
 
 
16
  <?php __("Custom Redirects", "profile-builder"); ?>
17
  <?php __("Redirect users after login, after they first register or when they try to access the default WordPress dashboard, login, lost password and registration forms.", "profile-builder"); ?>
18
  <?php __("Repeater Fields", "profile-builder"); ?>
118
  <?php __("Make sure users sign up with genuine emails. On registration users will receive a notification to confirm their email address.", "profile-builder"); ?>
119
  <?php __("Content Restriction", "profile-builder"); ?>
120
  <?php __("Restrict users from accessing certain pages, posts or custom post types based on user role or logged-in status.", "profile-builder"); ?>
121
+ <?php __("Email Customizer", "profile-builder"); ?>
122
+ <?php __("Personalize all emails sent to your users or admins. On registration, email confirmation, admin approval / un-approval.", "profile-builder"); ?>
123
  <?php __("Minimum Password Length and Strength Meter", "profile-builder"); ?>
124
  <?php __("Eliminate weak passwords altogether by setting a minimum password length and enforcing a certain password strength.", "profile-builder"); ?>
125
  <?php __("Login with Email or Username", "profile-builder"); ?>
130
  <?php __("With Extra Profile Fields you can create the exact registration form your project needs.", "profile-builder"); ?>
131
  <?php __("Get started with extra fields", "profile-builder"); ?>
132
  <?php __("Extra Profile Fields are available in Basic or PRO versions", "profile-builder"); ?>
 
133
  <?php __("Generic Uploads", "profile-builder"); ?>
134
  <?php __("Agree To Terms Checkbox", "profile-builder"); ?>
135
  <?php __("Datepicker", "profile-builder"); ?>
136
  <?php __("Timepicker", "profile-builder"); ?>
137
  <?php __("Colorpicker", "profile-builder"); ?>
 
138
  <?php __("Country Select", "profile-builder"); ?>
139
  <?php __("Currency Select", "profile-builder"); ?>
140
  <?php __("Timezone Select", "profile-builder"); ?>
141
+ <?php __("Map", "profile-builder"); ?>
142
+ <?php __("Select 2 Multiple", "profile-builder"); ?>
143
+ <?php __("Phone", "profile-builder"); ?>
144
+ <?php __("Hidden Input", "profile-builder"); ?>
145
  <?php __("Number", "profile-builder"); ?>
 
 
 
 
146
  <?php __("Validation", "profile-builder"); ?>
147
+ <?php __("Select CPT", "profile-builder"); ?>
148
  <?php __("HTML", "profile-builder"); ?>
149
  <?php __("Powerful Add-ons (**)", "profile-builder"); ?>
150
  <?php __("Everything you will need to manage your users is probably already available using the Pro Add-ons.", "profile-builder"); ?>
152
  <?php __("Find out more about PRO Modules", "profile-builder"); ?>
153
  <?php __("To create a page containing the users registered to this current site/blog, insert the following shortcode in a page of your chosing: %s.", "profile-builder"); ?>
154
  <?php __("Easy to edit templates for listing your website users as well as creating single user pages. Shortcode based, offering many options to customize your listings.", "profile-builder"); ?>
 
155
  <?php __("Keep your users out of the WordPress dashboard, redirect them to the front-page after login or registration, everything is just a few clicks away.", "profile-builder"); ?>
156
  <?php __("Set up multiple registration forms with different fields for certain user roles. Capture different information from different types of users.", "profile-builder"); ?>
157
  <?php __("Multiple Edit-profile Forms", "profile-builder"); ?>
216
  <?php __("Enter the minimum characters the password should have. Leave empty for no minimum limit", "profile-builder"); ?>
217
  <?php __("Minimum Password Strength:", "profile-builder"); ?>
218
  <?php __("Disabled", "profile-builder"); ?>
219
+ <?php __("Select Recover Password Page:", "profile-builder"); ?>
220
+ <?php __("None", "profile-builder"); ?>
221
+ <?php __('Select the page which contains the %1$s[wppb-recover-password]%2$s shortcode.', 'profile-builder' ); ?>
222
  <?php __("Form Fields", "profile-builder"); ?>
223
  <?php __("Manage Form Fields", "profile-builder"); ?>
224
  <?php __("Standard", "profile-builder"); ?>
1124
  <?php __("You do not have permission to view this user list.", "profile-builder"); ?>
1125
  <?php __("You do not have the required user role to view this user list.", "profile-builder"); ?>
1126
  <?php __("User not found", "profile-builder"); ?>
 
1127
  <?php __("Jabber", "profile-builder"); ?>
1128
  <?php __("Yim", "profile-builder"); ?>
1129
  <?php __("Aim", "profile-builder"); ?>
1364
  <?php __("State / County", "profile-builder"); ?>
1365
  <?php __("Postcode / Zip", "profile-builder"); ?>
1366
  <?php __("Email Address", "profile-builder"); ?>
 
1367
  <?php __("Ship to a different address?", "profile-builder"); ?>
1368
  <?php __("WooCommerce needs to be installed and activated for Profile Builder - WooCommerce Sync Add-on to work!", "profile-builder"); ?>
1369
  <?php __("Address line 2", "profile-builder"); ?>
1393
  <?php __("Displays customer shipping fields in front-end. ", "profile-builder"); ?>
1394
  <?php __("Choose Register form to display on My Account page:", "profile-builder"); ?>
1395
  <?php __("Default Register", "profile-builder"); ?>
1396
+ <?php __("Select which Profile Builder Register form to display on My Account page from WooCommerce. %s This will also add the Profile Builder Login form to MyAccount page.", "profile-builder"); ?>
1397
  <?php __("Choose Edit Profile form to display on My Account page:", "profile-builder"); ?>
1398
  <?php __("Select which Profile Builder Edit-profile form to display on My Account page from WooCommerce.", "profile-builder"); ?>
1399
  <?php __("CSS Class", "profile-builder"); ?>
1810
  <?php __("Add Entry", "profile-builder"); ?>
1811
  <?php __("Delete this item", "profile-builder"); ?>
1812
  <?php __("Please enter a value for the required field ", "profile-builder"); ?>
1813
+ <?php __("This field is available in our paid plans.", "profile-builder"); ?>
1814
+ <?php __("Install the free Paid Member Subscriptions plugin to get access this field.", "profile-builder"); ?>
1815
  <?php __("Profile Builder Forms", "profile-builder"); ?>
1816
+ <?php __("Remove", "profile-builder"); ?>
1817
  <?php __("Yes, I'd like to create a new site", "profile-builder"); ?>
1818
  <?php __("Your site url will look like this:<br>", "profile-builder"); ?>
1819
  <?php __("Site URL slug", "profile-builder"); ?>
1834
  <?php __("For security reasons, you must pass the remote ip to reCAPTCHA!", "profile-builder"); ?>
1835
  <?php __("To use reCAPTCHA you must get an API public key from:", "profile-builder"); ?>
1836
  <?php __("Click the BACK button on your browser, and try again.", "profile-builder"); ?>
1837
+ <?php __("Files must be smaller than ", "profile-builder"); ?>
1838
+ <?php __("Sorry, you cannot upload this file type for this field.", "profile-builder"); ?>
1839
+ <?php __("An error occurred, please try again later.", "profile-builder"); ?>
1840
+ <?php __("Select File", "profile-builder"); ?>
1841
+ <?php __("Upload ", "profile-builder"); ?>
1842
  <?php __("As an administrator you cannot change your role.", "profile-builder"); ?>
1843
  <?php __("Only administrators can see this field on edit profile forms.", "profile-builder"); ?>
1844
  <?php __("You cannot register this user role", "profile-builder"); ?>
1846
  <?php __("This username is invalid because it uses illegal characters.", "profile-builder"); ?>
1847
  <?php __("Please enter a valid username.", "profile-builder"); ?>
1848
  <?php __("This username is already reserved to be used soon.", "profile-builder"); ?>
 
1849
  <?php __("The hidden Honeypot field must be empty.", "profile-builder"); ?>
1850
  <?php __("You must enter a valid URL.", "profile-builder"); ?>
1851
  <?php __("Please add the Google Maps API key for this field.", "profile-builder"); ?>
1856
  <?php __('Value must be less than or equal to %1$s', 'profile-builder' ); ?>
1857
  <?php __("Required phone number format: ", "profile-builder"); ?>
1858
  <?php __("...Choose", "profile-builder"); ?>
 
 
 
 
 
1859
  <?php __("This display name is already in use. Please choose another one.", "profile-builder"); ?>
1860
  <?php __("Email address change request for %s", "profile-builder"); ?>
1861
  <?php __('Someone requested to change the email address for your account.<br/>If this was a mistake, just ignore this email and nothing will happen.<br/>To update your account email address to the one requested (%1$s), visit the following link: %2$s', 'profile-builder' ); ?>
translation/profile-builder.pot CHANGED
@@ -13,11 +13,11 @@ msgstr ""
13
  "X-Poedit-SourceCharset: UTF-8\n"
14
  "Plural-Forms: nplurals=2; plural=(n != 1);\n"
15
 
16
- #: index.php:359
17
  msgid "To enable updates, your licence needs to be renewed. Please go to the <a href=\"%s\">Cozmoslabs Account</a> page and login to renew."
18
  msgstr ""
19
 
20
- #: index.php:355
21
  msgid "To enable updates, please enter your serial number on the <a href=\"%s\">Register Version</a> page. If you don't have a serial number, please see <a href=\"%s\" target=\"_blank\">details & pricing</a>."
22
  msgstr ""
23
 
@@ -45,7 +45,7 @@ msgstr ""
45
  msgid "These Add-ons are available with the Pro and Unlimited license"
46
  msgstr ""
47
 
48
- #: admin/add-ons.php:45, admin/basic-info.php:180
49
  msgid "Multiple Registration Forms"
50
  msgstr ""
51
 
@@ -57,11 +57,11 @@ msgstr ""
57
  msgid "Multiple Edit Profile Forms"
58
  msgstr ""
59
 
60
- #: admin/add-ons.php:53, admin/basic-info.php:185
61
  msgid "Allow different user roles to edit their specific information. Set up multiple edit-profile forms with different fields for certain user roles."
62
  msgstr ""
63
 
64
- #: admin/add-ons.php:59, admin/basic-info.php:162, add-ons/user-listing/userlisting.php:11, add-ons/user-listing/userlisting.php:12, add-ons/user-listing/userlisting.php:17, add-ons/user-listing/userlisting.php:23, assets/misc/elementor/widgets/class-pb-widget-ul.php:58, assets/misc/elementor/widgets/class-pb-widget-ul.php:97
65
  msgid "User Listing"
66
  msgstr ""
67
 
@@ -69,15 +69,7 @@ msgstr ""
69
  msgid "Easy to edit templates for listing your users as well as creating single user pages."
70
  msgstr ""
71
 
72
- #: admin/add-ons.php:66, admin/basic-info.php:170, admin/general-settings.php:36
73
- msgid "Email Customizer"
74
- msgstr ""
75
-
76
- #: admin/add-ons.php:67
77
- msgid "Simple to use customization of the WordPress Registration Emails"
78
- msgstr ""
79
-
80
- #: admin/add-ons.php:73, admin/basic-info.php:174, add-ons/custom-redirects/custom_redirects_admin.php:33, add-ons/custom-redirects/custom_redirects_admin.php:34
81
  msgid "Custom Redirects"
82
  msgstr ""
83
 
@@ -85,7 +77,7 @@ msgstr ""
85
  msgid "Redirect users after login, after they first register or when they try to access the default WordPress dashboard, login, lost password and registration forms."
86
  msgstr ""
87
 
88
- #: admin/add-ons.php:80, admin/basic-info.php:188
89
  msgid "Repeater Fields"
90
  msgstr ""
91
 
@@ -317,11 +309,11 @@ msgstr ""
317
  msgid "Show"
318
  msgstr ""
319
 
320
- #: admin/admin-bar.php:81, add-ons/user-listing/userlisting.php:1811
321
  msgid "Hide"
322
  msgstr ""
323
 
324
- #: admin/admin-bar.php:92, admin/general-settings.php:348, admin/private-website.php:162, features/functions.php:1055, add-ons-advanced/woocommerce/woosync-page.php:149, features/content-restriction/content-restriction.php:174, features/two-factor-authentication/class-two-factor-authentication.php:156, assets/lib/class-mustache-templates/class-mustache-templates.php:392, assets/lib/wck-api/wordpress-creation-kit.php:410, admin/advanced-settings/includes/views/view-admin.php:112, admin/advanced-settings/includes/views/view-fields.php:312, admin/advanced-settings/includes/views/view-forms.php:407, admin/advanced-settings/includes/views/view-shortcodes.php:77, admin/advanced-settings/includes/views/view-userlisting.php:91
325
  msgid "Save Changes"
326
  msgstr ""
327
 
@@ -345,19 +337,19 @@ msgstr ""
345
  msgid "<strong>ERROR</strong>: The password must have the minimum length of %s characters"
346
  msgstr ""
347
 
348
- #: admin/admin-functions.php:139, admin/general-settings.php:334
349
  msgid "Very weak"
350
  msgstr ""
351
 
352
- #: admin/admin-functions.php:139, admin/general-settings.php:335, features/functions.php:788, features/functions.php:812
353
  msgid "Weak"
354
  msgstr ""
355
 
356
- #: admin/admin-functions.php:139, admin/general-settings.php:336, features/functions.php:788, features/functions.php:812
357
  msgid "Medium"
358
  msgstr ""
359
 
360
- #: admin/admin-functions.php:139, admin/general-settings.php:337, features/functions.php:788, features/functions.php:812
361
  msgid "Strong"
362
  msgstr ""
363
 
@@ -373,7 +365,7 @@ msgstr ""
373
  msgid "Add Field"
374
  msgstr ""
375
 
376
- #: admin/admin-functions.php:212, admin/general-settings.php:114, admin/general-settings.php:114, add-ons-advanced/social-connect/index.php:260
377
  msgid "Settings"
378
  msgstr ""
379
 
@@ -393,7 +385,7 @@ msgstr ""
393
  msgid "See details"
394
  msgstr ""
395
 
396
- #: admin/admin-functions.php:260, admin/review.php:94, add-ons/user-listing/userlisting.php:3386
397
  msgid "Dismiss this notice."
398
  msgstr ""
399
 
@@ -497,171 +489,163 @@ msgstr ""
497
  msgid "Restrict users from accessing certain pages, posts or custom post types based on user role or logged-in status."
498
  msgstr ""
499
 
500
- #: admin/basic-info.php:91
501
- msgid "Minimum Password Length and Strength Meter"
502
  msgstr ""
503
 
504
  #: admin/basic-info.php:92
505
- msgid "Eliminate weak passwords altogether by setting a minimum password length and enforcing a certain password strength."
506
  msgstr ""
507
 
508
  #: admin/basic-info.php:95
509
- msgid "Login with Email or Username"
510
  msgstr ""
511
 
512
  #: admin/basic-info.php:96
 
 
 
 
 
 
 
 
513
  msgid "Allow users to log in with their email or username when accessing your site."
514
  msgstr ""
515
 
516
- #: admin/basic-info.php:99, features/roles-editor/roles-editor.php:61, features/roles-editor/roles-editor.php:62, features/roles-editor/roles-editor.php:275, features/roles-editor/roles-editor.php:276, features/roles-editor/roles-editor.php:281, features/roles-editor/roles-editor.php:288
517
  msgid "Roles Editor"
518
  msgstr ""
519
 
520
- #: admin/basic-info.php:100
521
  msgid "Add, remove, clone and edit roles and also capabilities for these roles."
522
  msgstr ""
523
 
524
- #: admin/basic-info.php:113
525
  msgid "Customize Your Forms The Way You Want (*)"
526
  msgstr ""
527
 
528
- #: admin/basic-info.php:114
529
  msgid "With Extra Profile Fields you can create the exact registration form your project needs."
530
  msgstr ""
531
 
532
- #: admin/basic-info.php:118
533
  msgid "Get started with extra fields"
534
  msgstr ""
535
 
536
- #: admin/basic-info.php:116
537
  msgid "Extra Profile Fields are available in Basic or PRO versions"
538
  msgstr ""
539
 
540
- #: admin/basic-info.php:121
541
- msgid "Avatar Upload"
542
- msgstr ""
543
-
544
- #: admin/basic-info.php:122
545
  msgid "Generic Uploads"
546
  msgstr ""
547
 
548
- #: admin/basic-info.php:123
549
  msgid "Agree To Terms Checkbox"
550
  msgstr ""
551
 
552
- #: admin/basic-info.php:124
553
  msgid "Datepicker"
554
  msgstr ""
555
 
556
- #: admin/basic-info.php:125
557
  msgid "Timepicker"
558
  msgstr ""
559
 
560
- #: admin/basic-info.php:126
561
  msgid "Colorpicker"
562
  msgstr ""
563
 
564
- #: admin/basic-info.php:127
565
- msgid "reCAPTCHA"
566
- msgstr ""
567
-
568
- #: admin/basic-info.php:128
569
  msgid "Country Select"
570
  msgstr ""
571
 
572
- #: admin/basic-info.php:129
573
  msgid "Currency Select"
574
  msgstr ""
575
 
576
- #: admin/basic-info.php:130
577
  msgid "Timezone Select"
578
  msgstr ""
579
 
580
- #: admin/basic-info.php:134
581
- msgid "Input / Hidden Input"
582
- msgstr ""
583
-
584
- #: admin/basic-info.php:135
585
- msgid "Number"
586
- msgstr ""
587
-
588
  #: admin/basic-info.php:136
589
- msgid "Checkbox"
590
  msgstr ""
591
 
592
  #: admin/basic-info.php:137
593
- msgid "Select"
594
  msgstr ""
595
 
596
- #: admin/basic-info.php:138
597
- msgid "Radio Buttons"
598
  msgstr ""
599
 
600
  #: admin/basic-info.php:139
601
- msgid "Textarea"
602
  msgstr ""
603
 
604
  #: admin/basic-info.php:140
605
- msgid "Validation"
606
  msgstr ""
607
 
608
  #: admin/basic-info.php:141
609
- msgid "Map"
610
  msgstr ""
611
 
612
  #: admin/basic-info.php:142
 
 
 
 
613
  msgid "HTML"
614
  msgstr ""
615
 
616
- #: admin/basic-info.php:151
617
  msgid "Powerful Add-ons (**)"
618
  msgstr ""
619
 
620
- #: admin/basic-info.php:152
621
  msgid "Everything you will need to manage your users is probably already available using the Pro Add-ons."
622
  msgstr ""
623
 
624
- #: admin/basic-info.php:154
625
  msgid "Enable your add-ons"
626
  msgstr ""
627
 
628
- #: admin/basic-info.php:157
629
  msgid "Find out more about PRO Modules"
630
  msgstr ""
631
 
632
- #: admin/basic-info.php:166
633
  msgid "To create a page containing the users registered to this current site/blog, insert the following shortcode in a page of your chosing: %s."
634
  msgstr ""
635
 
636
- #: admin/basic-info.php:164
637
  msgid "Easy to edit templates for listing your website users as well as creating single user pages. Shortcode based, offering many options to customize your listings."
638
  msgstr ""
639
 
640
- #: admin/basic-info.php:171
641
- msgid "Personalize all emails sent to your users or admins. On registration, email confirmation, admin approval / un-approval."
642
- msgstr ""
643
-
644
- #: admin/basic-info.php:175
645
  msgid "Keep your users out of the WordPress dashboard, redirect them to the front-page after login or registration, everything is just a few clicks away."
646
  msgstr ""
647
 
648
- #: admin/basic-info.php:181
649
  msgid "Set up multiple registration forms with different fields for certain user roles. Capture different information from different types of users."
650
  msgstr ""
651
 
652
- #: admin/basic-info.php:184
653
  msgid "Multiple Edit-profile Forms"
654
  msgstr ""
655
 
656
- #: admin/basic-info.php:189
657
  msgid "Set up a repeating group of fields on register and edit profile forms. Limit the number of repeated groups for each user role."
658
  msgstr ""
659
 
660
- #: admin/basic-info.php:225
661
  msgid " * only available in the %1$sBasic and Pro versions%2$s."
662
  msgstr ""
663
 
664
- #: admin/basic-info.php:226
665
  msgid "** only available in the %1$sPro version%2$s."
666
  msgstr ""
667
 
@@ -697,7 +681,7 @@ msgstr ""
697
  msgid "Give us another try! Open a support ticket <a href='https://www.cozmoslabs.com/support/open-ticket/' target='_blank'>here</a>"
698
  msgstr ""
699
 
700
- #: admin/feedback.php:47, admin/manage-fields.php:65
701
  msgid "Other"
702
  msgstr ""
703
 
@@ -761,142 +745,154 @@ msgstr ""
761
  msgid "Two-Factor Authentication"
762
  msgstr ""
763
 
764
- #: admin/general-settings.php:37
765
  msgid "User Emails"
766
  msgstr ""
767
 
768
- #: admin/general-settings.php:38
769
  msgid "Administrator Emails"
770
  msgstr ""
771
 
772
- #: admin/general-settings.php:135
773
  msgid "Profile Builder Settings"
774
  msgstr ""
775
 
776
- #: admin/general-settings.php:148
777
  msgid "Load Profile Builder's own CSS file in the front-end:"
778
  msgstr ""
779
 
780
- #: admin/general-settings.php:151, admin/general-settings.php:164, admin/general-settings.php:179, admin/general-settings.php:228, admin/general-settings.php:275, admin/manage-fields.php:193, admin/private-website.php:70, admin/private-website.php:137, admin/private-website.php:150, add-ons/multiple-forms/edit-profile-forms.php:206, add-ons/multiple-forms/register-forms.php:229, add-ons/multiple-forms/register-forms.php:230, add-ons/user-listing/userlisting.php:2602, add-ons-advanced/bbpress/bbpress-page.php:161, add-ons-advanced/social-connect/index.php:347, add-ons-advanced/social-connect/index.php:406, features/content-restriction/content-restriction.php:90, features/two-factor-authentication/class-two-factor-authentication.php:125, admin/advanced-settings/includes/forms/placeholder-labels.php:135, admin/advanced-settings/includes/views/view-admin.php:18, admin/advanced-settings/includes/views/view-admin.php:34, admin/advanced-settings/includes/views/view-admin.php:50, admin/advanced-settings/includes/views/view-admin.php:69, admin/advanced-settings/includes/views/view-admin.php:100, admin/advanced-settings/includes/views/view-fields.php:18, admin/advanced-settings/includes/views/view-fields.php:66, admin/advanced-settings/includes/views/view-fields.php:184, admin/advanced-settings/includes/views/view-fields.php:200, admin/advanced-settings/includes/views/view-fields.php:220, admin/advanced-settings/includes/views/view-fields.php:243, admin/advanced-settings/includes/views/view-fields.php:263, admin/advanced-settings/includes/views/view-fields.php:280, admin/advanced-settings/includes/views/view-fields.php:298, admin/advanced-settings/includes/views/view-forms.php:19, admin/advanced-settings/includes/views/view-forms.php:148, admin/advanced-settings/includes/views/view-forms.php:166, admin/advanced-settings/includes/views/view-forms.php:183, admin/advanced-settings/includes/views/view-forms.php:198, admin/advanced-settings/includes/views/view-forms.php:213, admin/advanced-settings/includes/views/view-forms.php:233, admin/advanced-settings/includes/views/view-forms.php:250, admin/advanced-settings/includes/views/view-forms.php:286, admin/advanced-settings/includes/views/view-forms.php:307, admin/advanced-settings/includes/views/view-forms.php:327, admin/advanced-settings/includes/views/view-forms.php:349, admin/advanced-settings/includes/views/view-forms.php:371, admin/advanced-settings/includes/views/view-forms.php:391, admin/advanced-settings/includes/views/view-shortcodes.php:16, admin/advanced-settings/includes/views/view-shortcodes.php:32, admin/advanced-settings/includes/views/view-shortcodes.php:48, admin/advanced-settings/includes/views/view-shortcodes.php:64, admin/advanced-settings/includes/views/view-userlisting.php:53, admin/advanced-settings/includes/views/view-userlisting.php:75, assets/misc/elementor/widgets/class-pb-widget-l.php:75, assets/misc/elementor/widgets/class-pb-widget-rf-epf.php:183, assets/misc/elementor/widgets/class-pb-widget-ul.php:109
781
  msgid "Yes"
782
  msgstr ""
783
 
784
- #: admin/general-settings.php:153
785
  msgid "You can find the default file here: %1$s"
786
  msgstr ""
787
 
788
- #: admin/general-settings.php:160
789
  msgid "Automatically Log In:"
790
  msgstr ""
791
 
792
- #: admin/general-settings.php:165, admin/general-settings.php:180, admin/general-settings.php:229, admin/general-settings.php:274, admin/private-website.php:69, admin/private-website.php:136, admin/private-website.php:151, add-ons/multiple-forms/edit-profile-forms.php:206, add-ons/multiple-forms/register-forms.php:229, add-ons/multiple-forms/register-forms.php:230, add-ons-advanced/bbpress/bbpress-page.php:160, add-ons-advanced/social-connect/index.php:348, add-ons-advanced/social-connect/index.php:407, features/content-restriction/content-restriction.php:89, features/two-factor-authentication/class-two-factor-authentication.php:124, admin/advanced-settings/includes/forms/placeholder-labels.php:136, assets/misc/elementor/widgets/class-pb-widget-l.php:76, assets/misc/elementor/widgets/class-pb-widget-rf-epf.php:184, assets/misc/elementor/widgets/class-pb-widget-ul.php:110
793
  msgid "No"
794
  msgstr ""
795
 
796
- #: admin/general-settings.php:168
797
  msgid "Select \"Yes\" to automatically log in new users after successful registration."
798
  msgstr ""
799
 
800
- #: admin/general-settings.php:175
801
  msgid "\"Email Confirmation\" Activated:"
802
  msgstr ""
803
 
804
- #: admin/general-settings.php:183
805
  msgid "This works with front-end forms only. Recommended to redirect WP default registration to a Profile Builder one using \"Custom Redirects\" module."
806
  msgstr ""
807
 
808
- #: admin/general-settings.php:185
809
  msgid "You can find a list of unconfirmed email addresses %1$sUsers > All Users > Email Confirmation%2$s."
810
  msgstr ""
811
 
812
- #: admin/general-settings.php:193
813
  msgid "\"Email Confirmation\" Landing Page:"
814
  msgstr ""
815
 
816
- #: admin/general-settings.php:198
817
  msgid "Existing Pages"
818
  msgstr ""
819
 
820
- #: admin/general-settings.php:213
821
  msgid "Specify the page where the users will be directed when confirming the email account. This page can differ from the register page(s) and can be changed at any time. If none selected, a simple confirmation page will be displayed for the user."
822
  msgstr ""
823
 
824
- #: admin/general-settings.php:224
825
  msgid "\"Admin Approval\" Activated:"
826
  msgstr ""
827
 
828
- #: admin/general-settings.php:232
829
  msgid "You can find a list of users at %1$sUsers > All Users > Admin Approval%2$s."
830
  msgstr ""
831
 
832
- #: admin/general-settings.php:239
833
  msgid "\"Admin Approval\" on User Role:"
834
  msgstr ""
835
 
836
- #: admin/general-settings.php:258
837
  msgid "Select on what user roles to activate Admin Approval."
838
  msgstr ""
839
 
840
- #: admin/general-settings.php:270
841
  msgid "\"Roles Editor\" Activated:"
842
  msgstr ""
843
 
844
- #: admin/general-settings.php:278
845
  msgid "You can add / edit user roles at %1$sUsers > Roles Editor%2$s."
846
  msgstr ""
847
 
848
- #: admin/general-settings.php:289
849
  msgid "\"Admin Approval\" Feature:"
850
  msgstr ""
851
 
852
- #: admin/general-settings.php:292
853
  msgid "You decide who is a user on your website. Get notified via email or approve multiple users at once from the WordPress UI. Enable Admin Approval by upgrading to %1$sBasic or PRO versions%2$s."
854
  msgstr ""
855
 
856
- #: admin/general-settings.php:299
857
  msgid "Allow Users to Log in With:"
858
  msgstr ""
859
 
860
- #: admin/general-settings.php:303
861
  msgid "Username and Email"
862
  msgstr ""
863
 
864
- #: admin/general-settings.php:304, admin/manage-fields.php:333, front-end/login.php:341, front-end/login.php:355, front-end/login.php:543, add-ons/custom-redirects/custom_redirects_admin.php:61, add-ons/email-customizer/email-customizer.php:28, add-ons/user-listing/userlisting.php:113, add-ons/user-listing/userlisting.php:336, add-ons/user-listing/userlisting.php:882, add-ons/user-listing/userlisting.php:2555, features/admin-approval/class-admin-approval.php:174, features/email-confirmation/class-email-confirmation.php:168, admin/advanced-settings/includes/views/view-fields.php:124
865
  msgid "Username"
866
  msgstr ""
867
 
868
- #: admin/general-settings.php:305, front-end/login.php:538, front-end/recover.php:118, add-ons/email-customizer/email-customizer.php:29, add-ons/user-listing/userlisting.php:119, add-ons/user-listing/userlisting.php:888, add-ons/user-listing/userlisting.php:2556, features/admin-approval/class-admin-approval.php:177, features/email-confirmation/class-email-confirmation.php:169, add-ons-free/gdpr-communication-preferences/admin/manage-fields.php:24, add-ons-free/gdpr-communication-preferences/front-end/gdpr-communication-preferences.php:9, admin/advanced-settings/includes/shortcodes/resend-activation.php:9
869
  msgid "Email"
870
  msgstr ""
871
 
872
- #: admin/general-settings.php:308
873
  msgid "\"Username and Email\" - users can Log In with either their Username or their Email."
874
  msgstr ""
875
 
876
- #: admin/general-settings.php:309
877
  msgid "\"Username\" - users can only Log In with their Username. Both the Username and Email fields will be shown in the front-end forms."
878
  msgstr ""
879
 
880
- #: admin/general-settings.php:310
881
  msgid "\"Email\" - users can only Log In with their Email. The Username field will be hidden in the front-end forms and Usernames will be automatically generated based on the Emails."
882
  msgstr ""
883
 
884
- #: admin/general-settings.php:317
885
  msgid "Minimum Password Length:"
886
  msgstr ""
887
 
888
- #: admin/general-settings.php:322
889
  msgid "Enter the minimum characters the password should have. Leave empty for no minimum limit"
890
  msgstr ""
891
 
892
- #: admin/general-settings.php:329
893
  msgid "Minimum Password Strength:"
894
  msgstr ""
895
 
896
- #: admin/general-settings.php:333
897
  msgid "Disabled"
898
  msgstr ""
899
 
 
 
 
 
 
 
 
 
 
 
 
 
900
  #: admin/manage-fields.php:18
901
  msgid "Form Fields"
902
  msgstr ""
@@ -909,2167 +905,2167 @@ msgstr ""
909
  msgid "Standard"
910
  msgstr ""
911
 
912
- #: admin/manage-fields.php:60
913
  msgid "Advanced"
914
  msgstr ""
915
 
916
- #: admin/manage-fields.php:134
917
  msgid "Choose one of the supported field types"
918
  msgstr ""
919
 
920
- #: admin/manage-fields.php:136
921
  msgid ". Extra Field Types are available in <a href=\"%s\">Basic or PRO versions</a>."
922
  msgstr ""
923
 
924
- #: admin/manage-fields.php:172
925
  msgid "Use this in conjunction with WordPress functions to display the value in the page of your choosing<br/>Auto-completed but in some cases editable (in which case it must be unique)<br/>Changing this will only affect subsequent entries"
926
  msgstr ""
927
 
928
- #: admin/manage-fields.php:169
929
  msgid "Use this in conjunction with WordPress functions to display the value in the page of your choosing<br/>Auto-completed but in some cases editable (in which case it must be unique)<br/>Changing this might take long in case of a very big user-count"
930
  msgstr ""
931
 
932
- #: admin/manage-fields.php:186
933
  msgid "Field Title"
934
  msgstr ""
935
 
936
- #: admin/manage-fields.php:186
937
  msgid "Title of the field"
938
  msgstr ""
939
 
940
- #: admin/manage-fields.php:187, add-ons/multiple-forms/edit-profile-forms.php:245, add-ons/multiple-forms/register-forms.php:266
941
  msgid "Field"
942
  msgstr ""
943
 
944
- #: admin/manage-fields.php:188
945
  msgid "Meta-name"
946
  msgstr ""
947
 
948
- #: admin/manage-fields.php:189, add-ons/custom-redirects/custom_redirects_admin.php:70, add-ons/custom-redirects/custom_redirects_admin.php:100, add-ons/custom-redirects/custom_redirects_admin.php:119, add-ons/custom-redirects/custom_redirects_admin.php:144, add-ons/multiple-forms/edit-profile-forms.php:246, add-ons/multiple-forms/register-forms.php:267, add-ons/user-listing/userlisting.php:927, assets/misc/elementor/widgets/class-pb-widget-ul.php:172
949
  msgid "ID"
950
  msgstr ""
951
 
952
- #: admin/manage-fields.php:189, add-ons/multiple-forms/edit-profile-forms.php:246, add-ons/multiple-forms/register-forms.php:267
953
  msgid "A unique, auto-generated ID for this particular field<br/>You can use this in conjuction with filters to target this element if needed<br/>Can't be edited"
954
  msgstr ""
955
 
956
- #: admin/manage-fields.php:190, features/two-factor-authentication/class-two-factor-authentication.php:241, features/two-factor-authentication/class-two-factor-authentication.php:356
957
  msgid "Description"
958
  msgstr ""
959
 
960
- #: admin/manage-fields.php:190
961
  msgid "Enter a (detailed) description of the option for end users to read<br/>Optional"
962
  msgstr ""
963
 
964
- #: admin/manage-fields.php:191
965
  msgid "Row Count"
966
  msgstr ""
967
 
968
- #: admin/manage-fields.php:191
969
  msgid "Specify the number of rows for a 'Textarea' field<br/>If not specified, defaults to 5"
970
  msgstr ""
971
 
972
- #: admin/manage-fields.php:192
973
  msgid "Allowed Image Extensions"
974
  msgstr ""
975
 
976
- #: admin/manage-fields.php:192
977
  msgid "Specify the extension(s) you want to limit to upload<br/>Example: .ext1,.ext2,.ext3<br/>If not specified, defaults to: .jpg,.jpeg,.gif,.png (.*)"
978
  msgstr ""
979
 
980
- #: admin/manage-fields.php:193
981
  msgid "Use Simple Upload"
982
  msgstr ""
983
 
984
- #: admin/manage-fields.php:193
985
  msgid "Use a simple upload field instead of the WordPress upload"
986
  msgstr ""
987
 
988
- #: admin/manage-fields.php:194
989
  msgid "Allowed Upload Extensions"
990
  msgstr ""
991
 
992
- #: admin/manage-fields.php:194
993
  msgid "Specify the extension(s) you want to limit to upload<br/>Example: .ext1,.ext2,.ext3<br/>If not specified, defaults to all WordPress allowed file extensions (.*)"
994
  msgstr ""
995
 
996
- #: admin/manage-fields.php:195
997
  msgid "Avatar Size"
998
  msgstr ""
999
 
1000
- #: admin/manage-fields.php:195
1001
  msgid "Enter a value (between 20 and 200) for the size of the 'Avatar'<br/>If not specified, defaults to 100"
1002
  msgstr ""
1003
 
1004
- #: admin/manage-fields.php:196
1005
  msgid "Date-format"
1006
  msgstr ""
1007
 
1008
- #: admin/manage-fields.php:196
1009
  msgid "Specify the format of the date when using Datepicker<br/>Valid options: mm/dd/yy, mm/yy/dd, dd/yy/mm, dd/mm/yy, yy/dd/mm, yy/mm/dd, mm-dd-yy, yy-mm-dd, D, dd M yy, D, d M y, DD, dd-M-y, D, d M yy, mm/yy, mm/dd, dd/mm, @<br/>If not specified, defaults to mm/dd/yy<br/>ATTENTION: if you plan to use this field for sorting, please make sure to use year first, then month, and day last."
1010
  msgstr ""
1011
 
1012
- #: admin/manage-fields.php:197
1013
  msgid "Terms of Agreement"
1014
  msgstr ""
1015
 
1016
- #: admin/manage-fields.php:197
1017
  msgid "Enter a detailed description of the terms of agreement for the user to read.<br/>Links can be inserted by using standard HTML syntax: &lt;a href=\"custom_url\"&gt;custom_text&lt;/a&gt;"
1018
  msgstr ""
1019
 
1020
- #: admin/manage-fields.php:198
1021
  msgid "Options"
1022
  msgstr ""
1023
 
1024
- #: admin/manage-fields.php:198
1025
  msgid "Enter a comma separated list of values<br/>This can be anything, as it is hidden from the user, but should not contain special characters or apostrophes"
1026
  msgstr ""
1027
 
1028
- #: admin/manage-fields.php:199, add-ons-free/labels-edit/labels-edit.php:373
1029
  msgid "Labels"
1030
  msgstr ""
1031
 
1032
- #: admin/manage-fields.php:199
1033
  msgid "Enter a comma separated list of labels<br/>Visible for the user"
1034
  msgstr ""
1035
 
1036
- #: admin/manage-fields.php:200
1037
  msgid "reCAPTCHA Type"
1038
  msgstr ""
1039
 
1040
- #: admin/manage-fields.php:200
1041
  msgid "Choose the <a href=\"https://developers.google.com/recaptcha/docs/versions\" target=\"_blank\">type of reCAPTCHA</a> you wish to add to this site.<br/>Please note that the Invisible reCAPTCHA is a type of reCAPTCHA v2."
1042
  msgstr ""
1043
 
1044
- #: admin/manage-fields.php:201
1045
  msgid "Site Key"
1046
  msgstr ""
1047
 
1048
- #: admin/manage-fields.php:201
1049
  msgid "The site key from Google, <a href=\"https://www.google.com/recaptcha/admin/create\" target=\"_blank\">https://www.google.com/recaptcha/admin/create</a>"
1050
  msgstr ""
1051
 
1052
- #: admin/manage-fields.php:202
1053
  msgid "Secret Key"
1054
  msgstr ""
1055
 
1056
- #: admin/manage-fields.php:202
1057
  msgid "The secret key from Google, <a href=\"https://www.google.com/recaptcha/admin/create\" target=\"_blank\">https://www.google.com/recaptcha/admin/create</a>"
1058
  msgstr ""
1059
 
1060
- #: admin/manage-fields.php:203
1061
  msgid "Display on PB forms"
1062
  msgstr ""
1063
 
1064
- #: admin/manage-fields.php:203
1065
  msgid "PB Login"
1066
  msgstr ""
1067
 
1068
- #: admin/manage-fields.php:203
1069
  msgid "PB Register"
1070
  msgstr ""
1071
 
1072
- #: admin/manage-fields.php:203
1073
  msgid "PB Recover Password"
1074
  msgstr ""
1075
 
1076
- #: admin/manage-fields.php:203
1077
  msgid "Select on which Profile Builder forms to display reCAPTCHA"
1078
  msgstr ""
1079
 
1080
- #: admin/manage-fields.php:204
1081
  msgid "Display on default WP forms"
1082
  msgstr ""
1083
 
1084
- #: admin/manage-fields.php:204
1085
  msgid "Default WP Login"
1086
  msgstr ""
1087
 
1088
- #: admin/manage-fields.php:204
1089
  msgid "Default WP Register"
1090
  msgstr ""
1091
 
1092
- #: admin/manage-fields.php:204
1093
  msgid "Default WP Recover Password"
1094
  msgstr ""
1095
 
1096
- #: admin/manage-fields.php:204
1097
  msgid "Select on which default WP forms to display reCAPTCHA"
1098
  msgstr ""
1099
 
1100
- #: admin/manage-fields.php:205
1101
  msgid "User Roles"
1102
  msgstr ""
1103
 
1104
- #: admin/manage-fields.php:205
1105
  msgid "Select which user roles to show to the user ( drag and drop to re-order )"
1106
  msgstr ""
1107
 
1108
- #: admin/manage-fields.php:206
1109
  msgid "Display on Edit Profile"
1110
  msgstr ""
1111
 
1112
- #: admin/manage-fields.php:206
1113
  msgid "Check if you want the select user role field to appear on Edit Profile forms"
1114
  msgstr ""
1115
 
1116
- #: admin/manage-fields.php:207
1117
  msgid "User Roles Order"
1118
  msgstr ""
1119
 
1120
- #: admin/manage-fields.php:207
1121
  msgid "Save the user role order from the user roles checkboxes"
1122
  msgstr ""
1123
 
1124
- #: admin/manage-fields.php:208
1125
  msgid "Default Value"
1126
  msgstr ""
1127
 
1128
- #: admin/manage-fields.php:208
1129
  msgid "Default value of the field"
1130
  msgstr ""
1131
 
1132
- #: admin/manage-fields.php:209, admin/manage-fields.php:211, admin/manage-fields.php:212, admin/manage-fields.php:213
1133
  msgid "Default Option"
1134
  msgstr ""
1135
 
1136
- #: admin/manage-fields.php:209
1137
  msgid "Specify the option which should be selected by default"
1138
  msgstr ""
1139
 
1140
- #: admin/manage-fields.php:210
1141
  msgid "Default Option(s)"
1142
  msgstr ""
1143
 
1144
- #: admin/manage-fields.php:210
1145
  msgid "Specify the option which should be checked by default<br/>If there are multiple values, separate them with a ',' (comma)"
1146
  msgstr ""
1147
 
1148
- #: admin/manage-fields.php:211, admin/manage-fields.php:212, admin/manage-fields.php:213
1149
  msgid "Default option of the field"
1150
  msgstr ""
1151
 
1152
- #: admin/manage-fields.php:214
1153
  msgid "Show Currency Symbol"
1154
  msgstr ""
1155
 
1156
- #: admin/manage-fields.php:214
1157
  msgid "Whether the currency symbol should be displayed after the currency name in the select option."
1158
  msgstr ""
1159
 
1160
- #: admin/manage-fields.php:215
1161
  msgid "Show Post Type"
1162
  msgstr ""
1163
 
1164
- #: admin/manage-fields.php:215
1165
  msgid "Posts from what post type will be displayed in the select."
1166
  msgstr ""
1167
 
1168
- #: admin/manage-fields.php:216
1169
  msgid "Allowable Values"
1170
  msgstr ""
1171
 
1172
- #: admin/manage-fields.php:216
1173
  msgid "Enter a comma separated list of possible values. Upon registration if the value provided by the user does not match one of these values, the user will not be registered."
1174
  msgstr ""
1175
 
1176
- #: admin/manage-fields.php:217
1177
  msgid "Error Message"
1178
  msgstr ""
1179
 
1180
- #: admin/manage-fields.php:217
1181
  msgid "Set a custom error message that will be displayed to the user."
1182
  msgstr ""
1183
 
1184
- #: admin/manage-fields.php:218
1185
  msgid "Time Format"
1186
  msgstr ""
1187
 
1188
- #: admin/manage-fields.php:218
1189
  msgid "Specify the time format."
1190
  msgstr ""
1191
 
1192
- #: admin/manage-fields.php:219
1193
  msgid "Google Maps API Key"
1194
  msgstr ""
1195
 
1196
- #: admin/manage-fields.php:219
1197
  msgid "Enter your Google Maps API key ( <a href=\"https://console.developers.google.com/flows/enableapi?apiid=maps_backend\" target=\"_blank\">Get your API key</a> ). If more than one map fields are added to a form the API key from the first map displayed will be used."
1198
  msgstr ""
1199
 
1200
- #: admin/manage-fields.php:224
1201
  msgid "Default Latitude"
1202
  msgstr ""
1203
 
1204
- #: admin/manage-fields.php:225
1205
  msgid "The latitude at which the map should be displayed when no pins are attached."
1206
  msgstr ""
1207
 
1208
- #: admin/manage-fields.php:231
1209
  msgid "Default Longitude"
1210
  msgstr ""
1211
 
1212
- #: admin/manage-fields.php:232
1213
  msgid "The longitude at which the map should be displayed when no pins are attached."
1214
  msgstr ""
1215
 
1216
- #: admin/manage-fields.php:238
1217
  msgid "Default Zoom Level"
1218
  msgstr ""
1219
 
1220
- #: admin/manage-fields.php:239
1221
  msgid "Add a number from 0 to 19. The higher the number the higher the zoom."
1222
  msgstr ""
1223
 
1224
- #: admin/manage-fields.php:243
1225
  msgid "Map Height"
1226
  msgstr ""
1227
 
1228
- #: admin/manage-fields.php:243
1229
  msgid "The height of the map."
1230
  msgstr ""
1231
 
1232
- #: admin/manage-fields.php:244
1233
  msgid "Default Content"
1234
  msgstr ""
1235
 
1236
- #: admin/manage-fields.php:244
1237
  msgid "Default value of the textarea"
1238
  msgstr ""
1239
 
1240
- #: admin/manage-fields.php:245
1241
  msgid "HTML Content"
1242
  msgstr ""
1243
 
1244
- #: admin/manage-fields.php:245
1245
  msgid "Add your HTML (or text) content"
1246
  msgstr ""
1247
 
1248
- #: admin/manage-fields.php:246
1249
  msgid "Phone Format"
1250
  msgstr ""
1251
 
1252
- #: admin/manage-fields.php:246
1253
  msgid "You can use: # for numbers, parentheses ( ), - sign, + sign, dot . and spaces."
1254
  msgstr ""
1255
 
1256
- #: admin/manage-fields.php:246
1257
  msgid "Eg. (###) ###-####"
1258
  msgstr ""
1259
 
1260
- #: admin/manage-fields.php:246
1261
  msgid "Empty field won't check for correct phone number."
1262
  msgstr ""
1263
 
1264
- #: admin/manage-fields.php:247
1265
  msgid "Heading Tag"
1266
  msgstr ""
1267
 
1268
- #: admin/manage-fields.php:247
1269
  msgid "Change heading field size on front-end forms"
1270
  msgstr ""
1271
 
1272
- #: admin/manage-fields.php:248
1273
  msgid "Min Number Value"
1274
  msgstr ""
1275
 
1276
- #: admin/manage-fields.php:248
1277
  msgid "Min allowed number value (0 to allow only positive numbers)"
1278
  msgstr ""
1279
 
1280
- #: admin/manage-fields.php:248
1281
  msgid "Leave it empty for no min value"
1282
  msgstr ""
1283
 
1284
- #: admin/manage-fields.php:249
1285
  msgid "Max Number Value"
1286
  msgstr ""
1287
 
1288
- #: admin/manage-fields.php:249
1289
  msgid "Max allowed number value (0 to allow only negative numbers)"
1290
  msgstr ""
1291
 
1292
- #: admin/manage-fields.php:249
1293
  msgid "Leave it empty for no max value"
1294
  msgstr ""
1295
 
1296
- #: admin/manage-fields.php:250
1297
  msgid "Number Step Value"
1298
  msgstr ""
1299
 
1300
- #: admin/manage-fields.php:250
1301
  msgid "Step value 1 to allow only integers, 0.1 to allow integers and numbers with 1 decimal"
1302
  msgstr ""
1303
 
1304
- #: admin/manage-fields.php:250
1305
  msgid "To allow multiple decimals use for eg. 0.01 (for 2 deciamls) and so on"
1306
  msgstr ""
1307
 
1308
- #: admin/manage-fields.php:250
1309
  msgid "You can also use step value to specify the legal number intervals (eg. step value 2 will allow only -4, -2, 0, 2 and so on)"
1310
  msgstr ""
1311
 
1312
- #: admin/manage-fields.php:250
1313
  msgid "Leave it empty for no restriction"
1314
  msgstr ""
1315
 
1316
- #: admin/manage-fields.php:251, add-ons-advanced/woocommerce/index.php:261
1317
  msgid "Required"
1318
  msgstr ""
1319
 
1320
- #: admin/manage-fields.php:251
1321
  msgid "Whether the field is required or not"
1322
  msgstr ""
1323
 
1324
- #: admin/manage-fields.php:252
1325
  msgid "Overwrite Existing"
1326
  msgstr ""
1327
 
1328
- #: admin/manage-fields.php:252
1329
  msgid "Selecting 'Yes' will add the field to the list, but will overwrite any other field in the database that has the same meta-name<br/>Use this at your own risk"
1330
  msgstr ""
1331
 
1332
- #: admin/manage-fields.php:258
1333
  msgid "POIs Load Type"
1334
  msgstr ""
1335
 
1336
- #: admin/manage-fields.php:260
1337
  msgid "POIs of the listed users (as filtered & paginated)"
1338
  msgstr ""
1339
 
1340
- #: admin/manage-fields.php:261
1341
  msgid "POIs of all the users for the filter* (no pagination)"
1342
  msgstr ""
1343
 
1344
- #: admin/manage-fields.php:264
1345
  msgid "This option allows you to load on a single map the POIs for all users, or just these for the listed ones (this will take into account the filters and the faceted menus). *Please use this feature wisely, it will impact the performance."
1346
  msgstr ""
1347
 
1348
- #: admin/manage-fields.php:271
1349
  msgid "POI Bubble Info"
1350
  msgstr ""
1351
 
1352
- #: admin/manage-fields.php:274
1353
  msgid "Select the attributes to be listed inside the POI bubble."
1354
  msgstr ""
1355
 
1356
- #: admin/manage-fields.php:285
1357
  msgid "Number of Users per Map Iteration"
1358
  msgstr ""
1359
 
1360
- #: admin/manage-fields.php:286
1361
  msgid "When loading the map of all users with no pagination, the map script will iterate multiple times and will expose gradually POIs on the map, until all the POIs for the users that match the criteria will be added on the map (think of this as of pagination for the map POIs). The smaller the number of users per iteration, the fastest the iteration response will be, but for a large number of users, the map script will iterate multiple times. Setting a higher limit will decrease the performance, but might produce a smaller number of iterations. <br><br><b>Please adjust this value to your hosting capabilities, and make sure that the value you set is the best for performance.</b> We recommend a <b>maximum</b> value of 300."
1362
  msgstr ""
1363
 
1364
- #: admin/manage-fields.php:291
1365
  msgid "Maximum Selections"
1366
  msgstr ""
1367
 
1368
- #: admin/manage-fields.php:291
1369
  msgid "Select2 multi-value select boxes can set restrictions regarding the maximum number of options selected."
1370
  msgstr ""
1371
 
1372
- #: admin/manage-fields.php:292
1373
  msgid "User Inputted Options"
1374
  msgstr ""
1375
 
1376
- #: admin/manage-fields.php:292
1377
  msgid "Check this to allow users to create their own options beside the pre-existing ones."
1378
  msgstr ""
1379
 
1380
- #: admin/manage-fields.php:299
1381
  msgid "Form Field Properties"
1382
  msgstr ""
1383
 
1384
- #: admin/manage-fields.php:313
1385
  msgid "Registration & Edit Profile Forms"
1386
  msgstr ""
1387
 
1388
- #: admin/manage-fields.php:332, add-ons-advanced/buddypress/buddypress-activator.php:48, add-ons-advanced/buddypress/index.php:481, add-ons-advanced/campaign-monitor/admin/widget.php:226, add-ons-advanced/campaign-monitor/admin/widget.php:228
1389
  msgid "Name"
1390
  msgstr ""
1391
 
1392
- #: admin/manage-fields.php:333
1393
  msgid "Usernames cannot be changed."
1394
  msgstr ""
1395
 
1396
- #: admin/manage-fields.php:334, add-ons/user-listing/userlisting.php:894, add-ons-advanced/woocommerce/billing-fields.php:6, add-ons-advanced/woocommerce/shipping-fields.php:6, admin/advanced-settings/includes/views/view-fields.php:130
1397
  msgid "First Name"
1398
  msgstr ""
1399
 
1400
- #: admin/manage-fields.php:335, add-ons/user-listing/userlisting.php:897, add-ons-advanced/woocommerce/billing-fields.php:7, add-ons-advanced/woocommerce/shipping-fields.php:7, admin/advanced-settings/includes/views/view-fields.php:136
1401
  msgid "Last Name"
1402
  msgstr ""
1403
 
1404
- #: admin/manage-fields.php:336, add-ons/user-listing/userlisting.php:921, add-ons/user-listing/userlisting.php:2564
1405
  msgid "Nickname"
1406
  msgstr ""
1407
 
1408
- #: admin/manage-fields.php:337
1409
  msgid "Display name publicly as"
1410
  msgstr ""
1411
 
1412
- #: admin/manage-fields.php:338, add-ons-advanced/buddypress/buddypress-activator.php:71, add-ons-advanced/buddypress/index.php:504
1413
  msgid "Contact Info"
1414
  msgstr ""
1415
 
1416
- #: admin/manage-fields.php:339
1417
  msgid "E-mail"
1418
  msgstr ""
1419
 
1420
- #: admin/manage-fields.php:340, add-ons/email-customizer/email-customizer.php:33, add-ons/user-listing/userlisting.php:122, add-ons/user-listing/userlisting.php:903, add-ons/user-listing/userlisting.php:2558
1421
  msgid "Website"
1422
  msgstr ""
1423
 
1424
- #: admin/manage-fields.php:344
1425
  msgid "AIM"
1426
  msgstr ""
1427
 
1428
- #: admin/manage-fields.php:345
1429
  msgid "Yahoo IM"
1430
  msgstr ""
1431
 
1432
- #: admin/manage-fields.php:346
1433
  msgid "Jabber / Google Talk"
1434
  msgstr ""
1435
 
1436
- #: admin/manage-fields.php:349, add-ons-advanced/buddypress/buddypress-activator.php:78, add-ons-advanced/buddypress/index.php:511
1437
  msgid "About Yourself"
1438
  msgstr ""
1439
 
1440
- #: admin/manage-fields.php:350, add-ons/user-listing/userlisting.php:125, add-ons/user-listing/userlisting.php:906, add-ons/user-listing/userlisting.php:2559
1441
  msgid "Biographical Info"
1442
  msgstr ""
1443
 
1444
- #: admin/manage-fields.php:350
1445
  msgid "Share a little biographical information to fill out your profile. This may be shown publicly."
1446
  msgstr ""
1447
 
1448
- #: admin/manage-fields.php:351, front-end/login.php:121, front-end/recover.php:72, add-ons/email-customizer/email-customizer.php:30
1449
  msgid "Password"
1450
  msgstr ""
1451
 
1452
- #: admin/manage-fields.php:351
1453
  msgid "Type your password."
1454
  msgstr ""
1455
 
1456
- #: admin/manage-fields.php:352, front-end/recover.php:73
1457
  msgid "Repeat Password"
1458
  msgstr ""
1459
 
1460
- #: admin/manage-fields.php:352
1461
  msgid "Type your password again. "
1462
  msgstr ""
1463
 
1464
- #: admin/manage-fields.php:354
1465
  msgid "Blog Details"
1466
  msgstr ""
1467
 
1468
- #: admin/manage-fields.php:413
1469
  msgid "Select a Country"
1470
  msgstr ""
1471
 
1472
- #: admin/manage-fields.php:414
1473
  msgid "Afghanistan"
1474
  msgstr ""
1475
 
1476
- #: admin/manage-fields.php:415
1477
  msgid "Aland Islands"
1478
  msgstr ""
1479
 
1480
- #: admin/manage-fields.php:416
1481
  msgid "Albania"
1482
  msgstr ""
1483
 
1484
- #: admin/manage-fields.php:417
1485
  msgid "Algeria"
1486
  msgstr ""
1487
 
1488
- #: admin/manage-fields.php:418
1489
  msgid "American Samoa"
1490
  msgstr ""
1491
 
1492
- #: admin/manage-fields.php:419
1493
  msgid "Andorra"
1494
  msgstr ""
1495
 
1496
- #: admin/manage-fields.php:420
1497
  msgid "Angola"
1498
  msgstr ""
1499
 
1500
- #: admin/manage-fields.php:421
1501
  msgid "Anguilla"
1502
  msgstr ""
1503
 
1504
- #: admin/manage-fields.php:422
1505
  msgid "Antarctica"
1506
  msgstr ""
1507
 
1508
- #: admin/manage-fields.php:423
1509
  msgid "Antigua and Barbuda"
1510
  msgstr ""
1511
 
1512
- #: admin/manage-fields.php:424
1513
  msgid "Argentina"
1514
  msgstr ""
1515
 
1516
- #: admin/manage-fields.php:425
1517
  msgid "Armenia"
1518
  msgstr ""
1519
 
1520
- #: admin/manage-fields.php:426
1521
  msgid "Aruba"
1522
  msgstr ""
1523
 
1524
- #: admin/manage-fields.php:427
1525
  msgid "Australia"
1526
  msgstr ""
1527
 
1528
- #: admin/manage-fields.php:428
1529
  msgid "Austria"
1530
  msgstr ""
1531
 
1532
- #: admin/manage-fields.php:429
1533
  msgid "Azerbaijan"
1534
  msgstr ""
1535
 
1536
- #: admin/manage-fields.php:430
1537
  msgid "Bahamas"
1538
  msgstr ""
1539
 
1540
- #: admin/manage-fields.php:431
1541
  msgid "Bahrain"
1542
  msgstr ""
1543
 
1544
- #: admin/manage-fields.php:432
1545
  msgid "Bangladesh"
1546
  msgstr ""
1547
 
1548
- #: admin/manage-fields.php:433
1549
  msgid "Barbados"
1550
  msgstr ""
1551
 
1552
- #: admin/manage-fields.php:434
1553
  msgid "Belarus"
1554
  msgstr ""
1555
 
1556
- #: admin/manage-fields.php:435
1557
  msgid "Belgium"
1558
  msgstr ""
1559
 
1560
- #: admin/manage-fields.php:436
1561
  msgid "Belize"
1562
  msgstr ""
1563
 
1564
- #: admin/manage-fields.php:437
1565
  msgid "Benin"
1566
  msgstr ""
1567
 
1568
- #: admin/manage-fields.php:438
1569
  msgid "Bermuda"
1570
  msgstr ""
1571
 
1572
- #: admin/manage-fields.php:439
1573
  msgid "Bhutan"
1574
  msgstr ""
1575
 
1576
- #: admin/manage-fields.php:440
1577
  msgid "Bolivia"
1578
  msgstr ""
1579
 
1580
- #: admin/manage-fields.php:441
1581
  msgid "Bonaire, Saint Eustatius and Saba"
1582
  msgstr ""
1583
 
1584
- #: admin/manage-fields.php:442
1585
  msgid "Bosnia and Herzegovina"
1586
  msgstr ""
1587
 
1588
- #: admin/manage-fields.php:443
1589
  msgid "Botswana"
1590
  msgstr ""
1591
 
1592
- #: admin/manage-fields.php:444
1593
  msgid "Bouvet Island"
1594
  msgstr ""
1595
 
1596
- #: admin/manage-fields.php:445
1597
  msgid "Brazil"
1598
  msgstr ""
1599
 
1600
- #: admin/manage-fields.php:446
1601
  msgid "British Indian Ocean Territory"
1602
  msgstr ""
1603
 
1604
- #: admin/manage-fields.php:447
1605
  msgid "British Virgin Islands"
1606
  msgstr ""
1607
 
1608
- #: admin/manage-fields.php:448
1609
  msgid "Brunei"
1610
  msgstr ""
1611
 
1612
- #: admin/manage-fields.php:449
1613
  msgid "Bulgaria"
1614
  msgstr ""
1615
 
1616
- #: admin/manage-fields.php:450
1617
  msgid "Burkina Faso"
1618
  msgstr ""
1619
 
1620
- #: admin/manage-fields.php:451
1621
  msgid "Burundi"
1622
  msgstr ""
1623
 
1624
- #: admin/manage-fields.php:452
1625
  msgid "Cambodia"
1626
  msgstr ""
1627
 
1628
- #: admin/manage-fields.php:453
1629
  msgid "Cameroon"
1630
  msgstr ""
1631
 
1632
- #: admin/manage-fields.php:454
1633
  msgid "Canada"
1634
  msgstr ""
1635
 
1636
- #: admin/manage-fields.php:455
1637
  msgid "Cape Verde"
1638
  msgstr ""
1639
 
1640
- #: admin/manage-fields.php:456
1641
  msgid "Cayman Islands"
1642
  msgstr ""
1643
 
1644
- #: admin/manage-fields.php:457
1645
  msgid "Central African Republic"
1646
  msgstr ""
1647
 
1648
- #: admin/manage-fields.php:458
1649
  msgid "Chad"
1650
  msgstr ""
1651
 
1652
- #: admin/manage-fields.php:459
1653
  msgid "Chile"
1654
  msgstr ""
1655
 
1656
- #: admin/manage-fields.php:460
1657
  msgid "China"
1658
  msgstr ""
1659
 
1660
- #: admin/manage-fields.php:461
1661
  msgid "Christmas Island"
1662
  msgstr ""
1663
 
1664
- #: admin/manage-fields.php:462
1665
  msgid "Cocos Islands"
1666
  msgstr ""
1667
 
1668
- #: admin/manage-fields.php:463
1669
  msgid "Colombia"
1670
  msgstr ""
1671
 
1672
- #: admin/manage-fields.php:464
1673
  msgid "Comoros"
1674
  msgstr ""
1675
 
1676
- #: admin/manage-fields.php:465
1677
  msgid "Cook Islands"
1678
  msgstr ""
1679
 
1680
- #: admin/manage-fields.php:466
1681
  msgid "Costa Rica"
1682
  msgstr ""
1683
 
1684
- #: admin/manage-fields.php:467
1685
  msgid "Croatia"
1686
  msgstr ""
1687
 
1688
- #: admin/manage-fields.php:468
1689
  msgid "Cuba"
1690
  msgstr ""
1691
 
1692
- #: admin/manage-fields.php:469
1693
  msgid "Curacao"
1694
  msgstr ""
1695
 
1696
- #: admin/manage-fields.php:470
1697
  msgid "Cyprus"
1698
  msgstr ""
1699
 
1700
- #: admin/manage-fields.php:471
1701
  msgid "Czech Republic"
1702
  msgstr ""
1703
 
1704
- #: admin/manage-fields.php:472
1705
  msgid "Democratic Republic of the Congo"
1706
  msgstr ""
1707
 
1708
- #: admin/manage-fields.php:473
1709
  msgid "Denmark"
1710
  msgstr ""
1711
 
1712
- #: admin/manage-fields.php:474
1713
  msgid "Djibouti"
1714
  msgstr ""
1715
 
1716
- #: admin/manage-fields.php:475
1717
  msgid "Dominica"
1718
  msgstr ""
1719
 
1720
- #: admin/manage-fields.php:476
1721
  msgid "Dominican Republic"
1722
  msgstr ""
1723
 
1724
- #: admin/manage-fields.php:477
1725
  msgid "East Timor"
1726
  msgstr ""
1727
 
1728
- #: admin/manage-fields.php:478
1729
  msgid "Ecuador"
1730
  msgstr ""
1731
 
1732
- #: admin/manage-fields.php:479
1733
  msgid "Egypt"
1734
  msgstr ""
1735
 
1736
- #: admin/manage-fields.php:480
1737
  msgid "El Salvador"
1738
  msgstr ""
1739
 
1740
- #: admin/manage-fields.php:481
1741
  msgid "Equatorial Guinea"
1742
  msgstr ""
1743
 
1744
- #: admin/manage-fields.php:482
1745
  msgid "Eritrea"
1746
  msgstr ""
1747
 
1748
- #: admin/manage-fields.php:483
1749
  msgid "Estonia"
1750
  msgstr ""
1751
 
1752
- #: admin/manage-fields.php:484
1753
  msgid "Ethiopia"
1754
  msgstr ""
1755
 
1756
- #: admin/manage-fields.php:485
1757
  msgid "Falkland Islands"
1758
  msgstr ""
1759
 
1760
- #: admin/manage-fields.php:486
1761
  msgid "Faroe Islands"
1762
  msgstr ""
1763
 
1764
- #: admin/manage-fields.php:487
1765
  msgid "Fiji"
1766
  msgstr ""
1767
 
1768
- #: admin/manage-fields.php:488
1769
  msgid "Finland"
1770
  msgstr ""
1771
 
1772
- #: admin/manage-fields.php:489
1773
  msgid "France"
1774
  msgstr ""
1775
 
1776
- #: admin/manage-fields.php:490
1777
  msgid "French Guiana"
1778
  msgstr ""
1779
 
1780
- #: admin/manage-fields.php:491
1781
  msgid "French Polynesia"
1782
  msgstr ""
1783
 
1784
- #: admin/manage-fields.php:492
1785
  msgid "French Southern Territories"
1786
  msgstr ""
1787
 
1788
- #: admin/manage-fields.php:493
1789
  msgid "Gabon"
1790
  msgstr ""
1791
 
1792
- #: admin/manage-fields.php:494
1793
  msgid "Gambia"
1794
  msgstr ""
1795
 
1796
- #: admin/manage-fields.php:495
1797
  msgid "Georgia"
1798
  msgstr ""
1799
 
1800
- #: admin/manage-fields.php:496
1801
  msgid "Germany"
1802
  msgstr ""
1803
 
1804
- #: admin/manage-fields.php:497
1805
  msgid "Ghana"
1806
  msgstr ""
1807
 
1808
- #: admin/manage-fields.php:498
1809
  msgid "Gibraltar"
1810
  msgstr ""
1811
 
1812
- #: admin/manage-fields.php:499
1813
  msgid "Greece"
1814
  msgstr ""
1815
 
1816
- #: admin/manage-fields.php:500
1817
  msgid "Greenland"
1818
  msgstr ""
1819
 
1820
- #: admin/manage-fields.php:501
1821
  msgid "Grenada"
1822
  msgstr ""
1823
 
1824
- #: admin/manage-fields.php:502
1825
  msgid "Guadeloupe"
1826
  msgstr ""
1827
 
1828
- #: admin/manage-fields.php:503
1829
  msgid "Guam"
1830
  msgstr ""
1831
 
1832
- #: admin/manage-fields.php:504
1833
  msgid "Guatemala"
1834
  msgstr ""
1835
 
1836
- #: admin/manage-fields.php:505
1837
  msgid "Guernsey"
1838
  msgstr ""
1839
 
1840
- #: admin/manage-fields.php:506
1841
  msgid "Guinea"
1842
  msgstr ""
1843
 
1844
- #: admin/manage-fields.php:507
1845
  msgid "Guinea-Bissau"
1846
  msgstr ""
1847
 
1848
- #: admin/manage-fields.php:508
1849
  msgid "Guyana"
1850
  msgstr ""
1851
 
1852
- #: admin/manage-fields.php:509
1853
  msgid "Haiti"
1854
  msgstr ""
1855
 
1856
- #: admin/manage-fields.php:510
1857
  msgid "Heard Island and McDonald Islands"
1858
  msgstr ""
1859
 
1860
- #: admin/manage-fields.php:511
1861
  msgid "Honduras"
1862
  msgstr ""
1863
 
1864
- #: admin/manage-fields.php:512
1865
  msgid "Hong Kong"
1866
  msgstr ""
1867
 
1868
- #: admin/manage-fields.php:513
1869
  msgid "Hungary"
1870
  msgstr ""
1871
 
1872
- #: admin/manage-fields.php:514
1873
  msgid "Iceland"
1874
  msgstr ""
1875
 
1876
- #: admin/manage-fields.php:515
1877
  msgid "India"
1878
  msgstr ""
1879
 
1880
- #: admin/manage-fields.php:516
1881
  msgid "Indonesia"
1882
  msgstr ""
1883
 
1884
- #: admin/manage-fields.php:517
1885
  msgid "Iran"
1886
  msgstr ""
1887
 
1888
- #: admin/manage-fields.php:518
1889
  msgid "Iraq"
1890
  msgstr ""
1891
 
1892
- #: admin/manage-fields.php:519
1893
  msgid "Ireland"
1894
  msgstr ""
1895
 
1896
- #: admin/manage-fields.php:520
1897
  msgid "Isle of Man"
1898
  msgstr ""
1899
 
1900
- #: admin/manage-fields.php:521
1901
  msgid "Israel"
1902
  msgstr ""
1903
 
1904
- #: admin/manage-fields.php:522
1905
  msgid "Italy"
1906
  msgstr ""
1907
 
1908
- #: admin/manage-fields.php:523
1909
  msgid "Ivory Coast"
1910
  msgstr ""
1911
 
1912
- #: admin/manage-fields.php:524
1913
  msgid "Jamaica"
1914
  msgstr ""
1915
 
1916
- #: admin/manage-fields.php:525
1917
  msgid "Japan"
1918
  msgstr ""
1919
 
1920
- #: admin/manage-fields.php:526
1921
  msgid "Jersey"
1922
  msgstr ""
1923
 
1924
- #: admin/manage-fields.php:527
1925
  msgid "Jordan"
1926
  msgstr ""
1927
 
1928
- #: admin/manage-fields.php:528
1929
  msgid "Kazakhstan"
1930
  msgstr ""
1931
 
1932
- #: admin/manage-fields.php:529
1933
  msgid "Kenya"
1934
  msgstr ""
1935
 
1936
- #: admin/manage-fields.php:530
1937
  msgid "Kiribati"
1938
  msgstr ""
1939
 
1940
- #: admin/manage-fields.php:531
1941
  msgid "Kosovo"
1942
  msgstr ""
1943
 
1944
- #: admin/manage-fields.php:532
1945
  msgid "Kuwait"
1946
  msgstr ""
1947
 
1948
- #: admin/manage-fields.php:533
1949
  msgid "Kyrgyzstan"
1950
  msgstr ""
1951
 
1952
- #: admin/manage-fields.php:534
1953
  msgid "Laos"
1954
  msgstr ""
1955
 
1956
- #: admin/manage-fields.php:535
1957
  msgid "Latvia"
1958
  msgstr ""
1959
 
1960
- #: admin/manage-fields.php:536
1961
  msgid "Lebanon"
1962
  msgstr ""
1963
 
1964
- #: admin/manage-fields.php:537
1965
  msgid "Lesotho"
1966
  msgstr ""
1967
 
1968
- #: admin/manage-fields.php:538
1969
  msgid "Liberia"
1970
  msgstr ""
1971
 
1972
- #: admin/manage-fields.php:539
1973
  msgid "Libya"
1974
  msgstr ""
1975
 
1976
- #: admin/manage-fields.php:540
1977
  msgid "Liechtenstein"
1978
  msgstr ""
1979
 
1980
- #: admin/manage-fields.php:541
1981
  msgid "Lithuania"
1982
  msgstr ""
1983
 
1984
- #: admin/manage-fields.php:542
1985
  msgid "Luxembourg"
1986
  msgstr ""
1987
 
1988
- #: admin/manage-fields.php:543
1989
  msgid "Macao"
1990
  msgstr ""
1991
 
1992
- #: admin/manage-fields.php:544
1993
  msgid "Macedonia"
1994
  msgstr ""
1995
 
1996
- #: admin/manage-fields.php:545
1997
  msgid "Madagascar"
1998
  msgstr ""
1999
 
2000
- #: admin/manage-fields.php:546
2001
  msgid "Malawi"
2002
  msgstr ""
2003
 
2004
- #: admin/manage-fields.php:547
2005
  msgid "Malaysia"
2006
  msgstr ""
2007
 
2008
- #: admin/manage-fields.php:548
2009
  msgid "Maldives"
2010
  msgstr ""
2011
 
2012
- #: admin/manage-fields.php:549
2013
  msgid "Mali"
2014
  msgstr ""
2015
 
2016
- #: admin/manage-fields.php:550
2017
  msgid "Malta"
2018
  msgstr ""
2019
 
2020
- #: admin/manage-fields.php:551
2021
  msgid "Marshall Islands"
2022
  msgstr ""
2023
 
2024
- #: admin/manage-fields.php:552
2025
  msgid "Martinique"
2026
  msgstr ""
2027
 
2028
- #: admin/manage-fields.php:553
2029
  msgid "Mauritania"
2030
  msgstr ""
2031
 
2032
- #: admin/manage-fields.php:554
2033
  msgid "Mauritius"
2034
  msgstr ""
2035
 
2036
- #: admin/manage-fields.php:555
2037
  msgid "Mayotte"
2038
  msgstr ""
2039
 
2040
- #: admin/manage-fields.php:556
2041
  msgid "Mexico"
2042
  msgstr ""
2043
 
2044
- #: admin/manage-fields.php:557
2045
  msgid "Micronesia"
2046
  msgstr ""
2047
 
2048
- #: admin/manage-fields.php:558
2049
  msgid "Moldova"
2050
  msgstr ""
2051
 
2052
- #: admin/manage-fields.php:559
2053
  msgid "Monaco"
2054
  msgstr ""
2055
 
2056
- #: admin/manage-fields.php:560
2057
  msgid "Mongolia"
2058
  msgstr ""
2059
 
2060
- #: admin/manage-fields.php:561
2061
  msgid "Montenegro"
2062
  msgstr ""
2063
 
2064
- #: admin/manage-fields.php:562
2065
  msgid "Montserrat"
2066
  msgstr ""
2067
 
2068
- #: admin/manage-fields.php:563
2069
  msgid "Morocco"
2070
  msgstr ""
2071
 
2072
- #: admin/manage-fields.php:564
2073
  msgid "Mozambique"
2074
  msgstr ""
2075
 
2076
- #: admin/manage-fields.php:565
2077
  msgid "Myanmar"
2078
  msgstr ""
2079
 
2080
- #: admin/manage-fields.php:566
2081
  msgid "Namibia"
2082
  msgstr ""
2083
 
2084
- #: admin/manage-fields.php:567
2085
  msgid "Nauru"
2086
  msgstr ""
2087
 
2088
- #: admin/manage-fields.php:568
2089
  msgid "Nepal"
2090
  msgstr ""
2091
 
2092
- #: admin/manage-fields.php:569
2093
  msgid "Netherlands"
2094
  msgstr ""
2095
 
2096
- #: admin/manage-fields.php:570
2097
  msgid "New Caledonia"
2098
  msgstr ""
2099
 
2100
- #: admin/manage-fields.php:571
2101
  msgid "New Zealand"
2102
  msgstr ""
2103
 
2104
- #: admin/manage-fields.php:572
2105
  msgid "Nicaragua"
2106
  msgstr ""
2107
 
2108
- #: admin/manage-fields.php:573
2109
  msgid "Niger"
2110
  msgstr ""
2111
 
2112
- #: admin/manage-fields.php:574
2113
  msgid "Nigeria"
2114
  msgstr ""
2115
 
2116
- #: admin/manage-fields.php:575
2117
  msgid "Niue"
2118
  msgstr ""
2119
 
2120
- #: admin/manage-fields.php:576
2121
  msgid "Norfolk Island"
2122
  msgstr ""
2123
 
2124
- #: admin/manage-fields.php:577
2125
  msgid "North Korea"
2126
  msgstr ""
2127
 
2128
- #: admin/manage-fields.php:578
2129
  msgid "Northern Mariana Islands"
2130
  msgstr ""
2131
 
2132
- #: admin/manage-fields.php:579
2133
  msgid "Norway"
2134
  msgstr ""
2135
 
2136
- #: admin/manage-fields.php:580
2137
  msgid "Oman"
2138
  msgstr ""
2139
 
2140
- #: admin/manage-fields.php:581
2141
  msgid "Pakistan"
2142
  msgstr ""
2143
 
2144
- #: admin/manage-fields.php:582
2145
  msgid "Palau"
2146
  msgstr ""
2147
 
2148
- #: admin/manage-fields.php:583
2149
  msgid "Palestinian Territory"
2150
  msgstr ""
2151
 
2152
- #: admin/manage-fields.php:584
2153
  msgid "Panama"
2154
  msgstr ""
2155
 
2156
- #: admin/manage-fields.php:585
2157
  msgid "Papua New Guinea"
2158
  msgstr ""
2159
 
2160
- #: admin/manage-fields.php:586
2161
  msgid "Paraguay"
2162
  msgstr ""
2163
 
2164
- #: admin/manage-fields.php:587
2165
  msgid "Peru"
2166
  msgstr ""
2167
 
2168
- #: admin/manage-fields.php:588
2169
  msgid "Philippines"
2170
  msgstr ""
2171
 
2172
- #: admin/manage-fields.php:589
2173
  msgid "Pitcairn"
2174
  msgstr ""
2175
 
2176
- #: admin/manage-fields.php:590
2177
  msgid "Poland"
2178
  msgstr ""
2179
 
2180
- #: admin/manage-fields.php:591
2181
  msgid "Portugal"
2182
  msgstr ""
2183
 
2184
- #: admin/manage-fields.php:592
2185
  msgid "Puerto Rico"
2186
  msgstr ""
2187
 
2188
- #: admin/manage-fields.php:593
2189
  msgid "Qatar"
2190
  msgstr ""
2191
 
2192
- #: admin/manage-fields.php:594
2193
  msgid "Republic of the Congo"
2194
  msgstr ""
2195
 
2196
- #: admin/manage-fields.php:595
2197
  msgid "Reunion"
2198
  msgstr ""
2199
 
2200
- #: admin/manage-fields.php:596
2201
  msgid "Romania"
2202
  msgstr ""
2203
 
2204
- #: admin/manage-fields.php:597
2205
  msgid "Russia"
2206
  msgstr ""
2207
 
2208
- #: admin/manage-fields.php:598
2209
  msgid "Rwanda"
2210
  msgstr ""
2211
 
2212
- #: admin/manage-fields.php:599
2213
  msgid "Saint Barthelemy"
2214
  msgstr ""
2215
 
2216
- #: admin/manage-fields.php:600
2217
  msgid "Saint Helena"
2218
  msgstr ""
2219
 
2220
- #: admin/manage-fields.php:601
2221
  msgid "Saint Kitts and Nevis"
2222
  msgstr ""
2223
 
2224
- #: admin/manage-fields.php:602
2225
  msgid "Saint Lucia"
2226
  msgstr ""
2227
 
2228
- #: admin/manage-fields.php:603
2229
  msgid "Saint Martin"
2230
  msgstr ""
2231
 
2232
- #: admin/manage-fields.php:604
2233
  msgid "Saint Pierre and Miquelon"
2234
  msgstr ""
2235
 
2236
- #: admin/manage-fields.php:605
2237
  msgid "Saint Vincent and the Grenadines"
2238
  msgstr ""
2239
 
2240
- #: admin/manage-fields.php:606
2241
  msgid "Samoa"
2242
  msgstr ""
2243
 
2244
- #: admin/manage-fields.php:607
2245
  msgid "San Marino"
2246
  msgstr ""
2247
 
2248
- #: admin/manage-fields.php:608
2249
  msgid "Sao Tome and Principe"
2250
  msgstr ""
2251
 
2252
- #: admin/manage-fields.php:609
2253
  msgid "Saudi Arabia"
2254
  msgstr ""
2255
 
2256
- #: admin/manage-fields.php:610
2257
  msgid "Senegal"
2258
  msgstr ""
2259
 
2260
- #: admin/manage-fields.php:611
2261
  msgid "Serbia"
2262
  msgstr ""
2263
 
2264
- #: admin/manage-fields.php:612
2265
  msgid "Seychelles"
2266
  msgstr ""
2267
 
2268
- #: admin/manage-fields.php:613
2269
  msgid "Sierra Leone"
2270
  msgstr ""
2271
 
2272
- #: admin/manage-fields.php:614
2273
  msgid "Singapore"
2274
  msgstr ""
2275
 
2276
- #: admin/manage-fields.php:615
2277
  msgid "Sint Maarten"
2278
  msgstr ""
2279
 
2280
- #: admin/manage-fields.php:616
2281
  msgid "Slovakia"
2282
  msgstr ""
2283
 
2284
- #: admin/manage-fields.php:617
2285
  msgid "Slovenia"
2286
  msgstr ""
2287
 
2288
- #: admin/manage-fields.php:618
2289
  msgid "Solomon Islands"
2290
  msgstr ""
2291
 
2292
- #: admin/manage-fields.php:619
2293
  msgid "Somalia"
2294
  msgstr ""
2295
 
2296
- #: admin/manage-fields.php:620
2297
  msgid "South Africa"
2298
  msgstr ""
2299
 
2300
- #: admin/manage-fields.php:621
2301
  msgid "South Georgia and the South Sandwich Islands"
2302
  msgstr ""
2303
 
2304
- #: admin/manage-fields.php:622
2305
  msgid "South Korea"
2306
  msgstr ""
2307
 
2308
- #: admin/manage-fields.php:623
2309
  msgid "South Sudan"
2310
  msgstr ""
2311
 
2312
- #: admin/manage-fields.php:624
2313
  msgid "Spain"
2314
  msgstr ""
2315
 
2316
- #: admin/manage-fields.php:625
2317
  msgid "Sri Lanka"
2318
  msgstr ""
2319
 
2320
- #: admin/manage-fields.php:626
2321
  msgid "Sudan"
2322
  msgstr ""
2323
 
2324
- #: admin/manage-fields.php:627
2325
  msgid "Suriname"
2326
  msgstr ""
2327
 
2328
- #: admin/manage-fields.php:628
2329
  msgid "Svalbard and Jan Mayen"
2330
  msgstr ""
2331
 
2332
- #: admin/manage-fields.php:629
2333
  msgid "Swaziland"
2334
  msgstr ""
2335
 
2336
- #: admin/manage-fields.php:630
2337
  msgid "Sweden"
2338
  msgstr ""
2339
 
2340
- #: admin/manage-fields.php:631
2341
  msgid "Switzerland"
2342
  msgstr ""
2343
 
2344
- #: admin/manage-fields.php:632
2345
  msgid "Syria"
2346
  msgstr ""
2347
 
2348
- #: admin/manage-fields.php:633
2349
  msgid "Taiwan"
2350
  msgstr ""
2351
 
2352
- #: admin/manage-fields.php:634
2353
  msgid "Tajikistan"
2354
  msgstr ""
2355
 
2356
- #: admin/manage-fields.php:635
2357
  msgid "Tanzania"
2358
  msgstr ""
2359
 
2360
- #: admin/manage-fields.php:636
2361
  msgid "Thailand"
2362
  msgstr ""
2363
 
2364
- #: admin/manage-fields.php:637
2365
  msgid "Togo"
2366
  msgstr ""
2367
 
2368
- #: admin/manage-fields.php:638
2369
  msgid "Tokelau"
2370
  msgstr ""
2371
 
2372
- #: admin/manage-fields.php:639
2373
  msgid "Tonga"
2374
  msgstr ""
2375
 
2376
- #: admin/manage-fields.php:640
2377
  msgid "Trinidad and Tobago"
2378
  msgstr ""
2379
 
2380
- #: admin/manage-fields.php:641
2381
  msgid "Tunisia"
2382
  msgstr ""
2383
 
2384
- #: admin/manage-fields.php:642
2385
  msgid "Turkey"
2386
  msgstr ""
2387
 
2388
- #: admin/manage-fields.php:643
2389
  msgid "Turkmenistan"
2390
  msgstr ""
2391
 
2392
- #: admin/manage-fields.php:644
2393
  msgid "Turks and Caicos Islands"
2394
  msgstr ""
2395
 
2396
- #: admin/manage-fields.php:645
2397
  msgid "Tuvalu"
2398
  msgstr ""
2399
 
2400
- #: admin/manage-fields.php:646
2401
  msgid "U.S. Virgin Islands"
2402
  msgstr ""
2403
 
2404
- #: admin/manage-fields.php:647
2405
  msgid "Uganda"
2406
  msgstr ""
2407
 
2408
- #: admin/manage-fields.php:648
2409
  msgid "Ukraine"
2410
  msgstr ""
2411
 
2412
- #: admin/manage-fields.php:649
2413
  msgid "United Arab Emirates"
2414
  msgstr ""
2415
 
2416
- #: admin/manage-fields.php:650
2417
  msgid "United Kingdom"
2418
  msgstr ""
2419
 
2420
- #: admin/manage-fields.php:651
2421
  msgid "United States"
2422
  msgstr ""
2423
 
2424
- #: admin/manage-fields.php:652
2425
  msgid "United States Minor Outlying Islands"
2426
  msgstr ""
2427
 
2428
- #: admin/manage-fields.php:653
2429
  msgid "Uruguay"
2430
  msgstr ""
2431
 
2432
- #: admin/manage-fields.php:654
2433
  msgid "Uzbekistan"
2434
  msgstr ""
2435
 
2436
- #: admin/manage-fields.php:655
2437
  msgid "Vanuatu"
2438
  msgstr ""
2439
 
2440
- #: admin/manage-fields.php:656
2441
  msgid "Vatican"
2442
  msgstr ""
2443
 
2444
- #: admin/manage-fields.php:657
2445
  msgid "Venezuela"
2446
  msgstr ""
2447
 
2448
- #: admin/manage-fields.php:658
2449
  msgid "Vietnam"
2450
  msgstr ""
2451
 
2452
- #: admin/manage-fields.php:659
2453
  msgid "Wallis and Futuna"
2454
  msgstr ""
2455
 
2456
- #: admin/manage-fields.php:660
2457
  msgid "Western Sahara"
2458
  msgstr ""
2459
 
2460
- #: admin/manage-fields.php:661
2461
  msgid "Yemen"
2462
  msgstr ""
2463
 
2464
- #: admin/manage-fields.php:662
2465
  msgid "Zambia"
2466
  msgstr ""
2467
 
2468
- #: admin/manage-fields.php:663
2469
  msgid "Zimbabwe"
2470
  msgstr ""
2471
 
2472
- #: admin/manage-fields.php:696
2473
  msgid "Albania Lek"
2474
  msgstr ""
2475
 
2476
- #: admin/manage-fields.php:697
2477
  msgid "Afghanistan Afghani"
2478
  msgstr ""
2479
 
2480
- #: admin/manage-fields.php:698
2481
  msgid "Argentina Peso"
2482
  msgstr ""
2483
 
2484
- #: admin/manage-fields.php:699
2485
  msgid "Aruba Guilder"
2486
  msgstr ""
2487
 
2488
- #: admin/manage-fields.php:700
2489
  msgid "Australia Dollar"
2490
  msgstr ""
2491
 
2492
- #: admin/manage-fields.php:701
2493
  msgid "Azerbaijan New Manat"
2494
  msgstr ""
2495
 
2496
- #: admin/manage-fields.php:702
13
  "X-Poedit-SourceCharset: UTF-8\n"
14
  "Plural-Forms: nplurals=2; plural=(n != 1);\n"
15
 
16
+ #: index.php:370
17
  msgid "To enable updates, your licence needs to be renewed. Please go to the <a href=\"%s\">Cozmoslabs Account</a> page and login to renew."
18
  msgstr ""
19
 
20
+ #: index.php:366
21
  msgid "To enable updates, please enter your serial number on the <a href=\"%s\">Register Version</a> page. If you don't have a serial number, please see <a href=\"%s\" target=\"_blank\">details & pricing</a>."
22
  msgstr ""
23
 
45
  msgid "These Add-ons are available with the Pro and Unlimited license"
46
  msgstr ""
47
 
48
+ #: admin/add-ons.php:45, admin/basic-info.php:177
49
  msgid "Multiple Registration Forms"
50
  msgstr ""
51
 
57
  msgid "Multiple Edit Profile Forms"
58
  msgstr ""
59
 
60
+ #: admin/add-ons.php:53, admin/basic-info.php:182
61
  msgid "Allow different user roles to edit their specific information. Set up multiple edit-profile forms with different fields for certain user roles."
62
  msgstr ""
63
 
64
+ #: admin/add-ons.php:59, admin/basic-info.php:163, add-ons/user-listing/userlisting.php:11, add-ons/user-listing/userlisting.php:12, add-ons/user-listing/userlisting.php:17, add-ons/user-listing/userlisting.php:23, assets/misc/elementor/widgets/class-pb-widget-ul.php:58, assets/misc/elementor/widgets/class-pb-widget-ul.php:97
65
  msgid "User Listing"
66
  msgstr ""
67
 
69
  msgid "Easy to edit templates for listing your users as well as creating single user pages."
70
  msgstr ""
71
 
72
+ #: admin/add-ons.php:73, admin/basic-info.php:171, add-ons/custom-redirects/custom_redirects_admin.php:33, add-ons/custom-redirects/custom_redirects_admin.php:34
 
 
 
 
 
 
 
 
73
  msgid "Custom Redirects"
74
  msgstr ""
75
 
77
  msgid "Redirect users after login, after they first register or when they try to access the default WordPress dashboard, login, lost password and registration forms."
78
  msgstr ""
79
 
80
+ #: admin/add-ons.php:80, admin/basic-info.php:185
81
  msgid "Repeater Fields"
82
  msgstr ""
83
 
309
  msgid "Show"
310
  msgstr ""
311
 
312
+ #: admin/admin-bar.php:81, add-ons/user-listing/userlisting.php:1814
313
  msgid "Hide"
314
  msgstr ""
315
 
316
+ #: admin/admin-bar.php:92, admin/general-settings.php:390, admin/private-website.php:162, features/functions.php:1055, add-ons-advanced/woocommerce/woosync-page.php:149, features/content-restriction/content-restriction.php:174, features/two-factor-authentication/class-two-factor-authentication.php:156, assets/lib/class-mustache-templates/class-mustache-templates.php:421, assets/lib/wck-api/wordpress-creation-kit.php:410, admin/advanced-settings/includes/views/view-admin.php:112, admin/advanced-settings/includes/views/view-fields.php:309, admin/advanced-settings/includes/views/view-forms.php:407, admin/advanced-settings/includes/views/view-shortcodes.php:77, admin/advanced-settings/includes/views/view-userlisting.php:91
317
  msgid "Save Changes"
318
  msgstr ""
319
 
337
  msgid "<strong>ERROR</strong>: The password must have the minimum length of %s characters"
338
  msgstr ""
339
 
340
+ #: admin/admin-functions.php:139, admin/general-settings.php:340
341
  msgid "Very weak"
342
  msgstr ""
343
 
344
+ #: admin/admin-functions.php:139, admin/general-settings.php:341, features/functions.php:788, features/functions.php:812
345
  msgid "Weak"
346
  msgstr ""
347
 
348
+ #: admin/admin-functions.php:139, admin/general-settings.php:342, features/functions.php:788, features/functions.php:812
349
  msgid "Medium"
350
  msgstr ""
351
 
352
+ #: admin/admin-functions.php:139, admin/general-settings.php:343, features/functions.php:788, features/functions.php:812
353
  msgid "Strong"
354
  msgstr ""
355
 
365
  msgid "Add Field"
366
  msgstr ""
367
 
368
+ #: admin/admin-functions.php:212, admin/general-settings.php:120, admin/general-settings.php:120, add-ons-advanced/social-connect/index.php:260
369
  msgid "Settings"
370
  msgstr ""
371
 
385
  msgid "See details"
386
  msgstr ""
387
 
388
+ #: admin/admin-functions.php:260, admin/review.php:94, add-ons/user-listing/userlisting.php:3392
389
  msgid "Dismiss this notice."
390
  msgstr ""
391
 
489
  msgid "Restrict users from accessing certain pages, posts or custom post types based on user role or logged-in status."
490
  msgstr ""
491
 
492
+ #: admin/basic-info.php:91, admin/general-settings.php:42, admin/general-settings.php:36
493
+ msgid "Email Customizer"
494
  msgstr ""
495
 
496
  #: admin/basic-info.php:92
497
+ msgid "Personalize all emails sent to your users or admins. On registration, email confirmation, admin approval / un-approval."
498
  msgstr ""
499
 
500
  #: admin/basic-info.php:95
501
+ msgid "Minimum Password Length and Strength Meter"
502
  msgstr ""
503
 
504
  #: admin/basic-info.php:96
505
+ msgid "Eliminate weak passwords altogether by setting a minimum password length and enforcing a certain password strength."
506
+ msgstr ""
507
+
508
+ #: admin/basic-info.php:99
509
+ msgid "Login with Email or Username"
510
+ msgstr ""
511
+
512
+ #: admin/basic-info.php:100
513
  msgid "Allow users to log in with their email or username when accessing your site."
514
  msgstr ""
515
 
516
+ #: admin/basic-info.php:103, features/roles-editor/roles-editor.php:61, features/roles-editor/roles-editor.php:62, features/roles-editor/roles-editor.php:275, features/roles-editor/roles-editor.php:276, features/roles-editor/roles-editor.php:281, features/roles-editor/roles-editor.php:288
517
  msgid "Roles Editor"
518
  msgstr ""
519
 
520
+ #: admin/basic-info.php:104
521
  msgid "Add, remove, clone and edit roles and also capabilities for these roles."
522
  msgstr ""
523
 
524
+ #: admin/basic-info.php:117
525
  msgid "Customize Your Forms The Way You Want (*)"
526
  msgstr ""
527
 
528
+ #: admin/basic-info.php:118
529
  msgid "With Extra Profile Fields you can create the exact registration form your project needs."
530
  msgstr ""
531
 
532
+ #: admin/basic-info.php:122
533
  msgid "Get started with extra fields"
534
  msgstr ""
535
 
536
+ #: admin/basic-info.php:120
537
  msgid "Extra Profile Fields are available in Basic or PRO versions"
538
  msgstr ""
539
 
540
+ #: admin/basic-info.php:125
 
 
 
 
541
  msgid "Generic Uploads"
542
  msgstr ""
543
 
544
+ #: admin/basic-info.php:126
545
  msgid "Agree To Terms Checkbox"
546
  msgstr ""
547
 
548
+ #: admin/basic-info.php:127
549
  msgid "Datepicker"
550
  msgstr ""
551
 
552
+ #: admin/basic-info.php:128
553
  msgid "Timepicker"
554
  msgstr ""
555
 
556
+ #: admin/basic-info.php:129
557
  msgid "Colorpicker"
558
  msgstr ""
559
 
560
+ #: admin/basic-info.php:130
 
 
 
 
561
  msgid "Country Select"
562
  msgstr ""
563
 
564
+ #: admin/basic-info.php:131
565
  msgid "Currency Select"
566
  msgstr ""
567
 
568
+ #: admin/basic-info.php:132
569
  msgid "Timezone Select"
570
  msgstr ""
571
 
 
 
 
 
 
 
 
 
572
  #: admin/basic-info.php:136
573
+ msgid "Map"
574
  msgstr ""
575
 
576
  #: admin/basic-info.php:137
577
+ msgid "Select 2 Multiple"
578
  msgstr ""
579
 
580
+ #: admin/basic-info.php:138, add-ons-advanced/woocommerce/billing-fields.php:15
581
+ msgid "Phone"
582
  msgstr ""
583
 
584
  #: admin/basic-info.php:139
585
+ msgid "Hidden Input"
586
  msgstr ""
587
 
588
  #: admin/basic-info.php:140
589
+ msgid "Number"
590
  msgstr ""
591
 
592
  #: admin/basic-info.php:141
593
+ msgid "Validation"
594
  msgstr ""
595
 
596
  #: admin/basic-info.php:142
597
+ msgid "Select CPT"
598
+ msgstr ""
599
+
600
+ #: admin/basic-info.php:143
601
  msgid "HTML"
602
  msgstr ""
603
 
604
+ #: admin/basic-info.php:152
605
  msgid "Powerful Add-ons (**)"
606
  msgstr ""
607
 
608
+ #: admin/basic-info.php:153
609
  msgid "Everything you will need to manage your users is probably already available using the Pro Add-ons."
610
  msgstr ""
611
 
612
+ #: admin/basic-info.php:155
613
  msgid "Enable your add-ons"
614
  msgstr ""
615
 
616
+ #: admin/basic-info.php:158
617
  msgid "Find out more about PRO Modules"
618
  msgstr ""
619
 
620
+ #: admin/basic-info.php:167
621
  msgid "To create a page containing the users registered to this current site/blog, insert the following shortcode in a page of your chosing: %s."
622
  msgstr ""
623
 
624
+ #: admin/basic-info.php:165
625
  msgid "Easy to edit templates for listing your website users as well as creating single user pages. Shortcode based, offering many options to customize your listings."
626
  msgstr ""
627
 
628
+ #: admin/basic-info.php:172
 
 
 
 
629
  msgid "Keep your users out of the WordPress dashboard, redirect them to the front-page after login or registration, everything is just a few clicks away."
630
  msgstr ""
631
 
632
+ #: admin/basic-info.php:178
633
  msgid "Set up multiple registration forms with different fields for certain user roles. Capture different information from different types of users."
634
  msgstr ""
635
 
636
+ #: admin/basic-info.php:181
637
  msgid "Multiple Edit-profile Forms"
638
  msgstr ""
639
 
640
+ #: admin/basic-info.php:186
641
  msgid "Set up a repeating group of fields on register and edit profile forms. Limit the number of repeated groups for each user role."
642
  msgstr ""
643
 
644
+ #: admin/basic-info.php:222
645
  msgid " * only available in the %1$sBasic and Pro versions%2$s."
646
  msgstr ""
647
 
648
+ #: admin/basic-info.php:223
649
  msgid "** only available in the %1$sPro version%2$s."
650
  msgstr ""
651
 
681
  msgid "Give us another try! Open a support ticket <a href='https://www.cozmoslabs.com/support/open-ticket/' target='_blank'>here</a>"
682
  msgstr ""
683
 
684
+ #: admin/feedback.php:47, admin/manage-fields.php:76
685
  msgid "Other"
686
  msgstr ""
687
 
745
  msgid "Two-Factor Authentication"
746
  msgstr ""
747
 
748
+ #: admin/general-settings.php:43, admin/general-settings.php:37
749
  msgid "User Emails"
750
  msgstr ""
751
 
752
+ #: admin/general-settings.php:44, admin/general-settings.php:38
753
  msgid "Administrator Emails"
754
  msgstr ""
755
 
756
+ #: admin/general-settings.php:141
757
  msgid "Profile Builder Settings"
758
  msgstr ""
759
 
760
+ #: admin/general-settings.php:154
761
  msgid "Load Profile Builder's own CSS file in the front-end:"
762
  msgstr ""
763
 
764
+ #: admin/general-settings.php:157, admin/general-settings.php:170, admin/general-settings.php:185, admin/general-settings.php:234, admin/general-settings.php:281, admin/manage-fields.php:240, admin/private-website.php:70, admin/private-website.php:137, admin/private-website.php:150, add-ons/multiple-forms/edit-profile-forms.php:206, add-ons/multiple-forms/register-forms.php:229, add-ons/multiple-forms/register-forms.php:230, add-ons/user-listing/userlisting.php:2605, add-ons-advanced/bbpress/bbpress-page.php:161, add-ons-advanced/social-connect/index.php:347, add-ons-advanced/social-connect/index.php:406, features/content-restriction/content-restriction.php:90, features/two-factor-authentication/class-two-factor-authentication.php:125, admin/advanced-settings/includes/forms/placeholder-labels.php:135, admin/advanced-settings/includes/views/view-admin.php:18, admin/advanced-settings/includes/views/view-admin.php:34, admin/advanced-settings/includes/views/view-admin.php:50, admin/advanced-settings/includes/views/view-admin.php:69, admin/advanced-settings/includes/views/view-admin.php:100, admin/advanced-settings/includes/views/view-fields.php:18, admin/advanced-settings/includes/views/view-fields.php:66, admin/advanced-settings/includes/views/view-fields.php:184, admin/advanced-settings/includes/views/view-fields.php:200, admin/advanced-settings/includes/views/view-fields.php:220, admin/advanced-settings/includes/views/view-fields.php:243, admin/advanced-settings/includes/views/view-fields.php:263, admin/advanced-settings/includes/views/view-fields.php:280, admin/advanced-settings/includes/views/view-fields.php:296, admin/advanced-settings/includes/views/view-forms.php:19, admin/advanced-settings/includes/views/view-forms.php:148, admin/advanced-settings/includes/views/view-forms.php:166, admin/advanced-settings/includes/views/view-forms.php:183, admin/advanced-settings/includes/views/view-forms.php:198, admin/advanced-settings/includes/views/view-forms.php:213, admin/advanced-settings/includes/views/view-forms.php:233, admin/advanced-settings/includes/views/view-forms.php:250, admin/advanced-settings/includes/views/view-forms.php:286, admin/advanced-settings/includes/views/view-forms.php:307, admin/advanced-settings/includes/views/view-forms.php:327, admin/advanced-settings/includes/views/view-forms.php:349, admin/advanced-settings/includes/views/view-forms.php:371, admin/advanced-settings/includes/views/view-forms.php:391, admin/advanced-settings/includes/views/view-shortcodes.php:16, admin/advanced-settings/includes/views/view-shortcodes.php:32, admin/advanced-settings/includes/views/view-shortcodes.php:48, admin/advanced-settings/includes/views/view-shortcodes.php:64, admin/advanced-settings/includes/views/view-userlisting.php:53, admin/advanced-settings/includes/views/view-userlisting.php:75, assets/misc/elementor/widgets/class-pb-widget-l.php:75, assets/misc/elementor/widgets/class-pb-widget-rf-epf.php:183, assets/misc/elementor/widgets/class-pb-widget-ul.php:109
765
  msgid "Yes"
766
  msgstr ""
767
 
768
+ #: admin/general-settings.php:159
769
  msgid "You can find the default file here: %1$s"
770
  msgstr ""
771
 
772
+ #: admin/general-settings.php:166
773
  msgid "Automatically Log In:"
774
  msgstr ""
775
 
776
+ #: admin/general-settings.php:171, admin/general-settings.php:186, admin/general-settings.php:235, admin/general-settings.php:280, admin/private-website.php:69, admin/private-website.php:136, admin/private-website.php:151, add-ons/multiple-forms/edit-profile-forms.php:206, add-ons/multiple-forms/register-forms.php:229, add-ons/multiple-forms/register-forms.php:230, add-ons-advanced/bbpress/bbpress-page.php:160, add-ons-advanced/social-connect/index.php:348, add-ons-advanced/social-connect/index.php:407, features/content-restriction/content-restriction.php:89, features/two-factor-authentication/class-two-factor-authentication.php:124, admin/advanced-settings/includes/forms/placeholder-labels.php:136, assets/misc/elementor/widgets/class-pb-widget-l.php:76, assets/misc/elementor/widgets/class-pb-widget-rf-epf.php:184, assets/misc/elementor/widgets/class-pb-widget-ul.php:110
777
  msgid "No"
778
  msgstr ""
779
 
780
+ #: admin/general-settings.php:174
781
  msgid "Select \"Yes\" to automatically log in new users after successful registration."
782
  msgstr ""
783
 
784
+ #: admin/general-settings.php:181
785
  msgid "\"Email Confirmation\" Activated:"
786
  msgstr ""
787
 
788
+ #: admin/general-settings.php:189
789
  msgid "This works with front-end forms only. Recommended to redirect WP default registration to a Profile Builder one using \"Custom Redirects\" module."
790
  msgstr ""
791
 
792
+ #: admin/general-settings.php:191
793
  msgid "You can find a list of unconfirmed email addresses %1$sUsers > All Users > Email Confirmation%2$s."
794
  msgstr ""
795
 
796
+ #: admin/general-settings.php:199
797
  msgid "\"Email Confirmation\" Landing Page:"
798
  msgstr ""
799
 
800
+ #: admin/general-settings.php:204
801
  msgid "Existing Pages"
802
  msgstr ""
803
 
804
+ #: admin/general-settings.php:219
805
  msgid "Specify the page where the users will be directed when confirming the email account. This page can differ from the register page(s) and can be changed at any time. If none selected, a simple confirmation page will be displayed for the user."
806
  msgstr ""
807
 
808
+ #: admin/general-settings.php:230
809
  msgid "\"Admin Approval\" Activated:"
810
  msgstr ""
811
 
812
+ #: admin/general-settings.php:238
813
  msgid "You can find a list of users at %1$sUsers > All Users > Admin Approval%2$s."
814
  msgstr ""
815
 
816
+ #: admin/general-settings.php:245
817
  msgid "\"Admin Approval\" on User Role:"
818
  msgstr ""
819
 
820
+ #: admin/general-settings.php:264
821
  msgid "Select on what user roles to activate Admin Approval."
822
  msgstr ""
823
 
824
+ #: admin/general-settings.php:276
825
  msgid "\"Roles Editor\" Activated:"
826
  msgstr ""
827
 
828
+ #: admin/general-settings.php:284
829
  msgid "You can add / edit user roles at %1$sUsers > Roles Editor%2$s."
830
  msgstr ""
831
 
832
+ #: admin/general-settings.php:295
833
  msgid "\"Admin Approval\" Feature:"
834
  msgstr ""
835
 
836
+ #: admin/general-settings.php:298
837
  msgid "You decide who is a user on your website. Get notified via email or approve multiple users at once from the WordPress UI. Enable Admin Approval by upgrading to %1$sBasic or PRO versions%2$s."
838
  msgstr ""
839
 
840
+ #: admin/general-settings.php:305
841
  msgid "Allow Users to Log in With:"
842
  msgstr ""
843
 
844
+ #: admin/general-settings.php:309
845
  msgid "Username and Email"
846
  msgstr ""
847
 
848
+ #: admin/general-settings.php:310, admin/manage-fields.php:380, front-end/login.php:341, front-end/login.php:355, front-end/login.php:543, add-ons/custom-redirects/custom_redirects_admin.php:61, add-ons/email-customizer/email-customizer.php:28, add-ons/user-listing/userlisting.php:113, add-ons/user-listing/userlisting.php:339, add-ons/user-listing/userlisting.php:885, add-ons/user-listing/userlisting.php:2558, features/admin-approval/class-admin-approval.php:174, features/email-confirmation/class-email-confirmation.php:168, features/email-customizer/email-customizer.php:28, admin/advanced-settings/includes/views/view-fields.php:124
849
  msgid "Username"
850
  msgstr ""
851
 
852
+ #: admin/general-settings.php:311, front-end/login.php:538, front-end/recover.php:118, add-ons/email-customizer/email-customizer.php:29, add-ons/user-listing/userlisting.php:119, add-ons/user-listing/userlisting.php:891, add-ons/user-listing/userlisting.php:2559, features/admin-approval/class-admin-approval.php:177, features/email-confirmation/class-email-confirmation.php:169, features/email-customizer/email-customizer.php:29, add-ons-free/gdpr-communication-preferences/admin/manage-fields.php:24, add-ons-free/gdpr-communication-preferences/front-end/gdpr-communication-preferences.php:9, admin/advanced-settings/includes/shortcodes/resend-activation.php:9
853
  msgid "Email"
854
  msgstr ""
855
 
856
+ #: admin/general-settings.php:314
857
  msgid "\"Username and Email\" - users can Log In with either their Username or their Email."
858
  msgstr ""
859
 
860
+ #: admin/general-settings.php:315
861
  msgid "\"Username\" - users can only Log In with their Username. Both the Username and Email fields will be shown in the front-end forms."
862
  msgstr ""
863
 
864
+ #: admin/general-settings.php:316
865
  msgid "\"Email\" - users can only Log In with their Email. The Username field will be hidden in the front-end forms and Usernames will be automatically generated based on the Emails."
866
  msgstr ""
867
 
868
+ #: admin/general-settings.php:323
869
  msgid "Minimum Password Length:"
870
  msgstr ""
871
 
872
+ #: admin/general-settings.php:328
873
  msgid "Enter the minimum characters the password should have. Leave empty for no minimum limit"
874
  msgstr ""
875
 
876
+ #: admin/general-settings.php:335
877
  msgid "Minimum Password Strength:"
878
  msgstr ""
879
 
880
+ #: admin/general-settings.php:339
881
  msgid "Disabled"
882
  msgstr ""
883
 
884
+ #: admin/general-settings.php:350
885
+ msgid "Select Recover Password Page:"
886
+ msgstr ""
887
+
888
+ #: admin/general-settings.php:354, add-ons/user-listing/userlisting.php:598, add-ons/user-listing/userlisting.php:595, add-ons/user-listing/userlisting.php:593, add-ons/user-listing/userlisting.php:576, add-ons/user-listing/userlisting.php:573, add-ons/user-listing/userlisting.php:571, add-ons-advanced/bbpress/bbpress-page.php:90, add-ons-advanced/bbpress/bbpress-page.php:125, add-ons-advanced/woocommerce/woosync-page.php:80, add-ons-advanced/woocommerce/woosync-page.php:115, features/content-restriction/content-restriction.php:148, add-ons-advanced/campaign-monitor/admin/cmonitor-page.php:557, add-ons-advanced/mailchimp-integration/admin/mailchimp-page.php:232, add-ons-advanced/mailchimp-integration/admin/mailchimp-page.php:307
889
+ msgid "None"
890
+ msgstr ""
891
+
892
+ #: admin/general-settings.php:378
893
+ msgid "Select the page which contains the %1$s[wppb-recover-password]%2$s shortcode."
894
+ msgstr ""
895
+
896
  #: admin/manage-fields.php:18
897
  msgid "Form Fields"
898
  msgstr ""
905
  msgid "Standard"
906
  msgstr ""
907
 
908
+ #: admin/manage-fields.php:69
909
  msgid "Advanced"
910
  msgstr ""
911
 
912
+ #: admin/manage-fields.php:181
913
  msgid "Choose one of the supported field types"
914
  msgstr ""
915
 
916
+ #: admin/manage-fields.php:183
917
  msgid ". Extra Field Types are available in <a href=\"%s\">Basic or PRO versions</a>."
918
  msgstr ""
919
 
920
+ #: admin/manage-fields.php:219
921
  msgid "Use this in conjunction with WordPress functions to display the value in the page of your choosing<br/>Auto-completed but in some cases editable (in which case it must be unique)<br/>Changing this will only affect subsequent entries"
922
  msgstr ""
923
 
924
+ #: admin/manage-fields.php:216
925
  msgid "Use this in conjunction with WordPress functions to display the value in the page of your choosing<br/>Auto-completed but in some cases editable (in which case it must be unique)<br/>Changing this might take long in case of a very big user-count"
926
  msgstr ""
927
 
928
+ #: admin/manage-fields.php:233
929
  msgid "Field Title"
930
  msgstr ""
931
 
932
+ #: admin/manage-fields.php:233
933
  msgid "Title of the field"
934
  msgstr ""
935
 
936
+ #: admin/manage-fields.php:234, add-ons/multiple-forms/edit-profile-forms.php:245, add-ons/multiple-forms/register-forms.php:266
937
  msgid "Field"
938
  msgstr ""
939
 
940
+ #: admin/manage-fields.php:235
941
  msgid "Meta-name"
942
  msgstr ""
943
 
944
+ #: admin/manage-fields.php:236, add-ons/custom-redirects/custom_redirects_admin.php:70, add-ons/custom-redirects/custom_redirects_admin.php:100, add-ons/custom-redirects/custom_redirects_admin.php:119, add-ons/custom-redirects/custom_redirects_admin.php:144, add-ons/multiple-forms/edit-profile-forms.php:246, add-ons/multiple-forms/register-forms.php:267, add-ons/user-listing/userlisting.php:930, assets/misc/elementor/widgets/class-pb-widget-ul.php:172
945
  msgid "ID"
946
  msgstr ""
947
 
948
+ #: admin/manage-fields.php:236, add-ons/multiple-forms/edit-profile-forms.php:246, add-ons/multiple-forms/register-forms.php:267
949
  msgid "A unique, auto-generated ID for this particular field<br/>You can use this in conjuction with filters to target this element if needed<br/>Can't be edited"
950
  msgstr ""
951
 
952
+ #: admin/manage-fields.php:237, features/two-factor-authentication/class-two-factor-authentication.php:241, features/two-factor-authentication/class-two-factor-authentication.php:356
953
  msgid "Description"
954
  msgstr ""
955
 
956
+ #: admin/manage-fields.php:237
957
  msgid "Enter a (detailed) description of the option for end users to read<br/>Optional"
958
  msgstr ""
959
 
960
+ #: admin/manage-fields.php:238
961
  msgid "Row Count"
962
  msgstr ""
963
 
964
+ #: admin/manage-fields.php:238
965
  msgid "Specify the number of rows for a 'Textarea' field<br/>If not specified, defaults to 5"
966
  msgstr ""
967
 
968
+ #: admin/manage-fields.php:239
969
  msgid "Allowed Image Extensions"
970
  msgstr ""
971
 
972
+ #: admin/manage-fields.php:239
973
  msgid "Specify the extension(s) you want to limit to upload<br/>Example: .ext1,.ext2,.ext3<br/>If not specified, defaults to: .jpg,.jpeg,.gif,.png (.*)"
974
  msgstr ""
975
 
976
+ #: admin/manage-fields.php:240
977
  msgid "Use Simple Upload"
978
  msgstr ""
979
 
980
+ #: admin/manage-fields.php:240
981
  msgid "Use a simple upload field instead of the WordPress upload"
982
  msgstr ""
983
 
984
+ #: admin/manage-fields.php:241
985
  msgid "Allowed Upload Extensions"
986
  msgstr ""
987
 
988
+ #: admin/manage-fields.php:241
989
  msgid "Specify the extension(s) you want to limit to upload<br/>Example: .ext1,.ext2,.ext3<br/>If not specified, defaults to all WordPress allowed file extensions (.*)"
990
  msgstr ""
991
 
992
+ #: admin/manage-fields.php:242
993
  msgid "Avatar Size"
994
  msgstr ""
995
 
996
+ #: admin/manage-fields.php:242
997
  msgid "Enter a value (between 20 and 200) for the size of the 'Avatar'<br/>If not specified, defaults to 100"
998
  msgstr ""
999
 
1000
+ #: admin/manage-fields.php:243
1001
  msgid "Date-format"
1002
  msgstr ""
1003
 
1004
+ #: admin/manage-fields.php:243
1005
  msgid "Specify the format of the date when using Datepicker<br/>Valid options: mm/dd/yy, mm/yy/dd, dd/yy/mm, dd/mm/yy, yy/dd/mm, yy/mm/dd, mm-dd-yy, yy-mm-dd, D, dd M yy, D, d M y, DD, dd-M-y, D, d M yy, mm/yy, mm/dd, dd/mm, @<br/>If not specified, defaults to mm/dd/yy<br/>ATTENTION: if you plan to use this field for sorting, please make sure to use year first, then month, and day last."
1006
  msgstr ""
1007
 
1008
+ #: admin/manage-fields.php:244
1009
  msgid "Terms of Agreement"
1010
  msgstr ""
1011
 
1012
+ #: admin/manage-fields.php:244
1013
  msgid "Enter a detailed description of the terms of agreement for the user to read.<br/>Links can be inserted by using standard HTML syntax: &lt;a href=\"custom_url\"&gt;custom_text&lt;/a&gt;"
1014
  msgstr ""
1015
 
1016
+ #: admin/manage-fields.php:245
1017
  msgid "Options"
1018
  msgstr ""
1019
 
1020
+ #: admin/manage-fields.php:245
1021
  msgid "Enter a comma separated list of values<br/>This can be anything, as it is hidden from the user, but should not contain special characters or apostrophes"
1022
  msgstr ""
1023
 
1024
+ #: admin/manage-fields.php:246, add-ons-free/labels-edit/labels-edit.php:373
1025
  msgid "Labels"
1026
  msgstr ""
1027
 
1028
+ #: admin/manage-fields.php:246
1029
  msgid "Enter a comma separated list of labels<br/>Visible for the user"
1030
  msgstr ""
1031
 
1032
+ #: admin/manage-fields.php:247
1033
  msgid "reCAPTCHA Type"
1034
  msgstr ""
1035
 
1036
+ #: admin/manage-fields.php:247
1037
  msgid "Choose the <a href=\"https://developers.google.com/recaptcha/docs/versions\" target=\"_blank\">type of reCAPTCHA</a> you wish to add to this site.<br/>Please note that the Invisible reCAPTCHA is a type of reCAPTCHA v2."
1038
  msgstr ""
1039
 
1040
+ #: admin/manage-fields.php:248
1041
  msgid "Site Key"
1042
  msgstr ""
1043
 
1044
+ #: admin/manage-fields.php:248
1045
  msgid "The site key from Google, <a href=\"https://www.google.com/recaptcha/admin/create\" target=\"_blank\">https://www.google.com/recaptcha/admin/create</a>"
1046
  msgstr ""
1047
 
1048
+ #: admin/manage-fields.php:249
1049
  msgid "Secret Key"
1050
  msgstr ""
1051
 
1052
+ #: admin/manage-fields.php:249
1053
  msgid "The secret key from Google, <a href=\"https://www.google.com/recaptcha/admin/create\" target=\"_blank\">https://www.google.com/recaptcha/admin/create</a>"
1054
  msgstr ""
1055
 
1056
+ #: admin/manage-fields.php:250
1057
  msgid "Display on PB forms"
1058
  msgstr ""
1059
 
1060
+ #: admin/manage-fields.php:250
1061
  msgid "PB Login"
1062
  msgstr ""
1063
 
1064
+ #: admin/manage-fields.php:250
1065
  msgid "PB Register"
1066
  msgstr ""
1067
 
1068
+ #: admin/manage-fields.php:250
1069
  msgid "PB Recover Password"
1070
  msgstr ""
1071
 
1072
+ #: admin/manage-fields.php:250
1073
  msgid "Select on which Profile Builder forms to display reCAPTCHA"
1074
  msgstr ""
1075
 
1076
+ #: admin/manage-fields.php:251
1077
  msgid "Display on default WP forms"
1078
  msgstr ""
1079
 
1080
+ #: admin/manage-fields.php:251
1081
  msgid "Default WP Login"
1082
  msgstr ""
1083
 
1084
+ #: admin/manage-fields.php:251
1085
  msgid "Default WP Register"
1086
  msgstr ""
1087
 
1088
+ #: admin/manage-fields.php:251
1089
  msgid "Default WP Recover Password"
1090
  msgstr ""
1091
 
1092
+ #: admin/manage-fields.php:251
1093
  msgid "Select on which default WP forms to display reCAPTCHA"
1094
  msgstr ""
1095
 
1096
+ #: admin/manage-fields.php:252
1097
  msgid "User Roles"
1098
  msgstr ""
1099
 
1100
+ #: admin/manage-fields.php:252
1101
  msgid "Select which user roles to show to the user ( drag and drop to re-order )"
1102
  msgstr ""
1103
 
1104
+ #: admin/manage-fields.php:253
1105
  msgid "Display on Edit Profile"
1106
  msgstr ""
1107
 
1108
+ #: admin/manage-fields.php:253
1109
  msgid "Check if you want the select user role field to appear on Edit Profile forms"
1110
  msgstr ""
1111
 
1112
+ #: admin/manage-fields.php:254
1113
  msgid "User Roles Order"
1114
  msgstr ""
1115
 
1116
+ #: admin/manage-fields.php:254
1117
  msgid "Save the user role order from the user roles checkboxes"
1118
  msgstr ""
1119
 
1120
+ #: admin/manage-fields.php:255
1121
  msgid "Default Value"
1122
  msgstr ""
1123
 
1124
+ #: admin/manage-fields.php:255
1125
  msgid "Default value of the field"
1126
  msgstr ""
1127
 
1128
+ #: admin/manage-fields.php:256, admin/manage-fields.php:258, admin/manage-fields.php:259, admin/manage-fields.php:260
1129
  msgid "Default Option"
1130
  msgstr ""
1131
 
1132
+ #: admin/manage-fields.php:256
1133
  msgid "Specify the option which should be selected by default"
1134
  msgstr ""
1135
 
1136
+ #: admin/manage-fields.php:257
1137
  msgid "Default Option(s)"
1138
  msgstr ""
1139
 
1140
+ #: admin/manage-fields.php:257
1141
  msgid "Specify the option which should be checked by default<br/>If there are multiple values, separate them with a ',' (comma)"
1142
  msgstr ""
1143
 
1144
+ #: admin/manage-fields.php:258, admin/manage-fields.php:259, admin/manage-fields.php:260
1145
  msgid "Default option of the field"
1146
  msgstr ""
1147
 
1148
+ #: admin/manage-fields.php:261
1149
  msgid "Show Currency Symbol"
1150
  msgstr ""
1151
 
1152
+ #: admin/manage-fields.php:261
1153
  msgid "Whether the currency symbol should be displayed after the currency name in the select option."
1154
  msgstr ""
1155
 
1156
+ #: admin/manage-fields.php:262
1157
  msgid "Show Post Type"
1158
  msgstr ""
1159
 
1160
+ #: admin/manage-fields.php:262
1161
  msgid "Posts from what post type will be displayed in the select."
1162
  msgstr ""
1163
 
1164
+ #: admin/manage-fields.php:263
1165
  msgid "Allowable Values"
1166
  msgstr ""
1167
 
1168
+ #: admin/manage-fields.php:263
1169
  msgid "Enter a comma separated list of possible values. Upon registration if the value provided by the user does not match one of these values, the user will not be registered."
1170
  msgstr ""
1171
 
1172
+ #: admin/manage-fields.php:264
1173
  msgid "Error Message"
1174
  msgstr ""
1175
 
1176
+ #: admin/manage-fields.php:264
1177
  msgid "Set a custom error message that will be displayed to the user."
1178
  msgstr ""
1179
 
1180
+ #: admin/manage-fields.php:265
1181
  msgid "Time Format"
1182
  msgstr ""
1183
 
1184
+ #: admin/manage-fields.php:265
1185
  msgid "Specify the time format."
1186
  msgstr ""
1187
 
1188
+ #: admin/manage-fields.php:266
1189
  msgid "Google Maps API Key"
1190
  msgstr ""
1191
 
1192
+ #: admin/manage-fields.php:266
1193
  msgid "Enter your Google Maps API key ( <a href=\"https://console.developers.google.com/flows/enableapi?apiid=maps_backend\" target=\"_blank\">Get your API key</a> ). If more than one map fields are added to a form the API key from the first map displayed will be used."
1194
  msgstr ""
1195
 
1196
+ #: admin/manage-fields.php:271
1197
  msgid "Default Latitude"
1198
  msgstr ""
1199
 
1200
+ #: admin/manage-fields.php:272
1201
  msgid "The latitude at which the map should be displayed when no pins are attached."
1202
  msgstr ""
1203
 
1204
+ #: admin/manage-fields.php:278
1205
  msgid "Default Longitude"
1206
  msgstr ""
1207
 
1208
+ #: admin/manage-fields.php:279
1209
  msgid "The longitude at which the map should be displayed when no pins are attached."
1210
  msgstr ""
1211
 
1212
+ #: admin/manage-fields.php:285
1213
  msgid "Default Zoom Level"
1214
  msgstr ""
1215
 
1216
+ #: admin/manage-fields.php:286
1217
  msgid "Add a number from 0 to 19. The higher the number the higher the zoom."
1218
  msgstr ""
1219
 
1220
+ #: admin/manage-fields.php:290
1221
  msgid "Map Height"
1222
  msgstr ""
1223
 
1224
+ #: admin/manage-fields.php:290
1225
  msgid "The height of the map."
1226
  msgstr ""
1227
 
1228
+ #: admin/manage-fields.php:291
1229
  msgid "Default Content"
1230
  msgstr ""
1231
 
1232
+ #: admin/manage-fields.php:291
1233
  msgid "Default value of the textarea"
1234
  msgstr ""
1235
 
1236
+ #: admin/manage-fields.php:292
1237
  msgid "HTML Content"
1238
  msgstr ""
1239
 
1240
+ #: admin/manage-fields.php:292
1241
  msgid "Add your HTML (or text) content"
1242
  msgstr ""
1243
 
1244
+ #: admin/manage-fields.php:293
1245
  msgid "Phone Format"
1246
  msgstr ""
1247
 
1248
+ #: admin/manage-fields.php:293
1249
  msgid "You can use: # for numbers, parentheses ( ), - sign, + sign, dot . and spaces."
1250
  msgstr ""
1251
 
1252
+ #: admin/manage-fields.php:293
1253
  msgid "Eg. (###) ###-####"
1254
  msgstr ""
1255
 
1256
+ #: admin/manage-fields.php:293
1257
  msgid "Empty field won't check for correct phone number."
1258
  msgstr ""
1259
 
1260
+ #: admin/manage-fields.php:294
1261
  msgid "Heading Tag"
1262
  msgstr ""
1263
 
1264
+ #: admin/manage-fields.php:294
1265
  msgid "Change heading field size on front-end forms"
1266
  msgstr ""
1267
 
1268
+ #: admin/manage-fields.php:295
1269
  msgid "Min Number Value"
1270
  msgstr ""
1271
 
1272
+ #: admin/manage-fields.php:295
1273
  msgid "Min allowed number value (0 to allow only positive numbers)"
1274
  msgstr ""
1275
 
1276
+ #: admin/manage-fields.php:295
1277
  msgid "Leave it empty for no min value"
1278
  msgstr ""
1279
 
1280
+ #: admin/manage-fields.php:296
1281
  msgid "Max Number Value"
1282
  msgstr ""
1283
 
1284
+ #: admin/manage-fields.php:296
1285
  msgid "Max allowed number value (0 to allow only negative numbers)"
1286
  msgstr ""
1287
 
1288
+ #: admin/manage-fields.php:296
1289
  msgid "Leave it empty for no max value"
1290
  msgstr ""
1291
 
1292
+ #: admin/manage-fields.php:297
1293
  msgid "Number Step Value"
1294
  msgstr ""
1295
 
1296
+ #: admin/manage-fields.php:297
1297
  msgid "Step value 1 to allow only integers, 0.1 to allow integers and numbers with 1 decimal"
1298
  msgstr ""
1299
 
1300
+ #: admin/manage-fields.php:297
1301
  msgid "To allow multiple decimals use for eg. 0.01 (for 2 deciamls) and so on"
1302
  msgstr ""
1303
 
1304
+ #: admin/manage-fields.php:297
1305
  msgid "You can also use step value to specify the legal number intervals (eg. step value 2 will allow only -4, -2, 0, 2 and so on)"
1306
  msgstr ""
1307
 
1308
+ #: admin/manage-fields.php:297
1309
  msgid "Leave it empty for no restriction"
1310
  msgstr ""
1311
 
1312
+ #: admin/manage-fields.php:298, add-ons-advanced/woocommerce/index.php:261
1313
  msgid "Required"
1314
  msgstr ""
1315
 
1316
+ #: admin/manage-fields.php:298
1317
  msgid "Whether the field is required or not"
1318
  msgstr ""
1319
 
1320
+ #: admin/manage-fields.php:299
1321
  msgid "Overwrite Existing"
1322
  msgstr ""
1323
 
1324
+ #: admin/manage-fields.php:299
1325
  msgid "Selecting 'Yes' will add the field to the list, but will overwrite any other field in the database that has the same meta-name<br/>Use this at your own risk"
1326
  msgstr ""
1327
 
1328
+ #: admin/manage-fields.php:305
1329
  msgid "POIs Load Type"
1330
  msgstr ""
1331
 
1332
+ #: admin/manage-fields.php:307
1333
  msgid "POIs of the listed users (as filtered & paginated)"
1334
  msgstr ""
1335
 
1336
+ #: admin/manage-fields.php:308
1337
  msgid "POIs of all the users for the filter* (no pagination)"
1338
  msgstr ""
1339
 
1340
+ #: admin/manage-fields.php:311
1341
  msgid "This option allows you to load on a single map the POIs for all users, or just these for the listed ones (this will take into account the filters and the faceted menus). *Please use this feature wisely, it will impact the performance."
1342
  msgstr ""
1343
 
1344
+ #: admin/manage-fields.php:318
1345
  msgid "POI Bubble Info"
1346
  msgstr ""
1347
 
1348
+ #: admin/manage-fields.php:321
1349
  msgid "Select the attributes to be listed inside the POI bubble."
1350
  msgstr ""
1351
 
1352
+ #: admin/manage-fields.php:332
1353
  msgid "Number of Users per Map Iteration"
1354
  msgstr ""
1355
 
1356
+ #: admin/manage-fields.php:333
1357
  msgid "When loading the map of all users with no pagination, the map script will iterate multiple times and will expose gradually POIs on the map, until all the POIs for the users that match the criteria will be added on the map (think of this as of pagination for the map POIs). The smaller the number of users per iteration, the fastest the iteration response will be, but for a large number of users, the map script will iterate multiple times. Setting a higher limit will decrease the performance, but might produce a smaller number of iterations. <br><br><b>Please adjust this value to your hosting capabilities, and make sure that the value you set is the best for performance.</b> We recommend a <b>maximum</b> value of 300."
1358
  msgstr ""
1359
 
1360
+ #: admin/manage-fields.php:338
1361
  msgid "Maximum Selections"
1362
  msgstr ""
1363
 
1364
+ #: admin/manage-fields.php:338
1365
  msgid "Select2 multi-value select boxes can set restrictions regarding the maximum number of options selected."
1366
  msgstr ""
1367
 
1368
+ #: admin/manage-fields.php:339
1369
  msgid "User Inputted Options"
1370
  msgstr ""
1371
 
1372
+ #: admin/manage-fields.php:339
1373
  msgid "Check this to allow users to create their own options beside the pre-existing ones."
1374
  msgstr ""
1375
 
1376
+ #: admin/manage-fields.php:346
1377
  msgid "Form Field Properties"
1378
  msgstr ""
1379
 
1380
+ #: admin/manage-fields.php:360
1381
  msgid "Registration & Edit Profile Forms"
1382
  msgstr ""
1383
 
1384
+ #: admin/manage-fields.php:379, add-ons-advanced/buddypress/buddypress-activator.php:48, add-ons-advanced/buddypress/index.php:481, add-ons-advanced/campaign-monitor/admin/widget.php:226, add-ons-advanced/campaign-monitor/admin/widget.php:228
1385
  msgid "Name"
1386
  msgstr ""
1387
 
1388
+ #: admin/manage-fields.php:380
1389
  msgid "Usernames cannot be changed."
1390
  msgstr ""
1391
 
1392
+ #: admin/manage-fields.php:381, add-ons/user-listing/userlisting.php:897, add-ons-advanced/woocommerce/billing-fields.php:6, add-ons-advanced/woocommerce/shipping-fields.php:6, admin/advanced-settings/includes/views/view-fields.php:130
1393
  msgid "First Name"
1394
  msgstr ""
1395
 
1396
+ #: admin/manage-fields.php:382, add-ons/user-listing/userlisting.php:900, add-ons-advanced/woocommerce/billing-fields.php:7, add-ons-advanced/woocommerce/shipping-fields.php:7, admin/advanced-settings/includes/views/view-fields.php:136
1397
  msgid "Last Name"
1398
  msgstr ""
1399
 
1400
+ #: admin/manage-fields.php:383, add-ons/user-listing/userlisting.php:924, add-ons/user-listing/userlisting.php:2567
1401
  msgid "Nickname"
1402
  msgstr ""
1403
 
1404
+ #: admin/manage-fields.php:384
1405
  msgid "Display name publicly as"
1406
  msgstr ""
1407
 
1408
+ #: admin/manage-fields.php:385, add-ons-advanced/buddypress/buddypress-activator.php:71, add-ons-advanced/buddypress/index.php:504
1409
  msgid "Contact Info"
1410
  msgstr ""
1411
 
1412
+ #: admin/manage-fields.php:386
1413
  msgid "E-mail"
1414
  msgstr ""
1415
 
1416
+ #: admin/manage-fields.php:387, add-ons/email-customizer/email-customizer.php:33, add-ons/user-listing/userlisting.php:122, add-ons/user-listing/userlisting.php:906, add-ons/user-listing/userlisting.php:2561, features/email-customizer/email-customizer.php:33
1417
  msgid "Website"
1418
  msgstr ""
1419
 
1420
+ #: admin/manage-fields.php:391
1421
  msgid "AIM"
1422
  msgstr ""
1423
 
1424
+ #: admin/manage-fields.php:392
1425
  msgid "Yahoo IM"
1426
  msgstr ""
1427
 
1428
+ #: admin/manage-fields.php:393
1429
  msgid "Jabber / Google Talk"
1430
  msgstr ""
1431
 
1432
+ #: admin/manage-fields.php:396, add-ons-advanced/buddypress/buddypress-activator.php:78, add-ons-advanced/buddypress/index.php:511
1433
  msgid "About Yourself"
1434
  msgstr ""
1435
 
1436
+ #: admin/manage-fields.php:397, add-ons/user-listing/userlisting.php:125, add-ons/user-listing/userlisting.php:909, add-ons/user-listing/userlisting.php:2562
1437
  msgid "Biographical Info"
1438
  msgstr ""
1439
 
1440
+ #: admin/manage-fields.php:397
1441
  msgid "Share a little biographical information to fill out your profile. This may be shown publicly."
1442
  msgstr ""
1443
 
1444
+ #: admin/manage-fields.php:398, front-end/login.php:121, front-end/recover.php:72, add-ons/email-customizer/email-customizer.php:30, features/email-customizer/email-customizer.php:30
1445
  msgid "Password"
1446
  msgstr ""
1447
 
1448
+ #: admin/manage-fields.php:398
1449
  msgid "Type your password."
1450
  msgstr ""
1451
 
1452
+ #: admin/manage-fields.php:399, front-end/recover.php:73
1453
  msgid "Repeat Password"
1454
  msgstr ""
1455
 
1456
+ #: admin/manage-fields.php:399
1457
  msgid "Type your password again. "
1458
  msgstr ""
1459
 
1460
+ #: admin/manage-fields.php:401
1461
  msgid "Blog Details"
1462
  msgstr ""
1463
 
1464
+ #: admin/manage-fields.php:460
1465
  msgid "Select a Country"
1466
  msgstr ""
1467
 
1468
+ #: admin/manage-fields.php:461
1469
  msgid "Afghanistan"
1470
  msgstr ""
1471
 
1472
+ #: admin/manage-fields.php:462
1473
  msgid "Aland Islands"
1474
  msgstr ""
1475
 
1476
+ #: admin/manage-fields.php:463
1477
  msgid "Albania"
1478
  msgstr ""
1479
 
1480
+ #: admin/manage-fields.php:464
1481
  msgid "Algeria"
1482
  msgstr ""
1483
 
1484
+ #: admin/manage-fields.php:465
1485
  msgid "American Samoa"
1486
  msgstr ""
1487
 
1488
+ #: admin/manage-fields.php:466
1489
  msgid "Andorra"
1490
  msgstr ""
1491
 
1492
+ #: admin/manage-fields.php:467
1493
  msgid "Angola"
1494
  msgstr ""
1495
 
1496
+ #: admin/manage-fields.php:468
1497
  msgid "Anguilla"
1498
  msgstr ""
1499
 
1500
+ #: admin/manage-fields.php:469
1501
  msgid "Antarctica"
1502
  msgstr ""
1503
 
1504
+ #: admin/manage-fields.php:470
1505
  msgid "Antigua and Barbuda"
1506
  msgstr ""
1507
 
1508
+ #: admin/manage-fields.php:471
1509
  msgid "Argentina"
1510
  msgstr ""
1511
 
1512
+ #: admin/manage-fields.php:472
1513
  msgid "Armenia"
1514
  msgstr ""
1515
 
1516
+ #: admin/manage-fields.php:473
1517
  msgid "Aruba"
1518
  msgstr ""
1519
 
1520
+ #: admin/manage-fields.php:474
1521
  msgid "Australia"
1522
  msgstr ""
1523
 
1524
+ #: admin/manage-fields.php:475
1525
  msgid "Austria"
1526
  msgstr ""
1527
 
1528
+ #: admin/manage-fields.php:476
1529
  msgid "Azerbaijan"
1530
  msgstr ""
1531
 
1532
+ #: admin/manage-fields.php:477
1533
  msgid "Bahamas"
1534
  msgstr ""
1535
 
1536
+ #: admin/manage-fields.php:478
1537
  msgid "Bahrain"
1538
  msgstr ""
1539
 
1540
+ #: admin/manage-fields.php:479
1541
  msgid "Bangladesh"
1542
  msgstr ""
1543
 
1544
+ #: admin/manage-fields.php:480
1545
  msgid "Barbados"
1546
  msgstr ""
1547
 
1548
+ #: admin/manage-fields.php:481
1549
  msgid "Belarus"
1550
  msgstr ""
1551
 
1552
+ #: admin/manage-fields.php:482
1553
  msgid "Belgium"
1554
  msgstr ""
1555
 
1556
+ #: admin/manage-fields.php:483
1557
  msgid "Belize"
1558
  msgstr ""
1559
 
1560
+ #: admin/manage-fields.php:484
1561
  msgid "Benin"
1562
  msgstr ""
1563
 
1564
+ #: admin/manage-fields.php:485
1565
  msgid "Bermuda"
1566
  msgstr ""
1567
 
1568
+ #: admin/manage-fields.php:486
1569
  msgid "Bhutan"
1570
  msgstr ""
1571
 
1572
+ #: admin/manage-fields.php:487
1573
  msgid "Bolivia"
1574
  msgstr ""
1575
 
1576
+ #: admin/manage-fields.php:488
1577
  msgid "Bonaire, Saint Eustatius and Saba"
1578
  msgstr ""
1579
 
1580
+ #: admin/manage-fields.php:489
1581
  msgid "Bosnia and Herzegovina"
1582
  msgstr ""
1583
 
1584
+ #: admin/manage-fields.php:490
1585
  msgid "Botswana"
1586
  msgstr ""
1587
 
1588
+ #: admin/manage-fields.php:491
1589
  msgid "Bouvet Island"
1590
  msgstr ""
1591
 
1592
+ #: admin/manage-fields.php:492
1593
  msgid "Brazil"
1594
  msgstr ""
1595
 
1596
+ #: admin/manage-fields.php:493
1597
  msgid "British Indian Ocean Territory"
1598
  msgstr ""
1599
 
1600
+ #: admin/manage-fields.php:494
1601
  msgid "British Virgin Islands"
1602
  msgstr ""
1603
 
1604
+ #: admin/manage-fields.php:495
1605
  msgid "Brunei"
1606
  msgstr ""
1607
 
1608
+ #: admin/manage-fields.php:496
1609
  msgid "Bulgaria"
1610
  msgstr ""
1611
 
1612
+ #: admin/manage-fields.php:497
1613
  msgid "Burkina Faso"
1614
  msgstr ""
1615
 
1616
+ #: admin/manage-fields.php:498
1617
  msgid "Burundi"
1618
  msgstr ""
1619
 
1620
+ #: admin/manage-fields.php:499
1621
  msgid "Cambodia"
1622
  msgstr ""
1623
 
1624
+ #: admin/manage-fields.php:500
1625
  msgid "Cameroon"
1626
  msgstr ""
1627
 
1628
+ #: admin/manage-fields.php:501
1629
  msgid "Canada"
1630
  msgstr ""
1631
 
1632
+ #: admin/manage-fields.php:502
1633
  msgid "Cape Verde"
1634
  msgstr ""
1635
 
1636
+ #: admin/manage-fields.php:503
1637
  msgid "Cayman Islands"
1638
  msgstr ""
1639
 
1640
+ #: admin/manage-fields.php:504
1641
  msgid "Central African Republic"
1642
  msgstr ""
1643
 
1644
+ #: admin/manage-fields.php:505
1645
  msgid "Chad"
1646
  msgstr ""
1647
 
1648
+ #: admin/manage-fields.php:506
1649
  msgid "Chile"
1650
  msgstr ""
1651
 
1652
+ #: admin/manage-fields.php:507
1653
  msgid "China"
1654
  msgstr ""
1655
 
1656
+ #: admin/manage-fields.php:508
1657
  msgid "Christmas Island"
1658
  msgstr ""
1659
 
1660
+ #: admin/manage-fields.php:509
1661
  msgid "Cocos Islands"
1662
  msgstr ""
1663
 
1664
+ #: admin/manage-fields.php:510
1665
  msgid "Colombia"
1666
  msgstr ""
1667
 
1668
+ #: admin/manage-fields.php:511
1669
  msgid "Comoros"
1670
  msgstr ""
1671
 
1672
+ #: admin/manage-fields.php:512
1673
  msgid "Cook Islands"
1674
  msgstr ""
1675
 
1676
+ #: admin/manage-fields.php:513
1677
  msgid "Costa Rica"
1678
  msgstr ""
1679
 
1680
+ #: admin/manage-fields.php:514
1681
  msgid "Croatia"
1682
  msgstr ""
1683
 
1684
+ #: admin/manage-fields.php:515
1685
  msgid "Cuba"
1686
  msgstr ""
1687
 
1688
+ #: admin/manage-fields.php:516
1689
  msgid "Curacao"
1690
  msgstr ""
1691
 
1692
+ #: admin/manage-fields.php:517
1693
  msgid "Cyprus"
1694
  msgstr ""
1695
 
1696
+ #: admin/manage-fields.php:518
1697
  msgid "Czech Republic"
1698
  msgstr ""
1699
 
1700
+ #: admin/manage-fields.php:519
1701
  msgid "Democratic Republic of the Congo"
1702
  msgstr ""
1703
 
1704
+ #: admin/manage-fields.php:520
1705
  msgid "Denmark"
1706
  msgstr ""
1707
 
1708
+ #: admin/manage-fields.php:521
1709
  msgid "Djibouti"
1710
  msgstr ""
1711
 
1712
+ #: admin/manage-fields.php:522
1713
  msgid "Dominica"
1714
  msgstr ""
1715
 
1716
+ #: admin/manage-fields.php:523
1717
  msgid "Dominican Republic"
1718
  msgstr ""
1719
 
1720
+ #: admin/manage-fields.php:524
1721
  msgid "East Timor"
1722
  msgstr ""
1723
 
1724
+ #: admin/manage-fields.php:525
1725
  msgid "Ecuador"
1726
  msgstr ""
1727
 
1728
+ #: admin/manage-fields.php:526
1729
  msgid "Egypt"
1730
  msgstr ""
1731
 
1732
+ #: admin/manage-fields.php:527
1733
  msgid "El Salvador"
1734
  msgstr ""
1735
 
1736
+ #: admin/manage-fields.php:528
1737
  msgid "Equatorial Guinea"
1738
  msgstr ""
1739
 
1740
+ #: admin/manage-fields.php:529
1741
  msgid "Eritrea"
1742
  msgstr ""
1743
 
1744
+ #: admin/manage-fields.php:530
1745
  msgid "Estonia"
1746
  msgstr ""
1747
 
1748
+ #: admin/manage-fields.php:531
1749
  msgid "Ethiopia"
1750
  msgstr ""
1751
 
1752
+ #: admin/manage-fields.php:532
1753
  msgid "Falkland Islands"
1754
  msgstr ""
1755
 
1756
+ #: admin/manage-fields.php:533
1757
  msgid "Faroe Islands"
1758
  msgstr ""
1759
 
1760
+ #: admin/manage-fields.php:534
1761
  msgid "Fiji"
1762
  msgstr ""
1763
 
1764
+ #: admin/manage-fields.php:535
1765
  msgid "Finland"
1766
  msgstr ""
1767
 
1768
+ #: admin/manage-fields.php:536
1769
  msgid "France"
1770
  msgstr ""
1771
 
1772
+ #: admin/manage-fields.php:537
1773
  msgid "French Guiana"
1774
  msgstr ""
1775
 
1776
+ #: admin/manage-fields.php:538
1777
  msgid "French Polynesia"
1778
  msgstr ""
1779
 
1780
+ #: admin/manage-fields.php:539
1781
  msgid "French Southern Territories"
1782
  msgstr ""
1783
 
1784
+ #: admin/manage-fields.php:540
1785
  msgid "Gabon"
1786
  msgstr ""
1787
 
1788
+ #: admin/manage-fields.php:541
1789
  msgid "Gambia"
1790
  msgstr ""
1791
 
1792
+ #: admin/manage-fields.php:542
1793
  msgid "Georgia"
1794
  msgstr ""
1795
 
1796
+ #: admin/manage-fields.php:543
1797
  msgid "Germany"
1798
  msgstr ""
1799
 
1800
+ #: admin/manage-fields.php:544
1801
  msgid "Ghana"
1802
  msgstr ""
1803
 
1804
+ #: admin/manage-fields.php:545
1805
  msgid "Gibraltar"
1806
  msgstr ""
1807
 
1808
+ #: admin/manage-fields.php:546
1809
  msgid "Greece"
1810
  msgstr ""
1811
 
1812
+ #: admin/manage-fields.php:547
1813
  msgid "Greenland"
1814
  msgstr ""
1815
 
1816
+ #: admin/manage-fields.php:548
1817
  msgid "Grenada"
1818
  msgstr ""
1819
 
1820
+ #: admin/manage-fields.php:549
1821
  msgid "Guadeloupe"
1822
  msgstr ""
1823
 
1824
+ #: admin/manage-fields.php:550
1825
  msgid "Guam"
1826
  msgstr ""
1827
 
1828
+ #: admin/manage-fields.php:551
1829
  msgid "Guatemala"
1830
  msgstr ""
1831
 
1832
+ #: admin/manage-fields.php:552
1833
  msgid "Guernsey"
1834
  msgstr ""
1835
 
1836
+ #: admin/manage-fields.php:553
1837
  msgid "Guinea"
1838
  msgstr ""
1839
 
1840
+ #: admin/manage-fields.php:554
1841
  msgid "Guinea-Bissau"
1842
  msgstr ""
1843
 
1844
+ #: admin/manage-fields.php:555
1845
  msgid "Guyana"
1846
  msgstr ""
1847
 
1848
+ #: admin/manage-fields.php:556
1849
  msgid "Haiti"
1850
  msgstr ""
1851
 
1852
+ #: admin/manage-fields.php:557
1853
  msgid "Heard Island and McDonald Islands"
1854
  msgstr ""
1855
 
1856
+ #: admin/manage-fields.php:558
1857
  msgid "Honduras"
1858
  msgstr ""
1859
 
1860
+ #: admin/manage-fields.php:559
1861
  msgid "Hong Kong"
1862
  msgstr ""
1863
 
1864
+ #: admin/manage-fields.php:560
1865
  msgid "Hungary"
1866
  msgstr ""
1867
 
1868
+ #: admin/manage-fields.php:561
1869
  msgid "Iceland"
1870
  msgstr ""
1871
 
1872
+ #: admin/manage-fields.php:562
1873
  msgid "India"
1874
  msgstr ""
1875
 
1876
+ #: admin/manage-fields.php:563
1877
  msgid "Indonesia"
1878
  msgstr ""
1879
 
1880
+ #: admin/manage-fields.php:564
1881
  msgid "Iran"
1882
  msgstr ""
1883
 
1884
+ #: admin/manage-fields.php:565
1885
  msgid "Iraq"
1886
  msgstr ""
1887
 
1888
+ #: admin/manage-fields.php:566
1889
  msgid "Ireland"
1890
  msgstr ""
1891
 
1892
+ #: admin/manage-fields.php:567
1893
  msgid "Isle of Man"
1894
  msgstr ""
1895
 
1896
+ #: admin/manage-fields.php:568
1897
  msgid "Israel"
1898
  msgstr ""
1899
 
1900
+ #: admin/manage-fields.php:569
1901
  msgid "Italy"
1902
  msgstr ""
1903
 
1904
+ #: admin/manage-fields.php:570
1905
  msgid "Ivory Coast"
1906
  msgstr ""
1907
 
1908
+ #: admin/manage-fields.php:571
1909
  msgid "Jamaica"
1910
  msgstr ""
1911
 
1912
+ #: admin/manage-fields.php:572
1913
  msgid "Japan"
1914
  msgstr ""
1915
 
1916
+ #: admin/manage-fields.php:573
1917
  msgid "Jersey"
1918
  msgstr ""
1919
 
1920
+ #: admin/manage-fields.php:574
1921
  msgid "Jordan"
1922
  msgstr ""
1923
 
1924
+ #: admin/manage-fields.php:575
1925
  msgid "Kazakhstan"
1926
  msgstr ""
1927
 
1928
+ #: admin/manage-fields.php:576
1929
  msgid "Kenya"
1930
  msgstr ""
1931
 
1932
+ #: admin/manage-fields.php:577
1933
  msgid "Kiribati"
1934
  msgstr ""
1935
 
1936
+ #: admin/manage-fields.php:578
1937
  msgid "Kosovo"
1938
  msgstr ""
1939
 
1940
+ #: admin/manage-fields.php:579
1941
  msgid "Kuwait"
1942
  msgstr ""
1943
 
1944
+ #: admin/manage-fields.php:580
1945
  msgid "Kyrgyzstan"
1946
  msgstr ""
1947
 
1948
+ #: admin/manage-fields.php:581
1949
  msgid "Laos"
1950
  msgstr ""
1951
 
1952
+ #: admin/manage-fields.php:582
1953
  msgid "Latvia"
1954
  msgstr ""
1955
 
1956
+ #: admin/manage-fields.php:583
1957
  msgid "Lebanon"
1958
  msgstr ""
1959
 
1960
+ #: admin/manage-fields.php:584
1961
  msgid "Lesotho"
1962
  msgstr ""
1963
 
1964
+ #: admin/manage-fields.php:585
1965
  msgid "Liberia"
1966
  msgstr ""
1967
 
1968
+ #: admin/manage-fields.php:586
1969
  msgid "Libya"
1970
  msgstr ""
1971
 
1972
+ #: admin/manage-fields.php:587
1973
  msgid "Liechtenstein"
1974
  msgstr ""
1975
 
1976
+ #: admin/manage-fields.php:588
1977
  msgid "Lithuania"
1978
  msgstr ""
1979
 
1980
+ #: admin/manage-fields.php:589
1981
  msgid "Luxembourg"
1982
  msgstr ""
1983
 
1984
+ #: admin/manage-fields.php:590
1985
  msgid "Macao"
1986
  msgstr ""
1987
 
1988
+ #: admin/manage-fields.php:591
1989
  msgid "Macedonia"
1990
  msgstr ""
1991
 
1992
+ #: admin/manage-fields.php:592
1993
  msgid "Madagascar"
1994
  msgstr ""
1995
 
1996
+ #: admin/manage-fields.php:593
1997
  msgid "Malawi"
1998
  msgstr ""
1999
 
2000
+ #: admin/manage-fields.php:594
2001
  msgid "Malaysia"
2002
  msgstr ""
2003
 
2004
+ #: admin/manage-fields.php:595
2005
  msgid "Maldives"
2006
  msgstr ""
2007
 
2008
+ #: admin/manage-fields.php:596
2009
  msgid "Mali"
2010
  msgstr ""
2011
 
2012
+ #: admin/manage-fields.php:597
2013
  msgid "Malta"
2014
  msgstr ""
2015
 
2016
+ #: admin/manage-fields.php:598
2017
  msgid "Marshall Islands"
2018
  msgstr ""
2019
 
2020
+ #: admin/manage-fields.php:599
2021
  msgid "Martinique"
2022
  msgstr ""
2023
 
2024
+ #: admin/manage-fields.php:600
2025
  msgid "Mauritania"
2026
  msgstr ""
2027
 
2028
+ #: admin/manage-fields.php:601
2029
  msgid "Mauritius"
2030
  msgstr ""
2031
 
2032
+ #: admin/manage-fields.php:602
2033
  msgid "Mayotte"
2034
  msgstr ""
2035
 
2036
+ #: admin/manage-fields.php:603
2037
  msgid "Mexico"
2038
  msgstr ""
2039
 
2040
+ #: admin/manage-fields.php:604
2041
  msgid "Micronesia"
2042
  msgstr ""
2043
 
2044
+ #: admin/manage-fields.php:605
2045
  msgid "Moldova"
2046
  msgstr ""
2047
 
2048
+ #: admin/manage-fields.php:606
2049
  msgid "Monaco"
2050
  msgstr ""
2051
 
2052
+ #: admin/manage-fields.php:607
2053
  msgid "Mongolia"
2054
  msgstr ""
2055
 
2056
+ #: admin/manage-fields.php:608
2057
  msgid "Montenegro"
2058
  msgstr ""
2059
 
2060
+ #: admin/manage-fields.php:609
2061
  msgid "Montserrat"
2062
  msgstr ""
2063
 
2064
+ #: admin/manage-fields.php:610
2065
  msgid "Morocco"
2066
  msgstr ""
2067
 
2068
+ #: admin/manage-fields.php:611
2069
  msgid "Mozambique"
2070
  msgstr ""
2071
 
2072
+ #: admin/manage-fields.php:612
2073
  msgid "Myanmar"
2074
  msgstr ""
2075
 
2076
+ #: admin/manage-fields.php:613
2077
  msgid "Namibia"
2078
  msgstr ""
2079
 
2080
+ #: admin/manage-fields.php:614
2081
  msgid "Nauru"
2082
  msgstr ""
2083
 
2084
+ #: admin/manage-fields.php:615
2085
  msgid "Nepal"
2086
  msgstr ""
2087
 
2088
+ #: admin/manage-fields.php:616
2089
  msgid "Netherlands"
2090
  msgstr ""
2091
 
2092
+ #: admin/manage-fields.php:617
2093
  msgid "New Caledonia"
2094
  msgstr ""
2095
 
2096
+ #: admin/manage-fields.php:618
2097
  msgid "New Zealand"
2098
  msgstr ""
2099
 
2100
+ #: admin/manage-fields.php:619
2101
  msgid "Nicaragua"
2102
  msgstr ""
2103
 
2104
+ #: admin/manage-fields.php:620
2105
  msgid "Niger"
2106
  msgstr ""
2107
 
2108
+ #: admin/manage-fields.php:621
2109
  msgid "Nigeria"
2110
  msgstr ""
2111
 
2112
+ #: admin/manage-fields.php:622
2113
  msgid "Niue"
2114
  msgstr ""
2115
 
2116
+ #: admin/manage-fields.php:623
2117
  msgid "Norfolk Island"
2118
  msgstr ""
2119
 
2120
+ #: admin/manage-fields.php:624
2121
  msgid "North Korea"
2122
  msgstr ""
2123
 
2124
+ #: admin/manage-fields.php:625
2125
  msgid "Northern Mariana Islands"
2126
  msgstr ""
2127
 
2128
+ #: admin/manage-fields.php:626
2129
  msgid "Norway"
2130
  msgstr ""
2131
 
2132
+ #: admin/manage-fields.php:627
2133
  msgid "Oman"
2134
  msgstr ""
2135
 
2136
+ #: admin/manage-fields.php:628
2137
  msgid "Pakistan"
2138
  msgstr ""
2139
 
2140
+ #: admin/manage-fields.php:629
2141
  msgid "Palau"
2142
  msgstr ""
2143
 
2144
+ #: admin/manage-fields.php:630
2145
  msgid "Palestinian Territory"
2146
  msgstr ""
2147
 
2148
+ #: admin/manage-fields.php:631
2149
  msgid "Panama"
2150
  msgstr ""
2151
 
2152
+ #: admin/manage-fields.php:632
2153
  msgid "Papua New Guinea"
2154
  msgstr ""
2155
 
2156
+ #: admin/manage-fields.php:633
2157
  msgid "Paraguay"
2158
  msgstr ""
2159
 
2160
+ #: admin/manage-fields.php:634
2161
  msgid "Peru"
2162
  msgstr ""
2163
 
2164
+ #: admin/manage-fields.php:635
2165
  msgid "Philippines"
2166
  msgstr ""
2167
 
2168
+ #: admin/manage-fields.php:636
2169
  msgid "Pitcairn"
2170
  msgstr ""
2171
 
2172
+ #: admin/manage-fields.php:637
2173
  msgid "Poland"
2174
  msgstr ""
2175
 
2176
+ #: admin/manage-fields.php:638
2177
  msgid "Portugal"
2178
  msgstr ""
2179
 
2180
+ #: admin/manage-fields.php:639
2181
  msgid "Puerto Rico"
2182
  msgstr ""
2183
 
2184
+ #: admin/manage-fields.php:640
2185
  msgid "Qatar"
2186
  msgstr ""
2187
 
2188
+ #: admin/manage-fields.php:641
2189
  msgid "Republic of the Congo"
2190
  msgstr ""
2191
 
2192
+ #: admin/manage-fields.php:642
2193
  msgid "Reunion"
2194
  msgstr ""
2195
 
2196
+ #: admin/manage-fields.php:643
2197
  msgid "Romania"
2198
  msgstr ""
2199
 
2200
+ #: admin/manage-fields.php:644
2201
  msgid "Russia"
2202
  msgstr ""
2203
 
2204
+ #: admin/manage-fields.php:645
2205
  msgid "Rwanda"
2206
  msgstr ""
2207
 
2208
+ #: admin/manage-fields.php:646
2209
  msgid "Saint Barthelemy"
2210
  msgstr ""
2211
 
2212
+ #: admin/manage-fields.php:647
2213
  msgid "Saint Helena"
2214
  msgstr ""
2215
 
2216
+ #: admin/manage-fields.php:648
2217
  msgid "Saint Kitts and Nevis"
2218
  msgstr ""
2219
 
2220
+ #: admin/manage-fields.php:649
2221
  msgid "Saint Lucia"
2222
  msgstr ""
2223
 
2224
+ #: admin/manage-fields.php:650
2225
  msgid "Saint Martin"
2226
  msgstr ""
2227
 
2228
+ #: admin/manage-fields.php:651
2229
  msgid "Saint Pierre and Miquelon"
2230
  msgstr ""
2231
 
2232
+ #: admin/manage-fields.php:652
2233
  msgid "Saint Vincent and the Grenadines"
2234
  msgstr ""
2235
 
2236
+ #: admin/manage-fields.php:653
2237
  msgid "Samoa"
2238
  msgstr ""
2239
 
2240
+ #: admin/manage-fields.php:654
2241
  msgid "San Marino"
2242
  msgstr ""
2243
 
2244
+ #: admin/manage-fields.php:655
2245
  msgid "Sao Tome and Principe"
2246
  msgstr ""
2247
 
2248
+ #: admin/manage-fields.php:656
2249
  msgid "Saudi Arabia"
2250
  msgstr ""
2251
 
2252
+ #: admin/manage-fields.php:657
2253
  msgid "Senegal"
2254
  msgstr ""
2255
 
2256
+ #: admin/manage-fields.php:658
2257
  msgid "Serbia"
2258
  msgstr ""
2259
 
2260
+ #: admin/manage-fields.php:659
2261
  msgid "Seychelles"
2262
  msgstr ""
2263
 
2264
+ #: admin/manage-fields.php:660
2265
  msgid "Sierra Leone"
2266
  msgstr ""
2267
 
2268
+ #: admin/manage-fields.php:661
2269
  msgid "Singapore"
2270
  msgstr ""
2271
 
2272
+ #: admin/manage-fields.php:662
2273
  msgid "Sint Maarten"
2274
  msgstr ""
2275
 
2276
+ #: admin/manage-fields.php:663
2277
  msgid "Slovakia"
2278
  msgstr ""
2279
 
2280
+ #: admin/manage-fields.php:664
2281
  msgid "Slovenia"
2282
  msgstr ""
2283
 
2284
+ #: admin/manage-fields.php:665
2285
  msgid "Solomon Islands"
2286
  msgstr ""
2287
 
2288
+ #: admin/manage-fields.php:666
2289
  msgid "Somalia"
2290
  msgstr ""
2291
 
2292
+ #: admin/manage-fields.php:667
2293
  msgid "South Africa"
2294
  msgstr ""
2295
 
2296
+ #: admin/manage-fields.php:668
2297
  msgid "South Georgia and the South Sandwich Islands"
2298
  msgstr ""
2299
 
2300
+ #: admin/manage-fields.php:669
2301
  msgid "South Korea"
2302
  msgstr ""
2303
 
2304
+ #: admin/manage-fields.php:670
2305
  msgid "South Sudan"
2306
  msgstr ""
2307
 
2308
+ #: admin/manage-fields.php:671
2309
  msgid "Spain"
2310
  msgstr ""
2311
 
2312
+ #: admin/manage-fields.php:672
2313
  msgid "Sri Lanka"
2314
  msgstr ""
2315
 
2316
+ #: admin/manage-fields.php:673
2317
  msgid "Sudan"
2318
  msgstr ""
2319
 
2320
+ #: admin/manage-fields.php:674
2321
  msgid "Suriname"
2322
  msgstr ""
2323
 
2324
+ #: admin/manage-fields.php:675
2325
  msgid "Svalbard and Jan Mayen"
2326
  msgstr ""
2327
 
2328
+ #: admin/manage-fields.php:676
2329
  msgid "Swaziland"
2330
  msgstr ""
2331
 
2332
+ #: admin/manage-fields.php:677
2333
  msgid "Sweden"
2334
  msgstr ""
2335
 
2336
+ #: admin/manage-fields.php:678
2337
  msgid "Switzerland"
2338
  msgstr ""
2339
 
2340
+ #: admin/manage-fields.php:679
2341
  msgid "Syria"
2342
  msgstr ""
2343
 
2344
+ #: admin/manage-fields.php:680
2345
  msgid "Taiwan"
2346
  msgstr ""
2347
 
2348
+ #: admin/manage-fields.php:681
2349
  msgid "Tajikistan"
2350
  msgstr ""
2351
 
2352
+ #: admin/manage-fields.php:682
2353
  msgid "Tanzania"
2354
  msgstr ""
2355
 
2356
+ #: admin/manage-fields.php:683
2357
  msgid "Thailand"
2358
  msgstr ""
2359
 
2360
+ #: admin/manage-fields.php:684
2361
  msgid "Togo"
2362
  msgstr ""
2363
 
2364
+ #: admin/manage-fields.php:685
2365
  msgid "Tokelau"
2366
  msgstr ""
2367
 
2368
+ #: admin/manage-fields.php:686
2369
  msgid "Tonga"
2370
  msgstr ""
2371
 
2372
+ #: admin/manage-fields.php:687
2373
  msgid "Trinidad and Tobago"
2374
  msgstr ""
2375
 
2376
+ #: admin/manage-fields.php:688
2377
  msgid "Tunisia"
2378
  msgstr ""
2379
 
2380
+ #: admin/manage-fields.php:689
2381
  msgid "Turkey"
2382
  msgstr ""
2383
 
2384
+ #: admin/manage-fields.php:690
2385
  msgid "Turkmenistan"
2386
  msgstr ""
2387
 
2388
+ #: admin/manage-fields.php:691
2389
  msgid "Turks and Caicos Islands"
2390
  msgstr ""
2391
 
2392
+ #: admin/manage-fields.php:692
2393
  msgid "Tuvalu"
2394
  msgstr ""
2395
 
2396
+ #: admin/manage-fields.php:693
2397
  msgid "U.S. Virgin Islands"
2398
  msgstr ""
2399
 
2400
+ #: admin/manage-fields.php:694
2401
  msgid "Uganda"
2402
  msgstr ""
2403
 
2404
+ #: admin/manage-fields.php:695
2405
  msgid "Ukraine"
2406
  msgstr ""
2407
 
2408
+ #: admin/manage-fields.php:696
2409
  msgid "United Arab Emirates"
2410
  msgstr ""
2411
 
2412
+ #: admin/manage-fields.php:697
2413
  msgid "United Kingdom"
2414
  msgstr ""
2415
 
2416
+ #: admin/manage-fields.php:698
2417
  msgid "United States"
2418
  msgstr ""
2419
 
2420
+ #: admin/manage-fields.php:699
2421
  msgid "United States Minor Outlying Islands"
2422
  msgstr ""
2423
 
2424
+ #: admin/manage-fields.php:700
2425
  msgid "Uruguay"
2426
  msgstr ""
2427
 
2428
+ #: admin/manage-fields.php:701
2429
  msgid "Uzbekistan"
2430
  msgstr ""
2431
 
2432
+ #: admin/manage-fields.php:702
2433
  msgid "Vanuatu"
2434
  msgstr ""
2435
 
2436
+ #: admin/manage-fields.php:703
2437
  msgid "Vatican"
2438
  msgstr ""
2439
 
2440
+ #: admin/manage-fields.php:704
2441
  msgid "Venezuela"
2442
  msgstr ""
2443
 
2444
+ #: admin/manage-fields.php:705
2445
  msgid "Vietnam"
2446
  msgstr ""
2447
 
2448
+ #: admin/manage-fields.php:706
2449
  msgid "Wallis and Futuna"
2450
  msgstr ""
2451
 
2452
+ #: admin/manage-fields.php:707
2453
  msgid "Western Sahara"
2454
  msgstr ""
2455
 
2456
+ #: admin/manage-fields.php:708
2457
  msgid "Yemen"
2458
  msgstr ""
2459
 
2460
+ #: admin/manage-fields.php:709
2461
  msgid "Zambia"
2462
  msgstr ""
2463
 
2464
+ #: admin/manage-fields.php:710
2465
  msgid "Zimbabwe"
2466
  msgstr ""
2467
 
2468
+ #: admin/manage-fields.php:743
2469
  msgid "Albania Lek"
2470
  msgstr ""
2471
 
2472
+ #: admin/manage-fields.php:744
2473
  msgid "Afghanistan Afghani"
2474
  msgstr ""
2475
 
2476
+ #: admin/manage-fields.php:745
2477
  msgid "Argentina Peso"
2478
  msgstr ""
2479
 
2480
+ #: admin/manage-fields.php:746
2481
  msgid "Aruba Guilder"
2482
  msgstr ""
2483
 
2484
+ #: admin/manage-fields.php:747
2485
  msgid "Australia Dollar"
2486
  msgstr ""
2487
 
2488
+ #: admin/manage-fields.php:748
2489
  msgid "Azerbaijan New Manat"
2490
  msgstr ""
2491
 
2492
+ #: adm