diff --git a/database/auth.db b/database/auth.db index c3fe620..d2b71db 100644 Binary files a/database/auth.db and b/database/auth.db differ diff --git a/database/live.db b/database/live.db index ebf2ebd..8720f4a 100644 Binary files a/database/live.db and b/database/live.db differ diff --git a/docs/tooltip.md b/docs/tooltip.md new file mode 100644 index 0000000..19cd162 --- /dev/null +++ b/docs/tooltip.md @@ -0,0 +1,98 @@ +# Tooltip + +The tooltip library is Isotip, taken and modified by Sharkk! Here is the breakdown of the API. + +Configuring a specific tooltip is done via data attributes on an element. + +### **`data-tooltip-classname`** + +If you'd like to add a classname to the root tooltip element, set it here. + +### **`data-tooltip-content`** + +This sets the main body content of the tooltip into a `

` tag by default with a class of `tooltip-content`. Content is interpreted as plain text by default. To insert html, set the data-tooltip-html attribute to true. + +### **`data-tooltip-title`** + +This sets the title of the tooltip into a `

` tag by default with a class of `tooltip-title`. + +### **`data-tooltip-html`** + +Setting this to true will force Isotip to try and interpret the content as HTML. If it fails, it will interpret the content as plain text. + +### **`data-tooltip-placement`** + +This sets the position of the tooltip. Options are `top`, `right`, `bottom`, and `left`. By default, `top` is used for all tooltips. + +### **`data-tooltip-container`** + +This sets the element that the tooltip will be prepended to. By default, this is the `` element. + +Alternatively, programattic creation and destruction of tooltips is available. + +### **`data-tooltip-scrollContainer`** + +This sets the element that will have a scroll event bound to it. If your tooltip is inside a scrolling element (`overflow:scroll`), you need to add this!. + +### **`data-tooltip-autoclose`** + +If set to false, the tooltip will *not* close unless you do so programmatically with `isotip.close()`. Normal tooltips will not open until the open one has been closed! + +### **`init( config )`** + +The init method provides automatic event binding for tooltips. It sets up delegated event listeners for `.tooltip-click`, `.tooltip-hover`, and `.tooltip-focus` for click, mouseover, and focus events respectively. You can pass in an optional config object to overwrite any of the default options. + +```javascript +var options = { + html: false, // set to true to always interpret content as HTML + placement: 'top', // default placement of tooltips + container: 'body', // default containing element for the tooltip + scrollContainer: '.scroll-container', // default container for scroll watching + template: '

', // default template for the tooltip shell + removalDelay: 200, // default number of ms before the tooltip is removed + tooltipOffset: 10, // default number of px the tooltip is offset from the trigger element + windowPadding: { // window bounds for tooltip repositioning + top: 10, + right: 10, + bottom: 10, + left: 10 + } +}; + +Tooltip.init( config ); +``` + +### **`open( trigger, config )`** + +The open method will create the tooltip, insert it into the DOM, and position it in relation to it's trigger. The trigger can be an element or a CSS selector. The object to be passed in will serve as a replacement for the data attributes on the trigger. + +```javascript +var config = { + className: 'specific-class', // set to add a class to the tooltip + html: false, // set to true to interpret content as HTML + placement: 'top', // where to place the tooltip in relation to the trigger + content: 'Tooltip content', // the content to go into the tooltip, + title: 'Tooltip title', // the text to go in the title, if any + container: document.querySelector('.container'), // the container to append the tooltip to + scrollContainer: document.querySelector('.scroll-container'), // the container to bind the scroll event to + autoClose: false // set to false if you only want to close the tooltip programmatically. Normal tooltips won't open until the open one has been closed! +}; + +Tooltip.open( '.tooltip', config ); +``` + +### **`close( tooltip )`** + +The close method will remove a tooltip from the DOM. The tooltip to remove should be passed in and can be an element or a CSS selector. + +```javascript +Tooltip.close( '.tooltip' ); +``` + +### **`positionTooltip( tooltip, trigger, placement )`** + +The positionTooltip method will re-evaluate the position of a tooltip in relation to it's trigger element. Only the tooltip and trigger need to be passed in, and placement will default to what's been configured by `init()`. Tooltip and trigger can be either an element or a CSS selector. + +```javascript +Tooltip.positionTooltip( '.tooltip', '.tooltip-click', 'left' ); +``` diff --git a/public/assets/css/dragon.css b/public/assets/css/dragon.css index 4716166..a408291 100644 --- a/public/assets/css/dragon.css +++ b/public/assets/css/dragon.css @@ -25,12 +25,11 @@ body { border: none; font-size: 1rem; background: #f7f8fa linear-gradient(rgba(255, 255, 255, 0), rgba(0, 0, 0, 0.1)); + box-shadow: 0 1px 0 1px rgba(255, 255, 255, 0.3) inset, 0 0 0 1px #adb2bb inset; color: #111111; - margin: 0rem 0.25rem 0rem 0rem; padding: 0.5rem 1rem 0.5rem; text-align: center; border-radius: 3px; - box-shadow: 0 1px 0 1px rgba(255, 255, 255, 0.3) inset, 0 0 0 1px #adb2bb inset; user-select: none; text-decoration: none; transition: opacity 0.1s ease, background-color 0.1s ease, color 0.1s ease, background 0.1s ease; @@ -43,6 +42,11 @@ body { color: rgba(0, 0, 0, 0.8); } + &.badge { + font-size: 10px; + padding: 0.1rem 0.25rem; + } + &.primary { background-color: #f4cc67; background-image: linear-gradient(rgba(255, 255, 255, 0.15), rgba(0, 0, 0, 0.1)); @@ -98,6 +102,31 @@ header { main { padding: 1rem; + display: grid; + grid-template-columns: 1fr 8fr 1fr; + gap: 2rem; +} + +aside#left { + .box { + background-color: rgba(0, 0, 0, 0.2); + border-radius: 0.15rem; + + &#nav > a { + display: block; + width: 100%; + padding: 0.5rem 1rem; + border-radius: 0.15rem; + text-decoration: none; + color: black; + transition: color, background-color 0.2s ease; + + &:hover, &.active { + color: white; + background-color: black; + } + } + } } footer { @@ -116,11 +145,11 @@ footer { #char-bar { display: flex; - justify-content: space-between; align-items: center; padding: 0 1rem; height: 34px; color: white; + gap: 1rem; background-image: url('/assets/img/deco-bar2.jpg'); & > div { @@ -172,3 +201,60 @@ span.badge { width: 960px; margin: 0 auto; } + +.char-meter { + background-color: black; + height: 16px; + min-width: 100px; + border-radius: 0.1rem; + position: relative; + + & > div { + height: 100%; + border-radius: 0.1rem; + overflow: hidden; + + &.hp { + background-color: #e57373; + background-image: linear-gradient(rgba(255, 255, 255, 0.15), rgba(139, 0, 0, 0.1)); + box-shadow: 0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset; + border: 1px solid; + border-color: #d32f2f #c62828 #b71c1c; + } + + &.mp { + background-color: #5a9bd4; + background-image: linear-gradient(rgba(255, 255, 255, 0.15), rgba(60, 100, 150, 0.1)); + box-shadow: 0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset; + border: 1px solid; + border-color: #4a8ab0 #3a7a9c #2a6a88; + } + + &.tp { + background-color: #f4cc67; + background-image: linear-gradient(rgba(255, 255, 255, 0.15), rgba(0, 0, 0, 0.1)); + box-shadow: 0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset; + border: 1px solid; + border-color: #C59F43 #AA8326 #957321; + } + } +} + +.tooltip { + background-color: black; + color: white; + border: 1px solid #666; + font-size: 14px; + padding: 0.5rem; + box-shadow: 0 0 0.5rem 0.1rem rgba(0, 0, 0, 0.2); + border-radius: 0.1rem; + text-align: center; +} + +.tooltip-trigger { + width: 100%; + height: 100%; + position: absolute; + top: 0; + left: 0; +} diff --git a/public/assets/scripts/tooltip.js b/public/assets/scripts/tooltip.js new file mode 100644 index 0000000..6ef4575 --- /dev/null +++ b/public/assets/scripts/tooltip.js @@ -0,0 +1,3 @@ +var C=(O,q)=>()=>(q||O((q={exports:{}}).exports,q),q.exports);var w=C((A,H)=>{H.exports={options:{html:!1,placement:"top",container:"body",scrollContainer:window,template:'
',removalDelay:200,tooltipOffset:10,windowPadding:{top:10,right:10,bottom:10,left:10}},init:function O(q){var z=this;document.documentElement.scrollTop=1,this.documentElement=document.documentElement.scrollTop===1?document.documentElement:document.body;var J=document.createElement("div");if(J.innerHTML='
a',this.innerHTMLBug=!J.getElementsByTagName("link").length,J=void 0,"ontouchstart"in window||window.DocumentTouch&&document instanceof window.DocumentTouch)document.body.style.cursor="pointer";if(this.map={legend:[1,"
","
"],tr:[2,"","
"],col:[2,"","
"],defaultTag:this.innerHTMLBug?[1,"X
","
"]:[0,"",""]},this.map.td=this.map.th=[3,"","
"],this.map.option=this.map.optgroup=[1,'"],this.map.thead=this.map.tbody=this.map.colgroup=this.map.caption=this.map.tfoot=[1,"","
"],this.map.text=this.map.circle=this.map.ellipse=this.map.line=this.map.path=this.map.polygon=this.map.polyline=this.map.rect=[1,'',""],q instanceof Object){for(var D in q)if(window.Object.hasOwnProperty.call(q,D)&&window.Object.hasOwnProperty.call(this.options,D))if(typeof q[D]==="object"&&!Array.isArray(q[D])){for(var F in q[D])if(window.Object.hasOwnProperty.call(q[D],F)&&window.Object.hasOwnProperty.call(this.options[D],F))this.options[D][F]=q[D][F]}else this.options[D]=q[D]}function S(I){if(!I)I=window.event;var G=I.target||I.srcElement;if(z.currentTooltip&&z.currentTooltip.getAttribute("data-autoclose")==="false")return;else if(z.currentTooltip)if(G===z.currentTooltip)return;else if(G===z.currentTrigger){z.close(z.currentTooltip);return}else{if(z.hasParent(G,z.currentTooltip))return!1;z.close(z.currentTooltip)}if(!z.hasClass(G,"tooltip-click"))return;z.open(G)}function K(I){if(!I)I=window.event;var G=I.target||I.srcElement;if(!z.hasClass(G,"tooltip-hover"))return;if(z.currentTooltip&&z.currentTooltip.getAttribute("data-autoclose")==="false")return;else if(z.currentTooltip)if(G===z.currentTooltip)return;else{if(z.hasParent(G,z.currentTooltip))return!1;z.close(z.currentTooltip)}function Q(Z){if(!Z)Z=window.event;var V=I.target||I.srcElement;if(z.hasClass(V))return;if(z.currentTooltip&&z.currentTooltip.getAttribute("data-autoclose")==="false")return;z.close(z.currentTooltip),z.removeEventListener(G,"mouseout",Q);return}z.open(G),z.addEventListener(G,"mouseout",Q);return}function U(I){if(!I)I=window.event;var G=I.target||I.srcElement;if(!z.hasClass(G,"tooltip-focus"))return;if(z.currentTooltip&&z.currentTooltip.getAttribute("data-autoclose")==="false")return;else if(z.currentTooltip)z.close(z.currentTooltip);function Q(){if(z.currentTooltip&&z.currentTooltip.getAttribute("data-autoclose")==="false")return;z.close(z.currentTooltip),z.removeEventListener(G,"blur",Q);return}z.open(G),z.addEventListener(G,"blur",Q);return}this.windowChangeHandler=function I(){if(z.currentTooltip&&z.currentTrigger)z.positionTooltip(z.currentTooltip,z.currentTrigger);return},this.addEventListener(document.body,"click",S),this.addEventListener(document.body,"mouseover",K),this.addEventListener(document.body,"focus",U,!0),this.addEventListener(window,"scroll",this.windowChangeHandler),this.addEventListener(window,"resize",this.windowChangeHandler)},open:function O(q,z){if(typeof q==="string")q=document.body.querySelector(q);if(!z)z={};var J=z.className||q.getAttribute("data-tooltip-classname"),D=z.content||q.getAttribute("data-tooltip-content"),F=z.title||q.getAttribute("data-tooltip-title"),S=z.html||q.getAttribute("data-tooltip-html"),K=z.placement||q.getAttribute("data-tooltip-placement"),U=z.container||q.getAttribute("data-tooltip-container"),I=z.container||q.getAttribute("data-tooltip-scrollContainer"),G=document.querySelector(".tooltip"),Q=this.createDOMElement(this.options.template),Z,V;if(!D)return;if(this.currentTooltip&&this.currentTooltip.getAttribute("data-autoclose")==="false")return;if(Q.appendChild(this.createDOMElement('
')),J)this.addClass(Q,J);if(F)Z=this.createDOMElement('

'+F+"

"),Q.appendChild(Z);if((this.options.html||S)&&D)if(this.isElement(D))V=D;else if(this.getTagName(D))V=this.createDOMElement(D);else V=this.createDOMElement('

'+D+"

");else V=this.createDOMElement('

'+D+"

");if(Q.appendChild(V),U&&U!==this.options.container){if(typeof U==="string")this.currentContainer=document.querySelector(U)}else this.currentContainer=document.querySelector(this.options.container);if(I&&I!==this.options.scrollContainer){if(typeof I==="string")this.currentScrollContainer=document.querySelector(I)}else this.currentScrollContainer=this.options.scrollContainer;if(z.autoClose===!1||q.getAttribute("data-tooltip-autoclose")==="false")Q.setAttribute("data-autoclose","false");if(G)this.currentTooltip=G.parentNode.insertBefore(Q,G);else this.currentTooltip=this.currentContainer.appendChild(Q);if(this.currentTrigger=q,this.positionTooltip(this.currentTooltip,this.currentTrigger,K),this.currentScrollContainer!==window)this.addEventListener(this.currentScrollContainer,"scroll",this.windowChangeHandler);return this.currentTooltip},close:function O(q){if(typeof q==="string")q=document.body.querySelector(q);if(this.currentScrollContainer!==window)this.removeEventListener(this.currentScrollContainer,"scroll",this.windowChangeHandler);this.removeClass(q,"visible"),this.currentTooltip=null,this.currentTrigger=null,this.currentScrollContainer=null,window.setTimeout(function z(){if(q&&q instanceof window.Element&&q.parentNode)q.parentNode.removeChild(q);else if(q=document.body.querySelector(".tooltip"),q&&q.parentNode)q.parentNode.removeChild(q)},this.options.removalDelay)},positionTooltip:function O(q,z,J){if(typeof q==="string")q=document.body.querySelector(q);if(typeof z==="string")z=document.body.querySelector(z);if(!J)J=z.getAttribute("data-tooltip-placement")||this.options.placement;var D=this,F=q.querySelector(".tooltip-accent"),S=z.getBoundingClientRect(),K=Math.floor(S.width),U=Math.floor(S.height),I=S.left,G=S.top,Q=this.options.windowPadding.top,Z=(window.innerWidth||document.documentElement.clientWidth||document.body.clientWidth)-this.options.windowPadding.right,V=(window.innerHeight||document.documentElement.clientHeight||document.body.clientHeight)-this.options.windowPadding.bottom,x=this.options.windowPadding.left,b,R,B,P,M,_,j,L,y,X,$,k;if(this.currentScrollContainer.getBoundingClientRect){var E=this.currentScrollContainer.getBoundingClientRect();if(E.top>=0)b=E.top+this.options.windowPadding.top;if(E.right<=Z)R=E.right-this.options.windowPadding.right;if(E.bottom<=V)B=E.bottom-this.options.windowPadding.bottom;if(E.left>=x)P=E.left+this.options.windowPadding.left}function T(){if(M=I-j/2+K/2,_=G-L-D.options.tooltipOffset,y=M+j,D.addClass(q,"tooltip-top"),!$)$=parseInt(F.offsetWidth,10);if(!k)k=parseInt(F.offsetHeight,10);if(G+U<=b){if(D.hasClass(q,"visible"))D.removeClass(q,"visible")}if(y>Z||y>R)q.style.top="auto",q.style.bottom=V+D.options.windowPadding.bottom-G+D.options.tooltipOffset+"px",q.style.right=D.options.windowPadding.right+"px",F.style.left="auto",F.style.right=K/2-$/2+(Z-I-K)+"px";else if(_Z||y>R)return D.removeClass(q,"tooltip-right"),T();else q.style.top=_+"px",q.style.left=M+"px",F.style.right="",F.style.bottom="",F.style.left="",F.style.top=L/2-k/2+"px"}function Y(){if(M=I-j/2+K/2,_=G+U+D.options.tooltipOffset,y=M+j,X=_+L,D.addClass(q,"tooltip-bottom"),!$)$=parseInt(F.offsetWidth,10);if(G>=B){if(D.hasClass(q,"visible"))D.removeClass(q,"visible")}if(y>Z||y>R)q.style.top=_+"px",q.style.right=D.options.windowPadding.right+"px",F.style.left="auto",F.style.right=K/2-$/2+(Z-I-K)+"px";else if(X>V||X>B)return D.removeClass(q,"tooltip-bottom"),T();else if(Mb&&G=B)D.removeClass(q,"visible");return q},addEventListener:function O(q,z,J,D){if(!D)D=!1;if(q.addEventListener)return q.addEventListener(z,J,D),q;else{if(z==="focus")z="focusin";return q.attachEvent("on"+z,function(){J.call(q)}),q}},removeEventListener:function O(q,z,J,D){if(!D)D=!1;if(!q)return;if(q.removeEventListener)q.removeEventListener(z,J,D);else{if(z==="focus")z="focusin";q.detachEvent("on"+z,function(){J.call(q)})}return q},hasClass:function O(q,z){if(q.classList)return q.classList.contains(z);else return new RegExp("(^| )"+z+"( |$)","gi").test(q.className)},addClass:function O(q,z){if(q.classList)q.classList.add(z);else q.className+=" "+z;return q},removeClass:function O(q,z){if(q)if(q.classList)q.classList.remove(z);else q.className=q.className.replace(new RegExp("(^|\\b)"+z.split(" ").join("|")+"(\\b|$)","gi")," ");return q},setInnerText:function O(q,z){if(q.textContent!==void 0)q.textcontent=z;else q.innerText=z;return q},getTagName:function O(q){return/<([\w:]+)/.exec(q)},isElement:function O(q){return!!(q&&q.nodeType===1)},createDOMElement:function O(q){q=q.replace(/^\s+|\s+$/g,"");var z=this.getTagName(q)[1],J=this.map[z]||this.map.defaultTag,D=J[0],F=J[1],S=J[2],K=document.createElement("div");K.innerHTML=F+q+S;while(D--)K=K.lastChild;return K.removeChild(K.firstChild)},hasParent:function O(q,z){if(!q||!z)return!1;var J=!1;while(q.parentNode&&!J)if(q=q.parentNode,q===z)J=!0;return J}}});export default w(); + +//# debugId=9835718B3D42F23F64756E2164756E21 diff --git a/public/assets/scripts/tooltip.js.map b/public/assets/scripts/tooltip.js.map new file mode 100644 index 0000000..5d238cb --- /dev/null +++ b/public/assets/scripts/tooltip.js.map @@ -0,0 +1,10 @@ +{ + "version": 3, + "sources": ["../tooltip.js"], + "sourcesContent": [ + "'use strict'\n\nmodule.exports = {\n /**\n * Setup global options\n * These can and will be overwriteen if a config object is passed into this.init()\n */\n options: {\n html: false,\n placement: 'top',\n container: 'body',\n scrollContainer: window,\n template: '
',\n removalDelay: 200,\n tooltipOffset: 10,\n windowPadding: {\n top: 10,\n right: 10,\n bottom: 10,\n left: 10,\n },\n },\n\n /**\n * initializeTooltips - Initialize function to bind events and set any global data\n * @example\n * tooltips.init()\n * @param {object} (config) - Congifuration object that is used to overwrite the defaults in this.options\n * @return {void}\n */\n init: function initializeTooltips(config) {\n var self = this\n\n // Check to see if we should use document.body or document.documentElement\n document.documentElement.scrollTop = 1\n this.documentElement =\n document.documentElement.scrollTop === 1\n ? document.documentElement\n : document.body\n\n // Test for browser support\n var div = document.createElement('div')\n\n div.innerHTML =\n '
a'\n // Make sure that link elements get serialized correctly by innerHTML\n // This requires a wrapper element in IE\n this.innerHTMLBug = !div.getElementsByTagName('link').length\n div = undefined\n\n // touch event testing\n if (\n 'ontouchstart' in window ||\n (window.DocumentTouch && document instanceof window.DocumentTouch)\n ) {\n document.body.style.cursor = 'pointer'\n }\n\n // Wrap map from jquery.\n this.map = {\n legend: [1, '
', '
'],\n tr: [2, '', '
'],\n col: [2, '', '
'],\n // for script/link/style tags to work in IE6-8, you have to wrap\n // in a div with a non-whitespace character in front, ha!\n defaultTag: this.innerHTMLBug\n ? [1, 'X
', '
']\n : [0, '', ''],\n }\n\n this.map.td = this.map.th = [\n 3,\n '',\n '
',\n ]\n this.map.option = this.map.optgroup = [\n 1,\n '',\n ]\n this.map.thead =\n this.map.tbody =\n this.map.colgroup =\n this.map.caption =\n this.map.tfoot =\n [1, '', '
']\n this.map.text =\n this.map.circle =\n this.map.ellipse =\n this.map.line =\n this.map.path =\n this.map.polygon =\n this.map.polyline =\n this.map.rect =\n [\n 1,\n '',\n '',\n ]\n\n // Copy over ininitialization options to options object\n if (config instanceof Object) {\n for (var option in config) {\n if (\n window.Object.hasOwnProperty.call(config, option) &&\n window.Object.hasOwnProperty.call(this.options, option)\n ) {\n // If it's a nested object, loop through that one too\n if (\n typeof config[option] === 'object' &&\n !Array.isArray(config[option])\n ) {\n for (var subkey in config[option]) {\n if (\n window.Object.hasOwnProperty.call(\n config[option],\n subkey\n ) &&\n window.Object.hasOwnProperty.call(\n this.options[option],\n subkey\n )\n ) {\n this.options[option][subkey] =\n config[option][subkey]\n }\n }\n } else {\n this.options[option] = config[option]\n }\n }\n }\n }\n\n // Logic for handling a click event\n function clickHandler(evt) {\n if (!evt) {\n evt = window.event\n }\n\n var trigger = evt.target || evt.srcElement\n\n // If there's a tooltip open and it shouldn't close, don't close it _or_ open a new one\n if (\n self.currentTooltip &&\n self.currentTooltip.getAttribute('data-autoclose') === 'false'\n ) {\n return\n // If there's already a tooltip open, close that one...\n } else if (self.currentTooltip) {\n // ...unless the user is clicking on the tooltip itself...\n if (trigger === self.currentTooltip) {\n return\n // ...or if the user if clicking on the original trigger for that tooltip\n } else if (trigger === self.currentTrigger) {\n self.close(self.currentTooltip)\n\n return\n } else {\n // loop through the child elements in the tooltip to see if one of them has been clicked\n if (self.hasParent(trigger, self.currentTooltip)) {\n return false\n }\n\n self.close(self.currentTooltip)\n }\n }\n\n // If the element the user is clicking on isn't supposed to trigger a tooltip, bail\n if (!self.hasClass(trigger, 'tooltip-click')) {\n return\n }\n\n // Open the tooltip!\n self.open(trigger)\n }\n\n // Logic for handling a mouseover event\n function mouseoverHandler(evt) {\n if (!evt) {\n evt = window.event\n }\n\n var trigger = evt.target || evt.srcElement\n\n // If the element the user is hovering over isn't supposed to trigger a tooltip, bail\n if (!self.hasClass(trigger, 'tooltip-hover')) {\n return\n }\n\n // If there's a tooltip open and it shouldn't close, don't close it _or_ open a new one\n if (\n self.currentTooltip &&\n self.currentTooltip.getAttribute('data-autoclose') === 'false'\n ) {\n return\n // If there's already a tooltip open, close that one...\n } else if (self.currentTooltip) {\n // ...unless the user is hovering over the tooltip itself...\n if (trigger === self.currentTooltip) {\n return\n } else {\n // loop through the child elements in the tooltip to see if one of them has been clicked\n if (self.hasParent(trigger, self.currentTooltip)) {\n return false\n }\n\n self.close(self.currentTooltip)\n }\n }\n\n // Logic for handling the mouseout event\n function mouseoutHandler(moEvt) {\n if (!moEvt) {\n moEvt = window.event\n }\n\n var moTrigger = evt.target || evt.srcElement\n\n if (self.hasClass(moTrigger)) {\n return\n }\n\n // If the tooltip shouldn't autoclose, bail\n if (\n self.currentTooltip &&\n self.currentTooltip.getAttribute('data-autoclose') ===\n 'false'\n ) {\n return\n }\n\n self.close(self.currentTooltip)\n\n // Remove self event to keep things clean\n self.removeEventListener(trigger, 'mouseout', mouseoutHandler)\n\n return\n }\n\n self.open(trigger)\n\n // Add an event to remove the tooltip when the user moves their cursor away\n self.addEventListener(trigger, 'mouseout', mouseoutHandler)\n\n return\n }\n\n // Logic for handling a focus event\n function focusHandler(evt) {\n if (!evt) {\n evt = window.event\n }\n\n var trigger = evt.target || evt.srcElement\n\n // If the element the user is focusing on isn't supposed to trigger a tooltip, bail\n if (!self.hasClass(trigger, 'tooltip-focus')) {\n return\n }\n\n // If there's a tooltip open and it shouldn't close, don't close it _or_ open a new one\n if (\n self.currentTooltip &&\n self.currentTooltip.getAttribute('data-autoclose') === 'false'\n ) {\n return\n // If there's already a tooltip open, close that one...\n } else if (self.currentTooltip) {\n self.close(self.currentTooltip)\n }\n\n // Logic for handling the blur event\n function blurHandler() {\n // If the tooltip shouldn't automatically close, bail\n if (\n self.currentTooltip &&\n self.currentTooltip.getAttribute('data-autoclose') ===\n 'false'\n ) {\n return\n }\n\n self.close(self.currentTooltip)\n\n // Remove self event to keep things clean\n self.removeEventListener(trigger, 'blur', blurHandler)\n\n return\n }\n\n self.open(trigger)\n\n // Add an event to remove the tooltip when the user blurs from the element\n self.addEventListener(trigger, 'blur', blurHandler)\n\n return\n }\n\n this.windowChangeHandler = function windowChangeHandler() {\n if (self.currentTooltip && self.currentTrigger) {\n self.positionTooltip(self.currentTooltip, self.currentTrigger)\n }\n\n return\n }\n\n // Add the global click handler\n this.addEventListener(document.body, 'click', clickHandler)\n\n // Add the global mouseover handler\n this.addEventListener(document.body, 'mouseover', mouseoverHandler)\n\n // Add the global focus handler\n this.addEventListener(document.body, 'focus', focusHandler, true)\n\n // If a tooltip is open and the user scrolls, isotip needs to keep up with the trigger\n this.addEventListener(window, 'scroll', this.windowChangeHandler)\n\n // If a tooltip is open and the user resizes the page, isotip needs to keep up with the trigger\n this.addEventListener(window, 'resize', this.windowChangeHandler)\n },\n\n /**\n * openTooltip - Main open function to prepare and insert the tooltip\n * @example\n * tooltip.open( document.body.querySelector( '#tooltip-trigger' ))\n * @param {string|element} trigger - The element that serves as the trigger for the tooltip\n * @param {object} (options) - An object that corresponds to the possible options using data-tooltip attributes\n * @return {element} - Returns the tooltip that was inserted into the DOM\n */\n open: function openTooltip(trigger, options) {\n // We need a DOM element, so make it one if it isn't already\n if (typeof trigger === 'string') {\n trigger = document.body.querySelector(trigger)\n }\n\n // If no options are passed in, setup a blank object to prevent errors\n if (!options) {\n options = {}\n }\n\n // Setup tooltip variables, starting with the config object if there is one\n var className =\n options.className || trigger.getAttribute('data-tooltip-classname')\n var content =\n options.content || trigger.getAttribute('data-tooltip-content')\n var title = options.title || trigger.getAttribute('data-tooltip-title')\n var html = options.html || trigger.getAttribute('data-tooltip-html')\n var placement =\n options.placement || trigger.getAttribute('data-tooltip-placement')\n var container =\n options.container || trigger.getAttribute('data-tooltip-container')\n var scrollContainer =\n options.container ||\n trigger.getAttribute('data-tooltip-scrollContainer')\n var preExistingTooltip = document.querySelector('.tooltip')\n var tooltip = this.createDOMElement(this.options.template)\n var tooltipTitle\n var tooltipContent\n\n // If there isn't any content to be displayed, bail\n if (!content) {\n return\n }\n\n // If there's a tooltip open with autoClose set to false, don't open a new one\n if (\n this.currentTooltip &&\n this.currentTooltip.getAttribute('data-autoclose') === 'false'\n ) {\n return\n }\n\n tooltip.appendChild(\n this.createDOMElement('
')\n )\n\n // If there should be an added class name, add it\n if (className) {\n this.addClass(tooltip, className)\n }\n\n // If there's a title to be displayed, create the title element\n if (title) {\n tooltipTitle = this.createDOMElement(\n '

' + title + '

'\n )\n\n tooltip.appendChild(tooltipTitle)\n }\n\n // If the supplied string should be interpreted as html, make an element for it...\n if ((this.options.html || html) && content) {\n if (this.isElement(content)) {\n tooltipContent = content\n } else if (this.getTagName(content)) {\n tooltipContent = this.createDOMElement(content)\n } else {\n tooltipContent = this.createDOMElement(\n '

' + content + '

'\n )\n }\n // ...or create a default p instead\n } else {\n tooltipContent = this.createDOMElement(\n '

' + content + '

'\n )\n }\n\n tooltip.appendChild(tooltipContent)\n\n // If the a container was supplied and it's not also the body element, store that element\n if (container && container !== this.options.container) {\n if (typeof container === 'string') {\n this.currentContainer = document.querySelector(container)\n }\n // If they initialized tooltips and set a different global container, store that element\n } else {\n this.currentContainer = document.querySelector(\n this.options.container\n )\n }\n\n // If a scrollContainer was supplied and it's also not the window element, store that element\n if (\n scrollContainer &&\n scrollContainer !== this.options.scrollContainer\n ) {\n if (typeof scrollContainer === 'string') {\n this.currentScrollContainer =\n document.querySelector(scrollContainer)\n }\n // If they initialized tooltips and incase they set a different global container, store that element\n } else {\n this.currentScrollContainer = this.options.scrollContainer\n }\n\n // If autoClose is set to false, add an attribute for the event handler to look for\n if (\n options.autoClose === false ||\n trigger.getAttribute('data-tooltip-autoclose') === 'false'\n ) {\n tooltip.setAttribute('data-autoclose', 'false')\n }\n\n if (preExistingTooltip) {\n this.currentTooltip = preExistingTooltip.parentNode.insertBefore(\n tooltip,\n preExistingTooltip\n )\n } else {\n this.currentTooltip = this.currentContainer.appendChild(tooltip)\n }\n\n this.currentTrigger = trigger\n\n // Position the tooltip on the page\n this.positionTooltip(\n this.currentTooltip,\n this.currentTrigger,\n placement\n )\n\n // If a tooltip is open and the user scrolls, isotip needs to keep up with the trigger\n if (this.currentScrollContainer !== window) {\n this.addEventListener(\n this.currentScrollContainer,\n 'scroll',\n this.windowChangeHandler\n )\n }\n\n return this.currentTooltip\n },\n\n /**\n * closeTooltip - Main close function to close a specific tooltip\n * @example\n * tooltip.close( document.body.querySelector( '.tooltip' ))\n * @param {string|element} tooltip - The tooltip that needs to be closed\n * @return {void}\n */\n close: function closeTooltip(tooltip) {\n // We need a DOM element, so make it one if it isn't already\n if (typeof tooltip === 'string') {\n tooltip = document.body.querySelector(tooltip)\n }\n\n if (this.currentScrollContainer !== window) {\n this.removeEventListener(\n this.currentScrollContainer,\n 'scroll',\n this.windowChangeHandler\n )\n }\n\n this.removeClass(tooltip, 'visible')\n\n this.currentTooltip = null\n this.currentTrigger = null\n this.currentScrollContainer = null\n\n // We should assume that there will be some sort of tooltip animation with CSS or JS\n // So we can only remove the element after a certain period of time\n window.setTimeout(function removeElementFromDOM() {\n if (\n tooltip &&\n tooltip instanceof window.Element &&\n tooltip.parentNode\n ) {\n tooltip.parentNode.removeChild(tooltip)\n } else {\n tooltip = document.body.querySelector('.tooltip')\n\n if (tooltip && tooltip.parentNode) {\n tooltip.parentNode.removeChild(tooltip)\n }\n }\n }, this.options.removalDelay)\n },\n\n /**\n * positionTooltip - Logic for positioning the tooltip on the page\n * @example\n * this.positionTooltip( this.currentTooltip, this.currentTrigger, 'top' )\n * @param {string|element} tooltip - The tooltip that needs to be positioned\n * @param {string|element} trigger - The element that triggered the tooltip\n * @param {string} (placement) - The position that the tooltip needs to be placed in in relation to the trigger\n * @return {element} - Returns the tooltip that was positioned\n * @api private\n */\n positionTooltip: function positionTooltip(tooltip, trigger, placement) {\n if (typeof tooltip === 'string') {\n tooltip = document.body.querySelector(tooltip)\n }\n\n if (typeof trigger === 'string') {\n trigger = document.body.querySelector(trigger)\n }\n\n // Since we support this being done on scroll, we need a way to get the placement if it isn't specified\n if (!placement) {\n placement =\n trigger.getAttribute('data-tooltip-placement') ||\n this.options.placement\n }\n\n var self = this\n var tooltipAccent = tooltip.querySelector('.tooltip-accent')\n var triggerPosition = trigger.getBoundingClientRect()\n var triggerWidth = Math.floor(triggerPosition.width)\n var triggerHeight = Math.floor(triggerPosition.height)\n var triggerX = triggerPosition.left\n var triggerY = triggerPosition.top\n var windowTop = this.options.windowPadding.top\n var windowRight =\n (window.innerWidth ||\n document.documentElement.clientWidth ||\n document.body.clientWidth) - this.options.windowPadding.right\n var windowBottom =\n (window.innerHeight ||\n document.documentElement.clientHeight ||\n document.body.clientHeight) - this.options.windowPadding.bottom\n var windowLeft = this.options.windowPadding.left\n var containerTop\n var containerRight\n var containerBottom\n var containerLeft\n var tooltipX\n var tooltipY\n var tooltipWidth\n var tooltipHeight\n var tooltipRight\n var tooltipBottom\n var tooltipAccentWidth\n var tooltipAccentHeight\n\n if (this.currentScrollContainer.getBoundingClientRect) {\n var scrollContainerPosition =\n this.currentScrollContainer.getBoundingClientRect()\n\n if (scrollContainerPosition.top >= 0) {\n containerTop =\n scrollContainerPosition.top + this.options.windowPadding.top\n }\n\n if (scrollContainerPosition.right <= windowRight) {\n containerRight =\n scrollContainerPosition.right -\n this.options.windowPadding.right\n }\n\n if (scrollContainerPosition.bottom <= windowBottom) {\n containerBottom =\n scrollContainerPosition.bottom -\n this.options.windowPadding.bottom\n }\n\n if (scrollContainerPosition.left >= windowLeft) {\n containerLeft =\n scrollContainerPosition.left +\n this.options.windowPadding.left\n }\n }\n\n /**\n * We sometimes need to re-position the tooltip (I.E. switch from top to bottom)\n * Which is why these are separate functions\n */\n function positionTop() {\n tooltipX = triggerX - tooltipWidth / 2 + triggerWidth / 2\n tooltipY = triggerY - tooltipHeight - self.options.tooltipOffset\n tooltipRight = tooltipX + tooltipWidth\n\n self.addClass(tooltip, 'tooltip-top')\n\n if (!tooltipAccentWidth) {\n tooltipAccentWidth = parseInt(tooltipAccent.offsetWidth, 10)\n }\n\n if (!tooltipAccentHeight) {\n tooltipAccentHeight = parseInt(tooltipAccent.offsetHeight, 10)\n }\n\n if (triggerY + triggerHeight <= containerTop) {\n if (self.hasClass(tooltip, 'visible')) {\n self.removeClass(tooltip, 'visible')\n }\n }\n\n // If the tooltip extends beyond the right edge of the window...\n if (tooltipRight > windowRight || tooltipRight > containerRight) {\n tooltip.style.top = 'auto'\n tooltip.style.bottom =\n windowBottom +\n self.options.windowPadding.bottom -\n triggerY +\n self.options.tooltipOffset +\n 'px'\n tooltip.style.right = self.options.windowPadding.right + 'px'\n tooltipAccent.style.left = 'auto'\n tooltipAccent.style.right =\n triggerWidth / 2 -\n tooltipAccentWidth / 2 +\n (windowRight - triggerX - triggerWidth) +\n 'px'\n // ...or if the tooltip extends beyond the top of the window...\n } else if (tooltipY < windowTop || tooltipY < containerTop) {\n self.removeClass(tooltip, 'tooltip-top')\n\n return positionBottom()\n // ...or if the tooltip extends beyond the left edge of the window...\n } else if (tooltipX < windowLeft || tooltipX < containerLeft) {\n tooltip.style.top = 'auto'\n tooltip.style.bottom =\n windowBottom +\n self.options.windowPadding.bottom -\n triggerY +\n self.options.tooltipOffset +\n 'px'\n tooltip.style.left = self.options.windowPadding.left + 'px'\n tooltipAccent.style.right = 'auto'\n tooltipAccent.style.left =\n triggerWidth / 2 -\n tooltipAccentWidth / 2 +\n (triggerX - windowLeft) +\n 'px'\n // ...or it fits inside the window\n } else {\n tooltip.style.top = 'auto'\n tooltip.style.bottom =\n windowBottom +\n self.options.windowPadding.bottom -\n triggerY +\n self.options.tooltipOffset +\n 'px'\n tooltip.style.left = tooltipX + 'px'\n tooltipAccent.style.top = ''\n tooltipAccent.style.bottom = ''\n tooltipAccent.style.right = ''\n tooltipAccent.style.left =\n tooltipWidth / 2 - tooltipAccentWidth / 2 + 'px'\n }\n }\n\n function positionRight() {\n tooltipX = triggerX + triggerWidth + self.options.tooltipOffset\n tooltipY = triggerY - tooltipHeight / 2 + triggerHeight / 2\n tooltipRight = tooltipX + tooltipWidth\n\n self.addClass(tooltip, 'tooltip-right')\n\n if (!tooltipAccentHeight) {\n tooltipAccentHeight = parseInt(tooltipAccent.offsetHeight, 10)\n }\n\n // If the tooltip extends beyond the right edge of the screen...\n if (tooltipRight > windowRight || tooltipRight > containerRight) {\n self.removeClass(tooltip, 'tooltip-right')\n\n return positionTop()\n // ...or if it fits to the right of the trigger element\n } else {\n tooltip.style.top = tooltipY + 'px'\n tooltip.style.left = tooltipX + 'px'\n tooltipAccent.style.right = ''\n tooltipAccent.style.bottom = ''\n tooltipAccent.style.left = ''\n tooltipAccent.style.top =\n tooltipHeight / 2 - tooltipAccentHeight / 2 + 'px'\n }\n }\n\n function positionBottom() {\n tooltipX = triggerX - tooltipWidth / 2 + triggerWidth / 2\n tooltipY = triggerY + triggerHeight + self.options.tooltipOffset\n tooltipRight = tooltipX + tooltipWidth\n tooltipBottom = tooltipY + tooltipHeight\n\n self.addClass(tooltip, 'tooltip-bottom')\n\n if (!tooltipAccentWidth) {\n tooltipAccentWidth = parseInt(tooltipAccent.offsetWidth, 10)\n }\n\n if (triggerY >= containerBottom) {\n if (self.hasClass(tooltip, 'visible')) {\n self.removeClass(tooltip, 'visible')\n }\n }\n\n // If the tooltip extends beyond the right edge of the window...\n if (tooltipRight > windowRight || tooltipRight > containerRight) {\n tooltip.style.top = tooltipY + 'px'\n tooltip.style.right = self.options.windowPadding.right + 'px'\n tooltipAccent.style.left = 'auto'\n tooltipAccent.style.right =\n triggerWidth / 2 -\n tooltipAccentWidth / 2 +\n (windowRight - triggerX - triggerWidth) +\n 'px'\n // ...or if the tooltip extends beyond the top of the window...\n } else if (\n tooltipBottom > windowBottom ||\n tooltipBottom > containerBottom\n ) {\n self.removeClass(tooltip, 'tooltip-bottom')\n\n return positionTop()\n // ...or if the tooltip extends beyond the left edge of the window...\n } else if (tooltipX < windowLeft || tooltipX < windowLeft) {\n tooltip.style.top = tooltipY + 'px'\n tooltip.style.left = self.options.windowPadding.left + 'px'\n tooltipAccent.style.right = 'auto'\n tooltipAccent.style.left =\n triggerWidth / 2 -\n tooltipAccentWidth / 2 +\n (triggerX - windowLeft) +\n 'px'\n // ...or it fits inside the window\n } else {\n tooltip.style.top = tooltipY + 'px'\n tooltip.style.bottom = 'auto'\n tooltip.style.left = tooltipX + 'px'\n tooltipAccent.style.top = ''\n tooltipAccent.style.bottom = ''\n tooltipAccent.style.right = ''\n tooltipAccent.style.left =\n tooltipWidth / 2 - tooltipAccentWidth / 2 + 'px'\n }\n }\n\n function positionLeft() {\n tooltipX = triggerX - tooltipWidth - self.options.tooltipOffset\n tooltipY = triggerY - tooltipHeight / 2 + triggerHeight / 2\n\n self.addClass(tooltip, 'tooltip-left')\n\n if (!tooltipAccentHeight) {\n tooltipAccentHeight = parseInt(tooltipAccent.offsetHeight, 10)\n }\n\n // If the tooltip extends beyond the right edge of the screen...\n if (tooltipX < windowLeft || tooltipX < containerLeft) {\n self.removeClass(tooltip, 'tooltip-left')\n\n return positionTop()\n // ...or if it fits to the left of the trigger element\n } else {\n tooltip.style.top = tooltipY + 'px'\n tooltip.style.left = tooltipX + 'px'\n tooltipAccent.style.right = ''\n tooltipAccent.style.bottom = ''\n tooltipAccent.style.left = ''\n tooltipAccent.style.top =\n tooltipHeight / 2 - tooltipAccentHeight / 2 + 'px'\n }\n }\n\n tooltip.style.position = 'fixed'\n\n tooltipWidth = parseInt(tooltip.offsetWidth, 10)\n tooltipHeight = parseInt(tooltip.offsetHeight, 10)\n tooltipAccentWidth = parseInt(tooltipAccent.offsetWidth, 10)\n tooltipAccentHeight = parseInt(tooltipAccent.offsetHeight, 10)\n\n // clear any classes set before hand\n self.removeClass(tooltip, 'tooltip-top')\n self.removeClass(tooltip, 'tooltip-right')\n self.removeClass(tooltip, 'tooltip-bottom')\n self.removeClass(tooltip, 'tooltip-left')\n\n // position the tooltip\n if (placement === 'top') {\n positionTop()\n } else if (placement === 'right') {\n positionRight()\n } else if (placement === 'bottom') {\n positionBottom()\n } else if (placement === 'left') {\n positionLeft()\n }\n\n // try and give the tooltip enough time to position itself\n if (\n !self.hasClass(tooltip, 'visible') &&\n (containerTop === undefined ||\n (triggerY + triggerHeight > containerTop &&\n triggerY < containerBottom))\n ) {\n window.setTimeout(function () {\n self.addClass(tooltip, 'visible')\n }, 50)\n } else if (\n triggerY + triggerHeight <= containerTop ||\n triggerY >= containerBottom\n ) {\n self.removeClass(tooltip, 'visible')\n }\n\n return tooltip\n },\n\n /**\n * addEventListener - Small function to add an event listener. Should be compatible with IE8+\n * @example\n * this.addEventListener( document.body, 'click', this.open( this.currentTooltip ))\n * @param {element} el - The element node that needs to have the event listener added\n * @param {string} eventName - The event name (sans the \"on\")\n * @param {function} handler - The function to be run when the event is triggered\n * @return {element} - The element that had an event bound\n * @api private\n */\n addEventListener: function addEventListener(\n el,\n eventName,\n handler,\n useCapture\n ) {\n if (!useCapture) {\n useCapture = false\n }\n\n if (el.addEventListener) {\n el.addEventListener(eventName, handler, useCapture)\n\n return el\n } else {\n if (eventName === 'focus') {\n eventName = 'focusin'\n }\n\n el.attachEvent('on' + eventName, function () {\n handler.call(el)\n })\n\n return el\n }\n },\n\n /**\n * removeEventListener - Small function to remove and event listener. Should be compatible with IE8+\n * @example\n * this.removeEventListener( document.body, 'click', this.open( this.currentTooltip ))\n * @param {element} el - The element node that needs to have the event listener removed\n * @param {string} eventName - The event name (sans the \"on\")\n * @param {function} handler - The function that was to be run when the event is triggered\n * @return {element} - The element that had an event removed\n * @api private\n */\n removeEventListener: function removeEventListener(\n el,\n eventName,\n handler,\n useCapture\n ) {\n if (!useCapture) {\n useCapture = false\n }\n\n if (!el) {\n return\n }\n\n if (el.removeEventListener) {\n el.removeEventListener(eventName, handler, useCapture)\n } else {\n if (eventName === 'focus') {\n eventName = 'focusin'\n }\n\n el.detachEvent('on' + eventName, function () {\n handler.call(el)\n })\n }\n\n return el\n },\n\n /**\n * hasClass - Small function to see if an element has a specific class. Should be compatible with IE8+\n * @example\n * this.hasClass( this.currentTooltip, 'visible' )\n * @param {element} el - The element to check the class existence on\n * @param {string} className - The class to check for\n * @return {boolean} - True or false depending on if the element has the class\n * @api private\n */\n hasClass: function hasClass(el, className) {\n if (el.classList) {\n return el.classList.contains(className)\n } else {\n return new RegExp('(^| )' + className + '( |$)', 'gi').test(\n el.className\n )\n }\n },\n\n /**\n * addClass - Small function to add a class to an element. Should be compatible with IE8+\n * @example\n * this.addClass( this.currentTooltip, 'visible' )\n * @param {element} el - The element to add the class to\n * @param {string} className - The class name to add to the element\n * @return {element} - The element that had the class added to it\n * @api private\n */\n addClass: function addClass(el, className) {\n if (el.classList) {\n el.classList.add(className)\n } else {\n el.className += ' ' + className\n }\n\n return el\n },\n\n /**\n * removeClass - Small function to remove a class from an element. Should be compatible with IE8+\n * @example\n * this.removeClass( this.currentTooltip, 'visible' )\n * @param {element} el - The element to remove the class from\n * @param {string} className - The class name to remove from the element\n * @return {element} - The element that had the class removed from it\n * @api private\n */\n removeClass: function removeClass(el, className) {\n if (el) {\n if (el.classList) {\n el.classList.remove(className)\n } else {\n el.className = el.className.replace(\n new RegExp(\n '(^|\\\\b)' + className.split(' ').join('|') + '(\\\\b|$)',\n 'gi'\n ),\n ' '\n )\n }\n }\n\n return el\n },\n\n /**\n * setInnerText - Small function to set the inner text of an element. Should be compatible with IE8+\n * @example\n * this.setInnerText( this.currentTooltip, 'Hello world' )\n * @param {element} el - The element to have the text inserted into\n * @param {string} text - The text to insert into the element\n * @return {element} - The element with the new inner text\n * @api private\n */\n setInnerText: function setInnerText(el, text) {\n if (el.textContent !== undefined) {\n el.textcontent = text\n } else {\n el.innerText = text\n }\n\n return el\n },\n\n /**\n * getTagName - Small function to get the tag name of an html string.\n * @example\n * this.getTagName( '
' )\n * @param {string} html - The string of html to check for the tag\n * @return {object} - The object containing the tag of the root element in the html string as well as some other basic info\n * @api private\n */\n getTagName: function getTagName(html) {\n return /<([\\w:]+)/.exec(html)\n },\n\n /**\n * isElement - Small function to determine if object is a DOM element.\n * @example\n * this.isElement( '
' ); # false\n * @example\n * this.isElement( this.createElement('div') ); # true\n * @param {string} obj - The object to check\n * @return {boolean} - Whether or not the object is a DOM element.\n * @api private\n */\n isElement: function getTagName(obj) {\n return !!(obj && obj.nodeType === 1)\n },\n\n /**\n * createDOMElement - Small function to transform an html string into an element\n * @example\n * this.createDOMElement( '
Hello world
' )\n * @param {string} html - The string of html to turn into html\n * @return {element} - The element created from the string of html\n * @api private\n */\n createDOMElement: function createDOMElement(html) {\n // Remove whitespace\n html = html.replace(/^\\s+|\\s+$/g, '')\n\n // Get the tag name and match it to this.map incase it needs more nodes\n var templateTag = this.getTagName(html)[1]\n var wrap = this.map[templateTag] || this.map.defaultTag\n var depth = wrap[0]\n var prefix = wrap[1]\n var suffix = wrap[2]\n var el = document.createElement('div')\n\n el.innerHTML = prefix + html + suffix\n\n while (depth--) {\n el = el.lastChild\n }\n\n // Extract the fresh element\n return el.removeChild(el.firstChild)\n },\n\n /**\n * hasParent - Small element to find the closest parent to an element\n * @example\n * element.closest('.tooltip')\n * @param {Element} el - The element to start with\n * @param {Element} parent - The parent element to match against/search for\n * @return {bool} - Whether or not the element has the parent\n */\n hasParent: function hasParent(el, parent) {\n if (!el || !parent) {\n return false\n }\n\n var match = false\n\n while (el.parentNode && !match) {\n el = el.parentNode\n\n if (el === parent) {\n match = true\n }\n }\n\n return match\n },\n}\n" + ], + "mappings": "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", + "debugId": "9835718B3D42F23F64756E2164756E21", + "names": [] +} \ No newline at end of file diff --git a/public/index.php b/public/index.php index c446f88..380e258 100644 --- a/public/index.php +++ b/public/index.php @@ -13,7 +13,7 @@ $r = []; */ router_get($r, '/', function () { if (user()) must_have_character(); - echo render('layouts/basic', ['view' => 'pages/home']); + echo render('layouts/basic', ['view' => 'pages/home', 'activeTab' => nav_tabs['home']]); }); /* @@ -28,6 +28,7 @@ router_post($r, '/auth/logout', 'auth_controller_logout_post'); /* Characters */ +router_get($r, '/characters', 'char_controller_select_get'); router_post($r, '/character/create', 'char_controller_create_post'); router_post($r, '/character/select', 'auth_controller_change_character_post'); router_get($r, '/character/create-first', 'char_controller_create_first_get'); diff --git a/src/auth.php b/src/auth.php index b39ee14..11b8f8c 100644 --- a/src/auth.php +++ b/src/auth.php @@ -61,13 +61,9 @@ function guest_only(): void */ function must_have_character(): void { - // If there is a character selected, and the data exists, return early. - if ($_SESSION['user']['char_id'] !== 0 && !empty($_SESSION['char'])) return; - // If there is a character selected, make sure the session is up to date. - if ($_SESSION['user']['char_id'] !== 0 && empty($_SESSION['char'])) { - $char = db_query(db_live(), 'SELECT * FROM characters WHERE id = :c', [':c' => $_SESSION['user']['char_id']]); - $char = $char = $char->fetchArray(SQLITE3_ASSOC); + if ($_SESSION['user']['char_id'] !== 0) { + $char = db_query(db_live(), 'SELECT * FROM characters WHERE id = :c', [':c' => $_SESSION['user']['char_id']])->fetchArray(SQLITE3_ASSOC); $_SESSION['char'] = $char; return; } @@ -77,10 +73,7 @@ function must_have_character(): void // if no character selected, select the first one if ($_SESSION['user']['char_id'] === 0) { - $char = db_query(db_live(), 'SELECT * FROM characters WHERE user_id = :u ORDER BY id ASC LIMIT 1', [':u' => user('id')]); - $char = $char->fetchArray(SQLITE3_ASSOC); - $_SESSION['user']['char_id'] = $char['id']; - db_query(db_auth(), 'UPDATE users SET char_id = :c WHERE id = :u', [':c' => $char['id'], ':u' => user('id')]); - $_SESSION['char'] = $char; + $char = db_query(db_live(), 'SELECT * FROM characters WHERE user_id = :u ORDER BY id ASC LIMIT 1', [':u' => user('id')])->fetchArray(SQLITE3_ASSOC); + change_user_character($char['id']); } } diff --git a/src/components.php b/src/components.php index 5f7d052..5e6445f 100644 --- a/src/components.php +++ b/src/components.php @@ -1,5 +1,10 @@ 0, + 'chars' => 1, +]; + /** * Render the logout button's form. */ @@ -17,3 +22,11 @@ function c_char_bar(): string if (!char()) return ''; return render('components/char_bar', ['char' => char()]); } + +/** + * Render the left sidebar navigation menu. Provide the active tab to highlight it. + */ +function c_left_nav(int $activeTab): string +{ + return render('components/left_nav', ['activeTab' => $activeTab]); +} diff --git a/src/controllers/char.php b/src/controllers/char.php index 2dc951d..e961e87 100644 --- a/src/controllers/char.php +++ b/src/controllers/char.php @@ -1,5 +1,18 @@ 'pages/chars/select', 'chars' => $chars, 'activeTab' => nav_tabs['chars']]); +} + /** * Form to create your first character. */ @@ -10,7 +23,7 @@ function char_controller_create_first_get(): void // If the user already has a character, redirect them to the main page. if (char_count(user('id')) > 0) redirect('/'); - echo render('layouts/basic', ['view' => 'pages/chars/first']); + echo render('layouts/basic', ['view' => 'pages/chars/first', 'activeTab' => nav_tabs['chars']]); } /** diff --git a/src/helpers.php b/src/helpers.php index b42b10f..e1b4a85 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -147,3 +147,13 @@ function change_user_character(int $char_id): void db_query(db_auth(), "UPDATE users SET char_id = :c WHERE id = :u", [':c' => $char_id, ':u' => user('id')]); $_SESSION['char'] = char_find($char_id); } + +/** + * Get a percent between two ints, rounded to the nearest whole number or return 0. + */ +function percent(int $num, int $denom, int $precision = 4): int +{ + if ($denom === 0) return 0; + $p = ($num / $denom) * 100; + return $p < 0 ? 0 : round($p, $precision); +} diff --git a/templates/components/char_bar.php b/templates/components/char_bar.php index c1a6de9..0b29357 100644 --- a/templates/components/char_bar.php +++ b/templates/components/char_bar.php @@ -1,6 +1,30 @@
User - + + 0): ?> + + +
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
diff --git a/templates/components/left_nav.php b/templates/components/left_nav.php new file mode 100644 index 0000000..95b9073 --- /dev/null +++ b/templates/components/left_nav.php @@ -0,0 +1,4 @@ + diff --git a/templates/components/right_nav.php b/templates/components/right_nav.php new file mode 100644 index 0000000..e69de29 diff --git a/templates/layouts/basic.php b/templates/layouts/basic.php index 3244259..00b9a18 100644 --- a/templates/layouts/basic.php +++ b/templates/layouts/basic.php @@ -19,14 +19,28 @@ Login Register - +
- + + +
+ +
+ +
@@ -34,5 +48,10 @@

q

v

+ + diff --git a/templates/pages/chars/select.php b/templates/pages/chars/select.php new file mode 100644 index 0000000..4d38eac --- /dev/null +++ b/templates/pages/chars/select.php @@ -0,0 +1,16 @@ +

Characters

+ 0): ?> +
+ + $char): ?> + +
+ + +
+ + +

You have no characters.

+ diff --git a/templates/pages/home.php b/templates/pages/home.php index fabfcd9..8541bde 100644 --- a/templates/pages/home.php +++ b/templates/pages/home.php @@ -1,21 +1,8 @@ -

Welcome!

+

Welcome!

+ Login + Register - 0): ?> -

Characters

-
- - $char): ?> - -
- - -
- - -
- - - -
+

Home

+