diff --git a/PRIVACY.md b/PRIVACY.md new file mode 100644 index 00000000..fe73c0b9 --- /dev/null +++ b/PRIVACY.md @@ -0,0 +1,35 @@ +# Privacy Policy - Schoology Plus + +## Introduction + +It is Schoology Plus's policy to respect your privacy regarding any information we may collect while operating our browser extension. This Privacy Policy applies to the Schoology Plus browser extension for Chrome, Firefox, and Microsoft Edge (hereinafter, "us", "we", "the Extension", or "Schoology Plus"). We respect your privacy and are committed to protecting personally identifiable information accessible to us through the Extension. We have adopted this privacy policy ("Privacy Policy") to explain what information may be collected by our Extension, how we use this information, and under what circumstances we may disclose the information to third parties. This Privacy Policy applies only to information we collect through the Extension and does not apply to our collection of information from other sources. + +## Non Personally Identifying Information + +Like most website operators, Schoology Plus collects non-personally-identifying information of the sort that web browsers and servers typically make available, such as the browser type, language preference, referring site, and the date and time of each user request. Schoology Plus's purpose in collecting non-personally identifying information is to better understand how Schoology Plus's users use the Extension. From time to time, Schoology Plus may release non-personally-identifying information in the aggregate, e.g., by publishing a report on trends in the usage of the Extension. + +Schoology Plus uses Google Analytics to collect information for these purposes. If you would like to opt-out of tracking, you can install the [Google Analytics Opt-out Add-on](https://chrome.google.com/webstore/detail/google-analytics-opt-out/fllaojicojecljbmefodhfapmkghcbnh?hl=en) or another tracking-prevention extension. + +## Personally Identifying Information + +By using Schoology Plus, you grant us access to view and modify the contents of the Schoology website in your web browser. However, we DO NOT harvest or collect any information that can personally identify you longer than absolutely necessary to perform calculations or page enhancements. This information (including, but not limited to: school name, grades, and assignment details) is only modified locally and never leaves your computer. + +## Aggregated Statistics + +Schoology Plus may collect statistics about the behavior of users. Schoology Plus may display this information publicly or provide it to others. However, Schoology Plus does not disclose your personally-identifying information. + +## Cookies + +To enrich and perfect your online experience, Schoology Plus uses "Cookies", similar technologies and services provided by others to display personalized content and store your preferences on your computer. A cookie is a string of information that a website stores on a user's computer, and that the user's browser provides to the website each time the user returns. Schoology Plus uses cookies to help us identify and track users' extension preferences. Schoology Plus users who do not wish to have cookies placed on their computers should set their browsers to refuse cookies before using the Extension, with the drawback that certain features of Schoology Plus may not function properly without the aid of cookies. By continuing to use the Extension without changing your cookie settings, you hereby acknowledge and agree to Schoology Plus's use of cookies. + +## Privacy Policy Changes + +Although most changes are likely to be minor, Schoology Plus may change its Privacy Policy from time to time, and in Schoology Plus's sole discretion. Schoology Plus encourages users to frequently check this page for any changes to its Privacy Policy. Your continued use of the Extension after any change in this Privacy Policy will constitute your acceptance of such change. Any major changes will be prominently announced. + +## Disclaimer + +Schoology Plus is not affiliated with Schoology Inc. or the Los Angeles Unified School District. Schoology, the SCHOOLOGY® wordmark, and the S logo are registered and unregistered trademarks of Schoology, Inc. in the United States. All product names, logos, and brands are property of their respective owners. + +## Contact + +The developers of Schoology Plus can be contacted by email at [schoologyplus@aopell.me](mailto:schoologyplus@aopell.me) or through Discord at [this link](https://aopell.me/SchoologyPlus/discord). \ No newline at end of file diff --git a/README.md b/README.md index 229d2cac..7980af2b 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,12 @@ [![Chrome Download](https://img.shields.io/chrome-web-store/v/fbfppoaockpecjpbdmldojdehdpepfef.svg?label=chrome%20download)](https://chrome.google.com/webstore/detail/schoology-plus/fbfppoaockpecjpbdmldojdehdpepfef) [![Chrome Web Store Users](https://img.shields.io/chrome-web-store/users/fbfppoaockpecjpbdmldojdehdpepfef.svg)](https://chrome.google.com/webstore/detail/schoology-plus/fbfppoaockpecjpbdmldojdehdpepfef) [![Chrome Web Store Rating](https://img.shields.io/chrome-web-store/rating/fbfppoaockpecjpbdmldojdehdpepfef.svg)](https://chrome.google.com/webstore/detail/schoology-plus/fbfppoaockpecjpbdmldojdehdpepfef) -[![Firefox Download](https://img.shields.io/badge/dynamic/json.svg?label=firefox%20download&url=https%3A%2F%2Faopell.me%2FSchoologyPlus%2Fupdate.json&query=%24.addons%5B%27schoology.plus%40aopell.me%27%5D.updates%5B0%5D.version&colorB=orange)](http://aopell.me/SchoologyPlus/firefox-download.html) +[![Mozilla Add-on](https://img.shields.io/amo/v/schoology-plus?color=orange&label=firefox%20download)](https://addons.mozilla.org/en-US/firefox/addon/schoology-plus/) [![Discord](https://img.shields.io/discord/526898202495025172.svg?color=7289da&label=discord)](https://aopell.github.io/SchoologyPlus/discord.html) [![Changelog](https://img.shields.io/github/release/aopell/SchoologyPlus.svg?label=changelog&colorB=lightgrey)](https://aopell.me/SchoologyPlus/changelog) [![Download for Chrome](https://developer.chrome.com/webstore/images/ChromeWebStore_Badge_v2_206x58.png)](https://chrome.google.com/webstore/detail/schoology-plus/fbfppoaockpecjpbdmldojdehdpepfef) -[Download for Firefox](http://aopell.me/SchoologyPlus/firefox-download.html) +[Download for Firefox](https://addons.mozilla.org/en-US/firefox/addon/schoology-plus/) #### Contents - [Features](#features) diff --git a/css/all.css b/css/all.css index cea82376..aa3e68bd 100644 --- a/css/all.css +++ b/css/all.css @@ -254,7 +254,7 @@ video.easter-egg { } .restore-defaults { - color: red; + color: red !important; margin-top: 10px; font-weight: normal; } @@ -273,7 +273,7 @@ input[type=text].setting-item { } .close-button { - color: red; + color: red !important; font-weight: normal; font-size: 20px; } @@ -507,7 +507,8 @@ body .grading-groups-list .grading-group, body #grading-group- span.ajax-post-comment, .period-row .title, -.category-row .title { +.category-row .title, +._2mWUT a { color: var(--hover-color) !important; } @@ -518,6 +519,7 @@ body a.link-btn { footer li a, header li a, .splus-modal-footer-text a, -body a.link-btn.active { +body a.link-btn.active, +a._3_bfp { color: white !important; } \ No newline at end of file diff --git a/js/all.js b/js/all.js index 7f447e61..26b7676e 100644 --- a/js/all.js +++ b/js/all.js @@ -18,7 +18,7 @@ // Check Schoology domain { - const BLACKLISTED_DOMAINS = ["asset-cdn.schoology.com", "www.schoology.com", "schoology.com"]; + const BLACKLISTED_DOMAINS = ["asset-cdn.schoology.com", "ui.schoology.com", "www.schoology.com", "schoology.com"]; let dd = Setting.getValue("defaultDomain"); if (dd !== window.location.host && !BLACKLISTED_DOMAINS.includes(window.location.host)) { @@ -67,7 +67,7 @@ bottom.appendChild(createElement("span", ["footer-divider"], { textContent: "|" document.documentElement.style.setProperty("--default-visibility", "visible"); -let verboseModalFooterText = `© Aaron Opell, Glen Husman 2017-2020 | Schoology Plus v${chrome.runtime.getManifest().version_name || chrome.runtime.getManifest().version}${getBrowser() != "Chrome" || chrome.runtime.getManifest().update_url ? '' : ' dev'} | Discord Support Server | GitHub | Contributors | Changelog`; +let verboseModalFooterText = `© Aaron Opell, Glen Husman 2017-2020 | Schoology Plus v${chrome.runtime.getManifest().version_name || chrome.runtime.getManifest().version}${getBrowser() != "Chrome" || chrome.runtime.getManifest().update_url ? '' : ' dev'} | Discord Server | GitHub | Contributors | Privacy Policy | Changelog`; let modalFooterText = "Schoology Plus"; let frame = document.createElement("iframe"); diff --git a/js/background.js b/js/background.js index 9ae2707b..7a918a59 100644 --- a/js/background.js +++ b/js/background.js @@ -241,7 +241,7 @@ function loadAssignmentNotifications(storageContent) { timeModified = true; } let div = document.querySelector("div") || document.body.appendChild(document.createElement("div")); - div.innerHTML = response.output; + div.innerHTML = DOMPurify.sanitize(response.output); let notifications = div.querySelectorAll(".edge-sentence"); let months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; let totalAssignments = 0; diff --git a/js/version-specific.js b/js/version-specific.js index 65fb8aa7..83c09983 100644 --- a/js/version-specific.js +++ b/js/version-specific.js @@ -183,16 +183,6 @@ let migrationsTo = { new Date(2019, 1 /* February - don't you just love JavaScript */, 14) ) ]); - }, - "5.7": function(currentVersion, previousVersion) { - saveBroadcasts([ - createBroadcast( - 570, - 'Leave a review for Schoology Plus!', - '
Do you love Schoology Plus?
If so, we\'d really appreciate if you\'d leave us a review on the Chrome Web Store!

Click here to visit the page for Schoology Plus on the Chrome Web Store
', - new Date(2019, 11 /* December */, 11) - ) - ]); } }; diff --git a/lib/js/jquery.tipsy.min.js b/lib/js/jquery.tipsy.min.js index ca0c1903..a35dcae5 100644 --- a/lib/js/jquery.tipsy.min.js +++ b/lib/js/jquery.tipsy.min.js @@ -4,5 +4,4 @@ // released under the MIT license // NOTE: no need to include CSS, Schoology does it for us -!function(t,e,i){function s(t,e){return"function"==typeof t?t.call(e):t}function o(t){for(;t=t.parentNode;)if(t==document)return!0;return!1}function n(t){return"object"==typeof HTMLElement?t instanceof HTMLElement:t&&"object"==typeof t&&1===t.nodeType&&"string"==typeof t.nodeName}function l(){return"tipsyuid"+h++}function a(e,i){this.$element=t(e),this.options=i,this.enabled=!0,this.fixTitle()}var h=0;a.prototype={show:function(){if(o(this.$element[0])&&(!n(this.$element)||this.$element.is(":visible"))){var e;if(this.enabled&&(e=this.getTitle())){var i=this.tip();i.find(".tipsy-inner"+this.options.theme)[this.options.html?"html":"text"](e),i[0].className="tipsy"+this.options.theme,this.options.className&&i.addClass(s(this.options.className,this.$element[0])),i.remove().css({top:0,left:0,visibility:"hidden",display:"block"}).prependTo(document.body);var a=t.extend({},this.$element.offset());a=this.$element.parents("svg").size()>0?t.extend(a,this.$element[0].getBBox()):t.extend(a,{width:this.$element[0].offsetWidth||0,height:this.$element[0].offsetHeight||0});var h,f=i[0].offsetWidth,r=i[0].offsetHeight,p=s(this.options.gravity,this.$element[0]);switch(p.charAt(0)){case"n":h={top:a.top+a.height+this.options.offset,left:a.left+a.width/2-f/2};break;case"s":h={top:a.top-r-this.options.offset,left:a.left+a.width/2-f/2};break;case"e":h={top:a.top+a.height/2-r/2,left:a.left-f-this.options.offset};break;case"w":h={top:a.top+a.height/2-r/2,left:a.left+a.width+this.options.offset}}if(2==p.length&&("w"==p.charAt(1)?h.left=a.left+a.width/2-15:h.left=a.left+a.width/2-f+15),i.css(h).addClass("tipsy-"+p+this.options.theme),i.find(".tipsy-arrow"+this.options.theme)[0].className="tipsy-arrow"+this.options.theme+" tipsy-arrow-"+p.charAt(0)+this.options.theme,this.options.fade?(this.options.shadow&&t(".tipsy-inner").css({"box-shadow":"0px 0px "+this.options.shadowBlur+"px "+this.options.shadowSpread+"px rgba(0, 0, 0, "+this.options.shadowOpacity+")","-webkit-box-shadow":"0px 0px "+this.options.shadowBlur+"px "+this.options.shadowSpread+"px rgba(0, 0, 0, "+this.options.shadowOpacity+")"}),i.stop().css({opacity:0,display:"block",visibility:"visible"}).animate({opacity:this.options.opacity},this.options.fadeInTime)):i.css({visibility:"visible",opacity:this.options.opacity}),this.options.aria){var d=l();i.attr("id",d),this.$element.attr("aria-describedby",d)}}}},hide:function(){this.options.fade?this.tip().stop().fadeOut(this.options.fadeOutTime,function(){t(this).remove()}):this.tip().remove(),this.options.aria&&this.$element.removeAttr("aria-describedby")},fixTitle:function(){var t=this.$element,e=s(this.options.id,this.$element[0]);(t.prop("title")||"string"!=typeof t.prop("original-title"))&&(t.prop("original-title",t.prop("title")||"").removeAttr("title"),t.attr("aria-describedby",e),t.attr("tabindex")===i&&t.attr("tabindex",0))},getTitle:function(){var t,e=this.$element,i=this.options;return this.fixTitle(),"string"==typeof i.title?t=e.prop("title"==i.title?"original-title":i.title):"function"==typeof i.title&&(t=i.title.call(e[0])),t=(""+t).replace(/(^\s*|\s*$)/,""),t||i.fallback},tip:function(){var e=s(this.options.id,this.$element[0]);return this.$tip||(this.$tip=t('').html('
').attr("role","tooltip"),this.$tip.data("tipsy-pointee",this.$element[0])),this.$tip},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled}},t.fn.tipsy=function(e){function i(i){var s=t.data(i,"tipsy");return s||(s=new a(i,t.fn.tipsy.elementOptions(i,e)),t.data(i,"tipsy",s)),s}function s(){if(t.fn.tipsy.enabled===!0){var s=i(this);s.hoverState="in",0===e.delayIn?s.show():(s.fixTitle(),setTimeout(function(){"in"==s.hoverState&&o(s.$element)&&s.show()},e.delayIn))}}function n(){var t=i(this);t.hoverState="out",0===e.delayOut?t.hide():setTimeout(function(){"out"!=t.hoverState&&t.$element&&t.$element.is(":visible")||t.hide()},e.delayOut)}if(t.fn.tipsy.enable(),e===!0)return this.data("tipsy");if("string"==typeof e){var l=this.data("tipsy");return l&&l[e](),this}if(e=t.extend({},t.fn.tipsy.defaults,e),e.theme=e.theme&&""!==e.theme?"-"+e.theme:"",e.live||this.each(function(){i(this)}),"manual"!=e.trigger)if(e.live&&e.live!==!0)"focus"!=e.trigger&&(t(this).on("mouseenter",e.live,s),t(this).on("mouseleave",e.live,n)),"blur"!=e.trigger&&(t(this).on("focus",e.live,s),t(this).on("blur",e.live,n));else{if(e.live&&!t.live)throw"Since jQuery 1.9, pass selector as live argument. eg. $(document).tipsy({live: 'a.live'});";var h=e.live?"live":"bind";"focus"!=e.trigger&&this[h]("mouseenter",s)[h]("mouseleave",n),"blur"!=e.trigger&&this[h]("focus",s)[h]("blur",n)}return this},t.fn.tipsy.defaults={aria:!1,className:null,id:"tipsy",delayIn:0,delayOut:0,fade:!1,fadeInTime:400,fadeOutTime:400,shadow:!1,shadowBlur:8,shadowOpacity:1,shadowSpread:0,fallback:"",gravity:"n",html:!1,live:!1,offset:0,opacity:.8,title:"title",trigger:"interactive",theme:""},t.fn.tipsy.revalidate=function(){t(".tipsy").each(function(){var e=t.data(this,"tipsy-pointee");e&&o(e)||t(this).remove()})},t.fn.tipsy.enable=function(){t.fn.tipsy.enabled=!0},t.fn.tipsy.disable=function(){t.fn.tipsy.enabled=!1},t.fn.tipsy.elementOptions=function(e,i){return t.metadata?t.extend({},i,t(e).metadata()):i},t.fn.tipsy.autoNS=function(){return t(this).offset().top>t(document).scrollTop()+t(e).height()/2?"s":"n"},t.fn.tipsy.autoWE=function(){return t(this).offset().left>t(document).scrollLeft()+t(e).width()/2?"e":"w"},t.fn.tipsy.autoNWNE=function(){return t(this).offset().left>t(document).scrollLeft()+t(e).width()/2?"ne":"nw"},t.fn.tipsy.autoSWSE=function(){return t(this).offset().left>t(document).scrollLeft()+t(e).width()/2?"se":"sw"},t.fn.tipsy.autoBounds=function(i,s,o){return function(){var n={ns:o[0],ew:o.length>1?o[1]:!1},l=t(document).scrollTop()+i,a=t(document).scrollLeft()+s,h=t(this);return h.offset().top1?(o.ns=s[0],o.ew=s[1]):"e"==s[0]||"w"==s[0]?o.ew=s[0]:o.ns=s[0],a.offset().top0?t.extend(a,this.$element[0].getBBox()):t.extend(a,{width:this.$element[0].offsetWidth||0,height:this.$element[0].offsetHeight||0});var h,f=i[0].offsetWidth,r=i[0].offsetHeight,p=s(this.options.gravity,this.$element[0]);switch(p.charAt(0)){case"n":h={top:a.top+a.height+this.options.offset,left:a.left+a.width/2-f/2};break;case"s":h={top:a.top-r-this.options.offset,left:a.left+a.width/2-f/2};break;case"e":h={top:a.top+a.height/2-r/2,left:a.left-f-this.options.offset};break;case"w":h={top:a.top+a.height/2-r/2,left:a.left+a.width+this.options.offset}}if(2==p.length&&("w"==p.charAt(1)?h.left=a.left+a.width/2-15:h.left=a.left+a.width/2-f+15),i.css(h).addClass("tipsy-"+p+this.options.theme),i.find(".tipsy-arrow"+this.options.theme)[0].className="tipsy-arrow"+this.options.theme+" tipsy-arrow-"+p.charAt(0)+this.options.theme,this.options.fade?(this.options.shadow&&t(".tipsy-inner").css({"box-shadow":"0px 0px "+this.options.shadowBlur+"px "+this.options.shadowSpread+"px rgba(0, 0, 0, "+this.options.shadowOpacity+")","-webkit-box-shadow":"0px 0px "+this.options.shadowBlur+"px "+this.options.shadowSpread+"px rgba(0, 0, 0, "+this.options.shadowOpacity+")"}),i.stop().css({opacity:0,display:"block",visibility:"visible"}).animate({opacity:this.options.opacity},this.options.fadeInTime)):i.css({visibility:"visible",opacity:this.options.opacity}),this.options.aria){var d=l();i.attr("id",d),this.$element.attr("aria-describedby",d)}}}},hide:function(){this.options.fade?this.tip().stop().fadeOut(this.options.fadeOutTime,function(){t(this).remove()}):this.tip().remove(),this.options.aria&&this.$element.removeAttr("aria-describedby")},fixTitle:function(){var t=this.$element,e=s(this.options.id,this.$element[0]);(t.prop("title")||"string"!=typeof t.prop("original-title"))&&(t.prop("original-title",t.prop("title")||"").removeAttr("title"),t.attr("aria-describedby",e),t.attr("tabindex")===i&&t.attr("tabindex",0))},getTitle:function(){var t,e=this.$element,i=this.options;return this.fixTitle(),"string"==typeof i.title?t=e.prop("title"==i.title?"original-title":i.title):"function"==typeof i.title&&(t=i.title.call(e[0])),t=(""+t).replace(/(^\s*|\s*$)/,""),t||i.fallback},tip:function(){var e=s(this.options.id,this.$element[0]);return this.$tip||(this.$tip=t('').html('
').attr("role","tooltip"),this.$tip.data("tipsy-pointee",this.$element[0])),this.$tip},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled}},t.fn.tipsy=function(e){function i(i){var s=t.data(i,"tipsy");return s||(s=new a(i,t.fn.tipsy.elementOptions(i,e)),t.data(i,"tipsy",s)),s}function s(){if(t.fn.tipsy.enabled===!0){var s=i(this);s.hoverState="in",0===e.delayIn?s.show():(s.fixTitle(),setTimeout(function(){"in"==s.hoverState&&o(s.$element)&&s.show()},e.delayIn))}}function n(){var t=i(this);t.hoverState="out",0===e.delayOut?t.hide():setTimeout(function(){"out"!=t.hoverState&&t.$element&&t.$element.is(":visible")||t.hide()},e.delayOut)}if(t.fn.tipsy.enable(),e===!0)return this.data("tipsy");if("string"==typeof e){var l=this.data("tipsy");return l&&l[e](),this}if(e=t.extend({},t.fn.tipsy.defaults,e),e.theme=e.theme&&""!==e.theme?"-"+e.theme:"",e.live||this.each(function(){i(this)}),"manual"!=e.trigger)if(e.live&&e.live!==!0)"focus"!=e.trigger&&(t(this).on("mouseenter",e.live,s),t(this).on("mouseleave",e.live,n)),"blur"!=e.trigger&&(t(this).on("focus",e.live,s),t(this).on("blur",e.live,n));else{if(e.live&&!t.live)throw"Since jQuery 1.9, pass selector as live argument. eg. $(document).tipsy({live: 'a.live'});";var h=e.live?"live":"bind";"focus"!=e.trigger&&this[h]("mouseenter",s)[h]("mouseleave",n),"blur"!=e.trigger&&this[h]("focus",s)[h]("blur",n)}return this},t.fn.tipsy.defaults={aria:!1,className:null,id:"tipsy",delayIn:0,delayOut:0,fade:!1,fadeInTime:400,fadeOutTime:400,shadow:!1,shadowBlur:8,shadowOpacity:1,shadowSpread:0,fallback:"",gravity:"n",html:!1,live:!1,offset:0,opacity:.8,title:"title",trigger:"interactive",theme:""},t.fn.tipsy.revalidate=function(){t(".tipsy").each(function(){var e=t.data(this,"tipsy-pointee");e&&o(e)||t(this).remove()})},t.fn.tipsy.enable=function(){t.fn.tipsy.enabled=!0},t.fn.tipsy.disable=function(){t.fn.tipsy.enabled=!1},t.fn.tipsy.elementOptions=function(e,i){return t.metadata?t.extend({},i,t(e).metadata()):i},t.fn.tipsy.autoNS=function(){return t(this).offset().top>t(document).scrollTop()+t(e).height()/2?"s":"n"},t.fn.tipsy.autoWE=function(){return t(this).offset().left>t(document).scrollLeft()+t(e).width()/2?"e":"w"},t.fn.tipsy.autoNWNE=function(){return t(this).offset().left>t(document).scrollLeft()+t(e).width()/2?"ne":"nw"},t.fn.tipsy.autoSWSE=function(){return t(this).offset().left>t(document).scrollLeft()+t(e).width()/2?"se":"sw"},t.fn.tipsy.autoBounds=function(i,s,o){return function(){var n={ns:o[0],ew:o.length>1?o[1]:!1},l=t(document).scrollTop()+i,a=t(document).scrollLeft()+s,h=t(this);return h.offset().top1?(o.ns=s[0],o.ew=s[1]):"e"==s[0]||"w"==s[0]?o.ew=s[0]:o.ns=s[0],a.offset().top/gm),Ye=e(/^data-[\-\w.\u00B7-\uFFFF]/),Xe=e(/^aria-[\-\w]+$/),$e=e(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),Je=e(/^(?:\w+script|data):/i),Qe=e(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205f\u3000]/g),Ze="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};function et(e){if(Array.isArray(e)){for(var t=0,r=Array(e.length);t

').querySelector("svg img")&&(a=!0)}catch(e){}}(),function(){try{var e=O("</title><img>");De(/<\/title/,e.querySelector("title").innerHTML)&&(l=!0)}catch(e){}}());function fe(e){return x.call(e.ownerDocument||e,e,n.SHOW_ELEMENT|n.SHOW_COMMENT|n.SHOW_TEXT,function(){return n.FILTER_ACCEPT},!1)}function pe(e){return"object"===(void 0===p?"undefined":Ze(p))?e instanceof p:e&&"object"===(void 0===e?"undefined":Ze(e))&&"number"==typeof e.nodeType&&"string"==typeof e.nodeName}function me(e,t,r){k[e]&&Ae(k[e],function(e){e.call(d,t,r,ue)})}function ye(e){var t,r=void 0;if(me("beforeSanitizeElements",e,null),!((t=e)instanceof m||t instanceof y||"string"==typeof t.nodeName&&"string"==typeof t.textContent&&"function"==typeof t.removeChild&&t.attributes instanceof i&&"function"==typeof t.removeAttribute&&"function"==typeof t.setAttribute&&"string"==typeof t.namespaceURI))return _(e),1;var n=Me(e.nodeName);if(me("uponSanitizeElement",e,{tagName:n,allowedTags:I}),("svg"===n||"math"===n)&&0!==e.querySelectorAll("p, br").length)return _(e),1;if(I[n]&&!W[n])return"noscript"===n&&De(/<\/noscript/i,e.innerHTML)||"noembed"===n&&De(/<\/noembed/i,e.innerHTML)?(_(e),1):(!V||e.firstElementChild||e.content&&e.content.firstElementChild||!De(/</g,e.textContent)||(Ee(d.removed,{element:e.cloneNode()}),e.innerHTML?e.innerHTML=Ne(e.innerHTML,/</g,"<"):e.innerHTML=Ne(e.textContent,/</g,"<")),Y&&3===e.nodeType&&(r=e.textContent,r=Ne(r,w," "),r=Ne(r,D," "),e.textContent!==r&&(Ee(d.removed,{element:e.cloneNode()}),e.textContent=r)),me("afterSanitizeElements",e,null),0);if(ne&&!ae[n]&&"function"==typeof e.insertAdjacentHTML)try{var o=e.innerHTML;e.insertAdjacentHTML("AfterEnd",b?b.createHTML(o):o)}catch(e){}return _(e),1}function ge(e,t,r){if(re&&("id"===t||"name"===t)&&(r in c||r in de))return!1;if(!(G&&De(R,t)||q&&De(H,t))){if(!U[t]||B[t])return!1;if(!ce[t]&&!De(z,Ne(r,F,""))&&("src"!==t&&"xlink:href"!==t&&"href"!==t||"script"===e||0!==Oe(r,"data:")||!le[e])&&(!K||De(C,Ne(r,F,"")))&&r)return!1}return!0}function he(e){var t=void 0,r=void 0,n=void 0,o=void 0,i=void 0;me("beforeSanitizeAttributes",e,null);var a=e.attributes;if(a){var l={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:U};for(i=a.length;i--;){var c=(t=a[i]).name,s=t.namespaceURI;if(r=we(t.value),n=Me(c),l.attrName=n,l.attrValue=r,l.keepAttr=!0,l.forceKeepAttr=void 0,me("uponSanitizeAttribute",e,l),r=l.attrValue,!l.forceKeepAttr){if("name"===n&&"IMG"===e.nodeName&&a.id)o=a.id,a=ke(a,[]),N("id",e),N(c,e),xe(a,o)>i&&e.setAttribute("id",o.value);else{if("INPUT"===e.nodeName&&"type"===n&&"file"===r&&l.keepAttr&&(U[n]||!B[n]))continue;"id"===c&&e.setAttribute(c,""),N(c,e)}if(l.keepAttr)if(V&&De(/\/>/i,r))N(c,e);else if(De(/svg|math/i,e.namespaceURI)&&De(Re("</("+Se(be(ae),"|")+")","i"),r))N(c,e);else{Y&&(r=Ne(r,w," "),r=Ne(r,D," "));var u=e.nodeName.toLowerCase();if(ge(u,n,r))try{s?e.setAttributeNS(s,c,r):e.setAttribute(c,r),Le(d.removed)}catch(e){}}}}me("afterSanitizeAttributes",e,null)}}function ve(e){var t=void 0,r=fe(e);for(me("beforeSanitizeShadowDOM",e,null);t=r.nextNode();)me("uponSanitizeShadowNode",t,null),ye(t)||(t.content instanceof f&&ve(t.content),he(t));me("afterSanitizeShadowDOM",e,null)}return d.sanitize=function(e,t){var r=void 0,n=void 0,o=void 0,i=void 0,a=void 0;if("string"!=typeof(e=e||"\x3c!--\x3e")&&!pe(e)){if("function"!=typeof e.toString)throw He("toString is not a function");if("string"!=typeof(e=e.toString()))throw He("dirty is not a string, aborting")}if(!d.isSupported){if("object"===Ze(s.toStaticHTML)||"function"==typeof s.toStaticHTML){if("string"==typeof e)return s.toStaticHTML(e);if(pe(e))return s.toStaticHTML(e.outerHTML)}return e}if($||M(t),d.removed=[],"string"==typeof e&&(oe=!1),!oe)if(e instanceof p)1===(n=(r=O("\x3c!--\x3e")).ownerDocument.importNode(e,!0)).nodeType&&"BODY"===n.nodeName||"HTML"===n.nodeName?r=n:r.appendChild(n);else{if(!Q&&!Y&&!X&&te&&-1===e.indexOf("<"))return b?b.createHTML(e):e;if(!(r=O(e)))return Q?null:T}r&&J&&_(r.firstChild);for(var l=fe(oe?e:r);o=l.nextNode();)3===o.nodeType&&o===i||ye(o)||(o.content instanceof f&&ve(o.content),he(o),i=o);if(i=null,oe)return e;if(Q){if(Z)for(a=L.call(r.ownerDocument);r.firstChild;)a.appendChild(r.firstChild);else a=r;return ee&&(a=E.call(u,a,!0)),a}var c=X?r.outerHTML:r.innerHTML;return Y&&(c=Ne(c,w," "),c=Ne(c,D," ")),b&&te?b.createHTML(c):c},d.setConfig=function(e){M(e),$=!0},d.clearConfig=function(){ue=null,$=!1},d.isValidAttribute=function(e,t,r){ue||M({});var n=Me(e),o=Me(t);return ge(n,o,r)},d.addHook=function(e,t){"function"==typeof t&&(k[e]=k[e]||[],Ee(k[e],t))},d.removeHook=function(e){k[e]&&Le(k[e])},d.removeHooks=function(e){k[e]&&(k[e]=[])},d.removeAllHooks=function(){k={}},d}()}); +//# sourceMappingURL=purify.min.js.map diff --git a/lib/js/roundslider.js b/lib/js/roundslider.js new file mode 100644 index 00000000..b6241084 --- /dev/null +++ b/lib/js/roundslider.js @@ -0,0 +1,1613 @@ +/*! + * roundSlider v1.4.0 | (c) 2015-2020, Soundar + * MIT license | http://roundsliderui.com/licence.html + */ + +(function ($, window, undefined) { + "use strict"; + /*jslint nomen: true */ + + var pluginName = "roundSlider"; + + // The plugin initialization + $.fn[pluginName] = function (options) { + return CreateRoundSlider.call(this, options, arguments); + }; + + RoundSlider.prototype = { + + pluginName: pluginName, + version: "1.4.0", + + // after the control initialization the updated default values + // are merged into the options + options: {}, + + // holds the current roundSlider element + control: null, + + // default properties of the plugin. while add a new property, + // that type should be included in the "_props:" for validation + defaults: { + min: 0, + max: 100, + step: 1, + value: null, + radius: 85, + width: 18, + handleSize: "+0", + startAngle: 0, + endAngle: "+360", + animation: true, + showTooltip: true, + editableTooltip: true, + readOnly: false, + disabled: false, + keyboardAction: true, + mouseScrollAction: false, + lineCap: "butt", + sliderType: "default", + circleShape: "full", + handleShape: "round", + + // SVG related properties + svgMode: false, + borderWidth: 1, + borderColor: null, + pathColor: null, + rangeColor: null, + + // events + beforeCreate: null, + create: null, + start: null, + drag: null, + change: null, + stop: null, + tooltipFormat: null + }, + keys: { // key codes for + UP: 38, // up arrow + DOWN: 40, // down arrow + LEFT: 37, // left arrow + RIGHT: 39 // right arrow + }, + _props: function () { + return { + numberType: ["min", "max", "step", "radius", "width", "borderWidth", "startAngle"], + booleanType: ["animation", "showTooltip", "editableTooltip", "readOnly", "disabled", + "keyboardAction", "mouseScrollAction", "svgMode"], + stringType: ["sliderType", "circleShape", "handleShape", "lineCap"] + }; + }, + + _init: function () { + if (this.options.svgMode) { + var EMPTY_FUNCTION = function () { }; + this._appendSeperator = EMPTY_FUNCTION; + this._refreshSeperator = EMPTY_FUNCTION; + this._updateSeperator = EMPTY_FUNCTION; + this._appendOverlay = EMPTY_FUNCTION; + this._checkOverlay = EMPTY_FUNCTION; + this._updateWidth = EMPTY_FUNCTION; + } + + this._isBrowserSupport = this._isBrowserSupported(); + this._isKO = false; + this._isAngular = false; + if (this.control.is("input")) { + this._isInputType = true; + this._hiddenField = this.control; + this.control = this.$createElement("div"); + this.control.insertAfter(this._hiddenField); + this.options.value = this._hiddenField.val() || this.options.value; + var that = this; + this._checkKO() && setTimeout(function () { that._checkKO(); }, 1); + this._checkAngular(); + } + this._bindOnDrag = false; + var _updateOn = this._dataElement().attr("data-updateon"); + if (typeof _updateOn == "string") { if (_updateOn == "drag") this._bindOnDrag = true; } + else if (this._isAngular) this._bindOnDrag = true; + + this._onInit(); + }, + _onInit: function () { + this._initialize(); + this._update(); + this._render(); + }, + _initialize: function () { + var browserName = this.browserName = this.getBrowserName(); + if (browserName) this.control.addClass("rs-" + browserName); + if (!this._isBrowserSupport) return; + this._isReadOnly = false; + this._checkDataType(); + this._refreshCircleShape(); + }, + _render: function () { + this.container = this.$createElement("div.rs-container"); + this.innerContainer = this.$createElement("div.rs-inner-container"); + this.container.append(this.innerContainer); + var $rootCSS = "rs-control " + (this.options.svgMode ? "rs-svg-mode" : "rs-classic-mode"); + this.control.addClass($rootCSS).empty().append(this.container); + + if (this._isBrowserSupport) { + this._createLayers(); + this._createOtherLayers(); + this._setContainerClass(); + this._setRadius(); + this._setProperties(); + this._setValue(); + this._updateTooltipPos(); + this._bindControlEvents("_bind"); + } + else { + var msg = this.$createElement("div.rs-msg"); + msg.html(typeof this._throwError === "function" ? this._throwError() : this._throwError); + this.control.empty().addClass("rs-error").append(msg); + if (this._isInputType) this.control.append(this._dataElement()); + } + }, + _update: function () { + this._validateSliderType(); + this._updateStartEnd(); + this._validateStartEnd(); + this._handle1 = this._handle2 = this._handleDefaults(); + this._analyzeModelValue(); + this._validateModelValue(); + }, + _createLayers: function () { + if (this.options.svgMode) { + this._createSVGElements(); + this._setSVGAttributes(); + this._setSVGStyles(); + this._moveSliderRange(true); + return; + } + + this.block = this.$createElement("div.rs-block rs-outer rs-border"); + this.innerContainer.append(this.block); + + var padd = this.options.width, start = this._start, path; + path = this.$createElement("div.rs-path rs-transition"); + + if (this._rangeSlider || this._showRange) { + this.block1 = path.clone().addClass("rs-range-color").rsRotate(start); + this.block2 = path.clone().addClass("rs-range-color").css("opacity", "0").rsRotate(start); + this.block3 = path.clone().addClass("rs-path-color").rsRotate(start); + this.block4 = path.addClass("rs-path-color").css({ "opacity": "1", "z-index": "1" }).rsRotate(start - 180); + + this.block.append(this.block1, this.block2, this.block3, this.block4).addClass("rs-split"); + } + else this.block.append(path.addClass("rs-path-color")); + + this.lastBlock = this.$createElement("span.rs-block").css({ "padding": padd }); + this.innerBlock = this.$createElement("div.rs-inner rs-bg-color rs-border"); + this.lastBlock.append(this.innerBlock); + this.block.append(this.lastBlock); + }, + _createOtherLayers: function () { + this._appendHandle(); + this._appendSeperator(); // non SVG mode only + this._appendOverlay(); // non SVG mode only + this._appendHiddenField(); + }, + _setProperties: function () { + this._updatePre(); + this._setHandleShape(); + this._addAnimation(); + this._appendTooltip(); + if (!this.options.showTooltip) this._removeTooltip(); + if (this.options.disabled) this.disable(); + else if (this.options.readOnly) this._readOnly(true); + if (this.options.mouseScrollAction) this._bindScrollEvents("_bind"); + }, + _updatePre: function () { + this._prechange = this._predrag = this.options.value; + }, + _setValue: function () { + if (this._rangeSlider) { + this._setHandleValue(1); + this._setHandleValue(2); + } + else { + if (this._showRange) this._setHandleValue(1); + var index = (this.options.sliderType == "default") ? (this._active || 1) : parseFloat(this.bar.children().attr("index")); + this._setHandleValue(index); + } + }, + _appendTooltip: function () { + if (this.container.children(".rs-tooltip").length !== 0) return; + this.tooltip = this.$createElement("span.rs-tooltip rs-tooltip-text"); + this.container.append(this.tooltip); + this._tooltipEditable(); + this._updateTooltip(); + }, + _removeTooltip: function () { + if (this.container.children(".rs-tooltip").length == 0) return; + this.tooltip && this.tooltip.remove(); + }, + _tooltipEditable: function () { + var o = this.options, tooltip = this.tooltip, hook; + if (!tooltip || !o.showTooltip) return; + + if (o.editableTooltip) { + tooltip.addClass("edit"); + hook = "_bind"; + } + else { + tooltip.removeClass("edit"); + hook = "_unbind"; + } + this[hook](tooltip, "click", this._editTooltip); + }, + _editTooltip: function (e) { + var tooltip = this.tooltip; + if (!tooltip.hasClass("edit") || this._isReadOnly) return; + var border = parseFloat(tooltip.css("border-left-width")) * 2; + var input = this.input = this.$createElement("input.rs-input rs-tooltip-text").css({ + height: tooltip.outerHeight() - border, + width: tooltip.outerWidth() - border + }); + tooltip.html(input).removeClass("edit").addClass("hover"); + + input.focus().val(this._getTooltipValue(true)); + + this._bind(input, "blur", this._focusOut); + this._bind(input, "change", this._focusOut); + }, + _focusOut: function (e) { + if (e.type == "change") { + var val = this.input.val().replace("-", ","); + if (val[0] == ",") { + val = "-" + val.slice(1).replace("-", ","); + } + this.options.value = val; + this._analyzeModelValue(); + this._validateModelValue(); + this._setValue(); + this.input.val(this._getTooltipValue(true)); + } + else { + this.tooltip.addClass("edit").removeClass("hover"); + this._updateTooltip(); + } + this._raiseEvent("change"); + }, + _setHandleShape: function () { + var type = this.options.handleShape, allHandles = this._handles(); + allHandles.removeClass("rs-handle-dot rs-handle-square"); + if (type == "dot") allHandles.addClass("rs-handle-dot"); + else if (type == "square") allHandles.addClass("rs-handle-square"); + else this.options.handleShape = "round"; + }, + _setHandleValue: function (index) { + this._active = index; + var handle = this["_handle" + index]; + if (this.options.sliderType != "min-range") this.bar = this._activeHandleBar(); + this._changeSliderValue(handle.value, handle.angle); + }, + _setAnimation: function () { + if (this.options.animation) this._addAnimation(); + else this._removeAnimation(); + }, + _addAnimation: function () { + if (this.options.animation) this.control.addClass("rs-animation"); + }, + _removeAnimation: function () { + this.control.removeClass("rs-animation"); + }, + _setContainerClass: function () { + var circleShape = this.options.circleShape; + if (circleShape == "full" || circleShape == "pie" || circleShape.indexOf("custom") === 0) { + this.container.addClass("full " + circleShape); + } + else { + this.container.addClass(circleShape.split("-").join(" ")); + } + }, + _setRadius: function () { + var o = this.options, r = o.radius, d = r * 2, circleShape = o.circleShape; + var extraSize = 0, actualHeight, actualWidth; + var height = actualHeight = d, width = actualWidth = d; + + // whenever the radius changes, before update the container size + // check for the lineCap also, since that will make some additional size + // also, based on that need to align the handle bars + var isFullCircle = (circleShape == "full" || circleShape == "pie" || circleShape.indexOf("custom") === 0); + if (o.svgMode && !isFullCircle) { + var handleBars = this._handleBars(); + if (o.lineCap != "none") { + extraSize = (o.lineCap === "butt") ? (o.borderWidth / 2) : ((o.width / 2) + o.borderWidth); + if (circleShape.indexOf("bottom") != -1) { + handleBars.css("margin-top", extraSize + 'px'); + } + if (circleShape.indexOf("right") != -1) { + handleBars.css("margin-right", -extraSize + 'px'); + } + } + else { + // when lineCap none, then remove the styles that was set previously for the other lineCap props + $.each(handleBars, function (i, bar) { + bar.style.removeProperty("margin-top"); + bar.style.removeProperty("margin-right"); + }); + } + } + + if (circleShape.indexOf("half") === 0) { + switch (circleShape) { + case "half-top": + case "half-bottom": + height = r; actualHeight = r + extraSize; + break; + case "half-left": + case "half-right": + width = r; actualWidth = r + extraSize; + break; + } + } + else if (circleShape.indexOf("quarter") === 0) { + height = width = r; + actualHeight = actualWidth = r + extraSize; + } + + this.container.css({ "height": height, "width": width }); + this.control.css({ "height": actualHeight, "width": actualWidth }); + + // when needed, then only we can set the styles through script, otherwise CSS styles applicable + if (extraSize !== 0) this.innerContainer.css({ "height": actualHeight, "width": actualWidth }); + else this.innerContainer.removeAttr("style"); + + if (o.svgMode) { + this.svgContainer.height(d).width(d); + this.svgContainer.children("svg").height(d).width(d); + } + }, + _border: function (seperator) { + if (seperator) return parseFloat(this._startLine.children().css("border-bottom-width")); + if (this.options.svgMode) return this.options.borderWidth * 2; + return parseFloat(this.block.css("border-top-width")) * 2; + }, + _appendHandle: function () { + if (this._rangeSlider || !this._showRange) this._createHandle(1); + if (this._rangeSlider || this._showRange) this._createHandle(2); + }, + _appendSeperator: function () { + this._startLine = this._addSeperator(this._start, "rs-start"); + this._endLine = this._addSeperator(this._start + this._end, "rs-end"); + this._refreshSeperator(); + }, + _addSeperator: function (pos, cls) { + var line = this.$createElement("span.rs-seperator rs-border"), width = this.options.width, _border = this._border(); + var lineWrap = this.$createElement("span.rs-bar rs-transition " + cls).append(line).rsRotate(pos); + this.container.append(lineWrap); + return lineWrap; + }, + _refreshSeperator: function () { + var bars = this._startLine.add(this._endLine), seperators = bars.children().removeAttr("style"); + var o = this.options, width = o.width, _border = this._border(), size = width + _border; + if (o.lineCap == "round" && o.circleShape != "full") { + bars.addClass("rs-rounded"); + seperators.css({ width: size, height: (size / 2) + 1 }); + this._startLine.children().css("margin-top", -1).addClass(o.sliderType == "min-range" ? "rs-range-color" : "rs-path-color"); + this._endLine.children().css("margin-top", size / -2).addClass("rs-path-color"); + } + else { + bars.removeClass("rs-rounded"); + seperators.css({ "width": size, "margin-top": this._border(true) / -2 }).removeClass("rs-range-color rs-path-color"); + } + }, + _updateSeperator: function () { + this._startLine.rsRotate(this._start); + this._endLine.rsRotate(this._start + this._end); + }, + _createHandle: function (index) { + var handle = this.$createElement("div.rs-handle rs-move"), o = this.options, hs; + if ((hs = o.handleShape) != "round") handle.addClass("rs-handle-" + hs); + handle.attr({ "index": index, "tabIndex": "0" }); + + var id = this._dataElement()[0].id, id = id ? id + "_" : ""; + var label = id + "handle" + (o.sliderType == "range" ? "_" + (index == 1 ? "start" : "end") : ""); + handle.attr({ "role": "slider", "aria-label": label }); // WAI-ARIA support + + var bar = this.$createElement("div.rs-bar rs-transition").css("z-index", "7").append(handle).rsRotate(this._start); + bar.addClass(o.sliderType == "range" && index == 2 ? "rs-second" : "rs-first"); + this.container.append(bar); + this._refreshHandle(); + + this.bar = bar; + this._active = index; + if (index != 1 && index != 2) this["_handle" + index] = this._handleDefaults(); + this._bind(handle, "focus", this._handleFocus); + this._bind(handle, "blur", this._handleBlur); + return handle; + }, + _refreshHandle: function () { + var o = this.options, hSize = o.handleSize, width = o.width, h, w, isSquare = true, isNumber = this.isNumber; + if (typeof hSize === "string" && isNumber(hSize)) { + if (hSize.charAt(0) === "+" || hSize.charAt(0) === "-") { + try { hSize = width + parseFloat(hSize.charAt(0) + Math.abs(parseFloat(hSize))); } + catch (e) { console.warn(e); } + } + else if (hSize.indexOf(",")) { + var s = hSize.split(","); + if (isNumber(s[0]) && isNumber(s[1])) w = parseFloat(s[0]), h = parseFloat(s[1]), isSquare = false; + } + } + if (isSquare) h = w = isNumber(hSize) ? parseFloat(hSize) : width; + var diff = (width + this._border() - w) / 2; + this._handles().css({ height: h, width: w, "margin": -h / 2 + "px 0 0 " + diff + "px" }); + }, + _handleDefaults: function () { + var min = this.options.min; + return { angle: this._valueToAngle(min), value: min }; + }, + _handleBars: function () { + return this.container.children("div.rs-bar"); + }, + _handles: function () { + return this._handleBars().find(".rs-handle"); + }, + _activeHandleBar: function (index) { + index = (index != undefined) ? index : this._active; + return $(this._handleBars()[index - 1]); + }, + _handleArgs: function (index) { + index = (index != undefined) ? index : this._active; + var _handle = this["_handle" + index]; + return { + element: this._activeHandleBar(index).children(), + index: index, + isActive: index == this._active, + value: _handle ? _handle.value : null, + angle: _handle ? _handle.angle : null + }; + }, + _dataElement: function () { + return this._isInputType ? this._hiddenField : this.control; + }, + _raiseEvent: function (event) { + var preValue = this["_pre" + event], currentValue = this.options.value; + if (preValue !== currentValue) { + this["_pre" + event] = currentValue; + if (event == "change") this._updatePre(); + this._updateTooltip(); + if ((event == "change") || (this._bindOnDrag && event == "drag")) this._updateHidden(); + return this._raise(event, { value: currentValue, preValue: preValue, "handle": this._handleArgs() }); + } + }, + + // Events handlers + _elementDown: function (e) { + if (this._isReadOnly) return; + var $target = $(e.target); + + if ($target.hasClass("rs-handle")) { + this._handleDown(e); + } + else { + var point = this._getXY(e), center = this._getCenterPoint(); + var distance = this._getDistance(point, center); + var block = this.block || this.svgContainer; + var outerDistance = block.outerWidth() / 2; + var innerDistance = outerDistance - (this.options.width + this._border()); + + if (distance >= innerDistance && distance <= outerDistance) { + var handle = this.control.find(".rs-handle.rs-focus"), angle, value; + if (handle.length !== 0) { + // here, some handle was in already focused state, and user clicked on the slider path + // so this will make the handle unfocus, to avoid that we can prevent this event + e.preventDefault(); + } + + var d = this._getAngleValue(point, center); + angle = d.angle, value = d.value; + + if (this._rangeSlider) { + if (handle.length == 1) { + var active = parseFloat(handle.attr("index")); + if (!this._invertRange) { + if (active == 1 && angle > this._handle2.angle) active = 2; + else if (active == 2 && angle < this._handle1.angle) active = 1; + } + this._active = active; + } + else this._active = (this._handle2.angle - angle) < (angle - this._handle1.angle) ? 2 : 1; + this.bar = this._activeHandleBar(); + } + + this._changeSliderValue(value, angle); + this._raiseEvent("change"); + } + } + }, + _handleDown: function (e) { + e.preventDefault(); + var $target = $(e.target); + $target.focus(); + this._removeAnimation(); + this._bindMouseEvents("_bind"); + this.bar = $target.parent(); + this._active = parseFloat($target.attr("index")); + this._handles().removeClass("rs-move"); + this._raise("start", { value: this.options.value, "handle": this._handleArgs() }); + }, + _handleMove: function (e) { + e.preventDefault(); + var point = this._getXY(e), center = this._getCenterPoint(); + var d = this._getAngleValue(point, center, true), angle, value; + angle = d.angle, value = d.value; + + this._changeSliderValue(value, angle); + this._raiseEvent("drag"); + }, + _handleUp: function (e) { + this._handles().addClass("rs-move"); + this._bindMouseEvents("_unbind"); + this._addAnimation(); + this._raiseEvent("change"); + this._raise("stop", { value: this.options.value, "handle": this._handleArgs() }); + }, + _handleFocus: function (e) { + if (this._isReadOnly) return; + var $target = $(e.target); + this._handles().removeClass("rs-focus"); + $target.addClass("rs-focus"); + this.bar = $target.parent(); + this._active = parseFloat($target.attr("index")); + if (this.options.keyboardAction) { + this._bindKeyboardEvents("_unbind"); + this._bindKeyboardEvents("_bind"); + } + + // updates the class for change z-index + this.control.find("div.rs-bar").css("z-index", "7"); + this.bar.css("z-index", "8"); + }, + _handleBlur: function (e) { + this._handles().removeClass("rs-focus"); + if (this.options.keyboardAction) this._bindKeyboardEvents("_unbind"); + }, + _handleKeyDown: function (e) { + if (this._isReadOnly) return; + var key = e.keyCode, keyCodes = this.keys; + + if (key == 27) // if Esc key pressed then hanldes will be focused out + this._handles().blur(); + + if (!(key >= 35 && key <= 40)) return; // if not valid keys, then return + if (key >= 37 && key <= 40) this._removeAnimation(); + + var h = this["_handle" + this._active], val, ang; + + e.preventDefault(); + if (key == keyCodes.UP || key == keyCodes.RIGHT) // Up || Right Key + val = this._round(this._limitValue(h.value + this.options.step)); + else if (key == keyCodes.DOWN || key == keyCodes.LEFT) // Down || Left Key + val = this._round(this._limitValue(h.value - this._getMinusStep(h.value))); + else if (key == 36) // Home Key + val = this._getKeyValue("Home"); + else if (key == 35) // End Key + val = this._getKeyValue("End"); + + ang = this._valueToAngle(val); + this._changeSliderValue(val, ang); + this._raiseEvent("drag"); + }, + _handleKeyUp: function (e) { + this._addAnimation(); + this._raiseEvent("change"); + }, + _getMinusStep: function (val) { + var o = this.options, min = o.min, max = o.max, step = o.step; + if (val == max) { + var remain = (max - min) % step; + return remain == 0 ? step : remain; + } + return step; + }, + _getKeyValue: function (key) { + var o = this.options, min = o.min, max = o.max; + if (this._rangeSlider) { + if (key == "Home") return (this._active == 1) ? min : this._handle1.value; + else return (this._active == 1) ? this._handle2.value : max; + } + return (key == "Home") ? min : max; + }, + _elementScroll: function (event) { + if (this._isReadOnly) return; + event.preventDefault(); + var e = event.originalEvent || event, h, val, ang, delta; + delta = e.wheelDelta ? e.wheelDelta / 60 : (e.detail ? -e.detail / 2 : 0); + if (delta == 0) return; + + this._updateActiveHandle(event); + h = this["_handle" + this._active]; + val = h.value + (delta > 0 ? this.options.step : -this._getMinusStep(h.value)); + val = this._limitValue(val); + ang = this._valueToAngle(val); + + this._removeAnimation(); + this._changeSliderValue(val, ang); + this._raiseEvent("change"); + this._addAnimation(); + }, + _updateActiveHandle: function (e) { + var $target = $(e.target); + if ($target.hasClass("rs-handle") && $target.parent().parent()[0] == this.control[0]) { + this.bar = $target.parent(); + this._active = parseFloat($target.attr("index")); + } + if (!this.bar.find(".rs-handle").hasClass("rs-focus")) this.bar.find(".rs-handle").focus(); + }, + + // Events binding + _bindControlEvents: function (hook) { + this[hook](this.control, "mousedown", this._elementDown); + this[hook](this.control, "touchstart", this._elementDown); + }, + _bindScrollEvents: function (hook) { + this[hook](this.control, "mousewheel", this._elementScroll); + this[hook](this.control, "DOMMouseScroll", this._elementScroll); + }, + _bindMouseEvents: function (hook) { + this[hook]($(document), "mousemove", this._handleMove); + this[hook]($(document), "mouseup", this._handleUp); + this[hook]($(document), "mouseleave", this._handleUp); + + // *** for Touch support *** // + this[hook]($(document), "touchmove", this._handleMove); + this[hook]($(document), "touchend", this._handleUp); + this[hook]($(document), "touchcancel", this._handleUp); + }, + _bindKeyboardEvents: function (hook) { + this[hook]($(document), "keydown", this._handleKeyDown); + this[hook]($(document), "keyup", this._handleKeyUp); + }, + + // internal methods + _changeSliderValue: function (value, angle) { + var oAngle = this._oriAngle(angle), lAngle = this._limitAngle(angle); + if (!this._rangeSlider && !this._showRange) { + + this["_handle" + this._active] = { angle: angle, value: value }; + this.options.value = value; + this.bar.rsRotate(lAngle); + this._updateARIA(value); + } + else if ((this._active == 1 && oAngle <= this._oriAngle(this._handle2.angle)) || + (this._active == 2 && oAngle >= this._oriAngle(this._handle1.angle)) || this._invertRange) { + + this["_handle" + this._active] = { angle: angle, value: value }; + this.options.value = this._rangeSlider ? this._handle1.value + "," + this._handle2.value : value; + this.bar.rsRotate(lAngle); + this._updateARIA(value); + + if (this.options.svgMode) { + this._moveSliderRange(); + return; + } + + // classic DIV handling + var dAngle = this._oriAngle(this._handle2.angle) - this._oriAngle(this._handle1.angle), o2 = "1", o3 = "0"; + if (dAngle <= 180 && !(dAngle < 0 && dAngle > -180)) o2 = "0", o3 = "1"; + this.block2.css("opacity", o2); + this.block3.css("opacity", o3); + + (this._active == 1 ? this.block4 : this.block2).rsRotate(lAngle - 180); + (this._active == 1 ? this.block1 : this.block3).rsRotate(lAngle); + } + }, + + // SVG related functionalities + _createSVGElements: function () { + var svgEle = this.$createSVG("svg"); + var PATH = "path.rs-transition "; + var pathAttr = { fill: "transparent" }; + + this.$path = this.$createSVG(PATH + "rs-path", pathAttr); + this.$range = this._showRange ? this.$createSVG(PATH + "rs-range", pathAttr) : null; + this.$border = this.$createSVG(PATH + "rs-border", pathAttr); + this.$append(svgEle, [this.$path, this.$range, this.$border]); + + this.svgContainer = this.$createElement("div.rs-svg-container").append(svgEle); + this.innerContainer.append(this.svgContainer); + }, + _setSVGAttributes: function () { + var o = this.options, radius = o.radius, + border = o.borderWidth, width = o.width, + lineCap = o.lineCap; + var outerRadius = radius - (border / 2), + innerRadius = outerRadius - width - border; + var startAngle = this._start, + totalAngle = this._end, + endAngle = startAngle + totalAngle; + + // draw the path for border element + var border_d = this.$drawPath(radius, outerRadius, startAngle, endAngle, innerRadius, lineCap); + this.$setAttribute(this.$border, { + "d": border_d + }); + // and set the border width + $(this.$border).css("stroke-width", border); + + var pathRadius = radius - border - (width / 2); + this.svgPathLength = this.$getArcLength(pathRadius, totalAngle); + var d = this.$drawPath(radius, pathRadius, startAngle, endAngle); + var attr = { "d": d, "stroke-width": width, "stroke-linecap": lineCap }; + + // draw the path for slider path element + this.$setAttribute(this.$path, attr); + + if (this._showRange) { + // draw the path for slider range element + this.$setAttribute(this.$range, attr); + + // there was a small bug when lineCap was round/square, this will solve that + if (lineCap == "round" || lineCap == "square") this.$range.setAttribute("stroke-dashoffset", "0.01"); + else this.$range.removeAttribute("stroke-dashoffset"); + } + }, + _setSVGStyles: function () { + var o = this.options, + borderColor = o.borderColor, + pathColor = o.pathColor, + rangeColor = o.rangeColor; + + if (borderColor) { + $(this.$border).css("stroke", borderColor); + } + + if (pathColor) { + $(this.$path).css("stroke", pathColor); + } + + if (this._showRange && rangeColor) { + $(this.$range).css("stroke", rangeColor); + } + }, + _moveSliderRange: function (isInit) { + if (!this._showRange) return; + + var startAngle = this._start, + totalAngle = this._end; + var handle1Angle = this._handle1.angle - startAngle, + handle2Angle = this._handle2.angle - startAngle; + if (isInit) handle1Angle = handle2Angle = 0; + var dashArray = []; + + if (handle1Angle <= handle2Angle) { + // starting the dashArray from 0 means normal range, otherwise it's invert range + // so when handle1 value is smaller then it's a normal range selection only + dashArray.push(0); + } + else { + // when handle1 value is larger then it's a invert range selection, also swap the values + var temp = handle1Angle; + handle1Angle = handle2Angle; + handle2Angle = temp; + } + + var handle1Distance = (handle1Angle / totalAngle) * this.svgPathLength; + dashArray.push(handle1Distance); + + var handle2Distance = ((handle2Angle - handle1Angle) / totalAngle) * this.svgPathLength; + dashArray.push(handle2Distance, this.svgPathLength); + + this.$range.style.strokeDasharray = dashArray.join(" "); + }, + _isPropsRelatedToSVG: function (property) { + var svgRelatedProps = ["radius", "borderWidth", "width", "lineCap", "startAngle", "endAngle"]; + return this._hasProperty(property, svgRelatedProps); + }, + _isPropsRelatedToSVGStyles: function (property) { + var svgStylesRelatedProps = ["borderColor", "pathColor", "rangeColor"]; + return this._hasProperty(property, svgStylesRelatedProps); + }, + _hasProperty: function (property, list) { + if (typeof property == "string") { + return (list.indexOf(property) !== -1); + } + else { + var allProperties = Object.keys(property); + return allProperties.some(function (prop) { + return (list.indexOf(prop) !== -1); + }); + } + }, + + // WAI-ARIA support + _updateARIA: function (value) { + var o = this.options, min = o.min, max = o.max; + this.bar.children().attr({ "aria-valuenow": value }); + if (o.sliderType == "range") { + var handles = this._handles(); + handles.eq(0).attr({ "aria-valuemin": min }); + handles.eq(1).attr({ "aria-valuemax": max }); + + if (this._active == 1) handles.eq(1).attr({ "aria-valuemin": value }); + else handles.eq(0).attr({ "aria-valuemax": value }); + } + else this.bar.children().attr({ "aria-valuemin": min, "aria-valuemax": max }); + }, + // Listener for KO binding + _checkKO: function () { + var _data = this._dataElement().data("bind"); + if (typeof _data == "string" && typeof ko == "object") { + var _vm = ko.dataFor(this._dataElement()[0]); + if (typeof _vm == "undefined") return true; + var _all = _data.split(","), _handler; + for (var i = 0; i < _all.length; i++) { + var d = _all[i].split(":"); + if ($.trim(d[0]) == "value") { + _handler = $.trim(d[1]); + break; + } + } + if (_handler) { + this._isKO = true; + ko.computed(function () { this.option("value", _vm[_handler]()); }, this); + } + } + }, + // Listener for Angular binding + _checkAngular: function () { + if (typeof angular == "object" && typeof angular.element == "function") { + this._ngName = this._dataElement().attr("ng-model"); + if (typeof this._ngName == "string") { + this._isAngular = true; var that = this; + this._scope().$watch(this._ngName, function (newValue, oldValue) { that.option("value", newValue); }); + } + } + }, + _scope: function () { + return angular.element(this._dataElement()).scope(); + }, + _getDistance: function (p1, p2) { + return Math.sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y)); + }, + _getXY: function (e) { + if (e.type.indexOf("mouse") == -1) e = (e.originalEvent || e).changedTouches[0]; + return { x: e.pageX, y: e.pageY }; + }, + _getCenterPoint: function () { + var block = this.block || this.svgContainer; + var offset = block.offset(), center; + center = { + x: offset.left + (block.outerWidth() / 2), + y: offset.top + (block.outerHeight() / 2) + }; + return center; + }, + _getAngleValue: function (point, center, isDrag) { + var deg = Math.atan2(point.y - center.y, center.x - point.x); + var angle = (-deg / (Math.PI / 180)); + if (angle < this._start) angle += 360; + angle = this._checkAngle(angle, isDrag); + return this._processStepByAngle(angle); + }, + _checkAngle: function (angle, isDrag) { + var o_angle = this._oriAngle(angle), + preAngle = this["_handle" + this._active].angle, + o_preAngle = this._oriAngle(preAngle); + + if (o_angle > this._end) { + if (!isDrag) return preAngle; + angle = this._start + (o_preAngle <= this._end - o_preAngle ? 0 : this._end); + } + else if (isDrag) { + var d = this._handleDragDistance; + if (this.isNumber(d)) if (Math.abs(o_angle - o_preAngle) > d) return preAngle; + } + return angle; + }, + _processStepByAngle: function (angle) { + var value = this._angleToValue(angle); + return this._processStepByValue(value); + }, + _processStepByValue: function (value) { + var o = this.options, min = o.min, max = o.max, step = o.step, isMinHigher = (min > max); + var remain, currVal, nextVal, preVal, newVal, ang; + + step = (isMinHigher ? -step : step); + remain = (value - min) % step; + + currVal = value - remain; + nextVal = this._limitValue(currVal + step); + preVal = this._limitValue(currVal - step); + + if (!isMinHigher) { + if (value >= currVal) newVal = (value - currVal < nextVal - value) ? currVal : nextVal; + else newVal = (currVal - value > value - preVal) ? currVal : preVal; + } + else { + if (value <= currVal) newVal = (currVal - value < value - nextVal) ? currVal : nextVal; + else newVal = (value - currVal > preVal - value) ? currVal : preVal; + } + newVal = this._round(newVal), ang = this._valueToAngle(newVal); + return { value: newVal, angle: ang }; + }, + _round: function (val) { + var s = this.options.step.toString().split("."); + return s[1] ? parseFloat(val.toFixed(s[1].length)) : Math.round(val); + }, + _oriAngle: function (angle) { + var ang = angle - this._start; + if (ang < 0) ang += 360; + return ang; + }, + _limitAngle: function (angle) { + if (angle > 360 + this._start) angle -= 360; + if (angle < this._start) angle += 360; + return angle; + }, + _limitValue: function (value) { + var o = this.options, min = o.min, max = o.max, isMinHigher = (min > max); + if ((!isMinHigher && value < min) || (isMinHigher && value > min)) value = min; + if ((!isMinHigher && value > max) || (isMinHigher && value < max)) value = max; + return value; + }, + _angleToValue: function (angle) { + var o = this.options, min = o.min, max = o.max, value; + value = (this._oriAngle(angle) / this._end) * (max - min) + min; + return value; + }, + _valueToAngle: function (value) { + var o = this.options, min = o.min, max = o.max, angle; + angle = (((value - min) / (max - min)) * this._end) + this._start; + return angle; + }, + _appendHiddenField: function () { + this._hiddenField = this._hiddenField || this.$createElement("input"); + this._hiddenField.attr({ + "type": "hidden", "name": this._dataElement()[0].id || "" + }); + this.control.append(this._hiddenField); + this._updateHidden(); + }, + _updateHidden: function () { + var val = this.options.value; + this._hiddenField.val(val); + if (this._isKO || this._isAngular) this._hiddenField.trigger("change"); + if (this._isAngular) this._scope()[this._ngName] = val; + }, + _updateTooltip: function () { + if (this.tooltip && !this.tooltip.hasClass("hover")) + this.tooltip.html(this._getTooltipValue()); + this._updateTooltipPos(); + }, + _updateTooltipPos: function () { + this.tooltip && this.tooltip.css(this._getTooltipPos()); + }, + _getTooltipPos: function () { + var circleShape = this.options.circleShape, pos; + var tooltipHeight = this.tooltip.outerHeight(), tooltipWidth = this.tooltip.outerWidth(); + + if (circleShape == "full" || circleShape == "pie" || circleShape.indexOf("custom") === 0) { + return { + "margin-top": -tooltipHeight / 2, + "margin-left": -tooltipWidth / 2 + }; + } + else if (circleShape.indexOf("half") != -1) { + switch (circleShape) { + case "half-top": + case "half-bottom": + pos = { "margin-left": -tooltipWidth / 2 }; break; + case "half-left": + case "half-right": + pos = { "margin-top": -tooltipHeight / 2 }; break; + } + return pos; + } + return {}; + }, + _getTooltipValue: function (isNormal) { + var value = this.options.value; + if (this._rangeSlider) { + var p = value.split(","); + if (isNormal) return p[0] + " - " + p[1]; + return this._tooltipValue(p[0], 1) + " - " + this._tooltipValue(p[1], 2); + } + if (isNormal) return value; + return this._tooltipValue(value); + }, + _tooltipValue: function (value, index) { + var returnValue = this._raise("tooltipFormat", { value: value, "handle": this._handleArgs(index) }); + return (returnValue != null && typeof returnValue !== "boolean") ? returnValue : value; + }, + _validateStartAngle: function () { + var start = this.options.startAngle; + start = (this.isNumber(start) ? parseFloat(start) : 0) % 360; + if (start < 0) start += 360; + this.options.startAngle = start; + return start; + }, + _validateEndAngle: function () { + var o = this.options, start = o.startAngle, end = o.endAngle; + if (typeof end === "string" && this.isNumber(end) && (end.charAt(0) === "+" || end.charAt(0) === "-")) { + try { end = start + parseFloat(end.charAt(0) + Math.abs(parseFloat(end))); } + catch (e) { console.warn(e); } + } + end = (this.isNumber(end) ? parseFloat(end) : 360) % 360; + if (end <= start) end += 360; + return end; + }, + _refreshCircleShape: function () { + var circleShape = this.options.circleShape; + var allCircelShapes = ["half-top", "half-bottom", "half-left", "half-right", + "quarter-top-left", "quarter-top-right", "quarter-bottom-right", "quarter-bottom-left", + "pie", "custom-half", "custom-quarter"]; + var shape_codes = ["h1", "h2", "h3", "h4", "q1", "q2", "q3", "q4", "3/4", "ch", "cq"]; + + if (allCircelShapes.indexOf(circleShape) == -1) { + var index = shape_codes.indexOf(circleShape); + if (index != -1) circleShape = allCircelShapes[index]; + else if (circleShape == "half") circleShape = "half-top"; + else if (circleShape == "quarter") circleShape = "quarter-top-left"; + else circleShape = "full"; + } + this.options.circleShape = circleShape; + }, + _appendOverlay: function () { + var shape = this.options.circleShape; + if (shape == "pie") + this._checkOverlay(".rs-overlay", 270); + else if (shape == "custom-half" || shape == "custom-quarter") { + this._checkOverlay(".rs-overlay1", 180); + if (shape == "custom-quarter") + this._checkOverlay(".rs-overlay2", this._end); + } + }, + _checkOverlay: function (cls, angle) { + var overlay = this.container.children(cls); + if (overlay.length == 0) { + overlay = this.$createElement("div" + cls + " rs-transition rs-bg-color"); + this.container.append(overlay); + } + overlay.rsRotate(this._start + angle); + }, + _checkDataType: function () { + var m = this.options, i, prop, value, props = this._props(); + // to check number datatype + for (i in props.numberType) { + prop = props.numberType[i], value = m[prop]; + if (!this.isNumber(value)) m[prop] = this.defaults[prop]; + else m[prop] = parseFloat(value); + } + // to check input string + for (i in props.booleanType) { + prop = props.booleanType[i], value = m[prop]; + m[prop] = (value == "false") ? false : !!value; + } + // to check boolean datatype + for (i in props.stringType) { + prop = props.stringType[i], value = m[prop]; + m[prop] = ("" + value).toLowerCase(); + } + }, + _validateSliderType: function () { + var type = this.options.sliderType.toLowerCase(); + this._rangeSlider = this._showRange = false; + if (type == "range") this._rangeSlider = this._showRange = true; + else if (type.indexOf("min") != -1) { + this._showRange = true; + type = "min-range"; + } + else type = "default"; + this.options.sliderType = type; + }, + _updateStartEnd: function () { + var o = this.options, circle = o.circleShape, startAngle = o.startAngle, endAngle = o.endAngle; + + if (circle != "full") { + if (circle.indexOf("quarter") != -1) endAngle = "+90"; + else if (circle.indexOf("half") != -1) endAngle = "+180"; + else if (circle == "pie") endAngle = "+270"; + this.options.endAngle = endAngle; + + if (circle == "quarter-top-left" || circle == "half-top") startAngle = 0; + else if (circle == "quarter-top-right" || circle == "half-right") startAngle = 90; + else if (circle == "quarter-bottom-right" || circle == "half-bottom") startAngle = 180; + else if (circle == "quarter-bottom-left" || circle == "half-left") startAngle = 270; + this.options.startAngle = startAngle; + } + }, + _validateStartEnd: function () { + this._start = this._validateStartAngle(); + this._end = this._validateEndAngle(); + + var add = (this._start < this._end) ? 0 : 360; + this._end += add - this._start; + }, + _analyzeModelValue: function () { + var o = this.options, val = o.value, min = o.min, max = o.max, + lastValue, newValue, isNumber = this.isNumber; + if (val instanceof Array) val = val.toString(); + var valueIsString = (typeof val == "string"); + + var parts = valueIsString ? val.split(",") : [val]; + + if (this._rangeSlider) { + if (valueIsString) { + if (parts.length >= 2) newValue = (isNumber(parts[0]) ? parts[0] : min) + "," + + (isNumber(parts[1]) ? parts[1] : max); + else newValue = isNumber(parts[0]) ? min + "," + parts[0] : min + "," + min; + } + else newValue = isNumber(val) ? min + "," + val : min + "," + min; + } + else { + if (valueIsString) lastValue = parts.pop(), newValue = isNumber(lastValue) ? parseFloat(lastValue) : min; + else newValue = isNumber(val) ? parseFloat(val) : min; + } + this.options.value = newValue; + }, + _validateModelValue: function () { + var o = this.options, val = o.value; + if (this._rangeSlider) { + var parts = val.split(","), val1 = parseFloat(parts[0]), val2 = parseFloat(parts[1]); + val1 = this._limitValue(val1); + val2 = this._limitValue(val2); + if (!this._invertRange) { + var min = o.min, max = o.max; + var isMinHigher = (min > max); + if (isMinHigher) { + if (val1 < val2) val1 = val2; + } else { + if (val1 > val2) val2 = val1; + } + } + + this._handle1 = this._processStepByValue(val1); + this._handle2 = this._processStepByValue(val2); + this.options.value = this._handle1.value + "," + this._handle2.value; + } + else { + var index = this._showRange ? 2 : (this._active || 1); + this["_handle" + index] = this._processStepByValue(this._limitValue(val)); + if (this._showRange) this._handle1 = this._handleDefaults(); + this.options.value = this["_handle" + index].value; + } + }, + + // common core methods + $createElement: function (tag) { + var t = tag.split('.'); + return $(document.createElement(t[0])).addClass(t[1] || ""); + }, + $createSVG: function (tag, attr) { + var t = tag.split('.'); + var svgEle = document.createElementNS("http://www.w3.org/2000/svg", t[0]); + if (t[1]) { + svgEle.setAttribute("class", t[1]); + } + if (attr) { + this.$setAttribute(svgEle, attr); + } + return svgEle; + }, + $setAttribute: function (ele, attr) { + for (var key in attr) { + var val = attr[key]; + if (key === "class") { + var prev = ele.getAttribute('class'); + if (prev) val += " " + prev; + } + ele.setAttribute(key, val); + } + return ele; + }, + $append: function (parent, children) { + children.forEach(function (element) { + element && parent.appendChild(element); + }); + return parent; + }, + isNumber: function (number) { + number = parseFloat(number); + return typeof number === "number" && !isNaN(number); + }, + getBrowserName: function () { + var browserName = "", ua = window.navigator.userAgent; + if ((!!window.opr && !!opr.addons) || !!window.opera || ua.indexOf(' OPR/') >= 0) browserName = "opera"; + else if (typeof InstallTrigger !== 'undefined') browserName = "firefox"; + else if (ua.indexOf('MSIE ') > 0 || ua.indexOf('Trident/') > 0) browserName = "ie"; + else if (!!window.StyleMedia) browserName = "edge"; + else if (ua.indexOf('Safari') != -1 && ua.indexOf('Chrome') == -1) browserName = "safari"; + else if ((!!window.chrome && !!window.chrome.webstore) || (ua.indexOf('Chrome') != -1)) browserName = "chrome"; + return browserName; + }, + _isBrowserSupported: function () { + var properties = ["borderRadius", "WebkitBorderRadius", "MozBorderRadius", + "OBorderRadius", "msBorderRadius", "KhtmlBorderRadius"]; + for (var i = 0; i < properties.length; i++) { + if (document.body.style[properties[i]] !== undefined) return true; + } + }, + _throwError: function () { + return "This browser doesn't support the border-radious property."; + }, + _raise: function (event, args) { + var o = this.options, fn = o[event], val = true; + args = args || { value: o.value }; + args["id"] = this.id; + args["control"] = this.control; + args["options"] = o; + if (fn) { + args["type"] = event; + if (typeof fn === "string") fn = window[fn]; + if ($.isFunction(fn)) { + val = fn.call(this, args); + val = val === false ? false : val; + } + } + this.control.trigger($.Event(event, args)); + return val; + }, + _bind: function (element, _event, handler) { + $(element).bind(_event, $.proxy(handler, this)); + }, + _unbind: function (element, _event, handler) { + $(element).unbind(_event, $.proxy(handler, this)); + }, + _getInstance: function () { + return $.data(this._dataElement()[0], pluginName); + }, + _saveInstanceOnElement: function () { + $.data(this.control[0], pluginName, this); + }, + _saveInstanceOnID: function () { + var id = this.id; + if (id && typeof window[id] !== "undefined") + window[id] = this; + }, + _removeData: function () { + var control = this._dataElement()[0]; + $.removeData && $.removeData(control, pluginName); + if (control.id && typeof window[control.id]["_init"] === "function") + delete window[control.id]; + }, + _destroyControl: function () { + if (this._isInputType) this._dataElement().insertAfter(this.control).attr("type", "text"); + this.control.empty().removeClass("rs-control").height("").width(""); + this._removeAnimation(); + this._bindControlEvents("_unbind"); + }, + + // methods to dynamic options updation (through option) + _updateWidth: function () { + this.lastBlock.css("padding", this.options.width); + }, + _readOnly: function (bool) { + this._isReadOnly = bool; + this.container.removeClass("rs-readonly"); + if (bool) this.container.addClass("rs-readonly"); + }, + + // get & set for the properties + _get: function (property) { + return this.options[property]; + }, + _set: function (property, value, forceSet) { + var props = this._props(); + if ($.inArray(property, props.numberType) != -1) { // to check number datatype + if (!this.isNumber(value)) return; + value = parseFloat(value); + } + else if ($.inArray(property, props.booleanType) != -1) { // to check boolean datatype + value = (value == "false") ? false : !!value; + } + else if ($.inArray(property, props.stringType) != -1) { // to check input string + value = value.toLowerCase(); + } + + if (!forceSet && this.options[property] == value) return; + this.options[property] = value; + switch (property) { + case "startAngle": + case "endAngle": + this._validateStartEnd(); + this._updateSeperator(); // non SVG mode only + this._appendOverlay(); // non SVG mode only + case "min": + case "max": + case "step": + case "value": + this._analyzeModelValue(); + this._validateModelValue(); + this._setValue(); + this._updatePre(); + this._updateHidden(); + this._updateTooltip(); + break; + case "radius": + this._setRadius(); + this._updateTooltipPos(); + break; + case "width": + this._removeAnimation(); + this._updateWidth(); // non SVG mode only + this._setRadius(); + this._refreshHandle(); + this._updateTooltipPos(); + this._addAnimation(); + this._refreshSeperator(); // non SVG mode only + break; + case "borderWidth": + this._setRadius(); + this._refreshHandle(); + break; + case "handleSize": + this._refreshHandle(); + break; + case "handleShape": + this._setHandleShape(); + break; + case "animation": + this._setAnimation(); + break; + case "showTooltip": + this.options.showTooltip ? this._appendTooltip() : this._removeTooltip(); + break; + case "editableTooltip": + this._tooltipEditable(); + this._updateTooltipPos(); + break; + case "disabled": + this.options.disabled ? this.disable() : this.enable(); + break; + case "readOnly": + this.options.readOnly ? this._readOnly(true) : (!this.options.disabled && this._readOnly(false)); + break; + case "mouseScrollAction": + this._bindScrollEvents(this.options.mouseScrollAction ? "_bind" : "_unbind"); + break; + case "lineCap": + this._setRadius(); + this._refreshSeperator(); // non SVG mode only + break; + case "circleShape": + this._refreshCircleShape(); + if (this.options.circleShape == "full") { + this.options.startAngle = 0; + this.options.endAngle = "+360"; + } + case "sliderType": + this._destroyControl(); + this._onInit(); + break; + case "svgMode": + var $control = this.control, $options = this.options; + this.destroy(); + $control[pluginName]($options); + break; + } + return this; + }, + + // public methods + option: function (property, value) { + if (!this._getInstance() || !this._isBrowserSupport) return; + if ($.isPlainObject(property)) { + if (property["min"] !== undefined || property["max"] !== undefined) { + if (property["min"] !== undefined) { + this.options.min = property["min"]; + delete property["min"]; + } + if (property["max"] !== undefined) { + this.options.max = property["max"]; + delete property["max"]; + } + var val = this.options.value; + if (property["value"] !== undefined) { + val = property["value"] + delete property["value"]; + } + this._set("value", val, true); + } + for (var prop in property) { + this._set(prop, property[prop]); + } + } + else if (property && typeof property == "string") { + if (value === undefined) return this._get(property); + this._set(property, value); + } + + // whenever the properties set dynamically, check for SVG mode. also check + // any of the property was related to SVG. If yes, then redraw the SVG path + if (this.options.svgMode && property) { + if (this._isPropsRelatedToSVG(property)) { + this._setSVGAttributes(); + this._moveSliderRange(); + } + if (this._isPropsRelatedToSVGStyles(property)) { + this._setSVGStyles(); + } + } + + return this; + }, + getValue: function (index) { + if (this.options.sliderType == "range" && this.isNumber(index)) { + var i = parseFloat(index); + if (i == 1 || i == 2) + return this["_handle" + i].value; + } + return this._get("value"); + }, + setValue: function (value, index) { + if (this.isNumber(value)) { + if (this.isNumber(index)) { + var sliderType = this.options.sliderType; + if (sliderType == "range") { + var i = parseFloat(index), val = parseFloat(value); + if (i == 1) value = val + "," + this._handle2.value; + else if (i == 2) value = this._handle1.value + "," + val; + } + else if (sliderType == "default") this._active = index; + } + this._set("value", value); + } + }, + disable: function () { + this.options.disabled = true; + this.container.addClass("rs-disabled"); + this._readOnly(true); + }, + enable: function () { + this.options.disabled = false; + this.container.removeClass("rs-disabled"); + if (!this.options.readOnly) this._readOnly(false); + }, + destroy: function () { + if (!this._getInstance()) return; + this._destroyControl(); + this._removeData(); + if (this._isInputType) this.control.remove(); + } + }; + + $.fn.rsRotate = function (degree) { + var control = this, rotation = "rotate(" + degree + "deg)"; + control.css('-webkit-transform', rotation); + control.css('-moz-transform', rotation); + control.css('-ms-transform', rotation); + control.css('-o-transform', rotation); + control.css('transform', rotation); + return control; + } + + // The plugin constructor + function RoundSlider(control, options) { + this.id = control.id; + this.control = $(control); + + // the options value holds the updated defaults value + this.options = $.extend({}, this.defaults, options); + } + + // The plugin wrapper, prevents multiple instantiations + function CreateRoundSlider(options, args) { + + for (var i = 0; i < this.length; i++) { + var that = this[i], instance = $.data(that, pluginName); + if (!instance) { + var _this = new RoundSlider(that, options); + _this._saveInstanceOnElement(); + _this._saveInstanceOnID(); + + if (_this._raise("beforeCreate") !== false) { + _this._init(); + _this._raise("create"); + } + else _this._removeData(); + } + else if ($.isPlainObject(options)) { + if (typeof instance.option === "function") instance.option(options); + else if (that.id && window[that.id] && typeof window[that.id].option === "function") { + window[that.id].option(options); + } + } + else if (typeof options === "string") { + if (typeof instance[options] === "function") { + if ((options === "option" || options.indexOf("get") === 0) && args[2] === undefined) { + return instance[options](args[1]); + } + instance[options](args[1], args[2]); + } + } + } + return this; + } + + // ### SVG related logic + RoundSlider.prototype.$polarToCartesian = function (centerXY, radius, angleInDegrees) { + var angleInRadians = (angleInDegrees - 180) * Math.PI / 180; + + return [ + centerXY + (radius * Math.cos(angleInRadians)), + centerXY + (radius * Math.sin(angleInRadians)) + ].join(" "); + } + + RoundSlider.prototype.$drawArc = function (centerXY, radius, startAngle, endAngle, isOuter) { + var isCircle = (endAngle - startAngle == 360); + var largeArcFlag = Math.abs(startAngle - endAngle) <= 180 ? "0" : "1"; + var isClockwise = true; + var outerDirection = isClockwise ? 1 : 0; + var innerDirection = isClockwise ? 0 : 1; + var direction = isOuter ? outerDirection : innerDirection; + var _endAngle = isOuter ? endAngle : startAngle; + + var path = []; + + // if it is a perfect circle then draw two half circles, otherwise draw arc + if (isCircle) { + var midAngle = (startAngle + endAngle) / 2; + var midPoint = this.$polarToCartesian(centerXY, radius, midAngle); + var endPoint = this.$polarToCartesian(centerXY, radius, _endAngle); + path.push( + "A", 1, 1, 0, 0, direction, midPoint, + "A", 1, 1, 0, 0, direction, endPoint + ); + } + else { + var endPoint = this.$polarToCartesian(centerXY, radius, _endAngle); + path.push( + "A", radius, radius, 0, largeArcFlag, direction, endPoint + ); + } + + return path.join(" "); + } + + RoundSlider.prototype.$drawPath = function (centerXY, outerRadius, startAngle, endAngle, innerRadius, lineCap) { + var outerStart = this.$polarToCartesian(centerXY, outerRadius, startAngle); + var outerArc = this.$drawArc(centerXY, outerRadius, startAngle, endAngle, true); // draw outer circle + + var d = [ + "M " + outerStart, + outerArc + ]; + + if (innerRadius) { + var innerEnd = this.$polarToCartesian(centerXY, innerRadius, endAngle); + var innerArc = this.$drawArc(centerXY, innerRadius, startAngle, endAngle, false); // draw inner circle + + if (lineCap == "none") { + d.push( + "M " + innerEnd, + innerArc + ); + } + else if (lineCap == "round") { + d.push( + "A 1, 1, 0, 0, 1, " + innerEnd, + innerArc, + "A 1, 1, 0, 0, 1, " + outerStart + ); + } + else if (lineCap == "butt" || lineCap == "square") { + d.push( + "L " + innerEnd, + innerArc, + "L " + outerStart, + "Z" + ); + } + } + return d.join(" "); + } + + RoundSlider.prototype.$getArcLength = function (radius, degree = 360) { + // when degree not provided we can consider that arc as a complete circle + // circle's arc length formula => 2πR(Θ/360) + return 2 * Math.PI * radius * (degree / 360); + } + + $.fn[pluginName].prototype = RoundSlider.prototype; + +})(jQuery, window); \ No newline at end of file diff --git a/lib/js/roundslider.min.js b/lib/js/roundslider.min.js deleted file mode 100644 index c1524533..00000000 --- a/lib/js/roundslider.min.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! roundSlider v1.3.3 | (c) 2015-2019, Soundar | MIT license | http://roundsliderui.com/licence.html */ -;(function($,window,undefined){"use strict";function RoundSlider(n,t){this.id=n.id,this.control=$(n),this.options=$.extend({},this.defaults,t)}function CreateRoundSlider(n,t){for(var i,r,u,f=0;f<this.length;f++)if(i=this[f],r=$.data(i,pluginName),r){if($.isPlainObject(n))typeof r.option=="function"?r.option(n):i.id&&window[i.id]&&typeof window[i.id].option=="function"&&window[i.id].option(n);else if(typeof n=="string"&&typeof r[n]=="function"){if((n==="option"||n.indexOf("get")===0)&&t[2]===undefined)return r[n](t[1]);r[n](t[1],t[2])}}else u=new RoundSlider(i,n),u._saveInstanceOnElement(),u._saveInstanceOnID(),u._raise("beforeCreate")!==!1?(u._init(),u._raise("create")):u._removeData();return this}var pluginName="roundSlider";$.fn[pluginName]=function(n){return CreateRoundSlider.call(this,n,arguments)},RoundSlider.prototype={pluginName:pluginName,version:"1.3.3",options:{},control:null,defaults:{min:0,max:100,step:1,value:null,radius:85,width:18,handleSize:"+0",startAngle:0,endAngle:"+360",animation:!0,showTooltip:!0,editableTooltip:!0,readOnly:!1,disabled:!1,keyboardAction:!0,mouseScrollAction:!1,lineCap:"square",sliderType:"default",circleShape:"full",handleShape:"round",beforeCreate:null,create:null,start:null,drag:null,change:null,stop:null,tooltipFormat:null},keys:{UP:38,DOWN:40,LEFT:37,RIGHT:39},_props:function(){return{numberType:["min","max","step","radius","width","startAngle"],booleanType:["animation","showTooltip","editableTooltip","readOnly","disabled","keyboardAction","mouseScrollAction"],stringType:["sliderType","circleShape","handleShape","lineCap"]}},_init:function(){var t,n;this._isBrowserSupport=this._isBrowserSupported(),this._isKO=!1,this._isAngular=!1,this.control.is("input")&&(this._isInputType=!0,this._hiddenField=this.control,this.control=this.$createElement("div"),this.control.insertAfter(this._hiddenField),this.options.value=this._hiddenField.val()||this.options.value,t=this,this._checkKO()&&setTimeout(function(){t._checkKO()},1),this._checkAngular()),this._bindOnDrag=!1,n=this._dataElement().attr("data-updateon"),typeof n=="string"?n=="drag"&&(this._bindOnDrag=!0):this._isAngular&&(this._bindOnDrag=!0),this._onInit()},_onInit:function(){this._initialize(),this._update(),this._render()},_initialize:function(){var n=this.browserName=this.getBrowserName();(n&&this.control.addClass("rs-"+n),this._isBrowserSupport)&&(this._isReadOnly=!1,this._checkDataType(),this._refreshCircleShape())},_render:function(){if(this.container=this.$createElement("div.rs-container"),this.innerContainer=this.$createElement("div.rs-inner-container"),this.block=this.$createElement("div.rs-block rs-outer rs-border"),this.container.append(this.innerContainer.append(this.block)),this.control.addClass("rs-control").empty().append(this.container),this._setRadius(),this._isBrowserSupport)this._createLayers(),this._setProperties(),this._setValue(),this._updateTooltipPos(),this._bindControlEvents("_bind");else{var n=this.$createElement("div.rs-msg");n.html(typeof this._throwError=="function"?this._throwError():this._throwError),this.control.empty().addClass("rs-error").append(n),this._isInputType&&this.control.append(this._dataElement())}},_update:function(){this._validateSliderType(),this._updateStartEnd(),this._validateStartEnd(),this._handle1=this._handle2=this._handleDefaults(),this._analyzeModelValue(),this._validateModelValue()},_createLayers:function(){var i=this.options.width,t=this._start,n;n=this.$createElement("div.rs-path rs-transition"),this._rangeSlider||this._showRange?(this.block1=n.clone().addClass("rs-range-color").rsRotate(t),this.block2=n.clone().addClass("rs-range-color").css("opacity","0").rsRotate(t),this.block3=n.clone().addClass("rs-path-color").rsRotate(t),this.block4=n.addClass("rs-path-color").css({opacity:"1","z-index":"1"}).rsRotate(t-180),this.block.append(this.block1,this.block2,this.block3,this.block4).addClass("rs-split")):this.block.append(n.addClass("rs-path-color")),this.lastBlock=this.$createElement("span.rs-block").css({padding:i}),this.innerBlock=this.$createElement("div.rs-inner rs-bg-color rs-border"),this.lastBlock.append(this.innerBlock),this.block.append(this.lastBlock),this._appendHandle(),this._appendOverlay(),this._appendHiddenField()},_setProperties:function(){this._updatePre(),this._setHandleShape(),this._addAnimation(),this._appendTooltip(),this.options.showTooltip||this._removeTooltip(),this.options.disabled?this.disable():this.options.readOnly&&this._readOnly(!0),this.options.mouseScrollAction&&this._bindScrollEvents("_bind")},_updatePre:function(){this._prechange=this._predrag=this.options.value},_setValue:function(){if(this._rangeSlider)this._setHandleValue(1),this._setHandleValue(2);else{this._showRange&&this._setHandleValue(1);var n=this.options.sliderType=="default"?this._active||1:parseFloat(this.bar.children().attr("index"));this._setHandleValue(n)}},_appendTooltip:function(){this.container.children(".rs-tooltip").length===0&&(this.tooltip=this.$createElement("span.rs-tooltip rs-tooltip-text"),this.container.append(this.tooltip),this._tooltipEditable(),this._updateTooltip())},_removeTooltip:function(){this.container.children(".rs-tooltip").length!=0&&this.tooltip&&this.tooltip.remove()},_tooltipEditable:function(){var i=this.options,n=this.tooltip,t;n&&i.showTooltip&&(i.editableTooltip?(n.addClass("edit"),t="_bind"):(n.removeClass("edit"),t="_unbind"),this[t](n,"click",this._editTooltip))},_editTooltip:function(){var n=this.tooltip,i,t;n.hasClass("edit")&&!this._isReadOnly&&(i=parseFloat(n.css("border-left-width"))*2,t=this.input=this.$createElement("input.rs-input rs-tooltip-text").css({height:n.outerHeight()-i,width:n.outerWidth()-i}),n.html(t).removeClass("edit").addClass("hover"),t.focus().val(this._getTooltipValue(!0)),this._bind(t,"blur",this._focusOut),this._bind(t,"change",this._focusOut))},_focusOut:function(n){if(n.type=="change"){var t=this.input.val().replace("-",",");t[0]==","&&(t="-"+t.slice(1).replace("-",",")),this.options.value=t,this._analyzeModelValue(),this._validateModelValue(),this._setValue(),this.input.val(this._getTooltipValue(!0))}else this.tooltip.addClass("edit").removeClass("hover"),this._updateTooltip();this._raiseEvent("change")},_setHandleShape:function(){var t=this.options.handleShape,n=this._handles();n.removeClass("rs-handle-dot rs-handle-square"),t=="dot"?n.addClass("rs-handle-dot"):t=="square"?n.addClass("rs-handle-square"):this.options.handleShape="round"},_setHandleValue:function(n){this._active=n;var t=this["_handle"+n];this.options.sliderType!="min-range"&&(this.bar=this._activeHandleBar()),this._changeSliderValue(t.value,t.angle)},_setAnimation:function(){this.options.animation?this._addAnimation():this._removeAnimation()},_addAnimation:function(){this.options.animation&&this.control.addClass("rs-animation")},_removeAnimation:function(){this.control.removeClass("rs-animation")},_setRadius:function(){var t=this.options.radius,i=t*2,n=this.options.circleShape,r=i,u=i,f,e;if(this.container.removeClass().addClass("rs-container"),n.indexOf("half")===0){switch(n){case"half-top":case"half-bottom":r=t,u=i;break;case"half-left":case"half-right":r=i,u=t}this.container.addClass(n.replace("half-","")+" half")}else n.indexOf("quarter")===0?(r=u=t,f=n.split("-"),this.container.addClass(f[0]+" "+f[1]+" "+f[2])):this.container.addClass("full "+n);e={height:r,width:u},this.control.css(e),this.container.css(e)},_border:function(n){return n?parseFloat(this._startLine.children().css("border-bottom-width")):parseFloat(this.block.css("border-top-width"))*2},_appendHandle:function(){(this._rangeSlider||!this._showRange)&&this._createHandle(1),(this._rangeSlider||this._showRange)&&this._createHandle(2),this._startLine=this._addSeperator(this._start,"rs-start"),this._endLine=this._addSeperator(this._start+this._end,"rs-end"),this._refreshSeperator()},_addSeperator:function(n,t){var r=this.$createElement("span.rs-seperator rs-border"),u=this.options.width,f=this._border(),i=this.$createElement("span.rs-bar rs-transition "+t).append(r).rsRotate(n);return this.container.append(i),i},_refreshSeperator:function(){var i=this._startLine.add(this._endLine),r=i.children().removeAttr("style"),n=this.options,u=n.width,f=this._border(),t=u+f;n.lineCap=="round"&&n.circleShape!="full"?(i.addClass("rs-rounded"),r.css({width:t,height:t/2+1}),this._startLine.children().css("margin-top",-1).addClass(n.sliderType=="min-range"?"rs-range-color":"rs-path-color"),this._endLine.children().css("margin-top",t/-2).addClass("rs-path-color")):(i.removeClass("rs-rounded"),r.css({width:t,"margin-top":this._border(!0)/-2}).removeClass("rs-range-color rs-path-color"))},_updateSeperator:function(){this._startLine.rsRotate(this._start),this._endLine.rsRotate(this._start+this._end)},_createHandle:function(n){var t=this.$createElement("div.rs-handle rs-move"),u=this.options,f,r;(f=u.handleShape)!="round"&&t.addClass("rs-handle-"+f),t.attr({index:n,tabIndex:"0"});var i=this._dataElement()[0].id,i=i?i+"_":"",e=i+"handle"+(u.sliderType=="range"?"_"+(n==1?"start":"end"):"");return t.attr({role:"slider","aria-label":e}),r=this.$createElement("div.rs-bar rs-transition").css("z-index","7").append(t).rsRotate(this._start),r.addClass(u.sliderType=="range"&&n==2?"rs-second":"rs-first"),this.container.append(r),this._refreshHandle(),this.bar=r,this._active=n,n!=1&&n!=2&&(this["_handle"+n]=this._handleDefaults()),this._bind(t,"focus",this._handleFocus),this._bind(t,"blur",this._handleBlur),t},_refreshHandle:function(){var o=this.options,hSize=o.handleSize,width=o.width,h,w,isSquare=!0,isNumber=this.isNumber,s,diff;if(typeof hSize=="string"&&isNumber(hSize))if(hSize.charAt(0)==="+"||hSize.charAt(0)==="-")try{hSize=eval(width+hSize.charAt(0)+Math.abs(parseFloat(hSize)))}catch(e){console.warn(e)}else hSize.indexOf(",")&&(s=hSize.split(","),isNumber(s[0])&&isNumber(s[1])&&(w=parseFloat(s[0]),h=parseFloat(s[1]),isSquare=!1));isSquare&&(h=w=isNumber(hSize)?parseFloat(hSize):width),diff=(width+this._border()-w)/2,this._handles().css({height:h,width:w,margin:-h/2+"px 0 0 "+diff+"px"})},_handleDefaults:function(){var n=this.options.min;return{angle:this._valueToAngle(n),value:n}},_handles:function(){return this.container.children("div.rs-bar").find(".rs-handle")},_activeHandleBar:function(n){return n=n!=undefined?n:this._active,$(this.container.children("div.rs-bar")[n-1])},_handleArgs:function(n){n=n!=undefined?n:this._active;var t=this["_handle"+n];return{element:this._activeHandleBar(n).children(),index:n,isActive:n==this._active,value:t?t.value:null,angle:t?t.angle:null}},_dataElement:function(){return this._isInputType?this._hiddenField:this.control},_raiseEvent:function(n){var i=this["_pre"+n],t=this.options.value;if(i!==t)return this["_pre"+n]=t,n=="change"&&this._updatePre(),this._updateTooltip(),(n=="change"||this._bindOnDrag&&n=="drag")&&this._updateHidden(),this._raise(n,{value:t,preValue:i,handle:this._handleArgs()})},_elementDown:function(n){var u,t,h,i,r;if(!this._isReadOnly)if(u=$(n.target),u.hasClass("rs-handle"))this._handleDown(n);else{var f=this._getXY(n),e=this._getCenterPoint(),o=this._getDistance(f,e),s=this.block.outerWidth()/2,c=s-(this.options.width+this._border());o>=c&&o<=s&&(n.preventDefault(),t=this.control.find(".rs-handle.rs-focus"),this.control.attr("tabindex","0").focus().removeAttr("tabindex"),r=this._getAngleValue(f,e),h=r.angle,i=r.value,this._rangeSlider&&(t=this.control.find(".rs-handle.rs-focus"),this._active=t.length==1?parseFloat(t.attr("index")):this._handle2.value-i<i-this._handle1.value?2:1,this.bar=this._activeHandleBar()),this._changeSliderValue(i,h),this._raiseEvent("change"))}},_handleDown:function(n){n.preventDefault();var t=$(n.target);t.focus(),this._removeAnimation(),this._bindMouseEvents("_bind"),this.bar=t.parent(),this._active=parseFloat(t.attr("index")),this._handles().removeClass("rs-move"),this._raise("start",{value:this.options.value,handle:this._handleArgs()})},_handleMove:function(n){n.preventDefault();var u=this._getXY(n),f=this._getCenterPoint(),t=this._getAngleValue(u,f,!0),i,r;i=t.angle,r=t.value,this._changeSliderValue(r,i),this._raiseEvent("drag")},_handleUp:function(){this._handles().addClass("rs-move"),this._bindMouseEvents("_unbind"),this._addAnimation(),this._raiseEvent("change"),this._raise("stop",{value:this.options.value,handle:this._handleArgs()})},_handleFocus:function(n){if(!this._isReadOnly){var t=$(n.target);this._handles().removeClass("rs-focus"),t.addClass("rs-focus"),this.bar=t.parent(),this._active=parseFloat(t.attr("index")),this.options.keyboardAction&&(this._bindKeyboardEvents("_unbind"),this._bindKeyboardEvents("_bind")),this.control.find("div.rs-bar").css("z-index","7"),this.bar.css("z-index","8")}},_handleBlur:function(){this._handles().removeClass("rs-focus"),this.options.keyboardAction&&this._bindKeyboardEvents("_unbind")},_handleKeyDown:function(n){var t,r,u,i,f;this._isReadOnly||(t=n.keyCode,r=this.keys,t==27&&this._handles().blur(),t>=35&&t<=40)&&(t>=37&&t<=40&&this._removeAnimation(),u=this["_handle"+this._active],n.preventDefault(),t==r.UP||t==r.RIGHT?i=this._round(this._limitValue(u.value+this.options.step)):t==r.DOWN||t==r.LEFT?i=this._round(this._limitValue(u.value-this._getMinusStep(u.value))):t==36?i=this._getKeyValue("Home"):t==35&&(i=this._getKeyValue("End")),f=this._valueToAngle(i),this._changeSliderValue(i,f),this._raiseEvent("drag"))},_handleKeyUp:function(){this._addAnimation(),this._raiseEvent("change")},_getMinusStep:function(n){var t=this.options,f=t.min,u=t.max,i=t.step,r;return n==u?(r=(u-f)%i,r==0?i:r):i},_getKeyValue:function(n){var t=this.options,i=t.min,r=t.max;return this._rangeSlider?n=="Home"?this._active==1?i:this._handle1.value:this._active==1?this._handle2.value:r:n=="Home"?i:r},_elementScroll:function(n){if(!this._isReadOnly){n.preventDefault();var i=n.originalEvent||n,r,t,f,u;(u=i.wheelDelta?i.wheelDelta/60:i.detail?-i.detail/2:0,u!=0)&&(this._updateActiveHandle(n),r=this["_handle"+this._active],t=r.value+(u>0?this.options.step:-this._getMinusStep(r.value)),t=this._limitValue(t),f=this._valueToAngle(t),this._removeAnimation(),this._changeSliderValue(t,f),this._raiseEvent("change"),this._addAnimation())}},_updateActiveHandle:function(n){var t=$(n.target);t.hasClass("rs-handle")&&t.parent().parent()[0]==this.control[0]&&(this.bar=t.parent(),this._active=parseFloat(t.attr("index"))),this.bar.find(".rs-handle").hasClass("rs-focus")||this.bar.find(".rs-handle").focus()},_bindControlEvents:function(n){this[n](this.control,"mousedown",this._elementDown),this[n](this.control,"touchstart",this._elementDown)},_bindScrollEvents:function(n){this[n](this.control,"mousewheel",this._elementScroll),this[n](this.control,"DOMMouseScroll",this._elementScroll)},_bindMouseEvents:function(n){this[n]($(document),"mousemove",this._handleMove),this[n]($(document),"mouseup",this._handleUp),this[n]($(document),"mouseleave",this._handleUp),this[n]($(document),"touchmove",this._handleMove),this[n]($(document),"touchend",this._handleUp),this[n]($(document),"touchcancel",this._handleUp)},_bindKeyboardEvents:function(n){this[n]($(document),"keydown",this._handleKeyDown),this[n]($(document),"keyup",this._handleKeyUp)},_changeSliderValue:function(n,t){var u=this._oriAngle(t),i=this._limitAngle(t);if(this._rangeSlider||this._showRange){if(this._active==1&&u<=this._oriAngle(this._handle2.angle)||this._active==2&&u>=this._oriAngle(this._handle1.angle)||this._invertRange){this["_handle"+this._active]={angle:t,value:n},this.options.value=this._rangeSlider?this._handle1.value+","+this._handle2.value:n,this.bar.rsRotate(i),this._updateARIA(n);var r=this._oriAngle(this._handle2.angle)-this._oriAngle(this._handle1.angle),f="1",e="0";r<=180&&!(r<0&&r>-180)&&(f="0",e="1"),this.block2.css("opacity",f),this.block3.css("opacity",e),(this._active==1?this.block4:this.block2).rsRotate(i-180),(this._active==1?this.block1:this.block3).rsRotate(i)}}else this["_handle"+this._active]={angle:t,value:n},this.options.value=n,this.bar.rsRotate(i),this._updateARIA(n)},_updateARIA:function(n){var i=this.options,r=i.min,u=i.max,t;this.bar.children().attr({"aria-valuenow":n}),i.sliderType=="range"?(t=this._handles(),t.eq(0).attr({"aria-valuemin":r}),t.eq(1).attr({"aria-valuemax":u}),this._active==1?t.eq(1).attr({"aria-valuemin":n}):t.eq(0).attr({"aria-valuemax":n})):this.bar.children().attr({"aria-valuemin":r,"aria-valuemax":u})},_checkKO:function(){var f=this._dataElement().data("bind"),t,i,r,n,u;if(typeof f=="string"&&typeof ko=="object"){if(t=ko.dataFor(this._dataElement()[0]),typeof t=="undefined")return!0;for(i=f.split(","),n=0;n<i.length;n++)if(u=i[n].split(":"),$.trim(u[0])=="value"){r=$.trim(u[1]);break}r&&(this._isKO=!0,ko.computed(function(){this.option("value",t[r]())},this))}},_checkAngular:function(){if(typeof angular=="object"&&typeof angular.element=="function"&&(this._ngName=this._dataElement().attr("ng-model"),typeof this._ngName=="string")){this._isAngular=!0;var n=this;this._scope().$watch(this._ngName,function(t){n.option("value",t)})}},_scope:function(){return angular.element(this._dataElement()).scope()},_getDistance:function(n,t){return Math.sqrt((n.x-t.x)*(n.x-t.x)+(n.y-t.y)*(n.y-t.y))},_getXY:function(n){return n.type.indexOf("mouse")==-1&&(n=(n.originalEvent||n).changedTouches[0]),{x:n.pageX,y:n.pageY}},_getCenterPoint:function(){var n=this.block.offset();return{x:n.left+this.block.outerWidth()/2,y:n.top+this.block.outerHeight()/2}},_getAngleValue:function(n,t,i){var u=Math.atan2(n.y-t.y,t.x-n.x),r=-u/(Math.PI/180);return r<this._start&&(r+=360),r=this._checkAngle(r,i),this._processStepByAngle(r)},_checkAngle:function(n,t){var f=this._oriAngle(n),i=this["_handle"+this._active].angle,r=this._oriAngle(i),u;if(f>this._end){if(!t)return i;n=this._start+(r<=this._end-r?0:this._end)}else if(t&&(u=this._handleDragDistance,this.isNumber(u)&&Math.abs(f-r)>u))return i;return n},_processStepByAngle:function(n){var t=this._angleToValue(n);return this._processStepByValue(t)},_processStepByValue:function(n){var e=this.options,o=e.min,l=e.max,i=e.step,s=o>l,h,t,r,u,f,c;return i=s?-i:i,h=(n-o)%i,t=n-h,r=this._limitValue(t+i),u=this._limitValue(t-i),f=s?n<=t?t-n<n-r?t:r:n-t>u-n?t:u:n>=t?n-t<r-n?t:r:t-n>n-u?t:u,f=this._round(f),c=this._valueToAngle(f),{value:f,angle:c}},_round:function(n){var t=this.options.step.toString().split(".");return t[1]?parseFloat(n.toFixed(t[1].length)):Math.round(n)},_oriAngle:function(n){var t=n-this._start;return t<0&&(t+=360),t},_limitAngle:function(n){return n>360+this._start&&(n-=360),n<this._start&&(n+=360),n},_limitValue:function(n){var u=this.options,t=u.min,i=u.max,r=t>i;return(!r&&n<t||r&&n>t)&&(n=t),(!r&&n>i||r&&n<i)&&(n=i),n},_angleToValue:function(n){var t=this.options,i=t.min,r=t.max;return this._oriAngle(n)/this._end*(r-i)+i},_valueToAngle:function(n){var t=this.options,i=t.min,r=t.max;return(n-i)/(r-i)*this._end+this._start},_appendHiddenField:function(){this._hiddenField=this._hiddenField||this.$createElement("input"),this._hiddenField.attr({type:"hidden",name:this._dataElement()[0].id||""}),this.control.append(this._hiddenField),this._updateHidden()},_updateHidden:function(){var n=this.options.value;this._hiddenField.val(n),(this._isKO||this._isAngular)&&this._hiddenField.trigger("change"),this._isAngular&&(this._scope()[this._ngName]=n)},_updateTooltip:function(){this.tooltip&&!this.tooltip.hasClass("hover")&&this.tooltip.html(this._getTooltipValue()),this._updateTooltipPos()},_updateTooltipPos:function(){this.tooltip&&this.tooltip.css(this._getTooltipPos())},_getTooltipPos:function(){var n=this.options.circleShape,t,i=this.tooltip.outerHeight(),r=this.tooltip.outerWidth();if(n=="full"||n=="pie"||n.indexOf("custom")===0)return{"margin-top":-i/2,"margin-left":-r/2};if(n.indexOf("half")!=-1){switch(n){case"half-top":case"half-bottom":t={"margin-left":-r/2};break;case"half-left":case"half-right":t={"margin-top":-i/2}}return t}return{}},_getTooltipValue:function(n){var i=this.options.value,t;return this._rangeSlider?(t=i.split(","),n)?t[0]+" - "+t[1]:this._tooltipValue(t[0],1)+" - "+this._tooltipValue(t[1],2):n?i:this._tooltipValue(i)},_tooltipValue:function(n,t){var i=this._raise("tooltipFormat",{value:n,handle:this._handleArgs(t)});return i!=null&&typeof i!="boolean"?i:n},_validateStartAngle:function(){var n=this.options.startAngle;return n=(this.isNumber(n)?parseFloat(n):0)%360,n<0&&(n+=360),this.options.startAngle=n,n},_validateEndAngle:function(){var o=this.options,start=o.startAngle,end=o.endAngle;if(typeof end=="string"&&this.isNumber(end)&&(end.charAt(0)==="+"||end.charAt(0)==="-"))try{end=eval(start+end.charAt(0)+Math.abs(parseFloat(end)))}catch(e){console.warn(e)}return end=(this.isNumber(end)?parseFloat(end):360)%360,end<=start&&(end+=360),end},_refreshCircleShape:function(){var n=this.options.circleShape,i=["half-top","half-bottom","half-left","half-right","quarter-top-left","quarter-top-right","quarter-bottom-right","quarter-bottom-left","pie","custom-half","custom-quarter"],t;i.indexOf(n)==-1&&(t=["h1","h2","h3","h4","q1","q2","q3","q4","3/4","ch","cq"].indexOf(n),n=t!=-1?i[t]:n=="half"?"half-top":n=="quarter"?"quarter-top-left":"full"),this.options.circleShape=n},_appendOverlay:function(){var n=this.options.circleShape;n=="pie"?this._checkOverlay(".rs-overlay",270):(n=="custom-half"||n=="custom-quarter")&&(this._checkOverlay(".rs-overlay1",180),n=="custom-quarter"&&this._checkOverlay(".rs-overlay2",this._end))},_checkOverlay:function(n,t){var i=this.container.children(n);i.length==0&&(i=this.$createElement("div"+n+" rs-transition rs-bg-color"),this.container.append(i)),i.rsRotate(this._start+t)},_checkDataType:function(){var i=this.options,r,n,t,u=this._props();for(r in u.numberType)n=u.numberType[r],t=i[n],i[n]=this.isNumber(t)?parseFloat(t):this.defaults[n];for(r in u.booleanType)n=u.booleanType[r],t=i[n],i[n]=t=="false"?!1:!!t;for(r in u.stringType)n=u.stringType[r],t=i[n],i[n]=(""+t).toLowerCase()},_validateSliderType:function(){var n=this.options.sliderType.toLowerCase();this._rangeSlider=this._showRange=!1,n=="range"?this._rangeSlider=this._showRange=!0:n.indexOf("min")!=-1?(this._showRange=!0,n="min-range"):n="default",this.options.sliderType=n},_updateStartEnd:function(){var r=this.options,n=r.circleShape,t=r.startAngle,i=r.endAngle;n!="full"&&(n.indexOf("quarter")!=-1?i="+90":n.indexOf("half")!=-1?i="+180":n=="pie"&&(i="+270"),this.options.endAngle=i,n=="quarter-top-left"||n=="half-top"?t=0:n=="quarter-top-right"||n=="half-right"?t=90:n=="quarter-bottom-right"||n=="half-bottom"?t=180:(n=="quarter-bottom-left"||n=="half-left")&&(t=270),this.options.startAngle=t)},_validateStartEnd:function(){this._start=this._validateStartAngle(),this._end=this._validateEndAngle();var n=this._start<this._end?0:360;this._end+=n-this._start},_analyzeModelValue:function(){var f=this.options,n=f.value,t=f.min,s=f.max,e,u,r=this.isNumber,o=typeof n=="string",i;n instanceof Array&&(n=n.toString()),i=o?n.split(","):[n],this._rangeSlider?u=o?i.length>=2?(r(i[0])?i[0]:t)+","+(r(i[1])?i[1]:s):r(i[0])?t+","+i[0]:t+","+t:r(n)?t+","+n:t+","+t:o?(e=i.pop(),u=r(e)?parseFloat(e):t):u=r(n)?parseFloat(n):t,this.options.value=u},_validateModelValue:function(){var r=this.options.value,i;if(this._rangeSlider){var u=r.split(","),n=parseFloat(u[0]),t=parseFloat(u[1]);n=this._limitValue(n),t=this._limitValue(t),this._invertRange||n>t&&(t=n),this._handle1=this._processStepByValue(n),this._handle2=this._processStepByValue(t),this.options.value=this._handle1.value+","+this._handle2.value}else i=this._showRange?2:this._active||1,this["_handle"+i]=this._processStepByValue(this._limitValue(r)),this._showRange&&(this._handle1=this._handleDefaults()),this.options.value=this["_handle"+i].value},$createElement:function(n){var t=n.split(".");return $(document.createElement(t[0])).addClass(t[1]||"")},isNumber:function(n){return n=parseFloat(n),typeof n=="number"&&!isNaN(n)},getBrowserName:function(){var n="",t=window.navigator.userAgent;return!!window.opr&&!!opr.addons||!!window.opera||t.indexOf(" OPR/")>=0?n="opera":typeof InstallTrigger!="undefined"?n="firefox":t.indexOf("MSIE ")>0||t.indexOf("Trident/")>0?n="ie":window.StyleMedia?n="edge":t.indexOf("Safari")!=-1&&t.indexOf("Chrome")==-1?n="safari":(!window.chrome||!window.chrome.webstore)&&t.indexOf("Chrome")==-1||(n="chrome"),n},_isBrowserSupported:function(){for(var t=["borderRadius","WebkitBorderRadius","MozBorderRadius","OBorderRadius","msBorderRadius","KhtmlBorderRadius"],n=0;n<t.length;n++)if(document.body.style[t[n]]!==undefined)return!0},_throwError:function(){return"This browser doesn't support the border-radious property."},_raise:function(n,t){var u=this.options,i=u[n],r=!0;return t=t||{value:u.value},t.id=this.id,t.control=this.control,t.options=u,i&&(t.type=n,typeof i=="string"&&(i=window[i]),$.isFunction(i)&&(r=i.call(this,t),r=r===!1?!1:r)),this.control.trigger($.Event(n,t)),r},_bind:function(n,t,i){$(n).bind(t,$.proxy(i,this))},_unbind:function(n,t,i){$(n).unbind(t,$.proxy(i,this))},_getInstance:function(){return $.data(this._dataElement()[0],pluginName)},_saveInstanceOnElement:function(){$.data(this.control[0],pluginName,this)},_saveInstanceOnID:function(){var n=this.id;n&&typeof window[n]!="undefined"&&(window[n]=this)},_removeData:function(){var n=this._dataElement()[0];$.removeData&&$.removeData(n,pluginName),n.id&&typeof window[n.id]._init=="function"&&delete window[n.id]},_destroyControl:function(){this._isInputType&&this._dataElement().insertAfter(this.control).attr("type","text"),this.control.empty().removeClass("rs-control").height("").width(""),this._removeAnimation(),this._bindControlEvents("_unbind")},_updateWidth:function(){this.lastBlock.css("padding",this.options.width),this._refreshHandle()},_readOnly:function(n){this._isReadOnly=n,this.container.removeClass("rs-readonly"),n&&this.container.addClass("rs-readonly")},_get:function(n){return this.options[n]},_set:function(n,t){var i=this._props();if($.inArray(n,i.numberType)!=-1){if(!this.isNumber(t))return;t=parseFloat(t)}else $.inArray(n,i.booleanType)!=-1?t=t=="false"?!1:!!t:$.inArray(n,i.stringType)!=-1&&(t=t.toLowerCase());if(this.options[n]!=t){this.options[n]=t;switch(n){case"startAngle":case"endAngle":this._validateStartEnd(),this._updateSeperator(),this._appendOverlay();case"min":case"max":case"step":case"value":this._analyzeModelValue(),this._validateModelValue(),this._setValue(),this._updatePre(),this._updateHidden(),this._updateTooltip();break;case"radius":this._setRadius(),this._updateTooltipPos();break;case"width":this._removeAnimation(),this._updateWidth(),this._updateTooltipPos(),this._addAnimation(),this._refreshSeperator();break;case"handleSize":this._refreshHandle();break;case"handleShape":this._setHandleShape();break;case"animation":this._setAnimation();break;case"showTooltip":this.options.showTooltip?this._appendTooltip():this._removeTooltip();break;case"editableTooltip":this._tooltipEditable(),this._updateTooltipPos();break;case"disabled":this.options.disabled?this.disable():this.enable();break;case"readOnly":this.options.readOnly?this._readOnly(!0):!this.options.disabled&&this._readOnly(!1);break;case"mouseScrollAction":this._bindScrollEvents(this.options.mouseScrollAction?"_bind":"_unbind");break;case"lineCap":this._refreshSeperator();break;case"circleShape":this._refreshCircleShape(),this.options.circleShape=="full"&&(this.options.startAngle=0,this.options.endAngle="+360");case"sliderType":this._destroyControl(),this._onInit()}return this}},option:function(n,t){if(this._getInstance()&&this._isBrowserSupport){if($.isPlainObject(n)){(n.min!==undefined||n.max!==undefined)&&(n.min!==undefined&&(this.options.min=n.min,delete n.min),n.max!==undefined&&(this.options.max=n.max,delete n.max),n.value==undefined&&this._set("value",this.options.value));for(var i in n)this._set(i,n[i])}else if(n&&typeof n=="string"){if(t===undefined)return this._get(n);this._set(n,t)}return this}},getValue:function(n){if(this.options.sliderType=="range"&&this.isNumber(n)){var t=parseFloat(n);if(t==1||t==2)return this["_handle"+t].value}return this._get("value")},setValue:function(n,t){var i,r,u;this.isNumber(n)&&(this.isNumber(t)&&(i=this.options.sliderType,i=="range"?(r=parseFloat(t),u=parseFloat(n),r==1?n=u+","+this._handle2.value:r==2&&(n=this._handle1.value+","+u)):i=="default"&&(this._active=t)),this._set("value",n))},disable:function(){this.options.disabled=!0,this.container.addClass("rs-disabled"),this._readOnly(!0)},enable:function(){this.options.disabled=!1,this.container.removeClass("rs-disabled"),this.options.readOnly||this._readOnly(!1)},destroy:function(){this._getInstance()&&(this._destroyControl(),this._removeData(),this._isInputType&&this.control.remove())}},$.fn.rsRotate=function(n){var t=this,i="rotate("+n+"deg)";return t.css("-webkit-transform",i),t.css("-moz-transform",i),t.css("-ms-transform",i),t.css("-o-transform",i),t.css("transform",i),t},$.fn[pluginName].prototype=RoundSlider.prototype})(jQuery,window); \ No newline at end of file diff --git a/manifest.json b/manifest.json index f33f5274..7fa9881b 100644 --- a/manifest.json +++ b/manifest.json @@ -2,14 +2,13 @@ "manifest_version": 2, "name": "Schoology Plus", "short_name": "Schoology+", - "description": "Schoology Plus enhances your LAUSD Schoology experience with numerous interface improvements", + "description": "Schoology Plus enhances your Schoology experience with numerous interface improvements", "applications": { "gecko": { - "id": "schoology.plus@aopell.me", - "update_url": "https://aopell.me/SchoologyPlus/update.json" + "id": "schoology.plus@aopell.me" } }, - "version": "6.0", + "version": "6.1", "icons": { "128": "imgs/icon@128.png", "64": "imgs/icon@64.png", @@ -44,9 +43,10 @@ "default-icons.html", "lib/data/schoology-reorder-ui-langprops.json" ], - "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'", + "content_security_policy": "script-src 'self'; object-src 'self'", "background": { "scripts": [ + "lib/js/purify.min.js", "js/background.js" ], "persistent": true diff --git a/theme-editor.html b/theme-editor.html index bd1fa5b1..3244647a 100644 --- a/theme-editor.html +++ b/theme-editor.html @@ -561,7 +561,7 @@ <h6 style="line-height: 24px; vertical-align: middle;"><i </main> <script src="/lib/js/materialize.min.js"></script> <script src="/lib/js/jquery-ui.min.js"></script> - <script src="/lib/js/roundslider.min.js"></script> + <script src="/lib/js/roundslider.js"></script> <script src="/js/icons.js"></script> <script src="/js/theme-model.js"></script> <script src="/js/default-themes.js"></script> diff --git a/themes/README.md b/themes/README.md index 13b4d7b8..51a396af 100644 --- a/themes/README.md +++ b/themes/README.md @@ -109,7 +109,7 @@ A Schoology Plus theme has the following format and components (each component w |Key|`hue` |Value Type|`number`| |Description|The theme's HSL color hue, used to color the interface by modifying the saturation and lightness values. The default Schoology Plus theme uses hue 210, and this value will be used by default if no color definitions are present. -|Value Restrictions|Hues are integers between 0 and 359, however decimal numbers still work and numbers over 359 are subject to a modus of 360 (i.e. actual hue value will be `providedHueValue % 360`). +|Value Restrictions|Hues are integers between 0 and 359, however decimal numbers still work and numbers over 359 are subject to a modulus of 360 (i.e. actual hue value will be `providedHueValue % 360`). |Special Notes|N/A| **Example** ```json @@ -243,7 +243,7 @@ A Schoology Plus theme has the following format and components (each component w |Default Value|Schoology Plus default course icon set |Description|An array of two-key objects, where the key `regex` is a regular expression and the key `url` is an image URL to be used as an icon for courses with names matching the regular expression. |Value Restrictions|An array of objects where all values of `regex` keys are valid regular expressions and all values of `url` keys are direct image links. Images should be square and at least `32x32` in size, but this is not required. -|Special Notes|Course names are checked against regular expressions in array order, meaning the regexes in the objects with lower indecies in the array are checked first *in a non-case-sensitive manner*. If no regular expression matches a specific course, Schoology Plus will fallback to the default Schoology Plus icon set. If you want to prevent this behavior, add an entry such as `".": "https://example.com/my-image.png"` that will match all course titles. +|Special Notes|Course names are checked against regular expressions in array order, meaning the regexes in the objects with lower indices in the array are checked first *in a non-case-sensitive manner*. If no regular expression matches a specific course, Schoology Plus will fallback to the default Schoology Plus icon set. If you want to prevent this behavior, add an entry such as `{ regex: ".", url: "https://example.com/my-image.png" }` that will match all course titles. **Example** ```json "icons": [