WordPress.org

Plugin Directory

Changeset 586992


Ignore:
Timestamp:
08/17/12 20:48:28 (20 months ago)
Author:
MarcusPope
Message:

Finally got version 1.2 stable and ready for production. lots of good fixes and new features which are covered in the readme

Location:
preserved-html-editor-markup/trunk
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • preserved-html-editor-markup/trunk/admin.js

    r456532 r586992  
    11(function($) { 
     2    
     3    //Copied from tinymce source because it was privately scoped  
     4    function cloneFormats(node) { 
     5        var clone, temp, inner; 
     6 
     7        do { 
     8            if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) { 
     9                if (clone) { 
     10                    temp = node.cloneNode(false); 
     11                    temp.appendChild(clone); 
     12                    clone = temp; 
     13                } else { 
     14                    clone = inner = node.cloneNode(false); 
     15                } 
     16 
     17                clone.removeAttribute('id'); 
     18            } 
     19        } while (node = node.parentNode); 
     20 
     21        if (clone) 
     22            return {wrapper : clone, inner : inner}; 
     23    }; 
     24     
     25 
     26    window.emc2_tinymce_init = function(ed) { 
     27         
     28        ed.onNodeChange.add(function(ed, cm, n, co, ob) { 
     29            //This fixes a bug in the Format dropdown 
     30            //now when the cursor lands on an unwrapped piece 
     31            function getParent(name) { 
     32                //copied from tinymce source because it was privately scoped 
     33                var i, parents = ob.parents, func = name; 
     34 
     35                if (typeof(name) == 'string') { 
     36                    func = function(node) { 
     37                        return node.nodeName == name; 
     38                    }; 
     39                } 
     40 
     41                for (i = 0; i < parents.length; i++) { 
     42                    if (func(parents[i])) 
     43                        return parents[i]; 
     44                } 
     45            }; 
     46             
     47            if (c = cm.get('formatselect')) { 
     48                p = getParent(tinymce.DOM.isBlock); 
     49                if (p) { 
     50                    c.select(p.nodeName.toLowerCase()); 
     51                } 
     52                else { 
     53                    c.select(''); //BUGFIX: select the 'format' element 
     54                } 
     55            } 
     56        }, ed); 
     57         
     58        if (ed.settings.force_p_newlines == false) { 
     59            //When force_p_newlines is disabled we recreate the functionality from tinymce here to allow for br only inserts. 
     60            var prev_key = false; 
     61             
     62            ed.onKeyDown.add(function(ed, e) { 
     63                //keyPress doesn't capture backspace so use keydown isntead 
     64                if (e.keyCode != 13 || e.shiftKey) prev_key = false; 
     65                 
     66                if (tinymce.isGecko) { 
     67                    if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey) 
     68                        ed.forceBlocks.backspaceDelete(e, e.keyCode == 8); 
     69                } 
     70            }); 
     71             
     72            if (tinymce.isIE) { 
     73                tinymce.addUnload(function() { 
     74                    ed.forceBlocks._previousFormats = 0; // Fix IE leak 
     75                }); 
     76            } 
     77             
     78            ed.onKeyPress.add(function(ed, e) { 
     79                //Bug fix, since we removed the p tag  
     80                if (e.keyCode == 13 && !e.shiftKey) { 
     81                    if (prev_key && ed.settings.force_hybrid_newlines == true) { 
     82                        //double return 
     83                        //when a user enters two consecutive newlines in the wysiwyg editor, inject a new paragraph tag instead of the br tags* 
     84                        //*Except in firefox where that functionality could not be implemented in a timely manner due to some browser bugs 
     85                        if (!tinymce.isIE && !tinymce.isGecko) { 
     86                            if (!ed.forceBlocks.insertPara(e)) { 
     87                                tinymce.dom.Event.cancel(e); 
     88                            } 
     89                        } 
     90                        else if (tinymce.isIE) { 
     91                            ed.forceBlocks._previousFormats = 0; 
     92                             
     93                            // Clone the current formats, this will later be applied to the new block contents 
     94                            if (ed.selection.isCollapsed() && ed.settings.keep_styles) { 
     95                                ed.forceBlocks._previousFormats = cloneFormats(ed.selection.getStart()); 
     96                            } 
     97                             
     98                            //remove the last br tag that was inserted by the first enter key press 
     99                            var sel = ed.selection.getStart(); 
     100                                sel.innerHTML = sel.innerHTML.replace(/<br\/?>([^<br\/?>]*)$/i, ''); 
     101                        } 
     102                         
     103                        prev_key = false; 
     104                    } 
     105                    else { 
     106                        prev_key = true; 
     107                         
     108                        if ((tinymce.isIE || tinymce.isGecko) && e.keyCode == 13 && ed.selection.getNode().nodeName != 'LI') { 
     109                            ed.selection.setContent('<br id="__" /> ', {format : 'raw'}); 
     110                            var n = ed.dom.get('__'); 
     111                            n.removeAttribute('id'); 
     112                            ed.selection.select(n); 
     113                            ed.selection.collapse(); 
     114                            return tinymce.dom.Event.cancel(e); 
     115                        } 
     116                         
     117                        if (tinymce.isWebKit) { 
     118                             
     119                            function insertBr(ed) { 
     120                                var rng = ed.selection.getRng(), br, div = ed.dom.create('div', null, ' '), divYPos, vpHeight = ed.dom.getViewPort(ed.getWin()).h; 
     121             
     122                                // Insert BR element 
     123                                rng.insertNode(br = ed.dom.create('br')); 
     124             
     125                                // Place caret after BR 
     126                                rng.setStartAfter(br); 
     127                                rng.setEndAfter(br); 
     128                                ed.selection.setRng(rng); 
     129             
     130                                // Could not place caret after BR then insert an nbsp entity and move the caret 
     131                                if (ed.selection.getSel().focusNode == br.previousSibling) { 
     132                                    ed.selection.select(ed.dom.insertAfter(ed.dom.doc.createTextNode('\u00a0'), br)); 
     133                                    ed.selection.collapse(true); 
     134                                } 
     135             
     136                                // Create a temporary DIV after the BR and get the position as it 
     137                                // seems like getPos() returns 0 for text nodes and BR elements. 
     138                                ed.dom.insertAfter(div, br); 
     139                                divYPos = ed.dom.getPos(div).y; 
     140                                ed.dom.remove(div); 
     141             
     142                                // Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117 
     143                                if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port. 
     144                                    ed.getWin().scrollTo(0, divYPos); 
     145                            } 
     146                             
     147                            if (e.keyCode == 13 && !ed.dom.getParent(ed.selection.getNode(), 'h1,h2,h3,h4,h5,h6,ol,ul')) { 
     148                                insertBr(ed); 
     149                                tinymce.dom.Event.cancel(e); 
     150                            }                         
     151                        } 
     152                    } 
     153                } 
     154            }); 
     155             
     156            if (tinymce.isIE) { 
     157                ed.onKeyUp.add(function(ed, e) { 
     158                    // Let IE break the element and the wrap the new caret location in the previous formats 
     159                    if (e.keyCode == 13 && !e.shiftKey) { 
     160                        var parent = ed.selection.getStart(), fmt = ed.forceBlocks._previousFormats; 
     161         
     162                        // Parent is an empty block 
     163                        if (!parent.hasChildNodes() && fmt) { 
     164                            parent = ed.dom.getParent(parent, ed.dom.isBlock); 
     165         
     166                            if (parent && parent.nodeName != 'LI') { 
     167                                parent.innerHTML = ''; 
     168         
     169                                if (ed.forceBlocks._previousFormats) { 
     170                                    parent.appendChild(fmt.wrapper); 
     171                                    fmt.inner.innerHTML = '\uFEFF'; 
     172                                } else { 
     173                                    parent.innerHTML = '\uFEFF'; 
     174                                } 
     175         
     176                                selection.select(parent, 1); 
     177                                selection.collapse(true); 
     178                                ed.getDoc().execCommand('Delete', false, null); 
     179                                ed.forceBlocks._previousFormats = 0; 
     180                            } 
     181                        } 
     182                    } 
     183                }); 
     184            } 
     185        } 
     186    }; 
     187 
     188    window.emc2pm_fix_content = function(post_type) { 
     189        //used on the writting settings page for fixing legacy content 
     190        if (confirm('Are you sure? This process is not reversable')) { 
     191            $.get( 
     192                ajaxurl,  
     193                { 
     194                    nonce : emc2pm.fix_content_nonce, 
     195                    action : 'emc2pm_fix_posts', 
     196                    post_type : post_type 
     197                }, 
     198                function(r) { 
     199                    alert(r); 
     200                } 
     201            ); 
     202        } 
     203        return false; 
     204    }; 
     205     
    2206    //on dom load 
    3207    $(function() { 
    4  
     208        //first_switch_html & orig_title are used in the bugfix in the afterPreWpautop override below 
     209        var first_switch_html = true; 
     210        var orig_title = $('#post #title').val(); 
     211         
    5212        //disable evil... yeah that's right I said EVIL! 
    6213        if (window.switchEditors) { 
     
    16223 
    17224        $('body').bind('afterPreWpautop', function(e, o) { 
    18             //On Switch to HTML 
    19  
     225            //On Switch to HTML & On save/update from Visual tab 
     226             
    20227            //Now we replace all those temporary html comments with spaces and newlines 
    21228            o.data = o.unfiltered.replace(/<\!--mep-nl-->/g, "\r\n").replace(/<\!--mep-tab-->/g, "    "); 
     229             
     230            //And finally remove the code tag hack from the markup 
     231            o.data = o.data.replace(/<code style=['"]display: none;['"]><!--[\s\S]*?--><\/code>/g, function(s) { 
     232                return s.substring(s.indexOf('<!--'), s.indexOf('-->') + 3); 
     233            }); 
     234             
     235            if (first_switch_html) { 
     236                first_switch_html = false; 
     237                /*BUGFIX: in autosave.js, on document load, the textarea value is cached in a js var 'autosaveLast' and 
     238                  is used to determine if changes were made in onbeforeunload. The content is correct when we start in 
     239                  HTML mode.  But when we load the page in Visual mode it's "mep" encoded (properly so to render correctly.) 
     240                  So loading in Visual mode and switching to HTML mode will result in comparing the "mep" encoded version 
     241                  with the proper clean HTML version.  So on the very first transition we update the "autosaveLast" var 
     242                  value to the clean HTML content.  It's incredibly fortunate that not only is this variable publicly scoped 
     243                  against JS best practices, but also that in Visual mode tinymce uses a different variable to check for 
     244                  changes.  So adjusting autosaveLast will not affect the onbeforeunload event in Visual mode. 
     245                */ 
     246                 
     247                //only adjust the html if the visual tab isn't "dirty" because then it doesn't matter either way 
     248                //but if they made changes on the visual tab a tab switch would use those updates and the editor 
     249                //wouldn't alert the user to any changes on refresh. 
     250                if (tinyMCE && tinyMCE.activeEditor && tinyMCE.activeEditor.isDirty() == false) { 
     251                    autosaveLast = orig_title + o.data.replace(/\r/g, ''); //seems \r's are stripped out of textarea#content 
     252                                                                           //maybe I shouldn't be including them, thought they 
     253                                                                           //were necessary for windows platforms 
     254                } 
     255            }             
    22256        }).bind('afterWpautop', function(e, o) { 
    23257            //On Switch to Visual 
    24  
    25             //first preserve newlines and whitespace in pre & code tags because the browser will not mess with those 
     258            first_switch_html = false; //this var is only important if we start on the visual tab 
     259                                       //TODO: but a bug exists in wordpress that could be fixed here.  Basically if you 
     260                                       //load with HTML tab active, make a change, switch to Visual, and click Refresh 
     261                                       //you won't be prompted to save changes because the Visual tab sets originalContent 
     262                                       //to the current html value on tab switch. :(  So we could overwrite it with the 
     263                                       //true originalContent here on first switch. 
     264             
     265            //first: hack fix for preserving multi-line html comments 
     266            o.unfiltered = o.unfiltered.replace(/<!--[\s\S]*?-->/g, function(s) { 
     267                return "<code style='display: none;'>" + s + "</code>"; 
     268            }); 
     269 
     270            //next: preserve newlines and whitespace in pre & code tags because the browser will not mess with those 
    26271            if ( o.unfiltered.indexOf('<pre') != -1 || o.unfiltered.indexOf('<code') != -1 ) { 
    27272                o.unfiltered = o.unfiltered.replace(/<(pre|code)[^>]*>[\s\S]+?<\/\1>/g, function(s) { 
     
    30275            } 
    31276 
    32             //replace any newline characters with a custom mep html comment as a marker for where 
     277            //now: replace any newline characters with a custom mep html comment as a marker for where 
    33278            //newline chars should be added back in when we're done 
    34279            o.data = o.unfiltered.replace(/(\r\n|\n)/g, "<!--mep-nl-->").replace(/(\t|\s\s\s\s)/g, "<!--mep-tab-->"); 
    35280 
    36             //and restore the whitespace back in pre & code tags 
     281            //finally: restore the whitespace back in pre & code tags 
    37282            o.data = o.data.replace(/<mep-preserve-nl>/g, "\n").replace(/<mep-preserve-tab>/g, "\t").replace(/<mep-preserve-space>/g, " "); 
    38283        }); 
  • preserved-html-editor-markup/trunk/readme.txt

    r584935 r586992  
    44Tags: wpautop, editor, markup, html, white space, HTML5, WYSIWYG, visual, developer 
    55Requires at least: 3.2.1 
    6 Tested up to: 3.2.1 
     6Tested up to: 3.4 
    77Stable tag: trunk 
    88 
     
    1515It also supports HTML5 Block Anchor tags, something that is currently not supported in WP even via any existing plugins. 
    1616 
    17 There are a couple caveats to editing in this mode, but most of them are understood by developers and are even repeatedly asked for in forums and on Wordpress dev lists.  They are as follows: 
     17With version 1.2, you now have a little more control over how content is created.  And most of the previous caveats to using this plugin are now resolved. 
    1818 
    19   1. wpautop is disabled.  And I mean really disabled, not just disabled when editing in HTML, or when viewing the content, but the TinyMCE editor will never wrap your content in a p tag again (unless you tell it to of course.) 
     19  1. You can now choose whether to use BR tags OR P tags for newlines.  Even better you can use both, where one return key press injects a BR tag, and two return key presses will wrap a Paragraph tag.  This is great for being able to wrap headers at specific break points all while enjoying the semantic perks of paragraphs. 
     20   
     21  1. In addition to choosing what type of tags to use, you can also change the behavior depending on the type of post, including custom post types.  So Pages can default to BR tags, and Blog Posts can default to Paragraph tags. 
     22   
     23  1. If you have existing content that was created before activating this plugin, you can now use the Fixit feature to convert your existing content in a way that makes it render the same as before. Only use this feature (located under Admin > Settings > Writing: Fixing Existing Content) if you are installing this plugin for the first time, otherwise it will remove all of the formatted white space in your posts. 
     24   
     25  1. Multi-line HTML comments are now supported (Thanks to [@cwlee_klagroup](http://wordpress.org/support/profile/cwlee_klagroup) for suggesting the working fix!) 
     26   
     27  1. The Format drop down in the TinyMCE editor had a bug which is now fixed.  It will now select "Format" if you place the cursor on a section of bare text.  Currently the editor just leaves the previously selected format option in place.  It's minor but it's good to know when you have bare text in your content. 
     28   
     29  1. There was a fairly problematic bug in the old version where in some browsers you couldn't change the formatting of a single line in the Visual editor if you started from scratch.  Choosing a different Format option would change the entire document, with the only work around being to edit the document in HTML mode.  That was bad, and somehow went unnoticed for far too long.  Anyway, that is fixed now. 
    2030 
    21   1. In Visual mode, the "enter key" will inject BR tags instead of newlines in the HTML source.  It will still appear correct in the WYSIWYG result. 
     31The caveats that still remains are: 
    2232 
    23   1. Orphaned text content will not be wrapped in any tag - so you will want to make sure your theme inherently wraps the post content in a div tag (or some other block element) for valid HTML markup. 
     33  1. If you use the Paragraph tag setting for newlines there is a minor bug where it will only wrap your content in Paragraph tags if you specify Paragraph in the Format drop down or if you enter more than one paragraph of text.  So if you just type one sentence and click save it will not wrap the content in Paragraph tags.  I tried to fix this but ran out of my allotted time working on other core issues.  Should be fixed in the next release. 
     34   
     35  1. For performance reasons, it will only preserve spaces if 4 spaces are used consecutively - i.e. an expanded tab in developer terms.  It will not preserve intra-tag white space like &lt;p&nbsp;&nbsp;&nbsp;&nbsp;&gt;. 
    2436 
    25   1. White space is preserved; newlines, spaces and tabs will remain in your markup for the most part.  For performance reasons, it will only preserve spaces if 4 spaces are used consecutively - i.e. an expanded tab in developer terms.  It will not preserve intra-tag white space like &lt;p&nbsp;&nbsp;&nbsp;&nbsp;&gt;. 
    26  
    27 There are a couple of bugs in the parsing logic that I'm aware of and I will work to iron them out over the coming weeks. 
    28  
    29 Known issues: 
    30  
    31   1. Multi-line comment blocks, and comments with 4 or more consecutive spaces are not supported at the moment.  Because my plugin relies on embedding html comments into the dom, any html comment with a new-line or tab will be embedded with a nested html comment which is not supported by the html spec.  The only solution I can suggest is to rely on revision history and simply remove whatever content you were intending to hide. 
    32    
    33   1. If you do add 4 or more consecutive spaces inside of an element tag it will corrupt the markup and mangle the output.  But as this is intended for developer edits, this should be an extreme rarity given the habit is virtually non-existent in development communities. 
    34  
    35   1. If you use two or three spaces on a line they will be reduced to one. 
     37  1. If you do add 4 or more spaces inside of an element tag it will corrupt the markup and mangle the output.  But as this is intended for developer edits, this should be an extreme rarity given the habit is virtually non-existent in development communities. 
    3638 
    3739  1. PRE tags are not affected and behave as you would expect, however due to how browsers parse tags, the first newline in the content of a PRE tag will be wiped out unless it is padded with either another new line or multiple spaces. 
    3840 
    39   1. CODE tags are not preserving white space at all, and when wrapped around PRE tags they are being removed entirely. When PRE tags are wrapped around CODE tags they are not removed, however white space is removed.  I'm working to resolve this problem. 
     41  1. CODE tags are not preserving white space at all, and when wrapped with PRE tags white space is still removed.  I'm working to resolve this problem. 
    4042 
    4143== Installation == 
     
    43451. Upload the plugin contents to the `/wp-content/plugins/` directory 
    44461. Activate the plugin through the 'Plugins' menu in Wordpress Admin 
     471. If you have existing content that needs fixing, use the "Fix Posts" feature under Admin > Settings > Writing: Fix Existing Content. 
    45481. You're done! 
    4649 
    4750== Frequently Asked Questions == 
    4851 
    49 = Why doesn't the visual editor wrap my text content in paragraph &lt;p&gt; tags? = 
     52= When will code tag issues be resolve? = 
    5053 
    51 This is a side effect of disabling the poorly designed code formatter that is built into Wordpress.  However, it should be possible to add this feature at some point down the road. 
     54This is a tough one.  Not only do I have no idea why they're being trumped, but I also have a daughter that will be born pretty soon :D, and a project at work that is about to get hectic :(  I'll try to fix it when I can but if you have the skills to help debug the help would be greatly appreciated. 
     55 
     56= What exactly do the "Fix Posts" or "Fix XXX" buttons do to my content? = 
     57 
     58Firstly, only use this feature if you are starting new with version 1.2. And definitely backup your database before running these tools, they have only been tested on two sites so far.  And although in theory it is safe, you should still protect yourself. 
     59 
     60The fix actually just runs wpautop one final time on the posts in the database.  By default WordPress runs that function every time it displays content, so the raw data in the database is free of any paragraph tags & other formatting tweaks.  The Fix buttons update the raw content in the database with the formatted version wpautop produces.  And fortunately wpautop was designed in a way that it can be run multiple times so it shouldn't mangle your content. 
     61 
     62All of your post content will be converted, including past revisions.  So if you need to revert a page or post after you activate this feature, you won't have to reformat the previous version by hand. 
     63 
     64The plugin also keeps track of when it was activated, so it will only modify content that was edited before the plugin was activated.  So if you created some new content after activating the plugin and later realized all of your other content wasn't displaying correctly it's safe to use the Fix buttons without ruining your new content. 
    5265 
    5366== Upgrade Notice == 
    5467 
    55 1. No upgrade notices 
     68If you used version 1.0 or 1.1 to create content, do not use the Fix it features unless you are ok with losing the white space preservation of those posts. 
    5669 
    5770== Screenshots == 
     
    6174== Changelog == 
    6275 
     76= 1.2 = 
     77* Added support for user-specified newline behavior per post type 
     78* Added support for multi-line html comments (Thanks cwlee_klagroup!) 
     79* Fixed a bug found in TinyMCE related to Format drop down 
     80* Added tools to convert existing site content programmatically by post type. 
    6381= 1.1 = 
    6482* Refactored for support of < php5.3 by replacing function references with static function array refs 
  • preserved-html-editor-markup/trunk/sb_preserved_markup.php

    r584935 r586992  
    88Author: Marcus E. Pope, marcuspope 
    99Author URI: http://www.marcuspope.com 
    10 Version: 1.0 
     10Version: 1.2 
    1111 
    1212Copyright 2011 Marcus E. Pope (email : me@marcuspope.com) 
     
    2929add_action('plugins_loaded', array('MP_WP_Preserved_Markup', 'init'), 1); 
    3030 
     31register_activation_hook( __FILE__, array('MP_WP_Preserved_Markup', 'set_activation_time')); 
     32 
     33if (is_admin()) { 
     34    add_action('wp_ajax_emc2pm_fix_posts', array('MP_WP_Preserved_Markup', 'fix_database_content')); 
     35} 
     36 
    3137class MP_WP_Preserved_Markup { 
    3238 
     39    private static $valid_types = array(); 
     40     
    3341    public static function remove_evil() { 
    3442        //remove evil: wpautop will break html5 markup! 
    3543        remove_filter('the_content', 'wpautop'); 
     44         
     45        //massage evil-ish wp-texturize 
     46        if (has_filter('the_content', 'wptexturize')) { 
     47            remove_filter('the_content', 'wptexturize'); 
     48            add_filter('the_content', array('MP_WP_Preserved_Markup', 'better_wptexturize')); 
     49        } 
     50    } 
     51     
     52    public static function better_wptexturize($s) { 
     53        //this is probably the first of many tweaks we'll fix in texturize, 
     54        //but for now it's just targeting html comment tags 
     55         
     56        //replace all closing html tags with temp placeholders so texturize doesn't turn them into endash's 
     57        $s = preg_replace("/-->/m", "-mep-temp-closing-comment-tag->", $s); 
     58                 
     59        //now let wptexturize do its work 
     60        $s = wptexturize($s); 
     61         
     62        //and restore original values in our temp placeholders 
     63        $s = preg_replace("/-mep-temp-closing-comment-tag->/m", "-->", $s); 
     64         
     65        return $s; 
    3666    } 
    3767 
    3868    public static function init_tiny_mce($init) { 
     69         
     70        $post_type = self::get_cur_post_type(); 
     71        $insert_p = 'br'; 
     72         
     73        if (!empty($post_type)) { 
     74            //check post type page width specified by user setting 
     75            $options = get_option('emc2_editor_insert_p'); 
     76            //default to 'br' for backwards compatibility/forwards consistency 
     77            $insert_p = isset($options[$post_type]) ? $options[$post_type] : "br"; 
     78        } 
     79         
    3980        //Setup tinymce editor with necessary settings for better general editing 
    4081        $tags = "pre[*],iframe[*],object[*],param[*]"; //add pre and iframe to allowable tags for 
     
    4687        $init['force_p_newlines'] = false; 
    4788        $init['remove_linebreaks'] = false; 
    48         $init['force_br_newlines'] = true; 
     89        $init['force_br_newlines'] = false; 
     90         
     91        if ($insert_p == "br") { 
     92            //default behavior for this plugin 
     93        } 
     94        else if ($insert_p == "p") { 
     95            $init['force_p_newlines'] = true; 
     96        } 
     97        else if ($insert_p == "both") { 
     98            //insert p tag after two consecutive new lines 
     99            $init['force_hybrid_newlines'] = true; 
     100        } 
     101         
    49102        $init['remove_trailing_nbsp'] = false; 
    50103        $init['relative_urls'] = true; 
     
    57110        $init['fix_table_elements'] = false; 
    58111        $init['verify_html'] = false; 
     112        $init['setup'] = 'emc2_tinymce_init'; 
    59113 
    60114        /* 
     
    70124 
    71125    public static function fix_editor_content($html) { 
     126        //this filter is added dynamically by 'the_editor' event, but it will wreck our hidden markup  
    72127        remove_filter('the_editor_content', "wp_richedit_pre"); 
    73128        return $html; 
     
    81136        return $s; 
    82137    } 
     138     
     139    public static function comment_hack_callback($a) { 
     140        return "<code style='display: none;'>" . $a[0] . "</code>"; 
     141    } 
     142     
     143    public static function comment_unhack_callback($a) { 
     144        $s = $a[0]; 
     145        $start = strpos($s, '<!--'); 
     146        $stop = strrpos($s, '-->') + 3; 
     147        $s = substr($s, $start, $start - $stop); 
     148        return $s; 
     149    } 
    83150 
    84151    public static function fix_wysiwyg_content($c) { 
    85152        //If the page is rendered with the WYSIWYG editor selected by default, content is processed in PHP land 
    86153        //instead of using the JS land "equivalent" logic (I quote equivalent because there are sooooo many 
    87         //discrepancies between what JS wpautop and PHP wpautop functions do it's laughable. 
     154        //discrepancies between what JS wpautop and PHP wpautop functions do it's laughable.) 
    88155        if (wp_default_editor() == "tinymce") { 
     156             
     157            //Our whitespace preservation logic breaks existing multiline html comments. 
     158            //By wrapping them in hidden code blocks, we can preserve whitespace and hide the rendered content 
     159            $c = preg_replace_callback( 
     160                '/<!--[\s\S]*-->/m', 
     161                array( 
     162                    'MP_WP_Preserved_Markup', 
     163                    'comment_hack_callback' 
     164                ), 
     165                $c); 
     166             
    89167            //First we inject temporary whitespace markers in pre and code elements because they won't 
    90168            //be corrupted when the user switches to html mode.*   (actually IE9 will remove the first 
     
    113191            $c = convert_chars($c); 
    114192            $c = htmlspecialchars($c, ENT_NOQUOTES); 
     193            $c = apply_filters('richedit_pre', $c); 
    115194        } 
    116195 
     
    121200        //If the user clicks save while in the Visual (WYSIWYG) tab, we'll need to strip the whitespace placeholders 
    122201        //before inserting the data into the database to prevent duplication of whitespace 
    123         //WTF: ok so I ran into a problem of duplicating newlines when saving from the Visual tab, so I added this 
    124         //     code to strip what I thought was my whitespace markers not being converted back in JS land before being 
    125         //     sent to the server.  (in the same way that if you load the page on the Visual tab, the parsing logic 
    126         //     occurs on the server instead of the client!)  But after add this code I couldn't reproduce the issue 
    127         //     the post_content never contained any mep-xxx comments.  I'm leaving this code in here, because it's 
    128         //     the probable explanation for duplicated newlines, but I'm also not sure how the Visual content is 
    129         //     transfered from the iframe to the textarea. 
     202         
     203        //INFO: This should not be necessary because when the user clicks save from the Visual Tab the content is passed 
     204        //through the afterPreWpautop javascript event which we already use to handle tab switching.  I think my previous 
     205        //issue was caused by a js error in that function that resulted in nothing being stripped out before it was 
     206        //posted to the server here: 
    130207        if (isset($post['post_content'])) { 
    131208            $post['post_content'] = preg_replace('/<\!--mep-nl-->/m', "\r\n", $post['post_content']); 
    132209            $post['post_content'] = preg_replace('/<\!--mep-tab-->/m', "    ", $post['post_content']); 
     210            $post['post_content'] = preg_replace_callback( 
     211                '/<code style=[\'"]display: none;[\'"]><!--[\s\S]*?--><\/code>/m', 
     212                array( 
     213                    'MP_WP_Preserved_Markup', 
     214                    'comment_unhack_callback' 
     215                ), 
     216                $post['post_content'] 
     217            ); 
    133218        } 
    134219        return $post; 
     220    } 
     221     
     222    public static function set_activation_time() { 
     223        update_option('emc2pm_activate_date', time()); 
     224    } 
     225     
     226    public static function fix_database_content() { 
     227        /* 
     228            Iterate over every post in the database by specified post_type and 
     229            update the content with wpautop.  This is essentially what WordPress 
     230            did each time it rendered content from the database.  Since we're 
     231            leaving the content alone from now on, this gives the user the ability 
     232            to fix previously created content. 
     233        */ 
     234        global $wpdb; 
     235 
     236        //verify nonce and validate post type value 
     237        if (wp_verify_nonce(@$_GET['nonce'], "emc2pm_fix_content") && 
     238            post_type_exists(@$_GET['post_type'])) { 
     239             
     240            //get everything, revisions and all. I hesitated on this, but thought if a week later a user reverts a 
     241            //post to a previous revision, they shouldn't have to re-fix the post by hand. And since the modified date 
     242            //would be after the plugin activation date, the 'Fix XXX' feature wouldn't automatically fix it. 
     243            $posts = $wpdb->get_results($wpdb->prepare(" 
     244                SELECT ID, post_content, post_title, post_modified FROM $wpdb->posts 
     245                WHERE  post_type = %s", $_GET['post_type'])); 
     246             
     247            $errors = array(); 
     248             
     249            $limit = get_option('emc2pm_activate_date'); 
     250             
     251            foreach ($posts as $post) { 
     252                //Don't clobber whitespace in any content created after the plugin was activated. 
     253                //ISSUE: if the user activated the plugin, then modified content, then disabled the plugin and re-enabled it 
     254                //we would accidently unformat some whitespace... meh, there are worse problems in the world. 
     255                $modified = strtotime($post->post_modified); 
     256                if (strlen($modified) != 0 && //don't think this is possible, but just in case 
     257                    $modified > $limit) { 
     258                    continue; 
     259                } 
     260                     
     261                $new_content = wpautop($post->post_content); 
     262                 
     263                if ($new_content != "") { 
     264                     
     265                    $ob = ob_start(); //capture html db errors for cleaner alert 
     266                    $wpdb->query( $wpdb->prepare(" 
     267                        UPDATE $wpdb->posts SET post_content = %s 
     268                        WHERE ID = %d 
     269                    ", $new_content, $post->ID) ); 
     270                    $res = ob_get_clean(); 
     271                     
     272                    if (strstr($res, "error")) { 
     273                        $errors[] = $post->post_title; 
     274                    } 
     275                } 
     276            } 
     277         
     278            //present the processing results to user 
     279            $res = "Content Fixed"; 
     280            if (count($errors)) { 
     281                $res .= "\n\nBut the following posts could not be updated:\n"; 
     282                $res .= join("\n", $errors); 
     283            } 
     284                 
     285            die($res); 
     286        } 
     287        else { 
     288            die("ERROR: Access Denied"); 
     289        } 
    135290    } 
    136291 
     
    146301        //      Create a filter for overriding so I don't have to copy this plugin from 
    147302        //      the svn repo to my own hg/git repos  
    148         wp_enqueue_script('admin-js', WP_PLUGIN_URL.'/'.str_replace(basename( __FILE__),"",plugin_basename(__FILE__))."admin.js"); 
    149         //wp_enqueue_script('admin-js', WP_PLUGIN_URL.'/sb_preserved_markup/admin.js'); 
     303        wp_enqueue_script('emc2-pm-admin-js', WP_PLUGIN_URL.'/'.str_replace(basename( __FILE__),"",plugin_basename(__FILE__))."admin.js"); 
     304        //wp_enqueue_script('emc2-pm-admin-js', WP_PLUGIN_URL.'/sb_preserved_markup/admin.js'); 
     305         
     306        //provide nonce for ajax calls 
     307        wp_localize_script('emc2-pm-admin-js', 'emc2pm', array( 
     308            'fix_content_nonce' => wp_create_nonce('emc2pm_fix_content') 
     309        ));         
    150310 
    151311        add_filter('the_editor', array( 
     
    163323            'fix_post_content' 
    164324        ), 1); 
     325         
     326        self::admin_settings_init(); 
    165327    } 
    166328 
     
    176338        )); 
    177339    } 
     340     
     341    /* 
     342     * The following set of functions mostly apply to the settings page under Admin > Settings > Writing 
     343     */ 
     344    static function get_cur_post_type() { 
     345        global $pagenow; 
     346         
     347        //Get type of post we're currently editing (are even editing something?) 
     348        $post_id = (int) @$_GET['post']; 
     349        
     350        //Yep, consistency would be nice here, but oh well 
     351        if (!empty($post_id)) $post_type = get_post_type($post_id); 
     352        if (empty($post_type)) $post_type = sanitize_key(@$_GET['post_type']); 
     353        if (empty($post_type)) $post_type = $pagenow == "post-new.php" ? "post" : ""; 
     354         
     355        return $post_type; 
     356    } 
     357     
     358    static function admin_settings_init() { 
     359        global $pagenow; 
     360         
     361        //Unlike the wp.org code sample, we'll only waste resources when necessary 
     362        if ($pagenow == 'options-writing.php' || //register when viewing the writing options page 
     363            $pagenow == 'options.php') { //and when clicking save (required or it won't know to save the settings) 
     364 
     365            register_setting('writing', 'emc2_editor_insert_p', array('MP_WP_Preserved_Markup', 'validate_settings')); 
     366             
     367            //give this setting its own section 
     368            add_settings_section('emc2_editor_insert_p', 'WYSIWYG New Line Behavior', array('MP_WP_Preserved_Markup', 'render_setting_section'), 'writing'); 
     369 
     370            //Add settings fields for each gui'd post type that supports a wysiwyg editor 
     371            $types = get_post_types(array('show_ui' => true), "objects"); 
     372 
     373            foreach ($types as $id => $type) { 
     374                if (post_type_supports($id, 'editor')) { 
     375                     
     376                    //cache valid types for fixit buttons 
     377                    array_push(self::$valid_types, array( 
     378                        'id' => $id, 
     379                        'label' => $type->label)); 
     380                     
     381                    add_settings_field( 
     382                        'emc2_editor_insert_p_' . $id, //setting id 
     383                        $type->label, //setting title 
     384                        array('MP_WP_Preserved_Markup', 'render_setting_input'), //render callback 
     385                        'writing', //page 
     386                        'emc2_editor_insert_p', //section (default = top) 
     387                        array( 
     388                            'label_for' => 'emc2_editor_insert_p_' . $id, 
     389                            'id' => $id //pass id into render callback 
     390                        ) 
     391                    ); 
     392                } 
     393            } 
     394        } 
     395    } 
     396 
     397    static function render_setting_section($t) { 
     398        //add description of section to page 
     399        echo 
     400        "<p>By default the WordPress editor will automatically inject a new paragraph tag when the enter key is pressed. 
     401            The Preserved HTML Editor Markup plugin now gives you three options to chose from when a new line is created:</p> 
     402             
     403            <ul><li><b>P-Tag</b>: This option will continue using the default behavior of WordPress, while still preserving HTML whitespace &amp; allowing for block-level anchor tags.</li> 
     404                <li><b>BR-Tag</b>: This option will inject HTML line-breaks (&lt;BR&gt; tags) instead of new paragraph tags.<b>*</b></li> 
     405                <li><b>Both</b>: This option will inject HTML line-breaks by default, but will start a new paragraph tag if two consecutive enter keys are pressed. <b>**</b></li></ul> 
     406             
     407        <p><i>*This was the default behavior of this plugin prior to version 1.2.  You may continue to use this setting if you prefer, but I recommend the 'Both' setting as a compromise for Visual Tab and HTML Tab users.</i></p> 
     408        <p><i>**This feature is currently not 100% compatible with FireFox browsers.  It will not cause problems with the editor, but it will only insert a new paragraph tag after two consecutive returns if the current line is already wrapped in a paragraph tag. However FireFox does allow you to change the Format option for a single line of unwrapped text in the Visual Editor, so users can easily add paragraph tags around unformatted text via the toolbar.</i></p> 
     409        <h3>Fixing Existing Content</h3> 
     410        <p><b style='color: red;'>BE SURE TO BACK UP YOUR DATABASE</b>, as the changes are permanent otherwise.</p> 
     411        <p>If you have existing content that isn't displaying correctly with this plugin enabled, you should use the 'Fix ...' buttons below.  This will modify the content in the database with <a href='http://codex.wordpress.org/Function_Reference/wpautop'>wpautop</a>, including past revisions.</p> 
     412        <p>Fixing a content type multiple times should be harmless as WordPress does this by default. Content that you have modified via the editor after the plugin activation date will not be affected. But it is recommended that you fix your existing content soon after enabling the plugin.</p> 
     413        ";  
     414         
     415        echo "<p class='pressthis'>"; 
     416        foreach (self::$valid_types as $o) { 
     417            echo "<a style='width: auto; padding-right: 8px; cursor: pointer;' onclick='emc2pm_fix_content(\"{$o['id']}\"); return false;' href='#'><span>Fix {$o['label']}</span></a> &nbsp; "; 
     418        } 
     419        echo "</p>"; 
     420         
     421        echo "<h3>Configure New Line Behavior Per Post Type</h3>"; 
     422    } 
     423 
     424    static function render_setting_input($attr) { 
     425        //Display a list of option boxes for specifying the new line insertion behavior 
     426        $options = get_option('emc2_editor_insert_p'); 
     427        $value = isset($options[$attr['id']]) ? $options[$attr['id']] : 'br'; 
     428        ?> 
     429            <input id="<?php echo $attr['label_for'] . "_p"; ?>" name="emc2_editor_insert_p[<?php echo $attr['id']; ?>]" type="radio" value="p" <?php echo ($value == "p" ? "checked" : ""); ?> class="small-text" /> P-Tag<br> 
     430            <input id="<?php echo $attr['label_for'] . "_br"; ?>" name="emc2_editor_insert_p[<?php echo $attr['id']; ?>]" type="radio" value="br" <?php echo ($value == "br" ? "checked" : ""); ?> class="small-text" /> BR-Tag<br> 
     431            <input id="<?php echo $attr['label_for'] . "_both"; ?>" name="emc2_editor_insert_p[<?php echo $attr['id']; ?>]" type="radio" value="both" <?php echo ($value == "both" ? "checked" : ""); ?> class="small-text" /> Both 
     432        <?php 
     433    } 
     434     
     435    static function validate_settings($in) { 
     436        return $in; //it's an option box, I don't think people can mess that up 
     437    } 
    178438} 
Note: See TracChangeset for help on using the changeset viewer.