├── .babelrc ├── .editorconfig ├── .fecsignore ├── .fecsrc ├── .gitignore ├── .postcssrc.js ├── LICENSE ├── README.md ├── build ├── build.js ├── check-versions.js ├── dev-client.js ├── dev-server.js ├── loaders │ ├── svg-loader.js │ └── theme-loader.js ├── utils.js ├── vue-loader.conf.js ├── webpack.base.conf.js ├── webpack.dev.conf.js ├── webpack.prod.conf.js └── webpack.skeleton.conf.js ├── config ├── dev.env.js ├── icon.js ├── index.js ├── prod.env.js ├── sw-precache.js ├── sw.tmpl.js └── theme.js ├── dist ├── favicon.ico ├── index.html ├── service-worker.js ├── static │ ├── css │ │ ├── app.8d8fd3d1c98ef838d8d3f72f5a7aafe8.css │ │ └── app.8d8fd3d1c98ef838d8d3f72f5a7aafe8.css.map │ ├── fonts │ │ ├── KaTeX_AMS-Regular.672c961.eot │ │ ├── KaTeX_AMS-Regular.9971d27.ttf │ │ ├── KaTeX_AMS-Regular.e78f217.woff │ │ ├── KaTeX_AMS-Regular.f4c3270.woff2 │ │ ├── KaTeX_Caligraphic-Bold.3c3fce5.eot │ │ ├── KaTeX_Caligraphic-Bold.743b42a.ttf │ │ ├── KaTeX_Caligraphic-Bold.a2e0522.woff2 │ │ ├── KaTeX_Caligraphic-Bold.bac6199.woff │ │ ├── KaTeX_Caligraphic-Regular.244db27.ttf │ │ ├── KaTeX_Caligraphic-Regular.479a68e.woff2 │ │ ├── KaTeX_Caligraphic-Regular.a0ba281.eot │ │ ├── KaTeX_Caligraphic-Regular.a64e134.woff │ │ ├── KaTeX_Fraktur-Bold.0a0aa19.woff │ │ ├── KaTeX_Fraktur-Bold.2b4454d.eot │ │ ├── KaTeX_Fraktur-Bold.8e5f883.woff2 │ │ ├── KaTeX_Fraktur-Bold.ad26cc8.ttf │ │ ├── KaTeX_Fraktur-Regular.ae2b6f4.woff2 │ │ ├── KaTeX_Fraktur-Regular.d459632.ttf │ │ ├── KaTeX_Fraktur-Regular.dc81eae.eot │ │ ├── KaTeX_Fraktur-Regular.f980ca7.woff │ │ ├── KaTeX_Main-Bold.83f8b32.woff2 │ │ ├── KaTeX_Main-Bold.d327c21.eot │ │ ├── KaTeX_Main-Bold.d8a629d.woff │ │ ├── KaTeX_Main-Bold.e69b951.ttf │ │ ├── KaTeX_Main-Italic.07510ed.woff2 │ │ ├── KaTeX_Main-Italic.1b22614.ttf │ │ ├── KaTeX_Main-Italic.2702ac3.eot │ │ ├── KaTeX_Main-Italic.8dd42e0.woff │ │ ├── KaTeX_Main-Regular.2dffc87.woff │ │ ├── KaTeX_Main-Regular.31ec450.eot │ │ ├── KaTeX_Main-Regular.bd65225.woff2 │ │ ├── KaTeX_Main-Regular.d9162df.ttf │ │ ├── KaTeX_Math-Italic.031026c.eot │ │ ├── KaTeX_Math-Italic.55fbb3a.ttf │ │ ├── KaTeX_Math-Italic.afeebb7.woff2 │ │ ├── KaTeX_Math-Italic.da58601.woff │ │ ├── KaTeX_SansSerif-Regular.48c7df6.woff │ │ ├── KaTeX_SansSerif-Regular.7d5fa3e.woff2 │ │ ├── KaTeX_SansSerif-Regular.8075d14.ttf │ │ ├── KaTeX_SansSerif-Regular.a3319b7.eot │ │ ├── KaTeX_Script-Regular.5acb381.woff │ │ ├── KaTeX_Script-Regular.abb12fc.ttf │ │ ├── KaTeX_Script-Regular.c472b57.woff2 │ │ ├── KaTeX_Script-Regular.cf8394e.eot │ │ ├── KaTeX_Size1-Regular.5438d9d.eot │ │ ├── KaTeX_Size1-Regular.8cc60fd.ttf │ │ ├── KaTeX_Size2-Regular.1f5c2ab.eot │ │ ├── KaTeX_Size2-Regular.5976fff.ttf │ │ ├── KaTeX_Size4-Regular.5a3cee2.eot │ │ ├── KaTeX_Size4-Regular.81ab95e.ttf │ │ ├── KaTeX_Typewriter-Regular.2901747.ttf │ │ ├── KaTeX_Typewriter-Regular.3e9e27f.woff │ │ ├── KaTeX_Typewriter-Regular.8a6d8ed.woff2 │ │ ├── KaTeX_Typewriter-Regular.b2e9414.eot │ │ ├── MaterialIcons-Regular.ttf │ │ ├── MaterialIcons-Regular.woff │ │ ├── MaterialIcons-Regular.woff2 │ │ ├── fontello.068ca2b.ttf │ │ └── fontello.e73a064.eot │ ├── img │ │ ├── fontello.9354499.svg │ │ └── icons │ │ │ ├── android-chrome-192x192.png │ │ │ ├── android-chrome-512x512.png │ │ │ ├── apple-touch-icon-120x120.png │ │ │ ├── apple-touch-icon-152x152.png │ │ │ ├── apple-touch-icon-180x180.png │ │ │ ├── apple-touch-icon-60x60.png │ │ │ ├── apple-touch-icon-76x76.png │ │ │ ├── favicon-16x16.png │ │ │ ├── favicon-32x32.png │ │ │ └── favicon.ico │ ├── js │ │ ├── 0.8178e3c39015ab68bad3.js │ │ ├── 0.8178e3c39015ab68bad3.js.map │ │ ├── 1.1472ad6afc3a7a0ddd4b.js │ │ ├── 1.1472ad6afc3a7a0ddd4b.js.map │ │ ├── 2.aebbbe34e02e02c2bb11.js │ │ ├── 2.aebbbe34e02e02c2bb11.js.map │ │ ├── 3.804cbd7cc26cfe2a304b.js │ │ ├── 3.804cbd7cc26cfe2a304b.js.map │ │ ├── 4.3a0cb6b68f103cd5cc8c.js │ │ ├── 4.3a0cb6b68f103cd5cc8c.js.map │ │ ├── 5.534be72149f940d5c787.js │ │ ├── 5.534be72149f940d5c787.js.map │ │ ├── 6.f8e79bffb317bd7c7237.js │ │ ├── 6.f8e79bffb317bd7c7237.js.map │ │ ├── 7.8a197fea4ff6df352fc5.js │ │ ├── 7.8a197fea4ff6df352fc5.js.map │ │ ├── app.9d2aac39324f4d3c8e0b.js │ │ ├── app.9d2aac39324f4d3c8e0b.js.map │ │ ├── manifest.711933f9150cfee8c2aa.js │ │ ├── manifest.711933f9150cfee8c2aa.js.map │ │ ├── vendor.2fa3212c311ba94aab82.js │ │ ├── vendor.2fa3212c311ba94aab82.js.map │ │ ├── vue.3f88cca39697b990d89c.js │ │ └── vue.3f88cca39697b990d89c.js.map │ ├── manifest.json │ └── qrcode │ │ ├── README.md │ │ ├── index.html │ │ ├── lib │ │ ├── qrcode.js │ │ ├── qrcode.lib.min.js │ │ ├── quagga.min.js │ │ └── zepto.js │ │ └── src │ │ ├── alignpat.js │ │ ├── bitmat.js │ │ ├── bmparser.js │ │ ├── datablock.js │ │ ├── databr.js │ │ ├── datamask.js │ │ ├── decoder.js │ │ ├── detector.js │ │ ├── errorlevel.js │ │ ├── findpat.js │ │ ├── formatinf.js │ │ ├── gf256.js │ │ ├── gf256poly.js │ │ ├── grid.js │ │ ├── qrcode.js │ │ ├── rsdecoder.js │ │ ├── test.html │ │ └── version.js └── sw-register.js ├── index.html ├── package-lock.json ├── package.json ├── src ├── App.vue ├── app.js ├── assets │ ├── logo.png │ └── styles │ │ ├── global.styl │ │ ├── material-icons.styl │ │ └── variables.styl ├── components │ ├── AppHeader.vue │ ├── AppMask.vue │ ├── AppSidebar.vue │ ├── ProgressBar.vue │ └── Sidebar.vue ├── entry-client.js ├── entry-skeleton.js ├── event-bus.js ├── pages │ ├── About.vue │ ├── Detail.vue │ ├── Home.vue │ ├── Login.vue │ ├── Message.vue │ ├── NewThread.vue │ ├── NotFound.vue │ ├── Skeleton.vue │ └── User.vue ├── router.js ├── store │ ├── index.js │ ├── modules │ │ ├── app-shell.js │ │ └── user-info.js │ └── mutation-types.js └── sw-register.js ├── static ├── .gitkeep ├── fonts │ ├── MaterialIcons-Regular.ttf │ ├── MaterialIcons-Regular.woff │ └── MaterialIcons-Regular.woff2 ├── img │ └── icons │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon-120x120.png │ │ ├── apple-touch-icon-152x152.png │ │ ├── apple-touch-icon-180x180.png │ │ ├── apple-touch-icon-60x60.png │ │ ├── apple-touch-icon-76x76.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ └── favicon.ico ├── manifest.json └── qrcode │ ├── README.md │ ├── index.html │ ├── lib │ ├── qrcode.js │ ├── qrcode.lib.min.js │ ├── quagga.min.js │ └── zepto.js │ └── src │ ├── alignpat.js │ ├── bitmat.js │ ├── bmparser.js │ ├── datablock.js │ ├── databr.js │ ├── datamask.js │ ├── decoder.js │ ├── detector.js │ ├── errorlevel.js │ ├── findpat.js │ ├── formatinf.js │ ├── gf256.js │ ├── gf256poly.js │ ├── grid.js │ ├── qrcode.js │ ├── rsdecoder.js │ ├── test.html │ └── version.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "modules": false 7 | } 8 | ], 9 | "es2015", 10 | "stage-2" 11 | ], 12 | "plugins": [ 13 | "transform-runtime", 14 | "add-filehash", 15 | ["transform-imports", { 16 | "vuetify": { 17 | "transform": "vuetify/src/components/${member}", 18 | "preventFullImport": true 19 | } 20 | }] 21 | ], 22 | "comments": false, 23 | "env": { 24 | "test": { 25 | "presets": [ 26 | "env", 27 | "stage-2" 28 | ], 29 | "plugins": [ 30 | "istanbul" 31 | ] 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.fecsignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | static 4 | config/sw.tmpl.js 5 | *.index.html 6 | -------------------------------------------------------------------------------- /.fecsrc: -------------------------------------------------------------------------------- 1 | { 2 | "eslint": { 3 | "env": { 4 | "node": true, 5 | "browser": true 6 | }, 7 | 8 | "globals": { 9 | "console": true, 10 | "require": true, 11 | "define": true 12 | }, 13 | 14 | "rules": { 15 | "no-console": 0, 16 | "fecs-no-require": "off" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | *.DS_Store 61 | .vscode -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file https://github.com/michael-ciniawsky/postcss-load-config 3 | * @author *__ author __*{% if: *__ email __* %}(*__ email __*){% /if %} 4 | */ 5 | 6 | module.exports = { 7 | plugins: { 8 | 9 | // to edit target browsers: use "browserlist" field in package.json 10 | autoprefixer: {} 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 vanishcode 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lavas-cnode 2 | 基于 百度 Lavas 的 CNode第三方PWA客户端 3 | 4 | ### 体验 5 | [lavas-cnode](https://139.199.33.111)(为获得最好体验,请使用Android Chrome 60+ 访问.如提示不安全的链接,忽略直接访问即可.之后可以自行添加到桌面) 6 | ### 项目预览 7 | 8 | ![](https://ws1.sinaimg.cn/large/006tNc79ly1fjx62qeau8j30u01auafe.jpg) 9 | 10 | ![](https://ws2.sinaimg.cn/large/006tNc79ly1fjx7gzbhczj30u01d3go8.jpg) 11 | 12 | ![](https://ws4.sinaimg.cn/large/006tNc79ly1fjx63s9isqj30u01d5gre.jpg) 13 | 14 | ![](https://ws1.sinaimg.cn/large/006tNc79ly1fjx63xaly7j30u01d6wit.jpg) 15 | 16 | ![](https://ws2.sinaimg.cn/large/006tNc79ly1fjx6430u3xj30u01den5h.jpg) 17 | 18 | ![](https://ws3.sinaimg.cn/large/006tNc79ly1fjx7f4zdhtj30u01deabq.jpg) 19 | 20 | ### 技术栈 21 | 22 | 项目结构: Baidu Lavas -> Vuetify + ES6 + Stylus + Vue全家桶 23 | 24 | 部署: Ubuntu 16.04 + Nginx 25 | 26 | 27 | 28 | ### 关于此PWA 29 | 30 | * 二维码识别尚存在Bug 31 | * 反馈建议请至 Issue 或发送邮件至 [vanishcode@gmail.com](vanishcode@gmail.com) 32 | * Https 证书是自签的,因为域名没有时间备案 33 | * 这只是一个入门的demo项目,代码可能很水,勿喷 34 | 35 | 36 | 37 | ### 关于Lavas Project及Google PWA 38 | 39 | * [Lavas](https://lavas.baidu.com) 40 | * [Google PWA Introduction](https://developers.google.cn/web/progressive-web-apps) 41 | 42 | 43 | 44 | ### License 45 | 46 | MIT 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /build/build.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 生产环境构建入口 3 | * @author vanishcode(vanishcode@gmail.com) 4 | */ 5 | 6 | /* eslint-disable no-console */ 7 | 8 | 'use strict'; 9 | 10 | require('./check-versions')(); 11 | 12 | process.env.NODE_ENV = 'production'; 13 | 14 | const ora = require('ora'); 15 | const rm = require('rimraf'); 16 | const path = require('path'); 17 | const chalk = require('chalk'); 18 | const webpack = require('webpack'); 19 | const config = require('../config'); 20 | const webpackConfig = require('./webpack.prod.conf'); 21 | 22 | let spinner = ora('building for production...'); 23 | spinner.start(); 24 | 25 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), function (err) { 26 | if (err) { 27 | throw err; 28 | } 29 | webpack(webpackConfig, function (err, stats) { 30 | spinner.stop(); 31 | if (err) { 32 | throw err; 33 | } 34 | process.stdout.write(stats.toString({ 35 | colors: true, 36 | modules: false, 37 | children: false, 38 | chunks: false, 39 | chunkModules: false 40 | }) + '\n\n'); 41 | 42 | console.log(chalk.cyan(' Build complete.\n')); 43 | console.log(chalk.yellow('' 44 | + ' Tip: built files are meant to be served over an HTTP server.\n' 45 | + ' Opening index.html over file:// won\'t work.\n' 46 | )); 47 | 48 | if (!process.env.npm_config_report) { 49 | process.exit(); 50 | } 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /build/check-versions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 检查版本更新 3 | * @author vanishcode(vanishcode@gmail.com) 4 | */ 5 | 6 | /* eslint-disable no-console */ 7 | 8 | 'use strict'; 9 | 10 | const chalk = require('chalk'); 11 | const semver = require('semver'); 12 | const packageConfig = require('../package.json'); 13 | const shell = require('shelljs'); 14 | 15 | function exec(cmd) { 16 | return require('child_process').execSync(cmd).toString().trim(); 17 | } 18 | 19 | let versionRequirements = [ 20 | { 21 | name: 'node', 22 | currentVersion: semver.clean(process.version), 23 | versionRequirement: packageConfig.engines.node 24 | } 25 | ]; 26 | 27 | 28 | if (shell.which('npm')) { 29 | versionRequirements.push({ 30 | name: 'npm', 31 | currentVersion: exec('npm --version'), 32 | versionRequirement: packageConfig.engines.npm 33 | }); 34 | } 35 | 36 | module.exports = function () { 37 | let warnings = []; 38 | for (let i = 0, len = versionRequirements.length; i < len; i++) { 39 | let mod = versionRequirements[i]; 40 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 41 | warnings.push(mod.name + ': ' 42 | + chalk.red(mod.currentVersion) + ' should be ' 43 | + chalk.green(mod.versionRequirement) 44 | ); 45 | } 46 | } 47 | 48 | if (warnings.length) { 49 | console.log(chalk.yellow('\nTo use this template, you must update following to modules:\n')); 50 | 51 | warnings.forEach(function (warning) { 52 | console.log(' ' + warning); 53 | }); 54 | 55 | process.exit(1); 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /build/dev-client.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 开发环境客户端 3 | * @author vanishcode(vanishcode@gmail.com) 4 | */ 5 | 6 | 'use strict'; 7 | 8 | require('eventsource-polyfill'); 9 | var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true'); 10 | 11 | hotClient.subscribe(function (event) { 12 | if (event.action === 'reload') { 13 | window.location.reload(); 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /build/dev-server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 开发环境服务端 3 | * @author vanishcode(vanishcode@gmail.com) 4 | */ 5 | 6 | /* eslint-disable no-console */ 7 | 8 | 'use strict'; 9 | 10 | require('./check-versions')(); 11 | const config = require('../config'); 12 | 13 | if (!process.env.NODE_ENV) { 14 | process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV); 15 | } 16 | 17 | const opn = require('opn'); 18 | const path = require('path'); 19 | const express = require('express'); 20 | const webpack = require('webpack'); 21 | const proxyMiddleware = require('http-proxy-middleware'); 22 | const webpackConfig = require('./webpack.dev.conf'); 23 | 24 | // 默认调试服务器端口 25 | let port = process.env.PORT || config.dev.port; 26 | 27 | // 启动调试服务器时是否自动打开浏览器,默认为 false 28 | let autoOpenBrowser = !!config.dev.autoOpenBrowser; 29 | 30 | let app = express(); 31 | let compiler = webpack(webpackConfig); 32 | 33 | let devMiddleware = require('webpack-dev-middleware')(compiler, { 34 | publicPath: webpackConfig.output.publicPath, 35 | quiet: true 36 | }); 37 | 38 | let hotMiddleware = require('webpack-hot-middleware')(compiler, { 39 | log: function () {} 40 | }); 41 | 42 | // 当 html-webpack-plugin 的模版文件更新的时候,强制重新刷新调试页面 43 | compiler.plugin('compilation', function (compilation) { 44 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { 45 | hotMiddleware.publish({ 46 | action: 'reload' 47 | }); 48 | cb(); 49 | }); 50 | }); 51 | 52 | // 指定需要代理的请求列表 53 | let proxyTable = config.dev.proxyTable; 54 | 55 | // 代理请求 56 | Object.keys(proxyTable).forEach(function (context) { 57 | let options = proxyTable[context]; 58 | if (typeof options === 'string') { 59 | options = { 60 | target: options 61 | }; 62 | } 63 | app.use(proxyMiddleware(options.filter || context, options)); 64 | }); 65 | 66 | // 处理 history API 的回退情况(如果在线上环境中,也需要服务器做相应处理) 67 | app.use(require('connect-history-api-fallback')()); 68 | 69 | // 服务器部署 webpack 打包的静态资源 70 | app.use(devMiddleware); 71 | 72 | // 使用热更新, 如果编译出现错误会实时展示编译错误 73 | app.use(hotMiddleware); 74 | 75 | // 纯静态资源服务 76 | let staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory); 77 | app.use(staticPath, express.static('./static')); 78 | 79 | let uri = 'http://localhost:' + port; 80 | 81 | let newResolve; 82 | let readyPromise = new Promise(function (resolve) { 83 | newResolve = resolve; 84 | }); 85 | 86 | console.log('> Starting dev server...'); 87 | 88 | devMiddleware.waitUntilValid(function () { 89 | console.log('> Listening at ' + uri + '\n'); 90 | 91 | // 当测试环境下,不需要打开浏览器 92 | if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') { 93 | opn(uri); 94 | } 95 | newResolve(); 96 | }); 97 | 98 | let server = app.listen(port); 99 | 100 | module.exports = { 101 | ready: readyPromise, 102 | close: function () { 103 | server.close(); 104 | } 105 | }; 106 | -------------------------------------------------------------------------------- /build/loaders/svg-loader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file svg loader 3 | * 4 | * @desc 向app.js中注入通过vue-awesome注册自定义svg的代码 5 | * @author vanishcode(vanishcode@gmail.com) 6 | */ 7 | 8 | /* eslint-disable fecs-no-require, fecs-prefer-destructure */ 9 | 10 | 'use strict'; 11 | 12 | const fs = require('fs'); 13 | const path = require('path'); 14 | const iconConfigPath = require.resolve('../../config/icon'); 15 | 16 | module.exports = function (source) { 17 | // 删除require缓存 18 | delete require.cache[iconConfigPath]; 19 | 20 | const iconConfig = require(iconConfigPath); 21 | const svgDir = iconConfig.svgDir; 22 | const icons = iconConfig.icons; 23 | const prefix = iconConfig.prefix; 24 | 25 | // 验证`svg`文件夹 26 | try { 27 | if (!fs.statSync(svgDir).isDirectory()) { 28 | throw new Error(`Invalid directory of svg: ${svgDir}`); 29 | } 30 | } 31 | catch (err) { 32 | this.emitError(err); 33 | return source; 34 | } 35 | 36 | // 监听`svg`文件夹变化 37 | this.addContextDependency(svgDir); 38 | 39 | // 监听`config/icon.js`文件变化 40 | this.addDependency(iconConfigPath); 41 | 42 | // 从vue-awesome中导入 43 | if (icons) { 44 | source += icons.map(name => `import 'vue-awesome/icons/${name}';`).join(''); 45 | } 46 | 47 | // 从svg文件夹中取 48 | fs.readdirSync(svgDir).forEach(file => { 49 | let svg = fs.readFileSync(path.resolve(svgDir, file), 'utf8'); 50 | let sizeMatch = svg.match(/ viewBox="0 0 (\d+) (\d+)"/); 51 | let dMatch = svg.match(/ d="([^"]+)"/); 52 | let svgName = prefix + path.basename(file, path.extname(file)); 53 | 54 | if (!sizeMatch || !dMatch) { 55 | return; 56 | } 57 | 58 | // 注册使用到的svg 59 | source += `Icon.register( 60 | { 61 | '${svgName}': { 62 | width: ${parseInt(sizeMatch[1], 10)}, 63 | height: ${parseInt(sizeMatch[2], 10)}, 64 | d: '${dMatch[1]}' 65 | } 66 | });`; 67 | }); 68 | 69 | return source; 70 | }; 71 | -------------------------------------------------------------------------------- /build/loaders/theme-loader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file theme loader 3 | * 4 | * @desc 向每个.vue文件中注入样式相关的变量,不需要手动import 5 | * @author vanishcode(vanishcode@gmail.com) 6 | */ 7 | 8 | /* eslint-disable fecs-no-require, fecs-prefer-destructure */ 9 | 10 | 'use strict'; 11 | 12 | const theme = require('../../config/theme'); 13 | const loaderUtils = require('loader-utils'); 14 | 15 | const STYLE_TAG_REG = /(\)([\S\s]*?)(\<\/style\>)/g; 16 | 17 | // 定义在vuetify中默认的两组stylus hash:主题色和material相关 18 | let defaultVuetifyVariables = { 19 | themeColor: { 20 | primary: '$blue.darken-2', 21 | accent: '$blue.accent-2', 22 | secondary: '$grey.darken-3', 23 | info: '$blue.base', 24 | warning: '$amber.base', 25 | error: '$red.accent-2', 26 | success: '$green.base' 27 | }, 28 | materialDesign: { 29 | 'bg-color': '#fff', 30 | 'fg-color': '#000', 31 | 'text-color': '#000', 32 | 'primary-text-percent': .87, 33 | 'secondary-text-percent': .54, 34 | 'disabledORhints-text-percent': .38, 35 | 'divider-percent': .12, 36 | 'active-icon-percent': .54, 37 | 'inactive-icon-percent': .38 38 | } 39 | }; 40 | 41 | // 使用用户定义在config/theme.js中的变量覆盖默认值 42 | let themeColor = Object.assign( 43 | {}, 44 | defaultVuetifyVariables.themeColor, 45 | theme.theme.themeColor 46 | ); 47 | 48 | // 最终输出的stylus hash(themeColor部分) 49 | let themeColorTemplate = ` 50 | $theme := { 51 | primary: ${themeColor.primary} 52 | accent: ${themeColor.accent} 53 | secondary: ${themeColor.secondary} 54 | info: ${themeColor.info} 55 | warning: ${themeColor.warning} 56 | error: ${themeColor.error} 57 | success: ${themeColor.success} 58 | } 59 | `; 60 | 61 | let materialDesign = Object.assign( 62 | {}, 63 | defaultVuetifyVariables.materialDesign, 64 | theme.theme.materialDesign 65 | ); 66 | 67 | let materialDesignTemplate = ` 68 | $material-custom := { 69 | bg-color: ${materialDesign['bg-color']} 70 | fg-color: ${materialDesign['fg-color']} 71 | text-color: ${materialDesign['text-color']} 72 | primary-text-percent: ${materialDesign['primary-text-percent']} 73 | secondary-text-percent: ${materialDesign['secondary-text-percent']} 74 | disabledORhints-text-percent: ${materialDesign['disabledORhints-text-percent']} 75 | divider-percent: ${materialDesign['divider-percent']} 76 | active-icon-percent: ${materialDesign['active-icon-percent']} 77 | inactive-icon-percent: ${materialDesign['inactive-icon-percent']} 78 | } 79 | $material-theme := $material-custom 80 | `; 81 | 82 | // 引入项目变量和vuetify中使用的颜色变量 83 | let importVariablesTemplate = ` 84 | @import '~@/assets/styles/variables'; 85 | @import '~vuetify/src/stylus/settings/_colors'; 86 | `; 87 | 88 | let injectedTemplate = importVariablesTemplate 89 | + themeColorTemplate + materialDesignTemplate; 90 | 91 | module.exports = function (source) { 92 | this.cacheable(); 93 | let options = loaderUtils.getOptions(this); 94 | if (options && options.injectInVueFile) { 95 | 96 | // 向每一个.vue文件的 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 131 | 132 | 133 | 134 | 135 |
136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 |
145 |
146 | 147 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /dist/sw-register.js: -------------------------------------------------------------------------------- 1 | navigator.serviceWorker&&navigator.serviceWorker.register('/service-worker.js?v=20170926073510').then(function(){navigator.serviceWorker.addEventListener('message',function(e){if(e.data==='sw.update'){var dom=document.createElement('div');var themeColor=document.querySelector('meta[name=theme-color]');themeColor&&(themeColor.content='#000');dom.innerHTML='\n \n
\n
\n \n \u70B9\u51FB\u5237\u65B0\n
\n
\n ';document.body.appendChild(dom);setTimeout(function(){return document.getElementById('app-refresh').className+=' app-refresh-show'},16)}})}); -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | lavas-cnode 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | <% for (var jsFilePath of htmlWebpackPlugin.files.js) { %> 21 | 22 | <% } %> 23 | <% for (var cssFilePath of htmlWebpackPlugin.files.css) { %> 24 | 25 | <% } %> 26 | 27 | 28 | 29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lavas-cnode", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "codemirror": { 8 | "version": "5.30.0", 9 | "resolved": "http://registry.npm.taobao.org/codemirror/download/codemirror-5.30.0.tgz", 10 | "integrity": "sha1-huV91epVNay8+ccgeXtM7+BbWnA=" 11 | }, 12 | "codemirror-spell-checker": { 13 | "version": "1.1.2", 14 | "resolved": "http://registry.npm.taobao.org/codemirror-spell-checker/download/codemirror-spell-checker-1.1.2.tgz", 15 | "integrity": "sha1-HGYPkIlIPMtRE7m6nKGcP0mTNx4=", 16 | "requires": { 17 | "typo-js": "1.0.3" 18 | } 19 | }, 20 | "highlight.js": { 21 | "version": "9.12.0", 22 | "resolved": "http://registry.npm.taobao.org/highlight.js/download/highlight.js-9.12.0.tgz", 23 | "integrity": "sha1-5tnb5Xy+/mB1HwKvM2GVhwyQwB4=" 24 | }, 25 | "highlight.js-async-webpack": { 26 | "version": "1.0.4", 27 | "resolved": "http://registry.npm.taobao.org/highlight.js-async-webpack/download/highlight.js-async-webpack-1.0.4.tgz", 28 | "integrity": "sha1-wGtnv5nwSQRdYrdW5YVbCRLsYWw=" 29 | }, 30 | "marked": { 31 | "version": "0.3.6", 32 | "resolved": "http://registry.npm.taobao.org/marked/download/marked-0.3.6.tgz", 33 | "integrity": "sha1-ssbGGPzOzk74bE/Gy4p8v1rtqNc=" 34 | }, 35 | "mavon-editor": { 36 | "version": "2.2.6", 37 | "resolved": "http://registry.npm.taobao.org/mavon-editor/download/mavon-editor-2.2.6.tgz", 38 | "integrity": "sha1-B5eW209msKPuoGEirORnFfNj4z8=", 39 | "requires": { 40 | "highlight.js": "9.12.0", 41 | "highlight.js-async-webpack": "1.0.4" 42 | } 43 | }, 44 | "simplemde": { 45 | "version": "1.11.2", 46 | "resolved": "http://registry.npm.taobao.org/simplemde/download/simplemde-1.11.2.tgz", 47 | "integrity": "sha1-ojo12XjSxA7wfewAjJLwcNjggOM=", 48 | "requires": { 49 | "codemirror": "5.30.0", 50 | "codemirror-spell-checker": "1.1.2", 51 | "marked": "0.3.6" 52 | } 53 | }, 54 | "typo-js": { 55 | "version": "1.0.3", 56 | "resolved": "http://registry.npm.taobao.org/typo-js/download/typo-js-1.0.3.tgz", 57 | "integrity": "sha1-VNjrx5SfGngQkItgAsaEFSbJnVo=" 58 | }, 59 | "vue-simplemde": { 60 | "version": "0.4.4", 61 | "resolved": "http://registry.npm.taobao.org/vue-simplemde/download/vue-simplemde-0.4.4.tgz", 62 | "integrity": "sha1-iKuc7rm/HC/9jgKXi50GRoqhSdc=", 63 | "requires": { 64 | "simplemde": "1.11.2" 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lavas-cnode", 3 | "version": "1.0.0", 4 | "description": "Cnode SPA written by Baidu-Lavas.", 5 | "author": "vanishcode ", 6 | "private": true, 7 | "scripts": { 8 | "dev": "node build/dev-server.js", 9 | "lint": "fecs ./ --rule --type 'vue,js,css'", 10 | "start": "node build/dev-server.js", 11 | "build": "node build/build.js" 12 | }, 13 | "dependencies": { 14 | "axios": "^0.16.2", 15 | "fastclick": "^1.0.6", 16 | "iscroll": "5.1.0", 17 | "mavon-editor": "^2.2.6", 18 | "normalize.css": "^7.0.0", 19 | "vue": "^2.3.3", 20 | "vue-awesome": "^2.3.1", 21 | "vue-markdown": "^2.2.4", 22 | "vue-router": "^2.3.1", 23 | "vue-simplemde": "^0.4.4", 24 | "vuetify": "^0.15.2", 25 | "vuex": "^2.3.1" 26 | }, 27 | "devDependencies": { 28 | "autoprefixer": "^6.7.2", 29 | "babel-core": "^6.22.1", 30 | "babel-loader": "^6.2.10", 31 | "babel-plugin-add-filehash": "^6.9.4", 32 | "babel-plugin-transform-imports": "^1.4.1", 33 | "babel-plugin-transform-runtime": "^6.22.0", 34 | "babel-polyfill": "^6.23.0", 35 | "babel-preset-env": "^1.3.2", 36 | "babel-preset-es2015": "^6.24.1", 37 | "babel-preset-stage-2": "^6.22.0", 38 | "babel-register": "^6.22.0", 39 | "chalk": "^2.1.0", 40 | "connect-history-api-fallback": "^1.3.0", 41 | "copy-webpack-plugin": "^4.0.1", 42 | "css-loader": "^0.28.0", 43 | "eventsource-polyfill": "^0.9.6", 44 | "express": "^4.14.1", 45 | "extract-text-webpack-plugin": "^3.0.0", 46 | "fecs": "^1.4.0", 47 | "file-loader": "^0.11.1", 48 | "friendly-errors-webpack-plugin": "^1.1.3", 49 | "html-webpack-plugin": "^2.28.0", 50 | "http-proxy-middleware": "^0.17.3", 51 | "loader-utils": "^1.1.0", 52 | "opn": "^4.0.2", 53 | "optimize-css-assets-webpack-plugin": "^1.3.0", 54 | "ora": "^1.2.0", 55 | "rimraf": "^2.6.0", 56 | "semver": "^5.3.0", 57 | "shelljs": "^0.7.6", 58 | "stylus": "^0.54.5", 59 | "stylus-loader": "^3.0.1", 60 | "sw-precache": "^5.1.1", 61 | "sw-precache-webpack-plugin": "^0.11.3", 62 | "sw-register-webpack-plugin": "^1.0.7", 63 | "url-loader": "^0.5.8", 64 | "vue-loader": "^12.1.0", 65 | "vue-skeleton-webpack-plugin": "^1.1.3", 66 | "vue-style-loader": "^3.0.1", 67 | "vue-template-compiler": "^2.3.3", 68 | "webpack": "^3.3.1", 69 | "webpack-bundle-analyzer": "^2.2.1", 70 | "webpack-dev-middleware": "^1.10.0", 71 | "webpack-hot-middleware": "^2.18.0", 72 | "webpack-merge": "^4.1.0", 73 | "webpack-node-externals": "^1.6.0" 74 | }, 75 | "engines": { 76 | "node": ">= 4.0.0", 77 | "npm": ">= 3.0.0" 78 | }, 79 | "browserslist": [ 80 | "> 1%", 81 | "last 2 versions", 82 | "not ie <= 8" 83 | ] 84 | } 85 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 91 | 92 | 107 | 108 | 172 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file entry 3 | * @author vanishcode(vanishcode@gmail.com) 4 | */ 5 | 6 | import Vue from 'vue'; 7 | import { 8 | Vuetify, 9 | VBtn, 10 | VApp, 11 | VList, 12 | VIcon, 13 | VProgressCircular, 14 | VDivider, 15 | VBottomNav, 16 | VGrid, 17 | VAvatar, 18 | VTextField, 19 | VAlert, 20 | VChip, 21 | VCard, 22 | VSubHeader, 23 | VMenu 24 | 25 | } from 'vuetify'; 26 | import { createRouter } from './router'; 27 | import store from './store'; 28 | import App from './App.vue'; 29 | import Icon from 'vue-awesome/components/Icon.vue'; 30 | import Badge from 'vuetify/src/directives/badge'; 31 | 32 | // not fetch 33 | import axios from 'axios' 34 | 35 | Vue.prototype.$http = axios 36 | 37 | //Vue.directive(Badge) 38 | 39 | Vue.use(Vuetify, { 40 | components: { 41 | VApp, 42 | VBtn, 43 | VList, 44 | VIcon, 45 | VProgressCircular, 46 | VDivider, 47 | VBottomNav, 48 | VGrid, 49 | VAvatar, 50 | VTextField, 51 | VAlert, 52 | VChip, 53 | VCard, 54 | VSubHeader, 55 | VMenu 56 | } 57 | }); 58 | 59 | Vue.component('icon', Icon); 60 | 61 | Vue.config.productionTip = false; 62 | 63 | /* eslint-disable no-new */ 64 | export function createApp() { 65 | let router = createRouter(); 66 | let app = new Vue({ 67 | router, 68 | store, 69 | ...App 70 | }); 71 | return { app, router, store }; 72 | } -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanishcode/lavas-cnode/d23a38c9d6a54ee0b3fd6da50a18de8d8c4203d0/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/styles/global.styl: -------------------------------------------------------------------------------- 1 | @import './material-icons' 2 | @import '~vuetify/src/stylus/app' 3 | -------------------------------------------------------------------------------- /src/assets/styles/material-icons.styl: -------------------------------------------------------------------------------- 1 | @font-face 2 | font-family "Material Icons" 3 | font-style normal 4 | font-weight 400 5 | src local("Material Icons"), 6 | local("MaterialIcons-Regular"), 7 | url(/static/fonts/MaterialIcons-Regular.woff2) format('woff2'), 8 | url(/static/fonts/MaterialIcons-Regular.woff) format('woff'), 9 | url(/static/fonts/MaterialIcons-Regular.ttf) format('truetype') 10 | 11 | .material-icons 12 | display inline-block 13 | font normal normal 24px/24px "Material Icons" 14 | text-transform none 15 | letter-spacing normal 16 | word-wrap normal 17 | white-space nowrap 18 | direction ltr 19 | 20 | /* Support for all WebKit browsers. */ 21 | -webkit-font-smoothing antialiased 22 | /* Support for Safari and Chrome. */ 23 | text-rendering optimizeLegibility 24 | 25 | /* Support for Firefox. */ 26 | -moz-osx-font-smoothing grayscale 27 | 28 | /* Support for IE. */ 29 | font-feature-settings "liga" 30 | -------------------------------------------------------------------------------- /src/assets/styles/variables.styl: -------------------------------------------------------------------------------- 1 | $app-header-height = 52px 2 | $app-footer-height = 56px 3 | 4 | // app-sidebar 相关配置 5 | $app-sidebar-title-height = 75px 6 | $app-sidebar-nav-height = 45px 7 | $app-sidebar-width = 55 8 | $app-sidebar-left-icon-size = 20 9 | 10 | // app-mask 相关配置 11 | $app-mask-bgcolor = #000 12 | $app-mask-opacity = 0.5 13 | $app-mask-zindex = 999 14 | $app-mask-duration = .5s 15 | -------------------------------------------------------------------------------- /src/components/AppHeader.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 94 | 95 | 132 | -------------------------------------------------------------------------------- /src/components/AppMask.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 21 | 22 | 44 | -------------------------------------------------------------------------------- /src/components/ProgressBar.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 88 | 89 | -------------------------------------------------------------------------------- /src/entry-client.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file client entry 3 | * @author vanishcode(vanishcode@gmail.com) 4 | */ 5 | 6 | import 'babel-polyfill'; 7 | import Vue from 'vue'; 8 | import FastClick from 'fastclick'; 9 | import {createApp} from './app'; 10 | import ProgressBar from '@/components/ProgressBar.vue'; 11 | 12 | // 全局的进度条,在组件中可通过 $loading 访问 13 | let loading = Vue.prototype.$loading = new Vue(ProgressBar).$mount(); 14 | let {app, router, store} = createApp(); 15 | 16 | document.body.appendChild(loading.$el); 17 | FastClick.attach(document.body); 18 | 19 | Vue.mixin({ 20 | 21 | // 当复用的路由组件参数发生变化时,例如/detail/1 => /detail/2 22 | beforeRouteUpdate(to, from, next) { 23 | 24 | // asyncData方法中包含异步数据请求 25 | let asyncData = this.$options.asyncData; 26 | 27 | if (asyncData) { 28 | loading.start(); 29 | asyncData.call(this, { 30 | store: this.$store, 31 | route: to 32 | }).then(() => { 33 | loading.finish(); 34 | next(); 35 | }).catch(next); 36 | } 37 | else { 38 | next(); 39 | } 40 | }, 41 | 42 | // 路由切换时,保存页面滚动位置 43 | beforeRouteEnter(to, from, next) { 44 | next(vm => { 45 | 46 | // 通过 `vm` 访问组件实例 47 | vm.$el.scrollTop = vm.$store.state.appShell.historyPageScrollTop[to.fullPath] || 0; 48 | }); 49 | }, 50 | beforeRouteLeave(to, from, next) { 51 | this.$store.dispatch('appShell/saveScrollTop', {path: from.fullPath, scrollTop: this.$el.scrollTop}); 52 | next(); 53 | } 54 | }); 55 | 56 | // 此时异步组件已经加载完成 57 | router.beforeResolve((to, from, next) => { 58 | let matched = router.getMatchedComponents(to); 59 | let prevMatched = router.getMatchedComponents(from); 60 | 61 | // [a, b] 62 | // [a, b, c, d] 63 | // => [c, d] 64 | let diffed = false; 65 | let activated = matched.filter((c, i) => diffed || (diffed = (prevMatched[i] !== c))); 66 | 67 | if (!activated.length) { 68 | return next(); 69 | } 70 | 71 | loading.start(); 72 | Promise.all(activated.map(c => { 73 | 74 | /** 75 | * 两种情况下执行asyncData: 76 | * 1. 非keep-alive组件每次都需要执行 77 | * 2. keep-alive组件首次执行,执行后添加标志 78 | */ 79 | if (c.asyncData && (!c.asyncDataFetched || to.meta.notKeepAlive)) { 80 | return c.asyncData({ 81 | store, 82 | route: to 83 | }).then(() => { 84 | c.asyncDataFetched = true; 85 | }); 86 | } 87 | })).then(() => { 88 | loading.finish(); 89 | next(); 90 | }).catch(next); 91 | }); 92 | 93 | router.onReady(() => app.$mount('#app')); 94 | -------------------------------------------------------------------------------- /src/entry-skeleton.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 页面骨架 3 | * @author vanishcode(vanishcode@gmail.com) 4 | */ 5 | 6 | import Vue from 'vue'; 7 | import Skeleton from '@/pages/Skeleton'; 8 | 9 | export default new Vue({ 10 | components: { 11 | Skeleton 12 | }, 13 | template: '' 14 | }); 15 | -------------------------------------------------------------------------------- /src/event-bus.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 事件总线 3 | * @author vanishcode(vanishcode@gmail.com) 4 | */ 5 | 6 | import Vue from 'vue'; 7 | 8 | // 全局事件总线 9 | export default new Vue(); 10 | -------------------------------------------------------------------------------- /src/pages/About.vue: -------------------------------------------------------------------------------- 1 | 89 | 90 | 123 | 124 | 137 | -------------------------------------------------------------------------------- /src/pages/Detail.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 119 | 120 | 150 | -------------------------------------------------------------------------------- /src/pages/Home.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 97 | 98 | 105 | -------------------------------------------------------------------------------- /src/pages/Login.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 93 | 94 | 103 | -------------------------------------------------------------------------------- /src/pages/Message.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 118 | 119 | 122 | -------------------------------------------------------------------------------- /src/pages/NewThread.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 101 | 102 | 107 | -------------------------------------------------------------------------------- /src/pages/NotFound.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 34 | 35 | 49 | -------------------------------------------------------------------------------- /src/pages/User.vue: -------------------------------------------------------------------------------- 1 | 58 | 59 | 123 | 124 | 151 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file store index 3 | * @author vanishcode(vanishcode@gmail.com) 4 | */ 5 | 6 | import Vue from 'vue'; 7 | import Vuex from 'vuex'; 8 | import appShell from './modules/app-shell'; 9 | import userInfo from './modules/user-info'; 10 | 11 | Vue.use(Vuex); 12 | 13 | export default new Vuex.Store({ 14 | modules: { 15 | appShell, 16 | userInfo 17 | } 18 | }); -------------------------------------------------------------------------------- /src/store/modules/user-info.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 用户信息,包括ak,qrcode,用户数据/操作控制alert,loading 3 | * @author vanishcode(vanishcode@gmail.com) 4 | */ 5 | 6 | 7 | export default { 8 | state: { 9 | isLogin: false, 10 | isLoading: false 11 | }, 12 | mutations: { 13 | login(state) { 14 | state.isLogin = true; 15 | }, 16 | logout(state) { 17 | state.isLogin = false; 18 | } 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /src/store/mutation-types.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file app shell mutation types(or name of methods to change the state:) 3 | * @author vanishcode(vanishcode@gmail.com) 4 | */ 5 | 6 | export const ENABLE_PAGE_TRANSITION = 'ENABLE_PAGE_TRANSITION'; 7 | export const DISABLE_PAGE_TRANSITION = 'DISABLE_PAGE_TRANSITION'; 8 | export const SET_PAGE_SWITCHING = 'SET_PAGE_SWITCHING'; 9 | export const SET_PAGE_TRANSITION_NAME = 'SET_PAGE_TRANSITION_NAME'; 10 | export const SET_APP_HEADER = 'SET_APP_HEADER'; 11 | export const SET_APP_BOTTOM_NAV = 'SET_APP_BOTTOM_NAV'; 12 | export const ACTIVATE_APP_BOTTOM_NAV = 'ACTIVATE_APP_BOTTOM_NAV'; 13 | export const SET_SIDEBAR_VISIBILITY = 'SET_SIDEBAR_VISIBILITY'; 14 | export const SAVE_SCROLLTOP = 'SAVE_SCROLLTOP'; 15 | 16 | export const login = 'login' 17 | // loading alert notifiation -------------------------------------------------------------------------------- /src/sw-register.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file serviceworker register 3 | * @author vanishcode(vanishcode@gmail.com) 4 | */ 5 | 6 | // 注册的地址为 sw-precache-webpack-pulgin 生成的 service-worker.js 或者自己手动维护的 service worker 文件 7 | navigator.serviceWorker && navigator.serviceWorker.register('/service-worker.js').then(() => { 8 | navigator.serviceWorker.addEventListener('message', e => { 9 | 10 | // service-worker.js 如果更新成功会 postMessage 给页面,内容为 'sw.update' 11 | if (e.data === 'sw.update') { 12 | let dom = document.createElement('div'); 13 | let themeColor = document.querySelector('meta[name=theme-color]'); 14 | 15 | themeColor && (themeColor.content = '#000'); 16 | 17 | /* eslint-disable max-len */ 18 | dom.innerHTML = ` 19 | 25 |
26 |
27 | 28 | 点击刷新 29 |
30 |
31 | `; 32 | /* eslint-enable max-len */ 33 | 34 | document.body.appendChild(dom); 35 | setTimeout(() => document.getElementById('app-refresh').className += ' app-refresh-show', 16); 36 | } 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanishcode/lavas-cnode/d23a38c9d6a54ee0b3fd6da50a18de8d8c4203d0/static/.gitkeep -------------------------------------------------------------------------------- /static/fonts/MaterialIcons-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanishcode/lavas-cnode/d23a38c9d6a54ee0b3fd6da50a18de8d8c4203d0/static/fonts/MaterialIcons-Regular.ttf -------------------------------------------------------------------------------- /static/fonts/MaterialIcons-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanishcode/lavas-cnode/d23a38c9d6a54ee0b3fd6da50a18de8d8c4203d0/static/fonts/MaterialIcons-Regular.woff -------------------------------------------------------------------------------- /static/img/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanishcode/lavas-cnode/d23a38c9d6a54ee0b3fd6da50a18de8d8c4203d0/static/img/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /static/img/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanishcode/lavas-cnode/d23a38c9d6a54ee0b3fd6da50a18de8d8c4203d0/static/img/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /static/img/icons/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanishcode/lavas-cnode/d23a38c9d6a54ee0b3fd6da50a18de8d8c4203d0/static/img/icons/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /static/img/icons/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanishcode/lavas-cnode/d23a38c9d6a54ee0b3fd6da50a18de8d8c4203d0/static/img/icons/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /static/img/icons/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanishcode/lavas-cnode/d23a38c9d6a54ee0b3fd6da50a18de8d8c4203d0/static/img/icons/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /static/img/icons/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanishcode/lavas-cnode/d23a38c9d6a54ee0b3fd6da50a18de8d8c4203d0/static/img/icons/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /static/img/icons/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanishcode/lavas-cnode/d23a38c9d6a54ee0b3fd6da50a18de8d8c4203d0/static/img/icons/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /static/img/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanishcode/lavas-cnode/d23a38c9d6a54ee0b3fd6da50a18de8d8c4203d0/static/img/icons/favicon-16x16.png -------------------------------------------------------------------------------- /static/img/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanishcode/lavas-cnode/d23a38c9d6a54ee0b3fd6da50a18de8d8c4203d0/static/img/icons/favicon-32x32.png -------------------------------------------------------------------------------- /static/img/icons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanishcode/lavas-cnode/d23a38c9d6a54ee0b3fd6da50a18de8d8c4203d0/static/img/icons/favicon.ico -------------------------------------------------------------------------------- /static/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CNode 专业中文社区", 3 | "short_name": "CNode PWA", 4 | "icons": [{ 5 | "src": "/static/img/icons/android-chrome-192x192.png", 6 | "sizes": "192x192", 7 | "type": "image/png" 8 | }, { 9 | "src": "/static/img/icons/android-chrome-512x512.png", 10 | "sizes": "512x512", 11 | "type": "image/png" 12 | }], 13 | "start_url": "/", 14 | "background_color": "#000000", 15 | "display": "standalone", 16 | "theme_color": "#000" 17 | } -------------------------------------------------------------------------------- /static/qrcode/README.md: -------------------------------------------------------------------------------- 1 | ## html5-qrcode 使用文档 2 | 3 | ---- 4 | 5 | 2016年6月29日补充: 6 | 最近做了一些与表单相关的项目,使用了h5的input控件,在使用过程中遇到了很多的坑。也包括与这篇文章相关的。 7 | 8 | 首先我们应该知道使用h5新提供的属性getUserMedia这个属性,是可以调取系统的摄像头进行拍照或者是摄像的,但是兼容性支持的不好,所以当我们需要获取系统的多媒体权限时我们都不会采用这个属性。 9 | 10 | 使用标签我们可以间接的呼起系统选择文件的窗口,来读取系统文件。但是在WebView中,因为安卓权限的问题,我们是没办法直接获取读取文件这个操作的。而在原生的浏览器中是不存在这个问题的。所以选择使用这个input的时候一定要注意自己的页面是主要运行在webview中还是浏览器中。如果注意运行在客户端的webvie中,是需要客户端的同学支持的。 11 | 12 | 在IOS的某些系统版本中也会出现这个问题。具体的可以参考下面的参考文章。 13 | 参考文章: 14 | http://blog.csdn.net/hvkcoder/article/details/51365191 15 | 16 | https://forums.developer.apple.com/thread/22726 17 | 18 | http://www.cnblogs.com/soaringEveryday/p/4495221.html 19 | 20 | http://stackoverflow.com/questions/25942676/ios-8-sdk-modal-uiwebview-and-camera-image-picker 21 | 22 | 23 | 24 | ### 功能: 25 | **1.**h5页面在微博客户端中呼起摄像头扫描二维码并且解析。 26 | **2.**h5页面在非微博客户端中(原生浏览器或者微信客户端)呼起系统拍照或者上传图片按钮,拍照二维码或者上传二维码并且解析 27 | 28 | ### demo演示地址: 29 | 30 | [http://test.qrcode.weibo.com/zhiqiang/WebComponent/html5-Qrcode/](http://test.qrcode.weibo.com/zhiqiang/WebComponent/html5-Qrcode/) 31 | 32 | 需要配置 hosts : 33 | `xx.xxx.xxx.233 test.qrcode.weibo.com` 34 | 35 | ### 说明: 36 | 此插件需要配合`zepto.js` 或者 `jQuery.js`使用 37 | 38 | 39 | ### 使用方法: 40 | **1.**在需要使用的页面按照下面顺序引入`lib`目录下的 js 文件 41 | 42 | ```javascript 43 | 44 | 45 | 46 | ``` 47 | 48 | **2.**自定义按钮的 html 样式 49 | 为自定义的按钮添加自定义属性,属性名称为`node-type` 50 | 为 input 按钮添加自定义的属性, 属性名称为`node-type` 51 | 52 | >因为该插件需要使用`` ,该 html 结构在网页上面是有固定的显示样式,为了能够自定义按钮样式,我们可以按照下面的示例代码结构嵌套代码 53 | 54 | ```html 55 |
56 |
扫描二维码1 57 | 58 |
59 |
60 | ``` 61 | 62 | 然后设置 `input` 按钮的 `css` 隐藏按钮,比如我使用的是属性选择器 63 | 64 | ```css 65 | input[node-type=jsbridge]{ 66 | display:none; 67 | } 68 | ``` 69 | 70 | 这里我们只需要按照自己的需要定义`class="qr-btn"`的样式即可。 71 | 72 | **3.**在页面上初始化 Qrcode 对象 73 | 74 | ```javascript 75 | //初始化扫描二维码按钮,传入自定义的 node-type 属性 76 | $(function() { 77 | Qrcode.init($('[node-type=qr-btn]')); 78 | }); 79 | ``` 80 | -------------------------------------------------------------------------------- /static/qrcode/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 二维码扫描 8 | 9 | 10 | 11 | 12 |
13 |
扫描二维码1 14 | 15 |
16 |
17 |
18 |
扫描二维码2 19 | 20 |
21 |
22 |
23 |
24 | 25 | 26 | 27 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /static/qrcode/lib/qrcode.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | var Qrcode = function(tempBtn) { 3 | var _this_ = this; 4 | var isWeiboWebView = /__weibo__/.test(navigator.userAgent); 5 | 6 | if (isWeiboWebView) { 7 | if (window.WeiboJSBridge) { 8 | _this_.bridgeReady(tempBtn); 9 | } else { 10 | document.addEventListener('WeiboJSBridgeReady', function() { 11 | _this_.bridgeReady(tempBtn); 12 | }); 13 | } 14 | } else { 15 | _this_.nativeReady(tempBtn); 16 | } 17 | }; 18 | 19 | Qrcode.prototype = { 20 | nativeReady: function(tempBtn) { 21 | $('[node-type=jsbridge]', tempBtn).on('click', function(e) { 22 | 23 | e.stopPropagation(); 24 | }); 25 | 26 | $(tempBtn).bind('click', function(e) { 27 | 28 | $(this).find('input[node-type=jsbridge]').trigger('click'); 29 | }); 30 | 31 | $(tempBtn).bind('change', this.getImgFile); 32 | }, 33 | bridgeReady: function(tempBtn) { 34 | $(tempBtn).bind('click', this.weiBoBridge); 35 | }, 36 | getImgFile: function() { 37 | var _this_ = this; 38 | var inputDom = $(this).find('input[node-type=jsbridge]'); 39 | var imgFile = inputDom[0].files; 40 | var oFile = imgFile[0]; 41 | var oFReader = new FileReader(); 42 | var rFilter = /^(?:image\/bmp|image\/cis\-cod|image\/gif|image\/ief|image\/jpeg|image\/jpeg|image\/jpeg|image\/pipeg|image\/png|image\/svg\+xml|image\/tiff|image\/x\-cmu\-raster|image\/x\-cmx|image\/x\-icon|image\/x\-portable\-anymap|image\/x\-portable\-bitmap|image\/x\-portable\-graymap|image\/x\-portable\-pixmap|image\/x\-rgb|image\/x\-xbitmap|image\/x\-xpixmap|image\/x\-xwindowdump)$/i; 43 | 44 | if (imgFile.length === 0) { 45 | return; 46 | } 47 | 48 | if (!rFilter.test(oFile.type)) { 49 | alert("选择正确的图片格式!"); 50 | return; 51 | } 52 | 53 | oFReader.onload = function(oFREvent) { 54 | 55 | qrcode.decode(oFREvent.target.result); 56 | qrcode.callback = function(data) { 57 | //得到扫码的结果 58 | 59 | console.log(data) 60 | 61 | $('.tmp').val(data) 62 | $('.tmp').click() 63 | 64 | } 65 | }; 66 | 67 | oFReader.readAsDataURL(oFile); 68 | }, 69 | destory: function() { 70 | $(tempBtn).off('click'); 71 | } 72 | }; 73 | 74 | Qrcode.init = function(tempBtn) { 75 | var _this_ = this; 76 | tempBtn.each(function() { 77 | new _this_($(this)); 78 | }); 79 | }; 80 | window.Qrcode = Qrcode; 81 | })(window.Zepto ? Zepto : jQuery); -------------------------------------------------------------------------------- /static/qrcode/src/bitmat.js: -------------------------------------------------------------------------------- 1 | /* 2 | Ported to JavaScript by Lazar Laszlo 2011 3 | 4 | lazarsoft@gmail.com, www.lazarsoft.info 5 | 6 | */ 7 | 8 | /* 9 | * 10 | * Copyright 2007 ZXing authors 11 | * 12 | * Licensed under the Apache License, Version 2.0 (the "License"); 13 | * you may not use this file except in compliance with the License. 14 | * You may obtain a copy of the License at 15 | * 16 | * http://www.apache.org/licenses/LICENSE-2.0 17 | * 18 | * Unless required by applicable law or agreed to in writing, software 19 | * distributed under the License is distributed on an "AS IS" BASIS, 20 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 21 | * See the License for the specific language governing permissions and 22 | * limitations under the License. 23 | */ 24 | 25 | 26 | function BitMatrix( width, height) 27 | { 28 | if(!height) 29 | height=width; 30 | if (width < 1 || height < 1) 31 | { 32 | throw "Both dimensions must be greater than 0"; 33 | } 34 | this.width = width; 35 | this.height = height; 36 | var rowSize = width >> 5; 37 | if ((width & 0x1f) != 0) 38 | { 39 | rowSize++; 40 | } 41 | this.rowSize = rowSize; 42 | this.bits = new Array(rowSize * height); 43 | for(var i=0;i> 5); 66 | return ((URShift(this.bits[offset], (x & 0x1f))) & 1) != 0; 67 | } 68 | this.set_Renamed=function( x, y) 69 | { 70 | var offset = y * this.rowSize + (x >> 5); 71 | this.bits[offset] |= 1 << (x & 0x1f); 72 | } 73 | this.flip=function( x, y) 74 | { 75 | var offset = y * this.rowSize + (x >> 5); 76 | this.bits[offset] ^= 1 << (x & 0x1f); 77 | } 78 | this.clear=function() 79 | { 80 | var max = this.bits.length; 81 | for (var i = 0; i < max; i++) 82 | { 83 | this.bits[i] = 0; 84 | } 85 | } 86 | this.setRegion=function( left, top, width, height) 87 | { 88 | if (top < 0 || left < 0) 89 | { 90 | throw "Left and top must be nonnegative"; 91 | } 92 | if (height < 1 || width < 1) 93 | { 94 | throw "Height and width must be at least 1"; 95 | } 96 | var right = left + width; 97 | var bottom = top + height; 98 | if (bottom > this.height || right > this.width) 99 | { 100 | throw "The region must fit inside the matrix"; 101 | } 102 | for (var y = top; y < bottom; y++) 103 | { 104 | var offset = y * this.rowSize; 105 | for (var x = left; x < right; x++) 106 | { 107 | this.bits[offset + (x >> 5)] |= 1 << (x & 0x1f); 108 | } 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /static/qrcode/src/datablock.js: -------------------------------------------------------------------------------- 1 | /* 2 | Ported to JavaScript by Lazar Laszlo 2011 3 | 4 | lazarsoft@gmail.com, www.lazarsoft.info 5 | 6 | */ 7 | 8 | /* 9 | * 10 | * Copyright 2007 ZXing authors 11 | * 12 | * Licensed under the Apache License, Version 2.0 (the "License"); 13 | * you may not use this file except in compliance with the License. 14 | * You may obtain a copy of the License at 15 | * 16 | * http://www.apache.org/licenses/LICENSE-2.0 17 | * 18 | * Unless required by applicable law or agreed to in writing, software 19 | * distributed under the License is distributed on an "AS IS" BASIS, 20 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 21 | * See the License for the specific language governing permissions and 22 | * limitations under the License. 23 | */ 24 | 25 | 26 | function DataBlock(numDataCodewords, codewords) 27 | { 28 | this.numDataCodewords = numDataCodewords; 29 | this.codewords = codewords; 30 | 31 | this.__defineGetter__("NumDataCodewords", function() 32 | { 33 | return this.numDataCodewords; 34 | }); 35 | this.__defineGetter__("Codewords", function() 36 | { 37 | return this.codewords; 38 | }); 39 | } 40 | 41 | DataBlock.getDataBlocks=function(rawCodewords, version, ecLevel) 42 | { 43 | 44 | if (rawCodewords.length != version.TotalCodewords) 45 | { 46 | throw "ArgumentException"; 47 | } 48 | 49 | // Figure out the number and size of data blocks used by this version and 50 | // error correction level 51 | var ecBlocks = version.getECBlocksForLevel(ecLevel); 52 | 53 | // First count the total number of data blocks 54 | var totalBlocks = 0; 55 | var ecBlockArray = ecBlocks.getECBlocks(); 56 | for (var i = 0; i < ecBlockArray.length; i++) 57 | { 58 | totalBlocks += ecBlockArray[i].Count; 59 | } 60 | 61 | // Now establish DataBlocks of the appropriate size and number of data codewords 62 | var result = new Array(totalBlocks); 63 | var numResultBlocks = 0; 64 | for (var j = 0; j < ecBlockArray.length; j++) 65 | { 66 | var ecBlock = ecBlockArray[j]; 67 | for (var i = 0; i < ecBlock.Count; i++) 68 | { 69 | var numDataCodewords = ecBlock.DataCodewords; 70 | var numBlockCodewords = ecBlocks.ECCodewordsPerBlock + numDataCodewords; 71 | result[numResultBlocks++] = new DataBlock(numDataCodewords, new Array(numBlockCodewords)); 72 | } 73 | } 74 | 75 | // All blocks have the same amount of data, except that the last n 76 | // (where n may be 0) have 1 more byte. Figure out where these start. 77 | var shorterBlocksTotalCodewords = result[0].codewords.length; 78 | var longerBlocksStartAt = result.length - 1; 79 | while (longerBlocksStartAt >= 0) 80 | { 81 | var numCodewords = result[longerBlocksStartAt].codewords.length; 82 | if (numCodewords == shorterBlocksTotalCodewords) 83 | { 84 | break; 85 | } 86 | longerBlocksStartAt--; 87 | } 88 | longerBlocksStartAt++; 89 | 90 | var shorterBlocksNumDataCodewords = shorterBlocksTotalCodewords - ecBlocks.ECCodewordsPerBlock; 91 | // The last elements of result may be 1 element longer; 92 | // first fill out as many elements as all of them have 93 | var rawCodewordsOffset = 0; 94 | for (var i = 0; i < shorterBlocksNumDataCodewords; i++) 95 | { 96 | for (var j = 0; j < numResultBlocks; j++) 97 | { 98 | result[j].codewords[i] = rawCodewords[rawCodewordsOffset++]; 99 | } 100 | } 101 | // Fill out the last data block in the longer ones 102 | for (var j = longerBlocksStartAt; j < numResultBlocks; j++) 103 | { 104 | result[j].codewords[shorterBlocksNumDataCodewords] = rawCodewords[rawCodewordsOffset++]; 105 | } 106 | // Now add in error correction blocks 107 | var max = result[0].codewords.length; 108 | for (var i = shorterBlocksNumDataCodewords; i < max; i++) 109 | { 110 | for (var j = 0; j < numResultBlocks; j++) 111 | { 112 | var iOffset = j < longerBlocksStartAt?i:i + 1; 113 | result[j].codewords[iOffset] = rawCodewords[rawCodewordsOffset++]; 114 | } 115 | } 116 | return result; 117 | } 118 | -------------------------------------------------------------------------------- /static/qrcode/src/datamask.js: -------------------------------------------------------------------------------- 1 | /* 2 | Ported to JavaScript by Lazar Laszlo 2011 3 | 4 | lazarsoft@gmail.com, www.lazarsoft.info 5 | 6 | */ 7 | 8 | /* 9 | * 10 | * Copyright 2007 ZXing authors 11 | * 12 | * Licensed under the Apache License, Version 2.0 (the "License"); 13 | * you may not use this file except in compliance with the License. 14 | * You may obtain a copy of the License at 15 | * 16 | * http://www.apache.org/licenses/LICENSE-2.0 17 | * 18 | * Unless required by applicable law or agreed to in writing, software 19 | * distributed under the License is distributed on an "AS IS" BASIS, 20 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 21 | * See the License for the specific language governing permissions and 22 | * limitations under the License. 23 | */ 24 | 25 | 26 | DataMask = {}; 27 | 28 | DataMask.forReference = function(reference) 29 | { 30 | if (reference < 0 || reference > 7) 31 | { 32 | throw "System.ArgumentException"; 33 | } 34 | return DataMask.DATA_MASKS[reference]; 35 | } 36 | 37 | function DataMask000() 38 | { 39 | this.unmaskBitMatrix=function(bits, dimension) 40 | { 41 | for (var i = 0; i < dimension; i++) 42 | { 43 | for (var j = 0; j < dimension; j++) 44 | { 45 | if (this.isMasked(i, j)) 46 | { 47 | bits.flip(j, i); 48 | } 49 | } 50 | } 51 | } 52 | this.isMasked=function( i, j) 53 | { 54 | return ((i + j) & 0x01) == 0; 55 | } 56 | } 57 | 58 | function DataMask001() 59 | { 60 | this.unmaskBitMatrix=function(bits, dimension) 61 | { 62 | for (var i = 0; i < dimension; i++) 63 | { 64 | for (var j = 0; j < dimension; j++) 65 | { 66 | if (this.isMasked(i, j)) 67 | { 68 | bits.flip(j, i); 69 | } 70 | } 71 | } 72 | } 73 | this.isMasked=function( i, j) 74 | { 75 | return (i & 0x01) == 0; 76 | } 77 | } 78 | 79 | function DataMask010() 80 | { 81 | this.unmaskBitMatrix=function(bits, dimension) 82 | { 83 | for (var i = 0; i < dimension; i++) 84 | { 85 | for (var j = 0; j < dimension; j++) 86 | { 87 | if (this.isMasked(i, j)) 88 | { 89 | bits.flip(j, i); 90 | } 91 | } 92 | } 93 | } 94 | this.isMasked=function( i, j) 95 | { 96 | return j % 3 == 0; 97 | } 98 | } 99 | 100 | function DataMask011() 101 | { 102 | this.unmaskBitMatrix=function(bits, dimension) 103 | { 104 | for (var i = 0; i < dimension; i++) 105 | { 106 | for (var j = 0; j < dimension; j++) 107 | { 108 | if (this.isMasked(i, j)) 109 | { 110 | bits.flip(j, i); 111 | } 112 | } 113 | } 114 | } 115 | this.isMasked=function( i, j) 116 | { 117 | return (i + j) % 3 == 0; 118 | } 119 | } 120 | 121 | function DataMask100() 122 | { 123 | this.unmaskBitMatrix=function(bits, dimension) 124 | { 125 | for (var i = 0; i < dimension; i++) 126 | { 127 | for (var j = 0; j < dimension; j++) 128 | { 129 | if (this.isMasked(i, j)) 130 | { 131 | bits.flip(j, i); 132 | } 133 | } 134 | } 135 | } 136 | this.isMasked=function( i, j) 137 | { 138 | return (((URShift(i, 1)) + (j / 3)) & 0x01) == 0; 139 | } 140 | } 141 | 142 | function DataMask101() 143 | { 144 | this.unmaskBitMatrix=function(bits, dimension) 145 | { 146 | for (var i = 0; i < dimension; i++) 147 | { 148 | for (var j = 0; j < dimension; j++) 149 | { 150 | if (this.isMasked(i, j)) 151 | { 152 | bits.flip(j, i); 153 | } 154 | } 155 | } 156 | } 157 | this.isMasked=function( i, j) 158 | { 159 | var temp = i * j; 160 | return (temp & 0x01) + (temp % 3) == 0; 161 | } 162 | } 163 | 164 | function DataMask110() 165 | { 166 | this.unmaskBitMatrix=function(bits, dimension) 167 | { 168 | for (var i = 0; i < dimension; i++) 169 | { 170 | for (var j = 0; j < dimension; j++) 171 | { 172 | if (this.isMasked(i, j)) 173 | { 174 | bits.flip(j, i); 175 | } 176 | } 177 | } 178 | } 179 | this.isMasked=function( i, j) 180 | { 181 | var temp = i * j; 182 | return (((temp & 0x01) + (temp % 3)) & 0x01) == 0; 183 | } 184 | } 185 | function DataMask111() 186 | { 187 | this.unmaskBitMatrix=function(bits, dimension) 188 | { 189 | for (var i = 0; i < dimension; i++) 190 | { 191 | for (var j = 0; j < dimension; j++) 192 | { 193 | if (this.isMasked(i, j)) 194 | { 195 | bits.flip(j, i); 196 | } 197 | } 198 | } 199 | } 200 | this.isMasked=function( i, j) 201 | { 202 | return ((((i + j) & 0x01) + ((i * j) % 3)) & 0x01) == 0; 203 | } 204 | } 205 | 206 | DataMask.DATA_MASKS = new Array(new DataMask000(), new DataMask001(), new DataMask010(), new DataMask011(), new DataMask100(), new DataMask101(), new DataMask110(), new DataMask111()); 207 | 208 | -------------------------------------------------------------------------------- /static/qrcode/src/decoder.js: -------------------------------------------------------------------------------- 1 | /* 2 | Ported to JavaScript by Lazar Laszlo 2011 3 | 4 | lazarsoft@gmail.com, www.lazarsoft.info 5 | 6 | */ 7 | 8 | /* 9 | * 10 | * Copyright 2007 ZXing authors 11 | * 12 | * Licensed under the Apache License, Version 2.0 (the "License"); 13 | * you may not use this file except in compliance with the License. 14 | * You may obtain a copy of the License at 15 | * 16 | * http://www.apache.org/licenses/LICENSE-2.0 17 | * 18 | * Unless required by applicable law or agreed to in writing, software 19 | * distributed under the License is distributed on an "AS IS" BASIS, 20 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 21 | * See the License for the specific language governing permissions and 22 | * limitations under the License. 23 | */ 24 | 25 | 26 | Decoder={}; 27 | Decoder.rsDecoder = new ReedSolomonDecoder(GF256.QR_CODE_FIELD); 28 | 29 | Decoder.correctErrors=function( codewordBytes, numDataCodewords) 30 | { 31 | var numCodewords = codewordBytes.length; 32 | // First read into an array of ints 33 | var codewordsInts = new Array(numCodewords); 34 | for (var i = 0; i < numCodewords; i++) 35 | { 36 | codewordsInts[i] = codewordBytes[i] & 0xFF; 37 | } 38 | var numECCodewords = codewordBytes.length - numDataCodewords; 39 | try 40 | { 41 | Decoder.rsDecoder.decode(codewordsInts, numECCodewords); 42 | //var corrector = new ReedSolomon(codewordsInts, numECCodewords); 43 | //corrector.correct(); 44 | } 45 | catch ( rse) 46 | { 47 | throw rse; 48 | } 49 | // Copy back into array of bytes -- only need to worry about the bytes that were data 50 | // We don't care about errors in the error-correction codewords 51 | for (var i = 0; i < numDataCodewords; i++) 52 | { 53 | codewordBytes[i] = codewordsInts[i]; 54 | } 55 | } 56 | 57 | Decoder.decode=function(bits) 58 | { 59 | var parser = new BitMatrixParser(bits); 60 | var version = parser.readVersion(); 61 | var ecLevel = parser.readFormatInformation().ErrorCorrectionLevel; 62 | 63 | // Read codewords 64 | var codewords = parser.readCodewords(); 65 | 66 | // Separate into data blocks 67 | var dataBlocks = DataBlock.getDataBlocks(codewords, version, ecLevel); 68 | 69 | // Count total number of data bytes 70 | var totalBytes = 0; 71 | for (var i = 0; i < dataBlocks.length; i++) 72 | { 73 | totalBytes += dataBlocks[i].NumDataCodewords; 74 | } 75 | var resultBytes = new Array(totalBytes); 76 | var resultOffset = 0; 77 | 78 | // Error-correct and copy data blocks together into a stream of bytes 79 | for (var j = 0; j < dataBlocks.length; j++) 80 | { 81 | var dataBlock = dataBlocks[j]; 82 | var codewordBytes = dataBlock.Codewords; 83 | var numDataCodewords = dataBlock.NumDataCodewords; 84 | Decoder.correctErrors(codewordBytes, numDataCodewords); 85 | for (var i = 0; i < numDataCodewords; i++) 86 | { 87 | resultBytes[resultOffset++] = codewordBytes[i]; 88 | } 89 | } 90 | 91 | // Decode the contents of that stream of bytes 92 | var reader = new QRCodeDataBlockReader(resultBytes, version.VersionNumber, ecLevel.Bits); 93 | return reader; 94 | //return DecodedBitStreamParser.decode(resultBytes, version, ecLevel); 95 | } 96 | -------------------------------------------------------------------------------- /static/qrcode/src/errorlevel.js: -------------------------------------------------------------------------------- 1 | /* 2 | Ported to JavaScript by Lazar Laszlo 2011 3 | 4 | lazarsoft@gmail.com, www.lazarsoft.info 5 | 6 | */ 7 | 8 | /* 9 | * 10 | * Copyright 2007 ZXing authors 11 | * 12 | * Licensed under the Apache License, Version 2.0 (the "License"); 13 | * you may not use this file except in compliance with the License. 14 | * You may obtain a copy of the License at 15 | * 16 | * http://www.apache.org/licenses/LICENSE-2.0 17 | * 18 | * Unless required by applicable law or agreed to in writing, software 19 | * distributed under the License is distributed on an "AS IS" BASIS, 20 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 21 | * See the License for the specific language governing permissions and 22 | * limitations under the License. 23 | */ 24 | 25 | 26 | function ErrorCorrectionLevel(ordinal, bits, name) 27 | { 28 | this.ordinal_Renamed_Field = ordinal; 29 | this.bits = bits; 30 | this.name = name; 31 | this.__defineGetter__("Bits", function() 32 | { 33 | return this.bits; 34 | }); 35 | this.__defineGetter__("Name", function() 36 | { 37 | return this.name; 38 | }); 39 | this.ordinal=function() 40 | { 41 | return this.ordinal_Renamed_Field; 42 | } 43 | } 44 | 45 | ErrorCorrectionLevel.forBits=function( bits) 46 | { 47 | if (bits < 0 || bits >= FOR_BITS.length) 48 | { 49 | throw "ArgumentException"; 50 | } 51 | return FOR_BITS[bits]; 52 | } 53 | 54 | var L = new ErrorCorrectionLevel(0, 0x01, "L"); 55 | var M = new ErrorCorrectionLevel(1, 0x00, "M"); 56 | var Q = new ErrorCorrectionLevel(2, 0x03, "Q"); 57 | var H = new ErrorCorrectionLevel(3, 0x02, "H"); 58 | var FOR_BITS = new Array( M, L, H, Q); 59 | -------------------------------------------------------------------------------- /static/qrcode/src/formatinf.js: -------------------------------------------------------------------------------- 1 | /* 2 | Ported to JavaScript by Lazar Laszlo 2011 3 | 4 | lazarsoft@gmail.com, www.lazarsoft.info 5 | 6 | */ 7 | 8 | /* 9 | * 10 | * Copyright 2007 ZXing authors 11 | * 12 | * Licensed under the Apache License, Version 2.0 (the "License"); 13 | * you may not use this file except in compliance with the License. 14 | * You may obtain a copy of the License at 15 | * 16 | * http://www.apache.org/licenses/LICENSE-2.0 17 | * 18 | * Unless required by applicable law or agreed to in writing, software 19 | * distributed under the License is distributed on an "AS IS" BASIS, 20 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 21 | * See the License for the specific language governing permissions and 22 | * limitations under the License. 23 | */ 24 | 25 | 26 | var FORMAT_INFO_MASK_QR = 0x5412; 27 | var FORMAT_INFO_DECODE_LOOKUP = new Array(new Array(0x5412, 0x00), new Array(0x5125, 0x01), new Array(0x5E7C, 0x02), new Array(0x5B4B, 0x03), new Array(0x45F9, 0x04), new Array(0x40CE, 0x05), new Array(0x4F97, 0x06), new Array(0x4AA0, 0x07), new Array(0x77C4, 0x08), new Array(0x72F3, 0x09), new Array(0x7DAA, 0x0A), new Array(0x789D, 0x0B), new Array(0x662F, 0x0C), new Array(0x6318, 0x0D), new Array(0x6C41, 0x0E), new Array(0x6976, 0x0F), new Array(0x1689, 0x10), new Array(0x13BE, 0x11), new Array(0x1CE7, 0x12), new Array(0x19D0, 0x13), new Array(0x0762, 0x14), new Array(0x0255, 0x15), new Array(0x0D0C, 0x16), new Array(0x083B, 0x17), new Array(0x355F, 0x18), new Array(0x3068, 0x19), new Array(0x3F31, 0x1A), new Array(0x3A06, 0x1B), new Array(0x24B4, 0x1C), new Array(0x2183, 0x1D), new Array(0x2EDA, 0x1E), new Array(0x2BED, 0x1F)); 28 | var BITS_SET_IN_HALF_BYTE = new Array(0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4); 29 | 30 | 31 | function FormatInformation(formatInfo) 32 | { 33 | this.errorCorrectionLevel = ErrorCorrectionLevel.forBits((formatInfo >> 3) & 0x03); 34 | this.dataMask = (formatInfo & 0x07); 35 | 36 | this.__defineGetter__("ErrorCorrectionLevel", function() 37 | { 38 | return this.errorCorrectionLevel; 39 | }); 40 | this.__defineGetter__("DataMask", function() 41 | { 42 | return this.dataMask; 43 | }); 44 | this.GetHashCode=function() 45 | { 46 | return (this.errorCorrectionLevel.ordinal() << 3) | dataMask; 47 | } 48 | this.Equals=function( o) 49 | { 50 | var other = o; 51 | return this.errorCorrectionLevel == other.errorCorrectionLevel && this.dataMask == other.dataMask; 52 | } 53 | } 54 | 55 | FormatInformation.numBitsDiffering=function( a, b) 56 | { 57 | a ^= b; // a now has a 1 bit exactly where its bit differs with b's 58 | // Count bits set quickly with a series of lookups: 59 | return BITS_SET_IN_HALF_BYTE[a & 0x0F] + BITS_SET_IN_HALF_BYTE[(URShift(a, 4) & 0x0F)] + BITS_SET_IN_HALF_BYTE[(URShift(a, 8) & 0x0F)] + BITS_SET_IN_HALF_BYTE[(URShift(a, 12) & 0x0F)] + BITS_SET_IN_HALF_BYTE[(URShift(a, 16) & 0x0F)] + BITS_SET_IN_HALF_BYTE[(URShift(a, 20) & 0x0F)] + BITS_SET_IN_HALF_BYTE[(URShift(a, 24) & 0x0F)] + BITS_SET_IN_HALF_BYTE[(URShift(a, 28) & 0x0F)]; 60 | } 61 | 62 | FormatInformation.decodeFormatInformation=function( maskedFormatInfo) 63 | { 64 | var formatInfo = FormatInformation.doDecodeFormatInformation(maskedFormatInfo); 65 | if (formatInfo != null) 66 | { 67 | return formatInfo; 68 | } 69 | // Should return null, but, some QR codes apparently 70 | // do not mask this info. Try again by actually masking the pattern 71 | // first 72 | return FormatInformation.doDecodeFormatInformation(maskedFormatInfo ^ FORMAT_INFO_MASK_QR); 73 | } 74 | FormatInformation.doDecodeFormatInformation=function( maskedFormatInfo) 75 | { 76 | // Find the int in FORMAT_INFO_DECODE_LOOKUP with fewest bits differing 77 | var bestDifference = 0xffffffff; 78 | var bestFormatInfo = 0; 79 | for (var i = 0; i < FORMAT_INFO_DECODE_LOOKUP.length; i++) 80 | { 81 | var decodeInfo = FORMAT_INFO_DECODE_LOOKUP[i]; 82 | var targetInfo = decodeInfo[0]; 83 | if (targetInfo == maskedFormatInfo) 84 | { 85 | // Found an exact match 86 | return new FormatInformation(decodeInfo[1]); 87 | } 88 | var bitsDifference = this.numBitsDiffering(maskedFormatInfo, targetInfo); 89 | if (bitsDifference < bestDifference) 90 | { 91 | bestFormatInfo = decodeInfo[1]; 92 | bestDifference = bitsDifference; 93 | } 94 | } 95 | // Hamming distance of the 32 masked codes is 7, by construction, so <= 3 bits 96 | // differing means we found a match 97 | if (bestDifference <= 3) 98 | { 99 | return new FormatInformation(bestFormatInfo); 100 | } 101 | return null; 102 | } 103 | 104 | -------------------------------------------------------------------------------- /static/qrcode/src/gf256.js: -------------------------------------------------------------------------------- 1 | /* 2 | Ported to JavaScript by Lazar Laszlo 2011 3 | 4 | lazarsoft@gmail.com, www.lazarsoft.info 5 | 6 | */ 7 | 8 | /* 9 | * 10 | * Copyright 2007 ZXing authors 11 | * 12 | * Licensed under the Apache License, Version 2.0 (the "License"); 13 | * you may not use this file except in compliance with the License. 14 | * You may obtain a copy of the License at 15 | * 16 | * http://www.apache.org/licenses/LICENSE-2.0 17 | * 18 | * Unless required by applicable law or agreed to in writing, software 19 | * distributed under the License is distributed on an "AS IS" BASIS, 20 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 21 | * See the License for the specific language governing permissions and 22 | * limitations under the License. 23 | */ 24 | 25 | 26 | function GF256( primitive) 27 | { 28 | this.expTable = new Array(256); 29 | this.logTable = new Array(256); 30 | var x = 1; 31 | for (var i = 0; i < 256; i++) 32 | { 33 | this.expTable[i] = x; 34 | x <<= 1; // x = x * 2; we're assuming the generator alpha is 2 35 | if (x >= 0x100) 36 | { 37 | x ^= primitive; 38 | } 39 | } 40 | for (var i = 0; i < 255; i++) 41 | { 42 | this.logTable[this.expTable[i]] = i; 43 | } 44 | // logTable[0] == 0 but this should never be used 45 | var at0=new Array(1);at0[0]=0; 46 | this.zero = new GF256Poly(this, new Array(at0)); 47 | var at1=new Array(1);at1[0]=1; 48 | this.one = new GF256Poly(this, new Array(at1)); 49 | 50 | this.__defineGetter__("Zero", function() 51 | { 52 | return this.zero; 53 | }); 54 | this.__defineGetter__("One", function() 55 | { 56 | return this.one; 57 | }); 58 | this.buildMonomial=function( degree, coefficient) 59 | { 60 | if (degree < 0) 61 | { 62 | throw "System.ArgumentException"; 63 | } 64 | if (coefficient == 0) 65 | { 66 | return zero; 67 | } 68 | var coefficients = new Array(degree + 1); 69 | for(var i=0;i width || y < - 1 || y > height) 39 | { 40 | throw "Error.checkAndNudgePoints "; 41 | } 42 | nudged = false; 43 | if (x == - 1) 44 | { 45 | points[offset] = 0.0; 46 | nudged = true; 47 | } 48 | else if (x == width) 49 | { 50 | points[offset] = width - 1; 51 | nudged = true; 52 | } 53 | if (y == - 1) 54 | { 55 | points[offset + 1] = 0.0; 56 | nudged = true; 57 | } 58 | else if (y == height) 59 | { 60 | points[offset + 1] = height - 1; 61 | nudged = true; 62 | } 63 | } 64 | // Check and nudge points from end: 65 | nudged = true; 66 | for (var offset = points.length - 2; offset >= 0 && nudged; offset -= 2) 67 | { 68 | var x = Math.floor( points[offset]); 69 | var y = Math.floor( points[offset + 1]); 70 | if (x < - 1 || x > width || y < - 1 || y > height) 71 | { 72 | throw "Error.checkAndNudgePoints "; 73 | } 74 | nudged = false; 75 | if (x == - 1) 76 | { 77 | points[offset] = 0.0; 78 | nudged = true; 79 | } 80 | else if (x == width) 81 | { 82 | points[offset] = width - 1; 83 | nudged = true; 84 | } 85 | if (y == - 1) 86 | { 87 | points[offset + 1] = 0.0; 88 | nudged = true; 89 | } 90 | else if (y == height) 91 | { 92 | points[offset + 1] = height - 1; 93 | nudged = true; 94 | } 95 | } 96 | } 97 | 98 | 99 | 100 | GridSampler.sampleGrid3=function( image, dimension, transform) 101 | { 102 | var bits = new BitMatrix(dimension); 103 | var points = new Array(dimension << 1); 104 | for (var y = 0; y < dimension; y++) 105 | { 106 | var max = points.length; 107 | var iValue = y + 0.5; 108 | for (var x = 0; x < max; x += 2) 109 | { 110 | points[x] = (x >> 1) + 0.5; 111 | points[x + 1] = iValue; 112 | } 113 | transform.transformPoints1(points); 114 | // Quick check to see if points transformed to something inside the image; 115 | // sufficient to check the endpoints 116 | GridSampler.checkAndNudgePoints(image, points); 117 | try 118 | { 119 | for (var x = 0; x < max; x += 2) 120 | { 121 | var xpoint = (Math.floor( points[x]) * 4) + (Math.floor( points[x + 1]) * qrcode.width * 4); 122 | var bit = image[Math.floor( points[x])+ qrcode.width* Math.floor( points[x + 1])]; 123 | qrcode.imagedata.data[xpoint] = bit?255:0; 124 | qrcode.imagedata.data[xpoint+1] = bit?255:0; 125 | qrcode.imagedata.data[xpoint+2] = 0; 126 | qrcode.imagedata.data[xpoint+3] = 255; 127 | //bits[x >> 1][ y]=bit; 128 | if(bit) 129 | bits.set_Renamed(x >> 1, y); 130 | } 131 | } 132 | catch ( aioobe) 133 | { 134 | // This feels wrong, but, sometimes if the finder patterns are misidentified, the resulting 135 | // transform gets "twisted" such that it maps a straight line of points to a set of points 136 | // whose endpoints are in bounds, but others are not. There is probably some mathematical 137 | // way to detect this about the transformation that I don't know yet. 138 | // This results in an ugly runtime exception despite our clever checks above -- can't have 139 | // that. We could check each point's coordinates but that feels duplicative. We settle for 140 | // catching and wrapping ArrayIndexOutOfBoundsException. 141 | throw "Error.checkAndNudgePoints"; 142 | } 143 | } 144 | return bits; 145 | } 146 | 147 | GridSampler.sampleGridx=function( image, dimension, p1ToX, p1ToY, p2ToX, p2ToY, p3ToX, p3ToY, p4ToX, p4ToY, p1FromX, p1FromY, p2FromX, p2FromY, p3FromX, p3FromY, p4FromX, p4FromY) 148 | { 149 | var transform = PerspectiveTransform.quadrilateralToQuadrilateral(p1ToX, p1ToY, p2ToX, p2ToY, p3ToX, p3ToY, p4ToX, p4ToY, p1FromX, p1FromY, p2FromX, p2FromY, p3FromX, p3FromY, p4FromX, p4FromY); 150 | 151 | return GridSampler.sampleGrid3(image, dimension, transform); 152 | } 153 | -------------------------------------------------------------------------------- /static/qrcode/src/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | QRCODE 4 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 131 | 132 | 133 | 134 | 135 |
136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 |
145 |
146 | 147 | 148 | 149 | --------------------------------------------------------------------------------