Updated Vpatch: Code Embed Plugin for MP-WP

April 4th, 2020

Since there was already a lengthy discussion on the first draft of this patch let's start by looking at a diff between this version and the original (annotated with footnotes this time!):

1 --- old/footnotes.php
2 +++ current/footnotes.php
3 @@ -14,8 +14,8 @@
4 class mp_wp_content_processing {
5 const MP_WP_FOOTNOTES_OPEN = " ((";
6 const MP_WP_FOOTNOTES_CLOSE = "))";
7 - const MP_WP_CODEBLOCK_OPEN = "[[";
8 - const MP_WP_CODEBLOCK_CLOSE = "/]]";
9 + const MP_WP_CODEBLOCK_OPEN = "\[[0-9]\[";1
10 + const MP_WP_CODEBLOCK_CLOSE = "]]";
11
12 var $options;
13
14 @@ -40,7 +40,7 @@
15 'backlink'=>'↩',
16 'post_backlink'=>']',
17 'pre_identifier'=>'',
18 - 'list_style_type'=>'lower-roman',
19 + 'list_style_type'=>'decimal',2
20 'list_style_symbol'=>'†',
21 'post_identifier'=>'',
22 'pre_footnotes'=>'',
23 @@ -73,7 +73,7 @@
24 // Regex extraction of all codeblocks (or return if there are none)
25 if (
26 !preg_match_all(
27 - "/(".preg_quote(self::MP_WP_CODEBLOCK_OPEN).")(.*)(".preg_quote(self::MP_WP_CODEBLOCK_CLOSE, '/').")/Us",
28 + "/(".self::MP_WP_CODEBLOCK_OPEN.")(.*)(".preg_quote(self::MP_WP_CODEBLOCK_CLOSE, '/').")/Us",3
29 $data,
30 $codeblocks,
31 PREG_SET_ORDER
32 @@ -83,7 +83,7 @@
33 }
34
35 for ($i = 0; $i < count($codeblocks); $i++) {
36 - $codeblocks[$i]['snippet'] = $this->format_snippet($codeblocks[$i][2], $i+1);
37 + $codeblocks[$i]['snippet'] = $this->format_snippet($codeblocks[$i][2], substr($codeblocks[$i][1], 1, 1), $i+1);4
38 }
39
40 foreach ($codeblocks as $key => $value) {
41 @@ -93,41 +93,59 @@
42 return $data;
43 }
44
45 - function format_snippet($snippet, $snippet_number) {
46 - $formatted_snippet = htmlspecialchars($snippet);5
47 - $code_lines = explode("\r\n", $formatted_snippet);
48 -
49 + function format_snippet($snippet, $syntax_index, $snippet_number) {
50 + $highlighting_functions = array(
51 + 'highlight_as_plain_text',
52 + 'highlight_as_diff'
53 + );
54 +
55 + if (is_null($highlighting_functions[$syntax_index])) {
56 + $syntax_index = 0;
57 + }
58 +
59 + $code_lines = explode("\r\n", $snippet);
60 +
61 foreach ($code_lines as $idx => $line) {
62 $line_number = sprintf('S%d-L%d', $snippet_number, $idx+1);
63 $line_link = sprintf('<a href="#%s" name="%s">%d</a>', $line_number, $line_number, $idx+1);
64 $line_open = sprintf('<tr><td class="line-number-column">%s</td><td class="content-column">', $line_link);
65 $line_close = '</td></tr>';
66 -
67 - if (substr($line, 0, 5) == 'diff ') {
68 - $code_lines[$idx] = sprintf('%s<span class="line-filename">%s</span>%s', $line_open, $line, $line_close);
69 - } elseif (substr($line, 0, 4) == '--- ' || substr($line, 0, 4) == '+++ ' || substr($line, 0, 3) == '@@ ') {
70 - $code_lines[$idx] = sprintf('%s<span class="line-meta">%s</span>%s', $line_open, $line, $line_close);
71 - } elseif (substr($line, 0, 1) == '-') {
72 - $code_lines[$idx] = sprintf('%s<span class="line-removed">%s</span>%s', $line_open, $line, $line_close);
73 - } elseif (substr($line, 0, 1) == '+') {
74 - $code_lines[$idx] = sprintf('%s<span class="line-added">%s</span>%s', $line_open, $line, $line_close);
75 - } else {
76 - $code_lines[$idx] = sprintf('%s<span class="line-default">%s</span>%s', $line_open, $line, $line_close);
77 - }
78 +
79 + $code_lines[$idx] = $this->$highlighting_functions[$syntax_index]($line_open, $line, $line_close);
80 }
81
82 $formatted_snippet = implode("\n", $code_lines);
83
84 $formatted_snippet = sprintf(
85 '%s%s%s',
86 - '<pre class="mp-wp-codeblock"><table cellpadding="0" cellspacing="0"><tbody>',
87 + '<div class="mp-wp-codeblock"><table cellpadding="0" cellspacing="0"><tbody>',6
88 $formatted_snippet,
89 - '</tbody></table></pre>'
90 + '</tbody></table></div>'
91 );
92
93 return $formatted_snippet;
94 }
95
96 + function highlight_as_plain_text($line_open, $line, $line_close) {
97 + return sprintf('%s<span class="line-default">%s</span>%s', $line_open, $line, $line_close);
98 + }
99 +
100 + function highlight_as_diff($line_open, $line, $line_close) {
101 + if (substr($line, 0, 5) == 'diff ') {
102 + $highlighted_line = sprintf('%s<span class="line-filename">%s</span>%s', $line_open, $line, $line_close);
103 + } elseif (substr($line, 0, 4) == '--- ' || substr($line, 0, 4) == '+++ ' || substr($line, 0, 3) == '@@ ') {
104 + $highlighted_line = sprintf('%s<span class="line-meta">%s</span>%s', $line_open, $line, $line_close);
105 + } elseif (substr($line, 0, 1) == '-') {
106 + $highlighted_line = sprintf('%s<span class="line-removed">%s</span>%s', $line_open, $line, $line_close);
107 + } elseif (substr($line, 0, 1) == '+') {
108 + $highlighted_line = sprintf('%s<span class="line-added">%s</span>%s', $line_open, $line, $line_close);
109 + } else {
110 + $highlighted_line = sprintf('%s<span class="line-default">%s</span>%s', $line_open, $line, $line_close);
111 + }
112 +
113 + return $highlighted_line;
114 + }
115 +
116 /**
117 * Searches the text and extracts footnotes.
118 * Adds the identifier links and creats footnotes list.
119 @@ -140,11 +158,8 @@
120 // Check for and setup the starting number
121 $start_number = (preg_match("|<!\-\-startnum=(\d+)\-\->|",$data,$start_number_array)==1) ? $start_number_array[1] : 1;
122
123 - // Remove codeblocks from content to be parsed for footnotes
124 - $data_sans_codeblocks = preg_replace("/(<pre class=\"mp-wp-codeblock\">)(.*)(<\/pre>)/Us", '', $data);
125 -
126 - // Regex extraction of all footnotes from non-codeblock content (or return if there are none)
127 - if (!preg_match_all("/(".preg_quote(self::MP_WP_FOOTNOTES_OPEN)."|<footnote>)(.*)(".preg_quote(self::MP_WP_FOOTNOTES_CLOSE)."|<\/footnote>)/Us", $data_sans_codeblocks, $identifiers, PREG_SET_ORDER)) {
128 + // Regex extraction of all footnotes (or return if there are none)
129 + if (!preg_match_all("/(".preg_quote(self::MP_WP_FOOTNOTES_OPEN)."|<footnote>)(.*)(".preg_quote(self::MP_WP_FOOTNOTES_CLOSE)."|<\/footnote>)/Us", $data, $identifiers, PREG_SET_ORDER)) {
130 return $data;
131 }
132
133 @@ -250,12 +265,31 @@
134 ?>
135 <style type="text/css">
136 ol.footnotes { font-size: 0.8em; color: #666666; }
137 - pre.mp-wp-codeblock { background: none; color: #333; border: 1px solid #ddd; padding: 0; }
138 - td.line-number-column { background: #f5f6f7; text-align: right; }
139 + a.footnote-link,
140 + td.line-number-column {7
141 + -moz-user-select: none;
142 + -webkit-user-select: none;
143 + user-select: none;
144 + }
145 + div.mp-wp-codeblock {
146 + background: none;
147 + font-family: monospace;
148 + color: #333;
149 + border: 1px solid #ddd;
150 + padding: 0;
151 + overflow: auto;
152 + }
153 + td.line-number-column { background: #f5f6f7; text-align: right; vertical-align: top; }
154 td.line-number-column a { color: #555; padding: 0 5px; }
155 - td.content-column { padding-left: 10px; }
156 + td.content-column {
157 + padding-left: 10px;
158 + white-space: pre-wrap;
159 + tab-size: 4;
160 + -moz-tab-size: 4;
161 + max-width: 670px; /* adjust as necessary to fit your blog's viewport */
162 + }
163 span.line-filename { font-weight: bold; }
164 - span.line-meta { color: #999; }
165 + span.line-meta { color: #999; word-wrap: break-word; }8
166 span.line-added { color: green; }
167 span.line-removed { color:red; }
168
169

I think I hit on all the grievances raised during the original draft review. Overall I'm happy with the outcome and happy that I got to learn something about how to use sed and regular expressions for quick file manipulation. Next up for me will be adding proper server-side selection to the main mp-wp tree, if no one else has gotten to it first.

The updated patch in its entirety, in the form of file links and an embed in this post, why not:

mp-wp_add-embedded-vpatch-formatting.vpatch
mp-wp_add-embedded-vpatch-formatting.vpatch.billymg.sig

1 diff -uNr a/mp-wp/manifest b/mp-wp/manifest
2 --- a/mp-wp/manifest 4ad5c0b7eda9c670f311a23da92114ab10bf30b9990b46882047aea3ab569395f643cb4aa6144ee0ba74b9821df1e69b26d299c1f0e9c11631a78c46f95913bd
3 +++ b/mp-wp/manifest e728e4d8266826d750b5ddfbb65612507575faeed2a38a52da45f8a4f9650b7a0793207e8542c1a4c38f5476f9ab10882179eeb99a2cdcba33e3ccbdcf103dcd
4 @@ -5,3 +5,4 @@
5 569483 mp-wp_remove-tinymce-and-other-crud billymg Remove tinymce, most of the importers, the self-update feature, and the google gears and press-this plugins
6 602064 mp-wp_apply-htmlspecialchars-to-post-edit-content billymg Run post content through htmlspecialchars() before loading into the post edit UI
7 605926 mp-wp_comments_filtering diana_coman Recent comments widget should show only people's comments (no track/pingbacks); theme default changed to show trackbacks/pingbacks as last/at the bottom in an article's comments list.
8 +624366 mp-wp_add-embedded-vpatch-formatting billymg Add the ability to embed vpatches within article content. Embedded vpatch blocks will be formatted with diff syntax highlighting and anchored line numbers
9 diff -uNr a/mp-wp/wp-content/plugins/footnotes.php b/mp-wp/wp-content/plugins/footnotes.php
10 --- a/mp-wp/wp-content/plugins/footnotes.php 8e2449d4ac26ea05f080cec9d025ef8a8585221ee30da439b37ff1578d084e1c63cbe3f89e3d6868c19d0fa9f73a9af99b444251e7a854f6c87e316628d94859
11 +++ b/mp-wp/wp-content/plugins/footnotes.php 7498014fc2fd9fd154959bb316c49e2ba419e330ac958a6a19671610e35fb1f787e0190dc618ff7bb2cb13996efd09e395ff79032f175f628a71f3c44f9b5910
12 @@ -1,49 +1,28 @@
13 <?php
14 /*
15 -Plugin Name: WP-Footnotes
16 -Plugin URI: http://www.elvery.net/drzax/more-things/wordpress-footnotes-plugin/
17 -Version: 4.2
18 -Description: Allows a user to easily add footnotes to a post.
19 -Author: Simon Elvery
20 -Author URI: http://www.elvery.net/drzax/
21 +Plugin Name: MP-WP-Content-Processing
22 +Plugin URI: http://billymg.com/category/mp-wp/
23 +Description: Allows for the custom processing of article content. Currently supports footnotes and embedded vpatch snippets.
24 +Author: billymg
25 +Author URI: http://billymg.com
26 */
27
28 -/*
29 - * This file is part of WP-Footnotes a plugin for Word Press
30 - * Copyright (C) 2007 Simon Elvery
31 - *
32 - * This program is free software; you can redistribute it and/or
33 - * modify it under the terms of the GNU General Public License
34 - * as published by the Free Software Foundation; either version 2
35 - * of the License, or (at your option) any later version.
36 - *
37 - * This program is distributed in the hope that it will be useful,
38 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
39 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
40 - * GNU General Public License for more details.
41 - *
42 - * You should have received a copy of the GNU General Public License
43 - * along with this program; if not, write to the Free Software
44 - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
45 - */
46 -
47 -// Some important constants
48 -define('WP_FOOTNOTES_OPEN', " (("); //You can change this if you really have to, but I wouldn't recommend it.
49 -define('WP_FOOTNOTES_CLOSE', "))"); //Same with this one.
50 -define('WP_FOOTNOTES_VERSION', '4.2');
51 -
52 // Instantiate the class
53 -$swas_wp_footnotes = new swas_wp_footnotes();
54 +$mp_wp_content_processing = new mp_wp_content_processing();
55
56 // Encapsulate in a class
57 -class swas_wp_footnotes {
58 - var $current_options;
59 - var $default_options;
60 +class mp_wp_content_processing {
61 + const MP_WP_FOOTNOTES_OPEN = " ((";
62 + const MP_WP_FOOTNOTES_CLOSE = "))";
63 + const MP_WP_CODEBLOCK_OPEN = "\[[0-9]\[";
64 + const MP_WP_CODEBLOCK_CLOSE = "]]";
65 +
66 + var $options;
67
68 /**
69 * Constructor.
70 */
71 - function swas_wp_footnotes() {
72 + function mp_wp_content_processing() {
73 // Define the implemented option styles
74 $this->styles = array(
75 'decimal' => '1,2...10',
76 @@ -56,7 +35,7 @@
77 );
78
79 // Define default options
80 - $this->default_options = array('superscript'=>true,
81 + $this->options = array('superscript'=>true,
82 'pre_backlink'=>' [',
83 'backlink'=>'&#8617;',
84 'post_backlink'=>']',
85 @@ -66,7 +45,6 @@
86 'post_identifier'=>'',
87 'pre_footnotes'=>'',
88 'post_footnotes'=>'',
89 - 'style_rules'=>'ol.footnotes{font-size:0.8em; color:#666666;}',
90 'no_display_home'=>false,
91 'no_display_archive'=>false,
92 'no_display_date'=>false,
93 @@ -74,58 +52,98 @@
94 'no_display_search'=>false,
95 'no_display_feed'=>false,
96 'combine_identical_notes'=>false,
97 - 'priority'=>11,
98 - 'version'=>WP_FOOTNOTES_VERSION);
99 + 'codeblocks_priority'=>9, // highest value that comes before wpautop filter
100 + 'footnotes_priority'=>11);
101
102 - // Get the current settings or setup some defaults if needed
103 - if (!$this->current_options = get_option('swas_footnote_options')){
104 - $this->current_options = $this->default_options;
105 - update_option('swas_footnote_options', $this->current_options);
106 - } else {
107 - // Set any unset options
108 - if ($this->current_options['version'] != WP_FOOTNOTES_VERSION) {
109 - foreach ($this->default_options as $key => $value) {
110 - if (!isset($this->current_options[$key])) {
111 - $this->current_options[$key] = $value;
112 - }
113 - }
114 - $this->current_options['version'] = WP_FOOTNOTES_VERSION;
115 - update_option('swas_footnote_options', $this->current_options);
116 - }
117 + // Hook me up
118 + add_action('the_content', array($this, 'process_codeblocks'), $this->options['codeblocks_priority']);
119 + add_action('the_content', array($this, 'process_footnotes'), $this->options['footnotes_priority']);
120 + add_action('wp_head', array($this, 'insert_styles'));
121 + }
122 +
123 + /**
124 + * Searches the text and apply markup to codeblocks.
125 + * Adds line number links and diff syntax highlighting.
126 + * @param $data string The content of the post.
127 + * @return string The new content with formatted codeblocks.
128 + */
129 + function process_codeblocks($data) {
130 + global $post;
131 +
132 + // Regex extraction of all codeblocks (or return if there are none)
133 + if (
134 + !preg_match_all(
135 + "/(".self::MP_WP_CODEBLOCK_OPEN.")(.*)(".preg_quote(self::MP_WP_CODEBLOCK_CLOSE, '/').")/Us",
136 + $data,
137 + $codeblocks,
138 + PREG_SET_ORDER
139 + )
140 + ) {
141 + return $data;
142 }
143
144 -/*
145 - if (!empty($_POST['save_options'])){
146 - $footnotes_options['superscript'] = (array_key_exists('superscript', $_POST)) ? true : false;
147 - $footnotes_options['pre_backlink'] = $_POST['pre_backlink'];
148 - $footnotes_options['backlink'] = $_POST['backlink'];
149 - $footnotes_options['post_backlink'] = $_POST['post_backlink'];
150 - $footnotes_options['pre_identifier'] = $_POST['pre_identifier'];
151 - $footnotes_options['list_style_type'] = $_POST['list_style_type'];
152 - $footnotes_options['post_identifier'] = $_POST['post_identifier'];
153 - $footnotes_options['list_style_symbol'] = $_POST['list_style_symbol'];
154 - $footnotes_options['pre_footnotes'] = stripslashes($_POST['pre_footnotes']);
155 - $footnotes_options['post_footnotes'] = stripslashes($_POST['post_footnotes']);
156 - $footnotes_options['style_rules'] = stripslashes($_POST['style_rules']);
157 - $footnotes_options['no_display_home'] = (array_key_exists('no_display_home', $_POST)) ? true : false;
158 - $footnotes_options['no_display_archive'] = (array_key_exists('no_display_archive', $_POST)) ? true : false;
159 - $footnotes_options['no_display_date'] = (array_key_exists('no_display_date', $_POST)) ? true : false;
160 - $footnotes_options['no_display_category'] = (array_key_exists('no_display_category', $_POST)) ? true : false;
161 - $footnotes_options['no_display_search'] = (array_key_exists('no_display_search', $_POST)) ? true : false;
162 - $footnotes_options['no_display_feed'] = (array_key_exists('no_display_feed', $_POST)) ? true : false;
163 - $footnotes_options['combine_identical_notes'] = (array_key_exists('combine_identical_notes', $_POST)) ? true : false;
164 - $footnotes_options['priority'] = $_POST['priority'];
165 - update_option('swas_footnote_options', $footnotes_options);
166 - }elseif(!empty($_POST['reset_options'])){
167 - update_option('swas_footnote_options', '');
168 - update_option('swas_footnote_options', $this->default_options);
169 + for ($i = 0; $i < count($codeblocks); $i++) {
170 + $codeblocks[$i]['snippet'] = $this->format_snippet($codeblocks[$i][2], substr($codeblocks[$i][1], 1, 1), $i+1);
171 }
172 -*/
173
174 - // Hook me up
175 - add_action('the_content', array($this, 'process'), $this->current_options['priority']);
176 - add_action('admin_menu', array($this, 'add_options_page')); // Insert the Admin panel.
177 - add_action('wp_head', array($this, 'insert_styles'));
178 + foreach ($codeblocks as $key => $value) {
179 + $data = substr_replace($data, $value['snippet'], strpos($data,$value[0]),strlen($value[0]));
180 + }
181 +
182 + return $data;
183 + }
184 +
185 + function format_snippet($snippet, $syntax_index, $snippet_number) {
186 + $highlighting_functions = array(
187 + 'highlight_as_plain_text',
188 + 'highlight_as_diff'
189 + );
190 +
191 + if (is_null($highlighting_functions[$syntax_index])) {
192 + $syntax_index = 0;
193 + }
194 +
195 + $code_lines = explode("\r\n", $snippet);
196 +
197 + foreach ($code_lines as $idx => $line) {
198 + $line_number = sprintf('S%d-L%d', $snippet_number, $idx+1);
199 + $line_link = sprintf('<a href="#%s" name="%s">%d</a>', $line_number, $line_number, $idx+1);
200 + $line_open = sprintf('<tr><td class="line-number-column">%s</td><td class="content-column">', $line_link);
201 + $line_close = '</td></tr>';
202 +
203 + $code_lines[$idx] = $this->$highlighting_functions[$syntax_index]($line_open, $line, $line_close);
204 + }
205 +
206 + $formatted_snippet = implode("\n", $code_lines);
207 +
208 + $formatted_snippet = sprintf(
209 + '%s%s%s',
210 + '<div class="mp-wp-codeblock"><table cellpadding="0" cellspacing="0"><tbody>',
211 + $formatted_snippet,
212 + '</tbody></table></div>'
213 + );
214 +
215 + return $formatted_snippet;
216 + }
217 +
218 + function highlight_as_plain_text($line_open, $line, $line_close) {
219 + return sprintf('%s<span class="line-default">%s</span>%s', $line_open, $line, $line_close);
220 + }
221 +
222 + function highlight_as_diff($line_open, $line, $line_close) {
223 + if (substr($line, 0, 5) == 'diff ') {
224 + $highlighted_line = sprintf('%s<span class="line-filename">%s</span>%s', $line_open, $line, $line_close);
225 + } elseif (substr($line, 0, 4) == '--- ' || substr($line, 0, 4) == '+++ ' || substr($line, 0, 3) == '@@ ') {
226 + $highlighted_line = sprintf('%s<span class="line-meta">%s</span>%s', $line_open, $line, $line_close);
227 + } elseif (substr($line, 0, 1) == '-') {
228 + $highlighted_line = sprintf('%s<span class="line-removed">%s</span>%s', $line_open, $line, $line_close);
229 + } elseif (substr($line, 0, 1) == '+') {
230 + $highlighted_line = sprintf('%s<span class="line-added">%s</span>%s', $line_open, $line, $line_close);
231 + } else {
232 + $highlighted_line = sprintf('%s<span class="line-default">%s</span>%s', $line_open, $line, $line_close);
233 + }
234 +
235 + return $highlighted_line;
236 }
237
238 /**
239 @@ -134,25 +152,25 @@
240 * @param $data string The content of the post.
241 * @return string The new content with footnotes generated.
242 */
243 - function process($data) {
244 + function process_footnotes($data) {
245 global $post;
246
247 // Check for and setup the starting number
248 $start_number = (preg_match("|<!\-\-startnum=(\d+)\-\->|",$data,$start_number_array)==1) ? $start_number_array[1] : 1;
249
250 // Regex extraction of all footnotes (or return if there are none)
251 - if (!preg_match_all("/(".preg_quote(WP_FOOTNOTES_OPEN)."|<footnote>)(.*)(".preg_quote(WP_FOOTNOTES_CLOSE)."|<\/footnote>)/Us", $data, $identifiers, PREG_SET_ORDER)) {
252 + if (!preg_match_all("/(".preg_quote(self::MP_WP_FOOTNOTES_OPEN)."|<footnote>)(.*)(".preg_quote(self::MP_WP_FOOTNOTES_CLOSE)."|<\/footnote>)/Us", $data, $identifiers, PREG_SET_ORDER)) {
253 return $data;
254 }
255
256 // Check whether we are displaying them or not
257 $display = true;
258 - if ($this->current_options['no_display_home'] && is_home()) $display = false;
259 - if ($this->current_options['no_display_archive'] && is_archive()) $display = false;
260 - if ($this->current_options['no_display_date'] && is_date()) $display = false;
261 - if ($this->current_options['no_display_category'] && is_category()) $display = false;
262 - if ($this->current_options['no_display_search'] && is_search()) $display = false;
263 - if ($this->current_options['no_display_feed'] && is_feed()) $display = false;
264 + if ($this->options['no_display_home'] && is_home()) $display = false;
265 + if ($this->options['no_display_archive'] && is_archive()) $display = false;
266 + if ($this->options['no_display_date'] && is_date()) $display = false;
267 + if ($this->options['no_display_category'] && is_category()) $display = false;
268 + if ($this->options['no_display_search'] && is_search()) $display = false;
269 + if ($this->options['no_display_feed'] && is_feed()) $display = false;
270
271 $footnotes = array();
272
273 @@ -160,7 +178,7 @@
274 if ( array_key_exists(get_post_meta($post->ID, 'footnote_style', true), $this->styles) ) {
275 $style = get_post_meta($post->ID, 'footnote_style', true);
276 } else {
277 - $style = $this->current_options['list_style_type'];
278 + $style = $this->options['list_style_type'];
279 }
280
281 // Create 'em
282 @@ -175,7 +193,7 @@
283
284
285 // if we're combining identical notes check if we've already got one like this & record keys
286 - if ($this->current_options['combine_identical_notes']){
287 + if ($this->options['combine_identical_notes']){
288 for ($j=0; $j<count($footnotes); $j++){
289 if ($footnotes[$j]['text'] == $identifiers[$i]['text']){
290 $identifiers[$i]['use_footnote'] = $j;
291 @@ -206,12 +224,9 @@
292 $id_id = "identifier_".$key."_".$post->ID;
293 $id_num = ($style == 'decimal') ? $value['use_footnote']+$start_number : $this->convert_num($value['use_footnote']+$start_number, $style, count($footnotes));
294 $id_href = ( ($use_full_link) ? get_permalink($post->ID) : '' ) . "#footnote_".$value['use_footnote']."_".$post->ID;
295 -
296 -// $id_title = str_replace('"', "&quot;", htmlentities(strip_tags($value['text']), ENT_QUOTES, 'UTF-8'));
297 -
298 $id_title = str_replace('"', '`', strip_tags($value['text']));
299 - $id_replace = $this->current_options['pre_identifier'].'<a href="'.$id_href.'" id="'.$id_id.'" class="footnote-link footnote-identifier-link" title="'.$id_title.'">'.$id_num.'</a>'.$this->current_options['post_identifier'];
300 - if ($this->current_options['superscript']) $id_replace = '<sup>'.$id_replace.'</sup>';
301 + $id_replace = $this->options['pre_identifier'].'<a href="'.$id_href.'" id="'.$id_id.'" class="footnote-link footnote-identifier-link" title="'.$id_title.'">'.$id_num.'</a>'.$this->options['post_identifier'];
302 + if ($this->options['superscript']) $id_replace = '<sup>'.$id_replace.'</sup>';
303 if ($display) $data = substr_replace($data, $id_replace, strpos($data,$value[0]),strlen($value[0]));
304 else $data = substr_replace($data, '', strpos($data,$value[0]),strlen($value[0]));
305 }
306 @@ -219,14 +234,14 @@
307 // Display footnotes
308 if ($display) {
309 $start = ($start_number != 1) ? 'start="'.$start_number.'" ' : '';
310 - $data = $data.$this->current_options['pre_footnotes'];
311 + $data = $data.$this->options['pre_footnotes'];
312
313 $data = $data . '<ol '.$start.'class="footnotes">';
314 foreach ($footnotes as $key => $value) {
315 $data = $data.'<li id="footnote_'.$key.'_'.$post->ID.'" class="footnote"';
316 if ($style == 'symbol') {
317 $data = $data . ' style="list-style-type:none;"';
318 - } elseif($style != $this->current_options['list_style_type']) {
319 + } elseif($style != $this->options['list_style_type']) {
320 $data = $data . ' style="list-style-type:' . $style . ';"';
321 }
322 $data = $data . '>';
323 @@ -236,56 +251,58 @@
324 $data = $data.$value['text'];
325 if (!is_feed()){
326 foreach($value['identifiers'] as $identifier){
327 - $data = $data.$this->current_options['pre_backlink'].'<a href="'.( ($use_full_link) ? get_permalink($post->ID) : '' ).'#identifier_'.$identifier.'_'.$post->ID.'" class="footnote-link footnote-back-link">'.$this->current_options['backlink'].'</a>'.$this->current_options['post_backlink'];
328 + $data = $data.$this->options['pre_backlink'].'<a href="'.( ($use_full_link) ? get_permalink($post->ID) : '' ).'#identifier_'.$identifier.'_'.$post->ID.'" class="footnote-link footnote-back-link">'.$this->options['backlink'].'</a>'.$this->options['post_backlink'];
329 }
330 }
331 $data = $data . '</li>';
332 }
333 - $data = $data . '</ol>' . $this->current_options['post_footnotes'];
334 + $data = $data . '</ol>' . $this->options['post_footnotes'];
335 }
336 return $data;
337 }
338
339 - /**
340 - * Really insert the options page.
341 - */
342 - function footnotes_options_page() {
343 - $this->current_options = get_option('swas_footnote_options');
344 - foreach ($this->current_options as $key=>$setting) {
345 - $new_setting[$key] = htmlentities($setting);
346 - }
347 - $this->current_options = $new_setting;
348 - unset($new_setting);
349 - include (dirname(__FILE__) . '/options.php');
350 - }
351 -
352 - /**
353 - * Insert the options page into the admin area.
354 - */
355 - function add_options_page() {
356 - // Add a new menu under Options:
357 - add_options_page('Footnotes', 'Footnotes', 8, __FILE__, array($this, 'footnotes_options_page'));
358 - }
359 -
360 - function upgrade_post($data){
361 - $data = str_replace('<footnote>',WP_FOOTNOTES_OPEN,$data);
362 - $data = str_replace('</footnote>',WP_FOOTNOTES_CLOSE,$data);
363 - return $data;
364 - }
365 -
366 - function insert_styles(){
367 + function insert_styles() {
368 ?>
369 <style type="text/css">
370 - <?php if ($this->current_options['list_style_type'] != 'symbol'): ?>
371 - ol.footnotes li {list-style-type:<?php echo $this->current_options['list_style_type']; ?>;}
372 + ol.footnotes { font-size: 0.8em; color: #666666; }
373 + a.footnote-link,
374 + td.line-number-column {
375 + -moz-user-select: none;
376 + -webkit-user-select: none;
377 + user-select: none;
378 + }
379 + div.mp-wp-codeblock {
380 + background: none;
381 + font-family: monospace;
382 + color: #333;
383 + border: 1px solid #ddd;
384 + padding: 0;
385 + overflow: auto;
386 + }
387 + td.line-number-column { background: #f5f6f7; text-align: right; vertical-align: top; }
388 + td.line-number-column a { color: #555; padding: 0 5px; }
389 + td.content-column {
390 + padding-left: 10px;
391 + white-space: pre-wrap;
392 + tab-size: 4;
393 + -moz-tab-size: 4;
394 + max-width: 670px; /* adjust as necessary to fit your blog's viewport */
395 + }
396 + span.line-filename { font-weight: bold; }
397 + span.line-meta { color: #999; word-wrap: break-word; }
398 + span.line-added { color: green; }
399 + span.line-removed { color:red; }
400 +
401 + <?php if ($this->options['list_style_type'] != 'symbol'): ?>
402 + ol.footnotes li { list-style-type: <?php echo $this->options['list_style_type']; ?>; }
403 <?php endif; ?>
404 - <?php echo $this->current_options['style_rules'];?>
405 +
406 </style>
407 <?php
408 }
409
410
411 - function convert_num ($num, $style, $total){
412 + function convert_num ($num, $style, $total) {
413 switch ($style) {
414 case 'decimal-leading-zero' :
415 $width = max(2, strlen($total));
416 @@ -301,7 +318,7 @@
417 case 'symbol' :
418 $sym = '';
419 for ($i = 0; $i<$num; $i++) {
420 - $sym .= $this->current_options['list_style_symbol'];
421 + $sym .= $this->options['list_style_symbol'];
422 }
423 return $sym;
424 }
425 @@ -318,7 +335,7 @@
426 * @param string $case Upper or lower case.
427 * @return string The roman numeral
428 */
429 - function roman($num, $case= 'upper'){
430 + function roman($num, $case= 'upper') {
431 $num = (int) $num;
432 $conversion = array('M'=>1000, 'CM'=>900, 'D'=>500, 'CD'=>400, 'C'=>100, 'XC'=>90, 'L'=>50, 'XL'=>40, 'X'=>10, 'IX'=>9, 'V'=>5, 'IV'=>4, 'I'=>1);
433 $roman = '';
434 @@ -331,7 +348,7 @@
435 return ($case == 'lower') ? strtolower($roman) : $roman;
436 }
437
438 - function alpha($num, $case='upper'){
439 + function alpha($num, $case='upper') {
440 $j = 1;
441 for ($i = 'A'; $i <= 'ZZ'; $i++){
442 if ($j == $num){
443
  1. This is now a regex pattern to include an encoded syntax index. The current patch supports plain text/no highlighting (0) and linux diff -u (1). []
  2. I reverted this change to the default footnote marker style back to its original. I personally like the roman numerals though so that's what billymg.com is using now. Feel free to set it how you like using this variable. []
  3. The open tag no longer needs to be escaped now that it's a regex pattern. []
  4. Pass the new syntax index to format_snippet() so it knows how to highlight the codeblock. []
  5. Upon jfw's suggestion, I decided to not run the codeblock content through htmlspecialchars(). The idea being that the operator can escape their code with a simple line of sed before pasting into mp-wp. This way of thinking also opened up the possibility of including footnotes within codeblocks, since the '((' can be pre-escaped in the sed -> mp-wp workflow.

    This was all I needed for this article: cat [FILE] | sed 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g; s/"/\&quot;/g; s/'"'"'/\&apos;/g; s/((/\&lpar;\&lpar;/g; s/))/\&rpar;\&rpar;/g; s/\[([0-9])\[/\&lsqb;$1\&lsqb;/g; s/]]/\&rsqb;\&rsqb;/g' > [FILE_ESCAPED] []

  6. I changed this to a plain div because the pre tag wrapped around the other markup (which is probably a misuse of the pre tag in any case) would cause extra newlines to be added when copy/pasting from the snippet into a text file. []
  7. This little bit of CSS avoids selecting footnote markers and line numbers when copy/pasting text from a codeblock. Go ahead, try it out. []
  8. In other jfw-was-right, this change and the white-space: pre-wrap; above were added to eliminate the unseemly horizontal scrolling present in the initial draft. []
« MP-WP Roadmap Proposal
MP-WP User Survey »

7 Comments

  1. Diana Coman says:

    Looks quite good! I'll add it to the list to give it a spin sooner rather than later.

  2. Jacob Welsh says:

    Looking good!

    > i. This is now a regex pattern to include an encoded syntax index.

    Do you mean for this to allow only one digit? You could use \[([0-9]+)\[ so the parens capture the number portion (then there's surely a preg function to get at it).

    > ii. ... Feel free to set it how you like using this variable.

    This nonetheless comes at the cost of having to fork the v-tree. It occurred to me this one might warrant a global setting in wp-config.php, since it really seems to be a matter of taste/theme.

    > v. ... This was all I needed for this article:

    The closing characters don't generally need escaping, since they have special meaning only after the corresponding opening one (and footnotes-within-footnotes wouldn't work anyway AFAIK). Single/double quotes only need it in the context of an attribute (i.e. <a href="evil-user-input-here">). So my current filter is:

    sed 's/&/\&amp;/g; s/</\&lt;/g; s/((/(\&lpar;/g'

    > vi. I changed this to a plain div because the pre tag wrapped around the other markup (which is probably a misuse of the pre tag in any case) would cause extra newlines to be added when copy/pasting from the snippet into a text file.

    I don't quite follow, but how does this handle multiple sequential spaces (as might be significant e.g. in a regexp even if one fully adopts MP's space-indent ban)?

    > vii. This little bit of CSS avoids selecting footnote markers and line numbers when copy/pasting text from a codeblock. Go ahead, try it out.

    That's very shiny!

  3. billymg says:

    > Do you mean for this to allow only one digit? You could use \[([0-9]+)\[ so the parens capture the number portion (then there's surely a preg function to get at it).

    I meant for it to only be 0-9 as I think adding support for eight more languages will take quite some time. I'm grabbing the matched index from the array that preg_match_all populates (#S1-L37), which currently depends on it being a single-digit number (though it could be updated to grab any length between [[).

    > This nonetheless comes at the cost of having to fork the v-tree. It occurred to me this one might warrant a global setting in wp-config.php, since it really seems to be a matter of taste/theme.

    I could see that, perhaps the entire options can be set in wp-config.php? See also my comment on your recent article.

    > So my current filter is: [...]

    This is a nice simplification, thanks!

    > I don't quite follow, but how does this handle multiple sequential spaces [...] ?

    e.g. previously with the pre tag wrapped around the table, copying:

    line1
    line2
    

    and pasting it would yield:

    line1
    
    line2
    

    Multiple sequential spaces are handled as you would want/expect them to be (i.e. maintained). The pre tag was superfluous because of td.content-column { white-space: pre-wrap; } in the CSS.

  4. Jacob Welsh says:

    > I think adding support for eight more languages will take quite some time.

    Seems more likely that someone would do a whole bunch at once rather than a trickle, and also to be encouraged to reduce churn and chance of conflicts in the "address space" (hey, it wasn't my idea to use numbers instead of language names).

    I think the regexing is easier than you've made it on yourself. Why not un-indirect the MP_WP_CODEBLOCK_OPEN which isn't meaningfully customizable anyway, and shrink the first set of parens to include just the number (as I showed)? Then no need for substr and you get multi-digits for free.

  5. billymg says:

    > Why not un-indirect the MP_WP_CODEBLOCK_OPEN which isn't meaningfully customizable anyway, and shrink the first set of parens to include just the number (as I showed)? Then no need for substr and you get multi-digits for free.

    That's a good point, i'll update the patch accordingly. And per our other thread on your article i'll ping others in #o about what options people would like to see customizable.

  6. [...] below for the updated embeddable codeblocks patch. This is the third iteration after the first two discussions. This patch also now contains the server-side select mechanism, including the updates [...]

Leave a Reply

*
*

You can use the following HTML tags in your comment: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>