tiny_mce3.5.10でソースの改行が削除されてしまう件

tiny_mceのバージョンが古くなっていたので、3系で最新の3.5.10に更新したところ、ソースを表示した際に改行コードがすべて削除されてしまうということに気がつきました。
具体的には、ソースを表示した際に

<strong>hoge</strong>
fugo<br>
fugo2<br>

と表示してほしいのに

<strong>hoge</strong> fugo<br> fugo2

のように表示されるという感じです。

TinyMCEの設定マニュアルをみたところいろいろ設定が変わっていることが原因のようでした。
いろいろさわって確認してみると、

force_br_newlines : true,
force_p_newlines : false,
forced_root_block : '',

を削除すると、タグごとに改行されるようになるのですが、以下のようにbrタグのかわりにpタグで囲まれるということになり、これはこれで望む結果ではありません。

<p><strong>hoge</strong></p>
<p>fugo</p>
<p>fugo</p>

どうも、tinymceの設定のみで

<strong>hoge</strong>
fugo<br>
fugo2<br>

のように表示することは難しいようなので、苦肉の策として、ソース表示に切り替えた際に、ソースを整形するスクリプトに一旦処理させて戻すということをするようにしました。

●作業内容

整形スクリプトにはjquery-indent.jsを利用しようと思ったのですが、jquery1.10.2で使えなかったので、以下のような感じに修正を施し、jquery.indent.jsとして保存しました。

/**
 * @projectDescription	Indents (x)html source code.
 *
 * @author 	mizuki fujitani
 * @version 1.2 2009-08-12
 * @license GPL
 *
 * @edit yoshihiro nakade
 * 2014-02-25
 */

(function($){
	$.fn.indent = function(options){
		var DEFAULTS = {
			tab: '\t',
			xhtml: true, // ex. <img> --> <img />, <br> --> <br />
			singles: ['br', 'img', 'hr'],
			conserve: ['script'],
			selector: ''
		};
		var O = $.extend(DEFAULTS, options);

		var D = 0;

		var tabs = function(){
			var t = '\n', i;
			for(i = 0; i < D; i ++){
				t += O.tab;
			}
			return t;
		};

		var msie_style2lc = $.browser.msie ? function(styles){
			styles = styles.split(';');
			for(var i = 0;  i < styles.length; i ++){
				styles[i] = styles[i].split(':');
				styles[i][0] = styles[i][0].toLowerCase();
				styles[i] = styles[i].join(':');
			}
			return styles.join(';');
		} : 0;

		var tag2src = function(dom, empty){
			var tag, attrs, src, i;
			if(dom.nodeType === 1 /* HTMLElement */){
				var tag = dom.nodeName.toLowerCase(), attrs = dom.attributes, attr_name, attr_value, src;
				src = '<'+tag;
				for(i = 0; i < attrs.length; i ++){
					attr_name = attrs[i].nodeName.toLowerCase();
					attr_value = attrs[i].nodeValue;
					if($.browser.msie
						&& (attr_name.indexOf('jquery') >= 0
						|| attr_value === ''
						|| attr_value === null
						|| attr_value === false
						|| attr_value === 'inherit'
						|| (attr_name === 'tabindex' && attr_value === 0))){
						continue;
					}
					src += ' ' + attr_name + '="' + attr_value + '"';
				}
				if($.browser.msie && (attr_value = $(dom).attr('style'))){
					src += ' style="' + msie_style2lc(attr_value) + '"';
				}

				if(empty){
					src += O.xhtml && $.inArray(tag, O.singles) >= 0 ? ' />' : '></' + tag + '>';
				}else{
					src = [src + '>', '</' + tag + '>'];
				}
			}else{ /* TextNode */
				src = dom.nodeValue;
			}
			return src;
		};

		var insert_tabs = function(source){
			var indented = '', conserved = [], wrap = $('<div></div>'), i, tag, re;

			// 1:

			for(i = 0; i < O.conserve.length; i ++){
				tag = O.conserve[i],
				re = new RegExp(
					'(<'+tag+'(\\s+[\\w-:]+=[\"\']?.*?[\"\']?)*?\\s*>([\\s\\S]*?)<\\/'+tag+'>'+
					'|'+
					'<'+tag+'(\\s+[\\w-:]+=[\"\']?.*?[\"\']?)*?\\s*\\/?>)'+
					(tag === 'script' ? '(\\s*?<noscript>(.*?)<\\/noscript>)?':''), 'ig');

				source = source.replace(re, function(a0, a1, a2, a3, a4, a5, a6){
					conserved.push(tag == 'script' && typeof a5 === 'string' && a5.length ? a1 + a5 : a1);
					return '<div class="jquery-indent-conserved-'+(conserved.length-1)+'"></div>';
				});
			}

			// 2:

			source = source
				.replace(/<!--[\S\s]*?-->/g, '')
				.replace(/>\n?[\t ]+/g, '>')
				.replace(/[\t ]+</g, '<')
				.replace(/[\t ]*\n[\t ]*/g, '');

			// 3:

			wrap.html(source);
			if(O.selector){
				wrap = $(O.selector, wrap);
				if(wrap.length === 0 || !wrap.html().length){
					return '';
				}else if(wrap.length > 1){
					wrap = wrap.eq(0);
				}
				D = 1;
			}

			// 4:

			wrap.contents().each(function(){
				var children = $(this).contents(), src;

				if(children.length === 0){
					indented += tabs() + tag2src(this, true);
				}else if(children.length === 1 && children.get(0).nodeType === 3){
					src = tag2src(this, false);
					indented += tabs() + src[0] + children.get(0).nodeValue + src[1];
				}else{
					src = tag2src(this, false);
					indented += tabs() + src[0];
					D ++;
					children.each(arguments.callee);
					D --;
					indented += tabs() + src[1];
				}

			});

			// 5:

			if(O.selector){
				tag = tag2src(wrap.get(0));
				indented = tag[0] + indented + '\n' + tag[1];
			}

			indented = indented.replace(/\t*<div class="jquery-indent-conserved-(\d+)"><\/div>/g, function(a0, a1){
				return conserved[a1];
			}).replace(/^\n/, ''); // ^\n

			D = 0;

			return indented;
		};

		this.each(function() {
			var source=$(this).val();
			if(typeof source === 'string' && source.length){
				$(this).val(insert_tabs(source));
			}
		});
	}
})(jQuery);

スクリプト中の「$.browser」はjquery1.3以降、サポート外なので、plugin for the dependency problemから、jquery.depend.jsプラグインを取得して利用するようにします。

で、tinymceの設定は以下のような感じにします。

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script type="text/javascript" src="js/tiny_mce/tiny_mce.js"></script>
<script type="text/javascript" src="js/jquery.depend.min.js" charset="UTF-8"></script>
<script type="text/javascript" src="js/jquery.indent.js"></script>
<script type="text/javascript">
<!--
tinyMCE.init({
	mode : "exact",
	elements : "htmlbody",
	theme : "advanced",
	remove_linebreaks : false,
	force_br_newlines : true,
	force_p_newlines : false,
	forced_root_block : '',
});

jQuery(document).ready(function($){
	$('#toggleEditor').click(function(){
	    if (tinyMCE.getInstanceById('htmlbody') == null){
	         tinyMCE.execCommand('mceAddControl', false, 'htmlbody');
	    }else{
	         tinyMCE.execCommand('mceRemoveControl', false, 'htmlbody');
	         $('#htmlbody').indent({tab:'  '}); //ソース整形。インデントはスペース2つ。
	    }
	});
});
// -->
</script>

<a href="javascript:void(0);" id="toggleEditor">[ HTML直接編集/ブログ形式 ]</a>
<textarea name="htmlbody" id="htmlbody"></textarea>

結果、

<strong>hoge</strong>
fugo
<br />
fugo2
<br />

のように表示されるようになりました。
まぁ、よしとしましょう。