diff --git a/.gitignore b/.gitignore index 060b35b..0239700 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules/ dist/ npm-debug.log +src/model.js diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 88439a0..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,6 +0,0 @@ -// 将设置放入此文件中以覆盖默认值和用户设置。 -{ - "files.associations": { - "*.vue": "vue" - } -} diff --git a/README.md b/README.md index c9ee54b..91c8cf0 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ # Issue Blog > A blog for github issue + +This is a static version, it generate static spa and don't need fetch posts from internet. diff --git a/build.js b/build.js new file mode 100644 index 0000000..610d5ec --- /dev/null +++ b/build.js @@ -0,0 +1,69 @@ +require("babel-register"); +require('colors'); +require('stripmargin').inject(); +let fetch = require('node-fetch'); +let fs = require('bluebird').promisifyAll(require('fs')); +let RSS = require('rss'); +let consts = require('./src/const'); +let shell = require('shelljs'); +let { marked } = require('./src/utils'); + + +let feed = new RSS({ + title: consts.site_name, + description: consts.site_description, + feed_url: `${consts.site_url}/atom.xml`, + site_url: consts.site_url, + image_url: `${consts.site_url}/favicon.ico`, + webMaster: 'shyling' +}); + +(async function () { + let posts = (await (await fetch(`https://api.github.com/repos/${consts.owner}/${consts.repo}/issues?creator=${consts.owner}&state=all`)).json()) + .filter(post => post.state !== 'closed') + .map(post => { + return { + title: post.title, + state: post.state, + number: post.number, + body: post.body, + date: post.created_at, + tags: post.labels.map(label => { + return { + name: label.name, + color: label.color + }; + }) + } + }); + posts.forEach(post => { + feed.item({ + title: post.title, + description: marked(post.body), + url: `${consts.site_url}/#/post/${post.number}`, + guid: post.number.toString(), + }); + }) + let code = ` + let posts = ${JSON.stringify(posts)}; + export const Post = { + | async get(no) { + | let post = posts.find(post=>post.number == no); + | if(post){ + | return post; + | }else{ + | throw new Error('Post not found'); + | } + | }, + | async all() { + | return posts; + | } + };`.stripMargin(); + shell.rm('-rf', 'dist'); + await fs.writeFileAsync('./src/model.js', code, { encoding: 'utf-8' }); + require('./build/build.js'); + await fs.writeFileAsync('./dist/atom.xml', feed.xml(), { encoding: 'utf-8' }); +})() + .catch(e => { + console.error(e.toString().red); + }); diff --git a/index.html b/index.html index 46d6d97..3d0f3af 100644 --- a/index.html +++ b/index.html @@ -9,8 +9,6 @@ - -
diff --git a/package.json b/package.json index 2f56d2d..f54ea14 100644 --- a/package.json +++ b/package.json @@ -5,9 +5,7 @@ "author": "ling", "private": true, "scripts": { - "dev": "node build/dev-server.js", - "build": "node build/build.js", - "serve": "node server.js" + "build": "node --harmony_async_await build.js" }, "dependencies": { "animate.css": "^3.5.2", @@ -21,6 +19,7 @@ "muse-ui": "^2.0.0-alpha.8", "roboto-fontface": "^0.6.0", "vue": "^2.1.6", + "vue-duoshuo": "^1.0.2", "vue-router": "^2.1.1", "whatwg-fetch": "^2.0.1" }, @@ -32,7 +31,9 @@ "babel-preset-es2015": "^6.0.0", "babel-preset-stage-2": "^6.0.0", "babel-register": "^6.0.0", + "bluebird": "^3.4.7", "chalk": "^1.1.3", + "colors": "^1.1.2", "compression": "^1.6.2", "connect-history-api-fallback": "^1.1.0", "css-loader": "^0.26.1", @@ -47,12 +48,15 @@ "less": "^2.7.1", "less-loader": "^2.2.3", "morgan": "^1.7.0", + "node-fetch": "^1.6.3", "node-sass": "^4.1.1", "opn": "^4.0.2", "ora": "^0.4.0", + "rss": "^1.2.2", "sass-loader": "^4.1.1", "semver": "^5.3.0", "shelljs": "^0.7.4", + "stripmargin": "^1.0.7", "url-loader": "^0.5.7", "vue-loader": "^10.0.2", "vue-style-loader": "^1.0.0", @@ -63,7 +67,7 @@ "webpack-merge": "^2.0.0" }, "engines": { - "node": ">= 4.0.0", + "node": ">= 7.0.0", "npm": ">= 3.0.0" } } diff --git a/server.js b/server.js deleted file mode 100644 index 3f3b103..0000000 --- a/server.js +++ /dev/null @@ -1,46 +0,0 @@ -let path = require('path'); -let express = require('express'); -let http = require('http'); -let https = require('https'); -let fs = require('fs'); - -let app = express(); - -app.disable('x-powered-by'); -app.set('env', 'production'); - -app.use(require('morgan')('tiny')); -app.use(require('compression')()); -// Content-Security-Policy -app.use(function (req, res, next) { - if (req.secure && req.get('Upgrade-Insecure-Requests') == 1) { - res.set('Content-Security-Policy', 'upgrade-insecure-requests'); - } - next(); -}); - -app.use(express.static(path.join(__dirname + '/dist'), { - maxAge: 1e3 * 3600 * 24, - setHeaders: function (res, path, stat) { - console.log(path); - if (path.endsWith('index.html')) { - res.set('Cache-Control', 'private, max-age=0'); - } - } -})); - -http.createServer(app).listen(80); - -let key = path.join(__dirname, '/https', 'ssl.key'); -let cert = path.join(__dirname, '/https', 'ssl.crt'); - -if (fs.existsSync(key) && fs.existsSync(cert)) { - https.createServer({ - key: fs.readFileSync(key), - cert: fs.readFileSync(cert) - }, - app - ).listen(443); -} - - diff --git a/src/components/header.vue b/src/components/header.vue index 0726f16..b90eb9f 100644 --- a/src/components/header.vue +++ b/src/components/header.vue @@ -34,6 +34,7 @@ .tags { display: flex; flex-wrap: wrap; + justify-content: space-around; } .icon { & { @@ -69,7 +70,7 @@ diff --git a/src/const.js b/src/const.js index 9c9cd93..baeb65a 100644 --- a/src/const.js +++ b/src/const.js @@ -4,6 +4,7 @@ import { genLinks } from './utils' export const owner = 'lingmm'; export const repo = 'IssueBlog'; export const site_name = '泠的博客'; +export const site_url = 'http://shyling.com' export const site_description = `この光が空を越えて羽ばたいてゆく @@ -36,12 +37,15 @@ ouzz的博客|http://ouzz.me Just4fun|https://coolrc.me 御坂網路司令塔|https://misaka.genderqueer.gq Zhustec's Blog|http://blog.zhustec.me/ +石樱灯笼的博客|http://blog.catscarlet.com/ +千里冰封|http://ice1000.tech/ +Zelda's Blog|http://itsay.tech/ `); let about = ` 博主大四(目前正在实习ORZ),目前主要是Node相关 -目前坐标北京,住在某睡城。 +目前坐标北京,住在知春里。 专业三分热度,曾很喜欢Ruby、C++、OCaml,酱油Pythonist,偶尔写写前端,客串一下运维OvO。 diff --git a/src/main.js b/src/main.js index bfa96dd..5e1ae73 100644 --- a/src/main.js +++ b/src/main.js @@ -10,6 +10,7 @@ import 'roboto-fontface/css/roboto/roboto-fontface.css' import 'github-markdown-css/github-markdown.css' import 'animate.css/animate.css' import 'assets/style.scss' +import 'highlight.js/styles/github.css' import consts from './const' import App from './App' diff --git a/src/mixin.js b/src/mixin.js index 6f720bb..ee78c7a 100644 --- a/src/mixin.js +++ b/src/mixin.js @@ -1,25 +1,10 @@ -import marked from 'marked' import dateutil from 'dateutil' -import highlight from 'highlight.js' -import 'highlight.js/styles/github.css' +import { marked } from './utils' -marked.setOptions({ - renderer: new marked.Renderer(), - gfm: true, - tables: true, - breaks: true, - pedantic: false, - sanitize: false, - smartLists: true, - smartypants: false, - highlight: function (code) { - return highlight.highlightAuto(code).value; - } -}); export default { methods: { - marked: v => marked(v), + marked, init: v => { v = v || ''; let i = v.indexOf(''); diff --git a/src/model.js b/src/model.js deleted file mode 100644 index 5dcd916..0000000 --- a/src/model.js +++ /dev/null @@ -1,75 +0,0 @@ -import 'whatwg-fetch' -import Storage from './storage' -import { owner, repo } from './const' - -const FETCH_ALL_TIME = 'FETCH_ALL_TIME'; - -// cache expires after 1h, if cache available, not request remote -export const Post = { - perfix: 'https://api.github.com', - timeout: 60 * 60 * 1000, - - async get(number) { - let post = Storage.get(number); - if (post && (Date.now() <= (post.fetch_time + Post.timeout))) { - return post; - } else { - let res = await fetch(`${Post.perfix}/repos/${owner}/${repo}/issues/${number}`, { - cors: 'include' - }); - let data = await res.json(); - - let post = { - title: data.title, - date: data.created_at, - body: data.body, - state: data.state, - number: data.number, - tags: data.labels.map(label => { - return { - name: label.name, - color: label.color - }; - }), - fetch_time: Date.now() - }; - Storage.set(post.number, post); - return Post.get(number); - } - }, - async all() { - let time = Storage.get(FETCH_ALL_TIME) || 0; - if (Date.now() <= (time + Post.timeout)) { - return Storage.keys().sort((a, b) => b - a).map(Storage.get); - } else { - let res = await fetch(`${Post.perfix}/repos/${owner}/${repo}/issues?filter=created&state=all`, { - cors: 'include' - }); - - Storage.keys().map(Storage.delete); //delete all - - let data = await res.json(); - data.filter(single => { - return single.state !== 'closed'; - }).forEach(single => { - let data = { - title: single.title, - state: single.state, - number: single.number, - body: single.body, - date: single.created_at, - tags: single.labels.map(label => { - return { - name: label.name, - color: label.color - }; - }), - fetch_time: Date.now() - }; - Storage.set(data.number, data); - }); - Storage.set(FETCH_ALL_TIME, Date.now()); - return Post.all(); - } - } -} diff --git a/src/storage.js b/src/storage.js deleted file mode 100644 index feeb54a..0000000 --- a/src/storage.js +++ /dev/null @@ -1,70 +0,0 @@ -const PERFIX = 'ISSUEBLOG_POST'; -const TEST = 'TEST'; - -//maybe can inject to String prototype,don't like -function genStartsWith() { - let support = String.prototype.startsWith !== undefined; - if (support) { - return function (str, arg) { - return str.startsWith(arg); - } - } else { - return function (str, arg) { - return str.substr(0, arg.length) === arg; - } - } -} - -let startsWith = genStartsWith(); - -let LocalStorage = { - get: k => JSON.parse(localStorage.getItem(`${PERFIX}_${k}`)), - set: (k, v) => localStorage.setItem(`${PERFIX}_${k}`, JSON.stringify(v)), - delete: k => localStorage.removeItem(`${PERFIX}_${k}`), - destory: () => LocalStorage.keys(post_only = false).forEach(LocalStorage.delete), - keys: (post_only = true) => Object.keys(localStorage).filter(k => { - if (post_only) { - return startsWith(k, PERFIX) && /\d$/.test(k); - } else { - return startsWith(k, PERFIX); - } - }).map(k => k.substring(PERFIX.length + 1)), - each: func => { - for (let i of LocalStorage.keys()) { - func(LocalStorage.get(i), i); - } - } -} - -let MemoryStorage = { - _storage: {}, - get: k => MemoryStorage._storage[k], - set: (k, v) => MemoryStorage._storage[k] = v, - delete: k => delete MemoryStorage._storage[k], - destory: () => MemoryStorage._storage = {}, - keys: (post_only = true) => Object.keys(MemoryStorage._storage).filter(k => { - return post_only ? /\d$/.test(k) : true; - }), - each: func => { - for (let i of MemoryStorage.keys()) { - func(MemoryStorage.get(i), i); - } - } -} - -function Storage() { - if (window.localStorage) { - //test if in private mode - try { - LocalStorage.set(TEST, TEST); - LocalStorage.delete(TEST); - return LocalStorage; - } catch (e) { - return MemoryStorage; - } - } else { - return MemoryStorage; - } -} - -export default Storage(); diff --git a/src/utils.js b/src/utils.js index c93e473..f81c517 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,3 +1,7 @@ +import markdown from 'marked' +import highlight from 'highlight.js' + + // maybe need escape export const genLinks = links => { let out = '