├── src ├── common │ ├── constants.js │ ├── appSetup.js │ ├── service-worker.js │ ├── api │ │ ├── api.js │ │ └── githubApi.js │ ├── db.js │ ├── utils.js │ └── githubLanColor.js ├── assets │ ├── logo.png │ ├── img │ │ ├── dark.png │ │ └── light.png │ ├── svgIcon │ │ ├── menu.svg │ │ ├── menu2.svg │ │ ├── check.svg │ │ ├── arrow-drop-down.svg │ │ ├── zip.svg │ │ ├── trash.svg │ │ ├── logout-line.svg │ │ ├── close.svg │ │ ├── pluse.svg │ │ ├── untag.svg │ │ ├── warning.svg │ │ ├── save-file.svg │ │ ├── home.svg │ │ ├── check-circle.svg │ │ ├── copy.svg │ │ ├── setting-line.svg │ │ ├── moon-line.svg │ │ ├── tag.svg │ │ ├── edit.svg │ │ ├── all-tag.svg │ │ ├── code.svg │ │ ├── refresh-line.svg │ │ ├── search.svg │ │ ├── apps.svg │ │ ├── sun-line.svg │ │ ├── github.svg │ │ ├── github-fill.svg │ │ ├── star.svg │ │ └── folk.svg │ ├── github.svg │ ├── login.svg │ └── hacker.svg ├── components │ ├── AppLoading.vue │ ├── AppDialog.vue │ └── SvgIcon.vue ├── App.vue ├── main.js ├── pages │ ├── Home │ │ ├── RepoList │ │ │ ├── virtualListStore.js │ │ │ ├── RepoCardSkeleton.vue │ │ │ ├── RepoList.vue │ │ │ ├── RepoCard.vue │ │ │ └── RepoCardTags.vue │ │ ├── SideMenu │ │ │ ├── LanguageSection.vue │ │ │ ├── LanguageSectionItem.vue │ │ │ ├── MenuItem.vue │ │ │ ├── TagSection.vue │ │ │ ├── TagSectionItem.vue │ │ │ └── SideMenu.vue │ │ ├── Home.vue │ │ └── DetailView │ │ │ ├── Readme.vue │ │ │ └── DetailView.vue │ ├── components │ │ ├── Setting.vue │ │ └── TagConflic.vue │ └── Login.vue ├── store │ ├── store.js │ ├── common.js │ ├── tag.js │ └── repo.js ├── registerServiceWorker.js ├── router.js └── styles │ ├── app.scss │ ├── var.light.scss │ ├── var.scss │ └── normalize.css ├── public ├── robots.txt ├── favicon.ico ├── img │ └── icons │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── mstile-150x150.png │ │ ├── apple-touch-icon.png │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon-60x60.png │ │ ├── apple-touch-icon-76x76.png │ │ ├── apple-touch-icon-120x120.png │ │ ├── apple-touch-icon-152x152.png │ │ ├── apple-touch-icon-180x180.png │ │ ├── msapplication-icon-144x144.png │ │ └── safari-pinned-tab.svg └── index.html ├── .env ├── .env.development ├── _headers ├── functions ├── tsconfig.json └── api │ └── getToken.ts ├── .gitignore ├── babel.config.js ├── README.md ├── LICENSE ├── package.json └── vue.config.js /src/common/constants.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | NODE_ENV=production 2 | VUE_APP_CLIENT_ID=Ov23liafOUFCtp8g1uA6 3 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | VUE_APP_CLIENT_ID=9aa54cfe07a1b147faaa 3 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itning/starflare/main/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itning/starflare/main/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/img/dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itning/starflare/main/src/assets/img/dark.png -------------------------------------------------------------------------------- /src/assets/img/light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itning/starflare/main/src/assets/img/light.png -------------------------------------------------------------------------------- /_headers: -------------------------------------------------------------------------------- 1 | /service-worker.js 2 | Cache-Control: no-cache, max-age=0, must-revalidate 3 | x-test-header: 123456789 4 | -------------------------------------------------------------------------------- /public/img/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itning/starflare/main/public/img/icons/favicon-16x16.png -------------------------------------------------------------------------------- /public/img/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itning/starflare/main/public/img/icons/favicon-32x32.png -------------------------------------------------------------------------------- /public/img/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itning/starflare/main/public/img/icons/mstile-150x150.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itning/starflare/main/public/img/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /public/img/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itning/starflare/main/public/img/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/img/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itning/starflare/main/public/img/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itning/starflare/main/public/img/icons/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itning/starflare/main/public/img/icons/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itning/starflare/main/public/img/icons/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itning/starflare/main/public/img/icons/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itning/starflare/main/public/img/icons/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /public/img/icons/msapplication-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itning/starflare/main/public/img/icons/msapplication-icon-144x144.png -------------------------------------------------------------------------------- /functions/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "lib": ["esnext"], 6 | "types": ["@cloudflare/workers-types"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/assets/svgIcon/menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svgIcon/menu2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/AppLoading.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | 14 | 15 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | -------------------------------------------------------------------------------- /src/assets/svgIcon/check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svgIcon/arrow-drop-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svgIcon/zip.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svgIcon/trash.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svgIcon/logout-line.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svgIcon/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svgIcon/pluse.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svgIcon/untag.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svgIcon/warning.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | -------------------------------------------------------------------------------- /src/assets/svgIcon/save-file.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svgIcon/home.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svgIcon/check-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svgIcon/copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svgIcon/setting-line.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svgIcon/moon-line.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svgIcon/tag.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@vue/cli-plugin-babel/preset', 5 | { 6 | useBuiltIns: false 7 | } 8 | ] 9 | ], 10 | plugins: [ 11 | [ 12 | 'component', 13 | { 14 | libraryName: 'element-ui', 15 | styleLibraryName: 'theme-chalk' 16 | } 17 | ] 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /src/assets/svgIcon/edit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | import router from './router' 4 | import store from './store/store' 5 | import './registerServiceWorker' 6 | 7 | import App from './App.vue' 8 | 9 | import './common/appSetup' 10 | 11 | Vue.config.productionTip = false 12 | 13 | new Vue({ 14 | router, 15 | store, 16 | render: h => h(App) 17 | }).$mount('#app') 18 | 19 | window.store = store 20 | -------------------------------------------------------------------------------- /src/components/AppDialog.vue: -------------------------------------------------------------------------------- 1 | 6 | 14 | 15 | 22 | 23 | -------------------------------------------------------------------------------- /src/assets/svgIcon/all-tag.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svgIcon/code.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svgIcon/refresh-line.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svgIcon/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/pages/Home/RepoList/virtualListStore.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | export default new Vue({ 4 | data() { 5 | return { 6 | cards: {}, 7 | tags: {} 8 | } 9 | }, 10 | methods: { 11 | updateCardData({ id, data }) { 12 | Vue.set(this.cards, id, data) 13 | }, 14 | updateTagData({ id, data }) { 15 | Vue.set(this.tags, id, data) 16 | }, 17 | clean() { 18 | this.cards = {} 19 | this.tags = {} 20 | } 21 | } 22 | }) 23 | -------------------------------------------------------------------------------- /src/assets/svgIcon/apps.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svgIcon/sun-line.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/common/appSetup.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import SvgIcon from '../components/SvgIcon.vue' 3 | import './db' 4 | import '../styles/app.scss' 5 | import '../styles/normalize.css' 6 | import 'vue-virtual-scroller/dist/vue-virtual-scroller.css' 7 | import 'highlight.js/styles/atom-one-dark.css' 8 | 9 | Vue.component('SvgIcon', SvgIcon) 10 | 11 | const importAllSGV = () => { 12 | const requireAll = requireContext => requireContext.keys().map(requireContext) 13 | const req = require.context('../assets/svgIcon/', false, /\.svg$/) 14 | requireAll(req) 15 | } 16 | importAllSGV() 17 | -------------------------------------------------------------------------------- /src/common/service-worker.js: -------------------------------------------------------------------------------- 1 | workbox.core.setCacheNameDetails({ prefix: 'cdn-gitpage-test' }) 2 | 3 | workbox.core.skipWaiting() 4 | workbox.core.clientsClaim() 5 | 6 | const cdnPath = '' 7 | 8 | const tryUseCdn = asset => { 9 | // const hasHash = /\.[0-f]+\./ 10 | // if (hasHash.test(asset.url) && !asset.cdn) asset.url = cdnPath + asset.url 11 | return asset 12 | } 13 | 14 | const precacheManifest = [] 15 | .concat(self.__precacheManifest || []) 16 | .filter(({ url }) => !url.endsWith(".png")) 17 | .map(tryUseCdn) 18 | 19 | workbox.precaching.precacheAndRoute(precacheManifest, {}) 20 | -------------------------------------------------------------------------------- /src/components/SvgIcon.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | 17 | 33 | 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Starflare 2 | 3 | A web app helps you manage your GitHub stars simply and efficiently 4 | 5 | ## Preview 6 | 7 | ![dark theme](./src/assets/img/dark.png) 8 | ![light theme](./src/assets/img/light.png) 9 | 10 | [video](https://github.com/nieheyong/starflare/assets/9368693/81e3a5d3-6c2c-4b87-9897-22fe9c02ca7b) 11 | 12 | ## Features 13 | 14 | 1. Create tag 15 | 2. Add tag for repositories 16 | 3. instant search 17 | 4. installable PWA 18 | 5. deploy on Cloudflare pages 19 | 20 | ## Similar Projects 21 | 22 | - 23 | - 24 | - 25 | -------------------------------------------------------------------------------- /src/store/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import common from './common' 4 | import repo from './repo' 5 | import tag from './tag' 6 | 7 | Vue.use(Vuex) 8 | 9 | const store = new Vuex.Store({ 10 | modules: { 11 | common, 12 | repo, 13 | tag 14 | } 15 | }) 16 | 17 | export default store 18 | 19 | window.addEventListener( 20 | 'resize', 21 | () => { 22 | store.commit('common/closeSideMenu') 23 | }, 24 | false 25 | ) 26 | 27 | const prefersDark = window.matchMedia('(prefers-color-scheme: dark)') 28 | prefersDark.addListener(mediaQuery => { 29 | const theme = mediaQuery.matches ? 'dark' : 'light' 30 | store.commit('common/setAppTheme', theme) 31 | }) 32 | -------------------------------------------------------------------------------- /src/assets/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pages/components/Setting.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 29 | 30 | 39 | -------------------------------------------------------------------------------- /src/common/api/api.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { AuthToken } from '../utils' 3 | 4 | const http = axios.create({ baseURL: `/api` }) 5 | 6 | http.interceptors.request.use( 7 | req => { 8 | Object.assign(req.headers, { 9 | authorization: AuthToken.getAppToken() 10 | }) 11 | return req 12 | }, 13 | err => { 14 | throw err 15 | } 16 | ) 17 | 18 | http.interceptors.response.use( 19 | res => res, 20 | err => { 21 | if (err.response && err.response.status === 401) { 22 | AuthToken.clean() 23 | location.href = '/' 24 | } 25 | throw err 26 | } 27 | ) 28 | 29 | export const getToken = code => http.get(`getToken?code=${code}`) 30 | 31 | export const getTag = hash => http.get(`getTag?hash=${hash}`) 32 | 33 | export const updateTag = data => http.post(`updateTag`, data) 34 | -------------------------------------------------------------------------------- /src/assets/svgIcon/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /functions/api/getToken.ts: -------------------------------------------------------------------------------- 1 | interface Env { 2 | KV: KVNamespace 3 | CLIENT_ID: string 4 | CLIENT_SECRET: string 5 | } 6 | 7 | async function getToken( 8 | code: string, 9 | client_id: string, 10 | client_secret: string 11 | ) { 12 | const query = `code=${code}&client_id=${client_id}&client_secret=${client_secret}` 13 | const url = 'https://github.com/login/oauth/access_token?' + query 14 | const res = await fetch(url, { 15 | headers: { 16 | Accept: 'application/json' 17 | } 18 | }) 19 | const body = await res.json() 20 | if (!body.access_token) throw 'access_token empty' 21 | return body 22 | } 23 | 24 | export const onRequestGet: PagesFunction = async context => { 25 | const { request, env } = context 26 | const url = new URL(request.url) 27 | const code = url.searchParams.get('code') || '' 28 | const tokenData = await getToken(code, env.CLIENT_ID, env.CLIENT_SECRET) 29 | return Response.json(tokenData) 30 | } 31 | -------------------------------------------------------------------------------- /src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import { register } from 'register-service-worker' 4 | 5 | if (process.env.NODE_ENV === 'production') { 6 | register(`./service-worker.js`, { 7 | ready () { 8 | console.log( 9 | 'App is being served from cache by a service worker.\n' + 10 | 'For more details, visit https://goo.gl/AFskqB' 11 | ) 12 | }, 13 | registered () { 14 | console.log('Service worker has been registered.') 15 | }, 16 | cached () { 17 | console.log('Content has been cached for offline use.') 18 | }, 19 | updatefound () { 20 | console.log('New content is downloading.') 21 | }, 22 | updated () { 23 | console.log('New content is available; please refresh.') 24 | }, 25 | offline () { 26 | console.log('No internet connection found. App is running in offline mode.') 27 | }, 28 | error (error) { 29 | console.error('Error during service worker registration:', error) 30 | } 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | import store from './store/store' 4 | 5 | import { AuthToken } from './common/utils' 6 | 7 | import Login from './pages/Login.vue' 8 | 9 | Vue.use(VueRouter) 10 | 11 | const routes = [ 12 | { 13 | path: '/', 14 | redirect: '/home' 15 | }, 16 | { 17 | path: '/login', 18 | name: 'Login', 19 | component: Login 20 | }, 21 | { 22 | path: '/home', 23 | name: 'Home', 24 | component: () => 25 | import(/* webpackChunkName: "app-main" */ './pages/Home/Home.vue') 26 | } 27 | ] 28 | 29 | const router = new VueRouter({ 30 | mode: 'hash', 31 | base: process.env.BASE_URL, 32 | routes 33 | }) 34 | 35 | router.beforeEach((to, from, next) => { 36 | if (AuthToken.exist()) { 37 | if (to.path === '/login') { 38 | next({ path: '/home' }) 39 | } else { 40 | next() 41 | } 42 | } else { 43 | if (to.path !== '/login') next({ path: '/login' }) 44 | else next() 45 | } 46 | }) 47 | 48 | export default router 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Heyong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/assets/svgIcon/github-fill.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src/pages/Home/RepoList/RepoCardSkeleton.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 30 | 31 | 34 | 35 | -------------------------------------------------------------------------------- /src/assets/svgIcon/star.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/common/api/githubApi.js: -------------------------------------------------------------------------------- 1 | import qs from 'query-string' 2 | import axios from 'axios' 3 | import { AuthToken } from '../utils' 4 | 5 | const http = axios.create({ baseURL: `https://api.github.com` }) 6 | 7 | http.interceptors.request.use( 8 | req => { 9 | Object.assign(req.headers, { 10 | authorization: AuthToken.getGithubToken() 11 | }) 12 | return req 13 | }, 14 | err => { 15 | throw err 16 | } 17 | ) 18 | 19 | http.interceptors.response.use( 20 | res => res, 21 | err => { 22 | if (err.response && err.response.status === 401) { 23 | AuthToken.clean() 24 | location.href = '/' 25 | } 26 | throw err 27 | } 28 | ) 29 | 30 | export const getLoginUser = () => http.get(`user`) 31 | 32 | export const getUser = userName => http.get(`users/${userName}`) 33 | 34 | export const getLoginUserStarred = (per_page = 40, page = 1) => 35 | http.get(`user/starred?` + qs.stringify({ per_page, page })) 36 | 37 | export const getUserStarred = (userName, per_page = 40, page = 1) => 38 | http.get(`users/${userName}/starred?` + qs.stringify({ per_page, page })) 39 | 40 | export const getReadme = (owner, repo) => 41 | http.get(`repos/${owner}/${repo}/readme`, { 42 | headers: { Accept: 'application/vnd.github.VERSION.raw' } 43 | }) 44 | -------------------------------------------------------------------------------- /src/store/common.js: -------------------------------------------------------------------------------- 1 | const getUserInfo = () => { 2 | let userInfo = {} 3 | try { 4 | userInfo = localStorage.userInfo ? JSON.parse(localStorage.userInfo) : {} 5 | } catch {} 6 | return userInfo 7 | } 8 | 9 | export default { 10 | namespaced: true, 11 | state: { 12 | sideMenuShow: false, 13 | settingShow: false, 14 | appTheme: localStorage.appTheme === 'light' ? 'light' : 'dark', 15 | userInfo: getUserInfo() 16 | }, 17 | mutations: { 18 | closeSideMenu(state) { 19 | state.sideMenuShow = false 20 | }, 21 | toggleSideMenu(state) { 22 | state.sideMenuShow = !state.sideMenuShow 23 | }, 24 | toggleSetting(state) { 25 | state.settingShow = !state.settingShow 26 | }, 27 | setAppTheme(state, theme) { 28 | var themeColorMeta = document.querySelector('meta[name=theme-color]') 29 | themeColorMeta.content = theme === 'dark' ? '#141419' : '#f3f3f3' 30 | document.documentElement.setAttribute('data-theme', theme) 31 | state.appTheme = localStorage.appTheme = theme 32 | }, 33 | setUserInfo(state, userInfo) { 34 | state.userInfo = userInfo 35 | localStorage.userInfo = JSON.stringify(userInfo) 36 | } 37 | }, 38 | actions: { 39 | toggleAppTheme({ commit, state }) { 40 | const theme = state.appTheme === 'light' ? 'dark' : 'light' 41 | commit('setAppTheme', theme) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/pages/Home/SideMenu/LanguageSection.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 31 | 32 | 63 | 64 | -------------------------------------------------------------------------------- /src/common/db.js: -------------------------------------------------------------------------------- 1 | import Dexie from 'dexie' 2 | const db = new Dexie('Database') 3 | window.db = db 4 | 5 | db.version(1).stores({ 6 | data: 'name', 7 | readme: 'id' 8 | }) 9 | 10 | const genQueueSave = saveFun => { 11 | let saving = false 12 | let queueSaveData = null 13 | return async data => { 14 | queueSaveData = data 15 | if (saving) return 16 | saving = true 17 | while (queueSaveData) { 18 | const whenSaveDone = saveFun(queueSaveData) 19 | queueSaveData = null 20 | await whenSaveDone 21 | } 22 | saving = false 23 | } 24 | } 25 | 26 | const openDb = async () => { 27 | try { 28 | await db.open() 29 | } catch (e) { 30 | console.error(e) 31 | } 32 | } 33 | 34 | export const getData = async (name, fallback = null) => { 35 | try { 36 | await openDb() 37 | const res = await db.data.get(name) 38 | if (res) return res.data 39 | } catch (e) { 40 | console.error(e) 41 | } 42 | return fallback 43 | } 44 | 45 | export const setData = async (name, data) => { 46 | try { 47 | await openDb() 48 | await db.data.put({ name, data }) 49 | } catch (e) { 50 | console.error(e) 51 | } 52 | } 53 | 54 | export const getRepos = () => getData('repos', []) 55 | 56 | const setRepos = repos => setData('repos', repos) 57 | 58 | export const updateRepos = genQueueSave(setRepos) 59 | 60 | export const getUserTags = () => getData('tags', []) 61 | 62 | const setUserTags = userTag => setData('tags', userTag) 63 | 64 | export const updateUserTags = genQueueSave(setUserTags) 65 | 66 | export const getReadme = id => db.readme.get(id) 67 | 68 | export const setReadme = readme => 69 | db.readme.put({ ...readme, time: Date.now() }) 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "starflare", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vue-cli-service serve", 7 | "build": "vue-cli-service build && cp _headers dist/" 8 | }, 9 | "dependencies": { 10 | "axios": "^0.19.0", 11 | "core-js": "^3.3.2", 12 | "dexie": "^2.0.4", 13 | "dompurify": "^2.0.7", 14 | "element-ui": "^2.13.0", 15 | "highlight.js": "^9.15.10", 16 | "http-link-header": "^1.0.2", 17 | "marked": "^0.7.0", 18 | "md5": "^2.2.1", 19 | "query-string": "^6.8.3", 20 | "register-service-worker": "^1.6.2", 21 | "url-join": "^4.0.1", 22 | "vue": "^2.6.10", 23 | "vue-popperjs": "^2.2.0", 24 | "vue-router": "^3.1.3", 25 | "vue-text-highlight": "^2.0.10", 26 | "vue-virtual-scroller": "^1.0.0-rc.2", 27 | "vuedraggable": "^2.23.2", 28 | "vuex": "^3.0.1" 29 | }, 30 | "devDependencies": { 31 | "@cloudflare/workers-types": "^4.20240405.0", 32 | "@vue/cli-plugin-babel": "^4.0.0", 33 | "@vue/cli-plugin-pwa": "^4.0.0", 34 | "@vue/cli-plugin-router": "^4.0.0", 35 | "@vue/cli-plugin-vuex": "^4.0.0", 36 | "@vue/cli-service": "^4.0.0", 37 | "babel-plugin-component": "^1.1.1", 38 | "sass": "^1.74.1", 39 | "sass-loader": "^8.0.0", 40 | "svg-sprite-loader": "^4.1.6", 41 | "typescript": "^5.4.4", 42 | "vue-template-compiler": "^2.6.10" 43 | }, 44 | "engines": { 45 | "node": "^16", 46 | "yarn": "^1.22" 47 | }, 48 | "postcss": { 49 | "plugins": { 50 | "autoprefixer": {} 51 | } 52 | }, 53 | "prettier": { 54 | "semi": false, 55 | "singleQuote": true, 56 | "arrowParens": "avoid", 57 | "trailingComma": "none" 58 | }, 59 | "browserslist": [ 60 | "> 1%", 61 | "last 2 versions" 62 | ] 63 | } -------------------------------------------------------------------------------- /src/pages/Home/SideMenu/LanguageSectionItem.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 51 | 52 | 65 | 66 | -------------------------------------------------------------------------------- /src/pages/Home/SideMenu/MenuItem.vue: -------------------------------------------------------------------------------- 1 | 58 | 59 | 68 | 69 | 78 | 79 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 14 | 15 | 16 | Starflare 17 | 18 | 19 | 21 | 22 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 42 | 55 |
56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/assets/svgIcon/folk.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/login.svg: -------------------------------------------------------------------------------- 1 | 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 | -------------------------------------------------------------------------------- /src/common/utils.js: -------------------------------------------------------------------------------- 1 | import linkHeader from 'http-link-header' 2 | import qs from 'query-string' 3 | import githubLanColor from './githubLanColor' 4 | 5 | export const delay = ms => new Promise(res => setTimeout(res, ms)) 6 | 7 | export const AuthToken = { 8 | getAppToken() { 9 | return localStorage.appToken 10 | }, 11 | getGithubToken() { 12 | return localStorage.githubToken 13 | }, 14 | setToken(appToken, githubToken) { 15 | localStorage.githubToken = githubToken 16 | localStorage.appToken = appToken 17 | }, 18 | clean() { 19 | AuthToken.setToken('', '') 20 | }, 21 | exist() { 22 | return !!AuthToken.getAppToken() 23 | } 24 | } 25 | 26 | // https://stackoverflow.com/questions/4068373/center-a-popup-window-on-screen 27 | export const openWindowCenter = (url, title, w, h) => { 28 | // Fixes dual-screen position Most browsers Firefox 29 | var dualScreenLeft = 30 | window.screenLeft != undefined ? window.screenLeft : window.screenX 31 | var dualScreenTop = 32 | window.screenTop != undefined ? window.screenTop : window.screenY 33 | 34 | var width = window.innerWidth 35 | ? window.innerWidth 36 | : document.documentElement.clientWidth 37 | ? document.documentElement.clientWidth 38 | : screen.width 39 | var height = window.innerHeight 40 | ? window.innerHeight 41 | : document.documentElement.clientHeight 42 | ? document.documentElement.clientHeight 43 | : screen.height 44 | 45 | var systemZoom = width / window.screen.availWidth 46 | var left = (width - w) / 2 / systemZoom + dualScreenLeft 47 | var top = (height - h) / 2 / systemZoom + dualScreenTop 48 | var newWindow = window.open( 49 | url, 50 | title, 51 | 'scrollbars=yes, width=' + 52 | w / systemZoom + 53 | ', height=' + 54 | h / systemZoom + 55 | ', top=' + 56 | top + 57 | ', left=' + 58 | left 59 | ) 60 | 61 | // Puts focus on the newWindow 62 | if (window.focus) newWindow.focus() 63 | return newWindow 64 | } 65 | 66 | export const getPageFromLinkStr = linkStr => { 67 | const link = linkHeader.parse(linkStr) 68 | const refs = link.get('rel', 'last') 69 | if (refs.length) { 70 | const res = qs.parseUrl(refs[0].uri) 71 | return res.query.page 72 | } 73 | return 1 74 | } 75 | 76 | export const getLanguageColor = lan => githubLanColor[lan] 77 | 78 | export function downloadString(text, fileType, fileName) { 79 | const blob = new Blob([text], { type: fileType }) 80 | 81 | const a = document.createElement('a') 82 | a.download = fileName 83 | a.href = URL.createObjectURL(blob) 84 | a.dataset.downloadurl = [fileType, a.download, a.href].join(':') 85 | a.style.display = 'none' 86 | document.body.appendChild(a) 87 | a.click() 88 | document.body.removeChild(a) 89 | setTimeout(function () { 90 | URL.revokeObjectURL(a.href) 91 | }, 1500) 92 | } 93 | 94 | 95 | export function trackEvent(eventName, props = {}) { 96 | gtag('event', eventName, props) 97 | } -------------------------------------------------------------------------------- /src/store/tag.js: -------------------------------------------------------------------------------- 1 | import * as db from '../common/db' 2 | import * as api from '../common/api/api' 3 | import md5 from 'md5' 4 | 5 | const EMPTY_TAG_HASH = 'd751713988987e9331980363e24189ce' 6 | 7 | const getTagsHash = userTags => { 8 | const res = userTags.reduce((prev, tag) => { 9 | return [...prev, [tag.name, tag.repos]] 10 | }, []) 11 | return md5(JSON.stringify(res)) 12 | } 13 | 14 | export default { 15 | namespaced: true, 16 | state: { 17 | isSyncing: true, 18 | cloudTag: null, 19 | tags: [] 20 | }, 21 | getters: { 22 | tagConflic(state) { 23 | return !!state.cloudTag 24 | }, 25 | localUserTagHash(state) { 26 | return getTagsHash(state.tags) 27 | } 28 | }, 29 | mutations: { 30 | setSyncSatus(state, payload) { 31 | state.isSyncing = payload 32 | }, 33 | updateTags(state, tags) { 34 | state.tags = tags 35 | }, 36 | setCloudTag(state, tags) { 37 | state.cloudTag = tags 38 | }, 39 | cleanConflic(state) { 40 | state.cloudTag = null 41 | } 42 | }, 43 | actions: { 44 | updateAndSaveTags({ commit }, tags) { 45 | commit('updateTags', tags) 46 | db.updateUserTags(tags) 47 | }, 48 | removeTag({ state, dispatch }, tag) { 49 | const tags = state.tags.filter(item => item.name !== tag.name) 50 | dispatch('updateAndSaveTags', tags) 51 | }, 52 | async loadTags({ getters, commit, dispatch }) { 53 | const tags = await db.getUserTags() 54 | commit('updateTags', tags) 55 | return 56 | // commit('setSyncSatus', true) 57 | // const tags = await db.getUserTags() 58 | // commit('updateTags', tags) 59 | // const hashBase = localStorage.localUserTagHashBase || EMPTY_TAG_HASH 60 | // const tagChanged = hashBase !== getters.localUserTagHash 61 | // if (tagChanged) { 62 | // const res = await api.updateTag({ 63 | // baseHash: hashBase, 64 | // userTag: { 65 | // hash: getters.localUserTagHash, 66 | // tags 67 | // } 68 | // }) 69 | // if (res.data.message === 'ok') { 70 | // localStorage.localUserTagHashBase = getters.localUserTagHash 71 | // } else { 72 | // commit('setCloudTag', res.data) 73 | // } 74 | // //saveTag 75 | // } else { 76 | // const res = await api.getTag(hashBase) 77 | // const cloudTags = res.data 78 | // localStorage.localUserTagHashBase = cloudTags.hash 79 | // if (cloudTags.hash !== hashBase) { 80 | // dispatch('updateAndSaveTags', cloudTags.tags) 81 | // } 82 | // } 83 | // commit('setSyncSatus', false) 84 | }, 85 | async washTags({ state, commit, dispatch, rootState }) { 86 | const allRepoIds = new Set(rootState.repo.repos.map(repo => repo.id)) 87 | const freshTags = state.tags.map(tag => { 88 | tag.repos = tag.repos.filter(id => allRepoIds.has(id)) 89 | return tag 90 | }) 91 | dispatch('updateAndSaveTags', freshTags) 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/pages/Home/Home.vue: -------------------------------------------------------------------------------- 1 | 69 | 70 | 88 | 89 | 134 | -------------------------------------------------------------------------------- /src/pages/Home/DetailView/Readme.vue: -------------------------------------------------------------------------------- 1 | 66 | 67 | 80 | 81 | 147 | 148 | -------------------------------------------------------------------------------- /src/pages/Home/RepoList/RepoList.vue: -------------------------------------------------------------------------------- 1 | 66 | 67 | 102 | 103 | 150 | 151 | -------------------------------------------------------------------------------- /src/pages/Home/DetailView/DetailView.vue: -------------------------------------------------------------------------------- 1 | 2 | 67 | 68 | 95 | 96 | 155 | -------------------------------------------------------------------------------- /src/styles/app.scss: -------------------------------------------------------------------------------- 1 | @import 'var.scss'; 2 | @import 'var.light.scss'; 3 | html, 4 | body { 5 | margin: 0; 6 | padding: 0; 7 | width: 100%; 8 | height: 100%; 9 | font-size: var(--APP_FontSize); 10 | overflow: hidden; 11 | } 12 | 13 | body { 14 | box-sizing: border-box; 15 | text-rendering: optimizeLegibility; 16 | line-height: 1.5; 17 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, 18 | Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; 19 | background: var(--App_Background); 20 | color: var(--APP_HtmlColor); 21 | } 22 | 23 | *, 24 | *:before, 25 | *:after { 26 | box-sizing: inherit; 27 | } 28 | 29 | .u-text-left { 30 | text-align: left !important; 31 | } 32 | .u-text-center { 33 | text-align: center !important; 34 | } 35 | .u-text-right { 36 | text-align: right !important; 37 | } 38 | 39 | ::-webkit-scrollbar-thumb { 40 | border-radius: 10px; 41 | background: var(--APP_Scroller-Background); 42 | } 43 | 44 | ::-webkit-scrollbar { 45 | width: 4px; 46 | height: 4px; 47 | } 48 | 49 | ::-webkit-scrollbar-track, 50 | ::-webkit-scrollbar-track-piece, 51 | ::-webkit-scrollbar-corner, 52 | ::-webkit-scrollbar-button, 53 | ::-webkit-resizer { 54 | display: none; 55 | } 56 | 57 | a { 58 | color: var(--APP_AHerfColor); 59 | text-decoration: none; 60 | &:hover { 61 | text-decoration: underline; 62 | } 63 | } 64 | .o-input { 65 | -webkit-appearance: none; 66 | border-radius: 4px; 67 | box-sizing: border-box; 68 | display: inline-block; 69 | font-size: inherit; 70 | outline: none; 71 | border: unset; 72 | color: inherit; 73 | font: inherit; 74 | } 75 | 76 | @mixin button { 77 | box-sizing: border-box; 78 | display: inline-block; 79 | transition: 0.1s; 80 | margin: 0; 81 | outline: none; 82 | border: 1px solid transparent; 83 | border-radius: 4px; 84 | cursor: pointer; 85 | padding: 12px 20px; 86 | text-align: center; 87 | line-height: 1; 88 | white-space: nowrap; 89 | color: #606266; 90 | font-weight: 500; 91 | -moz-user-select: none; 92 | -ms-user-select: none; 93 | -webkit-appearance: none; 94 | -webkit-user-select: none; 95 | } 96 | .o-button { 97 | @include button(); 98 | } 99 | .o-button--success { 100 | @include button(); 101 | color: #000; 102 | background-color: #67c23a; 103 | border-color: #67c23a; 104 | } 105 | .o-button, 106 | .o-button--success { 107 | &:hover { 108 | background: #85ce61; 109 | border-color: #85ce61; 110 | } 111 | &:active { 112 | background: #5daf34; 113 | border-color: #5daf34; 114 | } 115 | &.is-round { 116 | border-radius: 100px; 117 | padding: 12px 23px; 118 | } 119 | } 120 | 121 | .o-loading { 122 | width: 50px; 123 | height: 50px; 124 | position: relative; 125 | @keyframes sk-bounce { 126 | 0%, 127 | 100% { 128 | transform: scale(0); 129 | -webkit-transform: scale(0); 130 | } 131 | 50% { 132 | transform: scale(1); 133 | -webkit-transform: scale(1); 134 | } 135 | } 136 | &::before, 137 | &::after { 138 | position: absolute; 139 | top: 0; 140 | left: 0; 141 | border-radius: 50%; 142 | background: var(--APP_Loading-Color); 143 | width: 100%; 144 | height: 100%; 145 | animation: sk-bounce 1s infinite ease-in-out; 146 | content: ' '; 147 | } 148 | 149 | &::after { 150 | animation-delay: -0.5s; 151 | } 152 | } 153 | 154 | .o-skeleton-loader { 155 | background: rgba(255, 255, 255, 0.05); 156 | border-radius: 2px; 157 | overflow: hidden; 158 | position: relative; 159 | &::after { 160 | @keyframes loading { 161 | 100% { 162 | transform: translateX(100%); 163 | } 164 | } 165 | animation: loading 1.5s infinite; 166 | background: linear-gradient( 167 | 90deg, 168 | transparent, 169 | rgba(255, 255, 255, 0.02), 170 | transparent 171 | ); 172 | content: ''; 173 | height: 100%; 174 | left: 0; 175 | position: absolute; 176 | right: 0; 177 | top: 0; 178 | transform: translateX(-100%); 179 | z-index: 1; 180 | } 181 | } 182 | 183 | .o-flex-expend { 184 | flex: 1; 185 | } 186 | -------------------------------------------------------------------------------- /src/pages/Login.vue: -------------------------------------------------------------------------------- 1 | 60 | 90 | 91 | 155 | -------------------------------------------------------------------------------- /src/pages/Home/SideMenu/TagSection.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 83 | 84 | 162 | 163 | -------------------------------------------------------------------------------- /src/store/repo.js: -------------------------------------------------------------------------------- 1 | import * as db from '../common/db' 2 | import { delay, getPageFromLinkStr } from '../common/utils' 3 | import * as githubApi from '../common/api/githubApi' 4 | import virtualListStore from '../pages/Home/RepoList/virtualListStore' 5 | 6 | export default { 7 | namespaced: true, 8 | state: { 9 | repos: [], 10 | isFetching: true, 11 | isSyncing: true, 12 | filter: { 13 | searchStr: '', 14 | type: 'all', 15 | tag: null, 16 | language: null 17 | }, 18 | scrollToTop: Date.now() 19 | }, 20 | getters: { 21 | untagedRepos(state, getters, rootState) { 22 | const tagedIds = rootState.tag.tags.reduce( 23 | (prev, tag) => prev.concat(tag.repos), 24 | [] 25 | ) 26 | const tagedIdSet = new Set(tagedIds) 27 | return state.repos.filter(repo => !tagedIdSet.has(repo.id)) 28 | }, 29 | filterByMenu(state, getters) { 30 | const { type, tag, language } = state.filter 31 | if (type === 'untaged') return getters.untagedRepos 32 | const allRepos = state.repos 33 | if (type === 'taged') 34 | return allRepos.filter(repo => tag.repos.includes(repo.id)) 35 | if (type === 'language') 36 | return allRepos.filter(repo => repo.language === language) 37 | if (type === 'all') return allRepos 38 | }, 39 | filterBySearch(state, getters) { 40 | const searchStr = state.filter.searchStr.toLowerCase() 41 | const repos = getters.filterByMenu 42 | if (searchStr) { 43 | const result = [] 44 | const searchKey = key => { 45 | return repo => { 46 | let value = repo[key] || '' 47 | value = value.toLowerCase() 48 | const valid = value.includes(searchStr) 49 | if (valid) { 50 | result.push(repo) 51 | return false 52 | } 53 | return true 54 | } 55 | } 56 | repos.filter(searchKey('name')).filter(searchKey('description')) 57 | return result 58 | } 59 | return repos 60 | }, 61 | reposShow(state, getters) { 62 | return getters.filterBySearch 63 | }, 64 | languages(state) { 65 | let language = state.repos.reduce((prev, repo) => { 66 | const lan = repo.language 67 | if (lan) { 68 | if (!prev.has(lan)) prev.set(lan, 1) 69 | else prev.set(lan, prev.get(lan) + 1) 70 | } 71 | return prev 72 | }, new Map()) 73 | 74 | language = [...language.entries()] 75 | .map(([name, count]) => ({ 76 | name, 77 | count 78 | })) 79 | .sort((a, b) => (a.name > b.name ? 1 : -1)) 80 | return language 81 | } 82 | }, 83 | mutations: { 84 | setSearchStr(state, searchStr) { 85 | state.filter.searchStr = searchStr 86 | state.scrollToTop = Date.now() 87 | }, 88 | closeRepoLoading(state) { 89 | state.isFetching = false 90 | }, 91 | setSyncSatus(state, payload) { 92 | state.isSyncing = payload 93 | }, 94 | updateRepos(state, repos) { 95 | state.repos = repos 96 | }, 97 | filterRepos(state, data) { 98 | state.scrollToTop = Date.now() 99 | state.filter = Object.assign( 100 | { 101 | searchStr: '', 102 | type: 'all', 103 | tag: null, 104 | language: null 105 | }, 106 | data 107 | ) 108 | virtualListStore.clean() 109 | } 110 | }, 111 | actions: { 112 | async loadRepos({ commit, dispatch }) { 113 | commit('setSyncSatus', true) 114 | const localRepos = await db.getRepos() 115 | commit('updateRepos', localRepos || []) 116 | const newStar = [] 117 | const patch = repoRes => { 118 | repoRes.data.forEach(repo => { 119 | const index = localRepos.findIndex(item => repo.id === item.id) 120 | if (-1 < index) localRepos[index] = repo 121 | else newStar.push(repo) 122 | }) 123 | const repos = newStar.concat(localRepos) 124 | commit('updateRepos', repos) 125 | db.updateRepos(repos) 126 | } 127 | 128 | const firstPageRes = await githubApi.getLoginUserStarred(100, 1) 129 | patch(firstPageRes) 130 | commit('closeRepoLoading') 131 | 132 | let pageCount = getPageFromLinkStr(firstPageRes.headers.link) 133 | const freshRepoRes = [firstPageRes] 134 | let page = 2 135 | while (page <= pageCount) { 136 | const limit = 2 137 | let remainPages = pageCount - page 138 | let count = remainPages >= limit ? limit : remainPages + 1 139 | const plist = [] 140 | while (count--) { 141 | plist.push(githubApi.getLoginUserStarred(100, page++)) 142 | } 143 | const resList = await Promise.all(plist) 144 | for (let res of resList) { 145 | await patch(res) 146 | freshRepoRes.push(res) 147 | } 148 | } 149 | const freshRepos = freshRepoRes.reduce( 150 | (prev, res) => prev.concat(res.data), 151 | [] 152 | ) 153 | db.updateRepos(freshRepos) 154 | commit('updateRepos', freshRepos) 155 | commit('setSyncSatus', false) 156 | dispatch('tag/washTags', null, { root: true }) 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/pages/Home/RepoList/RepoCard.vue: -------------------------------------------------------------------------------- 1 | 100 | 101 | 146 | 147 | 194 | 195 | -------------------------------------------------------------------------------- /src/pages/Home/SideMenu/TagSectionItem.vue: -------------------------------------------------------------------------------- 1 | 72 | 73 | 116 | 117 | 186 | -------------------------------------------------------------------------------- /src/styles/var.light.scss: -------------------------------------------------------------------------------- 1 | :root[data-theme='light'] { 2 | // app 3 | --APP_BrandColor: #e45009; 4 | --App_Background: #f3f3f3; 5 | --APP_FontSize: 13px; 6 | --APP_HtmlColor: #444; 7 | --APP_AHerfColor: var(--APP_BrandColor); 8 | --APP_Scroller-Background: rgba(0, 0, 0, 0.1); 9 | --APP_Loading-Color: rgba(0, 0, 0, 0.05); 10 | // side menu 11 | --SideMenu_Container_Background: #f3f3f3; 12 | --SideMenu_Avatar_Background: rgba(0, 0, 0, 0.05); 13 | --SideMenu_LoginName_Color: #444; 14 | // menu item 15 | --SideMenu_Menu_Color: rgb(101, 119, 134); 16 | --SideMenu_Menu_Color__Hover: #000; 17 | --SideMenu_Menu_Color__Active: #000; 18 | 19 | --SideMenu_Menu_Background: transparent; 20 | --SideMenu_Menu_Background__Hover: transparent; 21 | --SideMenu_Menu_Background__Active: rgba(0, 0, 0, 0.04); 22 | 23 | --SideMenu_Menu_BorderColor: transparent; 24 | --SideMenu_Menu_BorderColor__Hover: transparent; 25 | --SideMenu_Menu_BorderColor__Active: rgba(0, 0, 0, 0.1); 26 | 27 | --SideMenu_MenuIcon_Color: var(--SideMenu_Menu_Color); 28 | --SideMenu_MenuIcon_Color__Hover: #000; 29 | --SideMenu_MenuIcon_Color__Active: #fff; 30 | 31 | --SideMenu_MenuIcon_Background: rgba(0, 0, 0, 0.1); 32 | --SideMenu_MenuIcon_Background__Hover: rgba(0, 0, 0, 0.15); 33 | --SideMenu_MenuIcon_Background__Active: var(--APP_BrandColor); 34 | // tag 35 | --SideMenu_Tag_Color: rgb(101, 119, 134); 36 | --SideMenu_Tag_Color__Hover: var(--SideMenu_Menu_Color__Hover); 37 | --SideMenu_Tag_Color__Active: var(--SideMenu_Menu_Color__Active); 38 | 39 | --SideMenu_Tag_Background: transparent; 40 | --SideMenu_Tag_Background__Hover: transparent; 41 | --SideMenu_Tag_Background__Active: var(--SideMenu_Menu_Background__Active); 42 | 43 | --SideMenu_Tag_BorderColor: transparent; 44 | --SideMenu_Tag_BorderColor__Hover: transparent; 45 | --SideMenu_Tag_BorderColor__Active: var(--SideMenu_Menu_BorderColor__Active); 46 | 47 | --SideMenu_TagInput_Color: #000; 48 | --SideMenu_TagInput_Background: #fff; 49 | 50 | --SideMenu_TagToolButton_Color: #aaa; 51 | --SideMenu_TagToolButton_Color__Hover: #000; 52 | 53 | // language-item 54 | --SideMenu_LanguageItem_Color: var(--SideMenu_Tag_Color); 55 | --SideMenu_LanguageItem_Color__Hover: var(--SideMenu_Menu_Color__Hover); 56 | --SideMenu_LanguageItem_Color__Active: var(--SideMenu_Menu_Color__Active); 57 | 58 | --SideMenu_LanguageItem_Background: transparent; 59 | --SideMenu_LanguageItem_Background__Hover: transparent; 60 | --SideMenu_LanguageItem_Background__Active: var( 61 | --SideMenu_Menu_Background__Active 62 | ); 63 | 64 | --SideMenu_LanguageItem_BorderColor: transparent; 65 | --SideMenu_LanguageItem_BorderColor__Hover: transparent; 66 | --SideMenu_LanguageItem_BorderColor__Active: var( 67 | --SideMenu_Menu_BorderColor__Active 68 | ); 69 | 70 | // SyncButton 71 | --SideMenu_SyncButton_Color: #888; 72 | --SideMenu_SyncButton_Color__Hover: #000; 73 | 74 | --SideMenu_SyncButton_Background: transparent; 75 | --SideMenu_SyncButton_Background__Hover: rgba(0, 0, 0, 0.1); 76 | 77 | --SideMenu_SyncButton_BorderColor: rgba(0, 0, 0, 0.2); 78 | 79 | // IconButton 80 | --SideMenu_IconButton_Color: #888; 81 | --SideMenu_IconButton_Color__Hover: #000; 82 | 83 | --SideMenu_IconButton_Background: rgba(0, 0, 0, 0.1); 84 | --SideMenu_IconButton_Background__Hover: rgba(0, 0, 0, 0.15); 85 | 86 | //repo List 87 | --RepoList_Background: #fafafa; 88 | --RepoList_Search_Color: #000; 89 | --RepoList_Search_Background: rgba(0, 0, 0, 0.1); 90 | 91 | //repo Card 92 | --RepoCard_Background: transparent; 93 | --RepoCard_Background__Hover: transparent; 94 | --RepoCard_Background__Active: #fff; 95 | 96 | --RepoCard_Indictor_Background: transparent; 97 | --RepoCard_Indictor_Background__Hover: rgba(0, 0, 0, 0.1); 98 | --RepoCard_Indictor_Background__Active: var(--APP_BrandColor); 99 | 100 | --RepoCard_Title_Color: #111; 101 | --SideMenu_Title_Color__Hover: var(--APP_BrandColor); 102 | 103 | --RepoCard_Body_Color: #777; 104 | --RepoCard_Foot_Color: var(--RepoCard_Body_Color); 105 | 106 | // repo card tag 107 | --RepoCard_TagTogger_Color: #777; 108 | --RepoCard_TagTogger_Color__Hover: #000; 109 | 110 | --RepoCard_Tag_Color: #999; 111 | --RepoCard_Tag_Color__Hover: #000; 112 | --RepoCard_Tag_Color__Active: #fff; 113 | 114 | --RepoCard_Tag_Background: transparent; 115 | --RepoCard_Tag_Background__Hover: transparent; 116 | --RepoCard_Tag_Background__Active: var(--APP_BrandColor); 117 | 118 | --RepoCard_Tag_BorderColor: #999; 119 | --RepoCard_Tag_BorderColor__Hover: #999; 120 | --RepoCard_Tag_BorderColor__Active: var(--APP_BrandColor); 121 | 122 | // repo card Action Button 123 | --RepoCard_Tag_CancleButton_Color: #444; 124 | --RepoCard_Tag_CancleButton_Color__Hover: #000; 125 | 126 | --RepoCard_Tag_CancleButton_Background: #c0c0c0; 127 | --RepoCard_Tag_CancleButton_Background__Hover: #aaa; 128 | 129 | --RepoCard_Tag_CancleButton_BorderColor: transparent; 130 | --RepoCard_Tag_CancleButton_BorderColor__Hover: transparent; 131 | 132 | --RepoCard_Tag_ConfirmButton_Color: #ddd; 133 | --RepoCard_Tag_ConfirmButton_Color__Hover: #fff; 134 | 135 | --RepoCard_Tag_ConfirmButton_Background: #4a9600; 136 | --RepoCard_Tag_ConfirmButton_Background__Hover: #5a9600; 137 | 138 | --RepoCard_Tag_ConfirmButton_BorderColor: transparent; 139 | --RepoCard_Tag_ConfirmButton_BorderColor__Hover: transparent; 140 | 141 | // detail view 142 | --RpoeDetail_Background: #fff; 143 | 144 | --RpoeDetail_IconButton_Color: #aaa; 145 | --RpoeDetail_IconButton_Color__Hover: #000; 146 | 147 | --ReadMe-Color: var(--APP_HtmlColor); 148 | --ReadMe-Pre-Color: #d1e8ff; 149 | --ReadMe-Pre-Background: #444; 150 | --ReadMe-Blockquote-Color: var(--ReadMe-Color); 151 | --ReadMe-Hr-BorderColor: rgba(0, 0, 0, 0.05); 152 | // color: #dfadff; 153 | // color: #ff774b; 154 | --ReadMe-AHref-Color: var(--APP_BrandColor); 155 | } 156 | -------------------------------------------------------------------------------- /src/styles/var.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | // app 3 | --APP_BrandColor: #e45009; 4 | --App_Background: #141419; 5 | --APP_FontSize: 13px; 6 | --APP_HtmlColor: #ccc; 7 | --APP_AHerfColor: var(--APP_BrandColor); 8 | --APP_Scroller-Background: rgba(255, 255, 255, 0.1); 9 | --APP_Loading-Color: rgba(255, 255, 255, 0.05); 10 | // side menu 11 | --SideMenu_Container_Background: #141419; 12 | --SideMenu_Avatar_Background: rgba(0, 0, 0, 0.2); 13 | --SideMenu_LoginName_Color: #ccc; 14 | // menu item 15 | --SideMenu_Menu_Color: #999; 16 | --SideMenu_Menu_Color__Hover: #fff; 17 | --SideMenu_Menu_Color__Active: #fff; 18 | 19 | --SideMenu_Menu_Background: transparent; 20 | --SideMenu_Menu_Background__Hover: transparent; 21 | --SideMenu_Menu_Background__Active: rgba(255, 255, 255, 0.03); 22 | 23 | --SideMenu_Menu_BorderColor: transparent; 24 | --SideMenu_Menu_BorderColor__Hover: transparent; 25 | --SideMenu_Menu_BorderColor__Active: rgba(255, 255, 255, 0.03); 26 | 27 | --SideMenu_MenuIcon_Color: var(--SideMenu_Menu_Color); 28 | --SideMenu_MenuIcon_Color__Hover: #fff; 29 | --SideMenu_MenuIcon_Color__Active: #fff; 30 | 31 | --SideMenu_MenuIcon_Background: rgba(255, 255, 255, 0.1); 32 | --SideMenu_MenuIcon_Background__Hover: rgba(255, 255, 255, 0.2); 33 | --SideMenu_MenuIcon_Background__Active: var(--APP_BrandColor); 34 | // tag 35 | --SideMenu_Tag_Color: #666; 36 | --SideMenu_Tag_Color__Hover: var(--SideMenu_Menu_Color__Hover); 37 | --SideMenu_Tag_Color__Active: var(--SideMenu_Menu_Color__Active); 38 | 39 | --SideMenu_Tag_Background: transparent; 40 | --SideMenu_Tag_Background__Hover: transparent; 41 | --SideMenu_Tag_Background__Active: var(--SideMenu_Menu_Background__Active); 42 | 43 | --SideMenu_Tag_BorderColor: transparent; 44 | --SideMenu_Tag_BorderColor__Hover: transparent; 45 | --SideMenu_Tag_BorderColor__Active: var(--SideMenu_Menu_BorderColor__Active); 46 | 47 | --SideMenu_TagInput_Color: #fff; 48 | --SideMenu_TagInput_Background: #000; 49 | 50 | --SideMenu_TagToolButton_Color: #666; 51 | --SideMenu_TagToolButton_Color__Hover: #fff; 52 | 53 | // language-item 54 | --SideMenu_LanguageItem_Color: var(--SideMenu_Tag_Color); 55 | --SideMenu_LanguageItem_Color__Hover: var(--SideMenu_Menu_Color__Hover); 56 | --SideMenu_LanguageItem_Color__Active: var(--SideMenu_Menu_Color__Active); 57 | 58 | --SideMenu_LanguageItem_Background: transparent; 59 | --SideMenu_LanguageItem_Background__Hover: transparent; 60 | --SideMenu_LanguageItem_Background__Active: var( 61 | --SideMenu_Menu_Background__Active 62 | ); 63 | 64 | --SideMenu_LanguageItem_BorderColor: transparent; 65 | --SideMenu_LanguageItem_BorderColor__Hover: transparent; 66 | --SideMenu_LanguageItem_BorderColor__Active: var( 67 | --SideMenu_Menu_BorderColor__Active 68 | ); 69 | 70 | // SyncButton 71 | --SideMenu_SyncButton_Color: #666; 72 | --SideMenu_SyncButton_Color__Hover: #fff; 73 | 74 | --SideMenu_SyncButton_Background: transparent; 75 | --SideMenu_SyncButton_Background__Hover: rgba(255, 255, 255, 0.1); 76 | 77 | --SideMenu_SyncButton_BorderColor: rgba(255, 255, 255, 0.1); 78 | 79 | // IconButton 80 | --SideMenu_IconButton_Color: #666; 81 | --SideMenu_IconButton_Color__Hover: #fff; 82 | 83 | --SideMenu_IconButton_Background: rgba(255, 255, 255, 0.1); 84 | --SideMenu_IconButton_Background__Hover: rgba(255, 255, 255, 0.2); 85 | 86 | //repo List 87 | --RepoList_Background: #232323; 88 | --RepoList_Search_Color: #fff; 89 | --RepoList_Search_Background: #19191d; 90 | 91 | //repo Card 92 | --RepoCard_Background: transparent; 93 | --RepoCard_Background__Hover: transparent; 94 | --RepoCard_Background__Active: #1e1e1e; 95 | 96 | --RepoCard_Indictor_Background: transparent; 97 | --RepoCard_Indictor_Background__Hover: rgba(255, 255, 255, 0.1); 98 | --RepoCard_Indictor_Background__Active: var(--APP_BrandColor); 99 | 100 | --RepoCard_Title_Color: #eee; 101 | --SideMenu_Title_Color__Hover: var(--APP_BrandColor); 102 | 103 | --RepoCard_Body_Color: #888; 104 | --RepoCard_Foot_Color: var(--RepoCard_Body_Color); 105 | 106 | // repo card tag 107 | --RepoCard_TagTogger_Color: #888; 108 | --RepoCard_TagTogger_Color__Hover: #fff; 109 | 110 | --RepoCard_Tag_Color: #888; 111 | --RepoCard_Tag_Color__Hover: #fff; 112 | --RepoCard_Tag_Color__Active: #fff; 113 | 114 | --RepoCard_Tag_Background: transparent; 115 | --RepoCard_Tag_Background__Hover: transparent; 116 | --RepoCard_Tag_Background__Active: var(--APP_BrandColor); 117 | 118 | --RepoCard_Tag_BorderColor: #888; 119 | --RepoCard_Tag_BorderColor__Hover: #888; 120 | --RepoCard_Tag_BorderColor__Active: var(--APP_BrandColor); 121 | 122 | // repo card Action Button 123 | --RepoCard_Tag_CancleButton_Color: #ccc; 124 | --RepoCard_Tag_CancleButton_Color__Hover: #fff; 125 | 126 | --RepoCard_Tag_CancleButton_Background: #404040; 127 | --RepoCard_Tag_CancleButton_Background__Hover: #555; 128 | 129 | --RepoCard_Tag_CancleButton_BorderColor: transparent; 130 | --RepoCard_Tag_CancleButton_BorderColor__Hover: transparent; 131 | 132 | --RepoCard_Tag_ConfirmButton_Color: #ccc; 133 | --RepoCard_Tag_ConfirmButton_Color__Hover: #fff; 134 | 135 | --RepoCard_Tag_ConfirmButton_Background: #3a7500; 136 | --RepoCard_Tag_ConfirmButton_Background__Hover: #4a9600; 137 | 138 | --RepoCard_Tag_ConfirmButton_BorderColor: transparent; 139 | --RepoCard_Tag_ConfirmButton_BorderColor__Hover: transparent; 140 | 141 | // detail view 142 | --RpoeDetail_Background: #1e1e1e; 143 | 144 | --RpoeDetail_IconButton_Color: #666; 145 | --RpoeDetail_IconButton_Color__Hover: #fff; 146 | 147 | --ReadMe-Color: var(--APP_HtmlColor); 148 | --ReadMe-Pre-Color: #d1e8ff; 149 | --ReadMe-Pre-Background: #151515; 150 | --ReadMe-Blockquote-Color: var(--ReadMe-Color); 151 | --ReadMe-Hr-BorderColor: rgba(255, 255, 255, 0.05); 152 | // color: #dfadff; 153 | // color: #ff774b; 154 | --ReadMe-AHref-Color: var(--APP_BrandColor); 155 | } 156 | -------------------------------------------------------------------------------- /src/pages/components/TagConflic.vue: -------------------------------------------------------------------------------- 1 | 113 | 114 | 145 | 146 | 228 | -------------------------------------------------------------------------------- /src/pages/Home/RepoList/RepoCardTags.vue: -------------------------------------------------------------------------------- 1 | 82 | 83 | 109 | 110 | 223 | 224 | -------------------------------------------------------------------------------- /src/styles/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /* Document 4 | ========================================================================== */ 5 | 6 | /** 7 | * 1. Correct the line height in all browsers. 8 | * 2. Prevent adjustments of font size after orientation changes in iOS. 9 | */ 10 | 11 | html { 12 | line-height: 1.15; /* 1 */ 13 | -webkit-text-size-adjust: 100%; /* 2 */ 14 | } 15 | 16 | /* Sections 17 | ========================================================================== */ 18 | 19 | /** 20 | * Remove the margin in all browsers. 21 | */ 22 | 23 | body { 24 | margin: 0; 25 | } 26 | 27 | /** 28 | * Render the `main` element consistently in IE. 29 | */ 30 | 31 | main { 32 | display: block; 33 | } 34 | 35 | /** 36 | * Correct the font size and margin on `h1` elements within `section` and 37 | * `article` contexts in Chrome, Firefox, and Safari. 38 | */ 39 | 40 | h1 { 41 | font-size: 2em; 42 | margin: 0.67em 0; 43 | } 44 | 45 | /* Grouping content 46 | ========================================================================== */ 47 | 48 | /** 49 | * 1. Add the correct box sizing in Firefox. 50 | * 2. Show the overflow in Edge and IE. 51 | */ 52 | 53 | hr { 54 | box-sizing: content-box; /* 1 */ 55 | height: 0; /* 1 */ 56 | overflow: visible; /* 2 */ 57 | } 58 | 59 | /** 60 | * 1. Correct the inheritance and scaling of font size in all browsers. 61 | * 2. Correct the odd `em` font sizing in all browsers. 62 | */ 63 | 64 | pre { 65 | font-family: monospace, monospace; /* 1 */ 66 | font-size: 1em; /* 2 */ 67 | } 68 | 69 | /* Text-level semantics 70 | ========================================================================== */ 71 | 72 | /** 73 | * Remove the gray background on active links in IE 10. 74 | */ 75 | 76 | a { 77 | background-color: transparent; 78 | } 79 | 80 | /** 81 | * 1. Remove the bottom border in Chrome 57- 82 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 83 | */ 84 | 85 | abbr[title] { 86 | border-bottom: none; /* 1 */ 87 | text-decoration: underline; /* 2 */ 88 | text-decoration: underline dotted; /* 2 */ 89 | } 90 | 91 | /** 92 | * Add the correct font weight in Chrome, Edge, and Safari. 93 | */ 94 | 95 | b, 96 | strong { 97 | font-weight: bolder; 98 | } 99 | 100 | /** 101 | * 1. Correct the inheritance and scaling of font size in all browsers. 102 | * 2. Correct the odd `em` font sizing in all browsers. 103 | */ 104 | 105 | code, 106 | kbd, 107 | samp { 108 | font-family: monospace, monospace; /* 1 */ 109 | font-size: 1em; /* 2 */ 110 | } 111 | 112 | /** 113 | * Add the correct font size in all browsers. 114 | */ 115 | 116 | small { 117 | font-size: 80%; 118 | } 119 | 120 | /** 121 | * Prevent `sub` and `sup` elements from affecting the line height in 122 | * all browsers. 123 | */ 124 | 125 | sub, 126 | sup { 127 | font-size: 75%; 128 | line-height: 0; 129 | position: relative; 130 | vertical-align: baseline; 131 | } 132 | 133 | sub { 134 | bottom: -0.25em; 135 | } 136 | 137 | sup { 138 | top: -0.5em; 139 | } 140 | 141 | /* Embedded content 142 | ========================================================================== */ 143 | 144 | /** 145 | * Remove the border on images inside links in IE 10. 146 | */ 147 | 148 | img { 149 | border-style: none; 150 | } 151 | 152 | /* Forms 153 | ========================================================================== */ 154 | 155 | /** 156 | * 1. Change the font styles in all browsers. 157 | * 2. Remove the margin in Firefox and Safari. 158 | */ 159 | 160 | button, 161 | input, 162 | optgroup, 163 | select, 164 | textarea { 165 | font-family: inherit; /* 1 */ 166 | font-size: 100%; /* 1 */ 167 | line-height: 1.15; /* 1 */ 168 | margin: 0; /* 2 */ 169 | } 170 | 171 | /** 172 | * Show the overflow in IE. 173 | * 1. Show the overflow in Edge. 174 | */ 175 | 176 | button, 177 | input { 178 | /* 1 */ 179 | overflow: visible; 180 | } 181 | 182 | /** 183 | * Remove the inheritance of text transform in Edge, Firefox, and IE. 184 | * 1. Remove the inheritance of text transform in Firefox. 185 | */ 186 | 187 | button, 188 | select { 189 | /* 1 */ 190 | text-transform: none; 191 | } 192 | 193 | /** 194 | * Correct the inability to style clickable types in iOS and Safari. 195 | */ 196 | 197 | button, 198 | [type='button'], 199 | [type='reset'], 200 | [type='submit'] { 201 | -webkit-appearance: button; 202 | } 203 | 204 | /** 205 | * Remove the inner border and padding in Firefox. 206 | */ 207 | 208 | button::-moz-focus-inner, 209 | [type='button']::-moz-focus-inner, 210 | [type='reset']::-moz-focus-inner, 211 | [type='submit']::-moz-focus-inner { 212 | border-style: none; 213 | padding: 0; 214 | } 215 | 216 | /** 217 | * Restore the focus styles unset by the previous rule. 218 | */ 219 | 220 | button:-moz-focusring, 221 | [type='button']:-moz-focusring, 222 | [type='reset']:-moz-focusring, 223 | [type='submit']:-moz-focusring { 224 | outline: 1px dotted ButtonText; 225 | } 226 | 227 | /** 228 | * Correct the padding in Firefox. 229 | */ 230 | 231 | fieldset { 232 | padding: 0.35em 0.75em 0.625em; 233 | } 234 | 235 | /** 236 | * 1. Correct the text wrapping in Edge and IE. 237 | * 2. Correct the color inheritance from `fieldset` elements in IE. 238 | * 3. Remove the padding so developers are not caught out when they zero out 239 | * `fieldset` elements in all browsers. 240 | */ 241 | 242 | legend { 243 | box-sizing: border-box; /* 1 */ 244 | color: inherit; /* 2 */ 245 | display: table; /* 1 */ 246 | max-width: 100%; /* 1 */ 247 | padding: 0; /* 3 */ 248 | white-space: normal; /* 1 */ 249 | } 250 | 251 | /** 252 | * Add the correct vertical alignment in Chrome, Firefox, and Opera. 253 | */ 254 | 255 | progress { 256 | vertical-align: baseline; 257 | } 258 | 259 | /** 260 | * Remove the default vertical scrollbar in IE 10+. 261 | */ 262 | 263 | textarea { 264 | overflow: auto; 265 | } 266 | 267 | /** 268 | * 1. Add the correct box sizing in IE 10. 269 | * 2. Remove the padding in IE 10. 270 | */ 271 | 272 | [type='checkbox'], 273 | [type='radio'] { 274 | box-sizing: border-box; /* 1 */ 275 | padding: 0; /* 2 */ 276 | } 277 | 278 | /** 279 | * Correct the cursor style of increment and decrement buttons in Chrome. 280 | */ 281 | 282 | [type='number']::-webkit-inner-spin-button, 283 | [type='number']::-webkit-outer-spin-button { 284 | height: auto; 285 | } 286 | 287 | /** 288 | * 1. Correct the odd appearance in Chrome and Safari. 289 | * 2. Correct the outline style in Safari. 290 | */ 291 | 292 | [type='search'] { 293 | -webkit-appearance: textfield; /* 1 */ 294 | outline-offset: -2px; /* 2 */ 295 | } 296 | 297 | /** 298 | * Remove the inner padding in Chrome and Safari on macOS. 299 | */ 300 | 301 | [type='search']::-webkit-search-decoration { 302 | -webkit-appearance: none; 303 | } 304 | 305 | /** 306 | * 1. Correct the inability to style clickable types in iOS and Safari. 307 | * 2. Change font properties to `inherit` in Safari. 308 | */ 309 | 310 | ::-webkit-file-upload-button { 311 | -webkit-appearance: button; /* 1 */ 312 | font: inherit; /* 2 */ 313 | } 314 | 315 | /* Interactive 316 | ========================================================================== */ 317 | 318 | /* 319 | * Add the correct display in Edge, IE 10+, and Firefox. 320 | */ 321 | 322 | details { 323 | display: block; 324 | } 325 | 326 | /* 327 | * Add the correct display in all browsers. 328 | */ 329 | 330 | summary { 331 | display: list-item; 332 | } 333 | 334 | /* Misc 335 | ========================================================================== */ 336 | 337 | /** 338 | * Add the correct display in IE 10+. 339 | */ 340 | 341 | template { 342 | display: none; 343 | } 344 | 345 | /** 346 | * Add the correct display in IE 10. 347 | */ 348 | 349 | [hidden] { 350 | display: none; 351 | } 352 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const isProd = process.env.NODE_ENV === 'production' 3 | 4 | module.exports = { 5 | devServer: { 6 | proxy: { 7 | '/api': { 8 | target: 'http://192.168.0.105:7001', 9 | changeOrigin: true 10 | } 11 | } 12 | }, 13 | configureWebpack: config => { 14 | // if (isProd) { 15 | // config.externals = { 16 | // vue: 'Vue', 17 | // 'vue-router': 'VueRouter', 18 | // vuex: 'Vuex', 19 | // axios: 'axios', 20 | // marked: 'marked', 21 | // 'highlight.js': 'hljs', 22 | // 'vue-text-highlight': 'VueTextHighlight.default', 23 | // 'vue-virtual-scroller': 'VueVirtualScroller', 24 | // dompurify: 'DOMPurify', 25 | // vuedraggable: 'vuedraggable', 26 | // dexie: 'Dexie' 27 | // } 28 | // } 29 | config.plugins.push(new MyAppTagManagerHtmlPlugin()) 30 | config.devtool = 'source-map' 31 | }, 32 | publicPath: '', 33 | productionSourceMap: false, 34 | chainWebpack: config => { 35 | const svgPath = path.resolve('src/assets/svgIcon') 36 | config.module.rule('svg').exclude.add(svgPath).end() 37 | 38 | config.module 39 | .rule('svg-icon') 40 | .test(/\.(svg)(\?.*)?$/) 41 | .include.add(svgPath) 42 | .end() 43 | .use('svg-sprite-loader') 44 | .loader('svg-sprite-loader') 45 | .options({ 46 | symbolId: 'icon-[name]' 47 | }) 48 | .end() 49 | }, 50 | pwa: { 51 | workboxPluginMode: 'InjectManifest', 52 | workboxOptions: { 53 | // https://developers.google.com/web/tools/workbox/modules/workbox-webpack-plugin 54 | swSrc: './src/common/service-worker.js', 55 | swDest: 'service-worker.js', 56 | manifestTransforms: [addCdnFilesToPwaPrecache] 57 | }, 58 | // https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-pwa#configuration 59 | name: 'Starflare', 60 | themeColor: '#141419', 61 | msTileColor: '#141419', 62 | background_color: '#141419', 63 | appleMobileWebAppCapable: 'yes', 64 | appleMobileWebAppStatusBarStyle: 'black', 65 | manifestOptions: { 66 | background_color: '#141419', 67 | start_url: '.' 68 | } 69 | } 70 | } 71 | 72 | const headerFiles = [] 73 | 74 | const bodyFiles = [ 75 | // { 76 | // addDev: true, 77 | // tag: 'link', 78 | // url: 'https://cdn.jsdelivr.net/npm/normalize.css@8.0.1/normalize.css' 79 | // }, 80 | // { 81 | // addDev: true, 82 | // tag: 'link', 83 | // url: 84 | // 'https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.16.2/build/styles/atom-one-dark.min.css' 85 | // }, 86 | // { 87 | // addDev: true, 88 | // tag: 'link', 89 | // url: 90 | // 'https://cdn.jsdelivr.net/npm/vue-virtual-scroller@1.0.0-rc.2/dist/vue-virtual-scroller.css' 91 | // }, 92 | // { 93 | // tag: 'script', 94 | // url: 95 | // 'https://cdn.jsdelivr.net/npm/intersection-observer@0.7.0/intersection-observer.js' 96 | // }, 97 | // { 98 | // tag: 'script', 99 | // url: 'https://cdn.jsdelivr.net/npm/core-js-bundle@3.4.2/minified.js' 100 | // }, 101 | // { 102 | // tag: 'script', 103 | // url: 'https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.runtime.min.js' 104 | // }, 105 | // { 106 | // tag: 'script', 107 | // url: 'https://cdn.jsdelivr.net/npm/vuex@3.0.1/dist/vuex.min.js' 108 | // }, 109 | // { 110 | // tag: 'script', 111 | // url: 'https://cdn.jsdelivr.net/npm/vue-router@3.1.3/dist/vue-router.min.js' 112 | // }, 113 | // { 114 | // tag: 'script', 115 | // url: 'https://cdn.jsdelivr.net/npm/axios@0.19.0/dist/axios.min.js' 116 | // }, 117 | // { 118 | // tag: 'script', 119 | // url: 'https://cdn.jsdelivr.net/npm/marked@0.7.0/marked.min.js' 120 | // }, 121 | // { 122 | // tag: 'script', 123 | // url: 124 | // 'https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.16.2/build/highlight.min.js' 125 | // }, 126 | // { 127 | // tag: 'script', 128 | // url: 129 | // 'https://cdn.jsdelivr.net/npm/vue-text-highlight@2.0.10/dist/vue-text-highlight.min.js' 130 | // }, 131 | // { 132 | // tag: 'script', 133 | // url: 'https://cdn.jsdelivr.net/npm/dompurify@2.0.7/dist/purify.min.js' 134 | // }, 135 | // { 136 | // tag: 'script', 137 | // url: 138 | // 'https://cdn.jsdelivr.net/npm/vue-virtual-scroller@1.0.0-rc.2/dist/vue-virtual-scroller.min.js' 139 | // }, 140 | // { 141 | // tag: 'script', 142 | // url: 'https://cdn.jsdelivr.net/npm/sortablejs@1.8.4/Sortable.min.js' 143 | // }, 144 | // { 145 | // tag: 'script', 146 | // url: 147 | // 'https://cdn.jsdelivr.net/npm/vuedraggable@2.23.2/dist/vuedraggable.umd.min.js' 148 | // }, 149 | // { 150 | // tag: 'script', 151 | // url: 'https://cdn.jsdelivr.net/npm/dexie@2.0.4/dist/dexie.min.js' 152 | // } 153 | ] 154 | 155 | const cdnPath = '' 156 | class MyAppTagManagerHtmlPlugin { 157 | // https://github.com/jantimon/html-webpack-plugin/issues/1001#issuecomment-404762021 158 | apply(compiler) { 159 | compiler.hooks.compilation.tap('MyAppTagManagerHtmlPlugin', compilation => { 160 | compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync( 161 | 'MyAppTagManagerHtmlPlugin', 162 | (pluginArgs, cb) => { 163 | console.log('MyAppTagManagerHtmlPlugin Start') 164 | // You can use either `head` or `body` and either `push` or `unshift`: 165 | const tryUseCdn = item => { 166 | const hasHash = /\.[0-f]+\./ 167 | if (item.tagName === 'link' && hasHash.test(item.attributes.href)) { 168 | const url = cdnPath + item.attributes.href 169 | item.attributes.href = url 170 | console.log('Use cdn ' + url) 171 | } 172 | if ( 173 | item.tagName === 'script' && 174 | hasHash.test(item.attributes.src) 175 | ) { 176 | const url = cdnPath + item.attributes.src 177 | item.attributes.src = url 178 | console.log('Use cdn ' + url) 179 | } 180 | 181 | return item 182 | } 183 | const addCustomeFile = item => { 184 | if (item.tag === 'script') { 185 | console.log('inject ' + item.url) 186 | return { 187 | attributes: { 188 | type: 'text/javascript', 189 | src: item.url 190 | }, 191 | closeTag: true, 192 | tagName: 'script' 193 | } 194 | } 195 | if (item.tag === 'link') { 196 | console.log('inject ' + item.url) 197 | return { 198 | attributes: { 199 | href: item.url, 200 | rel: 'stylesheet' 201 | }, 202 | selfClosingTag: false, 203 | tagName: 'link', 204 | voidTag: true 205 | } 206 | } 207 | const error = 208 | 'MyAppTagManagerHtmlPlugin inject customeFile wrong,only support link,script tag' 209 | cb(error) 210 | } 211 | const addToHeader = isProd 212 | ? headerFiles 213 | : headerFiles.filter(item => item.addDev) 214 | const addToBody = isProd 215 | ? bodyFiles 216 | : bodyFiles.filter(item => item.addDev) 217 | 218 | pluginArgs.head = [ 219 | ...pluginArgs.head.map(tryUseCdn), 220 | ...addToHeader.map(addCustomeFile) 221 | ] 222 | pluginArgs.body = [ 223 | ...addToBody.map(addCustomeFile), 224 | ...pluginArgs.body.map(tryUseCdn) 225 | ] 226 | 227 | cb(null, pluginArgs) 228 | } 229 | ) 230 | }) 231 | } 232 | } 233 | 234 | function addCdnFilesToPwaPrecache(originalManifest) { 235 | const warnings = [] 236 | const files = [...headerFiles, ...bodyFiles] 237 | const manifest = files.map(item => { 238 | console.log(`Add CDN PWA cache: ${item.url}`) 239 | return { 240 | url: item.url, 241 | cdn: true 242 | } 243 | }) 244 | return { manifest: [...originalManifest, ...manifest], warnings } 245 | } 246 | -------------------------------------------------------------------------------- /src/common/githubLanColor.js: -------------------------------------------------------------------------------- 1 | export default { 2 | '1C Enterprise': '#814CCC', 3 | ABAP: '#E8274B', 4 | ActionScript: '#882B0F', 5 | Ada: '#02f88c', 6 | Agda: '#315665', 7 | 'AGS Script': '#B9D9FF', 8 | Alloy: '#64C800', 9 | 'Alpine Abuild': null, 10 | AMPL: '#E6EFBB', 11 | AngelScript: '#C7D7DC', 12 | ANTLR: '#9DC3FF', 13 | Apex: null, 14 | 'API Blueprint': '#2ACCA8', 15 | APL: '#5A8164', 16 | 'Apollo Guidance Computer': null, 17 | AppleScript: '#101F1F', 18 | Arc: '#aa2afe', 19 | ASP: '#6a40fd', 20 | AspectJ: '#a957b0', 21 | Assembly: '#6E4C13', 22 | Asymptote: '#4a0c0c', 23 | ATS: '#1ac620', 24 | Augeas: null, 25 | AutoHotkey: '#6594b9', 26 | AutoIt: '#1C3552', 27 | Awk: null, 28 | Ballerina: '#FF5000', 29 | Batchfile: '#C1F12E', 30 | Befunge: null, 31 | Bison: null, 32 | BitBake: null, 33 | BlitzBasic: null, 34 | BlitzMax: '#cd6400', 35 | Bluespec: null, 36 | Boo: '#d4bec1', 37 | Brainfuck: '#2F2530', 38 | Brightscript: null, 39 | Bro: null, 40 | C: '#555555', 41 | 'C#': '#178600', 42 | 'C++': '#f34b7d', 43 | 'C2hs Haskell': null, 44 | "Cap'n Proto": null, 45 | CartoCSS: null, 46 | Ceylon: '#dfa535', 47 | Chapel: '#8dc63f', 48 | Charity: null, 49 | ChucK: null, 50 | Cirru: '#ccccff', 51 | Clarion: '#db901e', 52 | Clean: '#3F85AF', 53 | Click: '#E4E6F3', 54 | CLIPS: null, 55 | Clojure: '#db5855', 56 | CMake: null, 57 | COBOL: null, 58 | CoffeeScript: '#244776', 59 | ColdFusion: '#ed2cd6', 60 | 'ColdFusion CFC': null, 61 | 'Common Lisp': '#3fb68b', 62 | 'Common Workflow Language': '#B5314C', 63 | 'Component Pascal': '#B0CE4E', 64 | Cool: null, 65 | Coq: null, 66 | Crystal: '#000100', 67 | Csound: null, 68 | 'Csound Document': null, 69 | 'Csound Score': null, 70 | CSS: '#563d7c', 71 | Cuda: '#3A4E3A', 72 | CWeb: null, 73 | Cycript: null, 74 | Cython: null, 75 | D: '#ba595e', 76 | Dart: '#00B4AB', 77 | DataWeave: '#003a52', 78 | 'DIGITAL Command Language': null, 79 | DM: '#447265', 80 | Dockerfile: '#384d54', 81 | Dogescript: '#cca760', 82 | DTrace: null, 83 | Dylan: '#6c616e', 84 | E: '#ccce35', 85 | eC: '#913960', 86 | ECL: '#8a1267', 87 | ECLiPSe: null, 88 | Eiffel: '#946d57', 89 | Elixir: '#6e4a7e', 90 | Elm: '#60B5CC', 91 | 'Emacs Lisp': '#c065db', 92 | EmberScript: '#FFF4F3', 93 | EQ: '#a78649', 94 | Erlang: '#B83998', 95 | 'F#': '#b845fc', 96 | 'F*': '#572e30', 97 | Factor: '#636746', 98 | Fancy: '#7b9db4', 99 | Fantom: '#14253c', 100 | 'Filebench WML': null, 101 | Filterscript: null, 102 | fish: null, 103 | FLUX: '#88ccff', 104 | Forth: '#341708', 105 | Fortran: '#4d41b1', 106 | FreeMarker: '#0050b2', 107 | Frege: '#00cafe', 108 | 'Game Maker Language': '#71b417', 109 | GAMS: null, 110 | GAP: null, 111 | 'GCC Machine Description': null, 112 | GDB: null, 113 | GDScript: '#355570', 114 | Genie: '#fb855d', 115 | Genshi: null, 116 | 'Gentoo Ebuild': null, 117 | 'Gentoo Eclass': null, 118 | Gherkin: '#5B2063', 119 | GLSL: null, 120 | Glyph: '#c1ac7f', 121 | Gnuplot: '#f0a9f0', 122 | Go: '#00ADD8', 123 | Golo: '#88562A', 124 | Gosu: '#82937f', 125 | Grace: null, 126 | 'Grammatical Framework': '#79aa7a', 127 | Groovy: '#e69f56', 128 | 'Groovy Server Pages': null, 129 | Hack: '#878787', 130 | Harbour: '#0e60e3', 131 | Haskell: '#5e5086', 132 | Haxe: '#df7900', 133 | HCL: null, 134 | HiveQL: '#dce200', 135 | HLSL: null, 136 | HTML: '#e34c26', 137 | Hy: '#7790B2', 138 | HyPhy: null, 139 | IDL: '#a3522f', 140 | Idris: '#b30000', 141 | 'IGOR Pro': null, 142 | 'Inform 7': null, 143 | 'Inno Setup': null, 144 | Io: '#a9188d', 145 | Ioke: '#078193', 146 | Isabelle: '#FEFE00', 147 | 'Isabelle ROOT': null, 148 | J: '#9EEDFF', 149 | Jasmin: null, 150 | Java: '#b07219', 151 | 'Java Server Pages': null, 152 | JavaScript: '#f1e05a', 153 | JFlex: null, 154 | Jison: null, 155 | 'Jison Lex': null, 156 | Jolie: '#843179', 157 | JSONiq: '#40d47e', 158 | Jsonnet: '#0064bd', 159 | JSX: null, 160 | Julia: '#a270ba', 161 | 'Jupyter Notebook': '#DA5B0B', 162 | Kotlin: '#F18E33', 163 | KRL: '#28430A', 164 | LabVIEW: null, 165 | Lasso: '#999999', 166 | Lean: null, 167 | Lex: '#DBCA00', 168 | LFE: '#4C3023', 169 | LilyPond: null, 170 | Limbo: null, 171 | 'Literate Agda': null, 172 | 'Literate CoffeeScript': null, 173 | 'Literate Haskell': null, 174 | LiveScript: '#499886', 175 | LLVM: '#185619', 176 | Logos: null, 177 | Logtalk: null, 178 | LOLCODE: '#cc9900', 179 | LookML: '#652B81', 180 | LoomScript: null, 181 | LSL: '#3d9970', 182 | Lua: '#000080', 183 | M: null, 184 | M4: null, 185 | M4Sugar: null, 186 | Makefile: '#427819', 187 | Mako: null, 188 | Mask: '#f97732', 189 | Mathematica: null, 190 | MATLAB: '#e16737', 191 | Max: '#c4a79c', 192 | MAXScript: '#00a6a6', 193 | mcfunction: '#E22837', 194 | Mercury: '#ff2b2b', 195 | Meson: '#007800', 196 | Metal: '#8f14e9', 197 | MiniD: null, 198 | Mirah: '#c7a938', 199 | Modelica: null, 200 | 'Modula-2': null, 201 | 'Modula-3': '#223388', 202 | 'Module Management System': null, 203 | Monkey: null, 204 | Moocode: null, 205 | MoonScript: null, 206 | MQL4: '#62A8D6', 207 | MQL5: '#4A76B8', 208 | MTML: '#b7e1f4', 209 | MUF: null, 210 | mupad: null, 211 | Myghty: null, 212 | NCL: '#28431f', 213 | Nearley: '#990000', 214 | Nemerle: '#3d3c6e', 215 | nesC: '#94B0C7', 216 | NetLinx: '#0aa0ff', 217 | 'NetLinx+ERB': '#747faa', 218 | NetLogo: '#ff6375', 219 | NewLisp: '#87AED7', 220 | Nextflow: '#3ac486', 221 | Nim: '#37775b', 222 | Nit: '#009917', 223 | Nix: '#7e7eff', 224 | NSIS: null, 225 | Nu: '#c9df40', 226 | NumPy: null, 227 | 'Objective-C': '#438eff', 228 | 'Objective-C++': '#6866fb', 229 | 'Objective-J': '#ff0c5a', 230 | OCaml: '#3be133', 231 | Omgrofl: '#cabbff', 232 | ooc: '#b0b77e', 233 | Opa: null, 234 | Opal: '#f7ede0', 235 | OpenCL: null, 236 | 'OpenEdge ABL': null, 237 | 'OpenRC runscript': null, 238 | OpenSCAD: null, 239 | Ox: null, 240 | Oxygene: '#cdd0e3', 241 | Oz: '#fab738', 242 | P4: '#7055b5', 243 | Pan: '#cc0000', 244 | Papyrus: '#6600cc', 245 | Parrot: '#f3ca0a', 246 | 'Parrot Assembly': null, 247 | 'Parrot Internal Representation': null, 248 | Pascal: '#E3F171', 249 | Pawn: '#dbb284', 250 | Pep8: '#C76F5B', 251 | Perl: '#0298c3', 252 | 'Perl 6': '#0000fb', 253 | PHP: '#4F5D95', 254 | PicoLisp: null, 255 | PigLatin: '#fcd7de', 256 | Pike: '#005390', 257 | PLpgSQL: null, 258 | PLSQL: '#dad8d8', 259 | PogoScript: '#d80074', 260 | Pony: null, 261 | PostScript: '#da291c', 262 | 'POV-Ray SDL': null, 263 | PowerBuilder: '#8f0f8d', 264 | PowerShell: '#012456', 265 | Processing: '#0096D8', 266 | Prolog: '#74283c', 267 | 'Propeller Spin': '#7fa2a7', 268 | Puppet: '#302B6D', 269 | PureBasic: '#5a6986', 270 | PureScript: '#1D222D', 271 | Python: '#3572A5', 272 | 'Python console': null, 273 | q: '#0040cd', 274 | QMake: null, 275 | QML: '#44a51c', 276 | Quake: '#882233', 277 | R: '#198CE7', 278 | Racket: '#3c5caa', 279 | Ragel: '#9d5200', 280 | RAML: '#77d9fb', 281 | Rascal: '#fffaa0', 282 | REALbasic: null, 283 | Reason: null, 284 | Rebol: '#358a5b', 285 | Red: '#f50000', 286 | Redcode: null, 287 | "Ren'Py": '#ff7f7f', 288 | RenderScript: null, 289 | REXX: null, 290 | Ring: '#2D54CB', 291 | RobotFramework: null, 292 | Roff: '#ecdebe', 293 | Rouge: '#cc0088', 294 | RPC: null, 295 | Ruby: '#701516', 296 | RUNOFF: '#665a4e', 297 | Rust: '#dea584', 298 | Sage: null, 299 | SaltStack: '#646464', 300 | SAS: '#B34936', 301 | Scala: '#c22d40', 302 | Scheme: '#1e4aec', 303 | Scilab: null, 304 | sed: '#64b970', 305 | Self: '#0579aa', 306 | ShaderLab: null, 307 | Shell: '#89e051', 308 | ShellSession: null, 309 | Shen: '#120F14', 310 | Slash: '#007eff', 311 | Slice: '#003fa2', 312 | Smali: null, 313 | Smalltalk: '#596706', 314 | Smarty: null, 315 | SMT: null, 316 | Solidity: '#AA6746', 317 | SourcePawn: '#5c7611', 318 | SQF: '#3F3F3F', 319 | SQLPL: null, 320 | Squirrel: '#800000', 321 | 'SRecode Template': '#348a34', 322 | Stan: '#b2011d', 323 | 'Standard ML': '#dc566d', 324 | Stata: null, 325 | SuperCollider: '#46390b', 326 | Swift: '#ffac45', 327 | SystemVerilog: '#DAE1C2', 328 | Tcl: '#e4cc98', 329 | Tcsh: null, 330 | Terra: '#00004c', 331 | TeX: '#3D6117', 332 | Thrift: null, 333 | 'TI Program': '#A0AA87', 334 | TLA: null, 335 | Turing: '#cf142b', 336 | TXL: null, 337 | TypeScript: '#2b7489', 338 | 'Unified Parallel C': null, 339 | 'Unix Assembly': null, 340 | Uno: null, 341 | UnrealScript: '#a54c4d', 342 | UrWeb: null, 343 | Vala: '#fbe5cd', 344 | VCL: '#148AA8', 345 | Verilog: '#b2b7f8', 346 | VHDL: '#adb2cb', 347 | 'Vim script': '#199f4b', 348 | 'Visual Basic': '#945db7', 349 | Volt: '#1F1F1F', 350 | Vue: '#2c3e50', 351 | wdl: '#42f1f4', 352 | WebAssembly: '#04133b', 353 | WebIDL: null, 354 | wisp: '#7582D1', 355 | X10: '#4B6BEF', 356 | xBase: '#403a40', 357 | XC: '#99DA07', 358 | Xojo: null, 359 | XProc: null, 360 | XQuery: '#5232e7', 361 | XS: null, 362 | XSLT: '#EB8CEB', 363 | Xtend: null, 364 | Yacc: '#4B6C4B', 365 | YARA: '#220000', 366 | YASnippet: '#32AB90', 367 | ZAP: '#0d665e', 368 | Zephir: '#118f9e', 369 | Zig: '#ec915c', 370 | ZIL: '#dc75e5', 371 | Zimpl: null 372 | } 373 | -------------------------------------------------------------------------------- /src/pages/Home/SideMenu/SideMenu.vue: -------------------------------------------------------------------------------- 1 | 156 | 157 | 227 | 228 | 321 | -------------------------------------------------------------------------------- /public/img/icons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /src/assets/hacker.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | --------------------------------------------------------------------------------