├── CNAME ├── loading.gif ├── css ├── line.png ├── style.css ├── highlight.css └── GitHub2.css ├── js ├── .DS_Store ├── director.min.js ├── marked.min.js ├── underscore-min.js └── jquery-2.0.3.min.js ├── config.js ├── index.html ├── README.md └── app.js /CNAME: -------------------------------------------------------------------------------- 1 | wuhao.pub 2 | -------------------------------------------------------------------------------- /loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuhaoworld/github-issues-blog/HEAD/loading.gif -------------------------------------------------------------------------------- /css/line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuhaoworld/github-issues-blog/HEAD/css/line.png -------------------------------------------------------------------------------- /js/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuhaoworld/github-issues-blog/HEAD/js/.DS_Store -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | var _config = { 2 | blog_name : '用于演示的博客', // 博客名称 3 | owner : 'lifesinger', // github 用户名 4 | repo : 'lifesinger.github.com',// github 中对应项目名 5 | duoshuo_id : 'hello1234', // 在第三方评论插件多说申请站点的子域名 6 | // access_token : 'abcde'+'fghijk', // 请求量大时需要在 github 后台单独设置一个读取公开库的 token, 注意将token 拆成两个字符串,否则会被系统自动删除掉 7 | per_page : '15' // 默认一页显示几篇文章 8 | } 9 | 10 | var duoshuoQuery = {short_name:_config['duoshuo_id']}; -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | body{ 2 | padding: 0px 10px 30px 10px; 3 | } 4 | .blog_title{ 5 | font-size: 26px; 6 | color: #333; 7 | text-decoration: none; 8 | } 9 | .blog_title:hover{ 10 | color:#21759b; 11 | text-decoration: none; 12 | } 13 | h2{ 14 | background-color: #eee; 15 | padding-left: 8px; 16 | margin-left: -8px; 17 | border-radius: 2px; 18 | } 19 | .avatar{ 20 | display: inline; 21 | margin: 0px 0px; 22 | } 23 | .create_at{ 24 | color: #999; 25 | display: block; 26 | margin: 15px 0px; 27 | font-size: 12px; 28 | } 29 | 30 | .loading{ 31 | border: none; 32 | box-shadow:none; 33 | width: 32px; 34 | height: 32px; 35 | margin: 70px auto; 36 | } 37 | .prev{ 38 | float: left; 39 | } 40 | .next{ 41 | float: right; 42 | } 43 | 44 | @media only screen and (min-width: 600px) { 45 | #container { 46 | border: 1px solid #ccc; 47 | box-shadow: 2px -2px 10px #ddd; 48 | padding: 30px 60px 40px 60px; 49 | } 50 | } 51 | 52 | #header{ 53 | margin-bottom: 20px; 54 | } 55 | .postlist{ 56 | margin: 20px 0px; 57 | list-style: none; 58 | } 59 | ul{ 60 | margin-top: 30px; 61 | } 62 | .postlist{ 63 | padding: 0px; 64 | } 65 | .postlist a:hover{ 66 | text-decoration: none; 67 | background-image: url(line.png); 68 | background-repeat: repeat-x; 69 | background-position: left bottom; 70 | padding-bottom: 4px; 71 | } 72 | img{ 73 | clear: both; 74 | display: block; 75 | margin: 30px auto; 76 | } 77 | .datetime{ 78 | float: right; 79 | color: #999; 80 | font-family: 'Open Sans', sans-serif 81 | } 82 | .ds-social-links{ 83 | width: 300px !important; /* 修正多说在 iphone5 宽度下带来的横向滚动条的问题*/ 84 | } 85 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 用于演示的博客 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 21 |
22 |
loading
23 |
24 | 37 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 基于 Github issues 的单页面静态博客 2 | 3 | 玉伯的博客(https://github.com/lifesinger/lifesinger.github.com/issues )让我第一次知道 github issues 还可以这样用 ,作者发了很多干货技术文章,让我不由得感叹 ,文章不在于形式,也不在于写在哪里,只要是好文,总不会被埋没。 4 | 5 | 即便如此,很多人仍然希望能有一个独立域名、可以自由修改主题的博客。Wordpress 、Typecho 太重,还要买 VPS、部署服务器环境、安装插件、主题,太折腾人,于是我想,完全可以利用 Github 提供的 API 来实现一个只有一个静态页面的博客,具体思路如下: 6 | 7 | 1. 作者在 Github issues 上写文章(写 issues) 8 | 2. 博客页面通过 JS Ajax 请求 Github API 来获取文章内容,进行页面的渲染 9 | 3. 通过社会化评论插件实现评论功能 10 | 11 | 于是花了几天时间实现了这个设想, DEMO:http://wuhao.pub/ 12 | 13 | 博客的 demo 内容是读取的玉伯博客的 issues。 14 | 15 | ## 1. 部署方法(方案1) 16 | 17 | 1. fork 本项目,然后再新建一个用于存放 blog(issues)的 repo。 (fork 的项目是没有 issue 的,所以得新建个项目) 18 | 2. 修改 gh-pages 分支下根目录的 config.js,填写好对应的博客名称,你自己的 github 用户名、对应项目名、多说 ID,保存。多说账号在这里申请http://duoshuo.com/ 19 | 20 | 21 | var _config = { 22 | blog_name : '用于演示的博客', // 博客名称 23 | owner: 'lifesinger', // github 用户名 24 | repo: 'lifesinger.github.com',// 用于存放 blog(issues)的项目名 25 | duoshuo_id : 'hello1234', // 在第三方评论插件多说申请站点的子域名 26 | // access_token: '', // 请求量大时需要在 github 后台单独设置一个读取公开库的 token 27 | per_page: '15' // 默认一页显示几篇文章 28 | } 29 | 30 | 31 | 保存后即可通过 `http://用户名.github.io/github-issues-blog` 访问 32 | 33 | 注意:至少得有一次提交,github 的 pages 功能才会生效,直接 fork,没有任何修改是不行的。 34 | 35 | 如果你想绑定独立域名,修改根目录的 CNAME 文件,将其中的网址修改为你的域名,并把你的域名 CNAME 到 `用户名.github.io` 即可 36 | 37 | ## 2. 部署方法(方案2) 38 | 39 | 1.克隆本项目,修改根目录的 config.js 40 | 41 | var _config = { 42 | blog_name : '用于演示的博客', // 博客名称 43 | owner: 'lifesinger', // github 用户名 44 | repo: 'lifesinger.github.com',// github 中对应项目名 45 | duoshuo_id : 'hello1234', // 在第三方评论插件多说申请站点的子域名 46 | // access_token: '', // 请求量大时需要在 github 后台单独设置一个读取公开库的 token 47 | per_page: '15' // 默认一页显示几篇文章 48 | } 49 | 50 | 2.填写好对应的博客名称,你自己的 github 用户名、对应项目名和多说 ID,保存。多说账号在这里申请http://duoshuo.com/ 51 | 3.将所有文件上传到一个静态空间,打开首页即可看到效果。 52 | 53 | 接下来就是在对应的 repo 的 issues 下写文章了! 54 | 55 | ## 3. 提高 API 访问次数的配额 56 | 57 | 默认情况下你是用匿名权限访问 github 接口的, github 的访问限制是一个小时最多 60 次请求,这显然是不够的,如何提高限制呢? 58 | 59 | 1. 到个人设置下的 Personal access tokens 页(https://github.com/settings/tokens ),如下图,点击右上角的 Generate new token 60 | 61 | ![](http://img-storage.qiniudn.com/15-6-12/56879685.jpg) 62 | 63 | 2. 填写名称,只勾选 `public_repo`,然后保存,github 会生成一个可访问你公开项目的 access_token,将它填入到配置文件的 access_token 的值中,并取消注释。 64 | ![](http://img-storage.qiniudn.com/15-6-12/64340386.jpg) 65 | 66 | 3. 打开 `app.js`,取消掉第 17 行和 88 行的注释,保存后重新上传即可 67 | 68 | data:{ 69 | // access_token:_config['access_token'] 70 | }, 71 | -------------------------------------------------------------------------------- /css/highlight.css: -------------------------------------------------------------------------------- 1 | /* 2 | Date: 17.V.2011 3 | Author: pumbur 4 | */ 5 | 6 | .hljs { 7 | display: block; 8 | overflow-x: auto; 9 | padding: 0.5em; 10 | background: #222; 11 | -webkit-text-size-adjust: none; 12 | } 13 | 14 | .profile .hljs-header *, 15 | .ini .hljs-title, 16 | .nginx .hljs-title { 17 | color: #fff; 18 | } 19 | 20 | .hljs-comment, 21 | .hljs-preprocessor, 22 | .hljs-preprocessor .hljs-title, 23 | .hljs-pragma, 24 | .hljs-shebang, 25 | .profile .hljs-summary, 26 | .diff, 27 | .hljs-pi, 28 | .hljs-doctype, 29 | .hljs-tag, 30 | .css .hljs-rule, 31 | .tex .hljs-special { 32 | color: #444; 33 | } 34 | 35 | .hljs-string, 36 | .hljs-symbol, 37 | .diff .hljs-change, 38 | .hljs-regexp, 39 | .xml .hljs-attribute, 40 | .smalltalk .hljs-char, 41 | .xml .hljs-value, 42 | .ini .hljs-value, 43 | .clojure .hljs-attribute, 44 | .coffeescript .hljs-attribute { 45 | color: #ffcc33; 46 | } 47 | 48 | .hljs-number, 49 | .hljs-addition { 50 | color: #00cc66; 51 | } 52 | 53 | .hljs-built_in, 54 | .hljs-literal, 55 | .hljs-type, 56 | .hljs-typename, 57 | .go .hljs-constant, 58 | .ini .hljs-keyword, 59 | .lua .hljs-title, 60 | .perl .hljs-variable, 61 | .php .hljs-variable, 62 | .mel .hljs-variable, 63 | .django .hljs-variable, 64 | .css .funtion, 65 | .smalltalk .method, 66 | .hljs-hexcolor, 67 | .hljs-important, 68 | .hljs-flow, 69 | .hljs-inheritance, 70 | .hljs-name, 71 | .parser3 .hljs-variable { 72 | color: #32aaee; 73 | } 74 | 75 | .hljs-keyword, 76 | .hljs-tag .hljs-title, 77 | .css .hljs-tag, 78 | .css .hljs-class, 79 | .css .hljs-id, 80 | .css .hljs-pseudo, 81 | .css .hljs-attr_selector, 82 | .hljs-winutils, 83 | .tex .hljs-command, 84 | .hljs-request, 85 | .hljs-status { 86 | color: #6644aa; 87 | } 88 | 89 | .hljs-title, 90 | .ruby .hljs-constant, 91 | .vala .hljs-constant, 92 | .hljs-parent, 93 | .hljs-deletion, 94 | .hljs-template_tag, 95 | .css .hljs-keyword, 96 | .objectivec .hljs-class .hljs-id, 97 | .smalltalk .hljs-class, 98 | .lisp .hljs-keyword, 99 | .apache .hljs-tag, 100 | .nginx .hljs-variable, 101 | .hljs-envvar, 102 | .bash .hljs-variable, 103 | .go .hljs-built_in, 104 | .vbscript .hljs-built_in, 105 | .lua .hljs-built_in, 106 | .rsl .hljs-built_in, 107 | .tail, 108 | .avrasm .hljs-label, 109 | .tex .hljs-formula, 110 | .tex .hljs-formula * { 111 | color: #bb1166; 112 | } 113 | 114 | .hljs-doctag, 115 | .profile .hljs-header, 116 | .ini .hljs-title, 117 | .apache .hljs-tag, 118 | .parser3 .hljs-title { 119 | font-weight: bold; 120 | } 121 | 122 | .coffeescript .javascript, 123 | .javascript .xml, 124 | .tex .hljs-formula, 125 | .xml .javascript, 126 | .xml .vbscript, 127 | .xml .css, 128 | .xml .hljs-cdata { 129 | opacity: 0.6; 130 | } 131 | 132 | .hljs, 133 | .hljs-subst, 134 | .diff .hljs-chunk, 135 | .css .hljs-value, 136 | .css .hljs-attribute { 137 | color: #aaa; 138 | } 139 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | Ractive.DEBUG = false; 2 | function index(page){ 3 | var page = parseInt(page) || 1; 4 | window._G = window._G || {post: {}, postList: {}}; 5 | $('title').html(_config['blog_name']); 6 | if(_G.postList[page] != undefined){ 7 | $('#container').html(_G.postList[page]); 8 | return; 9 | } 10 | 11 | $.ajax({ 12 | url:"https://api.github.com/repos/"+_config['owner']+"/"+_config['repo']+"/issues", 13 | data:{ 14 | filter : 'created', 15 | page : page, 16 | // access_token : _config['access_token'], 17 | per_page : _config['per_page'] 18 | }, 19 | beforeSend:function(){ 20 | $('#container').html('
'); 21 | }, 22 | success:function(data, textStatus, jqXHR){ 23 | var link = jqXHR.getResponseHeader("Link") || ""; 24 | var next = false; 25 | var prev = false; 26 | if(link.indexOf('rel="next"') > 0){ 27 | next = true; 28 | } 29 | if(link.indexOf('rel="prev"') > 0){ 30 | prev = true; 31 | } 32 | var ractive = new Ractive({ 33 | template : '#listTpl', 34 | data : { 35 | posts : data, 36 | next : next, 37 | prev : prev, 38 | page : page 39 | } 40 | }); 41 | window._G.postList[page] = ractive.toHTML(); 42 | $('#container').html(window._G.postList[page]); 43 | 44 | //将文章列表的信息存到全局变量中,避免重复请求 45 | for(i in data){ 46 | var ractive = new Ractive({ 47 | template : '#detailTpl', 48 | data : {post: data[i]} 49 | }); 50 | window._G.post[data[i].number] = {}; 51 | window._G.post[data[i].number].body = ractive.toHTML(); 52 | 53 | var title = data[i].title + " | " + _config['blog_name']; 54 | window._G.post[data[i].number].title = title; 55 | } 56 | } 57 | }); 58 | } 59 | 60 | function highlight(){ 61 | $('pre code').each(function(i, block) { 62 | hljs.highlightBlock(block); 63 | }); 64 | } 65 | 66 | // 动态加载多说评论框的函数 67 | function toggleDuoshuoComments(container, id){ 68 | var el = document.createElement('div'); 69 | var url = window.location.href; 70 | el.setAttribute('data-thread-key', id); 71 | el.setAttribute('data-url', url); 72 | DUOSHUO.EmbedThread(el); 73 | jQuery(container).append(el); 74 | } 75 | 76 | function detail(id){ 77 | if(!window._G){ 78 | window._G = {post: {}, postList: {}}; 79 | window._G.post[id] = {}; 80 | } 81 | 82 | if(_G.post[id].body != undefined){ 83 | $('#container').html(_G.post[id].body); 84 | $('title').html(_G.post[id].title); 85 | toggleDuoshuoComments('#container', id); 86 | highlight(); 87 | return; 88 | } 89 | $.ajax({ 90 | url:"https://api.github.com/repos/"+_config['owner']+"/"+_config['repo']+"/issues/" + id, 91 | data:{ 92 | // access_token:_config['access_token'] 93 | }, 94 | beforeSend:function(){ 95 | $('#container').html('
loading
'); 96 | }, 97 | success:function(data){ 98 | var ractive = new Ractive({ 99 | el: "#container", 100 | template: '#detailTpl', 101 | data: {post: data} 102 | }); 103 | 104 | $('title').html(data.title + " | " + _config['blog_name']); 105 | toggleDuoshuoComments('#container', id); 106 | highlight(); 107 | } 108 | }); 109 | 110 | } 111 | 112 | var helpers = Ractive.defaults.data; 113 | helpers.markdown2HTML = function(content){ 114 | return marked(content); 115 | } 116 | helpers.formatTime = function(time){ 117 | return time.substr(0,10); 118 | } 119 | 120 | var routes = { 121 | '/': index, 122 | 'p:page': index, 123 | 'post/:postId': detail 124 | }; 125 | var router = Router(routes); 126 | router.init('/'); -------------------------------------------------------------------------------- /js/director.min.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | // 4 | // Generated on Tue Dec 16 2014 12:13:47 GMT+0100 (CET) by Charlie Robbins, Paolo Fragomeni & the Contributors (Using Codesurgeon). 5 | // Version 1.2.6 6 | // 7 | (function(a){function k(a,b,c,d){var e=0,f=0,g=0,c=(c||"(").toString(),d=(d||")").toString(),h;for(h=0;hi.indexOf(d,e)||~i.indexOf(c,e)&&!~i.indexOf(d,e)||!~i.indexOf(c,e)&&~i.indexOf(d,e)){f=i.indexOf(c,e),g=i.indexOf(d,e);if(~f&&!~g||!~f&&~g){var j=a.slice(0,(h||1)+1).join(b);a=[j].concat(a.slice((h||1)+1))}e=(g>f?g:f)+1,h=0}else e=0}return a}function j(a,b){var c,d=0,e="";while(c=a.substr(d).match(/[^\w\d\- %@&]*\*[^\w\d\- %@&]*/))d=c.index+c[0].length,c[0]=c[0].replace(/^\*/,"([_.()!\\ %@&a-zA-Z0-9-]+)"),e+=a.substr(0,c.index)+c[0];a=e+=a.substr(d);var f=a.match(/:([^\/]+)/ig),g,h;if(f){h=f.length;for(var j=0;j7))this.history===!0?setTimeout(function(){window.onpopstate=d},500):window.onhashchange=d,this.mode="modern";else{var f=document.createElement("iframe");f.id="state-frame",f.style.display="none",document.body.appendChild(f),this.writeFrame(""),"onpropertychange"in document&&"attachEvent"in document&&document.attachEvent("onpropertychange",function(){event.propertyName==="location"&&c.check()}),window.setInterval(function(){c.check()},50),this.onHashChanged=d,this.mode="legacy"}e.listeners.push(a);return this.mode},destroy:function(a){if(!!e&&!!e.listeners){var b=e.listeners;for(var c=b.length-1;c>=0;c--)b[c]===a&&b.splice(c,1)}},setHash:function(a){this.mode==="legacy"&&this.writeFrame(a),this.history===!0?(window.history.pushState({},document.title,a),this.fire()):b.hash=a[0]==="/"?a:"/"+a;return this},writeFrame:function(a){var b=document.getElementById("state-frame"),c=b.contentDocument||b.contentWindow.document;c.open(),c.write("