├── 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 "
| " + content + " | |
|---|
" +
21 | " | |
";
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 |
--------------------------------------------------------------------------------