Scripts n Styles - Version 3

Version Description

  • AJAX Saving of Meta-box
  • Dynamically populate the Styles Dropdown for TinyMCE
  • Styles preview in Post Editor
  • Enqueue dependant scripts if you need (like jQuery)
  • Adjustable menu placement
  • CodeMirror Themes
Download this release

Release Info

Developer WraithKenny
Plugin Icon wp plugin Scripts n Styles
Version 3
Comparing to
See all releases

Code changes from version 2.0.4 to 3

Files changed (49) hide show
  1. README.txt +33 -11
  2. css/meta-box-styles.css +90 -63
  3. css/options-styles.css +12 -2
  4. images/icon32.png +0 -0
  5. images/menu.png +0 -0
  6. includes/class.SnS_AJAX.php +200 -0
  7. includes/class.SnS_Admin.php +211 -83
  8. includes/class.SnS_Admin_Meta_Box.php +288 -110
  9. includes/class.SnS_Form.php +144 -0
  10. includes/class.SnS_Global_Page.php +136 -0
  11. includes/class.SnS_List_Usage.php +176 -0
  12. includes/class.SnS_Settings_Page.php +97 -232
  13. includes/class.SnS_Usage_Page.php +87 -0
  14. js/{options-scripts.js → global-page.js} +3 -2
  15. js/meta-box-scripts.js +424 -8
  16. js/settings-page.js +18 -0
  17. libraries/{codemirror → CodeMirror2}/LICENSE +0 -0
  18. libraries/{codemirror → CodeMirror2}/lib/codemirror.css +13 -0
  19. libraries/{codemirror → CodeMirror2}/lib/codemirror.js +1238 -524
  20. libraries/CodeMirror2/mode/clike/clike.js +247 -0
  21. libraries/CodeMirror2/mode/clike/index.html +102 -0
  22. libraries/{codemirror/mode → CodeMirror2/mode/css}/css.js +3 -3
  23. libraries/CodeMirror2/mode/css/index.html +56 -0
  24. libraries/{codemirror/mode → CodeMirror2/mode/htmlmixed}/htmlmixed.js +11 -2
  25. libraries/CodeMirror2/mode/htmlmixed/index.html +52 -0
  26. libraries/CodeMirror2/mode/javascript/index.html +78 -0
  27. libraries/{codemirror/mode → CodeMirror2/mode/javascript}/javascript.js +15 -5
  28. libraries/CodeMirror2/mode/php/index.html +49 -0
  29. libraries/CodeMirror2/mode/php/php.js +120 -0
  30. libraries/CodeMirror2/mode/xml/index.html +45 -0
  31. libraries/{codemirror/mode → CodeMirror2/mode/xml}/xml.js +34 -7
  32. libraries/CodeMirror2/theme/cobalt.css +17 -0
  33. libraries/{codemirror → CodeMirror2}/theme/default.css +2 -1
  34. libraries/CodeMirror2/theme/eclipse.css +24 -0
  35. libraries/{codemirror → CodeMirror2}/theme/elegant.css +0 -0
  36. libraries/CodeMirror2/theme/monokai.css +27 -0
  37. libraries/{codemirror → CodeMirror2}/theme/neat.css +0 -0
  38. libraries/{codemirror → CodeMirror2}/theme/night.css +3 -3
  39. libraries/CodeMirror2/theme/rubyblue.css +20 -0
  40. libraries/codemirror/lib/overlay.js +0 -51
  41. libraries/codemirror/lib/runmode.js +0 -27
  42. screenshot-1.png +0 -0
  43. screenshot-2.png +0 -0
  44. screenshot-3.png +0 -0
  45. screenshot-4.png +0 -0
  46. screenshot-5.png +0 -0
  47. screenshot-6.png +0 -0
  48. scripts-n-styles.php +312 -315
  49. uninstall.php +18 -12
README.txt CHANGED
@@ -2,23 +2,26 @@
2
  Contributors: WraithKenny, Touvan
3
  Donate link: http://wordpressfoundation.org/donate/
4
  Tags: admin, CSS, javascript, code, custom, Style
5
- Requires at least: 3.1
6
- Tested up to: 3.2
7
- Stable tag: 2.0.4
 
8
 
9
  This plugin allows Admin users to individually add custom CSS, Classes and JavaScript directly to Post, Pages or any other custom post types.
10
 
11
  == Description ==
12
 
13
- This plugin allows Admin users the ability to add custom CSS (at the bottom of the 'head' tag) and JavaScript (at the bottom of the 'body' tag) directly into individual Post, Pages or any other registered custom post types. You can also add classes to the body tag and the post container (if your theme supports `body_class()` and `post_class()` functions).
 
 
14
 
15
  Because only well trusted users should ever be allowed to insert JavaScript directly into the pages of your site, this plugin restricts usage to admin type users. Admin's have access to even more sensitive areas by definition, so that should be relatively safe ;)
16
 
17
  A few notes about the implementation:
18
 
19
- * Admin users, or more specifically, *any user with the `manage_options` capability* (which by default is *only* the admin type user) can use this plugin's functionality. Some plugins extend user rolls, and so this plugin would naturally extend include rolls that have the appropriate capability.
20
- * CSS Styles are included inline, not linked, at the bottom of the `head` element with `style` tags by using `wp-head`. If your theme doesn't have this hook, this plugin (as well as most others) won't work.
21
- * JavaScript is included inline, not linked, at the bottom of the `body` element with `script` tags by using `wp-footer`. If your theme doesn't have this hook, this plugin (as well as most others) won't work.
22
  * **There is no input validation.** This plugin puts exactly what you type in the meta box directly into the `html` with no error checking. You are an Admin, and we trust you to be carefull. Try not to break anything.
23
 
24
  == Installation ==
@@ -37,15 +40,31 @@ Well, because plugins are supposed to, and should be expected to clean up after
37
 
38
  = Can I get around that somehow? =
39
 
40
- Sure, if you are an Admin, just go to the plugin editor and wipe out the uninstall.php (Replace everything with a space character) and then WordPress will not delete the meta data on uninstall.
41
 
42
  == Screenshots ==
43
 
44
- 1. The New and Improved Meta Box.
 
 
 
 
 
45
 
46
  == Changelog ==
47
 
48
- = 2 =
 
 
 
 
 
 
 
 
 
 
 
49
  * Better selection of `post_types` to add Scripts-n-Styles
50
  * micro-optimization for storage of class names.
51
  * Adds option page for globally adding Scripts and Styles.
@@ -71,6 +90,9 @@ Sure, if you are an Admin, just go to the plugin editor and wipe out the uninsta
71
 
72
  == Upgrade Notice ==
73
 
 
 
 
74
  = 2 =
75
  Adds new features.
76
 
@@ -84,4 +106,4 @@ Minor update. Adds a few new features.
84
  Some small plugin meta data updates.
85
 
86
  = 1.0 =
87
- Initial Release, there is nothing to upgrade from.
2
  Contributors: WraithKenny, Touvan
3
  Donate link: http://wordpressfoundation.org/donate/
4
  Tags: admin, CSS, javascript, code, custom, Style
5
+ Requires at least: 3.2
6
+ Tested up to: 3.3-RC1
7
+ Stable tag: 3
8
+ License: GPLv2 or later
9
 
10
  This plugin allows Admin users to individually add custom CSS, Classes and JavaScript directly to Post, Pages or any other custom post types.
11
 
12
  == Description ==
13
 
14
+ This plugin allows Admin users the ability to add custom CSS and JavaScript directly into individual Post, Pages or any other registered custom post types. You can also add classes to the body tag and the post container. There is a Global settings page for which you can write Scripts n Styles for the entire blog.
15
+
16
+ Admin's can also add classes to the TinyMCE "Formats" dropdown which users can use to style posts and pages directly. As of Scripts n Styles 3+ styles are reflected in the post editor.
17
 
18
  Because only well trusted users should ever be allowed to insert JavaScript directly into the pages of your site, this plugin restricts usage to admin type users. Admin's have access to even more sensitive areas by definition, so that should be relatively safe ;)
19
 
20
  A few notes about the implementation:
21
 
22
+ * Admin users, or more specifically, *any user with the `manage_options` and `unfiltered_html` capabilities* (which by default is *only* the admin type user) can use this plugin's functionality. Some plugins extend user rolls, and so this plugin would naturally extend include rolls that have the appropriate capability.
23
+ * CSS Styles are embeded, not linked, at the bottom of the `head` element with `style` tags by using `wp-head`. If your theme doesn't have this hook, this plugin (as well as most others) won't work.
24
+ * JavaScript is embeded, not linked, at the bottom of the `body` (or `head`) element with `script` tags by using `wp-footer` (or `wp-head`). If your theme doesn't have this hook, this plugin (as well as most others) won't work.
25
  * **There is no input validation.** This plugin puts exactly what you type in the meta box directly into the `html` with no error checking. You are an Admin, and we trust you to be carefull. Try not to break anything.
26
 
27
  == Installation ==
40
 
41
  = Can I get around that somehow? =
42
 
43
+ Sure, if you are an Admin, just go to the plugin editor and wipe out the uninstall.php and then WordPress will not delete the meta data on uninstall.
44
 
45
  == Screenshots ==
46
 
47
+ 1. Settings Page for Writing Scripts n Styles that apply to the whole blog.
48
+ 2. The Scripts panel of the Meta Box.
49
+ 3. The Styles panel of the Meta Box.
50
+ 4. The Classes panel. Add classes to the Style dropdown!
51
+ 5. Enqueue panel. You can enqueue jQuery from here if you need!
52
+ 6. Your styles are reflected in the Editor.
53
 
54
  == Changelog ==
55
 
56
+ = 3 =
57
+ * AJAX Saving of Meta-box
58
+ * Dynamically populate the Styles Dropdown for TinyMCE
59
+ * Styles preview in Post Editor
60
+ * Enqueue dependant scripts if you need (like jQuery)
61
+ * Adjustable menu placement
62
+ * CodeMirror Themes
63
+
64
+ = 2.0.3 =
65
+ * fixed some bugs
66
+
67
+ = 2.0.1 =
68
  * Better selection of `post_types` to add Scripts-n-Styles
69
  * micro-optimization for storage of class names.
70
  * Adds option page for globally adding Scripts and Styles.
90
 
91
  == Upgrade Notice ==
92
 
93
+ = 3 =
94
+ Adds new features.
95
+
96
  = 2 =
97
  Adds new features.
98
 
106
  Some small plugin meta data updates.
107
 
108
  = 1.0 =
109
+ Initial Release, there is nothing to upgrade from.
css/meta-box-styles.css CHANGED
@@ -1,101 +1,128 @@
1
  /* MetaBox.css */
2
 
3
- #uFp_meta_box .title {
 
 
 
 
4
  display: block;
 
 
5
  margin-top: 1.5em;
6
  }
7
- #uFp_meta_box .tabs-vertical .title,
8
- #uFp_meta_box .tabs-horizontal .title {
9
  display: none;
10
  }
11
-
12
- #uFp_meta_box .tabs-vertical,
13
- #uFp_meta_box .tabs-horizontal,
14
- #uFp_meta_box .ui-tabs-panel {
15
- overflow: hidden;
16
  }
17
 
18
- #uFp_meta_box .tabs-vertical .wp-tab-bar {
19
- margin-bottom: 3px;
20
- }
21
- #uFp_meta_box .tabs-vertical .wp-tab-bar li {
22
- display: inline;
23
- line-height: 1.35em;
24
- }
25
- #uFp_meta_box .tabs-vertical .wp-tab-bar .ui-state-active {
26
- background-color: #FFFFFF;
27
- border-color: #DFDFDF;
28
- border-style: solid solid none;
29
- border-width: 1px 1px 0;
30
  }
31
- #uFp_meta_box .tabs-vertical .wp-tab-bar .ui-state-active a {
32
- color: #333333;
33
  }
34
- #uFp_meta_box .tabs-vertical .ui-tabs-panel {
35
- border-style: solid;
36
- border-width: 1px;
37
- padding: 0.5em 0.9em;
38
- background-color: #FFFFFF;
39
- border-color: #DFDFDF;
40
  }
41
 
 
 
 
 
 
42
 
43
- #uFp_meta_box .tabs-horizontal .wp-tab-bar {
 
 
 
 
44
  float: left;
45
  margin: 0 -120px 0 5px;
46
  padding: 0;
47
  text-align: right;
48
  width: 120px;
49
  }
50
- #uFp_meta_box .tabs-horizontal .wp-tab-bar li {
51
  padding: 8px;
52
- display: block;
53
- }
54
- #uFp_meta_box .tabs-horizontal .wp-tab-bar li a {
55
- display: block;
56
  }
57
- #uFp_meta_box .tabs-horizontal .wp-tab-bar .ui-state-active {
58
- background-color: #FFFFFF;
59
- border-color: #DFDFDF;
60
  border-style: solid none solid solid;
61
  border-width: 1px 0 1px 1px;
62
  margin-right: -1px;
63
- border-radius: 3px 0 0 3px;
 
64
  font-weight: bold;
65
  text-decoration: none;
66
  }
67
- #uFp_meta_box .tabs-horizontal .wp-tab-bar .ui-state-active a {
68
- color: #333333;
69
- }
70
- #uFp_meta_box .tabs-horizontal .ui-tabs-panel {
71
- background-color: #FFFFFF;
72
- border-color: #DFDFDF;
73
  border-style: solid;
74
  border-width: 1px;
75
- min-height: 200px;
76
- margin: 0 5px 0 125px;
77
- overflow: hidden;
78
  padding: 0.5em 0.9em;
79
  }
80
- #uFp_meta_box .tabs-horizontal .inside div {
81
- overflow: hidden;
82
- }
83
 
84
- .ui-tabs .ui-tabs-hide {
85
- position: absolute;
86
- left: -10000px;
 
 
87
  display: block;
88
  }
 
 
 
 
89
 
90
- .CodeMirror {
91
- border: 1px solid #DFDFDF;
92
- background-color: white;
93
- border-radius: 3px;
94
- margin: 8px 0;
95
  }
96
- .CodeMirror-scroll {
97
- height: auto;
98
- min-height: 50px;
99
- max-height: 300px;
100
- overflow: auto;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  }
1
  /* MetaBox.css */
2
 
3
+
4
+ .wp-tab-bar {
5
+ display: none;
6
+ }
7
+ body.js .wp-tab-bar {
8
  display: block;
9
+ }
10
+ #SnS_meta_box .title {
11
  margin-top: 1.5em;
12
  }
13
+ body.js #SnS_meta_box .title,
14
+ body.js .wp-tab-panel {
15
  display: none;
16
  }
17
+ body.js .wp-tabs-panel-active {
18
+ display: block;
 
 
 
19
  }
20
 
21
+ .CodeMirror {
22
+ border: 1px solid #DFDFDF;
23
+ background-color: white;
24
+ border-radius: 3px;
25
+ margin: 8px 0;
26
+ -moz-background-clip: padding;
27
+ -webkit-background-clip: padding-box;
28
+ background-clip: padding-box;
29
+ overflow: hidden;
 
 
 
30
  }
31
+ .CodeMirror, #editorcontainer #content {
32
+ font-family: "Courier New", Courier, monospace;
33
  }
34
+ .CodeMirror-scroll {
35
+ height: auto;
36
+ min-height: 50px;
37
+ max-height: 300px;
38
+ overflow: auto;
 
39
  }
40
 
41
+ /* temp fix border-bottom rounding error problem.
42
+ #side-sortables .wp-tab-bar {
43
+ margin-bottom: 0px;
44
+ min-height: 19px;
45
+ }*/
46
 
47
+ /* core styles */
48
+ #post-body .wp-tab-bar a {
49
+ text-decoration: underline;
50
+ }
51
+ #post-body .wp-tab-bar {
52
  float: left;
53
  margin: 0 -120px 0 5px;
54
  padding: 0;
55
  text-align: right;
56
  width: 120px;
57
  }
58
+ #post-body .wp-tab-bar li {
59
  padding: 8px;
60
+ display: list-item;
 
 
 
61
  }
62
+ #post-body .wp-tab-active {
63
+ border-radius: 3px 0 0 3px;
 
64
  border-style: solid none solid solid;
65
  border-width: 1px 0 1px 1px;
66
  margin-right: -1px;
67
+ }
68
+ #post-body .wp-tab-active a {
69
  font-weight: bold;
70
  text-decoration: none;
71
  }
72
+ #post-body .wp-tab-panel {
73
+ margin: 0 5px 0 125px;
 
 
 
 
74
  border-style: solid;
75
  border-width: 1px;
76
+ height: 200px;
77
+ overflow: auto;
 
78
  padding: 0.5em 0.9em;
79
  }
80
+ /* end core styles */
 
 
81
 
82
+ #post-body #SnS_meta_box .wp-tab-bar li {
83
+ padding: 0px;
84
+ }
85
+ #post-body #SnS_meta_box .wp-tab-bar a {
86
+ padding: 8px;
87
  display: block;
88
  }
89
+ #SnS_meta_box .wp-tab-panel {
90
+ height: auto;
91
+ min-height: 200px;
92
+ }
93
 
94
+ #add-mce-dropdown-names label {
95
+ width: 50px;
96
+ display: inline-block
 
 
97
  }
98
+ .sns-ajax-loading {
99
+ vertical-align: middle;
100
+ }
101
+ .sns-ajax-wrap {
102
+ height: 23px;
103
+ }
104
+ #sns-classes {
105
+ overflow: hidden;
106
+ width: 100%;
107
+ }
108
+ #SnS_classes_mce_wrapper {
109
+ margin: 6px 0;
110
+ }
111
+ #mce-dropdown-names {
112
+ display: none;
113
+ }
114
+ body.js #mce-dropdown-names {
115
+ display: block;
116
+ }
117
+ #delete-mce-dropdown-names .sns-ajax-delete {
118
+ cursor: pointer;
119
+ display: inline-block;
120
+ height: 10px;
121
+ overflow: hidden;
122
+ text-indent: -9999px;
123
+ width: 10px;
124
+ background: url("/wp-admin/images/xit.gif") no-repeat scroll 0 0 transparent;
125
+ }
126
+ #delete-mce-dropdown-names .sns-ajax-delete:hover {
127
+ background: url("/wp-admin/images/xit.gif") no-repeat scroll -10px 0 transparent;
128
  }
css/options-styles.css CHANGED
@@ -1,14 +1,24 @@
1
  /* Options.css */
2
 
 
 
 
3
  .CodeMirror {
4
  border: 1px solid #DFDFDF;
5
  background-color: white;
6
  border-radius: 3px;
7
  margin: 8px 0;
 
 
 
 
8
  }
9
- .CodeMirror-scroll {
10
  height: auto;
11
  min-height: 50px;
12
  max-height: 300px;
13
  overflow: auto;
14
- }
 
 
 
1
  /* Options.css */
2
 
3
+ textarea.code {
4
+ display: block;
5
+ }
6
  .CodeMirror {
7
  border: 1px solid #DFDFDF;
8
  background-color: white;
9
  border-radius: 3px;
10
  margin: 8px 0;
11
+ -moz-background-clip: padding;
12
+ -webkit-background-clip: padding-box;
13
+ background-clip: padding-box;
14
+ overflow: hidden;
15
  }
16
+ body .CodeMirror-scroll {
17
  height: auto;
18
  min-height: 50px;
19
  max-height: 300px;
20
  overflow: auto;
21
+ }
22
+ #icon-sns {
23
+ background: no-repeat center url('../images/icon32.png');
24
+ }
images/icon32.png ADDED
Binary file
images/menu.png ADDED
Binary file
includes/class.SnS_AJAX.php ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ class SnS_AJAX
3
+ {
4
+ function init() {
5
+ // Keep track of current tab.
6
+ add_action( 'wp_ajax_sns_update_tab', array( __CLASS__, 'update_tab' ) );
7
+ // TinyMCE requests a css file.
8
+ add_action( 'wp_ajax_sns_tinymce_styles', array( __CLASS__, 'tinymce_styles' ) );
9
+
10
+ // Ajax Saves.
11
+ add_action( 'wp_ajax_sns_classes', array( __CLASS__, 'classes' ) );
12
+ add_action( 'wp_ajax_sns_scripts', array( __CLASS__, 'scripts' ) );
13
+ add_action( 'wp_ajax_sns_styles', array( __CLASS__, 'styles' ) );
14
+ add_action( 'wp_ajax_sns_dropdown', array( __CLASS__, 'dropdown' ) );
15
+ add_action( 'wp_ajax_sns_delete_class', array( __CLASS__, 'delete_class' ) );
16
+ }
17
+ function update_tab() {
18
+ check_ajax_referer( Scripts_n_Styles::$file );
19
+
20
+ $active_tab = isset( $_POST[ 'active_tab' ] ) ? 's'.$_POST[ 'active_tab' ] : 's0';
21
+
22
+ if ( ! $user = wp_get_current_user() ) exit( 'Bad User' );
23
+
24
+ $success = update_user_option( $user->ID, 'current_sns_tab', $active_tab, true);
25
+ exit();
26
+ }
27
+ function tinymce_styles() {
28
+ check_ajax_referer( 'sns_tinymce_styles' );
29
+
30
+ if ( empty( $_REQUEST[ 'post_id' ] ) ) exit( 'Bad post ID.' );
31
+ $post_id = absint( $_REQUEST[ 'post_id' ] );
32
+
33
+ $options = get_option( 'SnS_options' );
34
+ $SnS = get_post_meta( $post_id, '_SnS', true );
35
+ $styles = isset( $SnS['styles'] ) ? $SnS[ 'styles' ]: array();
36
+
37
+ header('Content-Type: text/css; charset=' . get_option('blog_charset'));
38
+
39
+ if ( ! empty( $options[ 'styles' ] ) ) echo $options[ 'styles' ];
40
+
41
+ if ( ! empty( $styles[ 'styles' ] ) ) echo $styles[ 'styles' ];
42
+
43
+ exit();
44
+ }
45
+
46
+ // AJAX handlers
47
+ function classes() {
48
+ check_ajax_referer( Scripts_n_Styles::$file );
49
+ if ( ! current_user_can( 'unfiltered_html' ) || ! current_user_can( 'edit_posts' ) ) exit( 'Insufficient Privileges.' );
50
+
51
+ if ( empty( $_REQUEST[ 'post_id' ] ) ) exit( 'Bad post ID.' );
52
+ if ( ! isset( $_REQUEST[ 'classes_body' ], $_REQUEST[ 'classes_post' ] ) ) exit( 'Data missing.' );
53
+
54
+ $post_id = absint( $_REQUEST[ 'post_id' ] );
55
+ $SnS = get_post_meta( $post_id, '_SnS', true );
56
+ $styles = isset( $SnS['styles'] ) ? $SnS[ 'styles' ]: array();
57
+
58
+ $styles = self::maybe_set( $styles, 'classes_body' );
59
+ $styles = self::maybe_set( $styles, 'classes_post' );
60
+
61
+ if ( ! empty( $styles ) )
62
+ $SnS[ 'styles' ] = $styles;
63
+ self::maybe_update( $post_id, '_SnS', $SnS );
64
+
65
+ header('Content-Type: application/json; charset=' . get_option('blog_charset'));
66
+ echo json_encode( array(
67
+ "classes_post" => $_REQUEST[ 'classes_post' ],
68
+ "classes_body" => $_REQUEST[ 'classes_body' ]
69
+ ) );
70
+
71
+ exit();
72
+ }
73
+ function scripts() {
74
+ check_ajax_referer( Scripts_n_Styles::$file );
75
+ if ( ! current_user_can( 'unfiltered_html' ) || ! current_user_can( 'edit_posts' ) ) exit( 'Insufficient Privileges.' );
76
+
77
+ if ( empty( $_REQUEST[ 'post_id' ] ) ) exit( 'Bad post ID.' );
78
+ if ( ! isset( $_REQUEST[ 'scripts' ], $_REQUEST[ 'scripts_in_head' ] ) ) exit( 'Data incorrectly sent.' );
79
+
80
+ $post_id = absint( $_REQUEST[ 'post_id' ] );
81
+ $SnS = get_post_meta( $post_id, '_SnS', true );
82
+ $scripts = isset( $SnS['scripts'] ) ? $SnS[ 'scripts' ]: array();
83
+
84
+ $scripts = self::maybe_set( $scripts, 'scripts_in_head' );
85
+ $scripts = self::maybe_set( $scripts, 'scripts' );
86
+
87
+ if ( ! empty( $scripts ) )
88
+ $SnS[ 'scripts' ] = $scripts;
89
+ self::maybe_update( $post_id, '_SnS', $SnS );
90
+
91
+ header('Content-Type: application/json; charset=' . get_option('blog_charset'));
92
+ echo json_encode( array(
93
+ "scripts" => $_REQUEST[ 'scripts' ],
94
+ "scripts_in_head" => $_REQUEST[ 'scripts_in_head' ],
95
+ ) );
96
+
97
+ exit();
98
+ }
99
+ function styles() {
100
+ check_ajax_referer( Scripts_n_Styles::$file );
101
+ if ( ! current_user_can( 'unfiltered_html' ) || ! current_user_can( 'edit_posts' ) ) exit( 'Insufficient Privileges.' );
102
+
103
+ if ( empty( $_REQUEST[ 'post_id' ] ) ) exit( 'Bad post ID.' );
104
+ if ( ! isset( $_REQUEST[ 'styles' ] ) ) exit( 'Data incorrectly sent.' );
105
+
106
+ $post_id = absint( $_REQUEST[ 'post_id' ] );
107
+ $SnS = get_post_meta( $post_id, '_SnS', true );
108
+ $styles = isset( $SnS['styles'] ) ? $SnS[ 'styles' ]: array();
109
+
110
+ $styles = self::maybe_set( $styles, 'styles' );
111
+
112
+ if ( ! empty( $styles ) )
113
+ $SnS[ 'styles' ] = $styles;
114
+ self::maybe_update( $post_id, '_SnS', $SnS );
115
+
116
+ header('Content-Type: application/json; charset=' . get_option('blog_charset'));
117
+ echo json_encode( array(
118
+ "styles" => $_REQUEST[ 'styles' ],
119
+ ) );
120
+
121
+ exit();
122
+ }
123
+ function dropdown() {
124
+ check_ajax_referer( Scripts_n_Styles::$file );
125
+ if ( ! current_user_can( 'unfiltered_html' ) || ! current_user_can( 'edit_posts' ) ) exit( 'Insufficient Privileges.' );
126
+
127
+ if ( empty( $_REQUEST[ 'format' ] ) ) exit( 'Missing Format.' );
128
+ if ( empty( $_REQUEST[ 'format' ][ 'title' ] ) ) exit( 'Title is required.' );
129
+ if ( empty( $_REQUEST[ 'format' ][ 'classes' ] ) ) exit( 'Classes is required.' );
130
+ if (
131
+ empty( $_REQUEST[ 'format' ][ 'inline' ] ) &&
132
+ empty( $_REQUEST[ 'format' ][ 'block' ] ) &&
133
+ empty( $_REQUEST[ 'format' ][ 'selector' ] )
134
+ ) exit( 'A type is required.' );
135
+
136
+ if ( empty( $_REQUEST[ 'post_id' ] ) ) exit( 'Bad post ID.' );
137
+ $post_id = absint( $_REQUEST[ 'post_id' ] );
138
+
139
+ $SnS = get_post_meta( $post_id, '_SnS', true );
140
+ $styles = isset( $SnS['styles'] ) ? $SnS[ 'styles' ]: array();
141
+
142
+ if ( ! isset( $styles[ 'classes_mce' ] ) ) $styles[ 'classes_mce' ] = array();
143
+
144
+ // pass title as key to be able to delete.
145
+ $styles[ 'classes_mce' ][ $_REQUEST[ 'format' ][ 'title' ] ] = $_REQUEST[ 'format' ];
146
+
147
+ if ( ! empty( $styles ) )
148
+ $SnS[ 'styles' ] = $styles;
149
+ update_post_meta( $post_id, '_SnS', $SnS );
150
+
151
+ header('Content-Type: application/json; charset=' . get_option('blog_charset'));
152
+ echo json_encode( array(
153
+ "classes_mce" => array_values( $styles[ 'classes_mce' ] )
154
+ ) );
155
+
156
+ exit();
157
+ }
158
+ function delete_class() {
159
+ check_ajax_referer( Scripts_n_Styles::$file );
160
+ if ( ! current_user_can( 'unfiltered_html' ) || ! current_user_can( 'edit_posts' ) ) exit( 'Insufficient Privileges.' );
161
+
162
+ if ( empty( $_REQUEST[ 'post_id' ] ) ) exit( 'Bad post ID.' );
163
+ $post_id = absint( $_REQUEST[ 'post_id' ] );
164
+ $SnS = get_post_meta( $post_id, '_SnS', true );
165
+ $styles = isset( $SnS['styles'] ) ? $SnS[ 'styles' ]: array();
166
+
167
+ $title = $_REQUEST[ 'delete' ];
168
+
169
+ if ( isset( $styles[ 'classes_mce' ][ $title ] ) ) unset( $styles[ 'classes_mce' ][ $title ] );
170
+ else exit ( 'No Format of that name.' );
171
+
172
+ if ( empty( $styles[ 'classes_mce' ] ) ) unset( $styles[ 'classes_mce' ] );
173
+
174
+ if ( ! empty( $styles ) )
175
+ $SnS[ 'styles' ] = $styles;
176
+ self::maybe_update( $post_id, '_SnS', $SnS );
177
+
178
+ if ( ! isset( $styles[ 'classes_mce' ] ) ) $styles[ 'classes_mce' ] = array( 'Empty' );
179
+
180
+ header('Content-Type: application/json; charset=' . get_option('blog_charset'));
181
+ echo json_encode( array(
182
+ "classes_mce" => array_values( $styles[ 'classes_mce' ] )
183
+ ) );
184
+
185
+ exit();
186
+ }
187
+
188
+ // Differs from SnS_Admin_Meta_Box::maybe_set() in that this needs no prefix.
189
+ function maybe_set( $o, $i ) {
190
+ if ( empty( $_REQUEST[ $i ] ) ) {
191
+ if ( isset( $o[ $i ] ) ) unset( $o[ $i ] );
192
+ } else $o[ $i ] = $_REQUEST[ $i ];
193
+ return $o;
194
+ }
195
+ function maybe_update( $id, $name, $meta ) {
196
+ if ( empty( $meta ) ) delete_post_meta( $id, $name );
197
+ else update_post_meta( $id, $name, $meta );
198
+ }
199
+ }
200
+ ?>
includes/class.SnS_Admin.php CHANGED
@@ -1,84 +1,212 @@
1
- <?php
2
- /**
3
- * Scripts n Styles Admin Class
4
- *
5
- * Allows WordPress admin users the ability to add custom CSS
6
- * and JavaScript directly to individual Post, Pages or custom
7
- * post types.
8
- */
9
-
10
- class SnS_Admin
11
- {
12
- /**#@+
13
- * Constants
14
- */
15
- const MENU_SLUG = 'Scripts-n-Styles';
16
- const VERSION = '2.0.2';
17
- /**#@-*/
18
-
19
- /**
20
- * Initializing method.
21
- * @static
22
- */
23
- static function init() {
24
- add_action( 'admin_menu', array( __CLASS__, 'admin_meta_box' ) );
25
- add_action( 'admin_menu', array( __CLASS__, 'settings_page' ) );
26
-
27
- $plugin_file = plugin_basename( Scripts_n_Styles::$file );
28
- add_filter( "plugin_action_links_$plugin_file", array( __CLASS__, 'plugin_action_links') );
29
-
30
- register_activation_hook( Scripts_n_Styles::$file, array( __CLASS__, 'upgrade' ) );
31
- }
32
-
33
- /**
34
- * Utility Method: Sets defaults if not previously set. Sets stored 'version' to VERSION.
35
- */
36
- static function upgrade() {
37
- $options = get_option( 'SnS_options' );
38
-
39
- if ( ! isset( $options[ 'show_usage' ] ) )
40
- $options[ 'show_usage' ] = 'no';
41
- $options[ 'version' ] = self::VERSION;
42
-
43
- update_option( 'SnS_options', $options );
44
- }
45
-
46
- /**
47
- * Utility Method: Compares VERSION to stored 'version' value.
48
- */
49
- static function upgrade_check() {
50
- if ( ! isset( $options[ 'version' ] ) || version_compare( self::VERSION, $options[ 'version' ], '>' ) )
51
- self::upgrade();
52
- }
53
-
54
- /**
55
- * Adds link to the Settings Page in the WordPress "Plugin Action Links" array.
56
- * @param array $actions
57
- * @return array
58
- */
59
- static function plugin_action_links( $actions ) {
60
- $actions[ 'settings' ] = '<a href="' . menu_page_url( self::MENU_SLUG, false ) . '"/>Settings</a>';
61
- return $actions;
62
- }
63
-
64
- /**
65
- * Settings Page
66
- * Adds Admin Menu Item via WordPress' "Administration Menus" API. Also hook actions to register options via WordPress' Settings API.
67
- */
68
- static function admin_meta_box() {
69
- include_once( 'class.SnS_Admin_Meta_Box.php' );
70
- SnS_Admin_Meta_Box::init();
71
- }
72
-
73
- /**
74
- * Settings Page
75
- * Adds Admin Menu Item via WordPress' "Administration Menus" API. Also hook actions to register options via WordPress' Settings API.
76
- */
77
- static function settings_page() {
78
- /* NOTE: Even when Scripts n Styles is not restricted by 'manage_options', Editors still can't submit the option page */
79
- include_once( 'class.SnS_Settings_Page.php' );
80
- SnS_Settings_Page::init();
81
- }
82
- }
83
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  ?>
1
+ <?php
2
+ /**
3
+ * Scripts n Styles Admin Class
4
+ *
5
+ * Allows WordPress admin users the ability to add custom CSS
6
+ * and JavaScript directly to individual Post, Pages or custom
7
+ * post types.
8
+ */
9
+
10
+ require_once( 'class.SnS_Admin_Meta_Box.php' );
11
+ require_once( 'class.SnS_Settings_Page.php' );
12
+ require_once( 'class.SnS_Usage_Page.php' );
13
+ require_once( 'class.SnS_Global_Page.php' );
14
+ require_once( 'class.SnS_AJAX.php' );
15
+ require_once( 'class.SnS_Form.php' );
16
+
17
+ class SnS_Admin
18
+ {
19
+ /**#@+
20
+ * Constants
21
+ */
22
+ const OPTION_GROUP = 'scripts_n_styles';
23
+ const MENU_SLUG = 'sns';
24
+ const VERSION = '3';
25
+ static $parent_slug = '';
26
+ /**#@-*/
27
+
28
+ /**
29
+ * Initializing method.
30
+ * @static
31
+ */
32
+ static function init() {
33
+ add_action( 'admin_menu', array( 'SnS_Admin_Meta_Box', 'init' ) );
34
+
35
+ add_action( 'admin_menu', array( __CLASS__, 'menu' ) );
36
+
37
+ add_action( 'admin_init', array( 'SnS_AJAX', 'init' ) );
38
+
39
+ $plugin_file = plugin_basename( Scripts_n_Styles::$file );
40
+ add_filter( "plugin_action_links_$plugin_file", array( __CLASS__, 'plugin_action_links') );
41
+
42
+ register_activation_hook( Scripts_n_Styles::$file, array( __CLASS__, 'upgrade' ) );
43
+ }
44
+
45
+ function menu() {
46
+ if ( ! current_user_can( 'manage_options' ) || ! current_user_can( 'unfiltered_html' ) ) return;
47
+
48
+ $options = get_option( 'SnS_options' );
49
+ $menu_spot = isset( $options[ 'menu_position' ] ) ? $options[ 'menu_position' ]: '';
50
+ $top_spots = array( 'menu', 'object', 'utility' );
51
+ $sub_spots = array( 'tools.php', 'options-general.php', 'themes.php' );
52
+
53
+ if ( in_array( $menu_spot, $top_spots ) ) $parent_slug = SnS_Admin::MENU_SLUG;
54
+ else if ( in_array( $menu_spot, $sub_spots ) ) $parent_slug = $menu_spot;
55
+ else $parent_slug = 'tools.php';
56
+
57
+ self::$parent_slug = $parent_slug;
58
+
59
+ switch( $menu_spot ) {
60
+ case 'menu':
61
+ add_menu_page( 'Scripts n Styles', 'Scripts n Styles', 'unfiltered_html', $parent_slug, array( 'SnS_Form', 'page' ), plugins_url( 'images/menu.png', Scripts_n_Styles::$file ) );
62
+ break;
63
+ case 'object':
64
+ add_object_page( 'Scripts n Styles', 'Scripts n Styles', 'unfiltered_html', $parent_slug, array( 'SnS_Form', 'page' ), plugins_url( 'images/menu.png', Scripts_n_Styles::$file ) );
65
+ break;
66
+ case 'utility':
67
+ add_utility_page( 'Scripts n Styles', 'Scripts n Styles', 'unfiltered_html', $parent_slug, array( 'SnS_Form', 'page' ), plugins_url( 'images/menu.png', Scripts_n_Styles::$file ) );
68
+ break;
69
+ }
70
+ SnS_Global_Page::init();
71
+ SnS_Settings_Page::init();
72
+ SnS_Usage_Page::init();
73
+ }
74
+
75
+ /**
76
+ * Nav Tabs
77
+ */
78
+ function nav() {
79
+ $page = $_REQUEST[ 'page' ];
80
+ ?>
81
+ <?php screen_icon(); ?>
82
+ <h2>Scripts n Styles</h2>
83
+ <?php screen_icon( 'none' ); ?>
84
+ <h3 class="nav-tab-wrapper">
85
+ <a class="nav-tab<?php echo ( self::MENU_SLUG == $page ) ? ' nav-tab-active': ''; ?>" href="<?php menu_page_url( self::MENU_SLUG ); ?>">Global</a>
86
+ <a class="nav-tab<?php echo ( self::MENU_SLUG . '_settings' == $page ) ? ' nav-tab-active': ''; ?>" href="<?php menu_page_url( self::MENU_SLUG . '_settings' ); ?>">Settings</a>
87
+ <a class="nav-tab<?php echo ( self::MENU_SLUG . '_usage' == $page ) ? ' nav-tab-active': ''; ?>" href="<?php menu_page_url( self::MENU_SLUG . '_usage' ); ?>">Usage</a>
88
+ </h3>
89
+ <?php
90
+ }
91
+
92
+ /**
93
+ * Settings Page help
94
+ */
95
+ function help() {
96
+ global $wp_version; // Back Compat for now
97
+ if ( version_compare( $wp_version, '3.2.1', '>') ) {
98
+ $screen = get_current_screen();
99
+ if ( 'post' != $screen->id ) {
100
+ $screen->add_help_tab( array(
101
+ 'title' => __('Scripts n Styles', 'scripts-n-styles'),
102
+ 'id' => 'scripts-n-styles',
103
+ 'content' =>
104
+ '<p>' . __( '<p>In default (non MultiSite) WordPress installs, both <em>Administrators</em> and
105
+ <em>Editors</em> can access <em>Scripts-n-Styles</em> on individual edit screens.
106
+ Only <em>Administrators</em> can access this Options Page. In MultiSite WordPress installs, only
107
+ <em>"Super Admin"</em> users can access either
108
+ <em>Scripts-n-Styles</em> on individual edit screens or this Options Page. If other plugins change
109
+ capabilities (specifically "unfiltered_html"),
110
+ other users can be granted access.</p>', 'scripts-n-styles' ) . '</p>'
111
+ )
112
+ );
113
+ $screen->set_help_sidebar(
114
+ '<p><strong>' . __( 'For more information:', 'scripts-n-styles' ) . '</strong></p>' .
115
+ '<p>' . __( '<a href="http://wordpress.org/extend/plugins/scripts-n-styles/faq/" target="_blank">Frequently Asked Questions</a>', 'scripts-n-styles' ) . '</p>' .
116
+ '<p>' . __( '<a href="https://github.com/unFocus/Scripts-n-Styles" target="_blank">Source on github</a>', 'scripts-n-styles' ) . '</p>' .
117
+ '<p>' . __( '<a href="http://wordpress.org/tags/scripts-n-styles" target="_blank">Support Forums</a>', 'scripts-n-styles' ) . '</p>'
118
+ );
119
+ } else {
120
+ $screen->add_help_tab( array(
121
+ 'title' => __('Scripts n Styles', 'scripts-n-styles'),
122
+ 'id' => 'scripts-n-styles',
123
+ 'content' =>
124
+ '<p>' . __( '<p>In default (non MultiSite) WordPress installs, both <em>Administrators</em> and
125
+ <em>Editors</em> can access <em>Scripts-n-Styles</em> on individual edit screens.
126
+ Only <em>Administrators</em> can access this Options Page. In MultiSite WordPress installs, only
127
+ <em>"Super Admin"</em> users can access either
128
+ <em>Scripts-n-Styles</em> on individual edit screens or this Options Page. If other plugins change
129
+ capabilities (specifically "unfiltered_html"),
130
+ other users can be granted access.</p>', 'scripts-n-styles' ) . '</p>'
131
+ )
132
+ );
133
+ }
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Utility Method: Sets defaults if not previously set. Sets stored 'version' to VERSION.
139
+ */
140
+ static function upgrade() {
141
+ $options = get_option( 'SnS_options' );
142
+ if ( ! $options ) $options = array();
143
+ $options[ 'version' ] = self::VERSION;
144
+ update_option( 'SnS_options', $options );
145
+
146
+ /*
147
+ * upgrade proceedure
148
+ */
149
+ $posts = get_posts(
150
+ array(
151
+ 'numberposts' => -1,
152
+ 'post_type' => 'any',
153
+ 'post_status' => 'any',
154
+ 'meta_query' => array(
155
+ 'relation' => 'OR',
156
+ array( 'key' => '_SnS_scripts' ),
157
+ array( 'key' => '_SnS_styles' ),
158
+ array( 'key' => 'uFp_scripts' ),
159
+ array( 'key' => 'uFp_styles' )
160
+ )
161
+ )
162
+ );
163
+
164
+ foreach( $posts as $post) {
165
+ $styles = get_post_meta( $post->ID, '_SnS_styles', true );
166
+ if ( empty( $styles ) )
167
+ $styles = get_post_meta( $post->ID, 'uFp_styles', true );
168
+
169
+ $scripts = get_post_meta( $post->ID, '_SnS_scripts', true );
170
+ if ( empty( $scripts ) )
171
+ $scripts = get_post_meta( $post->ID, 'uFp_scripts', true );
172
+
173
+ $SnS = array();
174
+ if ( ! empty( $styles ) )
175
+ $SnS[ 'styles' ] = $styles;
176
+
177
+ if ( ! empty( $scripts ) )
178
+ $SnS[ 'scripts' ] = $scripts;
179
+
180
+ if ( ! empty( $SnS ) )
181
+ update_post_meta( $post->ID, '_SnS', $SnS );
182
+
183
+ delete_post_meta( $post->ID, 'uFp_styles' );
184
+ delete_post_meta( $post->ID, 'uFp_scripts' );
185
+ delete_post_meta( $post->ID, '_SnS_styles' );
186
+ delete_post_meta( $post->ID, '_SnS_scripts' );
187
+ }
188
+
189
+ }
190
+
191
+ /**
192
+ * Utility Method: Compares VERSION to stored 'version' value.
193
+ */
194
+ static function upgrade_check() {
195
+ $options = get_option( 'SnS_options' );
196
+ if ( ! isset( $options[ 'version' ] ) || version_compare( self::VERSION, $options[ 'version' ], '>' ) )
197
+ self::upgrade();
198
+ }
199
+
200
+ /**
201
+ * Adds link to the Settings Page in the WordPress "Plugin Action Links" array.
202
+ * @param array $actions
203
+ * @return array
204
+ */
205
+ static function plugin_action_links( $actions ) {
206
+ $actions[ 'settings' ] = '<a href="' . menu_page_url( SnS_Settings_Page::MENU_SLUG, false ) . '"/>Settings</a>';
207
+ return $actions;
208
+ }
209
+
210
+ }
211
+
212
  ?>
includes/class.SnS_Admin_Meta_Box.php CHANGED
@@ -6,9 +6,6 @@
6
  * and JavaScript directly to individual Post, Pages or custom
7
  * post types.
8
  */
9
-
10
- // $hook_suffix = 'tools_page_Scripts-n-Styles'; // kept here for reference
11
- // $plugin_file = 'scripts-n-styles/scripts-n-styles.php'; // kept here for reference
12
 
13
  class SnS_Admin_Meta_Box
14
  {
@@ -20,14 +17,75 @@ class SnS_Admin_Meta_Box
20
  static $post_types;
21
 
22
  /**
23
- * Initializing method. Checks if is_admin() and registers action hooks for admin if true. Sets filters and actions for Theme side functions.
24
- * @static
25
- */
26
  static function init() {
27
  add_action( 'add_meta_boxes', array( __CLASS__, 'add_meta_boxes' ) );
28
  add_action( 'save_post', array( __CLASS__, 'save_post' ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  }
30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  /**
32
  * Admin Action: 'add_meta_boxes'
33
  * Main Meta Box function. Checks restriction options and display options, calls add_meta_box() and adds actions for adding admin CSS and JavaScript.
@@ -36,93 +94,125 @@ class SnS_Admin_Meta_Box
36
  if ( current_user_can( 'unfiltered_html' ) ) {
37
  self::$post_types = get_post_types( array('show_ui' => true, 'public' => true) ); // updated for http://core.trac.wordpress.org/changeset/18234
38
  foreach ( self::$post_types as $post_type ) {
39
- add_meta_box( 'uFp_meta_box', 'Scripts n Styles', array( __CLASS__, 'meta_box' ), $post_type, 'normal', 'high' );
40
  }
41
  add_filter( 'default_hidden_meta_boxes', array( __CLASS__, 'default_hidden_meta_boxes' ) );
42
  add_action( "admin_print_styles", array( __CLASS__, 'meta_box_styles'));
43
  add_action( "admin_print_scripts", array( __CLASS__, 'meta_box_scripts'));
44
- add_filter( 'contextual_help', array( __CLASS__, 'contextual_help_filter' ), 10, 3 );
45
  }
46
  }
 
47
  static function default_hidden_meta_boxes( $hidden ) {
48
- $hidden[] = 'uFp_meta_box';
49
  return $hidden;
50
  }
51
 
52
- function contextual_help_filter( $text, $screen_id, $screen ) {
53
- if ( in_array( $screen->post_type, self::$post_types ) )
54
- $text .= '<p>In default (non MultiSite) WordPress installs, both <em>Administrators</em> and
55
- <em>Editors</em> can access <em>Scripts-n-Styles</em> on individual edit screens.
56
- Only <em>Administrators</em> can access this Options Page. In MultiSite WordPress installs, only <em>"Super Admin"</em> users can access either
57
- <em>Scripts-n-Styles</em> on individual edit screens or this Options Page. If other plugins change capabilities (specifically "unfiltered_html"),
58
- other users can be granted access.</p>';
59
- return $text;
60
- }
61
-
62
-
63
  /**
64
  * Admin Action: 'add_meta_boxes'
65
  * Outputs the Meta Box. Only called on callback from add_meta_box() during the add_meta_boxes action.
66
  * @param unknown_type WordPress Post object.
67
  */
68
- static function meta_box( $post ) {
69
  $registered_handles = Scripts_n_Styles::get_wp_registered();
70
- $styles = get_post_meta( $post->ID, 'uFp_styles', true );
71
- $scripts = get_post_meta( $post->ID, 'uFp_scripts', true );
 
 
 
 
 
 
72
  ?>
73
- <div class="tabs-horizontal">
74
- <ul class="wp-tab-bar" style="display: none;">
75
- <li><a href="#uFp_scripts-tab">Scripts (bottom)</a></li>
76
- <li><a href="#uFp_styles-tab">Styles</a></li>
77
- <li><a href="#uFp_scripts_in_head-tab">Scripts (top)</a></li>
78
- <li><a href="#uFp_classes_body-tab">Classes</a></li>
79
- <?php /** / ?>
80
- <li><a href="#uFp_enqueue_scripts-tab">Include Scripts</a></li>
81
- <?php /**/ ?>
82
  </ul>
83
 
84
- <div id="uFp_scripts-tab">
85
- <input type="hidden" name="<?php echo self::NONCE_NAME ?>" id="<?php echo self::NONCE_NAME ?>" value="<?php echo wp_create_nonce( Scripts_n_Styles::$file ) ?>" />
86
- <p>
87
- <label for="uFp_scripts" class="title"><strong>Scripts</strong>: </label>
88
- <textarea class="code js" name="uFp_scripts" id="uFp_scripts" rows="5" cols="40" style="width: 98%;"><?php echo isset( $scripts[ 'scripts' ] ) ? $scripts[ 'scripts' ] : ''; ?></textarea>
89
- <em>This code will be included <strong>verbatim</strong> in <code>&lt;script></code> tags at the end of your page's (or post's) <code>&lt;body></code> tag.</em>
90
- </p>
 
91
  </div>
92
 
93
- <div id="uFp_styles-tab">
94
- <p>
95
- <label for="uFp_styles" class="title"><strong>Styles</strong>: </label>
96
- <textarea class="code css" name="uFp_styles" id="uFp_styles" rows="5" cols="40" style="width: 98%;"><?php echo isset( $styles[ 'styles' ] ) ? $styles[ 'styles' ] : ''; ?></textarea>
97
- <em>This code will be included <strong>verbatim</strong> in <code>&lt;style></code> tags in the <code>&lt;head></code> tag of your page (or post).</em>
98
- </p>
99
  </div>
100
 
101
- <div id="uFp_scripts_in_head-tab">
102
- <p>
103
- <label for="uFp_scripts_in_head" class="title"><strong>Scripts</strong> (for the <code>head</code> element): </label>
104
- <textarea class="codemirror js" name="uFp_scripts_in_head" id="uFp_scripts_in_head" rows="5" cols="40" style="width: 98%;"><?php echo isset( $scripts[ 'scripts_in_head' ] ) ? $scripts[ 'scripts_in_head' ] : ''; ?></textarea>
105
- <em>This code will be included <strong>verbatim</strong> in <code>&lt;script></code> tags at the end of your page's (or post's) <code>&lt;head></code> tag.</em>
106
- </p>
107
- </div>
108
-
109
- <div id="uFp_classes_body-tab">
110
  <strong class="title">Classes</strong>
111
- <p>
112
- <label for="uFp_classes_body">body classes: </label>
113
- <input style="width: 99%;" name="uFp_classes_body" id="uFp_classes_body" value="<?php echo isset( $styles[ 'classes_body' ] ) ? $styles[ 'classes_body' ] : ''; ?>" type="text" class="code" />
114
- </p>
115
- <p>
116
- <label for="uFp_classes_post">post classes: </label>
117
- <input style="width: 99%;" name="uFp_classes_post" id="uFp_classes_post" value="<?php echo isset( $styles[ 'classes_post' ] ) ? $styles[ 'classes_post' ] : ''; ?>" type="text" class="code" />
118
- </p>
119
- <p><em>These <strong>space separated</strong> class names will be pushed into the <code>body_class()</code> or <code>post_class()</code> function (provided your theme uses these functions).</em></p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  </div>
121
 
122
- <?php /** / ?>
123
- <div id="uFp_enqueue_scripts-tab">
124
  <strong class="title">Include Scripts</strong>
125
- <select name="uFp_enqueue_scripts[]" id="uFp_enqueue_scripts" size="5" multiple="multiple" style="height: auto; float: left; margin: 6px 10px 8px 0;">
126
  <?php // This is a bit intense here...
127
  if ( ! empty( $scripts[ 'enqueue_scripts' ] ) && is_array( $scripts[ 'enqueue_scripts' ] ) ) {
128
  foreach ( $registered_handles as $value ) { ?>
@@ -140,21 +230,42 @@ class SnS_Admin_Meta_Box
140
  </p>
141
  <?php } ?>
142
  <p><em>The chosen scripts will be enqueued and placed before your codes if your code is dependant on certain scripts (like jQuery).</em></p>
143
- <p>NOTE: Not all Scripts in the list are appropriate for use in themes. This is merely a generated list of all currently available registered scripts. It's possible some scripts could be registered only on the "front end" and therefore not listed here.</p>
144
  </div>
145
- <?php /**/ ?>
146
- </div>
147
  <?php
148
  }
149
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
  /**
151
  * Admin Action: 'admin_print_styles' Action added during 'add_meta_boxes' (which restricts output to Edit Screens).
152
  * Enqueues the CSS for admin styling of the Meta Box.
153
  */
154
  static function meta_box_styles() {
155
- wp_enqueue_style( 'sns-meta-box-styles', plugins_url( 'css/meta-box-styles.css', Scripts_n_Styles::$file), array( 'codemirror-default' ), SnS_Admin::VERSION );
156
- wp_enqueue_style( 'codemirror', plugins_url( 'libraries/codemirror/lib/codemirror.css', Scripts_n_Styles::$file), array(), '2.1' );
157
- wp_enqueue_style( 'codemirror-default', plugins_url( 'libraries/codemirror/theme/default.css', Scripts_n_Styles::$file), array( 'codemirror' ), '2.1' );
 
 
 
158
  }
159
 
160
  /**
@@ -162,10 +273,64 @@ class SnS_Admin_Meta_Box
162
  * Enqueues the JavaScript for the admin Meta Box.
163
  */
164
  static function meta_box_scripts() {
165
- wp_enqueue_script( 'sns-meta-box-scripts', plugins_url( 'js/meta-box-scripts.js', Scripts_n_Styles::$file), array( 'jquery-ui-tabs', 'codemirror-css', 'codemirror-javascript' ), SnS_Admin::VERSION, true );
166
- wp_enqueue_script( 'codemirror', plugins_url( 'libraries/codemirror/lib/codemirror.js', Scripts_n_Styles::$file), array(), '2.1' );
167
- wp_enqueue_script( 'codemirror-css', plugins_url( 'libraries/codemirror/mode/css.js', Scripts_n_Styles::$file), array( 'codemirror' ), '2.1' );
168
- wp_enqueue_script( 'codemirror-javascript', plugins_url( 'libraries/codemirror/mode/javascript.js', Scripts_n_Styles::$file), array( 'codemirror' ), '2.1' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  }
170
 
171
  /**
@@ -174,42 +339,55 @@ class SnS_Admin_Meta_Box
174
  * @param int $post_id ID value of the WordPress post.
175
  */
176
  static function save_post( $post_id ) {
177
- if ( isset( $_POST[ self::NONCE_NAME ] ) && wp_verify_nonce( $_POST[ self::NONCE_NAME ], Scripts_n_Styles::$file )
178
- && current_user_can( 'unfiltered_html' )
179
- && ! ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) ) {
180
-
181
- /*
182
- NOTE: There is no current_user_can( 'edit_post' ) check here, because as far as I
183
- can tell, in /wp-admin/post.php the calls edit_post(), write_post(), post_preview(),
184
- wp_untrash_post(), etc., the check is already done prior to the 'save_post' action,
185
- which is where this function is called. Other calls are from other pages so the
186
- NONCE covers those cases, and that leaves autosave, which is also checked here.
187
- */
188
 
189
- $scripts = array();
190
- $styles = array();
191
-
192
- if ( ! empty( $_POST[ 'uFp_scripts' ] ) )
193
- $scripts[ 'scripts' ] = $_POST[ 'uFp_scripts' ];
194
-
195
- if ( ! empty( $_POST[ 'uFp_styles' ] ) )
196
- $styles[ 'styles' ] = $_POST[ 'uFp_styles' ];
197
-
198
- if ( ! empty( $_POST[ 'uFp_scripts_in_head' ] ) )
199
- $scripts[ 'scripts_in_head' ] = $_POST[ 'uFp_scripts_in_head' ];
200
-
201
- if ( ! empty( $_POST[ 'uFp_classes_body' ] ) )
202
- $styles[ 'classes_body' ] = $_POST[ 'uFp_classes_body' ];
203
-
204
- if ( ! empty( $_POST[ 'uFp_classes_post' ] ) )
205
- $styles[ 'classes_post' ] = $_POST[ 'uFp_classes_post' ];
206
-
207
- if ( ! empty( $_POST[ 'uFp_enqueue_scripts' ] ) )
208
- $scripts[ 'enqueue_scripts' ] = $_POST[ 'uFp_enqueue_scripts' ];
209
-
210
- update_post_meta( $post_id, 'uFp_scripts', $scripts );
211
- update_post_meta( $post_id, 'uFp_styles', $styles );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
  }
 
213
  }
214
  }
215
  ?>
6
  * and JavaScript directly to individual Post, Pages or custom
7
  * post types.
8
  */
 
 
 
9
 
10
  class SnS_Admin_Meta_Box
11
  {
17
  static $post_types;
18
 
19
  /**
20
+ * Initializing method.
21
+ */
 
22
  static function init() {
23
  add_action( 'add_meta_boxes', array( __CLASS__, 'add_meta_boxes' ) );
24
  add_action( 'save_post', array( __CLASS__, 'save_post' ) );
25
+
26
+ add_filter( 'mce_buttons_2', array( __CLASS__, 'mce_buttons_2' ) );
27
+ add_filter( 'tiny_mce_before_init', array( __CLASS__, 'tiny_mce_before_init' ) );
28
+ add_filter( 'mce_css', array( __CLASS__, 'mce_css' ) );
29
+ }
30
+
31
+ function mce_buttons_2( $buttons ) {
32
+ global $post;
33
+ $SnS = get_post_meta( $post->ID, '_SnS', true );
34
+ $styles = isset( $SnS['styles'] ) ? $SnS[ 'styles' ]: array();
35
+
36
+ if ( ! empty( $styles[ 'classes_mce' ] ) )
37
+ array_unshift( $buttons, 'styleselect' );
38
+
39
+ return $buttons;
40
+ }
41
+ function tiny_mce_before_init( $initArray ) {
42
+ global $post;
43
+ $SnS = get_post_meta( $post->ID, '_SnS', true );
44
+ $styles = isset( $SnS['styles'] ) ? $SnS[ 'styles' ]: array();
45
+
46
+ // Add div as a format option, should probably use a string replace thing here.
47
+ // Better yet, a setting for adding these. Postpone for now.
48
+ //$initArray['theme_advanced_blockformats'] = "p,address,pre,h1,h2,h3,h4,h5,h6,div";
49
+
50
+ if ( ( ! empty( $styles[ 'classes_body' ] ) || ! empty( $styles[ 'classes_post' ] ) ) && ! isset( $initArray['body_class'] ) )
51
+ $initArray['body_class'] = '';
52
+
53
+ // Add body_class (and/or maybe post_class) values... somewhat problematic.
54
+ if ( ! empty( $styles[ 'classes_body' ] ) )
55
+ $initArray['body_class'] .= ' ' . $styles[ 'classes_body' ];
56
+ if ( ! empty( $styles[ 'classes_post' ] ) )
57
+ $initArray['body_class'] .= ' ' . $styles[ 'classes_post' ];
58
+
59
+ // In case Themes or plugins have added style_formats, not tested.
60
+ if ( isset( $initArray['style_formats'] ) )
61
+ $style_formats = json_decode( $initArray['style_formats'], true );
62
+ else
63
+ $style_formats = array();
64
+
65
+ if ( ! empty( $styles[ 'classes_mce' ] ) )
66
+ foreach ( $styles[ 'classes_mce' ] as $format )
67
+ $style_formats[] = $format;
68
+
69
+ if ( ! empty( $style_formats ) )
70
+ $initArray['style_formats'] = json_encode( $style_formats );
71
+
72
+ return $initArray;
73
  }
74
 
75
+ /**
76
+ * Admin Action: 'mce_css'
77
+ * Adds a styles sheet to TinyMCE via ajax that contains the current styles data.
78
+ */
79
+ static function mce_css( $mce_css ) {
80
+ global $post;
81
+ $url = admin_url( 'admin-ajax.php' );
82
+ $url = wp_nonce_url( $url, 'sns_tinymce_styles' );
83
+ $url = add_query_arg( 'post_id', $post->ID, $url );
84
+ $url = add_query_arg( 'action', 'sns_tinymce_styles', $url );
85
+ $mce_css .= ',' . $url;
86
+ return $mce_css;
87
+ }
88
+
89
  /**
90
  * Admin Action: 'add_meta_boxes'
91
  * Main Meta Box function. Checks restriction options and display options, calls add_meta_box() and adds actions for adding admin CSS and JavaScript.
94
  if ( current_user_can( 'unfiltered_html' ) ) {
95
  self::$post_types = get_post_types( array('show_ui' => true, 'public' => true) ); // updated for http://core.trac.wordpress.org/changeset/18234
96
  foreach ( self::$post_types as $post_type ) {
97
+ add_meta_box( 'SnS_meta_box', 'Scripts n Styles', array( __CLASS__, 'admin_meta_box' ), $post_type, 'normal', 'high' );
98
  }
99
  add_filter( 'default_hidden_meta_boxes', array( __CLASS__, 'default_hidden_meta_boxes' ) );
100
  add_action( "admin_print_styles", array( __CLASS__, 'meta_box_styles'));
101
  add_action( "admin_print_scripts", array( __CLASS__, 'meta_box_scripts'));
102
+ add_filter( 'contextual_help', array( 'SnS_Admin', 'help' ) );
103
  }
104
  }
105
+
106
  static function default_hidden_meta_boxes( $hidden ) {
107
+ $hidden[] = 'SnS_meta_box';
108
  return $hidden;
109
  }
110
 
 
 
 
 
 
 
 
 
 
 
 
111
  /**
112
  * Admin Action: 'add_meta_boxes'
113
  * Outputs the Meta Box. Only called on callback from add_meta_box() during the add_meta_boxes action.
114
  * @param unknown_type WordPress Post object.
115
  */
116
+ static function admin_meta_box( $post ) {
117
  $registered_handles = Scripts_n_Styles::get_wp_registered();
118
+ $SnS = get_post_meta( $post->ID, '_SnS', true );
119
+ $styles = isset( $SnS['styles'] ) ? $SnS[ 'styles' ]: array();
120
+ $scripts = isset( $SnS['scripts'] ) ? $SnS[ 'scripts' ]: array();
121
+
122
+ $screen = get_current_screen();
123
+ $position = get_user_option( "current_sns_tab" );
124
+ if ( ! in_array( $position, array( 's0', 's1', 's2', 's3' ) ) ) $position = 's0';
125
+ wp_nonce_field( Scripts_n_Styles::$file, self::NONCE_NAME );
126
  ?>
127
+ <ul class="wp-tab-bar">
128
+ <li<?php echo ( 's0' == $position ) ? ' class="wp-tab-active"': ''; ?>><a href="#SnS_scripts-tab">Scripts</a></li>
129
+ <li<?php echo ( 's1' == $position ) ? ' class="wp-tab-active"': ''; ?>><a href="#SnS_styles-tab">Styles</a></li>
130
+ <li<?php echo ( 's2' == $position ) ? ' class="wp-tab-active"': ''; ?>><a href="#SnS_classes_body-tab">Classes</a></li>
131
+ <li<?php echo ( 's3' == $position ) ? ' class="wp-tab-active"': ''; ?>><a href="#SnS_enqueue_scripts-tab">Include Scripts</a></li>
 
 
 
 
132
  </ul>
133
 
134
+ <div class="wp-tab-panel" id="SnS_scripts-tab">
135
+ <p><em>This code will be included <strong>verbatim</strong> in <code>&lt;script></code> tags at the end of your page's (or post's) ...</em></p>
136
+ <label for="SnS_scripts_in_head" class="title"><strong>Scripts</strong> (for the <code>head</code> element): </label>
137
+ <textarea class="codemirror js" name="SnS_scripts_in_head" id="SnS_scripts_in_head" rows="5" cols="40" style="width: 98%;"><?php echo isset( $scripts[ 'scripts_in_head' ] ) ? $scripts[ 'scripts_in_head' ] : ''; ?></textarea>
138
+ <p><em>... <code>&lt;/head></code> tag.</em></p>
139
+ <label for="SnS_scripts" class="title"><strong>Scripts</strong>: </label>
140
+ <textarea class="codemirror js" name="SnS_scripts" id="SnS_scripts" rows="5" cols="40" style="width: 98%;"><?php echo isset( $scripts[ 'scripts' ] ) ? $scripts[ 'scripts' ] : ''; ?></textarea>
141
+ <p><em>... <code>&lt;/body></code> tag.</em></p>
142
  </div>
143
 
144
+ <div class="wp-tab-panel" id="SnS_styles-tab">
145
+ <label for="SnS_styles" class="title"><strong>Styles</strong>: </label>
146
+ <textarea class="codemirror css" name="SnS_styles" id="SnS_styles" rows="5" cols="40" style="width: 98%;"><?php echo isset( $styles[ 'styles' ] ) ? $styles[ 'styles' ] : ''; ?></textarea>
147
+ <p><em>This code will be included <strong>verbatim</strong> in <code>&lt;style></code> tags in the <code>&lt;head></code> tag of your page (or post).</em></p>
 
 
148
  </div>
149
 
150
+ <div class="wp-tab-panel" id="SnS_classes_body-tab">
 
 
 
 
 
 
 
 
151
  <strong class="title">Classes</strong>
152
+ <div id="sns-classes">
153
+ <p>
154
+ <label for="SnS_classes_body"><strong>Body Classes</strong>: </label>
155
+ <input name="SnS_classes_body" id="SnS_classes_body" type="text" class="code" style="width: 99%;"
156
+ value="<?php echo isset( $styles[ 'classes_body' ] ) ? $styles[ 'classes_body' ] : ''; ?>" />
157
+ <small>Standard: <code><?php self::current_classes( 'body', $post->ID ); ?></code></small>
158
+ </p>
159
+ <p>
160
+ <label for="SnS_classes_post"><strong>Post Classes</strong>: </label>
161
+ <input name="SnS_classes_post" id="SnS_classes_post" type="text" class="code" style="width: 99%;"
162
+ value="<?php echo isset( $styles[ 'classes_post' ] ) ? $styles[ 'classes_post' ] : ''; ?>" />
163
+ <small>Standard: <code><?php self::current_classes( 'post', $post->ID ); ?></code></small>
164
+ </p>
165
+ <p><em>These <strong>space separated</strong> class names will be added to the <code>body_class()</code> or
166
+ <code>post_class()</code> function (provided your theme uses these functions).</em></p>
167
+ </div>
168
+
169
+ <?php
170
+ /*
171
+ * Note: Styles Dropdown section only makes sense when Javascript is enabled. (Otherwise, no TinyMCE.)
172
+ */
173
+ ?>
174
+ <div id="mce-dropdown-names" style="display: none;">
175
+ <h4>The Styles Dropdown</h4>
176
+ <div id="add-mce-dropdown-names">
177
+ <p>Add (or update) a class for the "Styles" drop-down:</p>
178
+ <p class="sns-mce-title">
179
+ <label for="SnS_classes_mce_title">Title:</label>
180
+ <input name="SnS_classes_mce_title" id="SnS_classes_mce_title"
181
+ value="" type="text" class="code" style="width: 80px;" />
182
+ </p>
183
+ <p class="sns-mce-type">
184
+ <label for="SnS_classes_mce_type">Type:</label>
185
+ <select name="SnS_classes_mce_type" id="SnS_classes_mce_type" style="width: 80px;">
186
+ <option value="inline">Inline</option>
187
+ <option value="block">Block</option>
188
+ <option value="selector">Selector</option>
189
+ </select>
190
+ </p>
191
+ <p class="sns-mce-element">
192
+ <label for="SnS_classes_mce_element">Element:</label>
193
+ <input name="SnS_classes_mce_element" id="SnS_classes_mce_element"
194
+ value="" type="text" class="code" style="width: 80px;" />
195
+ </p>
196
+ <p class="sns-mce-classes">
197
+ <label for="SnS_classes_mce_classes">classes:</label>
198
+ <input name="SnS_classes_mce_classes" id="SnS_classes_mce_classes"
199
+ value="" type="text" class="code" style="width: 80px;" />
200
+ </p>
201
+ <p class="sns-mce-wrapper" style="display: none;">
202
+ <label for="SnS_classes_mce_wrapper">Wrapper:</label>
203
+ <input name="SnS_classes_mce_wrapper" id="SnS_classes_mce_wrapper" type="checkbox" value="true" />
204
+ </p>
205
+ </div>
206
+
207
+ <div id="delete-mce-dropdown-names" style="display: none;">
208
+ <p id="instructions-mce-dropdown-names">Classes currently in the dropdown:</p>
209
+ </div>
210
+ </div>
211
  </div>
212
 
213
+ <div class="wp-tab-panel" id="SnS_enqueue_scripts-tab">
 
214
  <strong class="title">Include Scripts</strong>
215
+ <select name="SnS_enqueue_scripts[]" id="SnS_enqueue_scripts" size="5" multiple="multiple" style="height: auto; float: left; margin: 6px 10px 8px 0;">
216
  <?php // This is a bit intense here...
217
  if ( ! empty( $scripts[ 'enqueue_scripts' ] ) && is_array( $scripts[ 'enqueue_scripts' ] ) ) {
218
  foreach ( $registered_handles as $value ) { ?>
230
  </p>
231
  <?php } ?>
232
  <p><em>The chosen scripts will be enqueued and placed before your codes if your code is dependant on certain scripts (like jQuery).</em></p>
 
233
  </div>
 
 
234
  <?php
235
  }
236
 
237
+ function current_classes( $type, $post_id ) {
238
+ if ( 'body' == $type ) {
239
+ global $wp_query, $pagenow;
240
+
241
+ if ( 'post-new.php' == $pagenow ) {
242
+ echo join( ' ', get_body_class( '', $post_id ) );
243
+ echo ' (plus others once saved.)';
244
+ return;
245
+ }
246
+ // This returns more of what actually get used on the theme side.
247
+ $save = $wp_query;
248
+ $param = ( 'page' == get_post_type( $post_id ) ) ? 'page_id': 'p';
249
+ $wp_query = new WP_Query( "$param=$post_id" );
250
+ echo join( ' ', get_body_class( '', $post_id ) );
251
+ $wp_query = $save;
252
+
253
+ } else {
254
+ echo join( ' ', get_post_class( '', $post_id ) );
255
+ }
256
+ }
257
+
258
  /**
259
  * Admin Action: 'admin_print_styles' Action added during 'add_meta_boxes' (which restricts output to Edit Screens).
260
  * Enqueues the CSS for admin styling of the Meta Box.
261
  */
262
  static function meta_box_styles() {
263
+ $options = get_option( 'SnS_options' );
264
+ $cm_theme = isset( $options[ 'cm_theme' ] ) ? $options[ 'cm_theme' ] : 'default';
265
+
266
+ wp_enqueue_style( 'codemirror', plugins_url( 'libraries/CodeMirror2/lib/codemirror.css', Scripts_n_Styles::$file), array(), '2.18' );
267
+ wp_enqueue_style( "codemirror-$cm_theme", plugins_url( "libraries/CodeMirror2/theme/$cm_theme.css", Scripts_n_Styles::$file), array( 'codemirror' ), '2.18' );
268
+ wp_enqueue_style( 'sns-meta-box-styles', plugins_url( 'css/meta-box-styles.css', Scripts_n_Styles::$file), array( 'codemirror' ), SnS_Admin::VERSION );
269
  }
270
 
271
  /**
273
  * Enqueues the JavaScript for the admin Meta Box.
274
  */
275
  static function meta_box_scripts() {
276
+ $options = get_option( 'SnS_options' );
277
+ $cm_theme = isset( $options[ 'cm_theme' ] ) ? $options[ 'cm_theme' ] : 'default';
278
+
279
+ wp_enqueue_script(
280
+ 'codemirror',
281
+ plugins_url( 'libraries/CodeMirror2/lib/codemirror.js', Scripts_n_Styles::$file),
282
+ array(),
283
+ '2.18' );
284
+ wp_enqueue_script(
285
+ 'codemirror-css',
286
+ plugins_url( 'libraries/CodeMirror2/mode/css/css.js', Scripts_n_Styles::$file),
287
+ array( 'codemirror' ),
288
+ '2.18' );
289
+ wp_enqueue_script(
290
+ 'codemirror-javascript',
291
+ plugins_url( 'libraries/CodeMirror2/mode/javascript/javascript.js', Scripts_n_Styles::$file),
292
+ array( 'codemirror' ),
293
+ '2.18' );
294
+ /*wp_register_script(
295
+ 'codemirror-xml',
296
+ plugins_url( 'libraries/CodeMirror2/mode/xml/xml.js', Scripts_n_Styles::$file),
297
+ array( 'codemirror' ),
298
+ '2.11' );*/
299
+ /*wp_register_script(
300
+ 'codemirror-htmlmixed',
301
+ plugins_url( 'libraries/CodeMirror2/mode/htmlmixed/htmlmixed.js', Scripts_n_Styles::$file),
302
+ array( 'codemirror-xml',
303
+ 'codemirror-css',
304
+ 'codemirror-javascript'
305
+ ),
306
+ '2.11' );*/
307
+ /*wp_register_script(
308
+ 'codemirror-clike',
309
+ plugins_url( 'libraries/CodeMirror2/mode/clike/clike.js', Scripts_n_Styles::$file),
310
+ array( 'codemirror' ),
311
+ '2.11' );
312
+ wp_register_script(
313
+ 'codemirror-php',
314
+ plugins_url( 'libraries/CodeMirror2/mode/php/php.js', Scripts_n_Styles::$file),
315
+ array( 'codemirror-xml',
316
+ 'codemirror-css',
317
+ 'codemirror-javascript',
318
+ 'codemirror-clike'
319
+ ),
320
+ '2.11' );*/
321
+ wp_enqueue_script(
322
+ 'sns-meta-box-scripts',
323
+ plugins_url( 'js/meta-box-scripts.js', Scripts_n_Styles::$file),
324
+ array( 'editor',
325
+ 'jquery-ui-tabs',
326
+ 'codemirror-javascript',
327
+ 'codemirror-css'//,
328
+ //'codemirror-htmlmixed',
329
+ //'codemirror-php'
330
+ ),
331
+ SnS_Admin::VERSION, true );
332
+
333
+ wp_localize_script( 'sns-meta-box-scripts', 'codemirror_options', array( 'theme' => $cm_theme ) );
334
  }
335
 
336
  /**
339
  * @param int $post_id ID value of the WordPress post.
340
  */
341
  static function save_post( $post_id ) {
342
+ if ( ! isset( $_POST[ self::NONCE_NAME ] ) || ! wp_verify_nonce( $_POST[ self::NONCE_NAME ], Scripts_n_Styles::$file )
343
+ || ! current_user_can( 'unfiltered_html' )
344
+ || wp_is_post_revision( $post_id ) // is needed for get_post_meta compatibility.
345
+ || ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
346
+ ) return;
 
 
 
 
 
 
347
 
348
+ /*
349
+ NOTE: There is no current_user_can( 'edit_post' ) check here, because as far as I
350
+ can tell, in /wp-admin/post.php the calls edit_post(), write_post(), post_preview(),
351
+ wp_untrash_post(), etc., the check is already done prior to the 'save_post' action,
352
+ which is where this function is called. Other calls are from other pages so the
353
+ NONCE covers those cases, and that leaves autosave, which is also checked here.
354
+ */
355
+
356
+ $SnS = get_post_meta( $post_id, '_SnS', true );
357
+ $scripts = isset( $SnS['scripts'] ) ? $SnS[ 'scripts' ]: array();
358
+ $styles = isset( $SnS['styles'] ) ? $SnS[ 'styles' ]: array();
359
+
360
+ $scripts = self::maybe_set( $scripts, 'scripts_in_head' );
361
+ $scripts = self::maybe_set( $scripts, 'scripts' );
362
+ $scripts = self::maybe_set( $scripts, 'enqueue_scripts' );
363
+ $styles = self::maybe_set( $styles, 'styles' );
364
+ $styles = self::maybe_set( $styles, 'classes_body' );
365
+ $styles = self::maybe_set( $styles, 'classes_post' );
366
+
367
+ // This one isn't posted, it's ajax only. Cleanup anyway.
368
+ if ( isset( $styles[ 'classes_mce' ] ) && empty( $styles[ 'classes_mce' ] ) )
369
+ unset( $styles[ 'classes_mce' ] );
370
+
371
+ $SnS['scripts'] = $scripts;
372
+ $SnS['styles'] = $styles;
373
+
374
+ if ( empty( $SnS ) )
375
+ delete_post_meta( $post_id, '_SnS' );
376
+ else
377
+ update_post_meta( $post_id, '_SnS', $SnS );
378
+ }
379
+
380
+ /**
381
+ * maybe_set()
382
+ * Filters $o and Checks if the sent data $i is empty (intended to clear). If not, updates.
383
+ */
384
+ function maybe_set( $o, $i, $p = 'SnS_' ) {
385
+ if ( empty( $_REQUEST[ $p . $i ] ) ) {
386
+ if ( isset( $o[ $i ] ) ) unset( $o[ $i ] );
387
+ } else {
388
+ $o[ $i ] = $_REQUEST[ $p . $i ];
389
  }
390
+ return $o;
391
  }
392
  }
393
  ?>
includes/class.SnS_Form.php ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * SnS_Global_Page
4
+ *
5
+ * Allows WordPress admin users the ability to add custom CSS
6
+ * and JavaScript directly to individual Post, Pages or custom
7
+ * post types.
8
+ */
9
+
10
+ class SnS_Form
11
+ {
12
+ /**
13
+ * Settings Page
14
+ * Outputs a textarea for setting 'scripts_in_head'.
15
+ */
16
+ function textarea( $args ) {
17
+ extract( $args );
18
+ $options = get_option( $setting );
19
+ $value = isset( $options[ $label_for ] ) ? $options[ $label_for ] : '';
20
+ $output = '<textarea';
21
+ $output .= ( $style ) ? ' style="' . $style . '"': '';
22
+ $output .= ( $class ) ? ' class="' . $class . '"': '';
23
+ $output .= ( $rows ) ? ' rows="' . $rows . '"': '';
24
+ $output .= ( $cols ) ? ' cols="' . $cols . '"': '';
25
+ $output .= ' name="' . $setting . '[' . $label_for . ']"';
26
+ $output .= ' id="' . $label_for . '">';
27
+ $output .= $value . '</textarea>';
28
+ if ( $description ) {
29
+ $output .= $description;
30
+ }
31
+ echo $output;
32
+ }
33
+
34
+ /**
35
+ * Settings Page
36
+ * Outputs a select element for selecting options to set scripts for including.
37
+ */
38
+ function select( $args ) {
39
+ extract( $args );
40
+ $options = get_option( $setting );
41
+ $selected = isset( $options[ $label_for ] ) ? $options[ $label_for ] : array();
42
+
43
+ $output = '<select';
44
+ $output .= ' id="' . $label_for . '"';
45
+ $output .= ' name="' . $setting . '[' . $label_for . ']';
46
+ if ( isset( $multiple ) && $multiple )
47
+ $output .= '[]" multiple="multiple"';
48
+ else
49
+ $output .= '"';
50
+ $output .= ( $size ) ? ' size="' . $size . '"': '';
51
+ $output .= ( $style ) ? ' style="' . $style . '"': '';
52
+ $output .= '>';
53
+ foreach ( $choices as $choice ) {
54
+ $output .= '<option value="' . $choice . '"';
55
+ if ( isset( $multiple ) && $multiple )
56
+ foreach ( $selected as $handle ) $output .= selected( $handle, $choice, false );
57
+ else
58
+ $output .= selected( $selected, $choice, false );
59
+ $output .= '>' . $choice . '</option> ';
60
+ }
61
+ $output .= '</select>';
62
+ if ( ! empty( $show_current ) && ! empty( $selected ) ) {
63
+ $output .= '<p>' . $show_current;
64
+ foreach ( $selected as $handle ) $output .= '<code>' . $handle . '</code> ';
65
+ $output .= '</p>';
66
+ }
67
+ echo $output;
68
+ }
69
+
70
+ /**
71
+ * Settings Page
72
+ * Outputs the Admin Page and calls the Settings registered with the Settings API.
73
+ */
74
+ function take_action() {
75
+ global $action, $option_page, $page, $new_whitelist_options;
76
+
77
+ if ( ! current_user_can( 'manage_options' ) || ! current_user_can( 'unfiltered_html' ) || ( is_multisite() && ! is_super_admin() ) )
78
+ wp_die( __( 'Cheatin&#8217; uh?' ) );
79
+
80
+ if ( isset( $_REQUEST[ 'message' ] ) && $_REQUEST[ 'message' ] )
81
+ add_settings_error( $page, 'settings_updated', __( 'Settings saved.' ), 'updated' );
82
+
83
+ if ( ! isset( $_REQUEST[ 'action' ], $_REQUEST[ 'option_page' ], $_REQUEST[ 'page' ] ) )
84
+ return;
85
+
86
+ wp_reset_vars( array( 'action', 'option_page', 'page' ) );
87
+
88
+ check_admin_referer( $option_page . '-options' );
89
+
90
+ if ( ! isset( $new_whitelist_options[ $option_page ] ) )
91
+ return;
92
+
93
+ $options = $new_whitelist_options[ $option_page ];
94
+ foreach ( (array) $options as $option ) {
95
+ $old = get_option( $option );
96
+ $option = trim( $option );
97
+ $value = null;
98
+ if ( isset($_POST[ $option ]) )
99
+ $value = $_POST[ $option ];
100
+ if ( !is_array( $value ) )
101
+ $value = trim( $value );
102
+
103
+ $value = array_merge( $old, stripslashes_deep( $value ) );
104
+ update_option( $option, $value );
105
+ }
106
+
107
+ if ( ! count( get_settings_errors() ) )
108
+ add_settings_error( $page, 'settings_updated', __( 'Settings saved.' ), 'updated' );
109
+
110
+ if ( isset( $_POST[ $option ][ 'menu_position' ] ) && ( $value[ 'menu_position' ] != SnS_Admin::$parent_slug ) ) {
111
+ switch( $value[ 'menu_position' ] ) {
112
+ case 'menu':
113
+ case 'object':
114
+ case 'utility':
115
+ wp_redirect( add_query_arg( 'message', 1, admin_url( 'admin.php?page=sns_settings' ) ) );
116
+ break;
117
+ default:
118
+ wp_redirect( add_query_arg( 'message', 1, admin_url( $value[ 'menu_position' ].'?page=sns_settings' ) ) );
119
+ break;
120
+ }
121
+ }
122
+ return;
123
+ }
124
+
125
+ /**
126
+ * Settings Page
127
+ * Outputs the Admin Page and calls the Settings registered with the Settings API in init_options_page().
128
+ */
129
+ function page() {
130
+ SnS_Admin::upgrade_check();
131
+ ?>
132
+ <div class="wrap">
133
+ <?php SnS_Admin::nav(); ?>
134
+ <?php settings_errors(); ?>
135
+ <form action="" method="post" autocomplete="off">
136
+ <?php settings_fields( SnS_Admin::OPTION_GROUP ); ?>
137
+ <?php do_settings_sections( SnS_Admin::MENU_SLUG ); ?>
138
+ <?php submit_button(); ?>
139
+ </form>
140
+ </div>
141
+ <?php
142
+ }
143
+ }
144
+ ?>
includes/class.SnS_Global_Page.php ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * SnS_Global_Page
4
+ *
5
+ * Allows WordPress admin users the ability to add custom CSS
6
+ * and JavaScript directly to individual Post, Pages or custom
7
+ * post types.
8
+ */
9
+
10
+ class SnS_Global_Page
11
+ {
12
+ /**
13
+ * Constants
14
+ */
15
+ const OPTION_GROUP = 'scripts_n_styles';
16
+
17
+ /**
18
+ * Initializing method.
19
+ * @static
20
+ */
21
+ function init() {
22
+ if ( SnS_Admin::$parent_slug == SnS_Admin::MENU_SLUG ) $menu_title = 'Global';
23
+ else $menu_title = 'Scripts n Styles';
24
+
25
+ $hook_suffix = add_submenu_page( SnS_Admin::$parent_slug, 'Scripts n Styles', $menu_title, 'unfiltered_html', SnS_Admin::MENU_SLUG, array( 'SnS_Form', 'page' ) );
26
+
27
+ add_action( "load-$hook_suffix", array( __CLASS__, 'admin_load' ) );
28
+ add_action( "load-$hook_suffix", array( 'SnS_Admin', 'help' ) );
29
+ add_action( "load-$hook_suffix", array( 'SnS_Form', 'take_action'), 49 );
30
+ add_action( "admin_print_styles-$hook_suffix", array( __CLASS__, 'admin_enqueue_scripts' ) );
31
+ }
32
+
33
+ function admin_enqueue_scripts() {
34
+ $options = get_option( 'SnS_options' );
35
+ $cm_theme = isset( $options[ 'cm_theme' ] ) ? $options[ 'cm_theme' ] : 'default';
36
+ wp_enqueue_style( 'sns-options-styles', plugins_url('css/options-styles.css', Scripts_n_Styles::$file), array( 'codemirror' ), SnS_Admin::VERSION );
37
+ wp_enqueue_style( 'codemirror', plugins_url( 'libraries/CodeMirror2/lib/codemirror.css', Scripts_n_Styles::$file), array(), '2.18' );
38
+ wp_enqueue_style( "codemirror-$cm_theme", plugins_url( "libraries/CodeMirror2/theme/$cm_theme.css", Scripts_n_Styles::$file), array( 'codemirror' ), '2.18' );
39
+
40
+ wp_enqueue_script( 'sns-global-page-scripts', plugins_url('js/global-page.js', Scripts_n_Styles::$file), array( 'jquery', 'codemirror-css', 'codemirror-javascript' ), SnS_Admin::VERSION, true );
41
+ wp_localize_script( 'sns-global-page-scripts', 'codemirror_options', array( 'theme' => $cm_theme ) );
42
+ wp_enqueue_script( 'codemirror', plugins_url( 'libraries/CodeMirror2/lib/codemirror.js', Scripts_n_Styles::$file), array(), '2.18' );
43
+ wp_enqueue_script( 'codemirror-css', plugins_url( 'libraries/CodeMirror2/mode/css/css.js', Scripts_n_Styles::$file), array( 'codemirror' ), '2.18' );
44
+ wp_enqueue_script( 'codemirror-javascript', plugins_url( 'libraries/CodeMirror2/mode/javascript/javascript.js', Scripts_n_Styles::$file), array( 'codemirror' ), '2.18' );
45
+ }
46
+ /**
47
+ * Settings Page
48
+ * Adds Admin Menu Item via WordPress' "Administration Menus" API. Also hook actions to register options via WordPress' Settings API.
49
+ */
50
+ function admin_load() {
51
+
52
+ register_setting(
53
+ self::OPTION_GROUP,
54
+ 'SnS_options' );
55
+
56
+ add_settings_section(
57
+ 'global',
58
+ 'Global Scripts n Styles',
59
+ array( __CLASS__, 'global_section' ),
60
+ SnS_Admin::MENU_SLUG );
61
+
62
+ add_settings_field(
63
+ 'scripts',
64
+ '<strong>Scripts:</strong> ',
65
+ array( 'SnS_Form', 'textarea' ),
66
+ SnS_Admin::MENU_SLUG,
67
+ 'global',
68
+ array(
69
+ 'label_for' => 'scripts',
70
+ 'setting' => 'SnS_options',
71
+ 'class' => 'code js',
72
+ 'rows' => 5,
73
+ 'cols' => 40,
74
+ 'style' => 'min-width: 500px; width:97%;',
75
+ 'description' => '<span class="description" style="max-width: 500px; display: inline-block;">The "Scripts" will be included <strong>verbatim</strong> in <code>&lt;script></code> tags at the bottom of the <code>&lt;body></code> element of your html.</span>'
76
+ ) );
77
+ add_settings_field(
78
+ 'styles',
79
+ '<strong>Styles:</strong> ',
80
+ array( 'SnS_Form', 'textarea' ),
81
+ SnS_Admin::MENU_SLUG,
82
+ 'global',
83
+ array(
84
+ 'label_for' => 'styles',
85
+ 'setting' => 'SnS_options',
86
+ 'class' => 'code css',
87
+ 'rows' => 5,
88
+ 'cols' => 40,
89
+ 'style' => 'min-width: 500px; width:97%;',
90
+ 'description' => '<span class="description" style="max-width: 500px; display: inline-block;">The "Styles" will be included <strong>verbatim</strong> in <code>&lt;style></code> tags in the <code>&lt;head></code> element of your html.</span>'
91
+ ) );
92
+ add_settings_field(
93
+ 'scripts_in_head',
94
+ '<strong>Scripts</strong><br />(for the <code>head</code> element): ',
95
+ array( 'SnS_Form', 'textarea' ),
96
+ SnS_Admin::MENU_SLUG,
97
+ 'global',
98
+ array(
99
+ 'label_for' => 'scripts_in_head',
100
+ 'setting' => 'SnS_options',
101
+ 'class' => 'code js',
102
+ 'rows' => 5,
103
+ 'cols' => 40,
104
+ 'style' => 'min-width: 500px; width:97%;',
105
+ 'description' => '<span class="description" style="max-width: 500px; display: inline-block;">The "Scripts (in head)" will be included <strong>verbatim</strong> in <code>&lt;script></code> tags in the <code>&lt;head></code> element of your html.</span>'
106
+ ) );
107
+ add_settings_field(
108
+ 'enqueue_scripts',
109
+ '<strong>Enqueue Scripts</strong>: ',
110
+ array( 'SnS_Form', 'select' ),
111
+ SnS_Admin::MENU_SLUG,
112
+ 'global',
113
+ array(
114
+ 'label_for' => 'enqueue_scripts',
115
+ 'setting' => 'SnS_options',
116
+ 'choices' => Scripts_n_Styles::get_wp_registered(),
117
+ 'size' => 5,
118
+ 'style' => 'height: auto;',
119
+ 'multiple' => true,
120
+ 'show_current' => 'Currently Enqueued Scripts: '
121
+ ) );
122
+ }
123
+
124
+ /**
125
+ * Settings Page
126
+ * Outputs Description text for the Global Section.
127
+ */
128
+ function global_section() {
129
+ ?>
130
+ <div style="max-width: 55em;">
131
+ <p>Code entered here will be included in <em>every page (and post) of your site</em>, including the homepage and archives. The code will appear <strong>before</strong> Scripts and Styles registered individually.</p>
132
+ </div>
133
+ <?php
134
+ }
135
+ }
136
+ ?>
includes/class.SnS_List_Usage.php ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ if ( ! class_exists( 'WP_List_Table' ) ) require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
3
+
4
+ class SnS_List_Usage extends WP_List_Table {
5
+
6
+ function ajax_user_can() {
7
+ return current_user_can( 'unfiltered_html' ) && current_user_can( 'manage_options' );
8
+ }
9
+
10
+ function column_default( $post, $column_name ) {
11
+ $return = '';
12
+ switch( $column_name ){
13
+ case 'status':
14
+ return $post->post_status;
15
+ case 'ID':
16
+ case 'post_type':
17
+ return $post->$column_name;
18
+ case 'script_data':
19
+ if ( isset( $post->sns_scripts[ 'scripts_in_head' ] ) ) {
20
+ $return .= '<div>Scripts (head)</div>';
21
+ }
22
+ if ( isset( $post->sns_scripts[ 'scripts' ] ) ) {
23
+ $return .= '<div>Scripts</div>';
24
+ }
25
+ if ( isset( $post->sns_scripts[ 'enqueue_scripts' ] ) ) {
26
+ $return .= '<div>Enqueued Scripts</div>';
27
+ }
28
+ return $return;
29
+ case 'style_data':
30
+ if ( isset( $post->sns_styles[ 'classes_mce' ] ) ) {
31
+ $return .= '<div>TinyMCE Formats</div>';
32
+ }
33
+ if ( isset( $post->sns_styles[ 'styles' ] ) ) {
34
+ $return .= '<div>Styles</div>';
35
+ }
36
+ if ( isset( $post->sns_styles[ 'classes_post' ] ) ) {
37
+ $return .= '<div>Post Classes</div>';
38
+ }
39
+ if ( isset( $post->sns_styles[ 'classes_body' ] ) ) {
40
+ $return .= '<div>Body Classes</div>';
41
+ }
42
+ return $return;
43
+ default:
44
+ return print_r( $post, true );
45
+ }
46
+ }
47
+
48
+ function column_title( $post ) {
49
+ $edit_link = esc_url( get_edit_post_link( $post->ID ) );
50
+ $edit_title = esc_attr( sprintf( __( 'Edit &#8220;%s&#8221;' ), $post->post_title ) );
51
+
52
+ $actions = array(
53
+ 'edit' => sprintf('<a title="%s" href="%s">Edit</a>', $edit_title, $edit_link ),
54
+ );
55
+
56
+ $return = '<strong>';
57
+ if ( $this->ajax_user_can() && $post->post_status != 'trash' ) {
58
+ $return .= '<a class="row-title"';
59
+ $return .= ' href="'. $edit_link .'"';
60
+ $return .= ' title="'. $edit_title .'">';
61
+ $return .= $post->post_title;
62
+ $return .= '</a>';
63
+ } else {
64
+ $return .= $post->post_title;
65
+ }
66
+ $this->_post_states( $post );
67
+ $return .= '</strong>';
68
+ $return .= $this->row_actions( $actions );
69
+
70
+ return $return;
71
+ }
72
+
73
+ function get_columns() {
74
+ $columns = array(
75
+ 'title' => 'Title',
76
+ 'ID' => 'ID',
77
+ 'status' => 'Status',
78
+ 'post_type' => 'Post Type',
79
+ 'script_data' => 'Script Data',
80
+ 'style_data' => 'Style Data'
81
+ );
82
+
83
+ return $columns;
84
+ }
85
+
86
+ function prepare_items() {
87
+ $screen_id = get_current_screen()->id;
88
+ $per_page = $this->get_items_per_page( str_replace( '-', '_', "{$screen_id}_per_page" ) );
89
+
90
+ $this->_column_headers = array(
91
+ $this->get_columns(),
92
+ array(),
93
+ $this->get_sortable_columns()
94
+ );
95
+
96
+ /**
97
+ * Get Relavent Posts.
98
+ */
99
+ $posts = get_posts( array(
100
+ 'numberposts' => -1,
101
+ 'post_type' => 'any',
102
+ 'post_status' => 'any',
103
+ 'orderby' => 'ID',
104
+ 'order' => 'ASC',
105
+ 'meta_key' => '_SnS'
106
+ ) );
107
+
108
+ $items = $this->_add_meta_data( $posts );
109
+
110
+ $total_items = count( $items );
111
+
112
+ /**
113
+ * Reduce items to current page's posts.
114
+ */
115
+ $this->items = array_slice(
116
+ $items,
117
+ ( ( $this->get_pagenum() - 1 ) * $per_page ),
118
+ $per_page
119
+ );
120
+
121
+ $this->set_pagination_args( compact( 'total_items', 'per_page' ) );
122
+ }
123
+
124
+ function _post_states( $post ) {
125
+ $post_states = array();
126
+ $return = '';
127
+ if ( isset($_GET['post_status']) )
128
+ $post_status = $_GET['post_status'];
129
+ else
130
+ $post_status = '';
131
+
132
+ if ( !empty($post->post_password) )
133
+ $post_states['protected'] = __('Password protected');
134
+ if ( 'private' == $post->post_status && 'private' != $post_status )
135
+ $post_states['private'] = __('Private');
136
+ if ( 'draft' == $post->post_status && 'draft' != $post_status )
137
+ $post_states['draft'] = __('Draft');
138
+ if ( 'pending' == $post->post_status && 'pending' != $post_status )
139
+ /* translators: post state */
140
+ $post_states['pending'] = _x('Pending', 'post state');
141
+ if ( is_sticky($post->ID) )
142
+ $post_states['sticky'] = __('Sticky');
143
+
144
+ $post_states = apply_filters( 'display_post_states', $post_states );
145
+
146
+ if ( ! empty($post_states) ) {
147
+ $state_count = count($post_states);
148
+ $i = 0;
149
+ $return .= ' - ';
150
+ foreach ( $post_states as $state ) {
151
+ ++$i;
152
+ ( $i == $state_count ) ? $sep = '' : $sep = ', ';
153
+ $return .= "<span class='post-state'>$state$sep</span>";
154
+ }
155
+ }
156
+
157
+ if ( get_post_format( $post->ID ) )
158
+ $return .= ' - <span class="post-state-format">' . get_post_format_string( get_post_format( $post->ID ) ) . '</span>';
159
+
160
+ return $return;
161
+ }
162
+
163
+ function _add_meta_data( $posts ) {
164
+ foreach( $posts as $post) {
165
+ $SnS = get_post_meta( $post->ID, '_SnS', true );
166
+ $styles = isset( $SnS['styles'] ) ? $SnS[ 'styles' ]: array();
167
+ $scripts = isset( $SnS['scripts'] ) ? $SnS[ 'scripts' ]: array();
168
+ if ( ! empty( $styles ) )
169
+ $post->sns_styles = $styles;
170
+ if ( ! empty( $scripts ) )
171
+ $post->sns_scripts = $scripts;
172
+ }
173
+ return $posts;
174
+ }
175
+ }
176
+ ?>
includes/class.SnS_Settings_Page.php CHANGED
@@ -6,278 +6,143 @@
6
  * and JavaScript directly to individual Post, Pages or custom
7
  * post types.
8
  */
9
-
10
- // $hook_suffix = 'tools_page_Scripts-n-Styles'; // kept here for reference
11
- // $plugin_file = 'scripts-n-styles/scripts-n-styles.php'; // kept here for reference
12
 
13
  class SnS_Settings_Page
14
  {
15
  /**
16
  * Constants
17
  */
18
- const OPTION_GROUP = 'scripts_n_styles';
19
 
20
  /**
21
  * Initializing method.
22
  * @static
23
  */
24
- static function init() {
25
- /* NOTE: Even when Scripts n Styles is not restricted by 'manage_options', Editors still can't submit the option page */
26
- if ( current_user_can( 'manage_options' ) ) { // if they can't, they won't be able to save anyway.
27
- $hook_suffix = add_management_page(
28
- 'Scripts n Styles Settings', // $page_title (string) (required) The text to be displayed in the title tags of the page when the menu is selected
29
- 'Scripts n Styles', // $menu_title (string) (required) The text to be used for the menu
30
- 'unfiltered_html', // $capability (string) (required) The capability required for this menu to be displayed to the user.
31
- SnS_Admin::MENU_SLUG, // $menu_slug (string) (required) The slug name to refer to this menu by (should be unique for this menu).
32
- array( __CLASS__, 'options_page' ) // $function (callback) (optional) The function to be called to output the content for this page.
33
- );
34
- Scripts_n_Styles::$hook_suffix = $hook_suffix;
35
- add_action( "load-$hook_suffix", array( __CLASS__, 'init_options_page' ) );
36
- add_action( "load-options.php", array( __CLASS__, 'init_options_page' ) );
37
-
38
- add_action( "admin_print_styles-$hook_suffix", array( __CLASS__, 'options_styles'));
39
- add_action( "admin_print_scripts-$hook_suffix", array( __CLASS__, 'options_scripts'));
40
-
41
- add_contextual_help( $hook_suffix, self::contextual_help() );
42
  }
43
  }
44
 
45
- /**
46
- * Settings Page help
47
- */
48
- function contextual_help() {
49
- $contextual_help = '<p>In default (non MultiSite) WordPress installs, both <em>Administrators</em> and
50
- <em>Editors</em> can access <em>Scripts-n-Styles</em> on individual edit screens.
51
- Only <em>Administrators</em> can access this Options Page. In MultiSite WordPress installs, only <em>"Super Admin"</em> users can access either
52
- <em>Scripts-n-Styles</em> on individual edit screens or this Options Page. If other plugins change capabilities (specifically "unfiltered_html"),
53
- other users can be granted access.</p>';
54
- return $contextual_help;
 
 
 
 
 
 
 
 
 
55
  }
56
 
 
 
 
 
 
 
 
57
  /**
58
  * Settings Page
59
  * Adds Admin Menu Item via WordPress' "Administration Menus" API. Also hook actions to register options via WordPress' Settings API.
60
  */
61
- static function init_options_page() {
62
- register_setting(
63
- self::OPTION_GROUP, // $option_group (string) (required) A settings group name. Can be anything.
64
- 'SnS_options' // $option_name (string) (required) The name of an option to sanitize and save.
65
- );
66
  register_setting(
67
- self::OPTION_GROUP,
68
- 'SnS_enqueue_scripts'
69
- );
70
  add_settings_section(
71
- 'global',
72
- 'Global Scripts n Styles',
73
- array( __CLASS__, 'global_section' ),
74
- SnS_Admin::MENU_SLUG
75
- );
76
- add_settings_field(
77
- 'scripts',
78
- '<label for="scripts"><strong>Scripts:</strong> </label>',
79
- array( __CLASS__, 'scripts_field' ),
80
- SnS_Admin::MENU_SLUG,
81
- 'global'
82
- );
83
  add_settings_field(
84
- 'styles',
85
- '<label for="styles"><strong>Styles:</strong> </label>',
86
- array( __CLASS__, 'styles_field' ),
87
- SnS_Admin::MENU_SLUG,
88
- 'global'
89
- );
 
 
 
 
 
 
 
90
  add_settings_field(
91
- 'scripts_in_head',
92
- '<label for="scripts_in_head"><strong>Scripts</strong><br />(for the <code>head</code> element): </label>',
93
- array( __CLASS__, 'scripts_in_head_field' ),
94
- SnS_Admin::MENU_SLUG,
95
- 'global'
96
- );
97
- /*add_settings_field(
98
- 'enqueue_scripts',
99
- '<label for="enqueue_scripts"><strong>Enqueue Scripts</strong>: </label>',
100
- array( __CLASS__, 'enqueue_scripts_field' ),
101
- SnS_Admin::MENU_SLUG,
102
- 'global'
103
- );*/
104
  add_settings_section(
105
- 'usage',
106
- 'Scripts n Styles Usage',
107
- array( __CLASS__, 'usage_section' ),
108
- SnS_Admin::MENU_SLUG
109
- );
110
- }
111
-
112
- /**
113
- * Settings Page
114
- * Adds CSS styles to the Scripts n Styles Admin Page.
115
- */
116
- static function options_styles() {
117
- wp_enqueue_style( 'sns-options-styles', plugins_url('css/options-styles.css', Scripts_n_Styles::$file), array( 'codemirror-default' ), SnS_Admin::VERSION );
118
- wp_enqueue_style( 'codemirror', plugins_url( 'libraries/codemirror/lib/codemirror.css', Scripts_n_Styles::$file), array(), '2.1' );
119
- wp_enqueue_style( 'codemirror-default', plugins_url( 'libraries/codemirror/theme/default.css', Scripts_n_Styles::$file), array( 'codemirror' ), '2.1' );
120
- }
121
-
122
- /**
123
- * Settings Page
124
- * Adds JavaScript to the Scripts n Styles Admin Page.
125
- */
126
- static function options_scripts() {
127
- wp_enqueue_script( 'sns-options-scripts', plugins_url('js/options-scripts.js', Scripts_n_Styles::$file), array( 'jquery', 'codemirror-css', 'codemirror-javascript' ), SnS_Admin::VERSION, true );
128
- wp_enqueue_script( 'codemirror', plugins_url( 'libraries/codemirror/lib/codemirror.js', Scripts_n_Styles::$file), array(), '2.1' );
129
- wp_enqueue_script( 'codemirror-css', plugins_url( 'libraries/codemirror/mode/css.js', Scripts_n_Styles::$file), array( 'codemirror' ), '2.1' );
130
- wp_enqueue_script( 'codemirror-javascript', plugins_url( 'libraries/codemirror/mode/javascript.js', Scripts_n_Styles::$file), array( 'codemirror' ), '2.1' );
131
  }
132
 
133
  /**
134
  * Settings Page
135
  * Outputs Description text for the Global Section.
136
  */
137
- static function global_section() {
138
  ?>
139
  <div style="max-width: 55em;">
140
- <p>Code entered here will be included in <em>every page (and post) of your site</em>, including the homepage and archives. The code will appear <strong>before</strong> Scripts and Styles registered individually.</p>
141
  </div>
142
  <?php
143
  }
144
 
145
  /**
146
  * Settings Page
147
- * Outputs the Usage Section.
148
- */
149
- static function usage_section() {
150
- if ( ! isset( $_REQUEST[ 'usage' ] ) || ! $_REQUEST[ 'usage' ] ) {
151
- echo '<a href="' . menu_page_url( SnS_Admin::MENU_SLUG, false ) . '&usage=1"/>Show Usage</a>';
152
- return;
153
- }
154
- $options = get_option( 'SnS_options' );
155
-
156
- $all_posts = get_posts( array( 'numberposts' => -1, 'post_type' => 'any', 'post_status' => 'any' ) );
157
- $sns_posts = array();
158
- foreach( $all_posts as $post) {
159
- $temp_styles = get_post_meta( $post->ID, 'uFp_styles', true );
160
- $temp_scripts = get_post_meta( $post->ID, 'uFp_scripts', true );
161
- if ( ! empty( $temp_styles ) || ! empty( $temp_scripts ) )
162
- $sns_posts[] = $post;
163
- }
164
-
165
- if ( ! empty( $sns_posts ) ) {
166
- ?>
167
- <table cellspacing="0" class="widefat">
168
- <thead>
169
- <tr>
170
- <th>Title</th>
171
- <th>ID</th>
172
- <th>Status</th>
173
- <th>Post Type</th>
174
- </tr>
175
- </thead>
176
- <tbody>
177
- <?php foreach( $sns_posts as $post) {
178
- $temp_styles = get_post_meta( $post->ID, 'uFp_styles', true );
179
- $temp_scripts = get_post_meta( $post->ID, 'uFp_scripts', true );
180
- if ( ! empty( $temp_styles ) || ! empty( $temp_scripts ) ) { ?>
181
- <tr>
182
- <td>
183
- <strong><a class="row-title" title="Edit &#8220;<?php echo esc_attr( $post->post_title ); ?>&#8221;" href="<?php echo esc_url( get_edit_post_link( $post->ID ) ); ?>"><?php echo $post->post_title; ?></a></strong>
184
- <div class="row-actions"><span class="edit"><a title="Edit this item" href="<?php echo esc_url( get_edit_post_link( $post->ID ) ); ?>">Edit</a></span></div>
185
- </td>
186
- <td><?php echo $post->ID; ?></td>
187
- <td><?php echo $post->post_status; ?></td>
188
- <td><?php echo $post->post_type; ?></td>
189
- </tr>
190
- <?php }
191
- } ?>
192
- </tbody>
193
- <tfoot>
194
- <tr>
195
- <th>Title</th>
196
- <th>ID</th>
197
- <th>Status</th>
198
- <th>Post Type</th>
199
- </tr>
200
- </tfoot>
201
- </table>
202
- <?php
203
- } else {
204
- ?>
205
- <div style="max-width: 55em;">
206
- <p>No content items are currently using Scripts-n-Styles data.</p>
207
- </div>
208
- <?php
209
- }
210
- }
211
-
212
- /**
213
- * Settings Page
214
- * Outputs a textarea for setting 'scripts'.
215
- */
216
- static function scripts_field() {
217
- $options = get_option( 'SnS_options' );
218
- ?><textarea style="min-width: 500px; width:97%;" class="code js" rows="5" cols="40" name="SnS_options[scripts]" id="scripts"><?php echo isset( $options[ 'scripts' ] ) ? $options[ 'scripts' ] : ''; ?></textarea><br />
219
- <span class="description" style="max-width: 500px; display: inline-block;">The "Scripts" will be included <strong>verbatim</strong> in <code>&lt;script></code> tags at the bottom of the <code>&lt;body></code> element of your html.</span>
220
- <?php
221
- }
222
-
223
- /**
224
- * Settings Page
225
- * Outputs a textarea for setting 'styles'.
226
- */
227
- static function styles_field() {
228
- $options = get_option( 'SnS_options' );
229
- ?><textarea style="min-width: 500px; width:97%;" class="code css" rows="5" cols="40" name="SnS_options[styles]" id="styles"><?php echo isset( $options[ 'styles' ] ) ? $options[ 'styles' ] : ''; ?></textarea><br />
230
- <span class="description" style="max-width: 500px; display: inline-block;">The "Styles" will be included <strong>verbatim</strong> in <code>&lt;style></code> tags in the <code>&lt;head></code> element of your html.</span><?php
231
- }
232
-
233
- /**
234
- * Settings Page
235
- * Outputs a textarea for setting 'scripts_in_head'.
236
- */
237
- static function scripts_in_head_field() {
238
- $options = get_option( 'SnS_options' );
239
- ?><textarea style="min-width: 500px; width:97%;" class="code js" rows="5" cols="40" name="SnS_options[scripts_in_head]" id="scripts_in_head"><?php echo isset( $options[ 'scripts_in_head' ] ) ? $options[ 'scripts_in_head' ] : ''; ?></textarea><br />
240
- <span class="description" style="max-width: 500px; display: inline-block;">The "Scripts (in head)" will be included <strong>verbatim</strong> in <code>&lt;script></code> tags in the <code>&lt;head></code> element of your html.</span>
241
- <?php
242
- }
243
-
244
- /**
245
- * Settings Page
246
- * Outputs a select element for selecting options to set $sns_enqueue_scripts.
247
- */
248
- static function enqueue_scripts_field() {
249
- $registered_handles = Scripts_n_Styles::get_wp_registered();
250
- $sns_enqueue_scripts = get_option( 'SnS_enqueue_scripts' );
251
- if ( ! is_array( $sns_enqueue_scripts ) ) $sns_enqueue_scripts = array();
252
- ?>
253
- <select name="SnS_enqueue_scripts[]" id="enqueue_scripts" size="5" multiple="multiple" style="height: auto;">
254
- <?php foreach ( $registered_handles as $value ) { ?>
255
- <option value="<?php echo $value ?>"<?php foreach ( $sns_enqueue_scripts as $handle ) selected( $handle, $value ); ?>><?php echo $value ?></option>
256
- <?php } ?>
257
- </select>
258
- <?php if ( ! empty( $sns_enqueue_scripts ) && is_array( $sns_enqueue_scripts ) ) { ?>
259
- <p>Currently Enqueued Scripts:
260
- <?php foreach ( $sns_enqueue_scripts as $handle ) echo '<code>' . $handle . '</code> '; ?>
261
- </p>
262
- <?php }
263
- }
264
-
265
- /**
266
- * Settings Page
267
- * Outputs the Admin Page and calls the Settings registered with the Settings API in init_options_page().
268
- */
269
- static function options_page() {
270
- SnS_Admin::upgrade_check();
271
- global $title;
272
  ?>
273
- <div class="wrap">
274
- <?php screen_icon(); ?>
275
- <h2><?php echo esc_html($title); ?></h2>
276
- <form action="options.php" method="post" autocomplete="off">
277
- <?php settings_fields( self::OPTION_GROUP ); ?>
278
- <?php do_settings_sections( SnS_Admin::MENU_SLUG ); ?>
279
- <?php submit_button(); ?>
280
- </form>
 
 
 
 
281
  </div>
282
  <?php
283
  }
6
  * and JavaScript directly to individual Post, Pages or custom
7
  * post types.
8
  */
 
 
 
9
 
10
  class SnS_Settings_Page
11
  {
12
  /**
13
  * Constants
14
  */
15
+ const MENU_SLUG = 'sns_settings';
16
 
17
  /**
18
  * Initializing method.
19
  * @static
20
  */
21
+ function init() {
22
+ $hook_suffix = add_submenu_page( SnS_Admin::$parent_slug, 'Scripts n Styles', 'Settings', 'unfiltered_html', self::MENU_SLUG, array( 'SnS_Form', 'page' ) );
23
+
24
+ add_action( "load-$hook_suffix", array( __CLASS__, 'admin_load' ) );
25
+ add_action( "load-$hook_suffix", array( 'SnS_Admin', 'help' ) );
26
+ add_action( "load-$hook_suffix", array( 'SnS_Form', 'take_action' ), 49 );
27
+ add_action( "admin_print_styles-$hook_suffix", array( __CLASS__, 'admin_enqueue_scripts' ) );
28
+
29
+ // Make the page into a tab.
30
+ if ( SnS_Admin::MENU_SLUG != SnS_Admin::$parent_slug ) {
31
+ remove_submenu_page( SnS_Admin::$parent_slug, self::MENU_SLUG );
32
+ add_filter( 'parent_file', array( __CLASS__, 'parent_file') );
 
 
 
 
 
 
33
  }
34
  }
35
 
36
+ function admin_enqueue_scripts() {
37
+ $options = get_option( 'SnS_options' );
38
+ $cm_theme = isset( $options[ 'cm_theme' ] ) ? $options[ 'cm_theme' ] : 'default';
39
+
40
+ wp_enqueue_style( 'sns-options-styles', plugins_url('css/options-styles.css', Scripts_n_Styles::$file), array( 'codemirror' ), SnS_Admin::VERSION );
41
+ wp_enqueue_style( 'codemirror', plugins_url( 'libraries/CodeMirror2/lib/codemirror.css', Scripts_n_Styles::$file), array(), '2.18' );
42
+
43
+ foreach ( array( 'cobalt', 'default', 'eclipse', 'elegant', 'monokai', 'neat', 'night', 'rubyblue' ) as $theme )
44
+ wp_enqueue_style( "codemirror-$theme", plugins_url( "libraries/CodeMirror2/theme/$theme.css", Scripts_n_Styles::$file), array( 'codemirror' ), '2.18' );
45
+
46
+ wp_enqueue_script( 'sns-settings-page-scripts', plugins_url('js/settings-page.js', Scripts_n_Styles::$file), array( 'jquery', 'codemirror-css', 'codemirror-javascript' ), SnS_Admin::VERSION, true );
47
+ wp_localize_script( 'sns-settings-page-scripts', 'codemirror_options', array( 'theme' => $cm_theme ) );
48
+ wp_enqueue_script( 'codemirror', plugins_url( 'libraries/CodeMirror2/lib/codemirror.js', Scripts_n_Styles::$file), array(), '2.18' );
49
+ wp_enqueue_script( 'codemirror-css', plugins_url( 'libraries/CodeMirror2/mode/css/css.js', Scripts_n_Styles::$file), array( 'codemirror' ), '2.18' );
50
+ wp_enqueue_script( 'codemirror-javascript', plugins_url( 'libraries/CodeMirror2/mode/javascript/javascript.js', Scripts_n_Styles::$file), array( 'codemirror' ), '2.18' );
51
+
52
+ wp_enqueue_script( 'codemirror-xml', plugins_url( 'libraries/CodeMirror2/mode/xml/xml.js', Scripts_n_Styles::$file), array( 'codemirror' ), '2.18' );
53
+ wp_enqueue_script( 'codemirror-clike', plugins_url( 'libraries/CodeMirror2/mode/clike/clike.js', Scripts_n_Styles::$file), array( 'codemirror' ), '2.18' );
54
+ wp_enqueue_script( 'codemirror-php', plugins_url( 'libraries/CodeMirror2/mode/php/php.js', Scripts_n_Styles::$file), array( 'codemirror-xml', 'codemirror-css', 'codemirror-javascript', 'codemirror-clike' ), '2.18' );
55
  }
56
 
57
+ static function parent_file( $parent_file ) {
58
+ global $plugin_page, $submenu_file;
59
+ if ( self::MENU_SLUG == $plugin_page ) $submenu_file = SnS_Admin::MENU_SLUG;
60
+ return $parent_file;
61
+ }
62
+
63
+
64
  /**
65
  * Settings Page
66
  * Adds Admin Menu Item via WordPress' "Administration Menus" API. Also hook actions to register options via WordPress' Settings API.
67
  */
68
+ function admin_load() {
69
+ wp_enqueue_style( 'sns-options-styles', plugins_url('css/options-styles.css', Scripts_n_Styles::$file), array(), SnS_Admin::VERSION );
70
+
 
 
71
  register_setting(
72
+ SnS_Admin::OPTION_GROUP,
73
+ 'SnS_options' );
74
+
75
  add_settings_section(
76
+ 'settings',
77
+ 'Scripts n Styles Settings',
78
+ array( __CLASS__, 'settings_section' ),
79
+ SnS_Admin::MENU_SLUG );
80
+
 
 
 
 
 
 
 
81
  add_settings_field(
82
+ 'menu_position',
83
+ '<strong>Menu Position</strong>: ',
84
+ array( 'SnS_Form', 'select' ),
85
+ SnS_Admin::MENU_SLUG,
86
+ 'settings',
87
+ array(
88
+ 'label_for' => 'menu_position',
89
+ 'setting' => 'SnS_options',
90
+ 'choices' => array( 'menu', 'object', 'utility', 'tools.php', 'options-general.php', 'themes.php' ),
91
+ 'size' => 6,
92
+ 'style' => 'height: auto;'
93
+ ) );
94
+
95
  add_settings_field(
96
+ 'cm_theme',
97
+ '<strong>CodeMirror Theme</strong>: ',
98
+ array( 'SnS_Form', 'select' ),
99
+ SnS_Admin::MENU_SLUG,
100
+ 'settings',
101
+ array(
102
+ 'label_for' => 'cm_theme',
103
+ 'setting' => 'SnS_options',
104
+ 'choices' => array( 'cobalt', 'default', 'eclipse', 'elegant', 'monokai', 'neat', 'night', 'rubyblue' ),
105
+ 'size' => 8,
106
+ 'style' => 'height: auto;'
107
+ ) );
108
+
109
  add_settings_section(
110
+ 'demo',
111
+ 'Code Mirror Demo',
112
+ array( __CLASS__, 'demo_section' ),
113
+ SnS_Admin::MENU_SLUG );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  }
115
 
116
  /**
117
  * Settings Page
118
  * Outputs Description text for the Global Section.
119
  */
120
+ function settings_section() {
121
  ?>
122
  <div style="max-width: 55em;">
123
+ <p>Control how and where Scripts n Styles menus and metaboxes appear. These options are here because sometimes users really care about this stuff. Feel free to adjust to your liking. :-)</p>
124
  </div>
125
  <?php
126
  }
127
 
128
  /**
129
  * Settings Page
130
+ * Outputs Description text for the Global Section.
131
+ */
132
+ function demo_section() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
  ?>
134
+ <div style="max-width: 55em;">
135
+ <textarea id="codemirror_demo" name="code" style="min-width: 500px; width:97%;" rows="5" cols="40">
136
+ <?php echo esc_textarea( '<?php
137
+ function hello($who) {
138
+ return "Hello " . $who;
139
+ }
140
+ ?>
141
+ <p>The program says <?= hello("World") ?>.</p>
142
+ <script>
143
+ alert("And here is some JS code"); // also colored
144
+ </script>' ); ?>
145
+ </textarea>
146
  </div>
147
  <?php
148
  }
includes/class.SnS_Usage_Page.php ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * SnS_Settings_Page
4
+ *
5
+ * Allows WordPress admin users the ability to add custom CSS
6
+ * and JavaScript directly to individual Post, Pages or custom
7
+ * post types.
8
+ */
9
+
10
+ class SnS_Usage_Page
11
+ {
12
+ /**
13
+ * Constants
14
+ */
15
+ const MENU_SLUG = 'sns_usage';
16
+
17
+ /**
18
+ * Initializing method.
19
+ * @static
20
+ */
21
+ function init() {
22
+ $hook_suffix = add_submenu_page( SnS_Admin::$parent_slug, 'Scripts n Styles', 'Usage', 'unfiltered_html', self::MENU_SLUG, array( 'SnS_Form', 'page' ) );
23
+
24
+ add_action( "load-$hook_suffix", array( __CLASS__, 'admin_load' ) );
25
+ add_action( "load-$hook_suffix", array( 'SnS_Admin', 'help' ) );
26
+
27
+ // Make the page into a tab.
28
+ if ( SnS_Admin::MENU_SLUG != SnS_Admin::$parent_slug ) {
29
+ remove_submenu_page( SnS_Admin::$parent_slug, self::MENU_SLUG );
30
+ add_filter( 'parent_file', array( __CLASS__, 'parent_file') );
31
+ }
32
+ }
33
+
34
+ static function parent_file( $parent_file ) {
35
+ global $plugin_page, $submenu_file;
36
+ if ( self::MENU_SLUG == $plugin_page ) $submenu_file = SnS_Admin::MENU_SLUG;
37
+ return $parent_file;
38
+ }
39
+
40
+ /**
41
+ * Settings Page
42
+ * Adds Admin Menu Item via WordPress' "Administration Menus" API. Also hook actions to register options via WordPress' Settings API.
43
+ */
44
+ function admin_load() {
45
+ wp_enqueue_style( 'sns-options-styles', plugins_url('css/options-styles.css', Scripts_n_Styles::$file), array(), SnS_Admin::VERSION );
46
+
47
+ add_screen_option( 'per_page', array( 'label' => __( 'Per Page' ), 'default' => 20 ) );
48
+ add_filter( 'set-screen-option', array( __CLASS__, 'set_screen_option' ), 10, 3 );
49
+ // hack for core limitation: see http://core.trac.wordpress.org/ticket/18954
50
+ set_screen_options();
51
+
52
+ add_settings_section(
53
+ 'usage',
54
+ 'Scripts n Styles Usage',
55
+ array( __CLASS__, 'usage_section' ),
56
+ SnS_Admin::MENU_SLUG );
57
+ }
58
+
59
+ function set_screen_option( $false, $option, $value ) {
60
+ $screen_id = get_current_screen()->id;
61
+ $this_option = str_replace( '-', '_', "{$screen_id}_per_page" );
62
+ if ( $this_option != $option )
63
+ return false;
64
+
65
+ $value = (int) $value;
66
+ if ( $value < 1 || $value > 999 )
67
+ return false;
68
+
69
+ return $value;
70
+ }
71
+
72
+ /**
73
+ * Settings Page
74
+ * Outputs the Usage Section.
75
+ */
76
+ function usage_section() { ?>
77
+ <div style="max-width: 55em;">
78
+ <p>The following table shows content that utilizes Scripts n Styles.</p>
79
+ </div>
80
+ <?php
81
+ require_once( 'class.SnS_List_Usage.php' );
82
+ $usageTable = new SnS_List_Usage();
83
+ $usageTable->prepare_items();
84
+ $usageTable->display();
85
+ }
86
+ }
87
+ ?>
js/{options-scripts.js → global-page.js} RENAMED
@@ -1,10 +1,11 @@
1
  // Options JavaScript
2
 
3
  jQuery( document ).ready( function( $ ) {
 
4
  $( "textarea.js" ).each( function() {
5
- CodeMirror.fromTextArea( this, { lineNumbers: true, mode: "javascript" } );
6
  });
7
  $( "textarea.css" ).each( function() {
8
- CodeMirror.fromTextArea( this, { lineNumbers: true, mode: "css" } );
9
  });
10
  });
1
  // Options JavaScript
2
 
3
  jQuery( document ).ready( function( $ ) {
4
+ var theme = codemirror_options.theme ? codemirror_options.theme: 'default';
5
  $( "textarea.js" ).each( function() {
6
+ CodeMirror.fromTextArea( this, { lineNumbers: true, mode: "javascript", theme: theme } );
7
  });
8
  $( "textarea.css" ).each( function() {
9
+ CodeMirror.fromTextArea( this, { lineNumbers: true, mode: "css", theme: theme } );
10
  });
11
  });
js/meta-box-scripts.js CHANGED
@@ -1,12 +1,428 @@
1
- // Meta Box JavaScript
2
-
3
  jQuery( document ).ready( function( $ ) {
4
- $( "#uFp_meta_box" ).tabs().find( ".wp-tab-bar" ).show();
5
 
6
- $( "textarea.js" ).each( function() {
7
- CodeMirror.fromTextArea( this, { lineNumbers: true, mode: "javascript" } );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  });
9
- $( "textarea.css" ).each( function() {
10
- CodeMirror.fromTextArea( this, { lineNumbers: true, mode: "css" } );
 
 
 
 
 
 
 
 
 
 
 
11
  });
12
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  jQuery( document ).ready( function( $ ) {
 
2
 
3
+ // For compat: 3.3 || 3.2
4
+ var initData = tinyMCEPreInit.mceInit["content"] || tinyMCEPreInit.mceInit,
5
+ context = '#SnS_meta_box',
6
+ currentCodeMirror = [],
7
+ mceBodyClass = getMCEBodyClasses(),
8
+ nonce = $( '#scripts_n_styles_noncename' ).val(),
9
+ theme = codemirror_options.theme ? codemirror_options.theme: 'default';
10
+
11
+ //$('textarea', context).attr('autocomplete','off');
12
+
13
+ // Refresh when panel becomes unhidden
14
+ $( context + '-hide, '
15
+ + context + ' .hndle, '
16
+ + context + ' .handlediv ' )
17
+ .live( 'click', refreshCodeMirrors );
18
+
19
+ // add tab-switch handler
20
+ $( '.wp-tab-bar a', context ).live( 'click', onTabSwitch );
21
+
22
+ // activate first run
23
+ $( '.wp-tab-active a', context ).trigger( 'click' );
24
+
25
+ // must run before ajax click handlers are added.
26
+ setupAjaxUI();
27
+
28
+ refreshDeleteBtns();
29
+
30
+
31
+ $('#sns-ajax-update-scripts').click(function( event ){
32
+ event.preventDefault();
33
+ $(this).next().show();
34
+ $(currentCodeMirror).each(function (){ this.save(); });
35
+ var args = { _ajax_nonce: nonce, post_id: $( '#post_ID' ).val(), };
36
+
37
+ args.action = 'sns_scripts';
38
+ args.scripts = $( '#SnS_scripts' ).val();
39
+ args.scripts_in_head = $( '#SnS_scripts_in_head' ).val();
40
+
41
+ $.post( ajaxurl, args, function() { refreshMCE(); } );
42
+ });
43
+
44
+ $('#sns-ajax-update-styles').click(function( event ){
45
+ event.preventDefault();
46
+ $(this).next().show();
47
+ $(currentCodeMirror).each(function (){ this.save(); });
48
+ var args = { _ajax_nonce: nonce, post_id: $( '#post_ID' ).val(), };
49
+
50
+ args.action = 'sns_styles';
51
+ args.styles = $( '#SnS_styles' ).val();
52
+
53
+ $.post( ajaxurl, args, function() { refreshMCE(); } );
54
+ });
55
+
56
+ /*
57
+ * Expects return data.
58
+ */
59
+ $('#sns-ajax-update-classes').click(function( event ){
60
+ event.preventDefault();
61
+ $(this).next().show();
62
+ var args = { _ajax_nonce: nonce, post_id: $( '#post_ID' ).val(), };
63
+
64
+ args.action = 'sns_classes';
65
+ args.classes_body = $( '#SnS_classes_body' ).val();
66
+ args.classes_post = $( '#SnS_classes_post' ).val();
67
+
68
+ $.post( ajaxurl, args, function( data ) { refreshBodyClass( data ); } );
69
+ });
70
+
71
+ /*
72
+ * Expects return data.
73
+ */
74
+ $('#sns-ajax-update-dropdown').click(function( event ){
75
+ event.preventDefault();
76
+ $(this).next().show();
77
+ var args = { _ajax_nonce: nonce, post_id: $( '#post_ID' ).val(), };
78
+
79
+ args.action = 'sns_dropdown';
80
+ var format = {};
81
+ format.title = $( '#SnS_classes_mce_title' ).val();
82
+ format.classes = $( '#SnS_classes_mce_classes' ).val();
83
+ switch ( $( '#SnS_classes_mce_type' ).val() ) {
84
+ case 'inline':
85
+ format.inline = $( '#SnS_classes_mce_element' ).val();
86
+ break;
87
+ case 'block':
88
+ format.block = $( '#SnS_classes_mce_element' ).val();
89
+ if ( $( '#SnS_classes_mce_wrapper' ).prop('checked') )
90
+ format.wrapper = true;
91
+ break;
92
+ case 'selector':
93
+ format.selector = $( '#SnS_classes_mce_element' ).val();
94
+ break;
95
+ default:
96
+ return;
97
+ }
98
+ args.format = format;
99
+
100
+ $.post( ajaxurl, args, function( data ) { refreshStyleFormats( data ); } );
101
  });
102
+
103
+ /*
104
+ * Expects return data.
105
+ */
106
+ $('#delete-mce-dropdown-names .sns-ajax-delete').live( "click", function( event ){
107
+ event.preventDefault();
108
+ $(this).next().show();
109
+ var args = { _ajax_nonce: nonce, post_id: $( '#post_ID' ).val(), };
110
+
111
+ args.action = 'sns_delete_class';
112
+ args.delete = $( this ).attr( 'id' );
113
+
114
+ $.post( ajaxurl, args, function( data ) { refreshStyleFormats( data ); } );
115
  });
116
+
117
+ /*
118
+ * Returns the body_class of TinyMCE minus the Scripts n Styles values.
119
+ */
120
+ function getMCEBodyClasses() {
121
+ var t = [];
122
+ if ( initData.body_class )
123
+ t = initData.body_class.split(' ');
124
+
125
+ var bc = $('#SnS_classes_body').val().split(' ');
126
+ var pc = $('#SnS_classes_post').val().split(' ');
127
+ var p;
128
+ for ( var i = 0; i < t.length; i++ ) {
129
+ p = $.inArray( bc[i], t )
130
+ if ( -1 != p )
131
+ t.splice( p, 1 );
132
+ }
133
+ for ( var i = 0; i < t.length; i++ ) {
134
+ p = $.inArray( pc[i], t )
135
+ if ( -1 != p )
136
+ t.splice( p, 1 );
137
+ }
138
+ t = t.join(' ');
139
+ return t;
140
+ }
141
+
142
+ /*
143
+ * Builds and Adds the DOM for AJAX functionality.
144
+ */
145
+ function setupAjaxUI() {
146
+ // set up ajax ui. (need to come up with a better ID naming scheme.)
147
+ $('#SnS_scripts-tab').append(
148
+ '<div class="sns-ajax-wrap">'
149
+ + '<a id="sns-ajax-update-scripts" href="#" class="button">Update Scripts</a>'
150
+ + ' '
151
+ + '<img class="sns-ajax-loading" src="/wp-admin/images/wpspin_light.gif">'
152
+ + '</div>'
153
+ );
154
+
155
+ $('#SnS_styles-tab').append(
156
+ '<div class="sns-ajax-wrap">'
157
+ + '<a id="sns-ajax-update-styles" href="#" class="button">Update Styles</a>'
158
+ + ' '
159
+ + '<img class="sns-ajax-loading" src="/wp-admin/images/wpspin_light.gif">'
160
+ + '</div>'
161
+ );
162
+
163
+ $('#sns-classes').append(
164
+ '<div class="sns-ajax-wrap">'
165
+ + '<a id="sns-ajax-update-classes" href="#" class="button">Update Classes</a>'
166
+ + ' '
167
+ + '<img class="sns-ajax-loading" src="/wp-admin/images/wpspin_light.gif">'
168
+ + '</div>'
169
+ );
170
+
171
+ $('#add-mce-dropdown-names').append(
172
+ '<div class="sns-ajax-wrap">'
173
+ + '<a id="sns-ajax-update-dropdown" href="#" class="button">Add Class</a>'
174
+ + ' '
175
+ + '<img class="sns-ajax-loading" src="/wp-admin/images/wpspin_light.gif">'
176
+ + '</div>'
177
+ );
178
+
179
+ $('.sns-ajax-loading').hide();
180
+
181
+ if ( $( '#SnS_classes_mce_type').val() == 'block' ) {
182
+ $('#add-mce-dropdown-names .sns-mce-wrapper').show();
183
+ } else {
184
+ $('#add-mce-dropdown-names .sns-mce-wrapper').hide();
185
+ }
186
+
187
+ $( '#SnS_classes_mce_type' ).change(function() {
188
+ if ( $(this).val() == 'block' ) {
189
+ $('#add-mce-dropdown-names .sns-mce-wrapper').show();
190
+ } else {
191
+ $('#add-mce-dropdown-names .sns-mce-wrapper').hide();
192
+ }
193
+ });
194
+
195
+ $( '#mce-dropdown-names', context ).show();
196
+ }
197
+
198
+ /*
199
+ * Main Tab Switch Handler.
200
+ */
201
+ function onTabSwitch( event ) {
202
+ event.preventDefault();
203
+
204
+ clearCodeMirrors();
205
+
206
+ /*
207
+ * There is a weird bug where if clearCodeMirrors() is called right before
208
+ * loadCodeMirrors(), loading the page with the Styles tab active, and
209
+ * then switching to the Script tab, you can lose data from the second
210
+ * CodeMirror if leaving and returning to that tab. I've no idea what's
211
+ * going on there. Leaving code inbetween them is a fraggle, but working,
212
+ * workaround. Maybe has to do with execution time? No idea.
213
+ */
214
+
215
+ // switch active classes
216
+ $( '.wp-tab-active', context ).removeClass( 'wp-tab-active' );
217
+ $( this ).parent( 'li' ).addClass( 'wp-tab-active' );
218
+
219
+ $( '.wp-tabs-panel-active', context ).hide().removeClass( 'wp-tabs-panel-active' );
220
+ $( $( this ).attr( 'href' ) ).show().addClass( 'wp-tabs-panel-active' );
221
+
222
+ loadCodeMirrors();
223
+
224
+ $.post( ajaxurl, {
225
+ action: 'sns_update_tab',
226
+ _ajax_nonce: nonce,
227
+ active_tab: $( '.wp-tab-bar li', context ).index( $( this ).parent( 'li' ).get(0) )
228
+ }
229
+ );
230
+ }
231
+
232
+ /*
233
+ * CodeMirror Utilities.
234
+ */
235
+ function clearCodeMirrors() {
236
+ $(currentCodeMirror).each(function (){
237
+ this.toTextArea();
238
+ });
239
+ currentCodeMirror = [];
240
+ }
241
+ function refreshCodeMirrors() {
242
+ $(currentCodeMirror).each( function(){
243
+ this.refresh();
244
+ });
245
+ }
246
+ function loadCodeMirrors() {
247
+ // collect codemirrors
248
+ var settings;
249
+ // loop codemirrors
250
+ $( '.wp-tabs-panel-active textarea.codemirror', context ).each(function (){
251
+ if ( $(this).hasClass( 'js' ) )
252
+ settings = {
253
+ mode: "text/javascript",
254
+ theme: theme,
255
+ lineNumbers: true,
256
+ tabMode: "shift",
257
+ indentUnit: 4,
258
+ indentWithTabs: true
259
+ };
260
+ else if ( $(this).hasClass( 'css' ) )
261
+ settings = {
262
+ mode: "text/css",
263
+ theme: theme,
264
+ lineNumbers: true,
265
+ tabMode: "shift",
266
+ indentUnit: 4,
267
+ indentWithTabs: true
268
+ };
269
+ /*else if ( $(this).hasClass( 'htmlmixed' ) )
270
+ settings = {
271
+ mode: "text/html",
272
+ lineNumbers: true,
273
+ tabMode: "shift",
274
+ indentUnit: 8,
275
+ indentWithTabs: true,
276
+ enterMode: "keep",
277
+ matchBrackets: true
278
+ };
279
+ else if ( $(this).hasClass( 'php' ) )
280
+ settings = {
281
+ mode: "application/x-httpd-php",
282
+ lineNumbers: true,
283
+ tabMode: "shift",
284
+ indentUnit: 8,
285
+ indentWithTabs: true,
286
+ enterMode: "keep",
287
+ matchBrackets: true
288
+ };*/
289
+ else
290
+ return;
291
+
292
+ // initialize and store active codemirrors
293
+ currentCodeMirror.push( CodeMirror.fromTextArea( this, settings ) );
294
+ });
295
+ }
296
+
297
+ /*
298
+ * Refresh after AJAX.
299
+ */
300
+ function refreshDeleteBtns() {
301
+ // responsible for clearing out Delete Buttons, and Adding new ones.
302
+ // initData should always contain the latest settings.
303
+ if ( initData.style_formats && initData.style_formats.length ) {
304
+ $( '#delete-mce-dropdown-names .sns-ajax-delete-p' ).remove();
305
+ $( '#delete-mce-dropdown-names', context ).show();
306
+ var formats = initData.style_formats;
307
+ for ( var i = 0; i < formats.length; i++ ) {
308
+ var deleteBtn = {};
309
+ if ( formats[i].inline ) {
310
+ deleteBtn.element = formats[i].inline;
311
+ deleteBtn.wrapper = '';
312
+ } else if ( formats[i].block ) {
313
+ deleteBtn.element = formats[i].block;
314
+ if ( formats[i].wrapper )
315
+ deleteBtn.wrapper = ' (wrapper)';
316
+ else
317
+ deleteBtn.wrapper = '';
318
+ } else if ( formats[i].selector ) {
319
+ deleteBtn.element = formats[i].selector;
320
+ deleteBtn.wrapper = '';
321
+ } else {
322
+ alert( 'ERROR!' );
323
+ }
324
+ deleteBtn.title = formats[i].title;
325
+ deleteBtn.classes = formats[i].classes;
326
+ $( '#instructions-mce-dropdown-names', context ).after(
327
+ '<p class="sns-ajax-delete-p"><a title="delete" class="sns-ajax-delete" id="'
328
+ + deleteBtn.title + '">X</a> "'
329
+ + deleteBtn.title + '" <code>&lt;'
330
+ + deleteBtn.element + ' class="'
331
+ + deleteBtn.classes + '"&gt;</code>'
332
+ + deleteBtn.wrapper + '</p>'
333
+ );
334
+ }
335
+ } else {
336
+ $( '#delete-mce-dropdown-names', context ).hide();
337
+ }
338
+ }
339
+ function refreshBodyClass( data ) {
340
+ initData.body_class = mceBodyClass + ' ' + data.classes_body + ' ' + data.classes_post;
341
+
342
+ // needed for < 3.3
343
+ if ( tinymce.settings ) tinymce.settings.body_class = initData.body_class;
344
+ refreshMCE();
345
+ }
346
+ function refreshStyleFormats( data ) {
347
+ // error check
348
+ console.log(data.classes_mce);
349
+ if ( typeof data.classes_mce === 'undefined' ) {
350
+ alert( data );
351
+ $('.sns-ajax-loading').hide();
352
+ return;
353
+ } else if ( data.classes_mce.length && data.classes_mce != 'Empty' ) {
354
+ var style_formats = [];
355
+
356
+ for ( var i = 0; i < data.classes_mce.length; i++ ) { // loop returned classes_mce
357
+ var format = {};
358
+ format.title = data.classes_mce[i].title;
359
+
360
+ if ( data.classes_mce[i].inline )
361
+ format.inline = data.classes_mce[i].inline;
362
+ else if ( data.classes_mce[i].block ) {
363
+ format.block = data.classes_mce[i].block;
364
+ if (data.classes_mce[i].wrapper)
365
+ format.wrapper = true;
366
+ } else if ( data.classes_mce[i].selector )
367
+ format.selector = data.classes_mce[i].selector;
368
+ else
369
+ alert('dropdown format has bad type.');
370
+
371
+ format.classes = data.classes_mce[i].classes;
372
+ style_formats.push( format );
373
+ }
374
+ initData.style_formats = style_formats;
375
+
376
+ // needed for < 3.3
377
+ if ( tinymce.settings ) tinymce.settings.style_formats = initData.style_formats;
378
+ if ( initData.theme_advanced_buttons2.indexOf( "styleselect" ) == -1 ) {
379
+ var tempString = "styleselect,";
380
+ initData.theme_advanced_buttons2 = tempString.concat(initData.theme_advanced_buttons2);
381
+ }
382
+
383
+ // needed for < 3.3
384
+ if ( tinymce.settings ) tinymce.settings.theme_advanced_buttons2 = initData.theme_advanced_buttons2;
385
+ $( '#delete-mce-dropdown-names', context ).show();
386
+ } else {
387
+ delete initData.style_formats;
388
+ initData.theme_advanced_buttons2 = initData.theme_advanced_buttons2.replace("styleselect,", "");
389
+
390
+ // needed for < 3.3
391
+ if ( tinymce.settings ) tinymce.settings.theme_advanced_buttons2 = initData.theme_advanced_buttons2;
392
+ $( '#delete-mce-dropdown-names', context ).hide();
393
+ }
394
+
395
+ refreshDeleteBtns();
396
+ refreshMCE();
397
+ }
398
+ function refreshMCE() {
399
+ if ( tinyMCE.editors["content"] ) {
400
+ // needed for < 3.3 editor initialization.
401
+ if ( ! $( '#content' ).hasClass( '.theEditor' ) ) $( '#content' ).addClass( 'theEditor' );
402
+
403
+ if ( tinyMCE.editors["content"].isHidden() ) {
404
+ tinyMCE.editors["content"].remove();
405
+ tinyMCE.init( initData );
406
+ tinyMCE.editors["content"].hide();
407
+ } else {
408
+ // you've got to be kidding me.
409
+ if ( 1 == $('#content-html').length )
410
+ $('#content-html').click(); // 3.3
411
+ else if( 1 == $('#edButtonHTML').length )
412
+ switchEditors.go('content', 'html'); // 3.2
413
+
414
+ tinyMCE.editors["content"].remove();
415
+ tinyMCE.init( initData );
416
+ tinyMCE.editors["content"].hide();
417
+
418
+ if ( 1 == $('#content-tmce').length )
419
+ $('#content-tmce').click(); // 3.3
420
+ else if( 1 == $('#edButtonPreview').length )
421
+ switchEditors.go('content', 'tinymce'); // 3.2
422
+ }
423
+
424
+ }
425
+ $('.sns-ajax-loading').hide();
426
+ }
427
+
428
+ });
js/settings-page.js ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Options JavaScript
2
+
3
+ jQuery( document ).ready( function( $ ) {
4
+ var theme = codemirror_options.theme ? codemirror_options.theme: 'default';
5
+ var editor = CodeMirror.fromTextArea(document.getElementById("codemirror_demo"), {
6
+ lineNumbers: true,
7
+ matchBrackets: true,
8
+ mode: "application/x-httpd-php",
9
+ indentUnit: 4,
10
+ indentWithTabs: true,
11
+ enterMode: "keep",
12
+ tabMode: "shift",
13
+ theme: theme
14
+ });
15
+ $('#cm_theme').change( function(){
16
+ editor.setOption("theme", $(this).val());
17
+ });
18
+ });
libraries/{codemirror → CodeMirror2}/LICENSE RENAMED
File without changes
libraries/{codemirror → CodeMirror2}/lib/codemirror.css RENAMED
@@ -6,10 +6,14 @@
6
  .CodeMirror-scroll {
7
  overflow: auto;
8
  height: 300px;
 
 
 
9
  }
10
 
11
  .CodeMirror-gutter {
12
  position: absolute; left: 0; top: 0;
 
13
  background-color: #f7f7f7;
14
  border-right: 1px solid #eee;
15
  min-width: 2em;
@@ -19,6 +23,7 @@
19
  color: #aaa;
20
  text-align: right;
21
  padding: .4em .2em .4em .4em;
 
22
  }
23
  .CodeMirror-lines {
24
  padding: .4em;
@@ -37,6 +42,14 @@
37
  word-wrap: normal;
38
  }
39
 
 
 
 
 
 
 
 
 
40
  .CodeMirror textarea {
41
  font-family: inherit !important;
42
  font-size: inherit !important;
6
  .CodeMirror-scroll {
7
  overflow: auto;
8
  height: 300px;
9
+ /* This is needed to prevent an IE[67] bug where the scrolled content
10
+ is visible outside of the scrolling box. */
11
+ position: relative;
12
  }
13
 
14
  .CodeMirror-gutter {
15
  position: absolute; left: 0; top: 0;
16
+ z-index: 10;
17
  background-color: #f7f7f7;
18
  border-right: 1px solid #eee;
19
  min-width: 2em;
23
  color: #aaa;
24
  text-align: right;
25
  padding: .4em .2em .4em .4em;
26
+ white-space: pre !important;
27
  }
28
  .CodeMirror-lines {
29
  padding: .4em;
42
  word-wrap: normal;
43
  }
44
 
45
+ .CodeMirror-wrap pre {
46
+ word-wrap: break-word;
47
+ white-space: pre-wrap;
48
+ }
49
+ .CodeMirror-wrap .CodeMirror-scroll {
50
+ overflow-x: hidden;
51
+ }
52
+
53
  .CodeMirror textarea {
54
  font-family: inherit !important;
55
  font-size: inherit !important;
libraries/{codemirror → CodeMirror2}/lib/codemirror.js RENAMED
@@ -1,3 +1,5 @@
 
 
1
  // All functions that need access to the editor's state live inside
2
  // the CodeMirror function. Below that, at the bottom of the file,
3
  // some utilities are defined.
@@ -16,18 +18,19 @@ var CodeMirror = (function() {
16
  var targetDocument = options["document"];
17
  // The element in which the editor lives.
18
  var wrapper = targetDocument.createElement("div");
19
- wrapper.className = "CodeMirror";
20
  // This mess creates the base DOM structure for the editor.
21
  wrapper.innerHTML =
22
  '<div style="overflow: hidden; position: relative; width: 1px; height: 0px;">' + // Wraps and hides input textarea
23
- '<textarea style="position: absolute; width: 2px;" wrap="off"></textarea></div>' +
 
24
  '<div class="CodeMirror-scroll cm-s-' + options.theme + '">' +
25
  '<div style="position: relative">' + // Set to the height of the text, causes scrolling
26
- '<div style="position: absolute; height: 0; width: 0; overflow: hidden;"></div>' +
27
  '<div style="position: relative">' + // Moved around its parent to cover visible view
28
  '<div class="CodeMirror-gutter"><div class="CodeMirror-gutter-text"></div></div>' +
29
  // Provides positioning relative to (visible) text origin
30
  '<div class="CodeMirror-lines"><div style="position: relative">' +
 
31
  '<pre class="CodeMirror-cursor">&#160;</pre>' + // Absolutely positioned blinky cursor
32
  '<div></div>' + // This DIV contains the actual code
33
  '</div></div></div></div></div>';
@@ -35,21 +38,29 @@ var CodeMirror = (function() {
35
  // I've never seen more elegant code in my life.
36
  var inputDiv = wrapper.firstChild, input = inputDiv.firstChild,
37
  scroller = wrapper.lastChild, code = scroller.firstChild,
38
- measure = code.firstChild, mover = measure.nextSibling,
39
- gutter = mover.firstChild, gutterText = gutter.firstChild,
40
- lineSpace = gutter.nextSibling.firstChild,
41
- cursor = lineSpace.firstChild, lineDiv = cursor.nextSibling;
42
  if (options.tabindex != null) input.tabindex = options.tabindex;
43
  if (!options.gutter && !options.lineNumbers) gutter.style.display = "none";
44
 
 
 
 
 
 
 
 
 
 
45
  // Delayed object wrap timeouts, making sure only one is active. blinker holds an interval.
46
  var poll = new Delayed(), highlight = new Delayed(), blinker;
47
 
48
- // mode holds a mode API object. lines an array of Line objects
49
- // (see Line constructor), work an array of lines that should be
50
- // parsed, and history the undo history (instance of History
51
- // constructor).
52
- var mode, lines = [new Line("")], work, history = new History(), focused;
53
  loadMode();
54
  // The selection. These are always maintained to point at valid
55
  // positions. Inverted is used to remember that the user is
@@ -59,12 +70,12 @@ var CodeMirror = (function() {
59
  // whether the user is holding shift. reducedSelection is a hack
60
  // to get around the fact that we can't create inverted
61
  // selections. See below.
62
- var shiftSelecting, reducedSelection, lastDoubleClick;
63
  // Variables used by startOperation/endOperation to track what
64
  // happened during the operation.
65
- var updateInput, changes, textChanged, selectionChanged, leaveInputAlone;
66
  // Current visible range (may be bigger than the view window).
67
- var showingFrom = 0, showingTo = 0, lastHeight = 0, curKeyId = null;
68
  // editing will hold an object describing the things we put in the
69
  // textarea, to help figure out whether something changed.
70
  // bracketHighlighted is used to remember that a backet has been
@@ -72,69 +83,94 @@ var CodeMirror = (function() {
72
  var editing, bracketHighlighted;
73
  // Tracks the maximum line length so that the horizontal scrollbar
74
  // can be kept static when scrolling.
75
- var maxLine = "";
76
 
77
- // Initialize the content. Somewhat hacky (delayed prepareInput)
78
- // to work around browser issues.
79
  operation(function(){setValue(options.value || ""); updateInput = false;})();
80
- setTimeout(prepareInput, 20);
 
 
 
 
 
 
 
 
81
 
82
  // Register our event handlers.
83
  connect(scroller, "mousedown", operation(onMouseDown));
 
 
 
84
  // Gecko browsers fire contextmenu *after* opening the menu, at
85
  // which point we can't mess with it anymore. Context menu is
86
  // handled in onMouseDown for Gecko.
87
- if (!gecko) connect(scroller, "contextmenu", operation(onContextMenu));
88
- connect(code, "dblclick", operation(onDblClick));
89
- connect(scroller, "scroll", function() {updateDisplay([]); if (options.onScroll) options.onScroll(instance);});
 
 
 
90
  connect(window, "resize", function() {updateDisplay(true);});
91
  connect(input, "keyup", operation(onKeyUp));
 
92
  connect(input, "keydown", operation(onKeyDown));
93
  connect(input, "keypress", operation(onKeyPress));
94
  connect(input, "focus", onFocus);
95
  connect(input, "blur", onBlur);
96
 
97
- connect(scroller, "dragenter", function(e){e.stop();});
98
- connect(scroller, "dragover", function(e){e.stop();});
99
  connect(scroller, "drop", operation(onDrop));
100
  connect(scroller, "paste", function(){focusInput(); fastPoll();});
101
  connect(input, "paste", function(){fastPoll();});
102
  connect(input, "cut", function(){fastPoll();});
103
-
104
- // IE throws unspecified error in certain cases, when
105
  // trying to access activeElement before onload
106
  var hasFocus; try { hasFocus = (targetDocument.activeElement == input); } catch(e) { }
107
- if (hasFocus) onFocus();
108
  else onBlur();
109
 
110
- function isLine(l) {return l >= 0 && l < lines.length;}
111
  // The instance object that we'll return. Mostly calls out to
112
  // local functions in the CodeMirror function. Some do some extra
113
  // range checking and/or clipping. operation is used to wrap the
114
  // call so that changes it makes are tracked, and the display is
115
  // updated afterwards.
116
- var instance = {
117
  getValue: getValue,
118
  setValue: operation(setValue),
119
  getSelection: getSelection,
120
  replaceSelection: operation(replaceSelection),
121
- focus: function(){focusInput(); onFocus(); prepareInput(); fastPoll();},
122
  setOption: function(option, value) {
 
123
  options[option] = value;
124
- if (option == "lineNumbers" || option == "gutter") gutterChanged();
125
- else if (option == "mode" || option == "indentUnit") loadMode();
126
  else if (option == "readOnly" && value == "nocursor") input.blur();
127
  else if (option == "theme") scroller.className = scroller.className.replace(/cm-s-\w+/, "cm-s-" + value);
 
 
 
 
128
  },
129
  getOption: function(option) {return options[option];},
130
  undo: operation(undo),
131
  redo: operation(redo),
132
- indentLine: operation(function(n) {if (isLine(n)) indentLine(n, "smart");}),
 
 
133
  historySize: function() {return {undo: history.done.length, redo: history.undone.length};},
 
134
  matchBrackets: operation(function(){matchBrackets(true);}),
135
- getTokenAt: function(pos) {
136
  pos = clipPos(pos);
137
- return lines[pos.line].getTokenAt(mode, getStateBefore(pos.line), pos.ch);
 
 
 
 
138
  },
139
  cursorCoords: function(start){
140
  if (start == null) start = sel.inverted;
@@ -143,25 +179,46 @@ var CodeMirror = (function() {
143
  charCoords: function(pos){return pageCoords(clipPos(pos));},
144
  coordsChar: function(coords) {
145
  var off = eltOffset(lineSpace);
146
- var line = clipLine(Math.min(lines.length - 1, showingFrom + Math.floor((coords.y - off.top) / lineHeight())));
147
- return clipPos({line: line, ch: charFromX(clipLine(line), coords.x - off.left)});
148
  },
149
  getSearchCursor: function(query, pos, caseFold) {return new SearchCursor(query, pos, caseFold);},
150
- markText: operation(function(a, b, c){return operation(markText(a, b, c));}),
151
- setMarker: addGutterMarker,
152
- clearMarker: removeGutterMarker,
 
153
  setLineClass: operation(setLineClass),
 
 
154
  lineInfo: lineInfo,
155
- addWidget: function(pos, node, scroll) {
156
- var pos = localCoords(clipPos(pos), true);
157
- node.style.top = (showingFrom * lineHeight() + pos.yBot + paddingTop()) + "px";
158
- node.style.left = (pos.x + paddingLeft()) + "px";
159
  code.appendChild(node);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
  if (scroll)
161
- scrollIntoView(pos.x, pos.yBot, pos.x + node.offsetWidth, pos.yBot + node.offsetHeight);
162
  },
163
 
164
- lineCount: function() {return lines.length;},
165
  getCursor: function(start) {
166
  if (start == null) start = sel.inverted;
167
  return copyPos(start ? sel.from : sel.to);
@@ -172,9 +229,9 @@ var CodeMirror = (function() {
172
  else setCursor(line, ch);
173
  }),
174
  setSelection: operation(function(from, to) {setSelection(clipPos(from), clipPos(to || from));}),
175
- getLine: function(line) {if (isLine(line)) return lines[line].text;},
176
  setLine: operation(function(line, text) {
177
- if (isLine(line)) replaceRange(text, {line: line, ch: 0}, {line: line, ch: lines[line].text.length});
178
  }),
179
  removeLine: operation(function(line) {
180
  if (isLine(line)) replaceRange("", {line: line, ch: 0}, clipPos({line: line+1, ch: 0}));
@@ -182,56 +239,106 @@ var CodeMirror = (function() {
182
  replaceRange: operation(replaceRange),
183
  getRange: function(from, to) {return getRange(clipPos(from), clipPos(to));},
184
 
 
 
 
 
 
 
 
 
 
 
 
185
  operation: function(f){return operation(f)();},
186
  refresh: function(){updateDisplay(true);},
187
  getInputField: function(){return input;},
188
- getWrapperElement: function(){return wrapper;}
 
 
189
  };
190
 
 
 
 
 
 
 
 
191
  function setValue(code) {
192
- history = null;
193
  var top = {line: 0, ch: 0};
194
- updateLines(top, {line: lines.length - 1, ch: lines[lines.length-1].text.length},
195
  splitLines(code), top, top);
196
- history = new History();
197
  }
198
  function getValue(code) {
199
  var text = [];
200
- for (var i = 0, l = lines.length; i < l; ++i)
201
- text.push(lines[i].text);
202
  return text.join("\n");
203
  }
204
 
205
  function onMouseDown(e) {
206
- var ld = lastDoubleClick; lastDoubleClick = null;
207
- // First, see if this is a click in the gutter
208
- for (var n = e.target(); n != wrapper; n = n.parentNode)
 
 
 
209
  if (n.parentNode == gutterText) {
210
  if (options.onGutterClick)
211
- options.onGutterClick(instance, indexOf(gutterText.childNodes, n) + showingFrom);
212
- return e.stop();
213
  }
214
 
215
- if (gecko && e.button() == 3) onContextMenu(e);
216
- if (e.button() != 1) return;
 
 
 
 
 
 
 
 
217
  // For button 1, if it was clicked inside the editor
218
  // (posFromMouse returning non-null), we have to adjust the
219
  // selection.
220
- var start = posFromMouse(e), last = start, going;
221
- if (!start) {if (e.target() == scroller) e.stop(); return;}
222
 
223
  if (!focused) onFocus();
224
- e.stop();
225
- if (ld && +new Date - ld < 400) return selectLine(start.line);
226
 
227
- setCursor(start.line, start.ch, true);
228
- // And then we have to see if it's a drag event, in which case
229
- // the dragged-over text must be selected.
230
- function end() {
231
- focusInput();
232
- updateInput = true;
233
- move(); up();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
  }
 
 
 
235
  function extend(e) {
236
  var cur = posFromMouse(e, true);
237
  if (cur && !posEq(cur, last)) {
@@ -247,72 +354,97 @@ var CodeMirror = (function() {
247
 
248
  var move = connect(targetDocument, "mousemove", operation(function(e) {
249
  clearTimeout(going);
250
- e.stop();
251
  extend(e);
252
  }), true);
253
  var up = connect(targetDocument, "mouseup", operation(function(e) {
254
  clearTimeout(going);
255
  var cur = posFromMouse(e);
256
  if (cur) setSelectionUser(start, cur);
257
- e.stop();
258
- end();
 
 
259
  }), true);
260
  }
261
- function onDblClick(e) {
262
- var pos = posFromMouse(e);
263
- if (!pos) return;
264
- selectWordAt(pos);
265
- e.stop();
266
- lastDoubleClick = +new Date;
 
 
267
  }
268
  function onDrop(e) {
269
- var pos = posFromMouse(e, true), files = e.e.dataTransfer.files;
 
270
  if (!pos || options.readOnly) return;
271
  if (files && files.length && window.FileReader && window.File) {
272
- var n = files.length, text = Array(n), read = 0;
273
- for (var i = 0; i < n; ++i) loadFile(files[i], i);
274
  function loadFile(file, i) {
275
  var reader = new FileReader;
276
  reader.onload = function() {
277
  text[i] = reader.result;
278
- if (++read == n) replaceRange(text.join(""), clipPos(pos), clipPos(pos));
 
 
 
 
 
 
279
  };
280
  reader.readAsText(file);
281
  }
 
 
282
  }
283
  else {
284
  try {
285
- var text = e.e.dataTransfer.getData("Text");
286
- if (text) replaceRange(text, pos, pos);
 
 
 
 
 
 
287
  }
288
  catch(e){}
289
  }
290
  }
 
 
 
 
 
 
 
291
  function onKeyDown(e) {
292
  if (!focused) onFocus();
293
 
294
- var code = e.e.keyCode;
295
  // IE does strange things with escape.
296
- if (ie && code == 27) { e.e.returnValue = false; }
297
  // Tries to detect ctrl on non-mac, cmd on mac.
298
- var mod = (mac ? e.e.metaKey : e.e.ctrlKey) && !e.e.altKey, anyMod = e.e.ctrlKey || e.e.altKey || e.e.metaKey;
299
- if (code == 16 || e.e.shiftKey) shiftSelecting = shiftSelecting || (sel.inverted ? sel.to : sel.from);
300
  else shiftSelecting = null;
301
  // First give onKeyEvent option a chance to handle this.
302
- if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e.e))) return;
303
 
304
- if (code == 33 || code == 34) {scrollPage(code == 34); return e.stop();} // page up/down
305
  if (mod && ((code == 36 || code == 35) || // ctrl-home/end
306
  mac && (code == 38 || code == 40))) { // cmd-up/down
307
- scrollEnd(code == 36 || code == 38); return e.stop();
308
  }
309
- if (mod && code == 65) {selectAll(); return e.stop();} // ctrl-a
310
  if (!options.readOnly) {
311
  if (!anyMod && code == 13) {return;} // enter
312
- if (!anyMod && code == 9 && handleTab(e.e.shiftKey)) return e.stop(); // tab
313
- if (mod && code == 90) {undo(); return e.stop();} // ctrl-z
314
- if (mod && ((e.e.shiftKey && code == 90) || code == 89)) {redo(); return e.stop();} // ctrl-shift-z, ctrl-y
315
  }
 
316
 
317
  // Key id to use in the movementKeys map. We also pass it to
318
  // fastPoll in order to 'self learn'. We need this because
@@ -320,53 +452,64 @@ var CodeMirror = (function() {
320
  // its start when it is inverted and a movement key is pressed
321
  // (and later restore it again), shouldn't be used for
322
  // non-movement keys.
323
- curKeyId = (mod ? "c" : "") + code;
324
- if (sel.inverted && movementKeys.hasOwnProperty(curKeyId)) {
325
  var range = selRange(input);
326
  if (range) {
327
  reducedSelection = {anchor: range.start};
328
  setSelRange(input, range.start, range.start);
329
  }
330
  }
 
 
331
  fastPoll(curKeyId);
 
 
332
  }
333
  function onKeyUp(e) {
334
- if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e.e))) return;
335
  if (reducedSelection) {
336
  reducedSelection = null;
337
  updateInput = true;
338
  }
339
- if (e.e.keyCode == 16) shiftSelecting = null;
 
 
340
  }
341
  function onKeyPress(e) {
342
- if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e.e))) return;
343
  if (options.electricChars && mode.electricChars) {
344
- var ch = String.fromCharCode(e.e.charCode == null ? e.e.keyCode : e.e.charCode);
345
  if (mode.electricChars.indexOf(ch) > -1)
346
  setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 50);
347
  }
348
- var code = e.e.keyCode;
349
  // Re-stop tab and enter. Necessary on some browsers.
350
- if (code == 13) {if (!options.readOnly) handleEnter(); e.stop();}
351
- else if (!e.e.ctrlKey && !e.e.altKey && !e.e.metaKey && code == 9 && options.tabMode != "default") e.stop();
352
  else fastPoll(curKeyId);
353
  }
354
 
355
  function onFocus() {
356
  if (options.readOnly == "nocursor") return;
357
- if (!focused && options.onFocus) options.onFocus(instance);
358
- focused = true;
 
 
 
 
 
359
  slowPoll();
360
- if (wrapper.className.search(/\bCodeMirror-focused\b/) == -1)
361
- wrapper.className += " CodeMirror-focused";
362
  restartBlink();
363
  }
364
  function onBlur() {
365
- if (focused && options.onBlur) options.onBlur(instance);
 
 
 
 
366
  clearInterval(blinker);
367
- shiftSelecting = null;
368
- focused = false;
369
- wrapper.className = wrapper.className.replace(" CodeMirror-focused", "");
370
  }
371
 
372
  // Replace the range from from to to by the strings in newText.
@@ -374,23 +517,22 @@ var CodeMirror = (function() {
374
  function updateLines(from, to, newText, selFrom, selTo) {
375
  if (history) {
376
  var old = [];
377
- for (var i = from.line, e = to.line + 1; i < e; ++i) old.push(lines[i].text);
378
  history.addChange(from.line, newText.length, old);
379
  while (history.done.length > options.undoDepth) history.done.shift();
380
  }
381
  updateLinesNoUndo(from, to, newText, selFrom, selTo);
382
- if (newText.length < 5)
383
- highlightLines(from.line, from.line + newText.length)
384
  }
385
  function unredoHelper(from, to) {
386
  var change = from.pop();
387
  if (change) {
388
  var replaced = [], end = change.start + change.added;
389
- for (var i = change.start; i < end; ++i) replaced.push(lines[i].text);
390
  to.push({start: change.start, added: change.old.length, old: replaced});
391
  var pos = clipPos({line: change.start + change.old.length - 1,
392
  ch: editEnd(replaced[replaced.length-1], change.old[change.old.length-1])});
393
- updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: lines[end-1].text.length}, change.old, pos, pos);
 
394
  }
395
  }
396
  function undo() {unredoHelper(history.done, history.undone);}
@@ -398,51 +540,66 @@ var CodeMirror = (function() {
398
 
399
  function updateLinesNoUndo(from, to, newText, selFrom, selTo) {
400
  var recomputeMaxLength = false, maxLineLength = maxLine.length;
401
- for (var i = from.line; i <= to.line; ++i) {
402
- if (lines[i].text.length == maxLineLength) {recomputeMaxLength = true; break;}
403
- }
 
404
 
405
- var nlines = to.line - from.line, firstLine = lines[from.line], lastLine = lines[to.line];
406
  // First adjust the line structure, taking some care to leave highlighting intact.
407
  if (firstLine == lastLine) {
408
  if (newText.length == 1)
409
  firstLine.replace(from.ch, to.ch, newText[0]);
410
  else {
411
  lastLine = firstLine.split(to.ch, newText[newText.length-1]);
412
- var spliceargs = [from.line + 1, nlines];
413
- firstLine.replace(from.ch, firstLine.text.length, newText[0]);
414
- for (var i = 1, e = newText.length - 1; i < e; ++i) spliceargs.push(new Line(newText[i]));
415
- spliceargs.push(lastLine);
416
- lines.splice.apply(lines, spliceargs);
 
 
417
  }
418
  }
419
  else if (newText.length == 1) {
420
- firstLine.replace(from.ch, firstLine.text.length, newText[0] + lastLine.text.slice(to.ch));
421
- lines.splice(from.line + 1, nlines);
 
 
422
  }
423
  else {
424
- var spliceargs = [from.line + 1, nlines - 1];
425
- firstLine.replace(from.ch, firstLine.text.length, newText[0]);
426
- lastLine.replace(0, to.ch, newText[newText.length-1]);
427
- for (var i = 1, e = newText.length - 1; i < e; ++i) spliceargs.push(new Line(newText[i]));
428
- lines.splice.apply(lines, spliceargs);
429
- }
430
-
431
-
432
- for (var i = from.line, e = i + newText.length; i < e; ++i) {
433
- var l = lines[i].text;
434
- if (l.length > maxLineLength) {
435
- maxLine = l; maxLineLength = l.length;
436
- recomputeMaxLength = false;
437
- }
438
- }
439
- if (recomputeMaxLength) {
440
- maxLineLength = 0; maxLine = "";
441
- for (var i = 0, e = lines.length; i < e; ++i) {
442
- var l = lines[i].text;
443
  if (l.length > maxLineLength) {
444
- maxLineLength = l.length; maxLine = l;
 
445
  }
 
 
 
 
 
 
 
 
 
446
  }
447
  }
448
 
@@ -454,7 +611,9 @@ var CodeMirror = (function() {
454
  if (task < from.line) newWork.push(task);
455
  else if (task > to.line) newWork.push(task + lendiff);
456
  }
457
- if (newText.length) newWork.push(from.line);
 
 
458
  work = newWork;
459
  startWorker(100);
460
  // Remember that these lines changed, for updating the display
@@ -466,7 +625,7 @@ var CodeMirror = (function() {
466
  setSelection(selFrom, selTo, updateLine(sel.from.line), updateLine(sel.to.line));
467
 
468
  // Make sure the scroll-size div has the correct height.
469
- code.style.height = (lines.length * lineHeight() + 2 * paddingTop()) + "px";
470
  }
471
 
472
  function replaceRange(code, from, to) {
@@ -504,10 +663,10 @@ var CodeMirror = (function() {
504
 
505
  function getRange(from, to) {
506
  var l1 = from.line, l2 = to.line;
507
- if (l1 == l2) return lines[l1].text.slice(from.ch, to.ch);
508
- var code = [lines[l1].text.slice(from.ch)];
509
- for (var i = l1 + 1; i < l2; ++i) code.push(lines[i].text);
510
- code.push(lines[l2].text.slice(0, to.ch));
511
  return code.join("\n");
512
  }
513
  function getSelection() {
@@ -517,7 +676,7 @@ var CodeMirror = (function() {
517
  var pollingFast = false; // Ensures slowPoll doesn't cancel fastPoll
518
  function slowPoll() {
519
  if (pollingFast) return;
520
- poll.set(2000, function() {
521
  startOperation();
522
  readInput();
523
  if (focused) slowPoll();
@@ -530,7 +689,10 @@ var CodeMirror = (function() {
530
  function p() {
531
  startOperation();
532
  var changed = readInput();
533
- if (changed == "moved" && keyId) movementKeys[keyId] = true;
 
 
 
534
  if (!changed && !missed) {missed = true; poll.set(80, p);}
535
  else {pollingFast = false; slowPoll();}
536
  endOperation();
@@ -542,7 +704,7 @@ var CodeMirror = (function() {
542
  // to the data in the editing variable, and updates the editor
543
  // content or cursor if something changed.
544
  function readInput() {
545
- if (leaveInputAlone) return;
546
  var changed = false, text = input.value, sr = selRange(input);
547
  if (!sr) return false;
548
  var changed = editing.text != text, rs = reducedSelection;
@@ -612,14 +774,16 @@ var CodeMirror = (function() {
612
  // editor state.
613
  function prepareInput() {
614
  var text = [];
615
- var from = Math.max(0, sel.from.line - 1), to = Math.min(lines.length, sel.to.line + 2);
616
- for (var i = from; i < to; ++i) text.push(lines[i].text);
617
  text = input.value = text.join(lineSep);
618
  var startch = sel.from.ch, endch = sel.to.ch;
619
- for (var i = from; i < sel.from.line; ++i)
620
- startch += lineSep.length + lines[i].text.length;
621
- for (var i = from; i < sel.to.line; ++i)
622
- endch += lineSep.length + lines[i].text.length;
 
 
623
  editing = {text: text, from: from, to: to, start: startch, end: endch};
624
  setSelRange(input, startch, reducedSelection ? startch : endch);
625
  }
@@ -627,21 +791,29 @@ var CodeMirror = (function() {
627
  if (options.readOnly != "nocursor") input.focus();
628
  }
629
 
 
 
 
 
 
 
630
  function scrollCursorIntoView() {
631
  var cursor = localCoords(sel.inverted ? sel.from : sel.to);
632
- return scrollIntoView(cursor.x, cursor.y, cursor.x, cursor.yBot);
 
633
  }
634
  function scrollIntoView(x1, y1, x2, y2) {
635
- var pl = paddingLeft(), pt = paddingTop(), lh = lineHeight();
636
  y1 += pt; y2 += pt; x1 += pl; x2 += pl;
637
  var screen = scroller.clientHeight, screentop = scroller.scrollTop, scrolled = false, result = true;
638
  if (y1 < screentop) {scroller.scrollTop = Math.max(0, y1 - 2*lh); scrolled = true;}
639
  else if (y2 > screentop + screen) {scroller.scrollTop = y2 + lh - screen; scrolled = true;}
640
 
641
  var screenw = scroller.clientWidth, screenleft = scroller.scrollLeft;
642
- if (x1 < screenleft) {
 
643
  if (x1 < 50) x1 = 0;
644
- scroller.scrollLeft = Math.max(0, x1 - 10);
645
  scrolled = true;
646
  }
647
  else if (x2 > screenw + screenleft) {
@@ -654,32 +826,103 @@ var CodeMirror = (function() {
654
  }
655
 
656
  function visibleLines() {
657
- var lh = lineHeight(), top = scroller.scrollTop - paddingTop();
658
- return {from: Math.min(lines.length, Math.max(0, Math.floor(top / lh))),
659
- to: Math.min(lines.length, Math.ceil((top + scroller.clientHeight) / lh))};
 
 
660
  }
661
  // Uses a set of changes plus the current scroll position to
662
  // determine which DOM updates have to be made, and makes the
663
  // updates.
664
  function updateDisplay(changes) {
665
  if (!scroller.clientWidth) {
666
- showingFrom = showingTo = 0;
667
  return;
668
  }
669
- // First create a range of theoretically intact lines, and punch
670
- // holes in that using the change info.
671
- var intact = changes === true ? [] : [{from: showingFrom, to: showingTo, domStart: 0}];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
672
  for (var i = 0, l = changes.length || 0; i < l; ++i) {
673
  var change = changes[i], intact2 = [], diff = change.diff || 0;
674
  for (var j = 0, l2 = intact.length; j < l2; ++j) {
675
  var range = intact[j];
676
- if (change.to <= range.from)
677
- intact2.push({from: range.from + diff, to: range.to + diff, domStart: range.domStart});
678
- else if (range.to <= change.from)
 
679
  intact2.push(range);
680
  else {
681
  if (change.from > range.from)
682
- intact2.push({from: range.from, to: change.from, domStart: range.domStart})
683
  if (change.to < range.to)
684
  intact2.push({from: change.to + diff, to: range.to + diff,
685
  domStart: range.domStart + (change.to - range.from)});
@@ -687,151 +930,90 @@ var CodeMirror = (function() {
687
  }
688
  intact = intact2;
689
  }
 
 
690
 
691
- // Then, determine which lines we'd want to see, and which
692
- // updates have to be made to get there.
693
- var visible = visibleLines();
694
- var from = Math.min(showingFrom, Math.max(visible.from - 3, 0)),
695
- to = Math.min(lines.length, Math.max(showingTo, visible.to + 3)),
696
- updates = [], domPos = 0, domEnd = showingTo - showingFrom, pos = from, changedLines = 0;
697
-
698
- for (var i = 0, l = intact.length; i < l; ++i) {
699
- var range = intact[i];
700
- if (range.to <= from) continue;
701
- if (range.from >= to) break;
702
- if (range.domStart > domPos || range.from > pos) {
703
- updates.push({from: pos, to: range.from, domSize: range.domStart - domPos, domStart: domPos});
704
- changedLines += range.from - pos;
705
  }
706
- pos = range.to;
707
- domPos = range.domStart + (range.to - range.from);
708
- }
709
- if (domPos != domEnd || pos != to) {
710
- changedLines += Math.abs(to - pos);
711
- updates.push({from: pos, to: to, domSize: domEnd - domPos, domStart: domPos});
712
- }
713
-
714
- if (!updates.length) return;
715
- lineDiv.style.display = "none";
716
- // If more than 30% of the screen needs update, just do a full
717
- // redraw (which is quicker than patching)
718
- if (changedLines > (visible.to - visible.from) * .3)
719
- refreshDisplay(from = Math.max(visible.from - 10, 0), to = Math.min(visible.to + 7, lines.length));
720
- // Otherwise, only update the stuff that needs updating.
721
- else
722
- patchDisplay(updates);
723
- lineDiv.style.display = "";
724
-
725
- // Position the mover div to align with the lines it's supposed
726
- // to be showing (which will cover the visible display)
727
- var different = from != showingFrom || to != showingTo || lastHeight != scroller.clientHeight;
728
- showingFrom = from; showingTo = to;
729
- mover.style.top = (from * lineHeight()) + "px";
730
- if (different) {
731
- lastHeight = scroller.clientHeight;
732
- code.style.height = (lines.length * lineHeight() + 2 * paddingTop()) + "px";
733
- updateGutter();
734
  }
735
-
736
- var textWidth = stringWidth(maxLine);
737
- lineSpace.style.width = textWidth > scroller.clientWidth ? textWidth + "px" : "";
738
-
739
- // Since this is all rather error prone, it is honoured with the
740
- // only assertion in the whole file.
741
- if (lineDiv.childNodes.length != showingTo - showingFrom)
742
- throw new Error("BAD PATCH! " + JSON.stringify(updates) + " size=" + (showingTo - showingFrom) +
743
- " nodes=" + lineDiv.childNodes.length);
744
- updateCursor();
745
- }
746
-
747
- function refreshDisplay(from, to) {
748
- var html = [], start = {line: from, ch: 0}, inSel = posLess(sel.from, start) && !posLess(sel.to, start);
749
- for (var i = from; i < to; ++i) {
750
  var ch1 = null, ch2 = null;
751
  if (inSel) {
752
  ch1 = 0;
753
- if (sel.to.line == i) {inSel = false; ch2 = sel.to.ch;}
754
- }
755
- else if (sel.from.line == i) {
756
- if (sel.to.line == i) {ch1 = sel.from.ch; ch2 = sel.to.ch;}
757
  else {inSel = true; ch1 = sel.from.ch;}
758
  }
759
- html.push(lines[i].getHTML(ch1, ch2, true));
760
- }
761
- lineDiv.innerHTML = html.join("");
762
- }
763
- function patchDisplay(updates) {
764
- // Slightly different algorithm for IE (badInnerHTML), since
765
- // there .innerHTML on PRE nodes is dumb, and discards
766
- // whitespace.
767
- var sfrom = sel.from.line, sto = sel.to.line, off = 0,
768
- scratch = badInnerHTML && targetDocument.createElement("div");
769
- for (var i = 0, e = updates.length; i < e; ++i) {
770
- var rec = updates[i];
771
- var extra = (rec.to - rec.from) - rec.domSize;
772
- var nodeAfter = lineDiv.childNodes[rec.domStart + rec.domSize + off] || null;
773
- if (badInnerHTML)
774
- for (var j = Math.max(-extra, rec.domSize); j > 0; --j)
775
- lineDiv.removeChild(nodeAfter ? nodeAfter.previousSibling : lineDiv.lastChild);
776
- else if (extra) {
777
- for (var j = Math.max(0, extra); j > 0; --j)
778
- lineDiv.insertBefore(targetDocument.createElement("pre"), nodeAfter);
779
- for (var j = Math.max(0, -extra); j > 0; --j)
780
- lineDiv.removeChild(nodeAfter ? nodeAfter.previousSibling : lineDiv.lastChild);
781
- }
782
- var node = lineDiv.childNodes[rec.domStart + off], inSel = sfrom < rec.from && sto >= rec.from;
783
- for (var j = rec.from; j < rec.to; ++j) {
784
- var ch1 = null, ch2 = null;
785
- if (inSel) {
786
- ch1 = 0;
787
- if (sto == j) {inSel = false; ch2 = sel.to.ch;}
788
- }
789
- else if (sfrom == j) {
790
- if (sto == j) {ch1 = sel.from.ch; ch2 = sel.to.ch;}
791
- else {inSel = true; ch1 = sel.from.ch;}
792
- }
793
- if (badInnerHTML) {
794
- scratch.innerHTML = lines[j].getHTML(ch1, ch2, true);
795
- lineDiv.insertBefore(scratch.firstChild, nodeAfter);
796
- }
797
- else {
798
- node.innerHTML = lines[j].getHTML(ch1, ch2, false);
799
- node.className = lines[j].className || "";
800
- node = node.nextSibling;
801
- }
802
  }
803
- off += extra;
804
- }
805
  }
806
 
807
  function updateGutter() {
808
  if (!options.gutter && !options.lineNumbers) return;
809
  var hText = mover.offsetHeight, hEditor = scroller.clientHeight;
810
  gutter.style.height = (hText - hEditor < 2 ? hEditor : hText) + "px";
811
- var html = [];
812
- for (var i = showingFrom; i < Math.max(showingTo, showingFrom + 1); ++i) {
813
- var marker = lines[i].gutterMarker;
814
- var text = options.lineNumbers ? i + options.firstLineNumber : null;
815
- if (marker && marker.text)
816
- text = marker.text.replace("%N%", text != null ? text : "");
817
- else if (text == null)
818
- text = "\u00a0";
819
- html.push((marker && marker.style ? '<pre class="' + marker.style + '">' : "<pre>"), text, "</pre>");
820
- }
 
 
 
 
 
 
 
821
  gutter.style.display = "none";
822
  gutterText.innerHTML = html.join("");
823
- var minwidth = String(lines.length).length, firstNode = gutterText.firstChild, val = eltText(firstNode), pad = "";
824
  while (val.length + pad.length < minwidth) pad += "\u00a0";
825
  if (pad) firstNode.insertBefore(targetDocument.createTextNode(pad), firstNode.firstChild);
826
  gutter.style.display = "";
827
  lineSpace.style.marginLeft = gutter.offsetWidth + "px";
 
828
  }
829
  function updateCursor() {
830
- var head = sel.inverted ? sel.from : sel.to, lh = lineHeight();
831
- var x = charX(head.line, head.ch) + "px", y = (head.line - showingFrom) * lh + "px";
832
- inputDiv.style.top = (head.line * lh - scroller.scrollTop) + "px";
 
 
833
  if (posEq(sel.from, sel.to)) {
834
- cursor.style.top = y; cursor.style.left = x;
 
835
  cursor.style.display = "";
836
  }
837
  else cursor.style.display = "none";
@@ -849,9 +1031,14 @@ var CodeMirror = (function() {
849
  // updateLines, since they have to be expressed in the line
850
  // numbers before the update.
851
  function setSelection(from, to, oldFrom, oldTo) {
 
852
  if (posEq(sel.from, from) && posEq(sel.to, to)) return;
853
  if (posLess(to, from)) {var tmp = to; to = from; from = tmp;}
854
 
 
 
 
 
855
  if (posEq(from, to)) sel.inverted = false;
856
  else if (posEq(from, sel.to)) sel.inverted = false;
857
  else if (posEq(to, sel.from)) sel.inverted = true;
@@ -859,7 +1046,6 @@ var CodeMirror = (function() {
859
  // Some ugly logic used to only mark the lines that actually did
860
  // see a change in selection as changed, rather than the whole
861
  // selected range.
862
- if (oldFrom == null) {oldFrom = sel.from.line; oldTo = sel.to.line;}
863
  if (posEq(from, to)) {
864
  if (!posEq(sel.from, sel.to))
865
  changes.push({from: oldFrom, to: oldTo + 1});
@@ -884,42 +1070,61 @@ var CodeMirror = (function() {
884
  sel.from = from; sel.to = to;
885
  selectionChanged = true;
886
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
887
  function setCursor(line, ch, user) {
888
  var pos = clipPos({line: line, ch: ch || 0});
889
  (user ? setSelectionUser : setSelection)(pos, pos);
890
  }
891
 
892
- function clipLine(n) {return Math.max(0, Math.min(n, lines.length-1));}
893
  function clipPos(pos) {
894
  if (pos.line < 0) return {line: 0, ch: 0};
895
- if (pos.line >= lines.length) return {line: lines.length-1, ch: lines[lines.length-1].text.length};
896
- var ch = pos.ch, linelen = lines[pos.line].text.length;
897
  if (ch == null || ch > linelen) return {line: pos.line, ch: linelen};
898
  else if (ch < 0) return {line: pos.line, ch: 0};
899
  else return pos;
900
  }
901
 
902
  function scrollPage(down) {
903
- var linesPerPage = Math.floor(scroller.clientHeight / lineHeight()), head = sel.inverted ? sel.from : sel.to;
904
- setCursor(head.line + (Math.max(linesPerPage - 1, 1) * (down ? 1 : -1)), head.ch, true);
 
905
  }
906
  function scrollEnd(top) {
907
- var pos = top ? {line: 0, ch: 0} : {line: lines.length - 1, ch: lines[lines.length-1].text.length};
908
  setSelectionUser(pos, pos);
909
  }
910
  function selectAll() {
911
- var endLine = lines.length - 1;
912
- setSelection({line: 0, ch: 0}, {line: endLine, ch: lines[endLine].text.length});
913
  }
914
  function selectWordAt(pos) {
915
- var line = lines[pos.line].text;
916
  var start = pos.ch, end = pos.ch;
917
  while (start > 0 && /\w/.test(line.charAt(start - 1))) --start;
918
  while (end < line.length && /\w/.test(line.charAt(end))) ++end;
919
  setSelectionUser({line: pos.line, ch: start}, {line: pos.line, ch: end});
920
  }
921
  function selectLine(line) {
922
- setSelectionUser({line: line, ch: 0}, {line: line, ch: lines[line].text.length});
923
  }
924
  function handleEnter() {
925
  replaceSelection("\n", "end");
@@ -927,12 +1132,17 @@ var CodeMirror = (function() {
927
  indentLine(sel.from.line, options.enterMode == "keep" ? "prev" : "smart");
928
  }
929
  function handleTab(shift) {
 
 
 
 
 
930
  shiftSelecting = null;
931
  switch (options.tabMode) {
932
  case "default":
933
  return false;
934
  case "indent":
935
- for (var i = sel.from.line, e = sel.to.line; i <= e; ++i) indentLine(i, "smart");
936
  break;
937
  case "classic":
938
  if (posEq(sel.from, sel.to)) {
@@ -941,11 +1151,15 @@ var CodeMirror = (function() {
941
  break;
942
  }
943
  case "shift":
944
- for (var i = sel.from.line, e = sel.to.line; i <= e; ++i) indentLine(i, shift ? "subtract" : "add");
945
  break;
946
  }
947
  return true;
948
  }
 
 
 
 
949
 
950
  function indentLine(n, how) {
951
  if (how == "smart") {
@@ -953,9 +1167,9 @@ var CodeMirror = (function() {
953
  else var state = getStateBefore(n);
954
  }
955
 
956
- var line = lines[n], curSpace = line.indentation(), curSpaceString = line.text.match(/^\s*/)[0], indentation;
957
  if (how == "prev") {
958
- if (n) indentation = lines[n-1].indentation();
959
  else indentation = 0;
960
  }
961
  else if (how == "smart") indentation = mode.indent(state, line.text.slice(curSpaceString.length));
@@ -980,87 +1194,148 @@ var CodeMirror = (function() {
980
 
981
  function loadMode() {
982
  mode = CodeMirror.getMode(options, options.mode);
983
- for (var i = 0, l = lines.length; i < l; ++i)
984
- lines[i].stateAfter = null;
985
  work = [0];
986
  startWorker();
987
  }
988
  function gutterChanged() {
989
  var visible = options.gutter || options.lineNumbers;
990
  gutter.style.display = visible ? "" : "none";
991
- if (visible) updateGutter();
992
  else lineDiv.parentNode.style.marginLeft = 0;
993
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
994
 
995
  function markText(from, to, className) {
996
  from = clipPos(from); to = clipPos(to);
997
- var accum = [];
998
  function add(line, from, to, className) {
999
- var line = lines[line], mark = line.addMark(from, to, className);
1000
- mark.line = line;
1001
- accum.push(mark);
1002
  }
1003
  if (from.line == to.line) add(from.line, from.ch, to.ch, className);
1004
  else {
1005
  add(from.line, from.ch, null, className);
1006
  for (var i = from.line + 1, e = to.line; i < e; ++i)
1007
- add(i, 0, null, className);
1008
- add(to.line, 0, to.ch, className);
1009
  }
1010
  changes.push({from: from.line, to: to.line + 1});
1011
- return function() {
1012
- var start, end;
1013
- for (var i = 0; i < accum.length; ++i) {
1014
- var mark = accum[i], found = indexOf(lines, mark.line);
1015
- mark.line.removeMark(mark);
1016
- if (found > -1) {
1017
- if (start == null) start = found;
1018
- end = found;
1019
- }
1020
- }
1021
- if (start != null) changes.push({from: start, to: end + 1});
1022
- };
1023
  }
1024
 
1025
  function addGutterMarker(line, text, className) {
1026
- if (typeof line == "number") line = lines[clipLine(line)];
1027
  line.gutterMarker = {text: text, style: className};
1028
- updateGutter();
1029
  return line;
1030
  }
1031
  function removeGutterMarker(line) {
1032
- if (typeof line == "number") line = lines[clipLine(line)];
1033
  line.gutterMarker = null;
1034
- updateGutter();
1035
  }
1036
- function setLineClass(line, className) {
1037
- if (typeof line == "number") {
1038
- var no = line;
1039
- line = lines[clipLine(line)];
1040
- }
1041
- else {
1042
- var no = indexOf(lines, line);
1043
- if (no == -1) return null;
1044
- }
1045
- if (line.className != className) {
1046
- line.className = className;
1047
- changes.push({from: no, to: no + 1});
1048
- }
1049
  return line;
1050
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1051
 
1052
  function lineInfo(line) {
1053
  if (typeof line == "number") {
 
1054
  var n = line;
1055
- line = lines[line];
1056
  if (!line) return null;
1057
  }
1058
  else {
1059
- var n = indexOf(lines, line);
1060
- if (n == -1) return null;
1061
  }
1062
  var marker = line.gutterMarker;
1063
- return {line: n, text: line.text, markerText: marker && marker.text, markerClass: marker && marker.style};
 
1064
  }
1065
 
1066
  function stringWidth(str) {
@@ -1070,21 +1345,16 @@ var CodeMirror = (function() {
1070
  }
1071
  // These are used to go from pixel positions to character
1072
  // positions, taking varying character widths into account.
1073
- function charX(line, pos) {
1074
- if (pos == 0) return 0;
1075
- measure.innerHTML = "<pre><span>" + lines[line].getHTML(null, null, false, pos) + "</span></pre>";
1076
- return measure.firstChild.firstChild.offsetWidth;
1077
- }
1078
  function charFromX(line, x) {
1079
  if (x <= 0) return 0;
1080
- var lineObj = lines[line], text = lineObj.text;
1081
  function getX(len) {
1082
  measure.innerHTML = "<pre><span>" + lineObj.getHTML(null, null, false, len) + "</span></pre>";
1083
  return measure.firstChild.firstChild.offsetWidth;
1084
  }
1085
  var from = 0, fromX = 0, to = text.length, toX;
1086
  // Guess a suitable upper bound for our search.
1087
- var estimated = Math.min(to, Math.ceil(x / stringWidth("x")));
1088
  for (;;) {
1089
  var estX = getX(estimated);
1090
  if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2));
@@ -1103,59 +1373,136 @@ var CodeMirror = (function() {
1103
  }
1104
  }
1105
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1106
  function localCoords(pos, inLineWrap) {
1107
- var lh = lineHeight(), line = pos.line - (inLineWrap ? showingFrom : 0);
1108
- return {x: charX(pos.line, pos.ch), y: line * lh, yBot: (line + 1) * lh};
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1109
  }
1110
  function pageCoords(pos) {
1111
  var local = localCoords(pos, true), off = eltOffset(lineSpace);
1112
  return {x: off.left + local.x, y: off.top + local.y, yBot: off.top + local.yBot};
1113
  }
1114
 
1115
- function lineHeight() {
1116
- var nlines = lineDiv.childNodes.length;
1117
- if (nlines) return (lineDiv.offsetHeight / nlines) || 1;
1118
- measure.innerHTML = "<pre>x</pre>";
1119
- return measure.firstChild.offsetHeight || 1;
 
 
 
 
 
 
 
 
1120
  }
1121
  function paddingTop() {return lineSpace.offsetTop;}
1122
  function paddingLeft() {return lineSpace.offsetLeft;}
1123
 
1124
  function posFromMouse(e, liberal) {
1125
- var offW = eltOffset(scroller, true), x = e.e.clientX, y = e.e.clientY;
 
 
1126
  // This is a mess of a heuristic to try and determine whether a
1127
  // scroll-bar was clicked or not, and to return null if one was
1128
  // (and !liberal).
1129
  if (!liberal && (x - offW.left > scroller.clientWidth || y - offW.top > scroller.clientHeight))
1130
  return null;
1131
  var offL = eltOffset(lineSpace, true);
1132
- var line = showingFrom + Math.floor((y - offL.top) / lineHeight());
1133
- return clipPos({line: line, ch: charFromX(clipLine(line), x - offL.left)});
1134
  }
1135
  function onContextMenu(e) {
1136
  var pos = posFromMouse(e);
1137
  if (!pos || window.opera) return; // Opera is difficult.
1138
  if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to))
1139
- setCursor(pos.line, pos.ch);
1140
 
1141
  var oldCSS = input.style.cssText;
1142
- input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.pageY() - 1) +
1143
- "px; left: " + (e.pageX() - 1) + "px; z-index: 1000; background: white; " +
1144
- "border-width: 0; outline: none; overflow: hidden; opacity: .05;";
 
 
1145
  var val = input.value = getSelection();
1146
  focusInput();
1147
  setSelRange(input, 0, input.value.length);
1148
- leaveInputAlone = true;
1149
  function rehide() {
1150
- if (input.value != val) operation(replaceSelection)(input.value, "end");
 
 
1151
  input.style.cssText = oldCSS;
1152
  leaveInputAlone = false;
1153
  prepareInput();
1154
  slowPoll();
1155
  }
1156
-
1157
  if (gecko) {
1158
- e.stop()
1159
  var mouseup = connect(window, "mouseup", function() {
1160
  mouseup();
1161
  setTimeout(rehide, 20);
@@ -1178,7 +1525,7 @@ var CodeMirror = (function() {
1178
 
1179
  var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"};
1180
  function matchBrackets(autoclear) {
1181
- var head = sel.inverted ? sel.from : sel.to, line = lines[head.line], pos = head.ch - 1;
1182
  var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)];
1183
  if (!match) return;
1184
  var ch = match.charAt(0), forward = match.charAt(1) == ">", d = forward ? 1 : -1, st = line.styles;
@@ -1202,19 +1549,18 @@ var CodeMirror = (function() {
1202
  }
1203
  }
1204
  }
1205
- for (var i = head.line, e = forward ? Math.min(i + 50, lines.length) : Math.max(-1, i - 50); i != e; i+=d) {
1206
- var line = lines[i], first = i == head.line;
1207
  var found = scan(line, first && forward ? pos + 1 : 0, first && !forward ? pos : line.text.length);
1208
- if (found) {
1209
- var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
1210
- var one = markText({line: head.line, ch: pos}, {line: head.line, ch: pos+1}, style),
1211
- two = markText({line: i, ch: found.pos}, {line: i, ch: found.pos + 1}, style);
1212
- var clear = operation(function(){one(); two();});
1213
- if (autoclear) setTimeout(clear, 800);
1214
- else bracketHighlighted = clear;
1215
- break;
1216
- }
1217
  }
 
 
 
 
 
 
 
1218
  }
1219
 
1220
  // Finds the line to start with when starting a parse. Tries to
@@ -1226,62 +1572,69 @@ var CodeMirror = (function() {
1226
  var minindent, minline;
1227
  for (var search = n, lim = n - 40; search > lim; --search) {
1228
  if (search == 0) return 0;
1229
- var line = lines[search-1];
1230
  if (line.stateAfter) return search;
1231
  var indented = line.indentation();
1232
  if (minline == null || minindent > indented) {
1233
- minline = search;
1234
  minindent = indented;
1235
  }
1236
  }
1237
  return minline;
1238
  }
1239
  function getStateBefore(n) {
1240
- var start = findStartLine(n), state = start && lines[start-1].stateAfter;
1241
  if (!state) state = startState(mode);
1242
  else state = copyState(mode, state);
1243
- for (var i = start; i < n; ++i) {
1244
- var line = lines[i];
1245
  line.highlight(mode, state);
1246
  line.stateAfter = copyState(mode, state);
1247
- }
1248
- if (!lines[n].stateAfter) work.push(n);
 
1249
  return state;
1250
  }
1251
  function highlightLines(start, end) {
1252
  var state = getStateBefore(start);
1253
- for (var i = start; i < end; ++i) {
1254
- var line = lines[i];
1255
  line.highlight(mode, state);
1256
  line.stateAfter = copyState(mode, state);
1257
- }
1258
  }
1259
  function highlightWorker() {
1260
  var end = +new Date + options.workTime;
1261
  var foundWork = work.length;
1262
  while (work.length) {
1263
- if (!lines[showingFrom].stateAfter) var task = showingFrom;
1264
  else var task = work.pop();
1265
- if (task >= lines.length) continue;
1266
- var start = findStartLine(task), state = start && lines[start-1].stateAfter;
1267
  if (state) state = copyState(mode, state);
1268
  else state = startState(mode);
1269
 
1270
- var unchanged = 0;
1271
- for (var i = start, l = lines.length; i < l; ++i) {
1272
- var line = lines[i], hadState = line.stateAfter;
 
1273
  if (+new Date > end) {
1274
  work.push(i);
1275
  startWorker(options.workDelay);
1276
- changes.push({from: task, to: i});
1277
- return;
1278
  }
1279
  var changed = line.highlight(mode, state);
 
1280
  line.stateAfter = copyState(mode, state);
1281
- if (changed || !hadState) unchanged = 0;
1282
- else if (++unchanged > 3) break;
1283
- }
1284
- changes.push({from: task, to: i});
 
 
 
 
 
 
1285
  }
1286
  if (foundWork && options.onHighlightComplete)
1287
  options.onHighlightComplete(instance);
@@ -1302,13 +1655,17 @@ var CodeMirror = (function() {
1302
  var reScroll = false;
1303
  if (selectionChanged) reScroll = !scrollCursorIntoView();
1304
  if (changes.length) updateDisplay(changes);
1305
- else if (selectionChanged) updateCursor();
 
 
 
1306
  if (reScroll) scrollCursorIntoView();
1307
- if (selectionChanged) restartBlink();
1308
 
1309
  // updateInput can be set to a boolean value to force/prevent an
1310
  // update.
1311
- if (!leaveInputAlone && (updateInput === true || (updateInput !== false && selectionChanged)))
 
1312
  prepareInput();
1313
 
1314
  if (selectionChanged && options.matchBrackets)
@@ -1347,7 +1704,7 @@ var CodeMirror = (function() {
1347
  if (typeof query != "string") // Regexp match
1348
  this.matches = function(reverse, pos) {
1349
  if (reverse) {
1350
- var line = lines[pos.line].text.slice(0, pos.ch), match = line.match(query), start = 0;
1351
  while (match) {
1352
  var ind = line.indexOf(match[0]);
1353
  start += ind;
@@ -1359,7 +1716,7 @@ var CodeMirror = (function() {
1359
  }
1360
  }
1361
  else {
1362
- var line = lines[pos.line].text.slice(pos.ch), match = line.match(query),
1363
  start = match && pos.ch + line.indexOf(match[0]);
1364
  }
1365
  if (match)
@@ -1374,7 +1731,7 @@ var CodeMirror = (function() {
1374
  // Different methods for single-line and multi-line queries
1375
  if (target.length == 1)
1376
  this.matches = function(reverse, pos) {
1377
- var line = fold(lines[pos.line].text), len = query.length, match;
1378
  if (reverse ? (pos.ch >= len && (match = line.lastIndexOf(query, pos.ch - len)) != -1)
1379
  : (match = line.indexOf(query, pos.ch)) != -1)
1380
  return {from: {line: pos.line, ch: match},
@@ -1382,14 +1739,14 @@ var CodeMirror = (function() {
1382
  };
1383
  else
1384
  this.matches = function(reverse, pos) {
1385
- var ln = pos.line, idx = (reverse ? target.length - 1 : 0), match = target[idx], line = fold(lines[ln].text);
1386
  var offsetA = (reverse ? line.indexOf(match) + match.length : line.lastIndexOf(match));
1387
  if (reverse ? offsetA >= pos.ch || offsetA != match.length
1388
  : offsetA <= pos.ch || offsetA != line.length - match.length)
1389
  return;
1390
  for (;;) {
1391
- if (reverse ? !ln : ln == lines.length - 1) return;
1392
- line = fold(lines[ln += reverse ? -1 : 1].text);
1393
  match = target[reverse ? --idx : ++idx];
1394
  if (idx > 0 && idx < target.length - 1) {
1395
  if (line != match) return;
@@ -1425,17 +1782,25 @@ var CodeMirror = (function() {
1425
  }
1426
  if (reverse) {
1427
  if (!pos.line) return savePosAndFail(0);
1428
- pos = {line: pos.line-1, ch: lines[pos.line-1].text.length};
1429
  }
1430
  else {
1431
- if (pos.line == lines.length - 1) return savePosAndFail(lines.length);
1432
  pos = {line: pos.line+1, ch: 0};
1433
  }
1434
  }
1435
  },
1436
 
1437
  from: function() {if (this.atOccurrence) return copyPos(this.pos.from);},
1438
- to: function() {if (this.atOccurrence) return copyPos(this.pos.to);}
 
 
 
 
 
 
 
 
1439
  };
1440
 
1441
  for (var ext in extensions)
@@ -1456,10 +1821,13 @@ var CodeMirror = (function() {
1456
  enterMode: "indent",
1457
  electricChars: true,
1458
  onKeyEvent: null,
 
1459
  lineNumbers: false,
1460
  gutter: false,
 
1461
  firstLineNumber: 1,
1462
  readOnly: false,
 
1463
  onChange: null,
1464
  onCursorActivity: null,
1465
  onGutterClick: null,
@@ -1470,6 +1838,7 @@ var CodeMirror = (function() {
1470
  workDelay: 200,
1471
  undoDepth: 40,
1472
  tabindex: null,
 
1473
  document: window.document
1474
  };
1475
 
@@ -1495,7 +1864,7 @@ var CodeMirror = (function() {
1495
  return CodeMirror.getMode(options, "text/plain");
1496
  }
1497
  return mfactory(options, config || {});
1498
- }
1499
  CodeMirror.listModes = function() {
1500
  var list = [];
1501
  for (var m in modes)
@@ -1505,7 +1874,7 @@ var CodeMirror = (function() {
1505
  CodeMirror.listMIMEs = function() {
1506
  var list = [];
1507
  for (var m in mimeModes)
1508
- if (mimeModes.propertyIsEnumerable(m)) list.push(m);
1509
  return list;
1510
  };
1511
 
@@ -1567,11 +1936,11 @@ var CodeMirror = (function() {
1567
  }
1568
  return nstate;
1569
  }
1570
- CodeMirror.startState = startState;
1571
  function startState(mode, a1, a2) {
1572
  return mode.startState ? mode.startState(a1, a2) : true;
1573
  }
1574
- CodeMirror.copyState = copyState;
1575
 
1576
  // The character stream used by a mode's parser.
1577
  function StringStream(string) {
@@ -1593,7 +1962,7 @@ var CodeMirror = (function() {
1593
  if (ok) {++this.pos; return ch;}
1594
  },
1595
  eatWhile: function(match) {
1596
- var start = this.start;
1597
  while (this.eat(match)){}
1598
  return this.pos > start;
1599
  },
@@ -1628,18 +1997,98 @@ var CodeMirror = (function() {
1628
  };
1629
  CodeMirror.StringStream = StringStream;
1630
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1631
  // Line objects. These hold state related to a line, including
1632
  // highlighting info (the styles array).
1633
  function Line(text, styles) {
1634
  this.styles = styles || [text, null];
1635
- this.stateAfter = null;
1636
  this.text = text;
 
1637
  this.marked = this.gutterMarker = this.className = null;
 
 
 
 
 
 
 
 
 
 
 
 
 
1638
  }
1639
  Line.prototype = {
1640
  // Replace a piece of a line, keeping the styles around it intact.
1641
- replace: function(from, to, text) {
1642
- var st = [], mk = this.marked;
 
 
 
1643
  copyStyles(0, from, this.styles, st);
1644
  if (text) st.push(text, null);
1645
  copyStyles(to, this.text.length, this.styles, st);
@@ -1647,34 +2096,79 @@ var CodeMirror = (function() {
1647
  this.text = this.text.slice(0, from) + text + this.text.slice(to);
1648
  this.stateAfter = null;
1649
  if (mk) {
1650
- var diff = text.length - (to - from), end = this.text.length;
1651
- function fix(n) {return n <= Math.min(to, to + diff) ? n : n + diff;}
1652
- for (var i = 0; i < mk.length; ++i) {
1653
- var mark = mk[i], del = false;
1654
- if (mark.from >= end) del = true;
1655
- else {mark.from = fix(mark.from); if (mark.to != null) mark.to = fix(mark.to);}
1656
- if (del || mark.from >= mark.to) {mk.splice(i, 1); i--;}
1657
  }
1658
  }
1659
  },
1660
- // Split a line in two, again keeping styles intact.
1661
  split: function(pos, textBefore) {
1662
- var st = [textBefore, null];
1663
  copyStyles(pos, this.text.length, this.styles, st);
1664
- return new Line(textBefore + this.text.slice(pos), st);
 
 
 
 
 
 
 
 
 
 
 
1665
  },
1666
- addMark: function(from, to, style) {
1667
- var mk = this.marked, mark = {from: from, to: to, style: style};
1668
- if (this.marked == null) this.marked = [];
1669
- this.marked.push(mark);
1670
- this.marked.sort(function(a, b){return a.from - b.from;});
1671
- return mark;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1672
  },
1673
- removeMark: function(mark) {
1674
- var mk = this.marked;
1675
  if (!mk) return;
1676
- for (var i = 0; i < mk.length; ++i)
1677
- if (mk[i] == mark) {mk.splice(i, 1); break;}
 
 
 
 
 
 
 
 
 
 
 
 
1678
  },
1679
  // Run the given mode's parser over a line, update the styles
1680
  // array, which contains alternating fragments of text and CSS
@@ -1702,10 +2196,10 @@ var CodeMirror = (function() {
1702
  }
1703
  if (st.length != pos) {st.length = pos; changed = true;}
1704
  if (pos && st[pos-2] != prevWord) changed = true;
1705
- // Short lines with simple highlights always count as changed,
1706
- // because they are likely to highlight the same way in various
1707
- // contexts.
1708
- return changed || (st.length < 5 && this.text.length < 10);
1709
  },
1710
  // Fetch the parser token for a given character. Useful for hacks
1711
  // that want to inspect the mode state (say, for completion).
@@ -1725,12 +2219,15 @@ var CodeMirror = (function() {
1725
  // Produces an HTML fragment for the line, taking selection,
1726
  // marking, and highlighting into account.
1727
  getHTML: function(sfrom, sto, includePre, endAt) {
1728
- var html = [];
1729
  if (includePre)
1730
  html.push(this.className ? '<pre class="' + this.className + '">': "<pre>");
1731
  function span(text, style) {
1732
  if (!text) return;
1733
- if (style) html.push('<span class="cm-', style, '">', htmlEscape(text), "</span>");
 
 
 
1734
  else html.push(htmlEscape(text));
1735
  }
1736
  var st = this.styles, allText = this.text, marked = this.marked;
@@ -1742,10 +2239,10 @@ var CodeMirror = (function() {
1742
  span(" ", sfrom != null && sto == null ? "CodeMirror-selected" : null);
1743
  else if (!marked && sfrom == null)
1744
  for (var i = 0, ch = 0; ch < len; i+=2) {
1745
- var str = st[i], l = str.length;
1746
  if (ch + l > len) str = str.slice(0, len - ch);
1747
  ch += l;
1748
- span(str, st[i+1]);
1749
  }
1750
  else {
1751
  var pos = 0, i = 0, text = "", style, sg = 0;
@@ -1777,18 +2274,23 @@ var CodeMirror = (function() {
1777
  }
1778
  for (;;) {
1779
  var end = pos + text.length;
1780
- var apliedStyle = style;
1781
- if (extraStyle) apliedStyle = style ? style + extraStyle : extraStyle;
1782
- span(end > upto ? text.slice(0, upto - pos) : text, apliedStyle);
1783
  if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}
1784
  pos = end;
1785
- text = st[i++]; style = st[i++];
1786
  }
1787
  }
1788
  if (sfrom != null && sto == null) span(" ", "CodeMirror-selected");
1789
  }
1790
  if (includePre) html.push("</pre>");
1791
  return html.join("");
 
 
 
 
 
1792
  }
1793
  };
1794
  // Utility used by replace and split above
@@ -1807,6 +2309,191 @@ var CodeMirror = (function() {
1807
  }
1808
  }
1809
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1810
  // The history object 'chunks' changes that are made close together
1811
  // and at almost the same time into bigger undoable units.
1812
  function History() {
@@ -1840,44 +2527,34 @@ var CodeMirror = (function() {
1840
  }
1841
  };
1842
 
1843
- // Event stopping compatibility wrapper.
1844
- function stopEvent() {
1845
- if (this.preventDefault) {this.preventDefault(); this.stopPropagation();}
1846
- else {this.returnValue = false; this.cancelBubble = true;}
1847
- }
1848
  // Ensure an event has a stop method.
1849
  function addStop(event) {
1850
- if (!event.stop) event.stop = stopEvent;
1851
  return event;
1852
  }
1853
 
1854
- // Event wrapper, exposing the few operations we need.
1855
- function Event(orig) {this.e = orig;}
1856
- Event.prototype = {
1857
- stop: function() {stopEvent.call(this.e);},
1858
- target: function() {return this.e.target || this.e.srcElement;},
1859
- button: function() {
1860
- if (this.e.which) return this.e.which;
1861
- else if (this.e.button & 1) return 1;
1862
- else if (this.e.button & 2) return 3;
1863
- else if (this.e.button & 4) return 2;
1864
- },
1865
- pageX: function() {
1866
- if (this.e.pageX != null) return this.e.pageX;
1867
- var doc = this.target().ownerDocument;
1868
- return this.e.clientX + doc.body.scrollLeft + doc.documentElement.scrollLeft;
1869
- },
1870
- pageY: function() {
1871
- if (this.e.pageY != null) return this.e.pageY;
1872
- var doc = this.target().ownerDocument;
1873
- return this.e.clientY + doc.body.scrollTop + doc.documentElement.scrollTop;
1874
- }
1875
- };
1876
 
1877
  // Event handler registration. If disconnect is true, it'll return a
1878
  // function that unregisters the handler.
1879
  function connect(node, type, handler, disconnect) {
1880
- function wrapHandler(event) {handler(new Event(event || window.event));}
1881
  if (typeof node.addEventListener == "function") {
1882
  node.addEventListener(type, wrapHandler, false);
1883
  if (disconnect) return function() {node.removeEventListener(type, wrapHandler, false);};
@@ -1891,16 +2568,18 @@ var CodeMirror = (function() {
1891
  function Delayed() {this.id = null;}
1892
  Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}};
1893
 
1894
- // Some IE versions don't preserve whitespace when setting the
1895
- // innerHTML of a PRE tag.
1896
- var badInnerHTML = (function() {
1897
- var pre = document.createElement("pre");
1898
- pre.innerHTML = " "; return !pre.innerHTML;
1899
- })();
 
 
1900
 
1901
  var gecko = /gecko\/\d{7}/i.test(navigator.userAgent);
1902
  var ie = /MSIE \d/.test(navigator.userAgent);
1903
- var safari = /Apple Computer/.test(navigator.vendor);
1904
 
1905
  var lineSep = "\n";
1906
  // Feature-detect whether newlines in textareas are converted to \r\n
@@ -1912,6 +2591,7 @@ var CodeMirror = (function() {
1912
 
1913
  var tabSize = 8;
1914
  var mac = /Mac/.test(navigator.platform);
 
1915
  var movementKeys = {};
1916
  for (var i = 35; i <= 40; ++i)
1917
  movementKeys[i] = movementKeys["c" + i] = true;
@@ -1930,21 +2610,48 @@ var CodeMirror = (function() {
1930
  return n;
1931
  }
1932
 
 
 
 
 
 
1933
  // Find the position of an element by following the offsetParent chain.
1934
  // If screen==true, it returns screen (rather than page) coordinates.
1935
  function eltOffset(node, screen) {
1936
- var doc = node.ownerDocument.body;
1937
- var x = 0, y = 0, hitDoc = false;
1938
  for (var n = node; n; n = n.offsetParent) {
1939
- x += n.offsetLeft; y += n.offsetTop;
1940
- // Fixed-position elements don't have the document in their offset chain
1941
- if (n == doc) hitDoc = true;
1942
- }
1943
- var e = screen && hitDoc ? null : doc;
 
 
 
1944
  for (var n = node.parentNode; n != e; n = n.parentNode)
1945
  if (n.scrollLeft != null) { x -= n.scrollLeft; y -= n.scrollTop;}
1946
  return {left: x, top: y};
1947
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1948
  // Get a node's text content.
1949
  function eltText(node) {
1950
  return node.textContent || node.innerText || node.nodeValue || "";
@@ -1955,11 +2662,17 @@ var CodeMirror = (function() {
1955
  function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);}
1956
  function copyPos(x) {return {line: x.line, ch: x.ch};}
1957
 
 
1958
  function htmlEscape(str) {
1959
- return str.replace(/[<>&]/g, function(str) {
1960
- return str == "&" ? "&amp;" : str == "<" ? "&lt;" : "&gt;";
1961
- });
 
 
 
 
1962
  }
 
1963
  CodeMirror.htmlEscape = htmlEscape;
1964
 
1965
  // Used to position the cursor after an undo/redo by finding the
@@ -1981,8 +2694,9 @@ var CodeMirror = (function() {
1981
 
1982
  // See if "".split is the broken IE version, if so, provide an
1983
  // alternative way to split lines.
 
1984
  if ("\n\nb".split(/\n/).length != 3)
1985
- var splitLines = function(string) {
1986
  var pos = 0, nl, result = [];
1987
  while ((nl = string.indexOf("\n", pos)) > -1) {
1988
  result.push(string.slice(pos, string.charAt(nl-1) == "\r" ? nl - 1 : nl));
@@ -1992,23 +2706,23 @@ var CodeMirror = (function() {
1992
  return result;
1993
  };
1994
  else
1995
- var splitLines = function(string){return string.split(/\r?\n/);};
1996
  CodeMirror.splitLines = splitLines;
1997
 
1998
  // Sane model of finding and setting the selection in a textarea
1999
  if (window.getSelection) {
2000
- var selRange = function(te) {
2001
  try {return {start: te.selectionStart, end: te.selectionEnd};}
2002
  catch(e) {return null;}
2003
  };
2004
- if (safari)
2005
  // On Safari, selection set with setSelectionRange are in a sort
2006
  // of limbo wrt their anchor. If you press shift-left in them,
2007
  // the anchor is put at the end, and the selection expanded to
2008
  // the left. If you press shift-right, the anchor ends up at the
2009
  // front. This is not what CodeMirror wants, so it does a
2010
  // spurious modify() call to get out of limbo.
2011
- var setSelRange = function(te, start, end) {
2012
  if (start == end)
2013
  te.setSelectionRange(start, end);
2014
  else {
@@ -2017,14 +2731,14 @@ var CodeMirror = (function() {
2017
  }
2018
  };
2019
  else
2020
- var setSelRange = function(te, start, end) {
2021
  try {te.setSelectionRange(start, end);}
2022
  catch(e) {} // Fails on Firefox when textarea isn't part of the document
2023
  };
2024
  }
2025
  // IE model. Don't ask.
2026
  else {
2027
- var selRange = function(te) {
2028
  try {var range = te.ownerDocument.selection.createRange();}
2029
  catch(e) {return null;}
2030
  if (!range || range.parentElement() != te) return null;
@@ -2046,7 +2760,7 @@ var CodeMirror = (function() {
2046
  for (var i = val.indexOf("\r"); i > -1 && i < end; i = val.indexOf("\r", i+1), end++) {}
2047
  return {start: start, end: end};
2048
  };
2049
- var setSelRange = function(te, start, end) {
2050
  var range = te.createTextRange();
2051
  range.collapse(true);
2052
  var endrange = range.duplicate();
1
+ // CodeMirror v2.18
2
+
3
  // All functions that need access to the editor's state live inside
4
  // the CodeMirror function. Below that, at the bottom of the file,
5
  // some utilities are defined.
18
  var targetDocument = options["document"];
19
  // The element in which the editor lives.
20
  var wrapper = targetDocument.createElement("div");
21
+ wrapper.className = "CodeMirror" + (options.lineWrapping ? " CodeMirror-wrap" : "");
22
  // This mess creates the base DOM structure for the editor.
23
  wrapper.innerHTML =
24
  '<div style="overflow: hidden; position: relative; width: 1px; height: 0px;">' + // Wraps and hides input textarea
25
+ '<textarea style="position: absolute; width: 10000px;" wrap="off" ' +
26
+ 'autocorrect="off" autocapitalize="off"></textarea></div>' +
27
  '<div class="CodeMirror-scroll cm-s-' + options.theme + '">' +
28
  '<div style="position: relative">' + // Set to the height of the text, causes scrolling
 
29
  '<div style="position: relative">' + // Moved around its parent to cover visible view
30
  '<div class="CodeMirror-gutter"><div class="CodeMirror-gutter-text"></div></div>' +
31
  // Provides positioning relative to (visible) text origin
32
  '<div class="CodeMirror-lines"><div style="position: relative">' +
33
+ '<div style="position: absolute; width: 100%; height: 0; overflow: hidden; visibility: hidden"></div>' +
34
  '<pre class="CodeMirror-cursor">&#160;</pre>' + // Absolutely positioned blinky cursor
35
  '<div></div>' + // This DIV contains the actual code
36
  '</div></div></div></div></div>';
38
  // I've never seen more elegant code in my life.
39
  var inputDiv = wrapper.firstChild, input = inputDiv.firstChild,
40
  scroller = wrapper.lastChild, code = scroller.firstChild,
41
+ mover = code.firstChild, gutter = mover.firstChild, gutterText = gutter.firstChild,
42
+ lineSpace = gutter.nextSibling.firstChild, measure = lineSpace.firstChild,
43
+ cursor = measure.nextSibling, lineDiv = cursor.nextSibling;
44
+ if (!webkit) lineSpace.draggable = true;
45
  if (options.tabindex != null) input.tabindex = options.tabindex;
46
  if (!options.gutter && !options.lineNumbers) gutter.style.display = "none";
47
 
48
+ // Check for problem with IE innerHTML not working when we have a
49
+ // P (or similar) parent node.
50
+ try { stringWidth("x"); }
51
+ catch (e) {
52
+ if (e.message.match(/unknown runtime/i))
53
+ e = new Error("A CodeMirror inside a P-style element does not work in Internet Explorer. (innerHTML bug)");
54
+ throw e;
55
+ }
56
+
57
  // Delayed object wrap timeouts, making sure only one is active. blinker holds an interval.
58
  var poll = new Delayed(), highlight = new Delayed(), blinker;
59
 
60
+ // mode holds a mode API object. doc is the tree of Line objects,
61
+ // work an array of lines that should be parsed, and history the
62
+ // undo history (instance of History constructor).
63
+ var mode, doc = new BranchChunk([new LeafChunk([new Line("")])]), work, focused;
 
64
  loadMode();
65
  // The selection. These are always maintained to point at valid
66
  // positions. Inverted is used to remember that the user is
70
  // whether the user is holding shift. reducedSelection is a hack
71
  // to get around the fact that we can't create inverted
72
  // selections. See below.
73
+ var shiftSelecting, reducedSelection, lastClick, lastDoubleClick, draggingText;
74
  // Variables used by startOperation/endOperation to track what
75
  // happened during the operation.
76
+ var updateInput, changes, textChanged, selectionChanged, leaveInputAlone, gutterDirty;
77
  // Current visible range (may be bigger than the view window).
78
+ var displayOffset = 0, showingFrom = 0, showingTo = 0, lastHeight = 0, curKeyId = null;
79
  // editing will hold an object describing the things we put in the
80
  // textarea, to help figure out whether something changed.
81
  // bracketHighlighted is used to remember that a backet has been
83
  var editing, bracketHighlighted;
84
  // Tracks the maximum line length so that the horizontal scrollbar
85
  // can be kept static when scrolling.
86
+ var maxLine = "", maxWidth;
87
 
88
+ // Initialize the content.
 
89
  operation(function(){setValue(options.value || ""); updateInput = false;})();
90
+ var history = new History();
91
+
92
+ var slowPollInterval = 2000;
93
+ // Gecko and Opera Linux do not reliably fire any event when starting an IME compose
94
+ var alwaysPollForIME = (!win && !mac) && (gecko || window.opera);
95
+ if (options.pollForIME && alwaysPollForIME) slowPollInterval = 50;
96
+ function keyMightStartIME(keyCode) {
97
+ return (win && ((gecko && keyCode == 229) || (window.opera && keyCode == 197))) || (mac && gecko);
98
+ }
99
 
100
  // Register our event handlers.
101
  connect(scroller, "mousedown", operation(onMouseDown));
102
+ connect(scroller, "dblclick", operation(onDoubleClick));
103
+ connect(lineSpace, "dragstart", onDragStart);
104
+ connect(lineSpace, "selectstart", e_preventDefault);
105
  // Gecko browsers fire contextmenu *after* opening the menu, at
106
  // which point we can't mess with it anymore. Context menu is
107
  // handled in onMouseDown for Gecko.
108
+ if (!gecko) connect(scroller, "contextmenu", onContextMenu);
109
+ connect(scroller, "scroll", function() {
110
+ updateDisplay([]);
111
+ if (options.fixedGutter) gutter.style.left = scroller.scrollLeft + "px";
112
+ if (options.onScroll) options.onScroll(instance);
113
+ });
114
  connect(window, "resize", function() {updateDisplay(true);});
115
  connect(input, "keyup", operation(onKeyUp));
116
+ connect(input, "input", function() {fastPoll(curKeyId);});
117
  connect(input, "keydown", operation(onKeyDown));
118
  connect(input, "keypress", operation(onKeyPress));
119
  connect(input, "focus", onFocus);
120
  connect(input, "blur", onBlur);
121
 
122
+ connect(scroller, "dragenter", e_stop);
123
+ connect(scroller, "dragover", e_stop);
124
  connect(scroller, "drop", operation(onDrop));
125
  connect(scroller, "paste", function(){focusInput(); fastPoll();});
126
  connect(input, "paste", function(){fastPoll();});
127
  connect(input, "cut", function(){fastPoll();});
128
+
129
+ // IE throws unspecified error in certain cases, when
130
  // trying to access activeElement before onload
131
  var hasFocus; try { hasFocus = (targetDocument.activeElement == input); } catch(e) { }
132
+ if (hasFocus) setTimeout(onFocus, 20);
133
  else onBlur();
134
 
135
+ function isLine(l) {return l >= 0 && l < doc.size;}
136
  // The instance object that we'll return. Mostly calls out to
137
  // local functions in the CodeMirror function. Some do some extra
138
  // range checking and/or clipping. operation is used to wrap the
139
  // call so that changes it makes are tracked, and the display is
140
  // updated afterwards.
141
+ var instance = wrapper.CodeMirror = {
142
  getValue: getValue,
143
  setValue: operation(setValue),
144
  getSelection: getSelection,
145
  replaceSelection: operation(replaceSelection),
146
+ focus: function(){focusInput(); onFocus(); fastPoll();},
147
  setOption: function(option, value) {
148
+ var oldVal = options[option];
149
  options[option] = value;
150
+ if (option == "mode" || option == "indentUnit") loadMode();
 
151
  else if (option == "readOnly" && value == "nocursor") input.blur();
152
  else if (option == "theme") scroller.className = scroller.className.replace(/cm-s-\w+/, "cm-s-" + value);
153
+ else if (option == "lineWrapping" && oldVal != value) operation(wrappingChanged)();
154
+ else if (option == "pollForIME" && alwaysPollForIME) slowPollInterval = value ? 50 : 2000;
155
+ if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber" || option == "theme")
156
+ operation(gutterChanged)();
157
  },
158
  getOption: function(option) {return options[option];},
159
  undo: operation(undo),
160
  redo: operation(redo),
161
+ indentLine: operation(function(n, dir) {
162
+ if (isLine(n)) indentLine(n, dir == null ? "smart" : dir ? "add" : "subtract");
163
+ }),
164
  historySize: function() {return {undo: history.done.length, redo: history.undone.length};},
165
+ clearHistory: function() {history = new History();},
166
  matchBrackets: operation(function(){matchBrackets(true);}),
167
+ getTokenAt: operation(function(pos) {
168
  pos = clipPos(pos);
169
+ return getLine(pos.line).getTokenAt(mode, getStateBefore(pos.line), pos.ch);
170
+ }),
171
+ getStateAfter: function(line) {
172
+ line = clipLine(line == null ? doc.size - 1: line);
173
+ return getStateBefore(line + 1);
174
  },
175
  cursorCoords: function(start){
176
  if (start == null) start = sel.inverted;
179
  charCoords: function(pos){return pageCoords(clipPos(pos));},
180
  coordsChar: function(coords) {
181
  var off = eltOffset(lineSpace);
182
+ return coordsChar(coords.x - off.left, coords.y - off.top);
 
183
  },
184
  getSearchCursor: function(query, pos, caseFold) {return new SearchCursor(query, pos, caseFold);},
185
+ markText: operation(markText),
186
+ setBookmark: setBookmark,
187
+ setMarker: operation(addGutterMarker),
188
+ clearMarker: operation(removeGutterMarker),
189
  setLineClass: operation(setLineClass),
190
+ hideLine: operation(function(h) {return setLineHidden(h, true);}),
191
+ showLine: operation(function(h) {return setLineHidden(h, false);}),
192
  lineInfo: lineInfo,
193
+ addWidget: function(pos, node, scroll, vert, horiz) {
194
+ pos = localCoords(clipPos(pos));
195
+ var top = pos.yBot, left = pos.x;
196
+ node.style.position = "absolute";
197
  code.appendChild(node);
198
+ if (vert == "over") top = pos.y;
199
+ else if (vert == "near") {
200
+ var vspace = Math.max(scroller.offsetHeight, doc.height * textHeight()),
201
+ hspace = Math.max(code.clientWidth, lineSpace.clientWidth) - paddingLeft();
202
+ if (pos.yBot + node.offsetHeight > vspace && pos.y > node.offsetHeight)
203
+ top = pos.y - node.offsetHeight;
204
+ if (left + node.offsetWidth > hspace)
205
+ left = hspace - node.offsetWidth;
206
+ }
207
+ node.style.top = (top + paddingTop()) + "px";
208
+ node.style.left = node.style.right = "";
209
+ if (horiz == "right") {
210
+ left = code.clientWidth - node.offsetWidth;
211
+ node.style.right = "0px";
212
+ } else {
213
+ if (horiz == "left") left = 0;
214
+ else if (horiz == "middle") left = (code.clientWidth - node.offsetWidth) / 2;
215
+ node.style.left = (left + paddingLeft()) + "px";
216
+ }
217
  if (scroll)
218
+ scrollIntoView(left, top, left + node.offsetWidth, top + node.offsetHeight);
219
  },
220
 
221
+ lineCount: function() {return doc.size;},
222
  getCursor: function(start) {
223
  if (start == null) start = sel.inverted;
224
  return copyPos(start ? sel.from : sel.to);
229
  else setCursor(line, ch);
230
  }),
231
  setSelection: operation(function(from, to) {setSelection(clipPos(from), clipPos(to || from));}),
232
+ getLine: function(line) {if (isLine(line)) return getLine(line).text;},
233
  setLine: operation(function(line, text) {
234
+ if (isLine(line)) replaceRange(text, {line: line, ch: 0}, {line: line, ch: getLine(line).text.length});
235
  }),
236
  removeLine: operation(function(line) {
237
  if (isLine(line)) replaceRange("", {line: line, ch: 0}, clipPos({line: line+1, ch: 0}));
239
  replaceRange: operation(replaceRange),
240
  getRange: function(from, to) {return getRange(clipPos(from), clipPos(to));},
241
 
242
+ coordsFromIndex: function(off) {
243
+ var lineNo = 0, ch;
244
+ doc.iter(0, doc.size, function(line) {
245
+ var sz = line.text.length + 1;
246
+ if (sz > off) { ch = off; return true; }
247
+ off -= sz;
248
+ ++lineNo;
249
+ });
250
+ return clipPos({line: lineNo, ch: ch});
251
+ },
252
+
253
  operation: function(f){return operation(f)();},
254
  refresh: function(){updateDisplay(true);},
255
  getInputField: function(){return input;},
256
+ getWrapperElement: function(){return wrapper;},
257
+ getScrollerElement: function(){return scroller;},
258
+ getGutterElement: function(){return gutter;}
259
  };
260
 
261
+ function getLine(n) { return getLineAt(doc, n); }
262
+ function updateLineHeight(line, height) {
263
+ gutterDirty = true;
264
+ var diff = height - line.height;
265
+ for (var n = line; n; n = n.parent) n.height += diff;
266
+ }
267
+
268
  function setValue(code) {
 
269
  var top = {line: 0, ch: 0};
270
+ updateLines(top, {line: doc.size - 1, ch: getLine(doc.size-1).text.length},
271
  splitLines(code), top, top);
272
+ updateInput = true;
273
  }
274
  function getValue(code) {
275
  var text = [];
276
+ doc.iter(0, doc.size, function(line) { text.push(line.text); });
 
277
  return text.join("\n");
278
  }
279
 
280
  function onMouseDown(e) {
281
+ // Check whether this is a click in a widget
282
+ for (var n = e_target(e); n != wrapper; n = n.parentNode)
283
+ if (n.parentNode == code && n != mover) return;
284
+
285
+ // See if this is a click in the gutter
286
+ for (var n = e_target(e); n != wrapper; n = n.parentNode)
287
  if (n.parentNode == gutterText) {
288
  if (options.onGutterClick)
289
+ options.onGutterClick(instance, indexOf(gutterText.childNodes, n) + showingFrom, e);
290
+ return e_preventDefault(e);
291
  }
292
 
293
+ var start = posFromMouse(e);
294
+
295
+ switch (e_button(e)) {
296
+ case 3:
297
+ if (gecko && !mac) onContextMenu(e);
298
+ return;
299
+ case 2:
300
+ if (start) setCursor(start.line, start.ch, true);
301
+ return;
302
+ }
303
  // For button 1, if it was clicked inside the editor
304
  // (posFromMouse returning non-null), we have to adjust the
305
  // selection.
306
+ if (!start) {if (e_target(e) == scroller) e_preventDefault(e); return;}
 
307
 
308
  if (!focused) onFocus();
 
 
309
 
310
+ var now = +new Date;
311
+ if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) {
312
+ e_preventDefault(e);
313
+ setTimeout(focusInput, 20);
314
+ return selectLine(start.line);
315
+ } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) {
316
+ lastDoubleClick = {time: now, pos: start};
317
+ e_preventDefault(e);
318
+ return selectWordAt(start);
319
+ } else { lastClick = {time: now, pos: start}; }
320
+
321
+ var last = start, going;
322
+ if (dragAndDrop && !posEq(sel.from, sel.to) &&
323
+ !posLess(start, sel.from) && !posLess(sel.to, start)) {
324
+ // Let the drag handler handle this.
325
+ if (webkit) lineSpace.draggable = true;
326
+ var up = connect(targetDocument, "mouseup", operation(function(e2) {
327
+ if (webkit) lineSpace.draggable = false;
328
+ draggingText = false;
329
+ up();
330
+ if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
331
+ e_preventDefault(e2);
332
+ setCursor(start.line, start.ch, true);
333
+ focusInput();
334
+ }
335
+ }), true);
336
+ draggingText = true;
337
+ return;
338
  }
339
+ e_preventDefault(e);
340
+ setCursor(start.line, start.ch, true);
341
+
342
  function extend(e) {
343
  var cur = posFromMouse(e, true);
344
  if (cur && !posEq(cur, last)) {
354
 
355
  var move = connect(targetDocument, "mousemove", operation(function(e) {
356
  clearTimeout(going);
357
+ e_preventDefault(e);
358
  extend(e);
359
  }), true);
360
  var up = connect(targetDocument, "mouseup", operation(function(e) {
361
  clearTimeout(going);
362
  var cur = posFromMouse(e);
363
  if (cur) setSelectionUser(start, cur);
364
+ e_preventDefault(e);
365
+ focusInput();
366
+ updateInput = true;
367
+ move(); up();
368
  }), true);
369
  }
370
+ function onDoubleClick(e) {
371
+ for (var n = e_target(e); n != wrapper; n = n.parentNode)
372
+ if (n.parentNode == gutterText) return e_preventDefault(e);
373
+ var start = posFromMouse(e);
374
+ if (!start) return;
375
+ lastDoubleClick = {time: +new Date, pos: start};
376
+ e_preventDefault(e);
377
+ selectWordAt(start);
378
  }
379
  function onDrop(e) {
380
+ e.preventDefault();
381
+ var pos = posFromMouse(e, true), files = e.dataTransfer.files;
382
  if (!pos || options.readOnly) return;
383
  if (files && files.length && window.FileReader && window.File) {
 
 
384
  function loadFile(file, i) {
385
  var reader = new FileReader;
386
  reader.onload = function() {
387
  text[i] = reader.result;
388
+ if (++read == n) {
389
+ pos = clipPos(pos);
390
+ operation(function() {
391
+ var end = replaceRange(text.join(""), pos, pos);
392
+ setSelectionUser(pos, end);
393
+ })();
394
+ }
395
  };
396
  reader.readAsText(file);
397
  }
398
+ var n = files.length, text = Array(n), read = 0;
399
+ for (var i = 0; i < n; ++i) loadFile(files[i], i);
400
  }
401
  else {
402
  try {
403
+ var text = e.dataTransfer.getData("Text");
404
+ if (text) {
405
+ var end = replaceRange(text, pos, pos);
406
+ var curFrom = sel.from, curTo = sel.to;
407
+ setSelectionUser(pos, end);
408
+ if (draggingText) replaceRange("", curFrom, curTo);
409
+ focusInput();
410
+ }
411
  }
412
  catch(e){}
413
  }
414
  }
415
+ function onDragStart(e) {
416
+ var txt = getSelection();
417
+ // This will reset escapeElement
418
+ htmlEscape(txt);
419
+ e.dataTransfer.setDragImage(escapeElement, 0, 0);
420
+ e.dataTransfer.setData("Text", txt);
421
+ }
422
  function onKeyDown(e) {
423
  if (!focused) onFocus();
424
 
425
+ var code = e.keyCode;
426
  // IE does strange things with escape.
427
+ if (ie && code == 27) { e.returnValue = false; }
428
  // Tries to detect ctrl on non-mac, cmd on mac.
429
+ var mod = (mac ? e.metaKey : e.ctrlKey) && !e.altKey, anyMod = e.ctrlKey || e.altKey || e.metaKey;
430
+ if (code == 16 || e.shiftKey) shiftSelecting = shiftSelecting || (sel.inverted ? sel.to : sel.from);
431
  else shiftSelecting = null;
432
  // First give onKeyEvent option a chance to handle this.
433
+ if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
434
 
435
+ if (code == 33 || code == 34) {scrollPage(code == 34); return e_preventDefault(e);} // page up/down
436
  if (mod && ((code == 36 || code == 35) || // ctrl-home/end
437
  mac && (code == 38 || code == 40))) { // cmd-up/down
438
+ scrollEnd(code == 36 || code == 38); return e_preventDefault(e);
439
  }
440
+ if (mod && code == 65) {selectAll(); return e_preventDefault(e);} // ctrl-a
441
  if (!options.readOnly) {
442
  if (!anyMod && code == 13) {return;} // enter
443
+ if (!anyMod && code == 9 && handleTab(e.shiftKey)) return e_preventDefault(e); // tab
444
+ if (mod && code == 90) {undo(); return e_preventDefault(e);} // ctrl-z
445
+ if (mod && ((e.shiftKey && code == 90) || code == 89)) {redo(); return e_preventDefault(e);} // ctrl-shift-z, ctrl-y
446
  }
447
+ if (code == 36) { if (options.smartHome) { smartHome(); return e_preventDefault(e); } }
448
 
449
  // Key id to use in the movementKeys map. We also pass it to
450
  // fastPoll in order to 'self learn'. We need this because
452
  // its start when it is inverted and a movement key is pressed
453
  // (and later restore it again), shouldn't be used for
454
  // non-movement keys.
455
+ curKeyId = (mod ? "c" : "") + (e.altKey ? "a" : "") + code;
456
+ if (sel.inverted && movementKeys[curKeyId] === true) {
457
  var range = selRange(input);
458
  if (range) {
459
  reducedSelection = {anchor: range.start};
460
  setSelRange(input, range.start, range.start);
461
  }
462
  }
463
+ // Don't save the key as a movementkey unless it had a modifier
464
+ if (!mod && !e.altKey) curKeyId = null;
465
  fastPoll(curKeyId);
466
+
467
+ if (options.pollForIME && keyMightStartIME(code)) slowPollInterval = 50;
468
  }
469
  function onKeyUp(e) {
470
+ if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
471
  if (reducedSelection) {
472
  reducedSelection = null;
473
  updateInput = true;
474
  }
475
+ if (e.keyCode == 16) shiftSelecting = null;
476
+
477
+ if (slowPollInterval < 2000 && !alwaysPollForIME) slowPollInterval = 2000;
478
  }
479
  function onKeyPress(e) {
480
+ if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
481
  if (options.electricChars && mode.electricChars) {
482
+ var ch = String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode);
483
  if (mode.electricChars.indexOf(ch) > -1)
484
  setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 50);
485
  }
486
+ var code = e.keyCode;
487
  // Re-stop tab and enter. Necessary on some browsers.
488
+ if (code == 13) {if (!options.readOnly) handleEnter(); e_preventDefault(e);}
489
+ else if (!e.ctrlKey && !e.altKey && !e.metaKey && code == 9 && options.tabMode != "default") e_preventDefault(e);
490
  else fastPoll(curKeyId);
491
  }
492
 
493
  function onFocus() {
494
  if (options.readOnly == "nocursor") return;
495
+ if (!focused) {
496
+ if (options.onFocus) options.onFocus(instance);
497
+ focused = true;
498
+ if (wrapper.className.search(/\bCodeMirror-focused\b/) == -1)
499
+ wrapper.className += " CodeMirror-focused";
500
+ if (!leaveInputAlone) prepareInput();
501
+ }
502
  slowPoll();
 
 
503
  restartBlink();
504
  }
505
  function onBlur() {
506
+ if (focused) {
507
+ if (options.onBlur) options.onBlur(instance);
508
+ focused = false;
509
+ wrapper.className = wrapper.className.replace(" CodeMirror-focused", "");
510
+ }
511
  clearInterval(blinker);
512
+ setTimeout(function() {if (!focused) shiftSelecting = null;}, 150);
 
 
513
  }
514
 
515
  // Replace the range from from to to by the strings in newText.
517
  function updateLines(from, to, newText, selFrom, selTo) {
518
  if (history) {
519
  var old = [];
520
+ doc.iter(from.line, to.line + 1, function(line) { old.push(line.text); });
521
  history.addChange(from.line, newText.length, old);
522
  while (history.done.length > options.undoDepth) history.done.shift();
523
  }
524
  updateLinesNoUndo(from, to, newText, selFrom, selTo);
 
 
525
  }
526
  function unredoHelper(from, to) {
527
  var change = from.pop();
528
  if (change) {
529
  var replaced = [], end = change.start + change.added;
530
+ doc.iter(change.start, end, function(line) { replaced.push(line.text); });
531
  to.push({start: change.start, added: change.old.length, old: replaced});
532
  var pos = clipPos({line: change.start + change.old.length - 1,
533
  ch: editEnd(replaced[replaced.length-1], change.old[change.old.length-1])});
534
+ updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: getLine(end-1).text.length}, change.old, pos, pos);
535
+ updateInput = true;
536
  }
537
  }
538
  function undo() {unredoHelper(history.done, history.undone);}
540
 
541
  function updateLinesNoUndo(from, to, newText, selFrom, selTo) {
542
  var recomputeMaxLength = false, maxLineLength = maxLine.length;
543
+ if (!options.lineWrapping)
544
+ doc.iter(from.line, to.line, function(line) {
545
+ if (line.text.length == maxLineLength) {recomputeMaxLength = true; return true;}
546
+ });
547
 
548
+ var nlines = to.line - from.line, firstLine = getLine(from.line), lastLine = getLine(to.line);
549
  // First adjust the line structure, taking some care to leave highlighting intact.
550
  if (firstLine == lastLine) {
551
  if (newText.length == 1)
552
  firstLine.replace(from.ch, to.ch, newText[0]);
553
  else {
554
  lastLine = firstLine.split(to.ch, newText[newText.length-1]);
555
+ firstLine.replace(from.ch, null, newText[0]);
556
+ firstLine.fixMarkEnds(lastLine);
557
+ var added = [];
558
+ for (var i = 1, e = newText.length - 1; i < e; ++i)
559
+ added.push(Line.inheritMarks(newText[i], firstLine));
560
+ added.push(lastLine);
561
+ doc.insert(from.line + 1, added);
562
  }
563
  }
564
  else if (newText.length == 1) {
565
+ firstLine.replace(from.ch, null, newText[0]);
566
+ lastLine.replace(null, to.ch, "");
567
+ firstLine.append(lastLine);
568
+ doc.remove(from.line + 1, nlines);
569
  }
570
  else {
571
+ var added = [];
572
+ firstLine.replace(from.ch, null, newText[0]);
573
+ lastLine.replace(null, to.ch, newText[newText.length-1]);
574
+ firstLine.fixMarkEnds(lastLine);
575
+ for (var i = 1, e = newText.length - 1; i < e; ++i)
576
+ added.push(Line.inheritMarks(newText[i], firstLine));
577
+ if (nlines > 1) doc.remove(from.line + 1, nlines - 1);
578
+ doc.insert(from.line + 1, added);
579
+ }
580
+ if (options.lineWrapping) {
581
+ var perLine = scroller.clientWidth / charWidth() - 3;
582
+ doc.iter(from.line, from.line + newText.length, function(line) {
583
+ if (line.hidden) return;
584
+ var guess = Math.ceil(line.text.length / perLine) || 1;
585
+ if (guess != line.height) updateLineHeight(line, guess);
586
+ });
587
+ } else {
588
+ doc.iter(from.line, i + newText.length, function(line) {
589
+ var l = line.text;
590
  if (l.length > maxLineLength) {
591
+ maxLine = l; maxLineLength = l.length; maxWidth = null;
592
+ recomputeMaxLength = false;
593
  }
594
+ });
595
+ if (recomputeMaxLength) {
596
+ maxLineLength = 0; maxLine = ""; maxWidth = null;
597
+ doc.iter(0, doc.size, function(line) {
598
+ var l = line.text;
599
+ if (l.length > maxLineLength) {
600
+ maxLineLength = l.length; maxLine = l;
601
+ }
602
+ });
603
  }
604
  }
605
 
611
  if (task < from.line) newWork.push(task);
612
  else if (task > to.line) newWork.push(task + lendiff);
613
  }
614
+ var hlEnd = from.line + Math.min(newText.length, 500);
615
+ highlightLines(from.line, hlEnd);
616
+ newWork.push(hlEnd);
617
  work = newWork;
618
  startWorker(100);
619
  // Remember that these lines changed, for updating the display
625
  setSelection(selFrom, selTo, updateLine(sel.from.line), updateLine(sel.to.line));
626
 
627
  // Make sure the scroll-size div has the correct height.
628
+ code.style.height = (doc.height * textHeight() + 2 * paddingTop()) + "px";
629
  }
630
 
631
  function replaceRange(code, from, to) {
663
 
664
  function getRange(from, to) {
665
  var l1 = from.line, l2 = to.line;
666
+ if (l1 == l2) return getLine(l1).text.slice(from.ch, to.ch);
667
+ var code = [getLine(l1).text.slice(from.ch)];
668
+ doc.iter(l1 + 1, l2, function(line) { code.push(line.text); });
669
+ code.push(getLine(l2).text.slice(0, to.ch));
670
  return code.join("\n");
671
  }
672
  function getSelection() {
676
  var pollingFast = false; // Ensures slowPoll doesn't cancel fastPoll
677
  function slowPoll() {
678
  if (pollingFast) return;
679
+ poll.set(slowPollInterval, function() {
680
  startOperation();
681
  readInput();
682
  if (focused) slowPoll();
689
  function p() {
690
  startOperation();
691
  var changed = readInput();
692
+ if (changed && keyId) {
693
+ if (changed == "moved" && movementKeys[keyId] == null) movementKeys[keyId] = true;
694
+ if (changed == "changed") movementKeys[keyId] = false;
695
+ }
696
  if (!changed && !missed) {missed = true; poll.set(80, p);}
697
  else {pollingFast = false; slowPoll();}
698
  endOperation();
704
  // to the data in the editing variable, and updates the editor
705
  // content or cursor if something changed.
706
  function readInput() {
707
+ if (leaveInputAlone || !focused) return;
708
  var changed = false, text = input.value, sr = selRange(input);
709
  if (!sr) return false;
710
  var changed = editing.text != text, rs = reducedSelection;
774
  // editor state.
775
  function prepareInput() {
776
  var text = [];
777
+ var from = Math.max(0, sel.from.line - 1), to = Math.min(doc.size, sel.to.line + 2);
778
+ doc.iter(from, to, function(line) { text.push(line.text); });
779
  text = input.value = text.join(lineSep);
780
  var startch = sel.from.ch, endch = sel.to.ch;
781
+ doc.iter(from, sel.from.line, function(line) {
782
+ startch += lineSep.length + line.text.length;
783
+ });
784
+ doc.iter(from, sel.to.line, function(line) {
785
+ endch += lineSep.length + line.text.length;
786
+ });
787
  editing = {text: text, from: from, to: to, start: startch, end: endch};
788
  setSelRange(input, startch, reducedSelection ? startch : endch);
789
  }
791
  if (options.readOnly != "nocursor") input.focus();
792
  }
793
 
794
+ function scrollEditorIntoView() {
795
+ if (!cursor.getBoundingClientRect) return;
796
+ var rect = cursor.getBoundingClientRect();
797
+ var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight);
798
+ if (rect.top < 0 || rect.bottom > winH) cursor.scrollIntoView();
799
+ }
800
  function scrollCursorIntoView() {
801
  var cursor = localCoords(sel.inverted ? sel.from : sel.to);
802
+ var x = options.lineWrapping ? Math.min(cursor.x, lineSpace.offsetWidth) : cursor.x;
803
+ return scrollIntoView(x, cursor.y, x, cursor.yBot);
804
  }
805
  function scrollIntoView(x1, y1, x2, y2) {
806
+ var pl = paddingLeft(), pt = paddingTop(), lh = textHeight();
807
  y1 += pt; y2 += pt; x1 += pl; x2 += pl;
808
  var screen = scroller.clientHeight, screentop = scroller.scrollTop, scrolled = false, result = true;
809
  if (y1 < screentop) {scroller.scrollTop = Math.max(0, y1 - 2*lh); scrolled = true;}
810
  else if (y2 > screentop + screen) {scroller.scrollTop = y2 + lh - screen; scrolled = true;}
811
 
812
  var screenw = scroller.clientWidth, screenleft = scroller.scrollLeft;
813
+ var gutterw = options.fixedGutter ? gutter.clientWidth : 0;
814
+ if (x1 < screenleft + gutterw) {
815
  if (x1 < 50) x1 = 0;
816
+ scroller.scrollLeft = Math.max(0, x1 - 10 - gutterw);
817
  scrolled = true;
818
  }
819
  else if (x2 > screenw + screenleft) {
826
  }
827
 
828
  function visibleLines() {
829
+ var lh = textHeight(), top = scroller.scrollTop - paddingTop();
830
+ var from_height = Math.max(0, Math.floor(top / lh));
831
+ var to_height = Math.ceil((top + scroller.clientHeight) / lh);
832
+ return {from: lineAtHeight(doc, from_height),
833
+ to: lineAtHeight(doc, to_height)};
834
  }
835
  // Uses a set of changes plus the current scroll position to
836
  // determine which DOM updates have to be made, and makes the
837
  // updates.
838
  function updateDisplay(changes) {
839
  if (!scroller.clientWidth) {
840
+ showingFrom = showingTo = displayOffset = 0;
841
  return;
842
  }
843
+ // Compute the new visible window
844
+ var visible = visibleLines();
845
+ // Bail out if the visible area is already rendered and nothing changed.
846
+ if (changes !== true && changes.length == 0 && visible.from >= showingFrom && visible.to <= showingTo) return;
847
+ var from = Math.max(visible.from - 100, 0), to = Math.min(doc.size, visible.to + 100);
848
+ if (showingFrom < from && from - showingFrom < 20) from = showingFrom;
849
+ if (showingTo > to && showingTo - to < 20) to = Math.min(doc.size, showingTo);
850
+
851
+ // Create a range of theoretically intact lines, and punch holes
852
+ // in that using the change info.
853
+ var intact = changes === true ? [] :
854
+ computeIntact([{from: showingFrom, to: showingTo, domStart: 0}], changes);
855
+ // Clip off the parts that won't be visible
856
+ var intactLines = 0;
857
+ for (var i = 0; i < intact.length; ++i) {
858
+ var range = intact[i];
859
+ if (range.from < from) {range.domStart += (from - range.from); range.from = from;}
860
+ if (range.to > to) range.to = to;
861
+ if (range.from >= range.to) intact.splice(i--, 1);
862
+ else intactLines += range.to - range.from;
863
+ }
864
+ if (intactLines == to - from) return;
865
+ intact.sort(function(a, b) {return a.domStart - b.domStart;});
866
+
867
+ var th = textHeight(), gutterDisplay = gutter.style.display;
868
+ lineDiv.style.display = gutter.style.display = "none";
869
+ patchDisplay(from, to, intact);
870
+ lineDiv.style.display = "";
871
+
872
+ // Position the mover div to align with the lines it's supposed
873
+ // to be showing (which will cover the visible display)
874
+ var different = from != showingFrom || to != showingTo || lastHeight != scroller.clientHeight;
875
+ if (different) lastHeight = scroller.clientHeight;
876
+ showingFrom = from; showingTo = to;
877
+ displayOffset = heightAtLine(doc, from);
878
+ mover.style.top = (displayOffset * th) + "px";
879
+ code.style.height = (doc.height * th + 2 * paddingTop()) + "px";
880
+
881
+ // Since this is all rather error prone, it is honoured with the
882
+ // only assertion in the whole file.
883
+ if (lineDiv.childNodes.length != showingTo - showingFrom)
884
+ throw new Error("BAD PATCH! " + JSON.stringify(intact) + " size=" + (showingTo - showingFrom) +
885
+ " nodes=" + lineDiv.childNodes.length);
886
+
887
+ if (options.lineWrapping) {
888
+ maxWidth = scroller.clientWidth;
889
+ var curNode = lineDiv.firstChild;
890
+ doc.iter(showingFrom, showingTo, function(line) {
891
+ if (!line.hidden) {
892
+ var height = Math.round(curNode.offsetHeight / th) || 1;
893
+ if (line.height != height) {updateLineHeight(line, height); gutterDirty = true;}
894
+ }
895
+ curNode = curNode.nextSibling;
896
+ });
897
+ } else {
898
+ if (maxWidth == null) maxWidth = stringWidth(maxLine);
899
+ if (maxWidth > scroller.clientWidth) {
900
+ lineSpace.style.width = maxWidth + "px";
901
+ // Needed to prevent odd wrapping/hiding of widgets placed in here.
902
+ code.style.width = "";
903
+ code.style.width = scroller.scrollWidth + "px";
904
+ } else {
905
+ lineSpace.style.width = code.style.width = "";
906
+ }
907
+ }
908
+ gutter.style.display = gutterDisplay;
909
+ if (different || gutterDirty) updateGutter();
910
+ updateCursor();
911
+ }
912
+
913
+ function computeIntact(intact, changes) {
914
  for (var i = 0, l = changes.length || 0; i < l; ++i) {
915
  var change = changes[i], intact2 = [], diff = change.diff || 0;
916
  for (var j = 0, l2 = intact.length; j < l2; ++j) {
917
  var range = intact[j];
918
+ if (change.to <= range.from && change.diff)
919
+ intact2.push({from: range.from + diff, to: range.to + diff,
920
+ domStart: range.domStart});
921
+ else if (change.to <= range.from || change.from >= range.to)
922
  intact2.push(range);
923
  else {
924
  if (change.from > range.from)
925
+ intact2.push({from: range.from, to: change.from, domStart: range.domStart});
926
  if (change.to < range.to)
927
  intact2.push({from: change.to + diff, to: range.to + diff,
928
  domStart: range.domStart + (change.to - range.from)});
930
  }
931
  intact = intact2;
932
  }
933
+ return intact;
934
+ }
935
 
936
+ function patchDisplay(from, to, intact) {
937
+ // The first pass removes the DOM nodes that aren't intact.
938
+ if (!intact.length) lineDiv.innerHTML = "";
939
+ else {
940
+ function killNode(node) {
941
+ var tmp = node.nextSibling;
942
+ node.parentNode.removeChild(node);
943
+ return tmp;
 
 
 
 
 
 
944
  }
945
+ var domPos = 0, curNode = lineDiv.firstChild, n;
946
+ for (var i = 0; i < intact.length; ++i) {
947
+ var cur = intact[i];
948
+ while (cur.domStart > domPos) {curNode = killNode(curNode); domPos++;}
949
+ for (var j = 0, e = cur.to - cur.from; j < e; ++j) {curNode = curNode.nextSibling; domPos++;}
950
+ }
951
+ while (curNode) curNode = killNode(curNode);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
952
  }
953
+ // This pass fills in the lines that actually changed.
954
+ var nextIntact = intact.shift(), curNode = lineDiv.firstChild, j = from;
955
+ var sfrom = sel.from.line, sto = sel.to.line, inSel = sfrom < from && sto >= from;
956
+ var scratch = targetDocument.createElement("div"), newElt;
957
+ doc.iter(from, to, function(line) {
 
 
 
 
 
 
 
 
 
 
958
  var ch1 = null, ch2 = null;
959
  if (inSel) {
960
  ch1 = 0;
961
+ if (sto == j) {inSel = false; ch2 = sel.to.ch;}
962
+ } else if (sfrom == j) {
963
+ if (sto == j) {ch1 = sel.from.ch; ch2 = sel.to.ch;}
 
964
  else {inSel = true; ch1 = sel.from.ch;}
965
  }
966
+ if (nextIntact && nextIntact.to == j) nextIntact = intact.shift();
967
+ if (!nextIntact || nextIntact.from > j) {
968
+ if (line.hidden) scratch.innerHTML = "<pre></pre>";
969
+ else scratch.innerHTML = line.getHTML(ch1, ch2, true);
970
+ lineDiv.insertBefore(scratch.firstChild, curNode);
971
+ } else {
972
+ curNode = curNode.nextSibling;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
973
  }
974
+ ++j;
975
+ });
976
  }
977
 
978
  function updateGutter() {
979
  if (!options.gutter && !options.lineNumbers) return;
980
  var hText = mover.offsetHeight, hEditor = scroller.clientHeight;
981
  gutter.style.height = (hText - hEditor < 2 ? hEditor : hText) + "px";
982
+ var html = [], i = showingFrom;
983
+ doc.iter(showingFrom, Math.max(showingTo, showingFrom + 1), function(line) {
984
+ if (line.hidden) {
985
+ html.push("<pre></pre>");
986
+ } else {
987
+ var marker = line.gutterMarker;
988
+ var text = options.lineNumbers ? i + options.firstLineNumber : null;
989
+ if (marker && marker.text)
990
+ text = marker.text.replace("%N%", text != null ? text : "");
991
+ else if (text == null)
992
+ text = "\u00a0";
993
+ html.push((marker && marker.style ? '<pre class="' + marker.style + '">' : "<pre>"), text);
994
+ for (var j = 1; j < line.height; ++j) html.push("<br>&nbsp;");
995
+ html.push("</pre>");
996
+ }
997
+ ++i;
998
+ });
999
  gutter.style.display = "none";
1000
  gutterText.innerHTML = html.join("");
1001
+ var minwidth = String(doc.size).length, firstNode = gutterText.firstChild, val = eltText(firstNode), pad = "";
1002
  while (val.length + pad.length < minwidth) pad += "\u00a0";
1003
  if (pad) firstNode.insertBefore(targetDocument.createTextNode(pad), firstNode.firstChild);
1004
  gutter.style.display = "";
1005
  lineSpace.style.marginLeft = gutter.offsetWidth + "px";
1006
+ gutterDirty = false;
1007
  }
1008
  function updateCursor() {
1009
+ var head = sel.inverted ? sel.from : sel.to, lh = textHeight();
1010
+ var pos = localCoords(head, true);
1011
+ var globalY = pos.y + displayOffset * textHeight();
1012
+ inputDiv.style.top = Math.max(Math.min(globalY, scroller.offsetHeight), 0) + "px";
1013
+ inputDiv.style.left = (pos.x - scroller.scrollLeft) + "px";
1014
  if (posEq(sel.from, sel.to)) {
1015
+ cursor.style.top = pos.y + "px";
1016
+ cursor.style.left = (options.lineWrapping ? Math.min(pos.x, lineSpace.offsetWidth) : pos.x) + "px";
1017
  cursor.style.display = "";
1018
  }
1019
  else cursor.style.display = "none";
1031
  // updateLines, since they have to be expressed in the line
1032
  // numbers before the update.
1033
  function setSelection(from, to, oldFrom, oldTo) {
1034
+ if (oldFrom == null) {oldFrom = sel.from.line; oldTo = sel.to.line;}
1035
  if (posEq(sel.from, from) && posEq(sel.to, to)) return;
1036
  if (posLess(to, from)) {var tmp = to; to = from; from = tmp;}
1037
 
1038
+ // Skip over hidden lines.
1039
+ if (from.line != oldFrom) from = skipHidden(from, oldFrom, sel.from.ch);
1040
+ if (to.line != oldTo) to = skipHidden(to, oldTo, sel.to.ch);
1041
+
1042
  if (posEq(from, to)) sel.inverted = false;
1043
  else if (posEq(from, sel.to)) sel.inverted = false;
1044
  else if (posEq(to, sel.from)) sel.inverted = true;
1046
  // Some ugly logic used to only mark the lines that actually did
1047
  // see a change in selection as changed, rather than the whole
1048
  // selected range.
 
1049
  if (posEq(from, to)) {
1050
  if (!posEq(sel.from, sel.to))
1051
  changes.push({from: oldFrom, to: oldTo + 1});
1070
  sel.from = from; sel.to = to;
1071
  selectionChanged = true;
1072
  }
1073
+ function skipHidden(pos, oldLine, oldCh) {
1074
+ function getNonHidden(dir) {
1075
+ var lNo = pos.line + dir, end = dir == 1 ? doc.size : -1;
1076
+ while (lNo != end) {
1077
+ var line = getLine(lNo);
1078
+ if (!line.hidden) {
1079
+ var ch = pos.ch;
1080
+ if (ch > oldCh || ch > line.text.length) ch = line.text.length;
1081
+ return {line: lNo, ch: ch};
1082
+ }
1083
+ lNo += dir;
1084
+ }
1085
+ }
1086
+ var line = getLine(pos.line);
1087
+ if (!line.hidden) return pos;
1088
+ if (pos.line >= oldLine) return getNonHidden(1) || getNonHidden(-1);
1089
+ else return getNonHidden(-1) || getNonHidden(1);
1090
+ }
1091
  function setCursor(line, ch, user) {
1092
  var pos = clipPos({line: line, ch: ch || 0});
1093
  (user ? setSelectionUser : setSelection)(pos, pos);
1094
  }
1095
 
1096
+ function clipLine(n) {return Math.max(0, Math.min(n, doc.size-1));}
1097
  function clipPos(pos) {
1098
  if (pos.line < 0) return {line: 0, ch: 0};
1099
+ if (pos.line >= doc.size) return {line: doc.size-1, ch: getLine(doc.size-1).text.length};
1100
+ var ch = pos.ch, linelen = getLine(pos.line).text.length;
1101
  if (ch == null || ch > linelen) return {line: pos.line, ch: linelen};
1102
  else if (ch < 0) return {line: pos.line, ch: 0};
1103
  else return pos;
1104
  }
1105
 
1106
  function scrollPage(down) {
1107
+ var linesPerPage = Math.floor(scroller.clientHeight / textHeight()), head = sel.inverted ? sel.from : sel.to;
1108
+ var target = heightAtLine(doc, head.line) + (Math.max(linesPerPage - 1, 1) * (down ? 1 : -1));
1109
+ setCursor(lineAtHeight(doc, target), head.ch, true);
1110
  }
1111
  function scrollEnd(top) {
1112
+ var pos = top ? {line: 0, ch: 0} : {line: doc.size - 1, ch: getLine(doc.size-1).text.length};
1113
  setSelectionUser(pos, pos);
1114
  }
1115
  function selectAll() {
1116
+ var endLine = doc.size - 1;
1117
+ setSelection({line: 0, ch: 0}, {line: endLine, ch: getLine(endLine).text.length});
1118
  }
1119
  function selectWordAt(pos) {
1120
+ var line = getLine(pos.line).text;
1121
  var start = pos.ch, end = pos.ch;
1122
  while (start > 0 && /\w/.test(line.charAt(start - 1))) --start;
1123
  while (end < line.length && /\w/.test(line.charAt(end))) ++end;
1124
  setSelectionUser({line: pos.line, ch: start}, {line: pos.line, ch: end});
1125
  }
1126
  function selectLine(line) {
1127
+ setSelectionUser({line: line, ch: 0}, {line: line, ch: getLine(line).text.length});
1128
  }
1129
  function handleEnter() {
1130
  replaceSelection("\n", "end");
1132
  indentLine(sel.from.line, options.enterMode == "keep" ? "prev" : "smart");
1133
  }
1134
  function handleTab(shift) {
1135
+ function indentSelected(mode) {
1136
+ if (posEq(sel.from, sel.to)) return indentLine(sel.from.line, mode);
1137
+ var e = sel.to.line - (sel.to.ch ? 0 : 1);
1138
+ for (var i = sel.from.line; i <= e; ++i) indentLine(i, mode);
1139
+ }
1140
  shiftSelecting = null;
1141
  switch (options.tabMode) {
1142
  case "default":
1143
  return false;
1144
  case "indent":
1145
+ indentSelected("smart");
1146
  break;
1147
  case "classic":
1148
  if (posEq(sel.from, sel.to)) {
1151
  break;
1152
  }
1153
  case "shift":
1154
+ indentSelected(shift ? "subtract" : "add");
1155
  break;
1156
  }
1157
  return true;
1158
  }
1159
+ function smartHome() {
1160
+ var firstNonWS = Math.max(0, getLine(sel.from.line).text.search(/\S/));
1161
+ setCursor(sel.from.line, sel.from.ch <= firstNonWS && sel.from.ch ? 0 : firstNonWS, true);
1162
+ }
1163
 
1164
  function indentLine(n, how) {
1165
  if (how == "smart") {
1167
  else var state = getStateBefore(n);
1168
  }
1169
 
1170
+ var line = getLine(n), curSpace = line.indentation(), curSpaceString = line.text.match(/^\s*/)[0], indentation;
1171
  if (how == "prev") {
1172
+ if (n) indentation = getLine(n-1).indentation();
1173
  else indentation = 0;
1174
  }
1175
  else if (how == "smart") indentation = mode.indent(state, line.text.slice(curSpaceString.length));
1194
 
1195
  function loadMode() {
1196
  mode = CodeMirror.getMode(options, options.mode);
1197
+ doc.iter(0, doc.size, function(line) { line.stateAfter = null; });
 
1198
  work = [0];
1199
  startWorker();
1200
  }
1201
  function gutterChanged() {
1202
  var visible = options.gutter || options.lineNumbers;
1203
  gutter.style.display = visible ? "" : "none";
1204
+ if (visible) gutterDirty = true;
1205
  else lineDiv.parentNode.style.marginLeft = 0;
1206
  }
1207
+ function wrappingChanged(from, to) {
1208
+ if (options.lineWrapping) {
1209
+ wrapper.className += " CodeMirror-wrap";
1210
+ var perLine = scroller.clientWidth / charWidth() - 3;
1211
+ doc.iter(0, doc.size, function(line) {
1212
+ if (line.hidden) return;
1213
+ var guess = Math.ceil(line.text.length / perLine) || 1;
1214
+ if (guess != 1) updateLineHeight(line, guess);
1215
+ });
1216
+ lineSpace.style.width = code.style.width = "";
1217
+ } else {
1218
+ wrapper.className = wrapper.className.replace(" CodeMirror-wrap", "");
1219
+ maxWidth = null; maxLine = "";
1220
+ doc.iter(0, doc.size, function(line) {
1221
+ if (line.height != 1 && !line.hidden) updateLineHeight(line, 1);
1222
+ if (line.text.length > maxLine.length) maxLine = line.text;
1223
+ });
1224
+ }
1225
+ changes.push({from: 0, to: doc.size});
1226
+ }
1227
+
1228
+ function TextMarker() { this.set = []; }
1229
+ TextMarker.prototype.clear = operation(function() {
1230
+ for (var i = 0, e = this.set.length; i < e; ++i) {
1231
+ var mk = this.set[i].marked;
1232
+ if (!mk) continue;
1233
+ for (var j = 0; j < mk.length; ++j)
1234
+ if (mk[j].set == this.set) mk.splice(j--, 1);
1235
+ }
1236
+ // We don't know the exact lines that changed. Refreshing is
1237
+ // cheaper than finding them.
1238
+ changes.push({from: 0, to: doc.size});
1239
+ });
1240
+ TextMarker.prototype.find = function() {
1241
+ var from, to;
1242
+ for (var i = 0, e = this.set.length; i < e; ++i) {
1243
+ var line = this.set[i], mk = line.marked;
1244
+ for (var j = 0; j < mk.length; ++j) {
1245
+ var mark = mk[j];
1246
+ if (mark.set == this.set) {
1247
+ if (mark.from != null || mark.to != null) {
1248
+ var found = lineNo(line);
1249
+ if (found != null) {
1250
+ if (mark.from != null) from = {line: found, ch: mark.from};
1251
+ if (mark.to != null) to = {line: found, ch: mark.to};
1252
+ }
1253
+ }
1254
+ }
1255
+ }
1256
+ }
1257
+ return {from: from, to: to};
1258
+ };
1259
 
1260
  function markText(from, to, className) {
1261
  from = clipPos(from); to = clipPos(to);
1262
+ var tm = new TextMarker();
1263
  function add(line, from, to, className) {
1264
+ mark = getLine(line).addMark(new MarkedText(from, to, className, tm.set));
 
 
1265
  }
1266
  if (from.line == to.line) add(from.line, from.ch, to.ch, className);
1267
  else {
1268
  add(from.line, from.ch, null, className);
1269
  for (var i = from.line + 1, e = to.line; i < e; ++i)
1270
+ add(i, null, null, className);
1271
+ add(to.line, null, to.ch, className);
1272
  }
1273
  changes.push({from: from.line, to: to.line + 1});
1274
+ return tm;
1275
+ }
1276
+
1277
+ function setBookmark(pos) {
1278
+ pos = clipPos(pos);
1279
+ var bm = new Bookmark(pos.ch);
1280
+ getLine(pos.line).addMark(bm);
1281
+ return bm;
 
 
 
 
1282
  }
1283
 
1284
  function addGutterMarker(line, text, className) {
1285
+ if (typeof line == "number") line = getLine(clipLine(line));
1286
  line.gutterMarker = {text: text, style: className};
1287
+ gutterDirty = true;
1288
  return line;
1289
  }
1290
  function removeGutterMarker(line) {
1291
+ if (typeof line == "number") line = getLine(clipLine(line));
1292
  line.gutterMarker = null;
1293
+ gutterDirty = true;
1294
  }
1295
+
1296
+ function changeLine(handle, op) {
1297
+ var no = handle, line = handle;
1298
+ if (typeof handle == "number") line = getLine(clipLine(handle));
1299
+ else no = lineNo(handle);
1300
+ if (no == null) return null;
1301
+ if (op(line, no)) changes.push({from: no, to: no + 1});
 
 
 
 
 
 
1302
  return line;
1303
  }
1304
+ function setLineClass(handle, className) {
1305
+ return changeLine(handle, function(line) {
1306
+ if (line.className != className) {
1307
+ line.className = className;
1308
+ return true;
1309
+ }
1310
+ });
1311
+ }
1312
+ function setLineHidden(handle, hidden) {
1313
+ return changeLine(handle, function(line, no) {
1314
+ if (line.hidden != hidden) {
1315
+ line.hidden = hidden;
1316
+ updateLineHeight(line, hidden ? 0 : 1);
1317
+ if (hidden && (sel.from.line == no || sel.to.line == no))
1318
+ setSelection(skipHidden(sel.from, sel.from.line, sel.from.ch),
1319
+ skipHidden(sel.to, sel.to.line, sel.to.ch));
1320
+ return (gutterDirty = true);
1321
+ }
1322
+ });
1323
+ }
1324
 
1325
  function lineInfo(line) {
1326
  if (typeof line == "number") {
1327
+ if (!isLine(line)) return null;
1328
  var n = line;
1329
+ line = getLine(line);
1330
  if (!line) return null;
1331
  }
1332
  else {
1333
+ var n = lineNo(line);
1334
+ if (n == null) return null;
1335
  }
1336
  var marker = line.gutterMarker;
1337
+ return {line: n, handle: line, text: line.text, markerText: marker && marker.text,
1338
+ markerClass: marker && marker.style, lineClass: line.className};
1339
  }
1340
 
1341
  function stringWidth(str) {
1345
  }
1346
  // These are used to go from pixel positions to character
1347
  // positions, taking varying character widths into account.
 
 
 
 
 
1348
  function charFromX(line, x) {
1349
  if (x <= 0) return 0;
1350
+ var lineObj = getLine(line), text = lineObj.text;
1351
  function getX(len) {
1352
  measure.innerHTML = "<pre><span>" + lineObj.getHTML(null, null, false, len) + "</span></pre>";
1353
  return measure.firstChild.firstChild.offsetWidth;
1354
  }
1355
  var from = 0, fromX = 0, to = text.length, toX;
1356
  // Guess a suitable upper bound for our search.
1357
+ var estimated = Math.min(to, Math.ceil(x / charWidth()));
1358
  for (;;) {
1359
  var estX = getX(estimated);
1360
  if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2));
1373
  }
1374
  }
1375
 
1376
+ var tempId = Math.floor(Math.random() * 0xffffff).toString(16);
1377
+ function measureLine(line, ch) {
1378
+ var extra = "";
1379
+ // Include extra text at the end to make sure the measured line is wrapped in the right way.
1380
+ if (options.lineWrapping) {
1381
+ var end = line.text.indexOf(" ", ch + 2);
1382
+ extra = line.text.slice(ch + 1, end < 0 ? line.text.length : end + (ie ? 5 : 0));
1383
+ }
1384
+ measure.innerHTML = "<pre>" + line.getHTML(null, null, false, ch) +
1385
+ '<span id="CodeMirror-temp-' + tempId + '">' + (line.text.charAt(ch) || " ") + "</span>" +
1386
+ extra + "</pre>";
1387
+ var elt = document.getElementById("CodeMirror-temp-" + tempId);
1388
+ var top = elt.offsetTop, left = elt.offsetLeft;
1389
+ // Older IEs report zero offsets for spans directly after a wrap
1390
+ if (ie && ch && top == 0 && left == 0) {
1391
+ var backup = document.createElement("span");
1392
+ backup.innerHTML = "x";
1393
+ elt.parentNode.insertBefore(backup, elt.nextSibling);
1394
+ top = backup.offsetTop;
1395
+ }
1396
+ return {top: top, left: left};
1397
+ }
1398
  function localCoords(pos, inLineWrap) {
1399
+ var x, lh = textHeight(), y = lh * (heightAtLine(doc, pos.line) - (inLineWrap ? displayOffset : 0));
1400
+ if (pos.ch == 0) x = 0;
1401
+ else {
1402
+ var sp = measureLine(getLine(pos.line), pos.ch);
1403
+ x = sp.left;
1404
+ if (options.lineWrapping) y += Math.max(0, sp.top);
1405
+ }
1406
+ return {x: x, y: y, yBot: y + lh};
1407
+ }
1408
+ // Coords must be lineSpace-local
1409
+ function coordsChar(x, y) {
1410
+ if (y < 0) y = 0;
1411
+ var th = textHeight(), cw = charWidth(), heightPos = displayOffset + Math.floor(y / th);
1412
+ var lineNo = lineAtHeight(doc, heightPos);
1413
+ if (lineNo >= doc.size) return {line: doc.size - 1, ch: 0};
1414
+ var lineObj = getLine(lineNo), text = lineObj.text;
1415
+ var tw = options.lineWrapping, innerOff = tw ? heightPos - heightAtLine(doc, lineNo) : 0;
1416
+ if (x <= 0 && innerOff == 0) return {line: lineNo, ch: 0};
1417
+ function getX(len) {
1418
+ var sp = measureLine(lineObj, len);
1419
+ if (tw) {
1420
+ var off = Math.round(sp.top / th);
1421
+ return Math.max(0, sp.left + (off - innerOff) * scroller.clientWidth);
1422
+ }
1423
+ return sp.left;
1424
+ }
1425
+ var from = 0, fromX = 0, to = text.length, toX;
1426
+ // Guess a suitable upper bound for our search.
1427
+ var estimated = Math.min(to, Math.ceil((x + innerOff * scroller.clientWidth * .9) / cw));
1428
+ for (;;) {
1429
+ var estX = getX(estimated);
1430
+ if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2));
1431
+ else {toX = estX; to = estimated; break;}
1432
+ }
1433
+ if (x > toX) return {line: lineNo, ch: to};
1434
+ // Try to guess a suitable lower bound as well.
1435
+ estimated = Math.floor(to * 0.8); estX = getX(estimated);
1436
+ if (estX < x) {from = estimated; fromX = estX;}
1437
+ // Do a binary search between these bounds.
1438
+ for (;;) {
1439
+ if (to - from <= 1) return {line: lineNo, ch: (toX - x > x - fromX) ? from : to};
1440
+ var middle = Math.ceil((from + to) / 2), middleX = getX(middle);
1441
+ if (middleX > x) {to = middle; toX = middleX;}
1442
+ else {from = middle; fromX = middleX;}
1443
+ }
1444
  }
1445
  function pageCoords(pos) {
1446
  var local = localCoords(pos, true), off = eltOffset(lineSpace);
1447
  return {x: off.left + local.x, y: off.top + local.y, yBot: off.top + local.yBot};
1448
  }
1449
 
1450
+ var cachedHeight, cachedFor;
1451
+ function textHeight() {
1452
+ var offsetHeight = lineDiv.offsetHeight;
1453
+ if (offsetHeight == cachedFor) return cachedHeight;
1454
+ cachedFor = offsetHeight;
1455
+ measure.innerHTML = "<pre>x<br>x<br>x<br>x<br>x<br>x<br>x<br>x<br>x<br>x</pre>";
1456
+ return (cachedHeight = measure.firstChild.offsetHeight / 10 || 1);
1457
+ }
1458
+ var cachedWidth, cachedFor = 0;
1459
+ function charWidth() {
1460
+ if (scroller.clientWidth == cachedFor) return cachedWidth;
1461
+ cachedFor = scroller.clientWidth;
1462
+ return (cachedWidth = stringWidth("x"));
1463
  }
1464
  function paddingTop() {return lineSpace.offsetTop;}
1465
  function paddingLeft() {return lineSpace.offsetLeft;}
1466
 
1467
  function posFromMouse(e, liberal) {
1468
+ var offW = eltOffset(scroller, true), x, y;
1469
+ // Fails unpredictably on IE[67] when mouse is dragged around quickly.
1470
+ try { x = e.clientX; y = e.clientY; } catch (e) { return null; }
1471
  // This is a mess of a heuristic to try and determine whether a
1472
  // scroll-bar was clicked or not, and to return null if one was
1473
  // (and !liberal).
1474
  if (!liberal && (x - offW.left > scroller.clientWidth || y - offW.top > scroller.clientHeight))
1475
  return null;
1476
  var offL = eltOffset(lineSpace, true);
1477
+ return coordsChar(x - offL.left, y - offL.top);
 
1478
  }
1479
  function onContextMenu(e) {
1480
  var pos = posFromMouse(e);
1481
  if (!pos || window.opera) return; // Opera is difficult.
1482
  if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to))
1483
+ operation(setCursor)(pos.line, pos.ch);
1484
 
1485
  var oldCSS = input.style.cssText;
1486
+ inputDiv.style.position = "absolute";
1487
+ input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) +
1488
+ "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; " +
1489
+ "border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";
1490
+ leaveInputAlone = true;
1491
  var val = input.value = getSelection();
1492
  focusInput();
1493
  setSelRange(input, 0, input.value.length);
 
1494
  function rehide() {
1495
+ var newVal = splitLines(input.value).join("\n");
1496
+ if (newVal != val) operation(replaceSelection)(newVal, "end");
1497
+ inputDiv.style.position = "relative";
1498
  input.style.cssText = oldCSS;
1499
  leaveInputAlone = false;
1500
  prepareInput();
1501
  slowPoll();
1502
  }
1503
+
1504
  if (gecko) {
1505
+ e_stop(e);
1506
  var mouseup = connect(window, "mouseup", function() {
1507
  mouseup();
1508
  setTimeout(rehide, 20);
1525
 
1526
  var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"};
1527
  function matchBrackets(autoclear) {
1528
+ var head = sel.inverted ? sel.from : sel.to, line = getLine(head.line), pos = head.ch - 1;
1529
  var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)];
1530
  if (!match) return;
1531
  var ch = match.charAt(0), forward = match.charAt(1) == ">", d = forward ? 1 : -1, st = line.styles;
1549
  }
1550
  }
1551
  }
1552
+ for (var i = head.line, e = forward ? Math.min(i + 100, doc.size) : Math.max(-1, i - 100); i != e; i+=d) {
1553
+ var line = getLine(i), first = i == head.line;
1554
  var found = scan(line, first && forward ? pos + 1 : 0, first && !forward ? pos : line.text.length);
1555
+ if (found) break;
 
 
 
 
 
 
 
 
1556
  }
1557
+ if (!found) found = {pos: null, match: false};
1558
+ var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
1559
+ var one = markText({line: head.line, ch: pos}, {line: head.line, ch: pos+1}, style),
1560
+ two = found.pos != null && markText({line: i, ch: found.pos}, {line: i, ch: found.pos + 1}, style);
1561
+ var clear = operation(function(){one.clear(); two && two.clear();});
1562
+ if (autoclear) setTimeout(clear, 800);
1563
+ else bracketHighlighted = clear;
1564
  }
1565
 
1566
  // Finds the line to start with when starting a parse. Tries to
1572
  var minindent, minline;
1573
  for (var search = n, lim = n - 40; search > lim; --search) {
1574
  if (search == 0) return 0;
1575
+ var line = getLine(search-1);
1576
  if (line.stateAfter) return search;
1577
  var indented = line.indentation();
1578
  if (minline == null || minindent > indented) {
1579
+ minline = search - 1;
1580
  minindent = indented;
1581
  }
1582
  }
1583
  return minline;
1584
  }
1585
  function getStateBefore(n) {
1586
+ var start = findStartLine(n), state = start && getLine(start-1).stateAfter;
1587
  if (!state) state = startState(mode);
1588
  else state = copyState(mode, state);
1589
+ doc.iter(start, n, function(line) {
 
1590
  line.highlight(mode, state);
1591
  line.stateAfter = copyState(mode, state);
1592
+ });
1593
+ if (start < n) changes.push({from: start, to: n});
1594
+ if (n < doc.size && !getLine(n).stateAfter) work.push(n);
1595
  return state;
1596
  }
1597
  function highlightLines(start, end) {
1598
  var state = getStateBefore(start);
1599
+ doc.iter(start, end, function(line) {
 
1600
  line.highlight(mode, state);
1601
  line.stateAfter = copyState(mode, state);
1602
+ });
1603
  }
1604
  function highlightWorker() {
1605
  var end = +new Date + options.workTime;
1606
  var foundWork = work.length;
1607
  while (work.length) {
1608
+ if (!getLine(showingFrom).stateAfter) var task = showingFrom;
1609
  else var task = work.pop();
1610
+ if (task >= doc.size) continue;
1611
+ var start = findStartLine(task), state = start && getLine(start-1).stateAfter;
1612
  if (state) state = copyState(mode, state);
1613
  else state = startState(mode);
1614
 
1615
+ var unchanged = 0, compare = mode.compareStates, realChange = false,
1616
+ i = start, bail = false;
1617
+ doc.iter(i, doc.size, function(line) {
1618
+ var hadState = line.stateAfter;
1619
  if (+new Date > end) {
1620
  work.push(i);
1621
  startWorker(options.workDelay);
1622
+ if (realChange) changes.push({from: task, to: i + 1});
1623
+ return (bail = true);
1624
  }
1625
  var changed = line.highlight(mode, state);
1626
+ if (changed) realChange = true;
1627
  line.stateAfter = copyState(mode, state);
1628
+ if (compare) {
1629
+ if (hadState && compare(hadState, state)) return true;
1630
+ } else {
1631
+ if (changed !== false || !hadState) unchanged = 0;
1632
+ else if (++unchanged > 3) return true;
1633
+ }
1634
+ ++i;
1635
+ });
1636
+ if (bail) return;
1637
+ if (realChange) changes.push({from: task, to: i + 1});
1638
  }
1639
  if (foundWork && options.onHighlightComplete)
1640
  options.onHighlightComplete(instance);
1655
  var reScroll = false;
1656
  if (selectionChanged) reScroll = !scrollCursorIntoView();
1657
  if (changes.length) updateDisplay(changes);
1658
+ else {
1659
+ if (selectionChanged) updateCursor();
1660
+ if (gutterDirty) updateGutter();
1661
+ }
1662
  if (reScroll) scrollCursorIntoView();
1663
+ if (selectionChanged) {scrollEditorIntoView(); restartBlink();}
1664
 
1665
  // updateInput can be set to a boolean value to force/prevent an
1666
  // update.
1667
+ if (focused && !leaveInputAlone &&
1668
+ (updateInput === true || (updateInput !== false && selectionChanged)))
1669
  prepareInput();
1670
 
1671
  if (selectionChanged && options.matchBrackets)
1704
  if (typeof query != "string") // Regexp match
1705
  this.matches = function(reverse, pos) {
1706
  if (reverse) {
1707
+ var line = getLine(pos.line).text.slice(0, pos.ch), match = line.match(query), start = 0;
1708
  while (match) {
1709
  var ind = line.indexOf(match[0]);
1710
  start += ind;
1716
  }
1717
  }
1718
  else {
1719
+ var line = getLine(pos.line).text.slice(pos.ch), match = line.match(query),
1720
  start = match && pos.ch + line.indexOf(match[0]);
1721
  }
1722
  if (match)
1731
  // Different methods for single-line and multi-line queries
1732
  if (target.length == 1)
1733
  this.matches = function(reverse, pos) {
1734
+ var line = fold(getLine(pos.line).text), len = query.length, match;
1735
  if (reverse ? (pos.ch >= len && (match = line.lastIndexOf(query, pos.ch - len)) != -1)
1736
  : (match = line.indexOf(query, pos.ch)) != -1)
1737
  return {from: {line: pos.line, ch: match},
1739
  };
1740
  else
1741
  this.matches = function(reverse, pos) {
1742
+ var ln = pos.line, idx = (reverse ? target.length - 1 : 0), match = target[idx], line = fold(getLine(ln).text);
1743
  var offsetA = (reverse ? line.indexOf(match) + match.length : line.lastIndexOf(match));
1744
  if (reverse ? offsetA >= pos.ch || offsetA != match.length
1745
  : offsetA <= pos.ch || offsetA != line.length - match.length)
1746
  return;
1747
  for (;;) {
1748
+ if (reverse ? !ln : ln == doc.size - 1) return;
1749
+ line = fold(getLine(ln += reverse ? -1 : 1).text);
1750
  match = target[reverse ? --idx : ++idx];
1751
  if (idx > 0 && idx < target.length - 1) {
1752
  if (line != match) return;
1782
  }
1783
  if (reverse) {
1784
  if (!pos.line) return savePosAndFail(0);
1785
+ pos = {line: pos.line-1, ch: getLine(pos.line-1).text.length};
1786
  }
1787
  else {
1788
+ if (pos.line == doc.size - 1) return savePosAndFail(doc.size);
1789
  pos = {line: pos.line+1, ch: 0};
1790
  }
1791
  }
1792
  },
1793
 
1794
  from: function() {if (this.atOccurrence) return copyPos(this.pos.from);},
1795
+ to: function() {if (this.atOccurrence) return copyPos(this.pos.to);},
1796
+
1797
+ replace: function(newText) {
1798
+ var self = this;
1799
+ if (this.atOccurrence)
1800
+ operation(function() {
1801
+ self.pos.to = replaceRange(newText, self.pos.from, self.pos.to);
1802
+ })();
1803
+ }
1804
  };
1805
 
1806
  for (var ext in extensions)
1821
  enterMode: "indent",
1822
  electricChars: true,
1823
  onKeyEvent: null,
1824
+ lineWrapping: false,
1825
  lineNumbers: false,
1826
  gutter: false,
1827
+ fixedGutter: false,
1828
  firstLineNumber: 1,
1829
  readOnly: false,
1830
+ smartHome: true,
1831
  onChange: null,
1832
  onCursorActivity: null,
1833
  onGutterClick: null,
1838
  workDelay: 200,
1839
  undoDepth: 40,
1840
  tabindex: null,
1841
+ pollForIME: false,
1842
  document: window.document
1843
  };
1844
 
1864
  return CodeMirror.getMode(options, "text/plain");
1865
  }
1866
  return mfactory(options, config || {});
1867
+ };
1868
  CodeMirror.listModes = function() {
1869
  var list = [];
1870
  for (var m in modes)
1874
  CodeMirror.listMIMEs = function() {
1875
  var list = [];
1876
  for (var m in mimeModes)
1877
+ if (mimeModes.propertyIsEnumerable(m)) list.push({mime: m, mode: mimeModes[m]});
1878
  return list;
1879
  };
1880
 
1936
  }
1937
  return nstate;
1938
  }
1939
+ CodeMirror.copyState = copyState;
1940
  function startState(mode, a1, a2) {
1941
  return mode.startState ? mode.startState(a1, a2) : true;
1942
  }
1943
+ CodeMirror.startState = startState;
1944
 
1945
  // The character stream used by a mode's parser.
1946
  function StringStream(string) {
1962
  if (ok) {++this.pos; return ch;}
1963
  },
1964
  eatWhile: function(match) {
1965
+ var start = this.pos;
1966
  while (this.eat(match)){}
1967
  return this.pos > start;
1968
  },
1997
  };
1998
  CodeMirror.StringStream = StringStream;
1999
 
2000
+ function MarkedText(from, to, className, set) {
2001
+ this.from = from; this.to = to; this.style = className; this.set = set;
2002
+ }
2003
+ MarkedText.prototype = {
2004
+ attach: function(line) { this.set.push(line); },
2005
+ detach: function(line) {
2006
+ var ix = indexOf(this.set, line);
2007
+ if (ix > -1) this.set.splice(ix, 1);
2008
+ },
2009
+ split: function(pos, lenBefore) {
2010
+ if (this.to <= pos && this.to != null) return null;
2011
+ var from = this.from < pos || this.from == null ? null : this.from - pos + lenBefore;
2012
+ var to = this.to == null ? null : this.to - pos + lenBefore;
2013
+ return new MarkedText(from, to, this.style, this.set);
2014
+ },
2015
+ dup: function() { return new MarkedText(null, null, this.style, this.set); },
2016
+ clipTo: function(fromOpen, from, toOpen, to, diff) {
2017
+ if (this.from != null && this.from >= from)
2018
+ this.from = Math.max(to, this.from) + diff;
2019
+ if (this.to != null && this.to > from)
2020
+ this.to = to < this.to ? this.to + diff : from;
2021
+ if (fromOpen && to > this.from && (to < this.to || this.to == null))
2022
+ this.from = null;
2023
+ if (toOpen && (from < this.to || this.to == null) && (from > this.from || this.from == null))
2024
+ this.to = null;
2025
+ },
2026
+ isDead: function() { return this.from != null && this.to != null && this.from >= this.to; },
2027
+ sameSet: function(x) { return this.set == x.set; }
2028
+ };
2029
+
2030
+ function Bookmark(pos) {
2031
+ this.from = pos; this.to = pos; this.line = null;
2032
+ }
2033
+ Bookmark.prototype = {
2034
+ attach: function(line) { this.line = line; },
2035
+ detach: function(line) { if (this.line == line) this.line = null; },
2036
+ split: function(pos, lenBefore) {
2037
+ if (pos < this.from) {
2038
+ this.from = this.to = (this.from - pos) + lenBefore;
2039
+ return this;
2040
+ }
2041
+ },
2042
+ isDead: function() { return this.from > this.to; },
2043
+ clipTo: function(fromOpen, from, toOpen, to, diff) {
2044
+ if ((fromOpen || from < this.from) && (toOpen || to > this.to)) {
2045
+ this.from = 0; this.to = -1;
2046
+ } else if (this.from > from) {
2047
+ this.from = this.to = Math.max(to, this.from) + diff;
2048
+ }
2049
+ },
2050
+ sameSet: function(x) { return false; },
2051
+ find: function() {
2052
+ if (!this.line || !this.line.parent) return null;
2053
+ return {line: lineNo(this.line), ch: this.from};
2054
+ },
2055
+ clear: function() {
2056
+ if (this.line) {
2057
+ var found = indexOf(this.line.marked, this);
2058
+ if (found != -1) this.line.marked.splice(found, 1);
2059
+ this.line = null;
2060
+ }
2061
+ }
2062
+ };
2063
+
2064
  // Line objects. These hold state related to a line, including
2065
  // highlighting info (the styles array).
2066
  function Line(text, styles) {
2067
  this.styles = styles || [text, null];
 
2068
  this.text = text;
2069
+ this.height = 1;
2070
  this.marked = this.gutterMarker = this.className = null;
2071
+ this.stateAfter = this.parent = this.hidden = null;
2072
+ }
2073
+ Line.inheritMarks = function(text, orig) {
2074
+ var ln = new Line(text), mk = orig.marked;
2075
+ if (mk) {
2076
+ for (var i = 0; i < mk.length; ++i) {
2077
+ if (mk[i].to == null && mk[i].style) {
2078
+ var newmk = ln.marked || (ln.marked = []), mark = mk[i];
2079
+ var nmark = mark.dup(); newmk.push(nmark); nmark.attach(ln);
2080
+ }
2081
+ }
2082
+ }
2083
+ return ln;
2084
  }
2085
  Line.prototype = {
2086
  // Replace a piece of a line, keeping the styles around it intact.
2087
+ replace: function(from, to_, text) {
2088
+ // Reset line class if the whole text was replaced.
2089
+ if (!from && (to_ == null || to_ == this.text.length))
2090
+ this.className = this.gutterMarker = null;
2091
+ var st = [], mk = this.marked, to = to_ == null ? this.text.length : to_;
2092
  copyStyles(0, from, this.styles, st);
2093
  if (text) st.push(text, null);
2094
  copyStyles(to, this.text.length, this.styles, st);
2096
  this.text = this.text.slice(0, from) + text + this.text.slice(to);
2097
  this.stateAfter = null;
2098
  if (mk) {
2099
+ var diff = text.length - (to - from);
2100
+ for (var i = 0, mark = mk[i]; i < mk.length; ++i) {
2101
+ mark.clipTo(from == null, from || 0, to_ == null, to, diff);
2102
+ if (mark.isDead()) {mark.detach(this); mk.splice(i--, 1);}
 
 
 
2103
  }
2104
  }
2105
  },
2106
+ // Split a part off a line, keeping styles and markers intact.
2107
  split: function(pos, textBefore) {
2108
+ var st = [textBefore, null], mk = this.marked;
2109
  copyStyles(pos, this.text.length, this.styles, st);
2110
+ var taken = new Line(textBefore + this.text.slice(pos), st);
2111
+ if (mk) {
2112
+ for (var i = 0; i < mk.length; ++i) {
2113
+ var mark = mk[i];
2114
+ var newmark = mark.split(pos, textBefore.length);
2115
+ if (newmark) {
2116
+ if (!taken.marked) taken.marked = [];
2117
+ taken.marked.push(newmark); newmark.attach(taken);
2118
+ }
2119
+ }
2120
+ }
2121
+ return taken;
2122
  },
2123
+ append: function(line) {
2124
+ var mylen = this.text.length, mk = line.marked, mymk = this.marked;
2125
+ this.text += line.text;
2126
+ copyStyles(0, line.text.length, line.styles, this.styles);
2127
+ if (mymk) {
2128
+ for (var i = 0; i < mymk.length; ++i)
2129
+ if (mymk[i].to == null) mymk[i].to = mylen;
2130
+ }
2131
+ if (mk && mk.length) {
2132
+ if (!mymk) this.marked = mymk = [];
2133
+ outer: for (var i = 0; i < mk.length; ++i) {
2134
+ var mark = mk[i];
2135
+ if (!mark.from) {
2136
+ for (var j = 0; j < mymk.length; ++j) {
2137
+ var mymark = mymk[j];
2138
+ if (mymark.to == mylen && mymark.sameSet(mark)) {
2139
+ mymark.to = mark.to == null ? null : mark.to + mylen;
2140
+ if (mymark.isDead()) {
2141
+ mymark.detach(this);
2142
+ mk.splice(i--, 1);
2143
+ }
2144
+ continue outer;
2145
+ }
2146
+ }
2147
+ }
2148
+ mymk.push(mark);
2149
+ mark.attach(this);
2150
+ mark.from += mylen;
2151
+ if (mark.to != null) mark.to += mylen;
2152
+ }
2153
+ }
2154
  },
2155
+ fixMarkEnds: function(other) {
2156
+ var mk = this.marked, omk = other.marked;
2157
  if (!mk) return;
2158
+ for (var i = 0; i < mk.length; ++i) {
2159
+ var mark = mk[i], close = mark.to == null;
2160
+ if (close && omk) {
2161
+ for (var j = 0; j < omk.length; ++j)
2162
+ if (omk[j].sameSet(mark)) {close = false; break;}
2163
+ }
2164
+ if (close) mark.to = this.text.length;
2165
+ }
2166
+ },
2167
+ addMark: function(mark) {
2168
+ mark.attach(this);
2169
+ if (this.marked == null) this.marked = [];
2170
+ this.marked.push(mark);
2171
+ this.marked.sort(function(a, b){return (a.from || 0) - (b.from || 0);});
2172
  },
2173
  // Run the given mode's parser over a line, update the styles
2174
  // array, which contains alternating fragments of text and CSS
2196
  }
2197
  if (st.length != pos) {st.length = pos; changed = true;}
2198
  if (pos && st[pos-2] != prevWord) changed = true;
2199
+ // Short lines with simple highlights return null, and are
2200
+ // counted as changed by the driver because they are likely to
2201
+ // highlight the same way in various contexts.
2202
+ return changed || (st.length < 5 && this.text.length < 10 ? null : false);
2203
  },
2204
  // Fetch the parser token for a given character. Useful for hacks
2205
  // that want to inspect the mode state (say, for completion).
2219
  // Produces an HTML fragment for the line, taking selection,
2220
  // marking, and highlighting into account.
2221
  getHTML: function(sfrom, sto, includePre, endAt) {
2222
+ var html = [], first = true;
2223
  if (includePre)
2224
  html.push(this.className ? '<pre class="' + this.className + '">': "<pre>");
2225
  function span(text, style) {
2226
  if (!text) return;
2227
+ // Work around a bug where, in some compat modes, IE ignores leading spaces
2228
+ if (first && ie && text.charAt(0) == " ") text = "\u00a0" + text.slice(1);
2229
+ first = false;
2230
+ if (style) html.push('<span class="', style, '">', htmlEscape(text), "</span>");
2231
  else html.push(htmlEscape(text));
2232
  }
2233
  var st = this.styles, allText = this.text, marked = this.marked;
2239
  span(" ", sfrom != null && sto == null ? "CodeMirror-selected" : null);
2240
  else if (!marked && sfrom == null)
2241
  for (var i = 0, ch = 0; ch < len; i+=2) {
2242
+ var str = st[i], style = st[i+1], l = str.length;
2243
  if (ch + l > len) str = str.slice(0, len - ch);
2244
  ch += l;
2245
+ span(str, style && "cm-" + style);
2246
  }
2247
  else {
2248
  var pos = 0, i = 0, text = "", style, sg = 0;
2274
  }
2275
  for (;;) {
2276
  var end = pos + text.length;
2277
+ var appliedStyle = style;
2278
+ if (extraStyle) appliedStyle = style ? style + extraStyle : extraStyle;
2279
+ span(end > upto ? text.slice(0, upto - pos) : text, appliedStyle);
2280
  if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}
2281
  pos = end;
2282
+ text = st[i++]; style = "cm-" + st[i++];
2283
  }
2284
  }
2285
  if (sfrom != null && sto == null) span(" ", "CodeMirror-selected");
2286
  }
2287
  if (includePre) html.push("</pre>");
2288
  return html.join("");
2289
+ },
2290
+ cleanUp: function() {
2291
+ this.parent = null;
2292
+ if (this.marked)
2293
+ for (var i = 0, e = this.marked.length; i < e; ++i) this.marked[i].detach(this);
2294
  }
2295
  };
2296
  // Utility used by replace and split above
2309
  }
2310
  }
2311
 
2312
+ // Data structure that holds the sequence of lines.
2313
+ function LeafChunk(lines) {
2314
+ this.lines = lines;
2315
+ this.parent = null;
2316
+ for (var i = 0, e = lines.length, height = 0; i < e; ++i) {
2317
+ lines[i].parent = this;
2318
+ height += lines[i].height;
2319
+ }
2320
+ this.height = height;
2321
+ }
2322
+ LeafChunk.prototype = {
2323
+ chunkSize: function() { return this.lines.length; },
2324
+ remove: function(at, n) {
2325
+ for (var i = at, e = at + n; i < e; ++i) {
2326
+ var line = this.lines[i];
2327
+ line.cleanUp();
2328
+ this.height -= line.height;
2329
+ }
2330
+ this.lines.splice(at, n);
2331
+ },
2332
+ collapse: function(lines) {
2333
+ lines.splice.apply(lines, [lines.length, 0].concat(this.lines));
2334
+ },
2335
+ insertHeight: function(at, lines, height) {
2336
+ this.height += height;
2337
+ this.lines.splice.apply(this.lines, [at, 0].concat(lines));
2338
+ for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this;
2339
+ },
2340
+ iterN: function(at, n, op) {
2341
+ for (var e = at + n; at < e; ++at)
2342
+ if (op(this.lines[at])) return true;
2343
+ }
2344
+ };
2345
+ function BranchChunk(children) {
2346
+ this.children = children;
2347
+ var size = 0, height = 0;
2348
+ for (var i = 0, e = children.length; i < e; ++i) {
2349
+ var ch = children[i];
2350
+ size += ch.chunkSize(); height += ch.height;
2351
+ ch.parent = this;
2352
+ }
2353
+ this.size = size;
2354
+ this.height = height;
2355
+ this.parent = null;
2356
+ }
2357
+ BranchChunk.prototype = {
2358
+ chunkSize: function() { return this.size; },
2359
+ remove: function(at, n) {
2360
+ this.size -= n;
2361
+ for (var i = 0; i < this.children.length; ++i) {
2362
+ var child = this.children[i], sz = child.chunkSize();
2363
+ if (at < sz) {
2364
+ var rm = Math.min(n, sz - at), oldHeight = child.height;
2365
+ child.remove(at, rm);
2366
+ this.height -= oldHeight - child.height;
2367
+ if (sz == rm) { this.children.splice(i--, 1); child.parent = null; }
2368
+ if ((n -= rm) == 0) break;
2369
+ at = 0;
2370
+ } else at -= sz;
2371
+ }
2372
+ if (this.size - n < 25) {
2373
+ var lines = [];
2374
+ this.collapse(lines);
2375
+ this.children = [new LeafChunk(lines)];
2376
+ }
2377
+ },
2378
+ collapse: function(lines) {
2379
+ for (var i = 0, e = this.children.length; i < e; ++i) this.children[i].collapse(lines);
2380
+ },
2381
+ insert: function(at, lines) {
2382
+ var height = 0;
2383
+ for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height;
2384
+ this.insertHeight(at, lines, height);
2385
+ },
2386
+ insertHeight: function(at, lines, height) {
2387
+ this.size += lines.length;
2388
+ this.height += height;
2389
+ for (var i = 0, e = this.children.length; i < e; ++i) {
2390
+ var child = this.children[i], sz = child.chunkSize();
2391
+ if (at <= sz) {
2392
+ child.insertHeight(at, lines, height);
2393
+ if (child.lines && child.lines.length > 50) {
2394
+ while (child.lines.length > 50) {
2395
+ var spilled = child.lines.splice(child.lines.length - 25, 25);
2396
+ var newleaf = new LeafChunk(spilled);
2397
+ child.height -= newleaf.height;
2398
+ this.children.splice(i + 1, 0, newleaf);
2399
+ newleaf.parent = this;
2400
+ }
2401
+ this.maybeSpill();
2402
+ }
2403
+ break;
2404
+ }
2405
+ at -= sz;
2406
+ }
2407
+ },
2408
+ maybeSpill: function() {
2409
+ if (this.children.length <= 10) return;
2410
+ var me = this;
2411
+ do {
2412
+ var spilled = me.children.splice(me.children.length - 5, 5);
2413
+ var sibling = new BranchChunk(spilled);
2414
+ if (!me.parent) { // Become the parent node
2415
+ var copy = new BranchChunk(me.children);
2416
+ copy.parent = me;
2417
+ me.children = [copy, sibling];
2418
+ me = copy;
2419
+ } else {
2420
+ me.size -= sibling.size;
2421
+ me.height -= sibling.height;
2422
+ var myIndex = indexOf(me.parent.children, me);
2423
+ me.parent.children.splice(myIndex + 1, 0, sibling);
2424
+ }
2425
+ sibling.parent = me.parent;
2426
+ } while (me.children.length > 10);
2427
+ me.parent.maybeSpill();
2428
+ },
2429
+ iter: function(from, to, op) { this.iterN(from, to - from, op); },
2430
+ iterN: function(at, n, op) {
2431
+ for (var i = 0, e = this.children.length; i < e; ++i) {
2432
+ var child = this.children[i], sz = child.chunkSize();
2433
+ if (at < sz) {
2434
+ var used = Math.min(n, sz - at);
2435
+ if (child.iterN(at, used, op)) return true;
2436
+ if ((n -= used) == 0) break;
2437
+ at = 0;
2438
+ } else at -= sz;
2439
+ }
2440
+ }
2441
+ };
2442
+
2443
+ function getLineAt(chunk, n) {
2444
+ for (;;) {
2445
+ for (var i = 0, e = chunk.children.length; i < e; ++i) {
2446
+ var child = chunk.children[i], sz = child.chunkSize();
2447
+ if (n < sz) { chunk = child; break; }
2448
+ n -= sz;
2449
+ }
2450
+ if (chunk.lines) return chunk.lines[n];
2451
+ }
2452
+ }
2453
+ function lineNo(line) {
2454
+ if (line.parent == null) return null;
2455
+ var cur = line.parent, no = indexOf(cur.lines, line);
2456
+ for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) {
2457
+ for (var i = 0, e = chunk.children.length; ; ++i) {
2458
+ if (chunk.children[i] == cur) break;
2459
+ no += chunk.children[i].chunkSize();
2460
+ }
2461
+ }
2462
+ return no;
2463
+ }
2464
+ function lineAtHeight(chunk, h) {
2465
+ var n = 0;
2466
+ outer: do {
2467
+ for (var i = 0, e = chunk.children.length; i < e; ++i) {
2468
+ var child = chunk.children[i], ch = child.height;
2469
+ if (h < ch) { chunk = child; continue outer; }
2470
+ h -= ch;
2471
+ n += child.chunkSize();
2472
+ }
2473
+ return n;
2474
+ } while (!chunk.lines);
2475
+ for (var i = 0, e = chunk.lines.length; i < e; ++i) {
2476
+ var line = chunk.lines[i], lh = line.height;
2477
+ if (h < lh) break;
2478
+ h -= lh;
2479
+ }
2480
+ return n + i;
2481
+ }
2482
+ function heightAtLine(chunk, n) {
2483
+ var h = 0;
2484
+ outer: do {
2485
+ for (var i = 0, e = chunk.children.length; i < e; ++i) {
2486
+ var child = chunk.children[i], sz = child.chunkSize();
2487
+ if (n < sz) { chunk = child; continue outer; }
2488
+ n -= sz;
2489
+ h += child.height;
2490
+ }
2491
+ return h;
2492
+ } while (!chunk.lines);
2493
+ for (var i = 0; i < n; ++i) h += chunk.lines[i].height;
2494
+ return h;
2495
+ }
2496
+
2497
  // The history object 'chunks' changes that are made close together
2498
  // and at almost the same time into bigger undoable units.
2499
  function History() {
2527
  }
2528
  };
2529
 
2530
+ function stopMethod() {e_stop(this);}
 
 
 
 
2531
  // Ensure an event has a stop method.
2532
  function addStop(event) {
2533
+ if (!event.stop) event.stop = stopMethod;
2534
  return event;
2535
  }
2536
 
2537
+ function e_preventDefault(e) {
2538
+ if (e.preventDefault) e.preventDefault();
2539
+ else e.returnValue = false;
2540
+ }
2541
+ function e_stopPropagation(e) {
2542
+ if (e.stopPropagation) e.stopPropagation();
2543
+ else e.cancelBubble = true;
2544
+ }
2545
+ function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);}
2546
+ function e_target(e) {return e.target || e.srcElement;}
2547
+ function e_button(e) {
2548
+ if (e.which) return e.which;
2549
+ else if (e.button & 1) return 1;
2550
+ else if (e.button & 2) return 3;
2551
+ else if (e.button & 4) return 2;
2552
+ }
 
 
 
 
 
 
2553
 
2554
  // Event handler registration. If disconnect is true, it'll return a
2555
  // function that unregisters the handler.
2556
  function connect(node, type, handler, disconnect) {
2557
+ function wrapHandler(event) {handler(event || window.event);}
2558
  if (typeof node.addEventListener == "function") {
2559
  node.addEventListener(type, wrapHandler, false);
2560
  if (disconnect) return function() {node.removeEventListener(type, wrapHandler, false);};
2568
  function Delayed() {this.id = null;}
2569
  Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}};
2570
 
2571
+ // Detect drag-and-drop
2572
+ var dragAndDrop = function() {
2573
+ // IE8 has ondragstart and ondrop properties, but doesn't seem to
2574
+ // actually support ondragstart the way it's supposed to work.
2575
+ if (/MSIE [1-8]\b/.test(navigator.userAgent)) return false;
2576
+ var div = document.createElement('div');
2577
+ return "draggable" in div;
2578
+ }();
2579
 
2580
  var gecko = /gecko\/\d{7}/i.test(navigator.userAgent);
2581
  var ie = /MSIE \d/.test(navigator.userAgent);
2582
+ var webkit = /WebKit\//.test(navigator.userAgent);
2583
 
2584
  var lineSep = "\n";
2585
  // Feature-detect whether newlines in textareas are converted to \r\n
2591
 
2592
  var tabSize = 8;
2593
  var mac = /Mac/.test(navigator.platform);
2594
+ var win = /Win/.test(navigator.platform);
2595
  var movementKeys = {};
2596
  for (var i = 35; i <= 40; ++i)
2597
  movementKeys[i] = movementKeys["c" + i] = true;
2610
  return n;
2611
  }
2612
 
2613
+ function computedStyle(elt) {
2614
+ if (elt.currentStyle) return elt.currentStyle;
2615
+ return window.getComputedStyle(elt, null);
2616
+ }
2617
+
2618
  // Find the position of an element by following the offsetParent chain.
2619
  // If screen==true, it returns screen (rather than page) coordinates.
2620
  function eltOffset(node, screen) {
2621
+ var bod = node.ownerDocument.body;
2622
+ var x = 0, y = 0, skipBody = false;
2623
  for (var n = node; n; n = n.offsetParent) {
2624
+ var ol = n.offsetLeft, ot = n.offsetTop;
2625
+ // Firefox reports weird inverted offsets when the body has a border.
2626
+ if (n == bod) { x += Math.abs(ol); y += Math.abs(ot); }
2627
+ else { x += ol, y += ot; }
2628
+ if (screen && computedStyle(n).position == "fixed")
2629
+ skipBody = true;
2630
+ }
2631
+ var e = screen && !skipBody ? null : bod;
2632
  for (var n = node.parentNode; n != e; n = n.parentNode)
2633
  if (n.scrollLeft != null) { x -= n.scrollLeft; y -= n.scrollTop;}
2634
  return {left: x, top: y};
2635
  }
2636
+ // Use the faster and saner getBoundingClientRect method when possible.
2637
+ if (document.documentElement.getBoundingClientRect != null) eltOffset = function(node, screen) {
2638
+ // Take the parts of bounding client rect that we are interested in so we are able to edit if need be,
2639
+ // since the returned value cannot be changed externally (they are kept in sync as the element moves within the page)
2640
+ try { var box = node.getBoundingClientRect(); box = { top: box.top, left: box.left }; }
2641
+ catch(e) { box = {top: 0, left: 0}; }
2642
+ if (!screen) {
2643
+ // Get the toplevel scroll, working around browser differences.
2644
+ if (window.pageYOffset == null) {
2645
+ var t = document.documentElement || document.body.parentNode;
2646
+ if (t.scrollTop == null) t = document.body;
2647
+ box.top += t.scrollTop; box.left += t.scrollLeft;
2648
+ } else {
2649
+ box.top += window.pageYOffset; box.left += window.pageXOffset;
2650
+ }
2651
+ }
2652
+ return box;
2653
+ };
2654
+
2655
  // Get a node's text content.
2656
  function eltText(node) {
2657
  return node.textContent || node.innerText || node.nodeValue || "";
2662
  function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);}
2663
  function copyPos(x) {return {line: x.line, ch: x.ch};}
2664
 
2665
+ var escapeElement = document.createElement("pre");
2666
  function htmlEscape(str) {
2667
+ if (badTextContent) {
2668
+ escapeElement.innerHTML = "";
2669
+ escapeElement.appendChild(document.createTextNode(str));
2670
+ } else {
2671
+ escapeElement.textContent = str;
2672
+ }
2673
+ return escapeElement.innerHTML;
2674
  }
2675
+ var badTextContent = htmlEscape("\t") != "\t";
2676
  CodeMirror.htmlEscape = htmlEscape;
2677
 
2678
  // Used to position the cursor after an undo/redo by finding the
2694
 
2695
  // See if "".split is the broken IE version, if so, provide an
2696
  // alternative way to split lines.
2697
+ var splitLines, selRange, setSelRange;
2698
  if ("\n\nb".split(/\n/).length != 3)
2699
+ splitLines = function(string) {
2700
  var pos = 0, nl, result = [];
2701
  while ((nl = string.indexOf("\n", pos)) > -1) {
2702
  result.push(string.slice(pos, string.charAt(nl-1) == "\r" ? nl - 1 : nl));
2706
  return result;
2707
  };
2708
  else
2709
+ splitLines = function(string){return string.split(/\r?\n/);};
2710
  CodeMirror.splitLines = splitLines;
2711
 
2712
  // Sane model of finding and setting the selection in a textarea
2713
  if (window.getSelection) {
2714
+ selRange = function(te) {
2715
  try {return {start: te.selectionStart, end: te.selectionEnd};}
2716
  catch(e) {return null;}
2717
  };
2718
+ if (webkit)
2719
  // On Safari, selection set with setSelectionRange are in a sort
2720
  // of limbo wrt their anchor. If you press shift-left in them,
2721
  // the anchor is put at the end, and the selection expanded to
2722
  // the left. If you press shift-right, the anchor ends up at the
2723
  // front. This is not what CodeMirror wants, so it does a
2724
  // spurious modify() call to get out of limbo.
2725
+ setSelRange = function(te, start, end) {
2726
  if (start == end)
2727
  te.setSelectionRange(start, end);
2728
  else {
2731
  }
2732
  };
2733
  else
2734
+ setSelRange = function(te, start, end) {
2735
  try {te.setSelectionRange(start, end);}
2736
  catch(e) {} // Fails on Firefox when textarea isn't part of the document
2737
  };
2738
  }
2739
  // IE model. Don't ask.
2740
  else {
2741
+ selRange = function(te) {
2742
  try {var range = te.ownerDocument.selection.createRange();}
2743
  catch(e) {return null;}
2744
  if (!range || range.parentElement() != te) return null;
2760
  for (var i = val.indexOf("\r"); i > -1 && i < end; i = val.indexOf("\r", i+1), end++) {}
2761
  return {start: start, end: end};
2762
  };
2763
+ setSelRange = function(te, start, end) {
2764
  var range = te.createTextRange();
2765
  range.collapse(true);
2766
  var endrange = range.duplicate();
libraries/CodeMirror2/mode/clike/clike.js ADDED
@@ -0,0 +1,247 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ CodeMirror.defineMode("clike", function(config, parserConfig) {
2
+ var indentUnit = config.indentUnit,
3
+ keywords = parserConfig.keywords || {},
4
+ blockKeywords = parserConfig.blockKeywords || {},
5
+ atoms = parserConfig.atoms || {},
6
+ hooks = parserConfig.hooks || {},
7
+ multiLineStrings = parserConfig.multiLineStrings;
8
+ var isOperatorChar = /[+\-*&%=<>!?|\/]/;
9
+
10
+ var curPunc;
11
+
12
+ function tokenBase(stream, state) {
13
+ var ch = stream.next();
14
+ if (hooks[ch]) {
15
+ var result = hooks[ch](stream, state);
16
+ if (result !== false) return result;
17
+ }
18
+ if (ch == '"' || ch == "'") {
19
+ state.tokenize = tokenString(ch);
20
+ return state.tokenize(stream, state);
21
+ }
22
+ if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
23
+ curPunc = ch;
24
+ return null
25
+ }
26
+ if (/\d/.test(ch)) {
27
+ stream.eatWhile(/[\w\.]/);
28
+ return "number";
29
+ }
30
+ if (ch == "/") {
31
+ if (stream.eat("*")) {
32
+ state.tokenize = tokenComment;
33
+ return tokenComment(stream, state);
34
+ }
35
+ if (stream.eat("/")) {
36
+ stream.skipToEnd();
37
+ return "comment";
38
+ }
39
+ }
40
+ if (isOperatorChar.test(ch)) {
41
+ stream.eatWhile(isOperatorChar);
42
+ return "operator";
43
+ }
44
+ stream.eatWhile(/[\w\$_]/);
45
+ var cur = stream.current();
46
+ if (keywords.propertyIsEnumerable(cur)) {
47
+ if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
48
+ return "keyword";
49
+ }
50
+ if (atoms.propertyIsEnumerable(cur)) return "atom";
51
+ return "word";
52
+ }
53
+
54
+ function tokenString(quote) {
55
+ return function(stream, state) {
56
+ var escaped = false, next, end = false;
57
+ while ((next = stream.next()) != null) {
58
+ if (next == quote && !escaped) {end = true; break;}
59
+ escaped = !escaped && next == "\\";
60
+ }
61
+ if (end || !(escaped || multiLineStrings))
62
+ state.tokenize = tokenBase;
63
+ return "string";
64
+ };
65
+ }
66
+
67
+ function tokenComment(stream, state) {
68
+ var maybeEnd = false, ch;
69
+ while (ch = stream.next()) {
70
+ if (ch == "/" && maybeEnd) {
71
+ state.tokenize = tokenBase;
72
+ break;
73
+ }
74
+ maybeEnd = (ch == "*");
75
+ }
76
+ return "comment";
77
+ }
78
+
79
+ function Context(indented, column, type, align, prev) {
80
+ this.indented = indented;
81
+ this.column = column;
82
+ this.type = type;
83
+ this.align = align;
84
+ this.prev = prev;
85
+ }
86
+ function pushContext(state, col, type) {
87
+ return state.context = new Context(state.indented, col, type, null, state.context);
88
+ }
89
+ function popContext(state) {
90
+ var t = state.context.type;
91
+ if (t == ")" || t == "]" || t == "}")
92
+ state.indented = state.context.indented;
93
+ return state.context = state.context.prev;
94
+ }
95
+
96
+ // Interface
97
+
98
+ return {
99
+ startState: function(basecolumn) {
100
+ return {
101
+ tokenize: null,
102
+ context: new Context((basecolumn || 0) - indentUnit, 0, "top", false),
103
+ indented: 0,
104
+ startOfLine: true
105
+ };
106
+ },
107
+
108
+ token: function(stream, state) {
109
+ var ctx = state.context;
110
+ if (stream.sol()) {
111
+ if (ctx.align == null) ctx.align = false;
112
+ state.indented = stream.indentation();
113
+ state.startOfLine = true;
114
+ }
115
+ if (stream.eatSpace()) return null;
116
+ curPunc = null;
117
+ var style = (state.tokenize || tokenBase)(stream, state);
118
+ if (style == "comment" || style == "meta") return style;
119
+ if (ctx.align == null) ctx.align = true;
120
+
121
+ if ((curPunc == ";" || curPunc == ":") && ctx.type == "statement") popContext(state);
122
+ else if (curPunc == "{") pushContext(state, stream.column(), "}");
123
+ else if (curPunc == "[") pushContext(state, stream.column(), "]");
124
+ else if (curPunc == "(") pushContext(state, stream.column(), ")");
125
+ else if (curPunc == "}") {
126
+ while (ctx.type == "statement") ctx = popContext(state);
127
+ if (ctx.type == "}") ctx = popContext(state);
128
+ while (ctx.type == "statement") ctx = popContext(state);
129
+ }
130
+ else if (curPunc == ctx.type) popContext(state);
131
+ else if (ctx.type == "}" || ctx.type == "top" || (ctx.type == "statement" && curPunc == "newstatement"))
132
+ pushContext(state, stream.column(), "statement");
133
+ state.startOfLine = false;
134
+ return style;
135
+ },
136
+
137
+ indent: function(state, textAfter) {
138
+ if (state.tokenize != tokenBase && state.tokenize != null) return 0;
139
+ var firstChar = textAfter && textAfter.charAt(0), ctx = state.context, closing = firstChar == ctx.type;
140
+ if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : indentUnit);
141
+ else if (ctx.align) return ctx.column + (closing ? 0 : 1);
142
+ else return ctx.indented + (closing ? 0 : indentUnit);
143
+ },
144
+
145
+ electricChars: "{}"
146
+ };
147
+ });
148
+
149
+ (function() {
150
+ function words(str) {
151
+ var obj = {}, words = str.split(" ");
152
+ for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
153
+ return obj;
154
+ }
155
+ var cKeywords = "auto if break int case long char register continue return default short do sizeof " +
156
+ "double static else struct entry switch extern typedef float union for unsigned " +
157
+ "goto while enum void const signed volatile";
158
+
159
+ function cppHook(stream, state) {
160
+ if (!state.startOfLine) return false;
161
+ stream.skipToEnd();
162
+ return "meta";
163
+ }
164
+
165
+ // C#-style strings where "" escapes a quote.
166
+ function tokenAtString(stream, state) {
167
+ var next;
168
+ while ((next = stream.next()) != null) {
169
+ if (next == '"' && !stream.eat('"')) {
170
+ state.tokenize = null;
171
+ break;
172
+ }
173
+ }
174
+ return "string";
175
+ }
176
+
177
+ CodeMirror.defineMIME("text/x-csrc", {
178
+ name: "clike",
179
+ keywords: words(cKeywords),
180
+ blockKeywords: words("case do else for if switch while struct"),
181
+ atoms: words("null"),
182
+ hooks: {"#": cppHook}
183
+ });
184
+ CodeMirror.defineMIME("text/x-c++src", {
185
+ name: "clike",
186
+ keywords: words(cKeywords + " asm dynamic_cast namespace reinterpret_cast try bool explicit new " +
187
+ "static_cast typeid catch operator template typename class friend private " +
188
+ "this using const_cast inline public throw virtual delete mutable protected " +
189
+ "wchar_t"),
190
+ blockKeywords: words("catch class do else finally for if struct switch try while"),
191
+ atoms: words("true false null"),
192
+ hooks: {"#": cppHook}
193
+ });
194
+ CodeMirror.defineMIME("text/x-java", {
195
+ name: "clike",
196
+ keywords: words("abstract assert boolean break byte case catch char class const continue default " +
197
+ "do double else enum extends final finally float for goto if implements import " +
198
+ "instanceof int interface long native new package private protected public " +
199
+ "return short static strictfp super switch synchronized this throw throws transient " +
200
+ "try void volatile while"),
201
+ blockKeywords: words("catch class do else finally for if switch try while"),
202
+ atoms: words("true false null"),
203
+ hooks: {
204
+ "@": function(stream, state) {
205
+ stream.eatWhile(/[\w\$_]/);
206
+ return "meta";
207
+ }
208
+ }
209
+ });
210
+ CodeMirror.defineMIME("text/x-csharp", {
211
+ name: "clike",
212
+ keywords: words("abstract as base bool break byte case catch char checked class const continue decimal" +
213
+ " default delegate do double else enum event explicit extern finally fixed float for" +
214
+ " foreach goto if implicit in int interface internal is lock long namespace new object" +
215
+ " operator out override params private protected public readonly ref return sbyte sealed short" +
216
+ " sizeof stackalloc static string struct switch this throw try typeof uint ulong unchecked" +
217
+ " unsafe ushort using virtual void volatile while add alias ascending descending dynamic from get" +
218
+ " global group into join let orderby partial remove select set value var yield"),
219
+ blockKeywords: words("catch class do else finally for foreach if struct switch try while"),
220
+ atoms: words("true false null"),
221
+ hooks: {
222
+ "@": function(stream, state) {
223
+ if (stream.eat('"')) {
224
+ state.tokenize = tokenAtString;
225
+ return tokenAtString(stream, state);
226
+ }
227
+ stream.eatWhile(/[\w\$_]/);
228
+ return "meta";
229
+ }
230
+ }
231
+ });
232
+ CodeMirror.defineMIME("text/x-groovy", {
233
+ name: "clike",
234
+ keywords: words("abstract as assert boolean break byte case catch char class const continue def default " +
235
+ "do double else enum extends final finally float for goto if implements import " +
236
+ "in instanceof int interface long native new package property private protected public " +
237
+ "return short static strictfp super switch synchronized this throw throws transient " +
238
+ "try void volatile while"),
239
+ atoms: words("true false null"),
240
+ hooks: {
241
+ "@": function(stream, state) {
242
+ stream.eatWhile(/[\w\$_]/);
243
+ return "meta";
244
+ }
245
+ }
246
+ });
247
+ }());
libraries/CodeMirror2/mode/clike/index.html ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <title>CodeMirror 2: C-like mode</title>
5
+ <link rel="stylesheet" href="../../lib/codemirror.css">
6
+ <script src="../../lib/codemirror.js"></script>
7
+ <script src="clike.js"></script>
8
+ <link rel="stylesheet" href="../../theme/default.css">
9
+ <link rel="stylesheet" href="../../css/docs.css">
10
+ <style>.CodeMirror {border: 2px inset #dee;}</style>
11
+ </head>
12
+ <body>
13
+ <h1>CodeMirror 2: C-like mode</h1>
14
+
15
+ <form><textarea id="code" name="code">
16
+ /* C demo code */
17
+
18
+ #include <zmq.h>
19
+ #include <pthread.h>
20
+ #include <semaphore.h>
21
+ #include <time.h>
22
+ #include <stdio.h>
23
+ #include <fcntl.h>
24
+ #include <malloc.h>
25
+
26
+ typedef struct {
27
+ void* arg_socket;
28
+ zmq_msg_t* arg_msg;
29
+ char* arg_string;
30
+ unsigned long arg_len;
31
+ int arg_int, arg_command;
32
+
33
+ int signal_fd;
34
+ int pad;
35
+ void* context;
36
+ sem_t sem;
37
+ } acl_zmq_context;
38
+
39
+ #define p(X) (context->arg_##X)
40
+
41
+ void* zmq_thread(void* context_pointer) {
42
+ acl_zmq_context* context = (acl_zmq_context*)context_pointer;
43
+ char ok = 'K', err = 'X';
44
+ int res;
45
+
46
+ while (1) {
47
+ while ((res = sem_wait(&amp;context->sem)) == EINTR);
48
+ if (res) {write(context->signal_fd, &amp;err, 1); goto cleanup;}
49
+ switch(p(command)) {
50
+ case 0: goto cleanup;
51
+ case 1: p(socket) = zmq_socket(context->context, p(int)); break;
52
+ case 2: p(int) = zmq_close(p(socket)); break;
53
+ case 3: p(int) = zmq_bind(p(socket), p(string)); break;
54
+ case 4: p(int) = zmq_connect(p(socket), p(string)); break;
55
+ case 5: p(int) = zmq_getsockopt(p(socket), p(int), (void*)p(string), &amp;p(len)); break;
56
+ case 6: p(int) = zmq_setsockopt(p(socket), p(int), (void*)p(string), p(len)); break;
57
+ case 7: p(int) = zmq_send(p(socket), p(msg), p(int)); break;
58
+ case 8: p(int) = zmq_recv(p(socket), p(msg), p(int)); break;
59
+ case 9: p(int) = zmq_poll(p(socket), p(int), p(len)); break;
60
+ }
61
+ p(command) = errno;
62
+ write(context->signal_fd, &amp;ok, 1);
63
+ }
64
+ cleanup:
65
+ close(context->signal_fd);
66
+ free(context_pointer);
67
+ return 0;
68
+ }
69
+
70
+ void* zmq_thread_init(void* zmq_context, int signal_fd) {
71
+ acl_zmq_context* context = malloc(sizeof(acl_zmq_context));
72
+ pthread_t thread;
73
+
74
+ context->context = zmq_context;
75
+ context->signal_fd = signal_fd;
76
+ sem_init(&amp;context->sem, 1, 0);
77
+ pthread_create(&amp;thread, 0, &amp;zmq_thread, context);
78
+ pthread_detach(thread);
79
+ return context;
80
+ }
81
+ </textarea></form>
82
+
83
+ <script>
84
+ var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
85
+ lineNumbers: true,
86
+ matchBrackets: true,
87
+ mode: "text/x-csrc"
88
+ });
89
+ </script>
90
+
91
+ <p>Simple mode that tries to handle C-like languages as well as it
92
+ can. Takes two configuration parameters: <code>keywords</code>, an
93
+ object whose property names are the keywords in the language,
94
+ and <code>useCPP</code>, which determines whether C preprocessor
95
+ directives are recognized.</p>
96
+
97
+ <p><strong>MIME types defined:</strong> <code>text/x-csrc</code>
98
+ (C code), <code>text/x-c++src</code> (C++
99
+ code), <code>text/x-java</code> (Java
100
+ code), <code>text/x-groovy</code> (Groovy code).</p>
101
+ </body>
102
+ </html>
libraries/{codemirror/mode → CodeMirror2/mode/css}/css.js RENAMED
@@ -4,7 +4,7 @@ CodeMirror.defineMode("css", function(config) {
4
 
5
  function tokenBase(stream, state) {
6
  var ch = stream.next();
7
- if (ch == "@") {stream.eatWhile(/\w/); return ret("meta", stream.current());}
8
  else if (ch == "/" && stream.eat("*")) {
9
  state.tokenize = tokenCComment;
10
  return tokenCComment(stream, state);
@@ -20,7 +20,7 @@ CodeMirror.defineMode("css", function(config) {
20
  return state.tokenize(stream, state);
21
  }
22
  else if (ch == "#") {
23
- stream.eatWhile(/\w/);
24
  return ret("atom", "hash");
25
  }
26
  else if (ch == "!") {
@@ -38,7 +38,7 @@ CodeMirror.defineMode("css", function(config) {
38
  return ret(null, ch);
39
  }
40
  else {
41
- stream.eatWhile(/[\w\\\-_]/);
42
  return ret("variable", "variable");
43
  }
44
  }
4
 
5
  function tokenBase(stream, state) {
6
  var ch = stream.next();
7
+ if (ch == "@") {stream.eatWhile(/[\w\\\-]/); return ret("meta", stream.current());}
8
  else if (ch == "/" && stream.eat("*")) {
9
  state.tokenize = tokenCComment;
10
  return tokenCComment(stream, state);
20
  return state.tokenize(stream, state);
21
  }
22
  else if (ch == "#") {
23
+ stream.eatWhile(/[\w\\\-]/);
24
  return ret("atom", "hash");
25
  }
26
  else if (ch == "!") {
38
  return ret(null, ch);
39
  }
40
  else {
41
+ stream.eatWhile(/[\w\\\-]/);
42
  return ret("variable", "variable");
43
  }
44
  }
libraries/CodeMirror2/mode/css/index.html ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <title>CodeMirror 2: CSS mode</title>
5
+ <link rel="stylesheet" href="../../lib/codemirror.css">
6
+ <script src="../../lib/codemirror.js"></script>
7
+ <script src="css.js"></script>
8
+ <link rel="stylesheet" href="../../theme/default.css">
9
+ <style>.CodeMirror {background: #f8f8f8;}</style>
10
+ <link rel="stylesheet" href="../../css/docs.css">
11
+ </head>
12
+ <body>
13
+ <h1>CodeMirror 2: CSS mode</h1>
14
+ <form><textarea id="code" name="code">
15
+ /* Some example CSS */
16
+
17
+ @import url("something.css");
18
+
19
+ body {
20
+ margin: 0;
21
+ padding: 3em 6em;
22
+ font-family: tahoma, arial, sans-serif;
23
+ color: #000;
24
+ }
25
+
26
+ #navigation a {
27
+ font-weight: bold;
28
+ text-decoration: none !important;
29
+ }
30
+
31
+ h1 {
32
+ font-size: 2.5em;
33
+ }
34
+
35
+ h2 {
36
+ font-size: 1.7em;
37
+ }
38
+
39
+ h1:before, h2:before {
40
+ content: "::";
41
+ }
42
+
43
+ code {
44
+ font-family: courier, monospace;
45
+ font-size: 80%;
46
+ color: #418A8A;
47
+ }
48
+ </textarea></form>
49
+ <script>
50
+ var editor = CodeMirror.fromTextArea(document.getElementById("code"), {});
51
+ </script>
52
+
53
+ <p><strong>MIME types defined:</strong> <code>text/css</code>.</p>
54
+
55
+ </body>
56
+ </html>
libraries/{codemirror/mode → CodeMirror2/mode/htmlmixed}/htmlmixed.js RENAMED
@@ -9,10 +9,12 @@ CodeMirror.defineMode("htmlmixed", function(config, parserConfig) {
9
  if (/^script$/i.test(state.htmlState.context.tagName)) {
10
  state.token = javascript;
11
  state.localState = jsMode.startState(htmlMode.indent(state.htmlState, ""));
 
12
  }
13
  else if (/^style$/i.test(state.htmlState.context.tagName)) {
14
  state.token = css;
15
  state.localState = cssMode.startState(htmlMode.indent(state.htmlState, ""));
 
16
  }
17
  }
18
  return style;
@@ -27,6 +29,7 @@ CodeMirror.defineMode("htmlmixed", function(config, parserConfig) {
27
  if (stream.match(/^<\/\s*script\s*>/i, false)) {
28
  state.token = html;
29
  state.curState = null;
 
30
  return html(stream, state);
31
  }
32
  return maybeBackup(stream, /<\/\s*script\s*>/,
@@ -36,6 +39,7 @@ CodeMirror.defineMode("htmlmixed", function(config, parserConfig) {
36
  if (stream.match(/^<\/\s*style\s*>/i, false)) {
37
  state.token = html;
38
  state.localState = null;
 
39
  return html(stream, state);
40
  }
41
  return maybeBackup(stream, /<\/\s*style\s*>/,
@@ -45,13 +49,14 @@ CodeMirror.defineMode("htmlmixed", function(config, parserConfig) {
45
  return {
46
  startState: function() {
47
  var state = htmlMode.startState();
48
- return {token: html, localState: null, htmlState: state};
49
  },
50
 
51
  copyState: function(state) {
52
  if (state.localState)
53
  var local = CodeMirror.copyState(state.token == css ? cssMode : jsMode, state.localState);
54
- return {token: state.token, localState: local, htmlState: CodeMirror.copyState(htmlMode, state.htmlState)};
 
55
  },
56
 
57
  token: function(stream, state) {
@@ -67,6 +72,10 @@ CodeMirror.defineMode("htmlmixed", function(config, parserConfig) {
67
  return cssMode.indent(state.localState, textAfter);
68
  },
69
 
 
 
 
 
70
  electricChars: "/{}:"
71
  }
72
  });
9
  if (/^script$/i.test(state.htmlState.context.tagName)) {
10
  state.token = javascript;
11
  state.localState = jsMode.startState(htmlMode.indent(state.htmlState, ""));
12
+ state.mode = "javascript";
13
  }
14
  else if (/^style$/i.test(state.htmlState.context.tagName)) {
15
  state.token = css;
16
  state.localState = cssMode.startState(htmlMode.indent(state.htmlState, ""));
17
+ state.mode = "css";
18
  }
19
  }
20
  return style;
29
  if (stream.match(/^<\/\s*script\s*>/i, false)) {
30
  state.token = html;
31
  state.curState = null;
32
+ state.mode = "html";
33
  return html(stream, state);
34
  }
35
  return maybeBackup(stream, /<\/\s*script\s*>/,
39
  if (stream.match(/^<\/\s*style\s*>/i, false)) {
40
  state.token = html;
41
  state.localState = null;
42
+ state.mode = "html";
43
  return html(stream, state);
44
  }
45
  return maybeBackup(stream, /<\/\s*style\s*>/,
49
  return {
50
  startState: function() {
51
  var state = htmlMode.startState();
52
+ return {token: html, localState: null, mode: "html", htmlState: state};
53
  },
54
 
55
  copyState: function(state) {
56
  if (state.localState)
57
  var local = CodeMirror.copyState(state.token == css ? cssMode : jsMode, state.localState);
58
+ return {token: state.token, localState: local, mode: state.mode,
59
+ htmlState: CodeMirror.copyState(htmlMode, state.htmlState)};
60
  },
61
 
62
  token: function(stream, state) {
72
  return cssMode.indent(state.localState, textAfter);
73
  },
74
 
75
+ compareStates: function(a, b) {
76
+ return htmlMode.compareStates(a.htmlState, b.htmlState);
77
+ },
78
+
79
  electricChars: "/{}:"
80
  }
81
  });
libraries/CodeMirror2/mode/htmlmixed/index.html ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <title>CodeMirror 2: HTML mixed mode</title>
5
+ <link rel="stylesheet" href="../../lib/codemirror.css">
6
+ <script src="../../lib/codemirror.js"></script>
7
+ <script src="../xml/xml.js"></script>
8
+ <script src="../javascript/javascript.js"></script>
9
+ <script src="../css/css.js"></script>
10
+ <link rel="stylesheet" href="../../theme/default.css">
11
+ <script src="htmlmixed.js"></script>
12
+ <link rel="stylesheet" href="../../css/docs.css">
13
+ <style>.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
14
+ </head>
15
+ <body>
16
+ <h1>CodeMirror 2: HTML mixed mode</h1>
17
+ <form><textarea id="code" name="code">
18
+ <html style="color: green">
19
+ <!-- this is a comment -->
20
+ <head>
21
+ <title>Mixed HTML Example</title>
22
+ <style type="text/css">
23
+ h1 {font-family: comic sans; color: #f0f;}
24
+ div {background: yellow !important;}
25
+ body {
26
+ max-width: 50em;
27
+ margin: 1em 2em 1em 5em;
28
+ }
29
+ </style>
30
+ </head>
31
+ <body>
32
+ <h1>Mixed HTML Example</h1>
33
+ <script>
34
+ function jsFunc(arg1, arg2) {
35
+ if (arg1 && arg2) document.body.innerHTML = "achoo";
36
+ }
37
+ </script>
38
+ </body>
39
+ </html>
40
+ </textarea></form>
41
+ <script>
42
+ var editor = CodeMirror.fromTextArea(document.getElementById("code"), {mode: "text/html", tabMode: "indent"});
43
+ </script>
44
+
45
+ <p>The HTML mixed mode depends on the XML, JavaScript, and CSS modes.</p>
46
+
47
+ <p><strong>MIME types defined:</strong> <code>text/html</code>
48
+ (redefined, only takes effect if you load this parser after the
49
+ XML parser).</p>
50
+
51
+ </body>
52
+ </html>
libraries/CodeMirror2/mode/javascript/index.html ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <title>CodeMirror 2: JavaScript mode</title>
5
+ <link rel="stylesheet" href="../../lib/codemirror.css">
6
+ <script src="../../lib/codemirror.js"></script>
7
+ <script src="javascript.js"></script>
8
+ <link rel="stylesheet" href="../../theme/default.css">
9
+ <link rel="stylesheet" href="../../css/docs.css">
10
+ <style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
11
+ </head>
12
+ <body>
13
+ <h1>CodeMirror 2: JavaScript mode</h1>
14
+
15
+ <div><textarea id="code" name="code">
16
+ // Demo code (the actual new parser character stream implementation)
17
+
18
+ function StringStream(string) {
19
+ this.pos = 0;
20
+ this.string = string;
21
+ }
22
+
23
+ StringStream.prototype = {
24
+ done: function() {return this.pos >= this.string.length;},
25
+ peek: function() {return this.string.charAt(this.pos);},
26
+ next: function() {
27
+ if (this.pos &lt; this.string.length)
28
+ return this.string.charAt(this.pos++);
29
+ },
30
+ eat: function(match) {
31
+ var ch = this.string.charAt(this.pos);
32
+ if (typeof match == "string") var ok = ch == match;
33
+ else var ok = ch &amp;&amp; match.test ? match.test(ch) : match(ch);
34
+ if (ok) {this.pos++; return ch;}
35
+ },
36
+ eatWhile: function(match) {
37
+ var start = this.pos;
38
+ while (this.eat(match));
39
+ if (this.pos > start) return this.string.slice(start, this.pos);
40
+ },
41
+ backUp: function(n) {this.pos -= n;},
42
+ column: function() {return this.pos;},
43
+ eatSpace: function() {
44
+ var start = this.pos;
45
+ while (/\s/.test(this.string.charAt(this.pos))) this.pos++;
46
+ return this.pos - start;
47
+ },
48
+ match: function(pattern, consume, caseInsensitive) {
49
+ if (typeof pattern == "string") {
50
+ function cased(str) {return caseInsensitive ? str.toLowerCase() : str;}
51
+ if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) {
52
+ if (consume !== false) this.pos += str.length;
53
+ return true;
54
+ }
55
+ }
56
+ else {
57
+ var match = this.string.slice(this.pos).match(pattern);
58
+ if (match &amp;&amp; consume !== false) this.pos += match[0].length;
59
+ return match;
60
+ }
61
+ }
62
+ };
63
+ </textarea></div>
64
+
65
+ <script>
66
+ var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
67
+ lineNumbers: true,
68
+ matchBrackets: true
69
+ });
70
+ </script>
71
+
72
+ <p>JavaScript mode supports a single configuration
73
+ option, <code>json</code>, which will set the mode to expect JSON
74
+ data rather than a JavaScript program.</p>
75
+
76
+ <p><strong>MIME types defined:</strong> <code>text/javascript</code>, <code>application/json</code>.</p>
77
+ </body>
78
+ </html>
libraries/{codemirror/mode → CodeMirror2/mode/javascript}/javascript.js RENAMED
@@ -11,7 +11,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
11
  return {
12
  "if": A, "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
13
  "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C,
14
- "var": kw("var"), "function": kw("function"), "catch": kw("catch"),
 
15
  "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
16
  "in": operator, "typeof": operator, "instanceof": operator,
17
  "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom
@@ -51,11 +52,11 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
51
  return ret(ch);
52
  else if (ch == "0" && stream.eat(/x/i)) {
53
  stream.eatWhile(/[\da-f]/i);
54
- return ret("number", "atom");
55
  }
56
  else if (/\d/.test(ch)) {
57
- stream.match(/^\d*(?:\.\d*)?(?:e[+\-]?\d+)?/);
58
- return ret("number", "atom");
59
  }
60
  else if (ch == "/") {
61
  if (stream.eat("*")) {
@@ -75,6 +76,10 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
75
  return ret("operator", null, stream.current());
76
  }
77
  }
 
 
 
 
78
  else if (isOperatorChar.test(ch)) {
79
  stream.eatWhile(isOperatorChar);
80
  return ret("operator", null, stream.current());
@@ -224,13 +229,18 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
224
  function expression(type) {
225
  if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator);
226
  if (type == "function") return cont(functiondef);
227
- if (type == "keyword c") return cont(expression);
228
  if (type == "(") return cont(pushlex(")"), expression, expect(")"), poplex, maybeoperator);
229
  if (type == "operator") return cont(expression);
230
  if (type == "[") return cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator);
231
  if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator);
232
  return cont();
233
  }
 
 
 
 
 
234
  function maybeoperator(type, value) {
235
  if (type == "operator" && /\+\+|--/.test(value)) return cont(maybeoperator);
236
  if (type == "operator") return cont(expression);
11
  return {
12
  "if": A, "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
13
  "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C,
14
+ "var": kw("var"), "const": kw("var"), "let": kw("var"),
15
+ "function": kw("function"), "catch": kw("catch"),
16
  "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
17
  "in": operator, "typeof": operator, "instanceof": operator,
18
  "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom
52
  return ret(ch);
53
  else if (ch == "0" && stream.eat(/x/i)) {
54
  stream.eatWhile(/[\da-f]/i);
55
+ return ret("number", "number");
56
  }
57
  else if (/\d/.test(ch)) {
58
+ stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
59
+ return ret("number", "number");
60
  }
61
  else if (ch == "/") {
62
  if (stream.eat("*")) {
76
  return ret("operator", null, stream.current());
77
  }
78
  }
79
+ else if (ch == "#") {
80
+ stream.skipToEnd();
81
+ return ret("error", "error");
82
+ }
83
  else if (isOperatorChar.test(ch)) {
84
  stream.eatWhile(isOperatorChar);
85
  return ret("operator", null, stream.current());
229
  function expression(type) {
230
  if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator);
231
  if (type == "function") return cont(functiondef);
232
+ if (type == "keyword c") return cont(maybeexpression);
233
  if (type == "(") return cont(pushlex(")"), expression, expect(")"), poplex, maybeoperator);
234
  if (type == "operator") return cont(expression);
235
  if (type == "[") return cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator);
236
  if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator);
237
  return cont();
238
  }
239
+ function maybeexpression(type) {
240
+ if (type.match(/[;\}\)\],]/)) return pass();
241
+ return pass(expression);
242
+ }
243
+
244
  function maybeoperator(type, value) {
245
  if (type == "operator" && /\+\+|--/.test(value)) return cont(maybeoperator);
246
  if (type == "operator") return cont(expression);
libraries/CodeMirror2/mode/php/index.html ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <title>CodeMirror 2: PHP mode</title>
5
+ <link rel="stylesheet" href="../../lib/codemirror.css">
6
+ <script src="../../lib/codemirror.js"></script>
7
+ <script src="../xml/xml.js"></script>
8
+ <script src="../javascript/javascript.js"></script>
9
+ <script src="../css/css.js"></script>
10
+ <script src="../clike/clike.js"></script>
11
+ <script src="php.js"></script>
12
+ <link rel="stylesheet" href="../../theme/default.css">
13
+ <style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
14
+ <link rel="stylesheet" href="../../css/docs.css">
15
+ </head>
16
+ <body>
17
+ <h1>CodeMirror 2: PHP mode</h1>
18
+
19
+ <form><textarea id="code" name="code">
20
+ <?php
21
+ function hello($who) {
22
+ return "Hello " . $who;
23
+ }
24
+ ?>
25
+ <p>The program says <?= hello("World") ?>.</p>
26
+ <script>
27
+ alert("And here is some JS code"); // also colored
28
+ </script>
29
+ </textarea></form>
30
+
31
+ <script>
32
+ var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
33
+ lineNumbers: true,
34
+ matchBrackets: true,
35
+ mode: "application/x-httpd-php",
36
+ indentUnit: 8,
37
+ indentWithTabs: true,
38
+ enterMode: "keep",
39
+ tabMode: "shift"
40
+ });
41
+ </script>
42
+
43
+ <p>Simple HTML/PHP mode based on
44
+ the <a href="../clike/">C-like</a> mode. Depends on XML,
45
+ JavaScript, CSS, and C-like modes.</p>
46
+
47
+ <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>
48
+ </body>
49
+ </html>
libraries/CodeMirror2/mode/php/php.js ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 cfunction class clone const continue declare " +
17
+ "default do else elseif enddeclare endfor endforeach endif endswitch endwhile extends " +
18
+ "final for foreach function global goto if implements interface instanceof namespace " +
19
+ "new or private protected public static switch throw try use var while xor return" +
20
+ "die echo empty exit eval include include_once isset list require require_once print unset"),
21
+ blockKeywords: keywords("catch do else elseif for foreach if switch try while"),
22
+ atoms: keywords("true false null TRUE FALSE NULL"),
23
+ multiLineStrings: true,
24
+ hooks: {
25
+ "$": function(stream, state) {
26
+ stream.eatWhile(/[\w\$_]/);
27
+ return "variable-2";
28
+ },
29
+ "<": function(stream, state) {
30
+ if (stream.match(/<</)) {
31
+ stream.eatWhile(/[\w\.]/);
32
+ state.tokenize = heredoc(stream.current().slice(3));
33
+ return state.tokenize(stream, state);
34
+ }
35
+ return false;
36
+ },
37
+ "#": function(stream, state) {
38
+ stream.skipToEnd();
39
+ return "comment";
40
+ }
41
+ }
42
+ };
43
+
44
+ CodeMirror.defineMode("php", function(config, parserConfig) {
45
+ var htmlMode = CodeMirror.getMode(config, "text/html");
46
+ var jsMode = CodeMirror.getMode(config, "text/javascript");
47
+ var cssMode = CodeMirror.getMode(config, "text/css");
48
+ var phpMode = CodeMirror.getMode(config, phpConfig);
49
+
50
+ function dispatch(stream, state) { // TODO open PHP inside text/css
51
+ if (state.curMode == htmlMode) {
52
+ var style = htmlMode.token(stream, state.curState);
53
+ if (style == "meta" && /^<\?/.test(stream.current())) {
54
+ state.curMode = phpMode;
55
+ state.curState = state.php;
56
+ state.curClose = /^\?>/;
57
+ state.mode = 'php';
58
+ }
59
+ else if (style == "tag" && stream.current() == ">" && state.curState.context) {
60
+ if (/^script$/i.test(state.curState.context.tagName)) {
61
+ state.curMode = jsMode;
62
+ state.curState = jsMode.startState(htmlMode.indent(state.curState, ""));
63
+ state.curClose = /^<\/\s*script\s*>/i;
64
+ state.mode = 'javascript';
65
+ }
66
+ else if (/^style$/i.test(state.curState.context.tagName)) {
67
+ state.curMode = cssMode;
68
+ state.curState = cssMode.startState(htmlMode.indent(state.curState, ""));
69
+ state.curClose = /^<\/\s*style\s*>/i;
70
+ state.mode = 'css';
71
+ }
72
+ }
73
+ return style;
74
+ }
75
+ else if (stream.match(state.curClose, false)) {
76
+ state.curMode = htmlMode;
77
+ state.curState = state.html;
78
+ state.curClose = null;
79
+ state.mode = 'html';
80
+ return dispatch(stream, state);
81
+ }
82
+ else return state.curMode.token(stream, state.curState);
83
+ }
84
+
85
+ return {
86
+ startState: function() {
87
+ var html = htmlMode.startState();
88
+ return {html: html,
89
+ php: phpMode.startState(),
90
+ curMode: parserConfig.startOpen ? phpMode : htmlMode,
91
+ curState: parserConfig.startOpen ? phpMode.startState() : html,
92
+ curClose: parserConfig.startOpen ? /^\?>/ : null,
93
+ mode: parserConfig.startOpen ? 'php' : 'html'}
94
+ },
95
+
96
+ copyState: function(state) {
97
+ var html = state.html, htmlNew = CodeMirror.copyState(htmlMode, html),
98
+ php = state.php, phpNew = CodeMirror.copyState(phpMode, php), cur;
99
+ if (state.curState == html) cur = htmlNew;
100
+ else if (state.curState == php) cur = phpNew;
101
+ else cur = CodeMirror.copyState(state.curMode, state.curState);
102
+ return {html: htmlNew, php: phpNew, curMode: state.curMode, curState: cur, curClose: state.curClose};
103
+ },
104
+
105
+ token: dispatch,
106
+
107
+ indent: function(state, textAfter) {
108
+ if ((state.curMode != phpMode && /^\s*<\//.test(textAfter)) ||
109
+ (state.curMode == phpMode && /^\?>/.test(textAfter)))
110
+ return htmlMode.indent(state.html, textAfter);
111
+ return state.curMode.indent(state.curState, textAfter);
112
+ },
113
+
114
+ electricChars: "/{}:"
115
+ }
116
+ });
117
+ CodeMirror.defineMIME("application/x-httpd-php", "php");
118
+ CodeMirror.defineMIME("application/x-httpd-php-open", {name: "php", startOpen: true});
119
+ CodeMirror.defineMIME("text/x-php", phpConfig);
120
+ })();
libraries/CodeMirror2/mode/xml/index.html ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <title>CodeMirror 2: XML mode</title>
5
+ <link rel="stylesheet" href="../../lib/codemirror.css">
6
+ <script src="../../lib/codemirror.js"></script>
7
+ <script src="xml.js"></script>
8
+ <link rel="stylesheet" href="../../theme/default.css">
9
+ <style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
10
+ <link rel="stylesheet" href="../../css/docs.css">
11
+ </head>
12
+ <body>
13
+ <h1>CodeMirror 2: XML mode</h1>
14
+ <form><textarea id="code" name="code">
15
+ &lt;html style="color: green"&gt;
16
+ &lt;!-- this is a comment --&gt;
17
+ &lt;head&gt;
18
+ &lt;title&gt;HTML Example&lt;/title&gt;
19
+ &lt;/head&gt;
20
+ &lt;body&gt;
21
+ The indentation tries to be &lt;em&gt;somewhat &amp;quot;do what
22
+ I mean&amp;quot;&lt;/em&gt;... but might not match your style.
23
+ &lt;/body&gt;
24
+ &lt;/html&gt;
25
+ </textarea></form>
26
+ <script>
27
+ var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
28
+ mode: {name: "xml", htmlMode: true},
29
+ lineNumbers: true
30
+ });
31
+ </script>
32
+ <p>The XML mode supports two configuration parameters:</p>
33
+ <dl>
34
+ <dt><code>htmlMode (boolean)</code></dt>
35
+ <dd>This switches the mode to parse HTML instead of XML. This
36
+ means attributes do not have to be quoted, and some elements
37
+ (such as <code>br</code>) do not require a closing tag.</dd>
38
+ <dt><code>alignCDATA (boolean)</code></dt>
39
+ <dd>Setting this to true will force the opening tag of CDATA
40
+ blocks to not be indented.</dd>
41
+ </dl>
42
+
43
+ <p><strong>MIME types defined:</strong> <code>application/xml</code>, <code>text/html</code>.</p>
44
+ </body>
45
+ </html>
libraries/{codemirror/mode → CodeMirror2/mode/xml}/xml.js RENAMED
@@ -25,7 +25,7 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
25
  else return null;
26
  }
27
  else if (stream.match("--")) return chain(inBlock("comment", "-->"));
28
- else if (stream.match("DOCTYPE")) {
29
  stream.eatWhile(/[\w\._\-]/);
30
  return chain(inBlock("meta", ">"));
31
  }
@@ -128,7 +128,16 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
128
 
129
  function element(type) {
130
  if (type == "openTag") {curState.tagName = tagName; return cont(attributes, endtag(curState.startOfLine));}
131
- else if (type == "closeTag") {popContext(); return cont(endclosetag);}
 
 
 
 
 
 
 
 
 
132
  else if (type == "string") {
133
  if (!curState.context || curState.context.name != "!cdata") pushContext("!cdata");
134
  if (curState.tokenize == inText) popContext();
@@ -145,21 +154,30 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
145
  return cont();
146
  };
147
  }
148
- function endclosetag(type) {
149
- if (type == "endTag") return cont();
150
- return pass();
 
 
 
 
151
  }
152
 
153
  function attributes(type) {
154
  if (type == "word") {setStyle = "attribute"; return cont(attributes);}
155
  if (type == "equals") return cont(attvalue, attributes);
 
156
  return pass();
157
  }
158
  function attvalue(type) {
159
  if (type == "word" && Kludges.allowUnquoted) {setStyle = "string"; return cont();}
160
- if (type == "string") return cont();
161
  return pass();
162
  }
 
 
 
 
163
 
164
  return {
165
  startState: function() {
@@ -175,7 +193,8 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
175
 
176
  setStyle = type = tagName = null;
177
  var style = state.tokenize(stream, state);
178
- if ((style || type) && style != "xml-comment") {
 
179
  curState = state;
180
  while (true) {
181
  var comb = state.cc.pop() || element;
@@ -198,6 +217,14 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
198
  else return 0;
199
  },
200
 
 
 
 
 
 
 
 
 
201
  electricChars: "/"
202
  };
203
  });
25
  else return null;
26
  }
27
  else if (stream.match("--")) return chain(inBlock("comment", "-->"));
28
+ else if (stream.match("DOCTYPE", true, true)) {
29
  stream.eatWhile(/[\w\._\-]/);
30
  return chain(inBlock("meta", ">"));
31
  }
128
 
129
  function element(type) {
130
  if (type == "openTag") {curState.tagName = tagName; return cont(attributes, endtag(curState.startOfLine));}
131
+ else if (type == "closeTag") {
132
+ var err = false;
133
+ if (curState.context) {
134
+ err = curState.context.tagName != tagName;
135
+ } else {
136
+ err = true;
137
+ }
138
+ if (err) setStyle = "error";
139
+ return cont(endclosetag(err));
140
+ }
141
  else if (type == "string") {
142
  if (!curState.context || curState.context.name != "!cdata") pushContext("!cdata");
143
  if (curState.tokenize == inText) popContext();
154
  return cont();
155
  };
156
  }
157
+ function endclosetag(err) {
158
+ return function(type) {
159
+ if (err) setStyle = "error";
160
+ if (type == "endTag") { popContext(); return cont(); }
161
+ setStyle = "error";
162
+ return cont(arguments.callee);
163
+ }
164
  }
165
 
166
  function attributes(type) {
167
  if (type == "word") {setStyle = "attribute"; return cont(attributes);}
168
  if (type == "equals") return cont(attvalue, attributes);
169
+ if (type == "string") {setStyle = "error"; return cont(attributes);}
170
  return pass();
171
  }
172
  function attvalue(type) {
173
  if (type == "word" && Kludges.allowUnquoted) {setStyle = "string"; return cont();}
174
+ if (type == "string") return cont(attvaluemaybe);
175
  return pass();
176
  }
177
+ function attvaluemaybe(type) {
178
+ if (type == "string") return cont(attvaluemaybe);
179
+ else return pass();
180
+ }
181
 
182
  return {
183
  startState: function() {
193
 
194
  setStyle = type = tagName = null;
195
  var style = state.tokenize(stream, state);
196
+ state.type = type;
197
+ if ((style || type) && style != "comment") {
198
  curState = state;
199
  while (true) {
200
  var comb = state.cc.pop() || element;
217
  else return 0;
218
  },
219
 
220
+ compareStates: function(a, b) {
221
+ if (a.indented != b.indented || a.tokenize != b.tokenize) return false;
222
+ for (var ca = a.context, cb = b.context; ; ca = ca.prev, cb = cb.prev) {
223
+ if (!ca || !cb) return ca == cb;
224
+ if (ca.tagName != cb.tagName) return false;
225
+ }
226
+ },
227
+
228
  electricChars: "/"
229
  };
230
  });
libraries/CodeMirror2/theme/cobalt.css ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .cm-s-cobalt { background: #002240; color: white; }
2
+ .cm-s-cobalt span.CodeMirror-selected { background: #b36539 !important; }
3
+ .cm-s-cobalt .CodeMirror-gutter { background: #002240; border-right: 1px solid #aaa; }
4
+ .cm-s-cobalt .CodeMirror-gutter-text { color: #d0d0d0; }
5
+ .cm-s-cobalt .CodeMirror-cursor { border-left: 1px solid white !important; }
6
+
7
+ .cm-s-cobalt span.cm-comment { color: #08f; }
8
+ .cm-s-cobalt span.cm-atom { color: #845dc4; }
9
+ .cm-s-cobalt span.cm-number, .cm-s-cobalt span.cm-attribute { color: #ff80e1; }
10
+ .cm-s-cobalt span.cm-keyword { color: #ffee80; }
11
+ .cm-s-cobalt span.cm-string { color: #3ad900; }
12
+ .cm-s-cobalt span.cm-meta { color: #ff9d00; }
13
+ .cm-s-cobalt span.cm-variable-2, .cm-s-cobalt span.cm-tag { color: #9effff; }
14
+ .cm-s-cobalt span.cm-variable-3, .cm-s-cobalt span.cm-def { color: white; }
15
+ .cm-s-cobalt span.cm-error { color: #9d1e15; }
16
+ .cm-s-cobalt span.cm-bracket { color: #d8d8d8; }
17
+ .cm-s-cobalt span.cm-builtin, .cm-s-cobalt span.cm-special { color: #ff9e59; }
libraries/{codemirror → CodeMirror2}/theme/default.css RENAMED
@@ -4,11 +4,12 @@
4
  .cm-s-default span.cm-def {color: #00f;}
5
  .cm-s-default span.cm-variable {color: black;}
6
  .cm-s-default span.cm-variable-2 {color: #05a;}
7
- .cm-s-default span.cm-variable-3 {color: #0a5;}
8
  .cm-s-default span.cm-property {color: black;}
9
  .cm-s-default span.cm-operator {color: black;}
10
  .cm-s-default span.cm-comment {color: #a50;}
11
  .cm-s-default span.cm-string {color: #a11;}
 
12
  .cm-s-default span.cm-meta {color: #555;}
13
  .cm-s-default span.cm-error {color: #f00;}
14
  .cm-s-default span.cm-qualifier {color: #555;}
4
  .cm-s-default span.cm-def {color: #00f;}
5
  .cm-s-default span.cm-variable {color: black;}
6
  .cm-s-default span.cm-variable-2 {color: #05a;}
7
+ .cm-s-default span.cm-variable-3 {color: #085;}
8
  .cm-s-default span.cm-property {color: black;}
9
  .cm-s-default span.cm-operator {color: black;}
10
  .cm-s-default span.cm-comment {color: #a50;}
11
  .cm-s-default span.cm-string {color: #a11;}
12
+ .cm-s-default span.cm-string-2 {color: #f50;}
13
  .cm-s-default span.cm-meta {color: #555;}
14
  .cm-s-default span.cm-error {color: #f00;}
15
  .cm-s-default span.cm-qualifier {color: #555;}
libraries/CodeMirror2/theme/eclipse.css ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .cm-s-eclipse span.cm-meta {color: #FF1717;}
2
+ .cm-s-eclipse span.cm-keyword { font-weight: bold; color: #7F0055; }
3
+ .cm-s-eclipse span.cm-atom {color: #219;}
4
+ .cm-s-eclipse span.cm-number {color: #164;}
5
+ .cm-s-eclipse span.cm-def {color: #00f;}
6
+ .cm-s-eclipse span.cm-variable {color: black;}
7
+ .cm-s-eclipse span.cm-variable-2 {color: #0000C0;}
8
+ .cm-s-eclipse span.cm-variable-3 {color: #0000C0;}
9
+ .cm-s-eclipse span.cm-property {color: black;}
10
+ .cm-s-eclipse span.cm-operator {color: black;}
11
+ .cm-s-eclipse span.cm-comment {color: #3F7F5F;}
12
+ .cm-s-eclipse span.cm-string {color: #2A00FF;}
13
+ .cm-s-eclipse span.cm-string-2 {color: #f50;}
14
+ .cm-s-eclipse span.cm-error {color: #f00;}
15
+ .cm-s-eclipse span.cm-qualifier {color: #555;}
16
+ .cm-s-eclipse span.cm-builtin {color: #30a;}
17
+ .cm-s-eclipse span.cm-bracket {color: #cc7;}
18
+ .cm-s-eclipse span.cm-tag {color: #170;}
19
+ .cm-s-eclipse span.cm-attribute {color: #00c;}
20
+
21
+ .CodeMirror-matchingbracket{
22
+ border:1px solid grey;
23
+ color:black !important;;
24
+ }
libraries/{codemirror → CodeMirror2}/theme/elegant.css RENAMED
File without changes
libraries/CodeMirror2/theme/monokai.css ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Based on Sublime Text's Monokai theme */
2
+
3
+ .cm-s-monokai { background: #272822; color: #F8F8F2; }
4
+ .cm-s-monokai span.CodeMirror-selected { background: #FFE792 !important; }
5
+ .cm-s-monokai .CodeMirror-gutter { background: #272822; border-right: 0px; }
6
+ .cm-s-monokai .CodeMirror-gutter-text { color: #d0d0d0; }
7
+ .cm-s-monokai .CodeMirror-cursor { border-left: 1px solid #F8F8F0 !important; }
8
+
9
+ .cm-s-monokai span.cm-comment { color: #75715E; }
10
+ .cm-s-monokai span.cm-atom { color: #AE81FF; }
11
+ .cm-s-monokai span.cm-number { color: #AE81FF; }
12
+
13
+ .cm-s-monokai span.cm-property, .cm-s-monokai span.cm-attribute {color: #A6E22E;}
14
+ .cm-s-monokai span.cm-keyword { color: #F92672; }
15
+ .cm-s-monokai span.cm-string { color: #E6DB74; }
16
+
17
+ .cm-s-monokai span.cm-variable {color: #A6E22E;}
18
+ .cm-s-monokai span.cm-variable-2 { color: #9effff; }
19
+ .cm-s-monokai span.cm-def { color: #FD971F; }
20
+ .cm-s-monokai span.cm-error { background: #F92672; color: #F8F8F0; }
21
+ .cm-s-monokai span.cm-bracket { color: #f8F8F2; }
22
+ .cm-s-monokai span.cm-tag {color: #F92672;}
23
+
24
+ .CodeMirror-matchingbracket{
25
+ text-decoration: underline;
26
+ color: white !important;
27
+ }
libraries/{codemirror → CodeMirror2}/theme/neat.css RENAMED
File without changes
libraries/{codemirror → CodeMirror2}/theme/night.css RENAMED
@@ -8,12 +8,12 @@
8
 
9
  .cm-s-night span.cm-comment { color: #6900a1; }
10
  .cm-s-night span.cm-atom { color: #845dc4; }
11
- .cm-s-night span.cm-number { color: #ffd500; }
12
  .cm-s-night span.cm-keyword { color: #599eff; }
13
  .cm-s-night span.cm-string { color: #37f14a; }
14
  .cm-s-night span.cm-meta { color: #7678e2; }
15
- .cm-s-night span.cm-variable-2 { color: #99b2ff; }
16
- .cm-s-night span.cm-variable-3, .cm-s-night span.cm-def { white; }
17
  .cm-s-night span.cm-error { color: #9d1e15; }
18
  .cm-s-night span.cm-bracket { color: #8da6ce; }
19
  .cm-s-night span.cm-comment { color: #6900a1; }
8
 
9
  .cm-s-night span.cm-comment { color: #6900a1; }
10
  .cm-s-night span.cm-atom { color: #845dc4; }
11
+ .cm-s-night span.cm-number, .cm-s-night span.cm-attribute { color: #ffd500; }
12
  .cm-s-night span.cm-keyword { color: #599eff; }
13
  .cm-s-night span.cm-string { color: #37f14a; }
14
  .cm-s-night span.cm-meta { color: #7678e2; }
15
+ .cm-s-night span.cm-variable-2, .cm-s-night span.cm-tag { color: #99b2ff; }
16
+ .cm-s-night span.cm-variable-3, .cm-s-night span.cm-def { color: white; }
17
  .cm-s-night span.cm-error { color: #9d1e15; }
18
  .cm-s-night span.cm-bracket { color: #8da6ce; }
19
  .cm-s-night span.cm-comment { color: #6900a1; }
libraries/CodeMirror2/theme/rubyblue.css ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .cm-s-rubyblue { font:13px/1.4em Trebuchet, Verdana, sans-serif; } /* - customized editor font - */
2
+
3
+ .cm-s-rubyblue { background: #112435; color: white; }
4
+ .cm-s-rubyblue span.CodeMirror-selected { background: #0000FF !important; }
5
+ .cm-s-rubyblue .CodeMirror-gutter { background: #1F4661; border-right: 7px solid #3E7087; min-width:2.5em; }
6
+ .cm-s-rubyblue .CodeMirror-gutter-text { color: white; }
7
+ .cm-s-rubyblue .CodeMirror-cursor { border-left: 1px solid white !important; }
8
+
9
+ .cm-s-rubyblue span.cm-comment { color: #999; font-style:italic; }
10
+ .cm-s-rubyblue span.cm-atom { color: #F4C20B; }
11
+ .cm-s-rubyblue span.cm-number, .cm-s-rubyblue span.cm-attribute { color: #82C6E0; }
12
+ .cm-s-rubyblue span.cm-keyword { color: #F0F; }
13
+ .cm-s-rubyblue span.cm-string { color: #F08047; }
14
+ .cm-s-rubyblue span.cm-meta { color: #F0F; }
15
+ .cm-s-rubyblue span.cm-variable-2, .cm-s-rubyblue span.cm-tag { color: #7BD827; }
16
+ .cm-s-rubyblue span.cm-variable-3, .cm-s-rubyblue span.cm-def { color: white; }
17
+ .cm-s-rubyblue span.cm-error { color: #AF2018; }
18
+ .cm-s-rubyblue span.cm-bracket { color: #F0F; }
19
+ .cm-s-rubyblue span.CodeMirror-matchingbracket { color:#F0F !important; }
20
+ .cm-s-rubyblue span.cm-builtin, .cm-s-rubyblue span.cm-special { color: #FF9D00; }
libraries/codemirror/lib/overlay.js DELETED
@@ -1,51 +0,0 @@
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
- CodeMirror.overlayParser = function(base, overlay, combine) {
10
- return {
11
- startState: function() {
12
- return {
13
- base: CodeMirror.startState(base),
14
- overlay: CodeMirror.startState(overlay),
15
- basePos: 0, baseCur: null,
16
- overlayPos: 0, overlayCur: null
17
- };
18
- },
19
- copyState: function(state) {
20
- return {
21
- base: CodeMirror.copyState(base, state.base),
22
- overlay: CodeMirror.copyState(overlay, state.overlay),
23
- basePos: state.basePos, baseCur: null,
24
- overlayPos: state.overlayPos, overlayCur: null
25
- };
26
- },
27
-
28
- token: function(stream, state) {
29
- if (stream.start == state.basePos) {
30
- state.baseCur = base.token(stream, state.base);
31
- state.basePos = stream.pos;
32
- }
33
- if (stream.start == state.overlayPos) {
34
- stream.pos = stream.start;
35
- state.overlayCur = overlay.token(stream, state.overlay);
36
- state.overlayPos = stream.pos;
37
- }
38
- stream.pos = Math.min(state.basePos, state.overlayPos);
39
- if (stream.eol()) state.basePos = state.overlayPos = 0;
40
-
41
- if (state.overlayCur == null) return state.baseCur;
42
- if (state.baseCur != null && combine) return state.baseCur + " " + state.overlayCur;
43
- else return state.overlayCur;
44
- },
45
-
46
- indent: function(state, textAfter) {
47
- return base.indent(state.base, textAfter);
48
- },
49
- electricChars: base.electricChars
50
- };
51
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
libraries/codemirror/lib/runmode.js DELETED
@@ -1,27 +0,0 @@
1
- CodeMirror.runMode = function(string, modespec, callback) {
2
- var mode = CodeMirror.getMode({indentUnit: 2}, modespec);
3
- var isNode = callback.nodeType == 1;
4
- if (isNode) {
5
- var node = callback, accum = [];
6
- callback = function(string, style) {
7
- if (string == "\n")
8
- accum.push("<br>");
9
- else if (style)
10
- accum.push("<span class=\"cm-" + CodeMirror.htmlEscape(style) + "\">" + CodeMirror.htmlEscape(string) + "</span>");
11
- else
12
- accum.push(CodeMirror.htmlEscape(string));
13
- }
14
- }
15
- var lines = CodeMirror.splitLines(string), state = CodeMirror.startState(mode);
16
- for (var i = 0, e = lines.length; i < e; ++i) {
17
- if (i) callback("\n");
18
- var stream = new CodeMirror.StringStream(lines[i]);
19
- while (!stream.eol()) {
20
- var style = mode.token(stream, state);
21
- callback(stream.current(), style);
22
- stream.start = stream.pos;
23
- }
24
- }
25
- if (isNode)
26
- node.innerHTML = accum.join("");
27
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
screenshot-1.png CHANGED
Binary file
screenshot-2.png ADDED
Binary file
screenshot-3.png ADDED
Binary file
screenshot-4.png ADDED
Binary file
screenshot-5.png ADDED
Binary file
screenshot-6.png ADDED
Binary file
scripts-n-styles.php CHANGED
@@ -1,316 +1,313 @@
1
- <?php
2
- /*
3
- Plugin Name: Scripts n Styles
4
- Plugin URI: http://www.unfocus.com/projects/scripts-n-styles/
5
- Description: Allows WordPress admin users the ability to add custom CSS and JavaScript directly to individual Post, Pages or custom post types.
6
- Author: unFocus Projects
7
- Author URI: http://www.unfocus.com/
8
- Version: 2.0.4
9
- License: GPL2
10
- Network: true
11
- */
12
- /* Copyright 2010-2011 Kenneth Newman www.unfocus.com
13
-
14
- This program is free software; you can redistribute it and/or modify
15
- it under the terms of the GNU General Public License, version 2, as
16
- published by the Free Software Foundation.
17
-
18
- This program is distributed in the hope that it will be useful,
19
- but WITHOUT ANY WARRANTY; without even the implied warranty of
20
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21
- GNU General Public License for more details.
22
-
23
- You should have received a copy of the GNU General Public License
24
- along with this program; if not, write to the Free Software
25
- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
26
- */
27
-
28
- /**
29
- * Scripts n Styles
30
- *
31
- * Allows WordPress admin users the ability to add custom CSS
32
- * and JavaScript directly to individual Post, Pages or custom
33
- * post types.
34
- *
35
- * NOTE: No user except the "Super Admin" can use this plugin in MultiSite. I'll add features for MultiSite later, perhaps the ones below...
36
- * The "Super Admin" user has exclusive 'unfiltered_html' capabilities in MultiSite. Also, options.php checks for is_super_admin()
37
- * so the 'manage_options' capability for blog admins is insufficient to pass the check to manage options directly.
38
- *
39
- * The Tentative plan is for Super Admins to create Snippets or Shortcodes approved for use by users with certain capabilities
40
- * ('unfiltered_html' and/or 'manage_options'). The 'unfiltered_html' capability can be granted via another plugin. This plugin will
41
- * not deal with granting any capabilities.
42
- *
43
- * @package Scripts_n_Styles
44
- * @link http://www.unfocus.com/projects/scripts-n-styles/ Plugin URI
45
- * @author unFocus Projects
46
- * @link http://www.unfocus.com/ Author URI
47
- * @version 2.0.4
48
- * @license http://opensource.org/licenses/gpl-license.php GNU Public License
49
- * @copyright Copyright (c) 2010-2011, Kenneth Newman
50
- * @todo Add Post Type Selection on Options Page? Not sure that's usefull.
51
- * @todo Add Conditional Tags support as alternative to Globally applying Scripts n Styles.
52
- * @todo Create ability to add and register scripts and styles for enqueueing (via Options page).
53
- * @todo Create selection on Option page of which to pick registered scripts to make available on edit screens.
54
- * @todo Create shortcode to embed html/javascript snippets. See http://scribu.net/wordpress/optimal-script-loading.html in which this is already figured out :-)
55
- * @todo Create shortcode registration on Options page to make those snippets available on edit screens.
56
- * @todo Create shortcode registration of html snippets on edit screens for single use.
57
- * @todo Figure out and add Error messaging.
58
- * @todo Add ability to push class names into the TinyMCE editor Style Dropdown.
59
- * @todo Replace Multi-Select element with something better.
60
- * @todo Clean up Usage Table, paginate, don't display when empty.
61
- * @todo "Include Scripts" will be reintroduced when registing is finished.
62
- */
63
-
64
- class Scripts_n_Styles
65
- {
66
- /**#@+
67
- * @static
68
- */
69
- static $file = __FILE__;
70
- static $hook_suffix; // 'tools_page_Scripts-n-Styles';
71
- static $plugin_file; // 'scripts-n-styles/scripts-n-styles.php'; kept here for reference
72
- /**#@-*/
73
-
74
- /**
75
- * Initializing method. Checks if is_admin() and registers action hooks for admin if true. Sets filters and actions for Theme side functions.
76
- * @static
77
- */
78
- static function init() {
79
-
80
- if ( is_admin() && ! ( defined('DISALLOW_UNFILTERED_HTML') && DISALLOW_UNFILTERED_HTML ) ) {
81
- /* NOTE: Setting the DISALLOW_UNFILTERED_HTML constant to
82
- true in the wp-config.php would effectively disable this
83
- plugin's admin because no user would have the capability.
84
- */
85
- include_once( 'includes/class.SnS_Admin.php' );
86
- SnS_Admin::init();
87
-
88
- }
89
-
90
- add_filter( 'body_class', array( __CLASS__, 'body_classes' ) );
91
- add_filter( 'post_class', array( __CLASS__, 'post_classes' ) );
92
-
93
- add_action( 'wp_head', array( __CLASS__, 'styles' ), 11 );
94
- //add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_scripts' ), 11 );
95
- add_action( 'wp_head', array( __CLASS__, 'scripts_in_head' ), 11 );
96
- add_action( 'wp_footer', array( __CLASS__, 'scripts' ), 11 );
97
- }
98
-
99
- /**
100
- * Utility Method: Returns the value of $scripts if it is set, and if not, sets it via a call to the database.
101
- * @return array 'ufp_script' meta data entry.
102
- */
103
- static function get_scripts() {
104
- global $post;
105
- return get_post_meta( $post->ID, 'uFp_scripts', true );
106
- }
107
-
108
- /**
109
- * Utility Method: Returns the value of $styles if it is set, and if not, sets it via a call to the database.
110
- * @return array 'ufp_styles' meta data entry.
111
- */
112
- static function get_styles() {
113
- global $post;
114
- return get_post_meta( $post->ID, 'uFp_styles', true );
115
- }
116
-
117
- /**
118
- * Utility Method
119
- */
120
- static function get_wp_registered() {
121
- return array(
122
- // Starting with the list of Scripts registered by default on the Theme side (index page of twentyten).
123
- // This list should be trimmed down, as some probably aren't apporpriate for Theme enqueueing.
124
- 'l10n',
125
- 'utils',
126
- 'common',
127
- 'sack',
128
- 'quicktags',
129
- 'colorpicker',
130
- 'editor',
131
- 'prototype',
132
- 'wp-ajax-response',
133
- 'autosave',
134
- 'wp-lists',
135
- 'scriptaculous-root',
136
- 'scriptaculous-builder',
137
- 'scriptaculous-dragdrop',
138
- 'scriptaculous-effects',
139
- 'scriptaculous-slider',
140
- 'scriptaculous-sound',
141
- 'scriptaculous-controls',
142
- 'scriptaculous',
143
- 'cropper',
144
- 'jquery',
145
- 'jquery-ui-core',
146
- 'jquery-ui-position',
147
- 'jquery-ui-widget',
148
- 'jquery-ui-mouse',
149
- 'jquery-ui-button',
150
- 'jquery-ui-tabs',
151
- 'jquery-ui-sortable',
152
- 'jquery-ui-draggable',
153
- 'jquery-ui-droppable',
154
- 'jquery-ui-selectable',
155
- 'jquery-ui-resizable',
156
- 'jquery-ui-dialog',
157
- 'jquery-form',
158
- 'jquery-color',
159
- 'suggest',
160
- 'schedule',
161
- 'jquery-query',
162
- 'jquery-serialize-object',
163
- 'jquery-hotkeys',
164
- 'jquery-table-hotkeys',
165
- 'thickbox',
166
- 'jcrop',
167
- 'swfobject',
168
- 'swfupload',
169
- 'swfupload-swfobject',
170
- 'swfupload-queue',
171
- 'swfupload-speed',
172
- 'swfupload-all',
173
- 'swfupload-handlers',
174
- 'comment-reply',
175
- 'json2',
176
- 'imgareaselect',
177
- 'password-strength-meter',
178
- 'user-profile',
179
- 'admin-bar',
180
- 'wplink',
181
- 'wpdialogs-popup'
182
- );
183
- }
184
-
185
- /**
186
- * Theme Action: 'wp_head()'
187
- * Outputs the globally and individually set Styles in the Theme's head element.
188
- */
189
- static function styles() {
190
- // Global
191
- $options = get_option( 'SnS_options' );
192
- if ( ! empty( $options ) && ! empty( $options[ 'styles' ] ) ) {
193
- ?><style type="text/css"><?php
194
- echo $options[ 'styles' ];
195
- ?></style><?php
196
- }
197
-
198
- if ( ! is_singular() ) return;
199
- // Individual
200
- $styles = self::get_styles();
201
- if ( ! empty( $styles ) && ! empty( $styles[ 'styles' ] ) ) {
202
- ?><style type="text/css"><?php
203
- echo $styles[ 'styles' ];
204
- ?></style><?php
205
- }
206
- }
207
-
208
- /**
209
- * Theme Action: 'wp_footer()'
210
- * Outputs the globally and individually set Scripts at the end of the Theme's body element.
211
- */
212
- static function scripts() {
213
- // Global
214
- $options = get_option( 'SnS_options' );
215
- if ( ! empty( $options ) && ! empty( $options[ 'scripts' ] ) ) {
216
- ?><script type="text/javascript"><?php
217
- echo $options[ 'scripts' ];
218
- ?></script><?php
219
- }
220
-
221
- if ( ! is_singular() ) return;
222
- // Individual
223
- $scripts = self::get_scripts();
224
- if ( ! empty( $scripts ) && ! empty( $scripts[ 'scripts' ] ) ) {
225
- ?><script type="text/javascript"><?php
226
- echo $scripts[ 'scripts' ];
227
- ?></script><?php
228
- }
229
- }
230
-
231
- /**
232
- * Theme Action: 'wp_head()'
233
- * Outputs the globally and individually set Scripts in the Theme's head element.
234
- */
235
- static function scripts_in_head() {
236
- // Global
237
- $options = get_option( 'SnS_options' );
238
- if ( ! empty( $options ) && ! empty($options[ 'scripts_in_head' ]) ) {
239
- ?><script type="text/javascript"><?php
240
- echo $options[ 'scripts_in_head' ];
241
- ?></script><?php
242
- }
243
-
244
- if ( ! is_singular() ) return;
245
- // Individual
246
- $scripts = self::get_scripts();
247
- if ( ! empty( $scripts ) && ! empty( $scripts[ 'scripts_in_head' ] ) ) {
248
- ?><script type="text/javascript"><?php
249
- echo $scripts[ 'scripts_in_head' ];
250
- ?></script><?php
251
- }
252
- }
253
-
254
- /**
255
- * Theme Filter: 'body_class()'
256
- * Adds classes to the Theme's body tag.
257
- * @uses self::get_styles()
258
- * @param array $classes
259
- * @return array $classes
260
- */
261
- static function body_classes( $classes ) {
262
- if ( ! is_singular() ) return $classes;
263
-
264
- $styles = self::get_styles();
265
- if ( ! empty( $styles ) && ! empty( $styles[ 'classes_body' ] ) )
266
- $classes = array_merge( $classes, explode( " ", $styles[ 'classes_body' ] ) );
267
-
268
- return $classes;
269
- }
270
-
271
- /**
272
- * Theme Filter: 'post_class()'
273
- * Adds classes to the Theme's post container.
274
- * @uses self::get_styles()
275
- * @param array $classes
276
- * @return array $classes
277
- */
278
- static function post_classes( $classes ) {
279
- if ( ! is_singular() ) return $classes;
280
-
281
- $styles = self::get_styles();
282
- if ( ! empty( $styles ) && ! empty( $styles[ 'classes_post' ] ) )
283
- $classes = array_merge( $classes, explode( " ", $styles[ 'classes_post' ] ) );
284
-
285
- return $classes;
286
- }
287
-
288
- /**
289
- * Theme Action: 'wp_enqueue_scripts'
290
- * Enqueues chosen Scripts.
291
- * @uses self::get_enqueue()
292
- * @uses self::get_scripts()
293
- */
294
- static function enqueue_scripts() {
295
- // Global
296
- $enqueue_scripts = get_option( 'SnS_enqueue_scripts' );
297
-
298
- if ( is_array( $enqueue_scripts ) ) {
299
- foreach ( $enqueue_scripts as $handle )
300
- wp_enqueue_script( $handle );
301
- }
302
-
303
- if ( ! is_singular() ) return;
304
- // Individual
305
- $scripts = self::get_scripts();
306
- if ( ! empty( $scripts[ 'enqueue_scripts' ] ) && is_array( $scripts[ 'enqueue_scripts' ] ) ) {
307
- foreach ( $scripts[ 'enqueue_scripts' ] as $handle )
308
- wp_enqueue_script( $handle );
309
- }
310
- }
311
-
312
- }
313
-
314
- Scripts_n_Styles::init();
315
-
316
  ?>
1
+ <?php
2
+ /*
3
+ Plugin Name: Scripts n Styles
4
+ Plugin URI: http://www.unfocus.com/projects/scripts-n-styles/
5
+ Description: Allows WordPress admin users the ability to add custom CSS and JavaScript directly to individual Post, Pages or custom post types.
6
+ Author: unFocus Projects
7
+ Author URI: http://www.unfocus.com/
8
+ Version: 3
9
+ License: GPLv2 or later
10
+ Network: true
11
+ */
12
+
13
+ /* Copyright 2010-2011 Kenneth Newman www.unfocus.com
14
+
15
+ This program is free software; you can redistribute it and/or
16
+ modify it under the terms of the GNU General Public License
17
+ as published by the Free Software Foundation; either version 2
18
+ of the License, or (at your option) any later version.
19
+
20
+ This program is distributed in the hope that it will be useful,
21
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
22
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23
+ GNU General Public License for more details.
24
+
25
+ You should have received a copy of the GNU General Public License
26
+ along with this program; if not, write to the Free Software
27
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
28
+ */
29
+
30
+ /**
31
+ * Scripts n Styles
32
+ *
33
+ * Allows WordPress admin users the ability to add custom CSS
34
+ * and JavaScript directly to individual Post, Pages or custom
35
+ * post types.
36
+ *
37
+ * NOTE: No user except the "Super Admin" can use this plugin in MultiSite. I'll add features for MultiSite later, perhaps the ones below...
38
+ * The "Super Admin" user has exclusive 'unfiltered_html' capabilities in MultiSite. Also, options.php checks for is_super_admin()
39
+ * so the 'manage_options' capability for blog admins is insufficient to pass the check to manage options directly.
40
+ *
41
+ * The Tentative plan is for Super Admins to create Snippets or Shortcodes approved for use by users with certain capabilities
42
+ * ('unfiltered_html' and/or 'manage_options'). The 'unfiltered_html' capability can be granted via another plugin. This plugin will
43
+ * not deal with granting any capabilities.
44
+ *
45
+ * @package Scripts_n_Styles
46
+ * @link http://www.unfocus.com/projects/scripts-n-styles/ Plugin URI
47
+ * @author unFocus Projects
48
+ * @link http://www.unfocus.com/ Author URI
49
+ * @version 3
50
+ * @license http://opensource.org/licenses/gpl-license.php GNU Public License
51
+ * @copyright Copyright (c) 2010 - 2011, Kenneth Newman
52
+ *
53
+ * @todo Add Post Type Selection on Options Page? Not sure that's useful.
54
+ * @todo Add Conditional Tags support as alternative to Globally applying Scripts n Styles.
55
+ * @todo Create ability to add and register scripts and styles for enqueueing (via Options page).
56
+ * @todo Create selection on Option page of which to pick registered scripts to make available on edit screens.
57
+ * @todo Create shortcode to embed html/javascript snippets. See http://scribu.net/wordpress/optimal-script-loading.html in which this is already figured out :-)
58
+ * @todo Create shortcode registration on Options page to make those snippets available on edit screens.
59
+ * @todo Create shortcode registration of html snippets on edit screens for single use.
60
+ * @todo Add Error messaging.
61
+ * @todo Replace Multi-Select element with something better.
62
+ * @todo "Include Scripts" will be reintroduced when registering is finished.
63
+ * @todo Clean up tiny_mce_before_init in SnS_Admin_Meta_Box.
64
+ * @todo LESS.js support.
65
+ * @todo LESS.js highlighting support to CodeMirror.
66
+ * @todo Solarize theme to CodeMirror.
67
+ */
68
+
69
+ class Scripts_n_Styles
70
+ {
71
+ /**#@+
72
+ * @static
73
+ */
74
+ static $file = __FILE__;
75
+ /**#@-*/
76
+
77
+ /**
78
+ * Initializing method. Checks if is_admin() and registers action hooks for admin if true. Sets filters and actions for Theme side functions.
79
+ * @static
80
+ */
81
+ static function init() {
82
+
83
+ if ( is_admin() && ! ( defined('DISALLOW_UNFILTERED_HTML') && DISALLOW_UNFILTERED_HTML ) ) {
84
+ /* NOTE: Setting the DISALLOW_UNFILTERED_HTML constant to
85
+ true in the wp-config.php would effectively disable this
86
+ plugin's admin because no user would have the capability.
87
+ */
88
+ include_once( 'includes/class.SnS_Admin.php' );
89
+ SnS_Admin::init();
90
+
91
+ }
92
+
93
+ add_filter( 'body_class', array( __CLASS__, 'body_classes' ) );
94
+ add_filter( 'post_class', array( __CLASS__, 'post_classes' ) );
95
+
96
+ add_action( 'wp_head', array( __CLASS__, 'styles' ), 11 );
97
+ add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_scripts' ), 11 );
98
+ add_action( 'wp_head', array( __CLASS__, 'scripts_in_head' ), 11 );
99
+ add_action( 'wp_footer', array( __CLASS__, 'scripts' ), 11 );
100
+ }
101
+
102
+ /**
103
+ * Utility Method
104
+ */
105
+ static function get_wp_registered() {
106
+ return array(
107
+ // Starting with the list of Scripts registered by default on the Theme side (index page of twentyten).
108
+ 'l10n',
109
+ 'utils',
110
+ 'common',
111
+ 'sack',
112
+ 'quicktags',
113
+ 'colorpicker',
114
+ 'editor',
115
+ 'prototype',
116
+ 'wp-ajax-response',
117
+ 'autosave',
118
+ 'wp-lists',
119
+ 'scriptaculous-root',
120
+ 'scriptaculous-builder',
121
+ 'scriptaculous-dragdrop',
122
+ 'scriptaculous-effects',
123
+ 'scriptaculous-slider',
124
+ 'scriptaculous-sound',
125
+ 'scriptaculous-controls',
126
+ 'scriptaculous',
127
+ 'cropper',
128
+ 'jquery',
129
+ 'jquery-ui-core',
130
+ 'jquery-ui-position',
131
+ 'jquery-ui-widget',
132
+ 'jquery-ui-mouse',
133
+ 'jquery-ui-button',
134
+ 'jquery-ui-tabs',
135
+ 'jquery-ui-sortable',
136
+ 'jquery-ui-draggable',
137
+ 'jquery-ui-droppable',
138
+ 'jquery-ui-selectable',
139
+ 'jquery-ui-resizable',
140
+ 'jquery-ui-dialog',
141
+ 'jquery-form',
142
+ 'jquery-color',
143
+ 'suggest',
144
+ 'schedule',
145
+ 'jquery-query',
146
+ 'jquery-serialize-object',
147
+ 'jquery-hotkeys',
148
+ 'jquery-table-hotkeys',
149
+ 'thickbox',
150
+ 'jcrop',
151
+ 'swfobject',
152
+ 'swfupload',
153
+ 'swfupload-swfobject',
154
+ 'swfupload-queue',
155
+ 'swfupload-speed',
156
+ 'swfupload-all',
157
+ 'swfupload-handlers',
158
+ 'comment-reply',
159
+ 'json2',
160
+ 'imgareaselect',
161
+ 'password-strength-meter',
162
+ 'user-profile',
163
+ 'admin-bar',
164
+ 'wplink',
165
+ 'wpdialogs-popup'
166
+ );
167
+ }
168
+
169
+ /**
170
+ * Theme Action: 'wp_head()'
171
+ * Outputs the globally and individually set Styles in the Theme's head element.
172
+ */
173
+ static function styles() {
174
+ // Global
175
+ $options = get_option( 'SnS_options' );
176
+ if ( ! empty( $options ) && ! empty( $options[ 'styles' ] ) ) {
177
+ ?><style type="text/css"><?php
178
+ echo $options[ 'styles' ];
179
+ ?></style><?php
180
+ }
181
+
182
+ if ( ! is_singular() ) return;
183
+ // Individual
184
+ global $post;
185
+ $SnS = get_post_meta( $post->ID, '_SnS', true );
186
+ $styles = isset( $SnS['styles'] ) ? $SnS[ 'styles' ]: array();
187
+ if ( ! empty( $styles ) && ! empty( $styles[ 'styles' ] ) ) {
188
+ ?><style type="text/css"><?php
189
+ echo $styles[ 'styles' ];
190
+ ?></style><?php
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Theme Action: 'wp_footer()'
196
+ * Outputs the globally and individually set Scripts at the end of the Theme's body element.
197
+ */
198
+ static function scripts() {
199
+ // Global
200
+ $options = get_option( 'SnS_options' );
201
+ if ( ! empty( $options ) && ! empty( $options[ 'scripts' ] ) ) {
202
+ ?><script type="text/javascript"><?php
203
+ echo $options[ 'scripts' ];
204
+ ?></script><?php
205
+ }
206
+
207
+ if ( ! is_singular() ) return;
208
+ // Individual
209
+ global $post;
210
+ $SnS = get_post_meta( $post->ID, '_SnS', true );
211
+ $scripts = isset( $SnS['scripts'] ) ? $SnS[ 'scripts' ]: array();
212
+ if ( ! empty( $scripts ) && ! empty( $scripts[ 'scripts' ] ) ) {
213
+ ?><script type="text/javascript"><?php
214
+ echo $scripts[ 'scripts' ];
215
+ ?></script><?php
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Theme Action: 'wp_head()'
221
+ * Outputs the globally and individually set Scripts in the Theme's head element.
222
+ */
223
+ static function scripts_in_head() {
224
+ // Global
225
+ $options = get_option( 'SnS_options' );
226
+ if ( ! empty( $options ) && ! empty( $options[ 'scripts_in_head' ] ) ) {
227
+ ?><script type="text/javascript"><?php
228
+ echo $options[ 'scripts_in_head' ];
229
+ ?></script><?php
230
+ }
231
+
232
+ if ( ! is_singular() ) return;
233
+ // Individual
234
+ global $post;
235
+ $SnS = get_post_meta( $post->ID, '_SnS', true );
236
+ $scripts = isset( $SnS['scripts'] ) ? $SnS[ 'scripts' ]: array();
237
+ if ( ! empty( $scripts ) && ! empty( $scripts[ 'scripts_in_head' ] ) ) {
238
+ ?><script type="text/javascript"><?php
239
+ echo $scripts[ 'scripts_in_head' ];
240
+ ?></script><?php
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Theme Filter: 'body_class()'
246
+ * Adds classes to the Theme's body tag.
247
+ * @uses self::get_styles()
248
+ * @param array $classes
249
+ * @return array $classes
250
+ */
251
+ static function body_classes( $classes ) {
252
+ if ( ! is_singular() || is_admin() ) return $classes;
253
+
254
+ global $post;
255
+ $SnS = get_post_meta( $post->ID, '_SnS', true );
256
+ $styles = isset( $SnS['styles'] ) ? $SnS[ 'styles' ]: array();
257
+ if ( ! empty( $styles ) && ! empty( $styles[ 'classes_body' ] ) )
258
+ $classes = array_merge( $classes, explode( " ", $styles[ 'classes_body' ] ) );
259
+
260
+ return $classes;
261
+ }
262
+
263
+ /**
264
+ * Theme Filter: 'post_class()'
265
+ * Adds classes to the Theme's post container.
266
+ * @param array $classes
267
+ * @return array $classes
268
+ */
269
+ static function post_classes( $classes ) {
270
+ if ( ! is_singular() || is_admin() ) return $classes;
271
+
272
+ global $post;
273
+ $SnS = get_post_meta( $post->ID, '_SnS', true );
274
+ $styles = isset( $SnS['styles'] ) ? $SnS[ 'styles' ]: array();
275
+
276
+ if ( ! empty( $styles ) && ! empty( $styles[ 'classes_post' ] ) )
277
+ $classes = array_merge( $classes, explode( " ", $styles[ 'classes_post' ] ) );
278
+
279
+ return $classes;
280
+ }
281
+
282
+ /**
283
+ * Theme Action: 'wp_enqueue_scripts'
284
+ * Enqueues chosen Scripts.
285
+ */
286
+ static function enqueue_scripts() {
287
+ // Global
288
+ $options = get_option( 'SnS_options' );
289
+ if ( ! isset( $options[ 'enqueue_scripts' ] ) )
290
+ $enqueue_scripts = array();
291
+ else
292
+ $enqueue_scripts = $options[ 'enqueue_scripts' ];
293
+
294
+ foreach ( $enqueue_scripts as $handle )
295
+ wp_enqueue_script( $handle );
296
+
297
+ if ( ! is_singular() ) return;
298
+ // Individual
299
+ global $post;
300
+ $SnS = get_post_meta( $post->ID, '_SnS', true );
301
+ $scripts = isset( $SnS['scripts'] ) ? $SnS[ 'scripts' ]: array();
302
+
303
+ if ( ! empty( $scripts[ 'enqueue_scripts' ] ) && is_array( $scripts[ 'enqueue_scripts' ] ) ) {
304
+ foreach ( $scripts[ 'enqueue_scripts' ] as $handle )
305
+ wp_enqueue_script( $handle );
306
+ }
307
+ }
308
+
309
+ }
310
+
311
+ Scripts_n_Styles::init();
312
+
 
 
 
313
  ?>
uninstall.php CHANGED
@@ -1,14 +1,20 @@
1
  <?php
2
- if( !defined( 'ABSPATH') && !defined('WP_UNINSTALL_PLUGIN') )
3
- exit();
4
- $get_posts_args = array('numberposts' => -1,
5
- 'post_type' => 'any',
6
- 'post_status' => 'any' );
7
- $all_posts = get_posts( $get_posts_args );
8
- foreach( $all_posts as $postinfo) {
9
- delete_post_meta($postinfo->ID, 'uFp_scripts');
10
- delete_post_meta($postinfo->ID, 'uFp_styles');
11
- }
12
- delete_option('sns_options');
13
- delete_option('sns_enqueue_scripts');
 
 
 
 
 
 
14
  ?>
1
  <?php
2
+ if( ! defined( 'ABSPATH' ) && ! defined( 'WP_UNINSTALL_PLUGIN' ) ) exit();
3
+ $posts = get_posts( array(
4
+ 'numberposts' => -1,
5
+ 'post_type' => 'any',
6
+ 'post_status' => 'any',
7
+ 'orderby' => 'ID',
8
+ 'meta_key' => '_SnS'
9
+ ) );
10
+
11
+ foreach( $posts as $post)
12
+ delete_post_meta( $post->ID, '_SnS' );
13
+ delete_option( 'SnS_options' );
14
+
15
+ $users = get_users( 'meta_key=current_sns_tab' );
16
+ foreach( $users as $user ) delete_user_option( $user->ID, 'current_sns_tab', true );
17
+
18
+ $users = get_users( 'meta_key=scripts_n_styles_page_sns_usage_per_page' );
19
+ foreach( $users as $user ) delete_user_option( $user->ID, 'scripts_n_styles_page_sns_usage_per_page', true );
20
  ?>