├── src └── main │ ├── webapp │ ├── index.jsp │ ├── arclist │ │ ├── 1.gif │ │ ├── 10.gif │ │ ├── 11.gif │ │ ├── 12.gif │ │ ├── 13.gif │ │ ├── 14.gif │ │ ├── 15.gif │ │ ├── 16.gif │ │ ├── 17.gif │ │ ├── 18.gif │ │ ├── 19.gif │ │ ├── 2.gif │ │ ├── 20.gif │ │ ├── 21.gif │ │ ├── 22.gif │ │ ├── 23.gif │ │ ├── 24.gif │ │ ├── 25.gif │ │ ├── 26.gif │ │ ├── 27.gif │ │ ├── 28.gif │ │ ├── 29.gif │ │ ├── 3.gif │ │ ├── 30.gif │ │ ├── 31.gif │ │ ├── 32.gif │ │ ├── 33.gif │ │ ├── 34.gif │ │ ├── 35.gif │ │ ├── 36.gif │ │ ├── 37.gif │ │ ├── 38.gif │ │ ├── 39.gif │ │ ├── 4.gif │ │ ├── 40.gif │ │ ├── 41.gif │ │ ├── 42.gif │ │ ├── 43.gif │ │ ├── 44.gif │ │ ├── 45.gif │ │ ├── 46.gif │ │ ├── 47.gif │ │ ├── 48.gif │ │ ├── 49.gif │ │ ├── 5.gif │ │ ├── 50.gif │ │ ├── 51.gif │ │ ├── 52.gif │ │ ├── 53.gif │ │ ├── 54.gif │ │ ├── 55.gif │ │ ├── 56.gif │ │ ├── 57.gif │ │ ├── 58.gif │ │ ├── 59.gif │ │ ├── 6.gif │ │ ├── 60.gif │ │ ├── 61.gif │ │ ├── 62.gif │ │ ├── 63.gif │ │ ├── 64.gif │ │ ├── 65.gif │ │ ├── 66.gif │ │ ├── 67.gif │ │ ├── 68.gif │ │ ├── 69.gif │ │ ├── 7.gif │ │ ├── 70.gif │ │ ├── 71.gif │ │ ├── 72.gif │ │ ├── 73.gif │ │ ├── 74.gif │ │ ├── 75.gif │ │ ├── 8.gif │ │ └── 9.gif │ ├── bootstrap │ │ ├── fonts │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ ├── glyphicons-halflings-regular.woff │ │ │ └── glyphicons-halflings-regular.woff2 │ │ ├── css │ │ │ ├── reset.css │ │ │ └── chat.css │ │ └── js │ │ │ ├── jquery.qqFace.js │ │ │ ├── chat.js │ │ │ ├── bootstrap.min.js │ │ │ └── jQuery-2.2.0.min.js │ ├── WEB-INF │ │ └── web.xml │ └── index.html │ └── java │ └── com │ └── qg │ └── fangrui │ ├── core.java │ ├── code │ ├── Server.java │ └── BaseServer.java │ ├── util │ ├── NettyUtil.java │ ├── Constants.java │ ├── IsEmptyUtil.java │ └── DateUtil.java │ ├── model │ └── UserInfo.java │ ├── handler │ ├── MessageHandler.java │ ├── UserAuthHandler.java │ └── UserInfoManager.java │ ├── proto │ └── ChatProto.java │ └── ChatServer.java ├── Netty聊天室项目文档.docx ├── screenshot ├── 1.png ├── 2.png ├── 3.png ├── 4.png └── 5.png ├── .gitignore ├── README.md └── pom.xml /src/main/webapp/index.jsp: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Hello World!

4 | 5 | 6 | -------------------------------------------------------------------------------- /Netty聊天室项目文档.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/Netty聊天室项目文档.docx -------------------------------------------------------------------------------- /screenshot/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/screenshot/1.png -------------------------------------------------------------------------------- /screenshot/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/screenshot/2.png -------------------------------------------------------------------------------- /screenshot/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/screenshot/3.png -------------------------------------------------------------------------------- /screenshot/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/screenshot/4.png -------------------------------------------------------------------------------- /screenshot/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/screenshot/5.png -------------------------------------------------------------------------------- /src/main/webapp/arclist/1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/1.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/10.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/10.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/11.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/11.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/12.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/12.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/13.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/13.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/14.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/14.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/15.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/15.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/16.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/17.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/17.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/18.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/18.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/19.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/19.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/2.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/20.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/20.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/21.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/21.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/22.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/22.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/23.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/23.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/24.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/25.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/25.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/26.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/26.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/27.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/27.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/28.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/28.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/29.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/29.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/3.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/30.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/30.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/31.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/31.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/32.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/32.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/33.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/33.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/34.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/34.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/35.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/35.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/36.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/36.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/37.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/37.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/38.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/38.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/39.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/39.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/4.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/40.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/40.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/41.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/41.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/42.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/42.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/43.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/43.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/44.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/44.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/45.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/45.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/46.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/46.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/47.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/47.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/48.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/48.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/49.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/49.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/5.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/50.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/50.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/51.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/51.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/52.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/52.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/53.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/53.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/54.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/54.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/55.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/55.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/56.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/56.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/57.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/57.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/58.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/58.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/59.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/59.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/6.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/6.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/60.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/60.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/61.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/61.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/62.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/62.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/63.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/63.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/64.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/64.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/65.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/65.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/66.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/66.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/67.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/67.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/68.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/68.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/69.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/69.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/7.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/7.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/70.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/70.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/71.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/71.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/72.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/72.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/73.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/73.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/74.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/74.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/75.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/75.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/8.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/8.gif -------------------------------------------------------------------------------- /src/main/webapp/arclist/9.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/arclist/9.gif -------------------------------------------------------------------------------- /src/main/webapp/bootstrap/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/bootstrap/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /src/main/webapp/bootstrap/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/bootstrap/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /src/main/webapp/bootstrap/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/bootstrap/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /src/main/webapp/bootstrap/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuLin-Coder/No214NetworkChatRoom/HEAD/src/main/webapp/bootstrap/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /src/main/java/com/qg/fangrui/core.java: -------------------------------------------------------------------------------- 1 | package com.qg.fangrui; 2 | 3 | /** 4 | * Created by FunriLy on 2017/6/9. 5 | * From small beginnings comes great things. 6 | */ 7 | public class core { 8 | } 9 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | Archetype Created Web Application 7 | 8 | -------------------------------------------------------------------------------- /src/main/java/com/qg/fangrui/code/Server.java: -------------------------------------------------------------------------------- 1 | package com.qg.fangrui.code; 2 | 3 | /** 4 | * Created by FunriLy on 2017/6/10. 5 | * From small beginnings comes great things. 6 | */ 7 | public interface Server { 8 | 9 | void start(); 10 | 11 | void shutdown(); 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # Build Tools 3 | 4 | .gradle 5 | /build/ 6 | !gradle/wrapper/gradle-wrapper.jar 7 | 8 | target/ 9 | !.mvn/wrapper/maven-wrapper.jar 10 | 11 | out/ 12 | 13 | src/main/java/com/qg/fangrui/NettyChatMain.java 14 | 15 | ###################################################################### 16 | # IDE 17 | 18 | ### STS ### 19 | .apt_generated 20 | .classpath 21 | .factorypath 22 | .project 23 | .settings 24 | .springBeans 25 | 26 | ### IntelliJ IDEA ### 27 | .idea 28 | *.iws 29 | *.iml 30 | *.ipr 31 | 32 | ### NetBeans ### 33 | nbproject/private/ 34 | build/* 35 | nbbuild/ 36 | dist/ 37 | nbdist/ 38 | .nb-gradle/ 39 | 40 | ###################################################################### 41 | # Others 42 | *.log 43 | *.xml.versionsBackup 44 | *.swp 45 | 46 | !*/build/*.java 47 | !*/build/*.html 48 | !*/build/*.xml 49 | -------------------------------------------------------------------------------- /src/main/java/com/qg/fangrui/util/NettyUtil.java: -------------------------------------------------------------------------------- 1 | package com.qg.fangrui.util; 2 | 3 | import io.netty.channel.Channel; 4 | 5 | import java.net.SocketAddress; 6 | 7 | /** 8 | * Created by FunriLy on 2017/6/10. 9 | * From small beginnings comes great things. 10 | */ 11 | public class NettyUtil { 12 | 13 | /** 14 | * 获得Channel远程主机IP地址 15 | * @param channel 16 | * @return 17 | */ 18 | public static String parseChannelRemoteAddr(final Channel channel){ 19 | if (null == channel){ 20 | return ""; 21 | } 22 | 23 | SocketAddress address = channel.remoteAddress(); 24 | String addr = (address != null ? address.toString() : ""); 25 | if (addr.length() >= 0){ 26 | int index = addr.lastIndexOf("/"); 27 | if (index >= 0 ){ 28 | return addr.substring(index+1); 29 | } 30 | return addr; 31 | } 32 | return ""; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/qg/fangrui/util/Constants.java: -------------------------------------------------------------------------------- 1 | package com.qg.fangrui.util; 2 | 3 | /** 4 | * Created by FunriLy on 2017/6/10. 5 | * From small beginnings comes great things. 6 | */ 7 | public class Constants { 8 | 9 | 10 | public static String DEFAULT_HOST = "localhost"; 11 | public static int DEFAULT_PORT = 9090; 12 | public static String WEBSOCKET_URL = "ws://localhost:9090/websocket"; 13 | 14 | public static final int AUTH_CODe = 10001; 15 | public static final int MESS_CODE = 10002; 16 | public static final int PRIV_CODE = 10003; 17 | public static final int PING_CODE = 10011; 18 | public static final int PONG_CODE = 10012; 19 | 20 | /** 21 | * 系统消息类 22 | */ 23 | public static final int SYSTEM_USER_COUNT = 20001; //在线用户数 24 | public static final int SYSTEM_AUTH_STATE = 20002; //认证结果 25 | public static final int SYSTEM_OTHER_INFO = 20003; //系统通知 26 | public static final int SYSTEM_USER_LIST = 20004; //在线用户列表 27 | } 28 | -------------------------------------------------------------------------------- /src/main/webapp/bootstrap/css/reset.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (c) 2013, 16CODE(16code.com). All rights reserved. 4 | 5 | */ 6 | 7 | body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,p,blockquote,th,td{margin:0;padding:0}fieldset,img{border:0}:focus{outline:0}address,caption,cite,code,dfn,em,strong,th,var,optgroup{font-style:normal;font-weight:normal}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}abbr,acronym{border:0;font-variant:normal}input,button,textarea,select,optgroup,option{font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit}code,kbd,samp,tt{font-size:100%}input,button,textarea,select{*font-size:100%}body{line-height:1.5}ol,ul{list-style:none}table{border-collapse:collapse;border-spacing:0}caption,th{text-align:left}sup,sub{font-size:100%;vertical-align:baseline}:link,:visited,ins{text-decoration:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:"";line-height:0}.clearfix:after{clear:both}.unishow{display:none;} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

214.网络聊天室JavaWeb

2 | 3 | - 完整代码获取地址:从戎源码网 ([https://armycodes.com/](https://armycodes.com/)) 4 | - 技术探讨、资料分享,请加QQ群:692619798 5 | - 作者微信:19941326836 QQ:952045282 6 | - 承接计算机毕业设计、Java毕业设计、Python毕业设计、深度学习、机器学习 7 | - 选题+开题报告+任务书+程序定制+安装调试+论文+答辩ppt 一条龙服务 8 | - 所有选题地址 ([https://github.com/YuLin-Coder/AllProjectCatalog](https://github.com/YuLin-Coder/AllProjectCatalog)) 9 | 10 | ## 项目介绍 11 | 基于JavaWeb的网络聊天室【含报告】:前端 html、jquery、WebSocket,后端 maven、netty;集成支持多人同时在线聊天等功能于一体的系统。 12 | 13 | ## 功能介绍 14 | 15 | - 支持昵称登录:用户通过浏览器访问服务器时,需要确定自己的昵称,便于交流。 16 | 17 | - 支持多人同时在线:聊天室支持多人登录而不轻易崩溃。由于Netty框架封装的高性能NIO特性,可以明显看到多用户同时在线时交流时的流畅性 18 | 19 | - 同步显示在线人数和成员列表:聊天室支持多人登录,实时更新在线人数和列表,用户退出时即时发布广播消息 20 | 21 | - 支持文字和表情的内容:聊天室支持在线用户发送文字与表情内容: 22 | 23 | - 浏览器与服务器保持长连接,定时心跳检测 24 | 25 | ## 环境 26 | 27 | - IntelliJ IDEA 2021.3 28 | 29 | - Tomcat 7.0.73 30 | 31 | - JDK 1.8 32 | 33 | ## 运行截图 34 | 35 | ![](screenshot/1.png) 36 | 37 | ![](screenshot/2.png) 38 | 39 | ![](screenshot/3.png) 40 | 41 | ![](screenshot/4.png) 42 | 43 | ![](screenshot/5.png) 44 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | com.qg.fangrui 5 | NettyChatRoom 6 | war 7 | 1.0-SNAPSHOT 8 | NettyChatRoom Maven Webapp 9 | http://maven.apache.org 10 | 11 | 12 | junit 13 | junit 14 | 3.8.1 15 | test 16 | 17 | 18 | com.alibaba 19 | fastjson 20 | 1.2.8 21 | 22 | 23 | io.netty 24 | netty-all 25 | 5.0.0.Alpha2 26 | 27 | 28 | ch.qos.logback 29 | logback-classic 30 | 1.1.7 31 | 32 | 33 | 34 | NettyChatRoom 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/main/webapp/bootstrap/css/chat.css: -------------------------------------------------------------------------------- 1 | .qqFace{margin-top:4px;background:#fff;padding:2px;border:1px #dfe6f6 solid;} 2 | .qqFace table td{padding:0px;} 3 | .qqFace table td img{cursor:pointer;border:1px #fff solid;} 4 | .qqFace table td img:hover{border:1px #0066cc solid;} 5 | .top{ 6 | padding:10px 0; 7 | background:transparent; 8 | background-color:rgba(0,0,0,0.4); 9 | } 10 | #userCount{ 11 | margin:0 2px; 12 | font-weight:bolder; 13 | font-size:16px; 14 | } 15 | #content{ 16 | width:100%; 17 | height:100%; 18 | padding:10px 15px; 19 | font-size:16px; 20 | overflow: auto; 21 | } 22 | .title{ 23 | padding: 4px 0 0; 24 | color: #1259d2; 25 | font-weight:bold; 26 | } 27 | .item{ 28 | font-family: "Microsoft YaHei UI"; 29 | padding:4px 4px 0; 30 | 31 | } 32 | 33 | .user-list-bt{ 34 | border-radius: 3px; 35 | background-color: #fff; 36 | position: absolute; 37 | right: 10px; 38 | width: 28px; 39 | text-align: center; 40 | cursor: pointer; 41 | } 42 | 43 | .user-list { 44 | position: fixed; 45 | right: 2px; 46 | top: 46px; 47 | width: 100px; 48 | background-color: #ececec; 49 | border-radius: 3px; 50 | box-shadow: 0 0 2px 0 #b1b1b1; 51 | max-height: 500px; 52 | overflow-y: auto; 53 | overflow-x: hidden; 54 | transition: width 0.5s; 55 | } 56 | 57 | .user-list li{ 58 | text-align: center; 59 | border-bottom: 1px solid #b3b3b3; 60 | padding: 0 10px; 61 | } 62 | 63 | .user-list li:hover{ 64 | background-color: #cacaca; 65 | } 66 | 67 | .my-hide { 68 | width: 0px; 69 | } 70 | 71 | ::-webkit-scrollbar { 72 | width: 5px; 73 | } 74 | ::-webkit-scrollbar-thumb { 75 | border-radius: 10px; 76 | background: rgba(0,0,0,0.1); 77 | -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.5); 78 | } -------------------------------------------------------------------------------- /src/main/java/com/qg/fangrui/util/IsEmptyUtil.java: -------------------------------------------------------------------------------- 1 | package com.qg.fangrui.util; 2 | 3 | import java.util.Collection; 4 | import java.util.Map; 5 | import java.util.Set; 6 | 7 | /** 8 | * 判断对象是否为空工具类 9 | * Created by FunriLy on 2017/6/10. 10 | * From small beginnings comes great things. 11 | */ 12 | public class IsEmptyUtil { 13 | 14 | /** 15 | * 判断字符串是否为空 16 | * @param message 17 | * @return 18 | */ 19 | public static boolean isEmpty(final String message){ 20 | return (message == null) || (message.trim().length() <= 0); 21 | } 22 | 23 | /** 24 | * 判断字符是否为空 25 | * @param message 26 | * @return 27 | */ 28 | public static boolean isEmpty(final Character message){ 29 | return (message == null) || message.equals(' '); 30 | } 31 | 32 | /** 33 | * 判断对象是否为空 34 | * @param message 35 | * @return 36 | */ 37 | public static boolean isEmpty(final Object message){ 38 | return (message == null); 39 | } 40 | 41 | /** 42 | * 判断对象数组是否为空 43 | * @param message 44 | * @return 45 | */ 46 | public static boolean isEmpty(final Object[] message){ 47 | return (message == null) || (message.length <= 0); 48 | } 49 | 50 | /** 51 | * 判断Collection对象是否为空 52 | * @param message 53 | * @return 54 | */ 55 | public static boolean isEmpty(final Collection message){ 56 | return (message == null) || (message.size() <= 0); 57 | } 58 | 59 | /** 60 | * 判断Set对象是否为空 61 | * @param message 62 | * @return 63 | */ 64 | public static boolean isEmpty(final Set message){ 65 | return (message == null) || (message.size() <= 0); 66 | } 67 | 68 | /** 69 | * 判断Map对象是否为空 70 | * @param message 71 | * @return 72 | */ 73 | public static boolean isEmpty(final Map message){ 74 | return (message == null) || (message.size() <= 0); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/qg/fangrui/model/UserInfo.java: -------------------------------------------------------------------------------- 1 | package com.qg.fangrui.model; 2 | 3 | import io.netty.channel.Channel; 4 | 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | 7 | /** 8 | * Created by FunriLy on 2017/6/9. 9 | * From small beginnings comes great things. 10 | */ 11 | public class UserInfo { 12 | //线程安全自动递增,产生UID 13 | private static AtomicInteger uidGener = new AtomicInteger(1000); 14 | 15 | private boolean isAuth = false; // 是否认证 16 | private long time = 0; // 登录时间 17 | private int userId; // UID 18 | private String nick; // 昵称 19 | private String addr; // 地址 20 | private Channel channel; // 通道 21 | private String password; // 密码,后续可以可以完善 22 | 23 | /** 24 | * get & set 25 | */ 26 | public boolean isAuth() { 27 | return isAuth; 28 | } 29 | 30 | public void setAuth(boolean auth) { 31 | isAuth = auth; 32 | } 33 | 34 | public long getTime() { 35 | return time; 36 | } 37 | 38 | public void setTime(long time) { 39 | this.time = time; 40 | } 41 | 42 | public int getUserId() { 43 | return userId; 44 | } 45 | 46 | public void setUserId() { 47 | this.userId = uidGener.incrementAndGet(); 48 | } 49 | 50 | public String getNick() { 51 | return nick; 52 | } 53 | 54 | public void setNick(String nick) { 55 | this.nick = nick; 56 | } 57 | 58 | public String getAddr() { 59 | return addr; 60 | } 61 | 62 | public void setAddr(String addr) { 63 | this.addr = addr; 64 | } 65 | 66 | public Channel getChannel() { 67 | return channel; 68 | } 69 | 70 | public void setChannel(Channel channel) { 71 | this.channel = channel; 72 | } 73 | 74 | public String getPassword() { 75 | return password; 76 | } 77 | 78 | public void setPassword(String password) { 79 | this.password = password; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/qg/fangrui/util/DateUtil.java: -------------------------------------------------------------------------------- 1 | package com.qg.fangrui.util; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.text.ParseException; 7 | import java.text.SimpleDateFormat; 8 | import java.util.Date; 9 | 10 | /** 11 | * Created by FunriLy on 2017/6/10. 12 | * From small beginnings comes great things. 13 | */ 14 | public class DateUtil { 15 | 16 | private static final Logger LOGGER = LoggerFactory.getLogger(DateUtil.class); 17 | private static final String DEFAULT_DATE_PATTERN = "yyyy-MM-dd HH:mm:ss"; 18 | private static final String DEFAULT_TIME_PATTERN = "HH:mm:ss"; 19 | 20 | /** 21 | * 获得当前时间的字符串格式 22 | * @return 23 | */ 24 | public static String getCurrentDateTime(){ 25 | SimpleDateFormat sdf = new SimpleDateFormat(DEFAULT_DATE_PATTERN); 26 | return sdf.format(System.currentTimeMillis()); 27 | } 28 | 29 | /** 30 | * 比较两个时间 31 | * 如果A>B,返回1;如果A=B,返回0;如果A 0){ 43 | return 1; 44 | }else if(result==0){ 45 | return 0; 46 | }else{ 47 | return -1; 48 | } 49 | } 50 | 51 | /** 52 | * 把字符串日期转换成日期对象 53 | * @param dateString 54 | * @param dateFromat 55 | * @return 56 | */ 57 | public static Date convertStringToDate(String dateString, String dateFromat){ 58 | if (!IsEmptyUtil.isEmpty(dateString) && !IsEmptyUtil.isEmpty(dateFromat)){ 59 | try { 60 | SimpleDateFormat sdf = new SimpleDateFormat(dateFromat); 61 | return sdf.parse(dateString); 62 | } catch (Exception e){ 63 | LOGGER.warn("将字符串转化为日期出错!参数为 " + dateString + "-" + dateFromat, e); 64 | } 65 | } 66 | return null; 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/qg/fangrui/code/BaseServer.java: -------------------------------------------------------------------------------- 1 | package com.qg.fangrui.code; 2 | 3 | import io.netty.bootstrap.ServerBootstrap; 4 | import io.netty.channel.ChannelFuture; 5 | import io.netty.channel.DefaultEventLoopGroup; 6 | import io.netty.channel.nio.NioEventLoopGroup; 7 | import io.netty.channel.socket.nio.NioServerSocketChannel; 8 | 9 | import java.util.concurrent.Executor; 10 | import java.util.concurrent.ThreadFactory; 11 | import java.util.concurrent.atomic.AtomicInteger; 12 | 13 | /** 14 | * Created by FunriLy on 2017/6/10. 15 | * From small beginnings comes great things. 16 | */ 17 | public abstract class BaseServer implements Server { 18 | 19 | protected DefaultEventLoopGroup defLoopGroup; //池 20 | protected NioEventLoopGroup bossGroup; //两个线程组 21 | protected NioEventLoopGroup workGroup; 22 | protected NioServerSocketChannel ssch; 23 | protected ChannelFuture cf; 24 | protected ServerBootstrap b; 25 | 26 | public void init(){ 27 | // defLoopGroup = new DefaultEventLoopGroup(8, new Executor() { 28 | // private AtomicInteger index = new AtomicInteger(0); 29 | // @Override 30 | // public void execute(Runnable command) { 31 | // new Thread(command, "DEFAULTEVENTLOOPGROUP_" + index.incrementAndGet()); 32 | // } 33 | // }); 34 | // //Runtime.getRuntime().availableProcessors() 返回 处理器数 35 | // bossGroup = new NioEventLoopGroup(Runtime.getRuntime().availableProcessors(), 36 | // new Executor() { 37 | // private AtomicInteger index = new AtomicInteger(0); 38 | // @Override 39 | // public void execute(Runnable command) { 40 | // new Thread(command, "BOSS_" + index.incrementAndGet()); 41 | // } 42 | // }); 43 | // workGroup = new NioEventLoopGroup(Runtime.getRuntime().availableProcessors(), 44 | // new Executor() { 45 | // private AtomicInteger index = new AtomicInteger(0); 46 | // @Override 47 | // public void execute(Runnable command) { 48 | // new Thread(command, "WORK_" + index.incrementAndGet()); 49 | // } 50 | // }); 51 | defLoopGroup = new DefaultEventLoopGroup(8); 52 | bossGroup = new NioEventLoopGroup(Runtime.getRuntime().availableProcessors()); 53 | workGroup = new NioEventLoopGroup(Runtime.getRuntime().availableProcessors()); 54 | b = new ServerBootstrap(); 55 | } 56 | 57 | @Override 58 | public void shutdown(){ 59 | if (defLoopGroup != null){ 60 | defLoopGroup.shutdownGracefully(); 61 | } 62 | //优雅退出 63 | bossGroup.shutdownGracefully(); 64 | workGroup.shutdownGracefully(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/qg/fangrui/handler/MessageHandler.java: -------------------------------------------------------------------------------- 1 | package com.qg.fangrui.handler; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.qg.fangrui.model.UserInfo; 5 | import com.qg.fangrui.util.Constants; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.SimpleChannelInboundHandler; 8 | import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | /** 13 | * Created by FunriLy on 2017/6/11. 14 | * From small beginnings comes great things. 15 | */ 16 | public class MessageHandler extends SimpleChannelInboundHandler { 17 | 18 | private static final Logger logger = LoggerFactory.getLogger(MessageHandler.class); 19 | 20 | @Override 21 | protected void messageReceived(ChannelHandlerContext ctx, TextWebSocketFrame frame) throws Exception { 22 | UserInfo userInfo = UserInfoManager.getUserInfo(ctx.channel()); 23 | if (userInfo != null && userInfo.isAuth()) { 24 | JSONObject json = JSONObject.parseObject(frame.text()); 25 | // 广播返回用户发送的消息文本 26 | UserInfoManager.broadcastMess(userInfo.getUserId(), userInfo.getNick(), json.getString("mess")); 27 | } 28 | 29 | // TODO: 2017/6/23 30 | // if (userInfo != null && userInfo.isAuth()) { 31 | // JSONObject json = JSONObject.parseObject(frame.text()); 32 | // // 判断是聊天消息 33 | // if (json.getString("code").equals(String.valueOf(Constants.MESS_CODE))) { 34 | // // 广播返回用户发送的消息文本 35 | // UserInfoManager.broadcastMess(userInfo.getUserId(), userInfo.getNick(), json.getString("mess")); 36 | // } else if (json.getString("code").equals(String.valueOf(Constants.PRIV_CODE))) { 37 | // 38 | // } 39 | // } 40 | } 41 | 42 | @Override 43 | public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { 44 | UserInfoManager.removeChannel(ctx.channel()); 45 | UserInfoManager.broadCastInfo(Constants.SYSTEM_USER_COUNT,UserInfoManager.getAuthUserCount()); 46 | UserInfoManager.broadCastInfo(Constants.SYSTEM_USER_LIST, UserInfoManager.getUserInfoList()); 47 | super.channelUnregistered(ctx); 48 | } 49 | 50 | @Override 51 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 52 | UserInfo userInfo = UserInfoManager.getUserInfo(ctx.channel()); 53 | logger.error("connection error and close the channel", cause); 54 | UserInfoManager.removeChannel(ctx.channel()); 55 | UserInfoManager.broadCastInfo(Constants.SYSTEM_USER_COUNT, UserInfoManager.getAuthUserCount()); 56 | UserInfoManager.broadCastInfo(Constants.SYSTEM_USER_LIST, UserInfoManager.getUserInfoList()); 57 | UserInfoManager.broadCastInfo(Constants.SYSTEM_OTHER_INFO, "网络连接出错,已尝试重新连接!
"); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/webapp/bootstrap/js/jquery.qqFace.js: -------------------------------------------------------------------------------- 1 | // QQ表情插件 2 | (function($){ 3 | $.fn.qqFace = function(options){ 4 | var defaults = { 5 | id : 'facebox', 6 | path : 'face/', 7 | assign : 'content', 8 | tip : 'em_' 9 | }; 10 | var option = $.extend(defaults, options); 11 | var assign = $('#'+option.assign); 12 | var id = option.id; 13 | var path = option.path; 14 | var tip = option.tip; 15 | 16 | if(assign.length<=0){ 17 | alert('缺少表情赋值对象。'); 18 | return false; 19 | } 20 | 21 | $(this).click(function(e){ 22 | var strFace, labFace; 23 | if($('#'+id).length<=0){ 24 | strFace = ''; 32 | } 33 | $(this).parent().append(strFace); 34 | var offset = $(this).position(); 35 | var top = offset.top + $(this).outerHeight(); 36 | $('#'+id).css('bottom',top + 10); 37 | $('#'+id).css('left',offset.left); 38 | $('#'+id).show(); 39 | e.stopPropagation(); 40 | }); 41 | 42 | $(document).click(function(){ 43 | $('#'+id).hide(); 44 | $('#'+id).remove(); 45 | }); 46 | }; 47 | 48 | })(jQuery); 49 | 50 | jQuery.extend({ 51 | unselectContents: function(){ 52 | if(window.getSelection) 53 | window.getSelection().removeAllRanges(); 54 | else if(document.selection) 55 | document.selection.empty(); 56 | } 57 | }); 58 | jQuery.fn.extend({ 59 | selectContents: function(){ 60 | $(this).each(function(i){ 61 | var node = this; 62 | var selection, range, doc, win; 63 | if ((doc = node.ownerDocument) && (win = doc.defaultView) && typeof win.getSelection != 'undefined' && typeof doc.createRange != 'undefined' && (selection = window.getSelection()) && typeof selection.removeAllRanges != 'undefined'){ 64 | range = doc.createRange(); 65 | range.selectNode(node); 66 | if(i == 0){ 67 | selection.removeAllRanges(); 68 | } 69 | selection.addRange(range); 70 | } else if (document.body && typeof document.body.createTextRange != 'undefined' && (range = document.body.createTextRange())){ 71 | range.moveToElementText(node); 72 | range.select(); 73 | } 74 | }); 75 | }, 76 | 77 | setCaret: function(){ 78 | if(!$.browser.msie) return; 79 | var initSetCaret = function(){ 80 | var textObj = $(this).get(0); 81 | textObj.caretPos = document.selection.createRange().duplicate(); 82 | }; 83 | $(this).click(initSetCaret).select(initSetCaret).keyup(initSetCaret); 84 | }, 85 | 86 | insertAtCaret: function(textFeildValue){ 87 | var textObj = $(this).get(0); 88 | if(document.all && textObj.createTextRange && textObj.caretPos){ 89 | var caretPos=textObj.caretPos; 90 | caretPos.text = caretPos.text.charAt(caretPos.text.length-1) == '' ? 91 | textFeildValue+'' : textFeildValue; 92 | } else if(textObj.setSelectionRange){ 93 | var rangeStart=textObj.selectionStart; 94 | var rangeEnd=textObj.selectionEnd; 95 | var tempStr1=textObj.value.substring(0,rangeStart); 96 | var tempStr2=textObj.value.substring(rangeEnd); 97 | textObj.value=tempStr1+textFeildValue+tempStr2; 98 | textObj.focus(); 99 | var len=textFeildValue.length; 100 | textObj.setSelectionRange(rangeStart+len,rangeStart+len); 101 | textObj.blur(); 102 | }else{ 103 | textObj.value+=textFeildValue; 104 | } 105 | } 106 | }); -------------------------------------------------------------------------------- /src/main/java/com/qg/fangrui/proto/ChatProto.java: -------------------------------------------------------------------------------- 1 | package com.qg.fangrui.proto; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.qg.fangrui.util.DateUtil; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | /** 10 | * 聊天室的协议 11 | * {head(四字节)}{body}{tend} 12 | * 模仿《Netty权威指南》私有协议栈的开发 13 | * Created by FunriLy on 2017/6/10. 14 | * From small beginnings comes great things. 15 | */ 16 | public class ChatProto { 17 | 18 | public static final int PING_PROTO = 1 << 8 | 220; //ping消息(476) 19 | public static final int PONG_PROTO = 2 << 8 | 220; //pong消息(732) 20 | public static final int SYST_PROTO = 3 << 8 | 220; //系统消息(988) 21 | public static final int EROR_PROTO = 4 << 8 | 220; //错误消息(1244) 22 | public static final int AUTH_PROTO = 5 << 8 | 220; //认证消息(1500) 23 | public static final int MESS_PROTO = 6 << 8 | 220; //普通消息(1756) 24 | public static final int PRIV_PROTO = 7 << 8 | 220; //私聊消息(2012) 25 | 26 | private int version = 1; 27 | private int head; 28 | private String body; 29 | private Map extend = new HashMap(); 30 | 31 | public ChatProto(int head, String body){ 32 | this.head = head; 33 | this.body = body; 34 | } 35 | 36 | /** 37 | * 构建多种消息的构造器 38 | */ 39 | public static String buildPingProto(){ 40 | return buildProto(PING_PROTO, null); 41 | } 42 | 43 | public static String buildPongProto(){ 44 | return buildProto(PONG_PROTO, null); 45 | } 46 | 47 | public static String buildSystProto(int code, Object mess){ 48 | ChatProto chatProto = new ChatProto(SYST_PROTO, null); 49 | chatProto.extend.put("code", code); 50 | chatProto.extend.put("mess", mess); 51 | return JSONObject.toJSONString(chatProto); 52 | } 53 | 54 | public static String buildErrorProtr(int code,String mess){ 55 | ChatProto chatProto = new ChatProto(EROR_PROTO, null); 56 | chatProto.extend.put("code", code); 57 | chatProto.extend.put("mess", mess); 58 | return JSONObject.toJSONString(chatProto); 59 | } 60 | 61 | public static String buildAuthProto(boolean isSuccess) { 62 | ChatProto chatProto = new ChatProto(AUTH_PROTO, null); 63 | chatProto.extend.put("isSuccess", isSuccess); 64 | return JSONObject.toJSONString(chatProto); 65 | } 66 | 67 | /** 68 | * 广播消息的主要封装方法 69 | * @param uid 70 | * @param nick 71 | * @param mess 72 | * @return 73 | */ 74 | public static String buildMessProto(int uid, String nick, String mess) { 75 | ChatProto chatProto = new ChatProto(MESS_PROTO, mess); 76 | chatProto.extend.put("uid", uid); 77 | chatProto.extend.put("nick", nick); 78 | chatProto.extend.put("time", DateUtil.getCurrentDateTime()); 79 | return JSONObject.toJSONString(chatProto); 80 | } 81 | 82 | public static String buildPrivateMessage(int from, String fromNick, int to, String toNick, String mess){ 83 | // TODO: 2017/6/23 84 | return null; 85 | } 86 | 87 | public static String buildProto(int head, String body){ 88 | ChatProto chatProto = new ChatProto(head, body); 89 | return JSONObject.toJSONString(chatProto); 90 | } 91 | 92 | /** 93 | * get & set 94 | */ 95 | public int getVersion() { 96 | return version; 97 | } 98 | 99 | public void setVersion(int version) { 100 | this.version = version; 101 | } 102 | 103 | public int getHead() { 104 | return head; 105 | } 106 | 107 | public void setHead(int head) { 108 | this.head = head; 109 | } 110 | 111 | public String getBody() { 112 | return body; 113 | } 114 | 115 | public void setBody(String body) { 116 | this.body = body; 117 | } 118 | 119 | public Map getExtend() { 120 | return extend; 121 | } 122 | 123 | public void setExtend(Map extend) { 124 | this.extend = extend; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/main/java/com/qg/fangrui/ChatServer.java: -------------------------------------------------------------------------------- 1 | package com.qg.fangrui; 2 | 3 | import com.qg.fangrui.code.BaseServer; 4 | import com.qg.fangrui.handler.MessageHandler; 5 | import com.qg.fangrui.handler.UserAuthHandler; 6 | import com.qg.fangrui.handler.UserInfoManager; 7 | import io.netty.channel.ChannelInitializer; 8 | import io.netty.channel.ChannelOption; 9 | import io.netty.channel.socket.SocketChannel; 10 | import io.netty.channel.socket.nio.NioServerSocketChannel; 11 | import io.netty.handler.codec.http.HttpObjectAggregator; 12 | import io.netty.handler.codec.http.HttpServerCodec; 13 | import io.netty.handler.stream.ChunkedWriteHandler; 14 | import io.netty.handler.timeout.IdleStateHandler; 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | 18 | import java.net.InetSocketAddress; 19 | import java.util.concurrent.Executors; 20 | import java.util.concurrent.ScheduledExecutorService; 21 | import java.util.concurrent.TimeUnit; 22 | 23 | /** 24 | * Created by FunriLy on 2017/6/11. 25 | * From small beginnings comes great things. 26 | */ 27 | public class ChatServer extends BaseServer { 28 | 29 | private static final Logger logger = LoggerFactory.getLogger(ChatServer.class); 30 | 31 | private ScheduledExecutorService executorService; 32 | private int port; 33 | 34 | public ChatServer(int port){ 35 | this.port = port; 36 | //创建一个定长线程池,支持定时及周期性任务执行 37 | executorService = Executors.newScheduledThreadPool(2); 38 | } 39 | 40 | @Override 41 | public void start() { 42 | System.out.println("服务器初始化"); 43 | b.group(bossGroup, workGroup) 44 | .channel(NioServerSocketChannel.class) 45 | //用于可能长时间没有数据交流的连接 46 | .option(ChannelOption.SO_KEEPALIVE, true) //设置TCP连接,连接会自动测试链接的状态 47 | .option(ChannelOption.TCP_NODELAY, true) //启用TCP保活检测 48 | .option(ChannelOption.SO_BACKLOG, 1024) //当服务器请求处理线程全满时,用于临时存放已完成三次握手的请求的队列的最大长度 49 | .localAddress(new InetSocketAddress(port)) 50 | .childHandler(new ChannelInitializer() { 51 | @Override 52 | protected void initChannel(SocketChannel socketChannel) throws Exception { 53 | socketChannel.pipeline().addLast(defLoopGroup, 54 | new HttpServerCodec(), //请求解码器 55 | new HttpObjectAggregator(65536),//将多个消息转换成单一的消息对象 56 | new ChunkedWriteHandler(), //支持异步发送大的码流,一般用于发送文件流 57 | new IdleStateHandler(60, 0, 0), //检测链路是否读空闲 58 | new UserAuthHandler(), //处理握手和认证 59 | new MessageHandler() //处理消息的发送 60 | ); 61 | } 62 | }); 63 | 64 | try { 65 | 66 | cf = b.bind(port).sync(); 67 | InetSocketAddress address = (InetSocketAddress) cf.channel().localAddress(); 68 | logger.info("WebSocketServer start success, port is:{}", address.getPort()); 69 | 70 | //定时任务:扫描所有的Channel,关闭失效的Channel 71 | executorService.scheduleAtFixedRate(new Runnable() { 72 | @Override 73 | public void run() { 74 | logger.info("scanNotActiveChannel --------"); 75 | UserInfoManager.scanNotActiveChannel(); 76 | } 77 | }, 3, 60, TimeUnit.SECONDS); 78 | 79 | //定时任务:向所有客户端发送Ping消息 80 | executorService.scheduleAtFixedRate(new Runnable() { 81 | @Override 82 | public void run() { 83 | UserInfoManager.broadCastPing(); 84 | } 85 | }, 3, 50, TimeUnit.SECONDS); 86 | cf.channel().closeFuture().sync(); 87 | } catch (InterruptedException e) { 88 | logger.error("WebSocketServer start fail,", e); 89 | } 90 | } 91 | 92 | @Override 93 | public void shutdown() { 94 | if (executorService != null) { 95 | executorService.shutdown(); 96 | } 97 | super.shutdown(); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/main/webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 网络聊天室 | FunriLy 11 | 12 | 13 | 14 | 15 | 16 | 17 | 21 | 22 | 23 | 24 | 48 | 58 | 59 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 126 | -------------------------------------------------------------------------------- /src/main/webapp/bootstrap/js/chat.js: -------------------------------------------------------------------------------- 1 | var socket = null; 2 | var isAuth = false; 3 | var userNick = null; 4 | var userCount = 0; 5 | $(function () { 6 | $("#menuModal").modal('show'); 7 | var height = $(window).height(); 8 | $('#content').css("height", height - $('#top').height() - $('#opt').height() - 40); 9 | 10 | $('#loginBtn').click(function(){ 11 | userLogin(); 12 | }); 13 | 14 | $('#faceBtn').qqFace({ 15 | id: 'facebox', 16 | assign: 'mess', 17 | path: 'arclist/' //表情存放的路径 18 | }); 19 | 20 | $('#sendBtn').click(function () { 21 | var mess = $("#mess").val().trim(); 22 | if(mess){ 23 | sendMess(mess); 24 | $("#mess").val(''); 25 | } 26 | }).keyup(function(e){ 27 | var keyCode = e.which || e.keyCode; 28 | if(keyCode==13){ 29 | $("#sendBtn").click(); 30 | } 31 | }); 32 | }); 33 | 34 | function sendMess(mess) { 35 | send(true, "{'code':10002,'mess':'"+mess+"'}"); 36 | } 37 | ; 38 | 39 | 40 | function userLogin() { 41 | if (!userNick) { 42 | userNick = $('#nick').val().trim(); 43 | } 44 | if (userNick) { 45 | if (!window.WebSocket) { 46 | window.WebSocket = window.MozWebSocket; 47 | } 48 | if (window.WebSocket) { 49 | window.socket = new WebSocket("ws://localhost:9090/websocket"); 50 | window.socket.onmessage = function (event) { 51 | var data = eval("(" + event.data + ")"); 52 | console.log("onmessage data: " + JSON.stringify(data)); 53 | switch (data.head) { 54 | case 1 << 8 | 220: // ping message 55 | case 2 << 8 | 220: // pong message 56 | console.log("ping message: " + JSON.stringify(data)); 57 | pingInvake(data); 58 | break; 59 | case 3 << 8 | 220: // system message 60 | console.log("system message: " + JSON.stringify(data)); 61 | sysInvake(data); 62 | break; 63 | case 4 << 8 | 220: // error message 64 | console.log("error message: " + JSON.stringify(data)); 65 | closeInvake(null); 66 | break; 67 | case 5 << 8 | 220: // auth message 68 | console.log("auth message: " + JSON.stringify(data)); 69 | break; 70 | case 6 << 8 | 220: // broadcast message 71 | console.log("broadcast message: " + JSON.stringify(data)); 72 | broadcastInvake(data); 73 | break; 74 | 75 | } 76 | }; 77 | window.socket.onclose = function (event) { 78 | console.log("connection close!!!"); 79 | closeInvake(event); 80 | }; 81 | window.socket.onopen = function (event) { 82 | console.log("connection success!!"); 83 | openInvake(event); 84 | }; 85 | } else { 86 | alert("您的浏览器不支持WebSocket!!!"); 87 | } 88 | } else { 89 | $('#tipMsg').text("请输入昵称"); 90 | $('#tipModal').modal('show'); 91 | } 92 | } 93 | 94 | function send(auth, mess) { 95 | if (!window.socket) { 96 | return; 97 | } 98 | if (socket.readyState == WebSocket.OPEN || auth) { 99 | console.log("send: " + mess); 100 | window.socket.send(mess); 101 | } else { 102 | $('#tipMsg').text("连接没有成功,请重新登录"); 103 | $('#tipModal').modal('show'); 104 | } 105 | } 106 | ; 107 | 108 | function openInvake(event) { 109 | var obj = {}; 110 | obj.code = 10001; 111 | obj.nick = $('#nick').val().trim(); 112 | send(true, JSON.stringify(obj)); 113 | } 114 | ; 115 | 116 | 117 | function closeInvake(event) { 118 | window.socket = null; 119 | window.isAuth = false; 120 | window.userCount = 0; 121 | $('#tipMsg').text("登录失败,网络连接异常"); 122 | $('#tipModal').modal('show'); 123 | } 124 | ; 125 | 126 | /** 127 | * 处理系统消息 128 | * @param data 129 | */ 130 | function sysInvake(data) { 131 | switch (data.extend.code) { 132 | case 20001: // user count 133 | console.log("current user: " + data.extend.mess); 134 | userCount = data.extend.mess; 135 | $('#userCount').text(userCount); 136 | break; 137 | case 20002: // auth 138 | console.log("auth result: " + data.extend.mess); 139 | isAuth = data.extend.mess; 140 | if (isAuth) { 141 | $("#menuModal").modal('hide'); 142 | $('#chatWin').show(); 143 | $('#content').append('欢迎来到聊天室!!
'); 144 | // $('#content').scrollTop($('#content')[0].scrollHeight); 145 | } 146 | break; 147 | case 20003: // system message 148 | $('#content').append(data.extend.mess); 149 | console.log("system message: " + data.extend.mess); 150 | break; 151 | case 20004: // list message 先将在线用户列表打印出来 152 | console.log("list message: " , data.extend.mess); 153 | var userList = $('.user-list') 154 | var users = data.extend.mess; 155 | userList[0].innerHTML = ''; 156 | users.forEach(function(user){ 157 | var userName = user.slice(0,user.indexOf('(')) 158 | userList.append($('
  • '+userName+'
  • ')) 159 | }) 160 | break; 161 | } 162 | } 163 | ; 164 | 165 | /** 166 | * 处理广播消息 167 | * @param data 168 | */ 169 | function broadcastInvake(data) { 170 | var mess = data.body; 171 | var nick = data.extend.nick; 172 | var uid = data.extend.uid; 173 | var time = data.extend.time; 174 | mess = replace_em(mess); 175 | var html = '
    '+nick+' ('+uid+')  '+time+'
    '+mess+'
    '; 176 | $("#content").append(html); 177 | $('#content').scrollTop($('#content')[0].scrollHeight); 178 | 179 | } 180 | ; 181 | 182 | function erorInvake(data) { 183 | 184 | } 185 | ; 186 | 187 | /** 188 | * 处理ping消息 189 | * @param data 190 | */ 191 | function pingInvake(data) { 192 | //发送pong消息响应 193 | send(isAuth, "{'code':10012}"); 194 | }; 195 | //查看结果 196 | function replace_em(str) { 197 | str = str.replace(/\/g, '>'); 199 | str = str.replace(/\n/g, '
    '); 200 | str = str.replace(/\[em_([0-9]*)\]/g, ''); 201 | return str; 202 | }; -------------------------------------------------------------------------------- /src/main/java/com/qg/fangrui/handler/UserAuthHandler.java: -------------------------------------------------------------------------------- 1 | package com.qg.fangrui.handler; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.qg.fangrui.model.UserInfo; 5 | import com.qg.fangrui.util.Constants; 6 | import com.qg.fangrui.util.NettyUtil; 7 | import io.netty.channel.Channel; 8 | import io.netty.channel.ChannelHandlerContext; 9 | import io.netty.channel.SimpleChannelInboundHandler; 10 | import io.netty.handler.codec.http.FullHttpRequest; 11 | import io.netty.handler.codec.http.websocketx.*; 12 | import io.netty.handler.timeout.IdleState; 13 | import io.netty.handler.timeout.IdleStateEvent; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | /** 18 | * 用户认证处理类 19 | * Created by FunriLy on 2017/6/11. 20 | * From small beginnings comes great things. 21 | */ 22 | public class UserAuthHandler extends SimpleChannelInboundHandler { 23 | 24 | private static final Logger logger = LoggerFactory.getLogger(UserAuthHandler.class); 25 | 26 | private WebSocketServerHandshaker handshaker; 27 | 28 | @Override 29 | protected void messageReceived(ChannelHandlerContext ctx, Object o) throws Exception { 30 | //传统HTTP接入 31 | if (o instanceof FullHttpRequest) { 32 | handleHttpRequest(ctx, (FullHttpRequest) o); 33 | } 34 | //WebSocket接入 35 | else if (o instanceof WebSocketFrame) { 36 | handleWebSocket(ctx, (WebSocketFrame) o); 37 | } 38 | } 39 | 40 | /** 41 | * 重写心跳检测机制 42 | * @param ctx 43 | * @param evt 44 | * @throws Exception 45 | */ 46 | @Override 47 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 48 | //判断evt事件是不是IdleStateEvent事件 49 | if (evt instanceof IdleStateEvent) { 50 | IdleStateEvent evnet = (IdleStateEvent) evt; 51 | //判断是读空闲事件还是写空闲事件还是读写空闲事件 52 | if (evnet.state().equals(IdleState.READER_IDLE)) { 53 | //读操作发起操作 54 | final String remoteAddress = NettyUtil.parseChannelRemoteAddr(ctx.channel()); 55 | logger.warn("NETTY SERVER PIPELINE: IDLE exception [{}]", remoteAddress); 56 | //移除用户并更新数量 57 | UserInfo userInfo = UserInfoManager.getUserInfo(ctx.channel()); 58 | UserInfoManager.removeChannel(ctx.channel()); 59 | UserInfoManager.broadCastInfo(Constants.SYSTEM_USER_COUNT,UserInfoManager.getAuthUserCount()); 60 | UserInfoManager.broadCastInfo(Constants.SYSTEM_USER_LIST, UserInfoManager.getUserInfoList()); 61 | if (null != userInfo){ 62 | UserInfoManager.broadCastInfo(Constants.SYSTEM_OTHER_INFO, "心跳检测发生异常,用户 "+userInfo.getNick()+"(" 63 | +userInfo.getUserId()+") 已经强制下线!
    "); 64 | 65 | } 66 | } 67 | } 68 | ctx.fireUserEventTriggered(evt); 69 | } 70 | 71 | private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request) { 72 | //对URL进行判断,如果HTTP解码失败,返回HTTP异常 73 | if(!request.decoderResult().isSuccess() || !"websocket".equals(request.headers().get("Upgrade"))){ 74 | logger.warn("protobuf don't support websocket"); 75 | ctx.channel().close(); 76 | return; 77 | } 78 | WebSocketServerHandshakerFactory handshakerFactory = new WebSocketServerHandshakerFactory( 79 | Constants.WEBSOCKET_URL,null, false); 80 | handshaker = handshakerFactory.newHandshaker(request); 81 | 82 | if (handshaker == null){ 83 | WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel()); 84 | } else { 85 | // 动态加入websocket的编解码处理 86 | handshaker.handshake(ctx.channel(), request); 87 | UserInfo userInfo = new UserInfo(); 88 | userInfo.setAddr(NettyUtil.parseChannelRemoteAddr(ctx.channel())); 89 | // 存储已经连接的Channel 90 | UserInfoManager.addChannel(ctx.channel()); 91 | } 92 | } 93 | 94 | private void handleWebSocket(ChannelHandlerContext ctx, WebSocketFrame frame) { 95 | // 判断是否关闭链路命令 96 | if (frame instanceof CloseWebSocketFrame) { 97 | handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain()); 98 | UserInfo userInfo = UserInfoManager.getUserInfo(ctx.channel()); 99 | System.out.println(userInfo.toString()); 100 | 101 | UserInfoManager.removeChannel(ctx.channel()); 102 | //TODO:提示用户退出聊天室 103 | if (null != userInfo) { 104 | UserInfoManager.broadCastInfo(Constants.SYSTEM_OTHER_INFO, "用户 "+userInfo.getNick()+"(" 105 | +userInfo.getUserId()+") 退出聊天室!
    "); 106 | } 107 | return; 108 | } 109 | // 判断是否Ping消息 110 | if (frame instanceof PingWebSocketFrame) { 111 | logger.info("ping message:{}", frame.content().retain()); 112 | ctx.writeAndFlush(new PongWebSocketFrame(frame.content().retain())); 113 | return; 114 | } 115 | // 判断是否Pong消息 116 | if (frame instanceof PongWebSocketFrame) { 117 | logger.info("pong message:{}", frame.content().retain()); 118 | ctx.writeAndFlush(new PongWebSocketFrame(frame.content().retain())); 119 | return; 120 | } 121 | //非文本(二进制)信息 122 | if (!(frame instanceof TextWebSocketFrame)) { 123 | throw new UnsupportedOperationException(frame.getClass().getName() + " frame type not supported"); 124 | } 125 | 126 | String message = ((TextWebSocketFrame) frame).text(); 127 | JSONObject json = JSONObject.parseObject(message); 128 | int code = json.getInteger("code"); 129 | Channel channel = ctx.channel(); 130 | switch (code){ 131 | case Constants.PING_CODE: 132 | case Constants.PONG_CODE:{ 133 | UserInfoManager.updateUserTime(channel); 134 | logger.info("receive pong message, address: {}",NettyUtil.parseChannelRemoteAddr(channel)); 135 | return; 136 | } 137 | case Constants.AUTH_CODe:{ 138 | boolean isSuccess = UserInfoManager.saveUser(channel, json.getString("nick")); 139 | UserInfoManager.sendInfo(channel,Constants.SYSTEM_AUTH_STATE,isSuccess); 140 | if (isSuccess) { 141 | UserInfo userInfo = UserInfoManager.getUserInfo(channel); 142 | //更新在线人数 143 | UserInfoManager.broadCastInfo(Constants.SYSTEM_USER_COUNT,UserInfoManager.getAuthUserCount()); 144 | //更新列表 145 | UserInfoManager.broadCastInfo(Constants.SYSTEM_USER_LIST, UserInfoManager.getUserInfoList()); 146 | if (null != userInfo) { 147 | //增加人数 148 | UserInfoManager.broadCastInfo(Constants.SYSTEM_OTHER_INFO, "用户 "+ userInfo.getNick() + "(" + userInfo.getUserId() 149 | + ") 进入网络聊天室,大家热烈欢迎~
    "); 150 | } 151 | } 152 | return; 153 | } 154 | case Constants.MESS_CODE: //普通的消息留给MessageHandler处理 155 | break; 156 | case Constants.PRIV_CODE: //私聊的消息留给MessageHandler处理 157 | break; 158 | default: 159 | logger.warn("The code [{}] can't be auth!!!", code); 160 | return; 161 | } 162 | //后续消息交给MessageHandler处理 163 | ctx.fireChannelRead(frame.retain()); 164 | } 165 | 166 | @Override 167 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception{ 168 | logger.warn("NETTY SERVER PIPELINE: Unknown exception [{}]", cause.getMessage()); 169 | UserInfoManager.removeChannel(ctx.channel()); 170 | UserInfoManager.broadCastInfo(Constants.SYSTEM_USER_COUNT, UserInfoManager.getAuthUserCount()); 171 | UserInfoManager.broadCastInfo(Constants.SYSTEM_USER_LIST, UserInfoManager.getUserInfoList()); 172 | UserInfoManager.broadCastInfo(Constants.SYSTEM_OTHER_INFO, "网络发生未知错误,与部分用户断开连接,请确保网络正常!
    "); 173 | } 174 | 175 | } 176 | -------------------------------------------------------------------------------- /src/main/java/com/qg/fangrui/handler/UserInfoManager.java: -------------------------------------------------------------------------------- 1 | package com.qg.fangrui.handler; 2 | 3 | import com.qg.fangrui.model.UserInfo; 4 | import com.qg.fangrui.proto.ChatProto; 5 | import com.qg.fangrui.util.IsEmptyUtil; 6 | import com.qg.fangrui.util.NettyUtil; 7 | import io.netty.channel.Channel; 8 | import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.Set; 15 | import java.util.concurrent.ConcurrentHashMap; 16 | import java.util.concurrent.ConcurrentMap; 17 | import java.util.concurrent.atomic.AtomicInteger; 18 | import java.util.concurrent.locks.ReentrantReadWriteLock; 19 | 20 | /** 21 | * Channel 管理器 22 | * Created by FunriLy on 2017/6/11. 23 | * From small beginnings comes great things. 24 | */ 25 | public class UserInfoManager { 26 | 27 | private static final Logger logger = LoggerFactory.getLogger(UserInfoManager.class); 28 | //设置读写锁 29 | private static ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(true); 30 | //构建安全的数据结构 31 | private static ConcurrentMap userInfos = new ConcurrentHashMap(); 32 | private static AtomicInteger userCount = new AtomicInteger(0); 33 | 34 | /** 35 | * 添加Channel 36 | * @param channel 37 | */ 38 | public static void addChannel(Channel channel){ 39 | String remoteAddress = NettyUtil.parseChannelRemoteAddr(channel); 40 | if (!channel.isActive()){ 41 | logger.error("channel is not active, address : {}", remoteAddress); 42 | } 43 | 44 | UserInfo userInfo = new UserInfo(); 45 | userInfo.setAddr(remoteAddress); 46 | userInfo.setChannel(channel); 47 | userInfo.setTime(System.currentTimeMillis()); 48 | userInfos.put(channel, userInfo); 49 | } 50 | 51 | /** 52 | * 保存用户信息与Channel 53 | * @param channel 54 | * @param nick 55 | * @return 56 | */ 57 | public static boolean saveUser(Channel channel, String nick){ 58 | UserInfo userInfo = userInfos.get(channel); 59 | if (null == userInfo){ 60 | return false; 61 | } 62 | if (!channel.isActive()) { 63 | logger.error("channel is not active, address: {}, nick: {}", userInfo.getAddr(), nick); 64 | return false; 65 | } 66 | 67 | //添加认证 68 | userCount.incrementAndGet(); 69 | userInfo.setNick(nick); 70 | userInfo.setAuth(true); 71 | userInfo.setUserId(); 72 | userInfo.setTime(System.currentTimeMillis()); 73 | return true; 74 | } 75 | 76 | /** 77 | * 从缓存中移除Channel,并且关闭Channel 78 | * @param channel 79 | */ 80 | public static void removeChannel(Channel channel){ 81 | try { 82 | logger.warn("channel will be remove, address is :{}", NettyUtil.parseChannelRemoteAddr(channel)); 83 | //加写锁 84 | rwLock.writeLock().lock(); 85 | channel.close(); 86 | UserInfo userInfo = userInfos.get(channel); 87 | if (null != userInfo){ 88 | UserInfo tmp = userInfos.remove(channel); 89 | if (tmp != null && tmp.isAuth()){ 90 | //减少认证用户 91 | userCount.decrementAndGet(); 92 | } 93 | } 94 | } finally { 95 | rwLock.writeLock().unlock(); //解锁 96 | } 97 | } 98 | 99 | /** 100 | * 广播普通消息 101 | * @param uid 102 | * @param nick 103 | * @param message 104 | */ 105 | public static void broadcastMess(int uid, String nick, String message) { 106 | if (!IsEmptyUtil.isEmpty(message)) { 107 | try { 108 | rwLock.readLock().lock(); 109 | Set keySet = userInfos.keySet(); 110 | for (Channel channel : keySet) { 111 | UserInfo userInfo = userInfos.get(channel); 112 | if (userInfo == null || !userInfo.isAuth()) continue; 113 | channel.writeAndFlush(new TextWebSocketFrame(ChatProto.buildMessProto(uid, nick, message))); 114 | } 115 | } finally { 116 | rwLock.readLock().unlock(); 117 | } 118 | } 119 | } 120 | 121 | public static void broadcastMessToSomeone(int from, int to, String message){ 122 | // TODO: 2017/6/23 123 | } 124 | 125 | /** 126 | * 广播系统消息 127 | * @param code 128 | * @param mess 129 | */ 130 | public static void broadCastInfo(int code, Object mess) { 131 | try { 132 | rwLock.readLock().lock(); 133 | Set keySet = userInfos.keySet(); 134 | for (Channel channel : keySet){ 135 | UserInfo userInfo = userInfos.get(channel); 136 | if (null == userInfo || !userInfo.isAuth()) continue; 137 | channel.writeAndFlush(new TextWebSocketFrame(ChatProto.buildSystProto(code, mess))); 138 | } 139 | } finally { 140 | rwLock.readLock().unlock(); 141 | } 142 | } 143 | 144 | /** 145 | * 广播Ping 146 | */ 147 | public static void broadCastPing(){ 148 | try { 149 | rwLock.readLock().lock(); 150 | logger.info("broadCastPing userCount: {}", userCount.intValue()); 151 | Set keySet = userInfos.keySet(); 152 | for (Channel channel : keySet) { 153 | UserInfo userInfo = userInfos.get(channel); 154 | if (userInfo == null || !userInfo.isAuth()) continue; 155 | channel.writeAndFlush(new TextWebSocketFrame(ChatProto.buildPingProto())); 156 | } 157 | } finally { 158 | rwLock.readLock().unlock(); 159 | } 160 | } 161 | 162 | /** 163 | * 向某个Channel发送系统消息 164 | * @param channel 165 | * @param code 166 | * @param mess 167 | */ 168 | public static void sendInfo(Channel channel, int code, Object mess) { 169 | channel.writeAndFlush(new TextWebSocketFrame(ChatProto.buildSystProto(code, mess))); 170 | } 171 | 172 | /** 173 | * 向某个Channel发送Pong 174 | * @param channel 175 | */ 176 | public static void sendPong(Channel channel) { 177 | channel.writeAndFlush(new TextWebSocketFrame(ChatProto.buildPongProto())); 178 | } 179 | 180 | /** 181 | * 扫描并关闭失效的Channel 182 | */ 183 | public static void scanNotActiveChannel() { 184 | Set keySet = userInfos.keySet(); 185 | for (Channel channel : keySet) { 186 | UserInfo userInfo = userInfos.get(channel); 187 | if (userInfo == null) continue; 188 | if (!channel.isOpen() || !channel.isActive() || (!userInfo.isAuth() && 189 | (System.currentTimeMillis() - userInfo.getTime()) > 10000)) { 190 | removeChannel(channel); 191 | } 192 | } 193 | } 194 | 195 | /** 196 | * 获取某个Channel的用户信息 197 | * @param channel 198 | * @return 199 | */ 200 | public static UserInfo getUserInfo(Channel channel) { 201 | return userInfos.get(channel); 202 | } 203 | 204 | /** 205 | * 获取所有在线用户集合 206 | * @return 207 | */ 208 | public static ConcurrentMap getUserInfos() { 209 | return userInfos; 210 | } 211 | 212 | /** 213 | * 获得在线晕乎乎列表 214 | * @return 215 | */ 216 | public static List getUserInfoList(){ 217 | List userInfoList = new ArrayList(); 218 | try { 219 | rwLock.readLock().lock(); 220 | Set keySet = userInfos.keySet(); 221 | for (Channel channel : keySet) { 222 | UserInfo userInfo = getUserInfo(channel); 223 | if (null == userInfo) continue; 224 | userInfoList.add(userInfo.getNick() + "(" + userInfo.getUserId() + ")"); 225 | } 226 | } finally { 227 | rwLock.readLock().unlock(); 228 | } 229 | return userInfoList; 230 | } 231 | 232 | /** 233 | * 获取在线人数 234 | * @return 235 | */ 236 | public static int getAuthUserCount() { 237 | return userCount.get(); 238 | } 239 | 240 | /** 241 | * 更新用户的登录时间 242 | * @param channel 243 | */ 244 | public static void updateUserTime(Channel channel) { 245 | UserInfo userInfo = getUserInfo(channel); 246 | if (userInfo != null) { 247 | userInfo.setTime(System.currentTimeMillis()); 248 | } 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /src/main/webapp/bootstrap/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.6 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under the MIT license 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>2)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 3")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.6",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.6",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),a(c.target).is('input[type="radio"]')||a(c.target).is('input[type="checkbox"]')||c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.6",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.6",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.6",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
    ',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),c.isInStateTrue()?void 0:(clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide())},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.6",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.6",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.6",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); -------------------------------------------------------------------------------- /src/main/webapp/bootstrap/js/jQuery-2.2.0.min.js: -------------------------------------------------------------------------------- 1 | /*! jQuery v2.2.0 | (c) jQuery Foundation | jquery.org/license */ 2 | !function(a,b){"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){var c=[],d=a.document,e=c.slice,f=c.concat,g=c.push,h=c.indexOf,i={},j=i.toString,k=i.hasOwnProperty,l={},m="2.2.0",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return e.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:e.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a){return n.each(this,a)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(e.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:g,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){var b=a&&a.toString();return!n.isArray(a)&&b-parseFloat(b)+1>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!k.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?i[j.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=d.createElement("script"),b.text=a,d.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b){var c,d=0;if(s(a)){for(c=a.length;c>d;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):g.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:h.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,g=0,h=[];if(s(a))for(d=a.length;d>g;g++)e=b(a[g],g,c),null!=e&&h.push(e);else for(g in a)e=b(a[g],g,c),null!=e&&h.push(e);return f.apply([],h)},guid:1,proxy:function(a,b){var c,d,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(d=e.call(arguments,2),f=function(){return a.apply(b||this,d.concat(e.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:l}),"function"==typeof Symbol&&(n.fn[Symbol.iterator]=c[Symbol.iterator]),n.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){i["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=!!a&&"length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=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=ga(),z=ga(),A=ga(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+M+"))|)"+L+"*\\]",O=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+N+")*)|.*)\\)|)",P=new RegExp(L+"+","g"),Q=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),R=new RegExp("^"+L+"*,"+L+"*"),S=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),T=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),U=new RegExp(O),V=new RegExp("^"+M+"$"),W={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M+"|[*])"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},X=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Z=/^[^{]+\{\s*\[native \w/,$=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,_=/[+~]/,aa=/'|\\/g,ba=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),ca=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},da=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(ea){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fa(a,b,d,e){var f,h,j,k,l,o,r,s,w=b&&b.ownerDocument,x=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==x&&9!==x&&11!==x)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==x&&(o=$.exec(a)))if(f=o[1]){if(9===x){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(w&&(j=w.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(o[2])return H.apply(d,b.getElementsByTagName(a)),d;if((f=o[3])&&c.getElementsByClassName&&b.getElementsByClassName)return H.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==x)w=b,s=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(aa,"\\$&"):b.setAttribute("id",k=u),r=g(a),h=r.length,l=V.test(k)?"#"+k:"[id='"+k+"']";while(h--)r[h]=l+" "+qa(r[h]);s=r.join(","),w=_.test(a)&&oa(b.parentNode)||b}if(s)try{return H.apply(d,w.querySelectorAll(s)),d}catch(y){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(Q,"$1"),b,d,e)}function ga(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ha(a){return a[u]=!0,a}function ia(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ja(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function ka(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function la(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function na(a){return ha(function(b){return b=+b,ha(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 oa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=fa.support={},f=fa.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fa.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),(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ia(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ia(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Z.test(n.getElementsByClassName),c.getById=ia(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(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(ba,ca);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),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){return"undefined"!=typeof b.getElementsByClassName&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=Z.test(n.querySelectorAll))&&(ia(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ia(function(a){var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Z.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ia(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",O)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Z.test(o.compareDocumentPosition),t=b||Z.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?J(k,a)-J(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?J(k,a)-J(k,b):0;if(e===f)return ka(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?ka(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},fa.matches=function(a,b){return fa(a,null,null,b)},fa.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(T,"='$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 fa(b,n,null,[a]).length>0},fa.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fa.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.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},fa.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fa.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=fa.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=fa.selectors={cacheLength:50,createPseudo:ha,match:W,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(ba,ca),a[3]=(a[3]||a[4]||a[5]||"").replace(ba,ca),"~="===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]||fa.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]&&fa.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return W.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&U.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(ba,ca).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("(^|"+L+")"+a+"("+L+"|$)"))&&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=fa.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(P," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},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()]||fa.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ha(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ha(function(a){var b=[],c=[],d=h(a.replace(Q,"$1"));return d[u]?ha(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:ha(function(a){return function(b){return fa(a,b).length>0}}),contains:ha(function(a){return a=a.replace(ba,ca),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ha(function(a){return V.test(a||"")||fa.error("unsupported lang: "+a),a=a.replace(ba,ca).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:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!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 Y.test(a.nodeName)},input:function(a){return X.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:na(function(){return[0]}),last:na(function(a,b){return[b-1]}),eq:na(function(a,b,c){return[0>c?c+b:c]}),even:na(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:na(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:na(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:na(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function ra(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j,k=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(j=b[u]||(b[u]={}),i=j[b.uniqueID]||(j[b.uniqueID]={}),(h=i[d])&&h[0]===w&&h[1]===f)return k[2]=h[2];if(i[d]=k,k[2]=a(b,c,g))return!0}}}function sa(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ta(a,b,c){for(var d=0,e=b.length;e>d;d++)fa(a,b[d],c);return c}function ua(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function va(a,b,c,d,e,f){return d&&!d[u]&&(d=va(d)),e&&!e[u]&&(e=va(e,f)),ha(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ta(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ua(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ua(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ua(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function wa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ra(function(a){return a===b},h,!0),l=ra(function(a){return J(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}];f>i;i++)if(c=d.relative[a[i].type])m=[ra(sa(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return va(i>1&&sa(m),i>1&&qa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(Q,"$1"),c,e>i&&wa(a.slice(i,e)),f>e&&wa(a=a.slice(e)),f>e&&qa(a))}m.push(c)}return sa(m)}function xa(a,b){var c=b.length>0,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]=F.call(i));u=ua(u)}H.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&fa.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ha(f):f}return h=fa.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xa(e,d)),f.selector=a}return f},i=fa.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ba,ca),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=W.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ba,ca),_.test(j[0].type)&&oa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qa(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,!b||_.test(a)&&oa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ia(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ia(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ja("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ia(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ja("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ia(function(a){return null==a.getAttribute("disabled")})||ja(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fa}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.uniqueSort=n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},v=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},w=n.expr.match.needsContext,x=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,y=/^.[^:#\[\.,]*$/;function z(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(y.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return h.call(b,a)>-1!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(z(this,a||[],!1))},not:function(a){return this.pushStack(z(this,a||[],!0))},is:function(a){return!!z(this,"string"==typeof a&&w.test(a)?n(a):a||[],!1).length}});var A,B=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=n.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||A,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:B.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 n?b[0]:b,n.merge(this,n.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),x.test(e[1])&&n.isPlainObject(b))for(e in b)n.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&f.parentNode&&(this.length=1,this[0]=f),this.context=d,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?void 0!==c.ready?c.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};C.prototype=n.fn,A=n(d);var D=/^(?:parents|prev(?:Until|All))/,E={children:!0,contents:!0,next:!0,prev:!0};n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=w.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?h.call(n(a),this[0]):h.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.uniqueSort(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function F(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return u(a,"parentNode")},parentsUntil:function(a,b,c){return u(a,"parentNode",c)},next:function(a){return F(a,"nextSibling")},prev:function(a){return F(a,"previousSibling")},nextAll:function(a){return u(a,"nextSibling")},prevAll:function(a){return u(a,"previousSibling")},nextUntil:function(a,b,c){return u(a,"nextSibling",c)},prevUntil:function(a,b,c){return u(a,"previousSibling",c)},siblings:function(a){return v((a.parentNode||{}).firstChild,a)},children:function(a){return v(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(E[a]||n.uniqueSort(e),D.test(a)&&e.reverse()),this.pushStack(e)}});var G=/\S+/g;function H(a){var b={};return n.each(a.match(G)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?H(a):n.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),h>=c&&h--}),this},has:function(a){return a?n.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||(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},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().progress(c.notify).done(c.resolve).fail(c.reject):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=e.call(arguments),d=c.length,f=1!==d||a&&n.isFunction(a.promise)?d:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?e.call(arguments):d,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(d>1)for(i=new Array(d),j=new Array(d),k=new Array(d);d>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().progress(h(b,j,i)).done(h(b,k,c)).fail(g.reject):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(d,[n]),n.fn.triggerHandler&&(n(d).triggerHandler("ready"),n(d).off("ready"))))}});function J(){d.removeEventListener("DOMContentLoaded",J),a.removeEventListener("load",J),n.ready()}n.ready.promise=function(b){return I||(I=n.Deferred(),"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(n.ready):(d.addEventListener("DOMContentLoaded",J),a.addEventListener("load",J))),I.promise(b)},n.ready.promise();var K=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)K(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},L=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function M(){this.expando=n.expando+M.uid++}M.uid=1,M.prototype={register:function(a,b){var c=b||{};return a.nodeType?a[this.expando]=c:Object.defineProperty(a,this.expando,{value:c,writable:!0,configurable:!0}),a[this.expando]},cache:function(a){if(!L(a))return{};var b=a[this.expando];return b||(b={},L(a)&&(a.nodeType?a[this.expando]=b:Object.defineProperty(a,this.expando,{value:b,configurable:!0}))),b},set:function(a,b,c){var d,e=this.cache(a);if("string"==typeof b)e[b]=c;else for(d in b)e[d]=b[d];return e},get:function(a,b){return void 0===b?this.cache(a):a[this.expando]&&a[this.expando][b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=a[this.expando];if(void 0!==f){if(void 0===b)this.register(a);else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in f?d=[b,e]:(d=e,d=d in f?[d]:d.match(G)||[])),c=d.length;while(c--)delete f[d[c]]}(void 0===b||n.isEmptyObject(f))&&(a.nodeType?a[this.expando]=void 0:delete a[this.expando])}},hasData:function(a){var b=a[this.expando];return void 0!==b&&!n.isEmptyObject(b)}};var N=new M,O=new M,P=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,Q=/[A-Z]/g;function R(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(Q,"-$&").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:P.test(c)?n.parseJSON(c):c}catch(e){}O.set(a,b,c); 3 | }else c=void 0;return c}n.extend({hasData:function(a){return O.hasData(a)||N.hasData(a)},data:function(a,b,c){return O.access(a,b,c)},removeData:function(a,b){O.remove(a,b)},_data:function(a,b,c){return N.access(a,b,c)},_removeData:function(a,b){N.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=O.get(f),1===f.nodeType&&!N.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),R(f,d,e[d])));N.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){O.set(this,a)}):K(this,function(b){var c,d;if(f&&void 0===b){if(c=O.get(f,a)||O.get(f,a.replace(Q,"-$&").toLowerCase()),void 0!==c)return c;if(d=n.camelCase(a),c=O.get(f,d),void 0!==c)return c;if(c=R(f,d,void 0),void 0!==c)return c}else d=n.camelCase(a),this.each(function(){var c=O.get(this,d);O.set(this,d,b),a.indexOf("-")>-1&&void 0!==c&&O.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){O.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=N.get(a,b),c&&(!d||n.isArray(c)?d=N.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.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 N.get(a,c)||N.access(a,c,{empty:n.Callbacks("once memory").add(function(){N.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length",""],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};$.optgroup=$.option,$.tbody=$.tfoot=$.colgroup=$.caption=$.thead,$.th=$.td;function _(a,b){var c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function aa(a,b){for(var c=0,d=a.length;d>c;c++)N.set(a[c],"globalEval",!b||N.get(b[c],"globalEval"))}var ba=/<|&#?\w+;/;function ca(a,b,c,d,e){for(var f,g,h,i,j,k,l=b.createDocumentFragment(),m=[],o=0,p=a.length;p>o;o++)if(f=a[o],f||0===f)if("object"===n.type(f))n.merge(m,f.nodeType?[f]:f);else if(ba.test(f)){g=g||l.appendChild(b.createElement("div")),h=(Y.exec(f)||["",""])[1].toLowerCase(),i=$[h]||$._default,g.innerHTML=i[1]+n.htmlPrefilter(f)+i[2],k=i[0];while(k--)g=g.lastChild;n.merge(m,g.childNodes),g=l.firstChild,g.textContent=""}else m.push(b.createTextNode(f));l.textContent="",o=0;while(f=m[o++])if(d&&n.inArray(f,d)>-1)e&&e.push(f);else if(j=n.contains(f.ownerDocument,f),g=_(l.appendChild(f),"script"),j&&aa(g),c){k=0;while(f=g[k++])Z.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),l.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="",l.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var da=/^key/,ea=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,fa=/^([^.]*)(?:\.(.+)|)/;function ga(){return!0}function ha(){return!1}function ia(){try{return d.activeElement}catch(a){}}function ja(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)ja(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=ha;else if(!e)return this;return 1===f&&(g=e,e=function(a){return n().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=n.guid++)),a.each(function(){n.event.add(this,b,e,d,c)})}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=N.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return"undefined"!=typeof n&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(G)||[""],j=b.length;while(j--)h=fa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,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),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=N.hasData(a)&&N.get(a);if(r&&(i=r.events)){b=(b||"").match(G)||[""],j=b.length;while(j--)if(h=fa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==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,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&N.remove(a,"handle events")}},dispatch:function(a){a=n.event.fix(a);var b,c,d,f,g,h=[],i=e.call(arguments),j=(N.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.rnamespace||a.rnamespace.test(g.namespace))&&(a.handleObj=g,a.data=g.data,d=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==d&&(a.result=d)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&("click"!==a.type||isNaN(a.button)||a.button<1))for(;i!==this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>-1:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,la=/\s*$/g;function pa(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a:a}function qa(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function ra(a){var b=na.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function sa(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(N.hasData(a)&&(f=N.access(a),g=N.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}O.hasData(a)&&(h=O.access(a),i=n.extend({},h),O.set(b,i))}}function ta(a,b){var c=b.nodeName.toLowerCase();"input"===c&&X.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}function ua(a,b,c,d){b=f.apply([],b);var e,g,h,i,j,k,m=0,o=a.length,p=o-1,q=b[0],r=n.isFunction(q);if(r||o>1&&"string"==typeof q&&!l.checkClone&&ma.test(q))return a.each(function(e){var f=a.eq(e);r&&(b[0]=q.call(this,e,f.html())),ua(f,b,c,d)});if(o&&(e=ca(b,a[0].ownerDocument,!1,a,d),g=e.firstChild,1===e.childNodes.length&&(e=g),g||d)){for(h=n.map(_(e,"script"),qa),i=h.length;o>m;m++)j=e,m!==p&&(j=n.clone(j,!0,!0),i&&n.merge(h,_(j,"script"))),c.call(a[m],j,m);if(i)for(k=h[h.length-1].ownerDocument,n.map(h,ra),m=0;i>m;m++)j=h[m],Z.test(j.type||"")&&!N.access(j,"globalEval")&&n.contains(k,j)&&(j.src?n._evalUrl&&n._evalUrl(j.src):n.globalEval(j.textContent.replace(oa,"")))}return a}function va(a,b,c){for(var d,e=b?n.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||n.cleanData(_(d)),d.parentNode&&(c&&n.contains(d.ownerDocument,d)&&aa(_(d,"script")),d.parentNode.removeChild(d));return a}n.extend({htmlPrefilter:function(a){return a.replace(ka,"<$1>")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=_(h),f=_(a),d=0,e=f.length;e>d;d++)ta(f[d],g[d]);if(b)if(c)for(f=f||_(a),g=g||_(h),d=0,e=f.length;e>d;d++)sa(f[d],g[d]);else sa(a,h);return g=_(h,"script"),g.length>0&&aa(g,!i&&_(a,"script")),h},cleanData:function(a){for(var b,c,d,e=n.event.special,f=0;void 0!==(c=a[f]);f++)if(L(c)){if(b=c[N.expando]){if(b.events)for(d in b.events)e[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);c[N.expando]=void 0}c[O.expando]&&(c[O.expando]=void 0)}}}),n.fn.extend({domManip:ua,detach:function(a){return va(this,a,!0)},remove:function(a){return va(this,a)},text:function(a){return K(this,function(a){return void 0===a?n.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 ua(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=pa(this,a);b.appendChild(a)}})},prepend:function(){return ua(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=pa(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return ua(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return ua(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&&(n.cleanData(_(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return K(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&&!la.test(a)&&!$[(Y.exec(a)||["",""])[1].toLowerCase()]){a=n.htmlPrefilter(a);try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(_(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=[];return ua(this,arguments,function(b){var c=this.parentNode;n.inArray(this,a)<0&&(n.cleanData(_(this)),c&&c.replaceChild(b,this))},a)}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),f=e.length-1,h=0;f>=h;h++)c=h===f?this:this.clone(!0),n(e[h])[b](c),g.apply(d,c.get());return this.pushStack(d)}});var wa,xa={HTML:"block",BODY:"block"};function ya(a,b){var c=n(b.createElement(a)).appendTo(b.body),d=n.css(c[0],"display");return c.detach(),d}function za(a){var b=d,c=xa[a];return c||(c=ya(a,b),"none"!==c&&c||(wa=(wa||n("