├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitignore ├── LICENSE ├── README.md ├── assets └── favicon.ico ├── build ├── config.js ├── index.html ├── log-plugin.js ├── server.js ├── utils.js ├── webpack.base.js ├── webpack.dev.js └── webpack.prod.js ├── client ├── app.js ├── components │ ├── App.vue │ ├── footer.vue │ ├── header.vue │ ├── index.js │ ├── link.vue │ ├── list.vue │ ├── loading.vue │ └── tree.vue ├── index.js ├── promise-polyfill.js ├── pwa.js └── util │ └── index.js ├── note ├── a.md ├── b.md ├── c.md ├── d.md ├── e.md ├── f.md ├── hello.md ├── test.png └── test │ └── hello.md ├── package.json └── server ├── api.js ├── cmd.js ├── constant.js ├── handler.js ├── index.js ├── note.js ├── router.js └── search.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["es2015"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "vue", 3 | plugins: ['vuefix'] 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | .DS_Store 4 | /dist 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 涵曦 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 个人笔记-markdown 2 | 3 | 一个专注浏览的个人笔记. 4 | 5 | ### 快速使用 6 | 7 | ```bash 8 | git clone https://github.com/hanxi/note-md.git 9 | cd node-md 10 | npm install 11 | npm run build 12 | npm start 13 | ``` 14 | 15 | - 笔记存储在 `note` 目录 16 | - 浏览器访问 17 | 18 | ### 配置备份笔记 19 | 20 | - 在 GitHub 上建一个空项目(类似这个 [note-md-testbackup](https://github.com/hanxi/note-md-testbackup) ) 21 | - 项目类型一定要选 private(国内的 oschina 和 Coding 都提供免费私人项目) 22 | - 修改 `git.js` 中的 `gitUrl` 为自己刚新建的工程的 SSH 协议的路径(HTTPS需要输入密码不适合自动备份) 23 | - 前提是本地配好了 id_rsa 和 GitHub 上也填写好了 id_rsa.pub 24 | - 默认半小时提交一次 25 | 26 | ### 功能 27 | 28 | - [x] 实时刷新 29 | - [x] 定期备份 `note` 目录 30 | - [x] 目录树 31 | - [x] 笔记搜索 32 | 33 | ### 这些库帮我减少了不少时间 34 | 35 | - Markdown 转 HTML [showdown](https://github.com/showdownjs/showdown) 36 | - ~~轻量灵活的移动端 CSS 框架 [mobi.css](https://github.com/xcatliu/mobi.css)~~ (v0.0.1) 37 | - 基于 Material Design 的前端框架 [MDUI](https://github.com/zdhxiong/mdui) 38 | - 文件内容搜索工具 [find-in-files](https://github.com/kaesetoast/find-in-files) 39 | - 当然少不了 [Vue](https://cn.vuejs.org/) 40 | -------------------------------------------------------------------------------- /assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanxi/note-md/16eceede6f255cf83f19d9b147cff5e49de4b1e0/assets/favicon.ico -------------------------------------------------------------------------------- /build/config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const pkg = require('../package') 3 | 4 | module.exports = { 5 | port: 8080, 6 | title: '个人笔记 note-md', 7 | // when you use electron please set to relative path like ./ 8 | // otherwise only set to absolute path when you're using history mode 9 | publicPath: '/', 10 | // add these dependencies to a standalone vendor bundle 11 | vendor: [ 12 | 'vue', 13 | 'vuex', 14 | 'promise-polyfill', 15 | 'mdui' 16 | ], 17 | // disable babelrc by default 18 | babel: { 19 | babelrc: false, 20 | presets: [ 21 | ['es2015', {modules: false}], 22 | 'stage-1' 23 | ], 24 | // support jsx in render function 25 | plugins: [ 26 | 'transform-vue-jsx', 27 | 'transform-runtime' 28 | ] 29 | }, 30 | postcss: [ 31 | // add prefix via postcss since it's faster 32 | require('autoprefixer')({ 33 | // Vue does not support ie 8 and below 34 | browsers: ['last 2 versions', 'ie > 8'] 35 | }), 36 | require('postcss-nested') 37 | ], 38 | cssModules: false, 39 | proxyTable: { 40 | '/api/*' : 'http://localhost:3000' 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /build/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <%= htmlWebpackPlugin.options.title %> 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /build/log-plugin.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const chalk = require('chalk') 3 | 4 | // this plugin if for loggin url after each time the compilation is done. 5 | module.exports = class LogPlugin { 6 | constructor(port) { 7 | this.port = port 8 | } 9 | 10 | apply(compiler) { 11 | compiler.plugin('done', () => { 12 | console.log(`> VuePack is running at ${chalk.yellow(`http://localhost:${this.port}`)}\n`) 13 | }) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /build/server.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const fs = require('fs') 3 | const path = require('path') 4 | const chalk = require('chalk') 5 | const express = require('express') 6 | const webpack = require('webpack') 7 | const proxyMiddleware = require('http-proxy-middleware') 8 | const webpackConfig = require('./webpack.dev') 9 | const config = require('./config') 10 | const LogPlugin = require('./log-plugin') 11 | 12 | const app = express() 13 | const proxyTable = config.proxyTable 14 | 15 | const port = config.port 16 | webpackConfig.entry.client = [ 17 | `webpack-hot-middleware/client?reload=true`, 18 | webpackConfig.entry.client 19 | ] 20 | 21 | webpackConfig.plugins.push(new LogPlugin(port)) 22 | 23 | let compiler 24 | 25 | try { 26 | compiler = webpack(webpackConfig) 27 | } catch (err) { 28 | console.log(err.message) 29 | process.exit(1) 30 | } 31 | 32 | const devMiddleWare = require('webpack-dev-middleware')(compiler, { 33 | publicPath: webpackConfig.output.publicPath, 34 | quiet: true 35 | }) 36 | app.use(devMiddleWare) 37 | app.use(require('webpack-hot-middleware')(compiler, { 38 | log: () => {} 39 | })) 40 | 41 | devMiddleWare.waitUntilValid() 42 | 43 | // proxy api requests 44 | Object.keys(proxyTable).forEach(function (context) { 45 | var options = proxyTable[context] 46 | if (typeof options === 'string') { 47 | options = { target: options } 48 | } 49 | app.use(proxyMiddleware(context, options)) 50 | }) 51 | 52 | // handle fallback for HTML5 history API 53 | app.use(require('connect-history-api-fallback')()) 54 | 55 | app.listen(port) 56 | -------------------------------------------------------------------------------- /build/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 4 | const config = require('./config') 5 | 6 | const _ = module.exports = {} 7 | 8 | _.cwd = (file) => { 9 | return path.join(process.cwd(), file || '') 10 | } 11 | 12 | _.cssLoader = config.cssModules ? 13 | 'css-loader?-autoprefixer&modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]' : 14 | 'css-loader?-autoprefixer' 15 | 16 | _.cssProcessors = [ 17 | {loader: '', test: /\.css$/}, 18 | {loader: 'sass-loader?sourceMap', test: /\.scss$/}, 19 | {loader: 'less-loader?sourceMap', test: /\.less$/}, 20 | {loader: 'stylus-loader?sourceMap', test: /\.styl$/}, 21 | {loader: 'sass-loader?indentedSyntax&sourceMap', test: /\.sass$/}, 22 | ] 23 | 24 | _.outputPath = config.electron ? 25 | path.join(__dirname, '../app/dist') : 26 | path.join(__dirname, '../dist') 27 | 28 | _.outputIndexPath = config.electron ? 29 | path.join(__dirname, '../app/dist/index.html') : 30 | path.join(__dirname, '../dist/index.html') 31 | 32 | _.target = config.electron ? 33 | 'electron-renderer' : 34 | 'web' 35 | 36 | // https://github.com/egoist/vbuild/blob/master/lib/vue-loaders.js 37 | _.loadersOptions = () => { 38 | const isProd = process.env.NODE_ENV === 'production' 39 | 40 | function generateLoader(langs) { 41 | langs.unshift('css-loader?sourceMap&-autoprefixer') 42 | if (!isProd) { 43 | return ['vue-style-loader'].concat(langs).join('!') 44 | } 45 | return ExtractTextPlugin.extract({ 46 | fallback: 'vue-style-loader', 47 | use: langs.join('!') 48 | }) 49 | } 50 | 51 | return { 52 | minimize: isProd, 53 | options: { 54 | // css-loader relies on context 55 | context: process.cwd(), 56 | // postcss plugins apply to .css files 57 | postcss: config.postcss, 58 | babel: config.babel, 59 | vue: { 60 | // postcss plugins apply to css in .vue files 61 | postcss: config.postcss, 62 | loaders: { 63 | css: generateLoader([]), 64 | sass: generateLoader(['sass-loader?indentedSyntax&sourceMap']), 65 | scss: generateLoader(['sass-loader?sourceMap']), 66 | less: generateLoader(['less-loader?sourceMap']), 67 | stylus: generateLoader(['stylus-loader?sourceMap']), 68 | js: 'babel-loader' 69 | } 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /build/webpack.base.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const webpack = require('webpack') 4 | const HtmlWebpackPlugin = require('html-webpack-plugin') 5 | const CopyWebpackPlugin = require('copy-webpack-plugin') 6 | const config = require('./config') 7 | const _ = require('./utils') 8 | 9 | module.exports = { 10 | entry: { 11 | client: './client/index.js' 12 | }, 13 | output: { 14 | path: _.outputPath, 15 | filename: '[name].js', 16 | publicPath: config.publicPath 17 | }, 18 | performance: { 19 | hints: process.env.NODE_ENV === 'production' ? 'warning' : false 20 | }, 21 | resolve: { 22 | extensions: ['.js', '.vue', '.css', '.json'], 23 | alias: { 24 | root: path.join(__dirname, '../client'), 25 | components: path.join(__dirname, '../client/components'), 26 | util: path.join(__dirname, '../client/util') 27 | }, 28 | modules: [ 29 | _.cwd('node_modules'), 30 | // this meanse you can get rid of dot hell 31 | // for example import 'components/Foo' instead of import '../../components/Foo' 32 | _.cwd('client') 33 | ] 34 | }, 35 | module: { 36 | loaders: [ 37 | { 38 | test: /\.vue$/, 39 | loaders: ['vue-loader'] 40 | }, 41 | { 42 | test: /\.js$/, 43 | loaders: ['babel-loader'], 44 | exclude: [/node_modules/] 45 | }, 46 | { 47 | test: /\.es6$/, 48 | loaders: ['babel-loader'] 49 | }, 50 | { 51 | test: /\.(ico|jpg|png|gif|eot|otf|webp|ttf|woff|woff2)(\?.*)?$/, 52 | loader: 'file-loader', 53 | query: { 54 | name: 'assets/media/[name].[hash:8].[ext]' 55 | } 56 | }, 57 | { 58 | test: /\.svg$/, 59 | loader: 'raw-loader' 60 | } 61 | ] 62 | }, 63 | plugins: [ 64 | new HtmlWebpackPlugin({ 65 | title: config.title, 66 | template: path.resolve(__dirname, 'index.html'), 67 | filename: _.outputIndexPath 68 | }), 69 | new webpack.LoaderOptionsPlugin(_.loadersOptions()), 70 | new CopyWebpackPlugin([ 71 | { 72 | from: _.cwd('./assets'), 73 | // to the roor of dist path 74 | to: './' 75 | } 76 | ]) 77 | ], 78 | target: _.target 79 | } 80 | -------------------------------------------------------------------------------- /build/webpack.dev.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | process.env.NODE_ENV = 'development' 3 | 4 | const webpack = require('webpack') 5 | const base = require('./webpack.base') 6 | const _ = require('./utils') 7 | const FriendlyErrors = require('friendly-errors-webpack-plugin') 8 | 9 | base.devtool = 'eval-source-map' 10 | base.plugins.push( 11 | new webpack.DefinePlugin({ 12 | 'process.env.NODE_ENV': JSON.stringify('development') 13 | }), 14 | new webpack.HotModuleReplacementPlugin(), 15 | new webpack.NoErrorsPlugin(), 16 | new FriendlyErrors() 17 | ) 18 | 19 | // push loader for css files 20 | _.cssProcessors.forEach(processor => { 21 | let loaders 22 | if (processor.loader === '') { 23 | loaders = ['postcss-loader'] 24 | } else { 25 | loaders = ['postcss-loader', processor.loader] 26 | } 27 | base.module.loaders.push( 28 | { 29 | test: processor.test, 30 | loaders: ['style-loader', _.cssLoader].concat(loaders) 31 | } 32 | ) 33 | }) 34 | 35 | module.exports = base 36 | -------------------------------------------------------------------------------- /build/webpack.prod.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | process.env.NODE_ENV = 'production' 3 | 4 | const exec = require('child_process').execSync 5 | const webpack = require('webpack') 6 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 7 | const ProgressPlugin = require('webpack/lib/ProgressPlugin') 8 | const OfflinePlugin = require('offline-plugin') 9 | const base = require('./webpack.base') 10 | const pkg = require('../package') 11 | const _ = require('./utils') 12 | const config = require('./config') 13 | 14 | if (config.electron) { 15 | // remove dist folder in electron mode 16 | exec('rm -rf app/assets/') 17 | } else { 18 | // remove dist folder in web app mode 19 | exec('rm -rf dist/') 20 | // use source-map in web app mode 21 | base.devtool = 'source-map' 22 | } 23 | 24 | // a white list to add dependencies to vendor chunk 25 | base.entry.vendor = config.vendor 26 | // use hash filename to support long-term caching 27 | base.output.filename = '[name].[chunkhash:8].js' 28 | // add webpack plugins 29 | base.plugins.push( 30 | new ProgressPlugin(), 31 | new ExtractTextPlugin('styles.[contenthash:8].css'), 32 | new webpack.DefinePlugin({ 33 | 'process.env.NODE_ENV': JSON.stringify('production') 34 | }), 35 | new webpack.optimize.UglifyJsPlugin({ 36 | sourceMap: true, 37 | compress: { 38 | warnings: false 39 | }, 40 | output: { 41 | comments: false 42 | } 43 | }), 44 | // extract vendor chunks 45 | new webpack.optimize.CommonsChunkPlugin({ 46 | name: 'vendor', 47 | filename: 'vendor.[chunkhash:8].js' 48 | }), 49 | // progressive web app 50 | // it uses the publicPath in webpack config 51 | new OfflinePlugin({ 52 | relativePaths: false, 53 | AppCache: false, 54 | ServiceWorker: { 55 | events: true 56 | } 57 | }) 58 | ) 59 | 60 | // extract css in standalone css files 61 | _.cssProcessors.forEach(processor => { 62 | let loaders 63 | if (processor.loader === '') { 64 | loaders = ['postcss-loader'] 65 | } else { 66 | loaders = ['postcss-loader', processor.loader] 67 | } 68 | base.module.loaders.push({ 69 | test: processor.test, 70 | use: ExtractTextPlugin.extract({ 71 | use: [_.cssLoader].concat(loaders), 72 | fallback: 'style-loader' 73 | }) 74 | }) 75 | }) 76 | 77 | // minimize webpack output 78 | base.stats = { 79 | // Add children information 80 | children: false, 81 | // Add chunk information (setting this to `false` allows for a less verbose output) 82 | chunks: false, 83 | // Add built modules information to chunk information 84 | chunkModules: false, 85 | chunkOrigins: false, 86 | modules: false 87 | } 88 | 89 | module.exports = base 90 | -------------------------------------------------------------------------------- /client/app.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | import 'mdui/dist/css/mdui.css' 5 | 6 | import components from './components/' // 加载公共组件 7 | import App from './components/App' 8 | 9 | Vue.use(Vuex) 10 | 11 | Object.keys(components).forEach((key) => { 12 | var name = key.replace(/(\w)/, (v) => v.toUpperCase()) // 首字母大写 13 | Vue.component(`v${name}`, components[key]) 14 | }) 15 | 16 | const app = new Vue({ ...App }) 17 | 18 | window.addEventListener('popstate', (e) => { 19 | app.currentRoute = window.location.pathname 20 | }) 21 | 22 | export default app 23 | 24 | -------------------------------------------------------------------------------- /client/components/App.vue: -------------------------------------------------------------------------------- 1 | 14 | 84 | 238 | 239 | -------------------------------------------------------------------------------- /client/components/footer.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 24 | 26 | 27 | -------------------------------------------------------------------------------- /client/components/header.vue: -------------------------------------------------------------------------------- 1 | 3 | 16 | 58 | 59 | -------------------------------------------------------------------------------- /client/components/index.js: -------------------------------------------------------------------------------- 1 | import header from './header' 2 | import footer from './footer' 3 | import loading from './loading' 4 | import link from './link' 5 | import tree from './tree' 6 | import list from './list' 7 | export default { header, footer, loading, link, tree, list } 8 | 9 | -------------------------------------------------------------------------------- /client/components/link.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 30 | 31 | 36 | 37 | -------------------------------------------------------------------------------- /client/components/list.vue: -------------------------------------------------------------------------------- 1 | 3 | 30 | 35 | -------------------------------------------------------------------------------- /client/components/loading.vue: -------------------------------------------------------------------------------- 1 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /client/components/tree.vue: -------------------------------------------------------------------------------- 1 | 13 | 39 | 111 | 112 | -------------------------------------------------------------------------------- /client/index.js: -------------------------------------------------------------------------------- 1 | import './promise-polyfill' 2 | import app from './app' 3 | 4 | // enable progressive web app support (with offline-plugin) 5 | if (process.env.NODE_ENV === 'production') { 6 | require('./pwa') 7 | } 8 | 9 | app.$mount('#app') 10 | -------------------------------------------------------------------------------- /client/promise-polyfill.js: -------------------------------------------------------------------------------- 1 | import Promise from 'promise-polyfill' 2 | window.Promise = window.Promise || Promise 3 | -------------------------------------------------------------------------------- /client/pwa.js: -------------------------------------------------------------------------------- 1 | import runtime from 'offline-plugin/runtime' 2 | 3 | runtime.install({ 4 | // When an update is ready, tell ServiceWorker to take control immediately: 5 | onUpdateReady () { 6 | console.log('update ready') 7 | runtime.applyUpdate() 8 | }, 9 | 10 | // Reload to get the new version: 11 | onUpdated () { 12 | console.log('updated') 13 | window.location.reload() 14 | } 15 | }) 16 | -------------------------------------------------------------------------------- /client/util/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueResource from 'vue-resource' 3 | Vue.use(VueResource) 4 | 5 | const toast = (msg = '', time = 1500) => { 6 | var toast = document.createElement('div') 7 | toast.className = 'common-toast common-toast-show' 8 | toast.innerHTML = msg 9 | document.body.appendChild(toast) 10 | toast.style.display = 'block' 11 | toast.style.margin = `-${toast.offsetHeight / 2}px 0 0 -${toast.offsetWidth / 2}px` 12 | var timer = setTimeout(() => { 13 | toast.className = 'common-toast common-toast-hide' 14 | clearTimeout(timer) 15 | var timer2 = setTimeout(() => { 16 | document.body.removeChild(toast) 17 | clearTimeout(timer2) 18 | }, 200) 19 | }, time) 20 | } 21 | 22 | Vue.http.interceptors.push((request, next) => { 23 | next((response) => { 24 | console.log(response) 25 | if (!response.ok) { 26 | toast('加载失败') 27 | } 28 | }) 29 | }) 30 | 31 | export default { 32 | get (url, data = {}, success = () => { }, error = () => { }) { 33 | Vue.http.get(url, { params: data }).then((response) => { 34 | return response.json() 35 | }, error).then(success) 36 | }, 37 | post (url, data = {}, success = () => { }, error = () => { }) { 38 | Vue.http.post(url, { body: data }).then((response) => { 39 | return response.json() 40 | }, error).then(success) 41 | }, 42 | /** 43 | * 消息消失框 44 | */ 45 | toast 46 | } 47 | 48 | -------------------------------------------------------------------------------- /note/a.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanxi/note-md/16eceede6f255cf83f19d9b147cff5e49de4b1e0/note/a.md -------------------------------------------------------------------------------- /note/b.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanxi/note-md/16eceede6f255cf83f19d9b147cff5e49de4b1e0/note/b.md -------------------------------------------------------------------------------- /note/c.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanxi/note-md/16eceede6f255cf83f19d9b147cff5e49de4b1e0/note/c.md -------------------------------------------------------------------------------- /note/d.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanxi/note-md/16eceede6f255cf83f19d9b147cff5e49de4b1e0/note/d.md -------------------------------------------------------------------------------- /note/e.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanxi/note-md/16eceede6f255cf83f19d9b147cff5e49de4b1e0/note/e.md -------------------------------------------------------------------------------- /note/f.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanxi/note-md/16eceede6f255cf83f19d9b147cff5e49de4b1e0/note/f.md -------------------------------------------------------------------------------- /note/hello.md: -------------------------------------------------------------------------------- 1 | # 你好 2 | 3 | - 好 4 | - 不好 5 | 6 | ![testimg](test.png) 7 | 8 | - [ ] xxxx 9 | - [x] yyyy 10 | 11 | ```lua 12 | print("hello world") 13 | ``` 14 | 15 | - [ ] xxxx 16 | - [x] yyyy 17 | - [x] yyyy 18 | - [x] yyyy 19 | 20 | 21 | -------------------------------------------------------------------------------- /note/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanxi/note-md/16eceede6f255cf83f19d9b147cff5e49de4b1e0/note/test.png -------------------------------------------------------------------------------- /note/test/hello.md: -------------------------------------------------------------------------------- 1 | # 你好 2 | 3 | - 好 4 | - 不好 5 | 6 | ![testimg](../test.png) 7 | 8 | - [ ] xxxx 9 | - [x] yyyy 10 | 11 | ```lua 12 | print("hello world") 13 | ``` 14 | 15 | - [ ] xxxx 16 | - [x] yyyy 17 | - [x] yyyy 18 | - [x] yyyy 19 | 20 | 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "note-md", 3 | "version": "0.0.1", 4 | "description": "个人笔记-markdown", 5 | "scripts": { 6 | "init": "node server/git.js init", 7 | "build": "webpack --config build/webpack.prod.js", 8 | "start": "node server/index.js", 9 | "dev": "node build/server.js", 10 | "lint": "eslint client/* --ext .js --ext .vue" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/hanxi/note-md.git" 15 | }, 16 | "keywords": [ 17 | "markdown", 18 | "note", 19 | "笔记" 20 | ], 21 | "author": "涵曦", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/hanxi/note-md/issues" 25 | }, 26 | "homepage": "https://github.com/hanxi/note-md#readme", 27 | "dependencies": { 28 | "babel-runtime": "^6.18.0", 29 | "find-in-files": "^0.3.1", 30 | "is": "^3.2.0", 31 | "mdui": "^0.1.2", 32 | "mime-types": "^2.1.14", 33 | "moment": "^2.17.1", 34 | "promise-polyfill": "^6.0.2", 35 | "query-string": "^4.3.4", 36 | "remove-markdown": "^0.1.0", 37 | "showdown": "^1.5.5", 38 | "vue": "^2.1.0", 39 | "vue-resource": "^1.0.3", 40 | "vuex": "^2.1.1" 41 | }, 42 | "devDependencies": { 43 | "autoprefixer": "^6.4.0", 44 | "babel-core": "^6.16.0", 45 | "babel-helper-vue-jsx-merge-props": "^2.0.1", 46 | "babel-loader": "^6.2.4", 47 | "babel-plugin-syntax-jsx": "^6.13.0", 48 | "babel-plugin-transform-vue-jsx": "^3.1.0", 49 | "babel-preset-es2015": "^6.14.0", 50 | "babel-preset-stage-1": "^6.13.0", 51 | "chalk": "^1.1.3", 52 | "connect-history-api-fallback": "^1.3.0", 53 | "copy-webpack-plugin": "^4.0.1", 54 | "cross-env": "^2.0.0", 55 | "css-loader": "^0.23.1", 56 | "eslint": "^3.6.0", 57 | "eslint-config-vue": "latest", 58 | "eslint-plugin-vue": "latest", 59 | "eslint-plugin-vuefix": "^0.1.0", 60 | "express": "^4.14.0", 61 | "extract-text-webpack-plugin": "^2.0.0-beta.3", 62 | "file-loader": "^0.9.0", 63 | "friendly-errors-webpack-plugin": "^1.1.2", 64 | "html-webpack-plugin": "^2.22.0", 65 | "http-proxy-middleware": "^0.17.3", 66 | "offline-plugin": "^4.5.3", 67 | "postcss-loader": "^0.9.1", 68 | "postcss-nested": "^1.0.0", 69 | "raw-loader": "^0.5.1", 70 | "style-loader": "^0.13.1", 71 | "vue-loader": "^10.0.2", 72 | "vue-style-loader": "^1.0.0", 73 | "vue-template-compiler": "^2.1.3", 74 | "webpack": "2.2.0-rc.3", 75 | "webpack-dev-middleware": "^1.9.0", 76 | "webpack-hot-middleware": "^2.12.2" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /server/api.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const path = require('path') 5 | const parser = require('url') 6 | 7 | const router = require('./router') 8 | const note = require('./note') 9 | const search = require('./search') 10 | const C = require('./constant') 11 | 12 | const register = (url, method) => { 13 | router.register('/api' + url, method) 14 | } 15 | 16 | const notFound = (req, res) => { 17 | console.error(req.url, ' Not Found') 18 | const mime = req.headers.accepts || 'application/json' 19 | res.writeHead(404, { 'Content-Type': mime }) 20 | const ret = { 21 | error: 'Not Found' 22 | } 23 | res.end(JSON.stringify(ret)) 24 | } 25 | 26 | const resJson = (req, res, json) => { 27 | res.writeHead(200, { 'Content-Type': 'application/json' }) 28 | res.end(JSON.stringify(json)) 29 | } 30 | 31 | register('/folder', function (req, res) { 32 | const uri = parser.parse(req.url, true) 33 | const pathname = uri.query.pathname 34 | const dirname = path.join(C.noteDir, pathname) 35 | fs.access(dirname, (err) => { 36 | const exists = !err 37 | if (!exists) { 38 | return notFound(req, res) 39 | } 40 | note.getFolderList(pathname, dirname, (folder) => { 41 | return resJson(req, res, { folder }) 42 | }) 43 | }) 44 | }) 45 | 46 | register('/noteList', function (req, res) { 47 | const uri = parser.parse(req.url, true) 48 | const pathname = uri.query.pathname 49 | const dirname = path.join(C.noteDir, pathname) 50 | console.log('dirname', dirname) 51 | fs.access(dirname, (err) => { 52 | const exists = !err 53 | if (!exists) { 54 | return notFound(req, res) 55 | } 56 | note.getNoteList(pathname, dirname, (noteList) => { 57 | return resJson(req, res, { noteList }) 58 | }) 59 | }) 60 | }) 61 | 62 | register('/note', function (req, res) { 63 | const uri = parser.parse(req.url, true) 64 | const pathname = uri.query.pathname 65 | const filename = path.join(C.noteDir, pathname) 66 | fs.access(filename, (err) => { 67 | const exists = !err 68 | if (!exists) { 69 | return notFound(req, res) 70 | } 71 | note.getNote(filename, (content) => { 72 | return resJson(req, res, { content }) 73 | }) 74 | }) 75 | }) 76 | 77 | register('/search', function (req, res) { 78 | const uri = parser.parse(req.url, true) 79 | const text = uri.query.text 80 | search(text, (results) => { 81 | return resJson(req, res, { results }) 82 | }) 83 | }) 84 | 85 | -------------------------------------------------------------------------------- /server/cmd.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const exec = require('child_process').exec 5 | const C = require('./constant') 6 | 7 | const myExec = function (cmd) { 8 | const promise = new Promise(function (resolve, reject) { 9 | exec(cmd, { cwd: C.noteDir }, (error, stdout, stderr) => { 10 | console.log('='.repeat(80)) 11 | console.log(`cmd: ${cmd}`) 12 | console.log(`stdout: ${stdout}`) 13 | console.log(`stderr: ${stderr}`) 14 | if (error) { 15 | console.error(`exec error: ${error}`) 16 | reject() 17 | return 18 | } 19 | resolve() 20 | }) 21 | }) 22 | return promise 23 | } 24 | 25 | const _M = {} 26 | 27 | _M.init = () => { 28 | const exists = fs.existsSync(C.noteDir) 29 | if (!exists) { 30 | fs.mkdirSync(C.noteDir) 31 | } 32 | myExec('git init').then( 33 | () => myExec(`git remote add origin ${C.git}`) 34 | ).then( 35 | () => myExec('git pull origin master') 36 | ).then( 37 | () => console.log('init git success!'), 38 | () => console.log('init git failed!') 39 | ) 40 | } 41 | 42 | _M.backup = () => { 43 | myExec('git add .').then( 44 | () => myExec('git commit -m "auto backup"') 45 | ).then( 46 | () => myExec('git push -u origin master') 47 | ).then( 48 | () => console.log('auto backup success!'), 49 | () => console.log('auto backup failed!') 50 | ) 51 | } 52 | 53 | const method = process.argv[2] 54 | if ((typeof method) === 'string') { 55 | if (method in _M) { 56 | _M[method]() 57 | } 58 | } 59 | 60 | module.exports = _M 61 | 62 | -------------------------------------------------------------------------------- /server/constant.js: -------------------------------------------------------------------------------- 1 | const C = { 2 | port: 3000, 3 | noteDir: 'note', 4 | dist: 'dist', 5 | git: 'git@github.com:hanxi/note-md-testbackup.git' 6 | } 7 | 8 | module.exports = C 9 | -------------------------------------------------------------------------------- /server/handler.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Handler = function (method) { 4 | this.process = function (req, res) { 5 | return method.apply(this, [req, res]) 6 | } 7 | } 8 | 9 | exports.createHandler = function (method) { 10 | return new Handler(method) 11 | } 12 | 13 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const http = require('http') 4 | const fs = require('fs') 5 | 6 | const C = require('./constant') 7 | const router = require('./router') 8 | require('./api') 9 | 10 | router.addStaticPath(C.noteDir) 11 | router.addStaticPath(C.dist) 12 | 13 | const index = fs.readFileSync(`${C.dist}/index.html`) 14 | 15 | router.notFound((req, res) => { 16 | console.log('Not Found ' + req.url) 17 | // 返回 index.html 18 | res.writeHead(200, { 'Content-Type': 'text/html' }) 19 | res.end(index) 20 | }) 21 | 22 | const server = http.createServer((req, res) => { 23 | router.route(req, (handler) => { 24 | try { 25 | handler.process(req, res) 26 | } catch (e) { 27 | console.error(e) 28 | } 29 | }) 30 | }) 31 | 32 | server.listen(C.port, (err) => { 33 | if (err) { 34 | return console.error('something bad happened', err) 35 | } 36 | console.log(`server is listening on ${C.port}`) 37 | }) 38 | 39 | const cmd = require('./cmd') 40 | const autoBackupInterval = 30 * 60 * 1000 // 半小时备份一次 41 | cmd.backup() // 启动时备份一次 42 | setInterval(cmd.backup, autoBackupInterval) 43 | 44 | -------------------------------------------------------------------------------- /server/note.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const path = require('path') 5 | const moment = require('moment') 6 | const removeMd = require('remove-markdown') 7 | const showdown = require('showdown') 8 | showdown.setFlavor('github') 9 | const converter = new showdown.Converter() 10 | converter.setOption('headerLevelStart', 2) 11 | 12 | const asyncGetFolder = (folder, pathname, dirname, p) => { 13 | const pname = path.join(dirname, p) 14 | return new Promise(function (resolve, reject) { 15 | fs.stat(pname, (err, stat) => { 16 | if (!err) { 17 | if (stat.isDirectory()) { 18 | folder.push({ 19 | name: p, 20 | path: pathname + p + '/', 21 | children: [] 22 | }) 23 | } else if (stat.isFile()) { 24 | if (p.substr(-3) === '.md') { 25 | folder.push({ 26 | name: p, 27 | path: pathname + p 28 | }) 29 | } 30 | } 31 | } 32 | resolve() 33 | }) 34 | }) 35 | } 36 | 37 | exports.getFolderList = (pathname, dirname, callback) => { 38 | fs.readdir(dirname, (err, files) => { 39 | if (err) { 40 | console.error(err) 41 | return callback([]) 42 | } 43 | const folder = [] 44 | const promiseArr = [] 45 | for (const p of files) { 46 | if (p.substr(0, 1) !== '.') { 47 | promiseArr.push(asyncGetFolder(folder, pathname, dirname, p)) 48 | } 49 | } 50 | Promise.all(promiseArr).then((results) => { 51 | callback(folder) 52 | }) 53 | }) 54 | } 55 | 56 | const asyncGetNote = (noteList, pathname, dirname, p) => { 57 | const pname = path.join(dirname, p) 58 | return new Promise(function (resolve, reject) { 59 | fs.stat(pname, (err, stat) => { 60 | if (!err && stat.isFile()) { 61 | fs.readFile(pname, 'utf8', (err, text) => { 62 | if (!err) { 63 | const preview = removeMd(text) 64 | // read file head content 65 | noteList.push({ 66 | title: p, 67 | path: pathname + p, 68 | updateTime: moment(stat.mtime).format('YYYY-MM-DD HH:mm:ss'), 69 | preview: preview 70 | }) 71 | } 72 | resolve() 73 | }) 74 | } else { 75 | resolve() 76 | } 77 | }) 78 | }) 79 | } 80 | 81 | exports.getNoteList = (pathname, dirname, callback) => { 82 | fs.readdir(dirname, (err, files) => { 83 | if (err) { 84 | console.error(err) 85 | return callback([]) 86 | } 87 | const noteList = [] 88 | const promiseArr = [] 89 | for (const p of files) { 90 | if (p.substr(-3) === '.md') { 91 | promiseArr.push(asyncGetNote(noteList, pathname, dirname, p)) 92 | } 93 | } 94 | Promise.all(promiseArr).then((results) => { 95 | callback(noteList) 96 | }) 97 | }) 98 | } 99 | 100 | exports.getNote = (filename, callback) => { 101 | fs.readFile(filename, 'utf8', (err, text) => { 102 | if (err) { 103 | text = err.message 104 | } 105 | const html = converter.makeHtml(text) 106 | callback(html) 107 | }) 108 | } 109 | 110 | -------------------------------------------------------------------------------- /server/router.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const handlerFactory = require('./handler') 4 | const fs = require('fs') 5 | const parser = require('url') 6 | const path = require('path') 7 | const mime = require('mime-types') 8 | let handlers = {} 9 | 10 | const notFounds = { 11 | default () { 12 | return handlerFactory.createHandler((req, res) => { 13 | res.writeHead(404, { 'Content-Type': 'text/plain' }) 14 | res.end('No route registered for ' + req.url) 15 | }) 16 | } 17 | } 18 | 19 | const missing = (req, callback) => { 20 | const uri = parser.parse(req.url, true) 21 | if (uri.pathname.substr(-3) === '.md') { 22 | return callback(notFounds.default()) 23 | } 24 | const mimeType = mime.lookup(uri.pathname) 25 | if (mimeType) { 26 | existsStaticFile(uri.pathname, (filename) => { 27 | if (!filename) { 28 | return callback(notFounds.default()) 29 | } 30 | const handler = handlerFactory.createHandler((req, res) => { 31 | res.writeHead(200, mimeType) 32 | const fileStream = fs.createReadStream(filename) 33 | fileStream.pipe(res) 34 | }) 35 | callback(handler) 36 | }) 37 | } else { 38 | callback(notFounds.default()) 39 | } 40 | } 41 | 42 | exports.notFound = (callback) => { 43 | notFounds.default = () => handlerFactory.createHandler(callback) 44 | } 45 | 46 | exports.clear = () => { 47 | handlers = {} 48 | } 49 | 50 | exports.register = (url, method) => { 51 | handlers[url] = handlerFactory.createHandler(method) 52 | } 53 | 54 | exports.route = (req, callback) => { 55 | const uri = parser.parse(req.url, true) 56 | const handler = handlers[uri.pathname] 57 | if (!handler) { 58 | missing(req, callback) 59 | } else { 60 | callback(handler) 61 | } 62 | } 63 | 64 | const staticFilePaths = new Set([process.cwd()]) 65 | const asyncExistsFile = (filename) => { 66 | return new Promise(function (resolve, reject) { 67 | fs.access(filename, (err) => { 68 | const exists = !err 69 | resolve([filename, exists]) 70 | }) 71 | }) 72 | } 73 | const existsStaticFile = (pathname, callback) => { 74 | const promiseArr = [] 75 | for (const parrentPath of staticFilePaths) { 76 | const filename = path.join(parrentPath, pathname) 77 | promiseArr.push(asyncExistsFile(filename)) 78 | } 79 | 80 | Promise.all(promiseArr).then((results) => { 81 | for (const result of results) { 82 | if (result[1]) { 83 | return callback(result[0]) 84 | } 85 | } 86 | callback(null) 87 | }) 88 | } 89 | 90 | exports.addStaticPath = (p) => { 91 | staticFilePaths.add(p) 92 | } 93 | 94 | -------------------------------------------------------------------------------- /server/search.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const findInFiles = require('find-in-files') 4 | const C = require('./constant') 5 | 6 | module.exports = (text, callback) => { 7 | findInFiles.findSync({ 'term': text, 'flags': 'ig' }, C.noteDir, '.md$') 8 | .then((results) => { 9 | let ret = {} 10 | for (const key of Object.keys(results)) { 11 | ret[key.replace(C.noteDir,"")] = results[key] 12 | } 13 | callback(ret) 14 | }) 15 | } 16 | 17 | --------------------------------------------------------------------------------