├── README.md ├── class.mtg.plugin.php └── js └── tooltip.js /README.md: -------------------------------------------------------------------------------- 1 | # Vanilla-MTG-Card-Tooltips 2 | 3 | ## Usage: 4 | 5 | `[mtg]Card name[/mtg]` 6 | 7 | Will create a Link with a mouseover Tooltip of the card. 8 | 9 | ## Installation: 10 | 11 | - Create a `MTG` folder under `plugins` 12 | - Put `class.mtg.plugin.php` and the `js` folder into the `MTG` folder 13 | - Open the Vanilla dashboard and activate the `MTG` Plugin 14 | 15 | 16 | -------------------------------------------------------------------------------- /class.mtg.plugin.php: -------------------------------------------------------------------------------- 1 | 'MTG', 5 | 'Description' => "Adds MTG BBcode.", 6 | 'Version' => '0.1', 7 | 'MobileFriendly' => TRUE, 8 | 'Author' => "Questlog", 9 | 'AuthorEmail' => 'questlog@github.com', 10 | 'AuthorUrl' => 'http://github.com/questlog' 11 | ); 12 | 13 | use Nbbc\BBCode as BBCode; 14 | 15 | class MTG extends Gdn_Plugin { 16 | 17 | public function bbcode_afterBBCodeSetup_handler($Sender) { 18 | $nbbc = $Sender->EventArguments['BBCode']; 19 | 20 | $nbbc->addRule('mtg', [ 21 | 'mode' => BBCode::BBCODE_MODE_CALLBACK, 22 | 'method' => [$this, 'doCard'], 23 | 'allow_in' => ['listitem', 'block', 'columns', 'inline'], 24 | 'content' => BBCode::BBCODE_REQUIRED 25 | ]); 26 | } 27 | 28 | public function doCard(BBCode $bbcode, $action, $name, $default, $params, $content) { 29 | 30 | return "" . htmlspecialchars($content) . ""; 31 | } 32 | 33 | 34 | public function DiscussionController_Render_Before(&$Sender) { 35 | $this->PrepareController($Sender); 36 | } 37 | 38 | public function PostController_Render_Before(&$Sender) { 39 | $this->PrepareController($Sender); 40 | } 41 | 42 | public function MessagesController_Render_Before(&$Sender) { 43 | $this->PrepareController($Sender); 44 | } 45 | 46 | protected function PrepareController(&$Sender) { 47 | $Sender->AddJsFile('tooltip.js', 'plugins/MTG'); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /js/tooltip.js: -------------------------------------------------------------------------------- 1 | // Initialize namespaces. 2 | if (typeof Deckbox == "undefined") Deckbox = {}; 3 | Deckbox.ui = Deckbox.ui || {}; 4 | 5 | 6 | /** 7 | * Main tooltip type. Will be initialized for both the image tooltips and for the wow tooltips, or simple text tooltips. 8 | */ 9 | Deckbox.ui.Tooltip = function(className, type) { 10 | this.el = document.createElement('div'); 11 | this.el.className = className + ' ' + type; 12 | this.type = type; 13 | this.el.style.display = 'none'; 14 | document.body.appendChild(this.el); 15 | this.tooltips = {}; 16 | }; 17 | 18 | Deckbox.ui.Tooltip.prototype = { 19 | _padContent: function(content) { 20 | return "" + 21 | "
" + content + "
"; 22 | }, 23 | 24 | showWow: function(posX, posY, content, url, el) { 25 | /* IE does NOT escape quotes apparently. */ 26 | url = url.replace(/"/g, "%22"); 27 | /* Problematic with routes on server. */ 28 | url = url.replace(/\?/g, ""); 29 | 30 | if (this.tooltips[url] && this.tooltips[url].content) { 31 | content = this._padContent(this.tooltips[url].content); 32 | } else { 33 | content = this._padContent('Loading...'); 34 | this.tooltips[url] = this.tooltips[url] || {el: el}; 35 | Deckbox._.loadJS(url); 36 | /* Remeber these for when (if) the register call wants to show the tooltip. */ 37 | this.posX = posX; this.posY = posY; 38 | } 39 | 40 | this.el.style.width = ''; 41 | this.el.innerHTML = content; 42 | this.el.style.display = ''; 43 | this.el.style.width = (20 + Math.min(330, this.el.childNodes[0].offsetWidth)) + 'px'; 44 | this.move(posX, posY); 45 | }, 46 | 47 | showText: function(posX, posY, text) { 48 | this.el.innerHTML = text; 49 | this.el.style.display = ''; 50 | this.move(posX, posY); 51 | }, 52 | 53 | showImage: function(posX, posY, image) { 54 | if (image.complete) { 55 | this.el.innerHTML = ''; 56 | this.el.appendChild(image); 57 | } else { 58 | this.el.innerHTML = 'Loading...'; 59 | image.onload = function() { 60 | var self = Deckbox._.tooltip('image'); 61 | self.el.innerHTML = ''; 62 | image.onload = null; 63 | self.el.appendChild(image); 64 | self.move(posX, posY); 65 | } 66 | } 67 | this.el.style.display = ''; 68 | this.move(posX, posY); 69 | }, 70 | 71 | hide: function() { 72 | this.el.style.display = 'none'; 73 | }, 74 | 75 | move: function(posX, posY) { 76 | // The tooltip should be offset to the right so that it's not exactly next to the mouse. 77 | posX += 15; 78 | posY -= this.el.offsetHeight / 3; 79 | 80 | // Remeber these for when (if) the register call wants to show the tooltip. 81 | this.posX = posX; 82 | this.posY = posY; 83 | if (this.el.style.display == 'none') return; 84 | 85 | var pos = Deckbox._.fitToScreen(posX, posY, this.el); 86 | 87 | this.el.style.top = pos[1] + "px"; 88 | this.el.style.left = pos[0] + "px"; 89 | }, 90 | 91 | register: function(url, content) { 92 | this.tooltips[url].content = content; 93 | if (this.tooltips[url].el._shown) { 94 | this.el.style.width = ''; 95 | this.el.innerHTML = this._padContent(content); 96 | this.el.style.width = (20 + Math.min(330, this.el.childNodes[0].offsetWidth)) + 'px'; 97 | this.move(this.posX, this.posY); 98 | } 99 | } 100 | }; 101 | Deckbox.ui.Tooltip.hide = function() { 102 | Deckbox._.tooltip('image').hide(); 103 | Deckbox._.tooltip('wow').hide(); 104 | Deckbox._.tooltip('text').hide(); 105 | }; 106 | 107 | 108 | Deckbox._ = { 109 | onDocumentLoad: function(callback) { 110 | if (window.addEventListener) { 111 | window.addEventListener("load", callback, false); 112 | } else { 113 | window.attachEvent && window.attachEvent("onload", callback); 114 | } 115 | }, 116 | 117 | preloadImg: function(link) { 118 | var img = document.createElement('img'); 119 | img.style.display = "none" 120 | img.style.width = "1px" 121 | img.style.height = "1px" 122 | img.src = link.href + '/tooltip'; 123 | return img; 124 | }, 125 | 126 | pointerX: function(event) { 127 | var docElement = document.documentElement, 128 | body = document.body || { scrollLeft: 0 }; 129 | 130 | return event.pageX || 131 | (event.clientX + 132 | (docElement.scrollLeft || body.scrollLeft) - 133 | (docElement.clientLeft || 0)); 134 | }, 135 | 136 | pointerY: function(event) { 137 | var docElement = document.documentElement, 138 | body = document.body || { scrollTop: 0 }; 139 | 140 | return event.pageY || 141 | (event.clientY + 142 | (docElement.scrollTop || body.scrollTop) - 143 | (docElement.clientTop || 0)); 144 | }, 145 | 146 | scrollOffsets: function() { 147 | return [ 148 | window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, 149 | window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop 150 | ]; 151 | }, 152 | 153 | viewportSize: function() { 154 | var ua = navigator.userAgent, rootElement; 155 | if (ua.indexOf('AppleWebKit/') > -1 && !document.evaluate) { 156 | rootElement = document; 157 | } else if (Object.prototype.toString.call(window.opera) == '[object Opera]' && window.parseFloat(window.opera.version()) < 9.5) { 158 | rootElement = document.body; 159 | } else { 160 | rootElement = document.documentElement; 161 | } 162 | 163 | /* IE8 in quirks mode returns 0 for these sizes. */ 164 | var size = [rootElement['clientWidth'], rootElement['clientHeight']]; 165 | if (size[1] == 0) { 166 | return [document.body['clientWidth'], document.body['clientHeight']]; 167 | } else { 168 | return size; 169 | } 170 | }, 171 | 172 | fitToScreen: function(posX, posY, el) { 173 | var scroll = Deckbox._.scrollOffsets(), viewport = Deckbox._.viewportSize(); 174 | 175 | /* decide if wee need to switch sides for the tooltip */ 176 | /* too big for X */ 177 | if ((el.offsetWidth + posX) >= (viewport[0] - 15) ) { 178 | posX = posX - el.offsetWidth - 20; 179 | } 180 | 181 | /* If it's too high, we move it down. */ 182 | if (posY - scroll[1] < 0) { 183 | posY += scroll[1] - posY + 5; 184 | } 185 | /* If it's too low, we move it up. */ 186 | if (posY + el.offsetHeight - scroll[1] > viewport[1]) { 187 | posY -= posY + el.offsetHeight + 5 - scroll[1] - viewport[1]; 188 | } 189 | 190 | return [posX, posY]; 191 | }, 192 | 193 | addEvent: function(obj, type, fn) { 194 | if (obj.addEventListener) { 195 | if (type == 'mousewheel') obj.addEventListener('DOMMouseScroll', fn, false); 196 | obj.addEventListener( type, fn, false ); 197 | } else if (obj.attachEvent) { 198 | obj["e"+type+fn] = fn; 199 | obj[type+fn] = function() { obj["e"+type+fn]( window.event ); }; 200 | obj.attachEvent( "on"+type, obj[type+fn] ); 201 | } 202 | }, 203 | 204 | removeEvent: function(obj, type, fn) { 205 | if (obj.removeEventListener) { 206 | if(type == 'mousewheel') obj.removeEventListener('DOMMouseScroll', fn, false); 207 | obj.removeEventListener( type, fn, false ); 208 | } else if (obj.detachEvent) { 209 | obj.detachEvent( "on"+type, obj[type+fn] ); 210 | obj[type+fn] = null; 211 | obj["e"+type+fn] = null; 212 | } 213 | }, 214 | 215 | loadJS: function(url) { 216 | var s = document.createElement('s' + 'cript'); 217 | s.setAttribute("type", "text/javascript"); 218 | s.setAttribute("src", url); 219 | document.getElementsByTagName("head")[0].appendChild(s); 220 | }, 221 | 222 | loadCSS: function(url) { 223 | var s = document.createElement("link"); 224 | s.type = "text/css"; 225 | s.rel = "stylesheet"; 226 | s.href = url; 227 | document.getElementsByTagName("head")[0].appendChild(s); 228 | }, 229 | 230 | needsTooltip: function(el) { 231 | if (el.getAttribute('data-tt')) return true; 232 | 233 | var href; 234 | if (!el || !(el.tagName == 'A') || !(href = el.getAttribute('href'))) return false; 235 | if (el.className.match('no_tooltip')) return false; 236 | return href.match(/^https?:\/\/[^\/]*\/(mtg|wow|whi)\/.+/); 237 | }, 238 | 239 | tooltip: function(which) { 240 | if (which == 'image') return this._iT = this._iT || new Deckbox.ui.Tooltip('deckbox_i_tooltip', 'image'); 241 | if (which == 'wow') return this._wT = this._wT || new Deckbox.ui.Tooltip('deckbox_tooltip', 'wow'); 242 | if (which == 'text') return this._tT = this._tT || new Deckbox.ui.Tooltip('deckbox_t_tooltip', 'text'); 243 | }, 244 | 245 | target: function(event) { 246 | var target = event.target || event.srcElement || document; 247 | /* check if target is a textnode (safari) */ 248 | if (target.nodeType == 3) target = target.parentNode; 249 | return target; 250 | } 251 | }; 252 | 253 | /** 254 | * Bind the listeners. 255 | */ 256 | (function() { 257 | function onmouseover(event) { 258 | var el = Deckbox._.target(event); 259 | if (Deckbox._.needsTooltip(el)) { 260 | var no = el.getAttribute('data-nott'), url, img, 261 | posX = Deckbox._.pointerX(event), posY = Deckbox._.pointerY(event); 262 | if (!no) { 263 | el._shown = true; 264 | if (url = el.getAttribute('data-tt')) { 265 | showImage(el, url, posX, posY); 266 | } else if (el.href.match('/(mtg|whi)/')) { 267 | showImage(el, el.href + '/tooltip', posX, posY); 268 | } else { 269 | Deckbox._.tooltip('wow').showWow(posX, posY, null, el.href + '/tooltip', el); 270 | } 271 | } 272 | } 273 | } 274 | 275 | function showImage(el, url, posX, posY) { 276 | var img = document.createElement('img'); 277 | url = url.replace(/\?/g, ""); /* Problematic with routes on server. */ 278 | img.src = url; 279 | img.height = 310; 280 | 281 | setTimeout(function() { 282 | if (el._shown) Deckbox._.tooltip('image').showImage(posX, posY, img); 283 | }, 200); 284 | } 285 | 286 | function onmousemove(event) { 287 | var el = Deckbox._.target(event), posX = Deckbox._.pointerX(event), posY = Deckbox._.pointerY(event); 288 | if (Deckbox._.needsTooltip(el)) { 289 | Deckbox._.tooltip('image').move(posX, posY); 290 | Deckbox._.tooltip('wow').move(posX, posY, el.href); 291 | } 292 | } 293 | 294 | function onmouseout(event) { 295 | var el = Deckbox._.target(event); 296 | if (Deckbox._.needsTooltip(el)) { 297 | el._shown = false; 298 | Deckbox._.tooltip('image').hide(); 299 | Deckbox._.tooltip('wow').hide(); 300 | } 301 | } 302 | 303 | function click(event) { 304 | Deckbox._.tooltip('image').hide(); 305 | Deckbox._.tooltip('wow').hide(); 306 | } 307 | 308 | Deckbox._.addEvent(document, 'mouseover', onmouseover); 309 | Deckbox._.addEvent(document, 'mousemove', onmousemove); 310 | Deckbox._.addEvent(document, 'mouseout', onmouseout); 311 | Deckbox._.addEvent(document, 'click', click); 312 | 313 | var protocol = (document.location.protocol == 'https:') ? 'https:' : 'http:'; 314 | Deckbox._.loadCSS(protocol + '//deckbox.org/assets/external/deckbox_tooltip.css'); 315 | /* IE needs more shit */ 316 | 317 | if (!!window.attachEvent && !(Object.prototype.toString.call(window.opera) == '[object Opera]')) { 318 | Deckbox._.loadCSS(protocol + '//deckbox.org/assets/external/deckbox_tooltip_ie.css'); 319 | } 320 | 321 | /* Preload the tooltip images. */ 322 | Deckbox._.onDocumentLoad(function() { 323 | return; 324 | var allLinks = document.getElementsByTagName('a'); 325 | for (var i = 0; i < allLinks.length; i ++) { 326 | var link = allLinks[i]; 327 | if (Deckbox._.needsTooltip(link)) { 328 | document.body.appendChild(Deckbox._.preloadImg(link)); 329 | } 330 | } 331 | }); 332 | })(); 333 | --------------------------------------------------------------------------------