notify.js 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031
  1. (function(window, undefined) {
  2. var protocol = window.location.protocol;
  3. var localUreadData=[],hasNewMsg=false,hasGetData=false;
  4. var defaults = {
  5. realtime: protocol+'//s{0}-im-notify.csdn.net'
  6. , api: protocol+'//svc-notify.csdn.net'
  7. , app:'http://msg.csdn.net'
  8. , space: protocol+'//my.csdn.net/'
  9. , count: 5
  10. , subCount: 5
  11. , staticUrl: protocol+'//csdnimg.cn/rabbit/notev2/'
  12. , cssUrl: 'css/style.css'
  13. , 'realtime.js': 'js/realtime.js' // base staticUrl
  14. // , 'channel': 'message'
  15. , 'socket.io.js': 'js/socket.io.js'
  16. // , 'socket.io options': { transports: ["xhr-polling", "jsonp-polling"]}
  17. }
  18. //
  19. , csdn = window.csdn || (window.csdn = {})
  20. , isShowMsg = true
  21. // && location.search === '?smsg=1' // DEV
  22. , friendlyErrors = {
  23. 'timeout': '请求超时,请检查网络后重试!'
  24. , 'abort': '请求被中止!'
  25. , 'parsererror': '解析错误,请检查网络后重试!'
  26. , 'No id supplied': '未登录或登录超时,请重新登录!'
  27. , 'locked': ''
  28. }
  29. ;
  30. var getCookie =function (objName){//获取指定名称的cookie的值
  31. var arrStr = document.cookie.split("; ");
  32. for(var i = 0;i < arrStr.length;i ++){
  33. var temp = arrStr[i].split("=");
  34. if(temp[0] == objName) return decodeURI(temp[1]);
  35. }
  36. }
  37. //
  38. if (Function.prototype.bind === undefined) {
  39. Function.prototype.bind = function (ctx) {
  40. var self = this;
  41. return function () {
  42. self.apply(ctx, [].slice(arguments, 0));
  43. };
  44. };
  45. }
  46. function log() {
  47. if (typeof console !== 'undefined') {
  48. Function.prototype.apply.call(console.log, console, Array.prototype.slice.call(arguments));
  49. }
  50. }
  51. var $
  52. , currUser = { username: '' } //登录用户对象
  53. ;
  54. csdn.note = function(conf) {
  55. $ = jQuery;
  56. //配置项
  57. this.conf = conf;
  58. //Dom节点
  59. this.Dom = {};
  60. //初始化
  61. this.init();
  62. };
  63. csdn.note.prototype = {
  64. /*
  65. * 【初始化入口】
  66. */
  67. init: function() {
  68. var self = this, opts = self.conf;
  69. opts.wrap = $('#' + opts.wrapId);
  70. if(typeof opts.count === 'string') {
  71. opts.count = parseInt(opts.count, 10) || undefined;
  72. }
  73. if(typeof opts.subCount === 'string') {
  74. opts.subCount = parseInt(opts.subCount, 10) || undefined;
  75. }
  76. self.conf = $.extend({}, defaults,
  77. //
  78. opts);
  79. //初始化消息列表
  80. self.getDoms();
  81. //检查用户登录
  82. self.checkLogin(function(data) {
  83. //
  84. if(isShowMsg) {
  85. //加载样式与事件
  86. self.loadCss(self.staticUrl(self.conf.cssUrl));
  87. self.addEvent();
  88. }
  89. // 初始化实时系统
  90. self.initRealtime();
  91. });
  92. },
  93. loadCss : function(src, callback){
  94. $('<link rel="stylesheet" type="text/css"/>').appendTo('head:first').load(function(){
  95. callback && callback();
  96. }).attr('href', src);
  97. },
  98. /**
  99. * 显示指定范围的.loading元素,返回一个函数,执行后将隐藏loading
  100. * @param {String} [selector=''] css选择器,配合ctx决定使用哪个.loading
  101. * @param {jQuery Object} ctx=wrap .loading所在的上下文
  102. * @return {Function(Function(Boolean loading) doneCallback)}
  103. * 隐藏已显示的.loading元素,并在隐藏完毕后调用doneCallback
  104. * loading参数用于指示.loading当前还在显示中(共享loading时会有这种状况)
  105. */
  106. loading: function (selector, ctx) {
  107. var self = this
  108. , ctx = typeof selector === 'object' ? selector : self.Dom.wrap
  109. , selector = typeof selector === 'string' ? selector : ''
  110. , el = $(selector + ' .loading', ctx)
  111. , num = (el.data('loadingCount') || 0) + 1
  112. , completed = false
  113. ;
  114. el.show().data('loadingCount', num);
  115. return function (callback) {
  116. if(completed) return;
  117. num = el.data('loadingCount') - 1;
  118. if(num <= 0) {
  119. num = 0;
  120. el.fadeOut('fast', function(){
  121. el.hide(); // jQuery 1.4.3存在的bug,当fadeOut元素的父元素不可视时,fadeOut不会改变元素display
  122. callback && callback(false);
  123. });
  124. } else {
  125. callback && callback(true);
  126. }
  127. el.data('loadingCount', num);
  128. completed = true;
  129. }
  130. },
  131. emptyCb: function () {},
  132. wrapCb: function () {
  133. var args = [].slice.call(arguments, 0);
  134. return function (callback) {
  135. callback && callback.apply(args);
  136. };
  137. },
  138. toggleEmpty: function (toggle, ctx, list) {
  139. ctx = typeof ctx === 'string' ? $(ctx, this.Dom.wrap) : ctx;
  140. $('.empty', ctx).toggle(toggle && !$(list, ctx)[0]);
  141. },
  142. staticUrl: function (url) {
  143. // TODO 可配置不同url使用不同时间戳
  144. return this.conf.staticUrl + url + '?4d63d1f';
  145. },
  146. on: function (type, data, handler) {
  147. this.Dom.wrap.bind(type, data, handler);
  148. return this;
  149. },
  150. emit: function (type, params) {
  151. this.Dom.wrap.triggerHandler(type, params)
  152. return this;
  153. },
  154. lock: function (id, callback) {
  155. var self = this;
  156. if(self.lockers === undefined) {
  157. self.lockers = {};
  158. }
  159. if(!id || !self.lockers[id]) { // id为null 空白字符串等表示不使用lock
  160. self.lockers[id || ''] = true;
  161. callback(null, function () {
  162. self.lockers[id || ''] = false;
  163. });
  164. } else {
  165. callback('locked', self.emptyCb);
  166. }
  167. },
  168. /**
  169. * 填充this.Dom,建立必要的背景iframe
  170. * 注意:此时css尚未加载,所以样式都没生效
  171. */
  172. getDoms: function() {
  173. var wrap = this.Dom.wrap = this.conf.wrap
  174. , tip = wrap.next()
  175. ;
  176. if(!tip.is('.csdn_notice_tip')) {
  177. tip = $('.csdn_notice_tip');
  178. }
  179. this.Dom.tip = tip;
  180. this.Dom.btn = this.conf.btn;
  181. wrap.append('<iframe src="about:block" frameborder="0" allowTransparency="true" style="z-index:-1;position:absolute;top:0;left:0;width:100%;height:100%;background:transparent"></iframe>');
  182. return this;
  183. },
  184. resetPosition: function () {
  185. var self = this
  186. , offset = self.Dom.btn.offset()
  187. ;
  188. self.Dom.wrap.css({
  189. left: offset.left - 212 + 'px',
  190. top: offset.top + 25 + 'px'
  191. });
  192. self.Dom.tip.css({
  193. left: offset.left - 72 + 'px',
  194. top: offset.top + 18 + 'px'
  195. });
  196. },
  197. changeToNickname: function (ctx) {
  198. var self = this;
  199. ctx = ctx || self.Dom.wrap;
  200. var users = $('.user_name', ctx).filter(function(){
  201. return !$(this).data('nickname');
  202. });
  203. if(users[0]) {
  204. $.getJSON('//api.csdn.net/service/open/nick?callback=?', {
  205. users: users.map(function(){ return this.innerHTML; }).get().join()
  206. }, function(data){
  207. //
  208. users.each(function (i) {
  209. this.innerHTML = data[i].n;
  210. $(this).data('nickname', true);
  211. });
  212. });
  213. }
  214. },
  215. addEvent: function() {
  216. var self = this
  217. , unreads = -1
  218. , hasReadedItems = false
  219. , keepUnread = false
  220. , notifications
  221. ;
  222. self.unreads = unreads;
  223. self.initPanel();
  224. notifications = $('.notifications', self.Dom.wrap);
  225. self.Dom.btn.click(function(e, params) {
  226. self.emit('panel_showed', params);
  227. if(self.Dom.wrap.toggle().is(':visible')) {
  228. self.Dom.tip.hide();
  229. } else {
  230. self.emit('tip_showing');
  231. }
  232. return false;
  233. });
  234. $(window).resize(function(e){
  235. self.resetPosition();
  236. });
  237. $(document).click(function(e) {
  238. var t = e.target;
  239. try {
  240. if(!$.contains(self.Dom.wrap[0], t) && !$.contains(self.Dom.tip[0], t)) {
  241. $(document).trigger('notify-hide');
  242. }
  243. } catch(e) {
  244. log(e);
  245. }
  246. });
  247. $('.notice_list_con .notice_content', self.Dom.wrap).bind('click', function (e) {
  248. var target = e.target
  249. , item = $(target).parents(".list")
  250. , index = item.index()
  251. , detail = $('.detail_con .notice_content > *:eq(' + index + ')', self.Dom.wrap)
  252. ;
  253. item.hasClass("unread")&&self.setReaded([{containIds:item.attr("data-ids").split(",")}],function(data){
  254. keepUnread = true;
  255. item.removeClass('unread');
  256. //去除已读id
  257. var _strReadedIds = item.attr("data-ids");
  258. var _unread = [],_item={};
  259. for(var i=0;i<localUreadData.length;i++){
  260. _item = localUreadData[i];
  261. if(_item.containIds.join() === _strReadedIds){
  262. localUreadData.splice(i,1);
  263. }
  264. }
  265. self.emit('tip_showing');
  266. });
  267. detail.addClass('curr').show().siblings().removeClass('curr').hide();
  268. if(!item.hasClass("noslide")){
  269. self.goSlide(notifications.eq(0), notifications.eq(1), 'right').emit('detail_showed', [detail, index]);
  270. }
  271. if(item.hasClass("action")&&target.tagName=="A"){
  272. self.doaction({
  273. id:$(item).attr("data-ids").split(",")[0]*1,
  274. dataApi:$(".callback",item).attr("data-api"),
  275. args:$(target).attr("data-api")
  276. },function(err, data){
  277. $(".callback",item).html(data.msg);
  278. $(".callback",detail).html(data.msg);
  279. });
  280. }
  281. });
  282. $('.detail_con .notice_content', self.Dom.wrap).delegate('.item_title', 'click', function (e) {
  283. var target = e.target
  284. , item = $(target).parents(".detail_list")
  285. , index = item.index()
  286. , list = $('.notice_list_con .notice_content > *:eq(' + index + ')', self.Dom.wrap);
  287. if(item.hasClass("action")&&target.tagName=="A"){
  288. self.doaction({
  289. id:$(item).attr("data-ids").split(",")[0]*1,
  290. dataApi:$(".callback",item).attr("data-api"),
  291. args:$(target).attr("data-api")
  292. },function(err, data){
  293. $(".callback",item).html(data.msg);
  294. $(".callback",list).html(data.msg);
  295. });
  296. }
  297. });
  298. //当trigger click时,会将事件源对象变成这个close节点,扰乱了其它js的判断逻辑。
  299. function hidecb(){
  300. self.Dom.wrap.hide();
  301. self.emit('tip_showing');
  302. }
  303. $(document).bind("notify-hide",function(){
  304. hidecb();
  305. });
  306. $('.csdn_note .close1', self.Dom.wrap).click(function () {
  307. hidecb();
  308. });
  309. $('.go_back', self.Dom.wrap).click(function () {
  310. self.goSlide(notifications.eq(1), notifications.eq(0), 'left');
  311. self.emit('panel_showed');
  312. });
  313. $('.read_all', self.Dom.wrap).click(function () {
  314. self.setAllReaded();
  315. return false;
  316. });
  317. $('.prvnote, .nextnote', self.Dom.wrap).click(function () {
  318. var el = $(this);
  319. if(el.hasClass('disabled')) return;
  320. var form = $('.detail_con .notice_content .curr', self.Dom.wrap)
  321. , to = form[el.hasClass('prvnote') ? 'prev' : 'next']()
  322. , index = to.index()
  323. ;
  324. if (~index) {
  325. $('.notice_list_con .notice_content > *:eq(' + index + ')', self.Dom.wrap).removeClass('unread');
  326. $([form[0], to[0]]).toggleClass('curr');
  327. self.goSlide(form, to, el.hasClass('prvnote') ? 'left' : 'right').emit('detail_showed', [to, index]);
  328. }
  329. });
  330. self.on('panel_showed', function (e, showDetail) {
  331. self.resetPosition();
  332. if (!hasReadedItems) {
  333. $('.notice_content', self.Dom.wrap).empty();
  334. self.getAllReaded(self.showListCbWrap(function (err, data, loading) {
  335. hasReadedItems = true;
  336. }));
  337. }
  338. if(showDetail&&showDetail.data){
  339. self.fillList("prepend",showDetail);
  340. self.slideReset();
  341. hasGetData = true;
  342. hasNewMsg = false;
  343. }
  344. // 显示面板内容
  345. else if (localUreadData.length > 0&&hasNewMsg) {
  346. hasNewMsg = false;
  347. self.getUnreads(self.showListCbWrap(function (err, data, loading) {
  348. localUreadData = data.data;
  349. hasNewMsg = false;
  350. self.emit('tip_showing');
  351. }));
  352. }
  353. if (hasGetData) {
  354. self.Dom.wrap.one('list_showed', function (e, err) {
  355. if (err) {
  356. self.error(err);
  357. } else {
  358. if(showDetail) {
  359. var unreadsItem = $('.notice_list_con .unread', self.Dom.wrap);
  360. if(unreadsItem.length === 1) {
  361. unreadsItem.trigger('click');
  362. }
  363. }
  364. }
  365. });
  366. }
  367. }).on('detail_showed', function (e, detail, index) {
  368. // 显示通知详情
  369. $('.detail_con .prvnote', self.Dom.wrap).toggleClass('disabled', !(index > 0));
  370. $('.detail_con .nextnote', self.Dom.wrap).toggleClass('disabled', !detail.next()[0]);
  371. if (!detail.data('loaded') && !$('dd', detail)[0] && !$('.empty:visible', detail)[0] && !$('.loading:visible', detail)[0]) {
  372. self.getDetail(detail);
  373. }
  374. }).on('tip_showing', function () {
  375. keepUnread = true;
  376. // if ($('.notice_list_con', self.Dom.wrap).css('display') !== 'none') {
  377. // $('.notice_list_con .notice_content .unread', self.Dom.wrap).removeClass('unread');
  378. // }
  379. self.initBtn();
  380. if (localUreadData.length > 0) {
  381. self.resetPosition();
  382. $('strong', self.Dom.tip.show()).html(localUreadData.length);
  383. $('.icon-hasnotes',self.Dom.btn).show();
  384. //派发给toolbar
  385. }else{
  386. $('.icon-hasnotes',self.Dom.btn).hide();
  387. }
  388. $(document).trigger("toolbar-setNotesNum",localUreadData.length);
  389. }).on('receive_unreads', function (e, data) {
  390. data.initUnreadIds&&(localUreadData = data.initUnreadIds);
  391. // 收到未读消息实时通知 typeof data === number123123
  392. //data.realtimeMsg&&localUreadData = data.realtimeMsg;
  393. //self.unreads = unreads = (unreads === -1 ? 0 : unreads) + data;
  394. if (self.Dom.wrap.is(':visible')) {
  395. if($('.notice_list_con', self.Dom.wrap).is(':visible')){
  396. if(data.realtimeMsg){
  397. self.emit('panel_showed',{
  398. data:data.data
  399. });
  400. }else{
  401. self.emit('panel_showed');
  402. }
  403. }
  404. } else {
  405. self.emit('tip_showing');
  406. }
  407. }).on('receive_setreaded', function (e, data) {
  408. var _len = localUreadData.length;
  409. // 收到设置为已读实时通知 typeof data === array or number
  410. if (keepUnread) {
  411. } else { // TODO 如果焦点在当前浏览器窗口,什么都不做
  412. // 非查看窗口收到已读通知时需要重置状态,以便再次打开时能正确显示通知
  413. self.unreads = _len;
  414. hasReadedItems = false;
  415. }
  416. if (_len <= 0) {
  417. self.Dom.tip.hide();
  418. $(".icon-hasnotes",self.Dom.btn).hide();
  419. }
  420. $('strong', self.Dom.tip).html(_len);
  421. });
  422. $('.close2', self.Dom.tip).click(function () {
  423. self.Dom.tip.hide();
  424. self.setReaded([], function(){
  425. });
  426. });
  427. $('.tip_text', self.Dom.tip).click(function () {
  428. self.Dom.btn.trigger('click'
  429. // , ['Show one and only unread detail'] // 点击未读消息提示框后,如果未读条目只有一条,则自动进入哪一条未读
  430. );
  431. });
  432. return self;
  433. },
  434. initBtn: function(){
  435. if(this.conf.btn.find(".icon-hasnotes").length<=0){
  436. this.conf.btn.html('<div class="icon-hasnotes" style="display:none"></div>');
  437. }
  438. },
  439. initPanel: function () {
  440. $('.box', this.Dom.wrap).append('\
  441. <div class="notifications notice_list_con curr">\
  442. <div class="menu_title">\
  443. <span class="title"><a href="' + this.conf.app + '/" target="_blank" class="read_all">全部设为已读</a><a href="' + this.conf.app + '/" target="_blank" class="go_all">查看所有通知</a></span>\
  444. </div>\
  445. <div class="loading"></div>\
  446. <div class="empty">暂没有新通知</div>\
  447. <div class="notice_content"></div>\
  448. </div>\
  449. <div class="notifications detail_con" style="display: none">\
  450. <div class="menu_title">\
  451. <span class="title">\
  452. <a class="go_back" href="javascript:void 0;">返回通知列表</a>\
  453. <a class="notifications_page_none nextnote" href="javascript:void 0;">下一条</a>\
  454. <a class="notifications_page prvnote" href="javascript:void 0;">上一条</a>\
  455. </span>\
  456. </div>\
  457. <div class="notice_content"></div>\
  458. </div>\
  459. <div class="error"></div>');
  460. },
  461. fillList: function(insert, data) {
  462. if(!data || !data.data || !data.data.length) return;
  463. data = data.data;
  464. var self = this
  465. , wrap = self.Dom.wrap
  466. , list = new Array(data.length)
  467. , details = new Array(data.length)
  468. ;
  469. $.each(data, function (i, v) {
  470. var action = v.isaction?" action ":"";
  471. var isslide = !v.isslide?" noslide ":"";
  472. var dataIds=(function(){
  473. var _ids = [];
  474. for(var i=0,len=v.containIds.length,item=v.containIds;i<len;i++){
  475. _ids.push(item[i]*1);
  476. }
  477. return _ids.join(",");
  478. })();
  479. list[i] = '\
  480. <dl data-ids="'+dataIds+'" class="list rev_type'+v.type + (insert === 'append' ? '' : ' unread')+isslide+action+'" style="/*display: none*/">\
  481. <dt>\
  482. <i></i>\
  483. <span class="item_title">' + v.title + '</span>\
  484. <span class="count_down">' + v.time + '</span>\
  485. </dt>\
  486. </dl>';
  487. var remain = v.containIds.length - self.conf.subCount;
  488. details[i]='\
  489. <div style="/*display: none*/">\
  490. <dl class="detail_list rev_type' + v.type +isslide+action+ '" data-ids="' + v.containIds.join() + '">\
  491. <dt>\
  492. <i></i>\
  493. <span class="item_title">' + self.ubbDecode(v.longTitle) + '</span>\
  494. <span class="count_down"></span>\
  495. </dt>\
  496. </dl>\
  497. <div class="loading"></div>\
  498. <div class="empty">暂没有新通知</div>\
  499. <a class="notifications_more" target="_blank">查看其它 0 条</a>\
  500. </div>';
  501. });
  502. $('.notice_list_con .notice_content', wrap)[insert](list.join(''));
  503. $('.detail_con .notice_content', wrap)[insert](details.join(''));
  504. self.changeToNickname();
  505. },
  506. fillDetail: function (detail, data) {
  507. if(!data || !data.data || !data.data.length) return;
  508. var bodyTpl = data.bodyTpl
  509. , self = this
  510. , dl = $('dl', detail)
  511. , html
  512. ;
  513. data = data.data;
  514. if (data[0].body) {
  515. html = $.map(data, function (v) {
  516. return self.feTemplate(bodyTpl).render({
  517. userlink:self.conf.space + v.from_user,
  518. from_user:v.from_user,
  519. body:self.ubbDecode(v.body),
  520. time:v.time
  521. });
  522. }).join('');
  523. if (html) {
  524. dl.append(html);
  525. var remain = dl.attr('data-ids').split(',').length - data.length
  526. , url = data[0].url
  527. ;
  528. if(remain > 0 && url) {
  529. $('.notifications_more', detail).html(function (i, v) {
  530. return v.replace(/\d+/g, remain);
  531. }).attr('href', url).addClass('block');
  532. }
  533. }
  534. }
  535. if (!html) {
  536. $('dt .count_down', detail).html(data[0].time);
  537. detail.data('loaded', true);
  538. }
  539. self.changeToNickname(dl);
  540. },
  541. /**
  542. * 显示列表的回调包装。和getUnreads,getAllReaded配合使用,包含一些通用的操作,以及触发必要的事件
  543. * @param {Function} callback 当正常获取到数据时的回调
  544. * @return {Function} 包装之后的回调函数
  545. */
  546. showListCbWrap: function (callback) {
  547. var self = this;
  548. return function (err, data, loading) {
  549. if (err) {
  550. self.emit('list_showed', [err]);
  551. } else {
  552. callback && callback(err, data, loading);
  553. if (!loading) {
  554. var list = $('.notice_list_con .notice_content > .list', self.Dom.wrap);
  555. // 调整可见消息数量
  556. if (list.filter(':visible').length === 0) {
  557. var n = list.filter('.unread').length;
  558. if(n < self.conf.count) {
  559. n = self.conf.count;
  560. }
  561. list.slice(n).remove();
  562. $('.detail_con .notice_content > *', self.Dom.wrap).slice(n).remove();
  563. }
  564. list.show();
  565. self.slideReset();
  566. self.emit('list_showed');
  567. }
  568. }
  569. };
  570. },
  571. error: function (msg) {
  572. var self = this, func = arguments.callee;
  573. if(func.init === undefined) {
  574. func.ele = $('.error', self.Dom.wrap);
  575. self.on('panel_showed detail_showed', function () {
  576. func.ele.hide();
  577. });
  578. func.init = true;
  579. }
  580. msg = friendlyErrors[msg] !== undefined ? friendlyErrors[msg] : msg;
  581. func.ele.toggle(!!msg).html(msg);
  582. },
  583. invokeAPI: function (url, params, opts, callback) {
  584. var self = this
  585. , opts = opts || {}
  586. , callback = callback || self.emptyCb
  587. ;
  588. self.lock(opts.lock === undefined || opts.lock ? url : null, function (err, unlock) {
  589. if(err) {
  590. callback(err);
  591. return;
  592. }
  593. var done = opts.loading === undefined || opts.loading ? self.loading(opts.container) : self.wrapCb(false)
  594. , data
  595. , completed = false
  596. , complete = function (xhr, status) {
  597. if(completed) return;
  598. done(function (loading) {
  599. if(status && status !== 'success') {
  600. callback(status, null, loading);
  601. } else {
  602. if(data.status !== 200) {
  603. callback(data.error || data.status, data, loading);
  604. } else {
  605. if(opts.list) {
  606. // /detail/i.test(url) && (data.data = []); // DEV
  607. self.toggleEmpty(!loading && data.data && !data.data.length, opts.container, opts.list);
  608. }
  609. callback(null, data, loading);
  610. }
  611. }
  612. unlock();
  613. completed = true;
  614. });
  615. }
  616. ;
  617. setTimeout(function () {
  618. complete(null, 'timeout');
  619. }, 20000);
  620. $.ajax({
  621. url: self.conf.api + url,
  622. data: params,
  623. dataType: 'jsonp', // TODO jsonp方式调用不能产生正确的timeout错误等,考虑换成支持跨域的xhr(在ff chrome ie8+)
  624. success: function (json) {
  625. if(completed) return;
  626. data = json;
  627. },
  628. complete: complete
  629. });
  630. });
  631. },
  632. getUnreads: function (callback) {
  633. var self = this;
  634. self.invokeAPI('/get_unread?jsonpcallback=?', {}, {
  635. container: '.notice_list_con',
  636. list: '.notice_content > .list'
  637. }, function (err, data, loading) {
  638. self.fillList('prepend', data);
  639. callback && callback(err, data, loading);
  640. });
  641. },
  642. doaction : function(data,callback){
  643. var self = this;
  644. self.invokeAPI('/do_action?jsonpcallback=?', {
  645. id:data.id,
  646. dataApi:data.dataApi+"?me="+currUser.username+'&'+data.args,
  647. }, {
  648. }, function (err, data, loading) {
  649. callback && callback(err, data, loading);
  650. });
  651. },
  652. getAllReaded: function (callback) {
  653. var self = this;
  654. self.invokeAPI('/get_all?jsonpcallback=?', {
  655. username: currUser.username,
  656. status: 1,
  657. count: self.conf.count || 5,
  658. subcount: self.conf.subcount || 5
  659. }, {
  660. container: '.notice_list_con',
  661. list: '.notice_content > .list'
  662. }, function (err, data, loading) {
  663. self.fillList('append', data);
  664. callback && callback(err, data, loading);
  665. });
  666. },
  667. setAllReaded: function(){
  668. if(!localUreadData.length){
  669. return;
  670. }
  671. this.setReaded([],function(){
  672. $(".csdn_note .unread").each(function(i){
  673. $(this).removeClass("unread");
  674. localUreadData=[];
  675. });
  676. });
  677. },
  678. setReaded: function (data, next) {
  679. var self = this;
  680. self.invokeAPI('/set_readed?jsonpcallback=?', {
  681. ids: $.map(data, function (i) {
  682. return i.containIds.join();
  683. }).join()
  684. }, {
  685. loading: false
  686. },function(data){
  687. next&&next(data);
  688. });
  689. },
  690. getlocalUnread: function(){
  691. return localUreadData;
  692. },
  693. isGetAll: function(){
  694. return hasGetData;
  695. },
  696. isHasNewMsg: function(){
  697. return hasNewMsg;
  698. },
  699. getDetail: function(detail, start, limit){
  700. var self = this
  701. , ids = $('dl', detail).attr('data-ids').split(',')
  702. ;
  703. start = start || 0;
  704. limit = limit || self.conf.subCount;
  705. if (ids.length > 0) {
  706. self.invokeAPI('/get_details?jsonpcallback=?', {
  707. ids: ids.slice(start, start + limit).join()
  708. }, {
  709. container: detail,
  710. list: 'dl > dd',
  711. lock: false
  712. }, function (err, data) {
  713. if (err) {
  714. self.error(err);
  715. } else {
  716. self.fillDetail(detail, data);
  717. }
  718. });
  719. }
  720. },
  721. /**
  722. * 动画切换
  723. * @param {jQuery Object} from 动画开始的元素
  724. * @param {jQuery Object} to 动画结束的元素
  725. * @param {String} direction to在from的左边还是右边, left right
  726. */
  727. goSlide: function(from, to, direction) {
  728. var self = this
  729. , speed = 110
  730. // + 3000 // DEV
  731. , offsetW = from.width()
  732. , content = to.parents('.notice_content:first')
  733. , fromComplete, toComplete
  734. ;
  735. self.emit('goSliding');
  736. if (!to.hasClass('curr')) {
  737. from.removeClass('curr');
  738. to.addClass('curr');
  739. }
  740. from.css({
  741. position: 'relative'
  742. });
  743. to.css({
  744. position: 'absolute'
  745. , top: 0
  746. , left: (direction === 'left' ? -offsetW : offsetW) + 'px'
  747. , width: offsetW
  748. }).show();
  749. content.height(function (i, v) {
  750. return v - from.height() + Math.max(from.height(), to.height());
  751. });
  752. fromComplete = function () {
  753. from.css('position', '').hide();
  754. };
  755. from.animate({
  756. left: (direction === 'left' ? offsetW : -offsetW) + 'px'
  757. }, speed, fromComplete);
  758. toComplete = function () {
  759. to.css({
  760. position: ''
  761. , top: ''
  762. , left: ''
  763. });
  764. content.css('height', '');
  765. };
  766. to.animate({ left: 0 }, speed, toComplete);
  767. self.Dom.wrap.one('goSliding', function () {
  768. from.stop();
  769. fromComplete.call(from[0]);
  770. to.stop();
  771. toComplete.call(to[0]);
  772. });
  773. return this;
  774. },
  775. slideReset: function() {
  776. var self = this
  777. , container = $('.notice_list_con .notice_content', self.Dom.wrap)
  778. , items = container.children().filter(':visible')
  779. ;
  780. $('.notifications', this.Dom.wrap).eq(0).addClass('curr').show().end()
  781. .slice(1).hide();
  782. ;
  783. if(items.length > self.conf.count) {
  784. // container.css({
  785. // overflow: 'auto'
  786. // , height: '255px'
  787. // });
  788. container.addClass("hover-overflow");
  789. }
  790. },
  791. /*
  792. * 【LOGIC】检查登录
  793. */
  794. checkLogin: function(callback) {
  795. try{
  796. currUser.username = getCookie("UserName") || currUser.username;
  797. currUser.userInfo = getCookie("UserInfo") || currUser.userInfo;
  798. currUser.username && callback && callback(currUser.username);
  799. }catch(e){
  800. log(e);
  801. }
  802. },
  803. initRealtime: function () {
  804. var self = this;
  805. // 兼容独立和合并的realtime.js
  806. if(csdn.RealtimeClient === undefined) {
  807. $.ajax({
  808. url: self.staticUrl(self.conf['realtime.js'])
  809. , dataType: 'script'
  810. , cache: true // 已通过staticUrl对外链文件提供了缓存支持,getScript()可能会始终在url结尾加时间戳
  811. , success: arguments.callee.bind(self)
  812. });
  813. return;
  814. }
  815. self.conf['socket.io.js'] = self.staticUrl(self.conf['socket.io.js']);
  816. self.realtimeClient = new csdn.RealtimeClient($.extend({
  817. channel: currUser.username
  818. }, self.conf), function (msg) {
  819. // 分发收到的实时消息
  820. if(typeof msg === 'object') {
  821. if(msg.setReaded) {
  822. self.emit('receive_setreaded', [msg.setReaded]);
  823. }
  824. if(msg.initUnreadIds&&msg.initUnreadIds.length){
  825. hasNewMsg = true;
  826. self.emit('receive_unreads', msg);
  827. }
  828. if(msg.realtimeMsg){
  829. hasNewMsg = true;
  830. localUreadData = localUreadData.concat(msg.data);
  831. self.emit('receive_unreads', msg);
  832. }
  833. }
  834. });
  835. },
  836. /*
  837. * UBB转义
  838. */
  839. ubbDecode : function(content){
  840. var _this = this;
  841. content = $.trim(content);
  842. var re = /\[code=([\w#\.]+)\]([\s\S]*?)\[\/code\]/ig;
  843. function replaceQuote(str) {
  844. var m = /\[quote=([^\]]+)]([\s\S]*)\[\/quote\]/gi.exec(str);
  845. if (m) {
  846. return str.replace(m[0], '<fieldset><legend>引用“<span class="user_name">' + m[1] + '</span>”的评论:</legend>' + replaceQuote(m[2]) + '</fieldset>');
  847. } else {
  848. return str;
  849. }
  850. }
  851. var codelist = [];
  852. while ((mc = re.exec(content)) != null) {
  853. codelist.push(mc[0]);
  854. content = content.replace(mc[0], "--code--");
  855. }
  856. content = replaceQuote(content);
  857. content = content.replace(/\[reply]([\s\S]*?)\[\/reply\][\r\n]{0,2}/gi, "回复<span class='user_name'>$1</span>:");
  858. content = content.replace(/\[url=([^\]]+)]([\s\S]*?)\[\/url\]/gi, '<a href="$1" target="_blank">$2</a>');
  859. content = content.replace(/\[img(=([^\]]+))?]([\s\S]*?)\[\/img\]/gi, '<img src="$3" style="max-width:200px;max-height:100px;" border="0" title="$2" />');
  860. content = content.replace(/\r?\n/ig, "<br />");
  861. if (codelist.length > 0) {
  862. var re1 = /--code--/ig;
  863. var i = 0;
  864. while ((mc = re1.exec(content)) != null) {
  865. content = content.replace(mc[0], codelist[i]);
  866. i++;
  867. }
  868. }
  869. content = content.replace(/\[code=([\w#\.]+)\]([\s\S]*?)\[\/code\]/ig, function (m0, m1, m2) {
  870. if ($.trim(m2) == "") return '';
  871. return '<pre name="code2" class="' + m1 + '">' + _this.HTMLEncode(m2) + '</pre>';
  872. });
  873. content = content.replace(/(<br\s\S*>|<br>)/ig, function (m0) {
  874. if ($.trim(m0) == "") return '';
  875. //return _this.HTMLEncode(m0);
  876. return "&nbsp;&nbsp;";
  877. });
  878. //针对转义的"做处理
  879. content = content.replace(/(\\&quote\;|\&quote\;)/ig, function (m0) {
  880. if ($.trim(m0) == "") return '';
  881. //return _this.HTMLEncode(m0);
  882. return "";
  883. });
  884. return content;
  885. },
  886. HTMLEncode : function(str) {
  887. var s = "";
  888. if(str.length == 0) return "";
  889. s = str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\'/g, "&#39;").replace(/\"/g, "&quot;");
  890. return s;
  891. },
  892. /**
  893. * 简单模板替换
  894. * @param {String} tempStr 待替换字符串
  895. * @return {String} 替换后的html
  896. */
  897. feTemplate : function(tempStr){
  898. // TODO 未处理模板字段重复出现的问题 [fixed]
  899. // 未处理返回的模板对象复用的问题 [fixed]
  900. // 测试用例 //jsfiddle.net/SdzfU/2/
  901. return {
  902. render:function(conf){
  903. var result=tempStr;
  904. for(var name in conf){
  905. if(conf.hasOwnProperty(name)){
  906. result=result.replace(new RegExp("{"+"%"+name+"%"+"}","g"),conf[name]);
  907. }
  908. }
  909. return result;
  910. }
  911. }
  912. }
  913. };
  914. })(window);
  915. (function($, window, undefined) {
  916. if ($ === undefined) {
  917. // 按需加载jQuery
  918. var done = false
  919. , callback = arguments.callee
  920. , script = document.createElement('script')
  921. , head = document.getElementsByTagName('head')[0] || document.documentElement
  922. ;
  923. script.src = '//csdnimg.cn/www/js/jquery-1.4.2.min.js';
  924. script.charset = 'utf-8';
  925. script.onload = script.onreadystatechange = function () {
  926. if(!done && (!this.readyState || this.readyState === 'loaded' || this.readyState === 'complete')) {
  927. done = true;
  928. try {
  929. callback(window.jQuery, window);
  930. } catch(e) {
  931. window.console && window.console.log(e);
  932. }
  933. script.onload = script.onreadystatechange = null;
  934. if(head && script.parentNode) {
  935. head.removeChild(script);
  936. }
  937. }
  938. };
  939. head.insertBefore(script, head.firstChild);
  940. return;
  941. }
  942. // 初始化通知面板
  943. var script = $("#noticeScript")
  944. , opts = {
  945. instance: 'csdn_note'
  946. , btnId: script.attr('btnId') || 'header_notice_num'
  947. , wrapId: 'note1'
  948. }
  949. ;
  950. opts.btn = $('#' + opts.btnId);
  951. if(!opts.btn[0]) {
  952. return;
  953. }
  954. $.map(['instance', 'wrapId'
  955. , 'realtime', 'api', 'app', 'space', 'count', 'subCount'
  956. , 'staticUrl', 'cssUrl'
  957. , 'realtime.js', 'channel', 'socket.io.js', 'socket.io options'], function (v) {
  958. opts[v] = script.attr(v.replace(/[. ]/g, '-')) || opts[v];
  959. });
  960. if(opts.instance === "csdn_note") {
  961. $('\
  962. <div id="note1" class="csdn_note" style="display:none; position:absolute; z-index:9999; width:440px">\
  963. <span class="notice_top_arrow"><span class="inner"></span></span>\
  964. <div class="box"></div>\
  965. </div>').insertBefore(script);
  966. }
  967. $('\
  968. <div class="csdn_notice_tip" style="display:none; position:absolute; z-index:9990; width:170px">\
  969. <iframe src="about:blank" frameborder="0" scrolling="no" style="z-index:-1;position:absolute;top:0;left:0;width:100%;height:100%;background:transparent"></iframe>\
  970. <div class="tip_text">您有<strong>0</strong>条新通知</div>\
  971. <a href="javascript:void 0" class="close2"></a>\
  972. </div>').insertBefore(script).hide();
  973. window[opts.instance] = new csdn.note(opts);
  974. })(window.jQuery, window);