提交 c1710976 编写于 作者: X xjh22222228

init

上级 b12e51b0
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false
data/
main.ts
polyfills.ts
test.ts
environments/
assets/
\ No newline at end of file
module.exports = {
parserOptions: {
ecmaVersion: 11,
sourceType: 'module'
},
env: {
node: true,
es6: true,
mocha: true,
jest: true,
jasmine: true,
},
globals: {
App: true,
wx: true,
Component: true,
getApp: true,
Page: true
},
rules: {
'semi': ['error', 'never'],
'no-var': 2,
'constructor-super': 2,
'no-class-assign': 2,
'for-direction': 2,
'getter-return': 2,
'no-async-promise-executor': 2,
'no-compare-neg-zero': 2,
'no-cond-assign': 2,
'no-constant-condition': 2,
'no-control-regex': 2,
'no-debugger': 2,
'no-dupe-args': 2,
'no-dupe-keys': 2,
'no-duplicate-case': 2,
'no-empty-character-class': 2,
'no-ex-assign': 2,
'no-extra-boolean-cast': 2,
'no-extra-semi': 2,
'no-func-assign': 2,
'no-inner-declarations': 2,
'no-invalid-regexp': 2,
'no-irregular-whitespace': 2,
'no-misleading-character-class': 2,
'no-obj-calls': 2,
'no-prototype-builtins': 2,
'no-regex-spaces': 2,
'no-sparse-arrays': 2,
'no-unexpected-multiline': 2,
'no-unreachable': 2,
'no-unsafe-finally': 2,
'no-unsafe-negation': 2,
'use-isnan': 2,
'valid-typeof': 2,
'no-empty-pattern': 2,
'no-fallthrough': 2,
'no-global-assign': 2,
'no-octal': 2,
'no-redeclare': 2,
'no-self-assign': 2,
'no-unused-labels': 2,
'no-useless-catch': 2,
'no-useless-escape': 2,
'no-with': 2,
'no-delete-var': 2,
'no-shadow-restricted-names': 2,
'no-undef': 2,
'no-mixed-spaces-and-tabs': 2,
'no-const-assign': 2,
'no-dupe-class-members': 2,
'no-new-symbol': 2,
'no-this-before-super': 2,
'require-yield': 2,
'symbol-description': 2,
'space-infix-ops': 2,
'space-before-blocks': 2,
'no-trailing-spaces': 2,
'no-new-object': 2,
'no-multi-assign': 2,
'no-array-constructor': 2,
'func-call-spacing': 2,
'eol-last': 2,
'no-script-url': 2,
'no-return-assign': 2,
'no-useless-return': 2,
'no-proto': 2,
'no-new-wrappers': 2,
'eqeqeq': 2,
'no-eval': 2,
'no-extra-label': 2,
'no-implied-eval': 2,
'no-multi-spaces': 2,
'no-multi-str': 2,
'arrow-spacing': 2
},
settings: {
// support import modules from TypeScript files in JavaScript files
'import/resolver': { node: { extensions: ['.js', '.ts'] } },
},
}
name: Build web
on:
push:
branches:
- master
- main
- v5
- v3
- dev
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
persist-credentials: false
- name: Install and Build
run: |
npm install
npm run build
- name: Deploy
uses: JamesIves/github-pages-deploy-action@releases/v3
with:
ACCESS_TOKEN: ${{ secrets.TOKEN }}
BRANCH: gh-pages
FOLDER: dist
\ No newline at end of file
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/build
/tmp
/out-tsc
# dependencies
/node_modules
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
Thumbs.db
*.psd*
.eslintcache
The MIT License (MIT)
Copyright (c) 2018-present xiejiahe
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
<p align="center">
<a href="https://nav3.cn/?g">
<img src="src/assets/logo.png" width="130" />
</a>
<br />
<b>发现导航</b>
<p align="center">一个纯静态、易管理的强大导航网站,希望您会喜欢</p>
<p align="center">内置收录多达 800+ 优质网站, 助您工作、学习和生活</p>
<p align="center">
<img src="https://img.shields.io/github/v/release/xjh22222228/nav" />
<a href="https://github.com/xjh22222228/nav/stargazers"><img src="https://img.shields.io/github/stars/xjh22222228/nav" alt="Stars"/></a>
<img alt="Angular" src="https://img.shields.io/static/v1.svg?label=&message=Angular11&style=flat-square&color=C82B38">
<img src="https://img.shields.io/github/license/xjh22222228/nav" />
<a href="https://hits.dwyl.com/xjh22222228/nav">
<img src="https://hits.dwyl.com/xjh22222228/nav.svg" />
</a>
</p>
</p>
<br />
<br />
## 选择版本
目前有2个版本供选择, [v3](https://github.com/xjh22222228/nav/tree/v3)`v5`, 这2个版本都会长期维护:
- v5 - 也就是当前分支, 需要依赖于Github配置, 提供自动维护数据功能(微后台),但必须Fork到自己仓库里。
- v3 - 无需依赖Github, 您可以将代码部署在任意服务器, 但数据需要手工维护。
作者推荐您选择 `v5` 没有太多的心智负担。
## 预览
**主题**
- [sim 在线预览](https://nav3.cn/#/sim)
- [light 在线预览](https://nav3.cn/#/light)
![Preview](media/screenshot1.png)
![Preview](media/screenshot2.png)
![Preview](media/screenshot3.png)
![Preview](media/screenshot4.png)
## 拥有出色的特性
`发现导航` 的理念就是做一款无需依赖后端服务既简单又方便,没有繁杂的配置和数据库等配置概念, 做到开箱即用。
- [√] 内置 `800+` 实用网站。
- [√] 三叉树分类、结构清晰、分类清晰。
- [√] 颜值与简约并存,不再是杀马特时代。
- [√] 支持3种浏览模式,创新。
- [√] 支持足迹记忆。
- [√] 支持移动端浏览。
- [√] 支持搜索查询。
- [√] 支持自定义引擎搜索。
- [√] 纯静态, 提供自动化部署功能。
- [√] 完全开源,轻松定制化。
- [√] 多款主题切换。
- [√] 支持暗黑模式。
## 部署
推荐使用 `github pages` 服务, 这样就不需要提供服务器, 并且项目里自带了自动化部署服务,像数 `321` 一样简单。
1、Fork 当前项目。
2、[https://github.com/settings/tokens](https://github.com/settings/tokens) 申请 token, 勾选相应的权限, 如果不懂就全部选中。
3、到 https://github.com/用户名/nav/settings/secrets/new 添加刚刚申请的token, name填写 `TOKEN` 大写。
4、打开 https://github.com/用户名/nav/actions 点击 `绿色按钮`
5、往仓库推送一条Commit (非常重要)。
6、5分钟后打开 https://用户名.github.io/nav 就能看到一个非常强大的导航网站了。
注:如果想部署到自己的域名,那么以上教程同样适合,因为它提供了自动化部署, 之后可以通过 `CNAME``反向代理` 实现:
```conf
# nginx
server {
listen 80;
server_name www.nav3.cn nav3.cn;
location / {
proxy_pass https://xjh22222228.github.io/nav/;
}
}
```
## 配置
所有可配置位于文件 `nav.config.ts`
## 更新数据
只需要关注根目录 `data` 文件夹, 如果你使用了上面教程提供的自动化部署服务,那么当更新数据后大概5分钟即可看到。
```js
{
title: '工具',
icon: 'https://example/favicon.ico',
nav: [
{
title: '网站',
collapsed: false, // 默认展开,设置 false 折叠
showSideIcon: false, // 右侧边图标,如果设置Icon则默认显示
nav: [
{
name: '发现导航',
desc: '发现导航 - 精选实用导航网站',
url: 'https://nav3.cn',
}
]
}
]
}
```
## 图标
图标是支持继承的,每一级的 `icon` 字段都是可选,如果当前没有就会继承上一级Icon,
```js
{
title: 'Example',
icon: 'https://example/icon',
nav: [
{
showSideIcon: false, // 如果这层设置 icon 图标会默认在右侧边栏显示ICON, 设置 false 关闭显示
title: 'Example',
// icon: 'https://favicon.ico',
nav: [
{
// icon: 'https://favicon.ico',
},
]
}
]
}
```
## 开发构建
``` bash
# 下载
git clone --depth=1 https://github.com/xjh22222228/nav.git
# 安装依赖
npm i
# 启动
npm start
# 打包
npm run build
```
## 贡献
[点击这里](https://github.com/xjh22222228/nav/tree/master/data)
Thank you for your [contribution](https://github.com/xjh22222228/nav/issues), men.
<a href="https://github.com/YutHelloWorld">
<img src="https://avatars1.githubusercontent.com/u/20860159?s=460&v=4" width="30px" height="30px" />
</a>
<a href="https://github.com/JJJTHuang">
<img src="https://avatars3.githubusercontent.com/u/22817432?s=460&v=4" width="30px" height="30px" />
</a>
<a href="https://github.com/Fechin">
<img src="https://avatars1.githubusercontent.com/u/2541482?s=460&v=4" width="30px" height="30px" />
</a>
## 建议
如果有任何功能上的建议可通过 [issue](https://github.com/xjh22222228/nav/issues) 发起, Thank you.
## 支持
如果能帮到您,可以请作者喝杯咖啡~
<img src="https://raw.sevencdn.com/xjh22222228/public/gh-pages/img/32.png" width="600">
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"nav": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"aot": false,
"assets": [
"src/favicon.ico",
"src/assets",
{
"glob": "**/*",
"input": "./node_modules/@ant-design/icons-angular/src/inline-svg/",
"output": "/assets/"
}
],
"styles": [
"src/theme.less",
"src/styles.scss"
],
"scripts": []
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
}
]
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "nav:build"
},
"configurations": {
"production": {
"browserTarget": "nav:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "nav:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": []
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"tsconfig.app.json",
"tsconfig.spec.json",
"e2e/tsconfig.json"
],
"exclude": [
"**/node_modules/**"
]
}
},
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "nav:serve"
},
"configurations": {
"production": {
"devServerTarget": "nav:serve:production"
}
}
}
}
}
},
"defaultProject": "nav",
"cli": {
"analytics": false
}
}
\ No newline at end of file
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# You can see what browsers were selected by your queries by running:
# npx browserslist
> 0.5%
last 2 versions
Firefox ESR
not dead
not IE 9-11 # For IE 9-11 support, remove 'not'.
\ No newline at end of file
此差异已折叠。
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, './coverage/nav'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};
import { IConfig } from './src/types'
const c: IConfig = {
// [必填], 请填写您的仓库地址
gitRepoUrl: 'https://github.com/xjh22222228/nav',
// 网站标题
title: '发现导航 - 精选实用导航网站',
// 默认主题: light | sim
theme: 'light',
// 海报图, 只支持 sim 主题
posterImageUrl: 'assets/img/wallpaper.jpg',
// 搜索引擎列表, 为空时不显示搜索引擎
// 自定义引擎 icon 请使用网络图标
searchEngineList: [
{
name: '站内',
icon: 'assets/logo.png',
placeholder: '站内搜索'
},
{
name: '百度',
url: 'https://www.baidu.com/s?wd=',
icon: 'assets/engine/baidu.svg',
placeholder: '百度一下'
},
{
name: 'Google',
url: 'https://www.google.com/search?q=',
icon: 'assets/engine/google.svg',
},
{
name: '必应',
url: 'https://cn.bing.com/search?q=',
icon: 'assets/engine/bing.svg',
},
{
name: 'GitHub',
url: 'https://github.com/search?q=',
icon: 'assets/engine/github.svg',
placeholder: 'Search GitHub'
},
{
name: '知乎',
url: 'https://www.zhihu.com/search?type=content&q=',
icon: 'assets/engine/zhihu.svg',
},
{
name: '豆瓣',
url: 'https://search.douban.com/book/subject_search?search_text=',
icon: 'assets/engine/douban.svg',
placeholder: '书名、作者、ISBN'
}
],
// 错误图标, 图标地址访问不了时显示
// 建议使用网络地址,放在您任何服务器上, 减少入侵
errorIconUrl: '',
// 网站底部描述, 可以是 HTML
// 可以是版权信息,备案号
footerCopyright: '',
// 百度统计
tongjiUrl: 'https://hm.baidu.com/hm.js?4582be7af7e7c95ef75351e07c6c32ba',
indexLanguage: [
'英文',
'中文',
'GitHub'
],
appLanguage: [
'EN',
'CN',
'Git'
],
// 只支持 light 主题
// https://www.nav3.cn/#/index?q=grabient
backgroundLinear: [
'linear-gradient(62deg, #8EC5FC 0%, #E0C3FC 100%)',
'linear-gradient(90deg, #FEE140 0%, #FA709A 100%)',
'linear-gradient(0deg, #08AEEA 0%, #2AF598 100%)',
'linear-gradient(19deg, #21D4FD 0%, #B721FF 100%)',
'linear-gradient(19deg, #FAACA8 0%, #DDD6F3 100%)',
'linear-gradient(147deg, #FFE53B 0%, #FF2525 74%)',
'linear-gradient(180deg, #52ACFF 25%, #FFE32C 100%)',
'linear-gradient(225deg, #FF3CAC 0%, #784BA0 50%, #2B86C5 100%)',
'linear-gradient(0deg, #D9AFD9 0%, #97D9E1 100%)',
'linear-gradient(90deg, #00DBDE 0%, #FC00FF 100%)',
'linear-gradient(160deg, #0093E9 0%, #80D0C7 100%)',
'linear-gradient(90deg, #74EBD5 0%, #9FACE6 100%)',
'linear-gradient(45deg, #FA8BFF 0%, #2BD2FF 52%, #2BFF88 90%)',
'linear-gradient(90deg, #FAD961 0%, #F76B1C 100%)',
'linear-gradient(45deg, #FA8BFF 0%, #2BD2FF 52%, #2BFF88 90%)',
'linear-gradient(45deg, #FBDA61 0%, #FF5ACD 100%)',
'linear-gradient(90deg, #FF9A8B 0%, #FF6A88 55%, #FF99AC 100%)',
'linear-gradient(0deg, #FFDEE9 0%, #B5FFFC 100%)',
'linear-gradient(43deg, #4158D0 0%, #C850C0 46%, #FFCC70 100%)',
'linear-gradient(135deg, #8BC6EC 0%, #9599E2 100%)',
'linear-gradient(180deg, #A9C9FF 0%, #FFBBEC 100%)',
'linear-gradient(45deg, #FA8BFF 0%, #2BD2FF 52%, #2BFF88 90%)',
'linear-gradient(160deg, #0093E9 0%, #80D0C7 100%)',
'linear-gradient(132deg, #F4D03F 0%, #16A085 100%)',
'linear-gradient(62deg, #FBAB7E 0%, #F7CE68 100%)',
'linear-gradient(45deg, #85FFBD 0%, #FFFB7D 100%)'
]
}
export default c
584f65121c6a6411218ae15d841fad9314d2ed20
\ No newline at end of file
此差异已折叠。
{
"name": "nav",
"version": "5.0.0-beta.0",
"author": "xiejiahe",
"bugs": {
"url": "https://github.com/xjh22222228/nav/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/xjh22222228/nav.git"
},
"homepage": "https://xjh22222228.github.io/nav",
"scripts": {
"ng": "ng",
"start": "ng serve --open",
"build": "ng build --prod --base-href ./",
"test": "ng test",
"lint": "eslint --cache --ext .js,.ts ./src",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"@angular/animations": "~11.0.5",
"@angular/common": "~11.0.5",
"@angular/compiler": "~11.0.5",
"@angular/core": "~11.0.5",
"@angular/forms": "~11.0.5",
"@angular/platform-browser": "~11.0.5",
"@angular/platform-browser-dynamic": "~11.0.5",
"@angular/router": "~11.0.5",
"axios": "^0.21.1",
"js-base64": "^3.6.0",
"ng-zorro-antd": "^11.0.1",
"qs": "^6.9.4",
"rough-notation": "^0.5.1",
"rxjs": "~6.6.0",
"tslib": "^2.0.0",
"zone.js": "~0.10.2"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.1100.5",
"@angular/cli": "~11.0.5",
"@angular/compiler-cli": "~11.0.5",
"@types/jasmine": "~3.6.0",
"@types/node": "^12.11.1",
"codelyzer": "^6.0.0",
"eslint": "^7.17.0",
"jasmine-core": "~3.6.0",
"jasmine-spec-reporter": "~5.0.0",
"karma": "~5.1.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.0.3",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.5.0",
"protractor": "~7.0.0",
"ts-node": "~8.3.0",
"tslint": "~6.1.0",
"typescript": "~4.0.2"
},
"types": "./src/types/index.d.ts"
}
import { NgModule } from '@angular/core'
import { Routes, RouterModule } from '@angular/router'
const routes: Routes = []
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
<div id="xiejiahe">
<app-icon-git></app-icon-git>
<router-outlet></router-outlet>
</div>
// Copyright @ 2018-2021 xiejiahe. All rights reserved. MIT license.
import { Component } from '@angular/core'
import { Router, ActivatedRoute } from '@angular/router'
import config from '../../nav.config'
import { queryString, setLocation } from '../utils'
@Component({
selector: 'app-xiejiahe',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
constructor (private router: Router, private activatedRoute: ActivatedRoute) {}
ngOnInit() {
document.title = config.title
this.goRoute()
this.appendTongji()
this.activatedRoute.queryParams.subscribe(setLocation)
}
goRoute() {
const { page, id, q } = queryString()
const screenWidth = window.innerWidth
const queryParams = { page, id, q }
if (screenWidth < 768) {
this.router.navigate(['/app'], { queryParams })
}
}
appendTongji() {
if (
document.getElementById('tongji_url') ||
window.location.hostname === 'localhost'
) return
const script = document.createElement('script')
script.src = config.tongjiUrl
script.id = 'tongji_url'
script.async = true
document.head.appendChild(script)
}
}
// Copyright @ 2018-2021 xiejiahe. All rights reserved. MIT license.
import { BrowserModule } from '@angular/platform-browser'
import { NgModule } from '@angular/core'
import { RouterModule, Routes } from '@angular/router'
import { FormsModule } from '@angular/forms'
import config from '../../nav.config'
import { NzModalModule } from 'ng-zorro-antd/modal'
import { NzInputModule } from 'ng-zorro-antd/input'
import { NzRadioModule } from 'ng-zorro-antd/radio'
import { NzSelectModule } from 'ng-zorro-antd/select'
import { NzMessageModule } from 'ng-zorro-antd/message'
import { NzNotificationModule } from 'ng-zorro-antd/notification'
import { NzFormModule } from 'ng-zorro-antd/form'
import { NzEmptyModule } from 'ng-zorro-antd/empty'
import { NzButtonModule } from 'ng-zorro-antd/button'
import { ReactiveFormsModule } from '@angular/forms'
import { NzAvatarModule } from 'ng-zorro-antd/avatar'
// components
import { AppComponent } from './app.component'
// views
import LightComponent from '../view/index/light/index.component'
import SimComponent from '../view/index/sim/index.component'
import WebpComponent from '../view/app/default/app.component'
import { FixbarComponent } from '../components/fixbar/index.component'
import { MultipleSiteComponent } from '../components/multiple-site/index.component'
import { FooterComponent } from '../components/footer/footer.component'
import { IconGitComponent } from '../components/icon-git/icon-git.component'
import { NoDataComponent } from '../components/no-data/no-data.component'
import { SearchEngineComponent } from '../components/search-engine/search-engine.component';
import { CreateComponent } from '../components/create/create.component';
import { NZ_I18N } from 'ng-zorro-antd/i18n';
import { zh_CN } from 'ng-zorro-antd/i18n';
import { registerLocaleData } from '@angular/common';
import zh from '@angular/common/locales/zh';
import { HttpClientModule } from '@angular/common/http';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { LogoComponent } from '../components/logo/logo.component'
registerLocaleData(zh);
const appRoutes: Routes = [
{
path: 'sim',
component: SimComponent,
},
{
path: 'light',
component: LightComponent,
},
{
path: 'app',
component: WebpComponent,
},
{
path: '**',
redirectTo: '/' + config.theme,
},
]
@NgModule({
declarations: [
AppComponent,
LightComponent,
SimComponent,
WebpComponent,
FixbarComponent,
MultipleSiteComponent,
FooterComponent,
IconGitComponent,
NoDataComponent,
SearchEngineComponent,
CreateComponent,
LogoComponent
],
imports: [
NzModalModule,
NzInputModule,
NzRadioModule,
NzSelectModule,
NzMessageModule,
NzNotificationModule,
NzFormModule,
NzEmptyModule,
NzButtonModule,
ReactiveFormsModule,
NzAvatarModule,
BrowserModule,
FormsModule,
RouterModule.forRoot(
appRoutes,
{
enableTracing: false, // <-- debugging purposes only
useHash: true,
}
),
HttpClientModule,
BrowserAnimationsModule
],
providers: [{ provide: NZ_I18N, useValue: zh_CN }],
bootstrap: [
AppComponent,
],
})
export class AppModule { }
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1605624253236" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2442" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M345.706 297.423c15.35 12.792 35.817 20.467 53.726 17.908 20.467 0 38.375-10.233 53.725-23.025 17.91-15.35 30.7-35.817 40.934-58.842C512 189.97 512 136.245 496.65 90.194c-10.234-30.7-28.142-58.842-53.726-76.75C427.574 3.21 404.548-1.906 384.081 0.652c-12.791 2.558-25.583 7.675-38.375 15.35-23.025 15.35-38.376 40.934-48.61 66.518-12.791 46.05-15.35 92.101-2.557 138.152 10.233 28.142 25.583 56.284 51.167 76.75z m255.837 2.558c17.909 15.35 40.934 25.584 63.96 25.584 20.466 2.558 38.375-2.558 56.283-12.792 17.909-10.233 33.26-25.584 43.493-43.492 12.792-20.467 23.025-40.934 30.7-63.96 5.117-17.908 7.675-38.375 5.117-58.842-2.559-28.142-15.35-53.726-33.259-76.751-12.792-15.35-28.142-30.7-46.05-38.376-12.792-5.116-28.143-10.233-40.935-7.675-17.908 2.559-33.258 12.792-46.05 23.026-17.909 15.35-33.259 33.258-43.493 53.725-10.233 17.909-20.466 38.376-23.025 61.401-2.558 25.584-2.558 51.168 2.559 74.193 5.116 23.025 12.791 46.05 30.7 63.96zM245.929 509.768c17.91-15.35 28.143-35.818 35.818-56.285 10.233-33.258 10.233-66.517 7.675-99.776 0-12.792-5.117-25.584-10.234-38.376-12.792-28.142-35.817-53.725-63.959-69.076-23.025-10.233-46.05-15.35-66.518-10.233-25.583 2.558-46.05 20.467-61.4 40.934-20.467 28.142-30.7 63.96-35.818 97.218-2.558 20.467 0 40.934 5.117 61.4 7.675 30.701 23.025 58.843 46.05 79.31 17.91 15.35 40.935 23.026 63.96 23.026 28.142 0 56.284-7.675 79.31-28.142z m736.811-76.752c-2.558-20.467-7.675-38.375-17.908-56.284-10.234-20.467-28.143-40.934-48.61-51.167-23.025-12.792-51.167-15.35-76.75-12.792-12.792 2.558-28.143 5.117-40.935 12.792-17.908 10.233-30.7 28.142-40.933 48.609-10.234 25.584-15.35 53.726-15.35 81.868 0 25.583 0 53.726 7.674 79.31 5.117 17.908 15.35 38.375 33.26 48.608 17.908 15.35 40.933 20.467 63.959 23.026 17.908 2.558 38.375 2.558 56.284-2.559 17.908-5.116 35.817-15.35 46.05-30.7 12.792-15.35 20.467-35.817 23.026-53.726 12.792-30.7 10.233-58.842 10.233-86.985zM911.106 819.33c-2.559-35.817-20.467-71.634-46.05-99.776-5.118-5.117-10.234-10.234-17.91-15.35-33.258-28.142-66.517-58.843-99.776-89.543-33.259-33.26-63.96-69.076-92.101-107.452-20.467-33.259-48.61-63.96-86.985-81.868-23.025-10.233-51.167-15.35-76.751-12.792-46.05 5.117-86.985 30.7-115.127 66.518-7.675 7.675-12.791 17.909-17.908 28.142-20.467 30.7-46.05 61.401-74.193 86.985-15.35 15.35-30.7 28.142-46.05 40.934-7.676 7.675-17.91 15.35-25.584 23.025-30.7 23.025-61.401 53.726-79.31 86.985-12.792 23.025-20.467 48.609-23.025 76.75 0 23.026 2.558 46.051 10.233 66.518 7.675 23.026 17.909 46.051 33.26 63.96 25.583 30.7 63.958 51.167 102.334 53.725 48.609 2.559 97.218 0 143.269-7.675 20.467-2.558 40.934-10.233 63.959-12.792 46.05-5.116 92.101-2.558 135.594 10.234 35.817 12.792 74.192 17.909 112.568 20.467 38.375 2.558 79.31-2.558 115.127-23.025 25.583-12.792 46.05-35.818 58.842-61.401 20.467-33.26 30.7-71.635 25.584-112.569zM481.3 924.224H363.615c-12.792 0-25.584 0-38.376-2.559-25.584-5.117-48.61-20.467-63.96-43.492-12.791-15.35-20.466-33.259-23.025-53.726a246.563 246.563 0 0 1 0-61.4c5.117-23.026 17.909-43.493 33.26-58.843 12.791-12.792 30.7-23.026 48.608-30.7 7.675-2.56 15.35-5.117 23.026-5.117h69.076v-97.219h66.517c2.559 120.244 2.559 237.929 2.559 353.056z m263.512 0H583.634c-17.908-2.559-33.258-7.676-46.05-17.909-15.35-12.792-23.026-33.259-23.026-51.167v-173.97h66.518v161.178c0 7.675 2.558 12.792 7.675 17.908 5.117 5.117 12.792 7.675 20.467 7.675h69.076V678.62h66.518v245.604z" fill="#306CFF" p-id="2443"></path><path d="M340.59 734.904c-12.793 5.117-25.585 15.35-33.26 30.7-5.116 12.792-7.675 25.584-7.675 38.376 0 15.35 5.117 30.7 12.792 43.492 10.234 15.35 28.142 25.584 46.05 23.026h53.727V732.346H353.38c-2.558-2.559-7.675 0-12.792 2.558z" fill="#306CFF" p-id="2444"></path></svg>
\ No newline at end of file
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Bing icon</title><path d="M3.605 0L8.4 1.686V18.56l6.753-3.895-3.31-1.555-2.09-5.2 10.64 3.738v5.435L8.403 24l-4.797-2.67V0z" style="fill: rgb(78, 129, 187);"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1605660307841" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5230" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M687.8 607.5H339.7c37.4 62.9 71.5 130.3 102.1 202.1h142.1c43.6-68.2 78.2-135.6 103.9-202.1z m41.7-76.7V381.5H302v149.4h427.5z m173.1 356H121.4v-77.1h233.7c-28.6-62.9-58.9-117.3-90.7-163.1l60.1-39H219.2V303h593.6v304.5H702.3l60.1 39.9c-28.4 62.9-57.9 117-88.6 162.2h228.8v77.2z m-17.1-673.3H143.7v-76.2h741.8v76.2z" fill="#58CB6A" p-id="5231"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1605660214673" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4828" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M512 0C229.283787 0 0.142041 234.942803 0.142041 524.867683c0 231.829001 146.647305 428.553077 350.068189 497.952484 25.592898 4.819996 34.976961-11.38884 34.976961-25.294314 0-12.45521-0.469203-45.470049-0.725133-89.276559-142.381822 31.735193-172.453477-70.380469-172.453477-70.380469-23.246882-60.569859-56.816233-76.693384-56.816234-76.693385-46.493765-32.58829 3.540351-31.948468 3.540351-31.948467 51.356415 3.71097 78.356923 54.086324 78.356923 54.086324 45.683323 80.19108 119.817417 57.072162 148.993321 43.593236 4.649376-33.91059 17.915029-57.029508 32.50298-70.167195-113.675122-13.222997-233.151301-58.223843-233.1513-259.341366 0-57.285437 19.919806-104.163095 52.678715-140.846248-5.246544-13.265652-22.820334-66.626844 4.990615-138.884127 0 0 42.996069-14.076094 140.760939 53.787741 40.863327-11.644769 84.627183-17.445825 128.177764-17.6591 43.465272 0.213274 87.271782 6.014331 128.135109 17.6591 97.679561-67.906489 140.59032-53.787741 140.59032-53.787741 27.938914 72.257282 10.407779 125.618474 5.118579 138.884127 32.844219 36.683154 52.593405 83.560812 52.593405 140.846248 0 201.586726-119.646798 245.990404-233.663158 258.957473 18.341577 16.208835 34.721032 48.199958 34.721032 97.210357 0 70.167195-0.639822 126.7275-0.639823 143.960051 0 14.033439 9.213443 30.370239 35.190235 25.209005 203.250265-69.527373 349.769606-266.123484 349.769605-497.867175C1023.857959 234.942803 794.673558 0 512 0" fill="#3E75C3" p-id="4829"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1605628405639" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2857" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M214.101333 512c0-32.512 5.546667-63.701333 15.36-92.928L57.173333 290.218667A491.861333 491.861333 0 0 0 4.693333 512c0 79.701333 18.858667 154.88 52.394667 221.610667l172.202667-129.066667A290.56 290.56 0 0 1 214.101333 512" fill="#FBBC05" p-id="2858"></path><path d="M516.693333 216.192c72.106667 0 137.258667 25.002667 188.458667 65.962667L854.101333 136.533333C763.349333 59.178667 646.997333 11.392 516.693333 11.392c-202.325333 0-376.234667 113.28-459.52 278.826667l172.373334 128.853333c39.68-118.016 152.832-202.88 287.146666-202.88" fill="#EA4335" p-id="2859"></path><path d="M516.693333 807.808c-134.357333 0-247.509333-84.864-287.232-202.88l-172.288 128.853333c83.242667 165.546667 257.152 278.826667 459.52 278.826667 124.842667 0 244.053333-43.392 333.568-124.757333l-163.584-123.818667c-46.122667 28.458667-104.234667 43.776-170.026666 43.776" fill="#34A853" p-id="2860"></path><path d="M1005.397333 512c0-29.568-4.693333-61.44-11.648-91.008H516.650667V614.4h274.602666c-13.696 65.962667-51.072 116.650667-104.533333 149.632l163.541333 123.818667c93.994667-85.418667 155.136-212.650667 155.136-375.850667" fill="#4285F4" p-id="2861"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1605660236759" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5002" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M571.1 254v527.7h52.3l19 66.2 92-66.2h129.4V254H571.1z m233.3 467.8h-60.8L666 781l-16.9-59.2h-16v-409h171.3v409zM540.7 552.6s0-56.4-27.8-59.5c-27.9-3.1-114.5 0-114.5 0V317.5h128.4s-1.5-58-26.3-58H291.6l32.5-95.6s-52.6 3.1-71.2 36c-18.5 33-78.9 202.2-78.9 202.2s20.1 9.4 54.2-15.7c34-25.1 44.9-69 44.9-69l61.9-3.1 1.5 178.7s-106.8-1.6-128.4 0c-21.7 1.6-34 59.5-34 59.5h162.5s-13.9 98.7-55.7 170.8C239 795.5 160.1 851.9 160.1 851.9s57.2 23.5 112.9-9.4 96.8-177.7 96.8-177.7l130.7 162s11.9-77.1-2.1-98.9c-14-21.8-90.4-109.3-90.4-109.3l-33.3 29.8 23.7-95.8h142.3z" fill="#49C0FB" p-id="5003"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1608359352079" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1246" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M728.064 573.44a164.864 164.864 0 1 0-164.864 164.864 164.864 164.864 0 0 0 164.864-164.864z" fill="#F4CA1C" p-id="1247"></path><path d="M512.5632 771.584a258.43712 258.43712 0 1 1 258.43712-258.43712A258.72896 258.72896 0 0 1 512.5632 771.584z m0-448.86016a190.42816 190.42816 0 1 0 190.4128 190.40256 190.63808 190.63808 0 0 0-190.4128-190.42304z m1.13664-128.67584a34.00192 34.00192 0 0 1-34.00192-34.00704V95.44704a34.00704 34.00704 0 0 1 68.00896 0V160.0512a34.00704 34.00704 0 0 1-34.00704 33.9968z m0 768.512a34.00192 34.00192 0 0 1-34.00192-34.00192V863.9488a34.00704 34.00704 0 1 1 68.00896 0v64.60928a34.00192 34.00192 0 0 1-34.00704 34.00192z m414.85824-416.55296H863.9488a34.00704 34.00704 0 1 1 0-68.00896h64.60928a34.00704 34.00704 0 0 1 0 68.00896z m-768.512 0H95.44704a34.00704 34.00704 0 0 1 0-68.00896H160.0512a34.00704 34.00704 0 0 1 0 68.00896z m600.81664-248.86784a34.00192 34.00192 0 0 1-24.064-58.04544l45.69088-45.68576a34.00192 34.00192 0 0 1 48.08192 48.08704L784.896 287.1808a33.88416 33.88416 0 0 1-24.03328 9.9584zM217.45152 840.5504a34.00704 34.00704 0 0 1-24.064-58.05568l45.68576-45.68064a34.00704 34.00704 0 1 1 48.10752 48.08192l-45.68576 45.68064a33.91488 33.91488 0 0 1-24.04352 9.97376z m589.10208 0a33.90976 33.90976 0 0 1-24.0384-9.9584l-45.696-45.696a34.00704 34.00704 0 1 1 48.08704-48.09728l45.68576 45.68064a34.00704 34.00704 0 0 1-24.04352 58.07104zM263.13728 297.1392a33.88416 33.88416 0 0 1-24.064-9.96352l-45.68576-45.68576a34.00192 34.00192 0 1 1 48.08704-48.08192L287.1808 239.104a34.00192 34.00192 0 0 1-24.04352 58.0352z" fill="#595BB3" p-id="1248"></path></svg>
\ No newline at end of file
此差异已折叠。
/*! Ripple.js v1.2.1
* The MIT License (MIT)
* Copyright (c) 2014 Jacob Kelley */
!function(a,b,c){a.ripple=function(d,e){var f=this,g=f.log=function(){f.defaults.debug&&console&&console.log&&console.log.apply(console,arguments)};f.selector=d,f.defaults={debug:!1,on:"mousedown",opacity:.4,color:"auto",multi:!1,duration:.7,rate:function(a){return a},easing:"linear"},f.defaults=a.extend({},f.defaults,e);var h=function(b){var d,e,h=a(this);if(h.addClass("has-ripple"),e=a.extend({},f.defaults,h.data()),e.multi||!e.multi&&0===h.find(".ripple").length){if(d=a("<span></span>").addClass("ripple"),d.appendTo(h),g("Create: Ripple"),!d.height()&&!d.width()){var i=c.max(h.outerWidth(),h.outerHeight());d.css({height:i,width:i}),g("Set: Ripple size")}if(e.rate&&"function"==typeof e.rate){var j=c.round(d.width()/e.duration),k=e.rate(j),l=d.width()/k;e.duration.toFixed(2)!==l.toFixed(2)&&(g("Update: Ripple Duration",{from:e.duration,to:l}),e.duration=l)}var m="auto"==e.color?h.css("color"):e.color,n={animationDuration:e.duration.toString()+"s",animationTimingFunction:e.easing,background:m,opacity:e.opacity};g("Set: Ripple CSS",n),d.css(n)}e.multi||(g("Set: Ripple Element"),d=h.find(".ripple")),g("Destroy: Ripple Animation"),d.removeClass("ripple-animate");var o=b.pageX-h.offset().left-d.width()/2,p=b.pageY-h.offset().top-d.height()/2;e.multi&&(g("Set: Ripple animationend event"),d.one("animationend webkitAnimationEnd oanimationend MSAnimationEnd",function(){g("Note: Ripple animation ended"),g("Destroy: Ripple"),a(this).remove()})),g("Set: Ripple location"),g("Set: Ripple animation"),d.css({top:p+"px",left:o+"px"}).addClass("ripple-animate")};a(b).on(f.defaults.on,f.selector,h)}}(jQuery,document,Math);$.ripple.version = "1.2.1";
\ No newline at end of file
<nz-modal
[(nzVisible)]="visible"
[nzTitle]="getTitle()"
[nzOkLoading]="submiting"
(nzOnCancel)="hanldeCancel()"
(nzOnOk)="handleOk()"
>
<ng-container *nzModalContent>
<div *ngIf="isLogin; else ab">
<p><b>请完成所有添加后点击右下角将所有数据同步到远端!</b></p>
<nz-radio-group [(ngModel)]="radioType" style="margin-bottom:15px;">
<label nz-radio nzValue="1">新增一级分类</label>
<label nz-radio nzValue="2">新增二级分类</label>
<label nz-radio nzValue="3">新增三级分类</label>
<label nz-radio nzValue="6">新增网站</label>
</nz-radio-group>
<form nz-form [formGroup]="validateForm" (ngSubmit)="handleOk()">
<nz-form-item *ngIf="radioType !== '1'">
<nz-form-label [nzSpan]="4" nzRequired>一级分类</nz-form-label>
<nz-form-control [nzSpan]="20" nzErrorTip="请选择一级分类">
<nz-select
formControlName="oneSelect"
nzShowSearch
nzPlaceHolder="请选择一级分类"
(ngModelChange)="hanldeOneSelect($event)"
>
<nz-option
*ngFor="let item of websiteList"
[nzLabel]="item.title"
[nzValue]="item.title"
>
</nz-option>
</nz-select>
</nz-form-control>
</nz-form-item>
<nz-form-item *ngIf="radioType === '3' || radioType === '6'">
<nz-form-label [nzSpan]="4" nzRequired>二级分类</nz-form-label>
<nz-form-control [nzSpan]="20" nzErrorTip="请选择二级分类">
<nz-select
formControlName="twoSelect"
nzShowSearch
nzPlaceHolder="请选择二级分类"
(ngModelChange)="hanldeTwoSelect($event)"
>
<nz-option
*ngFor="let item of twoList"
[nzLabel]="item.title"
[nzValue]="item.title"
>
</nz-option>
</nz-select>
</nz-form-control>
</nz-form-item>
<nz-form-item *ngIf="radioType === '6'">
<nz-form-label [nzSpan]="4" nzRequired>三级分类</nz-form-label>
<nz-form-control [nzSpan]="20" nzErrorTip="请选择三级分类">
<nz-select
formControlName="threeSelect"
nzShowSearch
nzPlaceHolder="请选择三级分类"
>
<nz-option
*ngFor="let item of threeList"
[nzLabel]="item.title"
[nzValue]="item.title"
>
</nz-option>
</nz-select>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label [nzSpan]="4" nzRequired>{{ radioType === '4' ? '网站名称' : '分类名称'}}</nz-form-label>
<nz-form-control [nzSpan]="20" nzErrorTip="请输入名称">
<input formControlName="title" nz-input placeholder="发现导航" maxlength="10" />
</nz-form-control>
</nz-form-item>
<nz-form-item *ngIf="radioType === '6'">
<nz-form-label [nzSpan]="4" nzRequired>网站链接</nz-form-label>
<nz-form-control [nzSpan]="20">
<input formControlName="url" nz-input placeholder="https://nav3.cn" />
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label [nzSpan]="4">图标地址</nz-form-label>
<nz-form-control [nzSpan]="20">
<input formControlName="icon" nz-input placeholder="https://example.com/favicon.png" />
</nz-form-control>
</nz-form-item>
<nz-form-item *ngIf="radioType === '6'">
<nz-form-label [nzSpan]="4">网站描述</nz-form-label>
<nz-form-control [nzSpan]="20">
<input formControlName="description" nz-input placeholder="发现导航, 精选实用导航网站" />
</nz-form-control>
</nz-form-item>
</form>
</div>
<ng-template #ab>
<p>请在下方输入您的TOKEN进行登录校验</p>
<input nz-input placeholder="请输入TOKEN" [(ngModel)]="token" maxlength="100" autofocus />
<p style="margin-top: 15px;">
不知道Token如何获取?<a href="https://github.com/xjh22222228/nav#%E9%83%A8%E7%BD%B2" target="_blank">请先阅读我们的指南</a>
</p>
</ng-template>
</ng-container>
</nz-modal>
// Copyright @ 2018-2021 xiejiahe. All rights reserved. MIT license.
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'
import { setWebsiteList } from '../../utils'
import { NzMessageService } from 'ng-zorro-antd/message'
import { NzNotificationService } from 'ng-zorro-antd/notification'
import { updateFileContent } from '../../services'
import { getToken, setToken } from '../../utils/user'
import { FormBuilder, FormGroup, Validators } from '@angular/forms'
import { websiteList } from '../../store'
import { VERIFY_PATH } from '../../constants'
@Component({
selector: 'app-create',
templateUrl: './create.component.html',
styleUrls: ['./create.component.scss']
})
export class CreateComponent implements OnInit {
@Input() visible: boolean
@Output() onCancel = new EventEmitter()
validateForm!: FormGroup;
websiteList = websiteList
twoList = []
threeList = []
token = ''
isLogin = false
radioType = '6'
submiting = false
constructor(
private fb: FormBuilder,
private message: NzMessageService,
private notification: NzNotificationService,
) {}
ngOnInit(): void {
this.isLogin = !!getToken()
this.validateForm = this.fb.group({
title: [null, [Validators.required]],
oneSelect: [null, [Validators.required]],
twoSelect: [null, [Validators.required]],
threeSelect: [null, [Validators.required]],
url: [null, [Validators.required]],
icon: [null],
description: [''],
});
}
getTitle() {
if (!this.isLogin) {
return '请登录'
}
switch (this.radioType) {
case '1':
return '新增一级分类'
case '2':
return '新增二级分类'
case '3':
return '新增三级分类'
case '6':
return '新增网站'
}
}
hanldeOneSelect(value) {
if (!value) return
const findItem = this.websiteList.find(item => item.title === value);
this.twoList = findItem.nav
this.validateForm.get('twoSelect')!.setValue(null)
}
hanldeTwoSelect(value) {
if (!value) return
const { oneSelect } = this.validateForm.value
const oIdx = this.websiteList.findIndex(item => item.title === oneSelect)
const findItem = this.websiteList[oIdx].nav.find(item => item.title === value)
this.threeList = findItem.nav
this.validateForm.get('threeSelect')!.setValue(null)
}
hanldeCancel() {
this.onCancel.emit()
}
login() {
if (!this.token || this.token.length < 40) {
return this.message.error('请填写正确的Token');
}
this.submiting = true
updateFileContent({
message: 'verify',
content: 'OK',
path: VERIFY_PATH
}, this.token)
.then(() => {
setToken(this.token);
this.message.success('登录成功, 2秒后刷新!')
setTimeout(() => window.location.reload(), 2000)
})
.catch(res => {
this.notification.error('登录失败, 请填写正确Token', res.message as string)
})
.finally(() => {
this.submiting = false
})
}
handleOk() {
try {
if (!this.isLogin) {
return this.login();
}
for (const i in this.validateForm.controls) {
this.validateForm.controls[i].markAsDirty();
this.validateForm.controls[i].updateValueAndValidity();
}
let { title, icon, oneSelect, twoSelect, threeSelect, url, description } = this.validateForm.value
if (!title) return
title = title.trim()
if (icon) {
icon = icon.trim()
}
switch (this.radioType) {
// 新增一级分类
case '1':
const isExists = this.websiteList.some(item => item.title === title)
if (isExists) {
this.message.error('请不要重复添加!')
return
}
this.websiteList.push({
title,
icon,
nav: []
})
break
// 新增二级分类
case '2': {
if (!oneSelect) return
const findIdx = this.websiteList.findIndex(item => item.title === oneSelect)
const exists = this.websiteList[findIdx].nav.some(item => item.title === title)
if (exists) {
this.message.error('请不要重复添加!')
return
}
this.websiteList[findIdx].nav.push({
title,
icon,
nav: []
})
break
}
// 新增三级分类
case '3':
if (!oneSelect && !twoSelect) return
const oIdx = this.websiteList.findIndex(item => item.title === oneSelect)
const tIdx = this.websiteList[oIdx].nav.findIndex(item => item.title === twoSelect)
const exists = this.websiteList[oIdx].nav[tIdx].nav.some(item => item.title === title)
if (exists) {
this.message.error('请不要重复添加!')
return
}
this.websiteList[oIdx].nav[tIdx].nav.unshift({
title,
icon,
nav: []
})
break
// 新增网站
case '6': {
if (!oneSelect && !twoSelect && !threeSelect && !url) return
const oIdx = this.websiteList.findIndex(item => item.title === oneSelect)
const tIdx = this.websiteList[oIdx].nav.findIndex(item => item.title === twoSelect)
const eIdx = this.websiteList[oIdx].nav[tIdx].nav.findIndex(item => item.title === threeSelect)
const exists = this.websiteList[oIdx].nav[tIdx].nav[eIdx].nav.some(item => item.name === title)
if (exists) {
this.message.error('请不要重复添加!')
return
}
this.websiteList[oIdx].nav[tIdx].nav[eIdx].nav.unshift({
name: title,
icon,
url,
desc: description
})
break
}
}
this.validateForm.reset()
setWebsiteList(this.websiteList)
this.message.success('新增成功!')
} catch (error) {
this.notification.error('内部异常', error.message)
}
}
}
<div class="fixbar">
<div
class="wrapper dark-bg dark-border-color dark-action-hover"
(click)="handleSync()"
*ngIf="isLogin"
[class.rotate]="syncLoading"
>
<i class="iconfont iconwinfo-icon-tongbu"></i>
</div>
<div class="wrapper dark-bg dark-border-color dark-action-hover" (click)="toggleModal()">
<i class="iconfont iconchuangjian"></i>
</div>
<div class="wrapper dark-bg dark-border-color dark-action-hover" (click)="collapse()">
<i class="iconfont iconweibiaoti25 collapse" [class.active]="collapsed"></i>
</div>
<div class="wrapper dark-bg dark-border-color dark-action-hover" (click)="toggleMode()">
<i class="iconfont icondark dark" *ngIf="!isDark"></i>
<img class="iconfont icondark" src="assets/img/light.svg" *ngIf="isDark">
</div>
<div class="wrapper dark-bg dark-border-color dark-action-hover" (click)="scrollTop()">
<i class="iconfont iconjiantouarrow483 arrow"></i>
</div>
</div>
<app-create [visible]="showCreateModal" (onCancel)="toggleModal()"></app-create>
.fixbar {
z-index: 9;
position: fixed;
bottom: 30px;
right: 15px;
user-select: none;
.wrapper {
width: 40px;
height: 40px;
margin-top: 10px;
transition: .1s linear;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
border-radius: 50%;
background-color: #fff;
box-shadow: 0 0 5px rgba(0, 0, 0, .05);
&:hover {
box-shadow: 0 0 5px rgba(0, 0, 0, .15);
transform: scale(1.2);
}
}
img {
width: 25px;
height: 25px;
}
i {
transition: .1s linear;
display: inline-block;
font-size: 20px;
color: #999;
}
.arrow {
transform: rotate(180deg);
cursor: pointer;
font-weight: bold;
}
.collapse {
transform: rotate(-270deg);
&.active {
transform: rotate(-360deg);
}
}
}
// Copyright @ 2018-2021 xiejiahe. All rights reserved. MIT license.
import { Component, Output, EventEmitter, Input } from '@angular/core'
import { isDark as isDarkFn, randomBgImg } from '../../utils'
import { NzModalService } from 'ng-zorro-antd/modal'
import { NzMessageService } from 'ng-zorro-antd/message'
import { NzNotificationService } from 'ng-zorro-antd/notification'
import { getToken } from '../../utils/user'
import { updateFileContent } from '../../services'
import { websiteList } from '../../store'
import { DB_PATH } from '../../constants'
@Component({
selector: 'app-fixbar',
templateUrl: './index.component.html',
styleUrls: ['./index.component.scss']
})
export class FixbarComponent {
@Input() collapsed: boolean
@Input() randomBg: boolean
@Input() selector: string
@Output() onCollapse = new EventEmitter()
websiteList = websiteList
isDark: boolean = isDarkFn()
showCreateModal = false
syncLoading = false
isLogin = !!getToken()
constructor(
private message: NzMessageService,
private notification: NzNotificationService,
private modal: NzModalService
) {}
ngOnInit() {
if (isDarkFn()) {
document.body.classList.add('dark-container')
}
}
scrollTop() {
if (this.selector) {
const el = document.querySelector(this.selector)
if (el) {
el.scrollTop = 0
}
return
}
window.scrollTo({
top: 0,
behavior: 'smooth'
})
}
collapse() {
this.onCollapse.emit()
}
toggleMode() {
this.isDark = !this.isDark
window.localStorage.setItem('IS_DARK', String(Number(this.isDark)))
document.body.classList.toggle('dark-container')
if (this.isDark) {
const el = document.getElementById('random-light-bg')
el?.parentNode?.removeChild?.(el)
} else {
this.randomBg && randomBgImg()
}
}
toggleModal() {
this.showCreateModal = !this.showCreateModal
}
handleSync() {
if (this.syncLoading) {
this.message.warning('请不要频繁操作')
return
}
this.modal.info({
nzTitle: '同步数据到远端',
nzContent: '确定将所有数据同步到远端吗?这可能需要消耗一定的时间。',
nzOnOk: () => {
this.syncLoading = true;
updateFileContent({
message: 'update db',
content: JSON.stringify(this.websiteList),
path: DB_PATH
})
.then(() => {
this.message.success('同步成功, 大约需要5分钟构建时间')
})
.catch(res => {
this.notification.error(
`错误: ${res?.response?.status ?? 404}`,
'同步失败, 请重试'
)
})
.finally(() => {
this.syncLoading = false
})
}
});
}
}
<footer class="footer dark-text" [class]="className" >
<div class="total" [ngStyle]="{marginBottom: !footerCopyright && '5px'}">
共收录 {{ totalWeb }} 个网站
</div>
<div class="copyright" *ngIf="footerCopyright" [innerHTML]="footerCopyright"></div>
</footer>
.footer {
text-align: center;
margin-top: 5px;
color: #fff;
.total {
font-weight: bold;
}
.runtime {
font-weight: bold;
}
.copyright {
color: #fff;
margin-bottom: 5px;
}
}
// Copyright @ 2018-2021 xiejiahe. All rights reserved. MIT license.
import { Component, Input } from '@angular/core'
import config from '../../../nav.config'
import { totalWeb } from '../../utils'
@Component({
selector: 'app-footer',
templateUrl: './footer.component.html',
styleUrls: ['./footer.component.scss']
})
export class FooterComponent {
footerCopyright: string = config.footerCopyright;
totalWeb: number = totalWeb()
@Input() className: string
}
<a
[href]="gitRepoUrl"
target="_blank"
rel="noreferer noopener"
class="github-link"
*ngIf="gitRepoUrl"
>
<svg width="60" height="60" viewBox="0 0 250 250" style="fill:#20a0ff;color:#fff;position:fixed;top:-6px;border:0;right:-6px;z-index:10001;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" data-url="https://github.com/xjh22222228" fill="currentColor" style="transform-origin:130px 106px" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"><style>@keyframes octocat {0%, 100% {transform:rotate(0);}20%,60%{transform:rotate(-25deg);}40%, 80%{transform: rotate(100deg);}}.octo-arm {animation: octocat 1.1s linear infinite;}</style></path></svg>
</a>
// Copyright @ 2018-2021 xiejiahe. All rights reserved. MIT license.
import { Component, OnInit } from '@angular/core';
import config from '../../../nav.config';
@Component({
selector: 'app-icon-git',
templateUrl: './icon-git.component.html',
styleUrls: ['./icon-git.component.scss']
})
export class IconGitComponent implements OnInit {
gitRepoUrl: string = config.gitRepoUrl
constructor() { }
ngOnInit(): void {
}
}
<img
*ngIf="src && !hasError; else noSrc"
[src]="src"
class="icon dark-border-color"
(error)="onImgError($event)"
>
<ng-template #noSrc>
<nz-avatar
[nzSize]="35"
[nzGap]="gap"
[ngStyle]="{ 'background-color': color }"
[nzText]="name[0]"
nzSize="large"
style="vertical-align: middle;"
>
</nz-avatar>
</ng-template>
.icon {
display: inline-block;
width: 35px;
height: 35px;
vertical-align: middle;
border-radius: 50%;
pointer-events: none;
border: 1px solid #eee;
background-color: #fff;
}
import { Component, OnInit, Input } from '@angular/core'
@Component({
selector: 'app-logo',
templateUrl: './logo.component.html',
styleUrls: ['./logo.component.scss']
})
export class LogoComponent implements OnInit {
@Input() src: string
@Input() name: string
hasError = false
color = '#1890ff'
constructor() { }
ngOnInit(): void {
}
onImgError = () => {
this.hasError = true
}
}
<div class="mark dark-item-active" *ngIf="dataSource && dataSource.language && dataSource.language.length > 0">
<div class="button-box">
<a
[href]="dataSource.language[0]"
class="zh"
target="_blank"
*ngIf="dataSource.language[0]"
rel="noopener noreferer"
>
{{ language[0] }}
</a>
<a
[href]="dataSource.language[1]"
class="zh"
target="_blank"
*ngIf="dataSource.language[1]"
rel="noopener noreferer"
>
{{ language[1] }}
</a>
<a
[href]="dataSource.language[2]"
class="zh"
target="_blank"
*ngIf="dataSource.language[2]"
rel="noopener noreferer"
>
{{ language[2] }}
</a>
</div>
</div>
.mark {
z-index: 28;
position: absolute;
bottom: -50px;
left: 0;
width: 100%;
padding: 10px 0 5px 0px;
background: #fbfbfb;
cursor: auto;
display: flex;
justify-content: center;
align-content: center;
transition: .1s linear;
.button-box {
width: 100%;
text-align: center;
}
a {
display: inline-block;
position: relative;
width: 50px;
font-size: 12px;
padding: 3px 0;
border: none;
background: #2db7f5;
color: #fff;
border-radius: 3px;
overflow: hidden;
&:not(:nth-last-child(1)) {
margin-right: 10px;
}
&:after {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #000;
opacity: 0;
}
&:active:after {
opacity: .2;
}
}
.zh {
background: #26a2ff;
}
}
// Copyright @ 2018-2021 xiejiahe. All rights reserved. MIT license.
import { Component, Input } from '@angular/core'
import config from '../../../nav.config'
import { INavFourProp } from '../../types'
@Component({
selector: 'app-multiple-site',
templateUrl: './index.component.html',
styleUrls: ['./index.component.scss']
})
export class MultipleSiteComponent {
@Input() dataSource: INavFourProp
language: string[] = config.indexLanguage
}
<div class="no-result dark-text">
<nz-empty nzNotFoundContent="对不起,没有找到您想要的结果~"></nz-empty>
<div class="back">
<button nz-button nzType="primary" (click)="goBack()">返回上一层</button>
</div>
</div>
.no-result {
padding: 80px 0;
text-align: center;
.back {
margin-top: 30px;
span {
cursor: pointer;
}
}
}
// Copyright @ 2018-2021 xiejiahe. All rights reserved. MIT license.
import { Component } from '@angular/core';
@Component({
selector: 'app-no-data',
templateUrl: './no-data.component.html',
styleUrls: ['./no-data.component.scss']
})
export class NoDataComponent {
goBack = () => {
history.go(-1);
}
}
<div class="search-engine" *ngIf="searchEngineList && searchEngineList.length > 0">
<div class="input-wrapper dark-bg dark-border-color">
<div
class="left-icon"
(click)="toggleEngine($event)"
[ngStyle]="{ 'background-image': 'url(' + currentEngine.icon + ')' }"
>
</div>
<input
id="search-engine-input"
class="dark-input"
type="text"
autofocus
[placeholder]="currentEngine.placeholder || ''"
[(ngModel)]="keyword"
(keyup)="onKey($event)"
/>
<i class="search-icon iconfont iconsousuo dark-text" (click)="triggerSearch()"></i>
</div>
<div class="engine-main dark-bg dark-border-color" *ngIf="showEngine">
<div
class="item dark-bg-gary dark-item-hover"
*ngFor="let item of searchEngineList; let i=index"
(click)="clickEngineItem(i)"
>
<img class="icon" [src]="item.icon" alt="" />
<span class="name dark-text">{{ item.name }}</span>
</div>
</div>
</div>
$width: 500px;
.search-engine {
position: relative;
display: flex;
justify-content: center;
padding: 10px 0;
.input-wrapper {
position: relative;
width: $width;
height: 40px;
background: #fff;
border-radius: 5px;
border: 1px solid #ccc;
overflow: hidden;
input {
width: 100%;
height: 100%;
padding-left: 50px;
padding-right: 50px;
border-color: transparent;
font-size: 16px;
transition: all .3s;
background-color: transparent;
&::placeholder {
font-size: 14px;
}
&:focus {
border-color: #388bfd !important;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
}
.left-icon {
position: absolute;
top: 50%;
left: 15px;
width: 20px;
height: 20px;
background-repeat: no-repeat;
background-size: 20px 20px;
transform: translateY(-50%);
cursor: pointer;
}
.search-icon {
position: absolute;
top: 50%;
right: 15px;
font-size: 18px;
transform: translateY(-50%);
cursor: pointer;
}
}
.engine-main {
z-index: 9999990;
position: absolute;
top: 65px;
left: 50%;
width: $width;
padding: 15px 15px 0 15px;
background: #fff;
transform: translate(-50%, 0);
box-shadow: 0 1px 5px #ccc;
border-radius: 5px;
display: flex;
flex-wrap: wrap;
&:after {
content: '';
position: absolute;
top: -11px;
left: 17px;
width: 0;
height: 0;
border-style: solid;
border-width: 0 10px 20px 10px;
border-color: transparent transparent #ffffff transparent;
}
.item {
width: calc(33.33% - 10px);
padding: 10px;
display: flex;
background: #f6f6f6;
cursor: pointer;
margin-bottom: 15px;
border-radius: 3px;
transition: .1s linear;
box-sizing: border-box;
border: 1px solid transparent;
&:not(:nth-child(3n)) {
margin-right: 15px;
}
.icon {
width: 20px;
height: 20px;
}
.name {
margin-left: 10px;
font-size: 16px;
align-self: center;
}
}
}
}
// Copyright @ 2018-2021 xiejiahe. All rights reserved. MIT license.
import { Component, Output, EventEmitter } from '@angular/core'
import config from '../../../nav.config'
import { getDefaultSearchEngine, setDefaultSearchEngine, queryString } from '../../utils'
@Component({
selector: 'app-search-engine',
templateUrl: './search-engine.component.html',
styleUrls: ['./search-engine.component.scss']
})
export class SearchEngineComponent {
searchEngineList = config.searchEngineList
currentEngine = getDefaultSearchEngine()
showEngine = false
keyword = queryString().q
@Output() onSearch = new EventEmitter<string>()
inputFocus() {
const inputEl = document.getElementById('search-engine-input')
inputEl?.focus?.()
}
ngAfterViewInit() {
this.inputFocus()
document.addEventListener('click', () => {
this.toggleEngine(null, false)
})
}
toggleEngine(e?: Event, isShow?: boolean) {
if (this.searchEngineList.length <= 1) return
if (e) {
e.stopPropagation()
}
this.showEngine = typeof isShow === 'undefined'
? !this.showEngine
: isShow
}
clickEngineItem(index) {
this.currentEngine = config.searchEngineList[index]
this.toggleEngine()
this.inputFocus()
setDefaultSearchEngine(this.currentEngine)
}
triggerSearch() {
if (this.currentEngine.url) {
window.open(this.currentEngine.url + this.keyword)
}
this.onSearch.emit(this.keyword)
}
onKey(event: KeyboardEvent) {
if (event.code === 'Enter') {
this.triggerSearch()
}
}
}
// Copyright @ 2018-2021 xiejiahe. All rights reserved. MIT license.
export const VERIFY_PATH = 'README.md'
export const DB_PATH = 'README.md'
export const VERSION = '5.0.0-beta.0'
// Copyright @ 2018-2021 xiejiahe. All rights reserved. MIT license.
.dark-container {
.dark {
&-primary {
color: #58a6ff !important;
font-weight: 600;
}
&-input {
color: #f0f6fc !important;
border-color: #21262d !important;
&::placeholder {
color: #c9d1d9 !important;
}
&:focus {
color: #c9d1d9 !important;
}
}
&-title {
color: #c9d1d9 !important;
font-weight: 600 !important;
}
&-bg {
background-color: #0d1117 !important;
}
&-bg-gary {
background-color: #21262d !important;
}
&-text {
color: #6e7681 !important;
}
&-text-active {
color: #c9d1d9 !important;
font-weight: 600;
}
&-border-color {
border-color: #30363d !important;
&:after,&:before {
border-color: transparent !important;
}
}
&-item-active {
background-color: #21262d !important;
border-color: #21262d !important;
color: #c9d1d9 !important;
font-weight: 600;
}
&-scrollbar {
&::-webkit-scrollbar {
background-color: #0d1117 !important;
}
}
&-item-hover:hover {
background-color: #30363d !important;
border: 1px solid #8b949e !important;
}
// fix-bar
&-action-hover:hover {
background-color: #c9d1d9 !important;
& > * {
color: #fff !important;
}
}
}
}
export const environment = {
production: true
};
// This file can be replaced during build by using the `fileReplacements` array.
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.
export const environment = {
production: false
};
/*
* For easier debugging in development mode, you can import the following file
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
*
* This import should be commented out in production mode because it will have a negative impact
* on performance if an error is thrown.
*/
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.
<!DOCTYPE html>
<!--
.--,-``-.
/ / '.
/ ../ ;
,---, \ ``\ .`- ' ,---,
,-+-. / | .---.\___\/ \ : ,-+-. / |
,--.'|' | ,--.--. /. ./| \ : | ,---. ,--.'|' |
| | ,"' | / \ .-' . ' | / / / / \ | | ,"' |
| | / | |.--. .-. /___/ \: | \ \ \ / / ' | | / | |
| | | | | \__\/: . . \ ' . ___ / : | . ' / | | | | |
| | | |/ ," .--.; |\ \ '/ /\ / : ' ; :__ | | | |/
| | |--' / / ,. | \ \ / ,,/ ',- .___' | '.'|| | |--'
| |/ ; : .' \ \ \ \ ''\ ;/ .\ : :| |/
'---' | , .-./ '---" \ \ .' \ ; \ \ / '---'
`--`---' `--`-,,-' `--" `----'
2018/4/26 www.nav3.cn
,+++++++++++++++++++++++++++++++++++++++++++++++++.
( We want you! https://github.com/xjh22222228/nav )
`-,+++++++++++++++++++++++++++++++++++++++++++++++'
-->
<html>
<head>
<meta charset="utf-8">
<title>发现导航 - 精选实用导航网站</title>
<base href="/">
<meta name="author" content="https://github.com/xjh22222228">
<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="renderer" content="webkit">
<meta name="description" content="发现导航 - 精选实用导航网站">
<meta name="keywords" content="导航,前端资源,社区站点,设计师,实用工具,学习资源,运营,网络安全,node.js">
<link rel="icon" href="assets/logo.png">
<link rel ="apple-touch-icon" href="assets/logo.png">
<link rel="stylesheet" href="//at.alicdn.com/t/font_2267418_lvoce6r34sd.css">
</head>
<body>
<app-xiejiahe>
<div style="position: fixed;top: 0;left: 0;right: 0;bottom: 0;background: #fff;text-align: center;display: flex;justify-content: center;align-items: center;font-size: 14px;">L O A D I N G ...</div>
</app-xiejiahe>
<script src="./assets/js/jquery.min.js"></script>
<script src="./assets/js/ripple.min.js"></script>
</body>
</html>
/*! https://github.com/xjh22222228 */
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
*
* Learn more in https://angular.io/guide/browser-support
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js'; // Run `npm install --save classlist.js`.
/**
* Web Animations `@angular/platform-browser/animations`
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
*/
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
* because those flags need to be set before `zone.js` being loaded, and webpack
* will put import in the top of bundle, so user need to create a separate file
* in this directory (for example: zone-flags.ts), and put the following flags
* into that file, and then add the following code before importing zone.js.
* import './zone-flags.ts';
*
* The flags allowed in zone-flags.ts are listed here.
*
* The following flags will work for all browsers.
*
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*
* (window as any).__Zone_enable_cross_context_check = true;
*
*/
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/
// Copyright @ 2018-2021 xiejiahe. All rights reserved. MIT license.
import config from '../../nav.config'
import http from '../utils/http'
import { encode } from 'js-base64'
import { getToken } from '../utils/user'
const { gitRepoUrl } = config
let authorName = ''
let branchName = ''
const token = getToken()
try {
const { pathname } = new URL(gitRepoUrl)
const p = pathname.split('/')
authorName = p[1]
branchName = p[2]
} catch {}
// 获取文件信息
export function getFileContent(path: string, authToken?: string) {
return http.get(`/repos/${authorName}/${branchName}/contents/${path}`, {
headers: {
Authorization: `token ${authToken ? authToken : token}`
}
})
}
// 更新文件内容
type Iupdate = {
message: string
content: string
path: string
}
export async function updateFileContent(
{ message, content, path }: Iupdate,
authToken?: string
) {
const fileInfo = await getFileContent(path, authToken)
return http.put(`/repos/${authorName}/${branchName}/contents/${path}`, {
message: `rebot: ${message}`,
content: encode(content),
sha: fileInfo.data.sha
}, {
headers: {
Authorization: `token ${authToken ? authToken : token}`
}
})
}
// Copyright @ 2018-2021 xiejiahe. All rights reserved. MIT license.
import { websiteList as w } from '../utils'
export const websiteList = w
// Copyright @ 2018-2021 xiejiahe. All rights reserved. MIT license.
@import "./dark.scss";
* {
margin: 0;
padding: 0;
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
}
html {
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
body {
min-height: 100vh;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-overflow-scrolling: touch;
font: 14px/1.5 -apple-system,BlinkSystemFont,"Segoe UI",Roboto,Ubuntu,"Helvetica Neue",Helevetica,Arial,"PingFang SC","Hiragino Sans GB","Microsoft YaHei UI","Microsoft YaHei","Source Han Sans CN",sans-serif;
font-size: 14px;
color: #333;
}
footer,
header,
nav,
section {
display: block;
}
ol,
ul,
li {
list-style: none;
}
a,
a:link {
text-decoration: none;
}
h2 {
font-size: 16px;
}
img,
iframe {
border: none;
}
button,
input,
textarea {
font-size: 100%;
}
input[type="text"],
button {
-webkit-appearance: none;
outline: none;
border: 1px solid #e1e1e1;
}
i,
em {
font-style: normal;
}
.user-select-none {
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
}
.white-space-nowrap {
white-space: nowrap;
}
.text-align-center {
text-align: center;
}
.text-align-left {
text-align: left;
}
.text-align-right {
text-align: right;
}
.border-none {
border: none;
}
.pointer-events-none {
pointer-events: none;
}
.overflow-hidden {
overflow: hidden;
}
.touch-active:active {
background: rgba(0, 0, 0, .2) !important;
}
.cursor-pointer {
cursor: pointer;
}
/* 搜索高亮关键字 */
b {
color: #f50 !important;
}
// 折叠箭头
.down-arrow {
align-self: center;
margin-left: 15px;
cursor: pointer;
transition: .1s linear;
color: #666;
&.active {
transform: rotate(-90deg);
}
}
.rotate {
animation: quickRotate 1s linear infinite;
transform-origin: center;
}
@keyframes quickRotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.has-ripple{position:relative;overflow:hidden;-webkit-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.ripple{display:block;position:absolute;pointer-events:none;border-radius:50%;-webkit-transform:scale(0);-o-transform:scale(0);transform:scale(0);background:#fff;opacity:1}.ripple-animate{-webkit-animation:ripple;-o-animation:ripple;animation:ripple}@-webkit-keyframes ripple{100%{opacity:0;-webkit-transform:scale(2);transform:scale(2)}}@-o-keyframes ripple{100%{opacity:0;-o-transform:scale(2);transform:scale(2)}}@keyframes ripple{100%{opacity:0;transform:scale(2)}}
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
declare const require: any;
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);
// Custom Theming for NG-ZORRO
// For more information: https://ng.ant.design/docs/customize-theme/en
@import "../node_modules/ng-zorro-antd/ng-zorro-antd.less";
// Copyright @ 2018-2021 xiejiahe. All rights reserved. MIT license.
export type ThemeType =
| 'light'
| 'sim'
export interface INavFourProp {
icon?: string | null
name: string
desc: string
url?: string
language?: string|null|undefined[]
}
export interface INavThreeProp {
title?: string
icon?: string | null
showSideIcon?: boolean
collapsed?: boolean
nav: INavFourProp[]
}
export interface INavTwoProp {
title?: string
icon?: string | null
collapsed?: boolean
nav: INavThreeProp[]
}
export interface INavProps {
title: string
id?: number
nav: INavTwoProp[]
}
export interface ISearchEngineProps {
name: string
url?: string
icon: string
placeholder?: string
}
export interface IConfig {
title: string
theme: ThemeType
posterImageUrl: string
searchEngineList: ISearchEngineProps[]
gitRepoUrl: string,
errorIconUrl: string
footerCopyright: string|null
tongjiUrl: string
indexLanguage: string[]
appLanguage: string[]
backgroundLinear: string[]
}
// Copyright @ 2018-2021 xiejiahe. All rights reserved. MIT license.
import axios, { AxiosError } from 'axios'
import { getToken } from '../utils/user'
function handleError(error: AxiosError) {
}
const httpInstance = axios.create({
timeout: 60000,
baseURL: 'https://api.github.com'
})
const token = getToken()
Object.setPrototypeOf(httpInstance, axios)
httpInstance.interceptors.request.use(function (config) {
config.headers = {
Authorization: `token ${token}`,
...config.headers
}
return config
}, function (error) {
handleError(error)
return Promise.reject(error)
})
httpInstance.interceptors.response.use(function (res) {
return res
}, function (error) {
handleError(error)
return Promise.reject(error)
})
export default httpInstance
// Copyright @ 2018-2021 xiejiahe. All rights reserved. MIT license.
import qs from 'qs'
import config from '../../nav.config'
import { INavProps, ISearchEngineProps } from '../types'
import * as db from '../../data/db.json'
export const websiteList = getWebsiteList()
const { backgroundLinear, errorIconUrl, searchEngineList } = config
export function debounce(func, wait, immediate) {
let timeout
return function () {
let context = this
let args = arguments
if (timeout) clearTimeout(timeout)
if (immediate) {
let callNow = !timeout
timeout = setTimeout(() => {
timeout = null
}, wait)
if (callNow) func.apply(context, args)
} else {
timeout = setTimeout(() => {
func.apply(context, args)
}, wait)
}
}
}
export function randomInt(max: number) {
return Math.floor(Math.random() * max)
}
export function fuzzySearch(navList: INavProps[], keyword: string) {
let searchResultList = [{ nav: [] }]
function f(arr?: any[]) {
arr = arr || navList
for (let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i].nav)) {
f(arr[i].nav)
}
if (arr[i].name) {
const name = arr[i].name.toLowerCase()
const desc = arr[i].desc.toLowerCase()
const search = keyword.toLowerCase()
if (~name.indexOf(search) || ~desc.indexOf(search)) {
try {
let result = Object.assign({}, arr[i])
const regex = new RegExp(`(${keyword})`, 'i')
result.name = result.name.replace(regex, `$1`.bold())
result.desc = result.desc.replace(regex, `$1`.bold())
const idx = searchResultList[0].nav.findIndex(item => item.name === result.name)
if (idx === -1) {
searchResultList[0].nav.push(result)
}
} catch (err) {}
}
}
}
}
f()
return searchResultList
}
let total = 0
export function totalWeb(): number {
if (total) {
return total
}
function r(nav) {
if (!Array.isArray(nav)) return
for (let i = 0; i < nav.length; i++) {
if (nav[i].url) {
total += 1
} else {
r(nav[i].nav)
}
}
}
r(websiteList)
return total
}
let randomTimer
export function randomBgImg() {
if (isDark()) return
clearInterval(randomTimer)
const el = document.createElement('div')
el.id = 'random-light-bg'
el.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;z-index:-3;transition: 1s linear;'
el.style.backgroundImage = backgroundLinear[randomInt(backgroundLinear.length)]
document.body.appendChild(el)
function setBg() {
const randomBg = backgroundLinear[randomInt(backgroundLinear.length)]
el.style.opacity = '.3'
setTimeout(() => {
el.style.backgroundImage = randomBg
el.style.opacity = '1'
}, 1000)
}
randomTimer = setInterval(setBg, 10000)
}
export function queryString() {
const { href } = window.location
const search = href.slice(href.indexOf('?') + 1)
const parseQs = qs.parse(search)
let id = parseInt(parseQs.id) || 0
let page = parseInt(parseQs.page) || 0
if (parseQs.id === undefined && parseQs.page === undefined) {
try {
const location = window.localStorage.getItem('location')
if (location) {
return JSON.parse(location)
}
} catch {}
}
if (page > websiteList.length - 1) {
page = websiteList.length - 1;
id = 0;
} else {
page = page;
if (id <= websiteList[page].nav.length - 1) {
id = id;
} else {
id = websiteList[page].nav.length - 1;
}
}
return {
...parseQs,
q: parseQs.q || '',
id,
page
}
}
export function getWebsiteList() {
let webSiteList = (db as any).default
const scriptElAll = document.querySelectorAll('script')
const scriptUrl = scriptElAll[scriptElAll.length - 1].src
const storageScriptUrl = window.localStorage.getItem('s_url')
// 检测到网站更新,清除缓存
if (storageScriptUrl !== scriptUrl) {
const whiteList = ['token']
const len = window.localStorage.length
for (let i = 0; i < len; i++) {
const key = window.localStorage.key(i)
if (whiteList.includes(key)) {
continue
}
window.localStorage.removeItem(key)
}
window.localStorage.setItem('s_url', scriptUrl)
return webSiteList
}
try {
const w = window.localStorage.getItem('website')
const json = JSON.parse(w)
if (Array.isArray(json)) {
webSiteList = json
}
} catch {}
return webSiteList
}
export function setWebsiteList(v?: INavProps[]) {
v = v || websiteList
window.localStorage.setItem('website', JSON.stringify(v))
}
export function toggleCollapseAll(wsList?: INavProps[]): boolean {
wsList = wsList || websiteList
const { page, id } = queryString()
const collapsed = !websiteList[page].nav[id].collapsed
websiteList[page].nav[id].collapsed = collapsed
websiteList[page].nav[id].nav.map(item => {
item.collapsed = collapsed
return item
})
setWebsiteList(websiteList)
return collapsed
}
export function setLocation() {
const { page, id } = queryString()
window.localStorage.setItem('location', JSON.stringify({
page,
id
}))
}
export function getDefaultSearchEngine(): ISearchEngineProps {
let DEFAULT = (searchEngineList[0] || {}) as ISearchEngineProps
try {
const engine = window.localStorage.getItem('engine');
if (engine) {
DEFAULT = JSON.parse(engine)
}
} catch {}
return DEFAULT
}
export function setDefaultSearchEngine(engine: ISearchEngineProps) {
window.localStorage.setItem('engine', JSON.stringify(engine))
}
export function imgErrorInRemove(e) {
const el = e.currentTarget;
el?.parentNode?.removeChild?.(el)
}
export function isDark(): boolean {
const storageVal = window.localStorage.getItem('IS_DARK')
const darkMode = window?.matchMedia?.('(prefers-color-scheme: dark)')?.matches
if (!storageVal && darkMode) {
return darkMode
}
return Boolean(Number(storageVal))
}
// Copyright @ 2018-2021 xiejiahe. All rights reserved. MIT license.
import { annotate } from 'rough-notation'
import { queryString } from './index'
let ANNOTATE_EQUEUE = []
export function initRipple() {
(<any>window).$.ripple('.ripple-btn', {
multi: true,
debug: false,
opacity: .2,
})
}
export function setAnnotate(querySelector = '.top-nav .ripple-btn') {
const elList = document.querySelectorAll(querySelector) || []
if (elList.length === 0) return
ANNOTATE_EQUEUE.forEach(item => item.hide())
ANNOTATE_EQUEUE = []
const { page } = queryString()
const annotation = annotate(elList[page], {
type: 'underline',
color: '#f9826c',
padding: 3,
strokeWidth: 3
})
ANNOTATE_EQUEUE.push(annotation)
annotation.show()
}
// Copyright @ 2018-2021 xiejiahe. All rights reserved. MIT license.
export function getToken() {
return window.localStorage.getItem('token')
}
export function setToken(token: string) {
return window.localStorage.setItem('token', token)
}
<section class="app-page user-select-none">
<header class="header">
<div class="header-top">
<a href="https://github.com/xjh22222228/nav" target="_blank" class="logo">
<img src="assets/logo.png" alt="logo">
</a>
<div class="open" [class.active]="open" (click)="handleToggleOpen()">
<i></i>
<i></i>
<i></i>
</div>
</div>
<nav class="nav-open">
<div
*ngFor="let item of websiteList; let i = index;"
(click)="handleCilckNav(i)"
[class.active]="page === i"
class="nav-title"
>
{{ item.title }}
</div>
</nav>
</header>
<div class="wrapper">
<nav class="children-nav" *ngIf="websiteList[page].nav.length > 1">
<a
class="tag"
*ngFor="let item of websiteList[page].nav; let i = index"
[class.active]="id === i"
(click)="handleSidebarNav(i)"
>
{{ item.title }}
</a>
</nav>
<ul>
<li *ngFor="let item of websiteList[page].nav[id].nav">
<h2 class="block-title" *ngIf="item.title">
{{ item.title }} x {{ item.nav.length }}
</h2>
<div class="row">
<div class="item-list" *ngFor="let el of item.nav; index as i;">
<a (click)="handleToWebsite(el, i, $event)" href="javascript:;">
<div class="top">
<app-logo
[src]="el.icon || item.icon"
[name]="el.name"
>
</app-logo>
<em class="name" [title]="el.name">{{ el.name }}</em>
</div>
<div class="desc" [title]="el.desc">{{ el.desc }}</div>
</a>
<div class="bottom-slide">
<div class="wrapper-button" *ngIf="el.language && el.language.length">
<a
[href]="el.url"
*ngIf="el.language.length <= 3 && (!el.language[0] || !el.language[1])"
target="_blank"
>
默认
</a>
<a [href]="el.language[0]" *ngIf="el.language[0]" target="_blank">{{ language[0] }}</a>
<a [href]="el.language[1]" *ngIf="el.language[1]" target="_blank">{{ language[1] }}</a>
<a [href]="el.language[2]" *ngIf="el.language[2]" target="_blank">{{ language[2] }}</a>
</div>
</div>
</div>
</div>
</li>
</ul>
</div>
</section>
$bg-color: #fbfbfb;
.app-page {
padding: 45px 0;
background: #f6f6f6;
.header {
z-index: 9999;;
position: fixed;
top: 0;
left: 0;
right: 0;
text-align: center;
background: $bg-color;
box-shadow: 0 0 3px #ccc;
.header-top {
position: relative;
height: 45px;
border-bottom: 1px solid #eee;
background: $bg-color;
.logo {
display: inline-block;
img {
width: 35px;
height: 35px;
margin-top: 4px;
pointer-events: none;
}
}
}
.open {
position: absolute;
top: 9px;
left: 15px;
cursor: pointer;
i {
display: block;
margin-top: 6px;
height: 2px;
width: 25px;
background: #999;
transform-origin: right center;
transition: .1s linear;
}
&.active {
i:nth-child(1) {
transform: rotate(-45deg);
}
i:nth-child(2) {
opacity: 0;
}
i:nth-child(3) {
transform: translateY(2px) rotate(45deg);
}
}
}
.nav-open {
display: none;
box-shadow: 1px 1px 5px #ccc;
overflow: hidden;
background: $bg-color;
transition: .1s linear;
}
.nav-title {
display: inline-block;
width: 50%;
font-size: 16px;
padding: 8px 0;
&:active,
&.active {
background: rgba(0, 0, 0, .05);
}
}
}
.wrapper {
margin: 15px 10px 30px 10px;
.children-nav {
background: $bg-color;
margin: 15px 0;
padding: 10px 10px 5px;
white-space: nowrap;
overflow: auto;
box-shadow: 0 0 3px #ccc;
a {
display: inline-block;
font-size: 14px;
padding: 3px 5px;
margin: 0 3px 5px 0;
transition: background .1s linear;
}
.active {
background: #20a0ff;
color: #fff;
}
}
li {
margin-bottom: 10px;
background: $bg-color;
box-shadow: 0 0 5px #ccc;
}
.name {
display: inline-block;
font-size: 16px;
margin-left: 5px;
width: calc(100% - 50px);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: middle;
}
.desc {
padding: 10px;
border-top: 1px solid #eee;
margin-top: 8px;
word-break: break-all;
word-wrap: break-word;
font-size: 12px;
}
.block-title {
padding: 10px 0 10px 15px;
border-bottom: 1px solid #eee;
color: #3F51B5;
}
.top {
padding: 10px;
}
}
.row {
display: flex;
-webkit-flex-wrap: wrap;
flex-wrap: wrap;
a {
color: #333;
}
.item-list {
z-index: 111;
position: relative;
width: calc(50% - 18px);
margin: 12px;
border: 1px solid #eee;
overflow: hidden;
&:nth-child(odd) {
margin-right: 6px;
}
&:nth-child(even) {
margin-left: 6px;
}
.bottom-slide {
&.active .wrapper-button {
transform: translate3d(0, 0, 0);
}
}
.wrapper-button {
display: flex;
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: #fff;
text-align: center;
transition: .1s;
transform: translate3d(0, 110%, 0);
box-shadow: 0 -1px 1px #eee;
a {
flex: 1;
padding: 3px 0;
background: #2db7f5;
color: #fff;
&:active {
background: #26a2ff;
}
&:not(:nth-last-child(1)) {
margin-right: 3px;
}
}
}
}
}
}
// Copyright @ 2018-2021 xiejiahe. All rights reserved. MIT license.
import config from '../../../../nav.config'
import { Component } from '@angular/core'
import { Router, ActivatedRoute } from '@angular/router'
import { queryString } from '../../../utils'
import { INavProps } from '../../../types'
import { websiteList } from '../../../store'
@Component({
selector: 'app-home',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export default class WebpComponent {
constructor (private router: Router, private activatedRoute: ActivatedRoute) {}
websiteList: INavProps[] = websiteList
id: number = 0
page: number = 0
open: boolean = false
language: string[] = config.appLanguage
ngOnInit () {
this.activatedRoute.queryParams.subscribe(() => {
const { page, id } = queryString()
this.page = page
this.id = id
})
}
handleSidebarNav (index) {
const { page } = queryString()
this.router.navigate(['/app'], {
queryParams: {
page,
id: index,
}
})
}
handleCilckNav (index) {
this.router.navigate(['/app'], {
queryParams: {
page: index,
}
})
this.open = !this.open;
(<any>window).$('.nav-open').slideUp(200)
}
handleToggleOpen() {
this.open = !this.open;
(<any>window).$('.nav-open').slideToggle(200)
}
handleToWebsite(item, index, event) {
if (!Array.isArray(item.language)) {
window.open(item.url)
return
}
const el = (<any>window).$('.bottom-slide')
const $this = (<any>window).$(event.currentTarget)
const len = el.length
for (let i = 0; i < len; i++) {
if (i === index) {
continue
}
el.eq(i).removeClass('active')
}
$this.siblings('.bottom-slide').toggleClass('active')
}
}
<div class="index-wrapper">
<main class="homepage dark-border-color">
<app-fixbar
(onCollapse)="onCollapseAll()"
[collapsed]="websiteList[page].nav[id].collapsed"
selector=".main"
[randomBg]="true"
>
</app-fixbar>
<nav class="top-nav user-select-none dark-border-color dark-bg">
<a
*ngFor="let item of websiteList; let i = index;"
(click)="handleCilckTopNav(i)"
[class.active]="page === i"
[class.dark-text-active]="page === i"
class="ripple-btn dark-text"
>
{{ item.title }}
</a>
</nav>
<section class="index-section user-select-none dark-bg">
<aside class="sidebar dark-bg dark-border-color" id="sidebar">
<ul>
<li
class="tag dark-text"
[class.active]="id === i"
[class.dark-item-active]="id === i"
(click)="handleSidebarNav(i)"
*ngFor="let item of websiteList[page].nav; let i = index"
>
<div class="ripple-btn" *ngIf="item.title">{{ item.title }}</div>
</li>
</ul>
</aside>
<div class="main dark-scrollbar">
<app-search-engine (onSearch)="onSearch($event)"></app-search-engine>
<ul *ngIf="currentList.length && currentList[0].nav.length; else noData">
<li *ngFor="let item of currentList; let i=index">
<div class="title-wrapper dark-border-color" *ngIf="item.title">
<h2 class="block-title">
<span
(click)="onCollapse(item, i)"
class="cursor-pointer dark-primary"
>
{{ item.title }} x {{ item.nav.length }}
</span>
<i
class="iconfont iconjiantouarrow483 down-arrow"
[class.active]="item.collapsed"
(click)="onCollapse(item, i)"
>
</i>
</h2>
<img
*ngIf="item.icon && item.showSideIcon !== false"
[src]="item.icon"
class="side-logo"
(error)="onSideLogoError($event)"
/>
</div>
<div class="row" *ngIf="!item.collapsed">
<div class="click-btn dark-border-color" *ngFor="let el of item.nav">
<a [href]="el.url" target="_blank" rel="noopener noreferer">
<div class="top">
<app-logo
[src]="el.icon || item.icon || websiteList[page].nav[id].icon"
[name]="el.name"
>
</app-logo>
<em class="name dark-title" [innerHtml]="el.name" [title]="el.name"></em>
</div>
<div class="desc dark-border-color dark-text" [innerHtml]="el.desc"></div>
</a>
<app-multiple-site [dataSource]="el"></app-multiple-site>
</div>
</div>
</li>
</ul>
<ng-template #noData>
<app-no-data></app-no-data>
</ng-template>
</div>
</section>
</main>
<app-footer></app-footer>
</div>
<div class="layer-bg dark-bg"></div>
.layer-bg {
z-index: -1;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.index-wrapper {
display: flex;
flex-direction: column;
height: 100vh;
padding-top: 15px;
}
.homepage {
width: 1000px;
margin: 0 auto;
flex: 1;
position: relative;
background: rgb(251, 251, 251);
border-radius: 5px;
overflow: hidden;
transition: .1s linear;
border: 1px solid transparent;
.top-nav {
padding: 10px;
overflow: none;
white-space: nowrap;
border-bottom: 1px solid #eee;
text-align: center;
a {
display: inline-block;
padding: 10px 15px;
color: #000;
cursor: pointer;
border-radius: 5px;
overflow: hidden;
&:hover {
text-decoration: none;
}
}
}
}
.index-section {
position: relative;
height: calc(100% - 52px);
overflow: hidden;
// 侧边栏分类
$sidebarWidth: 80px;
.sidebar {
position: relative;
width: $sidebarWidth;
height: 100%;
text-align: center;
border-right: 1px solid #eee;
.tag {
position: relative;
cursor: pointer;
&.active {
color: #1890ff;
div:after {
top: 0;
border-top: 1px dashed #ccc;
}
div:before {
bottom: 0;
border-bottom: 1px dashed #ccc;
}
}
div {
display: block;
padding: 11px 0;
&:after,
&:before {
content: "";
position: absolute;
left: 0;
width: 100%;
}
}
}
}
.main {
position: absolute;
top: 0;
left: $sidebarWidth;
right: 0;
bottom: 30px;
padding-bottom: 50px;
overflow-y: auto;
.side-logo {
width: 30px;
height: 30px;
border-radius: 50%;
}
.name {
display: inline-block;
font-size: 15px;
margin-left: 5px;
width: calc(100% - 50px);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: middle;
}
.desc {
padding: 5px 10px 5px 10px;
border-top: 1px solid #eee;
margin-top: 8px;
word-break: break-all;
word-wrap: break-word;
font-size: 12px;
}
.title-wrapper {
border-bottom: 1px solid #ccc;
padding: 10px 30px 10px 15px;
display: flex;
justify-content: space-between;
}
.block-title {
display: inline-block;
position: relative;
color: #3F51B5;
align-self: center;
display: flex;
}
.top {
padding: 10px;
}
}
.row {
display: flex;
flex-wrap: wrap;
transition: .1s ease-out;
overflow: hidden;
a {
color: #333;
&:hover {
text-decoration: none;
}
}
.click-btn {
z-index: 111;
position: relative;
width: 200px;
margin: 12px;
border: 1px solid #eee;
overflow: hidden;
&:hover {
::ng-deep .mark {
bottom: 0 !important;
}
}
}
}
}
// Copyright @ 2018-2021 xiejiahe. All rights reserved. MIT license.
import { Component } from '@angular/core'
import { Router, ActivatedRoute } from '@angular/router'
import config from '../../../../nav.config'
import { INavProps, INavThreeProp } from '../../../types'
import {
debounce,
fuzzySearch,
randomBgImg,
queryString,
setWebsiteList,
toggleCollapseAll,
imgErrorInRemove
} from '../../../utils'
import { initRipple, setAnnotate } from '../../../utils/ripple'
import { websiteList } from '../../../store'
@Component({
selector: 'app-home',
templateUrl: './index.component.html',
styleUrls: ['./index.component.scss']
})
export default class HomeComponent {
constructor (private router: Router, private activatedRoute: ActivatedRoute) {}
websiteList: INavProps[] = websiteList
currentList: INavThreeProp[] = []
id: number = 0
page: number = 0
searchKeyword: string = ''
showInput = false
gitRepoUrl: string = config.gitRepoUrl
ngOnInit() {
randomBgImg()
const initList = () => {
this.currentList = this.websiteList[this.page].nav[this.id].nav
}
this.activatedRoute.queryParams.subscribe(() => {
const tempPage = this.page
const { id, page, q } = queryString()
this.searchKeyword = q
this.page = page
this.id = id
if (q) {
this.currentList = fuzzySearch(this.websiteList, q)
} else {
initList()
}
if (tempPage !== page) {
setAnnotate()
}
setWebsiteList(this.websiteList)
})
this.handleSearch = debounce(() => {
if (!this.searchKeyword) {
initList()
return
}
const params = queryString()
this.router.navigate(['/light'], {
queryParams: {
...params,
q: this.searchKeyword
}
})
}, 1000, true)
}
onSearch(v) {
this.searchKeyword = v
this.handleSearch()
}
handleOnClickSearch() {
this.showInput = true
setTimeout(() => {
const searchEl = document.querySelector('.search') as HTMLInputElement
if (searchEl) {
searchEl.focus()
}
}, 0)
}
handleCilckTopNav(index) {
const id = this.websiteList[index].id || 0
this.router.navigate(['/light'], {
queryParams: {
page: index,
id,
_: Date.now()
}
})
}
handleSidebarNav(index) {
const { page } = queryString()
this.websiteList[page].id = index
this.router.navigate(['/light'], {
queryParams: {
page,
id: index,
_: Date.now()
}
})
}
ngAfterViewInit () {
setAnnotate();
initRipple()
}
onCollapse = (item, index) => {
item.collapsed = !item.collapsed
this.websiteList[this.page].nav[this.id].nav[index] = item
setWebsiteList(this.websiteList)
}
onCollapseAll = () => {
toggleCollapseAll(this.websiteList)
}
handleSearch = null
onSideLogoError = imgErrorInRemove
}
<div class="sim">
<div class="wallpaper" [ngStyle]="{ 'background-image': 'url(' + posterImageUrl + ')' }">
<h1 class="title dark-title">{{ title }}</h1>
<h2 class="description dark-text-active">这里收录多达 <b>{{ totalWeb }}</b> 个优质网站, 助您工作、学习和生活</h2>
<app-search-engine (onSearch)="onSearch($event)"></app-search-engine>
</div>
<nav class="top-nav user-select-none dark-border-color">
<a
*ngFor="let item of websiteList; let i = index;"
[class.active]="page === i"
[class.dark-text-active]="page === i"
class="ripple-btn dark-text"
(click)="handleCilckTopNav(i)"
>
{{ item.title }}
</a>
</nav>
<div class="wrapper">
<nav class="sidebar dark-bg" id="sidebar">
<div *ngIf="websiteList[page].nav.length">
<div
*ngFor="let item of websiteList[page].nav; let i = index"
(click)="handleSidebarNav(i)"
[class.active]="id === i"
[class.dark-item-active]="id === i"
class="ripple-btn dark-text"
>
{{ item.title || websiteList[page].title }}
</div>
</div>
</nav>
<aside class="site-box dark-bg">
<div *ngFor="let item of currentList; let i=index">
<div class="nav-wrapper">
<div
class="title dark-primary dark-border-color"
*ngIf="item.title"
(click)="onCollapse(item, i)"
>
{{ item.title }} x {{ item.nav.length }}
<i class="iconfont iconjiantouarrow483 down-arrow" [class.active]="item.collapsed"></i>
</div>
<ul class="ul" *ngIf="!item.collapsed">
<li *ngFor="let ele of item.nav" class="dark-border-color">
<a
class="url-box"
[href]="ele.url"
target="_blank"
rel="noopener noreferer"
>
<div class="box-wrapper">
<div class="left">
<app-logo
[src]="ele.icon || item.icon || websiteList[page].nav[id].icon"
[name]="ele.name"
>
</app-logo>
</div>
<div class="right dark-title" [innerHtml]="ele.name" [title]="ele.name"></div>
</div>
<div class="desc dark-text" [innerHtml]="ele.desc"></div>
</a>
<app-multiple-site [dataSource]="ele"></app-multiple-site>
</li>
</ul>
</div>
</div>
<app-no-data *ngIf="currentList.length === 0 || currentList[0].nav.length === 0"></app-no-data>
</aside>
</div>
</div>
<app-footer className="sim-footer"></app-footer>
<app-fixbar
(onCollapse)="onCollapseAll()"
[collapsed]="websiteList[page].nav[id].collapsed"
>
</app-fixbar>
<div class="sim-bg dark-bg"></div>
$width: 1200px;
.sim-bg {
z-index: -1;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #f0f2f5;
}
.top-nav {
width: $width;
margin: 10px auto;
padding: 10px;
overflow: none;
white-space: nowrap;
border-bottom: 1px solid #eee;
text-align: center;
a {
display: inline-block;
padding: 10px 15px;
color: #000;
cursor: pointer;
border-radius: 5px;
overflow: hidden;
&:hover {
text-decoration: none;
}
}
}
.sim {
display: flex;
flex-direction: column;
min-height: 100vh;
.wallpaper {
height: 350px;
background-repeat: no-repeat;
background-position: top center;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
.title {
color: #fff;
margin-bottom: 10px;
font-size: 36px;
}
.description {
text-align: center;
color: #fff;
margin-bottom: 10px;
}
}
.wrapper {
flex: 1;
position: relative;
width: $width;
padding-bottom: 50px;
margin: 0 auto;
.sidebar {
width: 180px;
height: min-content;
padding: 10px 0;
padding-bottom: 0;
text-align: center;
float: left;
background-color: #fff;
box-shadow: 0 0 1px #ccc;
&.fix {
position: fixed;
top: 10px;
}
.ripple-btn {
padding: 10px 0;
margin-bottom: 10px;
cursor: pointer;
border-radius: 5px;
transition: .1s linear;
user-select: none;
font-weight: 500;
&.active {
color: #409eff;
background-color: rgb(231, 241, 253);
}
}
}
.site-box {
width: 990px;
position: relative;
background-color: #fff;
margin-left: 30px;
padding: 15px;
float: right;
.title {
font-size: 18px;
border-bottom: 1px solid #eee;
padding: 10px 0;
color: #3f51b5;
font-weight: 500;
display: flex;
cursor: pointer;
}
ul {
display: flex;
flex-wrap: wrap;
}
li {
position: relative;
width: 225px;
padding: 15px;
margin: 20px 20px 0 0;
transition: .1s ease-out;
border: 1px solid #eee;
overflow: hidden;
border-radius: 3px;
cursor: pointer;
&:nth-child(4n) {
margin-right: 0;
}
&:hover {
box-shadow: 2px 2px 25px -5px rgba(0,0,0,.2);
::ng-deep .mark {
bottom: 0 !important;
}
}
}
.url-box {
height: 100%;
.box-wrapper {
display: flex;
}
.right {
flex: 1;
margin-left: 10px;
font-size: 15px;
font-weight: 600;
color: #273a52;
align-self: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.desc {
margin-top: 8px;
color: #4c5d73;
}
}
}
}
}
::ng-deep .sim-footer {
margin-bottom: 10px;
& > * {
color: #666;
}
}
// Copyright @ 2018-2021 xiejiahe. All rights reserved. MIT license.
import config from '../../../../nav.config'
import { Component } from '@angular/core'
import { Router, ActivatedRoute } from '@angular/router'
import { INavProps, INavThreeProp } from '../../../types'
import {
debounce,
fuzzySearch,
queryString,
setWebsiteList,
toggleCollapseAll,
totalWeb,
imgErrorInRemove
} from '../../../utils'
import { initRipple, setAnnotate } from '../../../utils/ripple'
import { websiteList } from '../../../store'
const { gitRepoUrl, title, posterImageUrl } = config
let sidebarEl: HTMLElement;
@Component({
selector: 'app-home',
templateUrl: './index.component.html',
styleUrls: ['./index.component.scss']
})
export default class HomeComponent {
constructor (private router: Router, private activatedRoute: ActivatedRoute) {}
websiteList: INavProps[] = websiteList
currentList: INavThreeProp[] = []
id: number = 0
page: number = 0
searchKeyword: string = ''
gitRepoUrl: string = gitRepoUrl
totalWeb: number = totalWeb()
title: string = title
posterImageUrl: string = posterImageUrl
ngOnInit() {
const initList = () => {
this.currentList = this.websiteList[this.page].nav[this.id].nav
}
this.activatedRoute.queryParams.subscribe(() => {
const tempPage = this.page
const { id, page, q } = queryString()
this.searchKeyword = q
this.page = page
this.id = id
if (q) {
this.currentList = fuzzySearch(this.websiteList, q)
console.log(this.currentList)
} else {
initList()
}
if (tempPage !== page) {
setAnnotate()
}
setWebsiteList(this.websiteList)
})
this.handleSearch = debounce(() => {
if (!this.searchKeyword) {
initList()
return
}
this.currentList = fuzzySearch(this.websiteList, this.searchKeyword)
const params = queryString()
this.router.navigate(['/sim'], {
queryParams: {
...params,
q: this.searchKeyword
}
})
}, 1000, true)
}
onScroll() {
const y = window.scrollY
if (!sidebarEl) {
sidebarEl = document.getElementById('sidebar')
}
if (y >= 438) {
sidebarEl.classList.add('fix')
} else {
sidebarEl.classList.remove('fix')
}
}
ngOnDestroy() {
window.removeEventListener('scroll', this.onScroll)
}
ngAfterViewInit () {
initRipple()
setAnnotate();
window.addEventListener('scroll', this.onScroll)
}
handleSidebarNav(index) {
const { page } = queryString()
this.websiteList[page].id = index
this.router.navigate(['/sim'], {
queryParams: {
page,
id: index,
_: Date.now()
}
})
}
handleCilckTopNav(idx) {
const id = this.websiteList[idx].id || 0
this.router.navigate(['/sim'], {
queryParams: {
page: idx,
id,
_: Date.now()
}
})
}
onCollapse = (item, index) => {
item.collapsed = !item.collapsed
this.websiteList[this.page].nav[this.id].nav[index] = item
setWebsiteList(this.websiteList)
}
onCollapseAll = () => {
toggleCollapseAll(this.websiteList)
}
onSearch(v) {
this.searchKeyword = v
this.handleSearch()
}
handleSearch = null
onSideLogoError = imgErrorInRemove
}
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"include": [
"src/**/*.ts"
],
"exclude": [
"src/test.ts",
"src/**/*.spec.ts"
]
}
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"importHelpers": true,
"target": "es2015",
"typeRoots": [
"node_modules/@types"
],
"lib": [
"es2018",
"dom"
]
}
}
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": [
"jasmine",
"node"
]
},
"files": [
"src/test.ts",
"src/polyfills.ts"
],
"include": [
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}
{
"extends": "tslint:recommended",
"rules": {
"array-type": false,
"arrow-parens": false,
"deprecation": {
"severity": "warning"
},
"component-class-suffix": true,
"contextual-lifecycle": true,
"directive-class-suffix": true,
"directive-selector": [
true,
"attribute",
"app",
"camelCase"
],
"component-selector": [
true,
"element",
"app",
"kebab-case"
],
"import-blacklist": [
true,
"rxjs/Rx"
],
"interface-name": false,
"max-classes-per-file": false,
"max-line-length": [
true,
140
],
"member-access": false,
"member-ordering": [
true,
{
"order": [
"static-field",
"instance-field",
"static-method",
"instance-method"
]
}
],
"no-consecutive-blank-lines": false,
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-empty": false,
"no-inferrable-types": [
true,
"ignore-params"
],
"no-non-null-assertion": true,
"no-redundant-jsdoc": true,
"no-switch-case-fall-through": true,
"no-use-before-declare": true,
"no-var-requires": false,
"object-literal-key-quotes": [
true,
"as-needed"
],
"object-literal-sort-keys": false,
"ordered-imports": false,
"quotemark": [
true,
"single"
],
"trailing-comma": false,
"no-conflicting-lifecycle": true,
"no-host-metadata-property": true,
"no-input-rename": true,
"no-inputs-metadata-property": true,
"no-output-native": true,
"no-output-on-prefix": true,
"no-output-rename": true,
"no-outputs-metadata-property": true,
"template-banana-in-box": true,
"template-no-negated-async": true,
"use-lifecycle-interface": true,
"use-pipe-transform-interface": true
},
"rulesDirectory": [
"codelyzer"
]
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册