├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ ├── ci.yml │ └── docsearch.yml.bak ├── .gitignore ├── .prettierrc ├── .stylelintrc.js ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── babel.config.js ├── blog ├── advice │ ├── Api接口风格.md │ └── 配置windows.md ├── authors.yml ├── develop │ ├── vite学习.md │ ├── webpack集合.md │ ├── 为什么使用pnpm.md │ ├── 去除typescript代码类型.md │ └── 基础封装axios.md ├── lifestyle │ ├── 世间再无击节客,左耳从此不听风.md │ ├── 深谈个人对新技术的看法.md │ └── 第一次离职.md ├── program │ ├── HTTP请求配置客户端SSL证书.md │ ├── Socket文本编辑实时共享.md │ ├── Websocket心跳机制.md │ ├── socket1对1聊天.md │ ├── vite+vue3搭建uniapp开发环境.md │ ├── 如何给博客增加评论效果.md │ └── 搭建Electron+Vue3开发环境.md ├── project │ ├── Vercel部署个人博客.md │ ├── xx记账.md │ ├── 使用Vite开发Chrome插件.md │ ├── 第一个博客搭建之Vuepress.md │ └── 第二个博客搭建之Docusaurus.md └── reference │ └── 记一次前端面试过程.md ├── data ├── friend.ts ├── project.ts └── resource.ts ├── docs ├── skill │ ├── algorithm │ │ └── 数组中的第K个最大元素.md │ ├── code-specification │ │ ├── editorconfig.md │ │ ├── eslint.md │ │ ├── guides.md │ │ ├── husky.md │ │ ├── npmrc.md │ │ ├── prettier.md │ │ └── stylelint.md │ ├── css │ │ ├── Flex布局.md │ │ ├── 一些CSS属性.md │ │ ├── 如何实现toast垂直居中.md │ │ ├── 实现毛玻璃效果.md │ │ ├── 纯css实现固定宽高比.md │ │ └── 记Tailwind CSS使用.md │ ├── git │ │ ├── git commit规范.md │ │ ├── github actions示例.md │ │ ├── github apps示例.md │ │ ├── github 其他文件.md │ │ └── git修改默认分支main.md │ ├── introduction.md │ ├── js │ │ ├── ES2015常用语法特性.md │ │ ├── JS变量提升.md │ │ ├── JS如何获取当天零点时间戳.md │ │ ├── JS实现函数缓存.md │ │ ├── JS数组对象去重.md │ │ ├── ajax学习.md │ │ ├── js一行代码.md │ │ ├── 常用util.js.md │ │ └── 重构之对象映射类型.md │ ├── node │ │ ├── axios请求gbk页面乱码解决.md │ │ ├── npm镜像配置.md │ │ └── 使用 require.context 实现模块自动导入.md │ ├── vue │ │ ├── $router和$route的区别.md │ │ ├── Pinia.md │ │ ├── VueRouter导航守卫.md │ │ ├── customRef实现敏感词替换.md │ │ ├── h+render实现函数式组件调用.md │ │ ├── v-if与v-show的区别.md │ │ ├── 使用ShallowRef对接Redux到Vue.md │ │ ├── 图片懒加载指令.md │ │ ├── 对keep-alive组件的理解.md │ │ └── 异步组件实现按需加载.md │ └── web │ │ ├── TCP三次握手.md │ │ └── 跨域问题.md └── tools │ ├── Everything快速搜索本地文件.md │ ├── Vite相关插件.md │ ├── Wappalyzer识别网站上的技术.md │ ├── introduction.md │ └── 实用工具PowerToys.md ├── docsearch.json ├── docusaurus.config.js ├── i18n └── zh │ ├── code.json │ ├── docusaurus-plugin-content-blog │ └── options.json │ ├── docusaurus-plugin-content-docs │ └── current.json │ └── docusaurus-theme-classic │ ├── footer.json │ └── navbar.json ├── package-lock.json ├── package.json ├── react-app-env.d.ts ├── renovate.json ├── sidebars.js ├── src ├── components │ ├── BlogInfo │ │ └── index.tsx │ ├── BrowserWindow │ │ ├── index.tsx │ │ └── styles.module.css │ ├── Button │ │ └── index.tsx │ ├── CodeSandBox │ │ └── index.tsx │ ├── Codepen │ │ └── index.tsx │ ├── Comment │ │ └── index.tsx │ ├── Hero │ │ ├── img │ │ │ └── hero_main.svg │ │ ├── index.tsx │ │ └── styles.module.scss │ ├── Playground │ │ └── index.tsx │ ├── Svg │ │ ├── index.tsx │ │ └── styles.module.css │ └── svgIcons │ │ └── FavoriteIcon │ │ └── index.tsx ├── css │ └── custom.scss ├── pages │ ├── about.mdx │ ├── index.tsx.bak │ ├── project │ │ ├── _components │ │ │ ├── ShowcaseCard │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.css │ │ │ ├── ShowcaseFilterToggle │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.css │ │ │ ├── ShowcaseTagSelect │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.css │ │ │ └── ShowcaseTooltip │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.css │ │ ├── index.tsx │ │ └── styles.module.css │ └── resource │ │ ├── _components │ │ └── ResourceCard │ │ │ ├── index.tsx │ │ │ └── styles.module.css │ │ ├── index.tsx │ │ └── resource.module.css ├── plugin │ ├── plugin-baidu-push │ │ └── index.js │ ├── plugin-baidu-tongji │ │ └── index.js │ └── plugin-content-blog │ │ ├── lib │ │ ├── index.js │ │ └── types.js │ │ ├── package.json │ │ ├── src │ │ ├── index.ts │ │ └── types.ts │ │ ├── tsconfig.json │ │ └── tsconfig.tsbuildinfo ├── sw.js ├── theme │ ├── BlogArchivePage │ │ ├── index.tsx │ │ └── styles.module.css │ ├── BlogListPage │ │ ├── index.tsx │ │ └── useViewType.ts │ ├── BlogPostItem │ │ ├── Container │ │ │ ├── index.tsx │ │ │ └── styles.module.css │ │ ├── Content │ │ │ └── index.tsx │ │ ├── Footer │ │ │ ├── ReadMoreLink │ │ │ │ └── index.tsx │ │ │ ├── index.tsx │ │ │ └── styles.module.css │ │ ├── Header │ │ │ ├── Author │ │ │ │ └── index.tsx │ │ │ ├── Authors │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.css │ │ │ ├── Info │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.css │ │ │ ├── Title │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.css │ │ │ └── index.tsx │ │ └── index.tsx │ ├── BlogPostItems │ │ └── index.tsx │ ├── BlogPostPage │ │ ├── Metadata │ │ │ └── index.tsx │ │ └── index.tsx │ ├── BlogSidebar │ │ ├── Desktop │ │ │ ├── index.tsx │ │ │ └── styles.module.css │ │ ├── Mobile │ │ │ └── index.tsx │ │ └── index.tsx │ ├── BlogTagsListPage │ │ └── index.tsx │ ├── DocTagsListPage │ │ └── index.tsx │ ├── MDXPage │ │ ├── index.tsx │ │ └── styles.module.css │ ├── Navbar │ │ └── MobileSidebar │ │ │ └── Layout │ │ │ └── index.tsx │ └── TagsListByLetter │ │ ├── index.tsx │ │ └── styles.module.css ├── types.d.ts └── utils │ └── jsUtils.ts ├── static ├── .htaccess ├── img │ ├── buildwith.png │ ├── favicon.ico │ ├── icons │ │ ├── favicon.ico │ │ ├── icon-128.png │ │ ├── icon-16.png │ │ ├── icon-256.png │ │ ├── icon-32.png │ │ ├── icon-48.png │ │ └── icon-64.png │ ├── logo.png │ ├── logo.webp │ └── resource │ │ ├── antv.png │ │ ├── any-rule.ico │ │ ├── apifox.png │ │ ├── atoolbox.ico │ │ ├── axios.ico │ │ ├── bilibili.ico │ │ ├── bootcdn.png │ │ ├── bun.svg │ │ ├── coding.png │ │ ├── component party.svg │ │ ├── coolify.png │ │ ├── coolors.png │ │ ├── css-inspiration.png │ │ ├── cssfx.png │ │ ├── cypress.png │ │ ├── dbyun.png │ │ ├── digitalocean.png │ │ ├── docusaurus.svg │ │ ├── electron.ico │ │ ├── es6.png │ │ ├── figma.png │ │ ├── fresh.ico │ │ ├── gitee.ico │ │ ├── github.ico │ │ ├── github.png │ │ ├── google_fonts.ico │ │ ├── graphQL.svg │ │ ├── hoppscotch.png │ │ ├── igoutu.png │ │ ├── javascript.svg │ │ ├── jest.png │ │ ├── jsdelivr.webp │ │ ├── juejin.png │ │ ├── loading.ico │ │ ├── mdn.png │ │ ├── naiveUI.svg │ │ ├── namae.png │ │ ├── netlify.png │ │ ├── nuxt.svg │ │ ├── ossinsight.png │ │ ├── playwright.svg │ │ ├── prisma.png │ │ ├── quick reference.svg │ │ ├── railway.png │ │ ├── remix.png │ │ ├── roadmap.png │ │ ├── runoob.png │ │ ├── rust-logo-blk.svg │ │ ├── rust.svg │ │ ├── shields.png │ │ ├── stackblitz.png │ │ ├── stateofjs.svg │ │ ├── strapi.png │ │ ├── supabase.png │ │ ├── swc.png │ │ ├── swr.png │ │ ├── taro.png │ │ ├── terminalgif.ico │ │ ├── turbopack.svg │ │ ├── turborepo.svg │ │ ├── twind.svg │ │ ├── typeorm.ico │ │ ├── typescript.png │ │ ├── typing-svg.png │ │ ├── uiverse.png │ │ ├── vben-admin.png │ │ ├── vite.svg │ │ ├── webgradients.png │ │ ├── webpack.png │ │ └── zhubai.png ├── manifest.json └── svg │ └── juejin.svg ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | indent_style = space 10 | indent_size = 2 11 | max_line_length = 80 12 | trim_trailing_whitespace = true 13 | 14 | [*.md] 15 | insert_final_newline = false 16 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | __fixtures__ 2 | __mocks__ 3 | dist 4 | node_modules 5 | .yarn 6 | .history 7 | build 8 | coverage 9 | jest.config.js 10 | jest.transform.js 11 | docusaurus.config.js 12 | sidebars.js 13 | *.md 14 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parserOptions: { 3 | ecmaVersion: 7, 4 | sourceType: 'module', 5 | }, 6 | plugins: ['@docusaurus', '@typescript-eslint'], 7 | extends: [ 8 | 'plugin:@docusaurus/recommended', 9 | 'plugin:@typescript-eslint/recommended', 10 | ], 11 | } 12 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build-and-deploy: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v3 15 | 16 | - name: Use Node.js 16 17 | uses: actions/setup-node@v3 18 | with: 19 | node-version: '16.x' 20 | 21 | - name: Build Project 22 | run: | 23 | yarn install 24 | yarn run build 25 | 26 | - name: SSH Deploy 27 | uses: easingthemes/ssh-deploy@v2.2.11 28 | env: 29 | SSH_PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }} 30 | ARGS: '-avzr --delete' 31 | SOURCE: 'build' 32 | REMOTE_HOST: ${{ secrets.REMOTE_HOST }} 33 | REMOTE_USER: 'root' 34 | TARGET: '/home/blog/' 35 | -------------------------------------------------------------------------------- /.github/workflows/docsearch.yml.bak: -------------------------------------------------------------------------------- 1 | name: docsearch 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | algolia: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | - name: Get the content of docsearch.json as config 15 | id: algolia_config 16 | run: echo "::set-output name=config::$(cat docsearch.json | jq -r tostring)" 17 | 18 | - name: Run algolia/docsearch-scraper image 19 | env: 20 | ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }} 21 | ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_API_KEY }} 22 | CONFIG: ${{ steps.algolia_config.outputs.config }} 23 | run: | 24 | docker run \ 25 | --env APPLICATION_ID=${ALGOLIA_APP_ID} \ 26 | --env API_KEY=${ALGOLIA_API_KEY} \ 27 | --env "CONFIG=${CONFIG}" \ 28 | algolia/docsearch-scraper 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | /drafts 7 | /tmp 8 | cloudbaserc.json 9 | 10 | # Generated files 11 | .docusaurus 12 | .cache-loader 13 | 14 | # Misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | .env 21 | 22 | # Packages 23 | /packages 24 | 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | pnpm-error.log* 29 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": false, 6 | "singleQuote": true, 7 | "quoteProps": "as-needed", 8 | "jsxSingleQuote": false, 9 | "trailingComma": "all", 10 | "bracketSpacing": true, 11 | "jsxBracketSameLine": false, 12 | "arrowParens": "avoid", 13 | "proseWrap": "never" 14 | } 15 | -------------------------------------------------------------------------------- /.stylelintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['stylelint-config-standard-scss', 'stylelint-config-prettier-scss'], 3 | rules: { 4 | 'selector-pseudo-class-no-unknown': [ 5 | true, 6 | { 7 | // :global is a CSS modules feature to escape from class name hashing 8 | ignorePseudoClasses: ['global'], 9 | }, 10 | ], 11 | 'selector-class-pattern': null, 12 | 'custom-property-empty-line-before': null, 13 | 'selector-id-pattern': null, 14 | 'declaration-empty-line-before': null, 15 | 'no-descending-specificity': null, 16 | 'comment-empty-line-before': null, 17 | 'value-keyword-case': ['lower', { camelCaseSvgKeywords: true }], 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "prettier.enable": true, 3 | "editor.codeActionsOnSave": { 4 | "source.fixAll": "explicit", 5 | "source.organizeImports": "never" 6 | }, 7 | "[typescript]": { 8 | "editor.defaultFormatter": "esbenp.prettier-vscode" 9 | }, 10 | "[typescriptreact]": { 11 | "editor.defaultFormatter": "esbenp.prettier-vscode" 12 | }, 13 | "stylelint.validate": ["css", "less", "postcss", "scss", "sass"] 14 | } 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 xxsoftware 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 东云研究所的个人博客 3 |


4 | 5 |
 6 |  Build with 🦖Docusaurus 
 7 | 
8 | 9 |

10 |
11 | 🖥 Online Preview 12 |

13 | 14 | 15 | 16 |

17 | 18 | ## 👋 Introduction 19 | 20 | 在这里我会分享各类技术栈所遇到问题与解决方案,带你了解最新的技术栈以及实际开发中如何应用,并希望我的开发经历对你有所启发。 21 | 22 | 如果你想要搭建一个类似的站点,可直接 [Fork](https://github.com/xxsoftware/blog/fork) 本仓库使用 23 | 24 | ## ✨ Features 25 | 26 | - ✍️ **Markdown** - 写作方便 27 | - 🎨 **Beautiful** - 整洁,美观 28 | - 🖥️ **PWA** - 支持 PWA,可安装,离线可用 29 | - 🏞️ **i18n** - 支持国际化 30 | - 💯 **SEO** - 搜索引擎优化,易于收录 31 | - 📊 **谷歌分析** - 支持 Google Analytics 32 | - 🔎 **全文搜索** - 支持 [Algolia DocSearch](https://github.com/algolia/docsearch) 33 | - 🗃️ **博文视图** - 不同的博文视图,列表、宫格、卡片 34 | - 🌈 **资源导航** - 收集并分享有用、有意思的资源 35 | - 📦 **项目展示** - 展示你的项目,可用作于作品集 36 | 37 | ## 📊 Catalogue 38 | 39 | ```bash 40 | ├── blog # 博客 41 | │ ├── first-blog.md 42 | ├── docs # 文档/笔记 43 | │ └── doc.md 44 | ├── data # 项目/导航 45 | │ ├── project.ts # 项目 46 | │ └── resource.ts # 资源导航 47 | ├── i18n # 国际化 48 | ├── src 49 | │ ├── components # 组件 50 | │ ├── css # 自定义CSS 51 | │ ├── pages # 自定义页面 52 | │ ├── plugin # 自定义插件 53 | │ └── theme # 自定义主题组件 54 | ├── static # 静态资源文件 55 | │ └── img # 静态图片 56 | ├── docusaurus.config.js # 站点的配置信息 57 | ├── sidebars.js # 文档的侧边栏 58 | ├── package.json 59 | ├── tsconfig.json 60 | └── yarn.lock 61 | ``` 62 | 63 | ## 📥 Start 64 | 65 | ```sh 66 | git clone https://github.com/xxsoftware/blog.git 67 | cd blog 68 | yarn 69 | yarn start 70 | ``` 71 | 72 | Build 73 | 74 | ```sh 75 | yarn run build 76 | ``` 77 | 78 | ## 📝License 79 | 80 | [MIT](./LICENSE) 81 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | require.resolve('@docusaurus/core/lib/babel/preset'), 4 | [ 5 | "@babel/preset-react", 6 | { "runtime": "automatic", "importSource": "@emotion/react" } 7 | ] 8 | ], 9 | plugins: ["@emotion/babel-plugin"], 10 | } 11 | -------------------------------------------------------------------------------- /blog/advice/Api接口风格.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: api-style 3 | title: Api接口风格 4 | date: 2021-08-06 5 | authors: 東雲研究所 6 | tags: [随笔, api] 7 | keywords: [随笔, api] 8 | --- 9 | 10 | 11 | 12 | 前后端数据交互,经常要和 Api 打交道,于是关于 Api 接口的设计,有必要好好写一写 13 | 14 | ## Restful api 风格 15 | 16 | 首先还是得说一下**REST 是设计风格而不是标准**,也就是在写 api 接口的时候,喜欢就遵循。 17 | 18 | 这里举一个常见的 api 接口设计 19 | 20 | 常见的 CRUD 操作 21 | 22 | ```js 23 | POST /user/list // 获取列表 24 | POST /user/get // 获取用户 25 | POST /user/add // 添加用户 26 | POST /user/edit // 编辑用户 27 | POST /user/delete // 删除用户 28 | ``` 29 | 30 | 与之对应 Restful Api 风格 31 | 32 | ```js 33 | GET / user // 获取列表 34 | GET / user / { id } // 获取用户 35 | POST / user // 添加用户 36 | PUT / user / { id } // 编辑用户 37 | DELETE / user / { id } // 删除用户 38 | 39 | // {id} 通过后端路由 参数Params可以获取到 40 | ``` 41 | 42 | 可以看到 Restful 风格相比于正常的 POST 而言,少了请求的路径,而同时使用请求方法字段(GET,POST,PUT,DELETE) 要与之表明的意思也很明确(前提:在增删改查的时候),也就只是增删改查而已。 43 | 44 | ## 我何时使用 Restful 45 | 46 | 这里我要说说我个人使用情况下,如果单单只是增删改查的话,我会使用 Restful 风格,好用是一方面,不必在修改数据的还要在 body 中添加 id 这个字段。其次 restful 确实也算广泛,但也仅仅只是在增删改查中。 47 | 48 | 实际业务中复杂情况太多了,有的时候仅仅这四个请求方法不能很明确的表达所要的意思,例如下面一些业务逻辑 49 | 50 | ```js 51 | POST user/login 发送登录请求 52 | 53 | POST user/register 发送注册请求 54 | 55 | POST user/info 获取个人信息 56 | 57 | POST user/forget 忘记个人密码 58 | 59 | POST user/getCode 获取验证码 60 | ``` 61 | 62 | 此外还有充值,获取消费记录、登录记录等等就不一一列举了,总之这时候我毫不犹豫会使用 POST,可能有人会好奇,为啥获取信息和获取验证码的时候要使用 POST 请求,用 GET 不好吗?好,但后文会说为什么。 63 | 64 | ## 易猜测 api 接口 65 | 66 | 实际上,采用了 Restful 风格,几乎一猜就能猜到对应的 api。比如商品管理,无非就是获取商品列表,添加商品,编辑商品,删除商品。同时又传入的是对应的 ID,这要是 Mysql,ID 基本都是按顺序的,万一 api 鉴权没做好,都不知道数据怎么变动的。当然这种情况一般都是比较少见的了。 67 | 68 | ## 不易加密 69 | 70 | 上文不是说到为啥都要使用 POST 请求,原因也挺简单的,就是加密,GET 请求一般都不会携带过多参数,针对数据效验的话最多也就一个 MD5 效验,然而是远远不够的,而 POST 所能携带的数据不仅仅是 MD5 效验,还能携带风控算法,二次效验,浏览器指纹算法等等,能保证一定的防破解性。一些看似用 GET 请求方便的接口,但实际都要考虑所包含的风险,就如上面那个发送验证码的接口,如果不加以加密,特别容易仿造出与之对应的协议请求,再次仿造发送也不难。当然,对于这种限制类的业务,还是得要后端进行限制,例如 1 分钟只能发送一条,一天一号只能发送 10 条。 71 | 72 | ## 最后 73 | 74 | 其实可以发现绝大多数的网站基本上都不是采用 Restful 风格(貌似用的最多的也就是管理系统了),因为所涉及的业务逻辑实在是太复杂了,不单单只能使用请求方法来表明意思,有时候 Url 路径更能表达明确意思。 75 | 76 | Restful 风格想的太美好了,然而实际业务中 很多时候并不能单纯的通过 get post put delete 这四种请求发送来表明真实意义,所以我在增删改查的时候才会使用 Restful api 风格。 77 | 78 | 在我写项目中遇到一些复杂业务逻辑,我是毫不犹豫使用 Post 请求的,然后通过 url 路径表明 api 所要请求的路径,同时编写 Swagger Api 文档。什么样的风格都因人而异,主要自己用的习惯就行,毕竟 api 接口只是风格,并不作为标准来衡量。 79 | -------------------------------------------------------------------------------- /blog/authors.yml: -------------------------------------------------------------------------------- 1 | 東雲研究所: 2 | name: 東雲研究所 3 | title: 前端 / 社会人 4 | url: https://github.com/xxsoftware 5 | image_url: /img/logo.webp 6 | -------------------------------------------------------------------------------- /blog/develop/为什么使用pnpm.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: why-use-pnpm 3 | title: 为什么使用pnpm 4 | date: 2022-01-08 5 | authors: 東雲研究所 6 | tags: [node, pnpm] 7 | keywords: [node, pnpm] 8 | --- 9 | 10 | 11 | 12 | [pnpm 文档](https://pnpm.io/zh/) 13 | 14 | ## 前言 15 | 16 | 在一个 node 项目中免不了 node_modules 依赖,假设项目 A 用的了 Express 依赖,同时项目 B 也用到了 Express,并且两者所存放的位置不同,那么磁盘空间将会多出两份 Express 依赖,假设有 100 个项目,那么将会有有 100 倍的空间被浪费。这些空间还可以用磁盘空间来弥补,但是这 100 个项目如果都使用 npm i 去下载同样版本依赖,则是实实在在耗费网络资源去下载。 17 | 18 | pnpm 能解决以下两点问题 19 | 20 | - 包安装速度极快; 21 | - 磁盘空间利用非常高效。 22 | 23 | 而这些问题是一个 node 项目中常有的。相信此时的你都有点蠢蠢欲动了,而安装也很简单 24 | 25 | ## 安装 26 | 27 | 请查阅你的 node 版本与 pnpm 是否匹配 [安装 | pnpm](https://pnpm.io/zh/installation#兼容性) 28 | 29 | ``` 30 | npm install -g pnpm 31 | ``` 32 | 33 | ### 升级 34 | 35 | ``` 36 | pnpm add -g pnpm 37 | ``` 38 | 39 | 此时 pnpm 就已经安装完了,与 yarn 安装一样,都感觉没安装似的。 40 | 41 | ## 使用 42 | 43 | pnpm 命令几乎与 npm 一样,设置配置的方式也与 npm 相同,几乎不需要任何学习成本。查看 node_modules 属性会发现显示的空间貌似和原始的链接所占用的空间一样,但其实是同一个位置,官方中常用问题中也有介绍到 [常见问题 | pnpm](https://pnpm.io/zh/faq#如果包存储在全局存储中为什么我的-node_modules-使用了磁盘空间),所以真不用担心磁盘空间的问题。 44 | 45 | 这时候去查看 `D:\.pnpm-store\v3\files` 会发现都是一堆数字与字母命名的文件夹,而依赖都存放至这些杂乱无章的文件名之中。同时.pnpm-store 是根据你所在驱动器(这里是 F 盘)下创建的,可以通过 `pnpm store path`查看,也就是上文为什么说不要在 C 盘路径(包括桌面)去安装依赖了,所以不用担心 C 盘空间会越来越小(如果你的代码是在 C 盘编写的话,那当我没说)。 46 | 47 | ## 最后 48 | 49 | 不过还是要提醒一句,即便 pnpm 能解决磁盘问题,但还是存在一定的兼容性,如果一个项目是用 npm 或者 yarn 进行构建的,使用 pnpm 是绝对免不了一些问题,小问题暂时想不到,大问题无法运行,所以请三思再考虑对已有项目是否尝试升级 pnpm。 50 | 51 | 但我认为还是有必要尝试尝试下,不尝试,怎么能发现新大陆呢。 52 | 53 | > 参考链接:[关于现代包管理器的深度思考——为什么现在我更推荐 pnpm 而不是 npm/yarn? - 掘金 (juejin.cn)](https://juejin.cn/post/6932046455733485575#heading-14) 54 | -------------------------------------------------------------------------------- /blog/lifestyle/世间再无击节客,左耳从此不听风.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: rip-ch 3 | title: 世间再无击节客,左耳从此不听风 4 | date: 2023-05-15 5 | authors: 東雲研究所 6 | tags: ['杂谈'] 7 | keywords: ['杂谈'] 8 | description: rip 9 | toc_max_heading_level: 3 10 | --- 11 | 12 | ## 骨灰级编程大佬“左耳朵耗子”因心梗离世,一条建议值一个亿! 13 | 14 | 5 月 15 日,中国互联网届顶级程序员、知名技术专家“左耳朵耗子”-陈皓,在两天前突发心梗逝世,年仅 47 岁。在中国互联网风云跌宕起伏的 20 年发展史中,陈皓参与了多项重量级产品的演进,先后在亚马逊、阿里云、天猫任职,他的技术博客酷壳影响了无数程序员,饿了么 CTO 曾经发文说,陈皓当时给他们的一条建议价值 1 一个亿! 15 | 16 | ![img](https://imgheybox.max-c.com/bbs/2023/05/15/5a36d98c9a492cf4066718d3eae4c9bb/thumb.jpeg) 17 | 18 | 看到还是蛮震惊的,之前也看过他的视频博客,像 35 岁中年危机的视频还有工作也只做有价值的事之类的感触颇深 19 | 20 | 无论一个人怎么样,都不应对 TA 上纲上线进行“批斗”。如今陈皓突然离我们而去,他的博客依然是宝贵的财富,他的技术精神也值得宣扬继续传承下去,rip...... 21 | -------------------------------------------------------------------------------- /blog/lifestyle/第一次离职.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: dimission 3 | title: 第一次离职 4 | date: 2023-04-26 5 | authors: 東雲研究所 6 | tags: [随笔, 杂谈] 7 | keywords: [随笔, 杂谈] 8 | --- 9 | 10 | 四月份离职了,本来想趁着金四银五再找一份工作的,但是手头也没有趁手的个人项目,回想起来现在的个人项目要么是新技术出来时写的demo,要不就是在刚毕业时做的老技术项目了,正好五月份出去玩玩,回来好好沉淀一下自己做点个人项目。 11 | 而且在我域名和服务器过期之后,我已经很久没更新过笔记了,甚至我都不记得我的博客还有笔记这个东西,我也需要更新一下自己的博客了。 12 | 13 | 做完个人项目之后打算去面试找一份工作吧,已经好久没有面试过了。 -------------------------------------------------------------------------------- /blog/program/HTTP请求配置客户端SSL证书.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: http-config-client-ssl-certificate 3 | title: HTTP配置客户端SSL证书 4 | date: 2022-02-17 5 | authors: 東雲研究所 6 | tags: [http, ssl] 7 | keywords: [http, ssl] 8 | --- 9 | 10 | HTTPS 协议是由 SSL/TLS+HTTP 协议构建的可进行加密传输、身份认证的网络协议,要比 http 协议安全,很多大型互联网网站都用了都用了 https,那么他们之间有什么具体的区别,又如何配置呢? 11 | 12 | 13 | 14 | ## HTTP 和 HTTPS 15 | 16 | ### HTTP 17 | 18 | HTTP 协议也就是超文本传输协议,是一种使用明文数据传输的网络协议。一直以来 HTTP 协议都是最主流的网页协议,HTTP 协议被用于在 Web 浏览器和网站服务器之间传递信息,以明文方式发送内容,不提供任何方式的数据加密,如果攻击者截取了 Web 浏览器和网站服务器之间的传输报文,就可以直接读懂其中的信息。 19 | 20 | ![](https://img.xxsoftware.fun/https-demo.jpg) 21 | 22 | ### HTTPS 23 | 24 | 为了解决 HTTP 协议的这一缺陷,需要使用另一种协议:安全套接字层超文本传输协议 HTTPS,为了数据传输的安全,HTTPS 在 HTTP 的基础上加入了 SSL/TLS 协议,SSL/TLS 依靠证书来验证服务器的身份,并为浏览器和服务器之间的通信加密。HTTPS 协议可以理解为 HTTP 协议的升级,就是在 HTTP 的基础上增加了数据加密。在数据进行传输之前,对数据进行加密,然后再发送到服务器。这样,就算数据被第三者所截获,但是由于数据是加密的,所以你的个人信息仍然是安全的。这就是 HTTP 和 HTTPS 的最大区别。 25 | 26 | ### HTTP 和 HTTPS 的区别 27 | 28 | ![](https://img.xxsoftware.fun/http-safe.jpg) 29 | 30 | #### 1.安全性不同 31 | 32 | https:// 前缀表明是用 SSL (安全套接字)或 TSL 加密的,你的电脑与服务器之间收发的信息传输将更加安全。当你使用浏览器访问一个 HTTP 网站的时候,你会发现浏览器会对该 HTTP 网站显示“不安全”的安全警告,提示用户当前所访问的网站可能会存在风险。 33 | 34 | 而假如你访问的是一个HTTPS网站时,情况却是完全不一样。你会发现浏览器的地址栏会变成绿色,企业名称会展示在地址栏中,地址栏上面还会出现一把“安全锁”的图标。这些都会给予用户很大的视觉上的安全体验。 35 | 36 | #### 2.网站申请流程不同 37 | 38 | https协议需要到CA申请证书,一般免费证书很少,需要交费,Web服务器启用SSL需要获得一个服务器证书并将该证书与要使用SSL的服务器绑定。 39 | 40 | #### 3.默认端口不同 41 | 42 | http和https使用的是完全不同的连接方式,同时使用的端口也不同,http使用的是80端口,https使用的是443端口。在网络模型中,HTTP工作于应用层,而HTTPS工作在传输层。 43 | 44 | #### 4.对搜索排名的提升 45 | 46 | 这也是很多站长所关注的地方。百度和谷歌两大搜索引擎都已经明确表示,HTTPS网站将会作为搜索排名的一个重要权重指标。也就是说HTTPS网站比起HTTP网站在搜索排名中更有优势。 47 | 48 | ## 如何配置 HTTPS 49 | 50 | ### 首先你需要一个 http 网站 51 | 52 | 这是最起码的吧 53 | 54 | ### 购买申请一个 SSL 证书 55 | 56 | 之后你就需要一个SSl证书了,我这里用的是阿里云的免费证书,阿里云可以免费申请20个SSL证书,当然你也可以直接购买。 57 | 58 | ![](https://img.xxsoftware.fun/SSL.jpg) 59 | 60 | ### 部署 61 | 62 | 之后你就可以直接一键部署了,不过我的服务器是在京东云上的caddy,caddy是自带HTTPS的。 63 | 64 | 将 example.com 替换为您的域名,如果您使用的是 IPv6,请将 type=A 替换为 type=AAAA : 65 | 66 | curl "https://cloudflare-dns.com/dns-query?name=example.com&type=A" \ 67 | -H "accept: application/dns-json" 68 | 69 | 可能有些同学跑不通,到时候就要设置镜像或者下载刚刚在阿里云申请到的证书,把证书上传到服务器,然后在Caddyfile里 70 | 配置tls后面跟上你上传的证书名字。 71 | 之后再在命令行输入systemctl restart caddy重启caddy服务,你就发现你的网站从http协议变成https协议了 72 | -------------------------------------------------------------------------------- /blog/program/如何给博客增加评论效果.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: blog-comments 3 | title: 如何给博客增加评论效果 4 | date: 2023-01-17 5 | authors: 東雲研究所 6 | tags: [vue, vite, Giscus, github] 7 | keywords: [vue, vite, Giscus, github] 8 | description: 如何用Giscus给博客增加评论效果 9 | --- 10 | 11 | 我们在写博客时有时候想有一个评论的效果,有时候可以利用Giscus和github的功能完成这个。 12 | 13 | ## giscus 介绍 14 | 15 | giscus是利用 GitHub Discussions 实现的评论系统。 16 | giscus 加载时,会使用 GitHub Discussions 搜索 API 根据选定的映射方式(如 URL、pathname、 等)来查找与当前页面关联的 discussion。如果找不到匹配的 discussion,giscus bot 就会在第一次有人留下评论或回应时自动创建一个 discussion。 17 | 18 | 访客如果想要评论,必须按照 GitHub OAuth 流程授权 giscus app 代表他发布,或者可以直接在 GitHub Discussion 里评论。你可以在 GitHub 上管理评论。 19 | 20 | ## 操作 21 | 22 | 1.先安装 vuepress-plugin-comment2 23 | pnpm add -D vuepress-plugin-comment2@next 24 | 25 | 2.设置插件 26 | 27 | ```Vue title='.vuepress/config.js' 28 | const { commentPlugin } = require("vuepress-plugin-comment2"); 29 | module.exports = 30 | { plugins: [ commentPlugin({ // 插件选项 }), ], }; 31 | 32 | ``` 33 | 34 | 3.将仓库公开并开启评论区,以作为评论存放的地点 35 | 开启仓库的Disscussions 36 | 37 | ![](https://img.xxsoftware.fun/github_setting.png) 38 | 39 | ![](https://img.xxsoftware.fun/github_discussions.png) 40 | 41 | 4. 在完成以上步骤后,请前往 Giscus 页面open in new window 获得你的设置。 42 | 43 | ![](https://img.xxsoftware.fun/giscus.png) 44 | 45 | 5.补足插件设置就完成了 46 | 47 | ```Vue title='.vuepress/config.js' 48 | const { commentPlugin } = require("vuepress-plugin-comment2"); 49 | module.exports = { 50 | ... // 省略Vuepress的配置 51 | ... 52 | plugins: [ 53 | // vuepress-plugin-comment2评论与阅读量插件 54 | commentPlugin({ 55 | // 插件选项 56 | provider: "Giscus", //评论服务提供者。 57 | comment: true, //启用评论功能 58 | repo: "Chengyunlai/Xxxx", //远程仓库 59 | repoId: "xxx", //对应自己的仓库Id 60 | category: "Announcements", 61 | categoryId: "xxx" //对应自己的分类Id 62 | }), 63 | ], 64 | ... 65 | ... 66 | }; 67 | ``` 68 | 69 | ![](https://img.xxsoftware.fun/blog_comments.png) 70 | -------------------------------------------------------------------------------- /blog/project/使用Vite开发Chrome插件.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: Vite-chrome-extension 3 | title: 使用Vite开发Chrome插件 4 | date: 2021-09-18 5 | authors: 東雲研究所 6 | tags: [chrome, plugin, vue, develop] 7 | keywords: [chrome, plugin, vue, develop] 8 | description: 使用 Vue2 开发一个 Chrome 插件 9 | --- 10 | 11 | ![mini](https://img.xxsoftware.fun//mini.jpg) 12 | 13 | <!-- truncate --> 14 | 15 | ## 前言 16 | 17 | 之前学习开发 Chrome 插件的时候,用的是原生的技术,效率低,而这次发现了一个基于 vite 的浏览器插件入门模板,分享学习一下 18 | 19 | 相关代码开源[github 地址](https://github.com/antfu/vitesse-webext) 20 | 21 | ## 环境搭建 22 | 23 | ```sh 24 | npx degit antfu/vitesse-webext my-webext 25 | cd my-webext 26 | pnpm i 27 | ``` 28 | 29 | 安装完就可以跑了 30 | 31 | ### 项目结构 32 | 33 | ``` 34 | src - main source. 35 | contentScript - scripts and components to be injected as content_script 36 | background - scripts for background. 37 | components - auto-imported Vue components that are shared in popup and options page. 38 | styles - styles shared in popup and options page 39 | assets - assets used in Vue components 40 | manifest.ts - manifest for the extension. 41 | extension - extension package root. 42 | assets - static assets (mainly for manifest.json). 43 | dist - built files, also serve stub entry for Vite on development. 44 | scripts - development and bundling helper scripts. 45 | 46 | 47 | ``` 48 | 49 | 开发模式 pnpm dev 50 | 如果是 firefox pnpm start:firefox 51 | 生成扩展pnpm build,然后将文件打包在 extension 以下 52 | 53 | 整体的开发和 vue 开发基本上没太大的区别,不过有很多 api 54 | 55 | 在 src/popup/App.vue 可以修改扩展框弹出的内容 56 | 57 | ```vue title="src/popup/App.vue" 58 | <script setup lang="ts"> 59 | import { storageDemo } from '~/logic/storage' 60 | 61 | function openOptionsPage() { 62 | browser.runtime.openOptionsPage() 63 | } 64 | </script> 65 | 66 | <template> 67 | <main class="w-[300px] px-4 py-5 text-center text-gray-700"> 68 | <Logo /> 69 | <div>Popup</div> 70 | <SharedSubtitle /> 71 | 72 | <button class="btn mt-2" @click="openOptionsPage">Open Options</button> 73 | <div class="mt-2"> 74 | <span class="opacity-50">Storage:</span> {{ storageDemo }} 75 | </div> 76 | </main> 77 | </template> 78 | ``` 79 | 80 | ## 相关模板 81 | 82 | [vitesse-webext](https://github.com/antfu/vitesse-webext) 83 | 84 | [plasmo](https://www.plasmo.com/) 85 | 86 | ## 整体体验 87 | 88 | 当时写 Chrome 插件的效率比较低,还不能用框架。现在基于 vite 写一个插件就和编写网页一样简单,也非常感谢 antfu 大佬能带来这么一款模板脚手架,我目前仅仅只是体验了一下,写了几个跳转页面 demo,还需要了解更多 api 89 | -------------------------------------------------------------------------------- /blog/project/第二个博客搭建之Docusaurus.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: second-blog-is-docusaurus 3 | title: 第二个博客搭建之Docusaurus 4 | date: 2021-08-20 5 | authors: 東雲研究所 6 | tags: [blog, docusaurus, project] 7 | keywords: [blog, docusaurus, project] 8 | description: 使用 docusaurus 搭建个人博客,并对其主题进行魔改 9 | sticky: 5 10 | --- 11 | 12 | 博客地址: [東雲研究所的小站](https://blog.xxsoftware.fun/) 13 | 14 | 时隔近半年没好好整理文章,博客也写的不像个人样。:joy: 15 | 16 | 大半年没更新博客,一直忙着写项目(写到手软的那种),然后无意间在 B 站看到一个 Up 主 [峰华前端工程师](https://zxuqian.cn/) 基于 React 驱动的静态网站生成器搭建的个人博客。第一眼看到该站点的时候惊艳到我了,于是我在其基础上并魔改了一些页面功能,作为个人站点使用。 17 | 18 | > 不过国内 docusaurus 的使用者是真的少,Vuepress 都快烂大街了... 19 | 20 | <!-- truncate --> 21 | 22 | ## 安装 23 | 24 | 如果你想搭建一个类似的博客,可以 [fork 本项目](https://github.com/xxsoftware/blog/fork),修改个人信息,并将文章迁移过来。 25 | 26 | ```bash 27 | git clone https://github.com/xxsoftware/blog 28 | cd blog 29 | yarn 30 | yarn start 31 | ``` 32 | 33 | ## 一些页面 34 | 35 | ### [博客页](/) 36 | 37 | ![image-20230221120937768](https://img.xxsoftware.fun/blogshow.png) 38 | 39 | - 支持 3 种博文信息展示 40 | - 博客个人信息卡片 41 | - 可根据 `sticky` 字段对文章进行置顶推荐 42 | 43 | ### [归档页](/archive) 44 | 45 | ![image-20220804052418993](https://img.xxsoftware.fun/bloghistory.png) 46 | 47 | ### [资源导航](/resource) 48 | 49 | ![image-20220804052016538](https://img.xxsoftware.fun/blognav.png) 50 | 51 | - 在此分享所收藏的一些好用、实用网站。 52 | 53 | ### 评论 54 | 55 | ![image-20220804052746803](https://img.xxsoftware.fun/blog_comments.png) 56 | 57 | - 接入 [giscus](https://giscus.app) 作为评论系统,支持 GitHub 登录。 58 | 59 | ### [项目](/project) 60 | 61 | ![image-20220804052117492](https://img.xxsoftware.fun/blogproject.png) 62 | 63 | - 存放你的项目,或是当做一个作品集用于展示。 64 | 65 | ## 部署 66 | 67 | 按传统的方式,你编写好一篇文章后,需要重新打包成静态文件(.html),然后将静态文件上传到服务器(需要自己准备)上,然后通过 nginx 配置域名访问。如今有了自动化部署,你只需要将代码 push 到 Github 上,然后通过 CI/CD 自动化部署到服务器上。可以参考 [ci.yml](https://github.com/xxsoftware/blog/blob/main/.github/workflows/ci.yml) 配置文件。 68 | 69 | 这里推荐使用 [Vercel 部署个人博客](/vercel-deploy-blog),部署十分简单,你甚至不需要服务器,只需要有个 Github 账号,将你的博客项目添加为一个仓库中即可(也许需要科学上网)。 70 | 71 | ## 最后 72 | 73 | 博客的意义在于记录,记录自己的成长,记录自己的所思所想,记录自己的所学所得。希望更多的时间用在创作内容上,而不是在搭建博客上。 74 | 75 | 也就不浪费口舌了,博客搭建完毕,应该好好的去编写有意义的文章,才能够吸引他人的阅读。 76 | -------------------------------------------------------------------------------- /blog/reference/记一次前端面试过程.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: frontend-interview-experience 3 | title: 记第一次前端面试过程 4 | date: 2021-07-25 5 | authors: 東雲研究所 6 | tags: [随笔, 面试, fontend] 7 | keywords: [随笔, 面试, fontend] 8 | description: 记录一次前端面试过程 9 | draft: true 10 | --- 11 | 12 | 终于毕业了,玩了一段时间之后准备找工作的事情,然后面了第一次试遇到了很多不懂的事情 13 | 14 | 面试之前也没背过八股文,在学校里经常写vue的我以为面试是相对简单的事情,没想到第三个问题直接把我难住了,我记得是0.1+0.2为什么不等于0.3,平时都没怎么注意过这种问题直接把我考住了,后面还有setInterval的最短间隔时间,直接把我整的头晕了,后面有关vue的问题都答得不太好了。 15 | 16 | 面试之后我也不好意思问人家了,得,还是回去背背八股文和做做项目沉淀一下吧。 17 | -------------------------------------------------------------------------------- /data/friend.ts: -------------------------------------------------------------------------------- 1 | export const Friends: Friend[] = [] 2 | 3 | export type Friend = { 4 | title: string 5 | description: string 6 | website: string 7 | avatar?: any 8 | } 9 | -------------------------------------------------------------------------------- /data/project.ts: -------------------------------------------------------------------------------- 1 | export const projects: Project[] = [ 2 | { 3 | title: '東雲研究所的小站', 4 | description: '基于Docusaurus v2 静态网站生成器实现个人博客', 5 | preview: 'https://img.xxsoftware.fun/blog.png', 6 | website: 'https://blog.xxsoftware.fun/', 7 | source: 'https://github.com/xxsoftware/blog', 8 | tags: ['opensource', 'design', 'favorite'], 9 | type: 'web', 10 | }, 11 | { 12 | title: '前端示例代码库', 13 | description: 14 | '📦 整理前端样式和功能的实现代码,可以用来寻找灵感或直接使用示例中的代码', 15 | preview: 'https://img.xxsoftware.fun/lib.png', 16 | website: 'https://lib.xxsoftware.fun/', 17 | source: 'https://gitee.com/xxsoftware/vue', 18 | tags: ['opensource', 'design'], 19 | type: 'web', 20 | }, 21 | { 22 | title: 'xx记账', 23 | description: 24 | 'trao+vue3+pinia+nutUI+echarts构建的记账微信小程序', 25 | preview: 'https://img.xxsoftware.fun/taro-app.png', 26 | website: 'https://img.xxsoftware.fun/gh_6f9f88ffcb9a_1280.jpg', 27 | source: 'https://gitee.com/xxsoftware/taro-app', 28 | tags: ['opensource', 'product'], 29 | type: 'web', 30 | }, 31 | { 32 | title: '小游戏平台', 33 | description: 34 | 'vitesse+elementplus构建的小游戏平台', 35 | preview: 'https://img.xxsoftware.fun/game-minesweeper.png', 36 | website: 'https://www.xxsoftware.fun/', 37 | source: 'https://gitee.com/xxsoftware/games', 38 | tags: ['opensource', 'product'], 39 | type: 'web', 40 | }, 41 | ] 42 | 43 | export type Tag = { 44 | label: string 45 | description: string 46 | color: string 47 | } 48 | 49 | export type TagType = 50 | | 'favorite' 51 | | 'opensource' 52 | | 'product' 53 | | 'design' 54 | | 'large' 55 | | 'personal' 56 | 57 | export type ProjectType = 'personal' | 'web' | 'app' | 'toy' | 'other' 58 | 59 | export type Project = { 60 | title: string 61 | description: string 62 | preview?: any 63 | website: string 64 | source?: string | null 65 | tags: TagType[] 66 | type: ProjectType 67 | } 68 | 69 | export const Tags: Record<TagType, Tag> = { 70 | favorite: { 71 | label: '喜爱', 72 | description: '我最喜欢的网站,一定要去看看!', 73 | color: '#e9669e', 74 | }, 75 | opensource: { 76 | label: '开源', 77 | description: '开源项目可以提供灵感!', 78 | color: '#39ca30', 79 | }, 80 | product: { 81 | label: '产品', 82 | description: '与产品相关的项目!', 83 | color: '#dfd545', 84 | }, 85 | design: { 86 | label: '设计', 87 | description: '设计漂亮的网站!', 88 | color: '#a44fb7', 89 | }, 90 | large: { 91 | label: '大型', 92 | description: '大型项目,原多于平均数的页面', 93 | color: '#8c2f00', 94 | }, 95 | personal: { 96 | label: '个人', 97 | description: '个人项目', 98 | color: '#12affa', 99 | }, 100 | } 101 | 102 | export const TagList = Object.keys(Tags) as TagType[] 103 | 104 | export const groupByProjects = projects.reduce((group, project) => { 105 | const { type } = project 106 | group[type] = group[type] ?? [] 107 | group[type].push(project) 108 | return group 109 | }, {} as Record<ProjectType, Project[]>) 110 | -------------------------------------------------------------------------------- /docs/skill/algorithm/数组中的第K个最大元素.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: find-number 3 | slug: /find-number 4 | title: 数组中的第K个最大元素 5 | authors: 東雲研究所 6 | tags: [algorithm] 7 | keywords: [algorithm] 8 | --- 9 | 10 | ## 数组中的第 K 个最大元素 11 | 12 | 在未排序的数组中找到第`k`个最大的元素。请注意,你需要找的是数组排序后的第`k`个最大的元素,而不是第`k`个不同的元素。 13 | 14 | ### 示例 15 | 16 | ``` 17 | 输入: [3,2,1,5,6,4] 和 k = 2 18 | 输出: 5 19 | ``` 20 | 21 | ``` 22 | 输入: [3,2,3,1,2,4,5,5,6] 和 k = 4 23 | 输出: 4 24 | ``` 25 | 26 | ### 题解 27 | 28 | ```javascript 29 | /** 30 | * @param {number[]} nums 31 | * @param {number} k 32 | * @return {number} 33 | */ 34 | var findKthLargest = function (arr, k) { 35 | var adjustHeap = function (arr, i, n) { 36 | for (let k = 2 * i + 1; k < n; k = 2 * k + 1) { 37 | let parent = arr[i] 38 | if (k + 1 < n && arr[k] < arr[k + 1]) ++k 39 | if (parent < arr[k]) { 40 | ;[arr[i], arr[k]] = [arr[k], arr[i]] 41 | i = k 42 | } else { 43 | break 44 | } 45 | } 46 | } 47 | var n = arr.length 48 | for (let i = Math.floor(n / 2 - 1); i >= 0; --i) adjustHeap(arr, i, n) 49 | var target = 0 50 | for (let i = n - 1; i >= n - k; --i) { 51 | target = arr[0] 52 | if (i - 1 >= n - k) { 53 | ;[arr[0], arr[i]] = [arr[i], arr[0]] 54 | adjustHeap(arr, 0, i) 55 | } 56 | } 57 | return target 58 | } 59 | ``` 60 | 61 | ### 思路 62 | 63 | 采用大顶堆的数据结构解决问题,大顶堆要求根节点的关键字既大于或等于左子树的关键字值,又大于或等于右子树的关键字值并且为完全二叉树,首先定义`adjustHeap`函数左调整堆使用,首先以`i`作为双亲元素的下标,以`k`作为左孩子的下标,当右孩子存在时判断右孩子是否大于左孩子,大于左孩子则将`k`作为右孩子的指向下标,然后判断双亲值与`k`指向的孩子的节点值的大小,如果孩子值大于双亲值则交换,并且以`k`作为双亲节点沿着路径继续向下调整,否则就结束本次循环,然后定义`n`作为数组长度,之后将堆中每个作为双亲节点的子树进行调整,使整个树符合大顶堆的特征,之后进行`k`次循环,由于是大顶堆且已调整完成将顶堆的顶值也就是最大值取出赋值给`target`,之后判断是否需要进一步调整,如果需要则交换顶端值与最后一个值,然后调整顶堆符合大顶堆的条件,同样取出顶堆最大值,取出`k`次即可完成。 64 | -------------------------------------------------------------------------------- /docs/skill/code-specification/editorconfig.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: editorconfig 3 | slug: /editorconfig 4 | title: editorconfig 5 | authors: 東雲研究所 6 | keywords: ['code-specification', 'editorconfig'] 7 | --- 8 | 9 | [Editorconfig](https://editorconfig.org/) 有助于跨各种编辑器和 IDE 为处理同一项目的多个开发人员维护一致的编码样式。 10 | 11 | ## 使用 ESLint 做代码 lint,那么为什么还要使用 .editorconfig 呢? 12 | 13 | - ESLint 确实包含 .editorconfig 中的一些属性,如缩进等,但并不全部包含,如 .editorconfig 中的 insert_final_newline 属性 Eslint 就没有。Eslint 更偏向于对语法的提示,如定义了一个变量但是没有使用时应该给予提醒。而 .editorconfig 更偏向于代码风格,如缩进等。 14 | - ESLint 仅仅支持对 js 文件的校验,而 .editorconfig 不光可以检验 js 文件的代码风格,还可以对 .py(python 文件)、.md(markdown 文件)进行代码风格控制。 15 | 16 | > 根据项目需要,Eslint 和 .editorconfig 并不冲突,同时配合使用可以使代码风格更加优雅。 17 | 18 | ## 安装 EditorConfig 19 | 20 | [EditorConfig for VS Code](https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig) 21 | 22 | 创建 `.editorconfig`,示例内容如下 23 | 24 | ```editorconfig title='.editorconfig' 25 | # http://editorconfig.org 26 | 27 | root = true 28 | 29 | [*] 30 | charset = utf-8 31 | indent_style = space 32 | indent_size = 2 33 | end_of_line = lf 34 | insert_final_newline = true 35 | trim_trailing_whitespace = true 36 | quote_type = single 37 | 38 | [*.md] 39 | insert_final_newline = false 40 | trim_trailing_whitespace = false 41 | ``` -------------------------------------------------------------------------------- /docs/skill/code-specification/eslint.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: eslint 3 | slug: /eslint 4 | title: eslint 5 | authors: 東雲研究所 6 | keywords: ['code-specification', 'eslint'] 7 | --- 8 | 9 | ESLint 是一种用于识别和报告 ECMAScript/JavaScript 代码中发现的模式的工具,目的是使代码更加一致并避免错误。 10 | 11 | [Getting Started with ESLint](https://eslint.org/docs/latest/user-guide/getting-started) 12 | 13 | ## eslint-config 14 | 15 | 这里强烈推荐 [antfu/eslint-config](https://github.com/antfu/eslint-config),以及大佬的文章 [Why I don't use Prettier (antfu.me)](https://antfu.me/posts/why-not-prettier) 16 | 17 | 这份 eslint 配置对于 ts 与 vue 已经足够完整,如果还有其他需求,可自行添加 rule 或使用[overrides](https://eslint.org/docs/latest/user-guide/configuring/configuration-files#how-do-overrides-work)。 18 | 19 | ## 在 Vscode 中集成 ESlint 插件 20 | 21 | - 在 VScode 插件市场安装 [ESLint 插件](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) 22 | 23 | - 开启代码保存时自动执行 ESLint 修复功能(全局设置) 24 | 25 | ``` 26 | "editor.codeActionsOnSave": { 27 | "source.fixAll": false, 28 | "source.fixAll.eslint": true, 29 | "source.organizeImports": false 30 | }, 31 | ``` 32 | 33 | - 工作区示例如下 34 | 35 | ```json title='.vscode/settings.json' 36 | { 37 | "prettier.enable": false, 38 | "editor.formatOnSave": false, 39 | "editor.codeActionsOnSave": { 40 | "source.fixAll.eslint": true 41 | } 42 | } 43 | ``` 44 | 45 | ## 注意事项 46 | 47 | 由于 eslint 配置相对繁琐,所以很多时候编辑器的 eslint 可能都没有生效,具体看编辑器下方状态栏或者日志输出查看 ESLint 状态。如果为警告(黄色感叹号)或者错误(红色),那么 ESLint 就是没配置好,可能缺少某些依赖文件或是配置文件写错了。 48 | -------------------------------------------------------------------------------- /docs/skill/code-specification/guides.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: code-specification-guides 3 | slug: /code-specification 4 | title: 代码规范 5 | authors: 東雲研究所 6 | keywords: ['code-specification'] 7 | --- 8 | 9 | 杂乱不堪如同屎山般的代码,会让开发者头皮发麻,无从下手,往往更容易诱发bug。而一个良好的代码规范,能够修复团队的各个成员间代码格式不统一,有利于维护与审查。 10 | 11 | 这里主要不是介绍具体的代码规范标准,这些在对应的官方文档中的风格指南可查看。本文主要利用插件工具,在保存代码与上传代码时,根据配置规则来规范代码。 12 | 13 | > 本栏主要针对前端项目的代码规范配置,使用 VSCode 文本编辑器及其插件配置。 14 | 15 | import DocCardList from '@theme/DocCardList'; import {useCurrentSidebarCategory} from '@docusaurus/theme-common'; 16 | 17 | <DocCardList items={useCurrentSidebarCategory().items}/> -------------------------------------------------------------------------------- /docs/skill/code-specification/husky.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: husky 3 | slug: /husky 4 | title: husky 5 | authors: 東雲研究所 6 | keywords: ['code-style', 'husky'] 7 | --- 8 | 9 | 为了确保只有合格的代码才能够提交到仓库。需要配置自动化脚本,确保代码在提交前通过了代码验证工具的检验。 10 | 11 | 实际上 git 本身就设计了生命周期钩子来完成这个任务。但是设置过程比较复杂。所以通常情况下会使用 husky 来简化配置。 12 | 13 | [Husky](https://typicode.github.io/husky/#/) 14 | 15 | [Git - githooks](https://git-scm.com/docs/githooks) 16 | 17 | ```bash 18 | pnpm i husky -D 19 | ``` 20 | 21 | 会创建一个 npm script 22 | 23 | ``` 24 | npm set-script prepare "husky install" 25 | ``` 26 | 27 | ## githooks 28 | 29 | ### 在 commit 提交前执行 lint 代码校验 30 | 31 | 执行下方命令,以添加生命周期钩子: 32 | 33 | ```sql 34 | npx husky add .husky/pre-commit "pnpm lint" 35 | ``` 36 | 37 | 会创建 `.husky/pre-commit` 文件,其内容如下 38 | 39 | ```bash title='.husky/pre-commit' 40 | #!/usr/bin/env sh 41 | . "$(dirname -- "$0")/_/husky.sh" 42 | 43 | pnpm lint 44 | ``` 45 | 46 | 在每次提交时,都将会执行 lint 脚本来检查代码。 47 | 48 | ### 在 push 之前通过单元测试 49 | 50 | 不过更多的做法都是用 **github action** 配置 CI 在虚拟机上跑测试,而不是本地测试。(故这步可省略) 51 | 52 | 执行下方命令,以添加生命周期钩子: 53 | 54 | ```bash 55 | npx husky add .husky/pre-push "pnpm test" 56 | ``` 57 | 58 | ### 提交时自动检查 commit 信息是否符合要求 59 | 60 | [commitlint - Lint commit messages](https://commitlint.js.org/#/?id=getting-started) 61 | 62 | 安装 63 | 64 | ```bash 65 | pnpm i -g @commitlint/cli @commitlint/config-conventional 66 | ``` 67 | 68 | ```bash 69 | echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js 70 | ``` 71 | 72 | :::caution 注意 73 | 74 | windows 系统请勿使用上行命令,否则会导致编码不是 UTF-8。建议直接复制文本内容到 `commitlint.config.js` 75 | 76 | ```javascript title='commitlint.config.js' 77 | module.exports = {extends: ['@commitlint/config-conventional']}; 78 | ``` 79 | 80 | ::: 81 | 82 | 将 commitlint 脚本添加到 githooks 中, 让每次提交前都验证信息是否正常。 83 | 84 | ```bash 85 | npx husky add .husky/commit-msg "npx --no-install commitlint --edit "$1"" 86 | ``` 87 | 88 | 其内容如下 89 | 90 | ```bash title='.husky/commit-msg' 91 | #!/bin/sh 92 | . "$(dirname "$0")/_/husky.sh" 93 | 94 | npx --no-install commitlint --edit "$1" 95 | ``` 96 | 97 | 测试 commit 提交 `echo 'foo: bar' | commitlint` 将会报错,不符合 commit msg 规范。 98 | 99 | ``` 100 | echo 'foo: bar' | commitlint 101 | ⧗ input: foo: bar✖ type must be one of [build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test] [type-enum] 102 | 103 | ✖ found 1 problems, 0 warnings 104 | ⓘ Get help: https://github.com/conventional-changelog/commitlint/#what-is-commitlint 105 | ``` 106 | -------------------------------------------------------------------------------- /docs/skill/code-specification/npmrc.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: npmrc 3 | slug: /npmrc 4 | title: npmrc 5 | authors: 東雲研究所 6 | keywords: ['code-specification', 'npmrc'] 7 | --- 8 | 9 | 对于 pnpm 项目,通常会有一个 `.npmrc` 文件,用于配置npm的一些参数,比如使用pnpm的严格模式等,其内容如下。 10 | 11 | ```properties title='.npmrc' 12 | shamefully-hoist=true 13 | strict-peer-dependencies=false 14 | shell-emulator=true 15 | ``` 16 | 17 | 此外,配置仓库镜像源,node版本等等。更多配置可看 [.npmrc](https://pnpm.io/npmrc)。 18 | -------------------------------------------------------------------------------- /docs/skill/code-specification/prettier.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: prettier 3 | slug: /prettier 4 | title: prettier 5 | authors: 東雲研究所 6 | keywords: ['code-specification', 'prettier'] 7 | --- 8 | 9 | Prettier 是一个固执己见的代码格式化程序。 10 | 11 | [Install · Prettier](https://prettier.io/docs/en/install.html) 12 | 13 | ## 集成在 ESlint 中 14 | 15 | ESlint 与 Prettier 可能会冲突,故需做如下设置: 16 | 17 | ```js 18 | //1. 安装 eslint-config-prettier 插件 19 | npm i -D eslint-config-prettier 20 | //2. 在 eslint 的配置文件中写入以下内容 21 | extends: ['plugin:prettier/recommended'], // 避免与 prettier 冲突 22 | ``` 23 | 24 | ## prettier 与 eslint 如何选择 25 | 26 | prettier 只需要按照一个 vscode 插件,几乎没有任何门槛,按下 Ctrl + Alt + F 就可以美化你的代码。而 eslint 需要配合代码编辑器与相关规则,通过保存文件或者执行 eslint 命令才能格式化代码。但往往也是因为过少的配置,使 prettier 对代码的约束不如 eslint。 27 | 28 | 可以看看 Antfu 大佬的博客 [Why I don't use Prettier (antfu.me)](https://antfu.me/posts/why-not-prettier),阐述了他为何不使用 Prettier。 29 | 30 | 这两个我都有在使用,在临时编写 demo 代码的时候,肯定优先使用 prettier。 31 | 32 | 但是在实际项目中,如果不使用 eslint 的话,每次保存代码都需要手动格式化,还是比较繁琐的。 33 | -------------------------------------------------------------------------------- /docs/skill/code-specification/stylelint.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: stylelint 3 | slug: /stylelint 4 | title: stylelint 5 | authors: 東雲研究所 6 | keywords: ['code-specification', 'stylelint'] 7 | --- 8 | 9 | stylelint 主要针对 css 样式进行格式化(包括css预处理器),同时对一些属性拼写进行检查。 10 | 11 | [Getting started | Stylelint](https://stylelint.io/user-guide/get-started) 12 | 13 | 配置文件示例: 14 | 15 | ```json title='.stylelintrc.json' 16 | { 17 | "extends": ["stylelint-config-recommended","stylelint-config-standard"], 18 | "rules": { 19 | "indentation": 4 20 | } 21 | } 22 | ``` 23 | 24 | ## 配合 prettier 25 | 26 | [prettier/stylelint-config-prettier](https://github.com/prettier/stylelint-config-prettier) -------------------------------------------------------------------------------- /docs/skill/css/如何实现toast垂直居中.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: toast-center 3 | slug: /toast-center 4 | title: 如何实现toast垂直居中 5 | date: 2023-2-11 6 | authors: 東雲研究所 7 | tags: [css] 8 | keywords: [css] 9 | --- 10 | 11 | toast效果是前端非常常见的一种效果,就是中间有个文本弹框的效果 12 | 13 | ## 第 1 种方法 14 | 15 | .toast{ 16 | position: fixed; 17 | left:50%; 18 | top:50%; 19 | transform:translate(-50%,-50%); 20 | } 21 | 这是一个非常经典的居中,但是有一个缺点那就是他把transform属性占用掉了,如果我要写个hover让他scale一下大小就很烦了 22 | 还有一个问题就是文字多的情况下会换行 23 | 24 | ## 第 2 种方法 25 | 26 | .toast{ 27 | position:fixed; 28 | width:fit-content; 29 | inset-inline:1rem 30 | margin-inline:auto; 31 | } 32 | 这个就比较好了,不过兼容性可能有点问题 33 | 34 | ## 第 3 种方法 35 | 36 | .toast{ 37 | position:fixed; 38 | width:fit-content; 39 | left:1rem; 40 | right:1rem; 41 | margin-left:auto; 42 | margin-right:auto; 43 | } 44 | 第三个就完美了 45 | 46 | 这篇文章是看(张鑫旭老师的视频)[https://www.bilibili.com/video/BV19Y411q7kH/?spm_id_from=333.999.0.0&vd_source=ca3c9326184c271fd49027dac62987a6]学的,如果要深入 css,可以购买《css 新世界》 47 | -------------------------------------------------------------------------------- /docs/skill/css/纯css实现固定宽高比.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: css-Fixed-wh 3 | slug: /css-Fixed-wh 4 | title: 纯css实现固定宽高比 5 | date: 2022-3-11 6 | authors: 東雲研究所 7 | tags: [css] 8 | keywords: [css] 9 | --- 10 | 11 | 遇到实际开发时经常有这么一个需求,需要实现盒子的宽高比始终喂 2:1 在这种情况下我们可以用纯 css 实现这个需求 12 | 13 | ## 直接通过 css 宽高属性 aspect-ratio 控制 14 | 15 | ```html 16 | <style type="text/css"> 17 | .box { 18 | width: 100%; 19 | aspect-ratio: 2/1; 20 | background: red; 21 | } 22 | </style> 23 | ``` 24 | 25 | ## 上面的方法虽然简单 但是在兼容性上还是稍微欠缺点,在一些古早的项目了我们可以这么写 26 | 27 | ```html 28 | <style type="text/css"> 29 | .box { 30 | width: 100%; 31 | height: 0; 32 | padding-top: 50%; 33 | background: red; 34 | } 35 | </style> 36 | ``` 37 | 38 | 这是因为一个机制,当一个盒子高度为 0 时且 padding-top 的值为百分比时,padding-top 的依赖是我们的宽度 39 | 我们依靠这个机制也能完成2/1 40 | 注意:在用第二种方法时因为box的高度为0 所以box里的文本会偏出box。 41 | -------------------------------------------------------------------------------- /docs/skill/git/git commit规范.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: git-conmit-specification 3 | slug: git-conmit-specification 4 | title: git commit规范 5 | date: 2021-08-31 6 | authors: 東雲研究所 7 | tags: [git, commit] 8 | keywords: [git, commit] 9 | --- 10 | 11 | <!-- truncate --> 12 | 13 | 提交规范主要是为了让开发者提交完整的更新信息,方便查阅。 14 | 15 | 目前最为流行的提交信息规范来自于 Angular 团队。 16 | 17 | 规范中,主要就是要求提交内容要进行分类并填写内容,更为严格的规定是要求标注开发模块,整个语法如下 18 | 19 | ```bash 20 | type(scope?): subject #scope is optional; multiple scopes are supported (current delimiter options: "/", "\" and ",") 21 | ``` 22 | 23 | | type | commit 的类型 | 24 | | ----------- | ---------------------------------------------------------- | 25 | | feat | 新功能、新特性 (feature) | 26 | | fix | 修改 bug | 27 | | perf | 更改代码,以提高性能 | 28 | | refactor | 代码重构(重构,在不影响代码内部行为、功能下的代码修改) | 29 | | docs | 文档修改 (documentation) | 30 | | style | 代码格式修改, 注意不是 css 修改(例如分号修改) | 31 | | test | 测试用例新增、修改 | 32 | | build | 影响项目构建或依赖项修改 | 33 | | revert | 恢复上一次提交 (撤销,版本回退) | 34 | | ci | 持续集成相关文件修改 | 35 | | chore | 其他修改(不在上述类型中的修改),构建过程或辅助工具的变动 | 36 | | release | 发布新版本 | 37 | | workflow | 工作流相关文件修改 | 38 | | improvement | 改进 | 39 | 40 | 以下是一些示例: 41 | 42 | | commit message | 描述 | 43 | | ---------------------------------- | ------------------------- | 44 | | chore: init | 初始化项目 | 45 | | chore: update deps | 更新依赖 | 46 | | chore: wording | 调整文字(措词) | 47 | | chore: fix typos | 修复拼写错误 | 48 | | chore: release v1.0.0 | 发布 1.0.0 版本 | 49 | | fix: icon size | 修复图标大小 | 50 | | fix: value.length -> values.length | value 变量调整为 values | 51 | | feat(blog): add comment section | blog 新增评论部分 | 52 | | feat: support typescript | 新增 typescript 支持 | 53 | | feat: improve xxx types | 改善 xxx 类型 | 54 | | style(component): code | 调整 component 代码样式 | 55 | | refactor: xxx | 重构 xxx | 56 | | perf(utils): random function | 优化 utils 的 random 函数 | 57 | | docs: xxx.md | 添加 xxx.md 文章 | 58 | 59 | 更多示例可以参考主流开源项目的 commit。 60 | 61 | ## 检查 commit 规范 62 | 63 | 要检查 commit message 是否符合要求,可以使用 [commitlint](https://github.com/conventional-changelog/commitlint) 工具,并配合 [husky](https://github.com/typicode/husky) 对每次提交的 commit 进行检查。 64 | 65 | 当然规范不是强求,但 commit message 一定要能简要说明本次代码的改动主要部分,有利于他人与自己后期查看代码记录。 66 | -------------------------------------------------------------------------------- /docs/skill/git/github apps示例.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: github-apps-example 3 | slug: github-apps-example 4 | title: github apps示例 5 | date: 2021-10-01 6 | authors: 東雲研究所 7 | tags: [github, app] 8 | keywords: [github, app] 9 | --- 10 | 11 | <!-- truncate --> 12 | 13 | ### Github Dependabot 14 | 15 | 介绍:[About Dependabot security updates - GitHub Docs](https://docs.github.com/cn/code-security/dependabot/dependabot-security-updates/about-dependabot-security-updates) 16 | 17 | 简单说就是一个能够自动更新项目依赖,确保仓库代码依赖的包和应用程序一直处于最新版本的机器人。 18 | 19 | 将 `dependabot.yml` 配置文件放入仓库的 `.github` 目录中即可开启。当然也可以到 `Insights` => `Dependency graph` => `Dependabot` 中开启。如下图 20 | 21 | ![image-20221001171946879](https://img.xxsoftware.fun/image-20221001171946879.png) 22 | 23 | 然后创建你的配置文件,默认内容如下 24 | 25 | ![image-20221001172149673](https://img.xxsoftware.fun/image-20221001172149673.png) 26 | 27 | 其中要修改 package-ecosystem 配置,也就是包管理器,比如 node 就用 npm,python 就用 pip。可以在 [About Dependabot version updates - GitHub Docs](https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/about-dependabot-version-updates#supported-repositories-and-ecosystems) 中查看。 28 | 29 | 然后配置完毕后,根据时间周期,就会自动检测依赖更新,并创建一个 pull request 请求,仓库拥有者根据实际需要合并即可。 30 | 31 | ### [Stale](https://github.com/marketplace/stale) 32 | 33 | 可在一段时间不活动后关闭废弃的问题。即**关闭长时间未回复的 issues**。 34 | 35 | ### [Imgbot](https://github.com/marketplace/imgbot) 36 | 37 | Imgbot 是一个友好的机器人,可以优化您的图像并节省您的时间。优化的图像意味着在不牺牲质量的情况下减小文件大小。 38 | 39 | ### [giscus](https://github.com/marketplace/giscus) 40 | 41 | 由 GitHub 讨论提供支持的评论系统。让访问者通过 GitHub 在您的网站上发表评论和反应! 42 | 43 | ### [WakaTime](https://github.com/marketplace/wakatime) 44 | 45 | 从编程活动中自动生成的生产力指标、见解和时间跟踪。 46 | 47 | ### [wxwork](https://github.com/marketplace/wxwork-github-webhook) 48 | 49 | Github 企业微信群机器人,无需配置轻松集成 Github 与 企业微信。 50 | -------------------------------------------------------------------------------- /docs/skill/git/github 其他文件.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: github-other 3 | slug: github-other 4 | title: .github 其他文件 5 | date: 2021-10-01 6 | authors: 東雲研究所 7 | tags: [github] 8 | keywords: [github] 9 | --- 10 | 11 | <!-- truncate --> 12 | 13 | ### ISSUE_TEMPLATE 14 | 15 | **issues 默认模版** 16 | 17 | [Configuring issue templates for your repository - GitHub Docs](https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository) 18 | 19 | ### CODEOWNERS 20 | 21 | **仓库代码拥有者。用于合并请求批准** 22 | 23 | ``` 24 | # https://help.github.com/articles/about-codeowners/ 25 | 26 | * @xxsoftware 27 | ``` 28 | 29 | ### FUNDING.yml 30 | 31 | **配置赞助者按钮** 32 | 33 | [在仓库中显示赞助者按钮 - GitHub Docs](https://docs.github.com/cn/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/displaying-a-sponsor-button-in-your-repository#about-funding-files) 34 | 35 | ### CONTRIBUTING.md 36 | 37 | **贡献指南** 38 | -------------------------------------------------------------------------------- /docs/skill/git/git修改默认分支main.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: git-change-default-branch 3 | slug: git-change-default-branch 4 | title: git修改默认分支main 5 | date: 2021-08-04 6 | authors: 東雲研究所 7 | tags: [git] 8 | keywords: [git] 9 | --- 10 | 11 | <!-- truncate --> 12 | 13 | ## 前言 14 | 15 | GitHub 官方表示,从 2020 年 10 月 1 日起,在该平台上创建的所有新的源代码仓库将默认被命名为 "main",而不是原先的"master"。值得注意的是,现有的存储库不会受到此更改影响。 16 | 17 | 也就是现在从 github 初始化的项目都是 main 分支,然而在此之前安装的 git 默认分支为 master,本地使用 git 创建项目都是 master,通过如下命令可更改默认分支的名字 18 | 19 | ## 命令 20 | 21 | - 修改默认分支为 `main` 分支 22 | 23 | ``` 24 | git config --global init.defaultBranch main 25 | ``` 26 | 27 | - 修改当前项目的分支为 `main` 28 | 29 | ``` 30 | git branch -M main 31 | ``` 32 | 33 | 要更改为其他名字 只需把 main 替换即可 34 | 35 | ## 要求 36 | 37 | Git 版本为 **v2.28** 或更高 查看版本 `git --version` 38 | 39 | ## 其他 40 | 41 | #### 禁止忽略大小写 42 | 43 | ``` 44 | git config core.ignorecase false 45 | ``` 46 | -------------------------------------------------------------------------------- /docs/skill/introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: introduction 3 | slug: /skill 4 | title: 技术笔记简介 5 | keywords: 6 | - 前端 7 | - Vue 8 | - React 9 | - JavaScript 10 | - HTTP 11 | --- 12 | 13 | 本页面为个人学习中所涉及相关技术栈的笔记汇总,包含但不限于 14 | 15 | - Web 16 | - 前端 17 | - Vue 18 | - JavaScript(TypeScript) 19 | - HTTP 20 | 21 | **做到即查即用,能复制粘贴解决的,就绝不百度。** 22 | -------------------------------------------------------------------------------- /docs/skill/js/JS变量提升.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: js-variable-promotion 3 | slug: /js-variable-promotion 4 | title: js变量提升 5 | date: 2021-7-21 6 | authors: 東雲研究所 7 | tags: [js, es6] 8 | keywords: [js, es6] 9 | --- 10 | 11 | # JavaScript 变量提升 12 | 13 | 在`JavaScript`中变量声明与函数声明都会被提升到作用域顶部,优先级依次为: 函数声明 变量声明 变量赋值。 14 | 15 | ## 变量提升 16 | 17 | ### var 的变量提升 18 | 19 | ```javascript 20 | console.log(a) // undefined 21 | var a = 1 22 | console.log(a) // 1 23 | // console.log(b); // ReferenceError: b is not defined 24 | ``` 25 | 26 | 为了显示`a`与`b`的区别,打印了一下未声明的变量`b`,其抛出了一个`ReferenceError`异常,而`a`并未抛出异常,其实对`a`的定义并赋值类似于以下的操作,将`a`的声明提升到作用域最顶端,然后再执行赋值操作。 27 | 28 | ```javascript 29 | var a 30 | console.log(a) // undefined 31 | a = 1 32 | console.log(a) // 1 33 | ``` 34 | 35 | ### let/const 的变量提升 36 | 37 | `let`与`const`都具有块级作用域,对于变量提升的相关问题的表现是相同的。实际上关于这个问题目前有所分歧,但在`ES6`的文档中出现了`var/let hoisting`字样,也就是说官方文档说明`let`与`var`一样,都存在变量提升,我觉得能够接受的说法是: 38 | 39 | - `let`的「创建」过程被提升了,但是初始化没有提升。 40 | - `var`的「创建」和「初始化」都被提升了。 41 | - `function`的「创建」「初始化」和「赋值」都被提升了。 42 | 43 | `JS`中无论哪种形式声明`var`、`let`、`const`、`function`、`function*`、`class`都会存在提升现象,不同的是`var`,`function`,`function*`的声明会在提升时进行初始化赋值为`undefined`,因此访问这些变量的时候,不会报`ReferenceError`异常,而使用`let`,`const`,`class`声明的变量,被提升后不会被初始化,这些变量所处的状态被称为`temporal dead zone`,此时如果访问这些变量会抛出`ReferenceError`异常,看上去就像没被提升一样。关于这个问题的讨论,可以参考下边的链接,尤其是在`stackoverflow`中的回答。 44 | 45 | ``` 46 | https://www.jianshu.com/p/0f49c88cf169 47 | https://blog.bitsrc.io/hoisting-in-modern-javascript-let-const-and-var-b290405adfda 48 | https://stackoverflow.com/questions/31219420/are-variables-declared-with-let-or-const-not-hoisted-in-es6 49 | ``` 50 | 51 | ## 函数声明提升 52 | 53 | 函数声明会将声明与赋值都提前,也就是整个函数体都会被提升到作用域顶部。 54 | 55 | ```javascript 56 | s() // 1 57 | function s() { 58 | console.log(1) 59 | } 60 | ``` 61 | 62 | 函数表达式只会提升变量的声明,本质上是变量提升并将一个匿名函数对象赋值给变量。 63 | 64 | ```javascript 65 | console.log(s) // undefined 66 | var s = function s() { 67 | console.log(1) 68 | } 69 | console.log(s) // f s(){console.log(1);} 70 | ``` 71 | 72 | 由此来看,直接进行函数声明与函数表达式声明的函数之间就存在一个优先级关系。 73 | 74 | ```javascript 75 | var s = function () { 76 | console.log(0) 77 | } 78 | function s() { 79 | console.log(1) 80 | } 81 | s() // 0 82 | ``` 83 | 84 | ## 优先级 85 | 86 | 在`JS`中函数是第一等公民,在《你不知道的`JavaScript`》(上卷)一书的第`40`页中写到,函数会首先被提升,然后才是变量。也就是说,同一作用域下提升,函数会在更前面。即在`JS`引擎的执行的优先级是函数声明、变量声明、变量赋值。 87 | 88 | ```javascript 89 | function s() { 90 | //函数声明 91 | console.log(1) 92 | } 93 | 94 | var s // 变量声明 95 | 96 | // 函数已声明`a` 相同的变量名`var`声明会被直接忽略 97 | console.log(s) // f s(){...} 98 | 99 | s = function () { 100 | // 变量赋值 101 | console.log(0) 102 | } 103 | 104 | s() // 0 105 | ``` 106 | -------------------------------------------------------------------------------- /docs/skill/js/JS如何获取当天零点时间戳.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: how-does-js-get-today-zero-timestamp 3 | slug: /how-does-js-get-today-zero-timestamp 4 | title: JS如何获取当天零点时间戳 5 | date: 2021-08-18 6 | authors: 東雲研究所 7 | tags: [javascript] 8 | keywords: [javascript] 9 | --- 10 | 11 | <!-- truncate --> 12 | 13 | ## 需求 14 | 15 | 准备做一个签到系统,所以当天的 0 点就成为了判断是否签到过的关键点,那 Js 又如何获取对应的时间戳呢? 16 | 17 | ## 实现 18 | 19 | 我一开始是这么实现的,利用到的 js 时间库,moment 或者 dayjs 都行,这里选择 dayjs(因为轻量)。 20 | 21 | 代码如下 22 | 23 | ```js 24 | dayjs(dayjs().format('YYYY-MM-DD')).valueOf() 25 | ``` 26 | 27 | moment 的话,只需要将 dayjs 替换成 moment 即可。 28 | 29 | 中间部分取出来的时间为 `“2021-08-18”`,然后再通过 dayjs 转为 Dayjs 对象,并通过 valueOf(),就可获取到当天的零点的时间戳。 30 | 31 | 思路很明确,就是要先获取到当前日期,然后通过日期在转为时间戳即可 32 | 33 | 对应的原生 Js 代码也就很明显了 34 | 35 | ```js 36 | new Date(new Date().toLocaleDateString()).getTime() 37 | ``` 38 | 39 | 但要我选择我依旧毫不犹豫选择使用 js 时间库,一些复杂的时间计算,如时间格式化,计算两者时间秒/天数差,给指定时间增加/减少天数,这些如果使用原生 Js 代码,不如直接使用已有的库,何必造个轮子呢。 40 | 41 | 有关 dayjs 的具体使用就不做介绍了,贴个官方文档,要用的时候查阅一下便可。 42 | 43 | [Day.js · 中文文档 - 2kB 大小的 JavaScript 时间日期库](https://day.js.org/zh-CN/) 44 | -------------------------------------------------------------------------------- /docs/skill/js/JS实现函数缓存.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: js-implement-function-cache 3 | slug: /js-implement-function-cache 4 | title: JS实现函数缓存 5 | date: 2021-11-22 6 | authors: 東雲研究所 7 | tags: [javascript] 8 | keywords: [javascript] 9 | --- 10 | 11 | <!-- truncate --> 12 | 13 | ## 原理 14 | 15 | - 闭包 16 | - 柯里化 17 | 18 | - 高阶函数 19 | 20 | ## 例子:求和 21 | 22 | 正常的循环累加代码 23 | 24 | ```javascript 25 | function add() { 26 | let sum = 0 27 | for (let i = 0; i < arguments.length; i++) { 28 | sum += arguments[i] 29 | } 30 | return sum 31 | } 32 | ``` 33 | 34 | 使用数组的 reduce 方法 35 | 36 | ```javascript 37 | function add() { 38 | var arr = Array.prototype.slice.call(arguments) 39 | return arr.reduce(function (prev, cur) { 40 | return prev + cur 41 | }, 0) 42 | } 43 | ``` 44 | 45 | 但多次传入同样的参数 如 `add(1, 2, 3)` 都将执行运算对应的次数,将会耗费一定的性能。 46 | 47 | ### 使用函数缓存 48 | 49 | 使用闭包,将每次运算的参数与结果存入置 cache 对象中,如果 cache 中有,便直接获取,来达到缓存的目的 50 | 51 | ```javascript 52 | let add = (function () { 53 | let cache = {} 54 | 55 | return function () { 56 | let args = Array.prototype.join.call(arguments, ',') 57 | if (cache[args]) { 58 | return cache[args] 59 | } 60 | let sum = 0 61 | for (let i = 0; i < arguments.length; i++) { 62 | sum += arguments[i] 63 | } 64 | return (cache[args] = sum) 65 | } 66 | })() 67 | 68 | add(1, 2, 3) // 输出6 69 | add(1, 2, 3) // 直接从cache中获取 70 | ``` 71 | 72 | 已经达到缓存的目的了,但这时我想将乘法也想实现缓存的目的,那么又得写一大行这样的代码,同时原本求和的代码又想单独分离出来,就可以使用代理模式,具体演示如下 73 | 74 | ### 代理模式 75 | 76 | #### 创建缓存代理的工厂 77 | 78 | ```javascript 79 | let memoize = function (fn) { 80 | let cache = {} 81 | return function () { 82 | let args = Array.prototype.join.call(arguments, ',') 83 | if (args in cache) { 84 | return cache[args] 85 | } 86 | return (cache[args] = fn.apply(this.arguments)) 87 | } 88 | } 89 | ``` 90 | 91 | 那么通过`memoize` 就能将函数运行后的结果给缓存起来,如 92 | 93 | ```javascript 94 | let add1 = memoize(add) 95 | 96 | add1(1, 2, 3) // 输出6 97 | add1(1, 2, 3) // 直接从cache中获取 98 | ``` 99 | 100 | 我们只需要编写我们正常的业务逻辑(加法,乘法等),然后通过 memoize 调用 便可达到缓存的目的 101 | 102 | 同理乘法 103 | 104 | ```javascript 105 | function mult() { 106 | let a = 0 107 | for (let i = 0; i < arguments.length; i++) { 108 | a *= arguments[i] 109 | } 110 | return a 111 | } 112 | 113 | let mult1 = memoize(mult) 114 | 115 | mult1(1, 2, 3) // 输出6 116 | mult1(1, 2, 3) // 直接从cache中获取 117 | ``` 118 | -------------------------------------------------------------------------------- /docs/skill/js/JS数组对象去重.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: js-array-object-unique 3 | slug: /js-array-object-unique 4 | title: JS数组对象去重 5 | date: 2021-07-05 6 | authors: 東雲研究所 7 | tags: [javascript] 8 | keywords: [javascript] 9 | --- 10 | 11 | <!-- truncate --> 12 | 13 | 参考 [数组对象去重](https://www.nodejs.red/#/javascript/base?id=数组去重的三种实现方式) 14 | 15 | 数据如下: 16 | 17 | ```js 18 | [{ name: 'zs', age: 15 }, { name: 'lisi' }, { name: 'zs' }] 19 | ``` 20 | 21 | 想要将 name 为 zs 的数据去重,优先保留第一条相同数据 22 | 23 | ## 解决方法 24 | 25 | ### reduce 去重 26 | 27 | ```js 28 | let hash = {} 29 | 30 | function unique(arr, initialValue) { 31 | return arr.reduce(function (previousValue, currentValue, index, array) { 32 | hash[currentValue.name] ? '' : (hash[currentValue.name] = true && previousValue.push(currentValue)) 33 | 34 | return previousValue 35 | }, initialValue) 36 | } 37 | 38 | const uniqueArr = unique([{ name: 'zs', age: 15 }, { name: 'lisi' }, { name: 'zs' }], []) 39 | 40 | console.log(uniqueArr) // uniqueArr.length == 2 41 | ``` 42 | 43 | ### lodash 工具库去重 44 | 45 | [Lodash Documentation](https://lodash.com/docs/4.17.15#uniqBy) 46 | 47 | ```js 48 | _.uniqBy([{ x: 1 }, { x: 2 }, { x: 1 }], 'x') 49 | 50 | // => [{ 'x': 1 }, { 'x': 2 }] 51 | 52 | // 指定条件 53 | _.uniqBy([2.1, 1.2, 2.3], Math.floor) 54 | // => [2.1, 1.2] 55 | ``` 56 | 57 | 想要所有对象属性都一样才去重也简单 58 | 59 | ```js 60 | var objects = [ 61 | { x: 1, y: 2 }, 62 | { x: 2, y: 1 }, 63 | { x: 1, y: 2 }, 64 | ] 65 | 66 | _.uniqWith(objects, _.isEqual) 67 | // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }] 68 | ``` 69 | -------------------------------------------------------------------------------- /docs/skill/node/axios请求gbk页面乱码解决.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: axios-request-gbk-page-encoding-solution 3 | slug: /axios-request-gbk-page-encoding-solution 4 | title: axios请求gbk页面乱码解决 5 | date: 2021-09-19 6 | authors: 東雲研究所 7 | tags: [node, axios, encode] 8 | keywords: [node, axios, encode] 9 | --- 10 | 11 | <!-- truncate --> 12 | 13 | 使用 axios 请求 gbk 编码的网站,将会出现乱码,原因很简单,node 默认字符编码为 utf8,如果要正常显示 gbk 数据的话就需要将 gbk 转 utf8 格式。 14 | 15 | ## 解决办法 16 | 17 | 借助`iconv-lite`,不让 axios 自动处理响应数据,添加`responseType`和`transformResponse`参数,演示代码如下 18 | 19 | ```js 20 | import axios from 'axios' 21 | import * as iconv from 'iconv-lite' 22 | 23 | axios 24 | .get(`https://www.ip138.com/`, { 25 | responseType: 'arraybuffer', 26 | transformResponse: [ 27 | function (data) { 28 | return iconv.decode(data, 'gbk') 29 | }, 30 | ], 31 | }) 32 | .then((res) => { 33 | console.log(res.data) 34 | }) 35 | ``` 36 | 37 | 或者不使用`transformResponse`,在响应结束后使用`iconv.decode(res.data, 'gbk')`,使用`transformResponse`相对优雅一点。 38 | 39 | 如果返回的是 json 格式的话,可以直接`JSON.parse`转为 json 对象(前提得确保是 json 格式,不然解析报错) 40 | 41 | ```js 42 | return JSON.parse(iconv.decode(data, 'gbk')) 43 | ``` 44 | -------------------------------------------------------------------------------- /docs/skill/node/npm镜像配置.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: npm-mirror-config 3 | slug: /npm-mirror-config 4 | title: npm镜像配置 5 | date: 2022-03-17 6 | authors: 東雲研究所 7 | tags: [node, npm, electron] 8 | keywords: [node, npm, electron] 9 | --- 10 | 11 | <!-- truncate --> 12 | 13 | 由于原淘宝 npm 域名(**http://npm.taobao.org 和 http://registry.npm.taobao.org**)将于 **2022.06.30 号正式下线和停止 DNS 解析**,不妨提前修改镜像的地址,以免受到影响。 14 | 15 | 域名切换规则: 16 | 17 | - http://npm.taobao.org => http://npmmirror.com 18 | - http://registry.npm.taobao.org => http://registry.npmmirror.com 19 | 20 | 同时不推荐使用镜像下载依赖,因为有可能会导致与官方包不同步(亲测,就因为下载依赖折腾了一晚上,还以为是电脑问题),但有时候开启科学上网(或者没有),下载也不见得特别快,所以这时候才会使用国内镜像。 21 | 22 | ## 镜像站点 23 | 24 | [npmmirror 中国镜像站](https://www.npmmirror.com/) 25 | 26 | http://registry.npmjs.org 27 | 28 | ## 单次使用镜像 29 | 30 | ```sh 31 | npm install [name] --registry=https://registry.npmmirror.com 32 | ``` 33 | 34 | ## 永久配置镜像 35 | 36 | ```sh 37 | npm config set registry https://registry.npmmirror.com 38 | ``` 39 | 40 | ## 查看镜像 41 | 42 | ``` 43 | npm get registry 44 | ``` 45 | 46 | ## nrm镜像管理工具 47 | 48 | ``` 49 | npm install nrm -g 50 | ``` 51 | 52 | ### nrm ls 查看所有镜像 53 | 54 | ``` 55 | npm ---------- https://registry.npmjs.org/ 56 | yarn --------- https://registry.yarnpkg.com/ 57 | tencent ------ https://mirrors.cloud.tencent.com/npm/ 58 | cnpm --------- https://r.cnpmjs.org/ 59 | taobao ------- https://registry.npmmirror.com/ 60 | npmMirror ---- https://skimdb.npmjs.com/registry/ 61 | ``` 62 | 63 | ### nrm use 镜像 切换镜像 64 | 65 | ``` 66 | nrm use taobao 67 | ``` 68 | 69 | ## 清除 npm 缓存 70 | 71 | ```sh 72 | npm cache clean --force 73 | ``` 74 | 75 | ## 配置 electron 镜像 76 | 77 | ```sh 78 | npm config set ELECTRON_MIRROR https://npmmirror.com/mirrors/electron/ 79 | 80 | npm config set ELECTRON_BUILDER_BINARIES_MIRROR https://npmmirror.com/mirrors/electron-builder-binaries/ 81 | ``` 82 | -------------------------------------------------------------------------------- /docs/skill/node/使用 require.context 实现模块自动导入.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: use-require.context-to-auto-import-modules 3 | slug: /use-require.context-to-auto-import-modules 4 | title: 使用 require.context 实现模块自动导入 5 | date: 2021-09-12 6 | authors: 東雲研究所 7 | tags: [node, webpack] 8 | keywords: [node, webpack] 9 | --- 10 | 11 | <!-- truncate --> 12 | 13 | ## 前言 14 | 15 | 在写资源导航的时候,我在将资源分类为一个文件的时候,发现如果我每定义一个分类,那我就需要创建一个文件,然后又要通过`import form`导入,就很烦躁。 16 | 17 | 突然想到貌似 vue-element-admin 中的路由好像也是这样的,而 store 貌似定义完就无需再次导入,于是就开始研究代码,果不其然,发现了`require.context` 18 | 19 | [依赖管理 | webpack 中文文档 (docschina.org)](https://webpack.docschina.org/guides/dependency-management/) 20 | 21 | ## 实现 22 | 23 | require.context:是一个 webpack 提供的 api,通过执行 require.context 函数遍历获取到指定文件夹(及其下子文件夹)内的指定文件,然后自动导入。 24 | 25 | 语法:`require.context(directory, useSubdirectories = false, regExp = /^.//)` 26 | 27 | - directory 指定文件 28 | - useSubdirectories 是否遍历目录的子目录 29 | - regExp 匹配文件的正则表达式,即文件类型 30 | 31 | 而上图代码中对应的代码也明确表达要指定`./modules`目录下的,所有 js 文件 32 | 33 | ```js 34 | const modulesFiles = require.context('./modules', true, /\.js$/) 35 | ``` 36 | 37 | 输出一下看看 modulesFiles 到底是什么(console.dir 输出) 38 | 39 | 返回一个函数,但该函数包含三个属性 resolve()、keys()、id 40 | 41 | 其中`modulesFiles.keys()`则是指定目录下文件名数组 42 | 43 | ``` 44 |  ['./app.js', './permission.js','./settings.js', './tagsView.js', './user.js'] 45 | ``` 46 | 47 | 接着看下 vue-element-admin 中的下一行代码 48 | 49 | ```js 50 | const modules = modulesFiles.keys().reduce((modules, modulePath) => { 51 | // set './app.js' => 'app' 52 | const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1') 53 | const value = modulesFiles(modulePath) 54 | modules[moduleName] = value.default 55 | return modules 56 | }, {}) 57 | ``` 58 | 59 | 这边先输出一下 modules,看下结果是什么 60 | 61 | 没错,正对应着 modules 下的所有文件,以及所导出的对象 62 | 63 | 其中在循环体中还调用了`const value = modulesFiles(modulePath)`,其中 value 是 Module 对象,有个属性`default`,通过`value.default`便可获取到对应模块所导出的内容。 64 | 65 | 就此便可以实现自动导入模块。不过由于导出的是 store 对象,所封装的代码也有点过于复杂,这边我贴下我是如何自动导入数组对象的 66 | 67 | ```typescript 68 | const modulesFiles = require.context('./modules', true, /\.ts$/) 69 | 70 | let allData: any[] = [] 71 | 72 | modulesFiles.keys().forEach(modulePath => { 73 | const value = modulesFiles(modulePath) 74 | let data = value.default 75 | 76 | if (!data) return 77 | allData.push(...value.default) 78 | }) 79 | ``` 80 | 81 | ## 参考链接 82 | 83 | > [前端优化之 -- 使用 require.context 让项目实现路由自动导入 - 沐浴点阳光 - 博客园 (cnblogs.com)](https://www.cnblogs.com/garfieldzhong/p/12585280.html) 84 | -------------------------------------------------------------------------------- /docs/skill/vue/$router和$route的区别.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: $router-and-$route 3 | slug: /$router-and-$route 4 | title: $router和$route 的区别 5 | date: 2021-8-13 6 | authors: 東雲研究所 7 | tags: [vue, router] 8 | keywords: [vue, router] 9 | --- 10 | 11 | # $router和$route 的区别 12 | 13 | `Vue Router`是`Vue.js`的路由管理器,路由就是`SPA`单页应用的访问路径,在`Vue`实例内部,可以通过`$router`访问路由实例,即在路由定义文件中`export default`的`new Router(/*...*/)`路由实例,通过`$route`可以访问当前激活的路由的状态信息,包含了当前`URL`解析得到的信息,还有`URL`匹配到的路由记录,可以将`$router`理解为一个容器去管理了一组`$route`,而`$route`是进行了当前`URL`和组件的映射。 14 | 15 | ## $router 对象属性 16 | 17 | - `$router.app`: 配置了`router`的`Vue`根实例。 18 | - `$router.mode`: 路由使用的模式。 19 | - `$router.currentRoute`: 当前路由对应的路由信息对象。 20 | 21 | ## $router 对象方法 22 | 23 | - `$router.beforeEach(to, from, next)`: 全局前置守卫,守卫是异步解析执行,此时导航在所有守卫`resolve`完之前一直处于等待中状态,守卫方法接收三个参数: `to: Route`即将要进入的目标路由对象、`from: Route`: 当前导航正要离开的路由、`next: Function`: 调用该方法来`resolve`这个钩子,执行效果依赖`next`方法的调用参数,例如`next()`、`next(false)`、`next('/')`、`next({path:'/',name:'home',replace:true,query:{q:1}})`、`next(error)`等,通常在`main.js`中`import`的`Vue Router`实例中直接定义导航守卫,当然也可在`Vue`实例中访问`$router`来定义。 24 | - `$router.beforeResolve(to, from, next)`: 全局解析守卫,在`beforeRouteEnter`调用之后调用,同样接收`to`、`from`、`next`三个参数。 25 | - `$router.afterEach(to, from)`: 全局后置钩子,进入路由之后调用,接收`to`、`from`两个参数。 26 | - `$router.push(location[, onComplete[, onAbort]])`: 编程式导航,使用`$router.push`方法导航到不同的`URL`,此方法会向`history`栈添加一个新的记录,当点击浏览器后退按钮时,则回到之前的`URL`。 27 | - `$router.replace(location[, onComplete[, onAbort]])`: 编程式导航,跟`$router.push`很像,唯一的不同就是,其不会向`history`添加新记录,而是跟它的方法名一样替换掉当前的`history`记录。 28 | - `$router.go(n)`: 编程式导航,这个方法的参数是一个整数,意思是在`history`记录中向前或者后退多少步,类似`window.history.go(n)`。 29 | - `$router.back()`: 编程式导航,后退一步记录,等同于`$router.go(-1)`。 30 | - `$history.forward()`: 编程式导航,前进一步记录,等同于`$router.go(1)`。 31 | - `$router.getMatchedComponents([location])`: 返回目标位置或是当前路由匹配的组件数组 ,是数组的定义或构造类,不是实例,通常在服务端渲染的数据预加载时使用。 32 | - `$router.resolve(location[, current[, append]])`: 解析目标位置,格式和`<router-link>`的`to prop`相同,`current`是当前默认的路由,`append`允许在`current`路由上附加路径,如同 `router-link`。 33 | - `$router.addRoutes(route)`: 动态添加更多的路由规则,参数必须是一个符合`routes`选项要求的数组。 34 | - `$router.onReady(callback[, errorCallback])`: 该方法把一个回调排队,在路由完成初始导航时调用,这意味着它可以解析所有的异步进入钩子和路由初始化相关联的异步组件,这可以有效确保服务端渲染时服务端和客户端输出的一致,第二个参数`errorCallback`会在初始化路由解析运行出错时被调用。 35 | - `$router.onError(callback)`: 注册一个回调,该回调会在路由导航过程中出错时被调用,被调用的错误必须是下列情形中的一种,错误在一个路由守卫函数中被同步抛出、错误在一个路由守卫函数中通过调用`next(err)`的方式异步捕获并处理、渲染一个路由的过程中需要尝试解析一个异步组件时发生错误。 36 | 37 | ## $route 对象属性 38 | 39 | - `$route.path`: 返回字符串,对应当前路由的路径,总是解析为绝对路径。 40 | - `$route.params`: 返回一个`key-value`对象,包含了动态片段和全匹配片段,如果没有路由参数,就是一个空对象。 41 | - `$route.query`: 返回一个`key-value`对象,表示`URL`查询参数。 42 | - `$route.hash`: 返回当前路由的带`#`的`hash`值,如果没有`hash`值,则为空字符串。 43 | - `$route.fullPath`: 返回完成解析后的`URL`,包含查询参数和`hash`的完整路径。 44 | - `$route.matched`: 返回一个数组,包含当前路由的所有嵌套路径片段的路由记录,路由记录就是`routes`配置数组中的对象副本。 45 | - `$route.name`: 如果存在当前路由名称则返回当前路由的名称。 46 | - `$route.redirectedFrom`: 如果存在重定向,即为重定向来源的路由的名字。 47 | -------------------------------------------------------------------------------- /docs/skill/vue/customRef实现敏感词替换.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: customRef 3 | slug: /customRef 4 | title: customRef实现敏感词替换 5 | date: 2022-5-13 6 | authors: 東雲研究所 7 | tags: [vue, customRef] 8 | keywords: [vue, customRef] 9 | --- 10 | 11 | customRef可以帮助在输入内容时遇到敏感词汇,做一个*的替换。 12 | 13 | ## 思路 14 | 15 | 1.字符串的替换 str.replace(, 'X') 16 | 2.维护白名单列表 const list = ["sb","SB"] 17 | 3.在输入的过程中需要不断监听输入值是否触发机制,如果触发则替换 customRef 18 | 19 | ## 实现 20 | 21 | ```vue title="demo.vue" 22 | <script setup lang="ts"> 23 | // 字符串替换函数 24 | import { customRef } from 'vue' 25 | function replaceStr(str) { 26 | const list = ['sb', 'SB'] 27 | list.forEach(item => { 28 | str = str.replace(item, '**') 29 | }) 30 | return str 31 | } 32 | 33 | function useReplaceRef(value) { 34 | return customRef((track, trigger) => { 35 | return { 36 | get() { 37 | track() 38 | return value 39 | }, 40 | set(newValue) { 41 | // 赋值 42 | value = replaceStr(newValue) 43 | trigger() 44 | }, 45 | } 46 | }) 47 | } 48 | const msg = useReplaceRef('I am a string') 49 | // v-model没法中间拦截 我们可以使用customRef 50 | </script> 51 | 52 | <template> 53 | <div> 54 | <input type="text" v-model="msg" /> 55 | </div> 56 | </template> 57 | ``` 58 | 59 | 当然这是很简单的功能用 computed 之类的都行,只是因为 customRef 是个低频的 api,这里写个笔记,而且customRef更容易抽离出来做复用 60 | -------------------------------------------------------------------------------- /docs/skill/vue/v-if与v-show的区别.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: vif-and-vshow 3 | slug: /vif-and-vshow 4 | title: v-if 与 v-show 的区别 5 | date: 2021-8-23 6 | authors: 東雲研究所 7 | tags: [vue, v-if, v-show] 8 | keywords: [vue, v-if, v-show] 9 | --- 10 | 11 | # v-if 与 v-show 的区别 12 | 13 | `v-if`指令与`v-show`指令都可以根据值动态控制`DOM`元素显示隐藏,`v-if`和`v-show`属于`Vue`的内部常用的指令,指令的职责是当表达式的值改变时把某些特殊的行为应用到`DOM`上。 14 | 15 | ## 描述 16 | 17 | ### v-if 18 | 19 | `v-if`指令用于条件性地渲染一块内容,这块内容只会在指令的表达式返回`truthy`值的时候被渲染。 20 | 21 | ```html 22 | <div v-if="show">show</div> 23 | <div v-else>hide</div> 24 | ``` 25 | 26 | ### v-show 27 | 28 | `v-show`指令用法大致一样,不同的是带有`v-show`指令的元素始终会被渲染并保留在`DOM`中,`v-show`只是简单地切换元素的`CSS property display`。 29 | 30 | ```html 31 | <div v-show="show">show</div> 32 | ``` 33 | 34 | ## 区别 35 | 36 | - 实现方式: `v-if`是动态的向`DOM`树内添加或者删除`DOM`元素,`v-show`是通过设置`DOM`元素的`display`样式属性控制显隐。 37 | - 编译过程: `v-if`切换有一个局部编译卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件,`v-show`只是简单的基于`CSS`切换。 38 | - 编译条件: `v-if`是惰性的,如果初始条件为假,则什么也不做,只有在条件第一次变为真时才开始局部编译, `v-show`是在任何条件下都被编译,然后被缓存,而且`DOM`元素保留。 39 | - 性能消耗: `v-if`有更高的切换消耗,`v-show`有更高的初始渲染消耗。 40 | - 使用场景: `v-if`适合条件不太可能改变的情况,`v-show`适合条件频繁切换的情况。 41 | -------------------------------------------------------------------------------- /docs/skill/vue/使用ShallowRef对接Redux到Vue.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: shallowRef 3 | slug: /shallowRef 4 | title: 使用ShallowRef对接Redux到Vue 5 | date: 2022-11-23 6 | authors: 東雲研究所 7 | tags: [vue, shallowRef, Redux] 8 | keywords: [vue, shallowRef, Redux] 9 | --- 10 | 11 | > 官方文档:(https://cn.vuejs.org/api/reactivity-advanced.html#shallowref) 12 | 13 | shallowRef()​是ref() 的浅层作用形式。 14 | ref会不断递归把深层转成响应式,而shallowRef只会对第一层也就是value进行响应式转换。 15 | shallowRef() 常常用于对大型数据结构的性能优化或是与外部的状态管理系统集成。 16 | 将 Vue 的响应性系统与外部状态管理方案集成的大致思路是:将外部状态放在一个 shallowRef 中。一个浅层的 ref 中只有它的 .value 属性本身被访问时才是有响应性的,而不关心它内部的值。当外部状态改变时,替换此 ref 的 .value 才会触发更新。 17 | 18 | ```javascript title="src/store/index.js" 19 | import { shallowRef } from 'vue' 20 | 21 | import { createSlice, configureStore } from '@reduxjs/toolkit' 22 | // 创建counterSlice 23 | export const counterSlice = createSlice({ 24 | name: 'counter', 25 | initialState: { 26 | count: 0, 27 | }, 28 | reducers: { 29 | increment: state => { 30 | state.count++ 31 | }, 32 | }, 33 | }) 34 | // 导出ActionCreater 35 | const { increment } = counterSlice.actions 36 | // 生成store实例 37 | const store = configureStore({ 38 | reducer: { 39 | counter: counterSlice.reducer, 40 | }, 41 | }) 42 | 43 | const refState = shallowRef(store.getState()) 44 | // 订阅状态变化 45 | store.subscribe(() => { 46 | console.log('状态改变', store.getState()) 47 | // 给refState赋值,进行整体覆盖 48 | refState.value = store.getState() 49 | }) 50 | // 导出store实例和actionCreater 51 | export { store, increment, refState } 52 | ``` 53 | 54 | redux 这边的工作就完成了,因为我平时也不用 redux,如果有问题可以查询官网 55 | 56 | ```Vue title="app.vue" 57 | <script setup> 58 | import {store,increment,refState} from "./store" 59 | </script> 60 | <template> 61 | <div> 62 | <button @click ="store.dispatch(increment())">{{refState.counter.count}}</button> 63 | </div> 64 | </template> 65 | ``` 66 | -------------------------------------------------------------------------------- /docs/skill/vue/图片懒加载指令.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: v-lazyload 3 | slug: /v-lazyload 4 | title: 图片懒加载指令 5 | date: 2022-6-27 6 | authors: 東雲研究所 7 | tags: [vue, lazyload] 8 | keywords: [vue, lazyload] 9 | --- 10 | 11 | 图片懒加载可以一定程度上减少网络请求的发送,算是一种网络请求的优化,我们今天来实现图片懒加载的vue指令 12 | 13 | ## 思路 14 | 15 | 1.判断图片是否进入视口区域 只有进入视口区域才发送请求 16 | 查询是否进入视口区域有两种办法 一种是传统的判断距离 还有一种是使用VueUse提供的方法简便又准确 17 | 2.img.src = url 18 | 19 | ## 实现 20 | 21 | ```javascript title="imgLazyload.js" 22 | import { useIntersectionObserver } from '@vueuse/core' 23 | 24 | export default { 25 | mounted(el, binding) { 26 | // el:指令挂载到的元素 27 | // binding可以拿到指令表达式的值 在这里就是图片URL V-imgLazyload = "url" 28 | const { stop } = useIntersectionObserver( 29 | el, 30 | ([{ isIntersecting }], observerElement) => { 31 | if (isIntersecting) { 32 | el.src = binding.value 33 | // 加载图片之后停止监听 34 | stop() 35 | } 36 | }, 37 | ) 38 | }, 39 | } 40 | ``` 41 | 42 | 但是实际使用中存在 root margin 无法生效的问题。看 ElementPlus 的源码发现了一种更通用的懒加载实现方案:判断图片是否进入最近的滚动容器的视口,在判断是否进入的时候可以设置 margin。 43 | 所以第二种不使用useIntersectionObserver 44 | 45 | ```js title="imgLazyload.js" 46 | import { useThrottleFn, useEventListener } from '@vueuse/core' 47 | 48 | function isScroll(el) { 49 | let overflow = el.style['overflow'] 50 | 51 | if (!overflow) { 52 | overflow = document.defaultView.getComputedStyle(el)['overflow'] 53 | } 54 | 55 | return ['auto', 'scroll', 'overlay'].some(v => v === overflow) 56 | } 57 | 58 | function getScrollContainer(el) { 59 | let parent = el 60 | 61 | while (parent) { 62 | if ([window, document, document.documentElement].includes(parent)) { 63 | return window 64 | } 65 | 66 | if (isScroll(parent)) { 67 | return parent 68 | } 69 | 70 | parent = parent.parentElement 71 | } 72 | 73 | return parent 74 | } 75 | 76 | function isInContainer(el, container, marginTop = 0) { 77 | let rect 78 | const { top, left, right, bottom } = el.getBoundingClientRect() 79 | if (container instanceof Element) { 80 | rect = container.getBoundingClientRect() 81 | } else { 82 | rect = { 83 | top: 0, 84 | left: 0, 85 | bottom: window.innerHeight, 86 | right: window.innerWidth, 87 | } 88 | } 89 | 90 | return ( 91 | top - marginTop < rect.bottom && 92 | bottom > rect.top && 93 | left < rect.right && 94 | right > rect.left 95 | ) 96 | } 97 | 98 | export default { 99 | mounted(el, bindings) { 100 | const { top, src } = bindings.value 101 | 102 | const container = getScrollContainer(el) 103 | 104 | const loadImageHandler = () => { 105 | if (isInContainer(el, container, parseInt(top) || 0)) { 106 | el.setAttribute('src', src) 107 | stopListener() 108 | } 109 | } 110 | 111 | const stopListener = useEventListener( 112 | container, 113 | 'scroll', 114 | useThrottleFn(loadImageHandler), 115 | ) 116 | 117 | setTimeout(loadImageHandler, 50) 118 | }, 119 | } 120 | ``` 121 | -------------------------------------------------------------------------------- /docs/skill/vue/异步组件实现按需加载.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: defineAsyncComponent 3 | slug: /defineAsyncComponent 4 | title: 异步组件实现按需加载 5 | date: 2022-7-2 6 | authors: 東雲研究所 7 | tags: [vue, defineAsyncComponent] 8 | keywords: [vue, defineAsyncComponent] 9 | --- 10 | 11 | 有这么一个需求:下面的组件在页面初始化时不渲染,而是当用户正式浏览到的时候才进行渲染,我们可以使用defineAsyncComponent这个api。 12 | 13 | ## 实现 14 | 15 | ```vue title="App.vue" 16 | <script setup lang="ts"> 17 | import AsyncCom from '~/AsyncCom/index.vue' 18 | import { defineAsyncComponent, ref } from 'vue' 19 | import { useIntersectionObserver } from '@vueuse/core' 20 | // 异步加载组件 21 | const AsyncComp = defineAsyncComponent(() => import('./components/MyCom.vue')) 22 | // show就是trigger 23 | const show = ref(false) 24 | const AsyncTarget = ref(null) 25 | // 判断是否是视口区域 如果是则切换show 26 | 27 | onMounted(() => { 28 | // 如果前面只有图片的话 最好给container加个高度或者img.onload再回调下面的函数 29 | const { stop } = useIntersectionObserver( 30 | AsyncTarget, 31 | ([{ isIntersecting }], observerElement) => { 32 | if (isIntersecting) { 33 | show.value = true 34 | stop() 35 | } 36 | }, 37 | ) 38 | }) 39 | </script> 40 | 41 | <template> 42 | <div class="container"> 43 | <img v-for="item in 4" src="xxx.jpg" /> 44 | </div> 45 | <div class="AsyncTarget" ref="AsyncTarget"> 46 | <AsyncCom v-if="show" /> 47 | </div> 48 | </template> 49 | ``` 50 | -------------------------------------------------------------------------------- /docs/skill/web/跨域问题.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: cross-domain 3 | slug: /cross-domain 4 | title: 跨域问题 5 | date: 2021-10-07 6 | authors: 東雲研究所 7 | tags: [http, browser] 8 | keywords: [http, browser] 9 | --- 10 | 11 | # 跨域 12 | 13 | 制定`HTML`规则时,出于安全的考虑,一个源的网站不允许与另一个源的资源进行交互,浏览器制定此规则为**同源策略**。 14 | 15 | 同源即指的网站具有相同的域,即 **协议(protocol)、主机(host)、端口号(port)** 相同。 16 | 17 | 跨域资源嵌入是允许的,但是浏览器限制了`Javascript`不能与加载的内容进行交互,如嵌入的`<script>、<img>、<link>、<iframe>`等。 18 | 19 | ## 受限的场景 20 | 21 | - `XHR`请求不能发送。 22 | - 无法对跨域请求的资源进行修改。 23 | - 不同源的`Cookie`、`LocalStorage`无法读取。 24 | 25 | ## 跨域解决方案 26 | 27 | ### JSONP 跨域请求数据 28 | 29 | 由于`<script>`可以对跨域资源进行请求,于是可以对`DOM`动态地`append`一个`<script>`并添加`src`且携带一个`callback`函数名,待请求完成后调用`callback`。 30 | 31 | ```javascript 32 | //前端 33 | function jsonpHandle(data) { 34 | console.log(data) 35 | } //首先定义函数,请求完成后会携带参数调用函数 36 | var url = 'http://127.0.0.1/test.php?callback=jsonpHandle' 37 | var obj = $('<script></script>') 38 | obj.attr('src', url) 39 | $('body').append(obj) // 动态地添加一个script 40 | ``` 41 | 42 | ```php 43 | // 后端配合实现 44 | $data = ["a" => 1, "b" => 2]; 45 | $callback = $_GET['callback']; 46 | return $callback."(".json_encode($data).")"; 47 | ``` 48 | 49 | ### CORS 跨域 50 | 51 | 对于简单请求,浏览器会直接发送`CORS`请求,具体说来就是在`header`中加入`origin`请求头字段。同样,在响应头中,返回服务器设置的相关`CORS`头部字段,`Access-Control-Allow-Origin`字段为允许跨域请求的源。请求时浏览器在请求头的`Origin`中说明请求的源,服务器收到后发现允许该源跨域请求,则会成功返回。 52 | 对于非简单请求,浏览器会自动先发送一个`options`请求,如果发现服务器支持该请求,则会将真正的请求发送到后端,反之,如果浏览器发现服务端并不支持该请求,则会在控制台抛出错误。 53 | 54 | ```php 55 | //响应头 Response Headers 56 | header('Content-Type: text/html;charset=utf-8'); 57 | header('Access-Control-Allow-Origin:http://localhost:8080'); // *代表允许任何网址请求 58 | header('Access-Control-Allow-Methods:POST,GET'); // 允许请求的类型 59 | header('Access-Control-Allow-Credentials: true'); // 设置是否允许发送 cookies 60 | header('Access-Control-Allow-Headers: Content-Type,Origin,Refer'); // 允许自定义请求头的字段 61 | ``` 62 | 63 | ### Nginx 代理 64 | 65 | 通过代理的手段,监听同一端口添加不同路径实现不同服务的跨域访问。 66 | 67 | ``` 68 | location /test 69 | { 70 | proxy_pass http://127.0.0.1:81; 71 | } 72 | ``` 73 | 74 | ### 图片 ping 75 | 76 | 直接新建一个`<img>`,然后在地址中存放一些简单数据,这种方法只支持`get`请求,且只能单向地向服务器发送请求,在统计广告曝光次数中比较常见,`XSS`攻击也常用其获取`cookie`。 77 | 78 | ``` 79 | <img src="http://127.0.0.1?key=value"> 80 | ``` 81 | 82 | ### 相同主域 document.domain 83 | 84 | 例如对于`www.example.com`与`abc.example.com`,其主域名是一样的。 85 | 86 | ```javascript 87 | document.domain = 'example.com' //相同主域 88 | var ifrWin = document.getElementById('ifr').contentWindow //可以操作iframe 89 | ``` 90 | 91 | ### window.name 共享数据 92 | 93 | 不同域的`iframe`把共享的信息放在`window.name`里面,此方法只适用于两个`iframe`之间的跨域。 94 | 95 | ```javascript 96 | window.name = JSON.stringify({ a: 1, b: 2 }) 97 | ``` 98 | 99 | ### window.postMessage 100 | 101 | 使用`window.postMessage`来向其它的`window`对象发送消息,无论这个`window`对象是属于同源或不同源,这种方法不能和服务端交换数据。 102 | 103 | ```javascript 104 | //主window 105 | window.frames[0].postMessage({ a: 1 }, 'http://127.0.0.1:81') 106 | //iframe //出于安全考虑验证来源 107 | window.addEventListener('message', event => { 108 | if (event.origin === 'http://127.0.0.1') console.log(event.data) 109 | }) 110 | ``` 111 | -------------------------------------------------------------------------------- /docs/tools/Everything快速搜索本地文件.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: everything-quick-search-local-files 3 | slug: /everything-quick-search-local-files 4 | title: Everything快速搜索本地文件 5 | date: 2020-09-08 6 | authors: 東雲研究所 7 | tags: [工具] 8 | keywords: [工具] 9 | --- 10 | 11 | ![everything-Everything](https://img.xxsoftware.fun/everything-Everything.jpg) 12 | 13 | 你有没有经历过为了找一个**本地**文件,反复点开一个个文件夹,甚至有可能还找不到,于是开始 Windows 自带的搜索引擎去搜索想要的文件,然后等你吃完饭回来,搜索的进度条竟然还没有走完。而现在有一个软件,它可以让你不必等待,一个眨眼的功夫,你想要的文件就呈现在你的面前,它就是`Everything` 14 | 15 | 软件下载地址 [点我下载](https://www.voidtools.com/zh-cn/) 一路下一步,默认选项即可。 16 | 17 | ## 软件说明 18 | 19 | 可以说`Everything`是速度最快的文件搜索软件,可以瞬间搜索到你所需要的文件,并且页面简洁,体积仅有几兆,可以说你想要的优点都有了。内存占用也不过 200m(我本地文件很多),单丝毫不影响它搜索的速度。 20 | 21 | ## 使用方法 22 | 23 | ### 设置打开快捷键 24 | 25 | 请设置一个快捷键用于快捷的打开`Everything`,有时候你需要搜索一个文件时,不必在找到`Everything`的图标打开,只需要按下所设置的快捷键,这里我设置为`Ctrl+Shift+Alt+Q`,一是避免其他键冲突,二是按键方便。 26 | 27 | 设置方式如下图 28 | 29 | ![demo](https://img.xxsoftware.fun/demo.gif) 30 | 31 | 其余的相关快捷键感兴趣可以自行设置,这里就举我最常用的一个。 32 | 33 | ## 搜索文件 34 | 35 | 在搜索前建议你设置一下排除列表,避免文件过多,根据自己需求来设置排除的文件夹,这里我把我基本用不上的文件夹放在这。 36 | 37 | ![image-20200908001436884](https://img.xxsoftware.fun/image-20200908001436884.png) 38 | 39 | 这里我先放一个 gif 图,先感受一下 40 | 41 | ![demo1](https://img.xxsoftware.fun/demo1.gif) 42 | 43 | 我也只是简单的使用一下,实际使用过就知道这个工具有多牛逼,你就会知道我为什么会推荐了。想上面的简单使用已经够用了,还有一些针对性的操作,可以在 帮助->搜索语法 即可看到相关搜索语法帮助。 44 | 45 | 这里我就简单说几个常用的 比如我要打开`pycharm` 这个软件,这时候我会肯定会先输入`pycharm`,但是发现出现了特别多的结果且没有我想要的,于是我尝试补上`.exe`后缀,然后发现竟然没有!怎么可能我明明安装了,其实这个文件名是`pycharm64.exe`你输入`pycharm.exe`肯定搜不到。而遇到这种问题一般都不是这样搜索而是输入 `pycharm exe:` 意思就是我要找` 包含``pycharm `文件名同时又要是`exe`为后缀的。操作情况如下 46 | 47 | ![demo2](https://img.xxsoftware.fun/demo2.gif) 48 | 49 | 不过一般这种常用软件的话我还是建议你把这个文件设置一个快捷方式放在桌面,开始菜单或者任务栏。 50 | 51 | ### 右键搜索 Everything 52 | 53 | 有时候我只想在指定文件夹下搜索,而不是全局搜索,那么我可以右键点击文件夹空白区域,其中你会看到`搜索 Everything…`的选项,接着会在搜索框为你补上当前的文件夹的路径,然后你在输入你想搜索的文件名即可,这里我就不在赘述和放图了。 54 | 55 | ## 一些注意的坑 56 | 57 | ### 全字匹配 58 | 59 | 有时候我要搜索一个文件,文件名是`demo123.txt`,但是我输入`demo`的时候下面却没有`demo123`,而当我补上`123`的时候`demo123.txt`又出现了的我的面前了。别急,你多半是不小心按下了 Ctrl+B 触发了全字匹配,从而搜索不到文件,你只要在按一下 Ctrl+B 关闭即可。 60 | 61 | ![image-20200908004645736](https://img.xxsoftware.fun/image-20200908004645736.png) 62 | 63 | ## 总结 64 | 65 | 想必你如果已经安装完,并且试用一番,你可能会忍不住给该作者捐赠。没错,Everything 谁用谁知道,windows 必装之一。 66 | 67 | 但如果你并没有反复打开和搜索文件的需求,而电脑只是用来刷刷剧,玩玩游戏的话,请赶快把这篇文章关掉,不适合你。但如果有,赶快把这篇文章分享给别人吧,也许你的分享能让别人少掉几根头发。 68 | -------------------------------------------------------------------------------- /docs/tools/Wappalyzer识别网站上的技术.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: wappalyzer-recognize-technology 3 | slug: /wappalyzer-recognize-technology 4 | title: Wappalyzer识别网站上的技术 5 | date: 2021-07-20 6 | authors: 東雲研究所 7 | tags: [chrome, 插件] 8 | keywords: [chrome, 插件] 9 | --- 10 | 11 | 你是否还在为不知道心仪的网站所采用的技术栈而烦恼吗,如果是,那么这款 Chrome 插件值得你拥有 12 | 13 | <!-- truncate --> 14 | 15 | ## 前言 16 | 17 | 作为前端开发者而言,浏览器可以说是我们的栖息地,随着大前端发展,催生出许许多多的技术栈,各式各样的网站迎刃而出。在面对一个悉知而又陌生的网站,想了解所采用的技术栈无不得从利用开发者工具或者分析 js 入手,而有一款 Chrome 插件,能轻松分析出对应网站所采用技术。 18 | 19 | ## 开始 20 | 21 | 正如标题所言,利用它可以大致分析网站所用编程语言,框架,库,Web 服务器等等。 22 | 23 | 这里先贴下官方链接和使用截图 24 | 25 | [Find out what websites are built with - Wappalyzer](https://www.wappalyzer.com/) 26 | 27 | Wappalyzer 官网 28 | 29 | ![image-20210729074332788](https://img.xxsoftware.fun/wapp.png) 30 | 31 | 东云研究所的个人博客 32 | 33 | ![image-20210802131116542](https://img.xxsoftware.fun/wappblog.png) 34 | 35 | 我的组件库 36 | 37 | ![image-20210729074249566](https://img.xxsoftware.fun/wapplib.png) 38 | 39 | 本人测试过多个网站,分析结果与实际也如图所示,按理来说不会分析错,最多也就是没分析出来,不过效果可以说非常明显了,有时候看到一个心仪的网站,帮助我们去分析了解对应的技术栈,这再好不过了。 40 | -------------------------------------------------------------------------------- /docs/tools/introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: introduction 3 | slug: /tools 4 | title: 开发工具推荐 5 | --- 6 | 7 | 本页为个人开发中使用到的一些开发工具。 8 | 9 | 同时你也可以到上方的导航栏中的工具中,查看一些本人常用的工具,我都将其部署在自有的服务器上,加快访问速度。 -------------------------------------------------------------------------------- /docs/tools/实用工具PowerToys.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: power-toys 3 | slug: /power-toys 4 | title: PowerToys多功能软件 5 | date: 2022-09-08 6 | authors: 東雲研究所 7 | tags: [工具] 8 | keywords: [工具] 9 | --- 10 | 11 | ## 微软最强工具集 12 | 13 | 你听说过 PowerToys 吗?你用过 PowerToys 吗?你可能从来也没听说过,PowerToys 是微软最初发布于 Windows 95 平台的系统增强工具,2019 年,微软宣布开源 PowerToys 实用工具。 14 | 15 | 在`PowerToys`里集成了多项功能,我最喜欢的功能有颜色选择器、置顶窗口、图像大小调整器等等 ,但只支持 win10、win11 16 | 17 | 今天,就带你深入了解 PowerToys 18 | 19 | ## 下载安装 20 | 21 | 软件网址 [点我进入](https://learn.microsoft.com/zh-cn/windows/powertoys/install) 22 | 23 | 之后可以通过 github,Microsoft Store 或者 Windows 包管理器安装 24 | 25 | 我用的是 github 下载完安装软件一路下一步,默认选项即可就行了 26 | 27 | ## 不同功能与使用方法 28 | 29 | ### alt + space 快速查找软件 30 | 31 | ![](https://img.xxsoftware.fun/pt_serach.png) 32 | 33 | ### win+shift+c 颜色选择器 34 | 35 | ![](https://img.xxsoftware.fun/pt_color.png) 36 | 37 | ### win+shift+m 屏幕标尺 38 | 39 | ![](https://img.xxsoftware.fun/pt_rule.png) 40 | 41 | ### win+ctrl+t 让软件窗口固定在顶部 42 | 43 | ![](https://img.xxsoftware.fun/pt_top.png) 软件外面套了层灰色的边界就代表被置顶了 44 | 45 | ### hosts 文件编辑器 46 | 47 | ![](https://img.xxsoftware.fun/powerToys_hosts.png) 48 | 49 | ### win+shift+t 文本提取器 50 | 51 | ### powerrename 可以快速的用正则更换文件名 52 | 53 | 除此之外还有很多功能,在下载了 powerToys 之后就可以使用啦 54 | -------------------------------------------------------------------------------- /docsearch.json: -------------------------------------------------------------------------------- 1 | { 2 | "index_name": "東雲研究所", 3 | "start_urls": ["https://blog.xxsoftware.fun/"], 4 | "sitemap_urls": ["https://blog.xxsoftware.fun/sitemap.xml"], 5 | "selectors": { 6 | "lvl0": { 7 | "selector": "(//ul[contains(@class,'menu__list')]//a[contains(@class, 'menu__link menu__link--sublist menu__link--active')]/text() | //nav[contains(@class, 'navbar')]//a[contains(@class, 'navbar__link--active')]/text())[last()]", 8 | "type": "xpath", 9 | "global": true, 10 | "default_value": "Documentation" 11 | }, 12 | "lvl1": "header h1, article h1", 13 | "lvl2": "article h2", 14 | "lvl3": "article h3", 15 | "lvl4": "article h4", 16 | "lvl5": "article h5, article td:first-child", 17 | "lvl6": "article h6", 18 | "text": "article p, article li, article td:last-child" 19 | }, 20 | "custom_settings": { 21 | "attributesForFaceting": [ 22 | "type", 23 | "lang", 24 | "language", 25 | "version", 26 | "docusaurus_tag" 27 | ], 28 | "attributesToRetrieve": [ 29 | "hierarchy", 30 | "content", 31 | "anchor", 32 | "url", 33 | "url_without_anchor", 34 | "type" 35 | ], 36 | "attributesToHighlight": ["hierarchy", "content"], 37 | "attributesToSnippet": ["content:10"], 38 | "camelCaseAttributes": ["hierarchy", "content"], 39 | "searchableAttributes": [ 40 | "unordered(hierarchy.lvl0)", 41 | "unordered(hierarchy.lvl1)", 42 | "unordered(hierarchy.lvl2)", 43 | "unordered(hierarchy.lvl3)", 44 | "unordered(hierarchy.lvl4)", 45 | "unordered(hierarchy.lvl5)", 46 | "unordered(hierarchy.lvl6)", 47 | "content" 48 | ], 49 | "distinct": true, 50 | "attributeForDistinct": "url", 51 | "customRanking": [ 52 | "desc(weight.pageRank)", 53 | "desc(weight.level)", 54 | "asc(weight.position)" 55 | ], 56 | "ranking": [ 57 | "words", 58 | "filters", 59 | "typo", 60 | "attribute", 61 | "proximity", 62 | "exact", 63 | "custom" 64 | ], 65 | "highlightPreTag": "<span class='algolia-docsearch-suggestion--highlight'>", 66 | "highlightPostTag": "</span>", 67 | "minWordSizefor1Typo": 3, 68 | "minWordSizefor2Typos": 7, 69 | "allowTyposOnNumericTokens": false, 70 | "minProximity": 1, 71 | "ignorePlurals": true, 72 | "advancedSyntax": true, 73 | "attributeCriteriaComputedByMinProximity": true, 74 | "removeWordsIfNoResults": "allOptional", 75 | "separatorsToIndex": "_", 76 | "synonyms": [ 77 | ["js", "javascript"], 78 | ["ts", "typescript"] 79 | ] 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /i18n/zh/docusaurus-plugin-content-blog/options.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": { 3 | "message": "Blog", 4 | "description": "The title for the blog used in SEO" 5 | }, 6 | "description": { 7 | "message": "Blog", 8 | "description": "The description for the blog used in SEO" 9 | }, 10 | "sidebar.title": { 11 | "message": "近期文章", 12 | "description": "The label for the left sidebar" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /i18n/zh/docusaurus-plugin-content-docs/current.json: -------------------------------------------------------------------------------- 1 | { 2 | "version.label": { 3 | "message": "Next", 4 | "description": "The label for version current" 5 | }, 6 | "sidebar.skill.category.Vue": { 7 | "message": "Vue", 8 | "description": "The label for category Vue in sidebar skill" 9 | }, 10 | "sidebar.skill.category.逆向": { 11 | "message": "逆向", 12 | "description": "The label for category 逆向 in sidebar skill" 13 | }, 14 | "sidebar.skill.category.逆向.link.generated-index.title": { 15 | "message": "逆向笔记", 16 | "description": "The generated-index page title for category 逆向 in sidebar skill" 17 | }, 18 | "sidebar.skill.category.逆向.link.generated-index.description": { 19 | "message": "Web逆向与安卓逆向笔记", 20 | "description": "The generated-index page description for category 逆向 in sidebar skill" 21 | }, 22 | "sidebar.skill.category.安卓": { 23 | "message": "安卓", 24 | "description": "The label for category 安卓 in sidebar skill" 25 | }, 26 | "sidebar.skill.category.frida": { 27 | "message": "frida", 28 | "description": "The label for category frida in sidebar skill" 29 | }, 30 | "sidebar.skill.category.刷机": { 31 | "message": "刷机", 32 | "description": "The label for category 刷机 in sidebar skill" 33 | }, 34 | "sidebar.skill.category.Web": { 35 | "message": "Web", 36 | "description": "The label for category Web in sidebar skill" 37 | }, 38 | "sidebar.skill.category.密码学": { 39 | "message": "密码学", 40 | "description": "The label for category 密码学 in sidebar skill" 41 | }, 42 | "sidebar.skill.category.后端": { 43 | "message": "后端", 44 | "description": "The label for category 后端 in sidebar skill" 45 | }, 46 | "sidebar.skill.category.数据库": { 47 | "message": "数据库", 48 | "description": "The label for category 数据库 in sidebar skill" 49 | }, 50 | "sidebar.skill.category.Mysql": { 51 | "message": "Mysql", 52 | "description": "The label for category Mysql in sidebar skill" 53 | }, 54 | "sidebar.skill.category.MongoDB": { 55 | "message": "MongoDB", 56 | "description": "The label for category MongoDB in sidebar skill" 57 | }, 58 | "sidebar.skill.category.Redis": { 59 | "message": "Redis", 60 | "description": "The label for category Redis in sidebar skill" 61 | }, 62 | "sidebar.skill.category.Elasticsearch": { 63 | "message": "Elasticsearch", 64 | "description": "The label for category Elasticsearch in sidebar skill" 65 | }, 66 | "sidebar.skill.link.React": { 67 | "message": "React", 68 | "description": "The label for link React in sidebar skill, linking to /docs/category/react" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /i18n/zh/docusaurus-theme-classic/footer.json: -------------------------------------------------------------------------------- 1 | { 2 | "link.title.学习": { 3 | "message": "学习", 4 | "description": "The title of the footer links column with title=学习 in the footer" 5 | }, 6 | "link.title.社交媒体": { 7 | "message": "社交媒体", 8 | "description": "The title of the footer links column with title=社交媒体 in the footer" 9 | }, 10 | "link.title.友情链接": { 11 | "message": "友情链接", 12 | "description": "The title of the footer links column with title=友情链接 in the footer" 13 | }, 14 | "link.item.label.技术博客": { 15 | "message": "技术博客", 16 | "description": "The label of footer link with label=技术博客 linking to /#homepage_blogs" 17 | }, 18 | "link.item.label.技术笔记": { 19 | "message": "技术笔记", 20 | "description": "The label of footer link with label=技术笔记 linking to docs/skill" 21 | }, 22 | "link.item.label.实战项目": { 23 | "message": "实战项目", 24 | "description": "The label of footer link with label=实战项目 linking to project" 25 | }, 26 | "link.item.label.首页": { 27 | "message": "首页", 28 | "description": "The label of footer link with label=首页 linking to /" 29 | }, 30 | "link.item.label.关于我": { 31 | "message": "关于我", 32 | "description": "The label of footer link with label=关于我 linking to /about" 33 | }, 34 | "link.item.label.GitHub": { 35 | "message": "GitHub", 36 | "description": "The label of footer link with label=GitHub linking to https://github.com/xxsoftware" 37 | }, 38 | "link.item.label.掘金": { 39 | "message": "掘金", 40 | "description": "The label of footer link with label=掘金 linking to https://juejin.cn/user/1565318510545901" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /i18n/zh/docusaurus-theme-classic/navbar.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": { 3 | "message": "東雲研究所", 4 | "description": "The title in the navbar" 5 | }, 6 | "item.label.标签": { 7 | "message": "标签", 8 | "description": "Navbar item with label 标签" 9 | }, 10 | "item.label.归档": { 11 | "message": "归档", 12 | "description": "Navbar item with label 归档" 13 | }, 14 | "item.label.学习": { 15 | "message": "学习", 16 | "description": "Navbar item with label 学习" 17 | }, 18 | "item.label.小工具": { 19 | "message": "小工具", 20 | "description": "Navbar item with label 小工具" 21 | }, 22 | "item.label.实战项目": { 23 | "message": "实战项目", 24 | "description": "Navbar item with label 实战项目" 25 | }, 26 | "item.label.技术笔记": { 27 | "message": "技术笔记", 28 | "description": "Navbar item with label 技术笔记" 29 | }, 30 | "item.label.网址导航": { 31 | "message": "网址导航", 32 | "description": "Navbar item with label 网址导航" 33 | }, 34 | "item.label.JS代码还原": { 35 | "message": "JS代码还原", 36 | "description": "Navbar item with label JS代码还原" 37 | }, 38 | "item.label.CyberChef加密": { 39 | "message": "CyberChef加密", 40 | "description": "Navbar item with label CyberChef加密" 41 | }, 42 | "item.label.网盘": { 43 | "message": "网盘", 44 | "description": "Navbar item with label 网盘" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.module.scss' { 2 | const classes: { readonly [key: string]: string } 3 | export default classes 4 | } 5 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /sidebars.js: -------------------------------------------------------------------------------- 1 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ 2 | const sidebars = { 3 | skill: [ 4 | 'skill/introduction', 5 | 6 | { 7 | label: '代码规范', 8 | type: 'category', 9 | link: { 10 | type: 'doc', 11 | id: 'skill/code-specification/code-specification-guides', 12 | }, 13 | items: [ 14 | 'skill/code-specification/eslint', 15 | 'skill/code-specification/prettier', 16 | 'skill/code-specification/stylelint', 17 | 'skill/code-specification/editorconfig', 18 | 'skill/code-specification/husky', 19 | 'skill/code-specification/npmrc', 20 | ], 21 | }, 22 | { 23 | label: 'Vue', 24 | type: 'category', 25 | link: { 26 | type: 'generated-index', 27 | }, 28 | items: [ 29 | { 30 | type: 'autogenerated', 31 | dirName: 'skill/vue', 32 | }, 33 | ], 34 | }, 35 | 36 | { 37 | label: 'Web', 38 | type: 'category', 39 | link: { 40 | type: 'generated-index', 41 | }, 42 | items: [ 43 | { 44 | type: 'autogenerated', 45 | dirName: 'skill/web', 46 | }, 47 | ], 48 | }, 49 | { 50 | label: 'JavaScript', 51 | type: 'category', 52 | link: { 53 | type: 'generated-index', 54 | }, 55 | items: [ 56 | { 57 | type: 'autogenerated', 58 | dirName: 'skill/js', 59 | }, 60 | ], 61 | }, 62 | { 63 | label: 'Node', 64 | type: 'category', 65 | link: { 66 | type: 'generated-index', 67 | }, 68 | items: [ 69 | { 70 | type: 'autogenerated', 71 | dirName: 'skill/node', 72 | }, 73 | ], 74 | }, 75 | { 76 | label: 'Css', 77 | type: 'category', 78 | link: { 79 | type: 'generated-index', 80 | }, 81 | items: [ 82 | { 83 | type: 'autogenerated', 84 | dirName: 'skill/css', 85 | }, 86 | ], 87 | }, 88 | 89 | { 90 | label: 'Git', 91 | type: 'category', 92 | link: { 93 | type: 'generated-index', 94 | }, 95 | items: [ 96 | { 97 | type: 'autogenerated', 98 | dirName: 'skill/git', 99 | }, 100 | ], 101 | }, 102 | { 103 | label: '算法', 104 | type: 'category', 105 | link: { 106 | type: 'generated-index', 107 | }, 108 | items: [ 109 | { 110 | type: 'autogenerated', 111 | dirName: 'skill/algorithm', 112 | }, 113 | ], 114 | }, 115 | ], 116 | tools: [ 117 | 'tools/introduction', 118 | 'tools/everything-quick-search-local-files', 119 | 'tools/power-toys', 120 | 'tools/wappalyzer-recognize-technology', 121 | 'tools/vite-plugin', 122 | ], 123 | } 124 | 125 | module.exports = sidebars 126 | -------------------------------------------------------------------------------- /src/components/BrowserWindow/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import styles from './styles.module.css' 4 | 5 | function BrowserWindow({ children, minHeight, url }) { 6 | return ( 7 | <div className={styles.browserWindow} style={{ minHeight }}> 8 | <div className={styles.browserWindowHeader}> 9 | <div className={styles.buttons}> 10 | <span className={styles.dot} style={{ background: '#f25f58' }} /> 11 | <span className={styles.dot} style={{ background: '#fbbe3c' }} /> 12 | <span className={styles.dot} style={{ background: '#58cb42' }} /> 13 | </div> 14 | <div className={styles.browserWindowAddressBar}>{url}</div> 15 | <div className={styles.browserWindowMenuIcon}> 16 | <div> 17 | <span className={styles.bar} /> 18 | <span className={styles.bar} /> 19 | <span className={styles.bar} /> 20 | </div> 21 | </div> 22 | </div> 23 | 24 | <div className={styles.browserWindowBody}>{children}</div> 25 | </div> 26 | ) 27 | } 28 | 29 | export default BrowserWindow 30 | -------------------------------------------------------------------------------- /src/components/BrowserWindow/styles.module.css: -------------------------------------------------------------------------------- 1 | .browserWindow { 2 | border: 3px solid var(--ifm-color-emphasis-200); 3 | border-top-left-radius: var(--ifm-global-radius); 4 | border-top-right-radius: var(--ifm-global-radius); 5 | margin-bottom: 10px; 6 | } 7 | 8 | .browserWindowHeader { 9 | align-items: center; 10 | background: var(--ifm-color-emphasis-200); 11 | display: flex; 12 | padding: 0.5rem 1rem; 13 | } 14 | 15 | .row:after { 16 | content: ''; 17 | display: table; 18 | clear: both; 19 | } 20 | 21 | .buttons { 22 | white-space: nowrap; 23 | } 24 | 25 | .right { 26 | align-self: center; 27 | width: 10%; 28 | } 29 | 30 | .browserWindowAddressBar { 31 | flex: 1 0; 32 | margin: 0 1rem 0 0.5rem; 33 | border-radius: 12.5px; 34 | background-color: #fff; 35 | color: #666; 36 | padding: 5px 15px; 37 | font: 400 13px Arial; 38 | user-select: none; 39 | } 40 | 41 | .dot { 42 | margin-right: 6px; 43 | margin-top: 4px; 44 | height: 12px; 45 | width: 12px; 46 | background-color: #bbb; 47 | border-radius: 50%; 48 | display: inline-block; 49 | } 50 | 51 | .browserWindowMenuIcon { 52 | margin-left: auto; 53 | } 54 | 55 | .bar { 56 | width: 17px; 57 | height: 3px; 58 | background-color: #aaa; 59 | margin: 3px 0; 60 | display: block; 61 | } 62 | 63 | .browserWindowBody { 64 | padding: 1rem; 65 | } 66 | -------------------------------------------------------------------------------- /src/components/Button/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from '@emotion/styled' 3 | 4 | function Button({ isLink = false, children, ...rest }) { 5 | return ( 6 | <StyledButton as={isLink ? 'a' : 'button'} {...rest}> 7 | {children} 8 | </StyledButton> 9 | ) 10 | } 11 | 12 | const StyledButton = styled.button` 13 | display: inline-block; 14 | color: white; 15 | padding: 0.75em 20px; 16 | margin-left: -2px; 17 | font-weight: 600; 18 | background: linear-gradient( 19 | 90deg, 20 | var(--ifm-color-primary) 11.3%, 21 | var(--ifm-color-primary-light) 161.54% 22 | ); 23 | box-shadow: 0px 0px 32px rgba(0, 105, 165, 0.35); 24 | border-radius: 7px; 25 | font-family: 'Yuanti SC', 'Youyuan', 'You Yuan', '幼圆', 'PingFang SC', 26 | 'Microsoft Yahei', Helvetica, sans-serif; 27 | 28 | :hover { 29 | color: white; 30 | text-decoration: none; 31 | } 32 | ` 33 | 34 | export default Button 35 | -------------------------------------------------------------------------------- /src/components/CodeSandBox/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import useThemeContext from '@theme/hooks/useThemeContext' 4 | 5 | function index({ slug, title, height = '600px' }) { 6 | const { isDarkTheme } = useThemeContext() 7 | const themedSrc = `https://codesandbox.io/embed/${slug}?fontsize=14&hidenavigation=1&view=preview&theme=${ 8 | isDarkTheme ? 'dark' : 'light' 9 | }` 10 | return ( 11 | <div> 12 | <iframe 13 | src={themedSrc} 14 | style={{ 15 | width: '100%', 16 | height, 17 | border: 0, 18 | 'border-radius': '4px', 19 | overflow: 'hidden', 20 | }} 21 | title={title} 22 | allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking" 23 | sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts" 24 | ></iframe> 25 | </div> 26 | ) 27 | } 28 | 29 | index.propTypes = {} 30 | 31 | export default index 32 | -------------------------------------------------------------------------------- /src/components/Codepen/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | function Codepen({ height = 300, title, hash, theme = 'dark', ...rest }) { 4 | return ( 5 | <iframe 6 | height={height} 7 | style={{ width: '100%' }} 8 | scrolling="no" 9 | title={title} 10 | src={`https://codepen.io/zxuqian/embed/${hash}?height=${height}&theme-id=${theme}&default-tab=css,result`} 11 | frameborder="no" 12 | loading="lazy" 13 | allowtransparency="true" 14 | allowfullscreen="true" 15 | > 16 | See the Pen <a href={`https://codepen.io/zxuqian/pen/${hash}`}>{title}</a>{' '} 17 | by Xuqian Zhang (<a href="https://codepen.io/zxuqian">@zxuqian</a>) on{' '} 18 | <a href="https://codepen.io">CodePen</a>. 19 | </iframe> 20 | ) 21 | } 22 | 23 | export default Codepen 24 | -------------------------------------------------------------------------------- /src/components/Comment/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useThemeConfig, useColorMode } from '@docusaurus/theme-common' 3 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext' 4 | import { ThemeConfig } from '@docusaurus/preset-classic' 5 | import BrowserOnly from '@docusaurus/BrowserOnly' 6 | import Giscus, { GiscusProps } from '@giscus/react' 7 | 8 | interface CustomThemeConfig extends ThemeConfig { 9 | giscus: GiscusProps & { darkTheme: string } 10 | } 11 | 12 | const defaultConfig: Partial<GiscusProps> & { darkTheme: string } = { 13 | id: 'comments', 14 | mapping: 'title', 15 | reactionsEnabled: '1', 16 | emitMetadata: '0', 17 | inputPosition: 'top', 18 | lang: 'zh-CN', 19 | theme: 'light', 20 | darkTheme: 'dark', 21 | } 22 | 23 | export default function Comment(): JSX.Element { 24 | const themeConfig = useThemeConfig() as CustomThemeConfig 25 | const { i18n } = useDocusaurusContext() 26 | 27 | // merge default config 28 | const giscus = { ...defaultConfig, ...themeConfig.giscus } 29 | 30 | if (!giscus.repo || !giscus.repoId || !giscus.categoryId) { 31 | throw new Error( 32 | 'You must provide `repo`, `repoId`, and `categoryId` to `themeConfig.giscus`.', 33 | ) 34 | } 35 | 36 | giscus.theme = 37 | useColorMode().colorMode === 'dark' ? giscus.darkTheme : giscus.theme 38 | giscus.lang = i18n.currentLocale 39 | 40 | return ( 41 | <BrowserOnly fallback={<div>Loading Comments...</div>}> 42 | {() => <Giscus {...giscus} />} 43 | </BrowserOnly> 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /src/components/Playground/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Translate from '@docusaurus/Translate' 3 | import Link from '@docusaurus/Link' 4 | import Image from '@theme/IdealImage' 5 | import clsx from 'clsx' 6 | 7 | const Playgrounds = [ 8 | { 9 | name: '📦 CodeSandbox', 10 | image: require('@site/static/img/playgrounds/codesandbox.png'), 11 | url: 'https://new.docusaurus.io/codesandbox', 12 | description: ( 13 | <Translate id="playground.codesandbox.description"> 14 | CodeSandbox is a popular playground solution. Runs Docusaurus in a 15 | remote Docker container. 16 | </Translate> 17 | ), 18 | }, 19 | { 20 | name: '⚡ StackBlitz 🆕', 21 | image: require('@site/static/img/playgrounds/stackblitz.png'), 22 | url: 'https://new.docusaurus.io/stackblitz', 23 | description: ( 24 | <Translate 25 | id="playground.stackblitz.description" 26 | values={{ 27 | webContainersLink: ( 28 | <Link target="https://blog.stackblitz.com/posts/introducing-webcontainers/"> 29 | WebContainers 30 | </Link> 31 | ), 32 | }} 33 | > 34 | { 35 | 'StackBlitz uses a novel {webContainersLink} technology to run Docusaurus directly in your browser.' 36 | } 37 | </Translate> 38 | ), 39 | }, 40 | ] 41 | 42 | function PlaygroundCard({ name, image, url, description }) { 43 | return ( 44 | <div className="col col--6 margin-bottom--lg"> 45 | <div className={clsx('card')}> 46 | <div className={clsx('card__image')}> 47 | <Link to={url}> 48 | <Image img={image} alt={`${name}'s image`} /> 49 | </Link> 50 | </div> 51 | <div className="card__body"> 52 | <h3>{name}</h3> 53 | <p>{description}</p> 54 | </div> 55 | <div className="card__footer"> 56 | <div className="button-group button-group--block"> 57 | <Link className="button button--secondary" to={url}> 58 | <Translate id="playground.tryItButton">Try it now!</Translate> 59 | </Link> 60 | </div> 61 | </div> 62 | </div> 63 | </div> 64 | ) 65 | } 66 | 67 | export function PlaygroundCardsRow() { 68 | return ( 69 | <div className="row"> 70 | {Playgrounds.map(playground => ( 71 | <PlaygroundCard key={playground.name} {...playground} /> 72 | ))} 73 | </div> 74 | ) 75 | } 76 | -------------------------------------------------------------------------------- /src/components/Svg/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { type ReactNode, type ComponentProps } from 'react' 2 | import clsx from 'clsx' 3 | import styles from './styles.module.css' 4 | 5 | export interface SvgIconProps extends ComponentProps<'svg'> { 6 | viewBox?: string 7 | size?: 'inherit' | 'small' | 'medium' | 'large' 8 | color?: 'inherit' | 'primary' | 'secondary' | 'success' | 'error' | 'warning' 9 | svgClass?: string // Class attribute on the child 10 | colorAttr?: string // Applies a color attribute to the SVG element. 11 | children: ReactNode // Node passed into the SVG element. 12 | } 13 | 14 | export default function Svg(props: SvgIconProps): JSX.Element { 15 | const { 16 | svgClass, 17 | colorAttr, 18 | children, 19 | color = 'inherit', 20 | size = 'medium', 21 | viewBox = '0 0 24 24', 22 | ...rest 23 | } = props 24 | 25 | return ( 26 | <svg 27 | viewBox={viewBox} 28 | color={colorAttr} 29 | aria-hidden 30 | className={clsx(styles.svgIcon, styles[color], styles[size], svgClass)} 31 | {...rest} 32 | > 33 | {children} 34 | </svg> 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /src/components/Svg/styles.module.css: -------------------------------------------------------------------------------- 1 | .svgIcon { 2 | user-select: none; 3 | width: 1em; 4 | height: 1em; 5 | display: inline-block; 6 | fill: currentColor; 7 | flex-shrink: 0; 8 | color: inherit; 9 | } 10 | 11 | /* font-size */ 12 | .small { 13 | font-size: 1.25rem; 14 | } 15 | 16 | .medium { 17 | font-size: 1.5rem; 18 | } 19 | 20 | .large { 21 | font-size: 2.185rem; 22 | } 23 | 24 | /* colors */ 25 | .primary { 26 | color: var(--ifm-color-primary); 27 | } 28 | 29 | .secondary { 30 | color: var(--ifm-color-secondary); 31 | } 32 | 33 | .success { 34 | color: var(--ifm-color-success); 35 | } 36 | 37 | .error { 38 | color: var(--ifm-color-error); 39 | } 40 | 41 | .warning { 42 | color: var(--ifm-color-warning); 43 | } 44 | 45 | .inherit { 46 | color: inherit; 47 | } 48 | -------------------------------------------------------------------------------- /src/components/svgIcons/FavoriteIcon/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Svg, { type SvgIconProps } from '@site/src/components/Svg' 3 | 4 | export default function FavoriteIcon( 5 | props: Omit<SvgIconProps, 'children'>, 6 | ): JSX.Element { 7 | return ( 8 | <Svg {...props}> 9 | <path d="M12,21.35L10.55,20.03C5.4,15.36 2,12.27 2,8.5C2,5.41 4.42,3 7.5,3C9.24,3 10.91,3.81 12,5.08C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.41 22,8.5C22,12.27 18.6,15.36 13.45,20.03L12,21.35Z" /> 10 | </Svg> 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /src/pages/index.tsx.bak: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classnames from 'classnames'; 3 | import Layout from '@theme/Layout'; 4 | import Link from '@docusaurus/Link'; 5 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; 6 | import useBaseUrl from '@docusaurus/useBaseUrl'; 7 | import styles from './styles.module.css'; 8 | 9 | const features = [ 10 | { 11 | title: <>Easy to Use</>, 12 | imageUrl: 'img/undraw_docusaurus_mountain.svg', 13 | description: ( 14 | <> 15 | Docusaurus was designed from the ground up to be easily installed and 16 | used to get your website up and running quickly. 17 | </> 18 | ), 19 | }, 20 | { 21 | title: <>Focus on What Matters</>, 22 | imageUrl: 'img/undraw_docusaurus_tree.svg', 23 | description: ( 24 | <> 25 | Docusaurus lets you focus on your docs, and we'll do the chores. Go 26 | ahead and move your docs into the <code>docs</code> directory. 27 | </> 28 | ), 29 | }, 30 | { 31 | title: <>Powered by React</>, 32 | imageUrl: 'img/undraw_docusaurus_react.svg', 33 | description: ( 34 | <> 35 | Extend or customize your website layout by reusing React. Docusaurus can 36 | be extended while reusing the same header and footer. 37 | </> 38 | ), 39 | }, 40 | ]; 41 | 42 | function Feature({imageUrl, title, description}) { 43 | const imgUrl = useBaseUrl(imageUrl); 44 | return ( 45 | <div className={classnames('col col--4', styles.feature)}> 46 | {imgUrl && ( 47 | <div className="text--center"> 48 | <img className={styles.featureImage} src={imgUrl} alt={title} /> 49 | </div> 50 | )} 51 | <h3>{title}</h3> 52 | <p>{description}</p> 53 | </div> 54 | ); 55 | } 56 | 57 | function Home() { 58 | const context = useDocusaurusContext(); 59 | const {siteConfig = {}} = context; 60 | return ( 61 | <Layout 62 | title={`Hello from ${siteConfig.title}`} 63 | description="Description will go into a meta tag in <head />"> 64 | <header className={classnames('hero hero--primary', styles.heroBanner)}> 65 | <div className="container"> 66 | <h1 className="hero__title">{siteConfig.title}</h1> 67 | <p className="hero__subtitle">{siteConfig.tagline}</p> 68 | <div className={styles.buttons}> 69 | <Link 70 | className={classnames( 71 | 'button button--outline button--secondary button--lg', 72 | styles.getStarted, 73 | )} 74 | to={useBaseUrl('docs/doc1')}> 75 | Get Started 76 | </Link> 77 | </div> 78 | </div> 79 | </header> 80 | <main> 81 | {features && features.length && ( 82 | <section className={styles.features}> 83 | <div className="container"> 84 | <div className="row"> 85 | {features.map((props, idx) => ( 86 | <Feature key={idx} {...props} /> 87 | ))} 88 | </div> 89 | </div> 90 | </section> 91 | )} 92 | </main> 93 | </Layout> 94 | ); 95 | } 96 | 97 | export default Home; 98 | -------------------------------------------------------------------------------- /src/pages/project/_components/ShowcaseCard/styles.module.css: -------------------------------------------------------------------------------- 1 | .showcaseCard { 2 | position: relative; 3 | box-shadow: 0 10px 30px -5px rgb(0 0 0 / 30%); 4 | transition: box-shadow 0.5s, opacity 0.5s; 5 | will-change: transform; 6 | overflow: hidden; 7 | touch-action: none; 8 | } 9 | 10 | .showcaseCard::before { 11 | display: block; 12 | font-weight: bold; 13 | height: 0; 14 | overflow: hidden; 15 | visibility: hidden; 16 | } 17 | 18 | .showcaseCardImage { 19 | overflow: hidden; 20 | height: 150px; 21 | border-bottom: 2px solid var(--ifm-color-emphasis-200); 22 | } 23 | 24 | .showcaseCardHeader { 25 | display: flex; 26 | align-items: center; 27 | margin-bottom: 12px; 28 | } 29 | 30 | .showcaseCardTitle { 31 | margin-bottom: 0; 32 | flex: 1 1 auto; 33 | } 34 | 35 | .showcaseCardTitle a { 36 | text-decoration: none; 37 | background: linear-gradient( 38 | var(--ifm-color-primary), 39 | var(--ifm-color-primary) 40 | ) 41 | 0% 100% / 0% 1px no-repeat; 42 | transition: background-size ease-out 200ms; 43 | } 44 | 45 | .showcaseCardTitle a:not(:focus):hover { 46 | background-size: 100% 1px; 47 | } 48 | 49 | .showcaseCardTitle, 50 | .showcaseCardHeader .svgIconFavorite { 51 | margin-right: 0.25rem; 52 | } 53 | 54 | .showcaseCardHeader .svgIconFavorite { 55 | color: var(--site-color-svg-icon-favorite); 56 | } 57 | 58 | .showcaseCardSrcBtn { 59 | margin-left: 6px; 60 | padding-left: 12px; 61 | padding-right: 12px; 62 | border: none; 63 | } 64 | 65 | .showcaseCardSrcBtn:focus-visible { 66 | background-color: var(--ifm-color-secondary-dark); 67 | } 68 | 69 | html[data-theme='dark'] .showcaseCardSrcBtn { 70 | background-color: var(--ifm-color-emphasis-200) !important; 71 | color: inherit; 72 | } 73 | 74 | html[data-theme='dark'] .showcaseCardSrcBtn:hover { 75 | background-color: var(--ifm-color-emphasis-300) !important; 76 | } 77 | 78 | .showcaseCardBody { 79 | font-size: smaller; 80 | line-height: 1.66; 81 | } 82 | 83 | .cardFooter { 84 | display: flex; 85 | flex-wrap: wrap; 86 | } 87 | 88 | .tag { 89 | font-size: 0.675rem; 90 | border: 1px solid var(--ifm-color-secondary-darkest); 91 | cursor: default; 92 | margin-right: 6px; 93 | margin-bottom: 6px !important; 94 | border-radius: 12px; 95 | display: inline-flex; 96 | align-items: center; 97 | } 98 | 99 | .tag .textLabel { 100 | margin-left: 8px; 101 | } 102 | 103 | .tag .colorLabel { 104 | width: 7px; 105 | height: 7px; 106 | border-radius: 50%; 107 | margin-left: 6px; 108 | margin-right: 6px; 109 | } 110 | -------------------------------------------------------------------------------- /src/pages/project/_components/ShowcaseFilterToggle/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useCallback } from 'react' 2 | import { useHistory, useLocation } from '@docusaurus/router' 3 | 4 | import { prepareUserState } from '../../index.tsx' 5 | 6 | import styles from './styles.module.css' 7 | import clsx from 'clsx' 8 | 9 | export type Operator = 'OR' | 'AND' 10 | 11 | export const OperatorQueryKey = 'operator' 12 | 13 | export function readOperator(search: string): Operator { 14 | return (new URLSearchParams(search).get(OperatorQueryKey) ?? 'OR') as Operator 15 | } 16 | 17 | export default function ShowcaseFilterToggle(): JSX.Element { 18 | const id = 'showcase_filter_toggle' 19 | const location = useLocation() 20 | const history = useHistory() 21 | const [operator, setOperator] = useState(false) 22 | useEffect(() => { 23 | setOperator(readOperator(location.search) === 'AND') 24 | }, [location]) 25 | const toggleOperator = useCallback(() => { 26 | setOperator(o => !o) 27 | const searchParams = new URLSearchParams(location.search) 28 | searchParams.delete(OperatorQueryKey) 29 | if (!operator) { 30 | searchParams.append(OperatorQueryKey, operator ? 'OR' : 'AND') 31 | } 32 | history.push({ 33 | ...location, 34 | search: searchParams.toString(), 35 | state: prepareUserState(), 36 | }) 37 | }, [operator, location, history]) 38 | 39 | return ( 40 | <div> 41 | <input 42 | type="checkbox" 43 | id={id} 44 | className="screen-reader-only" 45 | aria-label="Toggle between or and and for the tags you selected" 46 | onChange={toggleOperator} 47 | onKeyDown={e => { 48 | if (e.key === 'Enter') { 49 | toggleOperator() 50 | } 51 | }} 52 | checked={operator} 53 | /> 54 | {/* eslint-disable-next-line jsx-a11y/label-has-associated-control */} 55 | <label htmlFor={id} className={clsx(styles.checkboxLabel, 'shadow--md')}> 56 | <span className={styles.checkboxLabelOr}>OR</span> 57 | <span className={styles.checkboxLabelAnd}>AND</span> 58 | </label> 59 | </div> 60 | ) 61 | } 62 | -------------------------------------------------------------------------------- /src/pages/project/_components/ShowcaseFilterToggle/styles.module.css: -------------------------------------------------------------------------------- 1 | .checkboxLabel { 2 | --height: 25px; 3 | --width: 80px; 4 | --border: 2px; 5 | display: flex; 6 | width: var(--width); 7 | height: var(--height); 8 | position: relative; 9 | border-radius: var(--height); 10 | border: var(--border) solid var(--ifm-color-primary-darkest); 11 | cursor: pointer; 12 | justify-content: space-around; 13 | opacity: 0.75; 14 | transition: opacity var(--ifm-transition-fast) 15 | var(--ifm-transition-timing-default); 16 | box-shadow: var(--ifm-global-shadow-md); 17 | } 18 | 19 | .checkboxLabel:hover { 20 | opacity: 1; 21 | box-shadow: var(--ifm-global-shadow-md), 22 | 0 0 2px 1px var(--ifm-color-primary-dark); 23 | } 24 | 25 | .checkboxLabel::after { 26 | position: absolute; 27 | content: ''; 28 | inset: 0; 29 | width: calc(var(--width) / 2); 30 | height: 100%; 31 | border-radius: var(--height); 32 | background-color: var(--ifm-color-primary-darkest); 33 | transition: transform var(--ifm-transition-fast) 34 | var(--ifm-transition-timing-default); 35 | transform: translateX(calc(var(--width) / 2 - var(--border))); 36 | } 37 | 38 | input:focus-visible ~ .checkboxLabel::after { 39 | outline: 2px solid currentColor; 40 | } 41 | 42 | .checkboxLabel > * { 43 | font-size: 0.8rem; 44 | color: inherit; 45 | transition: opacity 150ms ease-in 50ms; 46 | } 47 | 48 | input:checked ~ .checkboxLabel::after { 49 | transform: translateX(calc(-1 * var(--border))); 50 | } 51 | -------------------------------------------------------------------------------- /src/pages/project/_components/ShowcaseTagSelect/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | type ComponentProps, 3 | type ReactNode, 4 | type ReactElement, 5 | useCallback, 6 | useState, 7 | useEffect, 8 | } from 'react' 9 | import { useHistory, useLocation } from '@docusaurus/router' 10 | import { toggleListItem } from '@site/src/utils/jsUtils' 11 | import { prepareUserState } from '../../index.tsx' 12 | import type { TagType } from '@site/data/users' 13 | 14 | import styles from './styles.module.css' 15 | 16 | interface Props extends ComponentProps<'input'> { 17 | icon: ReactElement<ComponentProps<'svg'>> 18 | label: ReactNode 19 | tag: TagType 20 | } 21 | 22 | const TagQueryStringKey = 'tags' 23 | 24 | export function readSearchTags(search: string): TagType[] { 25 | return new URLSearchParams(search).getAll(TagQueryStringKey) as TagType[] 26 | } 27 | 28 | function replaceSearchTags(search: string, newTags: TagType[]) { 29 | const searchParams = new URLSearchParams(search) 30 | searchParams.delete(TagQueryStringKey) 31 | newTags.forEach(tag => searchParams.append(TagQueryStringKey, tag)) 32 | return searchParams.toString() 33 | } 34 | 35 | const ShowcaseTagSelect = React.forwardRef<HTMLLabelElement, Props>( 36 | ({ id, icon, label, tag, ...rest }, ref) => { 37 | const location = useLocation() 38 | const history = useHistory() 39 | const [selected, setSelected] = useState(false) 40 | useEffect(() => { 41 | const tags = readSearchTags(location.search) 42 | setSelected(tags.includes(tag)) 43 | }, [tag, location]) 44 | const toggleTag = useCallback(() => { 45 | const tags = readSearchTags(location.search) 46 | const newTags = toggleListItem(tags, tag) 47 | const newSearch = replaceSearchTags(location.search, newTags) 48 | history.push({ 49 | ...location, 50 | search: newSearch, 51 | state: prepareUserState(), 52 | }) 53 | }, [tag, location, history]) 54 | return ( 55 | <> 56 | <input 57 | type="checkbox" 58 | id={id} 59 | className="screen-reader-only" 60 | onKeyDown={e => { 61 | if (e.key === 'Enter') { 62 | toggleTag() 63 | } 64 | }} 65 | onFocus={e => { 66 | if (e.relatedTarget) { 67 | e.target.nextElementSibling?.dispatchEvent( 68 | new KeyboardEvent('focus'), 69 | ) 70 | } 71 | }} 72 | onBlur={e => { 73 | e.target.nextElementSibling?.dispatchEvent( 74 | new KeyboardEvent('blur'), 75 | ) 76 | }} 77 | onChange={toggleTag} 78 | checked={selected} 79 | {...rest} 80 | /> 81 | <label ref={ref} htmlFor={id} className={styles.checkboxLabel}> 82 | {label} 83 | {icon} 84 | </label> 85 | </> 86 | ) 87 | }, 88 | ) 89 | 90 | export default ShowcaseTagSelect 91 | -------------------------------------------------------------------------------- /src/pages/project/_components/ShowcaseTagSelect/styles.module.css: -------------------------------------------------------------------------------- 1 | .checkboxLabel:hover { 2 | opacity: 1; 3 | box-shadow: 0 0 2px 1px var(--ifm-color-secondary-darkest); 4 | } 5 | 6 | input[type='checkbox'] + .checkboxLabel { 7 | display: flex; 8 | align-items: center; 9 | cursor: pointer; 10 | line-height: 1.5; 11 | border-radius: 4px; 12 | padding: 0.275rem 0.8rem; 13 | opacity: 0.85; 14 | transition: opacity 200ms ease-out; 15 | border: 2px solid var(--ifm-color-secondary-darkest); 16 | } 17 | 18 | input:focus-visible + .checkboxLabel { 19 | outline: 2px solid currentColor; 20 | } 21 | 22 | input:checked + .checkboxLabel { 23 | opacity: 0.9; 24 | background-color: var(--site-color-checkbox-checked-bg); 25 | border: 2px solid var(--ifm-color-primary-darkest); 26 | } 27 | 28 | input:checked + .checkboxLabel:hover { 29 | opacity: 0.75; 30 | box-shadow: 0 0 2px 1px var(--ifm-color-primary-dark); 31 | } 32 | -------------------------------------------------------------------------------- /src/pages/project/_components/ShowcaseTooltip/styles.module.css: -------------------------------------------------------------------------------- 1 | .tooltip { 2 | border-radius: 4px; 3 | padding: 4px 8px; 4 | color: var(--site-color-tooltip); 5 | background: var(--site-color-tooltip-background); 6 | font-size: 0.8rem; 7 | z-index: 500; 8 | line-height: 1.4; 9 | font-weight: 500; 10 | max-width: 300px; 11 | opacity: 0.92; 12 | } 13 | 14 | .tooltipArrow { 15 | visibility: hidden; 16 | } 17 | 18 | .tooltipArrow, 19 | .tooltipArrow::before { 20 | position: absolute; 21 | width: 8px; 22 | height: 8px; 23 | background: inherit; 24 | } 25 | 26 | .tooltipArrow::before { 27 | visibility: visible; 28 | content: ''; 29 | transform: rotate(45deg); 30 | } 31 | 32 | .tooltip[data-popper-placement^='top'] > .tooltipArrow { 33 | bottom: -4px; 34 | } 35 | 36 | .tooltip[data-popper-placement^='bottom'] > .tooltipArrow { 37 | top: -4px; 38 | } 39 | -------------------------------------------------------------------------------- /src/pages/project/styles.module.css: -------------------------------------------------------------------------------- 1 | .filterCheckbox { 2 | justify-content: space-between; 3 | } 4 | 5 | .filterCheckbox, 6 | .checkboxList { 7 | display: flex; 8 | align-items: center; 9 | } 10 | 11 | .filterCheckbox > div:first-child { 12 | display: flex; 13 | flex: 1 1 auto; 14 | align-items: center; 15 | } 16 | 17 | .filterCheckbox > div > * { 18 | margin-bottom: 0; 19 | margin-right: 8px; 20 | } 21 | 22 | .checkboxList { 23 | flex-wrap: wrap; 24 | } 25 | 26 | .checkboxList, 27 | .showcaseList { 28 | padding: 0; 29 | list-style: none; 30 | } 31 | 32 | .checkboxListItem { 33 | user-select: none; 34 | white-space: nowrap; 35 | height: 32px; 36 | font-size: 0.8rem; 37 | margin-top: 0.5rem; 38 | margin-right: 0.5rem; 39 | } 40 | 41 | .checkboxListItem:last-child { 42 | margin-right: 0; 43 | } 44 | 45 | .searchContainer { 46 | margin-left: auto; 47 | } 48 | 49 | .searchContainer input { 50 | height: 30px; 51 | border-radius: 15px; 52 | padding: 10px; 53 | border: 1px solid gray; 54 | } 55 | 56 | .showcaseList { 57 | display: grid; 58 | grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); 59 | gap: 24px; 60 | } 61 | 62 | .showcaseFavorite { 63 | padding-top: 2rem; 64 | padding-bottom: 2rem; 65 | background-color: var(--site-color-favorite-background); 66 | } 67 | 68 | .showcaseFavoriteHeader { 69 | display: flex; 70 | align-items: center; 71 | } 72 | 73 | .showcaseFavoriteHeader > h2 { 74 | margin-bottom: 0; 75 | } 76 | 77 | .showcaseFavoriteHeader > svg { 78 | width: 30px; 79 | height: 30px; 80 | } 81 | 82 | .svgIconFavoriteXs, 83 | .svgIconFavorite { 84 | color: var(--site-color-svg-icon-favorite); 85 | } 86 | 87 | .svgIconFavoriteXs { 88 | margin-left: 0.625rem; 89 | font-size: 1rem; 90 | } 91 | 92 | .svgIconFavorite { 93 | margin-left: 1rem; 94 | } 95 | -------------------------------------------------------------------------------- /src/pages/resource/_components/ResourceCard/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import clsx from 'clsx' 3 | import Link from '@docusaurus/Link' 4 | 5 | import styles from './styles.module.css' 6 | import { type Resource } from '@site/data/resource' 7 | import Tooltip from '../../../project/_components/ShowcaseTooltip' 8 | 9 | const ResourceCard = memo(({ resource }: { resource: Resource }) => ( 10 | <li 11 | key={resource.name} 12 | className={clsx(styles.resourceCard, 'padding-vert--sm padding-horiz--md')} 13 | > 14 | <img 15 | src={ 16 | typeof resource.logo === 'string' 17 | ? resource.logo 18 | : (resource.logo as any)?.src?.src 19 | } 20 | alt={resource.name} 21 | className={clsx(styles.resourceCardImage)} 22 | /> 23 | <div className={styles.resourceCardBody}> 24 | <div className={clsx(styles.resourceCardHeader)}> 25 | <h4 className={styles.resourceCardTitle}> 26 | <Link href={resource.href} className={styles.resourceCardLink}> 27 | {resource.name} 28 | </Link> 29 | </h4> 30 | </div> 31 | <Tooltip 32 | key={resource.name} 33 | text={resource.desc} 34 | anchorEl="#__docusaurus" 35 | id={`tooltip-${resource.name}`} 36 | > 37 | <p className={styles.resourceCardDesc}>{resource.desc}</p> 38 | </Tooltip> 39 | </div> 40 | </li> 41 | )) 42 | 43 | export default ResourceCard 44 | -------------------------------------------------------------------------------- /src/pages/resource/_components/ResourceCard/styles.module.css: -------------------------------------------------------------------------------- 1 | .resourceCard { 2 | background-color: var(--ifm-card-background-color); 3 | border-radius: 8px; 4 | border: 1px solid rgb(204 204 204 / 50%); 5 | height: 100%; 6 | display: flex; 7 | flex-direction: row; 8 | overflow: hidden; 9 | transition: all 0.3s ease-in-out; 10 | } 11 | 12 | .resourceCard:hover { 13 | box-shadow: 0 10px 20px -10px rgb(0 0 0 / 20%); 14 | transform: translateY(-5px); 15 | } 16 | 17 | html[data-theme='dark'] .resourceCard:hover { 18 | box-shadow: 0 10px 20px -10px rgb(255 255 255 / 20%); 19 | transform: translateY(-5px); 20 | } 21 | 22 | .resourceCardImage { 23 | align-self: center; 24 | width: 64px; 25 | height: 64px; 26 | min-width: 64px; 27 | object-fit: contain; 28 | } 29 | 30 | .resourceCardHeader { 31 | display: flex; 32 | align-items: center; 33 | margin-bottom: 4px; 34 | } 35 | 36 | .resourceCardTitle { 37 | margin-bottom: 0; 38 | flex: 1 1 auto; 39 | } 40 | 41 | .resourceCardTitle a { 42 | text-decoration: none; 43 | background: linear-gradient( 44 | var(--ifm-color-primary), 45 | var(--ifm-color-primary) 46 | ) 47 | 0% 100% / 0% 1px no-repeat; 48 | transition: background-size ease-out 200ms; 49 | } 50 | 51 | .resourceCardTitle a:not(:focus):hover { 52 | background-size: 100% 1px; 53 | } 54 | 55 | .resourceCardBody { 56 | padding: 0.2rem 0 0 var(--ifm-card-horizontal-spacing); 57 | } 58 | 59 | .resourceCardDesc { 60 | cursor: pointer; 61 | font-size: smaller; 62 | line-height: 1.66; 63 | width: 100%; 64 | margin: 0; 65 | /* stylelint-disable-next-line value-no-vendor-prefix */ 66 | display: -webkit-box; 67 | overflow: hidden; 68 | -webkit-line-clamp: 2; 69 | -webkit-box-orient: vertical; 70 | } 71 | -------------------------------------------------------------------------------- /src/pages/resource/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import clsx from 'clsx' 3 | import Link from '@docusaurus/Link' 4 | import { PageMetadata } from '@docusaurus/theme-common' 5 | import Layout from '@theme/Layout' 6 | import ResourceCard from './_components/ResourceCard' 7 | import BackToTopButton from '@theme/BackToTopButton' 8 | import { resourceData } from '@site/data/resource' 9 | import styles from './resource.module.css' 10 | 11 | function CategoryNav() { 12 | const sidebar = { 13 | title: '', 14 | items: resourceData.map(w => ({ title: w.name, permalink: `#${w.name}` })), 15 | } 16 | 17 | return ( 18 | <nav className={clsx(styles.sidebar, 'thin-scrollbar')}> 19 | <div className={clsx(styles.sidebarItemTitle, 'margin-bottom--md')}> 20 | {sidebar.title} 21 | </div> 22 | <ul className={clsx(styles.sidebarItemList, 'clean-list')}> 23 | {sidebar.items.map(item => ( 24 | <li key={item.permalink} className={styles.sidebarItem}> 25 | <Link 26 | isNavLink 27 | to={item.permalink} 28 | className={styles.sidebarItemLink} 29 | activeClassName={styles.sidebarItemLinkActive} 30 | > 31 | {item.title} 32 | </Link> 33 | </li> 34 | ))} 35 | </ul> 36 | </nav> 37 | ) 38 | } 39 | 40 | function CategoryList() { 41 | return ( 42 | <div className={styles.category}> 43 | {resourceData.map(cate => ( 44 | <div key={cate.name}> 45 | <div className={styles.cateHeader}> 46 | <h2 id={cate.name} className="anchor"> 47 | {cate.name} 48 | <a 49 | className="hash-link" 50 | href={`#${cate.name}`} 51 | title={cate.name} 52 | ></a> 53 | </h2> 54 | </div> 55 | <section> 56 | <ul className={styles.resourceList}> 57 | {cate.resources.map(resource => ( 58 | <ResourceCard key={resource.name} resource={resource} /> 59 | ))} 60 | </ul> 61 | </section> 62 | </div> 63 | ))} 64 | </div> 65 | ) 66 | } 67 | 68 | export default function Resources() { 69 | const title = '网址导航' 70 | const description = '整合日常开发常用,推荐的网站导航页' 71 | 72 | return ( 73 | <> 74 | <PageMetadata title={title} description={description} /> 75 | <Layout> 76 | <div className="container margin-top--md"> 77 | <div className="row"> 78 | <aside className="col col--1"> 79 | <CategoryNav /> 80 | </aside> 81 | <main className="col col--11"> 82 | <CategoryList /> 83 | <BackToTopButton /> 84 | </main> 85 | </div> 86 | </div> 87 | </Layout> 88 | </> 89 | ) 90 | } 91 | -------------------------------------------------------------------------------- /src/pages/resource/resource.module.css: -------------------------------------------------------------------------------- 1 | .sidebar { 2 | max-height: calc(100vh - (var(--ifm-navbar-height) + 2rem)); 3 | overflow-y: auto; 4 | position: sticky; 5 | top: calc(var(--ifm-navbar-height) + 2rem); 6 | } 7 | 8 | .sidebarItemTitle { 9 | font-size: var(--ifm-h3-font-size); 10 | font-weight: var(--ifm-font-weight-bold); 11 | font-family: var(--ifm-heading-font-family); 12 | } 13 | 14 | .sidebarItemList { 15 | font-size: 0.9rem; 16 | } 17 | 18 | .sidebarItem { 19 | margin-top: 0.7rem; 20 | } 21 | 22 | .sidebarItemLink { 23 | color: var(--ifm-font-color-base); 24 | display: block; 25 | } 26 | 27 | .sidebarItemLink:hover { 28 | text-decoration: none; 29 | } 30 | 31 | .sidebarItemLinkActive { 32 | color: var(--ifm-color-primary) !important; 33 | } 34 | 35 | @media (max-width: 996px) { 36 | .sidebar { 37 | display: none; 38 | } 39 | } 40 | 41 | .hero { 42 | align-items: center; 43 | background-color: var(--ifm-color-primary); 44 | color: var(--ifm-font-color-base-inverse); 45 | display: flex; 46 | padding: 2rem 2rem; 47 | } 48 | 49 | .heroTitle { 50 | font-size: 2rem !important; 51 | font-weight: 700; 52 | line-height: 2rem; 53 | color: #fff; 54 | } 55 | 56 | .heroDesc { 57 | margin-bottom: 0; 58 | } 59 | 60 | .cateHeader { 61 | font-size: 1rem; 62 | font-weight: 600; 63 | } 64 | 65 | .cateHeader > h2 { 66 | font-family: sans-serif; 67 | } 68 | 69 | .cateBody { 70 | display: flex; 71 | padding: 0 1rem; 72 | } 73 | 74 | .resourceList { 75 | display: grid; 76 | grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); 77 | gap: 16px; 78 | padding: 0; 79 | } 80 | -------------------------------------------------------------------------------- /src/plugin/plugin-baidu-push/index.js: -------------------------------------------------------------------------------- 1 | module.exports = function (context, options) { 2 | return { 3 | name: 'docusaurus-plugin-baidu-push', 4 | injectHtmlTags() { 5 | return { 6 | headTags: [ 7 | { 8 | tagName: 'script', 9 | innerHTML: ` 10 | (function(){ 11 | var bp = document.createElement('script'); 12 | var curProtocol = window.location.protocol.split(':')[0]; 13 | if (curProtocol === 'https') { 14 | bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; 15 | } 16 | else { 17 | bp.src = 'http://push.zhanzhang.baidu.com/push.js'; 18 | } 19 | bp.defer = true; 20 | var s = document.getElementsByTagName("script")[0]; 21 | s.parentNode.insertBefore(bp, s); 22 | })(); 23 | `, 24 | }, 25 | ], 26 | } 27 | }, 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/plugin/plugin-baidu-tongji/index.js: -------------------------------------------------------------------------------- 1 | module.exports = function (context, options) { 2 | return { 3 | name: 'docusaurus-plugin-baidu-tongji', 4 | injectHtmlTags() { 5 | return { 6 | headTags: [ 7 | { 8 | tagName: 'script', 9 | innerHTML: ` 10 | var _hmt = _hmt || []; 11 | (function() { 12 | var hm = document.createElement("script"); 13 | hm.src = "https://hm.baidu.com/hm.js?c9a3849aa75f9c4a4e65f846cd1a5155"; 14 | hm.defer = true; 15 | var s = document.getElementsByTagName("script")[0]; 16 | s.parentNode.insertBefore(hm, s); 17 | })(); 18 | `, 19 | }, 20 | { 21 | tagName: 'meta', 22 | attributes: { 23 | name: 'baidu-site-verification', 24 | content: 'code-rqLUw5reVS', 25 | }, 26 | }, 27 | ], 28 | } 29 | }, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/plugin/plugin-content-blog/lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | var __awaiter = 3 | (this && this.__awaiter) || 4 | function (thisArg, _arguments, P, generator) { 5 | function adopt(value) { 6 | return value instanceof P 7 | ? value 8 | : new P(function (resolve) { 9 | resolve(value) 10 | }) 11 | } 12 | return new (P || (P = Promise))(function (resolve, reject) { 13 | function fulfilled(value) { 14 | try { 15 | step(generator.next(value)) 16 | } catch (e) { 17 | reject(e) 18 | } 19 | } 20 | function rejected(value) { 21 | try { 22 | step(generator['throw'](value)) 23 | } catch (e) { 24 | reject(e) 25 | } 26 | } 27 | function step(result) { 28 | result.done 29 | ? resolve(result.value) 30 | : adopt(result.value).then(fulfilled, rejected) 31 | } 32 | step((generator = generator.apply(thisArg, _arguments || [])).next()) 33 | }) 34 | } 35 | Object.defineProperty(exports, '__esModule', { value: true }) 36 | const blogPluginExports = require('@docusaurus/plugin-content-blog') 37 | const blogPlugin = blogPluginExports.default 38 | function blogPluginEnhanced(context, options) { 39 | return __awaiter(this, void 0, void 0, function* () { 40 | const blogPluginInstance = yield blogPlugin(context, options) 41 | return Object.assign(Object.assign({}, blogPluginInstance), { 42 | contentLoaded({ content, actions }) { 43 | return __awaiter(this, void 0, void 0, function* () { 44 | // Create default plugin pages 45 | yield blogPluginInstance.contentLoaded({ content, actions }) 46 | // Create your additional pages 47 | const { blogPosts, blogTags } = content 48 | const { setGlobalData } = actions 49 | setGlobalData({ 50 | blogs: blogPosts, 51 | tags: blogTags, 52 | }) 53 | }) 54 | }, 55 | }) 56 | }) 57 | } 58 | module.exports = Object.assign(Object.assign({}, blogPluginExports), { 59 | default: blogPluginEnhanced, 60 | }) 61 | -------------------------------------------------------------------------------- /src/plugin/plugin-content-blog/lib/types.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | Object.defineProperty(exports, '__esModule', { value: true }) 3 | -------------------------------------------------------------------------------- /src/plugin/plugin-content-blog/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plugin-content-blog", 3 | "version": "^2.2.0", 4 | "description": "Blog plugin for Docusaurus.", 5 | "main": "lib/index.js", 6 | "types": "src/plugin-content-blog.d.ts", 7 | "scripts": { 8 | "build": "tsc", 9 | "watch": "tsc --watch" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/facebook/docusaurus.git", 14 | "directory": "packages/docusaurus-plugin-content-blog" 15 | }, 16 | "publishConfig": { 17 | "access": "public" 18 | }, 19 | "license": "MIT", 20 | "dependencies": { 21 | "@docusaurus/core": "^2.2.0", 22 | "@docusaurus/logger": "^2.2.0", 23 | "@docusaurus/mdx-loader": "^2.2.0", 24 | "@docusaurus/utils": "^2.2.0", 25 | "@docusaurus/utils-common": "^2.2.0", 26 | "@docusaurus/utils-validation": "^2.2.0", 27 | "cheerio": "^1.0.0-rc.11", 28 | "feed": "^4.2.2", 29 | "fs-extra": "^10.1.0", 30 | "lodash": "^4.17.21", 31 | "reading-time": "^1.5.0", 32 | "remark-admonitions": "^1.2.1", 33 | "tslib": "^2.4.0", 34 | "unist-util-visit": "^2.0.3", 35 | "utility-types": "^3.10.0", 36 | "webpack": "^5.72.1" 37 | }, 38 | "devDependencies": { 39 | "@docusaurus/types": "^2.2.0", 40 | "escape-string-regexp": "^4.0.0" 41 | }, 42 | "peerDependencies": { 43 | "react": "^16.8.4 || ^17.0.0", 44 | "react-dom": "^16.8.4 || ^17.0.0" 45 | }, 46 | "engines": { 47 | "node": ">=16.14" 48 | }, 49 | "gitHead": "69ac49fc6909517f13615ee40290c4bd00c39df4" 50 | } 51 | -------------------------------------------------------------------------------- /src/plugin/plugin-content-blog/src/index.ts: -------------------------------------------------------------------------------- 1 | import { LoadContext, Plugin } from '@docusaurus/types' 2 | import * as blogPluginExports from '@docusaurus/plugin-content-blog' 3 | import type { PluginOptions } from '@docusaurus/plugin-content-blog' 4 | import { BlogContent } from './types' 5 | 6 | const blogPlugin = blogPluginExports.default 7 | 8 | async function blogPluginEnhanced( 9 | context: LoadContext, 10 | options: PluginOptions, 11 | ): Promise<Plugin<BlogContent>> { 12 | const blogPluginInstance: any = await blogPlugin(context, options) 13 | 14 | return { 15 | ...blogPluginInstance, 16 | async contentLoaded({ content, actions }) { 17 | // Create default plugin pages 18 | await blogPluginInstance.contentLoaded({ content, actions }) 19 | 20 | // Create your additional pages 21 | const { blogPosts, blogTags } = content 22 | const { setGlobalData } = actions 23 | 24 | setGlobalData({ 25 | blogs: blogPosts, 26 | tags: blogTags, 27 | }) 28 | }, 29 | } 30 | } 31 | 32 | module.exports = { 33 | ...blogPluginExports, 34 | default: blogPluginEnhanced, 35 | } 36 | -------------------------------------------------------------------------------- /src/plugin/plugin-content-blog/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { BrokenMarkdownLink, ContentPaths } from '@docusaurus/utils' 2 | import type { BlogPostMetadata } from '@docusaurus/plugin-content-blog' 3 | // @ts-ignore 4 | import type { Tag } from '@docusaurus/types' 5 | // @ts-ignore 6 | import type { Metadata as BlogPaginatedMetadata } from '@theme/BlogListPage' 7 | 8 | export type BlogContentPaths = ContentPaths 9 | 10 | export type BlogContent = { 11 | blogSidebarTitle: string 12 | blogPosts: BlogPost[] 13 | blogListPaginated: BlogPaginated[] 14 | blogTags: BlogTags 15 | blogTagsListPath: string 16 | } 17 | 18 | export type BlogTags = { 19 | [permalink: string]: BlogTag 20 | } 21 | 22 | export type BlogTag = Tag & { 23 | /** Blog post permalinks. */ 24 | items: string[] 25 | pages: BlogPaginated[] 26 | } 27 | 28 | export type BlogPost = { 29 | id: string 30 | metadata: BlogPostMetadata 31 | content: string 32 | } 33 | 34 | export type BlogPaginated = { 35 | metadata: BlogPaginatedMetadata 36 | /** Blog post permalinks. */ 37 | items: string[] 38 | } 39 | 40 | export type BlogBrokenMarkdownLink = BrokenMarkdownLink<BlogContentPaths> 41 | export type BlogMarkdownLoaderOptions = { 42 | siteDir: string 43 | contentPaths: BlogContentPaths 44 | truncateMarker: RegExp 45 | sourceToPermalink: { [aliasedPath: string]: string } 46 | onBrokenMarkdownLink: (brokenMarkdownLink: BlogBrokenMarkdownLink) => void 47 | } 48 | -------------------------------------------------------------------------------- /src/plugin/plugin-content-blog/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2016", 4 | "module": "commonjs", 5 | "skipLibCheck": true, 6 | "noEmit": false, 7 | "incremental": true, 8 | "rootDir": "src", 9 | "outDir": "lib", 10 | "declaration": false 11 | }, 12 | "include": ["src"], 13 | "exclude": [ 14 | "src/theme", 15 | "**/__tests__/**", 16 | "**/node_modules/**", 17 | "**/coverage/**", 18 | "**/lib/**/*", 19 | "**/__mocks__/**/*", 20 | "**/__fixtures__/**/*" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /src/sw.js: -------------------------------------------------------------------------------- 1 | import { registerRoute } from 'workbox-routing' 2 | import { StaleWhileRevalidate } from 'workbox-strategies' 3 | 4 | export default function swCustom(params) { 5 | if (params.debug) { 6 | console.log('[Docusaurus-PWA][SW]: running swCustom code', params) 7 | } 8 | 9 | // Cache responses from external resources 10 | registerRoute( 11 | context => 12 | [ 13 | /graph\.facebook\.com\/.*\/picture/, 14 | /netlify\.com\/img/, 15 | /avatars1\.githubusercontent/, 16 | ].some(regex => context.url.href.match(regex)), 17 | new StaleWhileRevalidate(), 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /src/theme/BlogArchivePage/styles.module.css: -------------------------------------------------------------------------------- 1 | .archiveCount { 2 | text-align: right; 3 | margin-top: -2.5rem; 4 | font-size: 0.85rem; 5 | opacity: 0.8; 6 | } 7 | 8 | .archive { 9 | max-width: 860px; 10 | margin: 0 auto; 11 | padding: 1rem 2.5rem; 12 | 13 | box-shadow: 0 1px 2px 0 rgb(0 0 0 / 10%); 14 | margin-bottom: 1rem; 15 | border-radius: 2px; 16 | background: var(--card-background); 17 | } 18 | 19 | .archiveTitle { 20 | display: inline-flex; 21 | align-items: center; 22 | } 23 | 24 | .archiveYear { 25 | position: relative; 26 | display: inline-flex; 27 | justify-content: space-between; 28 | align-items: flex-end; 29 | margin: 1rem 0 1rem 0.8rem; 30 | width: 100%; 31 | font-weight: 400; 32 | font-size: 1.4rem; 33 | } 34 | 35 | .archiveYear::after { 36 | content: ''; 37 | position: absolute; 38 | width: 100%; 39 | height: 2px; 40 | bottom: -1rem; 41 | left: -0.8rem; 42 | background: var(--ifm-color-primary); 43 | } 44 | 45 | .archiveYear span { 46 | margin-right: 1rem; 47 | font-size: 0.85rem; 48 | font-weight: 300; 49 | color: var(--ifm-secondary-text-color); 50 | } 51 | 52 | .archiveList { 53 | padding: 0; 54 | margin: 0; 55 | } 56 | 57 | .archiveItem { 58 | position: relative; 59 | display: flex; 60 | padding: 0.4rem 0; 61 | border-bottom: 1px solid var(--ifm-color-primary-light); 62 | list-style: none; 63 | } 64 | 65 | .archiveItem::before { 66 | content: ''; 67 | position: absolute; 68 | width: 2px; 69 | top: 0; 70 | height: 100%; 71 | background: rgb(0 120 231 / 30%); 72 | } 73 | 74 | .archiveItem a { 75 | display: flex; 76 | align-items: center; 77 | color: var(--ifm-text-color); 78 | padding: 0.4rem 1rem; 79 | font-size: 1.2rem; 80 | font-weight: 500; 81 | font-family: var(--ifm-font-family-base); 82 | 83 | text-decoration: none; 84 | transition: padding 0.3s; 85 | } 86 | 87 | .archiveItem a::before { 88 | content: ''; 89 | position: absolute; 90 | left: 0; 91 | top: 1.5rem; 92 | width: 0.5rem; 93 | height: 0.5rem; 94 | margin-left: -4px; 95 | border-radius: 50%; 96 | border: 1px solid rgb(0 120 231); 97 | background-color: rgb(255 255 255); 98 | z-index: 1; 99 | transition-duration: 0.3s; 100 | transition-delay: 0s; 101 | transition-property: background; 102 | } 103 | 104 | .archiveItem a:hover::before { 105 | background: rgb(0 120 231); 106 | } 107 | 108 | .archiveTime { 109 | opacity: 0.6; 110 | font-size: 0.85rem; 111 | font-weight: 400; 112 | margin-right: 0.5rem; 113 | width: 2.5rem; 114 | position: relative; 115 | color: var(--hty-secondary-text-color); 116 | 117 | font-family: 'Source Code Pro', Consolas, Monaco, SFMono-Regular, 118 | 'Ubuntu Mono', Menlo, monospace; 119 | } 120 | 121 | .archiveItem a > span:hover { 122 | color: var(--ifm-color-primary); 123 | } 124 | -------------------------------------------------------------------------------- /src/theme/BlogListPage/useViewType.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useState } from 'react' 2 | 3 | type ViewType = 'list' | 'grid' | 'card' 4 | 5 | export function useViewType() { 6 | const [viewType, setViewType] = useState<ViewType>('card') 7 | 8 | useEffect(() => { 9 | setViewType((localStorage.getItem('viewType') as ViewType) || 'card') 10 | }, []) 11 | 12 | const toggleViewType = useCallback((newViewType: ViewType) => { 13 | setViewType(newViewType) 14 | localStorage.setItem('viewType', newViewType) 15 | }, []) 16 | 17 | return { 18 | viewType, 19 | toggleViewType, 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/theme/BlogPostItem/Container/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useBaseUrlUtils } from '@docusaurus/useBaseUrl' 3 | import { useBlogPost } from '@docusaurus/theme-common/internal' 4 | import type { Props } from '@theme/BlogPostItem/Container' 5 | 6 | export default function BlogPostItemContainer({ 7 | children, 8 | className, 9 | }: Props): JSX.Element { 10 | const { frontMatter, assets } = useBlogPost() 11 | const { withBaseUrl } = useBaseUrlUtils() 12 | const image = assets.image ?? frontMatter.image 13 | return ( 14 | <article 15 | className={className} 16 | itemProp="blogPost" 17 | itemScope 18 | itemType="http://schema.org/BlogPosting" 19 | > 20 | {image && ( 21 | <meta 22 | itemProp="image" 23 | content={withBaseUrl(image, { absolute: true })} 24 | /> 25 | )} 26 | {children} 27 | </article> 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /src/theme/BlogPostItem/Container/styles.module.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/src/theme/BlogPostItem/Container/styles.module.css -------------------------------------------------------------------------------- /src/theme/BlogPostItem/Content/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import clsx from 'clsx' 3 | import { blogPostContainerID } from '@docusaurus/utils-common' 4 | import { useBlogPost } from '@docusaurus/theme-common/internal' 5 | import MDXContent from '@theme/MDXContent' 6 | import type { Props } from '@theme/BlogPostItem/Content' 7 | 8 | export default function BlogPostItemContent({ 9 | children, 10 | className, 11 | }: Props): JSX.Element { 12 | const { isBlogPostPage } = useBlogPost() 13 | return ( 14 | <div 15 | // This ID is used for the feed generation to locate the main content 16 | id={isBlogPostPage ? blogPostContainerID : undefined} 17 | className={clsx('markdown', className)} 18 | itemProp="articleBody" 19 | > 20 | <MDXContent>{children}</MDXContent> 21 | </div> 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /src/theme/BlogPostItem/Footer/ReadMoreLink/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Translate, { translate } from '@docusaurus/Translate' 3 | import Link from '@docusaurus/Link' 4 | import type { Props } from '@theme/BlogPostItem/Footer/ReadMoreLink' 5 | 6 | function ReadMoreLabel() { 7 | return ( 8 | <b> 9 | <Translate 10 | id="theme.blog.post.readMore" 11 | description="The label used in blog post item excerpts to link to full blog posts" 12 | > 13 | Read More 14 | </Translate> 15 | </b> 16 | ) 17 | } 18 | 19 | export default function BlogPostItemFooterReadMoreLink( 20 | props: Props, 21 | ): JSX.Element { 22 | const { blogPostTitle, ...linkProps } = props 23 | return ( 24 | <Link 25 | aria-label={translate( 26 | { 27 | message: 'Read more about {title}', 28 | id: 'theme.blog.post.readMoreLabel', 29 | description: 30 | 'The ARIA label for the link to full blog posts from excerpts', 31 | }, 32 | { title: blogPostTitle }, 33 | )} 34 | {...linkProps} 35 | > 36 | <ReadMoreLabel /> 37 | </Link> 38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /src/theme/BlogPostItem/Footer/styles.module.css: -------------------------------------------------------------------------------- 1 | .blogPostFooterDetailsFull { 2 | flex-direction: column; 3 | } 4 | 5 | .blogPostInfo { 6 | margin-top: 0.5em; 7 | display: flex; 8 | flex-wrap: wrap; 9 | align-items: center; 10 | margin-bottom: var(--ifm-spacing-m); 11 | gap: 4px; 12 | color: rgb(156 163 175); 13 | } 14 | 15 | .blogPostInfoTags { 16 | display: flex; 17 | gap: 4px; 18 | } 19 | 20 | .blogPostInfoTags > a { 21 | font-size: 0.8em; 22 | padding: 1px 5px; 23 | } 24 | 25 | .blogPostAuthor { 26 | display: inline-block; 27 | margin: 0 2px; 28 | font-size: 0.95rem; 29 | font-weight: 400; 30 | color: inherit; 31 | } 32 | 33 | .blogPostAuthor:hover { 34 | text-decoration: none; 35 | } 36 | 37 | .blogPostDate, 38 | .blogPostReadTime { 39 | font-size: 0.9rem; 40 | } 41 | 42 | .blogPostDetailsFull { 43 | flex-direction: column; 44 | } 45 | 46 | .divider { 47 | background-color: #eaecef; 48 | border: 0; 49 | height: var(--ifm-hr-height); 50 | margin: 0.25rem 0; 51 | } 52 | 53 | html[data-theme='dark'] .divider { 54 | background-color: #2f3336; 55 | } 56 | -------------------------------------------------------------------------------- /src/theme/BlogPostItem/Header/Author/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import clsx from 'clsx' 3 | import Link, { type Props as LinkProps } from '@docusaurus/Link' 4 | 5 | import type { Props } from '@theme/BlogPostItem/Header/Author' 6 | 7 | function MaybeLink(props: LinkProps): JSX.Element { 8 | if (props.href) { 9 | return <Link {...props} /> 10 | } 11 | return <>{props.children}</> 12 | } 13 | 14 | export default function BlogPostItemHeaderAuthor({ 15 | author, 16 | className, 17 | }: Props): JSX.Element { 18 | const { name, title, url, imageURL, email } = author 19 | const link = url || (email && `mailto:${email}`) || undefined 20 | return ( 21 | <div className={clsx('avatar margin-bottom--sm', className)}> 22 | {imageURL && ( 23 | <MaybeLink href={link} className="avatar__photo-link"> 24 | <img className="avatar__photo" src={imageURL} alt={name} /> 25 | </MaybeLink> 26 | )} 27 | 28 | {name && ( 29 | <div 30 | className="avatar__intro" 31 | itemProp="author" 32 | itemScope 33 | itemType="https://schema.org/Person" 34 | > 35 | <div className="avatar__name"> 36 | <MaybeLink href={link} itemProp="url"> 37 | <span itemProp="name">{name}</span> 38 | </MaybeLink> 39 | </div> 40 | {title && ( 41 | <small className="avatar__subtitle" itemProp="description"> 42 | {title} 43 | </small> 44 | )} 45 | </div> 46 | )} 47 | </div> 48 | ) 49 | } 50 | -------------------------------------------------------------------------------- /src/theme/BlogPostItem/Header/Authors/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import clsx from 'clsx' 3 | import { useBlogPost } from '@docusaurus/theme-common/internal' 4 | import BlogPostItemHeaderAuthor from '@theme/BlogPostItem/Header/Author' 5 | import type { Props } from '@theme/BlogPostItem/Header/Authors' 6 | import styles from './styles.module.css' 7 | 8 | // Component responsible for the authors layout 9 | export default function BlogPostItemHeaderAuthors({ 10 | className, 11 | }: Props): JSX.Element | null { 12 | const { 13 | metadata: { authors }, 14 | assets, 15 | } = useBlogPost() 16 | const authorsCount = authors.length 17 | if (authorsCount === 0) { 18 | return null 19 | } 20 | const imageOnly = authors.every(({ name }) => !name) 21 | return ( 22 | <div 23 | className={clsx( 24 | 'margin-top--md margin-bottom--sm', 25 | imageOnly ? styles.imageOnlyAuthorRow : 'row', 26 | className, 27 | )} 28 | > 29 | {authors.map((author, idx) => ( 30 | <div 31 | className={clsx( 32 | !imageOnly && 'col col--6', 33 | imageOnly ? styles.imageOnlyAuthorCol : styles.authorCol, 34 | )} 35 | key={idx} 36 | > 37 | <BlogPostItemHeaderAuthor 38 | author={{ 39 | ...author, 40 | // Handle author images using relative paths 41 | imageURL: assets.authorsImageUrls[idx] ?? author.imageURL, 42 | }} 43 | /> 44 | </div> 45 | ))} 46 | </div> 47 | ) 48 | } 49 | -------------------------------------------------------------------------------- /src/theme/BlogPostItem/Header/Authors/styles.module.css: -------------------------------------------------------------------------------- 1 | .authorCol { 2 | max-width: inherit !important; 3 | flex-grow: 1 !important; 4 | } 5 | 6 | .imageOnlyAuthorRow { 7 | display: flex; 8 | flex-flow: row wrap; 9 | } 10 | 11 | .imageOnlyAuthorCol { 12 | margin-left: 0.3rem; 13 | margin-right: 0.3rem; 14 | } 15 | -------------------------------------------------------------------------------- /src/theme/BlogPostItem/Header/Info/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import clsx from 'clsx' 3 | import { translate } from '@docusaurus/Translate' 4 | import { usePluralForm } from '@docusaurus/theme-common' 5 | import { useBlogPost } from '@docusaurus/theme-common/internal' 6 | import type { Props } from '@theme/BlogPostItem/Header/Info' 7 | 8 | import styles from './styles.module.css' 9 | 10 | // Very simple pluralization: probably good enough for now 11 | function useReadingTimePlural() { 12 | const { selectMessage } = usePluralForm() 13 | return (readingTimeFloat: number) => { 14 | const readingTime = Math.ceil(readingTimeFloat) 15 | return selectMessage( 16 | readingTime, 17 | translate( 18 | { 19 | id: 'theme.blog.post.readingTime.plurals', 20 | description: 21 | 'Pluralized label for "{readingTime} min read". Use as much plural forms (separated by "|") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)', 22 | message: 'One min read|{readingTime} min read', 23 | }, 24 | { readingTime }, 25 | ), 26 | ) 27 | } 28 | } 29 | 30 | export function ReadingTime({ readingTime }: { readingTime: number }) { 31 | const readingTimePlural = useReadingTimePlural() 32 | return <>{readingTimePlural(readingTime)}</> 33 | } 34 | 35 | function Date({ 36 | date, 37 | formattedDate, 38 | }: { 39 | date: string 40 | formattedDate: string 41 | }) { 42 | return ( 43 | <time dateTime={date} itemProp="datePublished"> 44 | {formattedDate} 45 | </time> 46 | ) 47 | } 48 | 49 | function Spacer() { 50 | return <>{' · '}</> 51 | } 52 | 53 | export default function BlogPostItemHeaderInfo({ 54 | className, 55 | }: Props): JSX.Element { 56 | const { metadata } = useBlogPost() 57 | const { date, formattedDate, readingTime } = metadata 58 | 59 | return ( 60 | <div className={clsx(styles.container, 'margin-vert--md', className)}> 61 | <Date date={date} formattedDate={formattedDate} /> 62 | {typeof readingTime !== 'undefined' && ( 63 | <> 64 | <Spacer /> 65 | <ReadingTime readingTime={readingTime} /> 66 | </> 67 | )} 68 | </div> 69 | ) 70 | } 71 | -------------------------------------------------------------------------------- /src/theme/BlogPostItem/Header/Info/styles.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | font-size: 0.9rem; 3 | } 4 | -------------------------------------------------------------------------------- /src/theme/BlogPostItem/Header/Title/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import clsx from 'clsx' 3 | import Link from '@docusaurus/Link' 4 | import { useBlogPost } from '@docusaurus/theme-common/internal' 5 | import type { Props } from '@theme/BlogPostItem/Header/Title' 6 | 7 | import styles from './styles.module.css' 8 | 9 | export default function BlogPostItemHeaderTitle({ 10 | className, 11 | }: Props): JSX.Element { 12 | const { metadata, isBlogPostPage } = useBlogPost() 13 | const { permalink, title } = metadata 14 | const TitleHeading = isBlogPostPage ? 'h1' : 'h2' 15 | return ( 16 | <TitleHeading className={clsx(styles.title, className)} itemProp="headline"> 17 | {isBlogPostPage ? ( 18 | title 19 | ) : ( 20 | <Link itemProp="url" to={permalink} className={styles.titleLink}> 21 | {title} 22 | </Link> 23 | )} 24 | </TitleHeading> 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /src/theme/BlogPostItem/Header/Title/styles.module.css: -------------------------------------------------------------------------------- 1 | .titleLink { 2 | position: relative; 3 | font-weight: 500; 4 | color: var(--ifm-heading-color); 5 | } 6 | 7 | .titleLink:hover { 8 | color: var(--ifm-link-hover-color); 9 | text-decoration: none; 10 | } 11 | 12 | .titleLink:hover::after { 13 | visibility: visible; 14 | transform: scaleX(1); 15 | } 16 | 17 | .titleLink::after { 18 | content: ''; 19 | position: absolute; 20 | bottom: 0; 21 | left: 0; 22 | width: 100%; 23 | height: 2px; 24 | background: var(--ifm-color-primary); 25 | visibility: hidden; 26 | transition: all 0.3s linear; 27 | transform: scaleX(0); 28 | } 29 | -------------------------------------------------------------------------------- /src/theme/BlogPostItem/Header/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import BlogPostItemHeaderTitle from '@theme/BlogPostItem/Header/Title' 3 | import BlogPostItemHeaderInfo from '@theme/BlogPostItem/Header/Info' 4 | import BlogPostItemHeaderAuthors from '@theme/BlogPostItem/Header/Authors' 5 | import { useBlogPost } from '@docusaurus/theme-common/internal' 6 | 7 | export default function BlogPostItemHeader(): JSX.Element { 8 | const { isBlogPostPage } = useBlogPost() 9 | return ( 10 | <header> 11 | <BlogPostItemHeaderTitle /> 12 | {isBlogPostPage && ( 13 | <> 14 | <BlogPostItemHeaderInfo /> 15 | <BlogPostItemHeaderAuthors /> 16 | </> 17 | )} 18 | </header> 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /src/theme/BlogPostItem/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import clsx from 'clsx' 3 | import { useBlogPost } from '@docusaurus/theme-common/internal' 4 | import BlogPostItemContainer from '@theme/BlogPostItem/Container' 5 | import BlogPostItemHeader from '@theme/BlogPostItem/Header' 6 | import BlogPostItemContent from '@theme/BlogPostItem/Content' 7 | import BlogPostItemFooter from '@theme/BlogPostItem/Footer' 8 | import type { Props } from '@theme/BlogPostItem' 9 | 10 | // apply a bottom margin in list view 11 | function useContainerClassName() { 12 | const { isBlogPostPage } = useBlogPost() 13 | return !isBlogPostPage ? 'blogPost-container margin-bottom--lg' : undefined 14 | } 15 | 16 | export default function BlogPostItem({ 17 | children, 18 | className, 19 | }: Props): JSX.Element { 20 | const containerClassName = useContainerClassName() 21 | return ( 22 | <BlogPostItemContainer className={clsx(containerClassName, className)}> 23 | <BlogPostItemHeader /> 24 | <BlogPostItemContent>{children}</BlogPostItemContent> 25 | <BlogPostItemFooter /> 26 | </BlogPostItemContainer> 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /src/theme/BlogPostItems/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { BlogPostProvider } from '@docusaurus/theme-common/internal' 3 | import BlogPostItem from '@theme/BlogPostItem' 4 | import type { Props } from '@theme/BlogPostItems' 5 | import { Fade } from 'react-awesome-reveal' 6 | 7 | export default function BlogPostItems({ 8 | items, 9 | component: BlogPostItemComponent = BlogPostItem, 10 | }: Props): JSX.Element { 11 | return ( 12 | <> 13 | {items.map(({ content: BlogPostContent }) => ( 14 | <BlogPostProvider 15 | key={BlogPostContent.metadata.permalink} 16 | content={BlogPostContent} 17 | > 18 | <Fade direction="left" duration={800} triggerOnce={true}> 19 | <BlogPostItemComponent> 20 | <BlogPostContent /> 21 | </BlogPostItemComponent> 22 | </Fade> 23 | </BlogPostProvider> 24 | ))} 25 | </> 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /src/theme/BlogPostPage/Metadata/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { PageMetadata } from '@docusaurus/theme-common' 3 | import { useBlogPost } from '@docusaurus/theme-common/internal' 4 | 5 | export default function BlogPostPageMetadata(): JSX.Element { 6 | const { assets, metadata } = useBlogPost() 7 | const { title, description, date, tags, authors, frontMatter } = metadata 8 | 9 | const { keywords } = frontMatter 10 | const image = assets.image ?? frontMatter.image 11 | return ( 12 | <PageMetadata 13 | title={title} 14 | description={description} 15 | keywords={keywords} 16 | image={image} 17 | > 18 | <meta property="og:type" content="article" /> 19 | <meta property="article:published_time" content={date} /> 20 | {/* TODO double check those article meta array syntaxes, see https://ogp.me/#array */} 21 | {authors.some(author => author.url) && ( 22 | <meta 23 | property="article:author" 24 | content={authors 25 | .map(author => author.url) 26 | .filter(Boolean) 27 | .join(',')} 28 | /> 29 | )} 30 | {tags.length > 0 && ( 31 | <meta 32 | property="article:tag" 33 | content={tags.map(tag => tag.label).join(',')} 34 | /> 35 | )} 36 | </PageMetadata> 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /src/theme/BlogPostPage/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { type ReactNode } from 'react' 2 | import clsx from 'clsx' 3 | import { 4 | HtmlClassNameProvider, 5 | ThemeClassNames, 6 | } from '@docusaurus/theme-common' 7 | import { 8 | BlogPostProvider, 9 | useBlogPost, 10 | } from '@docusaurus/theme-common/internal' 11 | import BlogLayout from '@theme/BlogLayout' 12 | import BlogPostItem from '@theme/BlogPostItem' 13 | import BlogPostPaginator from '@theme/BlogPostPaginator' 14 | import BlogPostPageMetadata from '@theme/BlogPostPage/Metadata' 15 | import BackToTopButton from '@theme/BackToTopButton' 16 | import TOC from '@theme/TOC' 17 | import type { Props } from '@theme/BlogPostPage' 18 | import type { BlogSidebar } from '@docusaurus/plugin-content-blog' 19 | import Comment from '@site/src/components/Comment' 20 | 21 | function BlogPostPageContent({ 22 | sidebar, 23 | children, 24 | }: { 25 | sidebar: BlogSidebar 26 | children: ReactNode 27 | }): JSX.Element { 28 | const { metadata, toc } = useBlogPost() 29 | const { nextItem, prevItem, frontMatter } = metadata 30 | const { 31 | hide_table_of_contents: hideTableOfContents, 32 | toc_min_heading_level: tocMinHeadingLevel, 33 | toc_max_heading_level: tocMaxHeadingLevel, 34 | hide_comment: hideComment, 35 | } = frontMatter 36 | 37 | return ( 38 | <BlogLayout 39 | // sidebar={sidebar} 40 | toc={ 41 | !hideTableOfContents && toc.length > 0 ? ( 42 | <TOC 43 | toc={toc} 44 | minHeadingLevel={tocMinHeadingLevel} 45 | maxHeadingLevel={tocMaxHeadingLevel} 46 | /> 47 | ) : undefined 48 | } 49 | > 50 | <BlogPostItem>{children}</BlogPostItem> 51 | 52 | {(nextItem || prevItem) && ( 53 | <BlogPostPaginator nextItem={nextItem} prevItem={prevItem} /> 54 | )} 55 | {!hideComment && <Comment />} 56 | <BackToTopButton /> 57 | </BlogLayout> 58 | ) 59 | } 60 | 61 | export default function BlogPostPage(props: Props): JSX.Element { 62 | const BlogPostContent = props.content 63 | return ( 64 | <BlogPostProvider content={props.content} isBlogPostPage> 65 | <HtmlClassNameProvider 66 | className={clsx( 67 | ThemeClassNames.wrapper.blogPages, 68 | ThemeClassNames.page.blogPostPage, 69 | )} 70 | > 71 | <BlogPostPageMetadata /> 72 | <BlogPostPageContent sidebar={props.sidebar}> 73 | <BlogPostContent /> 74 | </BlogPostPageContent> 75 | </HtmlClassNameProvider> 76 | </BlogPostProvider> 77 | ) 78 | } 79 | -------------------------------------------------------------------------------- /src/theme/BlogSidebar/Desktop/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import clsx from 'clsx' 3 | import Link from '@docusaurus/Link' 4 | import { translate } from '@docusaurus/Translate' 5 | import type { Props } from '@theme/BlogSidebar/Desktop' 6 | 7 | import styles from './styles.module.css' 8 | 9 | export default function BlogSidebarDesktop({ sidebar }: Props): JSX.Element { 10 | return ( 11 | <aside className="col col--3"> 12 | <nav 13 | className={clsx(styles.sidebar, 'thin-scrollbar')} 14 | aria-label={translate({ 15 | id: 'theme.blog.sidebar.navAriaLabel', 16 | message: 'Blog recent posts navigation', 17 | description: 'The ARIA label for recent posts in the blog sidebar', 18 | })} 19 | > 20 | <div className={clsx(styles.sidebarItemTitle, 'margin-bottom--md')}> 21 | {sidebar.title} 22 | </div> 23 | <ul className={clsx(styles.sidebarItemList, 'clean-list')}> 24 | {sidebar.items.map(item => ( 25 | <li key={item.permalink} className={styles.sidebarItem}> 26 | <Link 27 | isNavLink 28 | to={item.permalink} 29 | className={styles.sidebarItemLink} 30 | activeClassName={styles.sidebarItemLinkActive} 31 | > 32 | {item.title} 33 | </Link> 34 | </li> 35 | ))} 36 | </ul> 37 | </nav> 38 | </aside> 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /src/theme/BlogSidebar/Desktop/styles.module.css: -------------------------------------------------------------------------------- 1 | .sidebar { 2 | max-height: calc(100vh - (var(--ifm-navbar-height) + 2rem)); 3 | overflow-y: auto; 4 | position: sticky; 5 | top: calc(var(--ifm-navbar-height) + 2rem); 6 | } 7 | 8 | .sidebarItemTitle { 9 | font-size: var(--ifm-h3-font-size); 10 | font-weight: var(--ifm-font-weight-bold); 11 | font-family: var(--ifm-heading-font-family); 12 | } 13 | 14 | .sidebarItemList { 15 | font-size: 0.9rem; 16 | } 17 | 18 | .sidebarItem { 19 | margin-top: 0.7rem; 20 | } 21 | 22 | .sidebarItemLink { 23 | color: var(--ifm-font-color-base); 24 | display: block; 25 | } 26 | 27 | .sidebarItemLink:hover { 28 | text-decoration: none; 29 | } 30 | 31 | .sidebarItemLinkActive { 32 | color: var(--ifm-color-primary) !important; 33 | } 34 | 35 | @media (max-width: 996px) { 36 | .sidebar { 37 | display: none; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/theme/BlogSidebar/Mobile/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Link from '@docusaurus/Link' 3 | import { NavbarSecondaryMenuFiller } from '@docusaurus/theme-common' 4 | import type { Props } from '@theme/BlogSidebar/Mobile' 5 | 6 | function BlogSidebarMobileSecondaryMenu({ sidebar }: Props): JSX.Element { 7 | return ( 8 | <ul className="menu__list"> 9 | {sidebar.items.map(item => ( 10 | <li key={item.permalink} className="menu__list-item"> 11 | <Link 12 | isNavLink 13 | to={item.permalink} 14 | className="menu__link" 15 | activeClassName="menu__link--active" 16 | > 17 | {item.title} 18 | </Link> 19 | </li> 20 | ))} 21 | </ul> 22 | ) 23 | } 24 | 25 | export default function BlogSidebarMobile(props: Props): JSX.Element { 26 | return ( 27 | <NavbarSecondaryMenuFiller 28 | component={BlogSidebarMobileSecondaryMenu} 29 | props={props} 30 | /> 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /src/theme/BlogSidebar/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useWindowSize } from '@docusaurus/theme-common' 3 | import BlogSidebarDesktop from '@theme/BlogSidebar/Desktop' 4 | import BlogSidebarMobile from '@theme/BlogSidebar/Mobile' 5 | import type { Props } from '@theme/BlogSidebar' 6 | 7 | export default function BlogSidebar({ sidebar }: Props): JSX.Element | null { 8 | const windowSize = useWindowSize() 9 | if (!sidebar?.items.length) { 10 | return null 11 | } 12 | // Mobile sidebar doesn't need to be server-rendered 13 | if (windowSize === 'mobile') { 14 | return <BlogSidebarMobile sidebar={sidebar} /> 15 | } 16 | return <BlogSidebarDesktop sidebar={sidebar} /> 17 | } 18 | -------------------------------------------------------------------------------- /src/theme/BlogTagsListPage/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import clsx from 'clsx' 3 | import { 4 | PageMetadata, 5 | HtmlClassNameProvider, 6 | ThemeClassNames, 7 | translateTagsPageTitle, 8 | } from '@docusaurus/theme-common' 9 | import BlogLayout from '@theme/BlogLayout' 10 | import TagsListByLetter from '@theme/TagsListByLetter' 11 | import { TagsListByFlat } from '../TagsListByLetter' 12 | import type { Props } from '@theme/BlogTagsListPage' 13 | import SearchMetadata from '@theme/SearchMetadata' 14 | import { Icon } from '@iconify/react' 15 | 16 | export default function BlogTagsListPage({ 17 | tags, 18 | sidebar, 19 | }: Props): JSX.Element { 20 | const title = translateTagsPageTitle() 21 | 22 | const [type, setType] = useState<'list' | 'grid'>('list') 23 | 24 | return ( 25 | <HtmlClassNameProvider 26 | className={clsx( 27 | ThemeClassNames.wrapper.blogPages, 28 | ThemeClassNames.page.blogTagsListPage, 29 | )} 30 | > 31 | <PageMetadata title={title} /> 32 | <SearchMetadata tag="blog_tags_list" /> 33 | <BlogLayout sidebar={sidebar}> 34 | <div className="blogtag__swith-view"> 35 | <h1>{title}</h1> 36 | <div> 37 | <Icon 38 | icon="ph:list-fill" 39 | width="24" 40 | height="24" 41 | onClick={() => setType('list')} 42 | color={type === 'list' ? 'var(--ifm-color-primary)' : '#ccc'} 43 | /> 44 | <Icon 45 | icon="ph:grid-four" 46 | width="24" 47 | height="24" 48 | onClick={() => setType('grid')} 49 | color={type === 'grid' ? 'var(--ifm-color-primary)' : '#ccc'} 50 | /> 51 | </div> 52 | </div> 53 | {type === 'list' && <TagsListByLetter tags={tags} />} 54 | {type === 'grid' && <TagsListByFlat tags={tags} />} 55 | </BlogLayout> 56 | </HtmlClassNameProvider> 57 | ) 58 | } 59 | -------------------------------------------------------------------------------- /src/theme/DocTagsListPage/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import clsx from 'clsx' 3 | import { 4 | PageMetadata, 5 | HtmlClassNameProvider, 6 | ThemeClassNames, 7 | translateTagsPageTitle, 8 | } from '@docusaurus/theme-common' 9 | import Layout from '@theme/Layout' 10 | import TagsListByLetter from '@theme/TagsListByLetter' 11 | import SearchMetadata from '@theme/SearchMetadata' 12 | import type { Props } from '@theme/DocTagsListPage' 13 | import { Icon } from '@iconify/react' 14 | 15 | import { TagsListByFlat } from '../TagsListByLetter' 16 | 17 | export default function DocTagsListPage({ tags }: Props): JSX.Element { 18 | const title = translateTagsPageTitle() 19 | 20 | const [type, setType] = useState('letter') 21 | 22 | return ( 23 | <HtmlClassNameProvider 24 | className={clsx( 25 | ThemeClassNames.wrapper.docsPages, 26 | ThemeClassNames.page.docsTagsListPage, 27 | )} 28 | > 29 | <PageMetadata title={title} /> 30 | <SearchMetadata tag="doc_tags_list" /> 31 | <Layout> 32 | <div className="container margin-vert--lg"> 33 | <div className="row"> 34 | <main className="col col--8 col--offset-2"> 35 | <div className="blogtag__swith-view"> 36 | <h1>{title}</h1> 37 | <div> 38 | <div> 39 | <Icon 40 | icon="ph:list-fill" 41 | width="24" 42 | height="24" 43 | onClick={() => setType('list')} 44 | color={ 45 | type === 'list' ? 'var(--ifm-color-primary)' : '#ccc' 46 | } 47 | /> 48 | <Icon 49 | icon="ph:grid-four" 50 | width="24" 51 | height="24" 52 | onClick={() => setType('grid')} 53 | color={ 54 | type === 'grid' ? 'var(--ifm-color-primary)' : '#ccc' 55 | } 56 | /> 57 | </div> 58 | </div> 59 | </div> 60 | {type === 'letter' && <TagsListByLetter tags={tags} />} 61 | {type === 'flat' && <TagsListByFlat tags={tags} />} 62 | </main> 63 | </div> 64 | </div> 65 | </Layout> 66 | </HtmlClassNameProvider> 67 | ) 68 | } 69 | -------------------------------------------------------------------------------- /src/theme/MDXPage/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import clsx from 'clsx' 3 | import { 4 | PageMetadata, 5 | HtmlClassNameProvider, 6 | ThemeClassNames, 7 | } from '@docusaurus/theme-common' 8 | import Layout from '@theme/Layout' 9 | import MDXContent from '@theme/MDXContent' 10 | import TOC from '@theme/TOC' 11 | import type { Props } from '@theme/MDXPage' 12 | 13 | import styles from './styles.module.css' 14 | 15 | export default function MDXPage(props: Props): JSX.Element { 16 | const { content: MDXPageContent } = props 17 | const { 18 | metadata: { title, description, frontMatter }, 19 | } = MDXPageContent 20 | const { wrapperClassName, hide_table_of_contents: hideTableOfContents } = 21 | frontMatter 22 | 23 | return ( 24 | <HtmlClassNameProvider 25 | className={clsx( 26 | wrapperClassName ?? ThemeClassNames.wrapper.mdxPages, 27 | ThemeClassNames.page.mdxPage, 28 | )} 29 | > 30 | <PageMetadata title={title} description={description} /> 31 | <Layout> 32 | <main className="container container--fluid margin-vert--lg"> 33 | <div className={clsx('row', styles.mdxPageWrapper)}> 34 | <div className={clsx('col', 'col--8')}> 35 | <MDXContent> 36 | <MDXPageContent /> 37 | </MDXContent> 38 | </div> 39 | {!hideTableOfContents && MDXPageContent.toc && ( 40 | <div className="col col--2"> 41 | <TOC 42 | toc={MDXPageContent.toc} 43 | minHeadingLevel={frontMatter.toc_min_heading_level} 44 | maxHeadingLevel={frontMatter.toc_max_heading_level} 45 | /> 46 | </div> 47 | )} 48 | </div> 49 | </main> 50 | </Layout> 51 | </HtmlClassNameProvider> 52 | ) 53 | } 54 | -------------------------------------------------------------------------------- /src/theme/MDXPage/styles.module.css: -------------------------------------------------------------------------------- 1 | .mdxPageWrapper { 2 | justify-content: center; 3 | } 4 | -------------------------------------------------------------------------------- /src/theme/Navbar/MobileSidebar/Layout/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import clsx from 'clsx' 3 | import { useNavbarSecondaryMenu } from '@docusaurus/theme-common/internal' 4 | import type { Props } from '@theme/Navbar/MobileSidebar/Layout' 5 | import { BlogUser } from '@site/src/components/BlogInfo/index' 6 | 7 | export default function NavbarMobileSidebarLayout({ 8 | header, 9 | primaryMenu, 10 | secondaryMenu, 11 | }: Props): JSX.Element { 12 | const { shown: secondaryMenuShown } = useNavbarSecondaryMenu() 13 | return ( 14 | <div className="navbar-sidebar"> 15 | {header} 16 | <BlogUser isNavbar /> 17 | <div 18 | className={clsx('navbar-sidebar__items', { 19 | 'navbar-sidebar__items--show-secondary': secondaryMenuShown, 20 | })} 21 | > 22 | <div className="navbar-sidebar__item menu">{primaryMenu}</div> 23 | <div className="navbar-sidebar__item menu">{secondaryMenu}</div> 24 | </div> 25 | </div> 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /src/theme/TagsListByLetter/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { 3 | listTagsByLetters, 4 | type TagLetterEntry, 5 | } from '@docusaurus/theme-common' 6 | import Tag from '@theme/Tag' 7 | import type { Props } from '@theme/TagsListByLetter' 8 | 9 | import styles from './styles.module.css' 10 | 11 | function TagLetterEntryItem({ letterEntry }: { letterEntry: TagLetterEntry }) { 12 | return ( 13 | <article> 14 | <h2>{letterEntry.letter}</h2> 15 | <ul className="padding--none"> 16 | {letterEntry.tags.map(tag => ( 17 | <li key={tag.permalink} className={styles.tag}> 18 | <Tag {...tag} /> 19 | </li> 20 | ))} 21 | </ul> 22 | <hr /> 23 | </article> 24 | ) 25 | } 26 | 27 | export default function TagsListByLetter({ tags }: Props): JSX.Element { 28 | const letterList = listTagsByLetters(tags) 29 | return ( 30 | <section className="margin-vert--lg"> 31 | {letterList.map(letterEntry => ( 32 | <TagLetterEntryItem 33 | key={letterEntry.letter} 34 | letterEntry={letterEntry} 35 | /> 36 | ))} 37 | </section> 38 | ) 39 | } 40 | 41 | export function TagsListByFlat({ tags }: Props): JSX.Element { 42 | console.log(tags) 43 | return ( 44 | <section className="margin-vert--lg"> 45 | <ul className="padding--none"> 46 | {tags.map(tag => ( 47 | <li key={tag.permalink} className={styles.tag}> 48 | <Tag {...tag} /> 49 | </li> 50 | ))} 51 | </ul> 52 | </section> 53 | ) 54 | } 55 | -------------------------------------------------------------------------------- /src/theme/TagsListByLetter/styles.module.css: -------------------------------------------------------------------------------- 1 | .tag { 2 | display: inline-block; 3 | margin: 0.5rem 0.5rem 0 1rem; 4 | } 5 | -------------------------------------------------------------------------------- /src/types.d.ts: -------------------------------------------------------------------------------- 1 | /// <reference types="@docusaurus/plugin-ideal-image" /> 2 | -------------------------------------------------------------------------------- /src/utils/jsUtils.ts: -------------------------------------------------------------------------------- 1 | // Inspired by https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore#_difference 2 | export function difference<T>(...arrays: T[][]): T[] { 3 | return arrays.reduce((a, b) => a.filter(c => !b.includes(c))) 4 | } 5 | 6 | // Inspired by https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore#_sortby-and-_orderby 7 | export function sortBy<T>( 8 | array: T[], 9 | getter: (item: T) => string | number | boolean, 10 | ): T[] { 11 | const sortedArray = [...array] 12 | sortedArray.sort((a, b) => 13 | getter(a) > getter(b) ? 1 : getter(b) > getter(a) ? -1 : 0, 14 | ) 15 | return sortedArray 16 | } 17 | 18 | export function toggleListItem<T>(list: T[], item: T): T[] { 19 | const itemIndex = list.indexOf(item) 20 | if (itemIndex === -1) { 21 | return list.concat(item) 22 | } else { 23 | const newList = [...list] 24 | newList.splice(itemIndex, 1) 25 | return newList 26 | } 27 | } 28 | 29 | export function shuffle(arr) { 30 | let i = arr.length 31 | 32 | while (i) { 33 | const j = Math.floor(Math.random() * i--) 34 | 35 | ;[arr[j], arr[i]] = [arr[i], arr[j]] 36 | } 37 | 38 | return arr 39 | } 40 | -------------------------------------------------------------------------------- /static/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | RewriteEngine On 3 | RewriteCond %{HTTPS} off [OR] 4 | RewriteCond %{HTTP_HOST} ^www\. [NC] 5 | RewriteCond %{HTTP_HOST} ^(?:www\.)?(.+)$ [NC] 6 | RewriteRule ^ https://%1%{REQUEST_URI} [L,NE,R=301] 7 | 8 | 9 | # Compress HTML, CSS, JavaScript, Text, XML and fonts 10 | AddOutputFilterByType DEFLATE application/javascript 11 | AddOutputFilterByType DEFLATE application/rss+xml 12 | AddOutputFilterByType DEFLATE application/vnd.ms-fontobject 13 | AddOutputFilterByType DEFLATE application/x-font 14 | AddOutputFilterByType DEFLATE application/x-font-opentype 15 | AddOutputFilterByType DEFLATE application/x-font-otf 16 | AddOutputFilterByType DEFLATE application/x-font-truetype 17 | AddOutputFilterByType DEFLATE application/x-font-ttf 18 | AddOutputFilterByType DEFLATE application/x-javascript 19 | AddOutputFilterByType DEFLATE application/xhtml+xml 20 | AddOutputFilterByType DEFLATE application/xml 21 | AddOutputFilterByType DEFLATE font/opentype 22 | AddOutputFilterByType DEFLATE font/otf 23 | AddOutputFilterByType DEFLATE font/ttf 24 | AddOutputFilterByType DEFLATE image/svg+xml 25 | AddOutputFilterByType DEFLATE image/x-icon 26 | AddOutputFilterByType DEFLATE text/css 27 | AddOutputFilterByType DEFLATE text/html 28 | AddOutputFilterByType DEFLATE text/javascript 29 | AddOutputFilterByType DEFLATE text/plain 30 | AddOutputFilterByType DEFLATE text/xml 31 | 32 | # Remove browser bugs (only needed for ancient browsers) 33 | BrowserMatch ^Mozilla/4 gzip-only-text/html 34 | BrowserMatch ^Mozilla/4\.0[678] no-gzip 35 | BrowserMatch \bMSIE !no-gzip !gzip-only-text/html 36 | Header append Vary User-Agent 37 | 38 | # cache 39 | AddType application/x-font-woff .woff 40 | AddType video/mp4 .mp4 .m4v 41 | 42 | ExpiresActive on 43 | # Set the default expiry times. 44 | ExpiresDefault "access plus 2 days" 45 | ExpiresByType image/jpg "access plus 1 month" 46 | ExpiresByType image/svg+xml "access 1 month" 47 | ExpiresByType image/gif "access plus 1 month" 48 | ExpiresByType image/jpeg "access plus 1 month" 49 | ExpiresByType image/png "access plus 1 month" 50 | ExpiresByType image/webp "access plus 1 month" 51 | ExpiresByType video/mp4 "access plus 1 month" 52 | ExpiresByType font/opentype "access plus 1 month" 53 | ExpiresByType font/otf "access plus 1 month" 54 | ExpiresByType font/ttf "access plus 1 month" 55 | ExpiresByType application/x-font-woff "access plus 1 month" 56 | ExpiresByType application/font-woff2 "access plus 1 month" 57 | ExpiresByType text/css "access plus 1 month" 58 | ExpiresByType text/javascript "access plus 1 month" 59 | ExpiresByType application/javascript "access plus 1 month" 60 | ExpiresByType application/x-shockwave-flash "access plus 1 month" 61 | ExpiresByType image/ico "access plus 1 month" 62 | ExpiresByType image/x-icon "access plus 1 month" 63 | ExpiresByType text/html "access plus 600 seconds" -------------------------------------------------------------------------------- /static/img/buildwith.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/buildwith.png -------------------------------------------------------------------------------- /static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/favicon.ico -------------------------------------------------------------------------------- /static/img/icons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/icons/favicon.ico -------------------------------------------------------------------------------- /static/img/icons/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/icons/icon-128.png -------------------------------------------------------------------------------- /static/img/icons/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/icons/icon-16.png -------------------------------------------------------------------------------- /static/img/icons/icon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/icons/icon-256.png -------------------------------------------------------------------------------- /static/img/icons/icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/icons/icon-32.png -------------------------------------------------------------------------------- /static/img/icons/icon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/icons/icon-48.png -------------------------------------------------------------------------------- /static/img/icons/icon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/icons/icon-64.png -------------------------------------------------------------------------------- /static/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/logo.png -------------------------------------------------------------------------------- /static/img/logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/logo.webp -------------------------------------------------------------------------------- /static/img/resource/antv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/antv.png -------------------------------------------------------------------------------- /static/img/resource/any-rule.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/any-rule.ico -------------------------------------------------------------------------------- /static/img/resource/apifox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/apifox.png -------------------------------------------------------------------------------- /static/img/resource/atoolbox.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/atoolbox.ico -------------------------------------------------------------------------------- /static/img/resource/axios.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/axios.ico -------------------------------------------------------------------------------- /static/img/resource/bilibili.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/bilibili.ico -------------------------------------------------------------------------------- /static/img/resource/bootcdn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/bootcdn.png -------------------------------------------------------------------------------- /static/img/resource/coding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/coding.png -------------------------------------------------------------------------------- /static/img/resource/component party.svg: -------------------------------------------------------------------------------- 1 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#DD2E44" d="M11.626 7.488c-.112.112-.197.247-.268.395l-.008-.008L.134 33.141l.011.011c-.208.403.14 1.223.853 1.937.713.713 1.533 1.061 1.936.853l.01.01L28.21 24.735l-.008-.009c.147-.07.282-.155.395-.269 1.562-1.562-.971-6.627-5.656-11.313-4.687-4.686-9.752-7.218-11.315-5.656z"/><path fill="#EA596E" d="M13 12L.416 32.506l-.282.635.011.011c-.208.403.14 1.223.853 1.937.232.232.473.408.709.557L17 17l-4-5z"/><path fill="#A0041E" d="M23.012 13.066c4.67 4.672 7.263 9.652 5.789 11.124-1.473 1.474-6.453-1.118-11.126-5.788-4.671-4.672-7.263-9.654-5.79-11.127 1.474-1.473 6.454 1.119 11.127 5.791z"/><path fill="#AA8DD8" d="M18.59 13.609c-.199.161-.459.245-.734.215-.868-.094-1.598-.396-2.109-.873-.541-.505-.808-1.183-.735-1.862.128-1.192 1.324-2.286 3.363-2.066.793.085 1.147-.17 1.159-.292.014-.121-.277-.446-1.07-.532-.868-.094-1.598-.396-2.11-.873-.541-.505-.809-1.183-.735-1.862.13-1.192 1.325-2.286 3.362-2.065.578.062.883-.057 1.012-.134.103-.063.144-.123.148-.158.012-.121-.275-.446-1.07-.532-.549-.06-.947-.552-.886-1.102.059-.549.55-.946 1.101-.886 2.037.219 2.973 1.542 2.844 2.735-.13 1.194-1.325 2.286-3.364 2.067-.578-.063-.88.057-1.01.134-.103.062-.145.123-.149.157-.013.122.276.446 1.071.532 2.037.22 2.973 1.542 2.844 2.735-.129 1.192-1.324 2.286-3.362 2.065-.578-.062-.882.058-1.012.134-.104.064-.144.124-.148.158-.013.121.276.446 1.07.532.548.06.947.553.886 1.102-.028.274-.167.511-.366.671z"/><path fill="#77B255" d="M30.661 22.857c1.973-.557 3.334.323 3.658 1.478.324 1.154-.378 2.615-2.35 3.17-.77.216-1.001.584-.97.701.034.118.425.312 1.193.095 1.972-.555 3.333.325 3.657 1.479.326 1.155-.378 2.614-2.351 3.17-.769.216-1.001.585-.967.702.033.117.423.311 1.192.095.53-.149 1.084.16 1.233.691.148.532-.161 1.084-.693 1.234-1.971.555-3.333-.323-3.659-1.479-.324-1.154.379-2.613 2.353-3.169.77-.217 1.001-.584.967-.702-.032-.117-.422-.312-1.19-.096-1.974.556-3.334-.322-3.659-1.479-.325-1.154.378-2.613 2.351-3.17.768-.215.999-.585.967-.701-.034-.118-.423-.312-1.192-.096-.532.15-1.083-.16-1.233-.691-.149-.53.161-1.082.693-1.232z"/><path fill="#AA8DD8" d="M23.001 20.16c-.294 0-.584-.129-.782-.375-.345-.432-.274-1.061.156-1.406.218-.175 5.418-4.259 12.767-3.208.547.078.927.584.849 1.131-.078.546-.58.93-1.132.848-6.493-.922-11.187 2.754-11.233 2.791-.186.148-.406.219-.625.219z"/><path fill="#77B255" d="M5.754 16c-.095 0-.192-.014-.288-.042-.529-.159-.829-.716-.67-1.245 1.133-3.773 2.16-9.794.898-11.364-.141-.178-.354-.353-.842-.316-.938.072-.849 2.051-.848 2.071.042.551-.372 1.031-.922 1.072-.559.034-1.031-.372-1.072-.923-.103-1.379.326-4.035 2.692-4.214 1.056-.08 1.933.287 2.552 1.057 2.371 2.951-.036 11.506-.542 13.192-.13.433-.528.712-.958.712z"/><circle fill="#5C913B" cx="25.5" cy="9.5" r="1.5"/><circle fill="#9266CC" cx="2" cy="18" r="2"/><circle fill="#5C913B" cx="32.5" cy="19.5" r="1.5"/><circle fill="#5C913B" cx="23.5" cy="31.5" r="1.5"/><circle fill="#FFCC4D" cx="28" cy="4" r="2"/><circle fill="#FFCC4D" cx="32.5" cy="8.5" r="1.5"/><circle fill="#FFCC4D" cx="29.5" cy="12.5" r="1.5"/><circle fill="#FFCC4D" cx="7.5" cy="23.5" r="1.5"/></svg> -------------------------------------------------------------------------------- /static/img/resource/coolify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/coolify.png -------------------------------------------------------------------------------- /static/img/resource/coolors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/coolors.png -------------------------------------------------------------------------------- /static/img/resource/css-inspiration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/css-inspiration.png -------------------------------------------------------------------------------- /static/img/resource/cssfx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/cssfx.png -------------------------------------------------------------------------------- /static/img/resource/cypress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/cypress.png -------------------------------------------------------------------------------- /static/img/resource/dbyun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/dbyun.png -------------------------------------------------------------------------------- /static/img/resource/digitalocean.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/digitalocean.png -------------------------------------------------------------------------------- /static/img/resource/electron.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/electron.ico -------------------------------------------------------------------------------- /static/img/resource/es6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/es6.png -------------------------------------------------------------------------------- /static/img/resource/figma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/figma.png -------------------------------------------------------------------------------- /static/img/resource/fresh.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/fresh.ico -------------------------------------------------------------------------------- /static/img/resource/gitee.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/gitee.ico -------------------------------------------------------------------------------- /static/img/resource/github.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/github.ico -------------------------------------------------------------------------------- /static/img/resource/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/github.png -------------------------------------------------------------------------------- /static/img/resource/google_fonts.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/google_fonts.ico -------------------------------------------------------------------------------- /static/img/resource/graphQL.svg: -------------------------------------------------------------------------------- 1 | <svg viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg"><g fill="#e10098"><path d="m57.468 302.66-14.376-8.3 160.15-277.38 14.376 8.3z"/><path d="m39.8 272.2h320.3v16.6h-320.3z"/><path d="m206.348 374.026-160.21-92.5 8.3-14.376 160.21 92.5zm139.174-241.079-160.21-92.5 8.3-14.376 160.21 92.5z"/><path d="m54.482 132.883-8.3-14.375 160.21-92.5 8.3 14.376z"/><path d="m342.568 302.663-160.15-277.38 14.376-8.3 160.15 277.38zm-290.068-195.163h16.6v185h-16.6zm278.4 0h16.6v185h-16.6z"/><path d="m203.522 367-7.25-12.558 139.34-80.45 7.25 12.557z"/><path d="m369.5 297.9c-9.6 16.7-31 22.4-47.7 12.8s-22.4-31-12.8-47.7 31-22.4 47.7-12.8c16.8 9.7 22.5 31 12.8 47.7m-278.6-160.9c-9.6 16.7-31 22.4-47.7 12.8s-22.4-31-12.8-47.7 31-22.4 47.7-12.8c16.7 9.7 22.4 31 12.8 47.7m-60.4 160.9c-9.6-16.7-3.9-38 12.8-47.7 16.7-9.6 38-3.9 47.7 12.8 9.6 16.7 3.9 38-12.8 47.7-16.8 9.6-38.1 3.9-47.7-12.8m278.6-160.9c-9.6-16.7-3.9-38 12.8-47.7 16.7-9.6 38-3.9 47.7 12.8 9.6 16.7 3.9 38-12.8 47.7-16.7 9.6-38.1 3.9-47.7-12.8m-109.1 258.8c-19.3 0-34.9-15.6-34.9-34.9s15.6-34.9 34.9-34.9 34.9 15.6 34.9 34.9c0 19.2-15.6 34.9-34.9 34.9m0-321.8c-19.3 0-34.9-15.6-34.9-34.9s15.6-34.9 34.9-34.9 34.9 15.6 34.9 34.9-15.6 34.9-34.9 34.9"/></g></svg> -------------------------------------------------------------------------------- /static/img/resource/hoppscotch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/hoppscotch.png -------------------------------------------------------------------------------- /static/img/resource/igoutu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/igoutu.png -------------------------------------------------------------------------------- /static/img/resource/javascript.svg: -------------------------------------------------------------------------------- 1 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill="#F0DB4F" d="M1.408 1.408h125.184v125.185H1.408z"/><path fill="#323330" d="M116.347 96.736c-.917-5.711-4.641-10.508-15.672-14.981-3.832-1.761-8.104-3.022-9.377-5.926-.452-1.69-.512-2.642-.226-3.665.821-3.32 4.784-4.355 7.925-3.403 2.023.678 3.938 2.237 5.093 4.724 5.402-3.498 5.391-3.475 9.163-5.879-1.381-2.141-2.118-3.129-3.022-4.045-3.249-3.629-7.676-5.498-14.756-5.355l-3.688.477c-3.534.893-6.902 2.748-8.877 5.235-5.926 6.724-4.236 18.492 2.975 23.335 7.104 5.332 17.54 6.545 18.873 11.531 1.297 6.104-4.486 8.08-10.234 7.378-4.236-.881-6.592-3.034-9.139-6.949-4.688 2.713-4.688 2.713-9.508 5.485 1.143 2.499 2.344 3.63 4.26 5.795 9.068 9.198 31.76 8.746 35.83-5.176.165-.478 1.261-3.666.38-8.581zM69.462 58.943H57.753l-.048 30.272c0 6.438.333 12.34-.714 14.149-1.713 3.558-6.152 3.117-8.175 2.427-2.059-1.012-3.106-2.451-4.319-4.485-.333-.584-.583-1.036-.667-1.071l-9.52 5.83c1.583 3.249 3.915 6.069 6.902 7.901 4.462 2.678 10.459 3.499 16.731 2.059 4.082-1.189 7.604-3.652 9.448-7.401 2.666-4.915 2.094-10.864 2.07-17.444.06-10.735.001-21.468.001-32.237z"/></svg> -------------------------------------------------------------------------------- /static/img/resource/jest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/jest.png -------------------------------------------------------------------------------- /static/img/resource/jsdelivr.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/jsdelivr.webp -------------------------------------------------------------------------------- /static/img/resource/juejin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/juejin.png -------------------------------------------------------------------------------- /static/img/resource/loading.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/loading.ico -------------------------------------------------------------------------------- /static/img/resource/mdn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/mdn.png -------------------------------------------------------------------------------- /static/img/resource/naiveUI.svg: -------------------------------------------------------------------------------- 1 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 207.74 252.58"><defs><style>.cls-1{fill:#93ceaa;}.cls-2{fill:#4c9717;}.cls-3{fill:#5fbc21;}.cls-4{fill:#e8ceaa;opacity:0.6;}</style></defs><title>Naive UI - LOGO -------------------------------------------------------------------------------- /static/img/resource/namae.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/namae.png -------------------------------------------------------------------------------- /static/img/resource/netlify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/netlify.png -------------------------------------------------------------------------------- /static/img/resource/nuxt.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/img/resource/ossinsight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/ossinsight.png -------------------------------------------------------------------------------- /static/img/resource/prisma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/prisma.png -------------------------------------------------------------------------------- /static/img/resource/quick reference.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/img/resource/railway.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/railway.png -------------------------------------------------------------------------------- /static/img/resource/remix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/remix.png -------------------------------------------------------------------------------- /static/img/resource/roadmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/roadmap.png -------------------------------------------------------------------------------- /static/img/resource/runoob.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/runoob.png -------------------------------------------------------------------------------- /static/img/resource/rust-logo-blk.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/img/resource/rust.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/img/resource/shields.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/shields.png -------------------------------------------------------------------------------- /static/img/resource/stackblitz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/stackblitz.png -------------------------------------------------------------------------------- /static/img/resource/strapi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/strapi.png -------------------------------------------------------------------------------- /static/img/resource/supabase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/supabase.png -------------------------------------------------------------------------------- /static/img/resource/swc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/swc.png -------------------------------------------------------------------------------- /static/img/resource/swr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/swr.png -------------------------------------------------------------------------------- /static/img/resource/taro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/taro.png -------------------------------------------------------------------------------- /static/img/resource/terminalgif.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/terminalgif.ico -------------------------------------------------------------------------------- /static/img/resource/twind.svg: -------------------------------------------------------------------------------- 1 | 3 | 38 | 39 | 41 | 43 | 44 | 45 | 46 | 50 | 52 | 54 | 57 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /static/img/resource/typeorm.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/typeorm.ico -------------------------------------------------------------------------------- /static/img/resource/typescript.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/typescript.png -------------------------------------------------------------------------------- /static/img/resource/typing-svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/typing-svg.png -------------------------------------------------------------------------------- /static/img/resource/uiverse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/uiverse.png -------------------------------------------------------------------------------- /static/img/resource/vben-admin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/vben-admin.png -------------------------------------------------------------------------------- /static/img/resource/vite.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /static/img/resource/webgradients.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/webgradients.png -------------------------------------------------------------------------------- /static/img/resource/webpack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/webpack.png -------------------------------------------------------------------------------- /static/img/resource/zhubai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxsoftware/blog/b3fea6bebbd27dd914ae1b495a09923e7daa61d3/static/img/resource/zhubai.png -------------------------------------------------------------------------------- /static/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "東雲研究所的小站", 3 | "short_name": "東雲研究所的小站", 4 | "theme_color": "#12affa", 5 | "background_color": "#424242", 6 | "display": "standalone", 7 | "scope": "./", 8 | "start_url": "./index.html", 9 | "related_applications": [ 10 | { 11 | "platform": "webapp", 12 | "url": "https://blog.xxsoftware.fun/manifest.json" 13 | } 14 | ], 15 | "icons": [ 16 | { 17 | "src": "img/icons/icon-16.png", 18 | "sizes": "16x16", 19 | "type": "image/png" 20 | }, 21 | { 22 | "src": "img/icons/icon-32.png", 23 | "sizes": "32x32", 24 | "type": "image/png" 25 | }, 26 | { 27 | "src": "img/icons/icon-48.png", 28 | "sizes": "48x48", 29 | "type": "image/png" 30 | }, 31 | { 32 | "src": "img/icons/icon-64.png", 33 | "sizes": "64x64", 34 | "type": "image/png" 35 | }, 36 | { 37 | "src": "img/icons/icon-128.png", 38 | "sizes": "128x128", 39 | "type": "image/png" 40 | }, 41 | { 42 | "src": "img/icons/icon-256.png", 43 | "sizes": "256x256", 44 | "type": "image/png" 45 | } 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /static/svg/juejin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // This file is not used in compilation. It is here just for a nice editor experience. 3 | "extends": "@tsconfig/docusaurus/tsconfig.json", 4 | "compilerOptions": { 5 | "lib": ["DOM", "ESNext"], 6 | "baseUrl": ".", 7 | "resolveJsonModule": true, 8 | 9 | // Duplicated from the root config, because TS does not support extending 10 | // multiple configs and we want to dogfood the @tsconfig/docusaurus one 11 | "allowUnreachableCode": false, 12 | "exactOptionalPropertyTypes": false, 13 | "noFallthroughCasesInSwitch": true, 14 | "noImplicitOverride": true, 15 | "noImplicitReturns": true, 16 | "noPropertyAccessFromIndexSignature": false, 17 | "noUncheckedIndexedAccess": true, 18 | "strict": true, 19 | "alwaysStrict": true, 20 | "noImplicitAny": false, 21 | "noImplicitThis": true, 22 | "strictBindCallApply": true, 23 | "strictFunctionTypes": true, 24 | "strictNullChecks": true, 25 | "strictPropertyInitialization": true, 26 | "useUnknownInCatchVariables": true, 27 | "noUnusedLocals": false, 28 | "noUnusedParameters": false, 29 | "importsNotUsedAsValues": "remove", 30 | 31 | "moduleResolution": "NodeNext", 32 | 33 | // This is important. We run `yarn tsc` in website so we can catch issues 34 | // with our declaration files (mostly names that are forgotten to be 35 | // imported, invalid semantics...). Because we don't have end-to-end type 36 | // tests, removing this would make things much harder to catch. 37 | "skipLibCheck": false, 38 | "types": ["jest"] 39 | }, 40 | "exclude": ["src/sw.js"] 41 | } 42 | --------------------------------------------------------------------------------