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 | |
| 2 | |
| 3 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 3 | |
| 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 | |
| 11 | |
| 12 | |
| 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 | |
| 77 | ); |
| 78 | |
| 79 | // Define default options |
| 80 | - $this->default_options = array('superscript'=>true, |
| 81 | + $this->options = array('superscript'=>true, |
| 82 | 'pre_backlink'=>' [', |
| 83 | 'backlink'=>'↩', |
| 84 | 'post_backlink'=>']', |
| 85 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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('"', """, 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 |
- 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). [↩] - 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. [↩]
- The open tag no longer needs to be escaped now that it's a regex pattern. [↩]
- Pass the new syntax index to
format_snippet()so it knows how to highlight the codeblock. [↩] - 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 ofsedbefore 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/&/\&/g; s/</\</g; s/>/\>/g; s/"/\"/g; s/'"'"'/\'/g; s/((/\(\(/g; s/))/\)\)/g; s/\[([0-9])\[/\[$1\[/g; s/]]/\]\]/g' > [FILE_ESCAPED][↩] - I changed this to a plain
divbecause thepretag wrapped around the other markup (which is probably a misuse of thepretag in any case) would cause extra newlines to be added when copy/pasting from the snippet into a text file. [↩] - This little bit of CSS avoids selecting footnote markers and line numbers when copy/pasting text from a codeblock. Go ahead, try it out. [↩]
- 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. [↩]
Looks quite good! I'll add it to the list to give it a spin sooner rather than later.
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:
> 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!
> 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_allpopulates (#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
pretag wrapped around thetable, copying:and pasting it would yield:
Multiple sequential spaces are handled as you would want/expect them to be (i.e. maintained). The
pretag was superfluous because oftd.content-column { white-space: pre-wrap; }in the CSS.> 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.
> 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.
[...] 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 [...]
[...] . The characters in http://billymg.com/2020/04/updated-patch-code-embed-plugin-for-mp-wp/ didn't work. Consequently the codeblock in my [...]