├── assets ├── 0.png ├── 1.png ├── 2.png ├── 3.png ├── 4.png ├── logo.png └── logo-large.png ├── src ├── assets │ ├── search-baidu.png │ ├── search-bing.png │ ├── search-yahoo.png │ ├── network-error.png │ ├── search-google.png │ ├── search-dogedoge.png │ └── search-duckduckgo.svg ├── stylesheet │ ├── common.sass │ └── index.sass ├── theme │ ├── black.js │ └── default.js ├── services │ ├── config.js │ ├── storage.js │ ├── i18n.js │ ├── findOne.js │ ├── fetch.js │ ├── cookie.js │ ├── colors.json │ └── languages.js ├── lang │ ├── index.js │ ├── zh.js │ └── en.js ├── vuex │ ├── types.js │ ├── index.js │ └── modules │ │ ├── search.js │ │ ├── github.js │ │ └── bookmark.js ├── index.js ├── views │ ├── components │ │ ├── dialog-bookmark-edit.vue │ │ ├── bookmark.sass │ │ ├── setting.sass │ │ ├── search.sass │ │ ├── bookmark.vue │ │ ├── setting.vue │ │ ├── searchEngines.js │ │ ├── search.vue │ │ ├── github-trending.vue │ │ └── github-trending.sass │ ├── main.sass │ └── main.vue └── components │ ├── image-uploader │ ├── index.scss │ └── index.vue │ └── image-uploader-box │ └── index.vue ├── static ├── assets │ └── icon │ │ ├── icon.png │ │ ├── icon-128.png │ │ ├── icon-16.png │ │ └── icon-48.png ├── manifest.json ├── popup.html ├── popup.js └── popup.css ├── .babelrc ├── .markdownlintrc ├── __config__ ├── getLastCommit.js ├── log.js ├── global.config.js ├── database.js └── exec.js ├── postcss.config.js ├── app ├── index.js ├── routes │ ├── admin.js │ ├── index.js │ ├── login.js │ ├── qiniu.js │ ├── metadata.js │ └── github.js ├── models │ ├── metadata.js │ ├── index.js │ └── admin.js └── app.js ├── .sequelizerc ├── index.html ├── .gitignore ├── .env.example ├── .github └── FUNDING.yml ├── __database__ └── migrations │ ├── 20180114-create.admin.js │ └── 20171217-create-metadata.js ├── .eslintrc.yml ├── poi.config.js ├── README.md ├── package.json └── LICENSE /assets/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhuowenli/githuber/HEAD/assets/0.png -------------------------------------------------------------------------------- /assets/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhuowenli/githuber/HEAD/assets/1.png -------------------------------------------------------------------------------- /assets/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhuowenli/githuber/HEAD/assets/2.png -------------------------------------------------------------------------------- /assets/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhuowenli/githuber/HEAD/assets/3.png -------------------------------------------------------------------------------- /assets/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhuowenli/githuber/HEAD/assets/4.png -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhuowenli/githuber/HEAD/assets/logo.png -------------------------------------------------------------------------------- /assets/logo-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhuowenli/githuber/HEAD/assets/logo-large.png -------------------------------------------------------------------------------- /src/assets/search-baidu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhuowenli/githuber/HEAD/src/assets/search-baidu.png -------------------------------------------------------------------------------- /src/assets/search-bing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhuowenli/githuber/HEAD/src/assets/search-bing.png -------------------------------------------------------------------------------- /src/assets/search-yahoo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhuowenli/githuber/HEAD/src/assets/search-yahoo.png -------------------------------------------------------------------------------- /static/assets/icon/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhuowenli/githuber/HEAD/static/assets/icon/icon.png -------------------------------------------------------------------------------- /src/assets/network-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhuowenli/githuber/HEAD/src/assets/network-error.png -------------------------------------------------------------------------------- /src/assets/search-google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhuowenli/githuber/HEAD/src/assets/search-google.png -------------------------------------------------------------------------------- /src/assets/search-dogedoge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhuowenli/githuber/HEAD/src/assets/search-dogedoge.png -------------------------------------------------------------------------------- /static/assets/icon/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhuowenli/githuber/HEAD/static/assets/icon/icon-128.png -------------------------------------------------------------------------------- /static/assets/icon/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhuowenli/githuber/HEAD/static/assets/icon/icon-16.png -------------------------------------------------------------------------------- /static/assets/icon/icon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhuowenli/githuber/HEAD/static/assets/icon/icon-48.png -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-2"], 3 | "plugins": [ 4 | "transform-runtime" 5 | ], 6 | "comments": false 7 | } 8 | -------------------------------------------------------------------------------- /.markdownlintrc: -------------------------------------------------------------------------------- 1 | { 2 | "default": true, 3 | "MD007": { "indent": 4 }, 4 | "MD013": { "line_length": 300 }, 5 | "no-hard-tabs": true, 6 | "no-inline-html": false, 7 | "first-line-h1": false, 8 | "no-duplicate-header": false 9 | } 10 | -------------------------------------------------------------------------------- /__config__/getLastCommit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: 卓文理 3 | * Created: 2017-09-04 07:52:14 4 | * Last Modified: 2017-09-04 09:47:47 5 | * Modified By: 卓文理 6 | */ 7 | 8 | const exec = require('./exec'); 9 | 10 | module.exports = () => exec('git log -10 --stat --no-merges --pretty=oneline'); 11 | -------------------------------------------------------------------------------- /__config__/log.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: 卓文理 3 | * Created: 2017-09-04 07:52:32 4 | * Last Modified: 2017-09-04 09:48:16 5 | * Modified By: 卓文理 6 | */ 7 | 8 | const color = require('cli-color'); 9 | 10 | module.exports = function log(text, theme = 'green') { 11 | console.log(color[theme](text)); 12 | }; 13 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2017-04-28 17:34:06 5 | */ 6 | 7 | 'use strict'; 8 | 9 | module.exports = { 10 | plugins: [ 11 | require('autoprefixer')({ 12 | remove: false, 13 | browsers: ['iOS >= 7', 'Android >= 4.1'], 14 | }), 15 | ], 16 | }; 17 | 18 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2017-12-14 21:01:55 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const path = require('path'); 10 | const loadEnv = require('node-env-file'); 11 | 12 | loadEnv(path.resolve(__dirname, '../.env'), { raise: false }); 13 | 14 | require('babel-polyfill'); 15 | require('babel-register'); 16 | require('./app'); 17 | -------------------------------------------------------------------------------- /src/stylesheet/common.sass: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2018-01-18 18:17:02 5 | */ 6 | 7 | @mixin ellipsis 8 | text-overflow: ellipsis; 9 | overflow: hidden; 10 | white-space: nowrap; 11 | 12 | @mixin max-text($num) 13 | display: -webkit-box; 14 | -webkit-box-orient: vertical; 15 | -webkit-line-clamp: $num; 16 | overflow: hidden; -------------------------------------------------------------------------------- /.sequelizerc: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2017-12-17 15:09:16 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const path = require('path'); 10 | 11 | module.exports = { 12 | 'config': path.resolve('./__config__/database.js'), 13 | 'migrations-path': path.resolve('./__database__/migrations'), 14 | 'seeders-path': path.resolve('./__database__/seeds'), 15 | }; 16 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%= htmlWebpackPlugin.options.title %> 6 | <% if (htmlWebpackPlugin.options.description) { %> 7 | 8 | <% } %> 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /app/routes/admin.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2018-01-14 20:45:39 5 | */ 6 | 7 | 'use strict'; 8 | 9 | import Router from 'koa-router'; 10 | import models from '../models'; 11 | 12 | const router = new Router(); 13 | 14 | router.post('/admin', async (ctx, next) => { 15 | const data = ctx.request.body; 16 | const item = await models.admin.create({ ...data }); 17 | ctx.body = item; 18 | }); 19 | 20 | export default router; 21 | 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | *.log 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | pids 7 | *.pid 8 | *.seed 9 | *.pid.lock 10 | lib-cov 11 | coverage 12 | .nyc_output 13 | .grunt 14 | bower_components 15 | .lock-wscript 16 | build/Release 17 | node_modules/ 18 | jspm_packages/ 19 | typings/ 20 | .npm 21 | .eslintcache 22 | .node_repl_history 23 | *.tgz 24 | .yarn-integrity 25 | .env 26 | dist/ 27 | .vscode-upload.json 28 | *.pem 29 | *.crx 30 | __temp__ 31 | .DS_Store 32 | *.zip 33 | yarn.lock 34 | package-lock.json 35 | -------------------------------------------------------------------------------- /src/theme/black.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | export default { 5 | '--fontColor': '#ffffff', 6 | '--background': '#282828', 7 | '--backgroundLighter': '#232222', 8 | '--backgroundMedian': '#232222', 9 | '--backgroundLightest': '#000000', 10 | '--borderColor': '#252323', 11 | '--shadowColor': '#191414', 12 | '--green': '#67C23A', 13 | '--yellow': '#E6A23C', 14 | '--red': '#F56C6C', 15 | '--blue': '#409EFF', 16 | '--gray': '#8c8c8c', 17 | '--darkBlue': '#4285f4', 18 | }; 19 | -------------------------------------------------------------------------------- /__config__/global.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2017-12-17 14:39:21 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const baseConfig = { 10 | currentIP: require('ip').address(), 11 | appPort: 9001, 12 | webpackDevServerPort: 4000, 13 | }; 14 | 15 | module.exports = Object.assign(baseConfig, { 16 | appServerPath: ['http://', baseConfig.currentIP, ':', baseConfig.appPort].join(''), 17 | webpackServerPath: ['http://', baseConfig.currentIP, ':', baseConfig.webpackDevServerPort].join('') 18 | }); 19 | 20 | -------------------------------------------------------------------------------- /src/theme/default.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | export default { 5 | '--fontColor': '#303133', 6 | '--background': '#ffffff', 7 | '--backgroundLighter': '#f1f3f4', 8 | '--backgroundMedian': '#ebeff1', 9 | '--backgroundLightest': '#c0c4cc', 10 | '--borderColor': '#ebeef5', 11 | '--shadowColor': '#dddddd', 12 | '--green': '#67C23A', 13 | '--yellow': '#E6A23C', 14 | '--red': '#F56C6C', 15 | '--blue': '#409EFF', 16 | '--gray': '#909399', 17 | '--darkBlue': '#4285f4', 18 | '--orange': '#de5733', 19 | }; 20 | -------------------------------------------------------------------------------- /src/services/config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2018-01-20 21:14:27 5 | */ 6 | 7 | 'use strict'; 8 | 9 | export default { 10 | engineNavName: 'Web', 11 | engineName: 'google', 12 | since: 'daily', 13 | type: 'repositories', 14 | lang: [], 15 | locale: 'en', 16 | showBookmark: true, 17 | collapseBookmark: false, 18 | nightMode: false, 19 | openSearchInNewTab: false, 20 | openLinkInNewTab: false, 21 | openBookmarkInNewTab: false, 22 | showSetting: false, 23 | }; 24 | 25 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | NPM_CONFIG_LOGLEVEL=warn 3 | NPM_CONFIG_PRODUCTION=false 4 | NODE_MODULES_CACHE=false 5 | PORT=9001 6 | DEBUG=*,-babel,-koa*,-css-modules*,-engine*,-socket.io*,-mocha* 7 | 8 | # DB 9 | DATABASE_HOST=127.0.0.1 10 | DATABASE_NAME=githuber 11 | DATABASE_USERNAME=root 12 | DATABASE_PASSWORD=1234567890 13 | DATABASE_DIALECT=mysql 14 | 15 | # REDIS 16 | REDIS_URL=redis://localhost:6379/1 17 | 18 | # QINIU 19 | QINIU_ACCESS_KEY=123 20 | QINIU_SECRET_KEY=123 21 | QINIU_BUCKET=baidu 22 | QINIU_HOST=http://www.baidu.com 23 | 24 | 25 | # GITHUB 26 | CLIENT_ID=XXX 27 | CLIENT_SERECT=XXX 28 | 29 | -------------------------------------------------------------------------------- /src/services/storage.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2018-01-20 19:51:19 5 | */ 6 | 7 | 'use strict'; 8 | 9 | export default { 10 | getItem(key) { 11 | const item = window.localStorage.getItem(key); 12 | 13 | if (item) { 14 | return JSON.parse(item); 15 | } 16 | 17 | return null; 18 | }, 19 | setItem(key, val) { 20 | window.localStorage.setItem(key, JSON.stringify(val)); 21 | return key; 22 | }, 23 | removeItem(key) { 24 | window.localStorage.removeItem(key); 25 | return key; 26 | } 27 | }; 28 | 29 | -------------------------------------------------------------------------------- /src/lang/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2018-01-24 17:24:02 5 | */ 6 | 7 | 'use strict'; 8 | 9 | import Vue from 'vue'; 10 | import VueI18n from 'vue-i18n'; 11 | import storage from '../services/storage'; 12 | 13 | import enLocale from './en'; 14 | import zhLocale from './zh'; 15 | 16 | Vue.use(VueI18n); 17 | 18 | const config = storage.getItem('GITHUBER_CONFIGURATION'); 19 | const messages = { 20 | en: enLocale, 21 | zh: zhLocale 22 | }; 23 | 24 | const i18n = new VueI18n({ 25 | locale: (config && config.locale) || 'en', 26 | messages, 27 | }); 28 | 29 | export default i18n; 30 | -------------------------------------------------------------------------------- /__config__/database.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2017-12-17 14:57:42 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const path = require('path'); 10 | const loadEnv = require('node-env-file'); 11 | 12 | loadEnv(path.resolve(__dirname, '../.env'), { raise: false }); 13 | 14 | module.exports = { 15 | username: process.env.DATABASE_USERNAME, 16 | password: process.env.DATABASE_PASSWORD, 17 | database: process.env.DATABASE_NAME, 18 | host: process.env.DATABASE_HOST, 19 | dialect: process.env.DATABASE_DIALECT, 20 | seederStorage: 'sequelize', 21 | pool: { 22 | max: 10, 23 | min: 5, 24 | idle: 30000, 25 | }, 26 | // 修复中国时区问题 27 | timezone: '+08:00', 28 | }; 29 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['https://paypal.me/zhuowenli', 'https://www.buymeacoffee.com/zhuowenli'] 13 | -------------------------------------------------------------------------------- /src/vuex/types.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2018-01-18 09:46:00 5 | */ 6 | 7 | 'use strict'; 8 | 9 | // 搜索相关 10 | // =========================== 11 | export const RECEIVE_SUGGESTIONS = 'RECEIVE_SUGGESTIONS'; 12 | export const RECEIVE_SUPERPAGES = 'RECEIVE_SUPERPAGES'; 13 | export const RECEIVE_SEARCH_ENGINES = 'RECEIVE_SEARCH_ENGINES'; 14 | 15 | // GitHub 16 | // =========================== 17 | export const RECEIVE_GITHUB_TRENDINGS = 'RECEIVE_GITHUB_TRENDINGS'; 18 | 19 | // 书签 20 | // =========================== 21 | export const RECEIVE_BOOKMARKS = 'RECEIVE_BOOKMARKS'; 22 | export const SAVE_BOOKMARKS = 'SAVE_BOOKMARKS'; 23 | export const DELETE_BOOKMARKS = 'DELETE_BOOKMARKS'; 24 | export const RESTORE_BACKUP_BOOKMARKS = 'RESTORE_BACKUP_BOOKMARKS'; 25 | 26 | -------------------------------------------------------------------------------- /src/services/i18n.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2018-01-18 20:37:14 5 | */ 6 | 7 | 'use strict'; 8 | 9 | export default { 10 | Add: '添加', 11 | Edit: '编辑', 12 | Save: '保存', 13 | Baidu: '百度', 14 | Google: '谷歌', 15 | Bing: '必应', 16 | DuckDuckGo: 'DuckDuckGo', 17 | Web: '网页', 18 | Images: '图片', 19 | News: '新闻', 20 | Musics: '音乐', 21 | Videos: '视频', 22 | Maps: '地图', 23 | Sports: '运动', 24 | SearchFrom: '搜索自', 25 | NewTabs: '新标签页', 26 | Bookmark: '书签', 27 | AdditionalSearchEngine: '附加搜索引擎', 28 | openSearchInNewTap: '在新标签页中搜索', 29 | openLinkInNewTap: '在新标签页中打开链接', 30 | openBookmarkInNewTap: '在新标签页中打开书签', 31 | showBookmark: '显示书签栏', 32 | DeleteSuccess: '删除成功!', 33 | AddSuccess: '添加成功!', 34 | EditSuccess: '编辑成功!', 35 | }; 36 | 37 | -------------------------------------------------------------------------------- /app/models/metadata.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2018-01-15 22:10:38 5 | */ 6 | 7 | 'use strict'; 8 | 9 | export default function(sequelize, Sequelize) { 10 | const metadata = sequelize.define('metadata', { 11 | id: { 12 | type: Sequelize.INTEGER, 13 | primaryKey: true, 14 | autoIncrement: true, 15 | }, 16 | name: { 17 | type: Sequelize.STRING, 18 | allowNull: false, 19 | comment: 'key', 20 | }, 21 | content: { 22 | type: Sequelize.TEXT, 23 | allowNull: false, 24 | comment: '内容', 25 | }, 26 | }, { 27 | underscored: true, 28 | tableName: 'metadata', 29 | createdAt: 'created_at', 30 | updatedAt: 'updated_at', 31 | }); 32 | 33 | return metadata; 34 | } 35 | -------------------------------------------------------------------------------- /__config__/exec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: 卓文理 3 | * Created: 2017-09-04 09:38:50 4 | * Last Modified: 2017-09-04 09:39:23 5 | * Modified By: 卓文理 6 | */ 7 | 8 | const exec = require('child_process').exec; 9 | 10 | module.exports = (cmd, callback, ops) => { 11 | ops = ops || {}; 12 | 13 | return new Promise((resolve, reject) => { 14 | exec(cmd, ops, (err, stdout, stderr) => { 15 | if (err) { 16 | const errMsgs = [ 17 | '!!-- Exec Error --!!', 18 | `Cmd: [${cmd}]`, 19 | `Error: ${err}`, 20 | `Stderr: ${stderr}`, 21 | `Stdout: ${stdout}`, 22 | '!!-- Exec Error End --!!' 23 | ]; 24 | 25 | return reject(errMsgs.join('\n')); 26 | } 27 | 28 | resolve(String(stdout)); 29 | }); 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /src/vuex/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2018-01-17 18:03:38 5 | */ 6 | 7 | 'use strict'; 8 | 9 | import Vue from 'vue'; 10 | import Vuex from 'vuex'; 11 | import { post } from '../services/fetch'; 12 | 13 | import search from './modules/search'; 14 | import github from './modules/github'; 15 | import bookmark from './modules/bookmark'; 16 | 17 | Vue.use(Vuex); 18 | 19 | export const actions = { 20 | async fetchQiniuToken(store, key) { 21 | return post('http://githuber.zhuowenli.com/api/token/qiniu', { key }); 22 | // return post('http://192.168.0.100:9001/api/token/qiniu', { key }); 23 | }, 24 | }; 25 | export const getters = {}; 26 | export const mutations = {}; 27 | export default new Vuex.Store({ 28 | state: {}, 29 | actions, 30 | mutations, 31 | modules: { 32 | search, 33 | github, 34 | bookmark, 35 | }, 36 | strict: process.env.NODE_ENV !== 'production' 37 | }); 38 | -------------------------------------------------------------------------------- /app/routes/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2017-12-17 13:21:43 5 | */ 6 | 7 | 'use strict'; 8 | 9 | import Router from 'koa-router'; 10 | import compose from 'koa-compose'; 11 | import qiniuToken from './qiniu'; 12 | import admin from './admin'; 13 | import login from './login'; 14 | import metadata from './metadata'; 15 | import github from './github'; 16 | 17 | const router = new Router(); 18 | 19 | router.get('/', async (ctx) => { 20 | ctx.body = 'hello world!'; 21 | }); 22 | 23 | router.use('/api', qiniuToken.routes(), qiniuToken.allowedMethods()); 24 | router.use('/api', admin.routes(), admin.allowedMethods()); 25 | router.use('/api', login.routes(), login.allowedMethods()); 26 | router.use('/api', metadata.routes(), metadata.allowedMethods()); 27 | router.use('/api', github.routes(), github.allowedMethods()); 28 | 29 | export default function routes() { 30 | return compose([ 31 | router.routes(), 32 | router.allowedMethods() 33 | ]); 34 | } 35 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2018-01-17 15:44:45 5 | */ 6 | 7 | import Vue from 'vue'; 8 | import ElementUI from 'element-ui'; 9 | import 'element-ui/lib/theme-chalk/index.css'; 10 | import IScrollView from 'vue-iscroll-view'; 11 | import IScroll from 'iscroll'; 12 | 13 | import store from './vuex'; 14 | import './stylesheet/index.sass'; 15 | import main from './views/main.vue'; 16 | import i18n from './lang'; 17 | 18 | Vue.config.devtools = true; 19 | 20 | Vue.use(IScrollView, IScroll); 21 | Vue.use(ElementUI); 22 | 23 | export default new Vue({ 24 | i18n, 25 | store, 26 | render: c => c(main) 27 | }).$mount('#app'); 28 | 29 | if (chrome && chrome.runtime) { 30 | // 添加书签 31 | const { id } = chrome.runtime; 32 | 33 | if (id) { 34 | chrome.runtime.onMessage.addListener((request) => { 35 | const { name, message } = request; 36 | 37 | if (name === 'add' && message.id === id) { 38 | store.dispatch('bookmark/fetchBookmarks'); 39 | } 40 | }); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/models/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2017-12-17 16:16:03 5 | */ 6 | 7 | 'use strict'; 8 | 9 | import fs from 'fs'; 10 | import path from 'path'; 11 | import Sequelize from 'sequelize'; 12 | import config from '../../__config__/database'; 13 | 14 | const basename = path.basename(module.filename); 15 | const db = {}; 16 | 17 | let sequelize = null; 18 | 19 | if (config.use_env_variable) { 20 | sequelize = new Sequelize(process.env[config.use_env_variable]); 21 | } else { 22 | sequelize = new Sequelize(config.database, config.username, config.password, config); 23 | } 24 | 25 | fs.readdirSync(__dirname) 26 | .filter((file) => (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js')) 27 | .forEach((file) => { 28 | const model = sequelize.import(path.join(__dirname, file)); 29 | db[model.name] = model; 30 | }); 31 | 32 | Object.keys(db).forEach((modelName) => { 33 | if ('associate' in db[modelName]) { 34 | db[modelName].associate(db); 35 | } 36 | }); 37 | 38 | db.sequelize = sequelize; 39 | db.Sequelize = Sequelize; 40 | 41 | export default db; 42 | -------------------------------------------------------------------------------- /__database__/migrations/20180114-create.admin.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2018-01-14 20:20:19 5 | */ 6 | 7 | 'use strict'; 8 | 9 | module.exports = { 10 | up(queryInterface, Sequelize) { 11 | return queryInterface.createTable('admin', { 12 | id: { 13 | type: Sequelize.INTEGER, 14 | primaryKey: true, 15 | autoIncrement: true, 16 | }, 17 | name: { 18 | type: Sequelize.STRING, 19 | allowNull: false, 20 | comment: '名称', 21 | }, 22 | password: { 23 | type: Sequelize.STRING, 24 | allowNull: false, 25 | comment: '密码', 26 | }, 27 | created_at: { 28 | type: Sequelize.DATE, 29 | comment: '创建时间', 30 | }, 31 | updated_at: { 32 | type: Sequelize.DATE, 33 | comment: '更新时间', 34 | }, 35 | }); 36 | }, 37 | down(queryInterface) { 38 | return queryInterface.dropTable('admin'); 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /__database__/migrations/20171217-create-metadata.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2018-01-15 22:08:06 5 | */ 6 | 7 | 'use strict'; 8 | 9 | module.exports = { 10 | up(queryInterface, Sequelize) { 11 | return queryInterface.createTable('metadata', { 12 | id: { 13 | type: Sequelize.INTEGER, 14 | primaryKey: true, 15 | autoIncrement: true, 16 | }, 17 | name: { 18 | type: Sequelize.STRING, 19 | allowNull: false, 20 | comment: 'key', 21 | }, 22 | content: { 23 | type: Sequelize.TEXT, 24 | allowNull: false, 25 | comment: '内容', 26 | }, 27 | created_at: { 28 | type: Sequelize.DATE, 29 | comment: '创建时间', 30 | }, 31 | updated_at: { 32 | type: Sequelize.DATE, 33 | comment: '更新时间', 34 | }, 35 | }); 36 | }, 37 | down(queryInterface) { 38 | return queryInterface.dropTable('metadata'); 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | root: true 2 | 3 | env: 4 | es6: true 5 | browser: true 6 | jest: true 7 | 8 | globals: 9 | $: true 10 | chrome: true 11 | 12 | extends: 13 | - airbnb-base 14 | - plugin:vue/base 15 | 16 | parserOptions: 17 | sourceType: module 18 | parser: 'babel-eslint' 19 | 20 | rules: 21 | indent: 22 | - error 23 | - 4 24 | linebreak-style: 25 | - error 26 | - unix 27 | quotes: 28 | - error 29 | - single 30 | semi: 31 | - error 32 | - always 33 | camelcase: 0 34 | strict: 0 35 | no-eval: 0 36 | max-len: [1, { code: 120 }] 37 | global-require: 0 38 | no-unused-vars: 1 39 | func-names: 0 40 | no-console: 0 41 | arrow-parens: 0 42 | comma-dangle: ['error', 'only-multiline'] 43 | no-param-reassign: 0 44 | space-before-function-paren: 0 45 | prefer-destructuring: 0 46 | prefer-promise-reject-errors: 0 47 | object-curly-newline: 0 48 | import/no-unresolved: [0, {commonjs: true, amd: true}] 49 | import/no-extraneous-dependencies: 0 50 | import/no-absolute-path: 0 51 | import/prefer-default-export: 0 52 | import/extensions: ['off', 'always', { 53 | 'js': 'never', 54 | 'vue': 'never' 55 | }] 56 | -------------------------------------------------------------------------------- /app/routes/login.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2018-01-14 21:09:44 5 | */ 6 | 7 | 'use strict'; 8 | 9 | import crypto from 'crypto'; 10 | import Router from 'koa-router'; 11 | import models from '../models'; 12 | 13 | const router = new Router(); 14 | 15 | router.post('/login', async (ctx, next) => { 16 | const salt = 'abcdefghijklmnopqrstuvwxyz'; 17 | const { username, password } = ctx.request.body; 18 | 19 | if (!username || !password) { 20 | ctx.status = 401; 21 | ctx.body = { 22 | message: '请输入账号密码!' 23 | }; 24 | ctx.session.user = null; 25 | return false; 26 | } 27 | 28 | const item = await models.admin.findOne({ 29 | where: { name: username } 30 | }); 31 | 32 | const digest = crypto.pbkdf2Sync(password, salt, 4096, 60, 'sha1').toString('base64'); 33 | 34 | if (!item || item.password !== digest) { 35 | ctx.status = 401; 36 | ctx.body = { 37 | message: '账号密码有误!' 38 | }; 39 | ctx.session.user = null; 40 | return false; 41 | } 42 | 43 | ctx.session.user = username; 44 | ctx.body = item; 45 | 46 | return true; 47 | }); 48 | 49 | export default router; 50 | -------------------------------------------------------------------------------- /static/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "GITHUBER - New Tab", 4 | "description": "Display Github Trending repositories on New Tab Extensions", 5 | "version": "1.8.0", 6 | "icons": { 7 | "16": "assets/icon/icon-16.png", 8 | "48": "assets/icon/icon-48.png", 9 | "128": "assets/icon/icon-128.png" 10 | }, 11 | "action": { 12 | "default_icon": { 13 | "16": "assets/icon/icon-16.png", 14 | "48": "assets/icon/icon-48.png", 15 | "128": "assets/icon/icon-128.png" 16 | }, 17 | "default_popup": "./popup.html" 18 | }, 19 | "content_security_policy": { 20 | "extension_pages": "script-src 'self'; object-src 'self'; connect-src 'self' https://gtrend.infly.io http://suggestion.baidu.com http://nssug.baidu.com http://githuber.zhuowenli.com http://up-z0.qiniu.com" 21 | }, 22 | "chrome_url_overrides": { 23 | "newtab": "./index.html" 24 | }, 25 | "permissions": [ 26 | "tabs" 27 | ], 28 | "host_permissions": [ 29 | "https://gtrend.infly.io/*", 30 | "http://suggestion.baidu.com/*", 31 | "http://nssug.baidu.com/*", 32 | "http://githuber.zhuowenli.com/*", 33 | "http://up-z0.qiniu.com/*" 34 | ], 35 | "minimum_chrome_version": "88" 36 | } -------------------------------------------------------------------------------- /app/routes/qiniu.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2017-12-17 14:28:20 5 | */ 6 | 7 | 'use strict'; 8 | 9 | import moment from 'moment'; 10 | import Router from 'koa-router'; 11 | import qiniu from 'qiniu'; 12 | 13 | const router = new Router(); 14 | 15 | router.post('/token/qiniu', async (ctx) => { 16 | const data = ctx.request.body || {}; 17 | const bucket = process.env.QINIU_BUCKET; 18 | const accessKey = process.env.QINIU_ACCESS_KEY; 19 | const secretKey = process.env.QINIU_SECRET_KEY; 20 | 21 | let uploadKey = data.key; 22 | if (!uploadKey) { 23 | const momentDate = moment(); 24 | uploadKey = `tmp/${momentDate.format('YYYYMMDD')}/${Math.random(5).toString().slice(2, 12)}`; 25 | } 26 | const putPolicy = new qiniu.rs.PutPolicy({ 27 | scope: `${bucket}:${uploadKey}`, 28 | }); 29 | putPolicy.returnBody = '{"bucket":$(bucket),"key":$(key),"width":$(imageInfo.width),"height":$(imageInfo.height),"avinfo":$(avinfo)}'; 30 | 31 | const mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 32 | const token = putPolicy.uploadToken(mac); 33 | const fileUrl = `${process.env.QINIU_HOST}/${uploadKey}`; 34 | 35 | ctx.body = { 36 | token, 37 | key: uploadKey, 38 | url: fileUrl 39 | }; 40 | }); 41 | 42 | export default router; 43 | -------------------------------------------------------------------------------- /app/routes/metadata.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2018-01-15 22:11:45 5 | */ 6 | 7 | 'use strict'; 8 | 9 | import Router from 'koa-router'; 10 | import models from '../models'; 11 | 12 | const router = new Router(); 13 | 14 | router.get('/metadata/:name', async (ctx, next) => { 15 | const { name } = ctx.params; 16 | 17 | if (!name) { 18 | ctx.status = 401; 19 | ctx.body = { 20 | message: '请输入正确的name字段' 21 | }; 22 | return false; 23 | } 24 | 25 | const item = await models.metadata.findOne({ 26 | where: { name } 27 | }); 28 | 29 | if (!item) { 30 | ctx.body = { 31 | name, 32 | content: '' 33 | }; 34 | return false; 35 | } 36 | 37 | ctx.body = item; 38 | 39 | return true; 40 | }); 41 | 42 | // 添加 43 | router.post('/metadata/:name', async (ctx, next) => { 44 | const { name } = ctx.params; 45 | const data = ctx.request.body; 46 | 47 | let item = await models.metadata.findOne({ 48 | where: { name }, 49 | }); 50 | 51 | if (item) { 52 | await item.update(data); 53 | } else { 54 | item = await models.metadata.create({ 55 | name, 56 | ...data 57 | }); 58 | } 59 | 60 | ctx.body = item; 61 | }); 62 | 63 | export default router; 64 | -------------------------------------------------------------------------------- /app/models/admin.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2018-01-14 20:40:46 5 | */ 6 | 7 | 'use strict'; 8 | 9 | import crypto from 'crypto'; 10 | 11 | export default function(sequelize, Sequelize) { 12 | const admin = sequelize.define('admin', { 13 | id: { 14 | type: Sequelize.INTEGER, 15 | primaryKey: true, 16 | autoIncrement: true, 17 | }, 18 | name: { 19 | type: Sequelize.STRING, 20 | allowNull: false, 21 | validate: { 22 | notEmpty: true, 23 | len: [1, 20], 24 | }, 25 | }, 26 | password: { 27 | type: Sequelize.STRING, 28 | allowNull: false, 29 | comment: '密码', 30 | }, 31 | }, { 32 | underscored: true, 33 | tableName: 'admin', 34 | }); 35 | 36 | function hasSecurePassword(user) { 37 | const salt = 'abcdefghijklmnopqrstuvwxyz'; 38 | 39 | user.password = crypto.pbkdf2Sync(user.password, salt, 4096, 60, 'sha1').toString('base64'); 40 | 41 | return sequelize.Promise.resolve(user); 42 | } 43 | 44 | admin.beforeCreate((user) => { 45 | if (user.password) { 46 | return hasSecurePassword(user); 47 | } 48 | }); 49 | 50 | admin.beforeUpdate((user) => { 51 | if (user.password) { 52 | return hasSecurePassword(user); 53 | } 54 | }); 55 | 56 | return admin; 57 | } 58 | 59 | -------------------------------------------------------------------------------- /static/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
添加书签
10 |
11 |
12 |
13 | 14 |
15 |
16 | 17 |
18 |
名称必填
19 |
20 |
21 |
22 | 23 |
24 |
25 | 26 |
27 |
网址必填
28 |
29 |
30 | 31 |
32 |
33 | 34 |
添加成功!
35 |
36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /app/routes/github.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2018-05-06 13:21:58 5 | */ 6 | 7 | 'use strict'; 8 | 9 | 10 | import Router from 'koa-router'; 11 | import axios from 'axios'; 12 | 13 | const router = new Router(); 14 | const id = process.env.CLIENT_ID; 15 | const sercet = process.env.CLIENT_SERECT; 16 | 17 | router.get('/github/login', async (ctx, next) => { 18 | // 去到github授权页 19 | const dataStr = (new Date()).valueOf(); 20 | const path = `https://github.com/login/oauth/authorize?client_id=${id}&scope=['user']&state=${dataStr}`; 21 | 22 | ctx.redirect(path); // 送到授权的中间页 23 | }); 24 | 25 | // 添加 26 | router.get('/github/callback', async (ctx, next) => { 27 | const code = ctx.query.code; 28 | const data = await axios({ // 没有fetch,需要装node-fetch 29 | url: 'https://github.com/login/oauth/access_token', 30 | method: 'post', 31 | headers: { 32 | 'Content-Type': 'application/json' 33 | }, 34 | data: { 35 | client_id: id, 36 | client_secret: sercet, 37 | code 38 | }, 39 | }).then(res => res.data); 40 | 41 | const args = data.split('&'); 42 | const arg = args[0].split('='); 43 | const access_token = arg[1]; 44 | 45 | const url = `https://api.github.com/user?access_token=${access_token}`; // token就是oauth令牌环 46 | 47 | await axios.get(url) 48 | .then(res => { 49 | ctx.body = res.data; 50 | }); 51 | }); 52 | 53 | export default router; 54 | -------------------------------------------------------------------------------- /src/services/findOne.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2017-09-21 11:25:45 5 | */ 6 | 7 | 'use strict'; 8 | 9 | /** 10 | * 根据内容查询数组 11 | * 12 | * @export 13 | * @param {Array} arrs 数组 14 | * @param {any} where 查询内容 15 | * @returns {Object} result, index 16 | */ 17 | export default function findOne(arrs, where) { 18 | if (!Array.isArray(arrs) || !arrs.length) return {}; 19 | 20 | if (typeof where === 'object' && !Array.isArray(where) && Object.keys(where).length) { 21 | const keys = Object.keys(where); 22 | let result = null; 23 | let index = -1; 24 | 25 | arrs.map((arr, inx) => { 26 | let flag = true; 27 | 28 | keys.map(key => { 29 | if (arr[key] !== where[key]) flag = false; 30 | return key; 31 | }); 32 | 33 | if (flag) { 34 | result = arr; 35 | index = inx; 36 | } 37 | 38 | return arr; 39 | }); 40 | 41 | return { result, index }; 42 | } 43 | 44 | const index = arrs.indexOf(where); 45 | 46 | if (index > 1) { 47 | return { result: where, index }; 48 | } 49 | 50 | return {}; 51 | } 52 | 53 | // test 54 | // const data = [ 55 | // { 56 | // id: 1, 57 | // name: '张三' 58 | // }, 59 | // { 60 | // id: 2, 61 | // name: '李四' 62 | // }, 63 | // ]; 64 | 65 | // console.log(findOne(data, { id: 1 })); 66 | // console.log(findOne(data, { name: '李四' })); 67 | -------------------------------------------------------------------------------- /static/popup.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2018-01-23 14:26:31 5 | */ 6 | 7 | 'use strict'; 8 | 9 | /* eslint-disable */ 10 | 11 | var form = {}; 12 | var $title = $('#title'); 13 | var $url = $('#url'); 14 | var $img = $('#img'); 15 | var $button = $('#button'); 16 | var $notification = $('#notification'); 17 | 18 | chrome.tabs.query({ active: true }, function(tabs) { 19 | if (!tabs.length) return; 20 | var tab = tabs[0]; 21 | 22 | $title.val(tab.title); 23 | $url.val(tab.url); 24 | 25 | if (tab.favIconUrl) { 26 | $img.html('') 27 | } else { 28 | $img.html('' + tab.title[0] + '') 29 | } 30 | }); 31 | 32 | $button.on('click', function() { 33 | var name = $title.val(); 34 | var url = $url.val(); 35 | 36 | if (!name) { 37 | return $('#titleError').show(); 38 | } 39 | if (!url) { 40 | return $('#urlError').show(); 41 | } 42 | 43 | var storage = localStorage.getItem('GITHUBER_BOOKMARKS') || '[]'; 44 | var data = JSON.parse(storage); 45 | 46 | data.push({ 47 | name: name, 48 | logo: $img.find('img') && $img.find('img').attr('src') || '', 49 | url: url, 50 | }); 51 | 52 | localStorage.setItem('GITHUBER_BOOKMARKS', JSON.stringify(data)); 53 | 54 | chrome.runtime.sendMessage({ 55 | name: 'add', 56 | message: { 57 | id: chrome.runtime.id, 58 | } 59 | }, function(o) { 60 | $notification.addClass('show'); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /app/app.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2017-12-14 21:01:19 5 | */ 6 | 7 | 'use strict'; 8 | 9 | import debug from 'debug'; 10 | import Koa from 'koa'; 11 | import logger from 'koa-logger'; 12 | import convert from 'koa-convert'; 13 | import bodyParser from 'koa-bodyparser'; 14 | // import koaSession from 'koa-session'; 15 | import cors from 'koa2-cors'; 16 | import routes from './routes'; 17 | import globalConfig from '../__config__/global.config'; 18 | 19 | const app = new Koa(); 20 | const log = debug('app:log'); 21 | // const warn = debug('app:warn'); 22 | const error = debug('app:error'); 23 | 24 | // trust proxy 25 | app.proxy = true; 26 | app.keys = ['d0n7', '7311', '4ny0n3']; 27 | 28 | app.use(bodyParser()); 29 | app.use(convert(logger())); 30 | app.use(cors()); 31 | 32 | // 外层处理 33 | app.use(async (ctx, next) => { 34 | try { 35 | await next(); 36 | } catch (err) { 37 | ctx.status = err.status || 500; 38 | ctx.body = err.stack; 39 | ctx.app.emit('error', err, ctx); 40 | } 41 | }); 42 | 43 | // app.use(koaSession({ 44 | // key: 'koa:sess', 45 | // maxAge: 7200000, 46 | // overwrite: true, 47 | // httpOnly: true, 48 | // signed: true, 49 | // }, app)); 50 | 51 | // 路由配置 52 | app.use(routes()); 53 | 54 | app.on('error', (err) => { 55 | error('server error: %s', err.stack); 56 | }); 57 | 58 | const appUrl = ['http://', globalConfig.currentIP, ':', globalConfig.appPort].join(''); 59 | app.listen(globalConfig.appPort); 60 | log(`App is now listening on ${appUrl}`); 61 | 62 | process.on('SIGINT', () => { 63 | process.exit(); 64 | }); 65 | 66 | export default app; 67 | -------------------------------------------------------------------------------- /poi.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2017-08-22 15:05:24 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const path = require('path'); 10 | 11 | module.exports = (options, req) => ({ 12 | entry: 'src/index.js', 13 | host: '0.0.0.0', 14 | filename: { 15 | js: '[name].js', 16 | css: '[name].css', 17 | static: 'static/[name]-[hash].[ext]', 18 | }, 19 | html: { 20 | title: 'New Tabs', 21 | template: 'index.html', 22 | }, 23 | resolve: true, 24 | sourceMap: !!options.dev, 25 | extendWebpack(cfg) { 26 | // Disable progress bar while building 27 | // cfg.plugins.delete('progress-bar'); 28 | cfg.module 29 | .rule('scss') 30 | .use('sass-loader') 31 | .tap((opt) => { 32 | opt.implementation = require('sass'); 33 | opt.includePaths = [path.resolve(__dirname, './node_modules')]; 34 | return opt; 35 | }); 36 | cfg.module 37 | .rule('sass') 38 | .use('sass-loader') 39 | .tap((opt) => { 40 | opt.implementation = require('sass'); 41 | opt.includePaths = [path.resolve(__dirname, './node_modules')]; 42 | return opt; 43 | }); 44 | }, 45 | webpack(cfg) { 46 | cfg.resolve.modules.push(path.resolve('src')); 47 | cfg.resolve.alias.vue$ = 'vue/dist/vue.js'; 48 | 49 | if (!options.dev) { 50 | cfg.devtool = false; 51 | cfg.bail = true; 52 | } else { 53 | cfg.devtool = 'source-map'; 54 | } 55 | 56 | return cfg; 57 | }, 58 | vendor: options.mode === 'test' ? false : Object.keys(require('./package.json').dependencies), 59 | }); 60 | -------------------------------------------------------------------------------- /static/popup.css: -------------------------------------------------------------------------------- 1 | body{ 2 | font-family: "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif; 3 | width: 250px; 4 | height: 300px; 5 | -webkit-font-smoothing: antialiased; 6 | padding: 0; 7 | margin: 0; 8 | } 9 | 10 | .title{ 11 | width: 100%; 12 | height: 40px; 13 | line-height: 40px; 14 | padding-left: 10px; 15 | } 16 | 17 | #img{ 18 | display: block; 19 | width: 50px; 20 | height: 50px; 21 | margin: 0 auto; 22 | border-radius: 50%; 23 | overflow: hidden; 24 | } 25 | 26 | #img img{ 27 | display: block; 28 | width: 100%; 29 | height: 100%; 30 | } 31 | #img text{ 32 | display: flex; 33 | justify-content: center; 34 | align-items: center; 35 | width: 100%; 36 | height: 100%; 37 | background: #909399; 38 | border-radius: 50%; 39 | color: var(--background); 40 | font-size: 20px; 41 | user-select: none; 42 | } 43 | 44 | .el-form{ 45 | padding: 20px 10px 0; 46 | } 47 | .el-form-item__label{ 48 | width: 40px; 49 | } 50 | .el-form-item__content{ 51 | margin-left: 40px; 52 | } 53 | .el-form-item__error{ 54 | display: none; 55 | } 56 | 57 | #button{ 58 | display: block; 59 | width: 80%; 60 | margin: 0 auto; 61 | } 62 | 63 | #notification{ 64 | position: fixed; 65 | top: 0; 66 | left: 0; 67 | right: 0; 68 | bottom: 0; 69 | display: none; 70 | background: var(--background); 71 | flex-direction: column; 72 | justify-content: center; 73 | align-items: center; 74 | padding: 0; 75 | } 76 | 77 | #notification.show{ 78 | display: flex; 79 | } 80 | 81 | #notification .el-icon-success{ 82 | color: var(--green); 83 | font-size: 28px; 84 | } 85 | 86 | #notification .content{ 87 | font-size: 14px; 88 | padding-top: 10px; 89 | } -------------------------------------------------------------------------------- /src/lang/zh.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2018-01-18 20:37:14 5 | */ 6 | 7 | 'use strict'; 8 | 9 | export default { 10 | // search 11 | Baidu: '百度', 12 | Google: '谷歌', 13 | Bing: '必应', 14 | Yahoo: '雅虎', 15 | DuckDuckGo: 'DuckDuckGo', 16 | DogeDoge: 'DogeDoge', 17 | Web: '网页', 18 | Images: '图片', 19 | News: '新闻', 20 | Musics: '音乐', 21 | Videos: '视频', 22 | Maps: '地图', 23 | Sports: '运动', 24 | Search: '搜索', 25 | SearchFrom: '搜索自', 26 | 27 | NewTabs: '新标签页', 28 | Bookmark: '书签', 29 | 30 | // setting 31 | Setting: '设置', 32 | 33 | // 目标打开方式 34 | GoalOpeningMethod: '目标打开方式', 35 | AdditionalSearchEngine: '附加搜索引擎', 36 | openSearchInNewTab: '在新标签页中搜索', 37 | openLinkInNewTab: '在新标签页中打开链接', 38 | openBookmarkInNewTab: '在新标签页中打开书签', 39 | GoalOpeningMethodNote: '备注:以上打开方式仅作用于 Githuber 内部模块', 40 | 41 | // 视图 42 | View: '视图', 43 | showBookmark: '显示书签栏', 44 | collapseBookmark: '折叠书签栏', 45 | Theme: '夜间模式', 46 | Language: '语言', 47 | importTopSites: '导入最常访问', 48 | import: '导入', 49 | 50 | // 数据备份 51 | DataBackup: '数据备份', 52 | RestoreBackup: '恢复备份', 53 | 54 | // 关于 55 | About: '关于', 56 | AboutAuthor: '关于作者', 57 | Feedback: '意见反馈', 58 | SourceCode: '项目源码', 59 | 60 | // dialog 61 | Add: '添加', 62 | Edit: '编辑', 63 | Save: '保存', 64 | Cancel: '取消', 65 | Name: '名字', 66 | Url: '链接', 67 | Logo: 'Logo', 68 | DeleteSuccess: '删除成功!', 69 | AddSuccess: '添加成功!', 70 | EditSuccess: '编辑成功!', 71 | KeepUpload: '继续上传', 72 | Delete: '删除', 73 | UploadNote: '将文件拖到此处,或
点击上传', 74 | RestoreBackupSuccess: '备份恢复成功!', 75 | 76 | // Trending 77 | Today: '今日', 78 | ThisWeek: '本周', 79 | ThisMonth: '本月', 80 | AllLanguages: '所有语言', 81 | 82 | // network error 83 | NetworkErrorTitle: '网络异常', 84 | NetworkErrorContent: '如果你不知道发生了什么事,请点击刷新试试。', 85 | Refresh: '刷新', 86 | }; 87 | 88 | -------------------------------------------------------------------------------- /src/views/components/dialog-bookmark-edit.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 57 | 58 | 62 | -------------------------------------------------------------------------------- /src/components/image-uploader/index.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /* 3 | * @Author: 卓文理 4 | * @Email: 531840344@qq.com 5 | * @Date: 2017-02-04 15:10:36 6 | */ 7 | 8 | @import '../../stylesheet/common.sass'; 9 | 10 | .image-uploader{ 11 | img{ 12 | display: block; 13 | width: 100%; 14 | height: 100%; 15 | } 16 | .el-upload-dragger{ 17 | width: 180px; 18 | height: 180px; 19 | &__interact{ 20 | position: absolute; 21 | bottom: 0; 22 | left: 0; 23 | width: 100%; 24 | height: 100%; 25 | display: flex; 26 | align-items: center; 27 | justify-content: center; 28 | flex-direction: row; 29 | background-color: rgba(0,0,0,.72); 30 | opacity: 0; 31 | transition: opacity .3s ease; 32 | &.show{ 33 | opacity: 1; 34 | } 35 | .btn{ 36 | display: inline-block; 37 | width: 56px; 38 | color: var(--background); 39 | font-size: 14px; 40 | cursor: pointer; 41 | vertical-align: middle; 42 | transition: transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s; 43 | i{ 44 | color: var(--background); 45 | display: block; 46 | font-size: 24px; 47 | line-height: inherit; 48 | margin: 0 auto; 49 | } 50 | span{ 51 | opacity: 0; 52 | transition: opacity .15s linear; 53 | } 54 | &:hover{ 55 | transform: translateY(-13px); 56 | span{ 57 | opacity: 1; 58 | } 59 | } 60 | } 61 | } 62 | &__inner{ 63 | color: #888; 64 | line-height: 1.5; 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /src/lang/en.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2018-01-18 20:37:14 5 | */ 6 | 7 | 'use strict'; 8 | 9 | export default { 10 | // search 11 | Baidu: 'Baidu', 12 | Google: 'Google', 13 | Bing: 'Bing', 14 | DuckDuckGo: 'DuckDuckGo', 15 | Yahoo: 'Yahoo', 16 | DogeDoge: 'DogeDoge', 17 | Web: 'Web', 18 | Images: 'Images', 19 | News: 'News', 20 | Musics: 'Musics', 21 | Videos: 'Videos', 22 | Maps: 'Maps', 23 | Sports: 'Sports', 24 | Search: 'Search', 25 | SearchFrom: 'Search From', 26 | 27 | NewTabs: 'New Tabs', 28 | Bookmark: 'Bookmark', 29 | 30 | // setting 31 | Setting: 'Setting', 32 | 33 | // 目标打开方式 34 | GoalOpeningMethod: 'Goal opening method', 35 | AdditionalSearchEngine: 'Additional search engine', 36 | openSearchInNewTab: 'Open search in new tab', 37 | openLinkInNewTab: 'Open link in new tab', 38 | openBookmarkInNewTab: 'Open bookmark in new tab', 39 | GoalOpeningMethodNote: 'Note: The above open method only works on Githuber internal modules.', 40 | 41 | // 视图 42 | View: 'View', 43 | showBookmark: 'Show Bookmark', 44 | collapseBookmark: 'Collapse Bookmark', 45 | Theme: 'Night Mode', 46 | Language: 'Language', 47 | importTopSites: 'Import top sites', 48 | import: 'Import', 49 | 50 | // 数据备份 51 | DataBackup: 'Data Backup', 52 | RestoreBackup: 'Restore Backup', 53 | 54 | // 关于 55 | About: 'About', 56 | AboutAuthor: 'About author', 57 | Feedback: 'Feedback', 58 | SourceCode: 'Source code', 59 | 60 | // dialog 61 | Add: 'Add', 62 | Edit: 'Edit', 63 | Save: 'Save', 64 | Cancel: 'Cancel', 65 | Name: 'Name', 66 | Url: 'Url', 67 | Logo: 'Logo', 68 | DeleteSuccess: 'Delete Success!', 69 | AddSuccess: 'Add Success!', 70 | EditSuccess: 'Edit Success!', 71 | KeepUpload: 'Upload', 72 | Delete: 'Delete', 73 | UploadNote: 'Drop file here or click to upload', 74 | RestoreBackupSuccess: 'Restore Backup Success!', 75 | 76 | // Trending 77 | Today: 'Today', 78 | ThisWeek: 'This week', 79 | ThisMonth: 'This month', 80 | AllLanguages: 'All Languages', 81 | 82 | // network error 83 | NetworkErrorTitle: 'Network error', 84 | NetworkErrorContent: 'If you encounter an unkonwn problem select refresh.', 85 | Refresh: 'Refresh', 86 | }; 87 | 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 |

5 | 6 | gitmoji commits 7 | 8 | 9 | gitmoji commits 10 | 11 | 12 | PRs welcome 13 | 14 |

15 | 16 | ## GITHUBER 17 | 18 | :octocat: 这是一个帮助 GitHub 开发者每日发现优质内容的浏览器主页拓展。 19 | 20 | ✔ 支持四大搜索引擎:谷歌、百度、必应、雅虎
21 | ✔ 添加便捷书签
22 | ✔ 爬 Github Trending 展示所有编程语言的每日、每周、每月热门 Repo
23 | ✔ 项目开源,方便大家进行代码审计、添加新功能
24 | ✔ 支持中英文、备份数据、以及基本视图设置 25 | 26 | ![screenshot](./assets/0.png) 27 | 28 | One-click install from Google Chrome Web Store ⬇️ ⬇️ ⬇️ 29 | 30 | Try it now 31 | 32 | ## 本地开发 33 | 34 | 用于调试 `chrome` 特性,代码编译成功后手动刷新页面方可生效 35 | 36 | 1. 安装依赖 37 | 38 | ```bash 39 | npm i 40 | ``` 41 | 42 | 2. 编译代码 43 | 44 | ```bash 45 | npm run watch 46 | ``` 47 | 48 | 3. 打开 Chrome 扩展程序 [chrome://extensions/](chrome://extensions/),开启开发者模式 49 | 4. 点击**加载已解压的扩展程序**,找到编译后输出的 `dist` 目录,加载代码 50 | 51 | ## 服务端部署 52 | 53 | 1. 添加配置文件 54 | 55 | ```bash 56 | # 添加 .env 填写正确的配置信息 57 | cp .env.example .env 58 | ``` 59 | 60 | 2. 创建数据库(默认用MySQL) 61 | 62 | ```bash 63 | # 初始化数据表 64 | yarn db:migrate 65 | ``` 66 | 67 | 3. 启动服务 68 | 69 | ```bash 70 | # 开发环境 71 | yarn nodemon 72 | 73 | # 生产环境 74 | yarn start 75 | ``` 76 | 77 | ## TODO LIST 78 | 79 | - [ ] 高级设置(待定) 80 | - RSS 81 | - [ ] 数据同步 82 | - [x] 手动备份 83 | - 从云端恢复数据 84 | - 账号(登录,注册) 85 | - [ ] 最常访问(待定) 86 | 87 | ## 相关链接 88 | 89 | - [GitHub Octicons](https://octicons.github.com/) 90 | 91 | ## License 92 | 93 | Githuber © [zhuowenli](https://github.com/zhuowenli), Released under the [Mozilla Public License 2.0](./LICENSE) License. 94 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "githuber", 3 | "version": "1.7.1", 4 | "main": "index.js", 5 | "repository": "git@github.com:zhuowenli/githuber.git", 6 | "author": "卓文理 <531840344@qq.com>", 7 | "license": "Mozilla Public License Version 2.0", 8 | "scripts": { 9 | "dev": "cross-env NODE_OPTIONS=--openssl-legacy-provider poi --dev", 10 | "watch": "cross-env NODE_OPTIONS=--openssl-legacy-provider poi watch", 11 | "build": "rm -rf dist && cross-env NODE_OPTIONS=--openssl-legacy-provider NODE_ENV=production poi build", 12 | "start": "pm2 start ./app/index.js --name 'githuber-server'", 13 | "restart": "pm2 restart githuber-server", 14 | "nodemon": "nodemon --watch app ./app/index.js", 15 | "db:seed": "sequelize db:seed:all", 16 | "db:seed:undo": "sequelize db:seed:undo:all", 17 | "db:migrate": "sequelize db:migrate", 18 | "db:migrate:undo": "sequelize db:migrate:undo", 19 | "db:rollback": "sequelize db:migrate:undo:all", 20 | "create:migration": "sequelize migration:create", 21 | "create:seed": "sequelize seed:create" 22 | }, 23 | "dependencies": { 24 | "axios": "^0.19.2", 25 | "babel-polyfill": "^6.26.0", 26 | "bluebird": "^3.7.2", 27 | "csshake": "^1.5.3", 28 | "element-ui": "^2.13.2", 29 | "file-saver": "^1.3.8", 30 | "iscroll": "^5.2.0", 31 | "jquery": "^3.4.0", 32 | "jsonp": "^0.2.1", 33 | "koa": "^2.4.1", 34 | "koa-bodyparser": "^4.2.0", 35 | "koa-convert": "^1.2.0", 36 | "koa-logger": "^3.1.0", 37 | "koa-mount": "^3.0.0", 38 | "koa-proxy": "^0.9.0", 39 | "koa-router": "^7.3.0", 40 | "koa-session": "^5.7.1", 41 | "koa-static": "^4.0.2", 42 | "koa-views": "^6.1.3", 43 | "koa2-cors": "^2.0.5", 44 | "moment": "^2.19.3", 45 | "node-env-file": "^0.1.8", 46 | "normalize.css": "^7.0.0", 47 | "octicons": "^7.1.0", 48 | "qiniu": "^7.1.1", 49 | "querystring": "^0.2.0", 50 | "vue": "^2.5.13", 51 | "vue-i18n": "^7.4.0", 52 | "vue-iscroll-view": "^1.0.3", 53 | "vue-waterfall": "^1.0.6", 54 | "vuedraggable": "^2.16.0", 55 | "vuex": "^3.0.1" 56 | }, 57 | "devDependencies": { 58 | "autoprefixer": "^7.2.5", 59 | "babel-eslint": "^8.2.1", 60 | "babel-plugin-component": "^1.1.0", 61 | "babel-plugin-transform-runtime": "^6.23.0", 62 | "babel-preset-es2015": "^6.24.1", 63 | "babel-preset-stage-2": "^6.24.1", 64 | "babel-preset-vue": "^2.0.0", 65 | "cross-env": "^5.1.3", 66 | "eslint": "^4.15.0", 67 | "eslint-config-airbnb-base": "^12.1.0", 68 | "eslint-plugin-import": "^2.8.0", 69 | "eslint-plugin-vue": "^4.2.0", 70 | "mysql2": "^1.5.1", 71 | "nodemon": "^1.12.7", 72 | "pm2": "^2.9.1", 73 | "poi": "^9.5.5", 74 | "pug": "^2.0.0-rc.4", 75 | "sass": "^1.90.0", 76 | "sass-loader": "^7.3.1", 77 | "sequelize": "^5.8.9", 78 | "sequelize-cli": "^5.5.0" 79 | } 80 | } -------------------------------------------------------------------------------- /src/vuex/modules/search.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2018-01-18 14:46:48 5 | */ 6 | 7 | 'use strict'; 8 | 9 | import { get, jsonp } from '../../services/fetch'; 10 | import * as types from '../types'; 11 | 12 | export const getters = { 13 | suggestions: state => state.suggestions, 14 | superpages: state => state.superpages, 15 | customSearchEngines: state => state.customSearchEngines, 16 | }; 17 | 18 | export const actions = { 19 | /** 20 | * 获取搜索联想 21 | * 22 | * @param {any} { commit } state 23 | * @param {String} wd 关键词 24 | * @returns {Promise} 25 | */ 26 | async fetchSuggestion ({ commit }, wd) { 27 | const data = await jsonp('http://suggestion.baidu.com/su', { wd }).then(res => res.s); 28 | commit(types.RECEIVE_SUGGESTIONS, data); 29 | return data; 30 | }, 31 | 32 | /** 33 | * 获取网站联想 34 | * 35 | * @param {any} { commit } state 36 | * @param {any} wd 关键词 37 | * @returns {Promise} 38 | */ 39 | async fetchSuperpage({ commit }, wd) { 40 | let data = await jsonp('http://nssug.baidu.com/su', { wd, prod: 'superpage' }).then(res => res.s); 41 | 42 | data = data.map(item => { 43 | const obj = {}; 44 | 45 | try { 46 | const text = decodeURIComponent(item); 47 | const array = text.split('0{#S+_}'); 48 | console.log(text, array); 49 | const content = JSON.parse(array[1]); 50 | 51 | obj.name = content[4]; 52 | obj.url = content[1]; 53 | } catch (e) { 54 | console.log(e); 55 | } 56 | 57 | return obj; 58 | }).filter(item => item.name); 59 | 60 | commit(types.RECEIVE_SUPERPAGES, data); 61 | return data; 62 | }, 63 | 64 | /** 65 | * 获取自定义搜索引擎 66 | * 67 | * @param {any} { commit } state 68 | * @returns {Promise} 69 | */ 70 | fetchCustomSearchEngine({ commit }) { 71 | commit(types.RECEIVE_SEARCH_ENGINES, [ 72 | { 73 | name: 'GitHub', 74 | url: 'https://github.com/search?utf8=✓&q=%s' 75 | }, 76 | { 77 | name: 'Stack Overflow', 78 | url: 'http://stackoverflow.com/search?q=%s' 79 | }, 80 | ]); 81 | }, 82 | }; 83 | 84 | export const mutations = { 85 | [types.RECEIVE_SUGGESTIONS](state, data) { 86 | state.suggestions = data; 87 | }, 88 | [types.RECEIVE_SUPERPAGES](state, data) { 89 | state.superpages = data; 90 | }, 91 | [types.RECEIVE_SEARCH_ENGINES](state, data) { 92 | state.customSearchEngines = data; 93 | } 94 | }; 95 | 96 | export default { 97 | actions, 98 | getters, 99 | mutations, 100 | namespaced: true, 101 | state: { 102 | suggestions: [], 103 | superpages: [], 104 | customSearchEngines: [], 105 | }, 106 | }; 107 | -------------------------------------------------------------------------------- /src/views/components/bookmark.sass: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2018-01-20 21:28:46 5 | */ 6 | 7 | @import '~csshake/scss/_tools.scss' 8 | @import '../../stylesheet/common.sass' 9 | 10 | .bookmark 11 | touch-action: none 12 | overflow: hidden 13 | height: 100% 14 | padding: 10px 15 | &__item 16 | position: relative 17 | width: 100% 18 | height: 60px 19 | display: flex 20 | padding: 10px 0 21 | cursor: pointer 22 | border-bottom: 1px solid var(--backgroundLighter) 23 | transition: all .3s ease 24 | user-select: none 25 | color: var(--fontColor) 26 | &:last-child 27 | border-bottom: 0 28 | .logo 29 | display: flex 30 | align-items: center 31 | justify-content: center 32 | flex-shrink: 0; 33 | width: 40px 34 | height: 40px 35 | outline: 0 36 | img 37 | display: inline-block 38 | width: 32px 39 | 40 | .text 41 | display: flex 42 | align-items: center 43 | justify-content: center 44 | flex-shrink: 0; 45 | width: 40px 46 | height: 40px 47 | background: var(--gray) 48 | border-radius: 50% 49 | color: var(--background) 50 | font-size: 20px 51 | user-select: none 52 | outline: 0 53 | line-height: 1 54 | 55 | .name 56 | flex: 1 57 | display: flex 58 | height: 40px 59 | font-size: 12px 60 | padding-left: 10px 61 | span 62 | @include max-text(2) 63 | width: 100% 64 | justify-content: center 65 | max-height: 40px 66 | line-height: 20px 67 | 68 | .el-icon-plus 69 | display: block 70 | margin: 0 auto 71 | font-size: 20px 72 | color: var(--gray) 73 | .el-icon-close 74 | position: absolute 75 | left: 0 76 | color: var(--red) 77 | width: 20px 78 | height: 20px 79 | top: 50% 80 | margin-top: -10px 81 | line-height: 20px 82 | text-align: center 83 | opacity: 0 84 | transition: all .3s ease 85 | pointer-events: none 86 | .el-icon-check 87 | display: block 88 | margin: 0 auto 89 | color: var(--green) 90 | font-size: 24px 91 | &.edit 92 | padding-left: 20px 93 | .el-icon-close 94 | pointer-events: auto 95 | opacity: 1 96 | .logo, .text 97 | @include do-shake('shake-slow', 2, 2, 1, 1s) 98 | @extend %running 99 | animation-play-state: running; 100 | display: flex 101 | 102 | .main-aside--collapse .bookmark__item .name 103 | width: 0 104 | -------------------------------------------------------------------------------- /src/views/components/setting.sass: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2018-04-14 16:43:38 5 | */ 6 | 7 | @import '../../stylesheet/common.sass' 8 | 9 | .setting 10 | position: fixed 11 | top: 0 12 | right: 0 13 | bottom: 0 14 | display: flex 15 | flex-direction: column 16 | width: 360px 17 | height: 100vh 18 | overflow: hidden 19 | background: var(--background) 20 | z-index: 100 21 | box-shadow: 0 0 3px 0 var(--shadowColor) 22 | transform: translateX(100%) 23 | transition: transform 0.2s ease 24 | .setting-title 25 | color: var(--fontColor) 26 | &.show 27 | transform: translate3d(0,0,0) 28 | 29 | &-title 30 | position: relative 31 | height: 44px 32 | line-height: 44px 33 | border-bottom: 1px solid var(--borderColor) 34 | padding-left: 20px 35 | font-size: 14px 36 | z-index: 102 37 | background: var(--background) 38 | box-shadow: 0 0 3px 0 var(--borderColor) 39 | 40 | &-content 41 | height: calc(100vh - 44px) 42 | padding: 20px 43 | background: var(--backgroundLighter) 44 | 45 | .scroller:after 46 | content: '' 47 | display: block 48 | height: 20px 49 | 50 | .el-card 51 | box-shadow: none 52 | margin-bottom: 20px 53 | &__header 54 | font-weight: bold 55 | &__tips 56 | padding-top: 10px 57 | font-size: 12px 58 | color: var(--gray) 59 | &__upload 60 | display: flex 61 | flex-direction: row 62 | .upload 63 | position: relative 64 | margin-left: 10px 65 | input 66 | position: absolute 67 | top: 0 68 | left: 0 69 | opacity: 0 70 | width: 100% 71 | height: 100% 72 | cursor: pointer 73 | &:hover + .el-button--success 74 | background: #85ce61; 75 | border-color: #85ce61; 76 | color: var(--background); 77 | .link 78 | display: inline-block 79 | text-decoration: none 80 | margin-right: 12px 81 | color: var(--gray) 82 | font-size: 14px 83 | .version 84 | color: var(--gray) 85 | padding-top: 10px 86 | 87 | .el-form 88 | margin-top: -5px 89 | .el-form-item 90 | margin-bottom: 0 91 | display: flex 92 | &__label 93 | float: none 94 | flex: 1 95 | text-align: left 96 | &__content 97 | display: block 98 | .el-select 99 | width: 96px 100 | 101 | -------------------------------------------------------------------------------- /src/views/components/search.sass: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2018-01-19 18:16:25 5 | */ 6 | 7 | @import '../../stylesheet/common.sass' 8 | 9 | .search 10 | display: block 11 | width: 100% 12 | max-width: 540px 13 | padding: 10px 20px 15px 14 | background: var(--background) 15 | 16 | .el-autocomplete 17 | display: block 18 | width: 100% 19 | .el-input 20 | &-group__append 21 | border-left: 0 22 | .el-button 23 | background: var(--background) 24 | 25 | .el-input 26 | &--prefix .el-input__inner 27 | padding-left: 46px 28 | &__prefix 29 | left: 0 30 | &-group__append 31 | position: absolute 32 | right: 1px 33 | top: 1px 34 | bottom: 1px 35 | display: block 36 | border: 0 37 | background: var(--background) 38 | width: auto 39 | padding: 0 40 | .el-button 41 | margin: 0 42 | border: 0 43 | padding-left: 16px 44 | padding-right: 16px 45 | 46 | .icon-search 47 | display: inline-block 48 | width: 46px 49 | height: 40px 50 | outline: 0 51 | cursor: pointer 52 | @each $icon in google, baidu, bing, yahoo, dogedoge 53 | &--#{$icon} 54 | background: url('../../assets/search-#{$icon}.png') center center no-repeat; 55 | background-size: 30px 30px 56 | &--duckduckgo 57 | background: url('../../assets/search-duckduckgo.svg') center center no-repeat 58 | background-size: 30px 30px 59 | 60 | .el-tabs 61 | // padding-left: 20px 62 | padding-bottom: 5px 63 | &__item 64 | text-align: center 65 | &__header 66 | margin-bottom: 0 67 | &__nav-wrap:after 68 | display: none 69 | 70 | 71 | .el-autocomplete-suggestion 72 | cursor: pointer 73 | li 74 | padding: 0 75 | &:hover 76 | background: transparent 77 | 78 | .suggestion 79 | & > div 80 | padding: 5px 20px 81 | &__search 82 | color: var(--yellow) 83 | &:hover 84 | background: var(--yellow) 85 | color: var(--background) 86 | &__url 87 | color: var(--blue) 88 | &:hover 89 | background: var(--blue) 90 | color: var(--background) 91 | &__word 92 | color: var(--green) 93 | &:hover 94 | background: var(--green) 95 | color: var(--background) 96 | 97 | .highlighted .suggestion 98 | &__search 99 | background: var(--yellow) 100 | color: var(--background) 101 | &__url 102 | background: var(--blue) 103 | color: var(--background) 104 | &__word 105 | background: var(--green) 106 | color: var(--background) 107 | -------------------------------------------------------------------------------- /src/components/image-uploader/index.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 96 | 97 | 100 | -------------------------------------------------------------------------------- /src/assets/search-duckduckgo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/components/image-uploader-box/index.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 100 | 101 | 118 | -------------------------------------------------------------------------------- /src/views/components/bookmark.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 115 | 116 | 119 | -------------------------------------------------------------------------------- /src/stylesheet/index.sass: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2018-01-17 15:49:10 5 | */ 6 | 7 | @import '~normalize.css/normalize.css' 8 | @import '~octicons/index.scss' 9 | 10 | html 11 | font-family: "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif 12 | height: 100vh 13 | overflow: hidden 14 | -webkit-font-smoothing: antialiased 15 | 16 | body 17 | --borderColor: #ebeef5 18 | 19 | *, *:before, *:after 20 | box-sizing: border-box 21 | 22 | .el-upload-dragger 23 | background-color: var(--background) 24 | border: 1px dashed var(--borderColor) 25 | &:hover 26 | border-color: var(--backgroundLightest) 27 | 28 | .el-card 29 | background-color: var(--background) 30 | color: var(--fontColor) 31 | border: 1px solid var(--borderColor) 32 | &__header 33 | border-bottom: 1px solid var(--borderColor) 34 | 35 | .el-dialog 36 | max-width: 600px 37 | background: var(--background) 38 | color: var(--fontColor) 39 | &__title 40 | color: var(--fontColor) 41 | &__body 42 | padding: 10px 20px 43 | 44 | .el-input__inner 45 | background-color: var(--background) 46 | background: var(--background) 47 | color: var(--fontColor) 48 | border: 1px solid var(--borderColor) 49 | &:hover 50 | border-color: var(--backgroundLightest) 51 | &:focus 52 | border-color: var(--borderColor) 53 | 54 | .el-radio-button__inner 55 | background: var(--background) 56 | border: 1px solid var(--borderColor) 57 | .el-radio-button:first-child .el-radio-button__inner 58 | border-left-color: var(--borderColor) 59 | 60 | .el-input.is-active .el-input__inner 61 | border-color: var(--borderColor) 62 | 63 | .el-select 64 | &:hover .el-input__inner, 65 | .el-input.is-focus .el-input__inner, 66 | .el-input__inner:focus 67 | border-color: var(--backgroundLightest) 68 | .el-tag 69 | background-color: var(--backgroundLighter) 70 | &__close.el-icon-close 71 | color: var(--fontColor) 72 | background-color: var(--background) 73 | &:hover 74 | background-color: var(--backgroundLightest) 75 | &--info 76 | color: var(--fontColor) 77 | 78 | .el-tabs__item 79 | color: var(--fontColor) 80 | 81 | .el-form 82 | &-item:last-child 83 | margin-bottom: 0 84 | &-item__label 85 | color: var(--fontColor) 86 | 87 | .el-autocomplete-suggestion__wrap 88 | background-color: var(--background) 89 | border: 1px solid var(--borderColor) 90 | 91 | .el-popper[x-placement^=bottom] .popper__arrow 92 | border-bottom-color: var(--background) 93 | &::after 94 | border-bottom-color: var(--background) 95 | 96 | .el-select-dropdown 97 | border: 1px solid var(--borderColor) 98 | background-color: var(--background) 99 | .popper__arrow 100 | transform: none 101 | &__item 102 | color: var(--fontColor) 103 | &__item.selected 104 | background-color: var(--backgroundLighter) 105 | &__item.hover, 106 | &__item:hover 107 | background-color: var(--backgroundLighter) 108 | color: var(--blue) 109 | 110 | .el-select-dropdown.is-multiple 111 | .el-select-dropdown__item.selected, 112 | .el-select-dropdown__item.selected.hover 113 | background-color: var(--backgroundLighter) 114 | 115 | .el-dropdown-menu 116 | background-color: var(--background) 117 | border: 1px solid var(--borderColor) 118 | &__item 119 | color: var(--fontColor) 120 | &:focus, 121 | &:not(.is-disabled):hover 122 | background-color: var(--background) 123 | border-bottom-color: var(--gray) 124 | -------------------------------------------------------------------------------- /src/views/main.sass: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2018-01-19 18:03:22 5 | */ 6 | @import '../stylesheet/common.sass' 7 | 8 | .main 9 | display: flex 10 | height: 100vh 11 | overflow: hidden 12 | 13 | // 左侧悬浮栏 14 | &-aside 15 | position: relative 16 | background: var(--background) 17 | box-shadow: 0 0 3px 0 var(--shadowColor) 18 | transform: translateX(-100%) 19 | transition: all 0.2s ease 20 | width: 0 21 | overflow: hidden 22 | flex-shrink: 0; 23 | &--show 24 | width: 300px 25 | transform: translate3d(0, 0, 0) 26 | & + .main-content 27 | max-width: calc(100% - 300px); 28 | &.main-aside--collapse 29 | width: 60px 30 | & + .main-content 31 | max-width: calc(100% - 60px); 32 | 33 | &-content 34 | flex: 1 35 | max-width: 100% 36 | background: var(--backgroundLighter) 37 | 38 | // 头部 39 | &__header 40 | width: 100% 41 | background: var(--background) 42 | .search-border 43 | height: 4px 44 | width: 100% 45 | background: var(--background) 46 | transition: all 0.2s ease 47 | &.google 48 | .search-border 49 | background: linear-gradient(to right, #4285f4 ((100/6) * 1%), #ea4335 ((100/6) * 1%), #ea4335 ((100/6) * 2%), #f9bc05 ((100/6) * 2%), #f9bc05 ((100/6) * 3%), #4285f4 ((100/6) * 3%), #4285f4 ((100/6) * 4%), #34a852 ((100/6) * 4%), #34a852 ((100/6) * 5%), #ea4335 ((100/6) * 5%), #ea4335 ((100/6) * 6%)); 50 | .el-tabs__active-bar 51 | background: #1A73E8 52 | .el-tabs__item.is-active, .el-tabs__item:hover 53 | color: #1A73E8 54 | &.baidu 55 | .search-border, .el-tabs__active-bar 56 | background: #2d78f4 57 | .el-tabs__item.is-active, .el-tabs__item:hover 58 | color: #2d78f4 59 | &.yahoo 60 | .search-border, .el-tabs__active-bar 61 | background: #4d00ae 62 | .el-tabs__item.is-active, .el-tabs__item:hover 63 | color: #4d00ae 64 | &.dogedoge 65 | .search-border 66 | background: #ffce7f 67 | .el-tabs__active-bar 68 | background: #111 69 | .el-tabs__item.is-active, .el-tabs__item:hover 70 | color: #111 71 | &.bing 72 | .search-border, .el-tabs__active-bar 73 | background: #278483 74 | .el-tabs__item.is-active, .el-tabs__item:hover 75 | color: #278483 76 | &.duckduckgo 77 | .search-border, .el-tabs__active-bar 78 | background: #de5733 79 | .el-tabs__item.is-active, .el-tabs__item:hover 80 | color: #de5733 81 | 82 | .btn-setting 83 | position: absolute 84 | top: 10px 85 | right: 10px 86 | display: flex 87 | width: 30px 88 | height: 30px 89 | align-items: center 90 | justify-content: center 91 | border: 0 92 | background: var(--background) 93 | cursor: pointer 94 | outline: 0 95 | z-index: 101 96 | .oction-gear 97 | width: 14px 98 | height: 14px 99 | fill: var(--fontColor) 100 | .oction-x 101 | width: 12px 102 | height: 16px 103 | fill: var(--fontColor) 104 | 105 | // trending 106 | &__trending 107 | position: relative 108 | width: 100% 109 | height: calc(100vh - 109px) 110 | z-index: 10 111 | -------------------------------------------------------------------------------- /src/vuex/modules/github.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2018-01-18 10:53:12 5 | */ 6 | 7 | 'use strict'; 8 | 9 | import Promise from 'bluebird'; 10 | import { get } from '../../services/fetch'; 11 | import * as types from '../types'; 12 | import storage from '../../services/storage'; 13 | 14 | const time = new Date(); 15 | const year = new Date(time.getFullYear(), 0, 1); 16 | const toDay = time.getDate(); 17 | const toWeek = Math.ceil((((new Date() - year) / 86400000) + year.getDay() + 1) / 7); 18 | const toMonth = time.getMonth() + 1; 19 | 20 | const fetchTrendingRepos = async (lang, since, type = 'repositories') => { 21 | console.log(lang); 22 | const data = await get(`https://gtrend.infly.io/${type}?language=${encodeURIComponent(lang)}&since=${since}`); 23 | 24 | if (type === 'developers' && lang === 'JavaScript' && (Math.random() * 2) > 1) { 25 | data.push({ 26 | avatar: 'https://avatars3.githubusercontent.com/u/9620783?s=96&v=4', 27 | author: '卓文理', 28 | url: 'https://github.com/zhuowenli', 29 | username: 'zhuowenli', 30 | repo: { 31 | name: 'githuber', 32 | url: 'https://github.com/zhuowenli/githuber', 33 | description: ':octocat: Display Github Trending repositories on New Tab Extensions', 34 | } 35 | }); 36 | } 37 | 38 | return data; 39 | }; 40 | 41 | export const getters = { 42 | trendings: state => state.trendings, 43 | }; 44 | 45 | export const actions = { 46 | /** 47 | * 获取GitHub Trending 48 | * 49 | * @param {any} { commit } state 50 | * @param {Object} [query={}] 请求参数 51 | * @param {String} query.since 时间维度:daily、weekly、monthly 52 | * @param {String} query.lang 语言 53 | * @param {String} query.type repositories、developers 54 | * @returns {Promise} 55 | */ 56 | async fetchTrending ({ commit }, query = {}) { 57 | const data = await storage.getItem(JSON.stringify(query)); 58 | 59 | if ( 60 | data && data.repos.length && ( 61 | (query.since === 'daily' && data.toDay === toDay) || 62 | (query.since === 'weekly' && data.toWeek === toWeek) || 63 | (query.since === 'monthly' && data.toMonth === toMonth) 64 | ) 65 | ) { 66 | commit(types.RECEIVE_GITHUB_TRENDINGS, data.repos); 67 | return data.repos; 68 | } 69 | 70 | const { since, type } = query; 71 | 72 | let repos = []; 73 | let isAllLanguage = false; 74 | 75 | if (query.lang.length) { 76 | query.lang.map(item => { 77 | if (item === '') isAllLanguage = true; 78 | return item; 79 | }); 80 | } 81 | 82 | if (!query.lang.length || isAllLanguage) { 83 | repos = await fetchTrendingRepos('', since, type); 84 | } else { 85 | await Promise.map(query.lang, async lang => { 86 | const res = await fetchTrendingRepos(lang, since, type); 87 | repos = repos.concat(res); 88 | return res; 89 | }); 90 | repos = repos.sort((a, b) => (+b.added - a.added)); 91 | } 92 | 93 | commit(types.RECEIVE_GITHUB_TRENDINGS, repos); 94 | 95 | storage.setItem(JSON.stringify(query), { 96 | repos, 97 | toDay, 98 | toWeek, 99 | toMonth 100 | }); 101 | 102 | return repos; 103 | }, 104 | 105 | }; 106 | 107 | export const mutations = { 108 | [types.RECEIVE_GITHUB_TRENDINGS](state, data) { 109 | state.trendings = data; 110 | }, 111 | }; 112 | 113 | export default { 114 | actions, 115 | getters, 116 | mutations, 117 | namespaced: true, 118 | state: { 119 | trendings: [], 120 | }, 121 | }; 122 | -------------------------------------------------------------------------------- /src/vuex/modules/bookmark.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2018-01-20 20:59:49 5 | */ 6 | 7 | 'use strict'; 8 | 9 | import * as types from '../types'; 10 | import findOne from '../../services/findOne'; 11 | import storage from '../../services/storage'; 12 | import google from '../../assets/search-google.png'; 13 | 14 | export const getters = { 15 | bookmarks: state => state.bookmarks, 16 | }; 17 | 18 | export const actions = { 19 | async fetchBookmarks({ commit }) { 20 | let data = storage.getItem('GITHUBER_BOOKMARKS') || []; 21 | 22 | if (!data.length) { 23 | /* eslint-disable max-len */ 24 | data = [ 25 | { 26 | name: 'zhuowenli/githuber: 这是一个帮助 Githuber 每日发现优质内容的浏览器主页拓展。', 27 | logo: 'https://github.com/favicon.ico', 28 | url: 'https://github.com/zhuowenli/githuber' 29 | }, { 30 | name: 'zhuowenli - GitHub', 31 | url: 'https://github.com/zhuowenli', 32 | logo: 'http://st-qn.gittt.cn/2018/1/22/1516614358916991.png' 33 | }, { 34 | name: 'Dribbble - Show and tell for designers', 35 | url: 'https://dribbble.com/', 36 | logo: 'https://cdn.dribbble.com/assets/dribbble-ball-192-ec064e49e6f63d9a5fa911518781bee0c90688d052a038f8876ef0824f65eaf2.png' 37 | }, { 38 | name: 'Codrops - 右键点击列表进入编辑模式', 39 | url: 'https://tympanus.net/codrops/', 40 | logo: 'https://avatars3.githubusercontent.com/u/310036?s=200&v=4' 41 | }, { 42 | name: '编辑模式下双击可编辑、拖拽可排序', 43 | logo: google, 44 | url: 'https://www.google.com/' 45 | }, 46 | ]; 47 | 48 | storage.setItem('GITHUBER_BOOKMARKS', data); 49 | } 50 | 51 | commit(types.RECEIVE_BOOKMARKS, data); 52 | return data; 53 | }, 54 | async updateBookmarks({ commit }, data) { 55 | commit(types.RECEIVE_BOOKMARKS, data); 56 | return data; 57 | }, 58 | async saveBookmark({ commit }, { form: item, index }) { 59 | commit(types.SAVE_BOOKMARKS, { item, index }); 60 | return item; 61 | }, 62 | async removeBookmark({ commit }, item) { 63 | commit(types.DELETE_BOOKMARKS, item); 64 | }, 65 | async restoreBackupBookmarks({ commit }, data) { 66 | commit(types.RESTORE_BACKUP_BOOKMARKS, data); 67 | } 68 | }; 69 | 70 | export const mutations = { 71 | [types.RECEIVE_BOOKMARKS](state, data) { 72 | state.bookmarks = data; 73 | storage.setItem('GITHUBER_BOOKMARKS', state.bookmarks); 74 | }, 75 | [types.RESTORE_BACKUP_BOOKMARKS](state, data) { 76 | data.map(item => { 77 | const { index } = findOne(state.bookmarks, { name: item.name }); 78 | if (index === -1) { 79 | state.bookmarks.push(item); 80 | } 81 | return item; 82 | }); 83 | 84 | storage.setItem('GITHUBER_BOOKMARKS', state.bookmarks); 85 | }, 86 | [types.SAVE_BOOKMARKS](state, { item, index }) { 87 | if (index || index === 0) { 88 | state.bookmarks.splice(index, 1, item); 89 | } else { 90 | state.bookmarks.unshift(item); 91 | } 92 | storage.setItem('GITHUBER_BOOKMARKS', state.bookmarks); 93 | }, 94 | [types.DELETE_BOOKMARKS](state, item) { 95 | const index = state.bookmarks.indexOf(item); 96 | 97 | if (index >= 0) { 98 | state.bookmarks.splice(index, 1); 99 | storage.setItem('GITHUBER_BOOKMARKS', state.bookmarks); 100 | } 101 | }, 102 | }; 103 | 104 | export default { 105 | actions, 106 | getters, 107 | mutations, 108 | namespaced: true, 109 | state: { 110 | bookmarks: [], 111 | }, 112 | }; 113 | -------------------------------------------------------------------------------- /src/services/fetch.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2018-01-18 14:52:13 5 | */ 6 | 7 | 'use strict'; 8 | 9 | import axios from 'axios'; 10 | // import cookie from './cookie'; 11 | 12 | // const auths = {}; 13 | 14 | /** 15 | * XMLHttpRequest 封装 16 | * @export 17 | * @param {Object} [query={}] 请求参数 18 | * @returns Promise 19 | */ 20 | export function fetch(query = {}) { 21 | const params = Object.assign({}, query); 22 | 23 | if (params.method === 'get') { 24 | params.params = params.data; 25 | delete params.data; 26 | } 27 | 28 | params.headers = Object.assign(params.headers, { 29 | 'Content-Type': 'application/json', 30 | }); 31 | params.validateStatus = (status) => status >= 200 && status < 500; 32 | 33 | // console.log('=========== FETCH START ==========='); 34 | // console.log(params); 35 | return axios(params) 36 | .then((res) => { 37 | const { data, status } = res; 38 | 39 | if (status >= 400) { 40 | return Promise.reject(res); 41 | } 42 | 43 | // console.log(res, data); 44 | // console.log('=========== FETCH END ==========='); 45 | return data; 46 | }) 47 | .catch((error) => { 48 | const proxy = {}; 49 | 50 | proxy.name = '接口请求异常'; 51 | proxy.message = error.message; 52 | 53 | if (error.data) { 54 | return Promise.reject(error.data); 55 | } 56 | 57 | return Promise.reject(proxy); 58 | }); 59 | } 60 | 61 | /** 62 | * http get 请求简单封装 63 | * @export 64 | * @param {String} url 请求的URL 65 | * @param {Object} [data={}] 请求参数 66 | * @param {Object} [data={}] 请求头 67 | * @returns Promise 68 | */ 69 | export function get(url, data = {}, headers = {}) { 70 | return fetch({ 71 | url, 72 | data, 73 | method: 'get', 74 | headers, 75 | }); 76 | } 77 | 78 | /** 79 | * http post 请求简单封装 80 | * @export 81 | * @param {String} url 请求的URL 82 | * @param {Object} [data={}] 请求参数 83 | * @param {Object} [data={}] 请求头 84 | * @returns Promise 85 | */ 86 | export function post(url, data = {}, headers = {}) { 87 | return fetch({ 88 | url, 89 | data, 90 | method: 'post', 91 | headers, 92 | }); 93 | } 94 | 95 | /** 96 | * http delete 请求简单封装 97 | * @export 98 | * @param {String} url 请求的URL 99 | * @param {Object} [data={}] 请求参数 100 | * @param {Object} [data={}] 请求头 101 | * @returns Promise 102 | */ 103 | export function del(url, data = {}, headers = {}) { 104 | return fetch({ 105 | url, 106 | data, 107 | method: 'delete', 108 | headers, 109 | }); 110 | } 111 | 112 | /** 113 | * http put 请求简单封装 114 | * @export 115 | * @param {String} url 请求的URL 116 | * @param {Object} [data={}] 请求参数 117 | * @param {Object} [data={}] 请求头 118 | * @returns Promise 119 | */ 120 | export function put(url, data = {}, headers = {}) { 121 | return fetch({ 122 | url, 123 | data, 124 | method: 'put', 125 | headers, 126 | }); 127 | } 128 | 129 | /** 130 | * http jsonp 请求简单封装 131 | * 132 | * @export 133 | * @param {String} url 请求的URL 134 | * @param {Object} [query={}] 请求参数 135 | * @returns {Promise} 136 | */ 137 | export function jsonp(url, query = {}) { 138 | return fetch({ 139 | url, 140 | data: { 141 | ...query, 142 | cb: 'callback', 143 | t: new Date().getTime(), 144 | }, 145 | method: 'get', 146 | responseType: 'text', 147 | headers: {}, 148 | }).then((res) => { 149 | try { 150 | const startIndex = res.indexOf('('); 151 | const endIndex = res.lastIndexOf(')'); 152 | 153 | if (startIndex !== -1 && endIndex !== -1 && endIndex > startIndex) { 154 | const payload = res.slice(startIndex + 1, endIndex); 155 | const normalised = payload.replace(/[\r\n\t]/g, '').replace(/([,{\s])([a-zA-Z_$][0-9a-zA-Z_$]*)\s*:/g, '$1"$2":'); 156 | return JSON.parse(normalised); 157 | } 158 | } catch (e) { 159 | const proxy = {}; 160 | proxy.name = '接口请求异常'; 161 | proxy.message = (e && e.message) || 'Invalid JSONP response'; 162 | return Promise.reject(proxy); 163 | } 164 | 165 | const proxy = {}; 166 | proxy.name = '接口请求异常'; 167 | proxy.message = 'Invalid JSONP response'; 168 | return Promise.reject(proxy); 169 | }); 170 | } 171 | 172 | export default { 173 | fetch, 174 | get, 175 | put, 176 | delete: del, 177 | post, 178 | jsonp, 179 | }; 180 | -------------------------------------------------------------------------------- /src/services/cookie.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2018-01-18 14:54:16 5 | */ 6 | 7 | 'use strict'; 8 | 9 | 10 | const decode = decodeURIComponent; 11 | const encode = encodeURIComponent; 12 | 13 | // Helpers 14 | const isString = o => typeof o === 'string'; 15 | const same = s => s; 16 | 17 | function parseCookieString(text, shouldDecode) { 18 | const cookies = {}; 19 | 20 | if (isString(text) && text.length > 0) { 21 | const decodeValue = shouldDecode ? decode : same; 22 | const cookieParts = text.split(/;\s/g); 23 | let cookieName; 24 | let cookieValue; 25 | let cookieNameValue; 26 | 27 | for (let i = 0, len = cookieParts.length; i < len; i += 1) { 28 | // Check for normally-formatted cookie (name-value) 29 | cookieNameValue = cookieParts[i].match(/([^=]+)=/i); 30 | if (cookieNameValue instanceof Array) { 31 | try { 32 | cookieName = decode(cookieNameValue[1]); 33 | cookieValue = decodeValue(cookieParts[i] 34 | .substring(cookieNameValue[1].length + 1)); 35 | } catch (ex) { 36 | // Intentionally ignore the cookie - 37 | // the encoding is wrong 38 | } 39 | } else { 40 | // Means the cookie does not have an "=", so treat it as 41 | // a boolean flag 42 | cookieName = decode(cookieParts[i]); 43 | cookieValue = ''; 44 | } 45 | 46 | if (cookieName) { 47 | cookies[cookieName] = cookieValue; 48 | } 49 | } 50 | } 51 | 52 | return cookies; 53 | } 54 | 55 | function isNonEmptyString(s) { 56 | return isString(s) && s !== ''; 57 | } 58 | 59 | function validateCookieName(name) { 60 | if (!isNonEmptyString(name)) { 61 | throw new TypeError('Cookie name must be a non-empty string'); 62 | } 63 | } 64 | 65 | export default { 66 | /** 67 | * 获取 cookie 68 | * @param {String} cookie 名 69 | * @param {Function|Object} options Optional 70 | * @param {Boolean} options.raw 当 raw 为 true 时,cookie的值不会被URI解码 71 | * @param {Function} options.converter 该函数会在返回值之前执行。如果cookie不存在,则不使用该函数。 可以方便地传递函数而不是选项对象。 72 | * @return 如果 converter 不存在,则返回一个 string or undefined。 73 | * 如果获取的 cookie 为空,并且 converter 函数存在,则返回 converter 的返回值。 74 | */ 75 | get(name, options) { 76 | validateCookieName(name); 77 | 78 | if (typeof options === 'function') { 79 | options = { converter: options }; 80 | } else { 81 | options = options || {}; 82 | } 83 | 84 | const cookies = parseCookieString(document.cookie, !options.raw); 85 | return (options.converter || same)(cookies[name]); 86 | }, 87 | 88 | /** 89 | * 保存 cookie 90 | * @param {string} name 名称 91 | * @param {*} value 存储内容 92 | * @param {Object} options Optional 93 | * @param {Boolean} options.raw 当raw为true时,在设置之前不应该被URI编码。 94 | * @param {Number|Date} options.expires 到期时间 95 | * @param {Boolean} options.secure secure 96 | * @param {String} options.domain 域名 97 | * @return {string} 创建的cookie字符串。 98 | */ 99 | set(name, value, options) { 100 | validateCookieName(name); 101 | 102 | options = options || {}; 103 | const expires = options.expires; 104 | const domain = options.domain; 105 | const path = options.path; 106 | 107 | if (!options.raw) { 108 | value = encode(String(value)); 109 | } 110 | 111 | let text = `${name}=${value}`; 112 | 113 | // expires 114 | let date = expires; 115 | 116 | // 117 | if (!date) { 118 | date = new Date('2021/01/01'); 119 | } 120 | 121 | if (typeof date === 'number') { 122 | date = new Date(); 123 | date.setDate(date.getDate() + expires); 124 | } 125 | 126 | if (date instanceof Date) { 127 | text += `; expires=${date.toUTCString()}`; 128 | } 129 | 130 | // domain 131 | if (isNonEmptyString(domain)) { 132 | text += `; domain=${domain}`; 133 | } 134 | 135 | // path 136 | if (isNonEmptyString(path)) { 137 | text += `; path=${path}`; 138 | } 139 | 140 | // secure 141 | if (options.secure) { 142 | text += '; secure'; 143 | } 144 | 145 | document.cookie = text; 146 | return text; 147 | }, 148 | 149 | /** 150 | * 通过将过期日期设置为过去的某个时间,删除一个cookie。 151 | * @param {string} name 名称 152 | * @param {Object} options Optional expires 时间将被该方法覆盖。 153 | * @param {String} options.path path 154 | * @param {Boolean} options.secure secure 155 | * @param {String} options.domain 域名 156 | * @return {string} The created cookie string. 157 | */ 158 | remove(name, options) { 159 | options = options || {}; 160 | options.expires = new Date(0); 161 | return this.set(name, '', options); 162 | } 163 | }; 164 | -------------------------------------------------------------------------------- /src/views/components/setting.vue: -------------------------------------------------------------------------------- 1 | 62 | 63 | 126 | 127 | 130 | -------------------------------------------------------------------------------- /src/views/components/searchEngines.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2018-01-18 20:31:28 5 | */ 6 | 7 | 'use strict'; 8 | 9 | export default [ 10 | { 11 | name: 'Baidu', 12 | value: 'baidu', 13 | engines: [ 14 | { 15 | name: 'Web', 16 | value: 'Web', 17 | url: 'https://www.baidu.com/s?isource=infinity&wd=' 18 | }, { 19 | name: 'Images', 20 | value: 'Images', 21 | url: 'https://image.baidu.com/search/index?tn=baiduimage&word=' 22 | }, { 23 | name: 'News', 24 | value: 'News', 25 | url: 'http://news.baidu.com/ns?tn=news&ie=utf-8&word=' 26 | }, { 27 | name: 'Musics', 28 | value: 'Musics', 29 | url: 'http://music.baidu.com/search?ie=utf-8&key=' 30 | }, { 31 | name: 'Videos', 32 | value: 'Videos', 33 | url: 'http://video.baidu.com/v?ie=utf-8&word=' 34 | }, { 35 | name: 'Maps', 36 | value: 'Maps', 37 | url: 'http://map.baidu.com/?newmap=1&ie=utf-8&s=s%26wd%3D' 38 | } 39 | ] 40 | }, 41 | { 42 | name: 'Google', 43 | value: 'google', 44 | engines: [ 45 | { 46 | name: 'Web', 47 | value: 'Web', 48 | url: 'https://www.google.com/search?q=' 49 | }, { 50 | name: 'Images', 51 | value: 'Images', 52 | url: 'https://www.google.com/search?tbm=isch&q=' 53 | }, { 54 | name: 'News', 55 | value: 'News', 56 | url: 'https://www.google.com/search?tbm=nws&q=' 57 | }, { 58 | name: 'Videos', 59 | value: 'Videos', 60 | url: 'https://www.google.com/search?tbm=vid&q=' 61 | }, { 62 | name: 'Maps', 63 | value: 'Maps', 64 | url: 'https://www.google.com/maps/preview?q=' 65 | } 66 | ] 67 | }, 68 | { 69 | name: 'Yahoo', 70 | value: 'yahoo', 71 | engines: [ 72 | { 73 | name: 'Web', 74 | value: 'Web', 75 | url: 'https://search.yahoo.com/search?fr=yfp-t&fp=1&toggle=1&cop=mss&ei=UTF-8&p=' 76 | }, { 77 | name: 'Images', 78 | value: 'Images', 79 | url: 'https://images.search.yahoo.com/search/images?p=' 80 | }, { 81 | name: 'News', 82 | value: 'News', 83 | url: 'https://news.search.yahoo.com/search?p=' 84 | }, { 85 | name: 'Videos', 86 | value: 'Videos', 87 | url: 'https://video.search.yahoo.com/search/video?p=' 88 | }, { 89 | name: 'Sports', 90 | value: 'Sports', 91 | url: 'https://sports.search.yahoo.com/search?p=' 92 | } 93 | ] 94 | }, 95 | { 96 | name: 'Bing', 97 | value: 'bing', 98 | engines: [ 99 | { 100 | name: 'Web', 101 | value: 'Web', 102 | url: 'https://www.bing.com/search?isource=infinity&iname=bing&itype=web&q=' 103 | }, { 104 | name: 'Images', 105 | value: 'Images', 106 | url: 'https://www.bing.com/images/search?isource=infinity&iname=bing&q=' 107 | }, { 108 | name: 'News', 109 | value: 'News', 110 | url: 'https://www.bing.com/news/search?isource=infinity&iname=bing&q=' 111 | }, { 112 | name: 'Videos', 113 | value: 'Videos', 114 | url: 'https://www.bing.com/videos/search?isource=infinity&iname=bing&q=' 115 | }, { 116 | name: 'Maps', 117 | value: 'Maps', 118 | url: 'http://www.bing.com/maps/default.aspx?q=' 119 | } 120 | ] 121 | }, 122 | { 123 | name: 'DogeDoge', 124 | value: 'dogedoge', 125 | engines: [ 126 | { 127 | name: 'Web', 128 | value: 'Web', 129 | url: 'https://www.dogedoge.com/results?q=' 130 | } 131 | ] 132 | }, 133 | { 134 | name: 'DuckDuckGo', 135 | value: 'duckduckgo', 136 | engines: [ 137 | { 138 | name: 'Web', 139 | value: 'Web', 140 | url: 'https://duckduckgo.com/?ia=web&q=' 141 | }, { 142 | name: 'Images', 143 | value: 'Images', 144 | url: 'https://duckduckgo.com/?ia=images&iax=images&q=' 145 | }, { 146 | name: 'News', 147 | value: 'News', 148 | url: 'https://duckduckgo.com/?ia=news&iar=news&q=' 149 | }, { 150 | name: 'Videos', 151 | value: 'Videos', 152 | url: 'https://duckduckgo.com/?ia=videos&iax=videos&q=' 153 | }, { 154 | name: 'Maps', 155 | value: 'Maps', 156 | url: 'https://duckduckgo.com/?iaxm=maps&ia=web&q=' 157 | } 158 | ] 159 | } 160 | ]; 161 | -------------------------------------------------------------------------------- /src/services/colors.json: -------------------------------------------------------------------------------- 1 | { 2 | "Mercury": "#ff2b2b", 3 | "TypeScript": "#2b7489", 4 | "PureBasic": "#5a6986", 5 | "Objective-C++": "#6866fb", 6 | "Self": "#0579aa", 7 | "edn": "#db5855", 8 | "NewLisp": "#87AED7", 9 | "Jupyter Notebook": "#DA5B0B", 10 | "Rebol": "#358a5b", 11 | "Frege": "#00cafe", 12 | "Dart": "#00B4AB", 13 | "AspectJ": "#a957b0", 14 | "Shell": "#89e051", 15 | "Web Ontology Language": "#9cc9dd", 16 | "xBase": "#403a40", 17 | "Eiffel": "#946d57", 18 | "Nix": "#7e7eff", 19 | "RAML": "#77d9fb", 20 | "MTML": "#b7e1f4", 21 | "Racket": "#22228f", 22 | "Elixir": "#6e4a7e", 23 | "SAS": "#B34936", 24 | "Agda": "#315665", 25 | "wisp": "#7582D1", 26 | "D": "#ba595e", 27 | "Kotlin": "#F18E33", 28 | "Opal": "#f7ede0", 29 | "Crystal": "#776791", 30 | "Objective-C": "#438eff", 31 | "ColdFusion CFC": "#ed2cd6", 32 | "Oz": "#fab738", 33 | "Mirah": "#c7a938", 34 | "Objective-J": "#ff0c5a", 35 | "Gosu": "#82937f", 36 | "FreeMarker": "#0050b2", 37 | "Ruby": "#701516", 38 | "Component Pascal": "#b0ce4e", 39 | "Arc": "#aa2afe", 40 | "Brainfuck": "#2F2530", 41 | "Nit": "#009917", 42 | "APL": "#5A8164", 43 | "Go": "#375eab", 44 | "Visual Basic": "#945db7", 45 | "PHP": "#4F5D95", 46 | "Cirru": "#ccccff", 47 | "SQF": "#3F3F3F", 48 | "Glyph": "#e4cc98", 49 | "Java": "#b07219", 50 | "MAXScript": "#00a6a6", 51 | "Scala": "#DC322F", 52 | "Makefile": "#427819", 53 | "ColdFusion": "#ed2cd6", 54 | "Perl": "#0298c3", 55 | "Lua": "#000080", 56 | "Vue": "#2c3e50", 57 | "Verilog": "#b2b7f8", 58 | "Factor": "#636746", 59 | "Haxe": "#df7900", 60 | "Pure Data": "#91de79", 61 | "Forth": "#341708", 62 | "Red": "#ee0000", 63 | "Hy": "#7790B2", 64 | "Volt": "#1F1F1F", 65 | "LSL": "#3d9970", 66 | "eC": "#913960", 67 | "CoffeeScript": "#244776", 68 | "HTML": "#e44b23", 69 | "Lex": "#DBCA00", 70 | "API Blueprint": "#2ACCA8", 71 | "Swift": "#ffac45", 72 | "C": "#555555", 73 | "C++": "#555555", 74 | "AutoHotkey": "#6594b9", 75 | "Isabelle": "#FEFE00", 76 | "Metal": "#8f14e9", 77 | "Clarion": "#db901e", 78 | "JSONiq": "#40d47e", 79 | "Boo": "#d4bec1", 80 | "AutoIt": "#1C3552", 81 | "Clojure": "#db5855", 82 | "Rust": "#dea584", 83 | "Prolog": "#74283c", 84 | "SourcePawn": "#5c7611", 85 | "AMPL": "#E6EFBB", 86 | "FORTRAN": "#4d41b1", 87 | "ANTLR": "#9DC3FF", 88 | "Harbour": "#0e60e3", 89 | "Tcl": "#e4cc98", 90 | "BlitzMax": "#cd6400", 91 | "PigLatin": "#fcd7de", 92 | "Lasso": "#999999", 93 | "ECL": "#8a1267", 94 | "VHDL": "#adb2cb", 95 | "Elm": "#60B5CC", 96 | "Propeller Spin": "#7fa2a7", 97 | "X10": "#4B6BEF", 98 | "IDL": "#a3522f", 99 | "ATS": "#1ac620", 100 | "Ada": "#02f88c", 101 | "Unity3D Asset": "#ab69a1", 102 | "Nu": "#c9df40", 103 | "LFE": "#004200", 104 | "SuperCollider": "#46390b", 105 | "Oxygene": "#cdd0e3", 106 | "ASP": "#6a40fd", 107 | "Assembly": "#6E4C13", 108 | "Gnuplot": "#f0a9f0", 109 | "JFlex": "#DBCA00", 110 | "NetLinx": "#0aa0ff", 111 | "Turing": "#45f715", 112 | "Vala": "#fbe5cd", 113 | "Processing": "#0096D8", 114 | "Arduino": "#bd79d1", 115 | "FLUX": "#88ccff", 116 | "NetLogo": "#ff6375", 117 | "C Sharp": "#178600", 118 | "CSS": "#563d7c", 119 | "Emacs Lisp": "#c065db", 120 | "Stan": "#b2011d", 121 | "SaltStack": "#646464", 122 | "QML": "#44a51c", 123 | "Pike": "#005390", 124 | "LOLCODE": "#cc9900", 125 | "ooc": "#b0b77e", 126 | "Handlebars": "#01a9d6", 127 | "J": "#9EEDFF", 128 | "Mask": "#f97732", 129 | "EmberScript": "#FFF4F3", 130 | "TeX": "#3D6117", 131 | "Nemerle": "#3d3c6e", 132 | "KRL": "#28431f", 133 | "Ren'Py": "#ff7f7f", 134 | "Unified Parallel C": "#4e3617", 135 | "Golo": "#88562A", 136 | "Fancy": "#7b9db4", 137 | "OCaml": "#3be133", 138 | "Shen": "#120F14", 139 | "Pascal": "#b0ce4e", 140 | "F#": "#b845fc", 141 | "Puppet": "#302B6D", 142 | "ActionScript": "#882B0F", 143 | "Diff": "#88dddd", 144 | "Ragel in Ruby Host": "#9d5200", 145 | "Fantom": "#dbded5", 146 | "Zephir": "#118f9e", 147 | "Click": "#E4E6F3", 148 | "Smalltalk": "#596706", 149 | "DM": "#447265", 150 | "Ioke": "#078193", 151 | "PogoScript": "#d80074", 152 | "LiveScript": "#499886", 153 | "JavaScript": "#f1e05a", 154 | "VimL": "#199f4b", 155 | "PureScript": "#1D222D", 156 | "ABAP": "#E8274B", 157 | "Matlab": "#bb92ac", 158 | "Slash": "#007eff", 159 | "R": "#198ce7", 160 | "Erlang": "#B83998", 161 | "Pan": "#cc0000", 162 | "LookML": "#652B81", 163 | "Eagle": "#814C05", 164 | "Scheme": "#1e4aec", 165 | "PLSQL": "#dad8d8", 166 | "Python": "#3572A5", 167 | "Max": "#c4a79c", 168 | "Common Lisp": "#3fb68b", 169 | "Latte": "#A8FF97", 170 | "XQuery": "#5232e7", 171 | "Omgrofl": "#cabbff", 172 | "XC": "#99DA07", 173 | "Nimrod": "#37775b", 174 | "SystemVerilog": "#DAE1C2", 175 | "Chapel": "#8dc63f", 176 | "Groovy": "#e69f56", 177 | "Dylan": "#6c616e", 178 | "E": "#ccce35", 179 | "Parrot": "#f3ca0a", 180 | "Grammatical Framework": "#79aa7a", 181 | "Game Maker Language": "#8fb200", 182 | "Papyrus": "#6600cc", 183 | "NetLinx+ERB": "#747faa", 184 | "Clean": "#3F85AF", 185 | "Alloy": "#64C800", 186 | "Squirrel": "#800000", 187 | "PAWN": "#dbb284", 188 | "UnrealScript": "#a54c4d", 189 | "Standard ML": "#dc566d", 190 | "Slim": "#ff8f77", 191 | "Perl6": "#0000fb", 192 | "Julia": "#a270ba", 193 | "Haskell": "#29b544", 194 | "NCL": "#28431f", 195 | "Io": "#a9188d", 196 | "Rouge": "#cc0088", 197 | "cpp": "#f34b7d", 198 | "AGS Script": "#B9D9FF", 199 | "Dogescript": "#cca760", 200 | "nesC": "#94B0C7", 201 | "unknown": "#000000" 202 | } -------------------------------------------------------------------------------- /src/views/components/search.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 189 | 190 | 193 | -------------------------------------------------------------------------------- /src/views/main.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /src/views/components/github-trending.vue: -------------------------------------------------------------------------------- 1 | 83 | 84 | 178 | 179 | 182 | 183 | -------------------------------------------------------------------------------- /src/views/components/github-trending.sass: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2018-01-20 15:33:21 5 | */ 6 | 7 | @import '../../stylesheet/common.sass' 8 | 9 | .github-trending 10 | touch-action: none 11 | overflow: hidden 12 | height: 100% 13 | 14 | &__title 15 | display: flex 16 | flex-direction: row 17 | align-items: center 18 | flex-wrap: wrap 19 | padding-left: 5px 20 | padding-right: 10px 21 | .el-radio-group 22 | margin-left: 5px 23 | white-space: nowrap 24 | 25 | .select 26 | flex: 1 27 | display: flex 28 | align-items: center 29 | 30 | .el-select 31 | margin: 5px 32 | &.language 33 | display: flex 34 | &__tags 35 | position: relative 36 | display: flex 37 | max-width: none !important 38 | padding: 5px 30px 5px 2px 39 | min-width: 195px 40 | transform: none 41 | top: 0 42 | & > span 43 | display: flex 44 | & > .el-select__input 45 | margin-left: 5px 46 | & + .el-input 47 | position: absolute 48 | top: 0 49 | left: 0 50 | right: 0 51 | bottom: 0 52 | .el-input__inner 53 | height: 40px !important 54 | 55 | .scroller 56 | padding: 10px 57 | 58 | .vue-waterfall-slot 59 | padding: 10px 60 | 61 | &__content 62 | display: flex 63 | flex-direction: row 64 | flex-wrap: wrap 65 | 66 | &__loading 67 | position: relative 68 | height: 100px 69 | .el-loading-mask 70 | background: transparent 71 | 72 | &__error 73 | padding: 50px 0 74 | text-align: center 75 | .title 76 | font-size: 32px 77 | font-weight: bold 78 | color: #333333 79 | margin: 0 80 | .content 81 | font-size: 16px 82 | color: #888888 83 | margin-bottom: 20px 84 | .button 85 | height: 58px 86 | line-height: 58px 87 | width: 220px 88 | font-size: 22px 89 | border: 0 90 | border-radius: 58px 91 | outline: 0 92 | font-size: 22px 93 | color: var(--background) 94 | background: #6abcfe 95 | text-align: center 96 | cursor: pointer 97 | 98 | .trending 99 | display: block 100 | box-shadow: none 101 | padding: 10px 102 | width: 100% 103 | 104 | .octicon 105 | width: 14px 106 | height: 16px 107 | text-align: center 108 | margin-right: 4px 109 | &-repo-forked 110 | width: 10px 111 | 112 | $colors: #f5222d #fa541c #ffa940 #ACD925 #52D925 #25D952 #25D9AC #25ACD9 #2552D9 #5225D9 #AC25D9 #ff5a93 113 | $length: length($colors) 114 | 115 | @each $color in $colors 116 | $i: index($colors, $color); 117 | &:nth-child(#{$length}n + #{$i}) .developers 118 | border-bottom-color: $color 119 | 120 | .repositories 121 | position: relative 122 | .el-card__body 123 | padding: 16px 20px 124 | cursor: pointer 125 | 126 | &__title 127 | color: var(--darkBlue) 128 | font-size: 20px 129 | line-height: 30px 130 | margin-bottom: 8px 131 | @include ellipsis 132 | 133 | &__desc 134 | height: 72px 135 | font-size: 14px 136 | line-height: 24px 137 | color: var(--fontColor) 138 | word-break: break-all 139 | @include max-text(3) 140 | 141 | &__meta 142 | display: flex 143 | height: 24px 144 | align-items: center 145 | flex-direction: row 146 | margin-top: 8px 147 | font-size: 12px 148 | margin-bottom: 6px 149 | span 150 | display: inline-block 151 | .color 152 | width: 12px 153 | height: 12px 154 | border-radius: 50% 155 | margin-right: 3px 156 | .lang 157 | margin-right: 16px 158 | .stars 159 | position: relative 160 | margin-right: 16px 161 | .added 162 | position: absolute 163 | top: 0 164 | left: -8px 165 | color: var(--red) 166 | opacity: 0 167 | transition: all 0.2s ease 168 | 169 | &__built 170 | display: flex 171 | height: 24px 172 | align-items: center 173 | flex-direction: row 174 | font-size: 12px 175 | img 176 | display: inline-block 177 | width: 20px 178 | height: 20px 179 | border-radius: 3px 180 | margin-left: 4px 181 | 182 | &:hover .repositories__meta 183 | .added 184 | opacity: 1 185 | transform: translateY(-15px) 186 | 187 | .developers 188 | border-bottom: 2px solid transparent 189 | .el-card__body 190 | padding: 24px 30px 20px 191 | 192 | &__author 193 | position: relative 194 | display: flex 195 | cursor: pointer 196 | .avatar 197 | width: 60px 198 | height: 60px 199 | border-radius: 30px 200 | overflow: hidden 201 | 202 | .author 203 | display: flex 204 | flex-direction: column 205 | justify-content: center 206 | flex: 1 207 | padding-left: 16px 208 | h2 209 | margin: 0 210 | color: var(--darkBlue) 211 | font-size: 20px 212 | line-height: 30px 213 | font-weight: normal 214 | white-space: nowrap 215 | p 216 | margin: 0 217 | font-size: 14px 218 | line-height: 24px 219 | color: var(--fontColor) 220 | 221 | &__content 222 | position: relative 223 | display: flex 224 | flex-direction: column 225 | padding-top: 40px 226 | height: 112px 227 | &:before 228 | content: '' 229 | position: absolute 230 | top: 20px 231 | left: 0 232 | width: 16px 233 | height: 1px 234 | background: var(--backgroundLightest) 235 | 236 | h3 237 | display: flex 238 | flex-direction: row 239 | justify-content: center 240 | margin: 0 241 | height: 20px 242 | +max-text(1) 243 | cursor: pointer 244 | font-weight: normal 245 | margin-bottom: 12px 246 | white-space: nowrap 247 | span 248 | font-size: 16px 249 | vertical-align: top 250 | .octicon 251 | margin-top: 2px 252 | 253 | p 254 | height: 40px 255 | font-size: 12px 256 | line-height: 20px 257 | +max-text(2) 258 | color: var(--fontColor) 259 | margin: 0 260 | word-break: break-word 261 | 262 | 263 | @media only screen and (min-width: 960px) 264 | .github-trending .trending 265 | width: 50% 266 | max-width: 50% 267 | .main-aside:not(.main-aside--show), .main-aside--collapse 268 | & ~ .main-content .github-trending .trending 269 | width: 33.3% 270 | 271 | @media only screen and (min-width: 1280px) 272 | .github-trending .trending 273 | width: 33.3% 274 | .main-aside:not(.main-aside--show), .main-aside--collapse 275 | & ~ .main-content .github-trending .trending 276 | width: 25% 277 | 278 | @media only screen and (min-width: 1600px) 279 | .github-trending .trending 280 | width: 25% 281 | .main-aside:not(.main-aside--show), .main-aside--collapse 282 | & ~ .main-content .github-trending .trending 283 | width: 20% 284 | 285 | @media only screen and (min-width: 1920px) 286 | .github-trending .trending 287 | width: 20% 288 | .main-aside:not(.main-aside--show), .main-aside--collapse 289 | & ~ .main-content .github-trending .trending 290 | width: 16.66% 291 | 292 | @media only screen and (min-width: 2560px) 293 | .github-trending .trending 294 | width: 16.66% 295 | .main-aside:not(.main-aside--show), .main-aside--collapse 296 | & ~ .main-content .github-trending .trending 297 | width: 14.285% 298 | 299 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /src/services/languages.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 卓文理 3 | * @Email: 531840344@qq.com 4 | * @Date: 2018-01-20 18:33:18 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const languages = [ 10 | { 11 | name: 'Mercury', 12 | value: 'Mercury', 13 | color: '#ff2b2b', 14 | }, 15 | { 16 | name: 'PureBasic', 17 | value: 'PureBasic', 18 | color: '#5a6986', 19 | }, 20 | { 21 | name: 'Objective C++', 22 | value: 'Objective-C++', 23 | color: '#6866fb', 24 | }, 25 | { 26 | name: 'Self', 27 | value: 'Self', 28 | color: '#0579aa', 29 | }, 30 | { 31 | name: 'edn', 32 | value: 'edn', 33 | color: '#db5855', 34 | }, 35 | { 36 | name: 'NewLisp', 37 | value: 'NewLisp', 38 | color: '#87AED7', 39 | }, 40 | { 41 | name: 'Jupyter Notebook', 42 | value: 'Jupyter Notebook', 43 | color: '#DA5B0B', 44 | }, 45 | { 46 | name: 'Rebol', 47 | value: 'Rebol', 48 | color: '#358a5b', 49 | }, 50 | { 51 | name: 'Frege', 52 | value: 'Frege', 53 | color: '#00cafe', 54 | }, 55 | { 56 | name: 'Dart', 57 | value: 'Dart', 58 | color: '#00B4AB', 59 | }, 60 | { 61 | name: 'AspectJ', 62 | value: 'AspectJ', 63 | color: '#a957b0', 64 | }, 65 | { 66 | name: 'Shell', 67 | value: 'Shell', 68 | color: '#89e051', 69 | }, 70 | { 71 | name: 'Web Ontology Language', 72 | value: 'Web Ontology Language', 73 | color: '#9cc9dd', 74 | }, 75 | { 76 | name: 'xBase', 77 | value: 'xBase', 78 | color: '#403a40', 79 | }, 80 | { 81 | name: 'Eiffel', 82 | value: 'Eiffel', 83 | color: '#946d57', 84 | }, 85 | { 86 | name: 'Nix', 87 | value: 'Nix', 88 | color: '#7e7eff', 89 | }, 90 | { 91 | name: 'RAML', 92 | value: 'RAML', 93 | color: '#77d9fb', 94 | }, 95 | { 96 | name: 'MTML', 97 | value: 'MTML', 98 | color: '#b7e1f4', 99 | }, 100 | { 101 | name: 'Racket', 102 | value: 'Racket', 103 | color: '#22228f', 104 | }, 105 | { 106 | name: 'Elixir', 107 | value: 'Elixir', 108 | color: '#6e4a7e', 109 | }, 110 | { 111 | name: 'SAS', 112 | value: 'SAS', 113 | color: '#B34936', 114 | }, 115 | { 116 | name: 'Agda', 117 | value: 'Agda', 118 | color: '#315665', 119 | }, 120 | { 121 | name: 'wisp', 122 | value: 'wisp', 123 | color: '#7582D1', 124 | }, 125 | { 126 | name: 'D', 127 | value: 'D', 128 | color: '#ba595e', 129 | }, 130 | { 131 | name: 'Kotlin', 132 | value: 'Kotlin', 133 | color: '#F18E33', 134 | }, 135 | { 136 | name: 'Opal', 137 | value: 'Opal', 138 | color: '#f7ede0', 139 | }, 140 | { 141 | name: 'Crystal', 142 | value: 'Crystal', 143 | color: '#776791', 144 | }, 145 | { 146 | name: 'Objective C', 147 | value: 'Objective-C', 148 | color: '#438eff', 149 | }, 150 | { 151 | name: 'Oz', 152 | value: 'Oz', 153 | color: '#fab738', 154 | }, 155 | { 156 | name: 'Mirah', 157 | value: 'Mirah', 158 | color: '#c7a938', 159 | }, 160 | { 161 | name: 'Objective J', 162 | value: 'Objective-J', 163 | color: '#ff0c5a', 164 | }, 165 | { 166 | name: 'Gosu', 167 | value: 'Gosu', 168 | color: '#82937f', 169 | }, 170 | { 171 | name: 'FreeMarker', 172 | value: 'FreeMarker', 173 | color: '#0050b2', 174 | }, 175 | { 176 | name: 'Component Pascal', 177 | value: 'Component Pascal', 178 | color: '#b0ce4e', 179 | }, 180 | { 181 | name: 'Arc', 182 | value: 'Arc', 183 | color: '#aa2afe', 184 | }, 185 | { 186 | name: 'Brainfuck', 187 | value: 'Brainfuck', 188 | color: '#2F2530', 189 | }, 190 | { 191 | name: 'Nit', 192 | value: 'Nit', 193 | color: '#009917', 194 | }, 195 | { 196 | name: 'APL', 197 | value: 'APL', 198 | color: '#5A8164', 199 | }, 200 | { 201 | name: 'Go', 202 | value: 'Go', 203 | color: '#375eab', 204 | }, 205 | { 206 | name: 'Visual Basic', 207 | value: 'Visual Basic', 208 | color: '#945db7', 209 | }, 210 | { 211 | name: 'PHP', 212 | value: 'PHP', 213 | color: '#4F5D95', 214 | }, 215 | { 216 | name: 'Cirru', 217 | value: 'Cirru', 218 | color: '#ccccff', 219 | }, 220 | { 221 | name: 'SQF', 222 | value: 'SQF', 223 | color: '#3F3F3F', 224 | }, 225 | { 226 | name: 'Glyph', 227 | value: 'Glyph', 228 | color: '#e4cc98', 229 | }, 230 | { 231 | name: 'Java', 232 | value: 'Java', 233 | color: '#b07219', 234 | }, 235 | { 236 | name: 'MAXScript', 237 | value: 'MAXScript', 238 | color: '#00a6a6', 239 | }, 240 | { 241 | name: 'Scala', 242 | value: 'Scala', 243 | color: '#DC322F', 244 | }, 245 | { 246 | name: 'Makefile', 247 | value: 'Makefile', 248 | color: '#427819', 249 | }, 250 | { 251 | name: 'ColdFusion', 252 | value: 'ColdFusion', 253 | color: '#ed2cd6', 254 | }, 255 | { 256 | name: 'Perl', 257 | value: 'Perl', 258 | color: '#0298c3', 259 | }, 260 | { 261 | name: 'Lua', 262 | value: 'Lua', 263 | color: '#000080', 264 | }, 265 | { 266 | name: 'Vue', 267 | value: 'Vue', 268 | color: '#2c3e50', 269 | }, 270 | { 271 | name: 'Verilog', 272 | value: 'Verilog', 273 | color: '#b2b7f8', 274 | }, 275 | { 276 | name: 'Factor', 277 | value: 'Factor', 278 | color: '#636746', 279 | }, 280 | { 281 | name: 'Haxe', 282 | value: 'Haxe', 283 | color: '#df7900', 284 | }, 285 | { 286 | name: 'Pure Data', 287 | value: 'Pure Data', 288 | color: '#91de79', 289 | }, 290 | { 291 | name: 'Forth', 292 | value: 'Forth', 293 | color: '#341708', 294 | }, 295 | { 296 | name: 'Red', 297 | value: 'Red', 298 | color: '#ee0000', 299 | }, 300 | { 301 | name: 'Hy', 302 | value: 'Hy', 303 | color: '#7790B2', 304 | }, 305 | { 306 | name: 'Volt', 307 | value: 'Volt', 308 | color: '#1F1F1F', 309 | }, 310 | { 311 | name: 'LSL', 312 | value: 'LSL', 313 | color: '#3d9970', 314 | }, 315 | { 316 | name: 'eC', 317 | value: 'eC', 318 | color: '#913960', 319 | }, 320 | { 321 | name: 'CoffeeScript', 322 | value: 'CoffeeScript', 323 | color: '#244776', 324 | }, 325 | { 326 | name: 'Lex', 327 | value: 'Lex', 328 | color: '#DBCA00', 329 | }, 330 | { 331 | name: 'API Blueprint', 332 | value: 'API Blueprint', 333 | color: '#2ACCA8', 334 | }, 335 | { 336 | name: 'Swift', 337 | value: 'Swift', 338 | color: '#ffac45', 339 | }, 340 | { 341 | name: 'C', 342 | value: 'C', 343 | color: '#555555', 344 | }, 345 | { 346 | name: 'AutoHotkey', 347 | value: 'AutoHotkey', 348 | color: '#6594b9', 349 | }, 350 | { 351 | name: 'Isabelle', 352 | value: 'Isabelle', 353 | color: '#FEFE00', 354 | }, 355 | { 356 | name: 'Metal', 357 | value: 'Metal', 358 | color: '#8f14e9', 359 | }, 360 | { 361 | name: 'Clarion', 362 | value: 'Clarion', 363 | color: '#db901e', 364 | }, 365 | { 366 | name: 'JSONiq', 367 | value: 'JSONiq', 368 | color: '#40d47e', 369 | }, 370 | { 371 | name: 'Boo', 372 | value: 'Boo', 373 | color: '#d4bec1', 374 | }, 375 | { 376 | name: 'AutoIt', 377 | value: 'AutoIt', 378 | color: '#1C3552', 379 | }, 380 | { 381 | name: 'Clojure', 382 | value: 'Clojure', 383 | color: '#db5855', 384 | }, 385 | { 386 | name: 'Rust', 387 | value: 'Rust', 388 | color: '#dea584', 389 | }, 390 | { 391 | name: 'Prolog', 392 | value: 'Prolog', 393 | color: '#74283c', 394 | }, 395 | { 396 | name: 'SourcePawn', 397 | value: 'SourcePawn', 398 | color: '#5c7611', 399 | }, 400 | { 401 | name: 'AMPL', 402 | value: 'AMPL', 403 | color: '#E6EFBB', 404 | }, 405 | { 406 | name: 'FORTRAN', 407 | value: 'FORTRAN', 408 | color: '#4d41b1', 409 | }, 410 | { 411 | name: 'ANTLR', 412 | value: 'ANTLR', 413 | color: '#9DC3FF', 414 | }, 415 | { 416 | name: 'Harbour', 417 | value: 'Harbour', 418 | color: '#0e60e3', 419 | }, 420 | { 421 | name: 'Tcl', 422 | value: 'Tcl', 423 | color: '#e4cc98', 424 | }, 425 | { 426 | name: 'BlitzMax', 427 | value: 'BlitzMax', 428 | color: '#cd6400', 429 | }, 430 | { 431 | name: 'PigLatin', 432 | value: 'PigLatin', 433 | color: '#fcd7de', 434 | }, 435 | { 436 | name: 'Lasso', 437 | value: 'Lasso', 438 | color: '#999999', 439 | }, 440 | { 441 | name: 'ECL', 442 | value: 'ECL', 443 | color: '#8a1267', 444 | }, 445 | { 446 | name: 'VHDL', 447 | value: 'VHDL', 448 | color: '#adb2cb', 449 | }, 450 | { 451 | name: 'Elm', 452 | value: 'Elm', 453 | color: '#60B5CC', 454 | }, 455 | { 456 | name: 'Propeller Spin', 457 | value: 'Propeller Spin', 458 | color: '#7fa2a7', 459 | }, 460 | { 461 | name: 'X10', 462 | value: 'X10', 463 | color: '#4B6BEF', 464 | }, 465 | { 466 | name: 'IDL', 467 | value: 'IDL', 468 | color: '#a3522f', 469 | }, 470 | { 471 | name: 'ATS', 472 | value: 'ATS', 473 | color: '#1ac620', 474 | }, 475 | { 476 | name: 'Ada', 477 | value: 'Ada', 478 | color: '#02f88c', 479 | }, 480 | { 481 | name: 'Nu', 482 | value: 'Nu', 483 | color: '#c9df40', 484 | }, 485 | { 486 | name: 'LFE', 487 | value: 'LFE', 488 | color: '#004200', 489 | }, 490 | { 491 | name: 'SuperCollider', 492 | value: 'SuperCollider', 493 | color: '#46390b', 494 | }, 495 | { 496 | name: 'Oxygene', 497 | value: 'Oxygene', 498 | color: '#cdd0e3', 499 | }, 500 | { 501 | name: 'ASP', 502 | value: 'ASP', 503 | color: '#6a40fd', 504 | }, 505 | { 506 | name: 'Assembly', 507 | value: 'Assembly', 508 | color: '#6E4C13', 509 | }, 510 | { 511 | name: 'Gnuplot', 512 | value: 'Gnuplot', 513 | color: '#f0a9f0', 514 | }, 515 | { 516 | name: 'JFlex', 517 | value: 'JFlex', 518 | color: '#DBCA00', 519 | }, 520 | { 521 | name: 'NetLinx', 522 | value: 'NetLinx', 523 | color: '#0aa0ff', 524 | }, 525 | { 526 | name: 'Turing', 527 | value: 'Turing', 528 | color: '#45f715', 529 | }, 530 | { 531 | name: 'Vala', 532 | value: 'Vala', 533 | color: '#fbe5cd', 534 | }, 535 | { 536 | name: 'Processing', 537 | value: 'Processing', 538 | color: '#0096D8', 539 | }, 540 | { 541 | name: 'Arduino', 542 | value: 'Arduino', 543 | color: '#bd79d1', 544 | }, 545 | { 546 | name: 'FLUX', 547 | value: 'FLUX', 548 | color: '#88ccff', 549 | }, 550 | { 551 | name: 'NetLogo', 552 | value: 'NetLogo', 553 | color: '#ff6375', 554 | }, 555 | { 556 | name: 'C#', 557 | value: 'C#', 558 | color: '#178600', 559 | }, 560 | { 561 | name: 'Emacs Lisp', 562 | value: 'Emacs Lisp', 563 | color: '#c065db', 564 | }, 565 | { 566 | name: 'Stan', 567 | value: 'Stan', 568 | color: '#b2011d', 569 | }, 570 | { 571 | name: 'SaltStack', 572 | value: 'SaltStack', 573 | color: '#646464', 574 | }, 575 | { 576 | name: 'QML', 577 | value: 'QML', 578 | color: '#44a51c', 579 | }, 580 | { 581 | name: 'Pike', 582 | value: 'Pike', 583 | color: '#005390', 584 | }, 585 | { 586 | name: 'LOLCODE', 587 | value: 'LOLCODE', 588 | color: '#cc9900', 589 | }, 590 | { 591 | name: 'ooc', 592 | value: 'ooc', 593 | color: '#b0b77e', 594 | }, 595 | { 596 | name: 'Handlebars', 597 | value: 'Handlebars', 598 | color: '#01a9d6', 599 | }, 600 | { 601 | name: 'J', 602 | value: 'J', 603 | color: '#9EEDFF', 604 | }, 605 | { 606 | name: 'Mask', 607 | value: 'Mask', 608 | color: '#f97732', 609 | }, 610 | { 611 | name: 'EmberScript', 612 | value: 'EmberScript', 613 | color: '#FFF4F3', 614 | }, 615 | { 616 | name: 'TeX', 617 | value: 'TeX', 618 | color: '#3D6117', 619 | }, 620 | { 621 | name: 'Nemerle', 622 | value: 'Nemerle', 623 | color: '#3d3c6e', 624 | }, 625 | { 626 | name: 'KRL', 627 | value: 'KRL', 628 | color: '#28431f', 629 | }, 630 | { 631 | name: 'Ren\'Py', 632 | value: 'Ren\'Py', 633 | color: '#ff7f7f', 634 | }, 635 | { 636 | name: 'Unified Parallel C', 637 | value: 'Unified Parallel C', 638 | color: '#4e3617', 639 | }, 640 | { 641 | name: 'Golo', 642 | value: 'Golo', 643 | color: '#88562A', 644 | }, 645 | { 646 | name: 'Fancy', 647 | value: 'Fancy', 648 | color: '#7b9db4', 649 | }, 650 | { 651 | name: 'OCaml', 652 | value: 'OCaml', 653 | color: '#3be133', 654 | }, 655 | { 656 | name: 'Shen', 657 | value: 'Shen', 658 | color: '#120F14', 659 | }, 660 | { 661 | name: 'Pascal', 662 | value: 'Pascal', 663 | color: '#b0ce4e', 664 | }, 665 | { 666 | name: 'F#', 667 | value: 'F#', 668 | color: '#b845fc', 669 | }, 670 | { 671 | name: 'Puppet', 672 | value: 'Puppet', 673 | color: '#302B6D', 674 | }, 675 | { 676 | name: 'ActionScript', 677 | value: 'ActionScript', 678 | color: '#882B0F', 679 | }, 680 | { 681 | name: 'Diff', 682 | value: 'Diff', 683 | color: '#88dddd', 684 | }, 685 | { 686 | name: 'Ragel in Ruby Host', 687 | value: 'Ragel in Ruby Host', 688 | color: '#9d5200', 689 | }, 690 | { 691 | name: 'Fantom', 692 | value: 'Fantom', 693 | color: '#dbded5', 694 | }, 695 | { 696 | name: 'Zephir', 697 | value: 'Zephir', 698 | color: '#118f9e', 699 | }, 700 | { 701 | name: 'Click', 702 | value: 'Click', 703 | color: '#E4E6F3', 704 | }, 705 | { 706 | name: 'Smalltalk', 707 | value: 'Smalltalk', 708 | color: '#596706', 709 | }, 710 | { 711 | name: 'DM', 712 | value: 'DM', 713 | color: '#447265', 714 | }, 715 | { 716 | name: 'Ioke', 717 | value: 'Ioke', 718 | color: '#078193', 719 | }, 720 | { 721 | name: 'PogoScript', 722 | value: 'PogoScript', 723 | color: '#d80074', 724 | }, 725 | { 726 | name: 'LiveScript', 727 | value: 'LiveScript', 728 | color: '#499886', 729 | }, 730 | { 731 | name: 'VimL', 732 | value: 'VimL', 733 | color: '#199f4b', 734 | }, 735 | { 736 | name: 'PureScript', 737 | value: 'PureScript', 738 | color: '#1D222D', 739 | }, 740 | { 741 | name: 'ABAP', 742 | value: 'ABAP', 743 | color: '#E8274B', 744 | }, 745 | { 746 | name: 'Matlab', 747 | value: 'Matlab', 748 | color: '#bb92ac', 749 | }, 750 | { 751 | name: 'Slash', 752 | value: 'Slash', 753 | color: '#007eff', 754 | }, 755 | { 756 | name: 'R', 757 | value: 'R', 758 | color: '#198ce7', 759 | }, 760 | { 761 | name: 'Erlang', 762 | value: 'Erlang', 763 | color: '#B83998', 764 | }, 765 | { 766 | name: 'Pan', 767 | value: 'Pan', 768 | color: '#cc0000', 769 | }, 770 | { 771 | name: 'LookML', 772 | value: 'LookML', 773 | color: '#652B81', 774 | }, 775 | { 776 | name: 'Eagle', 777 | value: 'Eagle', 778 | color: '#814C05', 779 | }, 780 | { 781 | name: 'Scheme', 782 | value: 'Scheme', 783 | color: '#1e4aec', 784 | }, 785 | { 786 | name: 'PLSQL', 787 | value: 'PLSQL', 788 | color: '#dad8d8', 789 | }, 790 | { 791 | name: 'Max', 792 | value: 'Max', 793 | color: '#c4a79c', 794 | }, 795 | { 796 | name: 'Common Lisp', 797 | value: 'Common Lisp', 798 | color: '#3fb68b', 799 | }, 800 | { 801 | name: 'Latte', 802 | value: 'Latte', 803 | color: '#A8FF97', 804 | }, 805 | { 806 | name: 'XQuery', 807 | value: 'XQuery', 808 | color: '#5232e7', 809 | }, 810 | { 811 | name: 'Omgrofl', 812 | value: 'Omgrofl', 813 | color: '#cabbff', 814 | }, 815 | { 816 | name: 'XC', 817 | value: 'XC', 818 | color: '#99DA07', 819 | }, 820 | { 821 | name: 'Nimrod', 822 | value: 'Nimrod', 823 | color: '#37775b', 824 | }, 825 | { 826 | name: 'SystemVerilog', 827 | value: 'SystemVerilog', 828 | color: '#DAE1C2', 829 | }, 830 | { 831 | name: 'Chapel', 832 | value: 'Chapel', 833 | color: '#8dc63f', 834 | }, 835 | { 836 | name: 'Groovy', 837 | value: 'Groovy', 838 | color: '#e69f56', 839 | }, 840 | { 841 | name: 'Dylan', 842 | value: 'Dylan', 843 | color: '#6c616e', 844 | }, 845 | { 846 | name: 'E', 847 | value: 'E', 848 | color: '#ccce35', 849 | }, 850 | { 851 | name: 'Parrot', 852 | value: 'Parrot', 853 | color: '#f3ca0a', 854 | }, 855 | { 856 | name: 'Grammatical Framework', 857 | value: 'Grammatical Framework', 858 | color: '#79aa7a', 859 | }, 860 | { 861 | name: 'Game Maker Language', 862 | value: 'Game Maker Language', 863 | color: '#8fb200', 864 | }, 865 | { 866 | name: 'Papyrus', 867 | value: 'Papyrus', 868 | color: '#6600cc', 869 | }, 870 | { 871 | name: 'Clean', 872 | value: 'Clean', 873 | color: '#3F85AF', 874 | }, 875 | { 876 | name: 'Alloy', 877 | value: 'Alloy', 878 | color: '#64C800', 879 | }, 880 | { 881 | name: 'Squirrel', 882 | value: 'Squirrel', 883 | color: '#800000', 884 | }, 885 | { 886 | name: 'PAWN', 887 | value: 'PAWN', 888 | color: '#dbb284', 889 | }, 890 | { 891 | name: 'UnrealScript', 892 | value: 'UnrealScript', 893 | color: '#a54c4d', 894 | }, 895 | { 896 | name: 'Standard ML', 897 | value: 'Standard ML', 898 | color: '#dc566d', 899 | }, 900 | { 901 | name: 'Slim', 902 | value: 'Slim', 903 | color: '#ff8f77', 904 | }, 905 | { 906 | name: 'Perl6', 907 | value: 'Perl6', 908 | color: '#0000fb', 909 | }, 910 | { 911 | name: 'Julia', 912 | value: 'Julia', 913 | color: '#a270ba', 914 | }, 915 | { 916 | name: 'Haskell', 917 | value: 'Haskell', 918 | color: '#29b544', 919 | }, 920 | { 921 | name: 'NCL', 922 | value: 'NCL', 923 | color: '#28431f', 924 | }, 925 | { 926 | name: 'Io', 927 | value: 'Io', 928 | color: '#a9188d', 929 | }, 930 | { 931 | name: 'Rouge', 932 | value: 'Rouge', 933 | color: '#cc0088', 934 | }, 935 | { 936 | name: 'cpp', 937 | value: 'cpp', 938 | color: '#f34b7d', 939 | }, 940 | { 941 | name: 'AGS Script', 942 | value: 'AGS Script', 943 | color: '#B9D9FF', 944 | }, 945 | { 946 | name: 'Dogescript', 947 | value: 'Dogescript', 948 | color: '#cca760', 949 | }, 950 | { 951 | name: 'nesC', 952 | value: 'nesC', 953 | color: '#94B0C7', 954 | }, 955 | { 956 | name: 'unknown', 957 | value: 'unknown', 958 | color: '#aaa', 959 | }, 960 | ].sort((a, b) => { 961 | const nameA = a.name.toUpperCase(); 962 | const nameB = b.name.toUpperCase(); 963 | 964 | if (nameA < nameB) { 965 | return 1; 966 | } 967 | if (nameA > nameB) { 968 | return 1; 969 | } 970 | 971 | return 0; 972 | }); 973 | 974 | export default [ 975 | { 976 | name: 'C++', 977 | value: 'C++', 978 | color: '#555555', 979 | }, 980 | { 981 | name: 'CSS', 982 | value: 'CSS', 983 | color: '#563d7c', 984 | }, 985 | { 986 | name: 'HTML', 987 | value: 'HTML', 988 | color: '#e44b23', 989 | }, 990 | { 991 | name: 'JavaScript', 992 | value: 'JavaScript', 993 | color: '#f1e05a', 994 | }, 995 | { 996 | name: 'Python', 997 | value: 'Python', 998 | color: '#3572A5', 999 | }, 1000 | { 1001 | name: 'Ruby', 1002 | value: 'Ruby', 1003 | color: '#701516', 1004 | }, 1005 | { 1006 | name: 'TypeScript', 1007 | value: 'TypeScript', 1008 | color: '#2b7489', 1009 | } 1010 | ].concat(languages); 1011 | --------------------------------------------------------------------------------