New tag interface, tag auto-suggest while typing. Rough - suggest and parts of JS should probably be refactored. Hat tip: jhodgdon.

git-svn-id: http://svn.automattic.com/wordpress/trunk@6542 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
matt 2008-01-03 01:34:11 +00:00
parent 43c44fba24
commit ae18e22598
10 changed files with 500 additions and 25 deletions

View File

@ -7,6 +7,19 @@ define('DOING_AJAX', true);
if ( !is_user_logged_in() ) if ( !is_user_logged_in() )
die('-1'); die('-1');
if ( 'ajax-tag-search' == $_GET['action'] ) {
if ( !current_user_can( 'manage_categories' ) )
die('-1');
$s = $_GET['q']; // is this slashed already?
if ( strstr( $s, ',' ) )
die; // it's a multiple tag insert, we won't find anything
$results = $wpdb->get_col( "SELECT name FROM $wpdb->terms WHERE name LIKE ('%$s%')" );
echo join( $results, "\n" );
die;
}
function get_out_now() { exit; } function get_out_now() { exit; }
add_action( 'shutdown', 'get_out_now', -1 ); add_action( 'shutdown', 'get_out_now', -1 );

View File

@ -157,11 +157,6 @@ if ( $authors && count( $authors ) > 1 ) :
<?php echo $form_pingback ?> <?php echo $form_pingback ?>
<?php echo $form_prevstatus ?> <?php echo $form_prevstatus ?>
<fieldset id="tagdiv">
<legend><?php _e('Tags (separate multiple tags with commas: cats, pet food, dogs)'); ?></legend>
<div><input type="text" name="tags_input" class="tags-input" id="tags-input" size="30" tabindex="3" value="<?php echo get_tags_to_edit( $post_ID ); ?>" /></div>
</fieldset>
<p class="submit"> <p class="submit">
<span id="autosave"></span> <span id="autosave"></span>
<?php echo $saveasdraft; ?> <?php echo $saveasdraft; ?>
@ -200,14 +195,29 @@ if (current_user_can('upload_files')) {
<div id="advancedstuff" class="dbx-group" > <div id="advancedstuff" class="dbx-group" >
<div class="dbx-b-ox-wrapper">
<fieldset id="tagdiv" class="dbx-box">
<div class="dbx-h-andle-wrapper">
<h3 class="dbx-handle"><?php _e('Tags (separate multiple tags with commas: cats, pet food, dogs)'); ?></h3>
</div>
<div class="dbx-c-ontent-wrapper">
<div class="dbx-content">
<p id="jaxtag">
<input type="text" name="tags_input" class="tags-input" id="tags-input" size="40" tabindex="3" value="<?php echo get_tags_to_edit( $post_ID ); ?>" />
</p>
<p id="tagchecklist"></p>
</div></div>
</fieldset>
</div>
<div class="dbx-b-ox-wrapper"> <div class="dbx-b-ox-wrapper">
<fieldset id="postexcerpt" class="dbx-box"> <fieldset id="postexcerpt" class="dbx-box">
<div class="dbx-h-andle-wrapper"> <div class="dbx-h-andle-wrapper">
<h3 class="dbx-handle"><?php _e('Optional Excerpt') ?></h3> <h3 class="dbx-handle"><?php _e('Optional Excerpt') ?></h3>
</div> </div>
<div class="dbx-c-ontent-wrapper"> <div class="dbx-c-ontent-wrapper">
<div class="dbx-content"><textarea rows="1" cols="40" name="excerpt" tabindex="6" id="excerpt"><?php echo $post->post_excerpt ?></textarea></div> <div class="dbx-content"><textarea rows="1" cols="40" name="excerpt" tabindex="6" id="excerpt"><?php echo $post->post_excerpt ?></textarea>
</div> </div></div>
</fieldset> </fieldset>
</div> </div>

BIN
wp-admin/images/xit.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

77
wp-admin/js/post.js Normal file
View File

@ -0,0 +1,77 @@
// this file shoudl contain all the scripts used in the post/edit page
function new_tag_remove_tag() {
var id = jQuery( this ).attr( 'id' );
var num = id.substr( 10 );
var current_tags = jQuery( '#tags-input' ).val().split(',');
delete current_tags[num];
var new_tags = [];
jQuery.each( current_tags, function( key, val ) {
if ( val && !val.match(/^\s+$/) && '' != val ) {
new_tags = new_tags.concat( val );
}
});
jQuery( '#tags-input' ).val( new_tags.join( ',' ).replace( /\s*,+\s*/, ',' ).replace( /,+/, ',' ).replace( /,+\s+,+/, ',' ).replace( /,+\s*$/, '' ).replace( /^\s*,+/, '' ) );
tag_update_quickclicks();
return false;
}
function tag_update_quickclicks() {
var current_tags = jQuery( '#tags-input' ).val().split(',');
jQuery( '#tagchecklist' ).empty();
shown = false;
// jQuery.merge( current_tags, current_tags ); // this doesn't work anymore, need something to array_unique
jQuery.each( current_tags, function( key, val ) {
val = val.replace( /^\s+/, '' ).replace( /\s+$/, '' ); // trim
if ( !val.match(/^\s+$/) && '' != val ) {
txt = '<span><a id="tag-check-' + key + '" class="ntdelbutton">X</a>&nbsp;' + val + '</span> ';
jQuery( '#tagchecklist' ).append( txt );
jQuery( '#tag-check-' + key ).click( new_tag_remove_tag );
shown = true;
}
});
if ( shown )
jQuery( '#tagchecklist' ).prepend( '<strong>Tags used on this post:</strong><br />' );
}
function tag_flush_to_text() {
var newtags = jQuery('#tags-input').val() + ',' + jQuery('#newtag').val();
// massage
newtags = newtags.replace( /\s+,+\s*/g, ',' ).replace( /,+/g, ',' ).replace( /,+\s+,+/g, ',' ).replace( /,+\s*$/g, '' ).replace( /^\s*,+/g, '' );
jQuery('#tags-input').val( newtags );
tag_update_quickclicks();
jQuery('#newtag').val('');
jQuery('#newtag').blur();
return false;
}
function tag_press_key( e ) {
if ( 13 == e.keyCode ) {
tag_flush_to_text();
return false;
}
}
addLoadEvent( function() {
jQuery('#tags-input').hide();
tag_update_quickclicks();
// add the quickadd form
jQuery('#jaxtag').prepend('<span id="ajaxtag"><input type="text" name="newtag" id="newtag" size="16" autocomplete="off" value="Add new tag" /><input type="button" class="button" id="tagadd" value="' + catL10n.add + '"/><input type="hidden"/><input type="hidden"/><span class="howto">Separate tags with commas</span></span>');
jQuery('#tagadd').click( tag_flush_to_text );
// jQuery('#newtag').keydown( tag_press_key );
jQuery('#newtag').focus(function() {
if ( this.value == 'Add new tag' ) {
this.value = '';
this.style.color = '#333';
}
});
jQuery('#newtag').blur(function() {
if ( this.value == '' ) {
this.value = 'Add new tag';
this.style.color = '#999'
}
});
// auto-suggest stuff
jQuery('#newtag').suggest( 'admin-ajax.php?action=ajax-tag-search', { onSelect: tag_flush_to_text } );
});

View File

@ -6,6 +6,8 @@ $editing = true;
wp_enqueue_script('prototype'); wp_enqueue_script('prototype');
wp_enqueue_script('jquery'); wp_enqueue_script('jquery');
wp_enqueue_script('autosave'); wp_enqueue_script('autosave');
wp_enqueue_script('post');
require_once ('./admin-header.php'); require_once ('./admin-header.php');
if ( ! current_user_can('edit_posts') ) { ?> if ( ! current_user_can('edit_posts') ) { ?>

View File

@ -58,10 +58,13 @@ case 'edit':
exit(); exit();
} }
if($post->post_status == 'draft') { wp_enqueue_script('post');
if( 'draft' == $post->post_status ) {
wp_enqueue_script('prototype'); wp_enqueue_script('prototype');
wp_enqueue_script('autosave'); wp_enqueue_script('autosave');
} }
require_once('admin-header.php'); require_once('admin-header.php');
if ( !current_user_can('edit_post', $post_ID) ) if ( !current_user_can('edit_post', $post_ID) )

View File

@ -90,7 +90,7 @@ a:hover {
body { body {
background: #fff; background: #fff;
color: #000; color: #333;
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
@ -137,11 +137,6 @@ img {
border: 0; border: 0;
} }
input:focus, textarea:focus, label:focus {
background: #fff;
border: 1px solid #686868;
}
.form-invalid { .form-invalid {
background-color: #FF9999 !important; background-color: #FF9999 !important;
} }
@ -159,9 +154,6 @@ p, li, dl, dd, dt {
} }
textarea, input, select { textarea, input, select {
background: #f4f4f4;
border: 1px solid #b2b2b2;
color: #000;
font: 13px Verdana, Arial, Helvetica, sans-serif; font: 13px Verdana, Arial, Helvetica, sans-serif;
margin: 1px; margin: 1px;
padding: 3px; padding: 3px;
@ -296,10 +288,8 @@ form#upload #post_content {
} }
.submit input, .submit input:focus, .button, .button:focus { .submit input, .submit input:focus, .button, .button:focus {
background: url(images/fade-butt.png);
color: #246; color: #246;
padding: 0.3em; padding: 2px;
-moz-border-radius: 4px;
} }
.submit, .editform th, #postcustomsubmit { .submit, .editform th, #postcustomsubmit {
@ -387,7 +377,7 @@ input.disabled, textarea.disabled {
border: none; border: none;
} }
#postdiv, #titlediv, #guiddiv, #tagdiv { #postdiv, #titlediv, #guiddiv {
margin: 0 8px 0 0; margin: 0 8px 0 0;
padding: 0; padding: 0;
} }
@ -407,7 +397,7 @@ input.disabled, textarea.disabled {
line-height: 140%; line-height: 140%;
} }
#titlediv input, #guiddiv input, #tagdiv input { #titlediv input, #guiddiv input {
margin: 0; margin: 0;
width: 100%; width: 100%;
} }
@ -1396,3 +1386,70 @@ a.view-comment-post-link {
font-weight: bold; font-weight: bold;
border-bottom: 1px solid #448abd; border-bottom: 1px solid #448abd;
} }
/* Post Screen */
#tagchecklist {
margin-left: 5px;
font-size: 12px;
}
#tagchecklist span {
margin-right: 15px;
display: block;
float: left;
font-size: 11px;
}
#tagchecklist span a {
margin-top: 5px;
cursor: pointer;
width: 10px;
height: 10px;
background: url(images/xit.gif) no-repeat;
display: block;
float: left;
text-indent: -9999px;
overflow: hidden;
}
#tagchecklist span a:hover {
background: url(images/xit.gif) no-repeat -10px 0;
}
#newtag {
color: #999;
}
.howto {
font-style: italic;
display: block;
color: #999;
}
.ac_results {
border: 1px solid gray;
background-color: white;
padding: 0;
margin: 0;
list-style: none;
position: absolute;
z-index: 10000;
display: none;
}
.ac_results li {
padding: 2px 5px;
white-space: nowrap;
color: #101010;
text-align: left;
}
.ac_over {
cursor: pointer;
background-color: #F0F0B8;
}
.ac_match {
text-decoration: underline;
color: black;
}

View File

@ -1285,9 +1285,9 @@ function wp_nonce_ays( $action ) {
$html .= "\t\t<input type='hidden' name='" . attribute_escape( urldecode( $k ) ) . "' value='" . attribute_escape( urldecode( $v ) ) . "' />\n"; $html .= "\t\t<input type='hidden' name='" . attribute_escape( urldecode( $k ) ) . "' value='" . attribute_escape( urldecode( $v ) ) . "' />\n";
} }
$html .= "\t\t<input type='hidden' name='_wpnonce' value='" . wp_create_nonce( $action ) . "' />\n"; $html .= "\t\t<input type='hidden' name='_wpnonce' value='" . wp_create_nonce( $action ) . "' />\n";
$html .= "\t\t<div id='message' class='confirm fade'>\n\t\t<p>" . wp_specialchars( wp_explain_nonce( $action ) ) . "</p>\n\t\t<p><a href='$adminurl'>" . __( 'No' ) . "</a> <input type='submit' value='" . __( 'Yes' ) . "' /></p>\n\t\t</div>\n\t</form>\n"; $html .= "\t\t<div id='message' class='updated fade'>\n\t\t<p>" . wp_specialchars( wp_explain_nonce( $action ) ) . "</p>\n\t\t<p><a href='$adminurl'>" . __( 'No' ) . "</a> <input type='submit' value='" . __( 'Yes' ) . "' /></p>\n\t\t</div>\n\t</form>\n";
} else { } else {
$html .= "\t<div id='message' class='confirm fade'>\n\t<p>" . wp_specialchars( wp_explain_nonce( $action ) ) . "</p>\n\t<p><a href='$adminurl'>" . __( 'No' ) . "</a> <a href='" . clean_url( add_query_arg( '_wpnonce', wp_create_nonce( $action ), $_SERVER['REQUEST_URI'] ) ) . "'>" . __( 'Yes' ) . "</a></p>\n\t</div>\n"; $html .= "\t<div id='message' class='updated fade'>\n\t<p>" . wp_specialchars( wp_explain_nonce( $action ) ) . "</p>\n\t<p><a href='$adminurl'>" . __( 'No' ) . "</a> <a href='" . clean_url( add_query_arg( '_wpnonce', wp_create_nonce( $action ), $_SERVER['REQUEST_URI'] ) ) . "'>" . __( 'Yes' ) . "</a></p>\n\t</div>\n";
} }
$html .= "</body>\n</html>"; $html .= "</body>\n</html>";
wp_die( $html, $title ); wp_die( $html, $title );

310
wp-includes/js/jquery/suggest.js vendored Normal file
View File

@ -0,0 +1,310 @@
/*
* jquery.suggest 1.1 - 2007-08-06
*
* Uses code and techniques from following libraries:
* 1. http://www.dyve.net/jquery/?autocomplete
* 2. http://dev.jquery.com/browser/trunk/plugins/interface/iautocompleter.js
*
* All the new stuff written by Peter Vulgaris (www.vulgarisoip.com)
* Feel free to do whatever you want with this file
*
*/
(function($) {
$.suggest = function(input, options) {
var $input = $(input).attr("autocomplete", "off");
var $results = $(document.createElement("ul"));
var timeout = false; // hold timeout ID for suggestion results to appear
var prevLength = 0; // last recorded length of $input.val()
var cache = []; // cache MRU list
var cacheSize = 0; // size of cache in chars (bytes?)
$results.addClass(options.resultsClass).appendTo('body');
resetPosition();
$(window)
.load(resetPosition) // just in case user is changing size of page while loading
.resize(resetPosition);
$input.blur(function() {
setTimeout(function() { $results.hide() }, 200);
});
// help IE users if possible
try {
$results.bgiframe();
} catch(e) { }
// I really hate browser detection, but I don't see any other way
if ($.browser.mozilla)
$input.keypress(processKey); // onkeypress repeats arrow keys in Mozilla/Opera
else
$input.keydown(processKey); // onkeydown repeats arrow keys in IE/Safari
function resetPosition() {
// requires jquery.dimension plugin
var offset = $input.offset();
$results.css({
top: (offset.top + input.offsetHeight) + 'px',
left: offset.left + 'px'
});
}
function processKey(e) {
// handling up/down/escape requires results to be visible
// handling enter/tab requires that AND a result to be selected
if ((/27$|38$|40$/.test(e.keyCode) && $results.is(':visible')) ||
(/^13$|^9$/.test(e.keyCode) && getCurrentResult())) {
if (e.preventDefault)
e.preventDefault();
if (e.stopPropagation)
e.stopPropagation();
e.cancelBubble = true;
e.returnValue = false;
switch(e.keyCode) {
case 38: // up
prevResult();
break;
case 40: // down
nextResult();
break;
case 9: // tab
case 13: // return
selectCurrentResult();
break;
case 27: // escape
$results.hide();
break;
}
} else if ($input.val().length != prevLength) {
if (timeout)
clearTimeout(timeout);
timeout = setTimeout(suggest, options.delay);
prevLength = $input.val().length;
}
}
function suggest() {
var q = $.trim($input.val());
if (q.length >= options.minchars) {
cached = checkCache(q);
if (cached) {
displayItems(cached['items']);
} else {
$.get(options.source, {q: q}, function(txt) {
$results.hide();
var items = parseTxt(txt, q);
displayItems(items);
addToCache(q, items, txt.length);
});
}
} else {
$results.hide();
}
}
function checkCache(q) {
for (var i = 0; i < cache.length; i++)
if (cache[i]['q'] == q) {
cache.unshift(cache.splice(i, 1)[0]);
return cache[0];
}
return false;
}
function addToCache(q, items, size) {
while (cache.length && (cacheSize + size > options.maxCacheSize)) {
var cached = cache.pop();
cacheSize -= cached['size'];
}
cache.push({
q: q,
size: size,
items: items
});
cacheSize += size;
}
function displayItems(items) {
if (!items)
return;
if (!items.length) {
$results.hide();
return;
}
var html = '';
for (var i = 0; i < items.length; i++)
html += '<li>' + items[i] + '</li>';
$results.html(html).show();
$results
.children('li')
.mouseover(function() {
$results.children('li').removeClass(options.selectClass);
$(this).addClass(options.selectClass);
})
.click(function(e) {
e.preventDefault();
e.stopPropagation();
selectCurrentResult();
});
}
function parseTxt(txt, q) {
var items = [];
var tokens = txt.split(options.delimiter);
// parse returned data for non-empty items
for (var i = 0; i < tokens.length; i++) {
var token = $.trim(tokens[i]);
if (token) {
token = token.replace(
new RegExp(q, 'ig'),
function(q) { return '<span class="' + options.matchClass + '">' + q + '</span>' }
);
items[items.length] = token;
}
}
return items;
}
function getCurrentResult() {
if (!$results.is(':visible'))
return false;
var $currentResult = $results.children('li.' + options.selectClass);
if (!$currentResult.length)
$currentResult = false;
return $currentResult;
}
function selectCurrentResult() {
$currentResult = getCurrentResult();
if ($currentResult) {
$input.val($currentResult.text());
$results.hide();
if (options.onSelect)
options.onSelect.apply($input[0]);
}
}
function nextResult() {
$currentResult = getCurrentResult();
if ($currentResult)
$currentResult
.removeClass(options.selectClass)
.next()
.addClass(options.selectClass);
else
$results.children('li:first-child').addClass(options.selectClass);
}
function prevResult() {
$currentResult = getCurrentResult();
if ($currentResult)
$currentResult
.removeClass(options.selectClass)
.prev()
.addClass(options.selectClass);
else
$results.children('li:last-child').addClass(options.selectClass);
}
}
$.fn.suggest = function(source, options) {
if (!source)
return;
options = options || {};
options.source = source;
options.delay = options.delay || 100;
options.resultsClass = options.resultsClass || 'ac_results';
options.selectClass = options.selectClass || 'ac_over';
options.matchClass = options.matchClass || 'ac_match';
options.minchars = options.minchars || 2;
options.delimiter = options.delimiter || '\n';
options.onSelect = options.onSelect || false;
options.maxCacheSize = options.maxCacheSize || 65536;
this.each(function() {
new $.suggest(this, options);
});
return this;
};
})(jQuery);

View File

@ -80,6 +80,8 @@ class WP_Scripts {
$this->add( 'jquery', '/wp-includes/js/jquery/jquery.js', false, '1.1.4'); $this->add( 'jquery', '/wp-includes/js/jquery/jquery.js', false, '1.1.4');
$this->add( 'jquery-form', '/wp-includes/js/jquery/jquery.form.js', array('jquery'), '1.0.3'); $this->add( 'jquery-form', '/wp-includes/js/jquery/jquery.form.js', array('jquery'), '1.0.3');
$this->add( 'interface', '/wp-includes/js/jquery/interface.js', array('jquery'), '1.2'); $this->add( 'interface', '/wp-includes/js/jquery/interface.js', array('jquery'), '1.2');
$this->add( 'dimensions', '/wp-includes/js/jquery/jquery.dimensions.min.js', array('jquery'), '1.1.2');
$this->add( 'suggest', '/wp-includes/js/jquery/suggest.js', array('dimensions'), '1.1');
if ( is_admin() ) { if ( is_admin() ) {
global $pagenow; global $pagenow;
@ -129,6 +131,7 @@ class WP_Scripts {
$this->add( 'admin-users', '/wp-admin/js/users.js', array('wp-lists'), '20070823' ); $this->add( 'admin-users', '/wp-admin/js/users.js', array('wp-lists'), '20070823' );
$this->add( 'xfn', '/wp-admin/js/xfn.js', false, '3517' ); $this->add( 'xfn', '/wp-admin/js/xfn.js', false, '3517' );
$this->add( 'upload', '/wp-admin/js/upload.js', array('jquery'), '20070518' ); $this->add( 'upload', '/wp-admin/js/upload.js', array('jquery'), '20070518' );
$this->add( 'post', '/wp-admin/js/post.js', array('suggest'), '20080102' );
$this->localize( 'upload', 'uploadL10n', array( $this->localize( 'upload', 'uploadL10n', array(
'browseTitle' => attribute_escape(__('Browse your files')), 'browseTitle' => attribute_escape(__('Browse your files')),
'back' => __('&laquo; Back'), 'back' => __('&laquo; Back'),