[Slashdotjp-dev 1036] [563] merged from upstream/2.5.0.198 branch

Back to archive index

svnno****@sourc***** svnno****@sourc*****
2008年 4月 1日 (火) 13:03:21 JST


Revision: 563
          http://svn.sourceforge.jp/cgi-bin/viewcvs.cgi?root=slashdotjp&view=rev&rev=563
Author:   tach
Date:     2008-04-01 13:03:21 +0900 (Tue, 01 Apr 2008)

Log Message:
-----------
merged from upstream/2.5.0.198 branch

Modified Paths:
--------------
    slashjp/trunk/AUTHORS
    slashjp/trunk/INSTALL
    slashjp/trunk/Slash/DB/MySQL/MySQL.pm
    slashjp/trunk/Slash/Hook/Hook.pm
    slashjp/trunk/Slash/Slash.pm
    slashjp/trunk/Slash/Utility/Comments/Comments.pm
    slashjp/trunk/Slash/Utility/Environment/Environment.pm
    slashjp/trunk/bin/install-slashsite
    slashjp/trunk/debian/changelog
    slashjp/trunk/docs/INSTALL.pod
    slashjp/trunk/plugins/Admin/Admin.pm
    slashjp/trunk/plugins/Admin/admin.pl
    slashjp/trunk/plugins/Admin/templates/editStory;admin;default
    slashjp/trunk/plugins/Admin/templates/signoff;misc;default
    slashjp/trunk/plugins/Admin/templates/signoff_stats;misc;default
    slashjp/trunk/plugins/Ajax/PLUGIN
    slashjp/trunk/plugins/Ajax/htdocs/ajax.pl
    slashjp/trunk/plugins/Ajax/htdocs/images/admin.js
    slashjp/trunk/plugins/Ajax/htdocs/images/common.js
    slashjp/trunk/plugins/Ajax/htdocs/images/nodnix.js
    slashjp/trunk/plugins/Ajax/htdocs/images/sd_autocomplete.js
    slashjp/trunk/plugins/Ajax/htdocs/images/sectionprefs.js
    slashjp/trunk/plugins/Ajax/mysql_dump.sql
    slashjp/trunk/plugins/Ajax/templates/edit_comment;ajax;default
    slashjp/trunk/plugins/Ajax/templates/prefs_d2;ajax;default
    slashjp/trunk/plugins/Email/templates/dispStory;email;default
    slashjp/trunk/plugins/FAQSlashdot/faq/friends.shtml
    slashjp/trunk/plugins/FireHose/FireHose.pm
    slashjp/trunk/plugins/FireHose/PLUGIN
    slashjp/trunk/plugins/FireHose/firehose.css
    slashjp/trunk/plugins/FireHose/templates/firehose_tags_top;misc;default
    slashjp/trunk/plugins/Messages/templates/dailyheadlines;messages;default
    slashjp/trunk/plugins/Messages/templates/dailynews;messages;default
    slashjp/trunk/plugins/Moderation/Moderation.pm
    slashjp/trunk/plugins/Rating/Rating.pm
    slashjp/trunk/plugins/ResKey/mysql_dump.sql
    slashjp/trunk/plugins/Submit/submit.pl
    slashjp/trunk/plugins/TagModeration/TagModeration.pm
    slashjp/trunk/plugins/Tags/PLUGIN
    slashjp/trunk/plugins/Tags/Tags.pm
    slashjp/trunk/plugins/Tags/mysql_dump.sql
    slashjp/trunk/plugins/Tags/mysql_schema.sql
    slashjp/trunk/plugins/Tags/tagbox.pl
    slashjp/trunk/plugins/Tags/templates/tagsstorydivtagbox;misc;default
    slashjp/trunk/sql/mysql/defaults.sql
    slashjp/trunk/sql/mysql/upgrades
    slashjp/trunk/tagboxes/Despam/Despam.pm
    slashjp/trunk/themes/slashcode/THEME
    slashjp/trunk/themes/slashcode/htdocs/comments.pl
    slashjp/trunk/themes/slashcode/htdocs/images/comments.js
    slashjp/trunk/themes/slashcode/htdocs/images/dumper.js
    slashjp/trunk/themes/slashcode/templates/dispComment;misc;default
    slashjp/trunk/themes/slashcode/templates/dispLinkComment;misc;default
    slashjp/trunk/themes/slashcode/templates/dispStory;misc;default
    slashjp/trunk/themes/slashcode/templates/errors;comments;default
    slashjp/trunk/themes/slashcode/templates/html-header;misc;default
    slashjp/trunk/themes/slashcode/templates/modCommentLog;misc;default
    slashjp/trunk/themes/slashcode/templates/printCommComments;misc;default
    slashjp/trunk/themes/slashcode/templates/printCommentsMain;misc;default

Added Paths:
-----------
    slashjp/trunk/plugins/Ajax/htdocs/images/jquery-1.2.3.js
    slashjp/trunk/plugins/FireHose/templates/tagsnodnixuser;misc;default
    slashjp/trunk/plugins/Tags/tags_tagnamecache.pl

Removed Paths:
-------------
    slashjp/trunk/plugins/Ajax/htdocs/images/prototype.js
    slashjp/trunk/themes/slashcode/htdocs/images/comments2.js


-------------- next part --------------
Modified: slashjp/trunk/AUTHORS
===================================================================
--- slashjp/trunk/AUTHORS	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/AUTHORS	2008-04-01 04:03:21 UTC (rev 563)
@@ -8,6 +8,7 @@
 	Cliff Wood		cliff****@slash*****
 	Jamie McCarthy		jamie****@mccar*****
 	Tim Vroom		vroom****@slash*****
+	Scott Collins		scc****@slash*****
 
 Special thanks to some of the outstanding contributors to Slash,
 who have helped with various kinds of feedback, all of it invaluable.

Modified: slashjp/trunk/INSTALL
===================================================================
--- slashjp/trunk/INSTALL	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/INSTALL	2008-04-01 04:03:21 UTC (rev 563)
@@ -261,6 +261,14 @@
                         cpan> o conf prerequisites_policy follow
                         cpan> o conf commit
 
+            Data::JavaScript::Anon
+                There are bugs in versions earlier than 1.00 that break our
+                JS. Unfortunately, CPAN seems to prefer version 0.9 even
+                though 1.00 is available. You may have to install a better
+                version in CPAN by hand:
+
+                  cpan> install A/AD/ADAMK/Data-JavaScript-Anon-1.00.tar.gz
+
             Additional Libraries
                 You must have certain libraries existing on your system
                 before building, for Compress::Zlib, XML::Parser, DBI and

Modified: slashjp/trunk/Slash/DB/MySQL/MySQL.pm
===================================================================
--- slashjp/trunk/Slash/DB/MySQL/MySQL.pm	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/Slash/DB/MySQL/MySQL.pm	2008-04-01 04:03:21 UTC (rev 563)
@@ -538,7 +538,7 @@
 	my $page = $user->{currentPage};
 	my $skin = getCurrentSkin('name');
 	my $admin = $user->{is_admin};
-	my $theme = $user->{simpledesign} ? "light" : $user->{css_theme};
+	my $theme = ($user->{simpledesign} || $user->{pda}) ? "light" : $user->{css_theme};
 	my $constants = getCurrentStatic();
 
 	my $expire_time = $constants->{css_expire} || 3600;
@@ -557,7 +557,7 @@
 	$css_skins_ref = $self->getCSSValuesHashForCol('skin')   if !$css_skins_ref;
 	$css_themes_ref= $self->getCSSValuesHashForCol('theme') if !$css_themes_ref;
 
-	my $lowbandwidth = $user->{lowbandwidth} ? "yes" : "no";
+	my $lowbandwidth = ($user->{lowbandwidth} || $user->{pda}) ? "yes" : "no";
 
 	$page   = '' if !$css_pages_ref->{$page};
 	$skin   = '' if !$css_skins_ref->{$skin};
@@ -7966,15 +7966,16 @@
 }
 
 sub getSignoffCountHashForStoids {
-	my($self, $stoids) = @_;
+	my($self, $stoids, $adminsonly) = @_;
 	return {} if !@$stoids;	
 	my $stoid_list = join ',', @$stoids;
+	my $user_limit_clause = $adminsonly ? ' AND seclev >= 100' : '';
 
 	my $signoff_hash = $self->sqlSelectAllHashref(
 		"stoid", 
-		"stoid, COUNT(DISTINCT uid) AS cnt",
-		"signoff",
-		"stoid in ($stoid_list)",
+		"stoid, COUNT(DISTINCT signoff.uid) AS cnt",
+		"signoff, users",
+		"users.uid = signoff.uid AND stoid IN ($stoid_list) $user_limit_clause",
 		"GROUP BY stoid"
 	);
 	

Modified: slashjp/trunk/Slash/Hook/Hook.pm
===================================================================
--- slashjp/trunk/Slash/Hook/Hook.pm	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/Slash/Hook/Hook.pm	2008-04-01 04:03:21 UTC (rev 563)
@@ -5,9 +5,6 @@
 
 package Slash::Hook;
 use strict;
-use DBIx::Password;
-use Slash;
-use Slash::DB;
 use Slash::Utility::Environment; # avoid cross-caller issues
 use vars qw($VERSION);
 

Modified: slashjp/trunk/Slash/Slash.pm
===================================================================
--- slashjp/trunk/Slash/Slash.pm	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/Slash/Slash.pm	2008-04-01 04:03:21 UTC (rev 563)
@@ -217,6 +217,7 @@
 		&& $story->{rendered} && !$options->{force_cache_freshen}
 		&& !$form->{simpledesign} && !$user->{simpledesign}
 		&& !$form->{lowbandwidth} && !$user->{lowbandwidth}
+		&& !$form->{pda} && !$user->{pda} 
 		&& (!$form->{ssi} || $form->{ssi} ne 'yes')
 		&& !$user->{noicons}
 		&& !$form->{issue}

Modified: slashjp/trunk/Slash/Utility/Comments/Comments.pm
===================================================================
--- slashjp/trunk/Slash/Utility/Comments/Comments.pm	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/Slash/Utility/Comments/Comments.pm	2008-04-01 04:03:21 UTC (rev 563)
@@ -38,7 +38,7 @@
 @EXPORT		= qw(
 	constrain_score dispComment displayThread printComments
 	jsSelectComments commentCountThreshold commentThresholds discussion2
-	tempUofmLinkGenerate tempUofmCipherObj selectComments
+	tempUofmLinkGenerate tempUofmCipherObj selectComments preProcessReplyForm
 	getPoints preProcessComment postProcessComment prevComment saveComment
 );
 
@@ -405,8 +405,9 @@
 	my $anon_thresh   = Data::JavaScript::Anon->anon_dump($thresh_totals || {});
 	s/\s+//g for ($anon_thresh, $anon_roots, $anon_rootsh);
 
-	$user->{is_anon}  ||= 0;
-	$user->{is_admin} ||= 0;
+	$user->{is_anon}       ||= 0;
+	$user->{is_admin}      ||= 0;
+	$user->{is_subscriber} ||= 0;
 
 	my $extra = '';
 	if ($d2_seen_0) {
@@ -442,6 +443,7 @@
 user_uid = $user->{uid};
 user_is_anon = $user->{is_anon};
 user_is_admin = $user->{is_admin};
+user_is_subscriber = $user->{is_subscriber};
 user_threshold = $threshold;
 user_highlightthresh = $highlightthresh;
 
@@ -534,6 +536,17 @@
 	my($value, $hashref, $nocomm) = @_;
 	$hashref ||= {};
 	$hashref->{value} = $value;
+
+	# this is a cheap hack to NOT print titlebar in getError if we
+	# are calling from ajax.pl ... easier than reorganizing the code
+	# for now -- pudge 2008/03/04
+	for (0..9) {
+		if ((caller($_))[1] =~ /\bajax\.pl$/) {
+			$hashref->{no_titlebar} = 1;
+			last;
+		}
+	}
+
 	return slashDisplay('errors', $hashref,
 		{ Return => 1, Nocomm => $nocomm, Page => 'comments' });
 }
@@ -587,7 +600,7 @@
 
 	# Adjust reasons. Do we need a reason?
 	# Are you threatening me?
-	if ($reasons) {
+	if ($reasons && $C->{reason}) {
 		my $reason_id = $reasons->{$C->{reason}}{id};
 		if ($reason_id && $user->{"reason_alter_$reason_id"}) {
 			$hr->{reason_bonus} =
@@ -599,7 +612,7 @@
 
 	# Keep your friends close but your enemies closer.
 	# Or ignore them, we don't care.
-	if ($user->{uid} != $C->{uid}) {
+	if ($user->{people} && $user->{uid} != $C->{uid}) {
 		if ($user->{people}{FRIEND()}{$C->{uid}}) {
 			$hr->{people_bonus_friend} =
 				$user->{people_bonus_friend};
@@ -1325,6 +1338,18 @@
 
 #========================================================================
 
+sub preProcessReplyForm {
+	my($form, $reply) = @_;
+	return if !$form->{pid} || !$reply->{subject} || $form->{postersubj};
+
+	$form->{postersubj} = decode_entities($reply->{subject});
+	$form->{postersubj} =~ s/^Re://i;
+	$form->{postersubj} =~ s/\s\s/ /g;
+	$form->{postersubj} = "Re:$form->{postersubj}";
+}
+
+#========================================================================
+
 sub preProcessComment {
 	my($comm, $user, $discussion, $error_message) = @_; # probably $comm = $form
 	my $constants = getCurrentStatic();
@@ -1339,7 +1364,7 @@
 	my $tempSubject = strip_notags($comm->{postersubj});
 	my $tempComment = $comm->{postercomment};
 
-	$comm->{anon} = 0;
+	$comm->{anon} = $user->{is_anon};
 	if ($comm->{postanon}
 		&& $reader->checkAllowAnonymousPosting
 		&& $user->{karma} > -1
@@ -1424,22 +1449,23 @@
 		$comm->{comment} = parseDomainTags($comm->{comment},
 			!$comm->{anon} && $comm->{fakeemail});
 
-#		my $discussion = $slashdb->getDiscussion($comm->{sid}) || 0;	
 		my $extras = [];
 		my $disc_skin = $slashdb->getSkin($discussion->{primaryskid});
 		$extras = $slashdb->getNexusExtrasForChosen(
 			{ $disc_skin->{nexus} => 1 },
 			{ content_type => "comment" })
 			if $disc_skin && $disc_skin->{nexus};
-			
+
 		my $preview = {
-			nickname		=> $comm->{postanon}
+			nickname		=> $comm->{anon}
 							? getCurrentAnonymousCoward('nickname')
 							: $comm->{nickname},
+			uid			=> $comm->{anon}
+							? getCurrentAnonymousCoward('uid')
+							: $comm->{uid},
 			pid			=> $comm->{pid},
-			uid			=> $comm->{postanon} ? '' : $comm->{uid},
-			homepage		=> $comm->{postanon} ? '' : $comm->{homepage},
-			fakeemail		=> $comm->{postanon} ? '' : $comm->{fakeemail},
+			homepage		=> $comm->{anon} ? '' : $comm->{homepage},
+			fakeemail		=> $comm->{anon} ? '' : $comm->{fakeemail},
 			journal_last_entry_date	=> $comm->{journal_last_entry_date} || '',
 			'time'			=> $slashdb->getTime,
 			subject			=> $comm->{subject},
@@ -1584,7 +1610,7 @@
 	my $moddb = getObject("Slash::$constants->{m1_pluginname}");
 	if ($moddb) {
 		my $text = $moddb->checkDiscussionForUndoModeration($comm->{sid});
-		# XXX
+		# XXX doesn't work for D2
 		print $text if $text;
 	}
 
@@ -1617,13 +1643,16 @@
 		my $users  = $messages->checkMessageCodes(MSG_CODE_COMMENT_REPLY, [$parent->{uid}]);
 		if (_send_comment_msg($users->[0], \%users, $pts, $clean_comment)) {
 			my $data  = {
-				template_name	=> 'reply_msg',
-				subject		=> { template_name => 'reply_msg_subj' },
-				reply		=> $reply,
-				parent		=> $parent,
-				discussion	=> $discussion,
+				template_name   => 'reply_msg',
+				template_page   => 'comments',
+				subject         => {
+					template_name => 'reply_msg_subj',
+					template_page => 'comments',
+				},
+				reply           => $reply,
+				parent          => $parent,
+				discussion      => $discussion,
 			};
-
 			$messages->create($users->[0], MSG_CODE_COMMENT_REPLY, $data);
 			$users{$users->[0]}++;
 		}
@@ -1634,10 +1663,14 @@
 		my $users  = $messages->checkMessageCodes(MSG_CODE_JOURNAL_REPLY, [$discussion->{uid}]);
 		if (_send_comment_msg($users->[0], \%users, $pts, $clean_comment)) {
 			my $data  = {
-				template_name	=> 'journrep',
-				subject		=> { template_name => 'journrep_subj' },
-				reply		=> $reply,
-				discussion	=> $discussion,
+				template_name   => 'journrep',
+				template_page   => 'comments',
+				subject         => {
+					template_name => 'journrep_subj',
+					template_page => 'comments',
+				},
+				reply           => $reply,
+				discussion      => $discussion,
 			};
 
 			$messages->create($users->[0], MSG_CODE_JOURNAL_REPLY, $data);
@@ -1650,10 +1683,14 @@
 		my $users = $messages->getMessageUsers(MSG_CODE_NEW_COMMENT);
 
 		my $data  = {
-			template_name	=> 'commnew',
-			subject		=> { template_name => 'commnew_subj' },
-			reply		=> $reply,
-			discussion	=> $discussion,
+			template_name   => 'commnew',
+			template_page   => 'comments',
+			subject         => {
+				template_name => 'commnew_subj',
+				template_page => 'comments',
+			},
+			reply           => $reply,
+			discussion      => $discussion,
 		};
 
 		my @users_send;
@@ -1862,12 +1899,18 @@
 
 	$time_to_display = timeCalc($comment->{date});
 	unless ($user->{noscores}) {
-		$score_to_display .= " (Score:";
-		$score_to_display .= length($comment->{points}) ? $comment->{points} : "?";
+		$score_to_display .= "Score:";
+		if (length $comment->{points}) {
+			$score_to_display .= $comment->{points};
+			$score_to_display = qq[<a href="#" onclick="getModalPrefs('modcommentlog', 'Moderation Comment Log', $comment->{cid}); return false">$score_to_display</a>]
+				if $constants->{modal_prefs_active} && !$user->{is_anon};
+		} else {
+			$score_to_display .= '?';
+		}
 		if ($reasons && $comment->{reason}) {
 			$score_to_display .= ", $reasons->{$comment->{reason}}{name}";
 		}
-		$score_to_display .= ")";
+		$score_to_display = " ($score_to_display)";
 	}
 
 	if ($comment->{sid} && $comment->{cid}) {
@@ -1948,14 +1991,14 @@
 		&& $comment->{nickname} ne "-") { # this last test probably useless
 		my @link = ( );
 
-		push @link, linkComment({
+		push @link, (qq'<span id="reply_link_$comment->{cid}">' . linkComment({
 			sid	=> $comment->{sid},
 			pid	=> $comment->{cid},
 			op	=> 'Reply',
 			subject	=> 'Reply to This',
 			subject_only => 1,
-			onclick	=> (($discussion2 && $user->{test_code}) ? "replyTo($comment->{cid}); return false;" : '')
-		}) unless $user->{state}{discussion_archived};
+			onclick	=> (($discussion2 && (!$constants->{subscribe} || $user->{is_subscriber})) ? "replyTo($comment->{cid}); return false;" : '')
+		}) . '</span>') unless $user->{state}{discussion_archived};
 
 		push @link, linkComment({
 			sid	=> $comment->{sid},
@@ -2155,6 +2198,7 @@
 	# some commonly-used proxy ports to access our own site.
 	# If we can, they're coming from an open HTTP proxy, which
 	# we don't want to allow to post.
+	# XXX : this can become a reskey check -- pudge 2008-03
 	if ($constants->{comments_portscan}
 		&& ( $constants->{comments_portscan} == 2
 			|| $constants->{comments_portscan} == 1 && $user->{is_anon} )

Modified: slashjp/trunk/Slash/Utility/Environment/Environment.pm
===================================================================
--- slashjp/trunk/Slash/Utility/Environment/Environment.pm	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/Slash/Utility/Environment/Environment.pm	2008-04-01 04:03:21 UTC (rev 563)
@@ -1556,7 +1556,7 @@
 	my @defaults = (
 		['mode', 'thread'], qw[
 		savechanges commentsort threshold
-		posttype noboxes lowbandwidth simpledesign
+		posttype noboxes lowbandwidth simpledesign pda
 	]);
 
 	for my $param (@defaults) {

Modified: slashjp/trunk/bin/install-slashsite
===================================================================
--- slashjp/trunk/bin/install-slashsite	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/bin/install-slashsite	2008-04-01 04:03:21 UTC (rev 563)
@@ -26,7 +26,7 @@
 
 my %opts;
 # Remember to doublecheck these match usage()!
-usage('Options used incorrectly') unless getopts('hvu:H:n:xRL:i:T:P:a:e:p:o:g:', \%opts);
+usage('Options used incorrectly') unless getopts('hvfu:H:n:xRL:i:T:P:a:e:p:o:g:', \%opts);
 usage() if !$opts{'u'} and ($opts{'h'} or !keys %opts);
 # if invoked with both -u and -h, call usage() later, after we load Slash::Install
 version() if $opts{'v'};

Modified: slashjp/trunk/debian/changelog
===================================================================
--- slashjp/trunk/debian/changelog	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/debian/changelog	2008-04-01 04:03:21 UTC (rev 563)
@@ -1,3 +1,9 @@
+slash (2.5.0.198-1) unstable; urgency=low
+
+  * New upstream CVS release
+
+ -- Taku YASUI <tach****@osdn*****>  Tue, 01 Apr 2008 13:02:56 +0900
+
 slash (2.5.0.196-1) unstable; urgency=low
 
   * New upstream CVS release

Modified: slashjp/trunk/docs/INSTALL.pod
===================================================================
--- slashjp/trunk/docs/INSTALL.pod	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/docs/INSTALL.pod	2008-04-01 04:03:21 UTC (rev 563)
@@ -311,6 +311,14 @@
 	cpan> o conf prerequisites_policy follow
 	cpan> o conf commit
 
+=item B<Data::JavaScript::Anon>
+
+There are bugs in versions earlier than 1.00 that break our JS.  Unfortunately,
+CPAN seems to prefer version 0.9 even though 1.00 is available.  You may
+have to install a better version in CPAN by hand:
+
+  cpan> install A/AD/ADAMK/Data-JavaScript-Anon-1.00.tar.gz
+
 =item B<Additional Libraries>
 
 You must have certain libraries existing on your system before building,

Modified: slashjp/trunk/plugins/Admin/Admin.pm
===================================================================
--- slashjp/trunk/plugins/Admin/Admin.pm	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/plugins/Admin/Admin.pm	2008-04-01 04:03:21 UTC (rev 563)
@@ -222,7 +222,7 @@
 	my $slashdb = getCurrentDB();
 	my $form = getCurrentForm();
 	my $user = getCurrentUser();
-	return unless $user->{is_admin};
+	return unless $user->{is_admin} || $user->{acl}{signoff_allowed};
 	
 	my $stoid = $form->{stoid};
 	my $uid   = $user->{uid};
@@ -327,11 +327,11 @@
 	my($self, $days) = @_;
 	my $days_q = $self->sqlQuote($days);
 	my $signoff_info = $self->sqlSelectAllHashrefArray(
-		"stories.stoid, users.uid, (unix_timestamp(min(signoff_time)) - unix_timestamp(stories.time)) / 60 AS min_to_sign, users.nickname",
+		"stories.stoid, users.uid, (unix_timestamp(min(signoff_time)) - unix_timestamp(stories.time)) / 60 AS min_to_sign, users.nickname, users.seclev",
 		"stories, story_topics_rendered, signoff, users",
 		"stories.stoid = story_topics_rendered.stoid AND signoff.stoid=stories.stoid AND users.uid = signoff.uid
 	         AND stories.time <= NOW() AND stories.time > DATE_SUB(NOW(), INTERVAL $days_q DAY)",
-		"GROUP BY signoff.uid, signoff.stoid"
+		"GROUP BY signoff.uid, signoff.stoid ORDER BY users.seclev DESC"
 	);
 	return $signoff_info;
 

Modified: slashjp/trunk/plugins/Admin/admin.pl
===================================================================
--- slashjp/trunk/plugins/Admin/admin.pl	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/plugins/Admin/admin.pl	2008-04-01 04:03:21 UTC (rev 563)
@@ -1888,7 +1888,7 @@
 	}
 
 	my $usersignoffs 	= $slashdb->getUserSignoffHashForStoids($user->{uid}, $stoid_list);
-	my $storysignoffcnt	= $slashdb->getSignoffCountHashForStoids($stoid_list);
+	my $storysignoffcnt	= $slashdb->getSignoffCountHashForStoids($stoid_list, 1);
 
 	my $needed_signoffs = $slashdb->getActiveAdminCount;
 
@@ -2607,15 +2607,22 @@
 		my $signoff_info = $admin->getSignoffData($days);
 		foreach (@$signoff_info) {
 			$author_info->{$_->{uid}}{nickname} = $_->{nickname};
+			$author_info->{$_->{uid}}{uid} = $_->{uid};
 			$author_info->{$_->{uid}}{$days}{cnt}++;	
 			$author_info->{$_->{uid}}{$days}{tot_time} += $_->{min_to_sign};
+			$author_info->{$_->{uid}}{seclev} = $_->{seclev};
 			$stoids_for_days{$days}{$_->{stoid}}++;
 			push @{$author_info->{$_->{uid}}{$days}{mins}}, $_->{min_to_sign};
 		}
 	}
 
+	my @author_array = values %$author_info;
+
+	@author_array = sort { $b->{seclev} <=> $a->{seclev} } @author_array;
+
 	slashDisplay("signoff_stats", {
 		author_info	=> $author_info,
+		author_array    => \@author_array,
 		stoids_for_days	=> \%stoids_for_days,
 		num_days	=> $num_days
 	});

Modified: slashjp/trunk/plugins/Admin/templates/editStory;admin;default
===================================================================
--- slashjp/trunk/plugins/Admin/templates/editStory;admin;default	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/plugins/Admin/templates/editStory;admin;default	2008-04-01 04:03:21 UTC (rev 563)
@@ -248,7 +248,7 @@
 						<a href="[% constants.rootdir %]/admin.pl?op=static_files&amp;sid=[% sid %]">Media Files associated with this story</a>
 					</label>
 					<table cellpadding="3" cellspacing="3">
-					<tr><td>Select <br>Thumb</td><td colspan="3"></td><td>Click to <br>add to body</td></tr>
+					<tr><td>Select <br>Thumb</td><td colspan="3"></td><td>Click to <br>add to body</td><td>Click to add<br>to media</td></tr>
 					[% FOREACH mfile = story_static_files %]
 					[% bigfile = mfile.name %]
 					[% IF mfile.filetype != "image" || (mfile.filetype == "image" && bigfile.match('-thumb\.')) %]
@@ -269,11 +269,18 @@
 								[% height = height.int %]
 							[% END %]
 							[% bigfile = bigfile.replace('-thumb') %]
-							<a href="#bodytext" onclick="appendToBodytext('\n<a href=\'[% constants.imagedir %][% bigfile %]\'><img src=\'[% constants.imagedir %][% mfile.name %]\' width=\'[% mfile.width %]\' height=\'[% mfile.height %]\'></a>')"><img src="[% constants.imagedir %][% mfile.name %]" width="[% width %]" height="[% height %]"></a>
+							<a href="#bodytext" onclick="appendToBodytext('\n<img src=\'[% constants.imagedir %][% bigfile %]\'>')"><img src="[% constants.imagedir %][% mfile.name %]" width="[% width %]" height="[% height %]"></a>
 						[% ELSE %]
 							<a href="#bodytext" onclick="appendToBodytext('\n<a href=\'[% constants.imagedir %][% mfile.name %]\'>[% filename %]</a>')">Add [% filename %]</a>
 						[% END %]
 </td>
+<td>
+	[% IF mfile.filetype == "image" %]
+		<a href="#media" onclick="appendToMedia('\n<img src=\'[% constants.imagedir %][% bigfile %]\'>')"><img src="[% constants.imagedir %][% mfile.name %]" width="[% width %]" height="[% height %]"></a>
+	[% ELSE %]
+		<a href="#media" onclick="appendToMedia('\n<a href=\'[% constants.imagedir %][% mfile.name %]\'>[% filename %]</a>')">Add [% filename %]</a>
+	[% END %]
+</td>
 </tr>
 					[% END %]
 					[% END %]
@@ -293,6 +300,7 @@
 				<label>
 					<a href="#" onclick="toggleId('admin-media','hide','show'); return false">Media</a>
 				</label>
+				<a name="media"></a>
 				<textarea name="media" rows="[% user.textarea_rows || constants.textarea_rows %]" cols="[% user.textarea_cols || constants.textarea_cols %]" wrap="virtual" id="admin-media" class="[% storyref.media ? "show" : "hide" %]">[% storyref.media | strip_literal %]</textarea>
 				<div class="notes">[% ispell_comments.bodytext %]</div>
 				[% PROCESS editbuttons %]

Modified: slashjp/trunk/plugins/Admin/templates/signoff;misc;default
===================================================================
--- slashjp/trunk/plugins/Admin/templates/signoff;misc;default	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/plugins/Admin/templates/signoff;misc;default	2008-04-01 04:03:21 UTC (rev 563)
@@ -18,7 +18,6 @@
 [% IF storylink %]
 <li class="signoff">[% END %]
 , <span id="signoff_[% stoid %]" onclick="admin_signoff([% stoid %][% IF fhid; ",'firehose',"; fhid; END %])">[% UNLESS signed %]unsigned[% END %]</span>
-[% PROCESS ajax_reskey_signoff reskey_label => 'signoff-reskey-' _ stoid, reskey_name => 'ajax_admin' %]
 [% IF storylink %]</li>[% END %]
 [% END %]
 __seclev__

Modified: slashjp/trunk/plugins/Admin/templates/signoff_stats;misc;default
===================================================================
--- slashjp/trunk/plugins/Admin/templates/signoff_stats;misc;default	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/plugins/Admin/templates/signoff_stats;misc;default	2008-04-01 04:03:21 UTC (rev 563)
@@ -23,16 +23,22 @@
 	</td>
 	[% END %]
 </tr>
-
-[% FOREACH author = author_info.keys %]
-	<tr class="data_hl1"><td>[% author_info.$author.nickname %]</td>
+[% seen_admin = 0 %]
+[% separator = 0 %]
+[% FOREACH author = author_array %]
+	[% IF author.seclev >= 100; seen_admin = 1; END %]
+	[% IF seen_admin && author.seclev < 100 && !separator %]
+		<tr><td colspan="6"><b>Non-Admins</b></td></tr>
+		[% separator = 1; %]
+	[% END %]
+	<tr class="data_hl1"><td>[% author.nickname %]</td>
 	[% FOREACH days = num_days %]
 	[% num_stories = stoids_for_days.$days.keys.size || 0 %]
-	[% cnt = author_info.$author.$days.cnt || 0 %]
+	[% cnt = author.$days.cnt || 0 %]
 	<td>[% cnt %] of [% num_stories %]</td>
 	<td> 
 		[% IF cnt > 0 %]
-			[% avg_time = author_info.$author.$days.tot_time / cnt %]
+			[% avg_time = author.$days.tot_time / cnt %]
 			[% avg_time.int %] m
 		[% ELSE %]
 			N/A

Modified: slashjp/trunk/plugins/Ajax/PLUGIN
===================================================================
--- slashjp/trunk/plugins/Ajax/PLUGIN	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/plugins/Ajax/PLUGIN	2008-04-01 04:03:21 UTC (rev 563)
@@ -28,7 +28,7 @@
 htdoc=htdocs/images/slashbox.js
 htdoc=htdocs/images/common.js
 htdoc=htdocs/images/nodnix.js
-htdoc=htdocs/images/prototype.js
+htdoc=htdocs/images/jquery-1.2.3.js
 htdoc=htdocs/images/sectionprefs.js
 requiresplugin=ResKey
 template=templates/ajax_reskey_signoff;misc;default

Modified: slashjp/trunk/plugins/Ajax/htdocs/ajax.pl
===================================================================
--- slashjp/trunk/plugins/Ajax/htdocs/ajax.pl	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/plugins/Ajax/htdocs/ajax.pl	2008-04-01 04:03:21 UTC (rev 563)
@@ -54,6 +54,8 @@
 
 #	print STDERR "AJAX5 $$: $user->{uid}, $op\n";
 
+	my $options = {};
+
 	if ($reskey_name ne 'NA') {
 		my $reskey = getObject('Slash::ResKey');
 		my $rkey = $reskey->key($reskey_name);
@@ -61,6 +63,7 @@
 			print STDERR scalar(localtime) . " ajax.pl main no rkey for op='$op' name='$reskey_name'\n";
 			return;
 		}
+		$options->{rkey} = $rkey;
 		if ($ops->{$op}{reskey_type} eq 'createuse') {
 			$rkey->createuse;
 		} elsif ($ops->{$op}{reskey_type} eq 'touch') {
@@ -85,7 +88,6 @@
 	}
 #	print STDERR "AJAX6 $$: $user->{uid}, $op\n";
 
-	my $options = {};
 	my $retval = $ops->{$op}{function}->(
 		$slashdb, $constants, $user, $form, $options
 	);
@@ -273,6 +275,23 @@
 	my $pid = $form->{pid} || 0;
 	my $sid = $form->{sid} or return;
 
+	$user->{state}{ajax_accesslog_op} = 'comments_submit_reply';
+
+	my($error_message, $saved_comment);
+	my $discussion = $slashdb->getDiscussion($sid);
+	my $comment = preProcessComment($form, $user, $discussion, \$error_message);
+	if (!$error_message) {
+		$options->{rkey}->use or $error_message = $options->{rkey}->errstr;
+	}
+	$saved_comment = saveComment($form, $comment, $user, $discussion, \$error_message)
+		unless $error_message;
+	my $cid = $saved_comment && $saved_comment ne '-1' ? $saved_comment->{cid} : 0;
+
+	$options->{content_type} = 'application/json';
+	my %to_dump = ( cid => $cid, error => $error_message );
+#use Data::Dumper; print STDERR Dumper \%to_dump;
+
+	return Data::JavaScript::Anon->anon_dump(\%to_dump);
 }
 
 sub previewReply {
@@ -282,14 +301,24 @@
 
 	$user->{state}{ajax_accesslog_op} = 'comments_preview_reply';
 
+	my($error_message, $preview, $html);
 	my $discussion = $slashdb->getDiscussion($sid);
-	my $comment = preProcessComment($form, $user, $discussion);
-	my $preview = postProcessComment({ %$comment, %$user }, 0, $discussion);
-	my $html = prevComment($preview, $user);
+	my $comment = preProcessComment($form, $user, $discussion, \$error_message);
+	if ($comment && $comment ne '-1') {
+		$preview = postProcessComment({ %$comment, %$form, %$user }, 0, $discussion);
+		$html = prevComment($preview, $user);
+	}
 
-
+	$error_message ||= 'This comment will not be saved until you click the Submit button below.';
 	$options->{content_type} = 'application/json';
-	my %to_dump = (html => { "replyto_preview_$pid" => $html });
+	my %to_dump = (
+		error => $error_message,
+	);
+	$to_dump{html} = { "replyto_preview_$pid" => $html } if $html;
+	$to_dump{eval_first} = "\$('gotmodwarning_$pid').value = 1;"
+		if $form->{gotmodwarning} || ($error_message && $error_message eq
+			Slash::Utility::Comments::getError("moderations to be lost")
+		);
 #use Data::Dumper; print STDERR Dumper \%to_dump; 
 
 	return Data::JavaScript::Anon->anon_dump(\%to_dump);
@@ -304,8 +333,10 @@
 	$user->{state}{ajax_accesslog_op} = 'comments_reply_form';
 
 	my($reply, $pid_reply);
+	my $discussion = $slashdb->getDiscussion($sid);
 	$reply = $slashdb->getCommentReply($sid, $pid) if $pid;
 	$pid_reply = prepareQuoteReply($reply) if $pid && $reply;
+	preProcessReplyForm($form, $reply);
 
 	my $reskey = getObject('Slash::ResKey');
 	my $rkey = $reskey->key('comments', { nostate => 1 });
@@ -314,10 +345,11 @@
 	my %to_dump;
 	if ($rkey->success) {
 		my $reply_html = slashDisplay('edit_comment', {
-			sid    => $sid,
-			pid    => $pid,
-			reply  => $reply,
-			rkey   => $rkey
+			discussion => $discussion,
+			sid        => $sid,
+			pid        => $pid,
+			reply      => $reply,
+			rkey       => $rkey
 		}, { Return => 1 });
 		%to_dump = (html => { "replyto_$pid" => $reply_html });
 	} else {
@@ -326,7 +358,6 @@
 
 	$options->{content_type} = 'application/json';
 	$to_dump{eval_first} = "comment_body_reply[$pid] = '$pid_reply';" if $pid_reply;
-
 #use Data::Dumper; print STDERR Dumper \%to_dump; 
 
 	return Data::JavaScript::Anon->anon_dump(\%to_dump);
@@ -711,6 +742,22 @@
 			{ Page => 'misc', Skin => 'idle', Return => 1 }
 		);
 
+	} elsif ($form->{'section'} eq 'modcommentlog') {
+		my $moddb = getObject("Slash::$constants->{m1_pluginname}");
+		if ($moddb) {
+			# we hijack "tabbed" as our cid -- pudge
+			return $moddb->dispModCommentLog('cid', $form->{'tabbed'}, {
+				show_m2s        => ($constants->{m2}
+					? (defined($form->{show_m2s})
+						? $form->{show_m2s}
+						: $user->{m2_with_comm_mod}
+					) : 0),
+				need_m2_form    => $constants->{m2},
+				need_m2_button  => $constants->{m2},
+				title           => " "
+			});
+		}
+
 	} else {
 		
 		return
@@ -731,7 +778,7 @@
 	my $url = URI->new('//e.a/?' . $form->{'data'});
 	my %params = $url->query_form;
 
-        # D2 display
+	# D2 display
 	my $user_edits_table;
 	if ($params{'formname'} eq 'd2_display') {
 		$user_edits_table = {
@@ -744,7 +791,7 @@
 		};
 	}
 
-        # D2 posting
+	# D2 posting
 	if ($params{'formname'} eq 'd2_posting') {
 		$user_edits_table = {
 			emaildisplay      => $params{'emaildisplay'} || undef,
@@ -758,7 +805,17 @@
 		};
 	}
 
-        # Messages
+	# Messages
+	if ($params{'formname'} eq 'metamoderate') {
+		if ($constants->{m2} && $user->{is_admin}) {
+			# metaModerate uses $form ... whether it should or not! -- pudge
+			@$form{keys %params} = values %params;
+			my $metamod_db = getObject('Slash::Metamod');
+			$metamod_db->metaModerate($user->{is_admin}) if $metamod_db;
+		}
+	}
+
+	# Messages
 	if ($params{'formname'} eq 'messages') {
 		my $messages  = getObject('Slash::Messages');
 		my $messagecodes = $messages->getDescriptions('messagecodes');
@@ -1043,9 +1100,9 @@
 
 	my %mainops = (
 		comments_submit_reply  => {
-			function        => \&previewReply,
+			function        => \&submitReply,
 			reskey_name     => 'comments',
-			reskey_type     => 'use',
+			reskey_type     => 'touch',
 		},
 		comments_preview_reply  => {
 			function        => \&previewReply,

Modified: slashjp/trunk/plugins/Ajax/htdocs/images/admin.js
===================================================================
--- slashjp/trunk/plugins/Ajax/htdocs/images/admin.js	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/plugins/Ajax/htdocs/images/admin.js	2008-04-01 04:03:21 UTC (rev 563)
@@ -1,7 +1,7 @@
 // $Id$
 
 function um_ajax(the_behaviors, the_events) {
-	var params =[];
+	var params = {};
 	params['op'] = 'um_ajax';
 	params['behaviors'] = the_behaviors;
 	params['events'] = the_events;
@@ -9,32 +9,20 @@
 }
 
 function um_fetch_settings() {
-	var params =[];
+	var params = {};
 	params['op'] = 'um_fetch_settings';
 	ajax_update(params, 'links-vendors-content');
 }
 
 function um_set_settings(behavior) {
-	var params =[];
+	var params = {};
 	params['op'] = 'um_set_settings';
 	params['behavior'] = behavior;
 	ajax_update(params, 'links-vendors-content');
 }
 
-function admin_signoff(stoid, type, id) {
-	var params = [];
-	var reskeyel = $('signoff-reskey-' + stoid);
-	params['op'] = 'admin_signoff';
-	params['stoid'] = stoid;
-	params['reskey'] = reskeyel.value;
-	ajax_update(params, 'signoff_' + stoid);
-	if (type == "firehose") {
-		firehose_collapse_entry(id);
-	}
-}
-
 function admin_neverdisplay(stoid, type, fhid) {
-	var params = [];
+	var params = {};
 	params['op'] = 'admin_neverdisplay';
 	params['reskey'] = reskey_static;
 	params['stoid'] = stoid;
@@ -48,22 +36,22 @@
 }
 
 function admin_submit_memory(fhid) {
-	var params = [];
+	var params = {};
 	params['op'] = 'admin_submit_memory';
 	params['reskey'] = reskey_static;
-	params['submatch'] = $('submatch-'+fhid).value;
-	params['subnote'] = $('subnote-'+fhid).value;
+	params['submatch'] = $dom('submatch-'+fhid).value;
+	params['subnote'] = $dom('subnote-'+fhid).value;
 	ajax_update(params, 'sub_mem_message-'+fhid);
 }
 
 function adminTagsCommands(id, type) {
-	var toggletags_message_id = 'toggletags-message-' + id;
-	var toggletags_message_el = $(toggletags_message_id);
+	var toggletags_message_id = '#toggletags-message-' + id;
+	var toggletags_message_el = jQuery(toggletags_message_id)[0];
 	if (toggletags_message_el) {
 		toggletags_message_el.innerHTML = 'Executing commands...';
 	}
 
-	var params = [];
+	var params = {};
 	type = type || "stories";
 	params['op'] = 'tags_admin_commands';
 	if (type == "stories") {
@@ -74,9 +62,9 @@
 		params['id'] = id;
 	}
 	params['type'] = type;
-	var tags_admin_commands_el = $('tags_admin_commands-' + id);
+	var tags_admin_commands_el = $dom('tags_admin_commands-' + id);
 	params['commands'] = tags_admin_commands_el.value;
-	var reskeyel = $('admin_commands-reskey-' + id);
+	var reskeyel = $dom('admin_commands-reskey-' + id);
 	params['reskey'] = reskeyel.value;
 	ajax_update(params, 'tags-admin-' + id);
 
@@ -84,7 +72,7 @@
 }
 
 function tagsHistory(id, type) {
-	var params = [];
+	var params = {};
 	type = type || "stories";
 	params['type'] = type;
 	params['op'] = 'tags_history';
@@ -103,17 +91,17 @@
 }
 
 function remarks_create() {
-	var reskey = $('remarks_reskey');
-	var remark = $('remarks_new');
+	var reskey = $dom('remarks_reskey');
+	var remark = $dom('remarks_new');
 	if (!remark || !remark.value || !reskey || !reskey.value) {
 		return false;
 	}
 
-	var params = [];
+	var params = {};
 	params['op']     = 'remarks_create';
 	params['remark'] = remark.value;
 	params['reskey'] = reskey.value;
-	remarks_max = $('remarks_max');
+	remarks_max = $dom('remarks_max');
 	if (remarks_max && remarks_max.value) {
 		params['limit'] = remarks_max.value;
 	}
@@ -121,7 +109,7 @@
 }
 
 function remarks_fetch(secs, limit) {
-	var params = [];
+	var params = {};
 	params['op'] = 'remarks_fetch';
 	params['limit'] = limit;
 	// run it every 30 seconds; don't need to call again
@@ -129,7 +117,7 @@
 }
 
 function remarks_popup() {
-	var params = [];
+	var params = {};
 	params['op'] = 'remarks_config';
 	var title = "Remarks Config ";
 	var buttons = createPopupButtons('<a href="#" onclick="closePopup(\'remarksconfig-popup\', 1); return false">[X]</a>');
@@ -140,11 +128,11 @@
 }
 
 function remarks_config_save() {
-	var params = [];
-	var reskey = $('remarks_reskey');
-	var min_priority = $('remarks_min_priority');
-	var limit = $('remarks_limit');
-	var filter = $('remarks_filter');
+	var params = {};
+	var reskey = $dom('remarks_reskey');
+	var min_priority = $dom('remarks_min_priority');
+	var limit = $dom('remarks_limit');
+	var filter = $dom('remarks_filter');
 	params['op'] = 'remarks_config_save';
 	if (!reskey && !reskey.value) {
 		return false;
@@ -158,7 +146,7 @@
 	if (filter) {
 		params['filter'] = filter.value;
 	}
-	var message = $('remarksconfig-message');
+	var message = $dom('remarksconfig-message');
 	if (message) {
 		message.innerHTML = "Saving...";
 	}
@@ -166,31 +154,31 @@
 }
 
 function admin_slashdbox_fetch(secs) {
-	var params = [];
+	var params = {};
 	params['op'] = 'admin_slashdbox';
 	ajax_periodic_update(secs, params, "slashdbox-content");
 }
 
 function admin_perfbox_fetch(secs) {
-	var params = [];
+	var params = {};
 	params['op'] = 'admin_perfbox';
 	ajax_periodic_update(secs, params, "performancebox-content");
 }
 
 function admin_authorbox_fetch(secs) {
-	var params = [];
+	var params = {};
 	params['op'] = 'admin_authorbox';
 	ajax_periodic_update(secs, params, "authoractivity-content");
 }
 
 function admin_storyadminbox_fetch(secs) {
-	var params = [];
+	var params = {};
 	params['op'] = 'admin_storyadminbox';
 	ajax_periodic_update(secs, params, "storyadmin-content");
 }
 
 function admin_recenttagnamesbox_fetch(secs) {
-	var params = [];
+	var params = {};
 	params['op'] = 'admin_recenttagnamesbox';
 	ajax_periodic_update(secs, params, "recenttagnames-content");
 }
@@ -202,7 +190,7 @@
 		return;
 	}
 
-	var params = [];
+	var params = {};
 	params['op'] = 'console_update'
 	var handlers = {
 		onComplete: json_handler
@@ -216,11 +204,11 @@
 }
 
 function firehose_usage() {
-	var params = [];
+	var params = {};
 	params['op'] = 'firehose_usage'
 	var interval = 300000;
 	ajax_update(params, 'firehose_usage-content');
-	setTimeout("firehose_usage()", interval);
+	setTimeout(firehose_usage, interval);
 }
 
 function make_spelling_correction(misspelled_word, form_element) {
@@ -234,7 +222,7 @@
 	// Either learning a word or making a correction.
 	if (selected_index >= 1) {
 		if (selected_index == 1) {
-			var params = [];
+			var params = {};
 			params['op'] = 'admin_learnword';
 			params['word'] = misspelled_word;
 			ajax_update(params);
@@ -265,8 +253,8 @@
 }
 
 function firehose_reject (el) {
-	var params = [];
-	var fh = $('firehose-' + el.value);
+	var params = {};
+	var fh = $dom('firehose-' + el.value);
 	params['op'] = 'firehose_reject';
 	params['id'] = el.value;
 	params['reskey'] = reskey_static;
@@ -275,19 +263,19 @@
 }
 
 function firehose_open_note(id) {
-	var nf = $('note-form-'+id);
-	var nt = $('note-text-'+id);
-	var ni = $('note-input-'+id);
+	var nf = $dom('note-form-'+id);
+	var nt = $dom('note-text-'+id);
+	var ni = $dom('note-input-'+id);
 	nf.className="";
 	ni.focus();
 	nt.className="hide";
 }
 
 function firehose_save_note(id) {
-	var nf = $('note-form-'+id);
-	var nt = $('note-text-'+id);
-	var ni = $('note-input-'+id);
-	var params = [];
+	var nf = $dom('note-form-'+id);
+	var nt = $dom('note-text-'+id);
+	var ni = $dom('note-input-'+id);
+	var params = {};
 	params['op'] = 'firehose_save_note';
 	params['note'] = ni.value;
 	params['id'] = id;
@@ -297,30 +285,43 @@
 }
 
 function firehose_get_admin_extras(id) {
-	var params=[];
+	var params = {};
 	params['id'] = id;
 	params['op'] = 'firehose_get_admin_extras';
 	var handlers = {
-		onComplete: json_handler
+		onComplete: function(transport) {
+			json_handler(transport);
+			if (firehoseIsInWindow(id)) {
+				scrollToWindowFirehose(id);
+			}
+		}
 	};
 	ajax_update(params, '', handlers);
 }
 
 function firehose_get_and_post(id) {
-	var params=[];
+	var params = {};
 	params['id']  = id;
 	params['op'] = 'firehose_get_form';
 	firehose_collapse_entry(id);
 	var handlers = {
-		onComplete: function() { $('postform-'+id).submit();}
+		onComplete: function() { $dom('postform-'+id).submit();}
 	};
 	ajax_update(params, 'postform-'+id, handlers); 
 }
 
 function appendToBodytext(text) {
-	var obj = $('admin-bodytext');
+	var obj = $dom('admin-bodytext');
 	if (obj) {
 		obj.className = "show";
 		obj.value = obj.value  + text;
 	}
 }
+
+function appendToMedia(text) {
+	var obj = $dom('admin-media');
+	if (obj) {
+		obj.className = "show";
+		obj.value = obj.value  + text;
+	}
+}

Modified: slashjp/trunk/plugins/Ajax/htdocs/images/common.js
===================================================================
--- slashjp/trunk/plugins/Ajax/htdocs/images/common.js	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/plugins/Ajax/htdocs/images/common.js	2008-04-01 04:03:21 UTC (rev 563)
@@ -1,6 +1,10 @@
 // _*_ Mode: JavaScript; tab-width: 8; indent-tabs-mode: true _*_
 // $Id$
 
+function $dom( id ) {
+	return document.getElementById(id);
+}
+
 // global settings, but a firehose might use a local settings object instead
 var firehose_settings = {};
   firehose_settings.startdate = '';
@@ -25,6 +29,8 @@
   firehose_removals = null;
   firehose_future = null;
 
+  var firehose_cur = 0;
+
 // globals we haven't yet decided to move into |firehose_settings|
 var fh_play = 0;
 var fh_is_timed_out = 0;
@@ -43,9 +49,6 @@
 var is_ie = ua.match("/MSIE/");
 
 
-// eventually add site specific constants like this to a separate .js
-var sitename = "Slashdot";
-
 function createPopup(xy, titlebar, name, contents, message, onmouseout) {
 	var body = document.getElementsByTagName("body")[0]; 
 	var div = document.createElement("div");
@@ -88,7 +91,7 @@
 }
 
 function closePopup(id, refresh) {
-	var el = $(id);
+	var el = $dom(id);
 	if (el) {
 		el.parentNode.removeChild(el);
 	}
@@ -131,8 +134,9 @@
 }
 
 function getXYForId(id, addWidth, addHeight) {
-	var div = $(id);
-	var xy = Position.cumulativeOffset(div);
+	var div = $dom(id);
+	var offset = jQuery(div).offset();
+	var xy = [ offset.left, offset.top ];
 	if (addWidth) {
 		xy[0] = xy[0] + div.offsetWidth;
 	}
@@ -143,7 +147,7 @@
 }
 
 function firehose_toggle_advpref() {
-	var obj = $('fh_advprefs');
+	var obj = $dom('fh_advprefs');
 	if (obj.className == 'hide') {
 		obj.className = "";
 	} else {
@@ -152,12 +156,12 @@
 }
 
 function firehose_open_prefs() {
-	var obj = $('fh_advprefs');
+	var obj = $dom('fh_advprefs');
 	obj.className = "";
 }
 
 function toggleId(id, first, second) {
-	var obj =$(id);
+	var obj = $dom(id);
 	if (obj.className == first) {
 		obj.className = second;
 	} else if (obj.className == second) {
@@ -168,8 +172,8 @@
 }
 
 function toggleIntro(id, toggleid) {
-	var obj = $(id);
-	var toggle = $(toggleid);
+	var obj = $dom(id);
+	var toggle = $dom(toggleid);
 	if (obj.className == 'introhide') {
 		obj.className = "intro"
 		toggle.innerHTML = "[-]";
@@ -183,7 +187,7 @@
 
 function tagsToggleStoryDiv(id, is_admin, type) {
 	var bodyid = 'toggletags-body-' + id;
-	var tagsbody = $(bodyid);
+	var tagsbody = $dom(bodyid);
 	if (tagsbody.className == 'tagshide') {
 		tagsShowBody(id, is_admin, '', type);
 	} else {
@@ -194,22 +198,22 @@
 function tagsHideBody(id) {
 	// Make the body of the tagbox vanish
 	var tagsbodyid = 'toggletags-body-' + id;
-	var tagsbody = $(tagsbodyid);
+	var tagsbody = $dom(tagsbodyid);
 	tagsbody.className = "tagshide"
 
 	// Make the title of the tagbox change back to regular
 	var titleid = 'tagbox-title-' + id;
-	var title = $(titleid);
+	var title = $dom(titleid);
 	title.className = "tagtitleclosed";
 
 	// Make the tagbox change back to regular.
 	var tagboxid = 'tagbox-' + id;
-	var tagbox = $(tagboxid);
+	var tagbox = $dom(tagboxid);
 	tagbox.className = "tags";
 
 	// Toggle the button back.
 	var tagsbuttonid = 'toggletags-button-' + id;
-	var tagsbutton = $(tagsbuttonid);
+	var tagsbutton = $dom(tagsbuttonid);
 	tagsbutton.innerHTML = "[+]";
 }
 
@@ -228,35 +232,35 @@
 	
 	// Toggle the button to show the click was received
 	var tagsbuttonid = 'toggletags-button-' + id;
-	var tagsbutton = $(tagsbuttonid);
+	var tagsbutton = $dom(tagsbuttonid);
 	tagsbutton.innerHTML = "[-]";
 
 	// Make the tagbox change to the slashbox class
 	var tagboxid = 'tagbox-' + id;
-	var tagbox = $(tagboxid);
+	var tagbox = $dom(tagboxid);
 	tagbox.className = "tags";
 
 	// Make the title of the tagbox change to white-on-green
 	var titleid = 'tagbox-title-' + id;
-	var title = $(titleid);
+	var title = $dom(titleid);
 	title.className = "tagtitleopen";
 
 	// Make the body of the tagbox visible
 	var tagsbodyid = 'toggletags-body-' + id;
-	var tagsbody = $(tagsbodyid);
+	var tagsbody = $dom(tagsbodyid);
 	
 	tagsbody.className = "tagbody";
 	
 	// If the tags-user div hasn't been filled, fill it.
 	var tagsuserid = 'tags-user-' + id;
-	var tagsuser = $(tagsuserid);
+	var tagsuser = $dom(tagsuserid);
 	if (tagsuser.innerHTML == "") {
 		// The tags-user-123 div is empty, and needs to be
 		// filled with the tags this user has already
 		// specified for this story, and a reskey to allow
 		// the user to enter more tags.
 		tagsuser.innerHTML = "Retrieving...";
-		var params = [];
+		var params = {};
 		if (type == "stories") {
 			params['op'] = 'tags_get_user_story';
 			params['sidenc'] = id;
@@ -272,7 +276,7 @@
 		var handlers = {
 			onComplete: function() { 
 				var textid = 'newtags-' + id;
-				var input = $(textid);
+				var input = $dom(textid);
 				input.focus();
 			}
 		}
@@ -287,7 +291,7 @@
 		// user is not actually an admin.
 		if (is_admin) {
 			var tagsadminid = 'tags-admin-' + id;
-			params = [];
+			params = {};
 			if (type == "stories") {
 				params['op'] = 'tags_get_admin_story';
 				params['sidenc'] = id;
@@ -308,7 +312,7 @@
 			// We can't do that by passing it in, so do it
 			// manually now.
 			var textinputid = 'newtags-' + id;
-			var textinput = $(textinputid);
+			var textinput = $dom(textinputid);
 			textinput.value = textinput.value + ' ' + newtagspreloadtext;
 			textinput.focus();
 		}
@@ -382,12 +386,12 @@
 }
 
 function createTag(tag, id, type) {
-	var params = [];
+	var params = {};
 	params['id'] = id;
 	params['type'] = type;
 	if ( fh_is_admin && ("_#)^*".indexOf(tag[0]) != -1) ) {
 	  params['op'] = 'tags_admin_commands';
-	  params['reskey'] = $('admin_commands-reskey-' + id).value;
+	  params['reskey'] = $dom('admin_commands-reskey-' + id).value;
 	  params['command'] = tag;
 	} else {
 	  params['op'] = 'tags_create_tag';
@@ -402,15 +406,15 @@
 
 function tagsCreateForStory(id) {
 	var toggletags_message_id = 'toggletags-message-' + id;
-	var toggletags_message_el = $(toggletags_message_id);
+	var toggletags_message_el = $dom(toggletags_message_id);
 	toggletags_message_el.innerHTML = 'Saving tags...';
 
-	var params = [];
+	var params = {};
 	params['op'] = 'tags_create_for_story';
 	params['sidenc'] = id;
-	var newtagsel = $('newtags-' + id);
+	var newtagsel = $dom('newtags-' + id);
 	params['tags'] = newtagsel.value;
-	var reskeyel = $('newtags-reskey-' + id);
+	var reskeyel = $dom('newtags-reskey-' + id);
 	params['reskey'] = reskeyel.value;
 
 	ajax_update(params, 'tags-user-' + id);
@@ -421,15 +425,15 @@
 
 function tagsCreateForUrl(id) {
 	var toggletags_message_id = 'toggletags-message-' + id;
-	var toggletags_message_el = $(toggletags_message_id);
+	var toggletags_message_el = $dom(toggletags_message_id);
 	toggletags_message_el.innerHTML = 'Saving tags...';
 
-	var params = [];
+	var params = {};
 	params['op'] = 'tags_create_for_url';
 	params['id'] = id;
-	var newtagsel = $('newtags-' + id);
+	var newtagsel = $dom('newtags-' + id);
 	params['tags'] = newtagsel.value;
-	var reskeyel = $('newtags-reskey-' + id);
+	var reskeyel = $dom('newtags-reskey-' + id);
 	params['reskey'] = reskeyel.value;
 
 	ajax_update(params, 'tags-user-' + id);
@@ -440,7 +444,7 @@
 
 //Firehose functions begin
 function setOneTopTagForFirehose(id, newtag) {
-	var params = [];
+	var params = {};
 	params['op'] = 'firehose_update_one_tag';
 	params['id'] = id;
 	params['tags'] = newtag;
@@ -450,15 +454,15 @@
 
 function tagsCreateForFirehose(id) {
 	var toggletags_message_id = 'toggletags-message-' + id;
-	var toggletags_message_el = $(toggletags_message_id);
+	var toggletags_message_el = $dom(toggletags_message_id);
 	toggletags_message_el.innerHTML = 'Saving tags...';
 	
-	var params = [];
+	var params = {};
 	params['op'] = 'tags_create_for_firehose';
 	params['id'] = id;
-	var newtagsel = $('newtags-' + id);
+	var newtagsel = $dom('newtags-' + id);
 	params['tags'] = newtagsel.value; 
-	var reskeyel = $('newtags-reskey-' + id);
+	var reskeyel = $dom('newtags-reskey-' + id);
 	params['reskey'] = reskeyel.value;
 
 	ajax_update(params, 'tags-user-' + id);
@@ -466,16 +470,19 @@
 }
 
 function toggle_firehose_body(id, is_admin) {
-	var params = [];
+	var params = {};
 	setFirehoseAction();
 	params['op'] = 'firehose_fetch_text';
 	params['id'] = id;
-	var fhbody = $('fhbody-'+id);
-	var fh = $('firehose-'+id);
+	var fhbody = $dom('fhbody-'+id);
+	var fh = $dom('firehose-'+id);
 	var usertype = fh_is_admin ? " adminmode" : " usermode";
 	if (fhbody.className == "empty") {
 		var handlers = {
-			onComplete: function() { 
+			onComplete: function() {
+				if(firehoseIsInWindow(id)) { 
+					scrollToWindowFirehose(id); 
+				}
 				firehose_get_admin_extras(id); 
 			}
 		};
@@ -503,7 +510,7 @@
 }
 
 function toggleFirehoseTagbox(id) {
-	var fhtb = $('fhtagbox-'+id);
+	var fhtb = $dom('fhtagbox-'+id);
 	if (fhtb.className == "hide") {
 		fhtb.className = "tagbox";
 	} else {
@@ -521,7 +528,7 @@
 		["mode", 	"full", 	"abbrev",	"full",		"fulltitle"],
 		["mode", 	"fulltitle", 	"full",		"abbrev",	"full"]
 	];
-	var params = [];
+	var params = {};
 	params['op'] = 'firehose_set_options';
 	params['reskey'] = reskey_static;
 	var theForm = document.forms["firehoseform"];
@@ -548,7 +555,7 @@
 		}
 
 		if (classname) {
-			var els = document.getElementsByClassName(classname, $('firehoselist'));
+			var els = document.getElementsByClassName(classname, $dom('firehoselist'));
 			var classval = classname;
 			if (value) {
 				classval = classval + " hide";
@@ -572,12 +579,12 @@
 		var el = pairs[i];
 		if (name == el[0] && value == el[1]) {
 			firehose_settings[name] = value;
-			if ($(el[2])) {
-				$(el[2]).id = el[3];
-				if($(el[3])) {
+			if ($dom(el[2])) {
+				$dom(el[2]).id = el[3];
+				if($dom(el[3])) {
 					var namenew = el[0];
 					var valuenew = el[4];
-					$(el[3]).firstChild.onclick = function() { firehose_set_options(namenew, valuenew); return false;}
+					$dom(el[3]).firstChild.onclick = function() { firehose_set_options(namenew, valuenew); return false;}
 				}
 			}
 		}
@@ -587,7 +594,7 @@
 		if (name == "mode") {
 			fh_view_mode = value;
 		}
-		if ($('firehoselist')) {
+		if ($dom('firehoselist')) {
 			// set page
 			page = 0;
 			
@@ -598,12 +605,12 @@
 				var myAnim = new YAHOO.util.Anim("firehoselist", attributes); 
 				myAnim.duration = 1;
 				myAnim.onComplete.subscribe(function() {
-					$('firehoselist').style.opacity = "1";
+					$dom('firehoselist').style.opacity = "1";
 				});
 				myAnim.animate();
 			}
 			// remove elements
-			setTimeout("firehose_remove_all_items()", 600);
+			setTimeout(firehose_remove_all_items, 600);
 		}
 	}
 	}
@@ -623,11 +630,11 @@
 			firehose_settings.page = 0;
 			var issuedate = firehose_settings.issue.substr(5,2) + "/" + firehose_settings.issue.substr(8,2) + "/" + firehose_settings.issue.substr(10,2);
 
-			if ($('fhcalendar')) {
-				$('fhcalendar')._widget.setDate(issuedate, "day");
+			if ($dom('fhcalendar')) {
+				$dom('fhcalendar')._widget.setDate(issuedate, "day");
 			}
-			if ($('fhcalendar_pag')) {
-				$('fhcalendar_pag')._widget.setDate(issuedate, "day");
+			if ($dom('fhcalendar_pag')) {
+				$dom('fhcalendar_pag')._widget.setDate(issuedate, "day");
 			}
 		}
 		if (name == "color") {
@@ -662,7 +669,7 @@
 }
 
 function firehose_remove_all_items() {
-	var fhl = $('firehoselist');
+	var fhl = $dom('firehoselist');
 	var children = fhl.childNodes;
 	for (var i = children.length -1 ; i >= 0; i--) {
 		var el = children[i];
@@ -677,7 +684,7 @@
 	if (!check_logged_in()) return;
 
 	setFirehoseAction();
-	var params = [];
+	var params = {};
 	var handlers = {
 		onComplete: json_handler
 	};
@@ -685,7 +692,7 @@
 	params['id'] = id;
 	params['reskey'] = reskey_static;
 	params['dir'] = dir;
-	var updown = $('updown-' + id);
+	var updown = $dom('updown-' + id);
 	ajax_update(params, '', handlers);
 	if (updown) {
 		if (dir == "+") {
@@ -702,7 +709,7 @@
 
 function firehose_remove_tab(tabid) {
 	setFirehoseAction();
-	var params = [];
+	var params = {};
 	var handlers = {
 		onComplete:  json_handler
 	};
@@ -718,39 +725,34 @@
 // firehose functions end
 
 // helper functions
-function ajax_update(params, onsucc, options, url) {
-	var h = $H(params);
+function ajax_update(request_params, id, handlers, request_url) {
+	// make an ajax request to request_url with request_params, on success,
+	//  update the innerHTML of the element with id
 
-	if (!url)
-		url = '/ajax.pl';
+	var opts = {
+		url: request_url || '/ajax.pl',
+		data: request_params,
+		type: 'POST',
+		contentType: 'application/x-www-form-urlencoded',
+	};
 
-	if (!options)
-		options = {};
+	if ( id ) {
+		opts['success'] = function(html){
+			jQuery('#'+id).html(html);
+		}
+	}
 
-	options.method = 'post';
-	options.parameters = h.toQueryString();
+	if ( handlers && handlers.onComplete ) {
+		opts['complete'] = handlers.onComplete;
+	}
 
-	var ajax = new Ajax.Updater(
-		{ success: onsucc },
-		url,
-		options
-	);
+	jQuery.ajax(opts);
 }
 
-function ajax_periodic_update(secs, params, onsucc, options, url) {
-	var h = $H(params);
-	
-	if (!url) 
-		url = '/ajax.pl';
-		
-	if (!options)
-		options = {};
-
-	options.frequency = secs;
-	options.method = 'post';
-	options.parameters = h.toQueryString();
-
-	var ajax = new Ajax.PeriodicalUpdater({ success: onsucc }, url, options);
+function ajax_periodic_update(interval_in_seconds, request_params, id, handlers, request_url) {
+	setInterval(function(){
+		ajax_update(request_params, id, handlers, request_url);
+	}, interval_in_seconds*1000);
 }
 
 function eval_response(transport) {
@@ -766,6 +768,7 @@
 function json_handler(transport) {
 	var response = eval_response(transport);
 	json_update(response);
+	return response;
 }
 
 function json_update(response) {
@@ -779,35 +782,35 @@
 
 	if (response.html) {
 		for (el in response.html) {
-			if ($(el))
-				$(el).innerHTML = response.html[el];
+			if ($dom(el))
+				$dom(el).innerHTML = response.html[el];
 		}
 		
 	} 
 
 	if (response.value) {
 		for (el in response.value) {
-			if ($(el))
-				$(el).value = response.value[el];
+			if ($dom(el))
+				$dom(el).value = response.value[el];
 		}
 	}
 
 	if (response.html_append) {
 		for (el in response.html_append) {
-			if ($(el))
-				$(el).innerHTML = $(el).innerHTML + response.html_append[el];
+			if ($dom(el))
+				$dom(el).innerHTML = $dom(el).innerHTML + response.html_append[el];
 		}
 	}
 
 	if (response.html_append_substr) {
 		for (el in response.html_append_substr) {
-			if ($(el)) {
-				var this_html = $(el).innerHTML;
-				var i = $(el).innerHTML.search(/<span class="?substr"?> ?<\/span>[\s\S]*$/i);
+			if ($dom(el)) {
+				var this_html = $dom(el).innerHTML;
+				var i = $dom(el).innerHTML.search(/<span class="?substr"?> ?<\/span>[\s\S]*$/i);
 				if (i == -1) {
-					$(el).innerHTML += response.html_append_substr[el];
+					$dom(el).innerHTML += response.html_append_substr[el];
 				} else {
-					$(el).innerHTML = $(el).innerHTML.substr(0, i) +
+					$dom(el).innerHTML = $dom(el).innerHTML.substr(0, i) +
 						response.html_append_substr[el];
 				}
 			}
@@ -830,16 +833,14 @@
 		var fh = 'firehose-' + el[1];
 		var wait_interval = 800;
 		if(el[0] == "add") {
-			if (firehose_before[el[1]] && $('firehose-' + firehose_before[el[1]])) {
-				new Insertion.After('firehose-' + firehose_before[el[1]], el[2]);
-
-
-			} else if (firehose_after[el[1]] && $('firehose-' + firehose_after[el[1]])) {
-				new Insertion.Before('firehose-' + firehose_after[el[1]], el[2]);
+			if (firehose_before[el[1]] && jQuery('#firehose-' + firehose_before[el[1]]).size()) {
+				jQuery('#firehose-' + firehose_before[el[1]]).after(el[2]);
+			} else if (firehose_after[el[1]] && jQuery('#firehose-' + firehose_after[el[1]]).size()) {
+				jQuery('#firehose-' + firehose_after[el[1]]).before(el[2]);
 			} else if (insert_new_at == "bottom") {
-				new Insertion.Bottom('firehoselist', el[2]);
+				jQuery('#firehoselist').append(el[2]);
 			} else {
-				new Insertion.Top('firehoselist', el[2]);
+				jQuery('#firehoselist').prepend(el[2]);
 			}
 		
 			var toheight = 50;
@@ -871,8 +872,8 @@
 			}
 
 			myAnim.onComplete.subscribe(function() {
-				if ($(fh)) {
-					$(fh).style.height = "";
+				if ($dom(fh)) {
+					$dom(fh).style.height = "";
 					if (fh_use_jquery) {
 						jQuery("#" + fh + " h3 a[class!='skin']").click(
 				                	function(){
@@ -886,7 +887,7 @@
 			});
 			myAnim.animate();
 		} else if (el[0] == "remove") {
-			var fh_node = $(fh);
+			var fh_node = $dom(fh);
 			if (fh_is_admin && fh_view_mode == "fulltitle" && fh_node && fh_node.className == "article" ) {
 				// Don't delete admin looking at this in expanded view
 			} else {
@@ -919,7 +920,7 @@
 					});
 					myAnim.animate(); 
 				} else {
-					var elem = $(fh);
+					var elem = $dom(fh);
 					wait_interval = 25;
 					if (elem && elem.parentNode) {
 						elem.parentNode.removeChild(elem);
@@ -927,7 +928,7 @@
 				}
 			}
 		}
-		setTimeout("firehose_handle_update()", wait_interval);
+		setTimeout(firehose_handle_update, wait_interval);
 	} else {
 		firehose_reorder();
 		firehose_get_next_updates();
@@ -936,32 +937,28 @@
 
 function firehose_reorder() {
 	if (firehose_ordered) {
-		var fhlist = $('firehoselist');
+		var fhlist = $dom('firehoselist');
 		if (fhlist) {
 			var item_count = 0;
 			for (i = 0; i < firehose_ordered.length; i++) {
 				if (/^\d+$/.test(firehose_ordered[i])) {
 					item_count++;
 				}
-				var fhel = $('firehose-' + firehose_ordered[i]);
+				var fhel = $dom('firehose-' + firehose_ordered[i]);
 				if (fhlist && fhel) {
 					fhlist.appendChild(fhel);
 				}
 				if ( firehose_future[firehose_ordered[i]] ) {
-					if ($("ttype-" + firehose_ordered[i])) {
-						$("ttype-" + firehose_ordered[i]).className = "future";	
+					if ($dom("ttype-" + firehose_ordered[i])) {
+						$dom("ttype-" + firehose_ordered[i]).className = "future";
 					}
 				} else {
-					if ($("ttype-" + firehose_ordered[i]) && $("ttype-" + firehose_ordered[i]).className == "future") {
-						$("ttype-" + firehose_ordered[i]).className = "story";	
+					if ($dom("ttype-" + firehose_ordered[i]) && $dom("ttype-" + firehose_ordered[i]).className == "future") {
+						$dom("ttype-" + firehose_ordered[i]).className = "story";
 					}
 				}
 			}
-			if (console_updating) {
-				document.title = sitename + " - Console (" + item_count + ")";
-			} else {
-				document.title = sitename + " - Firehose (" + item_count + ")";
-			}
+			document.title = "[% sitename %] - " + (console_updating ? "Console" : "Firehose") + " (" + item_count + ")";
 		}
 	}
 
@@ -971,13 +968,13 @@
 	var interval = getFirehoseUpdateInterval();
 	//alert("fh_get_next_updates");
 	fh_is_updating = 0;
-	firehose_add_update_timerid(setTimeout("firehose_get_updates()", interval));
+	firehose_add_update_timerid(setTimeout(firehose_get_updates, interval));
 }
 
 
 function firehose_get_updates_handler(transport) {
-	if ($('busy')) {
-		$('busy').className = "hide";
+	if ($dom('busy')) {
+		$dom('busy').className = "hide";
 	}
 	var response = eval_response(transport);
 	var processed = 0;
@@ -1008,7 +1005,7 @@
 }
 
 function firehose_get_item_idstring() {
-	var fhl = $('firehoselist');
+	var fhl = $dom('firehoselist');
 	var str = "";
 	var children;
 	if (fhl) {
@@ -1033,7 +1030,7 @@
 	options = options || {};
 	run_before_update();
 	if ((fh_play == 0 && !options.oneupdate) || fh_is_updating == 1) {
-		firehose_add_update_timerid(setTimeout("firehose_get_updates()", 2000));
+		firehose_add_update_timerid(setTimeout(firehose_get_updates, 2000));
 	//	alert("wait loop: " + fh_is_updating);
 		return;
 	}
@@ -1042,7 +1039,7 @@
 		while(id = fh_update_timerids.pop()) { clearTimeout(id) };
 	}
 	fh_is_updating = 1
-	var params = [];
+	var params = {};
 	var handlers = {
 		onComplete: firehose_get_updates_handler
 	};
@@ -1058,8 +1055,8 @@
 		params['embed'] = 1;
 	}
 	params['fh_pageval'] = firehose_settings.pageval;
-	if ($('busy')) {
-		$('busy').className = "";
+	if ($dom('busy')) {
+		$dom('busy').className = "";
 	}
 	ajax_update(params, '', handlers);
 }
@@ -1103,8 +1100,8 @@
 	var secs = getSecsSinceLastFirehoseAction();
 	if (secs > inactivity_timeout) {
 		fh_is_timed_out = 1;
-		if ($('message_area'))
-			$('message_area').innerHTML = "サーバの反応が良くないので自動更新は遅れます";
+		if ($dom('message_area'))
+			$dom('message_area').innerHTML = "サーバの反応が良くないので自動更新は遅れます";
 		//firehose_pause();
 	}
 }
@@ -1113,15 +1110,15 @@
 	fh_play = 1;
 	setFirehoseAction();
 	firehose_set_options('pause', '0');
-	var pausepanel = $('pauseorplay');
-	if ($('message_area'))
-		$('message_area').innerHTML = "";
+	var pausepanel = $dom('pauseorplay');
+	if ($dom('message_area'))
+		$dom('message_area').innerHTML = "";
 	if (pausepanel) {
 		pausepanel.innerHTML = "更新";
 	}
-	var pause = $('pause');
+	var pause = $dom('pause');
 	
-	var play_div = $('play');
+	var play_div = $dom('play');
 	if (play_div) {
 		play_div.className = "hide";
 	}
@@ -1130,14 +1127,18 @@
 	}
 }
 
+function is_firehose_playing() {
+  return YAHOO.util.Dom.hasClass('play', 'hide');
+}
+
 function firehose_pause() {
 	fh_play = 0;
-	var pause = $('pause');
-	var play_div = $('play');
+	var pause = $dom('pause');
+	var play_div = $dom('play');
 	pause.className = "hide";
 	play_div.className = "show";
-	if ($('pauseorplay')) {
-		$('pauseorplay').innerHTML = "停止";
+	if ($dom('pauseorplay')) {
+		$dom('pauseorplay').innerHTML = "停止";
 	}
 	firehose_set_options('pause', '1');
 }
@@ -1147,8 +1148,8 @@
 }
 
 function firehose_collapse_entry(id) {
-	var fhbody = $('fhbody-'+id);
-	var fh = $('firehose-'+id);
+	var fhbody = $dom('fhbody-'+id);
+	var fh = $dom('firehose-'+id);
 	if (fhbody && fhbody.className == "body") {
 		fhbody.className = "hide";
 	}
@@ -1160,7 +1161,7 @@
 }
 
 function firehose_remove_entry(id) {
-	var fh = $('firehose-' + id);
+	var fh = $dom('firehose-' + id);
 	if (fh) {
 		var attributes = { 
 			height: { to: 0 }
@@ -1205,7 +1206,7 @@
 		fh_slider_init_set = 1;
 	}
 	var color = fh_colors[ newVal / fh_ticksize ];
-	$('fh_slider_img').title = "Firehose filtered to " + color;
+	$dom('fh_slider_img').title = "Firehose filtered to " + color;
 	if (fh_slider_init_set) {
 		firehose_set_options("color", color)
 	}
@@ -1222,7 +1223,7 @@
 function pausePopVendorStory(id) {
 	vendor_popup_id=id;
 	closePopup('vendorStory-26-popup');
-	vendor_popup_timerids[id] = setTimeout("vendorStoryPopup()", 500);
+	vendor_popup_timerids[id] = setTimeout(vendorStoryPopup, 500);
 }
 
 function clearVendorPopupTimers() {
@@ -1242,7 +1243,7 @@
 	}
 	};
 	createPopup(getXYForId('sponsorlinks', 0, 0), title, "vendorStory-" + id, "Loading", "", closepopup );
-	var params = [];
+	var params = {};
 	params['op'] = 'getTopVendorStory';
 	params['skid'] = id;
 	ajax_update(params, "vendorStory-" + id + "-contents");
@@ -1251,7 +1252,7 @@
 function pausePopVendorStory2(id) {
 	vendor_popup_id=id;
 	closePopup('vendorStory-26-popup');
-	vendor_popup_timerids[id] = setTimeout("vendorStoryPopup2()", 500);
+	vendor_popup_timerids[id] = setTimeout(vendorStoryPopup2, 500);
 }
 
 function vendorStoryPopup2() {
@@ -1267,14 +1268,14 @@
 		}
 	};
 	createPopup(getXYForId('sponsorlinks2', 0, 0), title, "vendorStory-" + id, "Loading", "", closepopup );
-	var params = [];
+	var params = {};
 	params['op'] = 'getTopVendorStory';
 	params['skid'] = id;
 	ajax_update(params, "vendorStory-" + id + "-contents");
 }
 
 function logToDiv(id, message) {
-	var div = $(id);
+	var div = $dom(id);
 	if (div) {
 	div.innerHTML = div.innerHTML + message + "<br>";
 	}
@@ -1282,19 +1283,19 @@
 
 
 function firehose_open_tab(id) {
-	var tf = $('tab-form-'+id);
-	var tt = $('tab-text-'+id);
-	var ti = $('tab-input-'+id);
+	var tf = $dom('tab-form-'+id);
+	var tt = $dom('tab-text-'+id);
+	var ti = $dom('tab-input-'+id);
 	tf.className="";
 	ti.focus();
 	tt.className="hide";
 }
 
 function firehose_save_tab(id) {
-	var tf = $('tab-form-'+id);
-	var tt = $('tab-text-'+id);
-	var ti = $('tab-input-'+id);
-	var params = [];
+	var tf = $dom('tab-form-'+id);
+	var tt = $dom('tab-text-'+id);
+	var ti = $dom('tab-input-'+id);
+	var params = {};
 	var handlers = {
 		onComplete: json_handler 
 	};
@@ -1375,8 +1376,8 @@
 var modal_inst  = 0;
 
 function init_modal_divs() {
-	modal_cover = $('modal_cover');
-	modal_box   = $('modal_box');
+	modal_cover = $dom('modal_cover');
+	modal_box   = $dom('modal_box');
 }
 
 function install_modal() {
@@ -1392,7 +1393,7 @@
 	modal_cover.parentNode.removeChild(modal_cover);
 	modal_box.parentNode.removeChild(modal_box);
 
-	var modal_parent = $('top_parent');
+	var modal_parent = $dom('top_parent');
 	modal_parent.parentNode.insertBefore(modal_cover, modal_parent);
 	modal_parent.parentNode.insertBefore(modal_box, modal_parent);
 	modal_inst = 1;
@@ -1423,12 +1424,12 @@
 }
 
 function getModalPrefs(section, title, tabbed) {
-        document.getElementById('preference_title').innerHTML = title;
-	var params = [];
+	document.getElementById('preference_title').innerHTML = title;
+	var params = {};
 	params['op'] = 'getModalPrefs';
 	params['section'] = section;
 	params['reskey'] = reskey_static;
-        params['tabbed'] = tabbed;
+	params['tabbed'] = tabbed;
 	var handlers = {onComplete:show_modal_box};
 	ajax_update(params, 'modal_box_content', handlers);
 
@@ -1436,21 +1437,21 @@
 }
 
 function firehose_get_media_popup(id) {
-	if ($('preference_title')) {
-		$('preference_title').innerHTML = "Media";
+	if ($dom('preference_title')) {
+		$dom('preference_title').innerHTML = "Media";
 	}
-	var params = [];
+	var params = {};
 	params['op'] = 'firehose_get_media';
 	params['id'] = id;
 	show_modal_box();
-	$('modal_box_content').innerHTML = "<h4>Loading...</h4><img src='/images/spinner_large.gif'>";
+	$dom('modal_box_content').innerHTML = "<h4>Loading...</h4><img src='/images/spinner_large.gif'>";
 	ajax_update(params, 'modal_box_content');
 }
 
 function saveModalPrefs() {
-	var params = [];
+	var params = {};
 	params['op'] = 'saveModalPrefs';
-	params['data'] = Form.serialize(document.forms['modal_prefs']);
+	params['data'] = jQuery("#modal_prefs").serialize();
 	params['reskey'] = reskey_static;
 	var handlers = {
 		onComplete: function() {
@@ -1473,7 +1474,7 @@
 		sep = ",";
 	}
 
-	var params = [];
+	var params = {};
 	params['op'] = 'page_save_user_boxes';
 	params['reskey'] = reskey_static;
 	params['bids'] = all;
@@ -1496,14 +1497,14 @@
 }
 
 function toggle_filter_prefs() {
-	var fps = $('filter_play_status');
-	var fp  = $('filter_prefs');
+	var fps = $dom('filter_play_status');
+	var fp  = $dom('filter_prefs');
 	if (fps) {
 		if (fps.className == "") {
 			fps.className = "hide";
 			if (fp) {
 				fp.className = "";
-				setTimeout("firehose_slider_init()",500);
+				setTimeout(firehose_slider_init,500);
 			} 
 		} else if (fps.className == "hide") {
 			fps.className = "";
@@ -1514,3 +1515,126 @@
 	}
 
 }
+
+function admin_signoff(stoid, type, id) {
+	var params = [];
+	params['op'] = 'admin_signoff';
+	params['stoid'] = stoid;
+	params['reskey'] = reskey_static;
+	ajax_update(params, 'signoff_' + stoid);
+	if (type == "firehose") {
+		firehose_collapse_entry(id);
+	}
+}
+
+
+function scrollWindowToFirehose(fhid) {
+	var firehose_y = getOffsetTop($('firehose-' + fhid));
+	console.log(firehose_y);
+	scroll(viewWindowLeft(), firehose_y);
+}
+
+function viewWindowLeft() {
+	if (self.pageXOffset) // all except Explorer
+	{
+		return self.pageXOffset;
+	}
+	else if (document.documentElement && document.documentElement.scrollTop)
+		// Explorer 6 Strict
+	{
+		return document.documentElement.scrollLeft;
+	}
+	else if (document.body) // all other Explorers
+	{
+		return document.body.scrollLeft;
+	}
+}
+
+function getOffsetTop (el) {
+	if (!el)
+		return false;
+	var ot = el.offsetTop;
+	while((el = el.offsetParent) != null)
+		ot += el.offsetTop;
+	return ot;
+}
+
+function firehoseIsInWindow(fhid, just_head) {
+	var in_window = isInWindow($('firehose-' + fhid));
+	return in_window;
+}
+
+function isInWindow(obj) {
+	var y = getOffsetTop(obj);
+
+	if (y > viewWindowTop() && y < viewWindowBottom()) {
+		return 1;
+	}
+	return 0;
+}
+
+function viewWindowTop() {
+	if (self.pageYOffset) // all except Explorer
+	{
+		return self.pageYOffset;
+	}
+	else if (document.documentElement && document.documentElement.scrollTop)
+		// Explorer 6 Strict
+	{
+		return document.documentElement.scrollTop;
+	}
+	else if (document.body) // all other Explorers
+	{
+		return document.body.scrollTop;
+	}
+	return;
+}
+
+function viewWindowBottom() {
+	return viewWindowTop() + (window.innerHeight || document.documentElement.clientHeight);
+}
+
+function firehose_get_cur() {
+	if (!firehose_cur) {
+		firehose_cur = firehose_ordered[0];
+		firehose_set_cur(firehose_cur);
+	}
+	return firehose_cur;
+}
+
+function firehose_set_cur(id) {
+	firehose_cur = id;
+}
+
+function firehose_get_pos_of_id(id) {
+	var ret;
+	for (var i=0; i< firehose_ordered.length; i++) {
+		if (firehose_ordered[i] == id) {
+			ret = i;
+		}
+	}
+	return ret;
+}
+
+function firehose_go_next() {
+	var cur = firehose_get_cur();
+	var pos = firehose_get_pos_of_id(cur);
+	if (pos < (firehose_ordered.length - 1)) {
+		pos++;
+	}
+	firehose_set_cur(firehose_ordered[pos]);
+	scrollWindowToFirehose(firehose_cur);
+}
+
+function firehose_go_prev() {
+	var cur = firehose_get_cur();
+	var pos = firehose_get_pos_of_id(cur);
+	if (pos>0) {
+		pos--;
+	}
+	firehose_set_cur(firehose_ordered[pos]);
+	scrollWindowToFirehose(firehose_cur);
+
+}
+
+

Copied: slashjp/trunk/plugins/Ajax/htdocs/images/jquery-1.2.3.js (from rev 561, slashjp/branches/upstream/current/plugins/Ajax/htdocs/images/jquery-1.2.3.js)
===================================================================
--- slashjp/trunk/plugins/Ajax/htdocs/images/jquery-1.2.3.js	                        (rev 0)
+++ slashjp/trunk/plugins/Ajax/htdocs/images/jquery-1.2.3.js	2008-04-01 04:03:21 UTC (rev 563)
@@ -0,0 +1,11 @@
+/*
+ * jQuery 1.2.3 - New Wave Javascript
+ *
+ * Copyright (c) 2008 John Resig (jquery.com)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * $Date: 2008/03/14 15:48:06 $
+ * $Rev: 4663 $
+ */
+eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('(J(){7(1e.3N)L w=1e.3N;L E=1e.3N=J(a,b){K 1B E.2l.4T(a,b)};7(1e.$)L D=1e.$;1e.$=E;L u=/^[^<]*(<(.|\\s)+>)[^>]*$|^#(\\w+)$/;L G=/^.[^:#\\[\\.]*$/;E.1n=E.2l={4T:J(d,b){d=d||T;7(d.15){6[0]=d;6.M=1;K 6}N 7(1o d=="25"){L c=u.2O(d);7(c&&(c[1]||!b)){7(c[1])d=E.4a([c[1]],b);N{L a=T.5J(c[3]);7(a)7(a.2w!=c[3])K E().2s(d);N{6[0]=a;6.M=1;K 6}N d=[]}}N K 1B E(b).2s(d)}N 7(E.1q(d))K 1B E(T)[E.1n.21?"21":"3U"](d);K 6.6E(d.1k==1M&&d||(d.5h||d.M&&d!=1e&&!d.15&&d[0]!=10&&d[0].15)&&E.2I(d)||[d])},5h:"1.2.3",87:J(){K 6.M},M:0,22:J(a){K a==10?E.2I(6):6[a]},2F:J(b){L a=E(b);a.54=6;K a},6E:J(a){6.M=0;1M.2l.1g.1i(6,a);K 6},R:J(a,b){K E.R(6,a,b)},4X:J(b){L a=-1;6.R(J(i){7(6==b)a=i});K a},1J:J(c,a,b){L d=c;7(c.1k==4e)7(a==10)K 6.M&&E[b||"1J"](6[0],c)||10;N{d={};d[c]=a}K 6.R(J(i){Q(c 1p d)E.1J(b?6.W:6,c,E.1l(6,d[c],b,i,c))})},1j:J(b,a){7((b==\'27\'||b==\'1R\')&&2M(a)<0)a=10;K 6.1J(b,a,"2o")},1u:J(b){7(1o b!="3V"&&b!=V)K 6.4x().3t((6[0]&&6[0].2i||T).5r(b));L a="";E.R(b||6,J(){E.R(6.3p,J(){7(6.15!=8)a+=6.15!=1?6.6K:E.1n.1u([6])})});K a},5m:J(b){7(6[0])E(b,6[0].2i).5k().3o(6[0]).2c(J(){L a=6;2b(a.1C)a=a.1C;K a}).3t(6);K 6},8w:J(a){K 6.R(J(){E(6).6z().5m(a)})},8p:J(a){K 6.R(J(){E(6).5m(a)})},3t:J(){K 6.3O(18,P,S,J(a){7(6.15==1)6.38(a)})},6q:J(){K 6.3O(18,P,P,J(a){7(6.15==1)6.3o(a,6.1C)})},6o:J(){K 6.3O(18,S,S,J(a){6.1a.3o(a,6)})},5a:J(){K 6.3O(18,S,P,J(a){6.1a.3o(a,6.2B)})},3h:J(){K 6.54||E([])},2s:J(b){L c=E.2c(6,J(a){K E.2s(b,a)});K 6.2F(/[^+>] [^+>]/.17(b)||b.1f("..")>-1?E.57(c):c)},5k:J(e){L f=6.2c(J(){7(E.14.1d&&!E.3E(6)){L a=6.69(P),4Y=T.3s("1x");4Y.38(a);K E.4a([4Y.3d])[0]}N K 6.69(P)});L d=f.2s("*").4R().R(J(){7(6[F]!=10)6[F]=V});7(e===P)6.2s("*").4R().R(J(i){7(6.15==3)K;L c=E.O(6,"2R");Q(L a 1p c)Q(L b 1p c[a])E.16.1b(d[i],a,c[a][b],c[a][b].O)});K f},1E:J(b){K 6.2F(E.1q(b)&&E.3y(6,J(a,i){K b.1P(a,i)})||E.3e(b,6))},56:J(b){7(b.1k==4e)7(G.17(b))K 6.2F(E.3e(b,6,P));N b=E.3e(b,6);L a=b.M&&b[b.M-1]!==10&&!b.15;K 6.1E(J(){K a?E.33(6,b)<0:6!=b})},1b:J(a){K!a?6:6.2F(E.37(6.22(),a.1k==4e?E(a).22():a.M!=10&&(!a.12||E.12(a,"3u"))?a:[a]))},3H:J(a){K a?E.3e(a,6).M>0:S},7j:J(a){K 6.3H("."+a)},5O:J(b){7(b==10){7(6.M){L c=6[0];7(E.12(c,"2k")){L e=c.3T,5I=[],11=c.11,2X=c.U=="2k-2X";7(e<0)K V;Q(L i=2X?e:0,2f=2X?e+1:11.M;i<2f;i++){L d=11[i];7(d.2p){b=E.14.1d&&!d.9J.1A.9y?d.1u:d.1A;7(2X)K b;5I.1g(b)}}K 5I}N K(6[0].1A||"").1r(/\\r/g,"")}K 10}K 6.R(J(){7(6.15!=1)K;7(b.1k==1M&&/5u|5t/.17(6.U))6.3k=(E.33(6.1A,b)>=0||E.33(6.31,b)>=0);N 7(E.12(6,"2k")){L a=b.1k==1M?b:[b];E("98",6).R(J(){6.2p=(E.33(6.1A,a)>=0||E.33(6.1u,a)>=0)});7(!a.M)6.3T=-1}N 6.1A=b})},3q:J(a){K a==10?(6.M?6[0].3d:V):6.4x().3t(a)},6S:J(a){K 6.5a(a).1V()},6Z:J(i){K 6.2K(i,i+1)},2K:J(){K 6.2F(1M.2l.2K.1i(6,18))},2c:J(b){K 6.2F(E.2c(6,J(a,i){K b.1P(a,i,a)}))},4R:J(){K 6.1b(6.54)},O:J(d,b){L a=d.23(".");a[1]=a[1]?"."+a[1]:"";7(b==V){L c=6.5n("8P"+a[1]+"!",[a[0]]);7(c==10&&6.M)c=E.O(6[0],d);K c==V&&a[1]?6.O(a[0]):c}N K 6.1N("8K"+a[1]+"!",[a[0],b]).R(J(){E.O(6,d,b)})},35:J(a){K 6.R(J(){E.35(6,a)})},3O:J(g,f,h,d){L e=6.M>1,3n;K 6.R(J(){7(!3n){3n=E.4a(g,6.2i);7(h)3n.8D()}L b=6;7(f&&E.12(6,"1O")&&E.12(3n[0],"4v"))b=6.3S("1U")[0]||6.38(6.2i.3s("1U"));L c=E([]);E.R(3n,J(){L a=e?E(6).5k(P)[0]:6;7(E.12(a,"1m")){c=c.1b(a)}N{7(a.15==1)c=c.1b(E("1m",a).1V());d.1P(b,a)}});c.R(6A)})}};E.2l.4T.2l=E.2l;J 6A(i,a){7(a.3Q)E.3P({1c:a.3Q,3l:S,1H:"1m"});N E.5g(a.1u||a.6x||a.3d||"");7(a.1a)a.1a.34(a)}E.1s=E.1n.1s=J(){L b=18[0]||{},i=1,M=18.M,5c=S,11;7(b.1k==8d){5c=b;b=18[1]||{};i=2}7(1o b!="3V"&&1o b!="J")b={};7(M==1){b=6;i=0}Q(;i<M;i++)7((11=18[i])!=V)Q(L a 1p 11){7(b===11[a])6w;7(5c&&11[a]&&1o 11[a]=="3V"&&b[a]&&!11[a].15)b[a]=E.1s(b[a],11[a]);N 7(11[a]!=10)b[a]=11[a]}K b};L F="3N"+(1B 3v()).3L(),6t=0,5b={};L H=/z-?4X|86-?84|1w|6k|7Z-?1R/i;E.1s({7Y:J(a){1e.$=D;7(a)1e.3N=w;K E},1q:J(a){K!!a&&1o a!="25"&&!a.12&&a.1k!=1M&&/J/i.17(a+"")},3E:J(a){K a.1F&&!a.1h||a.28&&a.2i&&!a.2i.1h},5g:J(a){a=E.3g(a);7(a){L b=T.3S("6f")[0]||T.1F,1m=T.3s("1m");1m.U="1u/4m";7(E.14.1d)1m.1u=a;N 1m.38(T.5r(a));b.38(1m);b.34(1m)}},12:J(b,a){K b.12&&b.12.2E()==a.2E()},1T:{},O:J(c,d,b){c=c==1e?5b:c;L a=c[F];7(!a)a=c[F]=++6t;7(d&&!E.1T[a])E.1T[a]={};7(b!=10)E.1T[a][d]=b;K d?E.1T[a][d]:a},35:J(c,b){c=c==1e?5b:c;L a=c[F];7(b){7(E.1T[a]){2V E.1T[a][b];b="";Q(b 1p E.1T[a])1Q;7(!b)E.35(c)}}N{1S{2V c[F]}1X(e){7(c.52)c.52(F)}2V E.1T[a]}},R:J(c,a,b){7(b){7(c.M==10){Q(L d 1p c)7(a.1i(c[d],b)===S)1Q}N Q(L i=0,M=c.M;i<M;i++)7(a.1i(c[i],b)===S)1Q}N{7(c.M==10){Q(L d 1p c)7(a.1P(c[d],d,c[d])===S)1Q}N Q(L i=0,M=c.M,1A=c[0];i<M&&a.1P(1A,i,1A)!==S;1A=c[++i]){}}K c},1l:J(b,a,c,i,d){7(E.1q(a))a=a.1P(b,i);K a&&a.1k==51&&c=="2o"&&!H.17(d)?a+"2S":a},1t:{1b:J(c,b){E.R((b||"").23(/\\s+/),J(i,a){7(c.15==1&&!E.1t.3Y(c.1t,a))c.1t+=(c.1t?" ":"")+a})},1V:J(c,b){7(c.15==1)c.1t=b!=10?E.3y(c.1t.23(/\\s+/),J(a){K!E.1t.3Y(b,a)}).6a(" "):""},3Y:J(b,a){K E.33(a,(b.1t||b).3X().23(/\\s+/))>-1}},68:J(b,c,a){L e={};Q(L d 1p c){e[d]=b.W[d];b.W[d]=c[d]}a.1P(b);Q(L d 1p c)b.W[d]=e[d]},1j:J(d,e,c){7(e=="27"||e=="1R"){L b,46={43:"4W",4U:"1Z",19:"3D"},3c=e=="27"?["7O","7M"]:["7J","7I"];J 5E(){b=e=="27"?d.7H:d.7F;L a=0,2N=0;E.R(3c,J(){a+=2M(E.2o(d,"7E"+6,P))||0;2N+=2M(E.2o(d,"2N"+6+"5X",P))||0});b-=24.7C(a+2N)}7(E(d).3H(":4d"))5E();N E.68(d,46,5E);K 24.2f(0,b)}K E.2o(d,e,c)},2o:J(e,k,j){L d;J 3x(b){7(!E.14.2d)K S;L a=T.4c.4K(b,V);K!a||a.4M("3x")==""}7(k=="1w"&&E.14.1d){d=E.1J(e.W,"1w");K d==""?"1":d}7(E.14.2z&&k=="19"){L c=e.W.50;e.W.50="0 7r 7o";e.W.50=c}7(k.1D(/4g/i))k=y;7(!j&&e.W&&e.W[k])d=e.W[k];N 7(T.4c&&T.4c.4K){7(k.1D(/4g/i))k="4g";k=k.1r(/([A-Z])/g,"-$1").2h();L h=T.4c.4K(e,V);7(h&&!3x(e))d=h.4M(k);N{L f=[],2C=[];Q(L a=e;a&&3x(a);a=a.1a)2C.4J(a);Q(L i=0;i<2C.M;i++)7(3x(2C[i])){f[i]=2C[i].W.19;2C[i].W.19="3D"}d=k=="19"&&f[2C.M-1]!=V?"2H":(h&&h.4M(k))||"";Q(L i=0;i<f.M;i++)7(f[i]!=V)2C[i].W.19=f[i]}7(k=="1w"&&d=="")d="1"}N 7(e.4n){L g=k.1r(/\\-(\\w)/g,J(a,b){K b.2E()});d=e.4n[k]||e.4n[g];7(!/^\\d+(2S)?$/i.17(d)&&/^\\d/.17(d)){L l=e.W.26,3K=e.3K.26;e.3K.26=e.4n.26;e.W.26=d||0;d=e.W.7f+"2S";e.W.26=l;e.3K.26=3K}}K d},4a:J(l,h){L k=[];h=h||T;7(1o h.3s==\'10\')h=h.2i||h[0]&&h[0].2i||T;E.R(l,J(i,d){7(!d)K;7(d.1k==51)d=d.3X();7(1o d=="25"){d=d.1r(/(<(\\w+)[^>]*?)\\/>/g,J(b,a,c){K c.1D(/^(aa|a6|7e|a5|4D|7a|a0|3m|9W|9U|9S)$/i)?b:a+"></"+c+">"});L f=E.3g(d).2h(),1x=h.3s("1x");L e=!f.1f("<9P")&&[1,"<2k 74=\'74\'>","</2k>"]||!f.1f("<9M")&&[1,"<73>","</73>"]||f.1D(/^<(9G|1U|9E|9B|9x)/)&&[1,"<1O>","</1O>"]||!f.1f("<4v")&&[2,"<1O><1U>","</1U></1O>"]||(!f.1f("<9w")||!f.1f("<9v"))&&[3,"<1O><1U><4v>","</4v></1U></1O>"]||!f.1f("<7e")&&[2,"<1O><1U></1U><6V>","</6V></1O>"]||E.14.1d&&[1,"1x<1x>","</1x>"]||[0,"",""];1x.3d=e[1]+d+e[2];2b(e[0]--)1x=1x.5o;7(E.14.1d){L g=!f.1f("<1O")&&f.1f("<1U")<0?1x.1C&&1x.1C.3p:e[1]=="<1O>"&&f.1f("<1U")<0?1x.3p:[];Q(L j=g.M-1;j>=0;--j)7(E.12(g[j],"1U")&&!g[j].3p.M)g[j].1a.34(g[j]);7(/^\\s/.17(d))1x.3o(h.5r(d.1D(/^\\s*/)[0]),1x.1C)}d=E.2I(1x.3p)}7(d.M===0&&(!E.12(d,"3u")&&!E.12(d,"2k")))K;7(d[0]==10||E.12(d,"3u")||d.11)k.1g(d);N k=E.37(k,d)});K k},1J:J(d,e,c){7(!d||d.15==3||d.15==8)K 10;L f=E.3E(d)?{}:E.46;7(e=="2p"&&E.14.2d)d.1a.3T;7(f[e]){7(c!=10)d[f[e]]=c;K d[f[e]]}N 7(E.14.1d&&e=="W")K E.1J(d.W,"9u",c);N 7(c==10&&E.14.1d&&E.12(d,"3u")&&(e=="9r"||e=="9o"))K d.9m(e).6K;N 7(d.28){7(c!=10){7(e=="U"&&E.12(d,"4D")&&d.1a)6Q"U 9i 9h\'t 9g 9e";d.9b(e,""+c)}7(E.14.1d&&/6O|3Q/.17(e)&&!E.3E(d))K d.4z(e,2);K d.4z(e)}N{7(e=="1w"&&E.14.1d){7(c!=10){d.6k=1;d.1E=(d.1E||"").1r(/6M\\([^)]*\\)/,"")+(2M(c).3X()=="96"?"":"6M(1w="+c*6L+")")}K d.1E&&d.1E.1f("1w=")>=0?(2M(d.1E.1D(/1w=([^)]*)/)[1])/6L).3X():""}e=e.1r(/-([a-z])/95,J(a,b){K b.2E()});7(c!=10)d[e]=c;K d[e]}},3g:J(a){K(a||"").1r(/^\\s+|\\s+$/g,"")},2I:J(b){L a=[];7(1o b!="93")Q(L i=0,M=b.M;i<M;i++)a.1g(b[i]);N a=b.2K(0);K a},33:J(b,a){Q(L i=0,M=a.M;i<M;i++)7(a[i]==b)K i;K-1},37:J(a,b){7(E.14.1d){Q(L i=0;b[i];i++)7(b[i].15!=8)a.1g(b[i])}N Q(L i=0;b[i];i++)a.1g(b[i]);K a},57:J(a){L c=[],2r={};1S{Q(L i=0,M=a.M;i<M;i++){L b=E.O(a[i]);7(!2r[b]){2r[b]=P;c.1g(a[i])}}}1X(e){c=a}K c},3y:J(c,a,d){L b=[];Q(L i=0,M=c.M;i<M;i++)7(!d&&a(c[i],i)||d&&!a(c[i],i))b.1g(c[i]);K b},2c:J(d,a){L c=[];Q(L i=0,M=d.M;i<M;i++){L b=a(d[i],i);7(b!==V&&b!=10){7(b.1k!=1M)b=[b];c=c.71(b)}}K c}});L v=8Y.8W.2h();E.14={5K:(v.1D(/.+(?:8T|8S|8R|8O)[\\/: ]([\\d.]+)/)||[])[1],2d:/77/.17(v),2z:/2z/.17(v),1d:/1d/.17(v)&&!/2z/.17(v),48:/48/.17(v)&&!/(8L|77)/.17(v)};L y=E.14.1d?"6H":"75";E.1s({8I:!E.14.1d||T.6F=="79",46:{"Q":"8F","8E":"1t","4g":y,75:y,6H:y,3d:"3d",1t:"1t",1A:"1A",2Y:"2Y",3k:"3k",8C:"8B",2p:"2p",8A:"8z",3T:"3T",6C:"6C",28:"28",12:"12"}});E.R({6B:J(a){K a.1a},8y:J(a){K E.4u(a,"1a")},8x:J(a){K E.2Z(a,2,"2B")},8v:J(a){K E.2Z(a,2,"4t")},8u:J(a){K E.4u(a,"2B")},8t:J(a){K E.4u(a,"4t")},8s:J(a){K E.5i(a.1a.1C,a)},8r:J(a){K E.5i(a.1C)},6z:J(a){K E.12(a,"8q")?a.8o||a.8n.T:E.2I(a.3p)}},J(c,d){E.1n[c]=J(b){L a=E.2c(6,d);7(b&&1o b=="25")a=E.3e(b,a);K 6.2F(E.57(a))}});E.R({6y:"3t",8m:"6q",3o:"6o",8l:"5a",8k:"6S"},J(c,b){E.1n[c]=J(){L a=18;K 6.R(J(){Q(L i=0,M=a.M;i<M;i++)E(a[i])[b](6)})}});E.R({8j:J(a){E.1J(6,a,"");7(6.15==1)6.52(a)},8i:J(a){E.1t.1b(6,a)},8h:J(a){E.1t.1V(6,a)},8g:J(a){E.1t[E.1t.3Y(6,a)?"1V":"1b"](6,a)},1V:J(a){7(!a||E.1E(a,[6]).r.M){E("*",6).1b(6).R(J(){E.16.1V(6);E.35(6)});7(6.1a)6.1a.34(6)}},4x:J(){E(">*",6).1V();2b(6.1C)6.34(6.1C)}},J(a,b){E.1n[a]=J(){K 6.R(b,18)}});E.R(["8f","5X"],J(i,c){L b=c.2h();E.1n[b]=J(a){K 6[0]==1e?E.14.2z&&T.1h["5e"+c]||E.14.2d&&1e["8e"+c]||T.6F=="79"&&T.1F["5e"+c]||T.1h["5e"+c]:6[0]==T?24.2f(24.2f(T.1h["5d"+c],T.1F["5d"+c]),24.2f(T.1h["5L"+c],T.1F["5L"+c])):a==10?(6.M?E.1j(6[0],b):V):6.1j(b,a.1k==4e?a:a+"2S")}});L C=E.14.2d&&4s(E.14.5K)<8c?"(?:[\\\\w*4r-]|\\\\\\\\.)":"(?:[\\\\w\\8b-\\8a*4r-]|\\\\\\\\.)",6v=1B 4q("^>\\\\s*("+C+"+)"),6u=1B 4q("^("+C+"+)(#)("+C+"+)"),6s=1B 4q("^([#.]?)("+C+"*)");E.1s({6r:{"":J(a,i,m){K m[2]=="*"||E.12(a,m[2])},"#":J(a,i,m){K a.4z("2w")==m[2]},":":{89:J(a,i,m){K i<m[3]-0},88:J(a,i,m){K i>m[3]-0},2Z:J(a,i,m){K m[3]-0==i},6Z:J(a,i,m){K m[3]-0==i},3j:J(a,i){K i==0},3J:J(a,i,m,r){K i==r.M-1},6n:J(a,i){K i%2==0},6l:J(a,i){K i%2},"3j-4p":J(a){K a.1a.3S("*")[0]==a},"3J-4p":J(a){K E.2Z(a.1a.5o,1,"4t")==a},"83-4p":J(a){K!E.2Z(a.1a.5o,2,"4t")},6B:J(a){K a.1C},4x:J(a){K!a.1C},82:J(a,i,m){K(a.6x||a.81||E(a).1u()||"").1f(m[3])>=0},4d:J(a){K"1Z"!=a.U&&E.1j(a,"19")!="2H"&&E.1j(a,"4U")!="1Z"},1Z:J(a){K"1Z"==a.U||E.1j(a,"19")=="2H"||E.1j(a,"4U")=="1Z"},80:J(a){K!a.2Y},2Y:J(a){K a.2Y},3k:J(a){K a.3k},2p:J(a){K a.2p||E.1J(a,"2p")},1u:J(a){K"1u"==a.U},5u:J(a){K"5u"==a.U},5t:J(a){K"5t"==a.U},59:J(a){K"59"==a.U},3I:J(a){K"3I"==a.U},58:J(a){K"58"==a.U},6j:J(a){K"6j"==a.U},6i:J(a){K"6i"==a.U},2G:J(a){K"2G"==a.U||E.12(a,"2G")},4D:J(a){K/4D|2k|6h|2G/i.17(a.12)},3Y:J(a,i,m){K E.2s(m[3],a).M},7X:J(a){K/h\\d/i.17(a.12)},7W:J(a){K E.3y(E.3G,J(b){K a==b.Y}).M}}},6g:[/^(\\[) *@?([\\w-]+) *([!*$^~=]*) *(\'?"?)(.*?)\\4 *\\]/,/^(:)([\\w-]+)\\("?\'?(.*?(\\(.*?\\))?[^(]*?)"?\'?\\)/,1B 4q("^([:.#]*)("+C+"+)")],3e:J(a,c,b){L d,2m=[];2b(a&&a!=d){d=a;L f=E.1E(a,c,b);a=f.t.1r(/^\\s*,\\s*/,"");2m=b?c=f.r:E.37(2m,f.r)}K 2m},2s:J(t,p){7(1o t!="25")K[t];7(p&&p.15!=1&&p.15!=9)K[];p=p||T;L d=[p],2r=[],3J,12;2b(t&&3J!=t){L r=[];3J=t;t=E.3g(t);L o=S;L g=6v;L m=g.2O(t);7(m){12=m[1].2E();Q(L i=0;d[i];i++)Q(L c=d[i].1C;c;c=c.2B)7(c.15==1&&(12=="*"||c.12.2E()==12))r.1g(c);d=r;t=t.1r(g,"");7(t.1f(" ")==0)6w;o=P}N{g=/^([>+~])\\s*(\\w*)/i;7((m=g.2O(t))!=V){r=[];L l={};12=m[2].2E();m=m[1];Q(L j=0,3f=d.M;j<3f;j++){L n=m=="~"||m=="+"?d[j].2B:d[j].1C;Q(;n;n=n.2B)7(n.15==1){L h=E.O(n);7(m=="~"&&l[h])1Q;7(!12||n.12.2E()==12){7(m=="~")l[h]=P;r.1g(n)}7(m=="+")1Q}}d=r;t=E.3g(t.1r(g,""));o=P}}7(t&&!o){7(!t.1f(",")){7(p==d[0])d.4l();2r=E.37(2r,d);r=d=[p];t=" "+t.6e(1,t.M)}N{L k=6u;L m=k.2O(t);7(m){m=[0,m[2],m[3],m[1]]}N{k=6s;m=k.2O(t)}m[2]=m[2].1r(/\\\\/g,"");L f=d[d.M-1];7(m[1]=="#"&&f&&f.5J&&!E.3E(f)){L q=f.5J(m[2]);7((E.14.1d||E.14.2z)&&q&&1o q.2w=="25"&&q.2w!=m[2])q=E(\'[@2w="\'+m[2]+\'"]\',f)[0];d=r=q&&(!m[3]||E.12(q,m[3]))?[q]:[]}N{Q(L i=0;d[i];i++){L a=m[1]=="#"&&m[3]?m[3]:m[1]!=""||m[0]==""?"*":m[2];7(a=="*"&&d[i].12.2h()=="3V")a="3m";r=E.37(r,d[i].3S(a))}7(m[1]==".")r=E.55(r,m[2]);7(m[1]=="#"){L e=[];Q(L i=0;r[i];i++)7(r[i].4z("2w")==m[2]){e=[r[i]];1Q}r=e}d=r}t=t.1r(k,"")}}7(t){L b=E.1E(t,r);d=r=b.r;t=E.3g(b.t)}}7(t)d=[];7(d&&p==d[0])d.4l();2r=E.37(2r,d);K 2r},55:J(r,m,a){m=" "+m+" ";L c=[];Q(L i=0;r[i];i++){L b=(" "+r[i].1t+" ").1f(m)>=0;7(!a&&b||a&&!b)c.1g(r[i])}K c},1E:J(t,r,h){L d;2b(t&&t!=d){d=t;L p=E.6g,m;Q(L i=0;p[i];i++){m=p[i].2O(t);7(m){t=t.7V(m[0].M);m[2]=m[2].1r(/\\\\/g,"");1Q}}7(!m)1Q;7(m[1]==":"&&m[2]=="56")r=G.17(m[3])?E.1E(m[3],r,P).r:E(r).56(m[3]);N 7(m[1]==".")r=E.55(r,m[2],h);N 7(m[1]=="["){L g=[],U=m[3];Q(L i=0,3f=r.M;i<3f;i++){L a=r[i],z=a[E.46[m[2]]||m[2]];7(z==V||/6O|3Q|2p/.17(m[2]))z=E.1J(a,m[2])||\'\';7((U==""&&!!z||U=="="&&z==m[5]||U=="!="&&z!=m[5]||U=="^="&&z&&!z.1f(m[5])||U=="$="&&z.6e(z.M-m[5].M)==m[5]||(U=="*="||U=="~=")&&z.1f(m[5])>=0)^h)g.1g(a)}r=g}N 7(m[1]==":"&&m[2]=="2Z-4p"){L e={},g=[],17=/(-?)(\\d*)n((?:\\+|-)?\\d*)/.2O(m[3]=="6n"&&"2n"||m[3]=="6l"&&"2n+1"||!/\\D/.17(m[3])&&"7U+"+m[3]||m[3]),3j=(17[1]+(17[2]||1))-0,d=17[3]-0;Q(L i=0,3f=r.M;i<3f;i++){L j=r[i],1a=j.1a,2w=E.O(1a);7(!e[2w]){L c=1;Q(L n=1a.1C;n;n=n.2B)7(n.15==1)n.4k=c++;e[2w]=P}L b=S;7(3j==0){7(j.4k==d)b=P}N 7((j.4k-d)%3j==0&&(j.4k-d)/3j>=0)b=P;7(b^h)g.1g(j)}r=g}N{L f=E.6r[m[1]];7(1o f=="3V")f=f[m[2]];7(1o f=="25")f=6c("S||J(a,i){K "+f+";}");r=E.3y(r,J(a,i){K f(a,i,m,r)},h)}}K{r:r,t:t}},4u:J(b,c){L d=[];L a=b[c];2b(a&&a!=T){7(a.15==1)d.1g(a);a=a[c]}K d},2Z:J(a,e,c,b){e=e||1;L d=0;Q(;a;a=a[c])7(a.15==1&&++d==e)1Q;K a},5i:J(n,a){L r=[];Q(;n;n=n.2B){7(n.15==1&&(!a||n!=a))r.1g(n)}K r}});E.16={1b:J(f,i,g,e){7(f.15==3||f.15==8)K;7(E.14.1d&&f.53!=10)f=1e;7(!g.2D)g.2D=6.2D++;7(e!=10){L h=g;g=J(){K h.1i(6,18)};g.O=e;g.2D=h.2D}L j=E.O(f,"2R")||E.O(f,"2R",{}),1v=E.O(f,"1v")||E.O(f,"1v",J(){L a;7(1o E=="10"||E.16.5f)K a;a=E.16.1v.1i(18.3R.Y,18);K a});1v.Y=f;E.R(i.23(/\\s+/),J(c,b){L a=b.23(".");b=a[0];g.U=a[1];L d=j[b];7(!d){d=j[b]={};7(!E.16.2y[b]||E.16.2y[b].4j.1P(f)===S){7(f.3F)f.3F(b,1v,S);N 7(f.6b)f.6b("4i"+b,1v)}}d[g.2D]=g;E.16.2a[b]=P});f=V},2D:1,2a:{},1V:J(e,h,f){7(e.15==3||e.15==8)K;L i=E.O(e,"2R"),29,4X;7(i){7(h==10||(1o h=="25"&&h.7T(0)=="."))Q(L g 1p i)6.1V(e,g+(h||""));N{7(h.U){f=h.2q;h=h.U}E.R(h.23(/\\s+/),J(b,a){L c=a.23(".");a=c[0];7(i[a]){7(f)2V i[a][f.2D];N Q(f 1p i[a])7(!c[1]||i[a][f].U==c[1])2V i[a][f];Q(29 1p i[a])1Q;7(!29){7(!E.16.2y[a]||E.16.2y[a].4h.1P(e)===S){7(e.67)e.67(a,E.O(e,"1v"),S);N 7(e.66)e.66("4i"+a,E.O(e,"1v"))}29=V;2V i[a]}}})}Q(29 1p i)1Q;7(!29){L d=E.O(e,"1v");7(d)d.Y=V;E.35(e,"2R");E.35(e,"1v")}}},1N:J(g,c,d,f,h){c=E.2I(c||[]);7(g.1f("!")>=0){g=g.2K(0,-1);L a=P}7(!d){7(6.2a[g])E("*").1b([1e,T]).1N(g,c)}N{7(d.15==3||d.15==8)K 10;L b,29,1n=E.1q(d[g]||V),16=!c[0]||!c[0].36;7(16)c.4J(6.4Z({U:g,2L:d}));c[0].U=g;7(a)c[0].65=P;7(E.1q(E.O(d,"1v")))b=E.O(d,"1v").1i(d,c);7(!1n&&d["4i"+g]&&d["4i"+g].1i(d,c)===S)b=S;7(16)c.4l();7(h&&E.1q(h)){29=h.1i(d,b==V?c:c.71(b));7(29!==10)b=29}7(1n&&f!==S&&b!==S&&!(E.12(d,\'a\')&&g=="4V")){6.5f=P;1S{d[g]()}1X(e){}}6.5f=S}K b},1v:J(c){L a;c=E.16.4Z(c||1e.16||{});L b=c.U.23(".");c.U=b[0];L f=E.O(6,"2R")&&E.O(6,"2R")[c.U],42=1M.2l.2K.1P(18,1);42.4J(c);Q(L j 1p f){L d=f[j];42[0].2q=d;42[0].O=d.O;7(!b[1]&&!c.65||d.U==b[1]){L e=d.1i(6,42);7(a!==S)a=e;7(e===S){c.36();c.44()}}}7(E.14.1d)c.2L=c.36=c.44=c.2q=c.O=V;K a},4Z:J(c){L a=c;c=E.1s({},a);c.36=J(){7(a.36)a.36();a.7S=S};c.44=J(){7(a.44)a.44();a.7R=P};7(!c.2L)c.2L=c.7Q||T;7(c.2L.15==3)c.2L=a.2L.1a;7(!c.4S&&c.5w)c.4S=c.5w==c.2L?c.7P:c.5w;7(c.64==V&&c.63!=V){L b=T.1F,1h=T.1h;c.64=c.63+(b&&b.2v||1h&&1h.2v||0)-(b.62||0);c.7N=c.7L+(b&&b.2x||1h&&1h.2x||0)-(b.60||0)}7(!c.3c&&((c.4f||c.4f===0)?c.4f:c.5Z))c.3c=c.4f||c.5Z;7(!c.7b&&c.5Y)c.7b=c.5Y;7(!c.3c&&c.2G)c.3c=(c.2G&1?1:(c.2G&2?3:(c.2G&4?2:0)));K c},2y:{21:{4j:J(){5M();K},4h:J(){K}},3C:{4j:J(){7(E.14.1d)K S;E(6).2j("4P",E.16.2y.3C.2q);K P},4h:J(){7(E.14.1d)K S;E(6).3w("4P",E.16.2y.3C.2q);K P},2q:J(a){7(I(a,6))K P;18[0].U="3C";K E.16.1v.1i(6,18)}},3B:{4j:J(){7(E.14.1d)K S;E(6).2j("4O",E.16.2y.3B.2q);K P},4h:J(){7(E.14.1d)K S;E(6).3w("4O",E.16.2y.3B.2q);K P},2q:J(a){7(I(a,6))K P;18[0].U="3B";K E.16.1v.1i(6,18)}}}};E.1n.1s({2j:J(c,a,b){K c=="4H"?6.2X(c,a,b):6.R(J(){E.16.1b(6,c,b||a,b&&a)})},2X:J(d,b,c){K 6.R(J(){E.16.1b(6,d,J(a){E(6).3w(a);K(c||b).1i(6,18)},c&&b)})},3w:J(a,b){K 6.R(J(){E.16.1V(6,a,b)})},1N:J(c,a,b){K 6.R(J(){E.16.1N(c,a,6,P,b)})},5n:J(c,a,b){7(6[0])K E.16.1N(c,a,6[0],S,b);K 10},2g:J(){L b=18;K 6.4V(J(a){6.4N=0==6.4N?1:0;a.36();K b[6.4N].1i(6,18)||S})},7D:J(a,b){K 6.2j(\'3C\',a).2j(\'3B\',b)},21:J(a){5M();7(E.2Q)a.1P(T,E);N E.3A.1g(J(){K a.1P(6,E)});K 6}});E.1s({2Q:S,3A:[],21:J(){7(!E.2Q){E.2Q=P;7(E.3A){E.R(E.3A,J(){6.1i(T)});E.3A=V}E(T).5n("21")}}});L x=S;J 5M(){7(x)K;x=P;7(T.3F&&!E.14.2z)T.3F("5W",E.21,S);7(E.14.1d&&1e==3b)(J(){7(E.2Q)K;1S{T.1F.7B("26")}1X(3a){3z(18.3R,0);K}E.21()})();7(E.14.2z)T.3F("5W",J(){7(E.2Q)K;Q(L i=0;i<T.4L.M;i++)7(T.4L[i].2Y){3z(18.3R,0);K}E.21()},S);7(E.14.2d){L a;(J(){7(E.2Q)K;7(T.39!="5V"&&T.39!="1y"){3z(18.3R,0);K}7(a===10)a=E("W, 7a[7A=7z]").M;7(T.4L.M!=a){3z(18.3R,0);K}E.21()})()}E.16.1b(1e,"3U",E.21)}E.R(("7y,7x,3U,7w,5d,4H,4V,7v,"+"7G,7u,7t,4P,4O,7s,2k,"+"58,7K,7q,7p,3a").23(","),J(i,b){E.1n[b]=J(a){K a?6.2j(b,a):6.1N(b)}});L I=J(a,c){L b=a.4S;2b(b&&b!=c)1S{b=b.1a}1X(3a){b=c}K b==c};E(1e).2j("4H",J(){E("*").1b(T).3w()});E.1n.1s({3U:J(g,d,c){7(E.1q(g))K 6.2j("3U",g);L e=g.1f(" ");7(e>=0){L i=g.2K(e,g.M);g=g.2K(0,e)}c=c||J(){};L f="4Q";7(d)7(E.1q(d)){c=d;d=V}N{d=E.3m(d);f="61"}L h=6;E.3P({1c:g,U:f,1H:"3q",O:d,1y:J(a,b){7(b=="1W"||b=="5U")h.3q(i?E("<1x/>").3t(a.4b.1r(/<1m(.|\\s)*?\\/1m>/g,"")).2s(i):a.4b);h.R(c,[a.4b,b,a])}});K 6},7n:J(){K E.3m(6.5T())},5T:J(){K 6.2c(J(){K E.12(6,"3u")?E.2I(6.7m):6}).1E(J(){K 6.31&&!6.2Y&&(6.3k||/2k|6h/i.17(6.12)||/1u|1Z|3I/i.17(6.U))}).2c(J(i,c){L b=E(6).5O();K b==V?V:b.1k==1M?E.2c(b,J(a,i){K{31:c.31,1A:a}}):{31:c.31,1A:b}}).22()}});E.R("5S,6d,5R,6D,5Q,6m".23(","),J(i,o){E.1n[o]=J(f){K 6.2j(o,f)}});L B=(1B 3v).3L();E.1s({22:J(d,b,a,c){7(E.1q(b)){a=b;b=V}K E.3P({U:"4Q",1c:d,O:b,1W:a,1H:c})},7l:J(b,a){K E.22(b,V,a,"1m")},7k:J(c,b,a){K E.22(c,b,a,"3i")},7i:J(d,b,a,c){7(E.1q(b)){a=b;b={}}K E.3P({U:"61",1c:d,O:b,1W:a,1H:c})},85:J(a){E.1s(E.4I,a)},4I:{2a:P,U:"4Q",2U:0,5P:"4o/x-7h-3u-7g",5N:P,3l:P,O:V,6p:V,3I:V,49:{3M:"4o/3M, 1u/3M",3q:"1u/3q",1m:"1u/4m, 4o/4m",3i:"4o/3i, 1u/4m",1u:"1u/a7",4G:"*/*"}},4F:{},3P:J(s){L f,2W=/=\\?(&|$)/g,1z,O;s=E.1s(P,s,E.1s(P,{},E.4I,s));7(s.O&&s.5N&&1o s.O!="25")s.O=E.3m(s.O);7(s.1H=="4E"){7(s.U.2h()=="22"){7(!s.1c.1D(2W))s.1c+=(s.1c.1D(/\\?/)?"&":"?")+(s.4E||"7d")+"=?"}N 7(!s.O||!s.O.1D(2W))s.O=(s.O?s.O+"&":"")+(s.4E||"7d")+"=?";s.1H="3i"}7(s.1H=="3i"&&(s.O&&s.O.1D(2W)||s.1c.1D(2W))){f="4E"+B++;7(s.O)s.O=(s.O+"").1r(2W,"="+f+"$1");s.1c=s.1c.1r(2W,"="+f+"$1");s.1H="1m";1e[f]=J(a){O=a;1W();1y();1e[f]=10;1S{2V 1e[f]}1X(e){}7(h)h.34(g)}}7(s.1H=="1m"&&s.1T==V)s.1T=S;7(s.1T===S&&s.U.2h()=="22"){L i=(1B 3v()).3L();L j=s.1c.1r(/(\\?|&)4r=.*?(&|$)/,"$a4="+i+"$2");s.1c=j+((j==s.1c)?(s.1c.1D(/\\?/)?"&":"?")+"4r="+i:"")}7(s.O&&s.U.2h()=="22"){s.1c+=(s.1c.1D(/\\?/)?"&":"?")+s.O;s.O=V}7(s.2a&&!E.5H++)E.16.1N("5S");7((!s.1c.1f("a3")||!s.1c.1f("//"))&&s.1H=="1m"&&s.U.2h()=="22"){L h=T.3S("6f")[0];L g=T.3s("1m");g.3Q=s.1c;7(s.7c)g.a2=s.7c;7(!f){L l=S;g.9Z=g.9Y=J(){7(!l&&(!6.39||6.39=="5V"||6.39=="1y")){l=P;1W();1y();h.34(g)}}}h.38(g);K 10}L m=S;L k=1e.78?1B 78("9X.9V"):1B 76();k.9T(s.U,s.1c,s.3l,s.6p,s.3I);1S{7(s.O)k.4C("9R-9Q",s.5P);7(s.5C)k.4C("9O-5A-9N",E.4F[s.1c]||"9L, 9K 9I 9H 5z:5z:5z 9F");k.4C("X-9C-9A","76");k.4C("9z",s.1H&&s.49[s.1H]?s.49[s.1H]+", */*":s.49.4G)}1X(e){}7(s.6Y)s.6Y(k);7(s.2a)E.16.1N("6m",[k,s]);L c=J(a){7(!m&&k&&(k.39==4||a=="2U")){m=P;7(d){6I(d);d=V}1z=a=="2U"&&"2U"||!E.6X(k)&&"3a"||s.5C&&E.6J(k,s.1c)&&"5U"||"1W";7(1z=="1W"){1S{O=E.6W(k,s.1H)}1X(e){1z="5x"}}7(1z=="1W"){L b;1S{b=k.5q("6U-5A")}1X(e){}7(s.5C&&b)E.4F[s.1c]=b;7(!f)1W()}N E.5v(s,k,1z);1y();7(s.3l)k=V}};7(s.3l){L d=53(c,13);7(s.2U>0)3z(J(){7(k){k.9t();7(!m)c("2U")}},s.2U)}1S{k.9s(s.O)}1X(e){E.5v(s,k,V,e)}7(!s.3l)c();J 1W(){7(s.1W)s.1W(O,1z);7(s.2a)E.16.1N("5Q",[k,s])}J 1y(){7(s.1y)s.1y(k,1z);7(s.2a)E.16.1N("5R",[k,s]);7(s.2a&&!--E.5H)E.16.1N("6d")}K k},5v:J(s,a,b,e){7(s.3a)s.3a(a,b,e);7(s.2a)E.16.1N("6D",[a,s,e])},5H:0,6X:J(r){1S{K!r.1z&&9q.9p=="59:"||(r.1z>=6T&&r.1z<9n)||r.1z==6R||r.1z==9l||E.14.2d&&r.1z==10}1X(e){}K S},6J:J(a,c){1S{L b=a.5q("6U-5A");K a.1z==6R||b==E.4F[c]||E.14.2d&&a.1z==10}1X(e){}K S},6W:J(r,b){L c=r.5q("9k-U");L d=b=="3M"||!b&&c&&c.1f("3M")>=0;L a=d?r.9j:r.4b;7(d&&a.1F.28=="5x")6Q"5x";7(b=="1m")E.5g(a);7(b=="3i")a=6c("("+a+")");K a},3m:J(a){L s=[];7(a.1k==1M||a.5h)E.R(a,J(){s.1g(3r(6.31)+"="+3r(6.1A))});N Q(L j 1p a)7(a[j]&&a[j].1k==1M)E.R(a[j],J(){s.1g(3r(j)+"="+3r(6))});N s.1g(3r(j)+"="+3r(a[j]));K s.6a("&").1r(/%20/g,"+")}});E.1n.1s({1G:J(c,b){K c?6.2e({1R:"1G",27:"1G",1w:"1G"},c,b):6.1E(":1Z").R(J(){6.W.19=6.5s||"";7(E.1j(6,"19")=="2H"){L a=E("<"+6.28+" />").6y("1h");6.W.19=a.1j("19");7(6.W.19=="2H")6.W.19="3D";a.1V()}}).3h()},1I:J(b,a){K b?6.2e({1R:"1I",27:"1I",1w:"1I"},b,a):6.1E(":4d").R(J(){6.5s=6.5s||E.1j(6,"19");6.W.19="2H"}).3h()},6N:E.1n.2g,2g:J(a,b){K E.1q(a)&&E.1q(b)?6.6N(a,b):a?6.2e({1R:"2g",27:"2g",1w:"2g"},a,b):6.R(J(){E(6)[E(6).3H(":1Z")?"1G":"1I"]()})},9f:J(b,a){K 6.2e({1R:"1G"},b,a)},9d:J(b,a){K 6.2e({1R:"1I"},b,a)},9c:J(b,a){K 6.2e({1R:"2g"},b,a)},9a:J(b,a){K 6.2e({1w:"1G"},b,a)},99:J(b,a){K 6.2e({1w:"1I"},b,a)},97:J(c,a,b){K 6.2e({1w:a},c,b)},2e:J(l,k,j,h){L i=E.6P(k,j,h);K 6[i.2P===S?"R":"2P"](J(){7(6.15!=1)K S;L g=E.1s({},i);L f=E(6).3H(":1Z"),4A=6;Q(L p 1p l){7(l[p]=="1I"&&f||l[p]=="1G"&&!f)K E.1q(g.1y)&&g.1y.1i(6);7(p=="1R"||p=="27"){g.19=E.1j(6,"19");g.32=6.W.32}}7(g.32!=V)6.W.32="1Z";g.40=E.1s({},l);E.R(l,J(c,a){L e=1B E.2t(4A,g,c);7(/2g|1G|1I/.17(a))e[a=="2g"?f?"1G":"1I":a](l);N{L b=a.3X().1D(/^([+-]=)?([\\d+-.]+)(.*)$/),1Y=e.2m(P)||0;7(b){L d=2M(b[2]),2A=b[3]||"2S";7(2A!="2S"){4A.W[c]=(d||1)+2A;1Y=((d||1)/e.2m(P))*1Y;4A.W[c]=1Y+2A}7(b[1])d=((b[1]=="-="?-1:1)*d)+1Y;e.45(1Y,d,2A)}N e.45(1Y,a,"")}});K P})},2P:J(a,b){7(E.1q(a)||(a&&a.1k==1M)){b=a;a="2t"}7(!a||(1o a=="25"&&!b))K A(6[0],a);K 6.R(J(){7(b.1k==1M)A(6,a,b);N{A(6,a).1g(b);7(A(6,a).M==1)b.1i(6)}})},94:J(b,c){L a=E.3G;7(b)6.2P([]);6.R(J(){Q(L i=a.M-1;i>=0;i--)7(a[i].Y==6){7(c)a[i](P);a.72(i,1)}});7(!c)6.5p();K 6}});L A=J(b,c,a){7(!b)K 10;c=c||"2t";L q=E.O(b,c+"2P");7(!q||a)q=E.O(b,c+"2P",a?E.2I(a):[]);K q};E.1n.5p=J(a){a=a||"2t";K 6.R(J(){L q=A(6,a);q.4l();7(q.M)q[0].1i(6)})};E.1s({6P:J(b,a,c){L d=b&&b.1k==92?b:{1y:c||!c&&a||E.1q(b)&&b,2u:b,3Z:c&&a||a&&a.1k!=91&&a};d.2u=(d.2u&&d.2u.1k==51?d.2u:{90:8Z,9D:6T}[d.2u])||8X;d.5y=d.1y;d.1y=J(){7(d.2P!==S)E(6).5p();7(E.1q(d.5y))d.5y.1i(6)};K d},3Z:{70:J(p,n,b,a){K b+a*p},5j:J(p,n,b,a){K((-24.8V(p*24.8U)/2)+0.5)*a+b}},3G:[],3W:V,2t:J(b,c,a){6.11=c;6.Y=b;6.1l=a;7(!c.47)c.47={}}});E.2t.2l={4y:J(){7(6.11.30)6.11.30.1i(6.Y,[6.2J,6]);(E.2t.30[6.1l]||E.2t.30.4G)(6);7(6.1l=="1R"||6.1l=="27")6.Y.W.19="3D"},2m:J(a){7(6.Y[6.1l]!=V&&6.Y.W[6.1l]==V)K 6.Y[6.1l];L r=2M(E.1j(6.Y,6.1l,a));K r&&r>-8Q?r:2M(E.2o(6.Y,6.1l))||0},45:J(c,b,d){6.5B=(1B 3v()).3L();6.1Y=c;6.3h=b;6.2A=d||6.2A||"2S";6.2J=6.1Y;6.4B=6.4w=0;6.4y();L e=6;J t(a){K e.30(a)}t.Y=6.Y;E.3G.1g(t);7(E.3W==V){E.3W=53(J(){L a=E.3G;Q(L i=0;i<a.M;i++)7(!a[i]())a.72(i--,1);7(!a.M){6I(E.3W);E.3W=V}},13)}},1G:J(){6.11.47[6.1l]=E.1J(6.Y.W,6.1l);6.11.1G=P;6.45(0,6.2m());7(6.1l=="27"||6.1l=="1R")6.Y.W[6.1l]="8N";E(6.Y).1G()},1I:J(){6.11.47[6.1l]=E.1J(6.Y.W,6.1l);6.11.1I=P;6.45(6.2m(),0)},30:J(a){L t=(1B 3v()).3L();7(a||t>6.11.2u+6.5B){6.2J=6.3h;6.4B=6.4w=1;6.4y();6.11.40[6.1l]=P;L b=P;Q(L i 1p 6.11.40)7(6.11.40[i]!==P)b=S;7(b){7(6.11.19!=V){6.Y.W.32=6.11.32;6.Y.W.19=6.11.19;7(E.1j(6.Y,"19")=="2H")6.Y.W.19="3D"}7(6.11.1I)6.Y.W.19="2H";7(6.11.1I||6.11.1G)Q(L p 1p 6.11.40)E.1J(6.Y.W,p,6.11.47[p])}7(b&&E.1q(6.11.1y))6.11.1y.1i(6.Y);K S}N{L n=t-6.5B;6.4w=n/6.11.2u;6.4B=E.3Z[6.11.3Z||(E.3Z.5j?"5j":"70")](6.4w,n,0,1,6.11.2u);6.2J=6.1Y+((6.3h-6.1Y)*6.4B);6.4y()}K P}};E.2t.30={2v:J(a){a.Y.2v=a.2J},2x:J(a){a.Y.2x=a.2J},1w:J(a){E.1J(a.Y.W,"1w",a.2J)},4G:J(a){a.Y.W[a.1l]=a.2J+a.2A}};E.1n.5L=J(){L b=0,3b=0,Y=6[0],5l;7(Y)8M(E.14){L d=Y.1a,41=Y,1K=Y.1K,1L=Y.2i,5D=2d&&4s(5K)<8J&&!/a1/i.17(v),2T=E.1j(Y,"43")=="2T";7(Y.6G){L c=Y.6G();1b(c.26+24.2f(1L.1F.2v,1L.1h.2v),c.3b+24.2f(1L.1F.2x,1L.1h.2x));1b(-1L.1F.62,-1L.1F.60)}N{1b(Y.5G,Y.5F);2b(1K){1b(1K.5G,1K.5F);7(48&&!/^t(8H|d|h)$/i.17(1K.28)||2d&&!5D)2N(1K);7(!2T&&E.1j(1K,"43")=="2T")2T=P;41=/^1h$/i.17(1K.28)?41:1K;1K=1K.1K}2b(d&&d.28&&!/^1h|3q$/i.17(d.28)){7(!/^8G|1O.*$/i.17(E.1j(d,"19")))1b(-d.2v,-d.2x);7(48&&E.1j(d,"32")!="4d")2N(d);d=d.1a}7((5D&&(2T||E.1j(41,"43")=="4W"))||(48&&E.1j(41,"43")!="4W"))1b(-1L.1h.5G,-1L.1h.5F);7(2T)1b(24.2f(1L.1F.2v,1L.1h.2v),24.2f(1L.1F.2x,1L.1h.2x))}5l={3b:3b,26:b}}J 2N(a){1b(E.2o(a,"a8",P),E.2o(a,"a9",P))}J 1b(l,t){b+=4s(l)||0;3b+=4s(t)||0}K 5l}})();',62,631,'||||||this|if||||||||||||||||||||||||||||||||||||||function|return|var|length|else|data|true|for|each|false|document|type|null|style||elem||undefined|options|nodeName||browser|nodeType|event|test|arguments|display|parentNode|add|url|msie|window|indexOf|push|body|apply|css|constructor|prop|script|fn|typeof|in|isFunction|replace|extend|className|text|handle|opacity|div|complete|status|value|new|firstChild|match|filter|documentElement|show|dataType|hide|attr|offsetParent|doc|Array|trigger|table|call|break|height|try|cache|tbody|remove|success|catch|start|hidden||ready|get|split|Math|string|left|width|tagName|ret|global|while|map|safari|animate|max|toggle|toLowerCase|ownerDocument|bind|select|prototype|cur||curCSS|selected|handler|done|find|fx|duration|scrollLeft|id|scrollTop|special|opera|unit|nextSibling|stack|guid|toUpperCase|pushStack|button|none|makeArray|now|slice|target|parseFloat|border|exec|queue|isReady|events|px|fixed|timeout|delete|jsre|one|disabled|nth|step|name|overflow|inArray|removeChild|removeData|preventDefault|merge|appendChild|readyState|error|top|which|innerHTML|multiFilter|rl|trim|end|json|first|checked|async|param|elems|insertBefore|childNodes|html|encodeURIComponent|createElement|append|form|Date|unbind|color|grep|setTimeout|readyList|mouseleave|mouseenter|block|isXMLDoc|addEventListener|timers|is|password|last|runtimeStyle|getTime|xml|jQuery|domManip|ajax|src|callee|getElementsByTagName|selectedIndex|load|object|timerId|toString|has|easing|curAnim|offsetChild|args|position|stopPropagation|custom|props|orig|mozilla|accepts|clean|responseText|defaultView|visible|String|charCode|float|teardown|on|setup|nodeIndex|shift|javascript|currentStyle|application|child|RegExp|_|parseInt|previousSibling|dir|tr|state|empty|update|getAttribute|self|pos|setRequestHeader|input|jsonp|lastModified|_default|unload|ajaxSettings|unshift|getComputedStyle|styleSheets|getPropertyValue|lastToggle|mouseout|mouseover|GET|andSelf|relatedTarget|init|visibility|click|absolute|index|container|fix|outline|Number|removeAttribute|setInterval|prevObject|classFilter|not|unique|submit|file|after|windowData|deep|scroll|client|triggered|globalEval|jquery|sibling|swing|clone|results|wrapAll|triggerHandler|lastChild|dequeue|getResponseHeader|createTextNode|oldblock|checkbox|radio|handleError|fromElement|parsererror|old|00|Modified|startTime|ifModified|safari2|getWH|offsetTop|offsetLeft|active|values|getElementById|version|offset|bindReady|processData|val|contentType|ajaxSuccess|ajaxComplete|ajaxStart|serializeArray|notmodified|loaded|DOMContentLoaded|Width|ctrlKey|keyCode|clientTop|POST|clientLeft|clientX|pageX|exclusive|detachEvent|removeEventListener|swap|cloneNode|join|attachEvent|eval|ajaxStop|substr|head|parse|textarea|reset|image|zoom|odd|ajaxSend|even|before|username|prepend|expr|quickClass|uuid|quickID|quickChild|continue|textContent|appendTo|contents|evalScript|parent|defaultValue|ajaxError|setArray|compatMode|getBoundingClientRect|styleFloat|clearInterval|httpNotModified|nodeValue|100|alpha|_toggle|href|speed|throw|304|replaceWith|200|Last|colgroup|httpData|httpSuccess|beforeSend|eq|linear|concat|splice|fieldset|multiple|cssFloat|XMLHttpRequest|webkit|ActiveXObject|CSS1Compat|link|metaKey|scriptCharset|callback|col|pixelLeft|urlencoded|www|post|hasClass|getJSON|getScript|elements|serialize|black|keyup|keypress|solid|change|mousemove|mouseup|dblclick|resize|focus|blur|stylesheet|rel|doScroll|round|hover|padding|offsetHeight|mousedown|offsetWidth|Bottom|Top|keydown|clientY|Right|pageY|Left|toElement|srcElement|cancelBubble|returnValue|charAt|0n|substring|animated|header|noConflict|line|enabled|innerText|contains|only|weight|ajaxSetup|font|size|gt|lt|uFFFF|u0128|417|Boolean|inner|Height|toggleClass|removeClass|addClass|removeAttr|replaceAll|insertAfter|prependTo|contentWindow|contentDocument|wrap|iframe|children|siblings|prevAll|nextAll|prev|wrapInner|next|parents|maxLength|maxlength|readOnly|readonly|reverse|class|htmlFor|inline|able|boxModel|522|setData|compatible|with|1px|ie|getData|10000|ra|it|rv|PI|cos|userAgent|400|navigator|600|slow|Function|Object|array|stop|ig|NaN|fadeTo|option|fadeOut|fadeIn|setAttribute|slideToggle|slideUp|changed|slideDown|be|can|property|responseXML|content|1223|getAttributeNode|300|method|protocol|location|action|send|abort|cssText|th|td|cap|specified|Accept|With|colg|Requested|fast|tfoot|GMT|thead|1970|Jan|attributes|01|Thu|leg|Since|If|opt|Type|Content|embed|open|area|XMLHTTP|hr|Microsoft|onreadystatechange|onload|meta|adobeair|charset|http|1_|img|br|plain|borderLeftWidth|borderTopWidth|abbr'.split('|'),0,{}))

Modified: slashjp/trunk/plugins/Ajax/htdocs/images/nodnix.js
===================================================================
--- slashjp/trunk/plugins/Ajax/htdocs/images/nodnix.js	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/plugins/Ajax/htdocs/images/nodnix.js	2008-04-01 04:03:21 UTC (rev 563)
@@ -100,6 +100,17 @@
 		firehose_up_down(g_nodnix_item_id, up_down);
 }
 
+function nodnix_not_tag( old_tag ) {
+  var new_tag = old_tag[0]=='!' ? old_tag.slice(1) : '!'+old_tag;
+	createTag(new_tag, g_nodnix_item_id, "firehose");
+	var tag_list = _get_nodnix('ol');
+	  // XXX not a good idea if the tag happens to be 'span' or 'li', etc
+	tag_list.innerHTML = tag_list.innerHTML.replace(old_tag, new_tag, "g");
+}
+
+function nodnix_del_tag( tag ) {
+}
+
 function hide_nod_menu() {
 	get_nod_menu().style.display = 'none';
 }
@@ -117,7 +128,7 @@
 	} else {
 		if ( g_pending_hidemenu )
 			clearTimeout(g_pending_hidemenu);
-		g_pending_hidemenu = setTimeout("hide_nodnix_menu()", delay);
+		g_pending_hidemenu = setTimeout(hide_nodnix_menu, delay);
 	}
 }
 
@@ -204,7 +215,21 @@
   YAHOO.util.Dom.removeClass(get_nix_menu(), 'soon');
 }
 
+function refresh_tag_bar( tag_list ) {
+  // ajax request to fill the user tags list
+  var params = {};
+  params['op'] = 'tags_get_user_firehose';
+  params['id'] = g_nodnix_item_id;
+  params['nodnix'] = 1;
+  ajax_update(params, tag_list, {});
+}
+
 function begin_nodnix_editing() {
+  if ( is_firehose_playing() ) {
+    firehose_pause();
+    end_nodnix_editing.restore_firehose_state = firehose_play;
+  }
+
   get_nodnix_listener().disable();
   YAHOO.util.Dom.addClass(get_nod_menu(), 'soon');
   YAHOO.util.Dom.addClass(get_nix_menu(), 'soon');
@@ -215,17 +240,22 @@
   var input = _get_nodnix('input');
   input.value = "";
   input.focus();
-  
-  _get_nodnix('ol').innerHTML = "";
 
+  var tag_list = _get_nodnix('ol');
+  tag_list.innerHTML = "";
+  refresh_tag_bar(tag_list);
+
   (input.getAttribute("updown")=="+" ? nod_completer : nix_completer).sendQuery();
-  setTimeout("soon_is_now()", 225);
+  setTimeout(soon_is_now, 225);
 }
 
 function end_nodnix_editing() {
   YAHOO.util.Dom.removeClass(get_nod_menu(), 'editing');
   YAHOO.util.Dom.removeClass(get_nix_menu(), 'editing');
+  end_nodnix_editing.restore_firehose_state();
+  end_nodnix_editing.restore_firehose_state = function(){}
 }
+end_nodnix_editing.restore_firehose_state = function(){}
 
 function handle_nodnix_blur( type, args ) {
   hide_nodnix_menu();
@@ -239,13 +269,16 @@
     nodnix_tag(tagname);
       // now 'harden' the tag
     var list = _get_nodnix('ol');
-    list.innerHTML = '<li>' + tagname + '</li>' + list.innerHTML;
+    list.innerHTML = handle_nodnix_select.template_string.split('$').join(tagname) + list.innerHTML;
     _get_nodnix('input').value = "";
   }
   if ( !stay_open )
     hide_nodnix_menu();
 }
 
+  // WARNING: keep this string in sync with tagsnodnixuser;misc;default
+handle_nodnix_select.template_string = '<li>$<span class="tag-actions"><a class="not-tag" onmousedown="nodnix_not_tag(\'$\'); return false" href="#">!</a> <a class="del-tag" onmousedown="nodnix_del_tag(\'$\'); return false" href="#">x</a></span></li>';
+
 function handle_completer_key( type, args ) {
   var key = args[0];
   var event = args[1];

Deleted: slashjp/trunk/plugins/Ajax/htdocs/images/prototype.js
===================================================================
--- slashjp/trunk/plugins/Ajax/htdocs/images/prototype.js	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/plugins/Ajax/htdocs/images/prototype.js	2008-04-01 04:03:21 UTC (rev 563)
@@ -1,1785 +0,0 @@
-/*  Prototype JavaScript framework, version 1.4.0
- *  (c) 2005 Sam Stephenson <sam****@conio*****>
- *
- *  THIS FILE IS AUTOMATICALLY GENERATED. When sending patches, please diff
- *  against the source tree, available from the Prototype darcs repository.
- *
- *  Prototype is freely distributable under the terms of an MIT-style license.
- *
- *  For details, see the Prototype web site: http://prototype.conio.net/
- *
-/*--------------------------------------------------------------------------*/
-
-var Prototype = {
-  Version: '1.4.0',
-  ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)',
-
-  emptyFunction: function() {},
-  K: function(x) {return x}
-}
-
-var Class = {
-  create: function() {
-    return function() {
-      this.initialize.apply(this, arguments);
-    }
-  }
-}
-
-var Abstract = new Object();
-
-Object.extend = function(destination, source) {
-  for (property in source) {
-    destination[property] = source[property];
-  }
-  return destination;
-}
-
-Object.inspect = function(object) {
-  try {
-    if (object == undefined) return 'undefined';
-    if (object == null) return 'null';
-    return object.inspect ? object.inspect() : object.toString();
-  } catch (e) {
-    if (e instanceof RangeError) return '...';
-    throw e;
-  }
-}
-
-Function.prototype.bind = function() {
-  var __method = this, args = $A(arguments), object = args.shift();
-  return function() {
-    return __method.apply(object, args.concat($A(arguments)));
-  }
-}
-
-Function.prototype.bindAsEventListener = function(object) {
-  var __method = this;
-  return function(event) {
-    return __method.call(object, event || window.event);
-  }
-}
-
-Object.extend(Number.prototype, {
-  toColorPart: function() {
-    var digits = this.toString(16);
-    if (this < 16) return '0' + digits;
-    return digits;
-  },
-
-  succ: function() {
-    return this + 1;
-  },
-
-  times: function(iterator) {
-    $R(0, this, true).each(iterator);
-    return this;
-  }
-});
-
-var Try = {
-  these: function() {
-    var returnValue;
-
-    for (var i = 0; i < arguments.length; i++) {
-      var lambda = arguments[i];
-      try {
-        returnValue = lambda();
-        break;
-      } catch (e) {}
-    }
-
-    return returnValue;
-  }
-}
-
-/*--------------------------------------------------------------------------*/
-
-var PeriodicalExecuter = Class.create();
-PeriodicalExecuter.prototype = {
-  initialize: function(callback, frequency) {
-    this.callback = callback;
-    this.frequency = frequency;
-    this.currentlyExecuting = false;
-
-    this.registerCallback();
-  },
-
-  registerCallback: function() {
-    setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
-  },
-
-  onTimerEvent: function() {
-    if (!this.currentlyExecuting) {
-      try {
-        this.currentlyExecuting = true;
-        this.callback();
-      } finally {
-        this.currentlyExecuting = false;
-      }
-    }
-  }
-}
-
-/*--------------------------------------------------------------------------*/
-
-function $() {
-  var elements = new Array();
-
-  for (var i = 0; i < arguments.length; i++) {
-    var element = arguments[i];
-    if (typeof element == 'string')
-      element = document.getElementById(element);
-
-    if (arguments.length == 1)
-      return element;
-
-    elements.push(element);
-  }
-
-  return elements;
-}
-Object.extend(String.prototype, {
-  stripTags: function() {
-    return this.replace(/<\/?[^>]+>/gi, '');
-  },
-
-  stripScripts: function() {
-    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
-  },
-
-  extractScripts: function() {
-    var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
-    var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
-    return (this.match(matchAll) || []).map(function(scriptTag) {
-      return (scriptTag.match(matchOne) || ['', ''])[1];
-    });
-  },
-
-  evalScripts: function() {
-    return this.extractScripts().map(eval);
-  },
-
-  escapeHTML: function() {
-    var div = document.createElement('div');
-    var text = document.createTextNode(this);
-    div.appendChild(text);
-    return div.innerHTML;
-  },
-
-  unescapeHTML: function() {
-    var div = document.createElement('div');
-    div.innerHTML = this.stripTags();
-    return div.childNodes[0] ? div.childNodes[0].nodeValue : '';
-  },
-
-  toQueryParams: function() {
-    var pairs = this.match(/^\??(.*)$/)[1].split('&');
-    return pairs.inject({}, function(params, pairString) {
-      var pair = pairString.split('=');
-      params[pair[0]] = pair[1];
-      return params;
-    });
-  },
-
-  toArray: function() {
-    return this.split('');
-  },
-
-  camelize: function() {
-    var oStringList = this.split('-');
-    if (oStringList.length == 1) return oStringList[0];
-
-    var camelizedString = this.indexOf('-') == 0
-      ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1)
-      : oStringList[0];
-
-    for (var i = 1, len = oStringList.length; i < len; i++) {
-      var s = oStringList[i];
-      camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
-    }
-
-    return camelizedString;
-  },
-
-  inspect: function() {
-    return "'" + this.replace('\\', '\\\\').replace("'", '\\\'') + "'";
-  }
-});
-
-String.prototype.parseQuery = String.prototype.toQueryParams;
-
-var $break    = new Object();
-var $continue = new Object();
-
-var Enumerable = {
-  each: function(iterator) {
-    var index = 0;
-    try {
-      this._each(function(value) {
-        try {
-          iterator(value, index++);
-        } catch (e) {
-          if (e != $continue) throw e;
-        }
-      });
-    } catch (e) {
-      if (e != $break) throw e;
-    }
-  },
-
-  all: function(iterator) {
-    var result = true;
-    this.each(function(value, index) {
-      result = result && !!(iterator || Prototype.K)(value, index);
-      if (!result) throw $break;
-    });
-    return result;
-  },
-
-  any: function(iterator) {
-    var result = true;
-    this.each(function(value, index) {
-      if (result = !!(iterator || Prototype.K)(value, index))
-        throw $break;
-    });
-    return result;
-  },
-
-  collect: function(iterator) {
-    var results = [];
-    this.each(function(value, index) {
-      results.push(iterator(value, index));
-    });
-    return results;
-  },
-
-  detect: function (iterator) {
-    var result;
-    this.each(function(value, index) {
-      if (iterator(value, index)) {
-        result = value;
-        throw $break;
-      }
-    });
-    return result;
-  },
-
-  findAll: function(iterator) {
-    var results = [];
-    this.each(function(value, index) {
-      if (iterator(value, index))
-        results.push(value);
-    });
-    return results;
-  },
-
-  grep: function(pattern, iterator) {
-    var results = [];
-    this.each(function(value, index) {
-      var stringValue = value.toString();
-      if (stringValue.match(pattern))
-        results.push((iterator || Prototype.K)(value, index));
-    })
-    return results;
-  },
-
-  include: function(object) {
-    var found = false;
-    this.each(function(value) {
-      if (value == object) {
-        found = true;
-        throw $break;
-      }
-    });
-    return found;
-  },
-
-  inject: function(memo, iterator) {
-    this.each(function(value, index) {
-      memo = iterator(memo, value, index);
-    });
-    return memo;
-  },
-
-  invoke: function(method) {
-    var args = $A(arguments).slice(1);
-    return this.collect(function(value) {
-      return value[method].apply(value, args);
-    });
-  },
-
-  max: function(iterator) {
-    var result;
-    this.each(function(value, index) {
-      value = (iterator || Prototype.K)(value, index);
-      if (value >= (result || value))
-        result = value;
-    });
-    return result;
-  },
-
-  min: function(iterator) {
-    var result;
-    this.each(function(value, index) {
-      value = (iterator || Prototype.K)(value, index);
-      if (value <= (result || value))
-        result = value;
-    });
-    return result;
-  },
-
-  partition: function(iterator) {
-    var trues = [], falses = [];
-    this.each(function(value, index) {
-      ((iterator || Prototype.K)(value, index) ?
-        trues : falses).push(value);
-    });
-    return [trues, falses];
-  },
-
-  pluck: function(property) {
-    var results = [];
-    this.each(function(value, index) {
-      results.push(value[property]);
-    });
-    return results;
-  },
-
-  reject: function(iterator) {
-    var results = [];
-    this.each(function(value, index) {
-      if (!iterator(value, index))
-        results.push(value);
-    });
-    return results;
-  },
-
-  sortBy: function(iterator) {
-    return this.collect(function(value, index) {
-      return {value: value, criteria: iterator(value, index)};
-    }).sort(function(left, right) {
-      var a = left.criteria, b = right.criteria;
-      return a < b ? -1 : a > b ? 1 : 0;
-    }).pluck('value');
-  },
-
-  toArray: function() {
-    return this.collect(Prototype.K);
-  },
-
-  zip: function() {
-    var iterator = Prototype.K, args = $A(arguments);
-    if (typeof args.last() == 'function')
-      iterator = args.pop();
-
-    var collections = [this].concat(args).map($A);
-    return this.map(function(value, index) {
-      iterator(value = collections.pluck(index));
-      return value;
-    });
-  },
-
-  inspect: function() {
-    return '#<Enumerable:' + this.toArray().inspect() + '>';
-  }
-}
-
-Object.extend(Enumerable, {
-  map:     Enumerable.collect,
-  find:    Enumerable.detect,
-  select:  Enumerable.findAll,
-  member:  Enumerable.include,
-  entries: Enumerable.toArray
-});
-var $A = Array.from = function(iterable) {
-  if (!iterable) return [];
-  if (iterable.toArray) {
-    return iterable.toArray();
-  } else {
-    var results = [];
-    for (var i = 0; i < iterable.length; i++)
-      results.push(iterable[i]);
-    return results;
-  }
-}
-
-Object.extend(Array.prototype, Enumerable);
-
-Array.prototype._reverse = Array.prototype.reverse;
-
-Object.extend(Array.prototype, {
-  _each: function(iterator) {
-    for (var i = 0; i < this.length; i++)
-      iterator(this[i]);
-  },
-
-  clear: function() {
-    this.length = 0;
-    return this;
-  },
-
-  first: function() {
-    return this[0];
-  },
-
-  last: function() {
-    return this[this.length - 1];
-  },
-
-  compact: function() {
-    return this.select(function(value) {
-      return value != undefined || value != null;
-    });
-  },
-
-  flatten: function() {
-    return this.inject([], function(array, value) {
-      return array.concat(value.constructor == Array ?
-        value.flatten() : [value]);
-    });
-  },
-
-  without: function() {
-    var values = $A(arguments);
-    return this.select(function(value) {
-      return !values.include(value);
-    });
-  },
-
-  indexOf: function(object) {
-    for (var i = 0; i < this.length; i++)
-      if (this[i] == object) return i;
-    return -1;
-  },
-
-  reverse: function(inline) {
-    return (inline !== false ? this : this.toArray())._reverse();
-  },
-
-  shift: function() {
-    var result = this[0];
-    for (var i = 0; i < this.length - 1; i++)
-      this[i] = this[i + 1];
-    this.length--;
-    return result;
-  },
-
-  inspect: function() {
-    return '[' + this.map(Object.inspect).join(', ') + ']';
-  }
-});
-var Hash = {
-  _each: function(iterator) {
-    for (key in this) {
-      var value = this[key];
-      if (typeof value == 'function') continue;
-
-      var pair = [key, value];
-      pair.key = key;
-      pair.value = value;
-      iterator(pair);
-    }
-  },
-
-  keys: function() {
-    return this.pluck('key');
-  },
-
-  values: function() {
-    return this.pluck('value');
-  },
-
-  merge: function(hash) {
-    return $H(hash).inject($H(this), function(mergedHash, pair) {
-      mergedHash[pair.key] = pair.value;
-      return mergedHash;
-    });
-  },
-
-  toQueryString: function() {
-    return this.map(function(pair) {
-      return pair.map(encodeURIComponent).join('=');
-    }).join('&');
-  },
-
-  inspect: function() {
-    return '#<Hash:{' + this.map(function(pair) {
-      return pair.map(Object.inspect).join(': ');
-    }).join(', ') + '}>';
-  }
-}
-
-function $H(object) {
-  var hash = Object.extend({}, object || {});
-  Object.extend(hash, Enumerable);
-  Object.extend(hash, Hash);
-  return hash;
-}
-ObjectRange = Class.create();
-Object.extend(ObjectRange.prototype, Enumerable);
-Object.extend(ObjectRange.prototype, {
-  initialize: function(start, end, exclusive) {
-    this.start = start;
-    this.end = end;
-    this.exclusive = exclusive;
-  },
-
-  _each: function(iterator) {
-    var value = this.start;
-    do {
-      iterator(value);
-      value = value.succ();
-    } while (this.include(value));
-  },
-
-  include: function(value) {
-    if (value < this.start)
-      return false;
-    if (this.exclusive)
-      return value < this.end;
-    return value <= this.end;
-  }
-});
-
-var $R = function(start, end, exclusive) {
-  return new ObjectRange(start, end, exclusive);
-}
-
-var Ajax = {
-  getTransport: function() {
-    return Try.these(
-      function() {return new ActiveXObject('Msxml2.XMLHTTP')},
-      function() {return new ActiveXObject('Microsoft.XMLHTTP')},
-      function() {return new XMLHttpRequest()}
-    ) || false;
-  },
-
-  activeRequestCount: 0
-}
-
-Ajax.Responders = {
-  responders: [],
-
-  _each: function(iterator) {
-    this.responders._each(iterator);
-  },
-
-  register: function(responderToAdd) {
-    if (!this.include(responderToAdd))
-      this.responders.push(responderToAdd);
-  },
-
-  unregister: function(responderToRemove) {
-    this.responders = this.responders.without(responderToRemove);
-  },
-
-  dispatch: function(callback, request, transport, json) {
-    this.each(function(responder) {
-      if (responder[callback] && typeof responder[callback] == 'function') {
-        try {
-          responder[callback].apply(responder, [request, transport, json]);
-        } catch (e) {}
-      }
-    });
-  }
-};
-
-Object.extend(Ajax.Responders, Enumerable);
-
-Ajax.Responders.register({
-  onCreate: function() {
-    Ajax.activeRequestCount++;
-  },
-
-  onComplete: function() {
-    Ajax.activeRequestCount--;
-  }
-});
-
-Ajax.Base = function() {};
-Ajax.Base.prototype = {
-  setOptions: function(options) {
-    this.options = {
-      method:       'post',
-      asynchronous: true,
-      parameters:   ''
-    }
-    Object.extend(this.options, options || {});
-  },
-
-  responseIsSuccess: function() {
-    return this.transport.status == undefined
-        || this.transport.status == 0
-        || (this.transport.status >= 200 && this.transport.status < 300);
-  },
-
-  responseIsFailure: function() {
-    return !this.responseIsSuccess();
-  }
-}
-
-Ajax.Request = Class.create();
-Ajax.Request.Events =
-  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
-
-Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
-  initialize: function(url, options) {
-    this.transport = Ajax.getTransport();
-    this.setOptions(options);
-    this.request(url);
-  },
-
-  request: function(url) {
-    var parameters = this.options.parameters || '';
-    if (parameters.length > 0) parameters += '&_=';
-
-    try {
-      this.url = url;
-      if (this.options.method == 'get' && parameters.length > 0)
-        this.url += (this.url.match(/\?/) ? '&' : '?') + parameters;
-
-      Ajax.Responders.dispatch('onCreate', this, this.transport);
-
-      this.transport.open(this.options.method, this.url,
-        this.options.asynchronous);
-
-      if (this.options.asynchronous) {
-        this.transport.onreadystatechange = this.onStateChange.bind(this);
-        setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10);
-      }
-
-      this.setRequestHeaders();
-
-      var body = this.options.postBody ? this.options.postBody : parameters;
-      this.transport.send(this.options.method == 'post' ? body : null);
-
-    } catch (e) {
-      this.dispatchException(e);
-    }
-  },
-
-  setRequestHeaders: function() {
-    var requestHeaders =
-      ['X-Requested-With', 'XMLHttpRequest',
-       'X-Prototype-Version', Prototype.Version];
-
-    if (this.options.method == 'post') {
-      requestHeaders.push('Content-type',
-        'application/x-www-form-urlencoded');
-
-      /* Force "Connection: close" for Mozilla browsers to work around
-       * a bug where XMLHttpReqeuest sends an incorrect Content-length
-       * header. See Mozilla Bugzilla #246651.
-       */
-      if (this.transport.overrideMimeType)
-        requestHeaders.push('Connection', 'close');
-    }
-
-    if (this.options.requestHeaders)
-      requestHeaders.push.apply(requestHeaders, this.options.requestHeaders);
-
-    for (var i = 0; i < requestHeaders.length; i += 2)
-      this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]);
-  },
-
-  onStateChange: function() {
-    var readyState = this.transport.readyState;
-    if (readyState != 1)
-      this.respondToReadyState(this.transport.readyState);
-  },
-
-  header: function(name) {
-    try {
-      return this.transport.getResponseHeader(name);
-    } catch (e) {}
-  },
-
-  evalJSON: function() {
-    try {
-      return eval(this.header('X-JSON'));
-    } catch (e) {}
-  },
-
-  evalResponse: function() {
-    try {
-      return eval(this.transport.responseText);
-    } catch (e) {
-      this.dispatchException(e);
-    }
-  },
-
-  respondToReadyState: function(readyState) {
-    var event = Ajax.Request.Events[readyState];
-    var transport = this.transport, json = this.evalJSON();
-
-    if (event == 'Complete') {
-      try {
-        (this.options['on' + this.transport.status]
-         || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
-         || Prototype.emptyFunction)(transport, json);
-      } catch (e) {
-        this.dispatchException(e);
-      }
-
-      if ((this.header('Content-type') || '').match(/^text\/javascript/i))
-        this.evalResponse();
-    }
-
-    try {
-      (this.options['on' + event] || Prototype.emptyFunction)(transport, json);
-      Ajax.Responders.dispatch('on' + event, this, transport, json);
-    } catch (e) {
-      this.dispatchException(e);
-    }
-
-    /* Avoid memory leak in MSIE: clean up the oncomplete event handler */
-    if (event == 'Complete')
-      this.transport.onreadystatechange = Prototype.emptyFunction;
-  },
-
-  dispatchException: function(exception) {
-    (this.options.onException || Prototype.emptyFunction)(this, exception);
-    Ajax.Responders.dispatch('onException', this, exception);
-  }
-});
-
-Ajax.Updater = Class.create();
-
-Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
-  initialize: function(container, url, options) {
-    this.containers = {
-      success: container.success ? $(container.success) : $(container),
-      failure: container.failure ? $(container.failure) :
-        (container.success ? null : $(container))
-    }
-
-    this.transport = Ajax.getTransport();
-    this.setOptions(options);
-
-    var onComplete = this.options.onComplete || Prototype.emptyFunction;
-    this.options.onComplete = (function(transport, object) {
-      this.updateContent();
-      onComplete(transport, object);
-    }).bind(this);
-
-    this.request(url);
-  },
-
-  updateContent: function() {
-    var receiver = this.responseIsSuccess() ?
-      this.containers.success : this.containers.failure;
-    var response = this.transport.responseText;
-
-    if (!this.options.evalScripts)
-      response = response.stripScripts();
-
-    if (receiver) {
-      if (this.options.insertion) {
-        new this.options.insertion(receiver, response);
-      } else {
-        Element.update(receiver, response);
-      }
-    }
-
-    if (this.responseIsSuccess()) {
-      if (this.onComplete)
-        setTimeout(this.onComplete.bind(this), 10);
-    }
-  }
-});
-
-Ajax.PeriodicalUpdater = Class.create();
-Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
-  initialize: function(container, url, options) {
-    this.setOptions(options);
-    this.onComplete = this.options.onComplete;
-
-    this.frequency = (this.options.frequency || 2);
-    this.decay = (this.options.decay || 1);
-
-    this.updater = {};
-    this.container = container;
-    this.url = url;
-
-    this.start();
-  },
-
-  start: function() {
-    this.options.onComplete = this.updateComplete.bind(this);
-    this.onTimerEvent();
-  },
-
-  stop: function() {
-    this.updater.onComplete = undefined;
-    clearTimeout(this.timer);
-    (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
-  },
-
-  updateComplete: function(request) {
-    if (this.options.decay) {
-      this.decay = (request.responseText == this.lastText ?
-        this.decay * this.options.decay : 1);
-
-      this.lastText = request.responseText;
-    }
-    this.timer = setTimeout(this.onTimerEvent.bind(this),
-      this.decay * this.frequency * 1000);
-  },
-
-  onTimerEvent: function() {
-    this.updater = new Ajax.Updater(this.container, this.url, this.options);
-  }
-});
-document.getElementsByClassName = function(className, parentElement) {
-  var children = ($(parentElement) || document.body).getElementsByTagName('*');
-  return $A(children).inject([], function(elements, child) {
-    if (child.className.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
-      elements.push(child);
-    return elements;
-  });
-}
-
-/*--------------------------------------------------------------------------*/
-
-if (!window.Element) {
-  var Element = new Object();
-}
-
-Object.extend(Element, {
-  visible: function(element) {
-    return $(element).style.display != 'none';
-  },
-
-  toggle: function() {
-    for (var i = 0; i < arguments.length; i++) {
-      var element = $(arguments[i]);
-      Element[Element.visible(element) ? 'hide' : 'show'](element);
-    }
-  },
-
-  hide: function() {
-    for (var i = 0; i < arguments.length; i++) {
-      var element = $(arguments[i]);
-      element.style.display = 'none';
-    }
-  },
-
-  show: function() {
-    for (var i = 0; i < arguments.length; i++) {
-      var element = $(arguments[i]);
-      element.style.display = '';
-    }
-  },
-
-  remove: function(element) {
-    element = $(element);
-    element.parentNode.removeChild(element);
-  },
-
-  update: function(element, html) {
-    $(element).innerHTML = html.stripScripts();
-    setTimeout(function() {html.evalScripts()}, 10);
-  },
-
-  getHeight: function(element) {
-    element = $(element);
-    return element.offsetHeight;
-  },
-
-  classNames: function(element) {
-    return new Element.ClassNames(element);
-  },
-
-  hasClassName: function(element, className) {
-    if (!(element = $(element))) return;
-    return Element.classNames(element).include(className);
-  },
-
-  addClassName: function(element, className) {
-    if (!(element = $(element))) return;
-    return Element.classNames(element).add(className);
-  },
-
-  removeClassName: function(element, className) {
-    if (!(element = $(element))) return;
-    return Element.classNames(element).remove(className);
-  },
-
-  // removes whitespace-only text node children
-  cleanWhitespace: function(element) {
-    element = $(element);
-    for (var i = 0; i < element.childNodes.length; i++) {
-      var node = element.childNodes[i];
-      if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
-        Element.remove(node);
-    }
-  },
-
-  empty: function(element) {
-    return $(element).innerHTML.match(/^\s*$/);
-  },
-
-  scrollTo: function(element) {
-    element = $(element);
-    var x = element.x ? element.x : element.offsetLeft,
-        y = element.y ? element.y : element.offsetTop;
-    window.scrollTo(x, y);
-  },
-
-  getStyle: function(element, style) {
-    element = $(element);
-    var value = element.style[style.camelize()];
-    if (!value) {
-      if (document.defaultView && document.defaultView.getComputedStyle) {
-        var css = document.defaultView.getComputedStyle(element, null);
-        value = css ? css.getPropertyValue(style) : null;
-      } else if (element.currentStyle) {
-        value = element.currentStyle[style.camelize()];
-      }
-    }
-
-    if (window.opera && ['left', 'top', 'right', 'bottom'].include(style))
-      if (Element.getStyle(element, 'position') == 'static') value = 'auto';
-
-    return value == 'auto' ? null : value;
-  },
-
-  setStyle: function(element, style) {
-    element = $(element);
-    for (name in style)
-      element.style[name.camelize()] = style[name];
-  },
-
-  getDimensions: function(element) {
-    element = $(element);
-    if (Element.getStyle(element, 'display') != 'none')
-      return {width: element.offsetWidth, height: element.offsetHeight};
-
-    // All *Width and *Height properties give 0 on elements with display none,
-    // so enable the element temporarily
-    var els = element.style;
-    var originalVisibility = els.visibility;
-    var originalPosition = els.position;
-    els.visibility = 'hidden';
-    els.position = 'absolute';
-    els.display = '';
-    var originalWidth = element.clientWidth;
-    var originalHeight = element.clientHeight;
-    els.display = 'none';
-    els.position = originalPosition;
-    els.visibility = originalVisibility;
-    return {width: originalWidth, height: originalHeight};
-  },
-
-  makePositioned: function(element) {
-    element = $(element);
-    var pos = Element.getStyle(element, 'position');
-    if (pos == 'static' || !pos) {
-      element._madePositioned = true;
-      element.style.position = 'relative';
-      // Opera returns the offset relative to the positioning context, when an
-      // element is position relative but top and left have not been defined
-      if (window.opera) {
-        element.style.top = 0;
-        element.style.left = 0;
-      }
-    }
-  },
-
-  undoPositioned: function(element) {
-    element = $(element);
-    if (element._madePositioned) {
-      element._madePositioned = undefined;
-      element.style.position =
-        element.style.top =
-        element.style.left =
-        element.style.bottom =
-        element.style.right = '';
-    }
-  },
-
-  makeClipping: function(element) {
-    element = $(element);
-    if (element._overflow) return;
-    element._overflow = element.style.overflow;
-    if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden')
-      element.style.overflow = 'hidden';
-  },
-
-  undoClipping: function(element) {
-    element = $(element);
-    if (element._overflow) return;
-    element.style.overflow = element._overflow;
-    element._overflow = undefined;
-  }
-});
-
-var Toggle = new Object();
-Toggle.display = Element.toggle;
-
-/*--------------------------------------------------------------------------*/
-
-Abstract.Insertion = function(adjacency) {
-  this.adjacency = adjacency;
-}
-
-Abstract.Insertion.prototype = {
-  initialize: function(element, content) {
-    this.element = $(element);
-    this.content = content.stripScripts();
-
-    if (this.adjacency && this.element.insertAdjacentHTML) {
-      try {
-        this.element.insertAdjacentHTML(this.adjacency, this.content);
-      } catch (e) {
-        if (this.element.tagName.toLowerCase() == 'tbody') {
-          this.insertContent(this.contentFromAnonymousTable());
-        } else {
-          throw e;
-        }
-      }
-    } else {
-      this.range = this.element.ownerDocument.createRange();
-      if (this.initializeRange) this.initializeRange();
-      this.insertContent([this.range.createContextualFragment(this.content)]);
-    }
-
-    setTimeout(function() {content.evalScripts()}, 10);
-  },
-
-  contentFromAnonymousTable: function() {
-    var div = document.createElement('div');
-    div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>';
-    return $A(div.childNodes[0].childNodes[0].childNodes);
-  }
-}
-
-var Insertion = new Object();
-
-Insertion.Before = Class.create();
-Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), {
-  initializeRange: function() {
-    this.range.setStartBefore(this.element);
-  },
-
-  insertContent: function(fragments) {
-    fragments.each((function(fragment) {
-      this.element.parentNode.insertBefore(fragment, this.element);
-    }).bind(this));
-  }
-});
-
-Insertion.Top = Class.create();
-Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), {
-  initializeRange: function() {
-    this.range.selectNodeContents(this.element);
-    this.range.collapse(true);
-  },
-
-  insertContent: function(fragments) {
-    fragments.reverse(false).each((function(fragment) {
-      this.element.insertBefore(fragment, this.element.firstChild);
-    }).bind(this));
-  }
-});
-
-Insertion.Bottom = Class.create();
-Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), {
-  initializeRange: function() {
-    this.range.selectNodeContents(this.element);
-    this.range.collapse(this.element);
-  },
-
-  insertContent: function(fragments) {
-    fragments.each((function(fragment) {
-      this.element.appendChild(fragment);
-    }).bind(this));
-  }
-});
-
-Insertion.After = Class.create();
-Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), {
-  initializeRange: function() {
-    this.range.setStartAfter(this.element);
-  },
-
-  insertContent: function(fragments) {
-    fragments.each((function(fragment) {
-      this.element.parentNode.insertBefore(fragment,
-        this.element.nextSibling);
-    }).bind(this));
-  }
-});
-
-/*--------------------------------------------------------------------------*/
-
-Element.ClassNames = Class.create();
-Element.ClassNames.prototype = {
-  initialize: function(element) {
-    this.element = $(element);
-  },
-
-  _each: function(iterator) {
-    this.element.className.split(/\s+/).select(function(name) {
-      return name.length > 0;
-    })._each(iterator);
-  },
-
-  set: function(className) {
-    this.element.className = className;
-  },
-
-  add: function(classNameToAdd) {
-    if (this.include(classNameToAdd)) return;
-    this.set(this.toArray().concat(classNameToAdd).join(' '));
-  },
-
-  remove: function(classNameToRemove) {
-    if (!this.include(classNameToRemove)) return;
-    this.set(this.select(function(className) {
-      return className != classNameToRemove;
-    }).join(' '));
-  },
-
-  toString: function() {
-    return this.toArray().join(' ');
-  }
-}
-
-Object.extend(Element.ClassNames.prototype, Enumerable);
-var Field = {
-  clear: function() {
-    for (var i = 0; i < arguments.length; i++)
-      $(arguments[i]).value = '';
-  },
-
-  focus: function(element) {
-    $(element).focus();
-  },
-
-  present: function() {
-    for (var i = 0; i < arguments.length; i++)
-      if ($(arguments[i]).value == '') return false;
-    return true;
-  },
-
-  select: function(element) {
-    $(element).select();
-  },
-
-  activate: function(element) {
-    element = $(element);
-    element.focus();
-    if (element.select)
-      element.select();
-  }
-}
-
-/*--------------------------------------------------------------------------*/
-
-var Form = {
-  serialize: function(form) {
-    var elements = Form.getElements($(form));
-    var queryComponents = new Array();
-
-    for (var i = 0; i < elements.length; i++) {
-      var queryComponent = Form.Element.serialize(elements[i]);
-      if (queryComponent)
-        queryComponents.push(queryComponent);
-    }
-
-    return queryComponents.join('&');
-  },
-
-  getElements: function(form) {
-    form = $(form);
-    var elements = new Array();
-
-    for (tagName in Form.Element.Serializers) {
-      var tagElements = form.getElementsByTagName(tagName);
-      for (var j = 0; j < tagElements.length; j++)
-        elements.push(tagElements[j]);
-    }
-    return elements;
-  },
-
-  getInputs: function(form, typeName, name) {
-    form = $(form);
-    var inputs = form.getElementsByTagName('input');
-
-    if (!typeName && !name)
-      return inputs;
-
-    var matchingInputs = new Array();
-    for (var i = 0; i < inputs.length; i++) {
-      var input = inputs[i];
-      if ((typeName && input.type != typeName) ||
-          (name && input.name != name))
-        continue;
-      matchingInputs.push(input);
-    }
-
-    return matchingInputs;
-  },
-
-  disable: function(form) {
-    var elements = Form.getElements(form);
-    for (var i = 0; i < elements.length; i++) {
-      var element = elements[i];
-      element.blur();
-      element.disabled = 'true';
-    }
-  },
-
-  enable: function(form) {
-    var elements = Form.getElements(form);
-    for (var i = 0; i < elements.length; i++) {
-      var element = elements[i];
-      element.disabled = '';
-    }
-  },
-
-  findFirstElement: function(form) {
-    return Form.getElements(form).find(function(element) {
-      return element.type != 'hidden' && !element.disabled &&
-        ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
-    });
-  },
-
-  focusFirstElement: function(form) {
-    Field.activate(Form.findFirstElement(form));
-  },
-
-  reset: function(form) {
-    $(form).reset();
-  }
-}
-
-Form.Element = {
-  serialize: function(element) {
-    element = $(element);
-    var method = element.tagName.toLowerCase();
-    var parameter = Form.Element.Serializers[method](element);
-
-    if (parameter) {
-      var key = encodeURIComponent(parameter[0]);
-      if (key.length == 0) return;
-
-      if (parameter[1].constructor != Array)
-        parameter[1] = [parameter[1]];
-
-      return parameter[1].map(function(value) {
-        return key + '=' + encodeURIComponent(value);
-      }).join('&');
-    }
-  },
-
-  getValue: function(element) {
-    element = $(element);
-    var method = element.tagName.toLowerCase();
-    var parameter = Form.Element.Serializers[method](element);
-
-    if (parameter)
-      return parameter[1];
-  }
-}
-
-Form.Element.Serializers = {
-  input: function(element) {
-    switch (element.type.toLowerCase()) {
-      case 'submit':
-      case 'hidden':
-      case 'password':
-      case 'text':
-        return Form.Element.Serializers.textarea(element);
-      case 'checkbox':
-      case 'radio':
-        return Form.Element.Serializers.inputSelector(element);
-    }
-    return false;
-  },
-
-  inputSelector: function(element) {
-    if (element.checked)
-      return [element.name, element.value];
-  },
-
-  textarea: function(element) {
-    return [element.name, element.value];
-  },
-
-  select: function(element) {
-    return Form.Element.Serializers[element.type == 'select-one' ?
-      'selectOne' : 'selectMany'](element);
-  },
-
-  selectOne: function(element) {
-    var value = '', opt, index = element.selectedIndex;
-    if (index >= 0) {
-      opt = element.options[index];
-      value = opt.value;
-      if (!value && !('value' in opt))
-        value = opt.text;
-    }
-    return [element.name, value];
-  },
-
-  selectMany: function(element) {
-    var value = new Array();
-    for (var i = 0; i < element.length; i++) {
-      var opt = element.options[i];
-      if (opt.selected) {
-        var optValue = opt.value;
-        if (!optValue && !('value' in opt))
-          optValue = opt.text;
-        value.push(optValue);
-      }
-    }
-    return [element.name, value];
-  }
-}
-
-/*--------------------------------------------------------------------------*/
-
-var $F = Form.Element.getValue;
-
-/*--------------------------------------------------------------------------*/
-
-Abstract.TimedObserver = function() {}
-Abstract.TimedObserver.prototype = {
-  initialize: function(element, frequency, callback) {
-    this.frequency = frequency;
-    this.element   = $(element);
-    this.callback  = callback;
-
-    this.lastValue = this.getValue();
-    this.registerCallback();
-  },
-
-  registerCallback: function() {
-    setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
-  },
-
-  onTimerEvent: function() {
-    var value = this.getValue();
-    if (this.lastValue != value) {
-      this.callback(this.element, value);
-      this.lastValue = value;
-    }
-  }
-}
-
-Form.Element.Observer = Class.create();
-Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
-  getValue: function() {
-    return Form.Element.getValue(this.element);
-  }
-});
-
-Form.Observer = Class.create();
-Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
-  getValue: function() {
-    return Form.serialize(this.element);
-  }
-});
-
-/*--------------------------------------------------------------------------*/
-
-Abstract.EventObserver = function() {}
-Abstract.EventObserver.prototype = {
-  initialize: function(element, callback) {
-    this.element  = $(element);
-    this.callback = callback;
-
-    this.lastValue = this.getValue();
-    if (this.element.tagName.toLowerCase() == 'form')
-      this.registerFormCallbacks();
-    else
-      this.registerCallback(this.element);
-  },
-
-  onElementEvent: function() {
-    var value = this.getValue();
-    if (this.lastValue != value) {
-      this.callback(this.element, value);
-      this.lastValue = value;
-    }
-  },
-
-  registerFormCallbacks: function() {
-    var elements = Form.getElements(this.element);
-    for (var i = 0; i < elements.length; i++)
-      this.registerCallback(elements[i]);
-  },
-
-  registerCallback: function(element) {
-    if (element.type) {
-      switch (element.type.toLowerCase()) {
-        case 'checkbox':
-        case 'radio':
-          Event.observe(element, 'click', this.onElementEvent.bind(this));
-          break;
-        case 'password':
-        case 'text':
-        case 'textarea':
-        case 'select-one':
-        case 'select-multiple':
-          Event.observe(element, 'change', this.onElementEvent.bind(this));
-          break;
-      }
-    }
-  }
-}
-
-Form.Element.EventObserver = Class.create();
-Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
-  getValue: function() {
-    return Form.Element.getValue(this.element);
-  }
-});
-
-Form.EventObserver = Class.create();
-Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
-  getValue: function() {
-    return Form.serialize(this.element);
-  }
-});
-if (!window.Event) {
-  var Event = new Object();
-}
-
-Object.extend(Event, {
-  KEY_BACKSPACE: 8,
-  KEY_TAB:       9,
-  KEY_RETURN:   13,
-  KEY_ESC:      27,
-  KEY_LEFT:     37,
-  KEY_UP:       38,
-  KEY_RIGHT:    39,
-  KEY_DOWN:     40,
-  KEY_DELETE:   46,
-
-  element: function(event) {
-    return event.target || event.srcElement;
-  },
-
-  isLeftClick: function(event) {
-    return (((event.which) && (event.which == 1)) ||
-            ((event.button) && (event.button == 1)));
-  },
-
-  pointerX: function(event) {
-    return event.pageX || (event.clientX +
-      (document.documentElement.scrollLeft || document.body.scrollLeft));
-  },
-
-  pointerY: function(event) {
-    return event.pageY || (event.clientY +
-      (document.documentElement.scrollTop || document.body.scrollTop));
-  },
-
-  stop: function(event) {
-    if (event.preventDefault) {
-      event.preventDefault();
-      event.stopPropagation();
-    } else {
-      event.returnValue = false;
-      event.cancelBubble = true;
-    }
-  },
-
-  // find the first node with the given tagName, starting from the
-  // node the event was triggered on; traverses the DOM upwards
-  findElement: function(event, tagName) {
-    var element = Event.element(event);
-    while (element.parentNode && (!element.tagName ||
-        (element.tagName.toUpperCase() != tagName.toUpperCase())))
-      element = element.parentNode;
-    return element;
-  },
-
-  observers: false,
-
-  _observeAndCache: function(element, name, observer, useCapture) {
-    if (!this.observers) this.observers = [];
-    if (element.addEventListener) {
-      this.observers.push([element, name, observer, useCapture]);
-      element.addEventListener(name, observer, useCapture);
-    } else if (element.attachEvent) {
-      this.observers.push([element, name, observer, useCapture]);
-      element.attachEvent('on' + name, observer);
-    }
-  },
-
-  unloadCache: function() {
-    if (!Event.observers) return;
-    for (var i = 0; i < Event.observers.length; i++) {
-      Event.stopObserving.apply(this, Event.observers[i]);
-      Event.observers[i][0] = null;
-    }
-    Event.observers = false;
-  },
-
-  observe: function(element, name, observer, useCapture) {
-    var element = $(element);
-    useCapture = useCapture || false;
-
-    if (name == 'keypress' &&
-        (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
-        || element.attachEvent))
-      name = 'keydown';
-
-    this._observeAndCache(element, name, observer, useCapture);
-  },
-
-  stopObserving: function(element, name, observer, useCapture) {
-    var element = $(element);
-    useCapture = useCapture || false;
-
-    if (name == 'keypress' &&
-        (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
-        || element.detachEvent))
-      name = 'keydown';
-
-    if (element.removeEventListener) {
-      element.removeEventListener(name, observer, useCapture);
-    } else if (element.detachEvent) {
-      element.detachEvent('on' + name, observer);
-    }
-  }
-});
-
-/* prevent memory leaks in IE */
-Event.observe(window, 'unload', Event.unloadCache, false);
-var Position = {
-  // set to true if needed, warning: firefox performance problems
-  // NOT neeeded for page scrolling, only if draggable contained in
-  // scrollable elements
-  includeScrollOffsets: false,
-
-  // must be called before calling withinIncludingScrolloffset, every time the
-  // page is scrolled
-  prepare: function() {
-    this.deltaX =  window.pageXOffset
-                || document.documentElement.scrollLeft
-                || document.body.scrollLeft
-                || 0;
-    this.deltaY =  window.pageYOffset
-                || document.documentElement.scrollTop
-                || document.body.scrollTop
-                || 0;
-  },
-
-  realOffset: function(element) {
-    var valueT = 0, valueL = 0;
-    do {
-      valueT += element.scrollTop  || 0;
-      valueL += element.scrollLeft || 0;
-      element = element.parentNode;
-    } while (element);
-    return [valueL, valueT];
-  },
-
-  cumulativeOffset: function(element) {
-    var valueT = 0, valueL = 0;
-    do {
-      valueT += element.offsetTop  || 0;
-      valueL += element.offsetLeft || 0;
-      element = element.offsetParent;
-    } while (element);
-    return [valueL, valueT];
-  },
-
-  positionedOffset: function(element) {
-    var valueT = 0, valueL = 0;
-    do {
-      valueT += element.offsetTop  || 0;
-      valueL += element.offsetLeft || 0;
-      element = element.offsetParent;
-      if (element) {
-        p = Element.getStyle(element, 'position');
-        if (p == 'relative' || p == 'absolute') break;
-      }
-    } while (element);
-    return [valueL, valueT];
-  },
-
-  offsetParent: function(element) {
-    if (element.offsetParent) return element.offsetParent;
-    if (element == document.body) return element;
-
-    while ((element = element.parentNode) && element != document.body)
-      if (Element.getStyle(element, 'position') != 'static')
-        return element;
-
-    return document.body;
-  },
-
-  // caches x/y coordinate pair to use with overlap
-  within: function(element, x, y) {
-    if (this.includeScrollOffsets)
-      return this.withinIncludingScrolloffsets(element, x, y);
-    this.xcomp = x;
-    this.ycomp = y;
-    this.offset = this.cumulativeOffset(element);
-
-    return (y >= this.offset[1] &&
-            y <  this.offset[1] + element.offsetHeight &&
-            x >= this.offset[0] &&
-            x <  this.offset[0] + element.offsetWidth);
-  },
-
-  withinIncludingScrolloffsets: function(element, x, y) {
-    var offsetcache = this.realOffset(element);
-
-    this.xcomp = x + offsetcache[0] - this.deltaX;
-    this.ycomp = y + offsetcache[1] - this.deltaY;
-    this.offset = this.cumulativeOffset(element);
-
-    return (this.ycomp >= this.offset[1] &&
-            this.ycomp <  this.offset[1] + element.offsetHeight &&
-            this.xcomp >= this.offset[0] &&
-            this.xcomp <  this.offset[0] + element.offsetWidth);
-  },
-
-  // within must be called directly before
-  overlap: function(mode, element) {
-    if (!mode) return 0;
-    if (mode == 'vertical')
-      return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
-        element.offsetHeight;
-    if (mode == 'horizontal')
-      return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
-        element.offsetWidth;
-  },
-
-  clone: function(source, target) {
-    source = $(source);
-    target = $(target);
-    target.style.position = 'absolute';
-    var offsets = this.cumulativeOffset(source);
-    target.style.top    = offsets[1] + 'px';
-    target.style.left   = offsets[0] + 'px';
-    target.style.width  = source.offsetWidth + 'px';
-    target.style.height = source.offsetHeight + 'px';
-  },
-
-  page: function(forElement) {
-    var valueT = 0, valueL = 0;
-
-    var element = forElement;
-    do {
-      valueT += element.offsetTop  || 0;
-      valueL += element.offsetLeft || 0;
-
-      // Safari fix
-      if (element.offsetParent==document.body)
-        if (Element.getStyle(element,'position')=='absolute') break;
-
-    } while (element = element.offsetParent);
-
-    element = forElement;
-    do {
-      valueT -= element.scrollTop  || 0;
-      valueL -= element.scrollLeft || 0;
-    } while (element = element.parentNode);
-
-    return [valueL, valueT];
-  },
-
-  clone: function(source, target) {
-    var options = Object.extend({
-      setLeft:    true,
-      setTop:     true,
-      setWidth:   true,
-      setHeight:  true,
-      offsetTop:  0,
-      offsetLeft: 0
-    }, arguments[2] || {})
-
-    // find page position of source
-    source = $(source);
-    var p = Position.page(source);
-
-    // find coordinate system to use
-    target = $(target);
-    var delta = [0, 0];
-    var parent = null;
-    // delta [0,0] will do fine with position: fixed elements,
-    // position:absolute needs offsetParent deltas
-    if (Element.getStyle(target,'position') == 'absolute') {
-      parent = Position.offsetParent(target);
-      delta = Position.page(parent);
-    }
-
-    // correct by body offsets (fixes Safari)
-    if (parent == document.body) {
-      delta[0] -= document.body.offsetLeft;
-      delta[1] -= document.body.offsetTop;
-    }
-
-    // set position
-    if(options.setLeft)   target.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
-    if(options.setTop)    target.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
-    if(options.setWidth)  target.style.width = source.offsetWidth + 'px';
-    if(options.setHeight) target.style.height = source.offsetHeight + 'px';
-  },
-
-  absolutize: function(element) {
-    element = $(element);
-    if (element.style.position == 'absolute') return;
-    Position.prepare();
-
-    var offsets = Position.positionedOffset(element);
-    var top     = offsets[1];
-    var left    = offsets[0];
-    var width   = element.clientWidth;
-    var height  = element.clientHeight;
-
-    element._originalLeft   = left - parseFloat(element.style.left  || 0);
-    element._originalTop    = top  - parseFloat(element.style.top || 0);
-    element._originalWidth  = element.style.width;
-    element._originalHeight = element.style.height;
-
-    element.style.position = 'absolute';
-    element.style.top    = top + 'px';;
-    element.style.left   = left + 'px';;
-    element.style.width  = width + 'px';;
-    element.style.height = height + 'px';;
-  },
-
-  relativize: function(element) {
-    element = $(element);
-    if (element.style.position == 'relative') return;
-    Position.prepare();
-
-    element.style.position = 'relative';
-    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
-    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
-
-    element.style.top    = top + 'px';
-    element.style.left   = left + 'px';
-    element.style.height = element._originalHeight;
-    element.style.width  = element._originalWidth;
-  }
-}
-
-// Safari returns margins on body which is incorrect if the child is absolutely
-// positioned.  For performance reasons, redefine Position.cumulativeOffset for
-// KHTML/WebKit only.
-if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
-  Position.cumulativeOffset = function(element) {
-    var valueT = 0, valueL = 0;
-    do {
-      valueT += element.offsetTop  || 0;
-      valueL += element.offsetLeft || 0;
-      if (element.offsetParent == document.body)
-        if (Element.getStyle(element, 'position') == 'absolute') break;
-
-      element = element.offsetParent;
-    } while (element);
-
-    return [valueL, valueT];
-  }
-}
\ No newline at end of file

Modified: slashjp/trunk/plugins/Ajax/htdocs/images/sd_autocomplete.js
===================================================================
--- slashjp/trunk/plugins/Ajax/htdocs/images/sd_autocomplete.js	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/plugins/Ajax/htdocs/images/sd_autocomplete.js	2008-04-01 04:03:21 UTC (rev 563)
@@ -79,6 +79,7 @@
 "books",
 "bsd",
 "developers",
+"entertainment",
 "features",
 "games",
 "hardware",
@@ -86,12 +87,14 @@
 "it",
 "linux",
 "mainpage",
+"news",
 "politics",
 "polls",
 "radio",
 "science",
 "search",
 "tacohell",
+"technology",
 "vendors",
 "vendor_amd",
 "yro" ];
@@ -126,7 +129,6 @@
 "links",
 "movies",
 "money",
-"news",
 "pilot",
 "starwars",
 "sun",
@@ -201,7 +203,6 @@
 "osnine",
 "osx",
 "portables",
-"technology",
 "utilities",
 "wireless",
 "portables",
@@ -380,9 +381,9 @@
           this._completer.autoHighlight = false;
           
 
-	  // widget must be visible to move
+    // widget must be visible to move
         YAHOO.util.Dom.removeClass(this._widget, "hidden");
-	  // move widget to be near the 'source'
+    // move widget to be near the 'source'
         var pos = YAHOO.util.Dom.getXY(this._sourceEl);
         pos[1] += this._sourceEl.offsetHeight;
         YAHOO.util.Dom.setXY(this._widget, pos);
@@ -394,7 +395,7 @@
             YAHOO.util.Dom.removeClass(this._spareInput, "hidden");
             this._spareInput.value = "";
             this._spareInput.focus();
-            this._pending_hide = setTimeout("YAHOO.slashdot.gCompleterWidget._hide()", 15000);
+            this._pending_hide = setTimeout(YAHOO.slashdot.gCompleterWidget._hide, 15000);
           }
         else
           YAHOO.util.Dom.addClass(this._spareInput, "hidden");
@@ -507,9 +508,9 @@
           me._hide();
           break;
         case 13:
-        	// I'm sorry to say we have to test first, something somehow somewhere can still leave
-        	//	leave this listener dangling; want to look deeper into this, as this would _still_
-        	//	leave the listener dangling
+          // I'm sorry to say we have to test first, something somehow somewhere can still
+          //  leave this listener dangling; want to look deeper into this, as this would _still_
+          //  leave the listener dangling
           if ( me._completer )
             me._completer.unmatchedItemSelectEvent.fire(me._completer, me, me._completer._sCurQuery);
           break;
@@ -517,6 +518,6 @@
           if ( me._pending_hide )
             clearTimeout(me._pending_hide);
           if ( me._needsSpareInput() )
-          me._pending_hide = setTimeout("YAHOO.slashdot.gCompleterWidget._hide()", 15000);
+          me._pending_hide = setTimeout(YAHOO.slashdot.gCompleterWidget._hide, 15000);
       }
   }

Modified: slashjp/trunk/plugins/Ajax/htdocs/images/sectionprefs.js
===================================================================
--- slashjp/trunk/plugins/Ajax/htdocs/images/sectionprefs.js	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/plugins/Ajax/htdocs/images/sectionprefs.js	2008-04-01 04:03:21 UTC (rev 563)
@@ -7,7 +7,7 @@
 	createPopup(getXYForId('links-sections-title'), title, "sectionprefs", "", "Loading...");
 	
 	var url = 'ajax.pl';
-	var params = []; 
+	var params = {};
 	params['op'] = 'getSectionPrefsHTML';
 
 	ajax_update(params, 'sectionprefs-contents');
@@ -26,12 +26,11 @@
 }
 
 function postSectionPrefChanges(el) {
-	var params = [];
+	var params = {};
 	params['op'] = 'setSectionNexusPrefs';
 	params[el.name] = el.value;
-	var h = $H(params);
 	
-	var sec_pref_msg = $("sectionprefs-message");
+	var sec_pref_msg = $dom("sectionprefs-message");
 	sec_pref_msg.innerHTML = "Saving...";
 	var url = 'ajax.pl';
 	ajax_update(params, 'sectionprefs-message'); 

Modified: slashjp/trunk/plugins/Ajax/mysql_dump.sql
===================================================================
--- slashjp/trunk/plugins/Ajax/mysql_dump.sql	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/plugins/Ajax/mysql_dump.sql	2008-04-01 04:03:21 UTC (rev 563)
@@ -155,7 +155,7 @@
 INSERT INTO ajax_ops VALUES (NULL, 'remarks_config_save', 'Slash::Remarks', 'ajaxConfigSave', 'ajax_admin', 'createuse');
 
 # signoff
-INSERT INTO ajax_ops VALUES (NULL, 'admin_signoff', 'Slash::Admin', 'ajax_signoff', 'ajax_admin', 'use');
+INSERT INTO ajax_ops VALUES (NULL, 'admin_signoff', 'Slash::Admin', 'ajax_signoff', 'ajax_user_static', 'createuse');
 
 # slashboxes
 INSERT INTO ajax_ops VALUES (NULL, 'admin_slashdbox', 'Slash::Admin', 'ajax_slashdbox', 'ajax_admin', 'createuse');

Modified: slashjp/trunk/plugins/Ajax/templates/edit_comment;ajax;default
===================================================================
--- slashjp/trunk/plugins/Ajax/templates/edit_comment;ajax;default	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/plugins/Ajax/templates/edit_comment;ajax;default	2008-04-01 04:03:21 UTC (rev 563)
@@ -20,16 +20,16 @@
 edit_comment
 __template__
 <div id="wide">
-[% IF pid %]
-        [% PROCESS titlebar title="Reply to: $reply.subject" %]
-[% ELSE %]
-        [% PROCESS titlebar title="Reply to: XXXXX" %]
-[% END %]
+[% this_title = pid ? reply.subject : discussion.title;
+   this_title = this_title | strip_html;
+   PROCESS titlebar title="Reply to: $this_title" %]
 
+<p>If you have difficulty with this form, please use the <a href="[% gSkin.rootdir %]/comments.pl?op=Reply&amp;sid=[% sid %]&amp;pid=[% pid %]">old form</a>.</p>
+
 [% IF user.is_anon %]<p>
 	[% IF constants.allow_anonymous %]
 You are not logged in.  You can <a href=\"${gSkin.rootdir}/login.pl\">log
-in now</a>, or <a href="[% gSkin.rootdir %]/users.pl">Create an Account</a>/
+in now</a>, or <a href="[% gSkin.rootdir %]/users.pl">Create an Account</a>.
 	[% ELSE %]
 You are not logged in.  You can <a href=\"${gSkin.rootdir}/login.pl\">log
 in now</a>, <a href="[% gSkin.rootdir %]/users.pl">Create an Account</a>,
@@ -43,22 +43,28 @@
 	<div id="replyto_reply_[% pid %]" class="replyto_reply">
 <input type="hidden" name="sid" value="[% sid %]">
 [% IF pid %]<input type="hidden" name="pid" value="[% pid %]">[% END %]
-[% IF gotmodwarning %]<input type="hidden" name="gotmodwarning" value="1">[% END # XXXXX %]
+<input type="hidden" name="gotmodwarning_[% pid %]" id="gotmodwarning_[% pid %]" value="0">
 [% reskey_label = "reskey_reply_$pid"; PROCESS reskey_tag %]
-<p><input type="text" name="postersubj_[% pid %]" id="postersubj_[% pid %]" value="Re:[% reply.subject | strip_attribute %]" size="50" maxlength="50">
-[<a href="[% gSkin.rootdir %]/my/comments" onclick="getModalPrefs('d2_posting', 'Discussion 2'); return false;">Customize Posting Preferences</a>]</p>
+<p><input type="text" name="postersubj_[% pid %]" id="postersubj_[% pid %]" value="[% form.postersubj | strip_attribute %]" size="50" maxlength="50">
+[% UNLESS user.is_anon %][<a href="[% gSkin.rootdir %]/my/comments" onclick="getModalPrefs('d2_posting', 'Discussion 2'); return false;">Options</a>]
+[%- IF constants.allow_anonymous && user.karma > -1 && (discussion.commentstatus == 'enabled' || discussion.commentstatus == 'logged_in') -%]
+	<input type="checkbox" name="postanon_[% pid %]" id="postanon_[% pid %]"> Post Anonymously
+[%- END %]</p>[% END %]
 <p><textarea wrap="virtual" name="postercomment_[% pid %]" id="postercomment_[% pid %]" rows="[% user.textarea_rows || constants.textarea_rows %]" cols="[% user.textarea_cols || constants.textarea_cols %]"></textarea></p>
 
-[% IF constants.allow_anonymous && user.karma > -1 && !user.is_anon && (discussion.commentstatus == 'enabled' || discussion.commentstatus == 'logged_in')  %]
-	<p><input type="checkbox" name="postanon"> Post Anonymously</p>
-[% END %]
-
 	</div>
 	<div id="replyto_msg_[% pid %]" class="replyto_msg"></div>
-	<div id="replyto_buttons_[% pid %]" class="replyto_buttons">
-<input type="button" name="preview_[% pid %]"    id="preview_[% pid %]"    value="Preview" class="button" onclick="previewReply([% pid %]); return false;">
-<input type="button" name="submit_[% pid %]"     id="submit_[% pid %]"     value="Submit"  class="button" onclick="fetchEl('preview_[% pid %]').style.display='inline'; fetchEl('submit_[% pid %]').style.display='none';  return false;" style="display: none">
-<input type="button" name="quotereply_[% pid %]" id="quotereply_[% pid %]" value="Quote"   class="button" onclick="quoteReply([% pid %]); return false;">
+	<div class="replyto_buttons">
+		<span id="replyto_buttons_1_[% pid %]">
+<input type="button" name="preview_[% pid %]"    id="preview_[% pid %]"    value="Preview"          class="button" onclick="previewReply([% pid %]); return false;">
+[%- IF pid # not for root-level reply %]
+<input type="button" name="quotereply_[% pid %]" id="quotereply_[% pid %]" value="Quote Parent"     class="button" onclick="quoteReply([% pid %]);   return false;">[% END %]
+		</span><span id="replyto_buttons_2_[% pid %]" style="display: none">
+<input type="button" name="submit_[% pid %]"     id="submit_[% pid %]"     value="Submit"           class="button" onclick="submitReply([% pid %]);  return false;">
+<input type="button" name="edit_[% pid %]"       id="edit_[% pid %]"       value="Continue Editing" class="button" onclick="editReply([% pid %]);    return false;">
+		</span><span id="replyto_buttons_3_[% pid %]">
+<input type="button" name="cancel_[% pid %]"     id="cancel_[% pid %]"     value="Cancel"           class="button" onclick="cancelReply([% pid %]);  return false;">
+		</span>
 	</div>
 </form>
 [% END # IF !user.is_anon || constants.allow_anonymous %]

Modified: slashjp/trunk/plugins/Ajax/templates/prefs_d2;ajax;default
===================================================================
--- slashjp/trunk/plugins/Ajax/templates/prefs_d2;ajax;default	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/plugins/Ajax/templates/prefs_d2;ajax;default	2008-04-01 04:03:21 UTC (rev 563)
@@ -42,7 +42,6 @@
                 <br>
 
                 [% IF user.discussion2 && user.discussion2 == "slashdot" %]
-                        <blockquote>
                         <div>
                         Retrieve [% comment_q_name = (user.is_subscriber || user.is_admin) ? 'd2_comment_q_all' : 'd2_comment_q';
                            comment_q = Slash.db.getDescriptions(comment_q_name);
@@ -54,7 +53,6 @@
                            comment_order_def = user.d2_comment_order || 0; # score
                            Slash.createSelect('d2_comment_order', comment_order, comment_order_def, 1) %] Comments First
                         </div>
-                        </blockquote>
                 [% END %]
 
                 <div id="modalprefhelp_sortorder" class="modalprefhelp" style="display: none;">
@@ -97,11 +95,11 @@
                         Show the actual domain of any link in brackets.
                 </div>
 
-                <blockquote><div>
+                <div>
                 <input type="radio" name="domaintags" [% domaintags.0 %] value=0> Never show link domains<br>
                 <input type="radio" name="domaintags" [% domaintags.1 %] value=1> Show the links domain only in recommended situations<br>
                 <input type="radio" name="domaintags" [% domaintags.2 %] value=2> Always show link domains
-                </div></blockquote>
+                </div>
 
         </td>
         </tr>

Modified: slashjp/trunk/plugins/Email/templates/dispStory;email;default
===================================================================
--- slashjp/trunk/plugins/Email/templates/dispStory;email;default	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/plugins/Email/templates/dispStory;email;default	2008-04-01 04:03:21 UTC (rev 563)
@@ -56,7 +56,7 @@
 To opt-out of further emailings:
     [% thisurl %]/email.pl?op=optout_form
 
-Copyright 1997-2005 [% constants.sitepublisher %].  All rights reserved.
+Copyright 1997-2008 [% constants.sitepublisher %].  All rights reserved.
 
 __version__
 $Id$

Modified: slashjp/trunk/plugins/FAQSlashdot/faq/friends.shtml
===================================================================
--- slashjp/trunk/plugins/FAQSlashdot/faq/friends.shtml	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/plugins/FAQSlashdot/faq/friends.shtml	2008-04-01 04:03:21 UTC (rev 563)
@@ -7,14 +7,21 @@
 <!--#include virtual="/slashcssbase.inc"-->
 <!--#include virtual="/slashhead-gen-full.inc"-->
 
-        <ul class="menu">
-                 <li><a href="accounts.shtml">Previous section</a></li>
-                 <li><a href="subscriptions.shtml">Next section</a></li>
-                 <li><a href="index.shtml">Index</a></li>
-        </ul>
-        
-        <hr>
+        <div class="generaltitle">
+                <div class="title">
+                        <h3>Friends and Journals</h3>
+                </div>
+        </div>
 
+        <div id="usermenu">
+                <ul class="menu" style="padding: 5px 10px 5px 10px;">
+                <li><span class="begin"><a href="/help" class="begin">Return to Help & Preferences</a></span></li>
+                <li><span class="begin"><a href="/faq" class="begin">Return to the FAQ</a></span></li>
+                <li><span class="begin"><a href="accounts.shtml" class="begin">Previous Section</a></span></li>
+                <li><span class="begin"><a href="subscriptions.shtml" class="begin">Next Section</a></span></li>
+                </ul>
+        </div>
+
 <h2><a name="fj100" id="fj100">Where is my journal?</a></h2>
         <p><em>Your</em> journal can be found at <a href="http://slashdot.org/my/journal">http://slashdot.org/my/journal</a>.</p>
         <p><em><small>

Modified: slashjp/trunk/plugins/FireHose/FireHose.pm
===================================================================
--- slashjp/trunk/plugins/FireHose/FireHose.pm	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/plugins/FireHose/FireHose.pm	2008-04-01 04:03:21 UTC (rev 563)
@@ -563,7 +563,7 @@
 				push @where, "popularity >= $pop_q";
 			}
 		}
-		if ($user->{is_admin}) {
+		if ($user->{is_admin} || $user->{acl}{signoff_allowed}) {
 			my $signoff_label = 'sign' . $user->{uid} . 'ed';
 
 			if ($options->{unsigned}) {
@@ -1126,7 +1126,7 @@
 	my $newtagspreloadtext = join ' ', @newtagspreload;
 	#print STDERR "ajaxGetUserFirehose $newtagspreloadtext\n\n";
 
-	return slashDisplay('tagsfirehosedivuser', {
+	return slashDisplay($form->{nodnix} ? 'tagsnodnixuser' : 'tagsfirehosedivuser', {
 		id =>		$id,
 		newtagspreloadtext =>	$newtagspreloadtext,
 	}, { Return => 1 });
@@ -1518,9 +1518,13 @@
 
 }
 
+# Return a positive number if data was altered, 0 if it was not,
+# or undef on error.
+
 sub setFireHose {
 	my($self, $id, $data) = @_;
-	return unless $id && $data;
+	return undef unless $id && $data;
+	return 0 if !%$data;
 	my $id_q = $self->sqlQuote($id);
 	
 	my $mcd = $self->getMCD();
@@ -1549,7 +1553,7 @@
 		$self->setGlobjAdminnote($globjid, $note);
 	}
 
-	return if !keys %$data;
+	return 0 if !keys %$data;
 
 	my $text_data = {};
 
@@ -1558,9 +1562,11 @@
 	$text_data->{bodytext} = delete $data->{bodytext} if exists $data->{bodytext};
 	$text_data->{media} = delete $data->{media} if exists $data->{media};
 
-	$self->sqlUpdate('firehose', $data, "id=$id_q");
-	$self->sqlUpdate('firehose_text', $text_data, "id=$id_q") if keys %$text_data;
-	
+	my $rows = $self->sqlUpdate('firehose', $data, "id=$id_q");
+#{ use Data::Dumper; my $dstr = Dumper($data); $dstr =~ s/\s+/ /g; print STDERR "setFireHose A rows=$rows for id=$id_q data: $dstr\n"; }
+	$rows += $self->sqlUpdate('firehose_text', $text_data, "id=$id_q") if keys %$text_data;
+#{ use Data::Dumper; my $dstr = Dumper($text_data); $dstr =~ s/\s+/ /g; print STDERR "setFireHose B rows=$rows for id=$id_q data: $dstr\n"; }
+
 	if ($mcd) {
 		 $mcd->delete("$mcdkey:$id", 3);
 	}
@@ -1572,6 +1578,8 @@
 #		$status = 'deleted' if $data->{accepted} eq 'yes' || $data->{rejected} eq 'yes';
 		$searchtoo->storeRecords(firehose => $id, { $status => 1 });
 	}
+
+	return $rows;
 }
 
 sub dispFireHose {
@@ -1597,6 +1605,9 @@
 	return [] unless $item && $user->{is_admin};
 	my $subnotes_ref = [];
 	my $sub_memory = $self->getSubmissionMemory();
+	my $url = "";
+	$url = $self->getUrl($item->{url_id}) if $item->{url_id};
+
 	foreach my $memory (@$sub_memory) {
 		my $match = $memory->{submatch};
 
@@ -1604,7 +1615,8 @@
 		    $item->{name}  =~ m/$match/i ||
 		    $item->{title}  =~ m/$match/i ||
 		    $item->{ipid}  =~ m/$match/i ||
-		    $item->{introtext} =~ m/$match/i) {
+		    $item->{introtext} =~ m/$match/i ||
+		    $url =~ m/$match/i) {
 			push @$subnotes_ref, $memory;
 		}
 	}

Modified: slashjp/trunk/plugins/FireHose/PLUGIN
===================================================================
--- slashjp/trunk/plugins/FireHose/PLUGIN	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/plugins/FireHose/PLUGIN	2008-04-01 04:03:21 UTC (rev 563)
@@ -32,6 +32,7 @@
 template=templates/tagsfirehosedivadmin;misc;default
 template=templates/tagsfirehosedivtagbox;misc;default
 template=templates/tagsfirehosedivuser;misc;default
+template=templates/tagsnodnixuser;misc;default
 task=firehose_reject_old.pl
 task=firehose_backend.pl
 task=firehose_get_thumbnails.pl

Modified: slashjp/trunk/plugins/FireHose/firehose.css
===================================================================
--- slashjp/trunk/plugins/FireHose/firehose.css	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/plugins/FireHose/firehose.css	2008-04-01 04:03:21 UTC (rev 563)
@@ -449,9 +449,15 @@
 .embed #nod-user-tags, #console #nod-input {left: 63px !important;}
 .embed #nix-user-tags, #console #nix-input {left: 68px !important;}
 .embed #nodmenu ul, #console #nodmenu ul {margin: 0 0 0 43px; background: #222; opacity: .9; border: 1px solid #111;}
-ol#nod-hardened, ol#nix-hardened, ol#nod-hardened li, ol#nix-hardened li {background: black !important;}
-ol#nod-hardened, ol#nix-hardened {margin-left: 0 !important; padding-left: 0 !important;}
+ol#nod-hardened, ol#nix-hardened, ol#nod-hardened li, ol#nix-hardened li {position: relative; background: black !important;}
+ol#nod-hardened, ol#nix-hardened {margin-left: 0 !important; padding-left: 0 !important; cursor: pointer;}
 
+.tag-actions {display: none; position: absolute; opacity: 0.6; top: -0.75em; right: 0; width: 1.5em; font-size: 120%; text-align: center; color: black; background-color: white; -moz-border-radius: 0.1em; -webkit-border-radius: 0.1em;}
+a.not-tag, a.del-tag {text-decoration: none;}
+li:hover .tag-actions {display: inline;}
+li .tag-actions:hover {opacity: 0.9;}
+li .tag-actions .not-tag:hover, li .tag-actions .del-tag:hover {color: red;}
+
 .article .title, .article h3, .article .generaltitle {border: none !important}
 .article .generaltitle, #firehoselist .article .title, .article h3 {line-height: 170% !important;}
 .briefarticle .generaltitle, .briefarticle .title, .briefarticle h3 {line-height: 160% !important;}

Modified: slashjp/trunk/plugins/FireHose/templates/firehose_tags_top;misc;default
===================================================================
--- slashjp/trunk/plugins/FireHose/templates/firehose_tags_top;misc;default	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/plugins/FireHose/templates/firehose_tags_top;misc;default	2008-04-01 04:03:21 UTC (rev 563)
@@ -26,7 +26,7 @@
 		[%- END -%]
 		[%- tags_seen.${parts.0} = 1 -%]
 	[%- END -%]
-	[%- IF item.type == "story" -%][% IF user.is_admin %][% PROCESS signoff stoid=item.srcid fhid = item.id %][% END %][% END %]
+	[%- IF item.type == "story" -%][% IF user.is_admin || user.acl.signoff_allowed %][% PROCESS signoff stoid=item.srcid fhid = item.id %][% END %][% END %]
 	[% IF item.type == "feed" && user.is_admin %]
 		[% feed_user = Slash.db.getUser(item.uid, "nickname"); %]
 		[% feed_user | strip_literal %]

Copied: slashjp/trunk/plugins/FireHose/templates/tagsnodnixuser;misc;default (from rev 561, slashjp/branches/upstream/current/plugins/FireHose/templates/tagsnodnixuser;misc;default)
===================================================================
--- slashjp/trunk/plugins/FireHose/templates/tagsnodnixuser;misc;default	                        (rev 0)
+++ slashjp/trunk/plugins/FireHose/templates/tagsnodnixuser;misc;default	2008-04-01 04:03:21 UTC (rev 563)
@@ -0,0 +1,23 @@
+__section__
+default
+__description__
+id = id
+newtagspreloadtext = text to preload the newtags-# field with
+
+WARNING: keep the related string in nodnix.js in sync with the <li> expansion, below
+__title__
+
+__page__
+misc
+__lang__
+en_US
+__name__
+tagsnodnixuser
+__template__
+[% FOREACH tag = newtagspreloadtext.split(' ') %]
+<li>[% tag %]<span class="tag-actions"><a class="not-tag" onmousedown="nodnix_not_tag('[% tag %]'); return false" href="#">!</a> <a class="del-tag" onmousedown="nodnix_del_tag('[% tag %]'); return false" href="#">x</a></span></li>
+[% END %]
+__seclev__
+10000
+__version__
+$Id: tagsnodnixuser;misc;default,v 1.4 2008/03/03 20:54:00 scc Exp $

Modified: slashjp/trunk/plugins/Messages/templates/dailyheadlines;messages;default
===================================================================
--- slashjp/trunk/plugins/Messages/templates/dailyheadlines;messages;default	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/plugins/Messages/templates/dailyheadlines;messages;default	2008-04-01 04:03:21 UTC (rev 563)
@@ -25,7 +25,7 @@
 [% END %]
 
 
-Copyright 1997-2007 [% constants.sitepublisher %].  All rights reserved.
+Copyright 1997-2008 [% constants.sitepublisher %].  All rights reserved.
 
 __seclev__
 500

Modified: slashjp/trunk/plugins/Messages/templates/dailynews;messages;default
===================================================================
--- slashjp/trunk/plugins/Messages/templates/dailynews;messages;default	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/plugins/Messages/templates/dailynews;messages;default	2008-04-01 04:03:21 UTC (rev 563)
@@ -52,7 +52,7 @@
 
 
 [% END %]
-Copyright 1997-2006 [% constants.sitepublisher %].  All rights reserved.
+Copyright 1997-2008 [% constants.sitepublisher %].  All rights reserved.
 
 __seclev__
 500

Modified: slashjp/trunk/plugins/Moderation/Moderation.pm
===================================================================
--- slashjp/trunk/plugins/Moderation/Moderation.pm	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/plugins/Moderation/Moderation.pm	2008-04-01 04:03:21 UTC (rev 563)
@@ -91,10 +91,12 @@
 				$self->countUsers({ max => 1 }), $self->getReasons
 			);
 
-			$html->{$score}  = "(Score:$points";
+			$html->{$score}  = "Score:$points";
+			$html->{$score} = qq[<a href="#" onclick="getModalPrefs('modcommentlog', 'Moderation Comment Log', $cid); return false">$html->{$score}</a>]
+				if $constants->{modal_prefs_active} && !$user->{is_anon};
 			$html->{$score} .= ", $reasons->{$comment->{reason}}{name}"
 				if $comment->{reason} && $reasons->{$comment->{reason}};
-			$html->{$score} .= ")";
+			$html->{$score} = "($html->{$score})";
 
 			my $ptstr = $user->{points} == 1 ? 'point' : 'points';
 			$html->{$select} = "Moderated '$reasons->{$reason}{name}.'  $user->{points} $ptstr left.";

Modified: slashjp/trunk/plugins/Rating/Rating.pm
===================================================================
--- slashjp/trunk/plugins/Rating/Rating.pm	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/plugins/Rating/Rating.pm	2008-04-01 04:03:21 UTC (rev 563)
@@ -52,13 +52,13 @@
 	my $extras;
 	$extras =  $slashdb->getNexusExtrasForChosen({$disc_skin->{nexus} => 1}, {content_type => "comment"}) if $disc_skin && $disc_skin->{nexus};
 
-	return unless $extras;
+	return 0 unless $extras;
 	
 	foreach my $extra(@$extras) {
 		$can_create_vote=1 if $extra->[1] eq "comment_vote";	
 	}
 
-	return unless $can_create_vote;
+	return 0 unless $can_create_vote;
 	
 	my $active = "yes";
 	my $val = 0;

Modified: slashjp/trunk/plugins/ResKey/mysql_dump.sql
===================================================================
--- slashjp/trunk/plugins/ResKey/mysql_dump.sql	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/plugins/ResKey/mysql_dump.sql	2008-04-01 04:03:21 UTC (rev 563)
@@ -54,7 +54,7 @@
 INSERT INTO reskey_resource_checks VALUES (NULL, 1, 'all', 'Slash::ResKey::Checks::AL2::NoPost',         501);
 INSERT INTO reskey_resource_checks VALUES (NULL, 1, 'all', 'Slash::ResKey::Checks::AL2::Spammer',        531);
 INSERT INTO reskey_resource_checks VALUES (NULL, 1, 'all', 'Slash::ResKey::Checks::Duration',            601);
-INSERT INTO reskey_resource_checks VALUES (NULL, 1, 'use', 'Slash::ResKey::Checks::ProxyScan',          1001);
+#INSERT INTO reskey_resource_checks VALUES (NULL, 1, 'use', 'Slash::ResKey::Checks::ProxyScan',          1001);
 
 # dummy example of how to disable the Slash::ResKey::Checks::User check for "touch"
 # (maybe, for example, because the check isn't needed)
@@ -66,8 +66,8 @@
 INSERT INTO reskey_vars VALUES (1, 'user_seclev', 0, 'Minimum seclev to use resource');
 INSERT INTO reskey_vars VALUES (1, 'duration_max-uses',      30, 'how many uses per timeframe');
 INSERT INTO reskey_vars VALUES (1, 'duration_max-failures',  10, 'how many failures per reskey');
-INSERT INTO reskey_vars VALUES (1, 'duration_uses',         120, 'min duration (in seconds) between uses');
-INSERT INTO reskey_vars VALUES (1, 'duration_creation-use',   5, 'min duration between (in seconds) creation and use');
+INSERT INTO reskey_vars VALUES (1, 'duration_uses',          60, 'min duration (in seconds) between uses');
+INSERT INTO reskey_vars VALUES (1, 'duration_creation-use',  10, 'min duration between (in seconds) creation and use');
 
 
 

Modified: slashjp/trunk/plugins/Submit/submit.pl
===================================================================
--- slashjp/trunk/plugins/Submit/submit.pl	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/plugins/Submit/submit.pl	2008-04-01 04:03:21 UTC (rev 563)
@@ -265,6 +265,9 @@
 		}
 	}
 
+	my $url = "";
+	$url = $slashdb->getUrl($sub->{url_id}) if $sub->{url_id};
+
         foreach my $memory (@$sub_memory) {
                 my $match = $memory->{submatch};
 
@@ -272,7 +275,8 @@
                     $sub->{name}  =~ m/$match/i ||
                     $sub->{subj}  =~ m/$match/i ||
                     $sub->{ipid}  =~ m/$match/i ||
-                    $sub->{story} =~ m/$match/i) {
+                    $sub->{story} =~ m/$match/i ||
+		    $url =~ m/$match/i ) {
                         push @$subnotes_ref, $memory;
                 }
         }

Modified: slashjp/trunk/plugins/TagModeration/TagModeration.pm
===================================================================
--- slashjp/trunk/plugins/TagModeration/TagModeration.pm	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/plugins/TagModeration/TagModeration.pm	2008-04-01 04:03:21 UTC (rev 563)
@@ -91,10 +91,12 @@
 				$self->countUsers({ max => 1 }), $self->getReasons
 			);
 
-			$html->{$score}  = "(Score:$points";
+			$html->{$score}  = "Score:$points";
+			$html->{$score} = qq[<a href="#" onclick="getModalPrefs('modcommentlog', 'Moderation Comment Log', $cid); return false">$html->{$score}</a>]
+				if $constants->{modal_prefs_active} && !$user->{is_anon};
 			$html->{$score} .= ", $reasons->{$comment->{reason}}{name}"
 				if $comment->{reason} && $reasons->{$comment->{reason}};
-			$html->{$score} .= ")";
+			$html->{$score} = "($html->{$score})";
 
 			my $ptstr = $user->{points} == 1 ? 'point' : 'points';
 			$html->{$select} = "Moderated '$reasons->{$reason}{name}.'  $user->{points} $ptstr left.";

Modified: slashjp/trunk/plugins/Tags/PLUGIN
===================================================================
--- slashjp/trunk/plugins/Tags/PLUGIN	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/plugins/Tags/PLUGIN	2008-04-01 04:03:21 UTC (rev 563)
@@ -6,6 +6,7 @@
 mysql_schema=mysql_schema.sql
 requiresplugin=Ajax
 task=tagbox.pl
+task=tags_tagnamecache.pl
 task=tags_udc.pl
 task=tags_updateclouts.pl
 template=templates/data;tags;default

Modified: slashjp/trunk/plugins/Tags/Tags.pm
===================================================================
--- slashjp/trunk/plugins/Tags/Tags.pm	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/plugins/Tags/Tags.pm	2008-04-01 04:03:21 UTC (rev 563)
@@ -603,6 +603,9 @@
 	}
 
 	# Is it descriptive?
+	# XXX this should be optimized by retrieving the list of _all_
+	# descriptive tagnames in memcached or the local closure and
+	# doing a lookup on that.
 	if ($types->{describe} && !$clid) {
 		$tn_data = $self->getTagnameDataFromId($tagnameid);
 		$clid = $types->{describe} if $tn_data->{descriptive};
@@ -685,7 +688,8 @@
 	for my $uid (keys %uid) {
 		# XXX getUser($foo, 'clout') does not work at the moment,
 		# so getUser($foo)->{clout} is used instead
-		$user_clout_hr->{$uid} = $self->getUser($uid)->{clout};
+		my $user = $self->getUser($uid);
+		$user_clout_hr->{$uid} = $self->getUser($uid)->{clout} if $user;
 	}
 
 
@@ -704,7 +708,9 @@
 			$tagname_clid = $default_clout_clid;
 		}
 		my $tagname_clout_name = $clout_types->{ $tagname_clid };
-		$tag_hr->{user_clout}    = $mult * $user_clout_hr   ->{$tag_hr->{uid}}{$tagname_clout_name};
+		my $clout = $user_clout_hr->{$tag_hr->{uid}};
+		my $clout_specific = $clout ? $clout->{$tagname_clout_name} : 0;
+		$tag_hr->{user_clout}    = $mult * $clout_specific;
 		$tag_hr->{total_clout} = $tag_hr->{tag_clout} * $tag_hr->{tagname_clout} * $tag_hr->{user_clout};
 	}
 }
@@ -849,7 +855,7 @@
 #		my $value = $mcd->get("$mcdkey$name");
 #		return $value if defined $value;
 #	}
-	my $private_clause = $options->{include_private} ? '' : " AND private='no'";
+	my $private_clause = ref($options) && $options->{include_private} ? '' : " AND private='no'";
 	my $id = $self->getTagnameidFromNameIfExists($name);
 	return [ ] if !$id;
 	my $hr_ar = $self->sqlSelectAllHashrefArray(
@@ -965,7 +971,7 @@
 	my($self, $constants, $user, $form) = @_;
 
 	my $sidenc = $form->{sidenc};
-	my $sid = $sidenc; $sid =~ tr{:}{/};
+	my $sid = $sidenc; $sid =~ tr{-}{/};
 	my $stoid = $self->getStoidFromSid($sid);
 	my $tags_reader = getObject('Slash::Tags', { db_type => 'reader' });
 #print STDERR scalar(localtime) . " ajaxGetUserStory for stoid=$stoid sidenc=$sidenc tr=$tags_reader\n";
@@ -1023,7 +1029,7 @@
 sub ajaxGetAdminStory {
 	my($slashdb, $constants, $user, $form) = @_;
 	my $sidenc = $form->{sidenc};
-	my $sid = $sidenc; $sid =~ tr{:}{/};
+	my $sid = $sidenc; $sid =~ tr{-}{/};
 
 	if (!$sid || $sid !~ regexSid() || !$user->{is_admin}) {
 		return getData('error', {}, 'tags');
@@ -1155,7 +1161,7 @@
 sub ajaxCreateForStory {
 	my($slashdb, $constants, $user, $form) = @_;
 	my $sidenc = $form->{sidenc};
-	my $sid = $sidenc; $sid =~ tr{:}{/};
+	my $sid = $sidenc; $sid =~ tr{-}{/};
 	my $tags = getObject('Slash::Tags');
 	my $tagsstring = $form->{tags};
 	if (!$sid || $sid !~ regexSid() || $user->{is_anon} || !$tags) {
@@ -1189,6 +1195,30 @@
 	return $retval;
 }
 
+sub ajaxDeactivateTag {
+	my($self, $constants, $user, $form) = @_;
+	my $type = $form->{type} || "stories";
+	my $tags = getObject('Slash::Tags'); # XXX isn't this the same as $self? -Jamie
+
+	my ($table, $id);
+
+	if ($type eq "firehose") {
+		my $firehose = getObject("Slash::FireHose");
+		my $item = $firehose->getFireHose($form->{id});
+		($table, $id) = $tags->getGlobjTarget($item->{globjid});
+	} else {
+		# XXX doesn't work yet for stories or urls
+		return;
+	}
+
+	$tags->deactivateTag({
+		uid =>		$user->{uid},
+		name =>		$form->{tag},
+		table =>	$table,
+		id =>		$id,
+	});
+}
+
 sub ajaxProcessAdminTags {
 	my($slashdb, $constants, $user, $form) = @_;
 #print STDERR "ajaxProcessAdminTags\n";
@@ -1197,7 +1227,7 @@
 	my($id, $table, $sid, $sidenc, $itemid);
 	if ($type eq "stories") {
 		$sidenc = $form->{sidenc};
-		$sid = $sidenc; $sid =~ tr{:}{/};
+		$sid = $sidenc; $sid =~ tr{-}{/};
 		$id = $slashdb->getStoidFromSid($sid);
 		$table = "stories";
 	} elsif ($type eq "urls") {
@@ -1293,7 +1323,7 @@
 	my $table;
 	if ($form->{type} eq "stories") {
 		my $sidenc = $form->{sidenc};
-		my $sid = $sidenc; $sid =~ tr{:}{/};
+		my $sid = $sidenc; $sid =~ tr{-}{/};
 		$id = $slashdb->getStoidFromSid($sid);
 		$table = "stories"
 	} elsif ($form->{type} eq "urls") {
@@ -1380,6 +1410,13 @@
 	my $len = length($prefix);
 	my $notize = $form->{prefix} =~ /^([-!])/ ? $1 : '';
 
+	my $minlen = $constants->{tags_prefixlist_minlen} || 3;
+	if ($len < $minlen) {
+		# Too short to give a meaningful suggestion, and the
+		# shorter the prefix the longer the DB query takes.
+		return '';
+	}
+
 	my $tnhr = $tags_reader->listTagnamesByPrefix($prefix);
 
 	my @priority =
@@ -1683,10 +1720,10 @@
 sub listTagnamesActive {
 	my($self, $options) = @_;
 	my $constants = getCurrentStatic();
-	my $max_num =         $options->{max_num}         || 100;
-	my $seconds =         $options->{seconds}         || (3600*6);
-	my $include_private = $options->{include_private} || 0;
-	my $min_slice =       $options->{min_slice}       || 0;
+	my $max_num =         ref($options) && $options->{max_num}         || 100;
+	my $seconds =         ref($options) && $options->{seconds}         || (3600*6);
+	my $include_private = ref($options) && $options->{include_private} || 0;
+	my $min_slice =       ref($options) && $options->{min_slice}       || 0;
 	$min_slice = 0 if !$constants->{plugin}{FireHose};
 
 	# This seems like a horrendous query, but I _think_ it will run
@@ -1791,8 +1828,8 @@
 sub listTagnamesRecent {
 	my($self, $options) = @_;
 	my $constants = getCurrentStatic();
-	my $seconds =         $options->{seconds}         || (3600*6);
-	my $include_private = $options->{include_private} || 0;
+	my $seconds =         ref($options) && $options->{seconds}         || (3600*6);
+	my $include_private = ref($options) && $options->{include_private} || 0;
 	my $private_clause = $include_private ? '' : " AND private='no'";
 	my $recent_ar = $self->sqlSelectColArrayref(
 		'DISTINCT tagnames.tagname',
@@ -1824,24 +1861,65 @@
 	$a2 cmp $b2 || $a1 cmp $b1;
 }
 
+{ # closure
+my $tagname_cache_lastcheck = 1;
 sub listTagnamesByPrefix {
 	my($self, $prefix_str, $options) = @_;
 	my $constants = getCurrentStatic();
 	my $reader = getObject('Slash::DB', { db_type => 'reader' });
-	my $like_str = $self->sqlQuote("$prefix_str%");
-	my $minc = $self->sqlQuote($options->{minc} || $constants->{tags_prefixlist_minc} ||  4);
-	my $mins = $self->sqlQuote($options->{mins} || $constants->{tags_prefixlist_mins} ||  3);
-	my $num  = $options->{num}  || $constants->{tags_prefixlist_num};
-	$num = 10 if !$num || $num !~ /^(\d+)$/ || $num < 1;
+	my $ret_hr;
 
 	my $mcd = undef;
 	$mcd = $self->getMCD() unless $options;
 	my $mcdkey = "$self->{_mcd_keyprefix}:tag_prefx:";
 	if ($mcd) {
-		my $ret_str = $mcd->get("$mcdkey$prefix_str");
-		return $ret_str if $ret_str;
+		$ret_hr = $mcd->get("$mcdkey$prefix_str");
+		return $ret_hr if $ret_hr;
 	}
 
+	# If the tagname_cache table has been filled, use it.
+	# Otherwise, perform an expensive query directly.
+	# The logic is that $tagname_cache_lastcheck stays a
+	# large positive number (a timestamp) until we determine
+	# that the table _does_ have rows, at which point that
+	# number drops to 0.  Once its value hits 0, it is never
+	# checked again.
+	if ($tagname_cache_lastcheck > 0 && $tagname_cache_lastcheck < time()-3600) {
+		my $rows = $reader->sqlCount('tagname_cache');
+		$tagname_cache_lastcheck = $rows ? 0 : time;
+	}
+	my $use_cache_table = $tagname_cache_lastcheck ? 0 : 1;
+	if ($use_cache_table) {
+		$ret_hr = $self->listTagnamesByPrefix_cache($prefix_str, $options);
+	} else {
+		$ret_hr = $self->listTagnamesByPrefix_direct($prefix_str, $options);
+	}
+
+	if ($mcd) {
+		# The expiration we use is much longer than the tags_cache_expire
+		# var since the cache data changes only once a day.
+		$mcd->set("$mcdkey$prefix_str", $ret_hr, 3600);
+	}
+
+	return $ret_hr;
+}
+}
+
+# This is a quick-and-dirty (and not very accurate) estimate which
+# is only performed for a site which has not built its tagname_cache
+# table yet.  Hopefully most sites will use this the first day the
+# Tags plugin is installed and then never again.
+
+sub listTagnamesByPrefix_direct {
+	my($self, $prefix_str, $options) = @_;
+	my $constants = getCurrentStatic();
+	my $like_str = $self->sqlQuote("$prefix_str%");
+	my $minc = $self->sqlQuote($options->{minc} || $constants->{tags_prefixlist_minc} ||  4);
+	my $mins = $self->sqlQuote($options->{mins} || $constants->{tags_prefixlist_mins} ||  3);
+	my $num  = $options->{num}  || $constants->{tags_prefixlist_num};
+	$num = 10 if !$num || $num !~ /^(\d+)$/ || $num < 1;
+
+	my $reader = getObject('Slash::DB', { db_type => 'reader' });
 	my $ar = $reader->sqlSelectAllHashrefArray(
 		'tagname,
 		 COUNT(DISTINCT tags.uid) AS c,
@@ -1860,13 +1938,25 @@
 	for my $hr (@$ar) {
 		$ret_hr->{ $hr->{tagname} } = $hr->{sc};
 	}
-	if ($mcd) {
-		my $mcdexp = $constants->{tags_cache_expire} || 180;
-		$mcd->set("$mcdkey$prefix_str", $ret_hr, $mcdexp)
-	}
 	return $ret_hr;
 }
 
+sub listTagnamesByPrefix_cache {
+	my($self, $prefix_str, $options) = @_;
+	my $constants = getCurrentStatic();
+	my $like_str = $self->sqlQuote("$prefix_str%");
+	my $num  = $options->{num}  || $constants->{tags_prefixlist_num};
+	$num = 10 if !$num || $num !~ /^(\d+)$/ || $num < 1;
+
+	my $reader = getObject('Slash::DB', { db_type => 'reader' });
+	my $ret_hr = $reader->sqlSelectAllKeyValue(
+		'tagname, weight',
+		'tagname_cache',
+		"tagname LIKE $like_str",
+		"ORDER BY weight DESC LIMIT $num");
+	return $ret_hr;
+}
+
 sub getPrivateTagnames {
 	my ($self) = @_;
 	my $user = getCurrentUser;

Modified: slashjp/trunk/plugins/Tags/mysql_dump.sql
===================================================================
--- slashjp/trunk/plugins/Tags/mysql_dump.sql	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/plugins/Tags/mysql_dump.sql	2008-04-01 04:03:21 UTC (rev 563)
@@ -10,6 +10,7 @@
 INSERT INTO vars (name, value, description) VALUES ('tags_cache_expire', '180', 'Local data cache expiration for tags');
 INSERT INTO vars (name, value, description) VALUES ('tags_list_mintc', '4', 'Minimum value of total_clout for tagged items shown at /tags/foo');
 INSERT INTO vars (name, value, description) VALUES ('tags_prefixlist_minc', '4', 'Minimum value of c (count) for tagnames returned by listTagnamesByPrefix');
+INSERT INTO vars (name, value, description) VALUES ('tags_prefixlist_minlen', '3', 'Minimum length of a tag prefix to bother looking up suggestions for');
 INSERT INTO vars (name, value, description) VALUES ('tags_prefixlist_mins', '3', 'Minimum value of s (clout sum) for tagnames returned by listTagnamesByPrefix');
 INSERT INTO vars (name, value, description) VALUES ('tags_prefixlist_num', '10', 'Number of tagnames returned by listTagnamesByPrefix');
 INSERT INTO vars (name, value, description) VALUES ('tags_prefixlist_priority', 'back bookmark feed hold journal none quik submission story', 'Tagnames to give priority to on autocomplete');
@@ -48,6 +49,7 @@
 INSERT INTO ajax_ops VALUES (NULL, 'tags_admin_commands', 'Slash::Tags', 'ajaxProcessAdminTags', 'ajax_admin', 'use');
 INSERT INTO ajax_ops VALUES (NULL, 'tags_history', 'Slash::Tags', 'ajaxTagHistory', 'ajax_admin', 'createuse');
 INSERT INTO ajax_ops VALUES (NULL, 'tags_list_tagnames', 'Slash::Tags', 'ajaxListTagnames', 'ajax_tags_read', 'createuse');
+INSERT INTO ajax_ops VALUES (NULL, 'tags_deactivate', 'Slash::Tags', 'ajaxDeactivateTag', 'ajax_tags_write', 'use');
 
 INSERT INTO menus VALUES (NULL, 'tagszg', 'Active', 'active', '[% gSkin.rootdir %]/tags',        1, 1, 1);
 INSERT INTO menus VALUES (NULL, 'tagszg', 'Recent', 'recent', '[% gSkin.rootdir %]/tags/recent', 1, 1, 2);

Modified: slashjp/trunk/plugins/Tags/mysql_schema.sql
===================================================================
--- slashjp/trunk/plugins/Tags/mysql_schema.sql	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/plugins/Tags/mysql_schema.sql	2008-04-01 04:03:21 UTC (rev 563)
@@ -33,7 +33,19 @@
 	PRIMARY KEY tagnameid (tagnameid),
 	UNIQUE tagname (tagname)
 ) TYPE=InnoDB;
-	
+
+# tagname_cache is not normalized because it's intended to be used
+# for quick lookups.
+
+DROP TABLE IF EXISTS tagname_cache;
+CREATE TABLE tagname_cache (
+	tagnameid	int UNSIGNED NOT NULL,
+	tagname		VARCHAR(64) NOT NULL,
+	weight		FLOAT UNSIGNED DEFAULT 0.0 NOT NULL,
+	PRIMARY KEY tagnameid (tagnameid),
+	UNIQUE tagname (tagname),
+) TYPE=InnoDB;
+
 DROP TABLE IF EXISTS tagname_params;
 CREATE TABLE tagname_params (
 	tagnameid	int UNSIGNED NOT NULL,

Modified: slashjp/trunk/plugins/Tags/tagbox.pl
===================================================================
--- slashjp/trunk/plugins/Tags/tagbox.pl	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/plugins/Tags/tagbox.pl	2008-04-01 04:03:21 UTC (rev 563)
@@ -282,7 +282,7 @@
 sub insert_feederlog {
 	my($tagbox, $feeder_ar) = @_;
 	for my $feeder_hr (@$feeder_ar) {
-main::tagboxLog("addFeederInfo: tbid=$tagbox->{tbid} tagid=$feeder_hr->{tagid} affected_id=$feeder_hr->{affected_id} imp=$feeder_hr->{importance}")
+{ my $fstr = Dumper($feeder_hr); $fstr =~ s/\s+/ /g; main::tagboxLog("addFeederInfo: tbid=$tagbox->{tbid} f: $fstr"); }
 	if (defined($feeder_hr->{tagid}));
 		$tagboxdb->addFeederInfo($tagbox->{tbid}, $feeder_hr);
 	}
@@ -301,6 +301,10 @@
 		for my $affected_hr (@$affected_ar) {
 			my $tagbox = $tagboxdb->getTagboxes($affected_hr->{tbid}, [qw( object )]);
 #my $ad = Dumper($affected_hr); $ad =~ s/\s+/ /g; my $tb = Dumper($tagbox); $tb =~ s/\s+/ /g; print STDERR "r_t_u affected_hr: $ad tagbox: $tb\n";
+if ($affected_hr->{tbid} == 17) {
+my $feeder_ar = $tagboxdb->sqlSelectAllHashrefArray('*', 'tagboxlog_feeder', "tbid=17 AND affected_id=$affected_hr->{affected_id}", 'ORDER BY tfid');
+print STDERR "r_t_u rows for tbid=17 id=$affected_hr->{affected_id}: " . Dumper($feeder_ar)
+}
 			$tagbox->{object}->run($affected_hr->{affected_id});
 			$tagboxdb->markTagboxRunComplete($affected_hr);
 			last if time() >= $run_until || $task_exit_flag;

Copied: slashjp/trunk/plugins/Tags/tags_tagnamecache.pl (from rev 561, slashjp/branches/upstream/current/plugins/Tags/tags_tagnamecache.pl)
===================================================================
--- slashjp/trunk/plugins/Tags/tags_tagnamecache.pl	                        (rev 0)
+++ slashjp/trunk/plugins/Tags/tags_tagnamecache.pl	2008-04-01 04:03:21 UTC (rev 563)
@@ -0,0 +1,144 @@
+#!/usr/bin/perl -w
+# This code is a part of Slash, and is released under the GPL.
+# Copyright 1997-2005 by Open Source Technology Group. See README
+# and COPYING for more information, or see http://slashcode.com/.
+# $Id: tags_tagnamecache.pl,v 1.1 2008/03/21 04:10:35 jamiemccarthy Exp $
+
+# Once a day, rewrite the tags_tagnamecache table, used for finding
+# tagname suggestions based on prefixes.
+
+use strict;
+use vars qw( %task $me $task_exit_flag );
+use Slash::DB;
+use Slash::Display;
+use Slash::Utility;
+use Slash::Constants ':slashd';
+
+(my $VERSION) = ' $Revision: 1.1 $ ' =~ /\$Revision:\s+([^\s]+)/;
+
+$task{$me}{timespec} = "30 6 * * *";
+$task{$me}{timespec_panic_1} = ''; # not that important
+$task{$me}{fork} = SLASHD_NOWAIT;
+
+$task{$me}{code} = sub {
+	my($virtual_user, $constants, $slashdb, $user) = @_;
+	my $tagsdb = getObject('Slash::Tags');
+	my $tagsdb_reader = getObject('Slash::Tags', { db_type => 'reader' });
+	my $daysback = $constants->{tags_tagnamecache_daysback} || 180;
+	my $min_tagid = getMinimumTagid($tagsdb, $daysback);
+	my $ar = getTagnameList($tagsdb_reader, $min_tagid);
+	my $rows_replaced = replaceTagnames($tagsdb, $ar);
+	my $rows_deleted = deleteTagnamesNotIn($tagsdb, $ar);
+	my $total_rows = $tagsdb->sqlCount('tagname_cache');
+	return "replaced $rows_replaced, deleted $rows_deleted, total $total_rows";
+};
+
+sub getMinimumTagid {
+	my($tagsdb, $daysback) = @_;
+	my $min = $tagsdb->sqlSelectNumericKeyAssumingMonotonic(
+		'tags', 'min', 'tagid',
+		"created_at >= DATE_SUB(NOW(), INTERVAL $daysback DAY)");
+	return $min;
+}
+
+# For now, let's include private tagnames in this list.  It's not
+# revealing private information since it's completely aggregated,
+# and while suggesting tagnames like 'nod' and 'nix' may not be
+# helpful, suggesting 'troll' and 'interesting' seems OK.
+
+sub getTagnameList {
+	my($tagsdb_reader, $min_tagid) = @_;
+	$min_tagid ||= 1;
+
+	my $constants = getCurrentStatic();
+	my $minc = $tagsdb_reader->sqlQuote($constants->{tags_prefixlist_minc} ||  4);
+        my $mins = $tagsdb_reader->sqlQuote($constants->{tags_prefixlist_mins} ||  3);
+	# $maxnum is to prevent $tagnameid_str from exceeding MySQL limits.
+	# Default max_allowed_packet should be 16 MB, so an ~80K query
+	# should be perfectly fine.
+	my $maxnum = 10000;
+
+	# Get the list of tagnameids sorted in a very rough order of
+	# "importance."
+	# Note that the query uses multiple columns to sort the data,
+	# but we skim off only the tagnameid on the client side since
+	# that's all we care about.
+
+	my $tagnameid_ar = $tagsdb_reader->sqlSelectColArrayref(
+		'tags.tagnameid,
+		 COUNT(DISTINCT tags.uid) AS c,
+		 SUM(tag_clout * IF(value IS NULL, 1, value)) AS s,
+		 COUNT(DISTINCT tags.uid)/3 + SUM(tag_clout * IF(value IS NULL, 1, value)) AS sc',
+		'tags, users_info, tagnames
+		 LEFT JOIN tagname_params USING (tagnameid)',
+		"tagnames.tagnameid=tags.tagnameid
+		 AND tags.uid=users_info.uid
+		 AND tags.inactivated IS NULL
+		 AND tagid >= $min_tagid",
+		"GROUP BY tags.tagnameid
+		 HAVING c >= $minc AND s >= $mins
+		 ORDER BY sc DESC, tagname ASC
+		 LIMIT $maxnum");
+	return [ ] if !$tagnameid_ar || !@$tagnameid_ar;
+	my $tagnameid_str = join(',', sort { $a <=> $b } @$tagnameid_ar);
+
+	# Now get the total list of tags (which will be very large,
+	# so this is a slow query)
+
+	my $tag_ar = $tagsdb_reader->sqlSelectAllHashrefArray(
+		'*, UNIX_TIMESTAMP(created_at) AS created_at_ut',
+		'tags',
+		"tagnameid IN ($tagnameid_str)
+		 AND tagid >= $min_tagid
+		 AND tags.inactivated IS NULL");
+
+	# This will call getUser() for every uid in the above list and
+	# getTagnameidClid() for every tagnameid (up to 10,000).  So
+	# this will be a very slow operation.
+
+	$tagsdb_reader->addCloutsToTagArrayref($tag_ar);
+
+	my $tagnameid_sum = { };
+	for my $hr (@$tag_ar) {
+		$tagnameid_sum->{ $hr->{tagnameid} } ||= 0;
+		$tagnameid_sum->{ $hr->{tagnameid} } += $hr->{total_clout};
+	}
+	my $ret_ar = [ ];
+	for my $tagnameid (@$tagnameid_ar) {
+		my $sum = $tagnameid_sum->{$tagnameid};
+		next unless $sum > 0;
+		my $tagname = $tagsdb_reader->getTagnameDataFromId($tagnameid)->{tagname};
+		push @$ret_ar, {
+			tagnameid =>	$tagnameid,
+			tagname =>	$tagname,
+			weight =>	$sum,
+		};
+	}
+
+	return $ret_ar;
+}
+
+sub replaceTagnames {
+	my($tagsdb, $ar) = @_;
+	my $rows = 0;
+	for my $hr (@$ar) {
+		$rows += $tagsdb->sqlReplace('tagname_cache', $hr);
+		Time::HiRes::sleep(0.01);
+	}
+	return $rows;
+}
+
+sub deleteTagnamesNotIn {
+	my($tagsdb, $ar) = @_;
+	my $tagnameid_str = join(',',
+		sort { $a <=> $b }
+		map { $_->{tagnameid} }
+		@$ar
+	);
+	my $rows = $tagsdb->sqlDelete('tagname_cache',
+		"tagnameid NOT IN ($tagnameid_str)");
+	return $rows;
+}
+
+1;
+

Modified: slashjp/trunk/plugins/Tags/templates/tagsstorydivtagbox;misc;default
===================================================================
--- slashjp/trunk/plugins/Tags/templates/tagsstorydivtagbox;misc;default	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/plugins/Tags/templates/tagsstorydivtagbox;misc;default	2008-04-01 04:03:21 UTC (rev 563)
@@ -13,7 +13,7 @@
 __name__
 tagsstorydivtagbox
 __template__
-[% sidenc = story.sid.replace("/",":") %]
+[% sidenc = story.sid.replace("/","-")    # no '/'s in html ids (see: http://www.w3.org/TR/html401/types.html) %]
 [% IF user.tags_canread_stories %]
 <div id="tagbox-[% sidenc %]" class="tags">
 

Modified: slashjp/trunk/sql/mysql/defaults.sql
===================================================================
--- slashjp/trunk/sql/mysql/defaults.sql	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/sql/mysql/defaults.sql	2008-04-01 04:03:21 UTC (rev 563)
@@ -833,7 +833,7 @@
 INSERT INTO vars (name, value, description) VALUES ('cur_performance_stats_lastid', '0', 'accesslogid to start searching at');
 INSERT INTO vars (name, value, description) VALUES ('cur_performance_stats_weeks', '8', 'number of weeks back to compare current stats to');
 INSERT INTO vars (name, value, description) VALUES ('currentqid',1,'The Current Question on the homepage pollbooth');
-INSERT INTO vars (name, value, description) VALUES ('cvs_tag_currentcode','T_2_5_0_196','The current cvs tag that the code was updated to - this does not affect site behavior but may be useful for your records');
+INSERT INTO vars (name, value, description) VALUES ('cvs_tag_currentcode','T_2_5_0_197','The current cvs tag that the code was updated to - this does not affect site behavior but may be useful for your records');
 INSERT INTO vars (name, value, description) VALUES ('datadir','/usr/local/slash/www.example.com','What is the root of the install for Slash');
 INSERT INTO vars (name, value, description) VALUES ('db_auto_increment_increment','1','If your master DB uses auto_increment_increment, i.e. multiple master replication, echo its value into this var');
 INSERT INTO vars (name, value, description) VALUES ('dbsparklines_disp','0','Display dbsparklines in the currentAdminUsers box?');

Modified: slashjp/trunk/sql/mysql/upgrades
===================================================================
--- slashjp/trunk/sql/mysql/upgrades	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/sql/mysql/upgrades	2008-04-01 04:03:21 UTC (rev 563)
@@ -5049,8 +5049,6 @@
 # not for slashdot
 UPDATE vars SET value = 'yes no binspam dupe notthebest offtopic stupid slownewsday interesting funny insightful' WHERE name = 'tagbox_top_excludetagnames';
 
-# PUDGE LAST UPDATED HERE
-
 # 2007-12-05
 UPDATE vars SET value = 'T_2_5_0_185' WHERE name = 'cvs_tag_currentcode';
 
@@ -5136,8 +5134,6 @@
 # 2008-02-13
 UPDATE vars SET value = 'T_2_5_0_194' WHERE name = 'cvs_tag_currentcode';
 
-# SLASHDOT LAST UPDATED HERE
-
 # For sites *without* plugins/Tags
 DELETE FROM clout_types;
 
@@ -5147,8 +5143,6 @@
 # 2008-02-20
 UPDATE vars SET value = 'T_2_5_0_195' WHERE name = 'cvs_tag_currentcode';
 
-# SLASHCODE/USEPERL LAST UPDATED HERE
-
 # for plugins/Tags
 INSERT INTO vars (name, value, description) VALUES ('tags_unknowntype_default_clid', '1', 'For tags of unknown type, which clout id do we pretend they are?');
 INSERT INTO vars (name, value, description) VALUES ('tags_unknowntype_default_mult', '0.3', 'For tags of unknown type, what multiplier do we give to the tagging user clout or type tags_unknowntype_default_clid?');
@@ -5156,3 +5150,50 @@
 # 2008-02-28
 UPDATE vars SET value = 'T_2_5_0_196' WHERE name = 'cvs_tag_currentcode';
 
+# for plugins/Tags
+INSERT INTO ajax_ops VALUES (NULL, 'tags_deactivate', 'Slash::Tags', 'ajaxDeactivateTag', 'ajax_tags_write', 'use');
+INSERT INTO vars (name, value, description) VALUES ('tags_prefixlist_minlen', '3', 'Minimum length of a tag prefix to bother looking up suggestions for');
+
+# 2008-03-12
+UPDATE vars SET value = 'T_2_5_0_197' WHERE name = 'cvs_tag_currentcode';
+
+# SLASHDOT LAST UPDATED HERE
+
+# for plugins/Ajax
+UPDATE ajax_ops set reskey_name = 'ajax_user_static', reskey_type='createuse' WHERE op='admin_signoff';
+
+# comments
+DELETE FROM reskey_resource_checks WHERE rkrid = 1;
+INSERT INTO reskey_resource_checks VALUES (NULL, 1, 'all', 'Slash::ResKey::Checks::User',                101);
+INSERT INTO reskey_resource_checks VALUES (NULL, 1, 'all', 'Slash::ResKey::Checks::ACL',                 201);
+INSERT INTO reskey_resource_checks VALUES (NULL, 1, 'all', 'Slash::ResKey::Checks::AL2::AnonNoPost',     301);
+INSERT INTO reskey_resource_checks VALUES (NULL, 1, 'all', 'Slash::ResKey::Checks::AL2::NoPostAnon',     401);
+INSERT INTO reskey_resource_checks VALUES (NULL, 1, 'all', 'Slash::ResKey::Checks::AL2::NoPost',         501);
+INSERT INTO reskey_resource_checks VALUES (NULL, 1, 'all', 'Slash::ResKey::Checks::AL2::Spammer',        531);
+INSERT INTO reskey_resource_checks VALUES (NULL, 1, 'all', 'Slash::ResKey::Checks::Duration',            601);
+
+DELETE FROM reskey_vars WHERE rkrid = 1;
+INSERT INTO reskey_vars VALUES (1, 'adminbypass', 1, 'If admin, bypass checks for duration, proxy, and user');
+INSERT INTO reskey_vars VALUES (1, 'acl_no', 'reskey_no_comments', 'If this ACL present, can\'t use resource');
+INSERT INTO reskey_vars VALUES (1, 'user_seclev', 0, 'Minimum seclev to use resource');
+INSERT INTO reskey_vars VALUES (1, 'duration_max-uses',      30, 'how many uses per timeframe');
+INSERT INTO reskey_vars VALUES (1, 'duration_max-failures',  10, 'how many failures per reskey');
+INSERT INTO reskey_vars VALUES (1, 'duration_uses',          60, 'min duration (in seconds) between uses');
+INSERT INTO reskey_vars VALUES (1, 'duration_creation-use',  10, 'min duration between (in seconds) creation and use');
+
+# for plugins/Tags
+CREATE TABLE tagname_cache (
+   tagnameid   int UNSIGNED NOT NULL,
+   tagname     VARCHAR(64) NOT NULL,
+   weight      FLOAT UNSIGNED DEFAULT 0.0 NOT NULL,
+   PRIMARY KEY tagnameid (tagnameid),
+   UNIQUE tagname (tagname)
+) TYPE=InnoDB;
+
+
+# 2008-03-19
+UPDATE vars SET value = 'T_2_5_0_198' WHERE name = 'cvs_tag_currentcode';
+
+# SLASHCODE/USEPERL LAST UPDATED HERE
+
+# PUDGE LAST UPDATED HERE

Modified: slashjp/trunk/tagboxes/Despam/Despam.pm
===================================================================
--- slashjp/trunk/tagboxes/Despam/Despam.pm	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/tagboxes/Despam/Despam.pm	2008-04-01 04:03:21 UTC (rev 563)
@@ -53,9 +53,10 @@
 
 	my $constants = getCurrentStatic();
 	my $tagsdb = getObject('Slash::Tags');
-	$self->{spamid}          = $tagsdb->getTagnameidCreate('binspam');
-	$self->{upvoteid}        = $tagsdb->getTagnameidCreate($constants->{tags_upvote_tagname} || 'nod');
+	$self->{spamid}		= $tagsdb->getTagnameidCreate('binspam');
 	return undef unless $self->{spamid};
+	$self->{upvoteid}	= $tagsdb->getTagnameidCreate($constants->{tags_upvote_tagname} || 'nod');
+	$self->{recalc_tbids}	= undef;
 
 	return $self;
 }
@@ -91,6 +92,7 @@
 		};
 		push @$ret_ar, $ret_hr;
 	}
+	main::tagboxLog("Despam->feed_newtags A " . scalar(@$ret_ar) . ": '@$ret_ar'");
 	return [ ] if !@$ret_ar;
 
 	# Tags applied to globjs that have a firehose entry associated
@@ -101,6 +103,7 @@
 		'globjid',
 		'firehose',
 		"globjid IN ($globjs_str)");
+	main::tagboxLog("Despam->feed_newtags B " . scalar(@$fh_globjs_ar) . ": '@$fh_globjs_ar'");
 	return [ ] if !@$fh_globjs_ar; # if no affected globjs have firehose entries, short-circuit out
 	my %fh_globjs = ( map { $_, 1 } @$fh_globjs_ar );
 	$ret_ar = [ grep { $fh_globjs{ $_->{affected_id} } } @$ret_ar ];
@@ -111,13 +114,10 @@
 
 sub feed_deactivatedtags {
 	my($self, $tags_ar) = @_;
-	# XXX This need not do anything, I don't think -- not even call
-	# feed_newtags.
-	# The way Despam is set up, 2 admin binspam tags will mark a globjid,
-	# and even if 1 of them is deactivated a moment later, we have no way
-	# to undo the process.
-	main::tagboxLog("Despam->feed_deactivatedtags called: tags_ar='" . join(' ', map { $_->{tagid} } @$tags_ar) .  "', returning nothing");
-	return [ ];
+	main::tagboxLog("Despam->feed_deactivatedtags called: tags_ar='" . join(' ', map { $_->{tagid} } @$tags_ar) .  "', calling feed_newtags");
+	my $ret_ar = $self->feed_newtags($tags_ar);
+        main::tagboxLog("Despam->feed_deactivatedtags returning " . scalar(@$ret_ar));
+	return $ret_ar;
 }
 
 sub feed_userchanges {
@@ -134,6 +134,7 @@
 	my $firehose_db = getObject('Slash::FireHose');
 	my $slashdb = getCurrentDB();
 
+	# Get the list of admin uids.
 	my $admins = $tagsdb->getAdmins();
 	my $admin_in_str = join(',',
 		sort { $a <=> $b }
@@ -141,170 +142,281 @@
 		keys %$admins);
 	return unless $admin_in_str;
 
+	# Get info about the firehose item that may have been tagged.
 	my $affected_id_q = $self->sqlQuote($affected_id);
-	my $fhid = $self->sqlSelect('id', 'firehose', "globjid = $affected_id_q");
-	warn "Slash::Tagbox::Despam->run bad data, fhid='$fhid' db='$firehose_db'" if !$fhid || !$firehose_db;
+	my $fhid = $self->sqlSelect('DISTINCT id', 'firehose', "globjid = $affected_id_q");
+	if (!$fhid || !$firehose_db) {
+		main::tagboxLog("Slash::Tagbox::Despam->run bad data, fhid='$fhid' db='$firehose_db'");
+		return ;
+	}
 	my $fhitem = $firehose_db->getFireHose($fhid);
+
+	# Get info about the uid and ipid that submitted the firehose item.
+	# We only track ipid for actual submissions, not journals/bookmarks.
 	my $submitter_uid = $fhitem->{uid};
-	my $submitter_srcid = $fhitem->{srcid_32};
+	my $submitter_ipid = '';
+	my $types = $slashdb->getGlobjTypes();
+	my $submission_gtid = $types->{submissions};
+	if ($submission_gtid) {
+		$submitter_ipid = $slashdb->sqlSelect(
+			'ipid',
+			'globjs, submissions',
+			"globjid=$affected_id
+			 AND gtid=$submission_gtid
+			 AND target_id=subid"
+			) || '';
+	}
 
-	my $binspam_count = $slashdb->sqlCount(
-		'tags, firehose',
-		"tags.uid IN ($admin_in_str)
-		 AND tags.inactivated IS NULL
-		 AND tags.tagnameid = $self->{spamid}
-		 AND tags.globjid = firehose.globjid
-		 AND firehose.uid = $submitter_uid");
-	my $binspam_tagids = $slashdb->sqlSelectColArrayref(
-		'tagid',
-		'tags, firehose',
-		"tags.uid IN ($admin_in_str)
-		 AND tags.inactivated IS NULL
-		 AND tags.tagnameid = $self->{spamid}
-		 AND tags.globjid = firehose.globjid
-		 AND firehose.uid = $submitter_uid");
-	main::tagboxLog(sprintf("%s->run marking fhid %d (%d) as is_spam (for count %d on uid %d: '%s')",
-		ref($self), $fhid, $affected_id, $binspam_count, $submitter_uid, join(' ', @$binspam_tagids)));
-	$firehose_db->setFireHose($fhid, { is_spam => 'yes' });
+	# First figure out how many times the globjid was tagged binspam by
+	# an admin.  It may be zero (if forceFeederRecalc was called, or if
+	# an old binspam tag was deactivated).  Even one admin binspam tag
+	# is enough to mark the individual item as binspam.
+	my $binspam_count_globjid = $slashdb->sqlCount(
+		'tags',
+		"globjid=$affected_id
+		 AND tagnameid=$self->{spamid}
+		 AND uid IN ($admin_in_str)
+		 AND inactivated IS NULL");
+	my $is_spam = $binspam_count_globjid > 0 ? 1 : 0;
 
-	if (isAnon($submitter_uid)) {
-		# Non-logged-in user, check by IP (srcid_32)
-		if ($submitter_srcid &&
-			$binspam_count > $constants->{tagbox_despam_binspamsallowed_ip}
-		) {
-			main::tagboxLog(sprintf("%s->run marking srcid %s for %d admin binspam tags, based on %d (%d)",
-			ref($self), $submitter_srcid, $binspam_count, $fhid, $affected_id));
-			$self->despam_srcid($submitter_srcid, $binspam_count);
-		}
-	} else {
-		# Logged-in user, check by uid
-		if ($binspam_count > $constants->{tagbox_despam_binspamsallowed}) {
-			main::tagboxLog(sprintf("%s->run marking uid %d for %d admin binspam tags, based on %d (%d)",
-				ref($self), $submitter_uid, $binspam_count, $fhid, $affected_id));
-			$self->despam_uid($submitter_uid, $binspam_count);
-		}
+	# Now see how many times this globjid's uid (or, if anonymous, ipid)
+	# was tagged binspam by an admin.  If greater than a certain
+	# threshold, that srcid (uid/ipid) will be given the 'spammer' al2.
+	my($check_type, $srcid, $table_clause, $where_clause) = (undef, undef);
+	if (!isAnon($submitter_uid)) {
+		# Logged-in user, check by uid.
+		$check_type = 'uid';
+		$srcid = $submitter_uid;
+		$table_clause = '';
+		$where_clause = "firehose.uid = $submitter_uid";
+	} elsif ($submitter_ipid) {
+		# Non-logged-in user, check by IP (submissions.ipid)
+		$check_type = 'ipid';
+		$srcid = convert_srcid('ipid', $submitter_ipid);
+		$table_clause = ', globjs, submissions';
+		$where_clause = "firehose.type='submission'
+			AND firehose.globjid=globjs.globjid
+			AND globjs.target_id=submissions.subid
+			AND submissions.ipid='$submitter_ipid'";
 	}
-}
+	# If neither of the above, it's an anonymous non-submission, so
+	# (at present) there's nothing we will do to block its "fellow"
+	# firehose items.
 
-sub despam_srcid {
-	my($self, $srcid, $count) = @_;
-	my $slashdb = getCurrentDB();
-	my $constants = getCurrentStatic();
+	# Find out which and how many other 'binspam' tags this contributor
+	# has amassed in total (where "contributor" can be an ipid or uid).
+	my $binspam_tagid_globj_hr = { };
+	if ($check_type) {
+		$binspam_tagid_globj_hr = $slashdb->sqlSelectAllKeyValue(
+			'tags.tagid, tags.globjid',
+			"tags, firehose$table_clause",
+			"tags.globjid = firehose.globjid
+			 AND tags.tagnameid = $self->{spamid}
+			 AND tags.uid IN ($admin_in_str)
+			 AND tags.inactivated IS NULL
+			 AND $where_clause");
+	}
 
-	my $al2_hr = $slashdb->getAL2($srcid);
-	if ($count > $constants->{tagbox_despam_binspamsallowed_ip}) {
-		main::tagboxLog("marking $srcid as spammer for $count");
-		if (!$al2_hr->{spammer}) {
-			$slashdb->setAL2($srcid, { spammer => 1, comment => "Despam $count" });
-		}
+	# This array contains the list of admin tags applied to
+	# firehose items from this srcid.  If there are too many
+	# of them, mark the srcid.
+	my $binspam_count = scalar(keys %$binspam_tagid_globj_hr);
+	my $mark_srcid = 0;
+	if ($binspam_count >
+		( $check_type eq 'uid'
+			? $constants->{tagbox_despam_binspamsallowed}
+			: $constants->{tagbox_despam_binspamsallowed_ip} )
+	) {
+		$is_spam = $mark_srcid = 1;
 	}
-}
 
-sub despam_uid {
-	my($self, $uid, $count) = @_;
-	my $constants = getCurrentStatic();
-	my $slashdb = getCurrentDB();
-	my $reader = getObject('Slash::DB', { db_type => 'reader' });
-	my $tagboxdb = getObject('Slash::Tagbox');
+	main::tagboxLog(sprintf("%s->run uid=%d ipid=%s check_type=%s affected_id=%d srcid=%s count=%d is_spam=%d mark_srcid=%d tagids: '%s'",
+		ref($self), ($submitter_uid || '0'), ($submitter_ipid || 'none'),
+		(defined($check_type) ? $check_type : 'undef'),
+		$affected_id, $srcid, $binspam_count, $is_spam, $mark_srcid,
+		join(' ', sort { $a <=> $b } keys %$binspam_tagid_globj_hr)));
 
-	# First, set the user's 'spammer' AL2.
-	my $adminuid = $constants->{tagbox_despam_al2adminuid};
-	my $al2_hr = $slashdb->getAL2($uid);
-	if (!$al2_hr->{spammer}) {
-		$slashdb->setAL2($uid, { spammer => 1, comment => "Despam $count" },
-			{ adminuid => $adminuid });
+	#			is_spam=0	is_spam=1	mark_srcid=1
+	#
+	# check_type undef	clear 1 globj	set 1 globjid	set 1 globjid
+	# check_type=uid	clear 1 globj	set 1 globjid	set all globjids, setAL2
+	# check_type=ipid	clear 1 globj	set 1 globjid	set all globjids, setAL2
+
+	# Always set/clear at least the one globjid affected.
+	my %globjids = ( $affected_id, 1 );
+	if ($mark_srcid && $check_type) {
+		# Set/clear both the individual globjid and all its
+		# fellow submitted globjids, if known.
+		for my $tagid (keys %$binspam_tagid_globj_hr) {
+			$globjids{ $binspam_tagid_globj_hr->{$tagid} } = 1;
+		}
 	}
+	# Convert that list of globjids to firehose ids.
+	my $globjid_in_str = join(',', sort { $a <=> $b } keys %globjids);
+	my $fhid_hr = $slashdb->sqlSelectAllKeyValue(
+		'id, globjid',
+		'firehose',
+		"globjid IN ($globjid_in_str)");
+	main::tagboxLog(sprintf("%s->run globjids '%s' -> fhids '%s'",
+		ref($self),
+		join(' ', sort { $a <=> $b } keys %globjids),
+		join(' ', sort { $a <=> $b } keys %$fhid_hr)));
 
-	# Next, set the user's clout manually to 0.
-	$slashdb->setUser($uid, { tag_clout => 0 });
-
-        # Next, mark as spam everything the user's submitted.
-	$slashdb->sqlUpdate('firehose', { is_spam => 'yes' },
-		"accepted != 'no' AND uid=$uid");
-
-	# Next, if $count is high enough, set the 'spammer' AL2 for all
-	# the IPID's the user has submitted from.
-	if ($count > $constants->{tagbox_despam_binspamsallowed_ip}) {
-		my $days = defined($constants->{tagbox_despam_ipdayslookback})
-			? $constants->{tagbox_despam_ipdayslookback} : 60;
-		my %srcid_used = ( );
-		if ($days) {
-			my $sub_ipid_ar = $reader->sqlSelectColArrayref(
-				'DISTINCT ipid',
-				'submissions',
-				"uid=$uid AND time >= DATE_SUB(NOW(), INTERVAL $days DAY) AND ipid != ''");
-			my $journal_srcid_ar = $reader->sqlSelectColArrayref(
-				'DISTINCT ' . get_srcid_sql_out('srcid_32'),
-				'journals',
-				"uid=$uid AND date >= DATE_SUB(NOW(), INTERVAL $days DAY) AND srcid_32 != 0");
-			my $book_srcid_ar = $reader->sqlSelectColArrayref(
-				'DISTINCT ' . get_srcid_sql_out('srcid_32'),
-				'bookmarks',
-				"uid=$uid AND createdtime >= DATE_SUB(NOW(), INTERVAL $days DAY) AND srcid_32 != 0");
-			for my $ipid (@$sub_ipid_ar) {
-				my $srcid = convert_srcid(ipid => $ipid);
-				$srcid_used{$srcid} = 1;
+	# Loop on all the fhids required to be changed, setting or
+	# clearing them as appropriate.
+	for my $fhid (sort { $a <=> $b } keys %$fhid_hr) {
+		my $globjid = $fhid_hr->{$fhid};
+		my $rows = $firehose_db->setFireHose($fhid, { is_spam => ($is_spam ? 'yes' : 'no') });
+		main::tagboxLog(sprintf("%s->run marked fhid %d (%d) as is_spam=%d rows=%s",
+			ref($self), $fhid, $globjid, $is_spam, $rows));
+		if ($rows > 0) {
+			# If this firehose item's spam status changed, either way, its
+			# scores now need to be recalculated immediately.
+			# Get the list of tbids we need to force a recalc for.
+			if (!defined $self->{recalc_tbids}) {
+				my $tagboxes = $tagboxdb->getTagboxes();
+				for my $tagbox_hr (@$tagboxes) {
+					push @{$self->{recalc_tbids}}, $tagbox_hr->{tbid}
+						if $tagbox_hr->{name} =~ /^(FHEditorPop|FireHoseScores)$/;
+				}
 			}
-			for my $srcid (@$journal_srcid_ar) {
-				$srcid_used{$srcid} = 1;
+			# Force the recalculations of their scores.
+			for my $tbid (@{$self->{recalc_tbids}}) {
+				$tagboxdb->forceFeederRecalc($tbid, $globjid);
+				main::tagboxLog(sprintf("%s->run force recalc tbid=%d globjid=%d",
+					ref($self), $tbid, $globjid));
 			}
-			for my $srcid (@$book_srcid_ar) {
-				$srcid_used{$srcid} = 1;
-			}
-			my @srcids = sort grep { $_ } keys %srcid_used;
-			for my $srcid (@srcids) {
-				$al2_hr = $slashdb->getAL2($srcid);
-				if (!$al2_hr->{spammer}) {
-					$slashdb->setAL2($srcid, { spammer => 1, comment => "Despam $count for $uid" });
-				}
-			}
 		}
 	}
 
-	# Next, declout everyone who's upvoted any of the user's
-	# recent submissions (except bookmarks, because those are
-	# generic enough).
-	my $daysback = $constants->{tagbox_despam_decloutdaysback} || 7;
-	my $upvoter_ar = $slashdb->sqlSelectColArrayref(
-		'DISTINCT tags.uid',
-		'tags, firehose',
-		"tags.globjid = firehose.globjid
-		 AND firehose.uid = $uid
-		 AND type IN ('submission', 'journal')
-		 AND createtime >= DATE_SUB(NOW(), INTERVAL $daysback DAY)
-		 AND tagnameid = $self->{upvoteid}
-		 AND inactivated IS NULL");
-	my $max_clout = defined($constants->{tagbox_despam_upvotermaxclout})
-		? $constants->{tagbox_despam_upvotermaxclout} : '0.85';
-	for my $upvoter (@$upvoter_ar) {
-		main::tagboxLog("setting user $upvoter clout to max $max_clout for upvoting user $uid");
-		$slashdb->setUser($upvoter, {
-			-tag_clout => "MAX(tag_clout, $max_clout)"
-		});
+	# If appropriate, mark the submitter's uid or ipid as a spammer
+	# and mark _all_ their submissions as binspam.
+	if ($mark_srcid && $check_type) {
+		main::tagboxLog(sprintf("%s->run marking spammer AL2 srcid=%s",
+			ref($self), $srcid));
+		$slashdb->setAL2($srcid, { spammer => 1 }, { adminuid => 1183959 });
 	}
-
-	# Next, insert tagboxlog_feeder entries to tell the relevant
-	# tagboxes to recalculate those scores.
-	my $tagboxes = $tagboxdb->getTagboxes();
-	my @tagboxids = map { $_->{tbid} } grep { $_->{name} =~ /^(FHEditorPop|FireHoseScores)$/ } @$tagboxes;
-	my $globjid_tagid = $slashdb->sqlSelectAllKeyValue(
-		'firehose.globjid, tagid',
-		'firehose, tags',
-		"firehose.uid=$uid
-		 AND firehose.globjid=tags.globjid
-		 AND tags.uid=$uid
-		 AND tagnameid=$self->{upvoteid}",
-		'GROUP BY firehose.globjid');
-	for my $globjid (sort keys %$globjid_tagid) {
-		for my $tbid (@tagboxids) {
-			$tagboxdb->addFeederInfo($tbid, {
-				affected_id => $globjid,
-				importance => 1,
-				tagid => $globjid_tagid->{ $globjid },
-			});
-		}
-	}
 }
 
+#sub despam_srcid {
+#	my($self, $srcid, $count) = @_;
+#	my $slashdb = getCurrentDB();
+#	my $constants = getCurrentStatic();
+#
+#	my $al2_hr = $slashdb->getAL2($srcid);
+#	if ($count > $constants->{tagbox_despam_binspamsallowed_ip}) {
+#		main::tagboxLog("marking $srcid as spammer for $count");
+#		if (!$al2_hr->{spammer}) {
+#			$slashdb->setAL2($srcid, { spammer => 1, comment => "Despam $count" });
+#		}
+#	}
+#}
+#
+#sub despam_uid {
+#	my($self, $uid, $count) = @_;
+#	my $constants = getCurrentStatic();
+#	my $slashdb = getCurrentDB();
+#	my $reader = getObject('Slash::DB', { db_type => 'reader' });
+#	my $tagboxdb = getObject('Slash::Tagbox');
+#
+#	# First, set the user's 'spammer' AL2.
+#	my $adminuid = $constants->{tagbox_despam_al2adminuid};
+#	my $al2_hr = $slashdb->getAL2($uid);
+#	if (!$al2_hr->{spammer}) {
+#		$slashdb->setAL2($uid, { spammer => 1, comment => "Despam $count" },
+#			{ adminuid => $adminuid });
+#	}
+#
+#	# Next, set the user's clout manually to 0.
+#	$slashdb->setUser($uid, { tag_clout => 0 });
+#
+#        # Next, mark as spam everything the user's submitted.
+#	$slashdb->sqlUpdate('firehose', { is_spam => 'yes' },
+#		"accepted != 'no' AND uid=$uid");
+#
+#	# Next, if $count is high enough, set the 'spammer' AL2 for all
+#	# the IPID's the user has submitted from.
+#	if ($count > $constants->{tagbox_despam_binspamsallowed_ip}) {
+#		my $days = defined($constants->{tagbox_despam_ipdayslookback})
+#			? $constants->{tagbox_despam_ipdayslookback} : 60;
+#		my %srcid_used = ( );
+#		if ($days) {
+#			my $sub_ipid_ar = $reader->sqlSelectColArrayref(
+#				'DISTINCT ipid',
+#				'submissions',
+#				"uid=$uid AND time >= DATE_SUB(NOW(), INTERVAL $days DAY) AND ipid != ''");
+#			my $journal_srcid_ar = $reader->sqlSelectColArrayref(
+#				'DISTINCT ' . get_srcid_sql_out('srcid_32'),
+#				'journals',
+#				"uid=$uid AND date >= DATE_SUB(NOW(), INTERVAL $days DAY) AND srcid_32 != 0");
+#			my $book_srcid_ar = $reader->sqlSelectColArrayref(
+#				'DISTINCT ' . get_srcid_sql_out('srcid_32'),
+#				'bookmarks',
+#				"uid=$uid AND createdtime >= DATE_SUB(NOW(), INTERVAL $days DAY) AND srcid_32 != 0");
+#			for my $ipid (@$sub_ipid_ar) {
+#				my $srcid = convert_srcid(ipid => $ipid);
+#				$srcid_used{$srcid} = 1;
+#			}
+#			for my $srcid (@$journal_srcid_ar) {
+#				$srcid_used{$srcid} = 1;
+#			}
+#			for my $srcid (@$book_srcid_ar) {
+#				$srcid_used{$srcid} = 1;
+#			}
+#			my @srcids = sort grep { $_ } keys %srcid_used;
+#			for my $srcid (@srcids) {
+#				$al2_hr = $slashdb->getAL2($srcid);
+#				if (!$al2_hr->{spammer}) {
+#					$slashdb->setAL2($srcid, { spammer => 1, comment => "Despam $count for $uid" });
+#				}
+#			}
+#		}
+#	}
+#
+#	# Next, declout everyone who's upvoted any of the user's
+#	# recent submissions (except bookmarks, because those are
+#	# generic enough).
+#	my $daysback = $constants->{tagbox_despam_decloutdaysback} || 7;
+#	my $upvoter_ar = $slashdb->sqlSelectColArrayref(
+#		'DISTINCT tags.uid',
+#		'tags, firehose',
+#		"tags.globjid = firehose.globjid
+#		 AND firehose.uid = $uid
+#		 AND type IN ('submission', 'journal')
+#		 AND createtime >= DATE_SUB(NOW(), INTERVAL $daysback DAY)
+#		 AND tagnameid = $self->{upvoteid}
+#		 AND inactivated IS NULL");
+#	my $max_clout = defined($constants->{tagbox_despam_upvotermaxclout})
+#		? $constants->{tagbox_despam_upvotermaxclout} : '0.85';
+#	for my $upvoter (@$upvoter_ar) {
+#		main::tagboxLog("setting user $upvoter clout to max $max_clout for upvoting user $uid");
+#		$slashdb->setUser($upvoter, {
+#			-tag_clout => "MAX(tag_clout, $max_clout)"
+#		});
+#	}
+#
+#	# Next, insert tagboxlog_feeder entries to tell the relevant
+#	# tagboxes to recalculate those scores.
+#	my $tagboxes = $tagboxdb->getTagboxes();
+#	my @tagboxids = map { $_->{tbid} } grep { $_->{name} =~ /^(FHEditorPop|FireHoseScores)$/ } @$tagboxes;
+#	my $globjid_tagid = $slashdb->sqlSelectAllKeyValue(
+#		'firehose.globjid, tagid',
+#		'firehose, tags',
+#		"firehose.uid=$uid
+#		 AND firehose.globjid=tags.globjid
+#		 AND tags.uid=$uid
+#		 AND tagnameid=$self->{upvoteid}",
+#		'GROUP BY firehose.globjid');
+#	for my $globjid (sort keys %$globjid_tagid) {
+#		for my $tbid (@tagboxids) {
+#			$tagboxdb->addFeederInfo($tbid, {
+#				affected_id => $globjid,
+#				importance => 1,
+#				tagid => $globjid_tagid->{ $globjid },
+#			});
+#		}
+#	}
+#}
+
 1;
 

Modified: slashjp/trunk/themes/slashcode/THEME
===================================================================
--- slashjp/trunk/themes/slashcode/THEME	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/themes/slashcode/THEME	2008-04-01 04:03:21 UTC (rev 563)
@@ -127,7 +127,6 @@
 htdoc=htdocs/topics.pl
 htdoc=htdocs/users.pl
 htdoc=htdocs/images/comments.js
-htdoc=htdocs/images/comments2.js
 htdoc=htdocs/images/dumper.js
 htdoc=htdocs/badge.pl
 htdoc=htdocs/help.pl
@@ -340,6 +339,7 @@
 plugin=Login
 plugin=Hof
 plugin=Messages
+plugin=Moderation
 plugin=PollBooth
 plugin=Print
 plugin=PubKey

Modified: slashjp/trunk/themes/slashcode/htdocs/comments.pl
===================================================================
--- slashjp/trunk/themes/slashcode/htdocs/comments.pl	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/themes/slashcode/htdocs/comments.pl	2008-04-01 04:03:21 UTC (rev 563)
@@ -203,7 +203,7 @@
 			header($title, $section) or return;
 			$header_emitted = 1;
 		}
-		print getError('login error');
+		print Slash::Utility::Comments::getError('login error');
 		$op = 'preview';
 	}
 	$op = 'default' if
@@ -217,7 +217,7 @@
 			header($title, $section) or return;
 			$header_emitted = 1;
 		}
-		print getError("nosubscription");
+		print Slash::Utility::Comments::getError("nosubscription");
 	}
 
 #print STDERR scalar(localtime) . " $$ B op=$op header_emitted=$header_emitted\n";
@@ -369,18 +369,6 @@
 	return fixurl($uri);
 }
 
-#################################################################
-# this groups all the errors together in
-# one template, called "errors;comments;default"
-# Why not just getData??? -Brian
-sub getError {
-	my($value, $hashref, $nocomm) = @_;
-	$hashref ||= {};
-	$hashref->{value} = $value;
-	return slashDisplay('errors', $hashref,
-		{ Return => 1, Nocomm => $nocomm });
-}
-
 ##################################################################
 sub delete {
 	my($form, $slashdb, $user, $constants) = @_;
@@ -449,7 +437,7 @@
 	if ($sid) { $sid =~ /(\d+)/; $sid = $1 }
 	if (!$sid) {
 		# Need a discussion ID to reply to, or there's no point.
-		print getError('no sid');
+		print Slash::Utility::Comments::getError('no sid');
 		return;
 	}
 
@@ -461,7 +449,7 @@
 
 	# An attempt to reply to a comment that doesn't exist is an error.
 	if ($pid && !%$reply) {
-		print getError('no such parent');
+		print Slash::Utility::Comments::getError('no such parent');
 		return;
 	} elsif ($pid) {
 		$pid_reply = prepareQuoteReply($reply);
@@ -485,12 +473,12 @@
 	# just in case the user fudged it.
 	if (($user->{is_anon} || $form->{postanon})
 		&& !$slashdb->checkAllowAnonymousPosting($user->{uid})) {
-		print getError('anonymous disallowed');
+		print Slash::Utility::Comments::getError('anonymous disallowed');
 		return;
 	}
 
 	if ($discussion->{type} eq 'archived') {
-		print getError('archive_error');
+		print Slash::Utility::Comments::getError('archive_error');
 		return;
 	}
 
@@ -498,12 +486,7 @@
 		$preview = previewForm(\$error_message, $discussion) or $error_flag++;
 	}
 
-	if (%$reply && !$form->{postersubj}) {
-		$form->{postersubj} = decode_entities($reply->{subject});
-		$form->{postersubj} =~ s/^Re://i;
-		$form->{postersubj} =~ s/\s\s/ /g;
-		$form->{postersubj} = "Re:$form->{postersubj}";
-	}
+	preProcessReplyForm($form, $reply);
 	
 	my $extras = [];	
 	my $disc_skin = $slashdb->getSkin($discussion->{primaryskid});
@@ -514,8 +497,10 @@
 		if $disc_skin && $disc_skin->{nexus};
 
 	my $gotmodwarning;
-	$gotmodwarning = 1 if $form->{gotmodwarning}
-		|| $error_message && $error_message eq getError("moderations to be lost");
+	$gotmodwarning = 1 if $form->{gotmodwarning} ||
+		($error_message && $error_message eq
+			Slash::Utility::Comments::getError("moderations to be lost")
+		);
 
 	slashDisplay('edit_comment', {
 		pid_reply	=> $pid_reply,
@@ -542,7 +527,7 @@
 
 	my $comment = preProcessComment($form, $user, $discussion, $error_message) or return;
 	return $$error_message if $comment eq '-1';
-	my $preview = postProcessComment({ %$comment, %$user }, 0, $discussion);
+	my $preview = postProcessComment({ %$user, %$form, %$comment }, 0, $discussion);
 
 	if ($constants->{plugin}{Subscribe}) {
 		$preview->{subscriber_bonus} =
@@ -685,9 +670,9 @@
 			# went wrong.
 			if ($ret_val < 0) {
 				if ($ret_val == -1) {
-					print getError('no points');
+					print Slash::Utility::Comments::getError('no points');
 				} elsif ($ret_val == -2){
-					print getError('not enough points');
+					print Slash::Utility::Comments::getError('not enough points');
 				}
 			} else {
 				$was_touched += $ret_val;

Modified: slashjp/trunk/themes/slashcode/htdocs/images/comments.js
===================================================================
--- slashjp/trunk/themes/slashcode/htdocs/images/comments.js	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/themes/slashcode/htdocs/images/comments.js	2008-04-01 04:03:21 UTC (rev 563)
@@ -14,6 +14,7 @@
 var root_comments_hash = {};
 var last_updated_comments = [];
 var last_updated_comments_index = 0;
+var reply_link_html = {};
 var comments_started = 0;
 var current_cid = 0;
 var more_comments_num;
@@ -35,6 +36,7 @@
 var comment_body_reply = [];
 var root_comment = 0;
 var discussion_id = 0;
+var user_is_subscriber = 0;
 var user_is_admin = 0;
 var user_is_anon = 0;
 var user_uid = 0;
@@ -179,7 +181,7 @@
 
 
 // this doesn't work
-//	var statusdiv = $('comment_status_' + abscid);
+//	var statusdiv = $dom('comment_status_' + abscid);
 //	statusdiv.innerHTML = 'Working ...';
 
 //	doModifiers();
@@ -219,7 +221,7 @@
 
 //	statusdiv.innerHTML = '';
 
-	if (!commentIsInWindow(abscid))
+	if (!commentIsInWindow(abscid, (cid != abscid)))
 		scrollWindowTo(abscid);
 
 	if (was_hidden)
@@ -415,7 +417,7 @@
 // Firefox (see ajaxFetchComments) ... we may make that into a separate
 // call later, as it has to be properly called AFTER addComment calls are
 // all done -- pudge
-function addComment(cid, comment, html) {
+function addComment(cid, comment, html, front) {
 	if (!loaded || !cid || !comment)
 		return false;
 
@@ -432,7 +434,7 @@
 	}
 	var pid = comment['pid'];
 
-	if ($('tree_' + cid)) {
+	if ($dom('tree_' + cid)) {
 		if (pid) {
 			var parent = comments[pid];
 			var seen = 0;
@@ -460,22 +462,28 @@
 	html = html || dummyComment(cid);
 
 	if (pid) {
-		var tree = $('tree_' + pid);
+		var tree = $dom('tree_' + pid);
 		if (tree) {
 			setDefaultDisplayMode(pid);
 			var parent = comments[pid];
-			parent['kids'].push(cid);
+			if (front)
+				parent['kids'].unshift(cid);
+			else
+				parent['kids'].push(cid);
 
-			var commtree = $('commtree_' + pid);
+			var commtree = $dom('commtree_' + pid);
 			if (commtree) {
-				commtree.innerHTML = commtree.innerHTML + html;
+				if (front)
+					commtree.innerHTML = html + commtree.innerHTML;
+				else
+					commtree.innerHTML = commtree.innerHTML + html;
 			} else {
 				tree.innerHTML = tree.innerHTML + '<ul id="commtree_' + pid + '">' + html + '</ul>';
 			}
 		}
 
 	} else {
-		var commlist = $('commentlisting');
+		var commlist = $dom('commentlisting');
 		if (commlist) {
 			root_comments.push(cid);
 			root_comments_hash[cid] = 1;
@@ -655,10 +663,10 @@
 	finishCommentUpdates();
 
 	if (roothiddens) {
-		$('roothiddens').innerHTML = roothiddens + ' comments are beneath your threshhold';
-		$('roothiddens').className = 'show';
+		$dom('roothiddens').innerHTML = roothiddens + ' comments are beneath your threshhold';
+		$dom('roothiddens').className = 'show';
 	} else {
-		$('roothiddens').className = 'hide';
+		$dom('roothiddens').className = 'hide';
 	}
 	/* NOTE need to display note for hidden root comments */
 	return void(0);
@@ -669,9 +677,17 @@
 /*******************/
 function numsort (a, b) { return (a - b) }
 
+function map_hash( hash, f ) {
+	var result = [];
+	jQuery.each(hash, function(k, v) {
+		result.push(f([k, v]));
+	});
+	return result;
+}
+
 function toHash(thisobject) {
-	return thisobject.map(function (pair) {
-		return pair.map(encodeURIComponent).join(',');
+	return map_hash(thisobject, function (pair) {
+		return jQuery.map(pair, encodeURIComponent).join(',');
 	}).join(';');
 }
 
@@ -685,7 +701,7 @@
 	if (option)
 		thresh = 1;
 
-	var params = [];
+	var params = {};
 	params['op']              = 'comments_fetch';
 
 	var newoldstuff = cids ? 0 : 1;
@@ -713,11 +729,9 @@
 		if (abbrev_comments[cids[i]] >= 0)
 			abbrev[cids[i]] = abbrev_comments[cids[i]];
 	}
-	params['abbreviated'] = $H(abbrev);
-	params['abbreviated'] = toHash(params['abbreviated']);
+	params['abbreviated'] = toHash(abbrev);
 
-	params['pieces'] = $H(cids ? fetch_comments_pieces : pieces_comments);
-	params['pieces'] = toHash(params['pieces']);
+	params['pieces'] = toHash(cids ? fetch_comments_pieces : pieces_comments);
 
 	if (placeholder_comments.length) {
 		params['placeholders'] = placeholder_comments;
@@ -813,30 +827,29 @@
 						}
 					}
 				}
-				$('titlecountnum').innerHTML = thresh_totals[6][6][1]; // total
+				$dom('titlecountnum').innerHTML = thresh_totals[6][6][1]; // total
 				updateTotals();
 			}
 
 			updateHiddens(cids);
 			if (do_update && highlight && last_updated_comments.length) {
-				for (var i = last_updated_comments_index + 1; i < last_updated_comments.length; i++) {
-					last_updated_comments_index = i;
-					if (highlight > 1 && last_updated_comments.length > i && !isUnread(last_updated_comments[i]))
-						continue;
-					setFocusComment(last_updated_comments[i], 1);
-					break;
+				var next_cid = commTreeNextComm(0, 0, 1);
+				if (next_cid) {
+					if (highlight > 1)
+						setFocusComment('-' + current_cid, 1);
+					setCurrentComment(next_cid);
+					setFocusComment(next_cid, 1);
 				}
-						
 			}
 			ajaxCommentsStatus(0);
 
 			if (adTimerInsert) {
-				var tree = $('tree_' + adTimerInsert);
+				var tree = $dom('tree_' + adTimerInsert);
 				if (tree) {
 					var adcall = '<iframe src="' + adTimerUrl + '" height="110" width="740" frameborder="0" border="0" scrolling="no" marginwidth="0" marginheight="0"></iframe>';
 					var html = '<li id="comment_ad_' + adTimerInsert + '" class="inlinead"> ' + adcall +'  </li>';
 
-					var commtree = $('commtree_' + adTimerInsert);
+					var commtree = $dom('commtree_' + adTimerInsert);
 					if (commtree) {
 						commtree.innerHTML = html + commtree.innerHTML;
 					} else {
@@ -888,7 +901,7 @@
 		||
 	    (user_highlightthresh_orig != user_highlightthresh))
 	) {
-		var params = [];
+		var params = {};
 		params['op'] = 'comments_set_prefs';
 		params['threshold'] = user_threshold;
 		params['highlightthresh'] = user_highlightthresh;
@@ -907,7 +920,7 @@
 	if (!shrunkdiv)
 		return false; // seems we shouldn't be here ...
 
-	var params = [];
+	var params = {};
 	params['op']  = 'comments_read_rest';
 	params['cid'] = cid;
 	params['sid'] = discussion_id;
@@ -940,7 +953,7 @@
 		return true;
 
 	el.disabled = 'true';
-	var params = [];
+	var params = {};
 	params['op']  = 'comments_moderate_cid';
 	params['cid'] = cid;
 	params['sid'] = discussion_id;
@@ -957,64 +970,110 @@
 	return false;
 }
 
+function cancelReply(pid) {
+	var replydiv = $dom('replyto_' + pid);
+	replydiv.innerHTML = '';
+	if (pid) { // XXX
+		var reply_link = $dom('reply_link_' + pid);
+		reply_link.innerHTML = reply_link_html[pid];
+		reply_link_html[pid] = '';
+	}
+}
+
 function editReply(pid) {
-	var replydiv = fetchEl('replyto_' + pid);
-	var reply = fetchEl('replyto_reply_' + pid);
-	var preview = fetchEl('replyto_preview_' + pid);
+	var replydiv = $dom('replyto_' + pid);
+	var reply = $dom('replyto_reply_' + pid);
+	var preview = $dom('replyto_preview_' + pid);
 	if (!replydiv || !reply || !preview)
 		return false;
 
 	preview.style.display = 'none';
 	reply.style.display   = 'block';
-	fetchEl('submit_' + pid).style.display  = 'none';
-	fetchEl('preview_' + pid).style.display = 'inline';
+
+	$dom('replyto_buttons_2_' + pid).style.display  = 'none';
+	$dom('replyto_buttons_1_' + pid).style.display = 'inline';
 }
 
-function previewReply(pid) {
-	var replydiv = fetchEl('replyto_' + pid);
-	var reply = fetchEl('replyto_reply_' + pid);
-	var preview = fetchEl('replyto_preview_' + pid);
-	var msg = fetchEl('replyto_msg_' + pid);
-	var this_reskey = fetchEl('reskey_reply_' + pid);
-	var postercomment = fetchEl('postercomment_' + pid);
-	var postersubj = fetchEl('postersubj_' + pid);
+function replyPreviewOrSubmit (pid, op, handlers) {
+	var replydiv = $dom('replyto_' + pid);
+	var reply = $dom('replyto_reply_' + pid);
+	var preview = $dom('replyto_preview_' + pid);
+	var this_reskey = $dom('reskey_reply_' + pid);
+	var msgdiv = 'replyto_msg_' + pid;
+	var msg = $dom(msgdiv);
 
-	if (!replydiv || !reply || !preview || !msg || !this_reskey)
+	if (!replydiv || !reply || !preview || !this_reskey || !msg)
 		return false;
 
-	var params = [];
-	params['op']  = 'comments_preview_reply';
+	var params = {};
+	params['op']  = op;
 	params['pid'] = pid;
 	params['sid'] = discussion_id;
-	params['postersubj'] = postersubj.value;
-	params['postercomment'] = postercomment.value;
 	params['reskey'] = this_reskey.value;
+	params['msgdiv'] = msgdiv;
+	params['gotmodwarning'] = $dom('gotmodwarning_' + pid).value;
+	params['postersubj'] = $dom('postersubj_' + pid).value;
+	params['postercomment'] = $dom('postercomment_' + pid).value;
 
-	// XXX disable Reply to This link 
+	var postanon = $dom('postanon_' + pid);
+	if (postanon && postanon.checked)
+		params['postanon'] = postanon.value;
+
 	msg.innerHTML = 'Loading...';
+	ajax_update(params, '', handlers);
+}
 
-	var handlers = {
+function submitReply(pid) {
+	return replyPreviewOrSubmit(pid, 'comments_submit_reply', {
 		onComplete: function(transport) {
+			var msg = $dom('replyto_msg_' + pid);
 			msg.innerHTML = '';
-			json_handler(transport);
-			reply.style.display   = 'none';
-			preview.style.display = 'block';
-			// depends on error result
-			fetchEl('submit_' + pid).style.display  = 'inline';
-			fetchEl('preview_' + pid).style.display = 'none';
+			var response = json_handler(transport);
+
+			var cid = response.cid;
+			if (response.error)
+				msg.innerHTML = response.error;
+			else if (cid) {
+				cancelReply(pid);
+				addComment(cid, { pid: pid, kids: [] }, '', 1);
+				setFocusComment(cid, 1);
+			}
 		}
-	};
+	});
 
-	ajax_update(params, '', handlers);
+}
 
+function previewReply(pid) {
+	return replyPreviewOrSubmit(pid, 'comments_preview_reply', {
+		onComplete: function(transport) {
+			var msg = $dom('replyto_msg_' + pid);
+			msg.innerHTML = '';
+			var response = json_handler(transport);
+
+			if (response.error)
+				msg.innerHTML = response.error;
+			if (response.html) {
+				$dom('replyto_reply_' + pid).style.display   = 'none';
+				$dom('replyto_preview_' + pid).style.display = 'block';
+				$dom('replyto_buttons_1_' + pid).style.display  = 'none';
+				$dom('replyto_buttons_2_' + pid).style.display = 'inline';
+			}
+		}
+	});
 }
 
 function replyTo(pid) {
-	var replydiv = fetchEl('replyto_' + pid);
+	var replydiv = $dom('replyto_' + pid);
 	if (!replydiv)
 		return false; // seems we shouldn't be here ...
 
-	var params = [];
+	var postercomment = $dom('postercomment_' + pid);
+	if (postercomment) {
+		postercomment.focus(); // already have one, bail
+		return false;
+	}
+
+	var params = {};
 	params['op']  = 'comments_reply_form';
 	params['pid'] = pid;
 	params['sid'] = discussion_id;
@@ -1022,7 +1081,15 @@
 	replydiv.innerHTML = 'Loading...';
 
 	var handlers = {
-		onComplete: json_handler
+		onComplete: function(transport) {
+			json_handler(transport);
+			if (pid) { // XXX
+				var reply_link = $dom('reply_link_' + pid);
+				reply_link_html[pid] = reply_link.innerHTML;
+				reply_link.innerHTML = '<a href="#" onclick="cancelReply(' + pid + '); return false;">Cancel Reply</a>';
+			}
+			$dom('postercomment_' + pid).focus();
+		}
 	};
 
 	ajax_update(params, '', handlers);
@@ -1032,7 +1099,7 @@
 
 function quoteReply(pid) {
 	var this_reply = getQuotedText(comment_body_reply[pid]);
-	var postercomment = $('postercomment_' + pid) || $('postercomment');
+	var postercomment = $dom('postercomment_' + pid) || $dom('postercomment');
 	if (postercomment)
 		postercomment.value = this_reply + postercomment.value;
 	return false;
@@ -1040,7 +1107,7 @@
 
 function getQuotedText(this_reply) {
 	// tailor whitespace to postmode
-	if (!$('posttype') || $('posttype').value != 2) {
+	if (!$dom('posttype') || $dom('posttype').value != 2) {
 		this_reply = this_reply.replace(/<br>/g, "\n");
 	} else {
 		this_reply = this_reply.replace(/<br>\n*/g, "<br>\n");
@@ -1075,7 +1142,7 @@
 
 function reloadForFirefox(obj_name) {
 	if (is_firefox) {
-		var obj = $(obj_name);
+		var obj = $dom(obj_name);
 		loadAllElements('span', obj);
 		loadAllElements('div', obj);
 		loadAllElements('li', obj);
@@ -1095,7 +1162,7 @@
 }
 
 function loadNamedElement(name) {
-	commentelements[name] = $(name);
+	commentelements[name] = $dom(name);
 	return;
 }
 
@@ -1107,9 +1174,9 @@
 		// any other special cases to ignore? -- pudge
 		if (!str.match(/^hidestring_/))
 			if (!obj || !grepCommentNode(obj, str))
-				obj = commentelements[str] = $(str);
+				obj = commentelements[str] = $dom(str);
 	} else {
-		obj = $(str);
+		obj = $dom(str);
 	}
 
 	return obj;
@@ -1255,7 +1322,7 @@
 }
 
 function boxStatus(bool) {
-	var box = $('commentControlBoxStatus');
+	var box = $dom('commentControlBoxStatus');
 	if (bool) {
 		boxStatusQueue.push(1);
 		box.className = '';
@@ -1268,7 +1335,7 @@
 
 function enableControls() {
 	boxStatus(0);
-	var morelink = $('more_comments_num_a');
+	var morelink = $dom('more_comments_num_a');
 	if (morelink)
 		morelink.className = 'show';
 
@@ -1277,11 +1344,11 @@
 }
 
 function floatButtons () {
-	$('gods').className='thor';
+	$dom('gods').className='thor';
 }
 
 function d2act () {
-	var gd = $('d2act'); 
+	var gd = $dom('d2act');
 	if (gd) {
 		var targetTop = YAHOO.util.Dom.getY('commentwrap');
 		var vOffset = 0;
@@ -1294,7 +1361,7 @@
   
 		var oldpos = gd.style.position;
 
-		var mode = $('d2out').className;
+		var mode = $dom('d2out').className;
 		if (mode=='horizontal rooted' || targetTop>vOffset) {
 			gd.style.position = 'absolute';
 			gd.className      = 'rooted';
@@ -1308,15 +1375,15 @@
 		// for Safari and maybe others, force redraw on change
 		if ( oldpos != gd.style.position ) {
 			gd.style.display = 'none';
-			setTimeout("$('d2act').style.display = 'inline'", 1);
+			setTimeout("$dom('d2act').style.display = 'inline'", 1);
 			// gd.style.display = 'inline';
 		}
 	}
 }
 
 function toggleDisplayOptions() {
-	var gods  = $('gods');
-	var d2out = $('d2out');
+	var gods  = $dom('gods');
+	var d2out = $dom('d2out');
 
 	// update user prefs
 	var newMode = '';
@@ -1345,7 +1412,7 @@
 	gods.style.display = 'block';
 
 	if (!user_is_anon) {
-		var params = [];
+		var params = {};
 		params['comments_control'] = newMode;
 		params['op'] = 'comments_set_prefs';
 		params['reskey'] = reskey_static;
@@ -1357,9 +1424,9 @@
 
 
 function updateTotals() {
-	$('currentHidden' ).innerHTML = currents['hidden'];
-	$('currentFull'   ).innerHTML = currents['full'];
-	$('currentOneline').innerHTML = currents['oneline'];
+	$dom('currentHidden' ).innerHTML = currents['hidden'];
+	$dom('currentFull'   ).innerHTML = currents['full'];
+	$dom('currentOneline').innerHTML = currents['oneline'];
 }
 
 function updateMoreNum(num) { // should be an integer, or empty string
@@ -1376,9 +1443,9 @@
 			num_a = 'Retrieve more of the ' + num + ' remaining comments';
 	}
 
-	var a = $('more_comments_num_a');
-	var b = $('more_comments_num_b');
-	var c = $('more_comments_num_c');
+	var a = $dom('more_comments_num_a');
+	var b = $dom('more_comments_num_b');
+	var c = $dom('more_comments_num_c');
 
 	if (a)
 		a.innerHTML = num_a;
@@ -1391,7 +1458,7 @@
 
 function scrollWindowTo(cid) {
 	var comment_y = getOffsetTop(fetchEl('comment_' + cid));
-	if ($('d2out').className == 'horizontal')
+	if ($dom('d2out').className == 'horizontal')
 		comment_y -= 60;
 	scroll(viewWindowLeft(), comment_y);
 }
@@ -1405,73 +1472,18 @@
 	return ol;
 }
 
-function getOffsetTop (el) {
-	if (!el)
-		return false;
-	var ot = el.offsetTop;
-	while((el = el.offsetParent) != null)
-		ot += el.offsetTop;
-	return ot;
-}
-
-function viewWindowLeft() {
-	if (self.pageXOffset) // all except Explorer
-	{
-		return self.pageXOffset;
-	}
-	else if (document.documentElement && document.documentElement.scrollTop)
-		// Explorer 6 Strict
-	{
-		return document.documentElement.scrollLeft;
-	}
-	else if (document.body) // all other Explorers
-	{
-		return document.body.scrollLeft;
-	}
-}
-
-function viewWindowTop() {
-	if (self.pageYOffset) // all except Explorer
-	{
-		return self.pageYOffset;
-	}
-	else if (document.documentElement && document.documentElement.scrollTop)
-		// Explorer 6 Strict
-	{
-		return document.documentElement.scrollTop;
-	}
-	else if (document.body) // all other Explorers
-	{
-		return document.body.scrollTop;
-	}
-	return;
-}
-
 function viewWindowRight() {
 	return viewWindowLeft() + (window.innerWidth || document.documentElement.clientWidth);
 }
 
-function viewWindowBottom() {
-	return viewWindowTop() + (window.innerHeight || document.documentElement.clientHeight);
-}
-
-function commentIsInWindow(cid) {
+function commentIsInWindow(cid, just_head) {
 	var in_window = isInWindow(fetchEl('comment_' + cid));
-	if (in_window && fetchEl('comment_sub_' + cid))
+	if (in_window && !just_head && fetchEl('comment_sub_' + cid))
 		in_window = isInWindow(fetchEl('comment_sub_' + cid));
 	return in_window;
 }
 
-function isInWindow(obj) {
-	var y = getOffsetTop(obj);
 
-	if (y > viewWindowTop() && y < viewWindowBottom()) {
-		return 1;
-	}
-	return 0;
-}
-
-
 /* code for the draggable threshold widget */
 
 function showPrefs( category ) {
@@ -1820,6 +1832,9 @@
 
 
 function setCurrentComment (cid) {
+	if (!cid)
+		return false;
+
 	var this_id;
 	if (current_cid) {
 		if (cid == current_cid)
@@ -1851,6 +1866,7 @@
 prev comm chrono: Q
 next comm chrono: E
 next unread comm: F
+reply: R
 */
 
 var validkeys = {
@@ -1861,6 +1877,7 @@
 	Q: { chrono : 1, prev: 1, comment: 1 },
 	E: { chrono : 1, next: 1, comment: 1 },
 	F: { thread : 1, next: 1, comment: 1, unread: 1 },
+	R: { reply  : 1 },
 };
 
 validkeys['H'] = validkeys['A'];
@@ -1897,8 +1914,11 @@
 			var next_cid = 0;
 			var key = k || String.fromCharCode(c);
 			var keyo = validkeys[key];
+			if (keyo && keyo['reply'] && user_is_subscriber && current_cid) { // XXX
+				replyTo(current_cid);
+
 			// forward and back between comments, in order of how they were loaded
-			if (keyo && keyo['chrono']) {
+			} else if (keyo && keyo['chrono']) {
 				var i = last_updated_comments_index;
 				var l = last_updated_comments.length - 1;
 				update = 1;
@@ -1940,11 +1960,11 @@
 							getNextUnread = 1;
 						if (keyo['comment']) {
 							next_cid = commTreeNextComm(current_cid, 0, getNextUnread);
-							if (!next_cid) {
+							if (!next_cid) { // && getNextUnread) {
 								if (ajaxCommentsWait())
 									return;
 								update = 2;
-								var highlight = 1 + getNextUnread;
+								var highlight = 1 + collapseCurrent;
 								ajaxFetchComments(0, 1, '', highlight);
 							}
 						} else
@@ -1988,12 +2008,15 @@
 	else
 		kids = rootSort();
 
+	var seen = 0;
 	for (var i = 0; i < kids.length; i++) {
 		var this_cid;
-		if (!old_cid)
+		if (!old_cid) {
 			this_cid = kids[i];
-		else if (kids[i] >= old_cid)
+		} else if ((kids[i] == old_cid) || seen) {
 			this_cid = kids[i+1];
+			seen = 1;
+		}
 
 		if (this_cid) {
 			if (!getNextUnread || (this_cid = getNextUnreadCid(this_cid)))

Deleted: slashjp/trunk/themes/slashcode/htdocs/images/comments2.js
===================================================================
--- slashjp/trunk/themes/slashcode/htdocs/images/comments2.js	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/themes/slashcode/htdocs/images/comments2.js	2008-04-01 04:03:21 UTC (rev 563)
@@ -1,524 +0,0 @@
-var comments;
-var root_comments;
-var authorcomments;
-var threshhold = 4;
-var fullthreshhold = 4;
-var promotedepth = 1;
-var behaviors = {
-	'default': { ancestors: 'none', parent: 'none', children: 'none', descendants: 'none', siblings: 'none', sameauthor: 'none' }, 
-	'focus': { ancestors: 'none', parent: 'none', children: 'none', descendants: 'none', siblings: 'none', sameauthor: 'none' }, 
-	'collapse': { ancestors: 'none', parent: 'none', siblings: 'none', sameauthor: 'none', currentmessage: 'oneline', children: 'hidden', descendants: 'hidden'} };
-var behaviorrange = ['none', 'full', 'oneline', 'hidden'];
-var authorcids = {};
-var displaymode = { 0: 1};
-var futuredisplaymode = { 0: 1};
-var focalcids = [];
-var currentdepth = 1;
-var pointsums = [];
-
-var remainingroots = [];
-var allroothiddens = 0;
-var rootpe;
-
-function togglevis(nr)
-{
-	var vista = (document.getElementById(nr).style.display == 'none') ? 'block' : 'none';
-	document.getElementById(nr).style.display = vista;
-}
-
-function blocking(nr) 
-{
-	togglevis(nr + "_oneline");	
-	togglevis(nr + "_full");	
-}
-
-function divit(id, content) {
-	return '<div id="' +id+ '">'+content+'</div>'; 
-}
-
-/* hocked from javascript.internet.com */
-function stripHTML(Word) {
-	a = Word.indexOf("<");
-	b = Word.indexOf(">");
-	len = Word.length;
-	c = Word.substring(0, a);
-	if(b == -1) b = a;
-	d = Word.substring((b + 1), len);
-	Word = c + d;
-	tagCheck = Word.indexOf("<");
-	if(tagCheck != -1) Word = stripHTML(Word);
-	return Word;
-}
-
-function wordbreak(content, maxlen) {
-	content = stripHTML(content);
-	var words = content.split(' ');
-	var retval = "";
-	for (var w = 0; w < words.length; w++) {
-		var newretval = retval +' '+ words[w];
-		if (newretval.length < maxlen) {
-			retval = newretval;
-		} else {
-			return retval + '...';
-		}
-	}
-	return retval + '...';
-}
-
-function replyTo() {
-	return '[ <a href="//ask.slashdot.org/comments.pl?sid=175930&amp;op=Reply&amp;threshold=1&amp;commentsort=0&amp;mode=thread&amp;pid=14620800">Reply to This</a> ]';
-}
-
-function flipLink(cid, mode) {
-	var newmode = mode=='full'?'oneline':'full';
-	var classa = newmode=='full'?'dwns':'ups';
-	var polarity = newmode=='full'?'':'-';
-
-	
-	return '<span class="'+classa+'" onClick="setFocusComment('+polarity+cid+'); void(0); return false;">&nbsp;</span>';
-}
-
-
-function renderCommentOneLine(cid) { 
-	var comment = comments[cid];
-	var retval = flipLink(cid, 'oneline') + '<a href="#" onclick="setFocusComment('+cid+'); return false">';
-
-	retval = retval + comment['subject']+'</a> by '+comment['nickname'];
-
-	retval = retval +' (Score:'+comment['points']+') ';
-
- 	return retval + wordbreak(comment['comment'], 45);
-}
-
-function renderCommentFull(cid) {
-	var comment = comments[cid];
-	return '<div class="commentTop"> '+flipLink(cid,'full')+'<div class="title"><h4><a name="14620800">'+comment['subject']+'</a></h4>                    (Score: '+comment['points']+', XXXXX) </div> <div class="details">                    by '+comment['nickname']+ ' on '+comment['date']+' (<a href="#" onclick="setFocusComment('+cid+'); return false">'+cid+'</a>) </div></div>            <div class="commentBody">  '+comment['comment']+'  </div>'+ replyTo(cid);
-}
-
-function renderComment(cid, mode) {
-	if (mode == 'oneline') {
-		displaymode[cid] = 'oneline'; 
-		return renderCommentOneLine(cid);
-	} else if (mode == 'full') {
-		 displaymode[cid] = 'full'; 
-		return renderCommentFull(cid);
-	} 
-	displaymode['cid'] = 'hidden';
-	return ""; /*this is when it's hidden*/
-}
-
-function updateComment(cid, mode) {
-	var existingdiv = $(cid + '_comment');
-	if (existingdiv) {
-		existingdiv.innerHTML = renderComment(cid, mode);
-		/* if (displaymode['cid'] == 'hidden') {
-			$(cid + "_tree").className = "hide";
-		} else {
-			$(cid + "_tree").className = "comment";
-		} */
-	}
-	displaymode[cid] = mode;
-	return void(0);
-}
-
-
-function decideMode(cid) {
-	var comment = comments[cid];
-	if (comment['points'] < threshhold) { 
-		return 'hidden'; 
-	} 
-	if (comment['points'] >= fullthreshhold) { return 'full'; }
-	if (promotedepth && comment['depth'] == currentdepth) { return 'full'; } 
-	return 'oneline';
-}
-
-function renderCommentTree(cid) {
-	var comment = comments[cid];
-	var retval;
-	/* if (futuredisplaymode[cid] == 'hidden') {
-		retval = '<li id="'+cid+'_tree" class="hide">';
-	} else { */
-		retval = '<li id="'+cid+'_tree" class="comment">';
-	/* } */
-	retval = retval + divit(cid + '_comment', renderComment(cid, futuredisplaymode[cid]));
-	retval = retval + '<ul id="'+cid+'_group">';
-	var hiddens = 0;
-	if (comment['kids'].length) {
-		for (var kiddie = 0; kiddie < comment['kids'].length; kiddie++) {
-			var kidrets =  renderCommentTree(comment['kids'][kiddie]);
-			retval = retval + kidrets[0];
-			hiddens += kidrets[1];
-			if (futuredisplaymode[comment['kids'][kiddie]] == 'hidden') {
-				hiddens++;			
-			}
-		}
-	}
-
-	if (futuredisplaymode[cid] == 'hidden') {
-		retval = retval + '<li id="'+cid+'_hiddens" class="hide"></li>';
-		hiddens += 1;
-	} else if (hiddens) {
-		retval = retval + '<li id="'+cid+'_hiddens">'+hiddens+" reply beneath your current threshhold.</li>";
-	} else {
-		retval = retval + '<li id="'+cid+'_hiddens" class="hide"></li>';
-	} 
-		
-	retval = retval + '</ul>';
-	retval = retval + '</li>';
-	
-	return [retval, hiddens];
-}
-
-function updateCommentTree(cid) {
-	var comment = comments[cid];
-	if (futuredisplaymode[cid] != displaymode[cid]) 
-		updateComment(cid, futuredisplaymode[cid]);
-
-	var kidhiddens = 0;
-	if (comment['kids'].length) {
-		for (var kiddie = 0; kiddie < comment['kids'].length; kiddie++) {
-			kidhiddens += updateCommentTree(comment['kids'][kiddie]);
-		}	
-	}
-
-	if (displaymode[cid] == 'hidden') {
-		$(cid+"_hiddens").className = "hide";
-		return kidhiddens + 1 ;
-
-	} else if (kidhiddens) {
-		$(cid+"_hiddens").innerHTML = kidhiddens+" reply are hidden."; 
-		$(cid+"_hiddens").className = "show";
-	} else {
-		$(cid+"_hiddens").className = "hide"; 
-	} 
-	return 0;
-}
-
-function renderRootsAsync() {
-	if (!remainingroots.length) { return void(0); }
-	var step = 2;
-	var i = 0;
-	var root;
-	var element = 'commentlisting';
-
-	while (remainingroots.length && i < step) {
-		root = remainingroots.shift();
-		rootret = renderCommentTree(root);
-		$(element).innerHTML = $(element).innerHTML + rootret[0];
-		allroothiddens += rootret[1];
-		i++;
-	}
-
-	if (remainingroots.length) { return void(0); }
-	if (allroothiddens) {
-		$(element).innerHTML = $(element).innerHTML + '<li id="roothiddens">'+allroothiddens+' comments are beneath your threshhold</li>'
-	} else {
-		$(element).innerHTML = $(element).innerHTML + '<li id="roothiddens" class="hide"></li>'
-	}
-
-	rootpe.currentlyExecuting = true;
-}
-
-function renderRoots(element) {
-
-	threshhold = Math.floor(Math.random() * 7)-1;	
-	fullthreshhold = threshhold + Math.floor(Math.random() * (6-threshhold));
-
-	renderThreshholdWidget();
-	/*randomizeBehaviors('default');*/
-	renderBehaviorWidget('default', 'defaultbehaviors');
-	/*randomizeBehaviors('focus');*/
-	renderBehaviorWidget('focus', 'focusbehaviors');
-	refreshDisplayModes(); 
-
-	remainingroots = root_comments.concat([]); 
-	rootpe = new PeriodicalExecuter(renderRootsAsync, .5);
-}
-
-function getComments(sid, element) {
-	var params = [];
-	params['op'] = 'get_comments';
-	params['sid'] = sid;
-	ajax_update(params, '', { evalScripts: 1 }, 'http://use.perl.org/ajax.pl');
-}
-
-function randomizeBehaviors(ctype) {
-	for (var relation in behaviors[ctype]) {
-		ind = Math.floor(Math.random() * behaviorrange.length);
-		behaviors[ctype][relation]=behaviorrange[ind];
-	}
-}
-
-function renderBehaviorWidget(ctype, elementname) {
-	var newhtml = "";
-
-	for (var relation in behaviors[ctype]) {
-		newhtml = newhtml + '<li style="display: inline; padding: .2em 1em;"><label>' + relation +
-			'<select id="' + ctype + '_' + relation + '">';
-		for (var i = 0; i < behaviorrange.length; i++) {
-			newhtml = newhtml + '<option value="' +behaviorrange[i] + '"';
-			if (behaviors[ctype][relation] == behaviorrange[i]) {
-				newhtml = newhtml + " selected";
-			}
-			newhtml = newhtml + ">" + behaviorrange[i] + "</option>";
-		}
-		newhtml = newhtml + "</select></label></li>";
-	}
-
-	$(elementname).innerHTML = newhtml;
-
-	return void(0);
-}
-
-function renderThreshholdWidget() {
-   if (pointsums.length) {
-       return void(0);
-   } else {
-	   pointsums[0] = 0;
-	   pointsums[1] = 0;
-	   pointsums[2] = 0;
-	   pointsums[3] = 0;
-	   pointsums[4] = 0;
-	   pointsums[5] = 0;
-	   pointsums[6] = 0;
-	   /* there's a better way to do this i hope? */
-   }	
-
-	for (var cid in comments) {
-		pointsums[comments[cid]['points']+1]++;
-	}
-	
-	var sum = 0;
-	for (var i=6; i >= 0; i--) {
-		pointsums[i] = pointsums[i] + sum;
-		sum = pointsums[i] + sum;
-	}
-	
-	$('threshholdselect').length = 0;
-	$('fullthreshholdselect').length = 0;
-	var retval = "";
-	for (var i = 0; i <= 6; i++) {
-		$('threshholdselect').options[i] = new Option((i-1)+": "+pointsums[i]+" comments", i-1); 
-		$('fullthreshholdselect').options[i] = new Option((i-1)+": "+pointsums[i]+" comments", i-1); 
-	}
-
-	$('threshholdselect').value = threshhold;
-	$('fullthreshholdselect').value = fullthreshhold;
-
-	return void(0);
-}
-
-function faGetSetting(ctype, relation, prevview, canbelower) {
-	var newview = behaviors[ctype][relation];
-
-	if ((viewmodevalue[newview] > newmodevalue[preview]) || canbelower) {
-		return newview;	
-	} 
-
-	return prevview; 
-}
-
-function getDescendants(cids) {
-	var descs = cids;
-	for (var i = 0; i < cids.length; i++) {
-		var cid = cids[i];
-		var kids = comments[cid]['kids'];
-		if (kids.length) {
-			descs = descs.concat(getDescendants(kids)); 
-		}
-	}
-	return descs;
-}
-
-function findAffected(type, cid, override) {
-	if (!cid) { return; }
-	var comment = comments[cid];
-
-	var pid = comment['pid'];
-	if (pid) {
-		if (faGetSetting(type, 'parent') != 'none') {
-			if (override || futuredisplaymode[pid] != 'full') {
-			futuredisplaymode[pid] = faGetSetting(type, 'parent', futuredisplaymode[pid], override);
-			}
-		}
-		if (faGetSetting(type, 'ancestors') != 'none') {
-			var parent = 0;
-			while (pid) {
-				parent = comments[pid];
-				pid = parent['pid'];
-				if (pid && (override || futuredisplaymode[pid] != 'full')) {
-					futuredisplaymode[pid] = faGetSetting(type, 'ancestors');
-			 	}
-			}
-		}
-		pid = comment['pid'];
-		if (faGetSetting(type, 'siblings') != 'none') {
-			var sibs = comments[pid]['kids'];
-			for(var i = 0; i < sibs.length; i++) {
-				var sib = sibs[i];
-				if (override || futuredisplaymode[sib] != 'full') {
-					futuredisplaymode[sib] = faGetSetting(type, 'siblings');
-				}
-			}
-		}
-	}
-	
-	var kids = comment['kids'];
-	if (kids.length) {
-		if (faGetSetting(type, 'children') != 'none') {
-			for (var i = 0; i < kids.length; i++) {
-				var kid = kids[i];	
-				if (override || futuredisplaymode[kid] != 'full') {
-					futuredisplaymode[kid] = faGetSetting(type, 'children');
-				}
-			}
-		}
-
-		if (faGetSetting(type, 'descendants') != 'none') {
-			var descendants = getDescendants(kids);
-			for (var i = 0; i < descendants.length; i++) {
-				var desc = descendants[i];
-				if (override || futuredisplaymode[desc] != 'full') {
-					futuredisplaymode[desc] = faGetSetting(type, 'descendants');
-				}
-			}
-		}
-	}
-
-	var uid = comment['uid'];
-	if (faGetSetting(type, 'sameauthor') != 'none') {
-		var sameauthor = authorcids[uid];	
-		for (var i = 0; i < sameauthor.length; i++) {
-			var sacid = sameauthor[i];
-			if (override || futuredisplaymode[sacid] != 'full') {
-				futuredisplaymode[sacid] = faGetSetting(type, 'sameauthor');
-			}
-		} 
-	}
-
-}
-
-function refreshDisplayModes() {
-	var fulls = Array();
-	authorcids = {};
-	for (var mykey in comments) {
-		uid = comments[mykey]['uid'];
-		if (!authorcids[uid]) {
-			authorcids[uid] = new Array(mykey);	
-		} else {
-			authorcids[uid].push(mykey);
-		}
-		futuredisplaymode[mykey] = decideMode(mykey);
-		if (futuredisplaymode[mykey] == 'full') {
-			fulls.push(mykey);
-		}
-	}
-	/* decide mode based on basic functions */
-	
-	for (var i = 0; i < fulls.length; i++) {
-		 findAffected('default', fulls[i], 0); 
-	}
-
-	if (focalcids.length) {
-		for (var i = 0; i < focalcids.length; i++) {
-			var focalcid = focalcids[i];	
-			if (focalcid > 0) {
-				futuredisplaymode[focalcid] = 'full';
-				findAffected('focus', focalcid, 0); 
-			} else {
-				focalcid = -1 * focalcid;
-				futuredisplaymode[focalcid] = behaviors['collapse']['currentmessage'];
-				findAffected('collapse', focalcid, 1);
-			}
-		}
-	}
-	return void(0);
-}
-
-function refreshCommentDisplays() {
-	refreshDisplayModes(); 
-
-	var roothiddens = 0;
-	for (var root = 0; root < root_comments.length; root++) {
-		roothiddens += updateCommentTree(root_comments[root]);
-	}
-	if (roothiddens) {
-		$('roothiddens').innerHTML = roothiddens + " comments are beneath your threshhold";
-		$('roothiddens').className = "show";
-	} else {
-		$('roothiddens').className = "hide";
-	}
-	/* NOTE need to display note for hidden root comments */
-	return void(0);
-}
-
-
-function refreshSettings() {
-	var changed = 0;
-	/* one of the threshholds have changed, rerender */
-
-	if (threshhold != $('threshholdselect').value) {
-		threshhold = $('threshholdselect').value;
-		changed = 1;
-	}
-
-	if (fullthreshhold != $('fullthreshholdselect').value) {
-		fullthreshhold = $('fullthreshholdselect').value;
-		changed = 1;
-	}
-	/* not sure if these are right is it value?*/
-
-	if (threshhold > fullthreshhold) {
-		threshhold = fullthreshhold;
-		$('threshholdselect').value = $('fullthreshholdselect').value;
-		changed = 1;
-	}
-	
-	var pd;
-	if ($('promotedepth').checked) {
-		pd = 1;
-	} else {
-		pd = 0;
-	}
-
-	if (promotedepth != pd) {	
-		promotedepth = pd; 
-		changed = 1;
-	}
-	
-	prefbehaviors = ['default', 'focus'];
-	for (var ctype in prefbehaviors) { 
-		for (var relation in behaviors[ctype]) {
-			if (behaviors[ctype][relation] != $(ctype+'_'+relation).value) {
-				behaviors[ctype][relation] = $(ctype+'_'+relation).value;
-				changed = 1;
-			}
-		}
-	}
-
-	if (changed) {
-		refreshCommentDisplays();
-	}
-	return void(0);
-}
-
-function setFocusComment(cid) {
-	var alreadyfocused = -1;
-	for (var i = 0; i < focalcids.length; i++) {
-		if (focalcids[i] == cid || focalcids[i] == (-1 * cid)) {
-			alreadyfocused = i;
-		}
-	}
-
-	if (alreadyfocused != -1) {
-		focalcids.splice(alreadyfocused, 1);
-	} else {
-		focalcids.push(cid);	
-	}
-	refreshCommentDisplays();
-	return void(0);
-}
-
-
-
-

Modified: slashjp/trunk/themes/slashcode/htdocs/images/dumper.js
===================================================================
--- slashjp/trunk/themes/slashcode/htdocs/images/dumper.js	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/themes/slashcode/htdocs/images/dumper.js	2008-04-01 04:03:21 UTC (rev 563)
@@ -1,6 +1,6 @@
 // $Id: dumper.js,v 1.2 2006/05/26 22:51:36 pudge Exp $
 
-// javascript:$('commentControlBox').innerHTML = Dumper(displaymode)
+// javascript:$dom('commentControlBox').innerHTML = Dumper(displaymode)
 
 // ===================================================================
 // Author: Matt Kruse <matt****@mattk*****>

Modified: slashjp/trunk/themes/slashcode/templates/dispComment;misc;default
===================================================================
--- slashjp/trunk/themes/slashcode/templates/dispComment;misc;default	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/themes/slashcode/templates/dispComment;misc;default	2008-04-01 04:03:21 UTC (rev 563)
@@ -34,7 +34,7 @@
 			[% ELSE %]
 			<h4><a name="[% cid %]">[% subject %]</a>
 			[%- END %]
-			[% UNLESS user.noscores %]<span id="comment_score_[% cid %]" class="score">(Score:[% IF points.length; points; ELSE; "?"; END %][% IF reasons && reason %], [% reasons.$reason.name %][% END %])</span>[% END %]</h4>
+			[% UNLESS user.noscores %]<span id="comment_score_[% cid %]" class="score">([% IF constants.modal_prefs_active && !user.is_anon %]<a href="#" onclick="getModalPrefs('modcommentlog', 'Moderation Comment Log', [% cid %]); return false">[% END %]Score:[% points.length ? points : "?" %][% IF constants.modal_prefs_active && !user.is_anon %]</a>[% END %][% IF reasons && reason %], [% reasons.$reason.name %][% END %])</span>[% END %]</h4>
 		</div>
 		<div class="details">
 			by

Modified: slashjp/trunk/themes/slashcode/templates/dispLinkComment;misc;default
===================================================================
--- slashjp/trunk/themes/slashcode/templates/dispLinkComment;misc;default	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/themes/slashcode/templates/dispLinkComment;misc;default	2008-04-01 04:03:21 UTC (rev 563)
@@ -22,14 +22,14 @@
 		[
 		
 		[% IF !user.state.discussion_archived && !user.state.discussion_future_nopost %]
-			[% Slash.linkComment({
+			<span id="reply_link_[% cid %]">[% Slash.linkComment({
 				sid	=> sid,
 				pid	=> cid,
 				op	=> 'Reply',
 				subject	=> 'Reply to This',
 				subject_only => 1,
-				onclick	=> ((discussion2 && user.test_code) ? "replyTo($cid); return false;" : '')
-			}) %]
+				onclick	=> ((discussion2 && (!constants.subscribe || user.is_subscriber)) ? "replyTo($cid); return false;" : '')
+			}) %]</span>
 		[% END %]
 
 		[% IF !(user.state.discussion_archived) && ( do_parent || can_mod || can_del ) %] | [% END %]

Modified: slashjp/trunk/themes/slashcode/templates/dispStory;misc;default
===================================================================
--- slashjp/trunk/themes/slashcode/templates/dispStory;misc;default	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/themes/slashcode/templates/dispStory;misc;default	2008-04-01 04:03:21 UTC (rev 563)
@@ -71,7 +71,7 @@
 		[% mypagemenu = PROCESS pagemenu; IF mypagemenu %]<br>[% mypagemenu %][% END %] 
 		<div class="topic">
 			[% seen_topics.${topic.tid} = 1 %]
-			[% IF user.noicons || user.simpledesign || user.lowbandwidth %]
+			[% IF user.noicons || user.simpledesign || user.lowbandwidth || user.pda %]
 				[ <a href="[% gSkin.rootdir %]/search.pl?tid=[% topic.tid %]">[% topic.textname %]</a> ]
 			[% ELSIF topic.image %]
 				<a href="[% gSkin.rootdir %]/search.pl?tid=[% topic.tid %]">
@@ -95,7 +95,17 @@
 			[% story.introtext %]
 		</div>
 
-[% IF full && user.is_admin && !preview && env.script_name != '/admin.pl' %]<br><a href="[% gSkin.rootdir -%]/admin.pl?op=edit&amp;sid=[% story.sid %]">[ Edit ]</a>&nbsp;[% IF constants.plugin.Ajax %][% PROCESS signoff stoid = story.stoid %] [% PROCESS neverdisplay stoid = stoid %][% END %][% END %]
+[% IF full && user.is_admin && !preview && env.script_name != '/admin.pl' %]<br><a href="[% gSkin.rootdir -%]/admin.pl?op=edit&amp;sid=[% story.sid %]">[ Edit ]</a>&nbsp;[% END %]
+[% IF full && !preview && env.script_name != '/admin.pl' %]
+	[% IF constants.plugin.Ajax %]
+		[% IF user.is_admin || user.acl.signoff_allowed %]
+			[% PROCESS signoff stoid = story.stoid %]
+		[% END %]
+		[% IF user.is_admin %]
+			[% PROCESS neverdisplay stoid = stoid %]
+		[% END %]
+	[% END %]
+[% END %]
 
 [% IF story.is_future && !user.is_admin %]<p>See any serious problems with this story?
 [% IF constants.ircslash_remarks %]

Modified: slashjp/trunk/themes/slashcode/templates/errors;comments;default
===================================================================
--- slashjp/trunk/themes/slashcode/templates/errors;comments;default	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/themes/slashcode/templates/errors;comments;default	2008-04-01 04:03:21 UTC (rev 563)
@@ -34,7 +34,7 @@
 to do so, because open proxies are used to spam web boards like this
 one. [% IF constants.comments_portscan!=2 %]If you really can't close it and still want to post, you'll have to
 <a href="[% gSkin.rootdir %]/users.pl">register and log in</a>.[% END %]
-<p>If you have questions, mention that your proxy is at
+If you have questions, mention that your proxy is at
 <tt>[% unencoded_ip %]</tt> on port <tt>[% port %]</tt>.
 
 [% # COMMENT TABLE NOT AVAILABLE FOR WRITING
@@ -69,133 +69,128 @@
 
 [% # NO POINTS.
 CASE "no points" %]
-You don't have any moderator points.<br>
+You don't have any moderator points.
 
 [% # NO POINTS.
 CASE "not enough points" %]
-You don't have enough moderator points.<br>
+You don't have enough moderator points.
 
 [% # COMMENTS MAX POSTS.
 CASE "comments max posts" %]
-<br><p><b>You've reached your maximum number of comments you can post: [% max_posts %] comments over [% timeframe %].
-</b></p>
+You've reached your maximum number of comments you can post: [% max_posts %] comments over [% timeframe %].
 
 [% # COMMENTS MAX POSTS.
 CASE "discussions max posts" %]
-<br><p><b>You've reached your maximum number of discussions you can submit: [% max_posts %] discussions over [% timeframe %].
-</b></p>
+You've reached your maximum number of discussions you can submit: [% max_posts %] discussions over [% timeframe %].
 
 [% # INVALID FORMKEY
 CASE "invalid formkey" %]
-[% PROCESS titlebar width="100%" title="Invalid form key!" %]
-<br><p><b>form key: [% formkey %] !</b></p>
+[% PROCESS titlebar width="100%" title="Invalid form key!" UNLESS no_titlebar %]
+Invalid form key: [% formkey %]!
  
 [% # RESPONSE LIMIT 
 CASE "comments response limit" %]
-[% PROCESS titlebar width="100%" title="Slow Down Cowboy!" %]
-<p>[% constants.sitename %] requires you to wait [% limit %] between
-hitting 'reply' and submitting a comment.</p>
-<p>It's been [% response %] since you hit 'reply'!</p>
+[% PROCESS titlebar width="100%" title="Slow Down Cowboy!" UNLESS no_titlebar %]
+[% constants.sitename %] requires you to wait [% limit %] between hitting 'Reply'
+and submitting a comment. It's been [% response %] since you hit 'Reply.'
 
 [% # POST LIMIT
 CASE "comments post limit" %]
-[% PROCESS titlebar width="100%" title="Slow Down Cowboy!" %]
-<p>[% constants.sitename %] requires you to wait between
-each successful posting of a comment to allow everyone a fair chance
-at posting a comment.</p>
-<p>It's been [% interval %] since you last successfully posted a comment.</p>
+[% PROCESS titlebar width="100%" title="Slow Down Cowboy!" UNLESS no_titlebar %]
+[% constants.sitename %] requires you to wait between each successful posting of
+a comment to allow everyone a fair chance at posting a comment.  It's been
+[% interval %] since you last successfully posted a comment.
 
 [% # POST LIMIT
 CASE "discussions post limit" %]
-[% PROCESS titlebar width="100%" title="Slow Down Cowboy!" %]
-<p>[% constants.sitename %] requires you to wait between
-each successful creation of a discussion .</p>
-<p>It's been [% interval %] since you last successfully created a discussion.</p>
+[% PROCESS titlebar width="100%" title="Slow Down Cowboy!" UNLESS no_titlebar %]
+[% constants.sitename %] requires you to wait between each successful creation
+of a discussion.  It's been [% interval %] since you last successfully created a discussion.
 
+
 [% # USED FORM
 CASE "used form" %]
-[% PROCESS titlebar width="100%" title="Form Error!" %]
+[% PROCESS titlebar width="100%" title="Form Error!" UNLESS no_titlebar %]
 This form has been used already to submit a comment [% interval %] ago.
 You can not use a form and hit the back button to use it again.
 
 [% # READ ONLY
 CASE "readonly" %]
-<br><b>You can't post to this page.</b><br>
+You can't post to this page.
 
 [% # TOO MANY POSTS
 CASE "comments post limit daily" %]
-[% PROCESS titlebar width="100%" title="Call It A Night, Cowboy!" %]
-<p>[% constants.sitename %] only allows
-[% IF user.is_anon; "anonymous users";
-ELSE; "a user with your karma"; END %]
-to post [% limit %] times per day (more or less, depending on
-moderation).
-[% IF user.is_anon; "A user from your IP has already shared his or her thoughts";
-ELSE; "You've already shared your thoughts"; END %]
+[% PROCESS titlebar width="100%" title="Call It A Night, Cowboy!" UNLESS no_titlebar %]
+[% constants.sitename %] only allows
+[% IF user.is_anon;
+	"anonymous users";
+ELSE;
+	"a user with your karma"; END %]
+to post [% limit %] times per day (more or less, depending on moderation).
+
+[% IF user.is_anon;
+	"A user from your IP has already shared his or her thoughts";
+ELSE;
+	"You've already shared your thoughts"; END %]
 with us that many times. Take a breather, and come back and see us in
 24 hours or so.
-<p>If you think this is unfair, please email
-[% IF constants.adminmail_post; constants.adminmail_post;
-ELSE; constants.adminmail; END %]
-with your
+
+If you think this is unfair, please email
+[% constants.adminmail_post || constants.adminmail %] with your
 [% IF user.is_anon %]
-MD5'd IPID, which is <tt>[% user.ipid %]</tt>[%
-ELSE %]
-username "<tt>[% user.nickname | strip_literal %]</tt>"[%
-END %]. Let us know how many comments <em>you</em> think you've
-posted in the last 24 hours.
+	MD5'd IPID, which is <tt>[% user.ipid %]</tt>[% ELSE %]
+	username "<tt>[% user.nickname | strip_literal %]</tt>"[% END %].
 
+Let us know how many comments <em>you</em> think you've posted in the last 24 hours.
+
+
 [% # TROLL MESSAGE.
 CASE "troll message" %]
 Due to excessive bad posting from this IP or Subnet, [% IF logged_in_allowed
-%]anonymous [% END %]comment posting
-has temporarily been disabled. 
+%]anonymous [% END %]comment posting has temporarily been disabled. 
+
 [% IF logged_in_allowed %]
 You can still login to post.  However, if bad posting continues from your IP
 or Subnet that privilege could be revoked as well.
 [% END %]
-If it's you, consider this a chance to
-sit in the timeout corner[% IF logged_in_allowed %] or login and improve your posting[% END %]
-. If it's someone else, this is a chance to hunt them down.
-If you think this is unfair, please email
-[% IF constants.adminmail_mod; constants.adminmail_mod;
-ELSE; constants.adminmail; END %]
-with your MD5'd IPID and SubnetID, which are
-"<tt>[% user.ipid %]</tt>" and "<tt>[% user.subnetid %]</tt>"[%
-IF !user.is_anon %]
-and (optionally, but preferably) your IP number
-"<tt>[% unencoded_ip %]</tt>" and your username "<tt>[% user.nickname | strip_literal %]</tt>"[%
-END %].
 
+If it's you, consider this a chance to sit in the timeout
+corner[% IF logged_in_allowed %] or login and improve your posting[% END %].
+If it's someone else, this is a chance to hunt them down.  If you think this is
+unfair, please email [% constants.adminmail_mod || constants.adminmail %]
+with your MD5'd IPID and SubnetID, which are "<tt>[% user.ipid %]</tt>" and
+"<tt>[% user.subnetid %]</tt>"[% IF !user.is_anon %]
+	and (optionally, but preferably) your IP number
+	"<tt>[% unencoded_ip %]</tt>" and your username
+	"<tt>[% user.nickname | strip_literal %]</tt>"[% END %].
+
 [% CASE "broken html" %]
 Your comment could not be processed.  Please try again.
 
 [% # LOW CHARS-PER-LINE
 CASE "low chars-per-line" %]
-Your comment has too few characters per line (currently [% ratio %]).<br>
+Your comment has too few characters per line (currently [% ratio %]).
 
 [% # FILTER MESSAGE.
 CASE "filter message" %]
-[% PROCESS titlebar width="100%" title="Lameness filter encountered. Post aborted!" %]
+[% PROCESS titlebar width="100%" title="Lameness filter encountered. Post aborted!" UNLESS no_titlebar %]
 [% IF err_message %]
-<b>Reason: [% err_message %]</b><br>
+Filter error: [% err_message %]
 [% END %]
 
 [% # COMPRESS FILTER.
 CASE "compress filter" %]
-[% PROCESS titlebar width="100%" title="Lameness filter encountered." %]
-Your comment violated the "[% ratio %]" compression filter. Try less
-whitespace and/or less repetition[%
-IF ratio == 'postersubj' %] in the subject line[% END %].
-<b>Comment aborted.</b>
+[% PROCESS titlebar width="100%" title="Lameness filter encountered." UNLESS no_titlebar %]
+Your comment violated the "[% ratio %]" compression filter. Try less whitespace
+and/or less repetition[% IF ratio == 'postersubj' %] in the subject line[% END %].
 
 [% # SUBMISSION ERROR.
 CASE "submission error" %]
-<p>There was an unknown error in the submission.<br>
+There was an unknown error in the submission.
 
 [% # MAXCID EXCEEDED.
 CASE "maxcid exceeded" %]
-Don't you have anything better to do with your life?<br>
+Don't you have anything better to do with your life?
 
 [% # DUPLICATION ERROR.
 CASE "duplication error" %]
@@ -214,31 +209,30 @@
 form [% formname %] [% formkey %] already submitted 
 
 [% CASE "seclevtoolow" %]
-[% PROCESS titlebar width="100%" title="No permissions" %]
+[% PROCESS titlebar width="100%" title="No permissions" UNLESS no_titlebar %]
 You do not have appropriate permissions to perform that action.
 
 [% CASE "nosubscription" %]
-[% PROCESS titlebar width="100%" title="Not subscriber, or not subscribed page" %]
-You can't see this discussion because it's scheduled in the future,
-where only subscribers can see it.<p>
-Either you are not a subscriber to [% constants.sitename %], or you
-have indicated you don't want comments pages ad-free, or you have
-set your daily limit of ad-free pages to lower than
-the default [% constants.subscribe_hits_btmd %].
-Any of these three possible issues can be resolved at
-<a href="[% gSkin.rootdir %]/subscribe.pl">your subscription page</a>.<p>
+[% PROCESS titlebar width="100%" title="Not subscriber, or not subscribed page" UNLESS no_titlebar %]
+You can't see this discussion because it's scheduled in the future, where only
+subscribers can see it.  Either you are not a subscriber to
+[% constants.sitename %], or you have indicated you don't want comments pages
+ad-free, or you have set your daily limit of ad-free pages to lower than the
+default [% constants.subscribe_hits_btmd %].  Any of these three possible
+issues can be resolved at <a href="[% gSkin.rootdir %]/subscribe.pl">your
+subscription page</a>.
 
 [% CASE "moderations to be lost" %]
-[% PROCESS titlebar width="100%" title="You've Already Moderated this Discussion" %]
+[% PROCESS titlebar width="100%" title="You've Already Moderated this Discussion" UNLESS no_titlebar %]
 If you continue to post this comment, all moderations done to this discussion will
 be undone!  Are you sure you want to post?
 
 [% # NO DISCUSSION ID SPECIFIED
 CASE "no sid" %]
-[% PROCESS titlebar width="100%" title="No Discussion Specified" %]
-You did not specify a discussion ID. If you clicked on a link internal
-to [% constants.sitename %], this is a bug. If not, someone sent you
-to a silly invalid URL.
+[% PROCESS titlebar width="100%" title="No Discussion Specified" UNLESS no_titlebar %]
+You did not specify a discussion ID. If you clicked on a link internal to
+[% constants.sitename %], this is a bug. If not, someone sent you to a silly
+invalid URL.
 
 [% CASE 'no_moderation' %]
 The moderation system is currently down.
@@ -248,7 +242,7 @@
 
 
 [% CASE %]
-An unexpected error has occurred.<br><b>[% value %]</b><br>
+An unexpected error has occurred: [% value %]
 [% END %]
 
 __seclev__

Modified: slashjp/trunk/themes/slashcode/templates/html-header;misc;default
===================================================================
--- slashjp/trunk/themes/slashcode/templates/html-header;misc;default	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/themes/slashcode/templates/html-header;misc;default	2008-04-01 04:03:21 UTC (rev 563)
@@ -28,7 +28,7 @@
 [% IF file.ie_cond %]<!--[if [% file.ie_cond %]]>[% END %]<link rel="[% file.rel %]" type="[% file.type %]" media="[% file.media %]" href="[% IF constants.css_use_imagedir; constants.imagedir; ELSE; constants.rootdir; END %]/[% file.file %]?[% constants.cvs_tag_currentcode %]"[% IF file.title %] title="[% file.title %]"[% END %]>[% IF file.ie_cond %]<![endif]-->[% END %]
 [% END -%]
 [%- IF constants.plugin.Ajax %]
-<script src="[% constants.imagedir %]/prototype.js?[% constants.cvs_tag_currentcode %]" type="text/javascript"></script>
+<script src="[% constants.imagedir %]/jquery-1.2.3.js?[% constants.cvs_tag_currentcode %]" type="text/javascript"></script>
 <script src="[% constants.imagedir %]/yui/yahoo.js?[% constants.cvs_tag_currentcode %]" type="text/javascript"></script>
 <script src="[% constants.imagedir %]/yui/dom.js?[% constants.cvs_tag_currentcode %]" type="text/javascript"></script>
 <script src="[% constants.imagedir %]/yui/event.js?[% constants.cvs_tag_currentcode %]" type="text/javascript"></script>

Modified: slashjp/trunk/themes/slashcode/templates/modCommentLog;misc;default
===================================================================
--- slashjp/trunk/themes/slashcode/templates/modCommentLog;misc;default	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/themes/slashcode/templates/modCommentLog;misc;default	2008-04-01 04:03:21 UTC (rev 563)
@@ -36,15 +36,17 @@
 __template__
 [% IF mod_admin %]
 [% 
+hr_shown = 0; mod_index = 0;
 down=0; cnt=0; m2_down=0; m2_count=0; m2_offside=0; 
+a_up=0; a_sum=0; a_count=0; up=0; sum=0; count=0; a_m2_count=0;
 a_down=0; a_cnt=0; a_m2_down=0; a_m2_up=0; m2_count=0; a_m2_offside=0; a_m2_unfair_votes=0; unanimous = 0; unresolved=0;
 show_uid_totals = (type == "uid" && (this_user.totalmods || this_user.downmods || this_user.upmods || this_user.m2unfair || this_user.m2unfairpercent || this_user.stirred));
 
 bg_toggle = 0;
 %]
 [% IF mods.size > 0  || show_uid_totals %]
-	[% IF user.is_admin and need_m2_form %]
-	<form  method="post" action="[% gSkin.rootdir %]/comments.pl">
+	[% IF user.is_admin && need_m2_form %]
+	<form method="post"[% IF constants.modal_prefs_active %] id="modal_prefs"[% END %] action="[% gSkin.rootdir %]/comments.pl">
 	<div>
 	[% END %]
 	[% IF meta_mod_only %]
@@ -53,7 +55,7 @@
 	[% total_cols = 6 %]	
 	[% IF title;
 		extra = "";
-		IF constants.m2;
+		IF constants.m2 && !constants.modal_prefs_active;
 			show_m2s_op = show_m2s ? 0 : 1;
 			self_url = PROCESS base_url_mod;
 			url_tail = PROCESS state_url_mod override = { show_m2s => show_m2s_op};
@@ -84,8 +86,6 @@
 			<th align=left>Moderatee [% IF constants.m2 %]<span class="admin_data_label">/ Metamoderator</span>[% END %]</th>
 		[% END %]
 	</tr>
-		[% hr_shown = 0; 
-		   mod_index = 0; %]
 		[% FOREACH moderation = mods %]
 		[% IF constants.m2 && type == "cid" && constants.show_m2s_with_mods && show_m2s && constants.m2_multicount; 
 			prev_index = mod_index - 1;
@@ -203,7 +203,13 @@
 	[% IF need_m2_button %]
 		<tr class="data_hl1"><td colspan="[% total_cols %]" align="right">
 		<input type="hidden" name="op" value="moderate">
-		<input type="submit" name="submit" value="MetaModerate"> [%# XXX really? %]
+		[%- IF constants.modal_prefs_active %]
+		<input type="hidden" name="formname" value="metamoderate">
+		<input type="hidden" name="refreshable" value="">
+		<input type="button" value="MetaModerate" onclick="saveModalPrefs()">
+		[%- ELSE %]
+		<input type="submit" name="submit" value="MetaModerate">
+		[%- END %]
 		</td></tr>
 	[% END %]
 [% END %]

Modified: slashjp/trunk/themes/slashcode/templates/printCommComments;misc;default
===================================================================
--- slashjp/trunk/themes/slashcode/templates/printCommComments;misc;default	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/themes/slashcode/templates/printCommComments;misc;default	2008-04-01 04:03:21 UTC (rev 563)
@@ -71,7 +71,8 @@
 		[% Slash.linkComment(next, 1) %]&gt;&gt;
 	[% END %]
 	</div>
-	[% m1_classname = "Slash::" _ constants.m1_pluginname;
+	[% UNLESS constants.modal_prefs_active;
+	   m1_classname = "Slash::" _ constants.m1_pluginname;
 	   moddb = Slash.getObject(m1_classname);
 	   IF moddb;
 		   moddb.dispModCommentLog('cid', cid, {
@@ -82,6 +83,7 @@
 			need_m2_button => constants.m2,
 			title => " " });
 	   END;
+	   END; # constants.modal_prefs_active
 	%]
 [% END %]
 
@@ -101,6 +103,7 @@
 	[% lcp %]
 
 [% IF discussion2 && !cid %]
+<div id="replyto_0"></div>
 <div class="prev-next"><a href="#" onclick="ajaxFetchComments(0,1); return false"><span id="more_comments_num_a" class="hide">Check for more</span></a>
 	[% UNLESS user.state.discussion_archived || user.state.discussion_future_nopost %]
 		| [% Slash.linkComment({
@@ -108,7 +111,8 @@
 			cid          => cid,
 			op           => 'reply',
 			subject      => 'Reply',
-			subject_only => 1
+			subject_only => 1,
+			onclick      => ((discussion2 && (!constants.subscribe || user.is_subscriber)) ? "replyTo(0); return false;" : '')
 		});
 	END %]
 </div>

Modified: slashjp/trunk/themes/slashcode/templates/printCommentsMain;misc;default
===================================================================
--- slashjp/trunk/themes/slashcode/templates/printCommentsMain;misc;default	2008-04-01 03:48:35 UTC (rev 562)
+++ slashjp/trunk/themes/slashcode/templates/printCommentsMain;misc;default	2008-04-01 04:03:21 UTC (rev 563)
@@ -201,7 +201,8 @@
 				cid          => cid,
 				op           => 'reply',
 				subject      => 'Reply',
-				subject_only => 1
+				subject_only => 1,
+				onclick      => ((discussion2 && (!constants.subscribe || user.is_subscriber)) ? "replyTo(0); return false;" : '')
 			}) %]
 		[% END %]
 		</span>
@@ -285,7 +286,8 @@
 				cid          => cid,
 				op           => 'reply',
 				subject      => 'Reply',
-				subject_only => 1
+				subject_only => 1,
+				onclick      => ((discussion2 && (!constants.subscribe || user.is_subscriber)) ? "replyTo(0); return false;" : '')
 			}) %]
 		[% END %]
 			<div id="bindings-legend">Keybindings Beta<br>


Slashdotjp-dev メーリングリストの案内
Back to archive index