Commits

Jean-Christian Denis committed a852ebc Merge

dc merge

  • Participants
  • Parent commits a19b0fa, f4ac115

Comments (0)

Files changed (30)

-cache
+cache
 inc/config.php
 \.htaccess
-public/filters
-themes/ductile/_work
-themes/ductile-work
-themes/ductile/default-templates
-themes/ductileMag
-themes/ductileFocus
+public
 0000000000000000000000000000000000000000 2.4.2
 0000000000000000000000000000000000000000 2.4.2
 6b3942c0cf7eee71ebd906ec0827c5962823dedf 2.4.2
+36578fa779919f730e714e10041b1f896e56a8f4 2.4.3
+7f3781b97754446cd2bd5dab96fec504e2b35ca5 2.4.4
+7f3781b97754446cd2bd5dab96fec504e2b35ca5 2.4.4
+6fce226121140735a51a8f447c80f1dacd2974ea 2.4.4
+6fce226121140735a51a8f447c80f1dacd2974ea 2.4.4
+514d5121a12586b7ff320924d44028b677a01020 2.4.4
 Dotclear 2.5.0 - 201?-??-13
 ===========================================================
+* Every types of entries may be used to inserted an entry link in current edited post
 * Add (none) option to image insertion title pattern
 * Smileys are not more converted in image in pre,code,kbd,script and math contents
 * Notes' title can be now enclosed in h4 (default), h3 or p HTML tag

admin/categories.php

 {
 	try
 	{
+		# Check if category to delete exists
 		$c = $core->blog->getCategory((integer) $_POST['del_cat']);
 		if ($c->isEmpty()) {
 			throw new Exception(__('This category does not exist.'));
 		}
 		unset($c);
+		
+		# Check if category where to move posts exists
+		$mov_cat = (integer) $_POST['mov_cat'];
+		$mov_cat = $mov_cat ? $mov_cat : null;
+		if ($mov_cat !== null) {
+			$c = $core->blog->getCategory((integer) $_POST['del_cat']);
+			if ($c->isEmpty()) {
+				throw new Exception(__('This category does not exist.'));
+			}
+			if ($mov_cat == $_POST['del_cat']) {
+				throw new Exception(__('The entries cannot be moved to the category you choose to delete.'));
+			}
+			unset($c);
+		}
+		
+		# Move posts
+		$core->blog->updPostsCategory($_POST['del_cat'],$mov_cat);
+		
+		# Delete category
 		$core->blog->delCategory($_POST['del_cat']);
+		
 		http::redirect('categories.php?del=1');
 	}
 	catch (Exception $e)
 
 if (!$rs->isEmpty())
 {
-	$deletable = array();
+	$cats = array();
+	$dest = array(' ' => '');
 	$l = $rs->level;
 	$full_name = array($rs->cat_title);
 	while ($rs->fetch())
 			array_pop($full_name);
 		}
 		$full_name[] = html::escapeHTML($rs->cat_title);
-		if ($rs->nb_post == 0) {
-			$deletable[implode(' / ',$full_name)] = $rs->cat_id;
-		}
+		
+		$cats[implode(' / ',$full_name)] = $rs->cat_id;
+		$dest[implode(' / ',$full_name)] = $rs->cat_id;
+		
 		$l = $rs->level;
 	}
 	
-	if (count($deletable) > 0)
-	{
-		echo
-		'<form action="categories.php" method="post" id="delete-category">'.
-		'<fieldset><legend>'.__('Remove a category').'</legend>'.
-		'<p><label for="del_cat">'.__('Choose a category to remove:').' '.
-		form::combo('del_cat',$deletable).'</label></p> '.
-		'<p><input type="submit" value="'.__('Delete').'" class="delete" /></p>'.
-		$core->formNonce().
-		'</fieldset>'.
-		'</form>';
-	}
+	echo
+	'<form action="categories.php" method="post" id="delete-category">'.
+	'<fieldset><legend>'.__('Remove a category').'</legend>'.
+	'<p><label for="del_cat">'.__('Choose a category to remove:').' '.
+	form::combo('del_cat',$cats).'</label></p> '.
+	'<p><label for="mov_cat">'.__('And choose the category which will receive its entries:').' '.
+	form::combo('mov_cat',$dest).'</label></p> '.
+	'<p><input type="submit" value="'.__('Delete').'" class="delete" /></p>'.
+	$core->formNonce().
+	'</fieldset>'.
+	'</form>';
 	
 	echo
 	'<form action="categories.php" method="post" id="reset-order">'.
 			$i = 1;
 			foreach ($feed->items as $item)
 			{
-				$dt = isset($item->link) ? '<a href="'.$item->link.'"'.$v.'" title="'.$item->title.' '.__('(external link)').'">'.
+				$dt = isset($item->link) ? '<a href="'.$item->link.'" title="'.$item->title.' '.__('(external link)').'">'.
 					$item->title.'</a>' : $item->title;
 			
 				if ($i < 3) {
 					$latest_news .=
 					'<dt>'.$dt.'</dt>'.
-					'<dd><p><strong>'.dt::dt2str('%d %B %Y',$item->pubdate,'Europe/Paris').'</strong>: '.
+					'<dd><p><strong>'.dt::dt2str(__('%d %B %Y:'),$item->pubdate,'Europe/Paris').'</strong> '.
 					'<em>'.text::cutString(html::clean($item->content),120).'...</em></p></dd>';
 				} else {
 					$latest_news .=
 					'<dt>'.$dt.'</dt>'.
-					'<dd>'.dt::dt2str('%d %B %Y',$item->pubdate,'Europe/Paris').'</dd>';
+					'<dd>'.dt::dt2str(__('%d %B %Y:'),$item->pubdate,'Europe/Paris').'</dd>';
 				}
 				$i++;
 				if ($i > 3) { break; }

admin/js/_posts_list.js

 };
 
 $(function() {
+	// Entry type switcher
+	$('#type').change(function() {
+		this.form.submit();
+	});
+
 	$('#form-entries tr.line').each(function() {
 		dotclear.postExpander(this);
 	});

admin/js/common.js

 
 	var sizeBox = function() {
 		This.css('height','auto');
-		if ($('body').height() > This.height()) {
-			This.css('height',$('body').height() + 'px');
+		if ($('#main').height() > This.height()) {
+			This.css('height',$('#main').height() + 'px');
 		}
 	};
 

admin/media_item.php

 		
 		echo
 		'<h3>'.__('Video size').'</h3>'.
-		'<p><label for="video_w" class="classic">'.__('Width:').' '.
+		'<p><label for="video_w" class="classic">'.__('Width:').'</label> '.
 		form::field('video_w',3,4,400).'  '.
-		'<label for="video_h" class="classic">'.__('Height:').' '.
+		'<label for="video_h" class="classic">'.__('Height:').'</label> '.
 		form::field('video_h',3,4,300).
 		'</p>';
 		

admin/popup_posts.php

 $page = !empty($_GET['page']) ? (integer) $_GET['page'] : 1;
 $nb_per_page =  10;
 
+$post_types = $core->getPostTypes();
+foreach ($post_types as $k => $v) {
+ 	$type_combo[__($k)] = (string) $k;
+}
+$type = !empty($_POST['type']) ? $_POST['type'] : null;
+if (!$type && $q) {
+	// Cope with search form
+	$type = !empty($_GET['type']) ? $_GET['type'] : null;
+}
+if (!in_array($type, $type_combo)) {
+	$type = null;
+}
+
 $params = array();
 $params['limit'] = array((($page-1)*$nb_per_page),$nb_per_page);
 $params['no_content'] = true;
 	$params['search'] = $q;
 }
 
+if ($type) {
+	$params['post_type'] = $type;
+}
+
 dcPage::openPopup(__('Add a link to an entry'),
 	dcPage::jsLoad('js/_posts_list.js').
 	dcPage::jsLoad('js/jsToolBar/popup_posts.js'));
 
 echo '<h2 class="page-title">'.__('Add a link to an entry').'</h2>';
 
+echo '<form action="popup_posts.php" method="post">'.
+	'<p><label for"type" class="classic">'.__('Entry type:').' '.form::combo('type',$type_combo,$type).'</label></p>'.
+	$core->formNonce().
+	'<noscript><div><input type="submit" value="'.__('Ok').'" /></div></noscript>'.
+	'</form>';
+
 echo '<form action="popup_posts.php" method="get">'.
-'<p><label for="q" class="classic">'.__('Search entry:').' '.form::field('q',30,255,html::escapeHTML($q)).'</label> '.
-' <input type="submit" value="'.__('Search').'" /></p>'.
-'</form>';
+	'<p><label for="q" class="classic">'.__('Search entry:').' '.form::field('q',30,255,html::escapeHTML($q)).'</label> '.
+	' <input type="submit" value="'.__('Search').'" /></p>'.
+	form::hidden('type',html::escapeHTML($type)).
+	'</form>';
 
 try {
 	$posts = $core->blog->getPosts($params);

admin/style/default.css

 	background: #575859;
 	height: 2em;
 }
+#info-box1 p {
+	margin: 0;
+	display: inline;
+}
 #info-box1 select {
 	width: 15em;
 }

admin/style/install.css

 	background: -moz-linear-gradient(top,  #2373A8,  #2C8FD1);
 	filter:  progid:DXImageTransform.Microsoft.gradient(startColorstr='#2373A8', endColorstr='#2C8FD1');
 	border: 1px solid #2C8FD1;
-}
+}

inc/admin/lib.dc.page.php

 		if ($core->auth->blog_count == 1 || $core->auth->blog_count > 20)
 		{
 			$blog_box =
-			__('Blog:').' <strong title="'.html::escapeHTML($core->blog->url).'">'.
+			'<p>'.__('Blog:').' <strong title="'.html::escapeHTML($core->blog->url).'">'.
 			html::escapeHTML($core->blog->name).'</strong>';
 			
 			if ($core->auth->blog_count > 20) {
 				$blog_box .= ' - <a href="blogs.php">'.__('Change blog').'</a>';
 			}
+			$blog_box .= '</p>';
 		}
 		else
 		{
 				$blogs[html::escapeHTML($rs_blogs->blog_name.' - '.$rs_blogs->blog_url)] = $rs_blogs->blog_id;
 			}
 			$blog_box =
-			'<label for="switchblog" class="classic">'.
+			'<p><label for="switchblog" class="classic">'.
 			__('Blogs:').' '.
 			$core->formNonce().
 			form::combo('switchblog',$blogs,$core->blog->id).
-			'</label>'.
-			'<noscript><div><input type="submit" value="'.__('ok').'" /></div></noscript>';
+			'</label></p>'.
+			'<noscript><p><input type="submit" value="'.__('ok').'" /></p></noscript>';
 		}
 		
 		$safe_mode = isset($_SESSION['sess_safe_mode']) && $_SESSION['sess_safe_mode'];
 		'<div id="info-box1">'.
 		'<form action="index.php" method="post">'.
 		$blog_box.
-		'<a href="'.$core->blog->url.'" onclick="window.open(this.href);return false;" title="'.__('Go to site').' ('.__('new window').')'.'">'.__('Go to site').' <img src="images/outgoing.png" alt="" /></a>'.
-		'</form>'.
+		'<p><a href="'.$core->blog->url.'" onclick="window.open(this.href);return false;" title="'.__('Go to site').' ('.__('new window').')'.'">'.__('Go to site').' <img src="images/outgoing.png" alt="" /></a>'.
+		'</p></form>'.
 		'</div>'.
 		'<div id="info-box2">'.
 		'<a'.(preg_match('/index.php$/',$_SERVER['REQUEST_URI']) ? ' class="active"' : '').' href="index.php">'.__('My dashboard').'</a>'.

inc/core/class.dc.blog.php

 	}
 	
 	/**
+	Updates posts category. <var>$new_cat_id</var> can be null.
+	
+	@param	old_cat_id	<b>integer</b>		Old category ID
+	@param	new_cat_id	<b>integer</b>		New category ID
+	*/
+	public function updPostsCategory($old_cat_id,$new_cat_id)
+	{
+		if (!$this->core->auth->check('contentadmin,categories',$this->id)) {
+			throw new Exception(__('You are not allowed to change entries category'));
+		}
+		
+		$old_cat_id = (integer) $old_cat_id;
+		$new_cat_id = (integer) $new_cat_id;
+		
+		$cur = $this->con->openCursor($this->prefix.'post');
+		
+		$cur->cat_id = ($new_cat_id ? $new_cat_id : null);
+		$cur->post_upddt = date('Y-m-d H:i:s');
+		
+		$cur->update(
+			'WHERE cat_id = '.$old_cat_id.' '.
+			"AND blog_id = '".$this->con->escape($this->id)."' "
+		);
+		$this->triggerBlog();
+	}
+	
+	/**
 	Deletes a post.
 	
 	@param	id		<b>integer</b>		Post ID
 		}
 		
 		if (isset($params['comment_ip'])) {
-			$strReq .= "AND comment_ip = '".$this->con->escape($params['comment_ip'])."' ";
+			$comment_ip = $this->con->escape(str_replace('*','%',$params['comment_ip']));
+			$strReq .= "AND comment_ip LIKE '".$comment_ip."' ";
 		}
 		
 		if (isset($params['q_author'])) {

inc/prepend.php

File contents unchanged.

locales/en/help/blog_pref.html

       <li><strong>online</strong>: blog open to visitors</li>
       <li><strong>offline</strong>: blog closed to visitors
       but open to writers.</li>
-      <li><strong>removed</strong>: blog closed to both visitors and writers.</li>
+      <li><strong>removed</strong>: blog closed to both visitors and writers, opened only for super-administrators.</li>
     </ul>
   </dd>
   

locales/fr/help/core_blog_pref.html

       <li><strong>en ligne</strong>&nbsp;: blog accessible aux visiteurs</li>
       <li><strong>hors ligne</strong>&nbsp;: blog inaccessible aux visiteurs
       mais les rédacteurs peuvent se connecter</li>
-      <li><strong>supprimé</strong>&nbsp;: blog inaccessible aux visiteurs
-      comme aux rédacteurs</li>
+      <li><strong>retiré</strong>&nbsp;: blog inaccessible aux visiteurs
+      comme aux rédacteurs, seuls les super-administrateurs peuvent y accéder</li>
     </ul>
   </dd>
   

locales/fr/help/themeEditor.html

 <a href="http://doc.dotclear.net/2.0/resources/templates">liste des marqueurs
 de template</a>.</p>
 
+<p>Notez que vous pouvez activer la coloration syntaxique dans vos préférences (onglet « Mes options »).</p>
+
 </body>
 </html>

locales/fr/main.po

 msgid "I want to log in in safe mode"
 msgstr "Me connecter en mode sans échec"
 
+msgid "%d %B %Y:"
+msgstr "%d %B %Y :"
+
 msgid "New blog"
 msgstr "Nouveau blog"
 
 msgid "Choose a category to remove:"
 msgstr "Choisissez une catégorie à supprimer :"
 
+msgid "And choose the category which will receive its entries:"
+msgstr "Et sélectionnez la catégorie qui recevra ses (éventuels) billets :"
+
+msgid "The entries cannot be moved to the category you choose to delete."
+msgstr "Vous ne pouvez déplacer les billets vers la catégorie que vous voulez supprimer."
+
 msgid "Delete"
 msgstr "Supprimer"
 
 msgstr "Langue du lien :"
 
 msgid "Add a link to an entry"
-msgstr "Ajouter un lien vers un billet"
+msgstr "Ajouter un lien vers une entrée"
 
 msgid "Search entry:"
-msgstr "Rechercher un billet :"
+msgstr "Rechercher une entrée :"
+
+msgid "Entry type:"
+msgstr "Type d'entrées :"
+
+msgid "post"
+msgstr "billet"
 
 msgid "Search"
 msgstr "Recherche"
 
 #, fuzzy
 msgid "Hide My favorites menu"
-msgstr "Mes favoris"
+msgstr "Cacher le menu « Mes favoris »"
 
 msgid "Iconset:"
 msgstr "Jeu d'icônes :"
 msgstr "Sélecteur de média"
 
 msgid "Link to an entry"
-msgstr "Lien vers un billet"
+msgstr "Lien vers une entrée"
 
 msgid "Activate enhanced uploader"
 msgstr "Activer l'interface avancée"

locales/fr/plugins.po

 msgid "Additional CSS"
 msgstr "Style additionnel"
 
+msgid "Any additional CSS styles (must be written using the CSS syntax):"
+msgstr "Styles CSS additionnels (doivent être écrits selon la syntaxe CSS) :"
+
 msgid "Configuration import / export"
 msgstr "Configuration de l'import/export"
 
 msgid "hidden"
 msgstr "masquée"
 
-msgid "Hidden"
-msgstr "Masquée"
+msgid "Hide"
+msgstr "Masquer"
 
 msgid "If checked this page will be active but not listed in widget Pages."
 msgstr "Si coché cette page sera active mais non listée dans le widget Pages."
 
+msgid "page"
+msgstr "page"
+
 msgid "Pings"
 msgstr "Signalements"
 

locales/ro/main.po

File contents unchanged.

locales/ro/plugins.po

File contents unchanged.

plugins/blogroll/index.php

 echo
 '<div class="multi-part clear" id="add-link" title="'.__('Add a link').'">'.
 '<form action="plugin.php" method="post" id="add-link-form">'.
-'<fieldset class="two-cols"><legend>'.__('Add a new link').'</legend>'.
+'<fieldset><legend>'.__('Add a new link').'</legend>'.
 '<p class="col"><label for="link_title" class="required"><abbr title="'.__('Required field').'">*</abbr> '.__('Title:').' '.
 form::field('link_title',30,255,$link_title).
 '</label></p>'.

plugins/blowupConfig/index.php

 
 echo
 '<fieldset><legend>'.__('Additional CSS').'</legend>'.
-'<p>'.form::textarea('extra_css',72,5,html::escapeHTML($blowup_user['extra_css']),'maximal','',false,'title="'.__('Additional CSS').'"').'</p>'.
+'<p><label for="extra_css">'.__('Any additional CSS styles (must be written using the CSS syntax):').' '.
+form::textarea('extra_css',72,5,html::escapeHTML($blowup_user['extra_css']),'maximal','',false,'title="'.__('Additional CSS').'"').
+'</label></p>'.
 '</fieldset>';
 
 

plugins/pages/page.php

 foreach ($core->blog->getAllPostStatus() as $k => $v) {
 	$status_combo[$v] = (string) $k;
 }
+$img_status_pattern = '<img class="img_select_option" alt="%1$s" title="%1$s" src="images/%2$s" />';
 
 # Formaters combo
 foreach ($core->getFormaters() as $v) {
 echo '<h2>'.html::escapeHTML($core->blog->name).
 ' &rsaquo; <a href="'.$p_url.'">'.__('Pages').'</a> &rsaquo; <span class="page-title">'.$page_title; 
 	if ($post_id) {
-		echo ' &ldquo;'.$post_title.'&rdquo;';
+		switch ($post_status) {
+			case 1:
+				$img_status = sprintf($img_status_pattern,__('published'),'check-on.png');
+				break;
+			case 0:
+				$img_status = sprintf($img_status_pattern,__('unpublished'),'check-off.png');
+				break;
+			case -1:
+				$img_status = sprintf($img_status_pattern,__('scheduled'),'scheduled.png');
+				break;
+			case -2:
+				$img_status = sprintf($img_status_pattern,__('pending'),'check-wrn.png');
+				break;
+			default:
+				$img_status = '';
+		}
+		echo ' &ldquo;'.$post_title.'&rdquo;'.' '.$img_status;
 	}
 echo	'</span></h2>';
 
 		'<p class="form-note warn">'.__('Warning: Trackbacks are not accepted on this blog.').'</p>').
 	
 	'<p><label for="post_selected" class="classic">'.form::checkbox('post_selected',1,$post_selected).' '.
-	__('Hidden').'</label></p>'.
+	__('Hide').'</label></p>'.
 	'<p class="form-note">'.
 	__('If checked this page will be active but not listed in widget Pages.').
 	'</p>'.

plugins/simpleMenu/index.php

 			// Selection du type d'item
 			echo '<form id="additem" action="'.$p_url.'&add=2" method="post">';
 			echo '<fieldset><legend>'.__('Select type').'</legend>';
-			echo '<p class="field"><label for"item_type" class="classic">'.__('Type of item menu:').'</label>'.form::combo('item_type',$items_combo,'').'</p>';
+			echo '<p class="field"><label for="item_type" class="classic">'.__('Type of item menu:').'</label>'.form::combo('item_type',$items_combo,'').'</p>';
 			echo '<p>'.$core->formNonce().'<input type="submit" name="appendaction" value="'.__('Continue...').'" />'.'</p>';
 			echo '</fieldset>';
 			echo '</form>';
 				echo '<fieldset><legend>'.$item_type_label.'</legend>';
 				switch ($item_type) {
 					case 'lang':
-						echo '<p class="field"><label for"item_select" class="classic">'.__('Select language:').'</label>'.
+						echo '<p class="field"><label for="item_select" class="classic">'.__('Select language:').'</label>'.
 							form::combo('item_select',$langs_combo,'');
 						break;
 					case 'category':
-						echo '<p class="field"><label for"item_select" class="classic">'.__('Select category:').'</label>'.
+						echo '<p class="field"><label for="item_select" class="classic">'.__('Select category:').'</label>'.
 							form::combo('item_select',$categories_combo,'');
 						break;
 					case 'archive':
-						echo '<p class="field"><label for"item_select" class="classic">'.__('Select month (if necessary):').'</label>'.
+						echo '<p class="field"><label for="item_select" class="classic">'.__('Select month (if necessary):').'</label>'.
 							form::combo('item_select',$months_combo,'');
 						break;
 					case 'pages':
-						echo '<p class="field"><label for"item_select" class="classic">'.__('Select page:').'</label>'.
+						echo '<p class="field"><label for="item_select" class="classic">'.__('Select page:').'</label>'.
 							form::combo('item_select',$pages_combo,'');
 						break;
 					case 'tags':
-						echo '<p class="field"><label for"item_select" class="classic">'.__('Select tag (if necessary):').'</label>'.
+						echo '<p class="field"><label for="item_select" class="classic">'.__('Select tag (if necessary):').'</label>'.
 							form::combo('item_select',$tags_combo,'');
 						break;
 					default:
 			// Libellé et description
 			echo '<form id="additem" action="'.$p_url.'&add=4" method="post">';
 			echo '<fieldset><legend>'.$item_type_label.($item_select_label != '' ? ' ('.$item_select_label.')' : '').'</legend>';
-			echo '<p class="field"><label for"item_label" class="classic required"><abbr title="'.__('Required field').'">*</abbr> '.
+			echo '<p class="field"><label for="item_label" class="classic required"><abbr title="'.__('Required field').'">*</abbr> '.
 				__('Label of item menu:').'</label>'.form::field('item_label',20,255,$item_label).'</p>';
-			echo '<p class="field"><label for"item_descr" class="classic">'.
+			echo '<p class="field"><label for="item_descr" class="classic">'.
 				__('Description of item menu:').'</label>'.form::field('item_descr',30,255,$item_descr).'</p>';
-			echo '<p class="field"><label for"item_url" class="classic required"><abbr title="'.__('Required field').'">*</abbr> '.
+			echo '<p class="field"><label for="item_url" class="classic required"><abbr title="'.__('Required field').'">*</abbr> '.
 				__('URL of item menu:').'</label>'.form::field('item_url',40,255,$item_url).'</p>';
 			echo form::hidden('item_type',$item_type).form::hidden('item_select',$item_select);
 			echo '<p>'.$core->formNonce().'<input type="submit" name="appendaction" value="'.__('Add this item').'" /></p>';

plugins/themeEditor/_admin.php

 		echo
 		'<fieldset><legend>'.__('Theme Editor').'</legend>'.
 		
-		'<p><label for"colorsyntax" class="classic">'.
+		'<p><label for="colorsyntax" class="classic">'.
 		form::checkbox('colorsyntax',1,$core->auth->user_prefs->interface->colorsyntax).' '.
 		__('Syntax color').'</label></p>'.
 

plugins/themeEditor/codemirror/multiplex.js

+CodeMirror.multiplexingMode = function(outer /*, others */) {
+  // Others should be {open, close, mode [, delimStyle]} objects
+  var others = Array.prototype.slice.call(arguments, 1);
+  var n_others = others.length;
+
+  function indexOf(string, pattern, from) {
+    if (typeof pattern == "string") return string.indexOf(pattern, from);
+    var m = pattern.exec(from ? string.slice(from) : string);
+    return m ? m.index + from : -1;
+  }
+
+  return {
+    startState: function() {
+      return {
+        outer: CodeMirror.startState(outer),
+        innerActive: null,
+        inner: null
+      };
+    },
+
+    copyState: function(state) {
+      return {
+        outer: CodeMirror.copyState(outer, state.outer),
+        innerActive: state.innerActive,
+        inner: state.innerActive && CodeMirror.copyState(state.innerActive.mode, state.inner)
+      };
+    },
+
+    token: function(stream, state) {
+      if (!state.innerActive) {
+        var cutOff = Infinity, oldContent = stream.string;
+        for (var i = 0; i < n_others; ++i) {
+          var other = others[i];
+          var found = indexOf(oldContent, other.open, stream.pos);
+          if (found == stream.pos) {
+            stream.match(other.open);
+            state.innerActive = other;
+            state.inner = CodeMirror.startState(other.mode, outer.indent ? outer.indent(state.outer, "") : 0);
+            return other.delimStyle;
+          } else if (found != -1 && found < cutOff) {
+            cutOff = found;
+          }
+        }
+        if (cutOff != Infinity) stream.string = oldContent.slice(0, cutOff);
+        var outerToken = outer.token(stream, state.outer);
+        if (cutOff != Infinity) stream.string = oldContent;
+        return outerToken;
+      } else {
+        var curInner = state.innerActive, oldContent = stream.string;
+        var found = indexOf(oldContent, curInner.close, stream.pos);
+        if (found == stream.pos) {
+          stream.match(curInner.close);
+          state.innerActive = state.inner = null;
+          return curInner.delimStyle;
+        }
+        if (found > -1) stream.string = oldContent.slice(0, found);
+        var innerToken = curInner.mode.token(stream, state.inner);
+        if (found > -1) stream.string = oldContent;
+        var cur = stream.current(), found = cur.indexOf(curInner.close);
+        if (found > -1) stream.backUp(cur.length - found);
+        return innerToken;
+      }
+    },
+    
+    indent: function(state, textAfter) {
+      var mode = state.innerActive ? state.innerActive.mode : outer;
+      if (!mode.indent) return CodeMirror.Pass;
+      return mode.indent(state.innerActive ? state.inner : state.outer, textAfter);
+    },
+
+    electricChars: outer.electricChars,
+
+    innerMode: function(state) {
+      return state.inner ? {state: state.inner, mode: state.innerActive.mode} : {state: state.outer, mode: outer};
+    }
+  };
+};

plugins/themeEditor/help.html

 
 <p>To help modify your templates, you may consult the <a href="http://doc.dotclear.net/2.0/resources/templates">template tag list</a>.</p>
 
+<p>Note: You can activate syntax coloration in your preferences (“My options” tab).</p>
+
 </body>
 </html>

plugins/themeEditor/index.php

   <link rel="stylesheet" type="text/css" href="index.php?pf=themeEditor/codemirror/codemirror.css" />
   <link rel="stylesheet" type="text/css" href="index.php?pf=themeEditor/codemirror.css" />
   <script type="text/JavaScript" src="index.php?pf=themeEditor/codemirror/codemirror.js"></script>
+  <script type="text/JavaScript" src="index.php?pf=themeEditor/codemirror/multiplex.js"></script>
   <script type="text/JavaScript" src="index.php?pf=themeEditor/codemirror/xml.js"></script>
   <script type="text/JavaScript" src="index.php?pf=themeEditor/codemirror/javascript.js"></script>
   <script type="text/JavaScript" src="index.php?pf=themeEditor/codemirror/css.js"></script>
 		$editorMode = (!empty($_REQUEST['css']) ? "css" : (!empty($_REQUEST['js']) ? "javascript" : "text/html"));
 		echo 
 		'<script>
+			window.CodeMirror.defineMode("dotclear", function(config) {
+				return CodeMirror.multiplexingMode(
+					CodeMirror.getMode(config, "'.$editorMode.'"),
+					{open: "{{tpl:", close: "}}",
+					 mode: CodeMirror.getMode(config, "text/plain"),
+					 delimStyle: "delimit"},
+					{open: "<tpl:", close: ">",
+					 mode: CodeMirror.getMode(config, "text/plain"),
+					 delimStyle: "delimit"},
+					{open: "</tpl:", close: ">",
+					 mode: CodeMirror.getMode(config, "text/plain"),
+					 delimStyle: "delimit"}
+					);
+			});
 	    	var editor = CodeMirror.fromTextArea(document.getElementById("file_content"), {
-	    		mode: "'.$editorMode.'",
+	    		mode: "dotclear",
 	       		tabMode: "indent",
 	       		lineWrapping: "true",
 	       		lineNumbers: "true",

themes/ductile/_config.php

 {
 	echo '<p>'.sprintf(__('To configure the top menu go to the <a href="%s">Simple Menu administration page</a>.'),'plugin.php?p=simpleMenu').'</p>';
 }
-echo '<p class="field"><label for"logo_src">'.__('Logo URL:').' '.
+echo '<p class="field"><label for="logo_src">'.__('Logo URL:').' '.
 	form::field('logo_src',40,255,$ductile_user['logo_src']).'</label>'.'</p>';
 echo '</fieldset>';