├── src ├── Chat │ ├── Global.asax │ ├── assets │ │ ├── img │ │ │ ├── bg.jpg │ │ │ ├── favicon.png │ │ │ ├── github_normal.png │ │ │ ├── no-profile64.png │ │ │ ├── twitter_normal.png │ │ │ └── facebook_normal.png │ │ ├── css │ │ │ └── default.css │ │ └── js │ │ │ └── jquery-3.2.1.min.js │ ├── ie │ │ ├── console.min.js │ │ ├── eventsource.min.js │ │ └── es5-shim.min.js │ ├── _layout.html │ ├── Properties │ │ ├── PublishProfiles │ │ │ └── WebDeploy.pubxml │ │ └── AssemblyInfo.cs │ ├── Web.config │ ├── default.css │ ├── Chat.csproj │ ├── index.html │ └── Global.asax.cs ├── NuGet.Config └── Chat.sln ├── .gitignore └── README.md /src/Chat/Global.asax: -------------------------------------------------------------------------------- 1 | <%@ Application Codebehind="Global.asax.cs" Inherits="Chat.Global" Language="C#" %> 2 | -------------------------------------------------------------------------------- /src/Chat/assets/img/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ServiceStackApps/Chat/HEAD/src/Chat/assets/img/bg.jpg -------------------------------------------------------------------------------- /src/Chat/assets/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ServiceStackApps/Chat/HEAD/src/Chat/assets/img/favicon.png -------------------------------------------------------------------------------- /src/Chat/assets/img/github_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ServiceStackApps/Chat/HEAD/src/Chat/assets/img/github_normal.png -------------------------------------------------------------------------------- /src/Chat/assets/img/no-profile64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ServiceStackApps/Chat/HEAD/src/Chat/assets/img/no-profile64.png -------------------------------------------------------------------------------- /src/Chat/assets/img/twitter_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ServiceStackApps/Chat/HEAD/src/Chat/assets/img/twitter_normal.png -------------------------------------------------------------------------------- /src/Chat/assets/img/facebook_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ServiceStackApps/Chat/HEAD/src/Chat/assets/img/facebook_normal.png -------------------------------------------------------------------------------- /src/NuGet.Config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/Chat/ie/console.min.js: -------------------------------------------------------------------------------- 1 | (function(n){"use strict";n.console=n.console||{};for(var t=n.console,i,r,u={},f=function(){},e="memory".split(","),o="assert,clear,count,debug,dir,dirxml,error,exception,group,groupCollapsed,groupEnd,info,log,markTimeline,profile,profiles,profileEnd,show,table,time,timeEnd,timeline,timelineEnd,timeStamp,trace,warn".split(",");i=e.pop();)t[i]||(t[i]=u);while(r=o.pop())t[r]||(t[r]=f)})(typeof window=="undefined"?this:window); -------------------------------------------------------------------------------- /src/Chat/_layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ title }} 5 | 6 | 7 | {{#if debug}}{{/if}} 8 | 9 | 10 | 11 | 16 | 17 | 18 | 19 | {{ page }} 20 | 21 | {{ scripts | raw }} 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Chat.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Chat", "Chat\Chat.csproj", "{724864E0-3970-4207-9D52-0C1A5DB15F31}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {724864E0-3970-4207-9D52-0C1A5DB15F31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {724864E0-3970-4207-9D52-0C1A5DB15F31}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {724864E0-3970-4207-9D52-0C1A5DB15F31}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {724864E0-3970-4207-9D52-0C1A5DB15F31}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /src/Chat/Properties/PublishProfiles/WebDeploy.pubxml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | MSDeploy 9 | Release 10 | Any CPU 11 | http://chat.servicestack.net 12 | True 13 | False 14 | netfx.servicestack.net 15 | Chat 16 | 17 | True 18 | WMSVC 19 | True 20 | deploy 21 | <_SavePWD>True 22 | False 23 | 24 | -------------------------------------------------------------------------------- /src/Chat/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Chat")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Chat")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("25350267-3f5b-4c45-8428-a634902e0e03")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Revision and Build Numbers 33 | // by using the '*' as shown below: 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 2 | [Bb]in/ 3 | [Oo]bj/ 4 | .idea/ 5 | 6 | # mstest test results 7 | TestResults 8 | 9 | ## Ignore Visual Studio temporary files, build results, and 10 | ## files generated by popular Visual Studio add-ons. 11 | 12 | # User-specific files 13 | *.suo 14 | *.user 15 | *.sln.docstates 16 | .vs/ 17 | *.secrets.cs 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Rr]elease/ 22 | deploy/ 23 | x64/ 24 | *_i.c 25 | *_p.c 26 | *.ilk 27 | *.meta 28 | *.obj 29 | *.pch 30 | *.pdb 31 | *.pgc 32 | *.pgd 33 | *.rsp 34 | *.sbr 35 | *.tlb 36 | *.tli 37 | *.tlh 38 | *.tmp 39 | *.log 40 | *.txt 41 | *.vspscc 42 | *.vssscc 43 | .builds 44 | 45 | # Visual C++ cache files 46 | ipch/ 47 | *.aps 48 | *.ncb 49 | *.opensdf 50 | *.sdf 51 | 52 | # Visual Studio profiler 53 | *.psess 54 | *.vsp 55 | *.vspx 56 | 57 | # Guidance Automation Toolkit 58 | *.gpState 59 | 60 | # ReSharper is a .NET coding add-in 61 | _ReSharper* 62 | 63 | # NCrunch 64 | *.ncrunch* 65 | .*crunch*.local.xml 66 | 67 | # Installshield output folder 68 | [Ee]xpress 69 | 70 | # DocProject is a documentation generator add-in 71 | DocProject/buildhelp/ 72 | DocProject/Help/*.HxT 73 | DocProject/Help/*.HxC 74 | DocProject/Help/*.hhc 75 | DocProject/Help/*.hhk 76 | DocProject/Help/*.hhp 77 | DocProject/Help/Html2 78 | DocProject/Help/html 79 | 80 | # Click-Once directory 81 | publish 82 | 83 | # Publish Web Output 84 | *.Publish.xml 85 | 86 | # NuGet Packages Directory 87 | packages 88 | 89 | # Windows Azure Build Output 90 | csx 91 | *.build.csdef 92 | 93 | # Windows Store app package directory 94 | AppPackages/ 95 | 96 | # Others 97 | [Bb]in 98 | [Oo]bj 99 | sql 100 | *.Cache 101 | ClientBin 102 | [Ss]tyle[Cc]op.* 103 | ~$* 104 | *.dbmdl 105 | 106 | Generated_Code #added for RIA/Silverlight projects 107 | 108 | # Backup & report files from converting an old project file to a newer 109 | # Visual Studio version. Backup files are not needed, because we have git ;-) 110 | _UpgradeReport_Files/ 111 | Backup*/ 112 | UpgradeLog*.XML 113 | 114 | ssl/ 115 | *.crt 116 | *.ssl 117 | *.pem 118 | results/ 119 | teststub.* 120 | *.sqlite 121 | -------------------------------------------------------------------------------- /src/Chat/Web.config: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /src/Chat/ie/eventsource.min.js: -------------------------------------------------------------------------------- 1 | /** @license 2 | * eventsource.js 3 | * Available under MIT License (MIT) 4 | * https://github.com/Yaffle/EventSource/ 5 | */ 6 | !function(a){"use strict";function b(){this.data={}}function c(){this.listeners=new b}function d(a){setTimeout(function(){throw a},0)}function e(a){this.type=a,this.target=void 0}function f(a,b){e.call(this,a),this.data=b.data,this.lastEventId=b.lastEventId}function x(a,b){var c=a;return c!==c&&(c=b),v>c?v:c>w?w:c}function y(a,b,c){try{"function"==typeof b&&b.call(a,c)}catch(e){d(e)}}function z(b,d){function P(){H=o,void 0!==D&&(D.abort(),D=void 0),0!==E&&(clearTimeout(E),E=0),0!==F&&(clearTimeout(F),F=0),A.readyState=o}function Q(a){var c=H===n||H===m?D.responseText:"",d=void 0,g=!1;if(H===m){var i=0,k="",F=void 0;if(j)try{i=D.status,k=D.statusText,F=D.getResponseHeader("Content-Type")}catch(Q){i=0,k="",F=void 0}else""!==a&&"error"!==a&&(i=200,k="OK",F=D.contentType);if((void 0===F||null===F)&&(F=""),0===i&&""===k&&"load"===a&&""!==c&&(i=200,k="OK",""===F)){var R=/^data\:([^,]*?)(?:;base64)?,[\S]*$/.exec(b);void 0!==R&&null!==R&&(F=R[1])}if(200===i&&u.test(F)){if(H=n,C=!0,B=h,A.readyState=n,d=new e("open"),A.dispatchEvent(d),y(A,A.onopen,d),H===o)return}else if(0!==i){var S="";S=200!==i?"EventSource's response has a status "+i+" "+k.replace(/\s+/g," ")+" that is not 200. Aborting the connection.":"EventSource's response has a Content-Type specifying an unsupported type: "+F.replace(/\s+/g," ")+". Aborting the connection.",setTimeout(function(){throw new Error(S)},0),g=!0}}if(H===n){c.length>G&&(C=!0);for(var T=G-1,U=c.length,V="\n";++T1048576||0===E&&!C)?0===E&&(C=!1,E=setTimeout(L,v)):(g?P():(H=l,D.abort(),0!==E&&(clearTimeout(E),E=0),B>16*h&&(B=16*h),B>w&&(B=w),E=setTimeout(L,B),B=2*B+1,A.readyState=m),d=new e("error"),A.dispatchEvent(d),y(A,A.onerror,d))}function R(){Q("progress")}function S(){Q("load")}function T(){Q("error")}b=b.toString();var g=i&&void 0!==d&&Boolean(d.withCredentials),h=x(1e3,0),v=x(45e3,0),z="",A=this,B=h,C=!1,D=new k,E=0,F=0,G=0,H=l,I=[],J="",K="",L=void 0,M=q,N="",O="";j&&(F=setTimeout(function U(){3===D.readyState&&Q("progress"),F=setTimeout(U,500)},0)),L=function(){if(E=0,H!==l)return Q(""),void 0;if(j&&(void 0!==D.sendAsBinary||void 0===D.onloadend)&&void 0!==a.document&&void 0!==a.document.readyState&&"complete"!==a.document.readyState)return E=setTimeout(L,4),void 0;D.onload=S,D.onerror=T,j&&(D.onabort=T,D.onreadystatechange=R),D.onprogress=R,C=!1,E=setTimeout(L,v),G=0,H=m,I.length=0,K="",J=z,O="",N="",M=q;var c=b.slice(0,5);c="data:"!==c&&"blob:"!==c?b+((-1===b.indexOf("?",0)?"?":"&")+"lastEventId="+encodeURIComponent(z)+"&r="+(Math.random()+1).toString().slice(2)):b,D.open("GET",c,!0),j&&(D.withCredentials=g,D.responseType="text",D.setRequestHeader("Accept","text/event-stream")),D.send(void 0)},c.call(this),this.close=P,this.url=b,this.readyState=m,this.withCredentials=g,this.onopen=void 0,this.onmessage=void 0,this.onerror=void 0,L()}function A(){this.CONNECTING=m,this.OPEN=n,this.CLOSED=o}b.prototype={get:function(a){return this.data[a+"~"]},set:function(a,b){this.data[a+"~"]=b},"delete":function(a){delete this.data[a+"~"]}},c.prototype={dispatchEvent:function(a){a.target=this;var b=a.type.toString(),c=this.listeners,e=c.get(b);if(void 0!==e)for(var f=e.length,g=-1,h=void 0;++g=0;)if(d[e]===b)return;d.push(b)},removeEventListener:function(a,b){a=a.toString();var c=this.listeners,d=c.get(a);if(void 0!==d){for(var e=d.length,f=[],g=-1;++g 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 8 | 9 | 2.0 10 | {724864E0-3970-4207-9D52-0C1A5DB15F31} 11 | {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} 12 | Library 13 | Properties 14 | Chat 15 | Chat 16 | v4.7.2 17 | true 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | true 28 | full 29 | false 30 | bin\ 31 | DEBUG;TRACE 32 | prompt 33 | 4 34 | 35 | 36 | pdbonly 37 | true 38 | bin\ 39 | TRACE 40 | prompt 41 | 4 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | Designer 72 | 73 | 74 | 75 | 76 | 77 | 78 | Global.asax 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 10.0 107 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | False 117 | False 118 | 1337 119 | / 120 | http://localhost:1337/ 121 | False 122 | False 123 | 124 | 125 | False 126 | 127 | 128 | 129 | 130 | 137 | -------------------------------------------------------------------------------- /src/Chat/index.html: -------------------------------------------------------------------------------- 1 | 4 | {{ var channels = qs.channels ?? 'home' }} 5 |
6 | 7 | 8 | 9 |
10 |
11 | {{#if !isAuthenticated}} 12 | {{#each ['twitter', 'facebook', 'github']}}{{/each}} 13 | {{/if}} 14 |
15 |
    16 |
  • 17 | 18 |
  • 19 |
  • 20 | clear 21 |
  • 22 |
23 |
24 |
25 |
26 | 54 | 55 |
56 |
57 | 58 | 59 |
60 | 61 | -------------------------------------------------------------------------------- /src/Chat/ie/es5-shim.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * https://github.com/es-shims/es5-shim 3 | * @license es5-shim Copyright 2009-2015 by contributors, MIT License 4 | * see https://github.com/es-shims/es5-shim/blob/v4.1.1/LICENSE 5 | */ 6 | (function(t,e){"use strict";if(typeof define==="function"&&define.amd){define(e)}else if(typeof exports==="object"){module.exports=e()}else{t.returnExports=e()}})(this,function(){var t=Array.prototype;var e=Object.prototype;var r=Function.prototype;var n=String.prototype;var i=Number.prototype;var a=t.slice;var o=t.splice;var u=t.push;var l=t.unshift;var f=r.call;var s=e.toString;var c=Array.isArray||function gt(t){return s.call(t)==="[object Array]"};var p=typeof Symbol==="function"&&typeof Symbol.toStringTag==="symbol";var h;var v=Function.prototype.toString,g=function yt(t){try{v.call(t);return true}catch(e){return false}},y="[object Function]",d="[object GeneratorFunction]";h=function dt(t){if(typeof t!=="function"){return false}if(p){return g(t)}var e=s.call(t);return e===y||e===d};var m;var b=RegExp.prototype.exec,w=function mt(t){try{b.call(t);return true}catch(e){return false}},T="[object RegExp]";m=function bt(t){if(typeof t!=="object"){return false}return p?w(t):s.call(t)===T};var x;var O=String.prototype.valueOf,j=function wt(t){try{O.call(t);return true}catch(e){return false}},S="[object String]";x=function Tt(t){if(typeof t==="string"){return true}if(typeof t!=="object"){return false}return p?j(t):s.call(t)===S};var E=function xt(t){var e=s.call(t);var r=e==="[object Arguments]";if(!r){r=!c(t)&&t!==null&&typeof t==="object"&&typeof t.length==="number"&&t.length>=0&&h(t.callee)}return r};var N=function(t){var e=Object.defineProperty&&function(){try{Object.defineProperty({},"x",{});return true}catch(t){return false}}();var r;if(e){r=function(t,e,r,n){if(!n&&e in t){return}Object.defineProperty(t,e,{configurable:true,enumerable:false,writable:true,value:r})}}else{r=function(t,e,r,n){if(!n&&e in t){return}t[e]=r}}return function n(e,i,a){for(var o in i){if(t.call(i,o)){r(e,o,i[o],a)}}}}(e.hasOwnProperty);function I(t){var e=typeof t;return t===null||e==="undefined"||e==="boolean"||e==="number"||e==="string"}var D={ToInteger:function Ot(t){var e=+t;if(e!==e){e=0}else if(e!==0&&e!==1/0&&e!==-(1/0)){e=(e>0||-1)*Math.floor(Math.abs(e))}return e},ToPrimitive:function jt(t){var e,r,n;if(I(t)){return t}r=t.valueOf;if(h(r)){e=r.call(t);if(I(e)){return e}}n=t.toString;if(h(n)){e=n.call(t);if(I(e)){return e}}throw new TypeError},ToObject:function(t){if(t==null){throw new TypeError("can't convert "+t+" to object")}return Object(t)},ToUint32:function St(t){return t>>>0}};var M=function Et(){};N(r,{bind:function Nt(t){var e=this;if(!h(e)){throw new TypeError("Function.prototype.bind called on incompatible "+e)}var r=a.call(arguments,1);var n;var i=function(){if(this instanceof n){var i=e.apply(this,r.concat(a.call(arguments)));if(Object(i)===i){return i}return this}else{return e.apply(t,r.concat(a.call(arguments)))}};var o=Math.max(0,e.length-r.length);var u=[];for(var l=0;l0&&typeof e!=="number"){r=a.call(arguments);if(r.length<2){r.push(this.length-t)}else{r[1]=D.ToInteger(e)}}return o.apply(this,r)}},!U);var k=[].unshift(0)!==1;N(t,{unshift:function(){l.apply(this,arguments);return this.length}},k);N(Array,{isArray:c});var A=Object("a");var C=A[0]!=="a"||!(0 in A);var P=function Mt(t){var e=true;var r=true;if(t){t.call("foo",function(t,r,n){if(typeof n!=="object"){e=false}});t.call([1],function(){"use strict";r=typeof this==="string"},"x")}return!!t&&e&&r};N(t,{forEach:function Ft(t){var e=D.ToObject(this),r=C&&x(this)?this.split(""):e,n=arguments[1],i=-1,a=r.length>>>0;if(!h(t)){throw new TypeError}while(++i>>0,i=Array(n),a=arguments[1];if(!h(t)){throw new TypeError(t+" is not a function")}for(var o=0;o>>0,i=[],a,o=arguments[1];if(!h(t)){throw new TypeError(t+" is not a function")}for(var u=0;u>>0,i=arguments[1];if(!h(t)){throw new TypeError(t+" is not a function")}for(var a=0;a>>0,i=arguments[1];if(!h(t)){throw new TypeError(t+" is not a function")}for(var a=0;a>>0;if(!h(t)){throw new TypeError(t+" is not a function")}if(!n&&arguments.length===1){throw new TypeError("reduce of empty array with no initial value")}var i=0;var a;if(arguments.length>=2){a=arguments[1]}else{do{if(i in r){a=r[i++];break}if(++i>=n){throw new TypeError("reduce of empty array with no initial value")}}while(true)}for(;i>>0;if(!h(t)){throw new TypeError(t+" is not a function")}if(!n&&arguments.length===1){throw new TypeError("reduceRight of empty array with no initial value")}var i,a=n-1;if(arguments.length>=2){i=arguments[1]}else{do{if(a in r){i=r[a--];break}if(--a<0){throw new TypeError("reduceRight of empty array with no initial value")}}while(true)}if(a<0){return i}do{if(a in r){i=t.call(void 0,i,r[a],a,e)}}while(a--);return i}},!J);var z=Array.prototype.indexOf&&[0,1].indexOf(1,2)!==-1;N(t,{indexOf:function Zt(t){var e=C&&x(this)?this.split(""):D.ToObject(this),r=e.length>>>0;if(!r){return-1}var n=0;if(arguments.length>1){n=D.ToInteger(arguments[1])}n=n>=0?n:Math.max(0,r+n);for(;n>>0;if(!r){return-1}var n=r-1;if(arguments.length>1){n=Math.min(n,D.ToInteger(arguments[1]))}n=n>=0?n:r-Math.abs(n);for(;n>=0;n--){if(n in e&&t===e[n]){return n}}return-1}},$);var B=!{toString:null}.propertyIsEnumerable("toString"),G=function(){}.propertyIsEnumerable("prototype"),H=!F("x","0"),L=["toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","constructor"],X=L.length;N(Object,{keys:function zt(t){var e=h(t),r=E(t),n=t!==null&&typeof t==="object",i=n&&x(t);if(!n&&!e&&!r){throw new TypeError("Object.keys called on a non-object")}var a=[];var o=G&&e;if(i&&H||r){for(var u=0;u9999?"+":"")+("00000"+Math.abs(n)).slice(0<=n&&n<=9999?-4:-6);e=t.length;while(e--){r=t[e];if(r<10){t[e]="0"+r}}return n+"-"+t.slice(0,2).join("-")+"T"+t.slice(2).join(":")+"."+("000"+this.getUTCMilliseconds()).slice(-3)+"Z"}},V);var W=function(){try{return Date.prototype.toJSON&&new Date(NaN).toJSON()===null&&new Date(K).toJSON().indexOf(Q)!==-1&&Date.prototype.toJSON.call({toISOString:function(){return true}})}catch(t){return false}}();if(!W){Date.prototype.toJSON=function Gt(t){var e=Object(this);var r=D.ToPrimitive(e);if(typeof r==="number"&&!isFinite(r)){return null}var n=e.toISOString;if(!h(n)){throw new TypeError("toISOString property is not callable")}return n.call(e)}}var _=Date.parse("+033658-09-27T01:46:40.000Z")===1e15;var tt=!isNaN(Date.parse("2012-04-04T24:00:00.500Z"))||!isNaN(Date.parse("2012-11-31T23:59:59.000Z"));var et=isNaN(Date.parse("2000-01-01T00:00:00.000Z"));if(!Date.parse||et||tt||!_){Date=function(t){function e(r,n,i,a,o,u,l){var f=arguments.length;if(this instanceof t){var s=f===1&&String(r)===r?new t(e.parse(r)):f>=7?new t(r,n,i,a,o,u,l):f>=6?new t(r,n,i,a,o,u):f>=5?new t(r,n,i,a,o):f>=4?new t(r,n,i,a):f>=3?new t(r,n,i):f>=2?new t(r,n):f>=1?new t(r):new t;N(s,{constructor:e},true);return s}return t.apply(this,arguments)}var r=new RegExp("^"+"(\\d{4}|[+-]\\d{6})"+"(?:-(\\d{2})"+"(?:-(\\d{2})"+"(?:"+"T(\\d{2})"+":(\\d{2})"+"(?:"+":(\\d{2})"+"(?:(\\.\\d{1,}))?"+")?"+"("+"Z|"+"(?:"+"([-+])"+"(\\d{2})"+":(\\d{2})"+")"+")?)?)?)?"+"$");var n=[0,31,59,90,120,151,181,212,243,273,304,334,365];function i(t,e){var r=e>1?1:0;return n[e]+Math.floor((t-1969+r)/4)-Math.floor((t-1901+r)/100)+Math.floor((t-1601+r)/400)+365*(t-1970)}function a(e){return Number(new t(1970,0,1,0,0,0,e))}for(var o in t){e[o]=t[o]}e.now=t.now;e.UTC=t.UTC;e.prototype=t.prototype;e.prototype.constructor=e;e.parse=function u(e){var n=r.exec(e);if(n){var o=Number(n[1]),u=Number(n[2]||1)-1,l=Number(n[3]||1)-1,f=Number(n[4]||0),s=Number(n[5]||0),c=Number(n[6]||0),p=Math.floor(Number(n[7]||0)*1e3),h=Boolean(n[4]&&!n[8]),v=n[9]==="-"?1:-1,g=Number(n[10]||0),y=Number(n[11]||0),d;if(f<(s>0||c>0||p>0?24:25)&&s<60&&c<60&&p<1e3&&u>-1&&u<12&&g<24&&y<60&&l>-1&&l=0){r+=nt.data[e];nt.data[e]=Math.floor(r/t);r=r%t*nt.base}},numToString:function Yt(){var t=nt.size;var e="";while(--t>=0){if(e!==""||t===0||nt.data[t]!==0){var r=String(nt.data[t]);if(e===""){e=r}else{e+="0000000".slice(0,7-r.length)+r}}}return e},pow:function qt(t,e,r){return e===0?r:e%2===1?qt(t,e-1,r*t):qt(t*t,e/2,r)},log:function Kt(t){var e=0;var r=t;while(r>=4096){e+=12;r/=4096}while(r>=2){e+=1;r/=2}return e}};N(i,{toFixed:function Qt(t){var e,r,n,i,a,o,u,l;e=Number(t);e=e!==e?0:Math.floor(e);if(e<0||e>20){throw new RangeError("Number.toFixed called with invalid number of decimals")}r=Number(this);if(r!==r){return"NaN"}if(r<=-1e21||r>=1e21){return String(r)}n="";if(r<0){n="-";r=-r}i="0";if(r>1e-21){a=nt.log(r*nt.pow(2,69,1))-69;o=a<0?r*nt.pow(2,-a,1):r/nt.pow(2,a,1);o*=4503599627370496;a=52-a;if(a>0){nt.multiply(0,o);u=e;while(u>=7){nt.multiply(1e7,0);u-=7}nt.multiply(nt.pow(10,u,1),0);u=a-1;while(u>=23){nt.divide(1<<23);u-=23}nt.divide(1<0){l=i.length;if(l<=e){i=n+"0.0000000000000000000".slice(0,e-l+2)+i}else{i=n+i.slice(0,l-e)+"."+i.slice(l-e)}}else{i=n+i}return i}},rt);var it=n.split;if("ab".split(/(?:ab)*/).length!==2||".".split(/(.?)(.?)/).length!==4||"tesst".split(/(s)*/)[1]==="t"||"test".split(/(?:)/,-1).length!==4||"".split(/.?/).length||".".split(/()()/).length>1){(function(){var t=typeof/()??/.exec("")[1]==="undefined";n.split=function(e,r){var n=this;if(typeof e==="undefined"&&r===0){return[]}if(!m(e)){return it.call(this,e,r)}var i=[];var a=(e.ignoreCase?"i":"")+(e.multiline?"m":"")+(e.extended?"x":"")+(e.sticky?"y":""),o=0,l,f,s,c;var p=new RegExp(e.source,a+"g");n+="";if(!t){l=new RegExp("^"+p.source+"$(?!\\s)",a)}var h=typeof r==="undefined"?-1>>>0:D.ToUint32(r);f=p.exec(n);while(f){s=f.index+f[0].length;if(s>o){i.push(n.slice(o,f.index));if(!t&&f.length>1){f[0].replace(l,function(){for(var t=1;t1&&f.index=h){break}}if(p.lastIndex===f.index){p.lastIndex++}f=p.exec(n)}if(o===n.length){if(c||!p.test("")){i.push("")}}else{i.push(n.slice(o))}return i.length>h?i.slice(0,h):i}})()}else if("0".split(void 0,0).length){n.split=function Vt(t,e){if(typeof t==="undefined"&&e===0){return[]}return it.call(this,t,e)}}var at=n.replace;var ot=function(){var t=[];"x".replace(/x(.)?/g,function(e,r){t.push(r)});return t.length===1&&typeof t[0]==="undefined"}();if(!ot){n.replace=function Wt(t,e){var r=h(e);var n=m(t)&&/\)[*?]/.test(t.source);if(!r||!n){return at.call(this,t,e)}else{var i=function(r){var n=arguments.length;var i=t.lastIndex;t.lastIndex=0;var a=t.exec(r)||[];t.lastIndex=i;a.push(arguments[n-2],arguments[n-1]);return e.apply(this,a)};return at.call(this,t,i)}}}var ut=n.substr;var lt="".substr&&"0b".substr(-1)!=="b";N(n,{substr:function _t(t,e){var r=t;if(t<0){r=Math.max(this.length+t,0)}return ut.call(this,r,e)}},lt);var ft=" \n \f\r \xa0\u1680\u180e\u2000\u2001\u2002\u2003"+"\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028"+"\u2029\ufeff";var st="\u200b";var ct="["+ft+"]";var pt=new RegExp("^"+ct+ct+"*");var ht=new RegExp(ct+ct+"*$");var vt=n.trim&&(ft.trim()||!st.trim());N(n,{trim:function te(){if(typeof this==="undefined"||this===null){throw new TypeError("can't convert "+this+" to object")}return String(this).replace(pt,"").replace(ht,"")}},vt);if(parseInt(ft+"08")!==8||parseInt(ft+"0x16")!==22){parseInt=function(t){var e=/^0[xX]/;return function r(n,i){var a=String(n).trim();var o=Number(i)||(e.test(a)?16:10);return t(a,o)}}(parseInt)}}); 7 | -------------------------------------------------------------------------------- /src/Chat/Global.asax.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Threading.Tasks; 7 | using Funq; 8 | using ServiceStack; 9 | using ServiceStack.Auth; 10 | using ServiceStack.Configuration; 11 | using ServiceStack.Redis; 12 | using ServiceStack.Script; 13 | 14 | namespace Chat 15 | { 16 | public partial class AppHost : AppHostBase 17 | { 18 | public AppHost() : base("Chat", typeof (ServerEventsServices).Assembly) {} 19 | 20 | public static void Load() => PreInit(); 21 | static partial void PreInit(); 22 | static partial void PreConfigure(IAppHost appHost); 23 | 24 | public override void Configure(Container container) 25 | { 26 | PreConfigure(this); 27 | 28 | Plugins.Add(new SharpPagesFeature()); 29 | Plugins.Add(new ServerEventsFeature()); 30 | SetConfig(new HostConfig { 31 | DefaultContentType = MimeTypes.Json, 32 | AllowSessionIdsInHttpParams = true, 33 | UseCamelCase = true, 34 | }); 35 | this.CustomErrorHttpHandlers.Remove(HttpStatusCode.Forbidden); 36 | 37 | //Register all Authentication methods you want to enable for this web app. 38 | Plugins.Add(new AuthFeature( 39 | () => new AuthUserSession(), 40 | new IAuthProvider[] { 41 | new TwitterAuthProvider(AppSettings), //Sign-in with Twitter 42 | new FacebookAuthProvider(AppSettings), //Sign-in with Facebook 43 | new GithubAuthProvider(AppSettings), //Sign-in with GitHub OAuth Provider 44 | new GoogleAuthProvider(AppSettings), //Sign-in with Google OAuth Provider 45 | })); 46 | 47 | container.RegisterAutoWiredAs(); 48 | 49 | var redisHost = AppSettings.GetString("RedisHost"); 50 | if (redisHost != null) 51 | { 52 | container.Register(new RedisManagerPool(redisHost)); 53 | 54 | container.Register(c => 55 | new RedisServerEvents(c.Resolve())); 56 | container.Resolve().Start(); 57 | } 58 | 59 | // for lte IE 9 support + allow connections from local web dev apps 60 | Plugins.Add(new CorsFeature( 61 | allowOriginWhitelist: new[] { "http://localhost", "http://127.0.0.1:8080", "http://localhost:8080", "http://localhost:8081", "http://null.jsbin.com" }, 62 | allowCredentials: true, 63 | allowedHeaders: "Content-Type, Allow, Authorization")); 64 | } 65 | } 66 | 67 | public interface IChatHistory 68 | { 69 | long GetNextMessageId(string channel); 70 | 71 | void Log(string channel, ChatMessage msg); 72 | 73 | List GetRecentChatHistory(string channel, long? afterId, int? take); 74 | 75 | void Flush(); 76 | } 77 | 78 | public class MemoryChatHistory : IChatHistory 79 | { 80 | public int DefaultLimit { get; set; } 81 | 82 | public IServerEvents ServerEvents { get; set; } 83 | 84 | public MemoryChatHistory() 85 | { 86 | DefaultLimit = 100; 87 | } 88 | 89 | Dictionary> MessagesMap = new Dictionary>(); 90 | 91 | public long GetNextMessageId(string channel) 92 | { 93 | return ServerEvents.GetNextSequence("chatMsg"); 94 | } 95 | 96 | public void Log(string channel, ChatMessage msg) 97 | { 98 | if (!MessagesMap.TryGetValue(channel, out var msgs)) 99 | MessagesMap[channel] = msgs = new List(); 100 | 101 | msgs.Add(msg); 102 | } 103 | 104 | public List GetRecentChatHistory(string channel, long? afterId, int? take) 105 | { 106 | if (!MessagesMap.TryGetValue(channel, out var msgs)) 107 | return new List(); 108 | 109 | var ret = msgs.Where(x => x.Id > afterId.GetValueOrDefault()) 110 | .Reverse() //get latest logs 111 | .Take(take.GetValueOrDefault(DefaultLimit)) 112 | .Reverse(); //reverse back 113 | 114 | return ret.ToList(); 115 | } 116 | 117 | public void Flush() 118 | { 119 | MessagesMap = new Dictionary>(); 120 | } 121 | } 122 | 123 | [Route("/channels/{Channel}/chat")] 124 | public class PostChatToChannel : IReturn 125 | { 126 | public string From { get; set; } 127 | public string ToUserId { get; set; } 128 | public string Channel { get; set; } 129 | public string Message { get; set; } 130 | public string Selector { get; set; } 131 | } 132 | 133 | public class ChatMessage 134 | { 135 | public long Id { get; set; } 136 | public string Channel { get; set; } 137 | public string FromUserId { get; set; } 138 | public string FromName { get; set; } 139 | public string DisplayName { get; set; } 140 | public string Message { get; set; } 141 | public string UserAuthId { get; set; } 142 | public bool Private { get; set; } 143 | } 144 | 145 | [Route("/channels/{Channel}/raw")] 146 | public class PostRawToChannel : IReturnVoid 147 | { 148 | public string From { get; set; } 149 | public string ToUserId { get; set; } 150 | public string Channel { get; set; } 151 | public string Message { get; set; } 152 | public string Selector { get; set; } 153 | } 154 | 155 | [Route("/chathistory")] 156 | public class GetChatHistory : IReturn 157 | { 158 | public string[] Channels { get; set; } 159 | public long? AfterId { get; set; } 160 | public int? Take { get; set; } 161 | } 162 | 163 | public class GetChatHistoryResponse 164 | { 165 | public List Results { get; set; } 166 | public ResponseStatus ResponseStatus { get; set; } 167 | } 168 | 169 | [Route("/reset")] 170 | public class ClearChatHistory : IReturnVoid { } 171 | 172 | [Route("/reset-serverevents")] 173 | public class ResetServerEvents : IReturnVoid { } 174 | 175 | [Route("/channels/{Channel}/object")] 176 | public class PostObjectToChannel : IReturnVoid 177 | { 178 | public string ToUserId { get; set; } 179 | public string Channel { get; set; } 180 | public string Selector { get; set; } 181 | 182 | public CustomType CustomType { get; set; } 183 | public SetterType SetterType { get; set; } 184 | } 185 | public class CustomType 186 | { 187 | public int Id { get; set; } 188 | public string Name { get; set; } 189 | } 190 | public class SetterType 191 | { 192 | public int Id { get; set; } 193 | public string Name { get; set; } 194 | } 195 | 196 | public class ServerEventsServices : Service 197 | { 198 | public IServerEvents ServerEvents { get; set; } 199 | public IChatHistory ChatHistory { get; set; } 200 | public IAppSettings AppSettings { get; set; } 201 | 202 | public async Task Any(PostRawToChannel request) 203 | { 204 | if (!IsAuthenticated && AppSettings.Get("LimitRemoteControlToAuthenticatedUsers", false)) 205 | throw new HttpError(HttpStatusCode.Forbidden, "You must be authenticated to use remote control."); 206 | 207 | // Ensure the subscription sending this notification is still active 208 | var sub = ServerEvents.GetSubscriptionInfo(request.From); 209 | if (sub == null) 210 | throw HttpError.NotFound($"Subscription {request.From} does not exist"); 211 | 212 | // Check to see if this is a private message to a specific user 213 | var msg = request.Message?.HtmlEncode(); 214 | if (request.ToUserId != null) 215 | { 216 | // Only notify that specific user 217 | await ServerEvents.NotifyUserIdAsync(request.ToUserId, request.Selector, msg); 218 | } 219 | else 220 | { 221 | // Notify everyone in the channel for public messages 222 | await ServerEvents.NotifyChannelAsync(request.Channel, request.Selector, msg); 223 | } 224 | } 225 | 226 | public async Task Any(PostChatToChannel request) 227 | { 228 | // Ensure the subscription sending this notification is still active 229 | var sub = ServerEvents.GetSubscriptionInfo(request.From); 230 | if (sub == null) 231 | throw HttpError.NotFound("Subscription {0} does not exist".Fmt(request.From)); 232 | 233 | var channel = request.Channel; 234 | 235 | var chatMessage = request.Message.IndexOf("{{", StringComparison.Ordinal) >= 0 236 | ? await HostContext.AppHost.ScriptContext.RenderScriptAsync(request.Message, new Dictionary { 237 | [nameof(Request)] = Request 238 | }) 239 | : request.Message; 240 | 241 | // Create a DTO ChatMessage to hold all required info about this message 242 | var msg = new ChatMessage 243 | { 244 | Id = ChatHistory.GetNextMessageId(channel), 245 | Channel = request.Channel, 246 | FromUserId = sub.UserId, 247 | FromName = sub.DisplayName, 248 | Message = chatMessage.HtmlEncode(), 249 | }; 250 | 251 | // Check to see if this is a private message to a specific user 252 | if (request.ToUserId != null) 253 | { 254 | // Mark the message as private so it can be displayed differently in Chat 255 | msg.Private = true; 256 | // Send the message to the specific user Id 257 | await ServerEvents.NotifyUserIdAsync(request.ToUserId, request.Selector, msg); 258 | 259 | // Also provide UI feedback to the user sending the private message so they 260 | // can see what was sent. Relay it to all senders active subscriptions 261 | var toSubs = ServerEvents.GetSubscriptionInfosByUserId(request.ToUserId); 262 | foreach (var toSub in toSubs) 263 | { 264 | // Change the message format to contain who the private message was sent to 265 | msg.Message = $"@{toSub.DisplayName}: {msg.Message}"; 266 | await ServerEvents.NotifySubscriptionAsync(request.From, request.Selector, msg); 267 | } 268 | } 269 | else 270 | { 271 | // Notify everyone in the channel for public messages 272 | await ServerEvents.NotifyChannelAsync(request.Channel, request.Selector, msg); 273 | } 274 | 275 | if (!msg.Private) 276 | ChatHistory.Log(channel, msg); 277 | 278 | return msg; 279 | } 280 | 281 | public object Any(GetChatHistory request) 282 | { 283 | var msgs = request.Channels.Map(x => 284 | ChatHistory.GetRecentChatHistory(x, request.AfterId, request.Take)) 285 | .SelectMany(x => x) 286 | .OrderBy(x => x.Id) 287 | .ToList(); 288 | 289 | return new GetChatHistoryResponse 290 | { 291 | Results = msgs 292 | }; 293 | } 294 | 295 | public object Any(ClearChatHistory request) 296 | { 297 | ChatHistory.Flush(); 298 | return HttpResult.Redirect("/"); 299 | } 300 | 301 | public void Any(ResetServerEvents request) 302 | { 303 | ServerEvents.Reset(); 304 | } 305 | 306 | public async Task Any(PostObjectToChannel request) 307 | { 308 | if (request.ToUserId != null) 309 | { 310 | if (request.CustomType != null) 311 | await ServerEvents.NotifyUserIdAsync(request.ToUserId, request.Selector ?? Selector.Id(), request.CustomType); 312 | if (request.SetterType != null) 313 | await ServerEvents.NotifyUserIdAsync(request.ToUserId, request.Selector ?? Selector.Id(), request.SetterType); 314 | } 315 | else 316 | { 317 | if (request.CustomType != null) 318 | await ServerEvents.NotifyChannelAsync(request.Channel, request.Selector ?? Selector.Id(), request.CustomType); 319 | if (request.SetterType != null) 320 | await ServerEvents.NotifyChannelAsync(request.Channel, request.Selector ?? Selector.Id(), request.SetterType); 321 | } 322 | } 323 | } 324 | 325 | public class Global : System.Web.HttpApplication 326 | { 327 | protected void Application_Start(object sender, EventArgs e) 328 | { 329 | new AppHost().Init(); 330 | } 331 | } 332 | 333 | [Route("/account")] 334 | public class GetUserDetails {} 335 | 336 | public class GetUserDetailsResponse 337 | { 338 | public string Provider { get; set; } 339 | public string UserId { get; set; } 340 | public string UserName { get; set; } 341 | public string FullName { get; set; } 342 | public string DisplayName { get; set; } 343 | public string FirstName { get; set; } 344 | public string LastName { get; set; } 345 | public string Company { get; set; } 346 | public string Email { get; set; } 347 | public string PhoneNumber { get; set; } 348 | 349 | public DateTime? BirthDate { get; set; } 350 | public string BirthDateRaw { get; set; } 351 | public string Address { get; set; } 352 | public string Address2 { get; set; } 353 | public string City { get; set; } 354 | public string State { get; set; } 355 | public string Country { get; set; } 356 | public string Culture { get; set; } 357 | public string Gender { get; set; } 358 | public string Language { get; set; } 359 | public string MailAddress { get; set; } 360 | public string Nickname { get; set; } 361 | public string PostalCode { get; set; } 362 | public string TimeZone { get; set; } 363 | } 364 | 365 | [Authenticate] 366 | public class UserDetailsService : Service 367 | { 368 | public object Get(GetUserDetails request) 369 | { 370 | var session = GetSession(); 371 | return session.ConvertTo(); 372 | } 373 | } 374 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Chat 2 | ===== 3 | 4 | Chat showcases ServiceStack's new support for [Server Sent Events](http://www.html5rocks.com/en/tutorials/eventsource/basics/) with a cursory Chat app packed with a number of features including: 5 | 6 | - Anonymous or Authenticated access with Twitter, Facebook or GitHub OAuth 7 | - Joining any arbitrary user-defined channel 8 | - Private messaging 9 | - Command history 10 | - Autocomplete of user names 11 | - Highlighting of mentions 12 | - Grouping messages by user 13 | - Active list of users, kept live with: 14 | - Periodic Heartbeats 15 | - Automatic unregistration on page unload 16 | - Remote Control 17 | - Send a global announcement to all users 18 | - Toggle on/off channel controls 19 | - Change the CSS style of any element 20 | - Change the HTML document's title 21 | - Redirect users to any url 22 | - Play a youtube video 23 | - Display an image url 24 | - Raise DOM events 25 | 26 | ### Preview 27 | 28 | > Live Demo: [chat.netcore.io](http://chat.netcore.io) 29 | 30 | [![Chat Overview](https://raw.githubusercontent.com/ServiceStack/Assets/master/img/apps/Chat/chat-overview.gif)](http://chat.netcore.io) 31 | 32 | 33 | 34 | ### Super lean front and back 35 | 36 | All this fits in a tiny footprint using just vanilla jQuery weighing just: 37 | 38 | - [1 default.cshtml page](https://github.com/ServiceStackApps/Chat/blob/master/src/Chat/default.cshtml), with just **60 lines of HTML markup** and **165 lines of JavaScript** 39 | - [2 ServiceStack Services](https://github.com/ServiceStackApps/Chat/blob/master/src/Chat/Global.asax.cs) 40 | 41 | > If you need to support IE8+ browsers see the modified [default_ieshim.cshtml](https://github.com/ServiceStackApps/Chat/blob/master/src/Chat/default_ieshim.cshtml) configured with IE SSE Polyfills. 42 | 43 | On the back-end Chat utilizes a variety of different Web Framework features spanning Dynamic Razor Views, Web Services, JSON Serialization, Server Push Events as well as Twitter, Facebook and GitHub OAuth integration, all in a single page ASP.NET ServiceStack WebApp running on **9 .NET dll** dependencies. 44 | 45 | ## Remote control 46 | 47 | The most interesting feature in Chat is its showcase of ServiceStack's JS client bindings which provide a number of different ways to interact and modify a live webapp by either: 48 | 49 | - Invoking Global Event Handlers 50 | - Modifying CSS via jQuery 51 | - Sending messages to Receivers 52 | - Raising jQuery Events 53 | 54 | All options above are designed to integrate with existing functionality by being able to invoke predefined handlers, object instances as well as modify jQuery CSS and raising DOM events. 55 | 56 | Clients can apply any of the above methods to all users, a specified user or just itself. 57 | 58 | ## Server Sent Events 59 | 60 | [Server Sent Events](http://www.html5rocks.com/en/tutorials/eventsource/basics/) (SSE) is an elegant [web technology](http://dev.w3.org/html5/eventsource/) for efficiently receiving push notifications from any HTTP Server. It can be thought of as a mix between long polling and one-way WebSockets and contains many benefits over each: 61 | 62 | - **Simple** - Server Sent Events is just a single long-lived HTTP Request that any HTTP Server can support 63 | - **Efficient** - Each client uses a single TCP connection and each message avoids the overhead of HTTP Connections and Headers that's [often faster than Web Sockets](http://matthiasnehlsen.com/blog/2013/05/01/server-sent-events-vs-websockets/). 64 | - **Resilient** - Browsers automatically detect when a connection is broken and automatically reconnects 65 | - **Interoperable** - As it's just plain-old HTTP, it's introspectable with your favorite HTTP Tools and even works through HTTP proxies (with buffering and checked-encoding turned off). 66 | - **Well Supported** - As a Web Standard it's supported in all major browsers except for IE which [can be enabled with polyfills](http://html5doctor.com/server-sent-events/#yaffle) - see [default_ieshim.cshtml](https://github.com/ServiceStackApps/Chat/blob/master/src/Chat/default_ieshim.cshtml) and its [Live Chat Example](http://chat.netcore.io/default_ieshim). 67 | 68 | ### Server Event Clients 69 | 70 | - [JavaScript Client](https://github.com/ServiceStack/ServiceStack/wiki/JavaScript-Server-Events-Client) 71 | - [C# Client](https://github.com/ServiceStack/ServiceStack/wiki/C%23-Server-Events-Client) 72 | 73 | ### Server Event Providers 74 | 75 | - Memory Server Events (default) 76 | - [Redis Server Events](https://github.com/ServiceStack/ServiceStack/wiki/Redis-Server-Events) 77 | 78 | ### Registering 79 | 80 | List most other [modular functionality](https://github.com/ServiceStack/ServiceStack/wiki/Plugins) in ServiceStack, Server Sent Events is encapsulated in a single Plugin that can be registered in your AppHost with: 81 | 82 | ```csharp 83 | Plugins.Add(new ServerEventsFeature()); 84 | ``` 85 | 86 | The registration above is all that's needed for most use-cases which just uses the defaults below: 87 | 88 | ```csharp 89 | class ServerEventsFeature 90 | { 91 | StreamPath = "/event-stream"; // The entry-point for Server Sent Events 92 | HeartbeatPath = "/event-heartbeat"; // Where to send heartbeat pulses 93 | UnRegisterPath = "/event-unregister"; // Where to unregister your subscription 94 | SubscribersPath = "/event-subscribers"; // Where to view public info of channel subscribers 95 | 96 | Timeout = TimeSpan.FromSeconds(30); // How long to wait for heartbeat before unsubscribing 97 | HeartbeatInterval = TimeSpan.FromSeconds(10); // Client Interval for sending heartbeat messages 98 | 99 | NotifyChannelOfSubscriptions = true; // Send notifications when subscribers join/leave 100 | } 101 | ``` 102 | 103 | > The paths allow you to customize the routes for the built-in Server Events API's, whilst setting either path to `null` disables that feature. 104 | 105 | There are also a number of hooks available providing entry points where custom logic can be added to modify or enhance existing behavior: 106 | 107 | ```csharp 108 | class ServerEventsFeature 109 | { 110 | Action> OnConnect; //Filter OnConnect messages 111 | 112 | Action OnCreated; // Fired when an Subscription is created 113 | Action OnSubscribe; // Fired when subscription is registered 114 | Action OnUnsubscribe; // Fired when subscription is unregistered 115 | } 116 | ``` 117 | 118 | ### Sending Server Events 119 | 120 | The way your Services send notifications is via the `IServerEvents` API which currently only has an in-memory `MemoryServerEvents` implementation which keeps a record of all subscriptions and connections in memory: 121 | 122 | > Server Events can also be configured to use a [distributed Redis backend](https://github.com/ServiceStack/ServiceStack/wiki/Redis-Server-Events) which allows Server Events to work across load-balanced app servers. 123 | 124 | ```csharp 125 | public interface IServerEvents : IDisposable 126 | { 127 | // External API's 128 | void NotifyAll(string selector, object message); 129 | 130 | void NotifyChannel(string channel, string selector, object message); 131 | 132 | void NotifySubscription(string subscriptionId, string selector, object message, string channel = null); 133 | 134 | void NotifyUserId(string userId, string selector, object message, string channel = null); 135 | 136 | void NotifyUserName(string userName, string selector, object message, string channel = null); 137 | 138 | void NotifySession(string sspid, string selector, object message, string channel = null); 139 | 140 | SubscriptionInfo GetSubscriptionInfo(string id); 141 | 142 | List GetSubscriptionInfosByUserId(string userId); 143 | 144 | // Admin API's 145 | void Register(IEventSubscription subscription, Dictionary connectArgs = null); 146 | 147 | void UnRegister(string subscriptionId); 148 | 149 | long GetNextSequence(string sequenceId); 150 | 151 | // Client API's 152 | List> GetSubscriptionsDetails(string channel = null); 153 | 154 | bool Pulse(string subscriptionId); 155 | 156 | // Clear all Registrations 157 | void Reset(); 158 | void Start(); 159 | void Stop(); 160 | } 161 | ``` 162 | 163 | The API's your Services predominantly deal with are the **External API's** which allow sending of messages at different levels of granularity. As Server Events have deep integration with ServiceStack's [Sessions](https://github.com/ServiceStack/ServiceStack/wiki/Sessions) and [Authentication Providers](https://github.com/ServiceStack/ServiceStack/wiki/Authentication-and-authorization) you're also able to notify specific users by either: 164 | 165 | ```csharp 166 | NotifyUserId() // UserAuthId 167 | NotifyUserName() // UserName 168 | NotifySession() // Permanent Session Id (ss-pid) 169 | ``` 170 | 171 | Whilst these all provide different ways to send a message to a single authenticated user, any user can be connected to multiple subscriptions at any one time (e.g. by having multiple tabs open). Each one of these subscriptions is uniquely identified by a `subscriptionId` which you can send a message with using: 172 | 173 | ```csharp 174 | NotifySubscription() // Unique Subscription Id 175 | ``` 176 | 177 | There are also API's to retrieve a users single event subscription as well as all subscriptions for a user: 178 | 179 | ```csharp 180 | IEventSubscription GetSubscription(string id); 181 | 182 | List GetSubscriptionsByUserId(string userId); 183 | ``` 184 | 185 | ### Event Subscription 186 | 187 | An Event Subscription allows you to inspect different metadata contained on each subscription as well as being able to `Publish()` messages directly to it, manually send a Heartbeat `Pulse()` (to keep the connection active) as well as `Unsubscribe()` to revoke the subscription and terminate the HTTP Connection. 188 | 189 | ```csharp 190 | public interface IEventSubscription : IMeta, IDisposable 191 | { 192 | DateTime CreatedAt { get; set; } 193 | DateTime LastPulseAt { get; set; } 194 | 195 | string Channel { get; } 196 | string UserId { get; } 197 | string UserName { get; } 198 | string DisplayName { get; } 199 | string SessionId { get; } 200 | string SubscriptionId { get; } 201 | bool IsAuthenticated { get; set; } 202 | 203 | Action OnUnsubscribe { get; set; } 204 | void Unsubscribe(); 205 | 206 | void Publish(string selector, object message); 207 | 208 | void Pulse(); 209 | } 210 | ``` 211 | 212 | The `IServerEvents` API also offers an API to UnRegister a subscription with: 213 | 214 | 215 | ```csharp 216 | void UnRegister(IEventSubscription subscription); 217 | ``` 218 | 219 | ## Channels 220 | 221 | Standard Publish / Subscribe patterns include the concept of a **Channel** upon which to subscribe and publish messages to. The channel in Server Events can be any arbitrary string which is declared on the fly when it's first used. 222 | 223 | > As Request DTO names are unique in ServiceStack they also make good channel names which benefit from providing a typed API for free, e.g: `typeof(Request).Name`. 224 | 225 | The API to send a message to a specific channel is: 226 | 227 | ```csharp 228 | void NotifyChannel(string channel, string selector, object message); 229 | ``` 230 | 231 | Which just includes the name of the `channel`, the `selector` you wish the message applies to and the `message` to send which can be any JSON serializable object. 232 | 233 | Along with being able to send a message to everyone on a channel, each API also offers an optional `channel` filter which when supplied will limit messages only to that channel: 234 | 235 | ```csharp 236 | void NotifyUserId(string userId, string selector, object message, string channel = null); 237 | ``` 238 | 239 | ## Client Bindings - ss-utils.js 240 | 241 | Like ServiceStack's other JavaScript interop libraries, the client bindings for ServiceStack's Server Events is in `ServiceStack.dll` and is available on any page with: 242 | 243 | ```html 244 | 245 | ``` 246 | 247 | To configure Server Sent Events on the client create a [native EventSource object](http://www.html5rocks.com/en/tutorials/eventsource/basics/) with: 248 | 249 | ```javascript 250 | var source = new EventSource('/event-stream?channel=channel&t=' + new Date().getTime()); 251 | ``` 252 | 253 | > The default url `/event-stream` can be modified with `ServerEventsFeature.StreamPath` 254 | 255 | As this is the native `EventSource` object, you can interact with it directly, e.g. you can add custom error handlers with: 256 | 257 | ```javascript 258 | source.addEventListener('error', function (e) { 259 | console.log("ERROR!", e); 260 | }, false); 261 | ``` 262 | 263 | The ServiceStack binding itself is just a thin jQuery plugin that extends `EventSource`, e.g: 264 | 265 | ```javascript 266 | $(source).handleServerEvents({ 267 | handlers: { 268 | onConnect: function (subscription) { 269 | activeSub = subscription; 270 | console.log("This is you joining, welcome " + u.displayName + ", img: " + u.profileUrl); 271 | }, 272 | onJoin: function (user) { 273 | console.log("Welcome, " + user.displayName); 274 | }, 275 | onLeave: function (user) { 276 | console.log(user.displayName + " #" + user.userId + ": has left the building"); 277 | }, 278 | //... Register custom handlers 279 | }, 280 | receivers { 281 | //... Register any receivers 282 | }, 283 | success: function (selector, msg, json) { // fired after every message 284 | console.log(selector, msg, json); 285 | }, 286 | }); 287 | ``` 288 | 289 | ServiceStack Server Events has 3 built-in events sent during a subscriptions life-cycle: 290 | 291 | - **onConnect** - sent when successfully connected, includes the subscriptions private `subscriptionId` as well as heartbeat and unregister urls that's used to automatically setup periodic heartbeats. 292 | - **onJoin** - sent when a new user joins the channel. 293 | - **onLeave** - sent when a user leaves the channel. 294 | 295 | > The onJoin/onLeave events can be turned off with `ServerEventsFeature.NotifyChannelOfSubscriptions=false`. 296 | 297 | ## Selectors 298 | 299 | A selector is a string that identifies what should handle the message, it's used by the client to route the message to different handlers. The client bindings in [/js/ss-utils.js](https://github.com/ServiceStack/EmailContacts/#servicestack-javascript-utils---jsss-utilsjs) supports 4 different handlers out of the box: 300 | 301 | ### Global Event Handlers 302 | 303 | To recap [Declarative Events](https://github.com/ServiceStack/EmailContacts/#declarative-events) allow you to define global handlers on a html page which can easily be applied on any element by decorating it with `data-{event}='{handler}'` attribute, eliminating the need to do manual bookkeeping of DOM events. 304 | 305 | The example below first invokes the `paintGreen` handler when the button is clicked and fires the `paintRed` handler when the button loses focus: 306 | 307 | ```javascript 308 | $(document).bindHandlers({ 309 | paintGreen: function(){ 310 | $(this).css("background","green"); 311 | }, 312 | paintRed: function(){ 313 | $(this).css("background","red"); 314 | }, 315 | }); 316 | ``` 317 | 318 | ```html 319 | 320 | ``` 321 | 322 | The selector to invoke a global event handler is: 323 | 324 | cmd.{handler} 325 | 326 | Where `{handler}` is the name of the handler you want to invoke, e.g `cmd.paintGreen`. When invoked from a server event the message (deserialized from JSON) is the first argument, the Server Sent DOM Event is the 2nd argument and `this` by default is assigned to `document.body`. 327 | 328 | ```javascript 329 | function paintGreen(msg /* JSON object msg */, e /*SSE DOM Event*/){ 330 | this // HTML Element or document.body 331 | $(this).css("background","green"); 332 | }, 333 | ``` 334 | 335 | ### Postfix jQuery selector 336 | 337 | All server event handler options also support a postfix jQuery selector for specifying what each handler should be bound to with a `$` followed by the jQuery selector, e.g: 338 | 339 | cmd.{handler}${jQuerySelector} 340 | 341 | A concrete example for calling the above API would be: 342 | 343 | cmd.paintGreen$#btnPaint 344 | 345 | Which will bind `this` to the `#btnSubmit` HTML Element, retaining the same behavior as if it were called with `data-click="paintGreen"`. 346 | 347 | > Note: Spaces in jQuery selectors need to be encoded with `%20` 348 | 349 | ### Modifying CSS via jQuery 350 | 351 | As it's a popular use-case Server Events also has native support for modifying CSS properties on any jQuery with: 352 | 353 | css.{propertyName}${jQuerySelector} {propertyValue} 354 | 355 | Where the message is the property value, which roughly translates to: 356 | 357 | $({jQuerySelector}).css({propertyName}, {propertyValue}) 358 | 359 | When no jQuery selector is specified it falls back to `document.body` by default. 360 | 361 | /css.background #eceff1 362 | 363 | Some other examples include: 364 | 365 | /css.background$#top #673ab7 // $('#top').css('background','#673ab7') 366 | /css.font$li bold 12px verdana // $('li').css('font','bold 12px verdana') 367 | /css.visibility$a,img hidden // $('a,img').css('visibility','#673ab7') 368 | /css.visibility$a%20img hidden // $('a img').css('visibility','hidden') 369 | 370 | ### jQuery Events 371 | 372 | A popular approach in building loosely-coupled applications is to have components interact with each other by raising events. It's similar to channels in Pub/Sub where interested parties can receive and process custom events on components they're listening on. jQuery supports this model by simulating DOM events that can be raised with [$.trigger()](http://api.jquery.com/trigger/). 373 | 374 | You can subscribe to custom events in the same way as normal DOM events, e.g: 375 | 376 | ```javascript 377 | $(document).on('customEvent', function(event, arg, msgEvent){ 378 | var target = event.target; 379 | }); 380 | ``` 381 | 382 | The selector to trigger this event is: 383 | 384 | trigger.customEvent arg 385 | trigger.customEvent$#btnPaint arg 386 | 387 | Where if no jQuery selector is specified it defaults to `document`. These selectors are equivalent to: 388 | 389 | ```javascript 390 | $(document).trigger('customEvent', 'arg') 391 | $("#btnPaint").trigger('customEvent', 'arg') 392 | ``` 393 | 394 | ### Receivers 395 | 396 | In programming languages based on message-passing like Smalltalk and Objective-C invoking a method is done by sending a message to a receiver. This is conceptually equivalent to invoking a method on an instance in C# where both these statements are roughly equivalent: 397 | 398 | ```objc 399 | // Objective-C 400 | [receiver method:argument] 401 | ``` 402 | 403 | ```csharp 404 | // C# 405 | receiver.method(argument) 406 | ``` 407 | 408 | Support for receivers is available in the following format: 409 | 410 | {receiver}.{target} {msg} 411 | 412 | ### Registering Receivers 413 | 414 | Registering a receiver can be either be done by adding it to the global `$.ss.eventReceivers` map with the object instance and the name you want it to be exported as. E.g. The `window` and `document` global objects can be setup to receive messages with: 415 | 416 | ```javascript 417 | $.ss.eventReceivers = { 418 | "window": window, 419 | "document": document 420 | }; 421 | ``` 422 | 423 | Once registered you can set any property or call any method on a receiver with: 424 | 425 | document.title New Window Title 426 | window.location http://google.com 427 | 428 | Where if `{target}` was a function it will be invoked with the message, otherwise its property will be set. 429 | By default when no `{jQuerySelector}` is defined, `this` is bound to the **receiver** instance. 430 | 431 | The alternative way to register a receiver is at registration with: 432 | 433 | ```javascript 434 | $(source).handleServerEvents({ 435 | ... 436 | receivers: { 437 | tv: { 438 | watch: function (id) { 439 | if (id.indexOf('youtu.be') >= 0) { 440 | var v = $.ss.splitOnLast(id, '/')[1]; 441 | $("#tv").html(templates.youtube.replace("{id}", v)).show(); 442 | } else { 443 | $("#tv").html(templates.generic.replace("{id}", id)).show(); 444 | } 445 | }, 446 | off: function () { 447 | $("#tv").hide().html(""); 448 | } 449 | } 450 | } 451 | }); 452 | ``` 453 | 454 | This registers a custom `tv` receiver that can now be called with: 455 | 456 | tv.watch http://youtu.be/518XP8prwZo 457 | tv.watch https://servicestack.net/img/logo-220.png 458 | tv.off 459 | 460 | ### Un Registering a Receiver 461 | 462 | As receivers are maintained in a simple map, they can be disabled at anytime with: 463 | 464 | ```javascript 465 | $.ss.eventReceivers["window"] = null; //or delete $.ss.eventReceivers["window"] 466 | ``` 467 | 468 | and re-enabled with: 469 | 470 | ```javascript 471 | $.ss.eventReceivers["window"] = window; 472 | ``` 473 | 474 | ## Chat Features 475 | 476 | The implementation of Chat is a great way to explore different Server Event features which make it easy to develop highly interactive and responsive web apps with very little effort. 477 | 478 | ### Active Subscribers 479 | 480 | One feature common to chat clients is to get details of all the active subscribers in a channel which we can get from the built-in `/event-subscribers` route, e.g: 481 | 482 | ```javascript 483 | $.getJSON("/event-subscribers?channel=@channel", function (users) { 484 | $.map(users, function(user) { 485 | usersMap[user.userId] = user; 486 | refCounter[user.userId] = (refCounter[user.userId] || 0) + 1; 487 | }); 488 | var html = $.map(usersMap, function(user) { return createUser(user); }).join(''); 489 | $("#users").html(html); 490 | }); 491 | ``` 492 | 493 | As a single user can have multiple subscriptions (e.g. multiple tabs open) users are merged into a single `usersMap` so each user is only listed once in the users list and a `refCounter` is maintained with the number of subscriptions each user has, so we're able to tell when the user has no more active subscriptions and can remove them from the list. 494 | 495 | ### Chat box 496 | 497 | Chat's text box provides a free-text entry input to try out different Server Event features where each text message is posted to a ServiceStack Service which uses the `IServerEvents` API to send notifications the channels subscribers. When a server event is received on the client, the ss-utils.js client bindings routes the message to the appropriate handler. As all messages go through this same process, the moment the log entry appears in your chat window is also when it appears for everyone else (i.e instant when running localhost). 498 | 499 | Normal chat messages (i.e. that don't specify a selector) uses the default `cmd.chat` selector which is sent to the `chat` handler that just echoes the entry into the chat log with: 500 | 501 | ```javascript 502 | chat: function (m, e) { 503 | addEntry({ id: m.id, userId: m.fromUserId, userName: m.fromName, msg: m.message, 504 | cls: m.private ? ' private' : '' }); 505 | } 506 | 507 | ``` 508 | 509 | ### Specifying a selector 510 | 511 | You can specify to use an alternative selector by prefixing the message with a `/{selector}`, e.g: 512 | 513 | /cmd.announce This is your captain speaking ... 514 | 515 | When a selector is specified in Chat it routes the message to the `/channels/{Channel}/raw` Service which passes the raw message through as a string. Normal Chat entries are instead posted to the `/channels/{Channel}/chat` Service, adding additional metadata to the chat message with the user id and name of the sender so it can be displayed in the chat log. The Javascript code that calls both Services is simply: 516 | 517 | ```javascript 518 | if (msg[0] == "/") { 519 | parts = $.ss.splitOnFirst(msg, " "); 520 | $.post("/channels/@channel/raw", 521 | { from: activeSub.id, toUserId: to, message: parts[1], selector: parts[0].substring(1) }); 522 | } else { 523 | $.post("/channels/@channel/chat", 524 | { from: activeSub.id, toUserId: to, message: msg, selector: "cmd.chat" }); 525 | } 526 | ``` 527 | 528 | ### Sending a message to a specific user 529 | 530 | Another special syntax supported in Chat is the ability to send messages to other users by prefixing it with `@` followed by the username, e.g: 531 | 532 | @mythz this is a private message 533 | @mythz /tv.watch http://youtu.be/518XP8prwZo 534 | 535 | There's also a special `@me` alias to send a message to yourself, e.g: 536 | 537 | @me /tv.watch http://youtu.be/518XP8prwZo 538 | 539 | ## Server Event Services 540 | 541 | By default ServiceStack doesn't expose any Services that can send notifications to other users by default. It's left up to your application as to what functionality and level of granularity should be enabled for your Application. Your Services can send notifications via the `IServerEvents` provider. 542 | 543 | Below is the annotated implementation for both Web Services used by Chat. The `PostRawToChannel` is a simple implementation that just relays the message sent to all users in the channel or just a specific user if `ToUserId` parameter is specified. 544 | 545 | The `PostChatToChannel` Service is used for sending Chat messages which sends a wrapped `ChatMessage` DTO instead that holds additional metadata about the message that the Chat UI requires: 546 | 547 | ```csharp 548 | [Route("/channels/{Channel}/chat")] 549 | public class PostChatToChannel : IReturn 550 | { 551 | public string From { get; set; } 552 | public string ToUserId { get; set; } 553 | public string Channel { get; set; } 554 | public string Message { get; set; } 555 | public string Selector { get; set; } 556 | } 557 | 558 | [Route("/channels/{Channel}/raw")] 559 | public class PostRawToChannel : IReturnVoid 560 | { 561 | public string From { get; set; } 562 | public string ToUserId { get; set; } 563 | public string Channel { get; set; } 564 | public string Message { get; set; } 565 | public string Selector { get; set; } 566 | } 567 | 568 | public class ServerEventsServices : Service 569 | { 570 | public IServerEvents ServerEvents { get; set; } 571 | public IChatHistory ChatHistory { get; set; } 572 | public IAppSettings AppSettings { get; set; } 573 | 574 | public void Any(PostRawToChannel request) 575 | { 576 | if (!IsAuthenticated && AppSettings.Get("LimitRemoteControlToAuthenticatedUsers", false)) 577 | throw new HttpError(HttpStatusCode.Forbidden, "You must be authenticated to use remote control."); 578 | 579 | // Ensure the subscription sending this notification is still active 580 | var sub = ServerEvents.GetSubscriptionInfo(request.From); 581 | if (sub == null) 582 | throw HttpError.NotFound("Subscription {0} does not exist".Fmt(request.From)); 583 | 584 | // Check to see if this is a private message to a specific user 585 | if (request.ToUserId != null) 586 | { 587 | // Only notify that specific user 588 | ServerEvents.NotifyUserId(request.ToUserId, request.Selector, request.Message); 589 | } 590 | else 591 | { 592 | // Notify everyone in the channel for public messages 593 | ServerEvents.NotifyChannel(request.Channel, request.Selector, request.Message); 594 | } 595 | } 596 | 597 | public object Any(PostChatToChannel request) 598 | { 599 | // Ensure the subscription sending this notification is still active 600 | var sub = ServerEvents.GetSubscriptionInfo(request.From); 601 | if (sub == null) 602 | throw HttpError.NotFound("Subscription {0} does not exist".Fmt(request.From)); 603 | 604 | var channel = request.Channel; 605 | 606 | // Create a DTO ChatMessage to hold all required info about this message 607 | var msg = new ChatMessage 608 | { 609 | Id = ChatHistory.GetNextMessageId(channel), 610 | FromUserId = sub.UserId, 611 | FromName = sub.DisplayName, 612 | Message = request.Message, 613 | }; 614 | 615 | // Check to see if this is a private message to a specific user 616 | if (request.ToUserId != null) 617 | { 618 | // Mark the message as private so it can be displayed differently in Chat 619 | msg.Private = true; 620 | // Send the message to the specific user Id 621 | ServerEvents.NotifyUserId(request.ToUserId, request.Selector, msg); 622 | 623 | // Also provide UI feedback to the user sending the private message so they 624 | // can see what was sent. Relay it to all senders active subscriptions 625 | var toSubs = ServerEvents.GetSubscriptionInfosByUserId(request.ToUserId); 626 | foreach (var toSub in toSubs) 627 | { 628 | // Change the message format to contain who the private message was sent to 629 | msg.Message = "@{0}: {1}".Fmt(toSub.DisplayName, msg.Message); 630 | ServerEvents.NotifySubscription(request.From, request.Selector, msg); 631 | } 632 | } 633 | else 634 | { 635 | // Notify everyone in the channel for public messages 636 | ServerEvents.NotifyChannel(request.Channel, request.Selector, msg); 637 | } 638 | 639 | if (!msg.Private) 640 | ChatHistory.Log(channel, msg); 641 | 642 | return msg; 643 | } 644 | } 645 | ``` 646 | 647 | 648 | 649 | -------------------------------------------------------------------------------- /src/Chat/assets/js/jquery-3.2.1.min.js: -------------------------------------------------------------------------------- 1 | /*! jQuery v3.2.1 | (c) JS Foundation and other contributors | jquery.org/license */ 2 | !function(a,b){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){"use strict";var c=[],d=a.document,e=Object.getPrototypeOf,f=c.slice,g=c.concat,h=c.push,i=c.indexOf,j={},k=j.toString,l=j.hasOwnProperty,m=l.toString,n=m.call(Object),o={};function p(a,b){b=b||d;var c=b.createElement("script");c.text=a,b.head.appendChild(c).parentNode.removeChild(c)}var q="3.2.1",r=function(a,b){return new r.fn.init(a,b)},s=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,t=/^-ms-/,u=/-([a-z])/g,v=function(a,b){return b.toUpperCase()};r.fn=r.prototype={jquery:q,constructor:r,length:0,toArray:function(){return f.call(this)},get:function(a){return null==a?f.call(this):a<0?this[a+this.length]:this[a]},pushStack:function(a){var b=r.merge(this.constructor(),a);return b.prevObject=this,b},each:function(a){return r.each(this,a)},map:function(a){return this.pushStack(r.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(f.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(a<0?b:0);return this.pushStack(c>=0&&c0&&b-1 in a)}var x=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=function(a,b){for(var c=0,d=a.length;c+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(N),U=new RegExp("^"+L+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+N),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),aa=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:d<0?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ba=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ca=function(a,b){return b?"\0"===a?"\ufffd":a.slice(0,-1)+"\\"+a.charCodeAt(a.length-1).toString(16)+" ":"\\"+a},da=function(){m()},ea=ta(function(a){return a.disabled===!0&&("form"in a||"label"in a)},{dir:"parentNode",next:"legend"});try{G.apply(D=H.call(v.childNodes),v.childNodes),D[v.childNodes.length].nodeType}catch(fa){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s=b&&b.ownerDocument,w=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==w&&9!==w&&11!==w)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==w&&(l=Z.exec(a)))if(f=l[1]){if(9===w){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(s&&(j=s.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(l[2])return G.apply(d,b.getElementsByTagName(a)),d;if((f=l[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==w)s=b,r=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(ba,ca):b.setAttribute("id",k=u),o=g(a),h=o.length;while(h--)o[h]="#"+k+" "+sa(o[h]);r=o.join(","),s=$.test(a)&&qa(b.parentNode)||b}if(r)try{return G.apply(d,s.querySelectorAll(r)),d}catch(x){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(P,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("fieldset");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&a.sourceIndex-b.sourceIndex;if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return function(b){return"form"in b?b.parentNode&&b.disabled===!1?"label"in b?"label"in b.parentNode?b.parentNode.disabled===a:b.disabled===a:b.isDisabled===a||b.isDisabled!==!a&&ea(b)===a:b.disabled===a:"label"in b&&b.disabled===a}}function pa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function qa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return!!b&&"HTML"!==b.nodeName},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),v!==n&&(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(n.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){return a.getAttribute("id")===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}}):(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c,d,e,f=b.getElementById(a);if(f){if(c=f.getAttributeNode("id"),c&&c.value===a)return[f];e=b.getElementsByName(a),d=0;while(f=e[d++])if(c=f.getAttributeNode("id"),c&&c.value===a)return[f]}return[]}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){if("undefined"!=typeof b.getElementsByClassName&&p)return b.getElementsByClassName(a)},r=[],q=[],(c.qsa=Y.test(n.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){a.innerHTML="";var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+K+"*[*^$|!~]?="),2!==a.querySelectorAll(":enabled").length&&q.push(":enabled",":disabled"),o.appendChild(a).disabled=!0,2!==a.querySelectorAll(":disabled").length&&q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Y.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"*"),s.call(a,"[s!='']:x"),r.push("!=",N)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Y.test(o.compareDocumentPosition),t=b||Y.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?I(k,a)-I(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?I(k,a)-I(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?la(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(S,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.escape=function(a){return(a+"").replace(ba,ca)},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(_,aa),a[3]=(a[3]||a[4]||a[5]||"").replace(_,aa),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return V.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&T.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(_,aa).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:!b||(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(O," ")+" ").indexOf(c)>-1:"|="===b&&(e===c||e.slice(0,c.length+1)===c+"-"))}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(P,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(_,aa),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return U.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(_,aa).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:oa(!1),disabled:oa(!0),checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:pa(function(){return[0]}),last:pa(function(a,b){return[b-1]}),eq:pa(function(a,b,c){return[c<0?c+b:c]}),even:pa(function(a,b){for(var c=0;c=0;)a.push(d);return a}),gt:pa(function(a,b,c){for(var d=c<0?c+b:c;++d1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function va(a,b,c){for(var d=0,e=b.length;d-1&&(f[j]=!(g[j]=l))}}else r=wa(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ya(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ta(function(a){return a===b},h,!0),l=ta(function(a){return I(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];i1&&ua(m),i>1&&sa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(P,"$1"),c,i0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=E.call(i));u=wa(u)}G.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&ga.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=ya(b[c]),f[u]?d.push(f):e.push(f);f=A(a,za(e,d)),f.selector=a}return f},i=ga.select=function(a,b,c,e){var f,i,j,k,l,m="function"==typeof a&&a,n=!e&&g(a=m.selector||a);if(c=c||[],1===n.length){if(i=n[0]=n[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&9===b.nodeType&&p&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(_,aa),b)||[])[0],!b)return c;m&&(b=b.parentNode),a=a.slice(i.shift().value.length)}f=V.needsContext.test(a)?0:i.length;while(f--){if(j=i[f],d.relative[k=j.type])break;if((l=d.find[k])&&(e=l(j.matches[0].replace(_,aa),$.test(i[0].type)&&qa(b.parentNode)||b))){if(i.splice(f,1),a=e.length&&sa(i),!a)return G.apply(c,e),c;break}}}return(m||h(a,n))(e,b,!p,c,!b||$.test(a)&&qa(b.parentNode)||b),c},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("fieldset"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){if(!c)return a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){if(!c&&"input"===a.nodeName.toLowerCase())return a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(J,function(a,b,c){var d;if(!c)return a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);r.find=x,r.expr=x.selectors,r.expr[":"]=r.expr.pseudos,r.uniqueSort=r.unique=x.uniqueSort,r.text=x.getText,r.isXMLDoc=x.isXML,r.contains=x.contains,r.escapeSelector=x.escape;var y=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&r(a).is(c))break;d.push(a)}return d},z=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},A=r.expr.match.needsContext;function B(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()}var C=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,D=/^.[^:#\[\.,]*$/;function E(a,b,c){return r.isFunction(b)?r.grep(a,function(a,d){return!!b.call(a,d,a)!==c}):b.nodeType?r.grep(a,function(a){return a===b!==c}):"string"!=typeof b?r.grep(a,function(a){return i.call(b,a)>-1!==c}):D.test(b)?r.filter(b,a,c):(b=r.filter(b,a),r.grep(a,function(a){return i.call(b,a)>-1!==c&&1===a.nodeType}))}r.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?r.find.matchesSelector(d,a)?[d]:[]:r.find.matches(a,r.grep(b,function(a){return 1===a.nodeType}))},r.fn.extend({find:function(a){var b,c,d=this.length,e=this;if("string"!=typeof a)return this.pushStack(r(a).filter(function(){for(b=0;b1?r.uniqueSort(c):c},filter:function(a){return this.pushStack(E(this,a||[],!1))},not:function(a){return this.pushStack(E(this,a||[],!0))},is:function(a){return!!E(this,"string"==typeof a&&A.test(a)?r(a):a||[],!1).length}});var F,G=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,H=r.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||F,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:G.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof r?b[0]:b,r.merge(this,r.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),C.test(e[1])&&r.isPlainObject(b))for(e in b)r.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&(this[0]=f,this.length=1),this}return a.nodeType?(this[0]=a,this.length=1,this):r.isFunction(a)?void 0!==c.ready?c.ready(a):a(r):r.makeArray(a,this)};H.prototype=r.fn,F=r(d);var I=/^(?:parents|prev(?:Until|All))/,J={children:!0,contents:!0,next:!0,prev:!0};r.fn.extend({has:function(a){var b=r(a,this),c=b.length;return this.filter(function(){for(var a=0;a-1:1===c.nodeType&&r.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?r.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?i.call(r(a),this[0]):i.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(r.uniqueSort(r.merge(this.get(),r(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function K(a,b){while((a=a[b])&&1!==a.nodeType);return a}r.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return y(a,"parentNode")},parentsUntil:function(a,b,c){return y(a,"parentNode",c)},next:function(a){return K(a,"nextSibling")},prev:function(a){return K(a,"previousSibling")},nextAll:function(a){return y(a,"nextSibling")},prevAll:function(a){return y(a,"previousSibling")},nextUntil:function(a,b,c){return y(a,"nextSibling",c)},prevUntil:function(a,b,c){return y(a,"previousSibling",c)},siblings:function(a){return z((a.parentNode||{}).firstChild,a)},children:function(a){return z(a.firstChild)},contents:function(a){return B(a,"iframe")?a.contentDocument:(B(a,"template")&&(a=a.content||a),r.merge([],a.childNodes))}},function(a,b){r.fn[a]=function(c,d){var e=r.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=r.filter(d,e)),this.length>1&&(J[a]||r.uniqueSort(e),I.test(a)&&e.reverse()),this.pushStack(e)}});var L=/[^\x20\t\r\n\f]+/g;function M(a){var b={};return r.each(a.match(L)||[],function(a,c){b[c]=!0}),b}r.Callbacks=function(a){a="string"==typeof a?M(a):r.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=e||a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),c<=h&&h--}),this},has:function(a){return a?r.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||b||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j};function N(a){return a}function O(a){throw a}function P(a,b,c,d){var e;try{a&&r.isFunction(e=a.promise)?e.call(a).done(b).fail(c):a&&r.isFunction(e=a.then)?e.call(a,b,c):b.apply(void 0,[a].slice(d))}catch(a){c.apply(void 0,[a])}}r.extend({Deferred:function(b){var c=[["notify","progress",r.Callbacks("memory"),r.Callbacks("memory"),2],["resolve","done",r.Callbacks("once memory"),r.Callbacks("once memory"),0,"resolved"],["reject","fail",r.Callbacks("once memory"),r.Callbacks("once memory"),1,"rejected"]],d="pending",e={state:function(){return d},always:function(){return f.done(arguments).fail(arguments),this},"catch":function(a){return e.then(null,a)},pipe:function(){var a=arguments;return r.Deferred(function(b){r.each(c,function(c,d){var e=r.isFunction(a[d[4]])&&a[d[4]];f[d[1]](function(){var a=e&&e.apply(this,arguments);a&&r.isFunction(a.promise)?a.promise().progress(b.notify).done(b.resolve).fail(b.reject):b[d[0]+"With"](this,e?[a]:arguments)})}),a=null}).promise()},then:function(b,d,e){var f=0;function g(b,c,d,e){return function(){var h=this,i=arguments,j=function(){var a,j;if(!(b=f&&(d!==O&&(h=void 0,i=[a]),c.rejectWith(h,i))}};b?k():(r.Deferred.getStackHook&&(k.stackTrace=r.Deferred.getStackHook()),a.setTimeout(k))}}return r.Deferred(function(a){c[0][3].add(g(0,a,r.isFunction(e)?e:N,a.notifyWith)),c[1][3].add(g(0,a,r.isFunction(b)?b:N)),c[2][3].add(g(0,a,r.isFunction(d)?d:O))}).promise()},promise:function(a){return null!=a?r.extend(a,e):e}},f={};return r.each(c,function(a,b){var g=b[2],h=b[5];e[b[1]]=g.add,h&&g.add(function(){d=h},c[3-a][2].disable,c[0][2].lock),g.add(b[3].fire),f[b[0]]=function(){return f[b[0]+"With"](this===f?void 0:this,arguments),this},f[b[0]+"With"]=g.fireWith}),e.promise(f),b&&b.call(f,f),f},when:function(a){var b=arguments.length,c=b,d=Array(c),e=f.call(arguments),g=r.Deferred(),h=function(a){return function(c){d[a]=this,e[a]=arguments.length>1?f.call(arguments):c,--b||g.resolveWith(d,e)}};if(b<=1&&(P(a,g.done(h(c)).resolve,g.reject,!b),"pending"===g.state()||r.isFunction(e[c]&&e[c].then)))return g.then();while(c--)P(e[c],h(c),g.reject);return g.promise()}});var Q=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;r.Deferred.exceptionHook=function(b,c){a.console&&a.console.warn&&b&&Q.test(b.name)&&a.console.warn("jQuery.Deferred exception: "+b.message,b.stack,c)},r.readyException=function(b){a.setTimeout(function(){throw b})};var R=r.Deferred();r.fn.ready=function(a){return R.then(a)["catch"](function(a){r.readyException(a)}),this},r.extend({isReady:!1,readyWait:1,ready:function(a){(a===!0?--r.readyWait:r.isReady)||(r.isReady=!0,a!==!0&&--r.readyWait>0||R.resolveWith(d,[r]))}}),r.ready.then=R.then;function S(){d.removeEventListener("DOMContentLoaded",S), 3 | a.removeEventListener("load",S),r.ready()}"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(r.ready):(d.addEventListener("DOMContentLoaded",S),a.addEventListener("load",S));var T=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===r.type(c)){e=!0;for(h in c)T(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,r.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(r(a),c)})),b))for(;h1,null,!0)},removeData:function(a){return this.each(function(){X.remove(this,a)})}}),r.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=W.get(a,b),c&&(!d||Array.isArray(c)?d=W.access(a,b,r.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=r.queue(a,b),d=c.length,e=c.shift(),f=r._queueHooks(a,b),g=function(){r.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return W.get(a,c)||W.access(a,c,{empty:r.Callbacks("once memory").add(function(){W.remove(a,[b+"queue",c])})})}}),r.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length\x20\t\r\n\f]+)/i,la=/^$|\/(?:java|ecma)script/i,ma={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ma.optgroup=ma.option,ma.tbody=ma.tfoot=ma.colgroup=ma.caption=ma.thead,ma.th=ma.td;function na(a,b){var c;return c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[],void 0===b||b&&B(a,b)?r.merge([a],c):c}function oa(a,b){for(var c=0,d=a.length;c-1)e&&e.push(f);else if(j=r.contains(f.ownerDocument,f),g=na(l.appendChild(f),"script"),j&&oa(g),c){k=0;while(f=g[k++])la.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),o.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="",o.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var ra=d.documentElement,sa=/^key/,ta=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,ua=/^([^.]*)(?:\.(.+)|)/;function va(){return!0}function wa(){return!1}function xa(){try{return d.activeElement}catch(a){}}function ya(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)ya(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=wa;else if(!e)return a;return 1===f&&(g=e,e=function(a){return r().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=r.guid++)),a.each(function(){r.event.add(this,b,e,d,c)})}r.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=W.get(a);if(q){c.handler&&(f=c,c=f.handler,e=f.selector),e&&r.find.matchesSelector(ra,e),c.guid||(c.guid=r.guid++),(i=q.events)||(i=q.events={}),(g=q.handle)||(g=q.handle=function(b){return"undefined"!=typeof r&&r.event.triggered!==b.type?r.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(L)||[""],j=b.length;while(j--)h=ua.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n&&(l=r.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=r.event.special[n]||{},k=r.extend({type:n,origType:p,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&r.expr.match.needsContext.test(e),namespace:o.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,o,g)!==!1||a.addEventListener&&a.addEventListener(n,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),r.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=W.hasData(a)&&W.get(a);if(q&&(i=q.events)){b=(b||"").match(L)||[""],j=b.length;while(j--)if(h=ua.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n){l=r.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+o.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&p!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,o,q.handle)!==!1||r.removeEvent(a,n,q.handle),delete i[n])}else for(n in i)r.event.remove(a,n+b[j],c,d,!0);r.isEmptyObject(i)&&W.remove(a,"handle events")}},dispatch:function(a){var b=r.event.fix(a),c,d,e,f,g,h,i=new Array(arguments.length),j=(W.get(this,"events")||{})[b.type]||[],k=r.event.special[b.type]||{};for(i[0]=b,c=1;c=1))for(;j!==this;j=j.parentNode||this)if(1===j.nodeType&&("click"!==a.type||j.disabled!==!0)){for(f=[],g={},c=0;c-1:r.find(e,this,null,[j]).length),g[e]&&f.push(d);f.length&&h.push({elem:j,handlers:f})}return j=this,i\x20\t\r\n\f]*)[^>]*)\/>/gi,Aa=/\s*$/g;function Ea(a,b){return B(a,"table")&&B(11!==b.nodeType?b:b.firstChild,"tr")?r(">tbody",a)[0]||a:a}function Fa(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function Ga(a){var b=Ca.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ha(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(W.hasData(a)&&(f=W.access(a),g=W.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;c1&&"string"==typeof q&&!o.checkClone&&Ba.test(q))return a.each(function(e){var f=a.eq(e);s&&(b[0]=q.call(this,e,f.html())),Ja(f,b,c,d)});if(m&&(e=qa(b,a[0].ownerDocument,!1,a,d),f=e.firstChild,1===e.childNodes.length&&(e=f),f||d)){for(h=r.map(na(e,"script"),Fa),i=h.length;l")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=r.contains(a.ownerDocument,a);if(!(o.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||r.isXMLDoc(a)))for(g=na(h),f=na(a),d=0,e=f.length;d0&&oa(g,!i&&na(a,"script")),h},cleanData:function(a){for(var b,c,d,e=r.event.special,f=0;void 0!==(c=a[f]);f++)if(U(c)){if(b=c[W.expando]){if(b.events)for(d in b.events)e[d]?r.event.remove(c,d):r.removeEvent(c,d,b.handle);c[W.expando]=void 0}c[X.expando]&&(c[X.expando]=void 0)}}}),r.fn.extend({detach:function(a){return Ka(this,a,!0)},remove:function(a){return Ka(this,a)},text:function(a){return T(this,function(a){return void 0===a?r.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return Ja(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ea(this,a);b.appendChild(a)}})},prepend:function(){return Ja(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ea(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ja(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ja(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(r.cleanData(na(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null!=a&&a,b=null==b?a:b,this.map(function(){return r.clone(this,a,b)})},html:function(a){return T(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!Aa.test(a)&&!ma[(ka.exec(a)||["",""])[1].toLowerCase()]){a=r.htmlPrefilter(a);try{for(;c1)}});function _a(a,b,c,d,e){return new _a.prototype.init(a,b,c,d,e)}r.Tween=_a,_a.prototype={constructor:_a,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||r.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(r.cssNumber[c]?"":"px")},cur:function(){var a=_a.propHooks[this.prop];return a&&a.get?a.get(this):_a.propHooks._default.get(this)},run:function(a){var b,c=_a.propHooks[this.prop];return this.options.duration?this.pos=b=r.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):_a.propHooks._default.set(this),this}},_a.prototype.init.prototype=_a.prototype,_a.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=r.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){r.fx.step[a.prop]?r.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[r.cssProps[a.prop]]&&!r.cssHooks[a.prop]?a.elem[a.prop]=a.now:r.style(a.elem,a.prop,a.now+a.unit)}}},_a.propHooks.scrollTop=_a.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},r.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},r.fx=_a.prototype.init,r.fx.step={};var ab,bb,cb=/^(?:toggle|show|hide)$/,db=/queueHooks$/;function eb(){bb&&(d.hidden===!1&&a.requestAnimationFrame?a.requestAnimationFrame(eb):a.setTimeout(eb,r.fx.interval),r.fx.tick())}function fb(){return a.setTimeout(function(){ab=void 0}),ab=r.now()}function gb(a,b){var c,d=0,e={height:a};for(b=b?1:0;d<4;d+=2-b)c=ca[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function hb(a,b,c){for(var d,e=(kb.tweeners[b]||[]).concat(kb.tweeners["*"]),f=0,g=e.length;f1)},removeAttr:function(a){return this.each(function(){r.removeAttr(this,a)})}}),r.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?r.prop(a,b,c):(1===f&&r.isXMLDoc(a)||(e=r.attrHooks[b.toLowerCase()]||(r.expr.match.bool.test(b)?lb:void 0)),void 0!==c?null===c?void r.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=r.find.attr(a,b), 4 | null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!o.radioValue&&"radio"===b&&B(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d=0,e=b&&b.match(L);if(e&&1===a.nodeType)while(c=e[d++])a.removeAttribute(c)}}),lb={set:function(a,b,c){return b===!1?r.removeAttr(a,c):a.setAttribute(c,c),c}},r.each(r.expr.match.bool.source.match(/\w+/g),function(a,b){var c=mb[b]||r.find.attr;mb[b]=function(a,b,d){var e,f,g=b.toLowerCase();return d||(f=mb[g],mb[g]=e,e=null!=c(a,b,d)?g:null,mb[g]=f),e}});var nb=/^(?:input|select|textarea|button)$/i,ob=/^(?:a|area)$/i;r.fn.extend({prop:function(a,b){return T(this,r.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[r.propFix[a]||a]})}}),r.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&r.isXMLDoc(a)||(b=r.propFix[b]||b,e=r.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=r.find.attr(a,"tabindex");return b?parseInt(b,10):nb.test(a.nodeName)||ob.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),o.optSelected||(r.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),r.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){r.propFix[this.toLowerCase()]=this});function pb(a){var b=a.match(L)||[];return b.join(" ")}function qb(a){return a.getAttribute&&a.getAttribute("class")||""}r.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).addClass(a.call(this,b,qb(this)))});if("string"==typeof a&&a){b=a.match(L)||[];while(c=this[i++])if(e=qb(c),d=1===c.nodeType&&" "+pb(e)+" "){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=pb(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).removeClass(a.call(this,b,qb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(L)||[];while(c=this[i++])if(e=qb(c),d=1===c.nodeType&&" "+pb(e)+" "){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=pb(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):r.isFunction(a)?this.each(function(c){r(this).toggleClass(a.call(this,c,qb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=r(this),f=a.match(L)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=qb(this),b&&W.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":W.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+pb(qb(c))+" ").indexOf(b)>-1)return!0;return!1}});var rb=/\r/g;r.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=r.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,r(this).val()):a,null==e?e="":"number"==typeof e?e+="":Array.isArray(e)&&(e=r.map(e,function(a){return null==a?"":a+""})),b=r.valHooks[this.type]||r.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=r.valHooks[e.type]||r.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(rb,""):null==c?"":c)}}}),r.extend({valHooks:{option:{get:function(a){var b=r.find.attr(a,"value");return null!=b?b:pb(r.text(a))}},select:{get:function(a){var b,c,d,e=a.options,f=a.selectedIndex,g="select-one"===a.type,h=g?null:[],i=g?f+1:e.length;for(d=f<0?i:g?f:0;d-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),r.each(["radio","checkbox"],function(){r.valHooks[this]={set:function(a,b){if(Array.isArray(b))return a.checked=r.inArray(r(a).val(),b)>-1}},o.checkOn||(r.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var sb=/^(?:focusinfocus|focusoutblur)$/;r.extend(r.event,{trigger:function(b,c,e,f){var g,h,i,j,k,m,n,o=[e||d],p=l.call(b,"type")?b.type:b,q=l.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!sb.test(p+r.event.triggered)&&(p.indexOf(".")>-1&&(q=p.split("."),p=q.shift(),q.sort()),k=p.indexOf(":")<0&&"on"+p,b=b[r.expando]?b:new r.Event(p,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=q.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:r.makeArray(c,[b]),n=r.event.special[p]||{},f||!n.trigger||n.trigger.apply(e,c)!==!1)){if(!f&&!n.noBubble&&!r.isWindow(e)){for(j=n.delegateType||p,sb.test(j+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),i=h;i===(e.ownerDocument||d)&&o.push(i.defaultView||i.parentWindow||a)}g=0;while((h=o[g++])&&!b.isPropagationStopped())b.type=g>1?j:n.bindType||p,m=(W.get(h,"events")||{})[b.type]&&W.get(h,"handle"),m&&m.apply(h,c),m=k&&h[k],m&&m.apply&&U(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=p,f||b.isDefaultPrevented()||n._default&&n._default.apply(o.pop(),c)!==!1||!U(e)||k&&r.isFunction(e[p])&&!r.isWindow(e)&&(i=e[k],i&&(e[k]=null),r.event.triggered=p,e[p](),r.event.triggered=void 0,i&&(e[k]=i)),b.result}},simulate:function(a,b,c){var d=r.extend(new r.Event,c,{type:a,isSimulated:!0});r.event.trigger(d,null,b)}}),r.fn.extend({trigger:function(a,b){return this.each(function(){r.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];if(c)return r.event.trigger(a,b,c,!0)}}),r.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(a,b){r.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),r.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),o.focusin="onfocusin"in a,o.focusin||r.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){r.event.simulate(b,a.target,r.event.fix(a))};r.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=W.access(d,b);e||d.addEventListener(a,c,!0),W.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=W.access(d,b)-1;e?W.access(d,b,e):(d.removeEventListener(a,c,!0),W.remove(d,b))}}});var tb=a.location,ub=r.now(),vb=/\?/;r.parseXML=function(b){var c;if(!b||"string"!=typeof b)return null;try{c=(new a.DOMParser).parseFromString(b,"text/xml")}catch(d){c=void 0}return c&&!c.getElementsByTagName("parsererror").length||r.error("Invalid XML: "+b),c};var wb=/\[\]$/,xb=/\r?\n/g,yb=/^(?:submit|button|image|reset|file)$/i,zb=/^(?:input|select|textarea|keygen)/i;function Ab(a,b,c,d){var e;if(Array.isArray(b))r.each(b,function(b,e){c||wb.test(a)?d(a,e):Ab(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d)});else if(c||"object"!==r.type(b))d(a,b);else for(e in b)Ab(a+"["+e+"]",b[e],c,d)}r.param=function(a,b){var c,d=[],e=function(a,b){var c=r.isFunction(b)?b():b;d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(null==c?"":c)};if(Array.isArray(a)||a.jquery&&!r.isPlainObject(a))r.each(a,function(){e(this.name,this.value)});else for(c in a)Ab(c,a[c],b,e);return d.join("&")},r.fn.extend({serialize:function(){return r.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=r.prop(this,"elements");return a?r.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!r(this).is(":disabled")&&zb.test(this.nodeName)&&!yb.test(a)&&(this.checked||!ja.test(a))}).map(function(a,b){var c=r(this).val();return null==c?null:Array.isArray(c)?r.map(c,function(a){return{name:b.name,value:a.replace(xb,"\r\n")}}):{name:b.name,value:c.replace(xb,"\r\n")}}).get()}});var Bb=/%20/g,Cb=/#.*$/,Db=/([?&])_=[^&]*/,Eb=/^(.*?):[ \t]*([^\r\n]*)$/gm,Fb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Gb=/^(?:GET|HEAD)$/,Hb=/^\/\//,Ib={},Jb={},Kb="*/".concat("*"),Lb=d.createElement("a");Lb.href=tb.href;function Mb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(L)||[];if(r.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Nb(a,b,c,d){var e={},f=a===Jb;function g(h){var i;return e[h]=!0,r.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Ob(a,b){var c,d,e=r.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&r.extend(!0,a,d),a}function Pb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}if(f)return f!==i[0]&&i.unshift(f),c[f]}function Qb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}r.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:tb.href,type:"GET",isLocal:Fb.test(tb.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Kb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":r.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Ob(Ob(a,r.ajaxSettings),b):Ob(r.ajaxSettings,a)},ajaxPrefilter:Mb(Ib),ajaxTransport:Mb(Jb),ajax:function(b,c){"object"==typeof b&&(c=b,b=void 0),c=c||{};var e,f,g,h,i,j,k,l,m,n,o=r.ajaxSetup({},c),p=o.context||o,q=o.context&&(p.nodeType||p.jquery)?r(p):r.event,s=r.Deferred(),t=r.Callbacks("once memory"),u=o.statusCode||{},v={},w={},x="canceled",y={readyState:0,getResponseHeader:function(a){var b;if(k){if(!h){h={};while(b=Eb.exec(g))h[b[1].toLowerCase()]=b[2]}b=h[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return k?g:null},setRequestHeader:function(a,b){return null==k&&(a=w[a.toLowerCase()]=w[a.toLowerCase()]||a,v[a]=b),this},overrideMimeType:function(a){return null==k&&(o.mimeType=a),this},statusCode:function(a){var b;if(a)if(k)y.always(a[y.status]);else for(b in a)u[b]=[u[b],a[b]];return this},abort:function(a){var b=a||x;return e&&e.abort(b),A(0,b),this}};if(s.promise(y),o.url=((b||o.url||tb.href)+"").replace(Hb,tb.protocol+"//"),o.type=c.method||c.type||o.method||o.type,o.dataTypes=(o.dataType||"*").toLowerCase().match(L)||[""],null==o.crossDomain){j=d.createElement("a");try{j.href=o.url,j.href=j.href,o.crossDomain=Lb.protocol+"//"+Lb.host!=j.protocol+"//"+j.host}catch(z){o.crossDomain=!0}}if(o.data&&o.processData&&"string"!=typeof o.data&&(o.data=r.param(o.data,o.traditional)),Nb(Ib,o,c,y),k)return y;l=r.event&&o.global,l&&0===r.active++&&r.event.trigger("ajaxStart"),o.type=o.type.toUpperCase(),o.hasContent=!Gb.test(o.type),f=o.url.replace(Cb,""),o.hasContent?o.data&&o.processData&&0===(o.contentType||"").indexOf("application/x-www-form-urlencoded")&&(o.data=o.data.replace(Bb,"+")):(n=o.url.slice(f.length),o.data&&(f+=(vb.test(f)?"&":"?")+o.data,delete o.data),o.cache===!1&&(f=f.replace(Db,"$1"),n=(vb.test(f)?"&":"?")+"_="+ub++ +n),o.url=f+n),o.ifModified&&(r.lastModified[f]&&y.setRequestHeader("If-Modified-Since",r.lastModified[f]),r.etag[f]&&y.setRequestHeader("If-None-Match",r.etag[f])),(o.data&&o.hasContent&&o.contentType!==!1||c.contentType)&&y.setRequestHeader("Content-Type",o.contentType),y.setRequestHeader("Accept",o.dataTypes[0]&&o.accepts[o.dataTypes[0]]?o.accepts[o.dataTypes[0]]+("*"!==o.dataTypes[0]?", "+Kb+"; q=0.01":""):o.accepts["*"]);for(m in o.headers)y.setRequestHeader(m,o.headers[m]);if(o.beforeSend&&(o.beforeSend.call(p,y,o)===!1||k))return y.abort();if(x="abort",t.add(o.complete),y.done(o.success),y.fail(o.error),e=Nb(Jb,o,c,y)){if(y.readyState=1,l&&q.trigger("ajaxSend",[y,o]),k)return y;o.async&&o.timeout>0&&(i=a.setTimeout(function(){y.abort("timeout")},o.timeout));try{k=!1,e.send(v,A)}catch(z){if(k)throw z;A(-1,z)}}else A(-1,"No Transport");function A(b,c,d,h){var j,m,n,v,w,x=c;k||(k=!0,i&&a.clearTimeout(i),e=void 0,g=h||"",y.readyState=b>0?4:0,j=b>=200&&b<300||304===b,d&&(v=Pb(o,y,d)),v=Qb(o,v,y,j),j?(o.ifModified&&(w=y.getResponseHeader("Last-Modified"),w&&(r.lastModified[f]=w),w=y.getResponseHeader("etag"),w&&(r.etag[f]=w)),204===b||"HEAD"===o.type?x="nocontent":304===b?x="notmodified":(x=v.state,m=v.data,n=v.error,j=!n)):(n=x,!b&&x||(x="error",b<0&&(b=0))),y.status=b,y.statusText=(c||x)+"",j?s.resolveWith(p,[m,x,y]):s.rejectWith(p,[y,x,n]),y.statusCode(u),u=void 0,l&&q.trigger(j?"ajaxSuccess":"ajaxError",[y,o,j?m:n]),t.fireWith(p,[y,x]),l&&(q.trigger("ajaxComplete",[y,o]),--r.active||r.event.trigger("ajaxStop")))}return y},getJSON:function(a,b,c){return r.get(a,b,c,"json")},getScript:function(a,b){return r.get(a,void 0,b,"script")}}),r.each(["get","post"],function(a,b){r[b]=function(a,c,d,e){return r.isFunction(c)&&(e=e||d,d=c,c=void 0),r.ajax(r.extend({url:a,type:b,dataType:e,data:c,success:d},r.isPlainObject(a)&&a))}}),r._evalUrl=function(a){return r.ajax({url:a,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},r.fn.extend({wrapAll:function(a){var b;return this[0]&&(r.isFunction(a)&&(a=a.call(this[0])),b=r(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this},wrapInner:function(a){return r.isFunction(a)?this.each(function(b){r(this).wrapInner(a.call(this,b))}):this.each(function(){var b=r(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=r.isFunction(a);return this.each(function(c){r(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(a){return this.parent(a).not("body").each(function(){r(this).replaceWith(this.childNodes)}),this}}),r.expr.pseudos.hidden=function(a){return!r.expr.pseudos.visible(a)},r.expr.pseudos.visible=function(a){return!!(a.offsetWidth||a.offsetHeight||a.getClientRects().length)},r.ajaxSettings.xhr=function(){try{return new a.XMLHttpRequest}catch(b){}};var Rb={0:200,1223:204},Sb=r.ajaxSettings.xhr();o.cors=!!Sb&&"withCredentials"in Sb,o.ajax=Sb=!!Sb,r.ajaxTransport(function(b){var c,d;if(o.cors||Sb&&!b.crossDomain)return{send:function(e,f){var g,h=b.xhr();if(h.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(g in b.xhrFields)h[g]=b.xhrFields[g];b.mimeType&&h.overrideMimeType&&h.overrideMimeType(b.mimeType),b.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest");for(g in e)h.setRequestHeader(g,e[g]);c=function(a){return function(){c&&(c=d=h.onload=h.onerror=h.onabort=h.onreadystatechange=null,"abort"===a?h.abort():"error"===a?"number"!=typeof h.status?f(0,"error"):f(h.status,h.statusText):f(Rb[h.status]||h.status,h.statusText,"text"!==(h.responseType||"text")||"string"!=typeof h.responseText?{binary:h.response}:{text:h.responseText},h.getAllResponseHeaders()))}},h.onload=c(),d=h.onerror=c("error"),void 0!==h.onabort?h.onabort=d:h.onreadystatechange=function(){4===h.readyState&&a.setTimeout(function(){c&&d()})},c=c("abort");try{h.send(b.hasContent&&b.data||null)}catch(i){if(c)throw i}},abort:function(){c&&c()}}}),r.ajaxPrefilter(function(a){a.crossDomain&&(a.contents.script=!1)}),r.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(a){return r.globalEval(a),a}}}),r.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),r.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(e,f){b=r("