fu: Close-up of Fu, bringing a scoop of water to her mouth (Default)
fu ([personal profile] fu) wrote in [site community profile] changelog2011-05-12 08:49 am

[dw-free] Contextual hover menu doesn't work on jquerified pages

[commit: http://hg.dwscoalition.org/dw-free/rev/c29f3ee7ce3d]

http://bugs.dwscoalition.org/show_bug.cgi?id=2507

Make contextual hover work on jQuerified pages.

Patch by [personal profile] fu.

Files modified:
  • cgi-bin/DW/BetaFeatures/journaljquery.pm
  • cgi-bin/LJ/S2.pm
  • cgi-bin/LJ/S2/EntryPage.pm
  • cgi-bin/ljdefaults.pl
  • cgi-bin/ljlib.pl
  • cgi-bin/weblib.pl
  • htdocs/js/contextualhover-jquery.js
  • htdocs/js/jquery.ajaxtip.js
  • htdocs/js/jquery.contextualhover.js
  • htdocs/js/jquery.hoverIntent.js
  • htdocs/js/jquery.hoverIntent.minified.js
  • htdocs/stc/contextualhover.css
--------------------------------------------------------------------------------
diff -r 634318082e89 -r c29f3ee7ce3d cgi-bin/DW/BetaFeatures/journaljquery.pm
--- a/cgi-bin/DW/BetaFeatures/journaljquery.pm	Thu May 12 16:42:01 2011 +0800
+++ b/cgi-bin/DW/BetaFeatures/journaljquery.pm	Thu May 12 16:45:29 2011 +0800
@@ -26,10 +26,10 @@
         "Thread expander",
         "Same-page poll submission",
         "Media embed placeholder expansion",
+        "Contextual hover",
     );
 
     my @notimplemented = (
-        "Contextual hover",
         "Icon browser",
         "Same-page comment tracking",
     );
diff -r 634318082e89 -r c29f3ee7ce3d cgi-bin/LJ/S2.pm
--- a/cgi-bin/LJ/S2.pm	Thu May 12 16:42:01 2011 +0800
+++ b/cgi-bin/LJ/S2.pm	Thu May 12 16:45:29 2011 +0800
@@ -191,13 +191,8 @@
         # used if we're using our jquery library
         LJ::need_res( { group => "jquery" }, qw(
                         js/md5.js
-
-                        js/jquery.ajaxtip.js
-                        js/tooltip.js
-                        js/tooltip.dynamic.js
-                        stc/ajaxtip.css
-
                         js/login-jquery.js
+
                         js/jquery.poll.js
                         js/jquery.mediaplaceholder.js
                     ) );
diff -r 634318082e89 -r c29f3ee7ce3d cgi-bin/LJ/S2/EntryPage.pm
--- a/cgi-bin/LJ/S2/EntryPage.pm	Thu May 12 16:42:01 2011 +0800
+++ b/cgi-bin/LJ/S2/EntryPage.pm	Thu May 12 16:45:29 2011 +0800
@@ -382,7 +382,6 @@
 
     LJ::need_res( "js/commentmanage.js" );
     LJ::need_res( { group => "jquery" }, qw(
-            js/jquery/jquery.ui.widget.js
             js/jquery.ajaxtip.js
             js/jquery.commentmanage.js
             js/tooltip.js
diff -r 634318082e89 -r c29f3ee7ce3d cgi-bin/ljdefaults.pl
--- a/cgi-bin/ljdefaults.pl	Thu May 12 16:42:01 2011 +0800
+++ b/cgi-bin/ljdefaults.pl	Thu May 12 16:45:29 2011 +0800
@@ -354,6 +354,7 @@
         "js/jquery/jquery.ui.sortable.js"   => "js/jquery/jquery.ui.sortable.min.js",
         "js/jquery/jquery.ui.widget.js"     => "js/jquery/jquery.ui.widget.min.js",
 
+        "js/hoverIntent.js"             => "js/hoverIntent.minified.js",
         "js/tooltip.js"                 => "js/tooltip.min.js",
         "js/tooltip.dynamic.js"         => "js/tooltip.dynamic.min.js",
     ) unless defined %LJ::MINIFY;
diff -r 634318082e89 -r c29f3ee7ce3d cgi-bin/ljlib.pl
--- a/cgi-bin/ljlib.pl	Thu May 12 16:42:01 2011 +0800
+++ b/cgi-bin/ljlib.pl	Thu May 12 16:45:29 2011 +0800
@@ -1337,14 +1337,29 @@
             if LJ::is_enabled('esn_ajax');
 
         # contextual popup JS
-        LJ::need_res( { priority => $LJ::LIB_RES_PRIORITY }, qw(
-                        js/ippu.js
-                        js/lj_ippu.js
-                        js/hourglass.js
-                        js/contextualhover.js
-                        stc/contextualhover.css
-                        ))
-            if $LJ::CTX_POPUP;
+        if ( $LJ::CTX_POPUP ) {
+            LJ::need_res( { priority => $LJ::LIB_RES_PRIORITY }, qw(
+                            js/ippu.js
+                            js/lj_ippu.js
+                            js/hourglass.js
+                            js/contextualhover.js
+                            stc/contextualhover.css
+                            ));
+
+            LJ::need_res( { priority => $LJ::LIB_RES_PRIORITY, group=> 'jquery' },
+                qw(
+                    js/jquery/jquery.ui.widget.js
+
+                    js/jquery.ajaxtip.js
+                    js/tooltip.js
+                    js/tooltip.dynamic.js
+                    stc/ajaxtip.css
+
+                    js/jquery.hoverIntent.js
+                    js/jquery.contextualhover.js
+                    stc/contextualhover.css
+                ));
+        }
 
         # development JS
         LJ::need_res( { priority => $LJ::LIB_RES_PRIORITY }, qw(
diff -r 634318082e89 -r c29f3ee7ce3d cgi-bin/weblib.pl
--- a/cgi-bin/weblib.pl	Thu May 12 16:42:01 2011 +0800
+++ b/cgi-bin/weblib.pl	Thu May 12 16:45:29 2011 +0800
@@ -2567,9 +2567,16 @@
     my $now = time();
     my %list;   # type -> [];
     my %oldest; # type -> $oldest
+    my %included = ();
     my $add = sub {
         my ($type, $what, $modtime) = @_;
 
+        # the same file may have been included twice
+        # if it was in two different groups and not JS
+        # so add another check here
+        next if $included{$what};
+        $included{$what} = 1;
+
         # in the concat-res case, we don't directly append the URL w/
         # the modtime, but rather do one global max modtime at the
         # end, which is done later in the tags function.
diff -r 634318082e89 -r c29f3ee7ce3d htdocs/js/contextualhover-jquery.js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/js/contextualhover-jquery.js	Thu May 12 16:45:29 2011 +0800
@@ -0,0 +1,62 @@
+(function($) {
+    $.widget("ui.contextualhover", {
+        popupDelay: 500,
+        hideDelay: 250,
+    });
+
+    var ContextualHover = {
+        setup: function() {
+            if (!Site || !Site.ctx_popup) return;
+            if (Site.ctx_popup_userhead)
+                ContextualHover._initUserhead();
+    
+            if (Site.ctx_popup_icons)
+                ContextualHover._initIcons();
+        },
+
+        _initUserhead: function() {
+            $("span.ljuser").each(function() {
+                var $usertag = $(this);
+                if ( $usertag.data("userdata") ) return;
+
+                $("img", $usertag).each(function() {
+                    // if the parent (a tag with link to userinfo) has userid in its URL, then
+                    // this is an openid user icon and we should use the userid
+                    var $parent = $(this).parent("a[href]");
+                    var data = {};
+                    var userid;
+                    if (userid = $parent.attr("href").match(/\?userid=(\d+)/i)) 
+                        data.userid = userid[1];
+                    else
+                        data.username = $usertag.attr("lj:user");
+                    if ( !data.username && !data.userid ) return;
+
+                    $usertag.data("userdata", data).addClass("ContextualPopup");
+                });
+            });
+        },
+
+        _initIcons: function() {
+
+            $("img[src*='/userpic/']").each(function() {
+                if ( $(this).data("icon_url") ) return;
+                if (this.src.match(/userpic\..+\/\d+\/\d+/) ||
+                    this.src.match(/\/userpic\/\d+\/\d+/)) {
+                    $(this).data("icon_url", this.src).addClass("ContextualPopup");
+                }
+            });
+        }
+    }
+
+    // for init
+    $.extend({ contextualhover: ContextualHover.setup });
+
+})(jQuery);
+
+// initialize on page load
+$(function() {
+    $.contextualhover();
+    $(".ContextualPopup").live("mousemove", function(e){
+        console.log(e.target);
+    });
+});
diff -r 634318082e89 -r c29f3ee7ce3d htdocs/js/jquery.ajaxtip.js
--- a/htdocs/js/jquery.ajaxtip.js	Thu May 12 16:42:01 2011 +0800
+++ b/htdocs/js/jquery.ajaxtip.js	Thu May 12 16:45:29 2011 +0800
@@ -1,7 +1,8 @@
 (function($) {
 $.widget("dw.ajaxtip", {
     options: {
-        content: undefined
+        content: undefined,
+        tooltip: { dynamic: true }
     },
     _namespace: function() {
         return this.options.namespace ? "."+this.options.namespace : "";
@@ -10,31 +11,31 @@
         var self = this;
         var ns = self._namespace();
 
-        var tipcontainer = $("<div class='ajaxtooltip' style='display: none'></div>")
+        var tipcontainer = $("<div class='ajaxtooltip ajaxtip' style='display: none'></div>")
                         .click(function(e) {e.stopPropagation()})
 
         self.element
             .after(tipcontainer)
             .bind("ajaxresult"+ns, function(e) {
-                self.element.data("tooltip").getTip()
-                     .addClass("ajaxresult-" + e.ajaxresult.status)
-                     .text(e.ajaxresult.message)
+                var tip = self.element.data("tooltip").getTip()
+                     .addClass("ajaxresult-" + e.ajaxresult.status);
+                if ( e.ajaxresult.message ) tip.text(e.ajaxresult.message);
             })
-            .tooltip({
+            .tooltip($.extend({
                 predelay: 0,
                 delay: 1500,
                 events: {
-                    def: "ajaxstart"+ns+",ajaxresult"+ns,
+                    def: "ajaxstart"+ns+", tooltipout"+ns+" ajaxresult"+ns,
                     widget: "ajaxstart"+ns+",ajaxresult"+ns,
                     tooltip: "mouseover,mouseleave"
                 },
                 position: "bottom center",
                 relative: true,
                 effect: "fade",
-
                 onBeforeShow: function(e) {
                     var tip = this.getTip();
-                    tip.removeClass("ajaxresult ajaxresult-success ajaxresult-error");
+                    tip.removeClass("ajaxresult ajaxresult-success ajaxresult-error")
+                        .appendTo("body");
 
                     if ( self.options.content && ! this.inprogress ){
                         tip.html(self.options.content)
@@ -43,8 +44,10 @@
                             .addClass("ajaxresult")
                     }
                 }
-            })
-            .dynamic({ classNames: "tip-top tip-right tip-bottom tip-left" });
+            },  self.options.tooltip));
+            if ( self.options.tooltip.dynamic )
+                self.element.dynamic({ classNames: "tip-top tip-right tip-bottom tip-left" });
+
     },
     _init: function() {
         if(this.options.content)
@@ -64,6 +67,11 @@
         var tip = this.element.data("tooltip");
         if( tip && tip.isShown() ) tip.hide();
      },
+    show: function() {
+        var tip = this.element.data("tooltip");
+        tip.show();
+        this.success(/*no msg*/);
+    },
     load: function(opts) {
         var self = this;
 
@@ -77,7 +85,7 @@
         self.element.trigger("ajaxstart" + self._namespace());
 
         $.ajax({
-            type: "POST",
+            type: opts.formmethod || "POST",
             url : opts.endpoint ? self._endpointurl( opts.endpoint) : opts.url,
             data: opts.data,
 
@@ -107,7 +115,7 @@
 
 $.extend( $.dw.ajaxtip, {
     closeall: function() {
-        $(".ajaxtooltip:visible").each(
+        $(".ajaxtip:visible").each(
             function(){
                 var tip = $(this).prev().data("tooltip");
                 if ( !tip.inprogress ) tip.hide()
diff -r 634318082e89 -r c29f3ee7ce3d htdocs/js/jquery.contextualhover.js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/js/jquery.contextualhover.js	Thu May 12 16:45:29 2011 +0800
@@ -0,0 +1,376 @@
+(function($, Site){
+
+$.fn.contextualhoversetup = function() {
+    if (!Site || !Site.ctx_popup) return;
+
+    this.each(function() {
+        if (Site.ctx_popup_userhead)
+            _initUserhead(this);
+
+        if (Site.ctx_popup_icons)
+            _initIcons(this);
+    });
+};
+
+function _initUserhead(context) {
+    $("span.ljuser",context).each(function() {
+        var $usertag = $(this);
+
+        $("img", $usertag).each(function() {
+            // if the parent (a tag with link to userinfo) has userid in its URL, then
+            // this is an openid user icon and we should use the userid
+            var $head = $(this);
+            var $parent = $head.parent("a[href]");
+            var data = {};
+            var userid;
+            if (userid = $parent.attr("href").match(/\?userid=(\d+)/i))
+                data.userid = userid[1];
+            else
+                data.username = $usertag.attr("lj:user");
+            if ( !data.username && !data.userid ) return;
+
+            data.type = "user";
+            $head.contextualhover( data );
+        });
+    });
+}
+
+function _initIcons(context) {
+    $("img[src*='/userpic/']",context).each(function() {
+        var $icon = $(this);
+        if (this.src.match(/userpic\..+\/\d+\/\d+/) ||
+            this.src.match(/\/userpic\/\d+\/\d+/)) {
+            $icon.contextualhover({ "icon_url": this.src, type: "icon" });
+        }
+    });
+}
+
+})(jQuery, Site);
+
+$.widget("dw.contextualhover", {
+options: {
+    disableAJAX: false,
+
+    username: undefined,
+    userid: undefined,
+    icon_url: undefined,
+    type: undefined // icon or user(head)
+},
+_create: function() {
+    var self = this;
+    var opts = self.options;
+
+    if ( opts.type == "icon" ) {
+        self.element.removeAttr("title");
+        var parent = self.element.parent("a")
+        if ( parent.length > 0 )
+            self.element = parent;
+    }
+
+    var trigger = self.element;
+    trigger.addClass("ContextualPopup");
+
+    var action = $.fn.hoverIntent ? "hoverIntent" : "hover";
+    trigger[action](
+    function() {
+        if ( self.cachedResults ) {
+            self._renderPopup();
+            self._trigger("complete");
+            return;
+        }
+
+        trigger.ajaxtip({ namespace: "contextualpopup", tooltip: {position: "bottom left",dynamic:false} })
+            .ajaxtip( "load", {
+                endpoint: "ctxpopup",
+                formmethod: "GET",
+                data: {
+                    "user": opts.username || "",
+                    "userid": opts.userid || 0,
+                    "userpic_url": opts.icon_url || "",
+                    "mode": "getinfo"
+                },
+                success: function( data, status, jqxhr ) {
+                    if ( data.error ) {
+                        if ( data.noshow )
+                            trigger.ajaxtip( "cancel" );
+                        else
+                            trigger.ajaxtip( "error", data.error )
+                    } else {
+                        self.cachedResults = data;
+                        self._renderPopup();
+
+                        // expire cache after 5 minutes
+                        setTimeout(function() {
+                            self.cachedResults = null;
+                        }, 60 * 5 * 1000);
+                    }
+                    self._trigger("complete");
+                },
+                error: function( jqxhr, status, error ) {
+                    trigger.ajaxtip( "error", "Error contacting server. " + error);
+                    self._trigger( "complete" );
+                }
+            });
+    },
+    function() {
+    }
+    )
+},
+
+_renderPopup: function() {
+    var self = this;
+    var opts = self.options;
+    var data = self.cachedResults;
+
+    if ( data && ( !data.username || !data.success || data.noshow ) ) {
+        this.element.ajaxtip("cancel");
+    }
+
+    this.element.ajaxtip("show")
+
+    var $inner = $("<div class='Inner'></div>");
+    var $content = $("<div class='Content'></div>");
+
+    if ( data.url_userpic ) {
+        var $link = $("<a></a>", { href: data.url_allpics });
+        var $icon = $("<img>", { src: data.url_userpic }).attr({width: data.userpic_w, height: data.userpic_h});
+        var $container = $("<div class='Userpic'></div>").append($link.append($icon));
+
+        $inner.append($container);
+    }
+
+    $inner.append($content);
+
+    var username = data.display_username;
+    var $relation = $("<div class='Relation'></div>");
+    var strings = {
+        member: "You are a member of " + username,
+        watching: "You have subscribed to " + username,
+        watched_by: username + " has subscribed to you",
+        mutual_watch: username + " and you have mutual subscriptions",
+        trusting: "You have granted access to " + username,
+        trusted_by: username + " has granted access to you",
+        mutual_trust: username + " and you have mutual access",
+        self: "This is you"
+    };
+    if ( data.is_comm ) {
+        var rels = [];
+        if (data.is_member) rels.push(strings.member);
+        if (data.is_watching) rels.push(strings.watching);
+
+        $relation.html(rels.length > 0 ? rels.join("<br />") : username);
+    } else if (data.is_syndicated ) {
+        $relation.html(data.is_watching ? strings.watching : username);
+    } else if (data.is_requester) {
+        $relation.html( strings.self );
+    } else {
+        var rels = [];
+        if ( data.is_trusting && data.is_trusted_by )
+            rels.push(strings.mutual_trust);
+        else if ( data.is_trusting )
+            rels.push(strings.trusting);
+        else if ( data.is_trusted_by )
+            rels.push(strings.trusted_by);
+
+        if ( data.is_watching && data.is_watched_by )
+            rels.push(strings.mutual_watch);
+        else if ( data.is_watching )
+            rels.push(strings.watching);
+        else if ( data.is_watched_by )
+            rels.push(strings.watched_by);
+
+        $relation.html(rels.length > 0 ? rels.join("<br />") : username);
+    }
+    $content.append($relation);
+
+    if ( data.is_logged_in && data.is_comm ) {
+        var $membership = $("<span></span>");
+        if ( ! data.is_closed_membership || data.is_member ) {
+            var $membershiplink = $("<a></a>");
+            var $membershipaction = data.is_member ? "leave" : "join";
+
+            if ( data.is_member )
+                $membershiplink.attr("href" , data.url_leavecomm ).html("Leave");
+            else
+                $membershiplink.attr("href", data.url_joincomm ).html("Join community");
+
+            if ( ! opts.disableAJAX ) {
+                $membershiplink.click(function(e) {
+                    e.stopPropagation(); e.preventDefault();
+                    self._changeRelation(data, membership_action, this, e);
+                });
+            }
+
+            $membership.append($membershiplink);
+        } else {
+            $membership.html("Community closed");
+        }
+        $content.append($membership);
+    }
+
+    var links = [];
+    if ( data.is_logged_in && ( data.is_person || data.is_identity ) && data.can_message ) {
+        var $sendmessage = $("<a></a>", { href: data.url_message }).html("Send message");
+        links.push($("<span></span>").append($sendmessage));
+    }
+
+    // relationships
+    if ( data.is_logged_in && ! data.is_requester ) {
+        if ( ! data.is_trusting ) {
+            if ( data.is_person || data.other_is_identity ) {
+                var $addtrust = $("<a></a>", { href: data.url_addtrust } ).html("Grant access");
+                links.push($("<span class='AddTrust'></span>").append($addtrust));
+
+                if( ! opts.disableAJAX ) {
+                    $addtrust.click(function(e) {
+                        e.stopPropagation(); e.preventDefault();
+                        self._changeRelation(data, "addTrust", this, e);
+                    });
+                }
+            }
+        } else {
+            if ( data.is_person || data.other_is_identity ) {
+                var $removetrust = $("<a></a>", { href: data.url_addtrust } ).html("Remove access");
+                links.push($("<span class='RemoveTrust'></span>").append($removetrust));
+
+                if( ! opts.disableAJAX ) {
+                    $removetrust.click(function(e) {
+                        e.stopPropagation(); e.preventDefault();
+                        self._changeRelation(data, "removeTrust", this, e);
+                    });
+                }
+            }
+        }
+
+        if ( !data.is_watching && !data.other_is_identity ) {
+            var $addwatch = $("<a></a>", { href: data.url_addwatch } ).html("Subscribe");
+            links.push($("<span class='AddWatch'></span>").append($addwatch));
+
+            if( ! opts.disableAJAX ) {
+                $addwatch.click(function(e) {
+                    e.stopPropagation(); e.preventDefault();
+                    self._changeRelation(data, "addWatch", this, e);
+                });
+            }
+        } else if ( data.is_watching ) {
+            var $removewatch = $("<a></a>", { href: data.url_addwatch } ).html("Remove subscription");
+            links.push($("<span class='RemoveWatch'></span>").append($removewatch));
+
+            if( ! opts.disableAJAX ) {
+                $removewatch.click(function(e) {
+                    e.stopPropagation(); e.preventDefault();
+                    self._changeRelation(data, "removeWatch", this, e);
+                });
+            }
+        }
+        $relation.addClass("RelationshipStatus");
+    }
+
+    // FIXME: double-check this when vgifts come out
+    if ( ( data.is_person || data.is_comm ) && ! data.is_requester && data.can_receive_vgifts ) {
+        var $sendvgift = $("<a></a>", { href: Site.siteroot + "/shop/vgift?to=" + data.username })
+            .html("Send a virtual gift");
+        links.push($("<span></span").append($sendvgift));
+    }
+
+    if ( data.is_logged_in && ! data.is_requester && ! data.is_syndicated ) {
+        if ( data.is_banned ) {
+            var $unbanlink = $("<a></a>", { href: Site.siteroot + "/manage/banusers" });
+            $unbanlink.html( data.is_comm ? "Unban community" : "Unban user" );
+            if( ! opts.disableAJAX ) {
+                $unbanlink.click(function(e) {
+                    e.stopPropagation(); e.preventDefault();
+                    self._changeRelation(data, "setUnban", this, e);
+                });
+            }
+            links.push($("<span class='SetUnban'></span>").append($unbanlink));
+
+        } else {
+            var $banlink = $("<a></a>", { href: Site.siteroot + "/manage/banusers" });
+            $banlink.html( data.is_comm ? "Ban community" : "Ban user" );
+            if( ! opts.disableAJAX ) {
+                $banlink.click(function(e) {
+                    e.stopPropagation(); e.preventDefault();
+                    self._changeRelation(data, "setBan", this, e);
+                });
+            }
+            links.push($("<span class='SetBan'></span>").append($banlink));
+        }
+    }
+
+    var linkslength = links.length;
+    $.each(links,function(index) {
+        $content.append(this);
+        $content.append("<br>");
+    });
+
+    $("<span>View: </span>").appendTo($content);
+    if ( data.is_person || data.is_comm || data.is_syndicated ) {
+        var $journallink = $("<a></a>", {href: data.url_journal});
+        if (data.is_person)
+            $journallink.html("Journal");
+        else if ( data.is_comm )
+            $journallink.html("Community");
+        else if ( data.is_syndicated )
+            $journallink.html("Feed");
+
+        $content.append(
+                $journallink,
+                $("<span> | </span>"),
+                $("<a></a>", { href: data.url_profile} ).html("Profile")
+        );
+    }
+
+    $content.append($("<div class='ljclear'>&nbsp;</div>"));
+
+    this.element
+        .ajaxtip("widget")
+            .removeClass("ajaxresult ajaxtooltip").addClass("ContextualPopup")
+            .empty().append($inner)
+},
+
+_changeRelation: function(info, action, link, e) {
+    if ( !info ) return;
+    var self = this;
+    var $link = $(link);
+
+    $link.ajaxtip({namespace: "changerelation"}).ajaxtip("load", {
+        endpoint: "changerelation",
+        data: {
+            target: info.username,
+            action: action,
+            auth_token: info[action+"_authtoken"]
+        },
+        success: function( data, status, jqxhr ) {
+            if ( data.error ) {
+                $link.ajaxtip( "error", data.error )
+            } else if ( ! data.success ) {
+                $link.ajaxtip( "error", "Did not change relation successfully" );
+                self._renderPopup(data);
+            } else {
+                if ( self.cachedResults ) {
+                    var updatedProps = [ "is_trusting", "is_watching", "is_member", "is_banned" ];
+                    $.each(updatedProps,function(){
+                        self.cachedResults[this]=data[this];
+                    });
+                }
+                self._renderPopup();
+            }
+            self._trigger("complete");
+        },
+        error: function( jqxhr, status, error ) {
+            $link.ajaxtip( "error", "Error contacting server. " + error);
+            self._trigger( "complete" );
+        }
+    });
+}
+
+});
+
+jQuery(document).ready(function($){
+    $(document).contextualhoversetup();
+    $(document.body).delegate( "*", "updatedcontent.entry.poll.comment", function(e) {
+        e.stopPropagation();
+        $(this).contextualhoversetup();
+    });
+});
diff -r 634318082e89 -r c29f3ee7ce3d htdocs/js/jquery.hoverIntent.js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/js/jquery.hoverIntent.js	Thu May 12 16:45:29 2011 +0800
@@ -0,0 +1,106 @@
+/**
+* hoverIntent is similar to jQuery's built-in "hover" function except that
+* instead of firing the onMouseOver event immediately, hoverIntent checks
+* to see if the user's mouse has slowed down (beneath the sensitivity
+* threshold) before firing the onMouseOver event.
+* 
+* hoverIntent r6 // 2011.02.26 // jQuery 1.5.1+
+* <http://cherne.net/brian/resources/jquery.hoverIntent.html>
+* 
+* hoverIntent is currently available for use in all personal or commercial 
+* projects under both MIT and GPL licenses. This means that you can choose 
+* the license that best suits your project, and use it accordingly.
+* 
+* // basic usage (just like .hover) receives onMouseOver and onMouseOut functions
+* $("ul li").hoverIntent( showNav , hideNav );
+* 
+* // advanced usage receives configuration object only
+* $("ul li").hoverIntent({
+*	sensitivity: 7, // number = sensitivity threshold (must be 1 or higher)
+*	interval: 100,   // number = milliseconds of polling interval
+*	over: showNav,  // function = onMouseOver callback (required)
+*	timeout: 0,   // number = milliseconds delay before onMouseOut function call
+*	out: hideNav    // function = onMouseOut callback (required)
+* });
+* 
+* @param  f  onMouseOver function || An object with configuration options
+* @param  g  onMouseOut function  || Nothing (use configuration options object)
+* @author    Brian Cherne brian(at)cherne(dot)net
+*/
+(function($) {
+	$.fn.hoverIntent = function(f,g) {
+		// default configuration options
+		var cfg = {
+			sensitivity: 7,
+			interval: 100,
+			timeout: 0
+		};
+		// override configuration options with user supplied object
+		cfg = $.extend(cfg, g ? { over: f, out: g } : f );
+
+		// instantiate variables
+		// cX, cY = current X and Y position of mouse, updated by mousemove event
+		// pX, pY = previous X and Y position of mouse, set by mouseover and polling interval
+		var cX, cY, pX, pY;
+
+		// A private function for getting mouse position
+		var track = function(ev) {
+			cX = ev.pageX;
+			cY = ev.pageY;
+		};
+
+		// A private function for comparing current and previous mouse position
+		var compare = function(ev,ob) {
+			ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
+			// compare mouse positions to see if they've crossed the threshold
+			if ( ( Math.abs(pX-cX) + Math.abs(pY-cY) ) < cfg.sensitivity ) {
+				$(ob).unbind("mousemove",track);
+				// set hoverIntent state to true (so mouseOut can be called)
+				ob.hoverIntent_s = 1;
+				return cfg.over.apply(ob,[ev]);
+			} else {
+				// set previous coordinates for next time
+				pX = cX; pY = cY;
+				// use self-calling timeout, guarantees intervals are spaced out properly (avoids JavaScript timer bugs)
+				ob.hoverIntent_t = setTimeout( function(){compare(ev, ob);} , cfg.interval );
+			}
+		};
+
+		// A private function for delaying the mouseOut function
+		var delay = function(ev,ob) {
+			ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
+			ob.hoverIntent_s = 0;
+			return cfg.out.apply(ob,[ev]);
+		};
+
+		// A private function for handling mouse 'hovering'
+		var handleHover = function(e) {
+			// copy objects to be passed into t (required for event object to be passed in IE)
+			var ev = jQuery.extend({},e);
+			var ob = this;
+
+			// cancel hoverIntent timer if it exists
+			if (ob.hoverIntent_t) { ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t); }
+
+			// if e.type == "mouseenter"
+			if (e.type == "mouseenter") {
+				// set "previous" X and Y position based on initial entry point
+				pX = ev.pageX; pY = ev.pageY;
+				// update "current" X and Y position based on mousemove
+				$(ob).bind("mousemove",track);
+				// start polling interval (self-calling timeout) to compare mouse coordinates over time
+				if (ob.hoverIntent_s != 1) { ob.hoverIntent_t = setTimeout( function(){compare(ev,ob);} , cfg.interval );}
+
+			// else e.type == "mouseleave"
+			} else {
+				// unbind expensive mousemove event
+				$(ob).unbind("mousemove",track);
+				// if hoverIntent state is true, then call the mouseOut function after the specified delay
+				if (ob.hoverIntent_s == 1) { ob.hoverIntent_t = setTimeout( function(){delay(ev,ob);} , cfg.timeout );}
+			}
+		};
+
+		// bind the function to the two event listeners
+		return this.bind('mouseenter',handleHover).bind('mouseleave',handleHover);
+	};
+})(jQuery);
\ No newline at end of file
diff -r 634318082e89 -r c29f3ee7ce3d htdocs/js/jquery.hoverIntent.minified.js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/js/jquery.hoverIntent.minified.js	Thu May 12 16:45:29 2011 +0800
@@ -0,0 +1,9 @@
+/**
+* hoverIntent r6 // 2011.02.26 // jQuery 1.5.1+
+* <http://cherne.net/brian/resources/jquery.hoverIntent.html>
+* 
+* @param  f  onMouseOver function || An object with configuration options
+* @param  g  onMouseOut function  || Nothing (use configuration options object)
+* @author    Brian Cherne brian(at)cherne(dot)net
+*/
+(function($){$.fn.hoverIntent=function(f,g){var cfg={sensitivity:7,interval:100,timeout:0};cfg=$.extend(cfg,g?{over:f,out:g}:f);var cX,cY,pX,pY;var track=function(ev){cX=ev.pageX;cY=ev.pageY};var compare=function(ev,ob){ob.hoverIntent_t=clearTimeout(ob.hoverIntent_t);if((Math.abs(pX-cX)+Math.abs(pY-cY))<cfg.sensitivity){$(ob).unbind("mousemove",track);ob.hoverIntent_s=1;return cfg.over.apply(ob,[ev])}else{pX=cX;pY=cY;ob.hoverIntent_t=setTimeout(function(){compare(ev,ob)},cfg.interval)}};var delay=function(ev,ob){ob.hoverIntent_t=clearTimeout(ob.hoverIntent_t);ob.hoverIntent_s=0;return cfg.out.apply(ob,[ev])};var handleHover=function(e){var ev=jQuery.extend({},e);var ob=this;if(ob.hoverIntent_t){ob.hoverIntent_t=clearTimeout(ob.hoverIntent_t)}if(e.type=="mouseenter"){pX=ev.pageX;pY=ev.pageY;$(ob).bind("mousemove",track);if(ob.hoverIntent_s!=1){ob.hoverIntent_t=setTimeout(function(){compare(ev,ob)},cfg.interval)}}else{$(ob).unbind("mousemove",track);if(ob.hoverIntent_s==1){ob.hoverIntent_t=setTimeout(function(){delay(ev,ob)},cfg.timeout)}}};return this.bind('mouseenter',handleHover).bind('mouseleave',handleHover)}})(jQuery);
\ No newline at end of file
diff -r 634318082e89 -r c29f3ee7ce3d htdocs/stc/contextualhover.css
--- a/htdocs/stc/contextualhover.css	Thu May 12 16:42:01 2011 +0800
+++ b/htdocs/stc/contextualhover.css	Thu May 12 16:45:29 2011 +0800
@@ -3,6 +3,7 @@
     margin: 5px 0 0 20px;
     font: normal 11px "Arial", "Verdana", sans-serif !important;
     text-align: left;
+    z-index: 99;
     }
 
 * html div.ContextualPopup {
--------------------------------------------------------------------------------

Post a comment in response:

This account has disabled anonymous posting.
If you don't have an account you can create one now.
No Subject Icon Selected
More info about formatting

If you are unable to use this captcha for any reason, please contact us by email at support@dreamwidth.org