var JELON = window.JELON || {}; ;(function (JL) { var constants = { ACCESS_TOKEN_KEY: 'xups-github-comments-token', // access_token key USER_INFO_KEY: 'xups-github-user-info', // 登录用户信息 key PER_PAGE: 10, // 每页的评论数 API_HOST: 'https://api.github.com' }; var queryUrl = function (key, url, uncode) { url = url || location.href; var reg = new RegExp('(\\?|&|#|&)' + key + '=([^?&#]*)'); var result = url.match(reg); if (uncode) { return result ? result[2] : ''; } return result ? decodeURIComponent(result[2]) : ''; }; var $ = JL.$ || function(str) { return /^(\[object HTML)[a-zA-Z]*(Element\])$/.test(Object.prototype.toString.call(str)) ? str : document.getElementById(str); }; var addClass = function (elem, className) { if (!elem) return; var classNames; var setClass; var i, l, cl; if (elem instanceof Array) { for (i = 0, l = elem.length; i < l; i++) { elem[i] = arguments.callee.call(this, elem[i], className); } } else if (typeof elem.item === 'function') { var result = []; for (i = 0, l = elem.length; i < l; i++) { result.push(arguments.callee.call(this, elem.item(i), className)); } elem = result; } else { elem = $(elem); if (!elem) return; if (className && typeof className === 'string') { classNames = className.split(/\s+/); if (elem.nodeType === 1) { if (!elem.className && classNames.length === 1) { elem.className = className; } else { setClass = ' ' + elem.className + ' '; for (i = 0, cl = classNames.length; i < cl; i++) { if (setClass.indexOf(' ' + classNames[i] + ' ') < 0) { setClass += classNames[i] + ' '; } } elem.className = setClass.trim(); } } } } return elem; }; var removeClass = function (elem, className) { if (!elem) return; var classNames, i, l, c, cl; if (elem instanceof Array) { for (i = 0, l = elem.length; i < l; i++) { elem[i] = arguments.callee.call(this, elem[i], className); } } else if (typeof elem.item === 'function') { var result = []; for (i = 0, l = elem.length; i < l; i++) { result.push(arguments.callee.call(this, elem.item(i), className)); } elem = result; } else { elem = $(elem); if (!elem) return; if ((className && typeof className === 'string') || className === undefined) { classNames = (className || '').split(/\s+/); if (elem.nodeType === 1 && elem.className) { if (className) { className = (' ' + elem.className + ' ').replace(/[\n\t\r]/g, ' '); for (c = 0, cl = classNames.length; c < cl; c++) { className = className.replace(' ' + classNames[c] + ' ', ' '); } elem.className = className.trim(); } else { elem.className = ''; } } } } return elem; }; /** * 格式化日期文本,如 yyyy-MM-dd hh:mm:ss */ var formatDate = function (format, date) { if (!date) return ''; if (typeof date == 'number') date = new Date(date * 1000); var o = { 'M+': date.getMonth() + 1, 'd+': date.getDate(), 'h+': date.getHours(), 'm+': date.getMinutes(), 's+': date.getSeconds(), 'q+': Math.floor((date.getMonth() + 3) / 3), 'S': date.getMilliseconds(), 'w': '日一二三四五六'.charAt(date.getDay()) }; format = format.replace(/y{4}/, date.getFullYear()).replace(/y{2}/, date.getFullYear().toString().substring(2)); for (var k in o) { var reg = new RegExp(k); format = format.replace(reg, match); } function match(m) { return m.length == 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length); } return format; }; /** * 分享到社交平台 * @param {String} type */ var share = function(type) { if(!type) return; switch (type) { case 'weibo' : //微博 window.open('http://service.weibo.com/share/share.php?url=' + encodeURIComponent(location.href) + '&title=' + document.title + '&language=zh_cn'); break; case 'qqzone' : //QQ空间 window.open('http://sns.qzone.qq.com/cgi-bin/qzshare/cgi_qzshare_onekey?url=' + encodeURIComponent(location.href) + '&title=' + document.title); break; case 'qq' : //QQ window.open('http://connect.qq.com/widget/shareqq/index.html?url=' + encodeURIComponent(location.href) + '&desc=Silence个人博客&title=' + document.title + '&callback=' + encodeURIComponent(location.href)); break; case 'douban' : //豆瓣 window.open('http://shuo.douban.com/!service/share?href=' + encodeURIComponent(location.href) + '&name=' + document.title + '&text=' + document.title); break; default : console.warn('未知的分享类型', type) } } /** * 过滤字符串中的style link script标签, 防止注入 * @param {String} str 需要处理的字符串 */ var htmlFilter = function (str) { if (typeof str !== 'string') return; str = str.replace(/()/g, '') .replace(/()/g, '') .replace(/.*?<\/script>/g, ''); return str; }; /** * 封装ajax函数 * @param {String} opt.method 连接的方式,包括POST和GET两种方式 * @param {String} opt.url 发送请求的url * @param {Boolean} opt.async 是否为异步请求,true为异步的,false为同步的 * @param {Object} opt.data 发送的参数,格式为对象类型 * @param {Function} opt.success 发送并接收成功调用的回调函数 * @param {Function} opt.fail 失败回调 */ var ajax = function (opt) { opt = opt || {}; opt.method = opt.method.toUpperCase() || 'POST'; opt.url = opt.url || ''; opt.async = opt.async || true; opt.data = opt.data || null; opt.success = opt.success || function () {}; var xhr = null; if (window.XMLHttpRequest) { xhr = new XMLHttpRequest(); } else { xhr = new ActiveXObject('Microsoft.XMLHTTP'); } var params = []; var token = window.localStorage.getItem(constants.ACCESS_TOKEN_KEY); for (var key in opt.data) { params.push(key + '=' + opt.data[key]); } var postData = params.join('&'); if (opt.method.toUpperCase() === 'POST') { xhr.open(opt.method, opt.url, opt.async); if (window.JSON) { postData = JSON.stringify(opt.data); xhr.setRequestHeader('Content-Type', 'application/json;charset=utf-8'); } else { xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=utf-8'); } if (opt.headers && opt.headers['Accept']) { xhr.setRequestHeader('Accept', opt.headers['Accept']); } else { xhr.setRequestHeader('Accept', 'application/vnd.github.squirrel-girl-preview, application/vnd.github.html+json'); } // 登录校验 if (token) { xhr.setRequestHeader('Authorization', 'token ' + token); } xhr.send(postData); } else if (opt.method.toUpperCase() === 'GET') { xhr.open(opt.method, opt.url + '?' + postData, opt.async); xhr.setRequestHeader('Accept', 'application/vnd.github.squirrel-girl-preview, application/vnd.github.html+json'); // 登录校验 if (token) { xhr.setRequestHeader('Authorization', 'token ' + token); } xhr.send(null); } xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if (xhr.status >= 200 && xhr.status < 300) { opt.success && opt.success(xhr.responseText); } else { opt.fail && opt.fail(xhr.status) } } }; xhr.onerror = function () { opt.fail && opt.fail({ message: '请求错误!' }); }; }; JL.issueComments = 0; JL.issueNumber = 0; JL.Utils = { ajax: ajax, queryUrl: queryUrl, addClass: addClass, removeClass: removeClass, formatDate: formatDate, share: share }; JL.Renders = { box: { tpl: [ '
', '
avatar
', '
', '
', '', '
', '
', '
', '
编辑
', '
预览
', '
', '
提交
', '
', '
' ].join(''), update: function () { var userInfo = localStorage.getItem(constants.USER_INFO_KEY); if (userInfo) { userInfo = JSON.parse(userInfo); } else { userInfo = {}; } // 默认头像路径 /img/jelon.jpg $('loginAvatar').src = userInfo.avatar_url || '/img/github.png'; } }, list: { tpl: [ '
', '
正在加载评论
', '
' ].join(''), /** * 评论列表模块视图更新 * @param {Number} page 评论列表当前页码 * @param {Number} comments 当前文章下所有评论总数 * @param {Number} list 当前列表下评论列表数据 * @param {Function} callback 回调 * @return void(0) */ update: function (page, comments, list, callback) { var perNavPageMaxSize = 10; var html = ''; var htmlList = []; var pageList = []; var allPages = Math.ceil(comments / constants.PER_PAGE); if (comments === 0) { html = '
暂无评论
'; } else { var item = ''; var pageItem = ''; for (var i = 0, len = list.length; i < len; i++) { item = [ '
  • ', '
    ', '', 'user-avatar', '', '
    ', '
    ', '
    ', '' + list[i].user.login + '', '' + formatDate('yyyy-MM-dd hh:mm', new Date(list[i].created_at)) + '', '', '', '回复', '
    ', '
    ' + (list[i].body_html || list[i].body) + '
    ', '
    ', '
  • ' ].join(''); htmlList.push(item); } if (allPages === 1) { pageItem = '' + page + ''; pageList.push(pageItem); } else if (allPages <= perNavPageMaxSize) { for (var i = 1; i <= allPages; i++) { if (i === page) { pageItem = '' + page + ''; } else { pageItem = '' + i + ''; } pageList.push(pageItem); } if (page !== 1) { pageList.unshift('上页'); } if (page !== allPages) { pageList.push('下页'); } } else if (allPages > perNavPageMaxSize) { if (page <= perNavPageMaxSize) { for (var i = 1; i <= perNavPageMaxSize; i++) { if (i === page) { pageItem = '' + page + ''; } else { pageItem = '' + i + ''; } pageList.push(pageItem); } if (page !== 1) { pageList.unshift('上页'); } pageList.push('...'); pageList.push('下页'); pageList.push('末页'); } else if (page > perNavPageMaxSize && page <= allPages - perNavPageMaxSize) { var mod = page % perNavPageMaxSize; var start = Math.floor(page / perNavPageMaxSize) * perNavPageMaxSize + 1; var end = Math.ceil(page / perNavPageMaxSize) * perNavPageMaxSize; pageList.push('首页'); pageList.push('上页'); for (var i = start; i <= end; i++) { if (i === page) { pageItem = '' + page + ''; } else { pageItem = '' + i + ''; } pageList.push(pageItem); } pageList.push('...'); pageList.push('下页'); pageList.push('末页'); } else if (page > perNavPageMaxSize && page > allPages - perNavPageMaxSize) { var start = allPages - perNavPageMaxSize + 1; var end = allPages; pageList.push('首页'); pageList.push('上页'); for (var i = start; i <= end; i++) { if (i === page) { pageItem = '' + page + ''; } else { pageItem = '' + i + ''; } pageList.push(pageItem); } if (page !== allPages) { pageList.push('下页'); } } } html = [ '
    总共 ' + JL.issueComments + ' 条评论
    ', '
      ', htmlList.join(''), '
    ', '' ].join(''); } $('JELON__commentList').innerHTML = html; if (localStorage.getItem(constants.USER_INFO_KEY)) { callback && callback(); } }, reactionUpdate: function (commentId, reactions) { var userInfo = localStorage.getItem(constants.USER_INFO_KEY); if (userInfo) { userInfo = JSON.parse(userInfo); } else { return; } var userId = userInfo.id; for (var i = 0, len = reactions.length; i < len; i++) { if (userId === reactions[i].user.id) { addClass($('JELON__comment_' + commentId + '_reactions').getElementsByClassName('like')[0], 'liked'); $('JELON__comment_' + commentId + '_reactions').getElementsByClassName('like')[0].innerHTML = '已赞'; break; } } }, addOne: function (data) { var oLi = document.createElement('li'); oLi.className = 'item'; var item = [ '
    ', '', 'user-avatar', '', '
    ', '
    ', '
    ', '' + data.user.login + '', '' + formatDate('yyyy-MM-dd hh:mm', new Date(data.created_at)) + '', '', '', '回复', '
    ', '
    ' + (data.body_html || data.body) + '
    ', '
    ' ].join(''); oLi.innerHTML = item; var oUl = $('JELON__commentList').getElementsByTagName('ul')[0]; if (oUl) { oUl.insertBefore(oLi, oUl.firstChild); $('JELON__commentsNum').innerHTML = JL.issueComments + 1; } else { $('JELON__commentList').innerHTML = [ '
    总共 ' + (JL.issueComments + 1) + ' 条评论
    ', '
      ', '
    • ', item, '
    • ', '
    ' ].join(''); } } }, signBar: { tpl: [ '
    ', '
    ' ].join(''), update: function () { var token = localStorage.getItem(constants.ACCESS_TOKEN_KEY); var userInfo = localStorage.getItem(constants.USER_INFO_KEY); var html = ''; if (token && userInfo) { userInfo = JSON.parse(userInfo); html = [ 'GitHub 已登录!', '退出' ].join(''); } else { html = [ 'GitHub 未登录?', '', '登录', '' ].join(''); } $('JELON__commentSignBar').innerHTML = html; } }, tips: { tpl: '
    注:评论支持 markdown 语法!
    ', update: function () { var userInfo = localStorage.getItem(constants.USER_INFO_KEY); var handler = ''; // 如果文章还没关联 issue 并且登录账号是自己时 if (userInfo && JSON.parse(userInfo).login === JL.options.owner && JL.issueNumber === 0) { handler = '初始化评论'; } $('JELON__comment_tips').innerHTML = handler + '注:评论支持 markdown 语法!'; } }, flashTitle: function (title) { var counter = 0; document.title = title + '...'; var timer = setInterval(function () { counter++; if (counter % 3 === 0) { document.title = title + '...'; } else if (counter % 3 === 1) { document.title = title + '..'; } else if (counter % 3 === 2) { document.title = title + '.'; } }, 100); }, loading: { create: function (oParent) { oParent = oParent || document.body; var oLoading = document.createElement('div'); oLoading.className = 'loading-mask'; oLoading.id = 'JELON__loadingMask'; oLoading.innerHTML = '
    加载中
    '; oParent.appendChild(oLoading); }, remove: function () { var oLoading = $('JELON__loadingMask'); oLoading.parentNode.removeChild(oLoading); } } }; JL.Actions = { init: function () { var code = queryUrl('code'); JL.Renders.signBar.update(); JL.Renders.box.update(); // if code,继续GitHub 授权 if (code) { JL.Renders.loading.create(); JL.Renders.flashTitle('登录中'); JL.Requests.getAccessToken({ client_id: JL.options.clientId, client_secret: JL.options.clientSecret, code: code }, function (res) { if (res.access_token || res.data) { if (res.data) { res.access_token = res.data.access_token; } localStorage.setItem(constants.ACCESS_TOKEN_KEY, res.access_token); // 保存 access_token 至 localStorage JL.Requests.getUserInfo({ access_token: res.access_token }, function (res) { if (res.login) { localStorage.setItem(constants.USER_INFO_KEY, JSON.stringify(res)); // 保存用户信息到 localStorage location.href = location.href.substring(0, location.href.indexOf('?')); JL.Renders.loading.remove(); } }); } else { // 登录失败 location.href = location.href.substring(0, location.href.indexOf('?')); JL.Renders.loading.remove(); } }); } else { JL.Requests.getIssueNumberByLabel(JL.options.label, function (res) { if (res.length > 0) { var number = res[0].number; var comments = res[0].comments; // 该 issue 下所有评论数 JL.issueNumber = number; JL.issueComments = comments; JL.Requests.getCommentListByIssueNumber(number, { page: 1, per_page: constants.PER_PAGE }, function (list) { JL.Renders.list.update(1, comments, list, function () { for (var i = 0, len = list.length; i < len; i++) { (function (commentId) { JL.Requests.getReactionsByCommentId(commentId, { content: 'heart' }, function (reactions) { JL.Renders.list.reactionUpdate(commentId, reactions); }); }(list[i].id)); } }); }); } else { // 授权码失效 if (typeof res !== 'object') { localStorage.removeItem(constants.ACCESS_TOKEN_KEY); localStorage.removeItem(constants.USER_INFO_KEY); JL.Renders.signBar.update(); JL.Renders.box.update(); console.warn('登录失败,请稍后刷新再试'); } else { JL.Renders.list.update(1, 0, []); JL.Renders.tips.update(); } } }); } }, signOut: function () { localStorage.removeItem(constants.ACCESS_TOKEN_KEY); localStorage.removeItem(constants.USER_INFO_KEY); JL.Renders.signBar.update(); JL.Renders.box.update(); }, pageJump: function (page) { JL.Requests.getCommentListByIssueNumber(JL.issueNumber, { page: Number(page), per_page: constants.PER_PAGE }, function (list) { JL.Renders.list.update(page, JL.issueComments, list, function () { for (var i = 0, len = list.length; i < len; i++) { (function (commentId) { JL.Requests.getReactionsByCommentId(commentId, { content: 'heart' }, function (reactions) { JL.Renders.list.reactionUpdate(commentId, reactions); }); }(list[i].id)); } }); }); }, editPreviewSwitch: function (flag) { if (flag === 'edit') { removeClass('JELON__previewSwitcher', 'on'); addClass('JELON__editSwitcher', 'on'); removeClass('JELON__previewBox', 'show'); addClass('JELON__editBox', 'show'); } else { removeClass('JELON__editSwitcher', 'on'); addClass('JELON__previewSwitcher', 'on'); removeClass('JELON__editBox', 'show'); addClass('JELON__previewBox', 'show'); var text = $('JELON__editBox').value.trim(); text = htmlFilter(text); if (text) { JL.Requests.markdown({ text: text, mode: 'markdown', context: 'github/gollum' }, function (res) { $('JELON__previewBox').innerHTML = res; }); } else { $('JELON__previewBox').innerHTML = ''; } } }, postComment: function () { var accessToken = localStorage.getItem(constants.ACCESS_TOKEN_KEY); var userInfo = localStorage.getItem(constants.USER_INFO_KEY); if (!accessToken || !userInfo) { alert('请先登录哦..!^_^'); return; } var body = $('JELON__editBox').value.trim(); body = htmlFilter(body); if (body) { JL.Renders.loading.create(); if (JL.issueNumber !== 0) { JL.Requests.createComment(JL.issueNumber, { body: body }, function (res) { if (res.id) { JL.Renders.list.addOne(res); JL.issueComments++; $('JELON__editBox').value = ''; $('JELON__previewBox').innerHTML = ''; } JL.Renders.loading.remove(); }); } else { // 如果还没有创建 issue,先创建 issue JL.Requests.createIssue({ title: document.title, body: location.href, labels: [ (JL.options.label || location.href) ] }, function (res) { if (res.number) { JL.issueNumber = res.number JL.Requests.createComment(JL.issueNumber, { body: body }, function (json) { if (res.id) { JL.Renders.list.addOne(json); JL.issueComments++; $('JELON__editBox').value = ''; $('JELON__previewBox').innerHTML = ''; } JL.Renders.loading.remove(); }); } }); } } }, like: function (commentId) { var oLiked = $('JELON__comment_' + commentId + '_reactions').getElementsByClassName('liked'); var oLike = $('JELON__comment_' + commentId + '_reactions').getElementsByClassName('like')[0]; var oNum = $('JELON__comment_' + commentId + '_reactions').getElementsByClassName('like-num')[0]; var accessToken = localStorage.getItem(constants.ACCESS_TOKEN_KEY); var userInfo = localStorage.getItem(constants.USER_INFO_KEY); if (oLiked.length) { return false; } else { if (accessToken && userInfo) { JL.Requests.createReaction(commentId, { content: 'heart' }, function (res) { if (res.content === 'heart') { addClass(oLike, 'liked'); oLike.innerHTML = '已赞'; oNum.innerHTML = Number(oNum.innerHTML) + 1; } }); } } }, createIssue: function () { JL.Renders.loading.create(); JL.Requests.createIssue({ title: document.title, body: location.href, labels: [ (JL.options.label || location.href) ] }, function (json) { if (json.number) { JL.issueNumber = json.number; JL.Renders.tips.update(); } JL.Renders.loading.remove(); }); }, reply: function (people, content) { var accessToken = localStorage.getItem(constants.ACCESS_TOKEN_KEY); var userInfo = localStorage.getItem(constants.USER_INFO_KEY); if (!accessToken || !userInfo) { return; } JL.Actions.editPreviewSwitch('edit'); $('JELON__editBox').value = ''; $('JELON__editBox').focus(); $('JELON__editBox').value = [ '@' + people + '\n', '> ' + content + '\n', '\n' ].join(''); $('JELON__previewBox').innerHTML = ''; } }; var createSuccessCb = function(callback){ return function (res) { if (typeof res === 'string') { if (window.JSON) { res = JSON.parse(res); } else { res = eval('(' + res + ')'); } } callback && callback(res); } } JL.Requests = { getIssueNumberByLabel: function (label, callback) { ajax({ url: constants.API_HOST + '/repos/' + JL.options.owner + '/' + JL.options.repo + '/issues', method: 'GET', data: { labels: [ label ], rnd: Math.random() }, success: createSuccessCb(callback), fail: callback }); }, createIssue: function (data, callback) { ajax({ url: constants.API_HOST + '/repos/' + JL.options.owner + '/' + JL.options.repo + '/issues', method: 'POST', data: data, success: createSuccessCb(callback), fail: callback }); }, getCommentListByIssueNumber: function (number, data, callback) { ajax({ url: constants.API_HOST + '/repos/' + JL.options.owner + '/' + JL.options.repo + '/issues/' + number + '/comments', method: 'GET', data: data, success: createSuccessCb(callback), fail: callback }); }, getReactionsByCommentId: function (id, data, callback) { if (typeof data === 'object' && !data.rnd) { data.rnd = Math.random(); } ajax({ url: constants.API_HOST + '/repos/' + JL.options.owner + '/' + JL.options.repo + '/issues/comments/' + id + '/reactions', method: 'GET', data: data, success: createSuccessCb(callback), fail: callback }); }, editIssue: function (number, data, callback) { ajax({ url: constants.API_HOST + '/repos/' + JL.options.owner + '/' + JL.options.owner + '/issues/' + number, method: 'POST', data: data, success: createSuccessCb(callback), fail: callback }); }, markdown: function (data, callback) { ajax({ url: constants.API_HOST + '/markdown', method: 'POST', data: data, success: callback, fail: callback }); }, getAccessToken: function (data, callback) { ajax({ // url: 'https://gh-oauth.imsun.net/', url: 'https://cors-anywhere.herokuapp.com/https://github.com/login/oauth/access_token', method: 'POST', headers: { 'Accept': 'application/json' }, data: data, success: createSuccessCb(callback), fail: callback }); }, getUserInfo: function (data, callback) { ajax({ url: constants.API_HOST + '/user', method: 'GET', data: data, success: createSuccessCb(callback), fail: callback }); }, createComment: function (number, data, callback) { ajax({ url: constants.API_HOST + '/repos/' + JL.options.owner + '/' + JL.options.repo + '/issues/' + number + '/comments', method: 'POST', data: data, success: createSuccessCb(callback), fail: callback }); }, createReaction: function (commentId, data, callback) { ajax({ url: constants.API_HOST + '/repos/' + JL.options.owner + '/' + JL.options.repo + '/issues/comments/' + commentId + '/reactions', method: 'POST', data: data, success: createSuccessCb(callback), fail: callback }); } }; JL.Comment = function (options) { JL.options = options || {}; var $container = $('comments'); if (options.container) { if (typeof options.container === 'object') { $container = options.container; } else if (typeof options.container === 'string') { if (/^#/.test(options.container)) { $container = $(options.container.replace(/^#/, '')); } else { $container = $(options.container); } } else { $container = $('comments'); } } $container.innerHTML = [ this.Renders.signBar.tpl, this.Renders.box.tpl, this.Renders.tips.tpl, this.Renders.list.tpl ].join(''); JL.Actions.init(); }; })(JELON);