├── .gitignore ├── README.md ├── build_tools ├── build.js ├── check-versions.js ├── dev-client.js ├── dev-server.js ├── utils.js ├── vue-loader.conf.js ├── webpack.base.conf.js ├── webpack.dev.conf.js ├── webpack.prod.conf.js └── webpack.test.conf.js ├── config ├── dev.env.js ├── index.js ├── prod.env.js └── test.env.js ├── deploy ├── index.html ├── package.json ├── src ├── assets │ ├── img │ │ ├── add-icon.png │ │ ├── back-icon.png │ │ ├── choice-icon.png │ │ ├── comment-icon.png │ │ ├── edit-title.png │ │ ├── email.png │ │ ├── fine.png │ │ ├── friendship1.png │ │ ├── friendship2.png │ │ ├── friendship3.png │ │ ├── friendship4.png │ │ ├── friendship5.png │ │ ├── friendship6.png │ │ ├── github.png │ │ ├── kotlin_logo.png │ │ ├── logo.png │ │ ├── logo_big.png │ │ ├── logo_k.png │ │ ├── pulldown-icon.png │ │ ├── register_success.png │ │ ├── toolbar_Italic.png │ │ ├── toolbar_a.png │ │ ├── toolbar_back.png │ │ ├── toolbar_blod.png │ │ ├── toolbar_cut_off_rule.png │ │ ├── toolbar_image.png │ │ ├── toolbar_next.png │ │ ├── toolbar_ordered_list.png │ │ ├── toolbar_paragraph_reference.png │ │ ├── toolbar_pre.png │ │ ├── toolbar_title.png │ │ ├── toolbar_unordered_list.png │ │ └── ttf.png │ ├── js │ │ ├── Cache.js │ │ ├── Config.js │ │ ├── Event.js │ │ ├── LoginMgr.js │ │ ├── Net.js │ │ ├── Parse.js │ │ └── Util.js │ └── logo.png ├── components │ ├── ArticleList.vue │ ├── ArticleMeta.vue │ ├── ArticleMetaDialog.vue │ ├── ArticleSideBar.vue │ ├── Avatar.vue │ ├── Comment.vue │ ├── Dialog.vue │ ├── DisplayPanels.vue │ ├── Footers.vue │ ├── Headers.vue │ ├── HomeLinkTitle.vue │ ├── Login.vue │ ├── NameCard.vue │ ├── Reply.vue │ ├── ReplyList.vue │ ├── SideBar.vue │ ├── TopicBar.vue │ └── UserBaseInfo.vue ├── componentsMobile │ ├── ArticleList.vue │ ├── Drawer.vue │ └── Login.vue ├── layout │ ├── AppMobile.vue │ ├── AppMobileSun.vue │ ├── AppWeb.vue │ └── AppWebFixContent.vue ├── main.js ├── router │ └── routes.js ├── views │ ├── 404.vue │ ├── Edit.vue │ ├── Home.vue │ ├── Post.vue │ ├── Topic.vue │ └── User.vue └── viewsMobile │ ├── Comments.vue │ ├── Edit.vue │ ├── Home.vue │ └── Post.vue ├── static ├── .gitkeep ├── css │ ├── atom-one-dark.min.css │ ├── font-awesome.min.css │ ├── github-markdown.css │ └── reset.scss ├── favicon.ico ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ └── fontawesome-webfont.woff2 ├── jquery-3.2.1.min.js ├── js │ ├── highlight.min.js │ └── rangeFn.js ├── layer.js ├── skin │ └── default │ │ ├── icon-ext.png │ │ ├── icon.png │ │ ├── layer.css │ │ ├── loading-0.gif │ │ ├── loading-1.gif │ │ └── loading-2.gif └── vConsole.js └── test └── unit ├── .eslintrc ├── index.js ├── karma.conf.js └── specs └── Hello.spec.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .gradle 3 | build 4 | *.iml 5 | *.ipr 6 | *.iws 7 | *.ids 8 | .idea 9 | local.properties 10 | out 11 | !build.release 12 | node_modules 13 | /log 14 | default.etcd 15 | /env 16 | /dist/ 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kotlin-cn frontend 2 | 3 | Hi, 4 | 5 | This is the website of kotlin-cn ,[VUEJS](http://vuejs.org/) Frameworks is used . 6 | 7 | 8 | ### Preview 9 | 10 | - Debug: [debug.kotlin-cn.org](https://debug.kotlin-cn.org/) 11 | 12 | - Release: [discuss.kotliner.cn](https://discuss.kotliner.cn/) , [kotlin-cn.org](https://kotlin-cn.org/) 13 | 14 | 15 | ### Getting Started 16 | 17 | - install [npm](https://www.npmjs.com/get-npm) 18 | 19 | - run in local 20 | 21 | ```shell 22 | npm install && npm run dev 23 | ``` 24 | 25 | - Check this site in your browser 26 | 27 | > http://localhost:3000/ 28 | 29 | 30 | ### Build release version 31 | 32 | Release in local 33 | 34 | ```shell 35 | npm run build 36 | ``` 37 | 38 | more about npm and VueJs 39 | 40 | checkout the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader). 41 | 42 | ``` bash 43 | # install dependencies 44 | npm install 45 | 46 | # serve with hot reload at localhost:8080 47 | npm run dev 48 | 49 | # build for production with minification 50 | npm run build 51 | 52 | # build for production and view the bundle analyzer report 53 | npm run build --report 54 | 55 | # run unit tests 56 | npm run unit 57 | 58 | # run all tests 59 | npm test 60 | ``` 61 | 62 | ### Any suggestions are Welcome 63 | 64 | Create an [issue](https://github.com/Kotlin-lang-CN/Kotlin-CN-Frontend/issues) to help us improve . 65 | 66 | Contact Us: 67 | 68 | [chpengzh](mailto:chpengzh@foxmail.com) 、 [Yeshen](mailto:me@yeshen.org) 69 | 70 | 71 | 72 | # 中文版 73 | 74 | 你好,这是kotlin 论坛的前端部分,使用了VueJs的框架和webpack的脚手架 75 | 76 | ### 预览 77 | 78 | - 内测版 : [debug.kotlin-cn.org](https://debug.kotlin-cn.org/) 79 | - 正式版 : [discuss.kotliner.cn](https://discuss.kotliner.cn/) , [kotlin-cn.org](https://kotlin-cn.org/) 80 | 81 | ### 从零开始 82 | 83 | - 安装[npm](https://www.npmjs.com/get-npm),国内可用[cnpm](https://npm.taobao.org/) 替代 84 | 85 | - 本地测试(Terminal中执行) 86 | 87 | ```shell 88 | npm install && npm run dev 89 | ``` 90 | 91 | - 用浏览器查看这个地址:`http://localhost:3000/` 92 | 93 | ### 输出正式版 94 | 95 | ```shell 96 | npm run build 97 | ``` 98 | 99 | 更多细节看VueJs的文档: [guide](http://vuejs-templates.github.io/webpack/) 和 [docs for vue-loader](http://vuejs.github.io/vue-loader). 100 | 101 | ### 协助我们?遇到问题? 102 | 103 | QQ群:561490348 -------------------------------------------------------------------------------- /build_tools/build.js: -------------------------------------------------------------------------------- 1 | require('./check-versions')() 2 | 3 | process.env.NODE_ENV = 'production' 4 | 5 | var ora = require('ora') 6 | var rm = require('rimraf') 7 | var path = require('path') 8 | var chalk = require('chalk') 9 | var webpack = require('webpack') 10 | var config = require('../config') 11 | var webpackConfig = require('./webpack.prod.conf') 12 | 13 | var spinner = ora('building for production...') 14 | spinner.start() 15 | 16 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 17 | if (err) throw err 18 | webpack(webpackConfig, function (err, stats) { 19 | spinner.stop() 20 | if (err) throw err 21 | process.stdout.write(stats.toString({ 22 | colors: true, 23 | modules: false, 24 | children: false, 25 | chunks: false, 26 | chunkModules: false 27 | }) + '\n\n') 28 | 29 | console.log(chalk.cyan(' Build complete.\n')) 30 | console.log(chalk.yellow( 31 | ' Tip: built files are meant to be served over an HTTP server.\n' + 32 | ' Opening index.html over file:// won\'t work.\n' 33 | )) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /build_tools/check-versions.js: -------------------------------------------------------------------------------- 1 | var chalk = require('chalk') 2 | var semver = require('semver') 3 | var packageConfig = require('../package.json') 4 | var shell = require('shelljs') 5 | function exec (cmd) { 6 | return require('child_process').execSync(cmd).toString().trim() 7 | } 8 | 9 | var versionRequirements = [ 10 | { 11 | name: 'node', 12 | currentVersion: semver.clean(process.version), 13 | versionRequirement: packageConfig.engines.node 14 | }, 15 | ] 16 | 17 | if (shell.which('npm')) { 18 | versionRequirements.push({ 19 | name: 'npm', 20 | currentVersion: exec('npm --version'), 21 | versionRequirement: packageConfig.engines.npm 22 | }) 23 | } 24 | 25 | module.exports = function () { 26 | var warnings = [] 27 | for (var i = 0; i < versionRequirements.length; i++) { 28 | var mod = versionRequirements[i] 29 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 30 | warnings.push(mod.name + ': ' + 31 | chalk.red(mod.currentVersion) + ' should be ' + 32 | chalk.green(mod.versionRequirement) 33 | ) 34 | } 35 | } 36 | 37 | if (warnings.length) { 38 | console.log('') 39 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 40 | console.log() 41 | for (var i = 0; i < warnings.length; i++) { 42 | var warning = warnings[i] 43 | console.log(' ' + warning) 44 | } 45 | console.log() 46 | process.exit(1) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /build_tools/dev-client.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | require('eventsource-polyfill') 3 | var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') 4 | 5 | hotClient.subscribe(function (event) { 6 | if (event.action === 'reload') { 7 | window.location.reload() 8 | } 9 | }) 10 | -------------------------------------------------------------------------------- /build_tools/dev-server.js: -------------------------------------------------------------------------------- 1 | require('./check-versions')() 2 | 3 | var config = require('../config') 4 | if (!process.env.NODE_ENV) { 5 | process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV) 6 | } 7 | 8 | var opn = require('opn') 9 | var path = require('path') 10 | var express = require('express') 11 | var webpack = require('webpack') 12 | var proxyMiddleware = require('http-proxy-middleware') 13 | var webpackConfig = process.env.NODE_ENV === 'testing' 14 | ? require('./webpack.prod.conf') 15 | : require('./webpack.dev.conf') 16 | 17 | // default port where dev server listens for incoming traffic 18 | var port = process.env.PORT || config.dev.port 19 | // automatically open browser, if not set will be false 20 | var autoOpenBrowser = !!config.dev.autoOpenBrowser 21 | // Define HTTP proxies to your custom API backend 22 | // https://github.com/chimurai/http-proxy-middleware 23 | var proxyTable = config.dev.proxyTable 24 | 25 | var app = express() 26 | var compiler = webpack(webpackConfig) 27 | 28 | var devMiddleware = require('webpack-dev-middleware')(compiler, { 29 | publicPath: webpackConfig.output.publicPath, 30 | quiet: true 31 | }) 32 | 33 | var hotMiddleware = require('webpack-hot-middleware')(compiler, { 34 | log: () => {} 35 | }) 36 | // force page reload when html-webpack-plugin template changes 37 | compiler.plugin('compilation', function (compilation) { 38 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { 39 | hotMiddleware.publish({ action: 'reload' }) 40 | cb() 41 | }) 42 | }) 43 | 44 | // proxy api requests 45 | Object.keys(proxyTable).forEach(function (context) { 46 | var options = proxyTable[context] 47 | if (typeof options === 'string') { 48 | options = { target: options } 49 | } 50 | app.use(proxyMiddleware(options.filter || context, options)) 51 | }) 52 | 53 | // handle fallback for HTML5 history API 54 | app.use(require('connect-history-api-fallback')()) 55 | 56 | // serve webpack bundle output 57 | app.use(devMiddleware) 58 | 59 | // enable hot-reload and state-preserving 60 | // compilation error display 61 | app.use(hotMiddleware) 62 | 63 | // serve pure static assets 64 | var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) 65 | app.use(staticPath, express.static('./static')) 66 | 67 | var uri = 'http://localhost:' + port 68 | 69 | var _resolve 70 | var readyPromise = new Promise(resolve => { 71 | _resolve = resolve 72 | }) 73 | 74 | console.log('> Starting dev server...') 75 | devMiddleware.waitUntilValid(() => { 76 | console.log('> Listening at ' + uri + '\n') 77 | // when env is testing, don't need open it 78 | if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') { 79 | opn(uri) 80 | } 81 | _resolve() 82 | }) 83 | 84 | var server = app.listen(port) 85 | 86 | module.exports = { 87 | ready: readyPromise, 88 | close: () => { 89 | server.close() 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /build_tools/utils.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var config = require('../config') 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 4 | 5 | exports.assetsPath = function (_path) { 6 | var assetsSubDirectory = process.env.NODE_ENV === 'production' 7 | ? config.build.assetsSubDirectory 8 | : config.dev.assetsSubDirectory 9 | return path.posix.join(assetsSubDirectory, _path) 10 | } 11 | 12 | exports.cssLoaders = function (options) { 13 | options = options || {} 14 | 15 | var cssLoader = { 16 | loader: 'css-loader', 17 | options: { 18 | minimize: process.env.NODE_ENV === 'production', 19 | sourceMap: options.sourceMap 20 | } 21 | } 22 | 23 | // generate loader string to be used with extract text plugin 24 | function generateLoaders (loader, loaderOptions) { 25 | var loaders = [cssLoader] 26 | if (loader) { 27 | loaders.push({ 28 | loader: loader + '-loader', 29 | options: Object.assign({}, loaderOptions, { 30 | sourceMap: options.sourceMap 31 | }) 32 | }) 33 | } 34 | 35 | // Extract CSS when that option is specified 36 | // (which is the case during production build) 37 | if (options.extract) { 38 | return ExtractTextPlugin.extract({ 39 | use: loaders, 40 | fallback: 'vue-style-loader' 41 | }) 42 | } else { 43 | return ['vue-style-loader'].concat(loaders) 44 | } 45 | } 46 | 47 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 48 | return { 49 | css: generateLoaders(), 50 | postcss: generateLoaders(), 51 | less: generateLoaders('less'), 52 | sass: generateLoaders('sass', { indentedSyntax: true }), 53 | scss: generateLoaders('sass'), 54 | stylus: generateLoaders('stylus'), 55 | styl: generateLoaders('stylus') 56 | } 57 | } 58 | 59 | // Generate loaders for standalone style files (outside of .vue) 60 | exports.styleLoaders = function (options) { 61 | var output = [] 62 | var loaders = exports.cssLoaders(options) 63 | for (var extension in loaders) { 64 | var loader = loaders[extension] 65 | output.push({ 66 | test: new RegExp('\\.' + extension + '$'), 67 | use: loader 68 | }) 69 | } 70 | return output 71 | } 72 | -------------------------------------------------------------------------------- /build_tools/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils') 2 | var config = require('../config') 3 | var isProduction = process.env.NODE_ENV === 'production' 4 | 5 | module.exports = { 6 | loaders: utils.cssLoaders({ 7 | sourceMap: isProduction 8 | ? config.build.productionSourceMap 9 | : config.dev.cssSourceMap, 10 | extract: isProduction 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /build_tools/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var utils = require('./utils') 3 | var config = require('../config') 4 | var vueLoaderConfig = require('./vue-loader.conf') 5 | 6 | function resolve(dir) { 7 | return path.join(__dirname, '..', dir) 8 | } 9 | 10 | module.exports = { 11 | entry: { 12 | app: './src/main.js' 13 | }, 14 | output: { 15 | path: config.build.assetsRoot, 16 | filename: '[name].js', 17 | publicPath: process.env.NODE_ENV === 'production' 18 | ? config.build.assetsPublicPath 19 | : config.dev.assetsPublicPath 20 | }, 21 | resolve: { 22 | extensions: ['.js', '.vue', '.json'], 23 | alias: { 24 | '@': resolve('src') 25 | } 26 | }, 27 | module: { 28 | rules: [ 29 | { 30 | test: /\.vue$/, 31 | loader: 'vue-loader', 32 | options: vueLoaderConfig 33 | }, 34 | { 35 | test: /\.js$/, 36 | loader: 'babel-loader?presets=es2015', 37 | include: [resolve('src'), resolve('test')] 38 | }, 39 | { 40 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 41 | loader: 'url-loader', 42 | options: { 43 | limit: 10000, 44 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 45 | } 46 | }, 47 | { 48 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 49 | loader: 'url-loader', 50 | options: { 51 | limit: 10000, 52 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 53 | } 54 | } 55 | ] 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /build_tools/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils') 2 | var webpack = require('webpack') 3 | var config = require('../config') 4 | var merge = require('webpack-merge') 5 | var baseWebpackConfig = require('./webpack.base.conf') 6 | var HtmlWebpackPlugin = require('html-webpack-plugin') 7 | var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 8 | 9 | // add hot-reload related code to entry chunks 10 | Object.keys(baseWebpackConfig.entry).forEach(function (name) { 11 | baseWebpackConfig.entry[name] = ['./build_tools/dev-client'].concat(baseWebpackConfig.entry[name]) 12 | }) 13 | 14 | module.exports = merge(baseWebpackConfig, { 15 | module: { 16 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) 17 | }, 18 | // cheap-module-eval-source-map is faster for development 19 | devtool: '#cheap-module-eval-source-map', 20 | plugins: [ 21 | new webpack.DefinePlugin({ 22 | 'process.env': config.dev.env 23 | }), 24 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage 25 | new webpack.HotModuleReplacementPlugin(), 26 | new webpack.NoEmitOnErrorsPlugin(), 27 | // https://github.com/ampedandwired/html-webpack-plugin 28 | new HtmlWebpackPlugin({ 29 | filename: 'index.html', 30 | template: 'index.html', 31 | inject: true 32 | }), 33 | new FriendlyErrorsPlugin() 34 | ] 35 | }) 36 | -------------------------------------------------------------------------------- /build_tools/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var utils = require('./utils') 3 | var webpack = require('webpack') 4 | var config = require('../config') 5 | var merge = require('webpack-merge') 6 | var baseWebpackConfig = require('./webpack.base.conf') 7 | var CopyWebpackPlugin = require('copy-webpack-plugin') 8 | var HtmlWebpackPlugin = require('html-webpack-plugin') 9 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 10 | var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') 11 | 12 | var env = process.env.NODE_ENV === 'testing' 13 | ? require('../config/test.env') 14 | : config.build.env 15 | 16 | var webpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | rules: utils.styleLoaders({ 19 | sourceMap: config.build.productionSourceMap, 20 | extract: true 21 | }) 22 | }, 23 | devtool: config.build.productionSourceMap ? '#source-map' : false, 24 | output: { 25 | path: config.build.assetsRoot, 26 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 27 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 28 | }, 29 | plugins: [ 30 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 31 | new webpack.DefinePlugin({ 32 | 'process.env': env 33 | }), 34 | new webpack.optimize.UglifyJsPlugin({ 35 | compress: { 36 | warnings: false 37 | }, 38 | sourceMap: true 39 | }), 40 | // extract css into its own file 41 | new ExtractTextPlugin({ 42 | filename: utils.assetsPath('css/[name].[contenthash].css') 43 | }), 44 | // Compress extracted CSS. We are using this plugin so that possible 45 | // duplicated CSS from different components can be deduped. 46 | new OptimizeCSSPlugin({ 47 | cssProcessorOptions: { 48 | safe: true 49 | } 50 | }), 51 | // generate dist index.html with correct asset hash for caching. 52 | // you can customize output by editing /index.html 53 | // see https://github.com/ampedandwired/html-webpack-plugin 54 | new HtmlWebpackPlugin({ 55 | filename: process.env.NODE_ENV === 'testing' 56 | ? 'index.html' 57 | : config.build.index, 58 | template: 'index.html', 59 | inject: true, 60 | minify: { 61 | removeComments: true, 62 | collapseWhitespace: true, 63 | removeAttributeQuotes: true 64 | // more options: 65 | // https://github.com/kangax/html-minifier#options-quick-reference 66 | }, 67 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 68 | chunksSortMode: 'dependency' 69 | }), 70 | // split vendor js into its own file 71 | new webpack.optimize.CommonsChunkPlugin({ 72 | name: 'vendor', 73 | minChunks: function (module, count) { 74 | // any required modules inside node_modules are extracted to vendor 75 | return ( 76 | module.resource && 77 | /\.js$/.test(module.resource) && 78 | module.resource.indexOf( 79 | path.join(__dirname, '../node_modules') 80 | ) === 0 81 | ) 82 | } 83 | }), 84 | // extract webpack runtime and module manifest to its own file in order to 85 | // prevent vendor hash from being updated whenever app bundle is updated 86 | new webpack.optimize.CommonsChunkPlugin({ 87 | name: 'manifest', 88 | chunks: ['vendor'] 89 | }), 90 | // copy custom static assets 91 | new CopyWebpackPlugin([ 92 | { 93 | from: path.resolve(__dirname, '../static'), 94 | to: config.build.assetsSubDirectory, 95 | ignore: ['.*'] 96 | } 97 | ]) 98 | ] 99 | }) 100 | 101 | if (config.build.productionGzip) { 102 | var CompressionWebpackPlugin = require('compression-webpack-plugin') 103 | 104 | webpackConfig.plugins.push( 105 | new CompressionWebpackPlugin({ 106 | asset: '[path].gz[query]', 107 | algorithm: 'gzip', 108 | test: new RegExp( 109 | '\\.(' + 110 | config.build.productionGzipExtensions.join('|') + 111 | ')$' 112 | ), 113 | threshold: 10240, 114 | minRatio: 0.8 115 | }) 116 | ) 117 | } 118 | 119 | if (config.build.bundleAnalyzerReport) { 120 | var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 121 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) 122 | } 123 | 124 | module.exports = webpackConfig 125 | -------------------------------------------------------------------------------- /build_tools/webpack.test.conf.js: -------------------------------------------------------------------------------- 1 | // This is the webpack config used for unit tests. 2 | 3 | var utils = require('./utils') 4 | var webpack = require('webpack') 5 | var merge = require('webpack-merge') 6 | var baseConfig = require('./webpack.base.conf') 7 | 8 | var webpackConfig = merge(baseConfig, { 9 | // use inline sourcemap for karma-sourcemap-loader 10 | module: { 11 | rules: utils.styleLoaders() 12 | }, 13 | devtool: '#inline-source-map', 14 | resolveLoader: { 15 | alias: { 16 | // necessary to to make lang="scss" work in test when using vue-loader's ?inject option 17 | // see discussion at https://github.com/vuejs/vue-loader/issues/724 18 | 'scss-loader': 'sass-loader' 19 | } 20 | }, 21 | plugins: [ 22 | new webpack.DefinePlugin({ 23 | 'process.env': require('../config/test.env') 24 | }) 25 | ] 26 | }) 27 | 28 | // no need for app entry during tests 29 | delete webpackConfig.entry 30 | 31 | module.exports = webpackConfig 32 | -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var prodEnv = require('./prod.env') 3 | 4 | module.exports = merge(prodEnv, { 5 | NODE_ENV: '"development"' 6 | }) 7 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | // see http://vuejs-templates.github.io/webpack for documentation. 2 | var path = require('path') 3 | 4 | module.exports = { 5 | build: { 6 | env: require('./prod.env'), 7 | index: path.resolve(__dirname, '../dist/index.html'), 8 | assetsRoot: path.resolve(__dirname, '../dist'), 9 | assetsSubDirectory: 'static', 10 | assetsPublicPath: '/', 11 | productionSourceMap: true, 12 | // Gzip off by default as many popular static hosts such as 13 | // Surge or Netlify already gzip all static assets for you. 14 | // Before setting to `true`, make sure to: 15 | // npm install --save-dev compression-webpack-plugin 16 | productionGzip: false, 17 | productionGzipExtensions: ['js', 'css'], 18 | // Run the build command with an extra argument to 19 | // View the bundle analyzer report after build finishes: 20 | // `npm run build --report` 21 | // Set to `true` or `false` to always turn it on or off 22 | bundleAnalyzerReport: process.env.npm_config_report 23 | }, 24 | dev: { 25 | env: require('./dev.env'), 26 | port: 3000, 27 | autoOpenBrowser: true, 28 | assetsSubDirectory: 'static', 29 | assetsPublicPath: '/', 30 | proxyTable: {}, 31 | // CSS Sourcemaps off by default because relative paths are "buggy" 32 | // with this option, according to the CSS-Loader README 33 | // (https://github.com/webpack/css-loader#sourcemaps) 34 | // In our experience, they generally work as expected, 35 | // just be aware of this issue when enabling this option. 36 | cssSourceMap: false 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"' 3 | } 4 | -------------------------------------------------------------------------------- /config/test.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var devEnv = require('./dev.env') 3 | 4 | module.exports = merge(devEnv, { 5 | NODE_ENV: '"testing"' 6 | }) 7 | -------------------------------------------------------------------------------- /deploy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | mode=$1 4 | 5 | if [ 'release' = ${mode} ]; then 6 | 7 | cp src/assets/js/Config.js /tmp/Config.js 8 | cp ../env/Config.js src/assets/js/Config.js 9 | cnpm install && npm run build 10 | cp /tmp/Config.js src/assets/js/Config.js 11 | 12 | rsync -azP dist/ root@106.75.105.42:frontend/ 13 | 14 | elif [ 'dev' = ${mode} ]; then 15 | 16 | cnpm install && npm run build 17 | rsync -azP dist/ root@debug.kotlin-cn.org:frontend/ 18 | 19 | else 20 | 21 | echo 'Error: deploy mode should be one of `release` or `dev`' 22 | echo 'try `./deploy dev` or `./deploy release` instead' 23 | exit -1 24 | 25 | fi 26 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Kotlin China 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "forum_ui", 3 | "version": "1.0.0", 4 | "description": "A Vue.js project", 5 | "author": "yisheng ", 6 | "private": true, 7 | "scripts": { 8 | "dev": "node build_tools/dev-server.js", 9 | "start": "node build_tools/dev-server.js", 10 | "build": "node build_tools/build.js", 11 | "unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run", 12 | "test": "npm run unit", 13 | "lint": "eslint --ext .js,.vue src test/unit/specs" 14 | }, 15 | "dependencies": { 16 | "js-cookie": "^2.1.4", 17 | "lodash": "latest", 18 | "marked": "^0.3.6", 19 | "md5": "^2.2.1", 20 | "moment": "^2.18.1", 21 | "page": "^1.7.1", 22 | "qiniu-js": "^1.0.19", 23 | "tagify": "^0.1.1", 24 | "vue": "^2.3.3", 25 | "vue-input-tag": "^0.0.11", 26 | "vue-loading-template": "^0.1.7", 27 | "vue-router": "^2.3.1", 28 | "vue-scroll": "^2.0.3" 29 | }, 30 | "devDependencies": { 31 | "autoprefixer": "^6.7.2", 32 | "babel-core": "^6.22.1", 33 | "babel-eslint": "^7.1.1", 34 | "babel-loader": "^6.2.10", 35 | "babel-plugin-istanbul": "^4.1.1", 36 | "babel-plugin-transform-runtime": "^6.22.0", 37 | "babel-preset-env": "^1.3.2", 38 | "babel-preset-es2015": "^6.0.0", 39 | "babel-preset-stage-2": "^6.22.0", 40 | "babel-register": "^6.22.0", 41 | "chai": "^3.5.0", 42 | "chalk": "^1.1.3", 43 | "connect-history-api-fallback": "^1.3.0", 44 | "copy-webpack-plugin": "^4.0.1", 45 | "cross-env": "^4.0.0", 46 | "css-loader": "^0.28.0", 47 | "eslint": "^3.19.0", 48 | "eslint-config-standard": "^6.2.1", 49 | "eslint-friendly-formatter": "^2.0.7", 50 | "eslint-loader": "^1.7.1", 51 | "eslint-plugin-html": "^2.0.0", 52 | "eslint-plugin-promise": "^3.4.0", 53 | "eslint-plugin-standard": "^2.0.1", 54 | "eventsource-polyfill": "^0.9.6", 55 | "express": "^4.14.1", 56 | "extract-text-webpack-plugin": "^2.0.0", 57 | "file-loader": "^0.11.1", 58 | "friendly-errors-webpack-plugin": "^1.1.3", 59 | "html-webpack-plugin": "^2.28.0", 60 | "http-proxy-middleware": "^0.17.3", 61 | "inject-loader": "^3.0.0", 62 | "karma": "^1.4.1", 63 | "karma-coverage": "^1.1.1", 64 | "karma-mocha": "^1.3.0", 65 | "karma-phantomjs-launcher": "^1.0.2", 66 | "karma-phantomjs-shim": "^1.4.0", 67 | "karma-sinon-chai": "^1.3.1", 68 | "karma-sourcemap-loader": "^0.3.7", 69 | "karma-spec-reporter": "0.0.30", 70 | "karma-webpack": "^2.0.2", 71 | "less": "^2.7.2", 72 | "less-loader": "^4.0.3", 73 | "lolex": "^1.5.2", 74 | "mocha": "^3.2.0", 75 | "node-sass": "^4.5.3", 76 | "opn": "^4.0.2", 77 | "optimize-css-assets-webpack-plugin": "^1.3.0", 78 | "ora": "^1.2.0", 79 | "phantomjs-prebuilt": "^2.1.14", 80 | "rimraf": "^2.6.0", 81 | "sass-loader": "^6.0.5", 82 | "semver": "^5.3.0", 83 | "shelljs": "^0.7.6", 84 | "sinon": "^2.1.0", 85 | "sinon-chai": "^2.8.0", 86 | "url-loader": "^0.5.8", 87 | "vue-loader": "^12.1.0", 88 | "vue-style-loader": "^3.0.1", 89 | "vue-template-compiler": "^2.3.3", 90 | "webpack": "^2.6.1", 91 | "webpack-bundle-analyzer": "^2.2.1", 92 | "webpack-dev-middleware": "^1.10.0", 93 | "webpack-hot-middleware": "^2.18.0", 94 | "webpack-merge": "^4.1.0" 95 | }, 96 | "engines": { 97 | "node": ">= 4.0.0", 98 | "npm": ">= 3.0.0" 99 | }, 100 | "browserslist": [ 101 | "> 1%", 102 | "last 2 versions", 103 | "not ie <= 8" 104 | ] 105 | } 106 | -------------------------------------------------------------------------------- /src/assets/img/add-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/src/assets/img/add-icon.png -------------------------------------------------------------------------------- /src/assets/img/back-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/src/assets/img/back-icon.png -------------------------------------------------------------------------------- /src/assets/img/choice-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/src/assets/img/choice-icon.png -------------------------------------------------------------------------------- /src/assets/img/comment-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/src/assets/img/comment-icon.png -------------------------------------------------------------------------------- /src/assets/img/edit-title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/src/assets/img/edit-title.png -------------------------------------------------------------------------------- /src/assets/img/email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/src/assets/img/email.png -------------------------------------------------------------------------------- /src/assets/img/fine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/src/assets/img/fine.png -------------------------------------------------------------------------------- /src/assets/img/friendship1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/src/assets/img/friendship1.png -------------------------------------------------------------------------------- /src/assets/img/friendship2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/src/assets/img/friendship2.png -------------------------------------------------------------------------------- /src/assets/img/friendship3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/src/assets/img/friendship3.png -------------------------------------------------------------------------------- /src/assets/img/friendship4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/src/assets/img/friendship4.png -------------------------------------------------------------------------------- /src/assets/img/friendship5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/src/assets/img/friendship5.png -------------------------------------------------------------------------------- /src/assets/img/friendship6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/src/assets/img/friendship6.png -------------------------------------------------------------------------------- /src/assets/img/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/src/assets/img/github.png -------------------------------------------------------------------------------- /src/assets/img/kotlin_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/src/assets/img/kotlin_logo.png -------------------------------------------------------------------------------- /src/assets/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/src/assets/img/logo.png -------------------------------------------------------------------------------- /src/assets/img/logo_big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/src/assets/img/logo_big.png -------------------------------------------------------------------------------- /src/assets/img/logo_k.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/src/assets/img/logo_k.png -------------------------------------------------------------------------------- /src/assets/img/pulldown-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/src/assets/img/pulldown-icon.png -------------------------------------------------------------------------------- /src/assets/img/register_success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/src/assets/img/register_success.png -------------------------------------------------------------------------------- /src/assets/img/toolbar_Italic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/src/assets/img/toolbar_Italic.png -------------------------------------------------------------------------------- /src/assets/img/toolbar_a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/src/assets/img/toolbar_a.png -------------------------------------------------------------------------------- /src/assets/img/toolbar_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/src/assets/img/toolbar_back.png -------------------------------------------------------------------------------- /src/assets/img/toolbar_blod.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/src/assets/img/toolbar_blod.png -------------------------------------------------------------------------------- /src/assets/img/toolbar_cut_off_rule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/src/assets/img/toolbar_cut_off_rule.png -------------------------------------------------------------------------------- /src/assets/img/toolbar_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/src/assets/img/toolbar_image.png -------------------------------------------------------------------------------- /src/assets/img/toolbar_next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/src/assets/img/toolbar_next.png -------------------------------------------------------------------------------- /src/assets/img/toolbar_ordered_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/src/assets/img/toolbar_ordered_list.png -------------------------------------------------------------------------------- /src/assets/img/toolbar_paragraph_reference.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/src/assets/img/toolbar_paragraph_reference.png -------------------------------------------------------------------------------- /src/assets/img/toolbar_pre.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/src/assets/img/toolbar_pre.png -------------------------------------------------------------------------------- /src/assets/img/toolbar_title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/src/assets/img/toolbar_title.png -------------------------------------------------------------------------------- /src/assets/img/toolbar_unordered_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/src/assets/img/toolbar_unordered_list.png -------------------------------------------------------------------------------- /src/assets/img/ttf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/src/assets/img/ttf.png -------------------------------------------------------------------------------- /src/assets/js/Cache.js: -------------------------------------------------------------------------------- 1 | class Cache { 2 | set(key, value) { 3 | localStorage.setItem(key, value); 4 | } 5 | 6 | get(key) { 7 | let value = localStorage.getItem(key); 8 | return value === null ? '' : value; 9 | } 10 | 11 | del(key) { 12 | localStorage.removeItem(key); 13 | } 14 | } 15 | export default new Cache(); 16 | -------------------------------------------------------------------------------- /src/assets/js/Config.js: -------------------------------------------------------------------------------- 1 | const CGI = { 2 | account: "https://debug.kotlin-cn.org/api/account", 3 | github: 'https://debug.kotlin-cn.org/api/github', 4 | admin: 'https://debug.kotlin-cn.org/api/admin', 5 | article: "https://debug.kotlin-cn.org/api/article", 6 | reply: 'https://debug.kotlin-cn.org/api/reply', 7 | rss: 'https://debug.kotlin-cn.org/api/rss', 8 | flower: 'https://debug.kotlin-cn.org/api/flower', 9 | misc: 'https://debug.kotlin-cn.org/api/misc', 10 | creator: 'https://debug.kotlin-cn.org/api/creator' 11 | }; 12 | 13 | const Config = { 14 | URL: { 15 | account: { 16 | register: CGI.account + '/register', 17 | login: CGI.account + '/login', 18 | user: CGI.account + '/user/{0}', 19 | password: CGI.account + '/user/{0}/password', 20 | update: CGI.account + '/user/{0}/update', 21 | profile: CGI.account + '/profile', 22 | profile_update: CGI.account + '/profile/update' 23 | }, 24 | github: { 25 | createState: CGI.github + '/state', 26 | auth: CGI.github + '/auth' 27 | }, 28 | admin: { 29 | articleList: CGI.admin + '/article/list',//管理员视角查看所有文章内容 30 | updateArticleState: CGI.admin + '/article/{0}/state',//更新文章状态 31 | updateUserState: CGI.admin + '/user/{0}/state',//跟新用户状态 32 | updateReplyState: CGI.admin + '/reply/{0}/state'//更新回复状态 33 | }, 34 | article: { 35 | post: CGI.article + '/post', 36 | detail: CGI.article + '/post/{0}', 37 | update: CGI.article + '/post/{0}/update', 38 | delete: CGI.article + '/post/{0}/delete', 39 | 40 | category: CGI.article + '/category',//获取文章类别列表 41 | getLatest: CGI.article + '/list',//获取最新文章列表 42 | getFine: CGI.article + '/fine', //获取精品文章列表 43 | getCategory: CGI.article + '/category/{0}'//获取特定类别最新文章列表 44 | }, 45 | reply: { 46 | count: CGI.reply + '/count', 47 | article: CGI.reply + '/article/{0}', 48 | delete: CGI.reply + '/id/{0}/delete', 49 | user: CGI.reply + '/user/{0}' 50 | }, 51 | flower: { 52 | article_star: CGI.flower + '/article/{0}/star', 53 | article_unstar: CGI.flower + '/article/{0}/unstar', 54 | article_count: CGI.flower + '/article/star/count', 55 | reply_star: CGI.flower + '/reply/{0}/star', 56 | reply_unstar: CGI.flower + '/reply/{0}/unstar', 57 | reply_count: CGI.flower + '/reply/star/count' 58 | }, 59 | rss: { 60 | fine: CGI.rss + "/fine",//精品文章订阅 61 | latest: CGI.rss + '/latest'//最新文章订阅 62 | }, 63 | misc: { 64 | dashboard: CGI.misc + '/dashboard',//网站公告栏 65 | homeLink: CGI.misc + '/home/link'//首页广告栏链接 66 | }, 67 | creator: { 68 | article_count: CGI.creator + '/article/count', 69 | reply_count: CGI.creator + '/reply/count', 70 | article: CGI.creator + '/article', 71 | reply: CGI.creator + '/reply' 72 | } 73 | }, 74 | OAuth: { 75 | github: { 76 | cgi: 'http://github.com/login/oauth/authorize', 77 | clientId: '4515da98f829c9feb99f', 78 | scope: 'user' 79 | } 80 | }, 81 | UI: { 82 | root: '/', 83 | account: '/account', 84 | edit: '/edit', 85 | post: '/post', 86 | comments: '/comments', 87 | topic: '/topic', 88 | user: '/user' 89 | }, 90 | Topic: { 91 | select: [ 92 | 'p1', 'p2', 'p3', 'p4' 93 | ], 94 | def: "p1", 95 | data: { 96 | p1: { 97 | title: '【视频】Kotlin 入门到进阶', 98 | link: '/topic/p1/', 99 | logo: 'fa fa-puzzle-piece', 100 | color: '#ea4335', 101 | toc: '6284762828886261760', 102 | readme: '6284762828886261760' 103 | }, 104 | p2: { 105 | title: 'Kotlin in Chinese', 106 | link: '/topic/p2/', 107 | logo: 'fa fa-send', 108 | color: '#4285f4', 109 | toc: '6287247888258359296', 110 | readme: '6284215952900517888' 111 | }, 112 | p3: { 113 | title: '社区博客', 114 | link: '//www.kotliner.cn', 115 | logo: 'fa fa-comments-o', 116 | color: '#fbbc05', 117 | toc: '6287247888258359296', 118 | readme: '6284762828886261760' 119 | }, 120 | p4: { 121 | title: '中文站', 122 | link: '//kotlincn.net', 123 | logo: 'fa fa-support', 124 | color: '#4285f4', 125 | toc: '6289370960008368128', 126 | readme: '6284215952900517888' 127 | } 128 | } 129 | } 130 | }; 131 | 132 | export default Config; 133 | -------------------------------------------------------------------------------- /src/assets/js/Event.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | const bus = new Vue(); 4 | 5 | const Event = { 6 | on: function (key, func) { 7 | bus.$on(key, func); 8 | }, 9 | emit: function (key, value) { 10 | bus.$emit(key, value); 11 | } 12 | }; 13 | export default Event; 14 | -------------------------------------------------------------------------------- /src/assets/js/LoginMgr.js: -------------------------------------------------------------------------------- 1 | import Cookie from 'js-cookie'; 2 | import Event from './Event.js'; 3 | 4 | const LoginMgr = { 5 | 6 | info: function () { 7 | let uid = Cookie.get("X-App-UID"); 8 | let username = Cookie.get("X-App-Name"); 9 | let email = Cookie.get("X-App-Email"); 10 | let token = Cookie.get("X-App-Token"); 11 | let role = Cookie.get('X-App-Role'); 12 | let logo = Cookie.get('X-App-Logo'); 13 | if (uid && uid.length > 0 14 | && username && username.length > 0 15 | && email && email.length > 0 16 | && token && token.length > 0) { 17 | this.uid = uid; 18 | this.username = username; 19 | this.email = email; 20 | this.token = token; 21 | this.role = role; 22 | this.isLogin = true; 23 | this.isAdminRole = this.role === '1'; 24 | this.logo = logo; 25 | return this 26 | } else { 27 | this.uid = undefined; 28 | this.username = undefined; 29 | this.email = undefined; 30 | this.token = undefined; 31 | this.role = undefined; 32 | this.logo = undefined; 33 | this.isLogin = false; 34 | this.isAdminRole = false; 35 | return this 36 | } 37 | }, 38 | 39 | check: function (loginMode, guestMode) { 40 | let userInfo = this.info(); 41 | if (userInfo.isLogin) { 42 | return loginMode ? loginMode(userInfo) : ''; 43 | } else { 44 | return guestMode ? guestMode() : ''; 45 | } 46 | }, 47 | 48 | isAdmin: function () { 49 | const info = this.info(); 50 | return info.isAdminRole 51 | }, 52 | 53 | require: function (loginAlready, mode) { 54 | let info = this.info(); 55 | if (info.isLogin) { 56 | loginAlready(info) 57 | } else { 58 | Event.emit(mode ? mode : 'request_login', loginAlready) 59 | } 60 | }, 61 | 62 | login: function (data) { 63 | console.log(data); 64 | Cookie.set('X-App-Name', data.username); 65 | Cookie.set('X-App-Email', data.email); 66 | Cookie.set('X-App-UID', data.uid); 67 | Cookie.set('X-App-Token', data.token); 68 | Cookie.set('X-App-Role', data.role); 69 | Cookie.set('X-App-Logo', data.logo); 70 | Cookie.remove('X-App-Github'); 71 | const info = this.info(); 72 | Event.emit('login', info); 73 | }, 74 | 75 | logout: function () { 76 | Cookie.remove('X-App-Email'); 77 | Cookie.remove('X-App-Token'); 78 | Cookie.remove('X-App-UID'); 79 | Cookie.remove('X-App-Name'); 80 | Cookie.remove('X-App-Role'); 81 | Cookie.remove('X-App-Logo'); 82 | Cookie.remove('X-App-Github'); 83 | this.info(); 84 | Event.emit('login', false); 85 | } 86 | 87 | }; 88 | 89 | export default LoginMgr 90 | -------------------------------------------------------------------------------- /src/assets/js/Net.js: -------------------------------------------------------------------------------- 1 | import bigJSON from './Parse.js'; 2 | import Cookie from 'js-cookie'; 3 | import Event from './Event.js'; 4 | 5 | function generateHeaders() { 6 | let deviceId = Cookie.get("X-App-Device"); 7 | if (deviceId === undefined) { 8 | deviceId = "WEB-" + Date.now().toString(36); 9 | deviceId += Math.random().toString(36).slice(2); 10 | deviceId += Math.random().toString(36).slice(3); 11 | deviceId = deviceId.toUpperCase(); 12 | Cookie.set("X-App-Device", deviceId); 13 | } 14 | let platform = 0; 15 | let contentType = 'application/x-www-form-urlencoded'; 16 | let system = navigator.platform; 17 | let info = navigator.userAgent.toLowerCase().match(/(msie|firefox|chrome|opera|version).*?([\d.]+)/); 18 | let vendor = (info && info.length >= 3) ? info[1].replace(/version/, "'safari") + info[2] : "null"; 19 | let token = Cookie.get("X-App-Token"); 20 | return { 21 | 'X-App-Device': deviceId, 22 | 'X-App-Platform': platform, 23 | 'X-App-Vendor': vendor, 24 | 'X-App-System': system, 25 | 'Content-Type': contentType, 26 | 'X-App-Token': token 27 | } 28 | } 29 | 30 | class Net { 31 | ajax(request, success, fail) { 32 | let errHandler = function (error) { 33 | const msg = (error.code < 0 ? '系统错误' : error.msg) + '(错误码 ' + error.code + ')'; 34 | Event.emit('error', error.msg ? msg : error); 35 | console.log(error); 36 | if (fail) fail(error) 37 | }; 38 | $.ajax({ 39 | url: request.url, 40 | cache: !1, 41 | type: request.type, 42 | dataType: "text", 43 | headers: generateHeaders(), 44 | data: request.condition, 45 | success: (data) => { 46 | try { 47 | let resp = bigJSON.parse(data); 48 | if (0 !== resp.code) { 49 | errHandler(resp); 50 | } else { 51 | if (success) success(resp); 52 | } 53 | } catch (err) { 54 | errHandler(err) 55 | } 56 | }, 57 | error: errHandler 58 | }) 59 | } 60 | 61 | get(request, success, fail) { 62 | request.type = 'GET'; 63 | this.ajax(request, success, fail) 64 | } 65 | 66 | post(request, success, fail) { 67 | request.type = 'POST'; 68 | this.ajax(request, success, fail) 69 | } 70 | 71 | } 72 | 73 | export default new Net(); 74 | 75 | -------------------------------------------------------------------------------- /src/assets/js/Parse.js: -------------------------------------------------------------------------------- 1 | //var BigNumber = null; 2 | /* 3 | json_parse.js 4 | 2012-06-20 5 | 6 | Public Domain. 7 | 8 | NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. 9 | 10 | This file creates a json_parse function. 11 | During create you can (optionally) specify some behavioural switches 12 | 13 | require('json-bigint')(options) 14 | 15 | The optional options parameter holds switches that drive certain 16 | aspects of the parsing process: 17 | * options.strict = true will warn about duplicate-key usage in the json. 18 | The default (strict = false) will silently ignore those and overwrite 19 | values for keys that are in duplicate use. 20 | 21 | The resulting function follows this signature: 22 | json_parse(text, reviver) 23 | This method parses a JSON text to produce an object or array. 24 | It can throw a SyntaxError exception. 25 | 26 | The optional reviver parameter is a function that can filter and 27 | transform the results. It receives each of the keys and values, 28 | and its return value is used instead of the original value. 29 | If it returns what it received, then the structure is not modified. 30 | If it returns undefined then the member is deleted. 31 | 32 | Example: 33 | 34 | // Parse the text. Values that look like ISO date strings will 35 | // be converted to Date objects. 36 | 37 | myData = json_parse(text, function (key, value) { 38 | var a; 39 | if (typeof value === 'string') { 40 | a = 41 | /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); 42 | if (a) { 43 | return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], 44 | +a[5], +a[6])); 45 | } 46 | } 47 | return value; 48 | }); 49 | 50 | This is a reference implementation. You are free to copy, modify, or 51 | redistribute. 52 | 53 | This code should be minified before deployment. 54 | See http://javascript.crockford.com/jsmin.html 55 | 56 | USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO 57 | NOT CONTROL. 58 | */ 59 | 60 | /*members "", "\"", "\/", "\\", at, b, call, charAt, f, fromCharCode, 61 | hasOwnProperty, message, n, name, prototype, push, r, t, text 62 | */ 63 | 64 | var json_parse = function (options) { 65 | "use strict"; 66 | 67 | // This is a function that can parse a JSON text, producing a JavaScript 68 | // data structure. It is a simple, recursive descent parser. It does not use 69 | // eval or regular expressions, so it can be used as a model for implementing 70 | // a JSON parser in other languages. 71 | 72 | // We are defining the function inside of another function to avoid creating 73 | // global variables. 74 | 75 | 76 | // Default options one can override by passing options to the parse() 77 | var _options = { 78 | "strict": false, // not being strict means do not generate syntax errors for "duplicate key" 79 | "storeAsString": false // toggles whether the values should be stored as BigNumber (default) or a string 80 | }; 81 | 82 | 83 | // If there are options, then use them to override the default _options 84 | if (options !== undefined && options !== null) { 85 | if (options.strict === true) { 86 | _options.strict = true; 87 | } 88 | if (options.storeAsString === true) { 89 | _options.storeAsString = true; 90 | } 91 | } 92 | 93 | 94 | var at, // The index of the current character 95 | ch, // The current character 96 | escapee = { 97 | '"': '"', 98 | '\\': '\\', 99 | '/': '/', 100 | b: '\b', 101 | f: '\f', 102 | n: '\n', 103 | r: '\r', 104 | t: '\t' 105 | }, 106 | text, 107 | 108 | error = function (m) { 109 | 110 | // Call error when something is wrong. 111 | 112 | throw { 113 | name: 'SyntaxError', 114 | message: m, 115 | at: at, 116 | text: text 117 | }; 118 | }, 119 | 120 | next = function (c) { 121 | 122 | // If a c parameter is provided, verify that it matches the current character. 123 | 124 | if (c && c !== ch) { 125 | error("Expected '" + c + "' instead of '" + ch + "'"); 126 | } 127 | 128 | // Get the next character. When there are no more characters, 129 | // return the empty string. 130 | 131 | ch = text.charAt(at); 132 | at += 1; 133 | return ch; 134 | }, 135 | 136 | number = function () { 137 | // Parse a number value. 138 | 139 | var number, 140 | string = ''; 141 | 142 | if (ch === '-') { 143 | string = '-'; 144 | next('-'); 145 | } 146 | while (ch >= '0' && ch <= '9') { 147 | string += ch; 148 | next(); 149 | } 150 | if (ch === '.') { 151 | string += '.'; 152 | while (next() && ch >= '0' && ch <= '9') { 153 | string += ch; 154 | } 155 | } 156 | if (ch === 'e' || ch === 'E') { 157 | string += ch; 158 | next(); 159 | if (ch === '-' || ch === '+') { 160 | string += ch; 161 | next(); 162 | } 163 | while (ch >= '0' && ch <= '9') { 164 | string += ch; 165 | next(); 166 | } 167 | } 168 | number = +string; 169 | if (!isFinite(number)) { 170 | error("Bad number"); 171 | } else { 172 | if (string.length > 15) 173 | return (_options.storeAsString === true) ? string : new BigNumber(string); 174 | return number; 175 | } 176 | }, 177 | 178 | string = function () { 179 | 180 | // Parse a string value. 181 | 182 | var hex, 183 | i, 184 | string = '', 185 | uffff; 186 | 187 | // When parsing for string values, we must look for " and \ characters. 188 | 189 | if (ch === '"') { 190 | while (next()) { 191 | if (ch === '"') { 192 | next(); 193 | return string; 194 | } 195 | if (ch === '\\') { 196 | next(); 197 | if (ch === 'u') { 198 | uffff = 0; 199 | for (i = 0; i < 4; i += 1) { 200 | hex = parseInt(next(), 16); 201 | if (!isFinite(hex)) { 202 | break; 203 | } 204 | uffff = uffff * 16 + hex; 205 | } 206 | string += String.fromCharCode(uffff); 207 | } else if (typeof escapee[ch] === 'string') { 208 | string += escapee[ch]; 209 | } else { 210 | break; 211 | } 212 | } else { 213 | string += ch; 214 | } 215 | } 216 | } 217 | error("Bad string"); 218 | }, 219 | 220 | white = function () { 221 | 222 | // Skip whitespace. 223 | 224 | while (ch && ch <= ' ') { 225 | next(); 226 | } 227 | }, 228 | 229 | word = function () { 230 | 231 | // true, false, or null. 232 | 233 | switch (ch) { 234 | case 't': 235 | next('t'); 236 | next('r'); 237 | next('u'); 238 | next('e'); 239 | return true; 240 | case 'f': 241 | next('f'); 242 | next('a'); 243 | next('l'); 244 | next('s'); 245 | next('e'); 246 | return false; 247 | case 'n': 248 | next('n'); 249 | next('u'); 250 | next('l'); 251 | next('l'); 252 | return null; 253 | } 254 | error("Unexpected '" + ch + "'"); 255 | }, 256 | 257 | value, // Place holder for the value function. 258 | 259 | array = function () { 260 | 261 | // Parse an array value. 262 | 263 | var array = []; 264 | 265 | if (ch === '[') { 266 | next('['); 267 | white(); 268 | if (ch === ']') { 269 | next(']'); 270 | return array; // empty array 271 | } 272 | while (ch) { 273 | array.push(value()); 274 | white(); 275 | if (ch === ']') { 276 | next(']'); 277 | return array; 278 | } 279 | next(','); 280 | white(); 281 | } 282 | } 283 | error("Bad array"); 284 | }, 285 | 286 | object = function () { 287 | 288 | // Parse an object value. 289 | 290 | var key, 291 | object = {}; 292 | 293 | if (ch === '{') { 294 | next('{'); 295 | white(); 296 | if (ch === '}') { 297 | next('}'); 298 | return object; // empty object 299 | } 300 | while (ch) { 301 | key = string(); 302 | white(); 303 | next(':'); 304 | if (_options.strict === true && Object.hasOwnProperty.call(object, key)) { 305 | error('Duplicate key "' + key + '"'); 306 | } 307 | object[key] = value(); 308 | white(); 309 | if (ch === '}') { 310 | next('}'); 311 | return object; 312 | } 313 | next(','); 314 | white(); 315 | } 316 | } 317 | error("Bad object"); 318 | }; 319 | 320 | value = function () { 321 | 322 | // Parse a JSON value. It could be an object, an array, a string, a number, 323 | // or a word. 324 | 325 | white(); 326 | switch (ch) { 327 | case '{': 328 | return object(); 329 | case '[': 330 | return array(); 331 | case '"': 332 | return string(); 333 | case '-': 334 | return number(); 335 | default: 336 | return ch >= '0' && ch <= '9' ? number() : word(); 337 | } 338 | }; 339 | 340 | // Return the json_parse function. It will have access to all of the above 341 | // functions and variables. 342 | 343 | return function (source, reviver) { 344 | var result; 345 | text = source + ''; 346 | at = 0; 347 | ch = ' '; 348 | result = value(); 349 | white(); 350 | if (ch) { 351 | error("Syntax error"); 352 | } 353 | 354 | // If there is a reviver function, we recursively walk the new structure, 355 | // passing each name/value pair to the reviver function for possible 356 | // transformation, starting with a temporary root object that holds the result 357 | // in an empty key. If there is not a reviver function, we simply return the 358 | // result. 359 | 360 | return typeof reviver === 'function' 361 | ? (function walk(holder, key) { 362 | var k, v, value = holder[key]; 363 | if (value && typeof value === 'object') { 364 | Object.keys(value).forEach(function (k) { 365 | v = walk(value, k); 366 | if (v !== undefined) { 367 | value[k] = v; 368 | } else { 369 | delete value[k]; 370 | } 371 | }); 372 | } 373 | return reviver.call(holder, key, value); 374 | }({'': result}, '')) 375 | : result; 376 | }; 377 | } 378 | let bigJSON = {}; 379 | bigJSON.parse = json_parse({storeAsString: true}); 380 | module.exports = bigJSON; 381 | -------------------------------------------------------------------------------- /src/assets/js/Util.js: -------------------------------------------------------------------------------- 1 | const I64BIT_TABLE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-'.split(''); 2 | 3 | function hash(input) { 4 | let hash = 5381; 5 | let i = input.length - 1; 6 | 7 | if (typeof input === 'string') { 8 | for (; i > -1; i--) 9 | hash += (hash << 5) + input.charCodeAt(i); 10 | } 11 | else { 12 | for (; i > -1; i--) 13 | hash += (hash << 5) + input[i]; 14 | } 15 | let value = hash & 0x7FFFFFFF; 16 | let retValue = ''; 17 | do { 18 | retValue += I64BIT_TABLE[value & 0x3F]; 19 | } 20 | while (value >>= 6); 21 | 22 | return retValue; 23 | } 24 | 25 | const Util = { 26 | 27 | isValidEmail(s) { 28 | let reg = /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(.[a-zA-Z0-9_-])+/; 29 | return reg.test(s); 30 | }, 31 | 32 | isValidName(s) { 33 | let reg = /^[a-zA-Z]{2,20}$/; 34 | return reg.test(s); 35 | }, 36 | 37 | isValidPass(s) { 38 | return s !== null && s.length > 5; 39 | }, 40 | 41 | isMobile() { 42 | let ua = navigator.userAgent; 43 | return ua.match(/(Android)[\s\/]+([\d\.]+)/) !== null 44 | || ua.match(/(iPad|iPhone|iPod)\s+OS\s([\d_\.]+)/) !== null 45 | || ua.match(/(Windows\s+Phone)\s([\d\.]+)/) !== null; 46 | }, 47 | 48 | //generate anchor from title text 49 | anchorHash(text) { 50 | return hash(text.replace('#', '_')) 51 | } 52 | }; 53 | if (!String.prototype.format) { 54 | String.prototype.format = function () { 55 | let args = arguments; 56 | return this.replace(/{(\d+)}/g, function (match, number) { 57 | return typeof args[number] !== 'undefined' 58 | ? args[number] 59 | : match 60 | ; 61 | }); 62 | }; 63 | } 64 | export default Util; 65 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/src/assets/logo.png -------------------------------------------------------------------------------- /src/components/ArticleList.vue: -------------------------------------------------------------------------------- 1 | 44 | 149 | 256 | -------------------------------------------------------------------------------- /src/components/ArticleMeta.vue: -------------------------------------------------------------------------------- 1 | 14 | 50 | 106 | -------------------------------------------------------------------------------- /src/components/ArticleMetaDialog.vue: -------------------------------------------------------------------------------- 1 | 16 | 74 | 88 | 160 | -------------------------------------------------------------------------------- /src/components/ArticleSideBar.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 70 | -------------------------------------------------------------------------------- /src/components/Avatar.vue: -------------------------------------------------------------------------------- 1 | 8 | 44 | 81 | -------------------------------------------------------------------------------- /src/components/Comment.vue: -------------------------------------------------------------------------------- 1 | 16 | 107 | 185 | 186 | -------------------------------------------------------------------------------- /src/components/Dialog.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 50 | 51 | 56 | 57 | 88 | -------------------------------------------------------------------------------- /src/components/DisplayPanels.vue: -------------------------------------------------------------------------------- 1 | 2 | 7 | 47 | 48 | 61 | -------------------------------------------------------------------------------- /src/components/Footers.vue: -------------------------------------------------------------------------------- 1 | 14 | 34 | 35 | 81 | -------------------------------------------------------------------------------- /src/components/HomeLinkTitle.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 60 | 61 | 66 | 67 | 118 | -------------------------------------------------------------------------------- /src/components/Login.vue: -------------------------------------------------------------------------------- 1 | 42 | 199 | 200 | 205 | 206 | 303 | -------------------------------------------------------------------------------- /src/components/NameCard.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 73 | 74 | 79 | 80 | 137 | -------------------------------------------------------------------------------- /src/components/Reply.vue: -------------------------------------------------------------------------------- 1 | 9 | 53 | 69 | -------------------------------------------------------------------------------- /src/components/ReplyList.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 135 | 136 | -------------------------------------------------------------------------------- /src/components/SideBar.vue: -------------------------------------------------------------------------------- 1 | 34 | 77 | 78 | 83 | 84 | 205 | -------------------------------------------------------------------------------- /src/components/TopicBar.vue: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 29 | 30 | -------------------------------------------------------------------------------- /src/components/UserBaseInfo.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | 157 | 158 | 186 | -------------------------------------------------------------------------------- /src/componentsMobile/ArticleList.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 75 | 76 | 147 | -------------------------------------------------------------------------------- /src/componentsMobile/Drawer.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 60 | -------------------------------------------------------------------------------- /src/componentsMobile/Login.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 152 | 153 | 300 | -------------------------------------------------------------------------------- /src/layout/AppMobile.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 62 | 63 | 211 | 212 | 213 | 257 | -------------------------------------------------------------------------------- /src/layout/AppMobileSun.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 28 | 29 | 103 | -------------------------------------------------------------------------------- /src/layout/AppWeb.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 61 | 62 | 63 | 77 | 78 | 127 | -------------------------------------------------------------------------------- /src/layout/AppWebFixContent.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 49 | 50 | 82 | 83 | 84 | 133 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import page from 'page' 2 | import routes from './router/routes' 3 | import Event from './assets/js/Event.js' 4 | import moment from 'moment'; 5 | import Vue from 'vue' 6 | 7 | Vue.config.productionTip = false; 8 | 9 | moment.locale('zh-cn'); 10 | Vue.filter('moment', function (value) { 11 | return moment(value).fromNow(); 12 | }); 13 | 14 | function isMobile() { 15 | let ua = navigator.userAgent; 16 | return ua.match(/(Android)[\s\/]+([\d\.]+)/) !== null 17 | || ua.match(/(iPad|iPhone|iPod)\s+OS\s([\d_\.]+)/) !== null 18 | || ua.match(/(Windows\s+Phone)\s([\d\.]+)/) !== null; 19 | } 20 | 21 | Array.prototype.indexOf = function (val) { 22 | for (let i = 0; i < this.length; i++) { 23 | if (this[i] === val) return i; 24 | } 25 | return -1; 26 | }; 27 | 28 | Array.prototype.remove = function (val) { 29 | const index = this.indexOf(val); 30 | if (index > -1) { 31 | this.splice(index, 1); 32 | } 33 | }; 34 | 35 | const app = new Vue({ 36 | el: '#app', 37 | data: {ViewComponent: {render: h => h('div', 'loading...')}}, 38 | render (h) { 39 | return h(this.ViewComponent) 40 | } 41 | }); 42 | 43 | Object.keys(routes).forEach(route => { 44 | let Component; 45 | if (isMobile() && !route.startsWith("/m")) { 46 | Component = require(routes["/m" + route] + '.vue'); 47 | } else { 48 | Component = require(routes[route] + '.vue'); 49 | } 50 | page(route, (ctx) => { 51 | app.$root.params = ctx.params; 52 | app.ViewComponent = Component; 53 | Event.emit('route-update', ''); 54 | } 55 | ) 56 | }); 57 | 58 | page('*', () => app.ViewComponent = require('./views/404.vue')); 59 | 60 | page(); 61 | -------------------------------------------------------------------------------- /src/router/routes.js: -------------------------------------------------------------------------------- 1 | export default { 2 | '/': './views/Home', 3 | '/edit': './views/Edit', 4 | '/post/:id': './views/Post', 5 | '/edit/:id': './views/Edit', 6 | '/topic': './views/Topic', 7 | '/topic/:key': './views/Topic', 8 | '/topic/:key/:id': './views/Topic', 9 | '/user/:uid':'./views/User', 10 | 11 | '/m/': './viewsMobile/Home', 12 | '/m/post/:id': './viewsMobile/Post', 13 | '/m/edit': './viewsMobile/Edit', 14 | '/m/edit/:id': './viewsMobile/Edit', 15 | '/m/comments/:id': './viewsMobile/Comments', 16 | '/comments/:id': './viewsMobile/Comments', 17 | '/m/topic': './views/404', 18 | '/m/topic/:key': './views/404', 19 | '/m/topic/:key/:id': './views/404', 20 | '/m/user/:uid':'./views/User' 21 | } 22 | -------------------------------------------------------------------------------- /src/views/404.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 10 | 17 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 78 | 79 | 134 | -------------------------------------------------------------------------------- /src/views/Post.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 125 | 126 | 195 | -------------------------------------------------------------------------------- /src/views/Topic.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 120 | 121 | 156 | -------------------------------------------------------------------------------- /src/views/User.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 59 | 60 | -------------------------------------------------------------------------------- /src/viewsMobile/Comments.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 92 | 93 | 124 | -------------------------------------------------------------------------------- /src/viewsMobile/Home.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 70 | 71 | 109 | -------------------------------------------------------------------------------- /src/viewsMobile/Post.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 93 | 94 | 95 | 176 | 177 | -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/static/.gitkeep -------------------------------------------------------------------------------- /static/css/atom-one-dark.min.css: -------------------------------------------------------------------------------- 1 | .hljs{display:block;overflow-x:auto;padding:0.5em;color:#abb2bf;background:#282c34}.hljs-comment,.hljs-quote{color:#5c6370;font-style:italic}.hljs-doctag,.hljs-keyword,.hljs-formula{color:#c678dd}.hljs-section,.hljs-name,.hljs-selector-tag,.hljs-deletion,.hljs-subst{color:#e06c75}.hljs-literal{color:#56b6c2}.hljs-string,.hljs-regexp,.hljs-addition,.hljs-attribute,.hljs-meta-string{color:#98c379}.hljs-built_in,.hljs-class .hljs-title{color:#e6c07b}.hljs-attr,.hljs-variable,.hljs-template-variable,.hljs-type,.hljs-selector-class,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-number{color:#d19a66}.hljs-symbol,.hljs-bullet,.hljs-link,.hljs-meta,.hljs-selector-id,.hljs-title{color:#61aeee}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:bold}.hljs-link{text-decoration:underline} -------------------------------------------------------------------------------- /static/css/reset.scss: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /** 4 | * 1. Set default font family to sans-serif. 5 | * 2. Prevent iOS and IE text size adjust after device orientation change, 6 | * without disabling user zoom. 7 | */ 8 | 9 | *{ 10 | margin: 0; 11 | padding: 0; 12 | border: 0; 13 | } 14 | 15 | html { 16 | font-family: sans-serif; /* 1 */ 17 | -ms-text-size-adjust: 100%; /* 2 */ 18 | -webkit-text-size-adjust: 100%; /* 2 */ 19 | height: 100%; 20 | } 21 | 22 | /** 23 | * Remove default margin. 24 | */ 25 | 26 | body { 27 | margin: 0; 28 | height: 100%; 29 | } 30 | 31 | /* HTML5 display definitions 32 | ========================================================================== */ 33 | 34 | /** 35 | * Correct `block` display not defined for any HTML5 element in IE 8/9. 36 | * Correct `block` display not defined for `details` or `summary` in IE 10/11 37 | * and Firefox. 38 | * Correct `block` display not defined for `main` in IE 11. 39 | */ 40 | 41 | article, 42 | aside, 43 | details, 44 | figcaption, 45 | figure, 46 | footer, 47 | header, 48 | main, 49 | menu, 50 | nav, 51 | section, 52 | summary { 53 | display: block; 54 | } 55 | 56 | /** 57 | * 1. Correct `inline-block` display not defined in IE 8/9. 58 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. 59 | */ 60 | 61 | audio, 62 | canvas, 63 | progress, 64 | video { 65 | display: inline-block; /* 1 */ 66 | vertical-align: baseline; /* 2 */ 67 | } 68 | 69 | /** 70 | * Prevent modern browsers from displaying `audio` without controls. 71 | * Remove excess height in iOS 5 devices. 72 | */ 73 | 74 | audio:not([controls]) { 75 | display: none; 76 | height: 0; 77 | } 78 | 79 | /** 80 | * Address `[hidden]` styling not present in IE 8/9/10. 81 | * Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22. 82 | */ 83 | 84 | [hidden], 85 | template { 86 | display: none; 87 | } 88 | 89 | /* Links 90 | ========================================================================== */ 91 | 92 | /** 93 | * Remove the gray background color from active links in IE 10. 94 | */ 95 | 96 | a { 97 | background-color: transparent; 98 | } 99 | 100 | /** 101 | * Improve readability of focused elements when they are also in an 102 | * active/hover state. 103 | */ 104 | 105 | a:active, 106 | a:hover { 107 | outline: 0; 108 | } 109 | 110 | /* Text-level semantics 111 | ========================================================================== */ 112 | 113 | /** 114 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome. 115 | */ 116 | 117 | abbr[title] { 118 | border-bottom: 1px dotted; 119 | } 120 | 121 | /** 122 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. 123 | */ 124 | 125 | b, 126 | strong { 127 | font-weight: bold; 128 | } 129 | 130 | /** 131 | * Address styling not present in Safari and Chrome. 132 | */ 133 | 134 | dfn { 135 | font-style: italic; 136 | } 137 | 138 | /** 139 | * Address variable `h1` font-size and margin within `section` and `article` 140 | * contexts in Firefox 4+, Safari, and Chrome. 141 | */ 142 | 143 | h1 { 144 | font-size: 2em; 145 | margin: 0.67em 0; 146 | } 147 | 148 | /** 149 | * Address styling not present in IE 8/9. 150 | */ 151 | 152 | mark { 153 | background: #ff0; 154 | color: #000; 155 | } 156 | 157 | /** 158 | * Address inconsistent and variable font size in all browsers. 159 | */ 160 | 161 | small { 162 | font-size: 80%; 163 | } 164 | 165 | /** 166 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 167 | */ 168 | 169 | sub, 170 | sup { 171 | font-size: 75%; 172 | line-height: 0; 173 | position: relative; 174 | vertical-align: baseline; 175 | } 176 | 177 | sup { 178 | top: -0.5em; 179 | } 180 | 181 | sub { 182 | bottom: -0.25em; 183 | } 184 | 185 | /* Embedded content 186 | ========================================================================== */ 187 | 188 | /** 189 | * Remove border when inside `a` element in IE 8/9/10. 190 | */ 191 | 192 | img { 193 | border: 0; 194 | } 195 | 196 | /** 197 | * Correct overflow not hidden in IE 9/10/11. 198 | */ 199 | 200 | svg:not(:root) { 201 | overflow: hidden; 202 | } 203 | 204 | /* Grouping content 205 | ========================================================================== */ 206 | 207 | /** 208 | * Address margin not present in IE 8/9 and Safari. 209 | */ 210 | 211 | figure { 212 | margin: 1em 40px; 213 | } 214 | 215 | /** 216 | * Address differences between Firefox and other browsers. 217 | */ 218 | 219 | hr { 220 | box-sizing: content-box; 221 | height: 0; 222 | } 223 | 224 | /** 225 | * Contain overflow in all browsers. 226 | */ 227 | 228 | pre { 229 | overflow: auto; 230 | } 231 | 232 | /** 233 | * Address odd `em`-unit font size rendering in all browsers. 234 | */ 235 | 236 | code, 237 | kbd, 238 | pre, 239 | samp { 240 | font-family: monospace, monospace; 241 | font-size: 1em; 242 | } 243 | 244 | /* Forms 245 | ========================================================================== */ 246 | 247 | /** 248 | * Known limitation: by default, Chrome and Safari on OS X allow very limited 249 | * styling of `select`, unless a `border` property is set. 250 | */ 251 | 252 | /** 253 | * 1. Correct color not being inherited. 254 | * Known issue: affects color of disabled elements. 255 | * 2. Correct font properties not being inherited. 256 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. 257 | */ 258 | 259 | button, 260 | input, 261 | optgroup, 262 | select, 263 | textarea { 264 | color: inherit; /* 1 */ 265 | font: inherit; /* 2 */ 266 | margin: 0; /* 3 */ 267 | } 268 | 269 | /** 270 | * Address `overflow` set to `hidden` in IE 8/9/10/11. 271 | */ 272 | 273 | button { 274 | overflow: visible; 275 | } 276 | 277 | /** 278 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 279 | * All other form control elements do not inherit `text-transform` values. 280 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. 281 | * Correct `select` style inheritance in Firefox. 282 | */ 283 | 284 | button, 285 | select { 286 | text-transform: none; 287 | } 288 | 289 | /** 290 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 291 | * and `video` controls. 292 | * 2. Correct inability to style clickable `input` types in iOS. 293 | * 3. Improve usability and consistency of cursor style between image-type 294 | * `input` and others. 295 | */ 296 | 297 | button, 298 | html input[type="button"], /* 1 */ 299 | input[type="reset"], 300 | input[type="submit"] { 301 | -webkit-appearance: button; /* 2 */ 302 | cursor: pointer; /* 3 */ 303 | } 304 | 305 | /** 306 | * Re-set default cursor for disabled elements. 307 | */ 308 | 309 | button[disabled], 310 | html input[disabled] { 311 | cursor: default; 312 | } 313 | 314 | /** 315 | * Remove inner padding and border in Firefox 4+. 316 | */ 317 | 318 | button::-moz-focus-inner, 319 | input::-moz-focus-inner { 320 | border: 0; 321 | padding: 0; 322 | } 323 | 324 | /** 325 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 326 | * the UA stylesheet. 327 | */ 328 | 329 | input { 330 | line-height: normal; 331 | } 332 | 333 | /** 334 | * It's recommended that you don't attempt to style these elements. 335 | * Firefox's implementation doesn't respect box-sizing, padding, or width. 336 | * 337 | * 1. Address box sizing set to `content-box` in IE 8/9/10. 338 | * 2. Remove excess padding in IE 8/9/10. 339 | */ 340 | 341 | input[type="checkbox"], 342 | input[type="radio"] { 343 | box-sizing: border-box; /* 1 */ 344 | padding: 0; /* 2 */ 345 | } 346 | 347 | /** 348 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain 349 | * `font-size` values of the `input`, it causes the cursor style of the 350 | * decrement button to change from `default` to `text`. 351 | */ 352 | 353 | input[type="number"]::-webkit-inner-spin-button, 354 | input[type="number"]::-webkit-outer-spin-button { 355 | height: auto; 356 | } 357 | 358 | /** 359 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome. 360 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome. 361 | */ 362 | 363 | input[type="search"] { 364 | -webkit-appearance: textfield; /* 1 */ 365 | box-sizing: content-box; /* 2 */ 366 | } 367 | 368 | /** 369 | * Remove inner padding and search cancel button in Safari and Chrome on OS X. 370 | * Safari (but not Chrome) clips the cancel button when the search input has 371 | * padding (and `textfield` appearance). 372 | */ 373 | 374 | input[type="search"]::-webkit-search-cancel-button, 375 | input[type="search"]::-webkit-search-decoration { 376 | -webkit-appearance: none; 377 | } 378 | 379 | /** 380 | * Define consistent border, margin, and padding. 381 | */ 382 | 383 | fieldset { 384 | border: 1px solid #c0c0c0; 385 | margin: 0 2px; 386 | padding: 0.35em 0.625em 0.75em; 387 | } 388 | 389 | /** 390 | * 1. Correct `color` not being inherited in IE 8/9/10/11. 391 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 392 | */ 393 | 394 | legend { 395 | border: 0; /* 1 */ 396 | padding: 0; /* 2 */ 397 | } 398 | 399 | /** 400 | * Remove default vertical scrollbar in IE 8/9/10/11. 401 | */ 402 | 403 | textarea { 404 | overflow: auto; 405 | } 406 | 407 | /** 408 | * Don't inherit the `font-weight` (applied by a rule above). 409 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. 410 | */ 411 | 412 | optgroup { 413 | font-weight: bold; 414 | } 415 | 416 | /* Tables 417 | ========================================================================== */ 418 | 419 | /** 420 | * Remove most spacing between table cells. 421 | */ 422 | 423 | table { 424 | border-collapse: collapse; 425 | border-spacing: 0; 426 | } 427 | 428 | td, 429 | th { 430 | padding: 0; 431 | } 432 | .clearfix { 433 | overflow: hidden; 434 | _zoom: 1; 435 | } 436 | -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/static/favicon.ico -------------------------------------------------------------------------------- /static/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/static/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /static/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/static/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /static/js/rangeFn.js: -------------------------------------------------------------------------------- 1 | // 获取光标位置 2 | function getCursortPosition(textDom) { 3 | var cursorPos = 0; 4 | if (document.selection) { 5 | // IE Support 6 | textDom.focus(); 7 | var selectRange = document.selection.createRange(); 8 | selectRange.moveStart('character', -textDom.value.length); 9 | cursorPos = selectRange.text.length; 10 | } else if (textDom.selectionStart || textDom.selectionStart == '0') { 11 | // Firefox support 12 | cursorPos = textDom.selectionStart; 13 | } 14 | return cursorPos; 15 | } 16 | 17 | // 设置光标位置 18 | function setCaretPosition(textDom, pos) { 19 | if (textDom.setSelectionRange) { 20 | // IE Support 21 | textDom.focus(); 22 | textDom.setSelectionRange(pos, pos); 23 | } else if (textDom.createTextRange) { 24 | // Firefox support 25 | var range = textDom.createTextRange(); 26 | range.collapse(true); 27 | range.moveEnd('character', pos); 28 | range.moveStart('character', pos); 29 | range.select(); 30 | } 31 | } 32 | // 获取选中文字 33 | function getSelectText() { 34 | var userSelection, text; 35 | if (window.getSelection) { 36 | // Firefox support 37 | userSelection = window.getSelection(); 38 | } else if (document.selection) { 39 | // IE Support 40 | userSelection = document.selection.createRange(); 41 | } 42 | if (!(text = userSelection.text)) { 43 | text = userSelection; 44 | } 45 | return text; 46 | } 47 | 48 | /** 49 | * 选中特定范围的文本 50 | * 参数: 51 | * textDom [JavaScript DOM String] 当前对象 52 | * startPos [Int] 起始位置 53 | * endPos [Int] 终点位置 54 | */ 55 | function setSelectText(textDom, startPos, endPos) { 56 | var startPos = parseInt(startPos), 57 | endPos = parseInt(endPos), 58 | textLength = textDom.value.length; 59 | if (textLength) { 60 | if (!startPos) { 61 | startPos = 0; 62 | } 63 | if (!endPos) { 64 | endPos = textLength; 65 | } 66 | if (startPos > textLength) { 67 | startPos = textLength; 68 | } 69 | if (endPos > textLength) { 70 | endPos = textLength; 71 | } 72 | if (startPos < 0) { 73 | startPos = textLength + startPos; 74 | } 75 | if (endPos < 0) { 76 | endPos = textLength + endPos; 77 | } 78 | if (textDom.createTextRange) { 79 | // IE Support 80 | var range = textDom.createTextRange(); 81 | range.moveStart("character", -textLength); 82 | range.moveEnd("character", -textLength); 83 | range.moveStart("character", startPos); 84 | range.moveEnd("character", endPos); 85 | range.select(); 86 | } else { 87 | // Firefox support 88 | textDom.setSelectionRange(startPos, endPos); 89 | textDom.focus(); 90 | } 91 | } 92 | } 93 | function insertAfterText(textDom, value) { 94 | var selectRange; 95 | if (document.selection) { 96 | // IE Support 97 | textDom.focus(); 98 | selectRange = document.selection.createRange(); 99 | selectRange.text = value; 100 | textDom.focus(); 101 | } else if (textDom.selectionStart || textDom.selectionStart == '0') { 102 | // Firefox support 103 | var startPos = textDom.selectionStart; 104 | var endPos = textDom.selectionEnd; 105 | var scrollTop = textDom.scrollTop; 106 | textDom.value = textDom.value.substring(0, startPos) + value + textDom.value.substring(endPos, textDom.value.length); 107 | textDom.focus(); 108 | textDom.selectionStart = startPos + value.length; 109 | textDom.selectionEnd = startPos + value.length; 110 | textDom.scrollTop = scrollTop; 111 | } else { 112 | textDom.value += value; 113 | textDom.focus(); 114 | } 115 | } 116 | 117 | module.exports = { 118 | getCursortPosition:getCursortPosition, 119 | setCaretPosition:setCaretPosition, 120 | getSelectText:getSelectText, 121 | setSelectText:setSelectText, 122 | insertAfterText:insertAfterText 123 | }; 124 | -------------------------------------------------------------------------------- /static/skin/default/icon-ext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/static/skin/default/icon-ext.png -------------------------------------------------------------------------------- /static/skin/default/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/static/skin/default/icon.png -------------------------------------------------------------------------------- /static/skin/default/loading-0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/static/skin/default/loading-0.gif -------------------------------------------------------------------------------- /static/skin/default/loading-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/static/skin/default/loading-1.gif -------------------------------------------------------------------------------- /static/skin/default/loading-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-lang-CN/Kotlin-CN-Frontend/a57f84e5556697f304fb87ceb31c69b2566fcb11/static/skin/default/loading-2.gif -------------------------------------------------------------------------------- /test/unit/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "globals": { 6 | "expect": true, 7 | "sinon": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/unit/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | Vue.config.productionTip = false 4 | 5 | // require all test files (files that ends with .spec.js) 6 | const testsContext = require.context('./specs', true, /\.spec$/) 7 | testsContext.keys().forEach(testsContext) 8 | 9 | // require all src files except main.js for coverage. 10 | // you can also change this to match only the subset of files that 11 | // you want coverage for. 12 | const srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/) 13 | srcContext.keys().forEach(srcContext) 14 | -------------------------------------------------------------------------------- /test/unit/karma.conf.js: -------------------------------------------------------------------------------- 1 | // This is a karma config file. For more details see 2 | // http://karma-runner.github.io/0.13/config/configuration-file.html 3 | // we are also using it with karma-webpack 4 | // https://github.com/webpack/karma-webpack 5 | 6 | var webpackConfig = require('../../build_tools/webpack.test.conf') 7 | 8 | module.exports = function (config) { 9 | config.set({ 10 | // to run in additional browsers: 11 | // 1. install corresponding karma launcher 12 | // http://karma-runner.github.io/0.13/config/browsers.html 13 | // 2. add it to the `browsers` array below. 14 | browsers: ['PhantomJS'], 15 | frameworks: ['mocha', 'sinon-chai', 'phantomjs-shim'], 16 | reporters: ['spec', 'coverage'], 17 | webpack: webpackConfig, 18 | webpackMiddleware: { 19 | noInfo: true 20 | }, 21 | coverageReporter: { 22 | dir: './coverage', 23 | reporters: [ 24 | { type: 'lcov', subdir: '.' }, 25 | { type: 'text-summary' } 26 | ] 27 | } 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /test/unit/specs/Hello.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Hello from '@/components/Hello' 3 | 4 | describe('Edit.vue', () => { 5 | it('should render correct contents', () => { 6 | const Constructor = Vue.extend(Hello) 7 | const vm = new Constructor().$mount() 8 | expect(vm.$el.querySelector('.hello h1').textContent) 9 | .to.equal('Welcome to Your Vue.js App') 10 | }) 11 | }) 12 | --------------------------------------------------------------------------------