if (!window['String']['prototype']['trim']) {
window['String']['prototype']['trim'] = function () {
return this.replace(/^\s+|\s+$/g, '');
};
}
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: 5, // 每页的评论数
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;
};
var htmlEncode = function (str) {
if (typeof str !== 'string') return;
str = str.replace(/&/g, '&')
.replace(//g, '>')
.replace(/\"/g, '"')
.replace(/\'/g, ''')
.replace(/ /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
};
JL.Renders = {
box: {
tpl: [
''
].join(''),
update: function () {
var userInfo = localStorage.getItem(constants.USER_INFO_KEY);
if (userInfo) {
userInfo = JSON.parse(userInfo);
} else {
userInfo = {};
}
// 默认头像路径 /img/jelon.jpg
$('JELON__loginAvatar').src = userInfo.avatar_url || '/img/unsigned_avatar.jpg';
}
},
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 = 5;
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 = [
'',
'',
'',
''
].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 = [
'',
'',
'',
pageList.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) {
console.log(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 = [
'',
''
].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 = [
'',
''
].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: '',
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 = htmlEncode(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 = htmlEncode(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 = '';
}
};
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: function (res) {
if (typeof res === 'string') {
if (window.JSON) {
res = JSON.parse(res);
} else {
res = eval('(' + res + ')');
}
}
callback && callback(res);
},
fail: function (err) {
callback && callback(err);
}
});
},
createIssue: function (data, callback) {
ajax({
url: constants.API_HOST + '/repos/' + JL.options.owner + '/' + JL.options.repo + '/issues',
method: 'POST',
data: data,
success: function (res) {
if (typeof res === 'string') {
if (window.JSON) {
res = JSON.parse(res);
} else {
res = eval('(' + res + ')');
}
}
callback && callback(res);
},
fail: function (err) {
callback && callback(err);
}
});
},
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: function (res) {
if (typeof res === 'string') {
if (window.JSON) {
res = JSON.parse(res);
} else {
res = eval('(' + res + ')');
}
}
callback && callback(res);
},
fail: function (err) {
callback && callback(err);
}
});
},
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: function (res) {
if (typeof res === 'string') {
if (window.JSON) {
res = JSON.parse(res);
} else {
res = eval('(' + res + ')');
}
}
callback && callback(res);
},
fail: function (err) {
callback && callback(err);
}
});
},
editIssue: function (number, data, callback) {
ajax({
url: constants.API_HOST + '/repos/' + JL.options.owner + '/' + JL.options.owner + '/issues/' + number,
method: 'POST',
data: data,
success: function (res) {
if (typeof res === 'string') {
if (window.JSON) {
res = JSON.parse(res);
} else {
res = eval('(' + res + ')');
}
}
callback && callback(res);
},
fail: function (err) {
callback && callback(err);
}
});
},
markdown: function (data, callback) {
ajax({
url: constants.API_HOST + '/markdown',
method: 'POST',
data: data,
success: function (res) {
callback && callback(res);
},
fail: function (err) {
callback && callback(err);
}
});
},
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: function (res) {
if (typeof res === 'string') {
if (window.JSON) {
res = JSON.parse(res);
} else {
res = eval('(' + res + ')');
}
}
callback && callback(res);
},
fail: function (err) {
callback && callback(err);
}
});
},
getUserInfo: function (data, callback) {
ajax({
url: constants.API_HOST + '/user',
method: 'GET',
data: data,
success: function (res) {
if (typeof res === 'string') {
if (window.JSON) {
res = JSON.parse(res);
} else {
res = eval('(' + res + ')');
}
}
callback && callback(res);
},
fail: function (err) {
callback && callback(err);
}
});
},
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: function (res) {
if (typeof res === 'string') {
if (window.JSON) {
res = JSON.parse(res);
} else {
res = eval('(' + res + ')');
}
}
callback && callback(res);
},
fail: function (err) {
callback && callback(err);
}
});
},
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: function (res) {
if (typeof res === 'string') {
if (window.JSON) {
res = JSON.parse(res);
} else {
res = eval('(' + res + ')');
}
}
callback && callback(res);
},
fail: function (err) {
callback && callback(err);
}
});
}
};
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);