├── .bunfig.toml
├── .changelogrc.js
├── .commitlintrc.js
├── .dumirc.ts
├── .editorconfig
├── .env.example
├── .eslintignore
├── .eslintrc.js
├── .fatherrc.ts
├── .github
├── ISSUE_TEMPLATE
│ ├── 1_bug_report.yml
│ ├── 2_feature_request.yml
│ ├── 3_question.yml
│ └── other.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ ├── auto-merge.yml
│ ├── issue-check-inactive.yml
│ ├── issue-close-require.yml
│ ├── issue-remove-inactive.yml
│ ├── release.yml
│ └── test.yml
├── .gitignore
├── .husky
├── commit-msg
└── pre-commit
├── .i18nrc.js
├── .npmrc
├── .prettierignore
├── .prettierrc.js
├── .releaserc.js
├── .remarkrc.js
├── .stylelintrc.js
├── CHANGELOG.md
├── LICENSE
├── README.md
├── clean-package.config.js
├── docs
├── changelog.md
├── index.md
└── index.tsx
├── package.json
├── public
└── robots.txt
├── renovate.json
├── scripts
└── syncSimpleIconList.mjs
├── src
├── Readme
│ └── share.ts
├── ReadmeContributing
│ ├── index.md
│ └── index.tsx
├── ReadmeCredits
│ ├── index.md
│ └── index.tsx
├── ReadmeDevelopment
│ ├── index.md
│ └── index.tsx
├── ReadmeFeatures
│ ├── index.md
│ └── index.tsx
├── ReadmeHero
│ ├── index.md
│ └── index.tsx
├── ReadmeInstallation
│ ├── index.md
│ └── index.tsx
├── ReadmeLicense
│ ├── index.md
│ └── index.tsx
├── ShieldShare
│ ├── index.md
│ └── index.tsx
├── ShieldsBilibili
│ ├── index.md
│ └── index.tsx
├── ShieldsCustom
│ ├── Double.tsx
│ ├── Single.tsx
│ ├── Website.tsx
│ ├── index.md
│ ├── index.tsx
│ └── share.ts
├── ShieldsDiscord
│ ├── index.md
│ └── index.tsx
├── ShieldsDocker
│ ├── index.md
│ ├── index.tsx
│ └── share.ts
├── ShieldsGithub
│ ├── Action.tsx
│ ├── Codespace.tsx
│ ├── Contributors.tsx
│ ├── Release.tsx
│ ├── Social.tsx
│ ├── StarHistory.tsx
│ ├── index.md
│ ├── index.tsx
│ └── share.ts
├── ShieldsNpm
│ ├── index.md
│ ├── index.tsx
│ └── share.ts
├── ShieldsSocial
│ ├── index.md
│ └── index.tsx
├── ShieldsVercel
│ ├── Deploy.tsx
│ ├── Website.tsx
│ ├── index.md
│ ├── index.tsx
│ └── share.ts
├── ShieldsWebsite
│ ├── index.md
│ └── index.tsx
├── Sponsor
│ ├── Avatar.tsx
│ ├── const.ts
│ ├── demos
│ │ ├── data.ts
│ │ └── index.tsx
│ ├── index.md
│ ├── index.tsx
│ ├── style.ts
│ └── utils.ts
├── components
│ ├── Highlight
│ │ └── index.tsx
│ ├── Label
│ │ └── index.tsx
│ ├── Markdown
│ │ ├── HighlightStyle.tsx
│ │ ├── index.tsx
│ │ └── style.ts
│ ├── MarkdownEditor
│ │ ├── index.tsx
│ │ └── style.ts
│ ├── MarkdownPreivew
│ │ ├── index.tsx
│ │ └── style.ts
│ ├── MarkdownStorybook
│ │ ├── index.tsx
│ │ └── style.ts
│ └── Title
│ │ └── index.tsx
├── const
│ ├── dockerShieldControls.ts
│ ├── githubShieldControls.ts
│ ├── icons.ts
│ ├── npmShieldControls.ts
│ ├── sample.ts
│ ├── shareShieldControls.ts
│ ├── shieldBaseControls.ts
│ ├── socialShieldControls.ts
│ └── url.ts
├── index.ts
├── services
│ ├── genCustomShield.ts
│ ├── genDockerShield.ts
│ ├── genGithubShield.ts
│ ├── genMarkdownContributing.ts
│ ├── genMarkdownCredits.ts
│ ├── genMarkdownDevelopment.ts
│ ├── genMarkdownFeatures.ts
│ ├── genMarkdownHero.ts
│ ├── genMarkdownInstallation.ts
│ ├── genMarkdownLicense.ts
│ ├── genNpmShield.ts
│ ├── genShareShield.ts
│ ├── genSocialShield.ts
│ ├── genSponsor.tsx
│ ├── genVercelShield.ts
│ └── sponsorkit
│ │ ├── github
│ │ ├── index.ts
│ │ └── makeQuery.ts
│ │ ├── index.ts
│ │ ├── opencollective
│ │ ├── index.ts
│ │ └── makeQuery.ts
│ │ └── types.ts
├── store
│ ├── action.ts
│ ├── index.ts
│ ├── initialState.ts
│ ├── selectors.ts
│ └── store.ts
├── types
│ ├── i18next.d.ts
│ └── shields.ts
└── utils
│ ├── addBackTopTop.ts
│ ├── formatCustomLabel.ts
│ ├── genPickList.ts
│ ├── genShield.ts
│ ├── genSiteHeadTitle.ts
│ ├── genThemeModeImg.ts
│ └── remarkFormat.ts
├── tests
└── .gitkeep
├── tsconfig.json
├── vercel.json
└── vitest.config.ts
/.bunfig.toml:
--------------------------------------------------------------------------------
1 | [install.lockfile]
2 |
3 | save = false
4 |
--------------------------------------------------------------------------------
/.changelogrc.js:
--------------------------------------------------------------------------------
1 | module.exports = require('@lobehub/lint').changelog;
2 |
--------------------------------------------------------------------------------
/.commitlintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = require('@lobehub/lint').commitlint;
2 |
--------------------------------------------------------------------------------
/.dumirc.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'dumi';
2 | import { SiteThemeConfig } from 'dumi-theme-lobehub';
3 | import { INavItem } from 'dumi/dist/client/theme-api/types';
4 |
5 | import { description, homepage } from './package.json';
6 |
7 | const isProduction = process.env.NODE_ENV === 'production';
8 |
9 | const isWin = process.platform === 'win32';
10 |
11 | const nav: INavItem[] = [
12 | { link: '/components/readme-hero', title: 'Generator' },
13 | { link: 'https://simpleicons.org/', mode: 'override', title: 'Icons' },
14 | { link: '/changelog', title: 'Changelog' },
15 | ];
16 |
17 | const themeConfig: SiteThemeConfig = {
18 | actions: [
19 | {
20 | icon: 'Github',
21 | link: homepage,
22 | openExternal: true,
23 | text: 'Github',
24 | },
25 | {
26 | link: '/components/readme-hero',
27 | text: 'Get Started',
28 | type: 'primary',
29 | },
30 | ],
31 | analytics: {
32 | plausible: {
33 | domain: 'readme-wizard.lobehub.com',
34 | scriptBaseUrl: 'https://plausible.lobehub-inc.cn',
35 | },
36 | },
37 | apiHeader: {
38 | docUrl: `{github}/tree/master/src/{atomId}/index.md`,
39 | match: ['/components'],
40 | sourceUrl: `{github}/tree/master/src/{atomId}/index.tsx`,
41 | type: 'doc',
42 | },
43 | description,
44 | giscus: {
45 | category: 'Ideas',
46 | categoryId: 'DIC_kwDOJloKoM4CXsCu',
47 | repo: 'lobehub/lobe-readme-wizard',
48 | repoId: 'R_kgDOKTF8TQ',
49 | },
50 | metadata: {
51 | openGraph: {
52 | image:
53 | 'https://repository-images.githubusercontent.com/691108941/a66e25b3-1481-429c-b565-419bfb859ecb',
54 | },
55 | },
56 | name: 'ReadmeWizard',
57 | nav,
58 | prefersColor: {
59 | default: 'dark',
60 | switch: false,
61 | },
62 | socialLinks: {
63 | discord: 'https://discord.gg/AYFPHvv2jT',
64 | github: homepage,
65 | },
66 | title: 'ReadmeWizard - LobeHub',
67 | };
68 |
69 | export default defineConfig({
70 | base: '/',
71 | define: {
72 | 'process.env': process.env,
73 | },
74 | exportStatic: {},
75 | extraBabelPlugins: ['antd-style'],
76 | favicons: ['https://lobehub.com/favicon.ico'],
77 | jsMinifier: 'swc',
78 | locales: [{ id: 'en-US', name: 'English' }],
79 | mfsu: isWin ? undefined : {},
80 | npmClient: 'pnpm',
81 | publicPath: '/',
82 | sitemap: {
83 | hostname: 'https://readme-wizard.lobehub.com',
84 | },
85 | ssr: isProduction ? {} : false,
86 | styles: [
87 | `html, body { background: transparent; }
88 |
89 | @media (prefers-color-scheme: dark) {
90 | html, body { background: #000; }
91 | }`,
92 | ],
93 | themeConfig,
94 | title: 'ReadmeWizard',
95 | });
96 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
15 | [Makefile]
16 | indent_style = tab
17 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | SPONSORKIT_GITHUB_TOKEN=xxxxxx
2 | SPONSORKIT_GITHUB_LOGIN=lobehub
3 | SPONSORKIT_GITHUB_TYPE=organization
4 |
5 | SPONSORKIT_OPENCOLLECTIVE_ID=lobehub
6 | SPONSORKIT_OPENCOLLECTIVE_KEY=xxxxxx
7 | SPONSORKIT_OPENCOLLECTIVE_TYPE=collective
8 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | # Eslintignore for LobeHub
2 | ################################################################
3 |
4 | # dependencies
5 | node_modules
6 |
7 | # ci
8 | coverage
9 | .coverage
10 |
11 | # test
12 | jest*
13 | _test_
14 | __test__
15 |
16 | # umi
17 | .umi
18 | .umi-production
19 | .umi-test
20 | .dumi/tmp*
21 | !.dumirc.ts
22 |
23 | # production
24 | dist
25 | es
26 | logs
27 |
28 | # misc
29 | # add other ignore file below
30 | **/**/makeQuery.ts
31 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | const config = require('@lobehub/lint').eslint;
2 |
3 | config.rules['unicorn/no-negated-condition'] = 0;
4 | config.rules['unicorn/prefer-type-error'] = 0;
5 | config.rules['unicorn/prefer-logical-operator-over-ternary'] = 0;
6 | config.rules['unicorn/no-null'] = 0;
7 | config.rules['unicorn/no-typeof-undefined'] = 0;
8 | config.rules['unicorn/explicit-length-check'] = 0;
9 | config.rules['unicorn/prefer-code-point'] = 0;
10 | config.rules['no-extra-boolean-cast'] = 0;
11 | config.rules['unicorn/no-useless-undefined'] = 0;
12 | config.rules['react/no-unknown-property'] = 0;
13 | config.rules['unicorn/prefer-ternary'] = 0;
14 |
15 | module.exports = config;
16 |
--------------------------------------------------------------------------------
/.fatherrc.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'father';
2 |
3 | export default defineConfig({
4 | esm: { output: 'es' },
5 | });
6 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/1_bug_report.yml:
--------------------------------------------------------------------------------
1 | name: '🐛 反馈缺陷 Bug Report'
2 | description: '反馈一个问题缺陷 | Report an bug'
3 | title: '[Bug] '
4 | labels: '🐛 Bug'
5 | body:
6 | - type: dropdown
7 | attributes:
8 | label: '💻 系统环境 | Operating System'
9 | options:
10 | - Windows
11 | - macOS
12 | - Ubuntu
13 | - Other Linux
14 | - Other
15 | validations:
16 | required: true
17 | - type: dropdown
18 | attributes:
19 | label: '🌐 浏览器 | Browser'
20 | options:
21 | - Chrome
22 | - Edge
23 | - Safari
24 | - Firefox
25 | - Other
26 | validations:
27 | required: true
28 | - type: textarea
29 | attributes:
30 | label: '🐛 问题描述 | Bug Description'
31 | description: A clear and concise description of the bug.
32 | validations:
33 | required: true
34 | - type: textarea
35 | attributes:
36 | label: '🚦 期望结果 | Expected Behavior'
37 | description: A clear and concise description of what you expected to happen.
38 | - type: textarea
39 | attributes:
40 | label: '📷 复现步骤 | Recurrence Steps'
41 | description: A clear and concise description of how to recurrence.
42 | - type: textarea
43 | attributes:
44 | label: '📝 补充信息 | Additional Information'
45 | description: If your problem needs further explanation, or if the issue you're seeing cannot be reproduced in a gist, please add more information here.
46 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/2_feature_request.yml:
--------------------------------------------------------------------------------
1 | name: '🌠 功能需求 Feature Request'
2 | description: '需求或建议 | Suggest an idea'
3 | title: '[Request] '
4 | labels: '🌠 Feature Request'
5 | body:
6 | - type: textarea
7 | attributes:
8 | label: '🥰 需求描述 | Feature Description'
9 | description: Please add a clear and concise description of the problem you are seeking to solve with this feature request.
10 | validations:
11 | required: true
12 | - type: textarea
13 | attributes:
14 | label: '🧐 解决方案 | Proposed Solution'
15 | description: Describe the solution you'd like in a clear and concise manner.
16 | validations:
17 | required: true
18 | - type: textarea
19 | attributes:
20 | label: '📝 补充信息 | Additional Information'
21 | description: Add any other context about the problem here.
22 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/3_question.yml:
--------------------------------------------------------------------------------
1 | name: '😇 疑问或帮助 Help Wanted'
2 | description: '疑问或需要帮助 | Need help'
3 | title: '[Question] '
4 | labels: '😇 Help Wanted'
5 | body:
6 | - type: textarea
7 | attributes:
8 | label: '🧐 问题描述 | Proposed Solution'
9 | description: A clear and concise description of the proplem.
10 | validations:
11 | required: true
12 | - type: textarea
13 | attributes:
14 | label: '📝 补充信息 | Additional Information'
15 | description: Add any other context about the problem here.
16 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/other.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: '📝 其他 Other'
3 | about: '其他问题 | Other issues'
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 | ---
8 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | #### 💻 变更类型 | Change Type
2 |
3 |
4 |
5 | - [ ] ✨ feat
6 | - [ ] 🐛 fix
7 | - [ ] 💄 style
8 | - [ ] 🔨 chore
9 | - [ ] 📝 docs
10 |
11 | #### 🔀 变更说明 | Description of Change
12 |
13 |
14 |
15 | #### 📝 补充信息 | Additional Information
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.github/workflows/auto-merge.yml:
--------------------------------------------------------------------------------
1 | name: Dependabot Auto Merge
2 | on:
3 | pull_request_target:
4 | types: [labeled, edited]
5 |
6 | jobs:
7 | merge:
8 | if: contains(github.event.pull_request.labels.*.name, 'dependencies')
9 | name: Dependabot Auto Merge
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v4
13 |
14 | - name: Install bun
15 | uses: oven-sh/setup-bun@v2
16 |
17 | - name: Install deps
18 | run: bun i
19 |
20 | - name: Merge
21 | uses: ahmadnassri/action-dependabot-auto-merge@v2
22 | with:
23 | command: merge
24 | target: minor
25 | github-token: ${{ secrets.GH_TOKEN }}
26 |
--------------------------------------------------------------------------------
/.github/workflows/issue-check-inactive.yml:
--------------------------------------------------------------------------------
1 | name: Issue Check Inactive
2 |
3 | on:
4 | schedule:
5 | - cron: '0 0 */15 * *'
6 |
7 | permissions:
8 | contents: read
9 |
10 | jobs:
11 | issue-check-inactive:
12 | permissions:
13 | issues: write # for actions-cool/issues-helper to update issues
14 | pull-requests: write # for actions-cool/issues-helper to update PRs
15 | runs-on: ubuntu-latest
16 | steps:
17 | - name: check-inactive
18 | uses: actions-cool/issues-helper@v3
19 | with:
20 | actions: 'check-inactive'
21 | inactive-label: 'Inactive'
22 | inactive-day: 30
23 |
--------------------------------------------------------------------------------
/.github/workflows/issue-close-require.yml:
--------------------------------------------------------------------------------
1 | name: Issue Close Require
2 |
3 | on:
4 | schedule:
5 | - cron: '0 0 * * *'
6 |
7 | permissions:
8 | contents: read
9 |
10 | jobs:
11 | issue-close-require:
12 | permissions:
13 | issues: write # for actions-cool/issues-helper to update issues
14 | pull-requests: write # for actions-cool/issues-helper to update PRs
15 | runs-on: ubuntu-latest
16 | steps:
17 | - name: need reproduce
18 | uses: actions-cool/issues-helper@v3
19 | with:
20 | actions: 'close-issues'
21 | labels: '✅ Fixed'
22 | inactive-day: 3
23 | body: |
24 | Since the issue was labeled with `✅ Fixed`, but no response in 3 days. This issue will be closed. If you have any questions, you can comment and reply.
25 |
26 | 由于该 issue 被标记为已修复,同时 3 天未收到回应。现关闭 issue,若有任何问题,可评论回复。
27 | - name: need reproduce
28 | uses: actions-cool/issues-helper@v3
29 | with:
30 | actions: 'close-issues'
31 | labels: '🤔 Need Reproduce'
32 | inactive-day: 3
33 | body: |
34 | Since the issue was labeled with `🤔 Need Reproduce`, but no response in 3 days. This issue will be closed. If you have any questions, you can comment and reply.
35 |
36 | 由于该 issue 被标记为需要更多信息,却 3 天未收到回应。现关闭 issue,若有任何问题,可评论回复。
37 | - name: need reproduce
38 | uses: actions-cool/issues-helper@v3
39 | with:
40 | actions: 'close-issues'
41 | labels: "🙅🏻♀️ WON'T DO"
42 | inactive-day: 3
43 | body: |
44 | Since the issue was labeled with `🙅🏻♀️ WON'T DO`, and no response in 3 days. This issue will be closed. If you have any questions, you can comment and reply.
45 |
46 | 由于该 issue 被标记为暂不处理,同时 3 天未收到回应。现关闭 issue,若有任何问题,可评论回复。
47 |
--------------------------------------------------------------------------------
/.github/workflows/issue-remove-inactive.yml:
--------------------------------------------------------------------------------
1 | name: Issue Remove Inactive
2 |
3 | on:
4 | issues:
5 | types: [edited]
6 | issue_comment:
7 | types: [created, edited]
8 |
9 | permissions:
10 | contents: read
11 |
12 | jobs:
13 | issue-remove-inactive:
14 | permissions:
15 | issues: write # for actions-cool/issues-helper to update issues
16 | pull-requests: write # for actions-cool/issues-helper to update PRs
17 | runs-on: ubuntu-latest
18 | steps:
19 | - name: remove inactive
20 | if: github.event.issue.state == 'open' && github.actor == github.event.issue.user.login
21 | uses: actions-cool/issues-helper@v3
22 | with:
23 | actions: 'remove-labels'
24 | issue-number: ${{ github.event.issue.number }}
25 | labels: 'Inactive'
26 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release CI
2 | on:
3 | push:
4 | branches:
5 | - main
6 |
7 | jobs:
8 | release:
9 | name: Release
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v4
13 |
14 | - name: Install bun
15 | uses: oven-sh/setup-bun@v2
16 |
17 | - name: Install deps
18 | run: bun i
19 |
20 | - name: CI
21 | run: bun run ci
22 |
23 | - name: Test
24 | run: bun run test
25 |
26 | - name: Build
27 | run: bun run build
28 |
29 | - name: Release
30 | run: bun run release
31 | env:
32 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
33 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
34 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test CI
2 | on:
3 | pull_request:
4 | push:
5 | branches:
6 | - '!main'
7 | jobs:
8 | test:
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - uses: actions/checkout@v4
13 |
14 | - name: Install bun
15 | uses: oven-sh/setup-bun@v2
16 |
17 | - name: Install deps
18 | run: bun i
19 |
20 | - name: CI
21 | run: bun run ci
22 |
23 | - name: Test and coverage
24 | run: bun run test:coverage
25 |
26 | - name: Upload coverage to Codecov
27 | uses: codecov/codecov-action@v4
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Gitignore for LobeHub
2 | ################################################################
3 |
4 | # general
5 | .DS_Store
6 | .idea
7 | .vscode
8 | .history
9 | .temp
10 | .env.local
11 | venv
12 | temp
13 | tmp
14 |
15 | # dependencies
16 | node_modules
17 | *.log
18 | *.lock
19 | package-lock.json
20 |
21 | # ci
22 | coverage
23 | .coverage
24 | .eslintcache
25 | .stylelintcache
26 |
27 | # production
28 | dist
29 | es
30 | logs
31 | test-output
32 |
33 | # umi
34 | .umi
35 | .umi-production
36 | .umi-test
37 | .dumi/tmp*
38 |
39 | # husky
40 | .husky/prepare-commit-msg
41 |
42 | # misc
43 | # add other ignore file below
44 | bun.lockb
45 | .vercel
46 | .env
47 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | npx --no-install commitlint --edit $1
4 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | npx --no-install lint-staged
4 |
--------------------------------------------------------------------------------
/.i18nrc.js:
--------------------------------------------------------------------------------
1 | const { description } = require('./package.json');
2 | const { defineConfig } = require('@lobehub/i18n-cli');
3 |
4 | module.exports = defineConfig({
5 | reference: description,
6 | entry: 'public/locales/en',
7 | entryLocale: 'en',
8 | output: 'public/locales',
9 | outputLocales: ['cn'],
10 | splitToken: 2500,
11 | temperature: 0,
12 | modelName: 'gpt-3.5-turbo',
13 | });
14 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | lockfile=false
2 | resolution-mode=highest
3 | public-hoist-pattern[]=*@umijs/lint*
4 | public-hoist-pattern[]=*changelog*
5 | public-hoist-pattern[]=*commitlint*
6 | public-hoist-pattern[]=*eslint*
7 | public-hoist-pattern[]=*postcss*
8 | public-hoist-pattern[]=*prettier*
9 | public-hoist-pattern[]=*remark*
10 | public-hoist-pattern[]=*semantic-release*
11 | public-hoist-pattern[]=*stylelint*
12 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # Prettierignore for LobeHub
2 | ################################################################
3 |
4 | # general
5 | .DS_Store
6 | .editorconfig
7 | .idea
8 | .vscode
9 | .history
10 | .temp
11 | .env.local
12 | .husky
13 | .npmrc
14 | .gitkeep
15 | venv
16 | temp
17 | tmp
18 | LICENSE
19 |
20 | # dependencies
21 | node_modules
22 | *.log
23 | *.lock
24 | package-lock.json
25 |
26 | # ci
27 | coverage
28 | .coverage
29 | .eslintcache
30 | .stylelintcache
31 | test-output
32 | __snapshots__
33 | *.snap
34 |
35 | # production
36 | dist
37 | es
38 | logs
39 |
40 | # umi
41 | .umi
42 | .umi-production
43 | .umi-test
44 | .dumi/tmp*
45 |
46 | # ignore files
47 | .*ignore
48 |
49 | # docker
50 | docker
51 | Dockerfile*
52 |
53 | # image
54 | *.webp
55 | *.gif
56 | *.png
57 | *.jpg
58 | *.svg
59 |
60 | # misc
61 | # add other ignore file below
62 | .next
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = require('@lobehub/lint').prettier;
2 |
--------------------------------------------------------------------------------
/.releaserc.js:
--------------------------------------------------------------------------------
1 | module.exports = require('@lobehub/lint').semanticRelease;
2 |
--------------------------------------------------------------------------------
/.remarkrc.js:
--------------------------------------------------------------------------------
1 | module.exports = require('@lobehub/lint').remarklint;
2 |
--------------------------------------------------------------------------------
/.stylelintrc.js:
--------------------------------------------------------------------------------
1 | const config = require('@lobehub/lint').stylelint;
2 |
3 | module.exports = {
4 | ...config,
5 | rules: {
6 | 'selector-id-pattern': null,
7 | },
8 | };
9 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Changelog
4 |
5 | ### [Version 0.10.4](https://github.com/lobehub/lobe-readme-wizard/compare/v0.10.3...v0.10.4)
6 |
7 | Released on **2025-02-07**
8 |
9 | #### 🐛 Bug Fixes
10 |
11 | - **misc**: Fix build, Fix build.
12 |
13 |
14 |
15 |
16 | Improvements and Fixes
17 |
18 | #### What's fixed
19 |
20 | - **misc**: Fix build ([730a9c0](https://github.com/lobehub/lobe-readme-wizard/commit/730a9c0))
21 | - **misc**: Fix build ([427bbad](https://github.com/lobehub/lobe-readme-wizard/commit/427bbad))
22 |
23 |
24 |
25 |
26 |
27 | [](#readme-top)
28 |
29 |
30 |
31 | ### [Version 0.10.3](https://github.com/lobehub/lobe-readme-wizard/compare/v0.10.2...v0.10.3)
32 |
33 | Released on **2025-02-07**
34 |
35 | #### 🐛 Bug Fixes
36 |
37 | - **misc**: Fix demo.
38 |
39 |
40 |
41 |
42 | Improvements and Fixes
43 |
44 | #### What's fixed
45 |
46 | - **misc**: Fix demo ([7487649](https://github.com/lobehub/lobe-readme-wizard/commit/7487649))
47 |
48 |
49 |
50 |
51 |
52 | [](#readme-top)
53 |
54 |
55 |
56 | ### [Version 0.10.2](https://github.com/lobehub/lobe-readme-wizard/compare/v0.10.1...v0.10.2)
57 |
58 | Released on **2025-02-07**
59 |
60 | #### 🐛 Bug Fixes
61 |
62 | - **misc**: Fix sponsor kit.
63 |
64 |
65 |
66 |
67 | Improvements and Fixes
68 |
69 | #### What's fixed
70 |
71 | - **misc**: Fix sponsor kit ([5c2e77d](https://github.com/lobehub/lobe-readme-wizard/commit/5c2e77d))
72 |
73 |
74 |
75 |
76 |
77 | [](#readme-top)
78 |
79 |
80 |
81 | ### [Version 0.10.1](https://github.com/lobehub/lobe-readme-wizard/compare/v0.10.0...v0.10.1)
82 |
83 | Released on **2024-11-24**
84 |
85 | #### 💄 Styles
86 |
87 | - **misc**: Update og.
88 |
89 |
90 |
91 |
92 | Improvements and Fixes
93 |
94 | #### Styles
95 |
96 | - **misc**: Update og ([9586724](https://github.com/lobehub/lobe-readme-wizard/commit/9586724))
97 |
98 |
99 |
100 |
101 |
102 | [](#readme-top)
103 |
104 |
105 |
106 | ## [Version 0.10.0](https://github.com/lobehub/lobe-readme-wizard/compare/v0.9.4...v0.10.0)
107 |
108 | Released on **2024-09-20**
109 |
110 | #### ✨ Features
111 |
112 | - **misc**: Update sitemap.
113 |
114 |
115 |
116 |
117 | Improvements and Fixes
118 |
119 | #### What's improved
120 |
121 | - **misc**: Update sitemap ([4fd8614](https://github.com/lobehub/lobe-readme-wizard/commit/4fd8614))
122 |
123 |
124 |
125 |
126 |
127 | [](#readme-top)
128 |
129 |
130 |
131 | ### [Version 0.9.4](https://github.com/lobehub/lobe-readme-wizard/compare/v0.9.3...v0.9.4)
132 |
133 | Released on **2024-06-25**
134 |
135 | #### 🐛 Bug Fixes
136 |
137 | - **misc**: Add cache.
138 |
139 |
140 |
141 |
142 | Improvements and Fixes
143 |
144 | #### What's fixed
145 |
146 | - **misc**: Add cache ([3012739](https://github.com/lobehub/lobe-readme-wizard/commit/3012739))
147 |
148 |
149 |
150 |
151 |
152 | [](#readme-top)
153 |
154 |
155 |
156 | ### [Version 0.9.3](https://github.com/lobehub/lobe-readme-wizard/compare/v0.9.2...v0.9.3)
157 |
158 | Released on **2024-05-16**
159 |
160 | #### 🐛 Bug Fixes
161 |
162 | - **misc**: Fix dumi.
163 |
164 |
165 |
166 |
167 | Improvements and Fixes
168 |
169 | #### What's fixed
170 |
171 | - **misc**: Fix dumi ([7ca0012](https://github.com/lobehub/lobe-readme-wizard/commit/7ca0012))
172 |
173 |
174 |
175 |
176 |
177 | [](#readme-top)
178 |
179 |
180 |
181 | ### [Version 0.9.2](https://github.com/lobehub/lobe-readme-wizard/compare/v0.9.1...v0.9.2)
182 |
183 | Released on **2024-04-30**
184 |
185 | #### 🐛 Bug Fixes
186 |
187 | - **misc**: Fix api import.
188 |
189 |
190 |
191 |
192 | Improvements and Fixes
193 |
194 | #### What's fixed
195 |
196 | - **misc**: Fix api import ([a0dc87f](https://github.com/lobehub/lobe-readme-wizard/commit/a0dc87f))
197 |
198 |
199 |
200 |
201 |
202 | [](#readme-top)
203 |
204 |
205 |
206 | ### [Version 0.9.1](https://github.com/lobehub/lobe-readme-wizard/compare/v0.9.0...v0.9.1)
207 |
208 | Released on **2024-04-30**
209 |
210 | #### 🐛 Bug Fixes
211 |
212 | - **misc**: Fix deps.
213 |
214 |
215 |
216 |
217 | Improvements and Fixes
218 |
219 | #### What's fixed
220 |
221 | - **misc**: Fix deps ([2c92216](https://github.com/lobehub/lobe-readme-wizard/commit/2c92216))
222 |
223 |
224 |
225 |
226 |
227 | [](#readme-top)
228 |
229 |
230 |
231 | ## [Version 0.9.0](https://github.com/lobehub/lobe-readme-wizard/compare/v0.8.10...v0.9.0)
232 |
233 | Released on **2024-04-29**
234 |
235 | #### ✨ Features
236 |
237 | - **misc**: Update Sponsor provider.
238 |
239 |
240 |
241 |
242 | Improvements and Fixes
243 |
244 | #### What's improved
245 |
246 | - **misc**: Update Sponsor provider ([61ca14b](https://github.com/lobehub/lobe-readme-wizard/commit/61ca14b))
247 |
248 |
249 |
250 |
251 |
252 | [](#readme-top)
253 |
254 |
255 |
256 | ### [Version 0.8.10](https://github.com/lobehub/lobe-readme-wizard/compare/v0.8.9...v0.8.10)
257 |
258 | Released on **2023-12-25**
259 |
260 | #### 🐛 Bug Fixes
261 |
262 | - **misc**: Fix sponsor fliter.
263 |
264 |
265 |
266 |
267 | Improvements and Fixes
268 |
269 | #### What's fixed
270 |
271 | - **misc**: Fix sponsor fliter ([3a4cd47](https://github.com/lobehub/lobe-readme-wizard/commit/3a4cd47))
272 |
273 |
274 |
275 |
276 |
277 | [](#readme-top)
278 |
279 |
280 |
281 | ### [Version 0.8.9](https://github.com/lobehub/lobe-readme-wizard/compare/v0.8.8...v0.8.9)
282 |
283 | Released on **2023-12-23**
284 |
285 | #### 🐛 Bug Fixes
286 |
287 | - **misc**: Fix func typo.
288 |
289 |
290 |
291 |
292 | Improvements and Fixes
293 |
294 | #### What's fixed
295 |
296 | - **misc**: Fix func typo ([5a86506](https://github.com/lobehub/lobe-readme-wizard/commit/5a86506))
297 |
298 |
299 |
300 |
301 |
302 | [](#readme-top)
303 |
304 |
305 |
306 | ### [Version 0.8.8](https://github.com/lobehub/lobe-readme-wizard/compare/v0.8.7...v0.8.8)
307 |
308 | Released on **2023-12-23**
309 |
310 | #### 🐛 Bug Fixes
311 |
312 | - **misc**: Fix SponsorKit.
313 |
314 |
315 |
316 |
317 | Improvements and Fixes
318 |
319 | #### What's fixed
320 |
321 | - **misc**: Fix SponsorKit ([d1e3ecb](https://github.com/lobehub/lobe-readme-wizard/commit/d1e3ecb))
322 |
323 |
324 |
325 |
326 |
327 | [](#readme-top)
328 |
329 |
330 |
331 | ### [Version 0.8.7](https://github.com/lobehub/lobe-readme-wizard/compare/v0.8.6...v0.8.7)
332 |
333 | Released on **2023-12-06**
334 |
335 | #### 💄 Styles
336 |
337 | - **misc**: Update sponsor style.
338 |
339 |
340 |
341 |
342 | Improvements and Fixes
343 |
344 | #### Styles
345 |
346 | - **misc**: Update sponsor style ([cddd270](https://github.com/lobehub/lobe-readme-wizard/commit/cddd270))
347 |
348 |
349 |
350 |
351 |
352 | [](#readme-top)
353 |
354 |
355 |
356 | ### [Version 0.8.6](https://github.com/lobehub/lobe-readme-wizard/compare/v0.8.5...v0.8.6)
357 |
358 | Released on **2023-12-06**
359 |
360 | #### 💄 Styles
361 |
362 | - **misc**: Add auto height to sponsor.
363 |
364 |
365 |
366 |
367 | Improvements and Fixes
368 |
369 | #### Styles
370 |
371 | - **misc**: Add auto height to sponsor ([e1cbb69](https://github.com/lobehub/lobe-readme-wizard/commit/e1cbb69))
372 |
373 |
374 |
375 |
376 |
377 | [](#readme-top)
378 |
379 |
380 |
381 | ### [Version 0.8.5](https://github.com/lobehub/lobe-readme-wizard/compare/v0.8.4...v0.8.5)
382 |
383 | Released on **2023-12-06**
384 |
385 | #### 🐛 Bug Fixes
386 |
387 | - **misc**: Fix size.
388 |
389 |
390 |
391 |
392 | Improvements and Fixes
393 |
394 | #### What's fixed
395 |
396 | - **misc**: Fix size ([0f00424](https://github.com/lobehub/lobe-readme-wizard/commit/0f00424))
397 |
398 |
399 |
400 |
401 |
402 | [](#readme-top)
403 |
404 |
405 |
406 | ### [Version 0.8.4](https://github.com/lobehub/lobe-readme-wizard/compare/v0.8.3...v0.8.4)
407 |
408 | Released on **2023-12-06**
409 |
410 | #### 🐛 Bug Fixes
411 |
412 | - **misc**: Fix og.
413 |
414 |
415 |
416 |
417 | Improvements and Fixes
418 |
419 | #### What's fixed
420 |
421 | - **misc**: Fix og ([703f45a](https://github.com/lobehub/lobe-readme-wizard/commit/703f45a))
422 |
423 |
424 |
425 |
426 |
427 | [](#readme-top)
428 |
429 |
430 |
431 | ### [Version 0.8.3](https://github.com/lobehub/lobe-readme-wizard/compare/v0.8.2...v0.8.3)
432 |
433 | Released on **2023-12-06**
434 |
435 | #### 🐛 Bug Fixes
436 |
437 | - **misc**: Fix @vercel/og.
438 |
439 |
440 |
441 |
442 | Improvements and Fixes
443 |
444 | #### What's fixed
445 |
446 | - **misc**: Fix @vercel/og ([d198d99](https://github.com/lobehub/lobe-readme-wizard/commit/d198d99))
447 |
448 |
449 |
450 |
451 |
452 | [](#readme-top)
453 |
454 |
455 |
456 | ### [Version 0.8.2](https://github.com/lobehub/lobe-readme-wizard/compare/v0.8.1...v0.8.2)
457 |
458 | Released on **2023-12-06**
459 |
460 | #### 🐛 Bug Fixes
461 |
462 | - **misc**: Debug.
463 |
464 |
465 |
466 |
467 | Improvements and Fixes
468 |
469 | #### What's fixed
470 |
471 | - **misc**: Debug ([d064561](https://github.com/lobehub/lobe-readme-wizard/commit/d064561))
472 |
473 |
474 |
475 |
476 |
477 | [](#readme-top)
478 |
479 |
480 |
481 | ### [Version 0.8.1](https://github.com/lobehub/lobe-readme-wizard/compare/v0.8.0...v0.8.1)
482 |
483 | Released on **2023-12-06**
484 |
485 | #### 🐛 Bug Fixes
486 |
487 | - **misc**: Fix import.
488 |
489 |
490 |
491 |
492 | Improvements and Fixes
493 |
494 | #### What's fixed
495 |
496 | - **misc**: Fix import ([ba6dd2b](https://github.com/lobehub/lobe-readme-wizard/commit/ba6dd2b))
497 |
498 |
499 |
500 |
501 |
502 | [](#readme-top)
503 |
504 |
505 |
506 | ## [Version 0.8.0](https://github.com/lobehub/lobe-readme-wizard/compare/v0.7.2...v0.8.0)
507 |
508 | Released on **2023-12-06**
509 |
510 | #### ✨ Features
511 |
512 | - **misc**: Add SponsorKit.
513 |
514 |
515 |
516 |
517 | Improvements and Fixes
518 |
519 | #### What's improved
520 |
521 | - **misc**: Add SponsorKit ([4f38bc4](https://github.com/lobehub/lobe-readme-wizard/commit/4f38bc4))
522 |
523 |
524 |
525 |
526 |
527 | [](#readme-top)
528 |
529 |
530 |
531 | ### [Version 0.7.2](https://github.com/lobehub/lobe-readme-wizard/compare/v0.7.1...v0.7.2)
532 |
533 | Released on **2023-11-16**
534 |
535 | #### 🐛 Bug Fixes
536 |
537 | - **misc**: Fix build.
538 |
539 |
540 |
541 |
542 | Improvements and Fixes
543 |
544 | #### What's fixed
545 |
546 | - **misc**: Fix build ([ef162a3](https://github.com/lobehub/lobe-readme-wizard/commit/ef162a3))
547 |
548 |
549 |
550 |
551 |
552 | [](#readme-top)
553 |
554 |
555 |
556 | ### [Version 0.7.1](https://github.com/lobehub/lobe-readme-wizard/compare/v0.7.0...v0.7.1)
557 |
558 | Released on **2023-10-11**
559 |
560 | #### 💄 Styles
561 |
562 | - **misc**: Update docker shields style.
563 |
564 |
565 |
566 |
567 | Improvements and Fixes
568 |
569 | #### Styles
570 |
571 | - **misc**: Update docker shields style ([7853b56](https://github.com/lobehub/lobe-readme-wizard/commit/7853b56))
572 |
573 |
574 |
575 |
576 |
577 | [](#readme-top)
578 |
579 |
580 |
581 | ## [Version 0.7.0](https://github.com/lobehub/lobe-readme-wizard/compare/v0.6.1...v0.7.0)
582 |
583 | Released on **2023-10-11**
584 |
585 | #### ✨ Features
586 |
587 | - **misc**: Add docker shield.
588 |
589 |
590 |
591 |
592 | Improvements and Fixes
593 |
594 | #### What's improved
595 |
596 | - **misc**: Add docker shield ([c098ab0](https://github.com/lobehub/lobe-readme-wizard/commit/c098ab0))
597 |
598 |
599 |
600 |
601 |
602 | [](#readme-top)
603 |
604 |
605 |
606 | ### [Version 0.6.1](https://github.com/lobehub/lobe-readme-wizard/compare/v0.6.0...v0.6.1)
607 |
608 | Released on **2023-09-28**
609 |
610 | #### 🐛 Bug Fixes
611 |
612 | - **misc**: Fix link typo.
613 |
614 |
615 |
616 |
617 | Improvements and Fixes
618 |
619 | #### What's fixed
620 |
621 | - **misc**: Fix link typo ([355598a](https://github.com/lobehub/lobe-readme-wizard/commit/355598a))
622 |
623 |
624 |
625 |
626 |
627 | [](#readme-top)
628 |
629 |
630 |
631 | ## [Version 0.6.0](https://github.com/lobehub/lobe-readme-wizard/compare/v0.5.0...v0.6.0)
632 |
633 | Released on **2023-09-28**
634 |
635 | #### ✨ Features
636 |
637 | - **misc**: Add social shield.
638 |
639 |
640 |
641 |
642 | Improvements and Fixes
643 |
644 | #### What's improved
645 |
646 | - **misc**: Add social shield ([a359e24](https://github.com/lobehub/lobe-readme-wizard/commit/a359e24))
647 |
648 |
649 |
650 |
651 |
652 | [](#readme-top)
653 |
654 |
655 |
656 | ## [Version 0.5.0](https://github.com/lobehub/lobe-readme-wizard/compare/v0.4.0...v0.5.0)
657 |
658 | Released on **2023-09-28**
659 |
660 | #### ✨ Features
661 |
662 | - **misc**: Add share shield.
663 |
664 |
665 |
666 |
667 | Improvements and Fixes
668 |
669 | #### What's improved
670 |
671 | - **misc**: Add share shield ([2deb3ad](https://github.com/lobehub/lobe-readme-wizard/commit/2deb3ad))
672 |
673 |
674 |
675 |
676 |
677 | [](#readme-top)
678 |
679 |
680 |
681 | ## [Version 0.4.0](https://github.com/lobehub/lobe-readme-wizard/compare/v0.3.1...v0.4.0)
682 |
683 | Released on **2023-09-28**
684 |
685 | #### ✨ Features
686 |
687 | - **misc**: Add bilibili shield.
688 |
689 |
690 |
691 |
692 | Improvements and Fixes
693 |
694 | #### What's improved
695 |
696 | - **misc**: Add bilibili shield ([ab1b9da](https://github.com/lobehub/lobe-readme-wizard/commit/ab1b9da))
697 |
698 |
699 |
700 |
701 |
702 | [](#readme-top)
703 |
704 |
705 |
706 | ### [Version 0.3.1](https://github.com/lobehub/lobe-readme-wizard/compare/v0.3.0...v0.3.1)
707 |
708 | Released on **2023-09-18**
709 |
710 | #### 💄 Styles
711 |
712 | - **misc**: Fix style.
713 |
714 |
715 |
716 |
717 | Improvements and Fixes
718 |
719 | #### Styles
720 |
721 | - **misc**: Fix style ([949c7f1](https://github.com/lobehub/lobe-readme-wizard/commit/949c7f1))
722 |
723 |
724 |
725 |
726 |
727 | [](#readme-top)
728 |
729 |
730 |
731 | ## [Version 0.3.0](https://github.com/lobehub/lobe-readme-wizard/compare/v0.2.1...v0.3.0)
732 |
733 | Released on **2023-09-18**
734 |
735 | #### ♻ Code Refactoring
736 |
737 | - **misc**: Refactor to dumi.
738 |
739 | #### ✨ Features
740 |
741 | - **misc**: Update demos.
742 |
743 | #### 🐛 Bug Fixes
744 |
745 | - **misc**: Fix typo.
746 |
747 |
748 |
749 |
750 | Improvements and Fixes
751 |
752 | #### Code refactoring
753 |
754 | - **misc**: Refactor to dumi ([ee3cd5f](https://github.com/lobehub/lobe-readme-wizard/commit/ee3cd5f))
755 |
756 | #### What's improved
757 |
758 | - **misc**: Update demos ([1515782](https://github.com/lobehub/lobe-readme-wizard/commit/1515782))
759 |
760 | #### What's fixed
761 |
762 | - **misc**: Fix typo ([d0e4c08](https://github.com/lobehub/lobe-readme-wizard/commit/d0e4c08))
763 |
764 |
765 |
766 |
767 |
768 | [](#readme-top)
769 |
770 |
771 |
772 | ### [Version 0.2.1](https://github.com/lobehub/lobe-readme-generator/compare/v0.2.0...v0.2.1)
773 |
774 | Released on **2023-09-18**
775 |
776 | #### 🐛 Bug Fixes
777 |
778 | - **misc**: Simple icon.
779 |
780 |
781 |
782 |
783 | Improvements and Fixes
784 |
785 | #### What's fixed
786 |
787 | - **misc**: Simple icon ([0bb612c](https://github.com/lobehub/lobe-readme-generator/commit/0bb612c))
788 |
789 |
790 |
791 |
792 |
793 | [](#readme-top)
794 |
795 |
796 |
797 | ## [Version 0.2.0](https://github.com/lobehub/lobe-readme-generator/compare/v0.1.0...v0.2.0)
798 |
799 | Released on **2023-09-17**
800 |
801 | #### ✨ Features
802 |
803 | - **misc**: Add readme generator.
804 |
805 |
806 |
807 |
808 | Improvements and Fixes
809 |
810 | #### What's improved
811 |
812 | - **misc**: Add readme generator ([aad748d](https://github.com/lobehub/lobe-readme-generator/commit/aad748d))
813 |
814 |
815 |
816 |
817 |
818 | [](#readme-top)
819 |
820 |
821 |
822 | ## [Version 0.1.0](https://github.com/lobehub/lobe-readme-generator/compare/v0.0.1...v0.1.0)
823 |
824 | Released on **2023-09-17**
825 |
826 | #### ✨ Features
827 |
828 | - **misc**: Add shield editor.
829 |
830 | #### 🐛 Bug Fixes
831 |
832 | - **misc**: Fix ci, Fix next version, Fix scripts.
833 |
834 |
835 |
836 |
837 | Improvements and Fixes
838 |
839 | #### What's improved
840 |
841 | - **misc**: Add shield editor ([0102c80](https://github.com/lobehub/lobe-readme-generator/commit/0102c80))
842 |
843 | #### What's fixed
844 |
845 | - **misc**: Fix ci ([1b3d3eb](https://github.com/lobehub/lobe-readme-generator/commit/1b3d3eb))
846 | - **misc**: Fix next version ([b8e3e7f](https://github.com/lobehub/lobe-readme-generator/commit/b8e3e7f))
847 | - **misc**: Fix scripts ([dea5c8c](https://github.com/lobehub/lobe-readme-generator/commit/dea5c8c))
848 |
849 |
850 |
851 |
852 |
853 | [](#readme-top)
854 |
855 |
856 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 LobeHub
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 |
7 |
8 |
9 |
Readme Wizard
10 |
11 | Generate lobe style product README _by MAGIC_ \~ 🪄
12 |
13 | [Documents](https://readme-wizard.lobehub.com) · [Changelog](./CHANGELOG.md) · [Report Bug][github-issues-link] · [Request Feature][github-issues-link]
14 |
15 | [![][github-release-shield]][github-release-link]
16 | [![][vecel-shield]][vecel-link]
17 | [![][discord-shield]][discord-link]
18 | [![][github-releasedate-shield]][github-releasedate-link]
19 | [![][github-action-test-shield]][github-action-test-link]
20 | [![][github-action-release-shield]][github-action-release-link]
21 | [![][github-contributors-shield]][github-contributors-link]
22 | [![][github-forks-shield]][github-forks-link]
23 | [![][github-stars-shield]][github-stars-link]
24 | [![][github-issues-shield]][github-issues-link]
25 | [![][github-license-shield]][github-license-link]
26 |
27 | 
28 |
29 |
30 |
31 |
32 | Table of contents
33 |
34 | #### TOC
35 |
36 | - [✨ Features](#-features)
37 |
38 | - [⌨️ Local Development](#️-local-development)
39 |
40 | - [🤝 Contributing](#-contributing)
41 |
42 | - [🔗 Links](#-links)
43 |
44 | - [Credits](#credits)
45 | - [More Products](#more-products)
46 | - [Design Resources](#design-resources)
47 | - [Development Resources](#development-resources)
48 |
49 | ####
50 |
51 |
52 |
53 | ## ✨ Features
54 |
55 | - [x] 📝 **Readme Generator**
56 | - [x] Hero
57 | - [x] Features
58 | - [x] Installation
59 | - [x] Development
60 | - [x] Contributing
61 | - [x] Credits
62 | - [x] License
63 | - [x] 🔖 **Shields Generator**
64 | - [x] Custom
65 | - [x] Website
66 | - [x] Github
67 | - [x] NPM
68 | - [x] Vercel
69 | - [x] Discord
70 | - [ ] 🌏 i18n template support
71 | - [ ] 📦 Generator by `package.json`
72 | - [ ] 🪄 Generator features by openAI
73 | - [ ] ⌨️ CLI tool support
74 |
75 |
76 |
77 | [![][back-to-top]](#readme-top)
78 |
79 |
80 |
81 | ## ⌨️ Local Development
82 |
83 | You can use Github Codespaces for online development:
84 |
85 | [![][codespaces-shield]][codespaces-link]
86 |
87 | Or clone it for local development:
88 |
89 | [![][bun-shield]][bun-link]
90 |
91 | ```bash
92 | $ git clone https://github.com/lobehub/lobe-readme-wizard.git
93 | $ cd lobe-readme-wizard
94 | $ bun install
95 | $ bun dev
96 | ```
97 |
98 |
99 |
100 | [![][back-to-top]](#readme-top)
101 |
102 |
103 |
104 | ## 🤝 Contributing
105 |
106 | Contributions of all types are more than welcome, if you are interested in contributing code, feel free to check out our GitHub [Issues][github-issues-link] to get stuck in to show us what you’re made of.
107 |
108 | [![][pr-welcome-shield]][pr-welcome-link]
109 |
110 | [![][github-contrib-shield]][github-contrib-link]
111 |
112 |
113 |
114 | [![][back-to-top]](#readme-top)
115 |
116 |
117 |
118 | ## 🔗 Links
119 |
120 | ### Credits
121 |
122 | - **recharts** -
123 | - **tremor** -
124 | - **react-activity-calendar** -
125 |
126 | ### More Products
127 |
128 | - **[🤯 Lobe Chat](https://github.com/lobehub/lobe-chat)** - An open-source, extensible (Function Calling), high-performance chatbot framework. It supports one-click free deployment of your private ChatGPT/LLM web application.
129 | - **[🅰️ Lobe Theme](https://github.com/lobehub/sd-webui-lobe-theme)** - The modern theme for stable diffusion webui, exquisite interface design, highly customizable UI, and efficiency boosting features.
130 | - **[🧸 Lobe Vidol](https://github.com/lobehub/lobe-vidol)** - Experience the magic of virtual idol creation with Lobe Vidol, enjoy the elegance of our Exquisite UI Design, dance along using MMD Dance Support, and engage in Smooth Conversations.
131 |
132 | ### Design Resources
133 |
134 | - **[🍭 Lobe UI](https://ui.lobehub.com)** - An open-source UI component library for building AIGC web apps.
135 | - **[🥨 Lobe Icons](https://lobehub.com/icons)** - Popular AI / LLM Model Brand SVG Logo and Icon Collection.
136 | - **[📊 Lobe Charts](https://charts.lobehub.com)** - React modern charts components built on recharts
137 |
138 | ### Development Resources
139 |
140 | - **[🎤 Lobe TTS](https://tts.lobehub.com)** - A high-quality & reliable TTS/STT library for Server and Browser
141 | - **[🌏 Lobe i18n](https://github.com/lobehub/lobe-cli-toolbox/blob/master/packages/lobe-i18n)** - Automation ai tool for the i18n (internationalization) translation process.
142 |
143 | [More Resources](https://lobehub.com/resources)
144 |
145 |
146 |
147 | [![][back-to-top]](#readme-top)
148 |
149 |
150 |
151 | ---
152 |
153 | #### 📝 License
154 |
155 | Copyright © 2023 [LobeHub][profile-link].
156 | This project is [MIT](./LICENSE) licensed.
157 |
158 | > _Generate by [🧙♂️ Readme Wizard](https://github.com/lobehub/lobe-readme-wizard 'Generate lobe style product README by MAGIC')_
159 |
160 |
161 |
162 | [back-to-top]: https://img.shields.io/badge/-BACK_TO_TOP-black?style=flat-square
163 | [bun-link]: https://bun.sh
164 | [bun-shield]: https://img.shields.io/badge/-speedup%20with%20bun-black?logo=bun&style=for-the-badge
165 | [codespaces-link]: https://codespaces.new/lobehub/lobe-readme-wizard
166 | [codespaces-shield]: https://github.com/codespaces/badge.svg
167 | [discord-link]: https://discord.gg/AYFPHvv2jT
168 | [discord-shield]: https://img.shields.io/discord/1127171173982154893?color=5865F2&label=discord&labelColor=black&logo=discord&logoColor=white&style=flat-square
169 | [github-action-release-link]: https://github.com/lobehub/lobe-readme-wizard/actions/workflows/release.yml
170 | [github-action-release-shield]: https://img.shields.io/github/actions/workflow/status/lobehub/lobe-readme-wizard/release.yml?label=release&labelColor=black&logo=githubactions&logoColor=white&style=flat-square
171 | [github-action-test-link]: https://github.com/lobehub/lobe-readme-wizard/actions/workflows/test.yml
172 | [github-action-test-shield]: https://img.shields.io/github/actions/workflow/status/lobehub/lobe-readme-wizard/test.yml?label=test&labelColor=black&logo=githubactions&logoColor=white&style=flat-square
173 | [github-contrib-link]: https://github.com/lobehub/lobe-readme-wizard/graphs/contributors
174 | [github-contrib-shield]: https://contrib.rocks/image?repo=lobehub%2Flobe-readme-wizard
175 | [github-contributors-link]: https://github.com/lobehub/lobe-readme-wizard/graphs/contributors
176 | [github-contributors-shield]: https://img.shields.io/github/contributors/lobehub/lobe-readme-wizard?color=c4f042&labelColor=black&style=flat-square
177 | [github-forks-link]: https://github.com/lobehub/lobe-readme-wizard/network/members
178 | [github-forks-shield]: https://img.shields.io/github/forks/lobehub/lobe-readme-wizard?color=8ae8ff&labelColor=black&style=flat-square
179 | [github-issues-link]: https://github.com/lobehub/lobe-readme-wizard/issues
180 | [github-issues-shield]: https://img.shields.io/github/issues/lobehub/lobe-readme-wizard?color=ff80eb&labelColor=black&style=flat-square
181 | [github-license-link]: https://github.com/lobehub/lobe-readme-wizard/blob/main/LICENSE
182 | [github-license-shield]: https://img.shields.io/github/license/lobehub/lobe-readme-wizard?color=white&labelColor=black&style=flat-square
183 | [github-release-link]: https://github.com/lobehub/lobe-readme-wizard/releases
184 | [github-release-shield]: https://img.shields.io/github/v/release/lobehub/lobe-readme-wizard?logo=github&color=369eff&labelColor=black&style=flat-square
185 | [github-releasedate-link]: https://github.com/lobehub/lobe-readme-wizard/releases
186 | [github-releasedate-shield]: https://img.shields.io/github/release-date/lobehub/lobe-readme-wizard?labelColor=black&style=flat-square
187 | [github-stars-link]: https://github.com/lobehub/lobe-readme-wizard/network/stargazers
188 | [github-stars-shield]: https://img.shields.io/github/stars/lobehub/lobe-readme-wizard?color=ffcb47&labelColor=black&style=flat-square
189 | [pr-welcome-link]: https://github.com/lobehub/lobe-chat/pulls
190 | [pr-welcome-shield]: https://img.shields.io/badge/🤯_pr_welcome-%E2%86%92-ffcb47?labelColor=black&style=for-the-badge
191 | [profile-link]: https://github.com/lobehub
192 | [vecel-link]: https://lobe-readme-wizard.vercel.app
193 | [vecel-shield]: https://img.shields.io/website?down_message=offline&label=vecel&labelColor=black&logo=vercel&style=flat-square&up_message=online&url=https%3A%2F%2Flobe-readme-wizard.vercel.app
194 |
--------------------------------------------------------------------------------
/clean-package.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | indent: 2,
3 | remove: ['scripts', 'lint-staged', 'devDependencies', 'publishConfig', 'clean-package'],
4 | };
5 |
--------------------------------------------------------------------------------
/docs/changelog.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Changelog
3 | description: New updates and improvements to @lobehub/ui
4 | nav:
5 | title: Changelog
6 | order: 999
7 | ---
8 |
9 |
10 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | hero:
3 | title: Readme Wizard
4 | description: Generate lobe style product README by MAGIC ~ 🪄
5 | ---
6 |
7 |
8 |
--------------------------------------------------------------------------------
/docs/index.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from 'antd';
2 | import { Link } from 'dumi';
3 | import { Center } from 'react-layout-kit';
4 |
5 | import ReadmeHero from '@/ReadmeHero';
6 |
7 | export default () => {
8 | return (
9 |
10 |
11 |
12 | Find more MAGIC 🔮
13 |
14 |
15 | );
16 | };
17 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@lobehub/readme-wizard",
3 | "version": "0.10.4",
4 | "description": "Generate lobe style product README by magic",
5 | "homepage": "https://github.com/lobehub/lobe-readme-generator",
6 | "bugs": {
7 | "url": "https://github.com/lobehub/lobe-readme-wizard/issues/new/choose"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/lobehub/lobe-readme-wizard.git"
12 | },
13 | "license": "MIT",
14 | "author": "LobeHub ",
15 | "sideEffects": false,
16 | "main": "es/index.js",
17 | "module": "es/index.js",
18 | "types": "es/index.d.ts",
19 | "files": [
20 | "es"
21 | ],
22 | "scripts": {
23 | "build": "father build",
24 | "build:watch": "father dev",
25 | "ci": "npm run lint",
26 | "dev": "dumi dev",
27 | "docs:build": "dumi build",
28 | "docs:build-analyze": "ANALYZE=1 dumi build",
29 | "docs:dev": "dumi dev",
30 | "doctor": "father doctor",
31 | "i18n": "lobe-i18n",
32 | "icon-sync": "node ./scripts/syncSimpleIconList.mjs",
33 | "lint": "npm run lint:ts && npm run lint:style",
34 | "lint:md": "remark . --quiet --frail --output",
35 | "lint:style": "stylelint \"{src,tests}/**/*.{js,jsx,ts,tsx}\" --fix",
36 | "lint:ts": "eslint \"{src,tests}/**/*.{js,jsx,ts,tsx}\" --fix",
37 | "prepack": "clean-package",
38 | "postpack": "clean-package restore",
39 | "prepare": "husky && npm run setup",
40 | "prettier": "prettier -c --write \"**/**\"",
41 | "pull": "git pull",
42 | "release": "semantic-release",
43 | "setup": "dumi setup",
44 | "start": "next start",
45 | "stylelint": "stylelint \"src/**/*.{js,jsx,ts,tsx}\" --fix",
46 | "test": "vitest --passWithNoTests",
47 | "test:coverage": "vitest run --coverage --passWithNoTests",
48 | "test:update": "vitest -u",
49 | "type-check": "tsc --noEmit"
50 | },
51 | "lint-staged": {
52 | "*.md": [
53 | "remark --quiet --output --",
54 | "prettier --write --no-error-on-unmatched-pattern"
55 | ],
56 | "*.json": [
57 | "prettier --write --no-error-on-unmatched-pattern"
58 | ],
59 | "*.{js,jsx}": [
60 | "prettier --write",
61 | "stylelint --fix",
62 | "eslint --fix"
63 | ],
64 | "*.{ts,tsx}": [
65 | "prettier --parser=typescript --write",
66 | "stylelint --fix",
67 | "eslint --fix"
68 | ]
69 | },
70 | "dependencies": {
71 | "@babel/runtime": "^7.26.7",
72 | "@lobehub/ui": "^1.164.11",
73 | "@vercel/og": "~0.6.5",
74 | "ahooks": "^3.8.4",
75 | "antd": "^5.23.4",
76 | "antd-style": "^3.7.1",
77 | "dotenv": "^16.4.7",
78 | "fast-deep-equal": "^3.1.3",
79 | "immer": "^10.1.1",
80 | "leva": "^0.10.0",
81 | "lodash-es": "^4.17.21",
82 | "lucide-react": "^0.396.0",
83 | "node-html-parser": "^6.1.13",
84 | "polished": "^4.3.1",
85 | "query-string": "^9.1.1",
86 | "react": "^19.0.0",
87 | "react-dom": "^19.0.0",
88 | "react-layout-kit": "^1.9.1",
89 | "react-markdown": "^9.0.3",
90 | "react-syntax-highlighter": "^15.6.1",
91 | "rehype-highlight": "^7.0.2",
92 | "rehype-raw": "^7.0.0",
93 | "remark-gfm": "^4.0.0",
94 | "remark-toc": "^9.0.0",
95 | "simple-icons": "^10.4.0",
96 | "swr": "^2.3.2",
97 | "url-join": "^5.0.0",
98 | "use-merge-value": "^1.2.0",
99 | "utility-types": "^3.11.0",
100 | "zustand": "^4.5.6",
101 | "zustand-utils": "^1.3.2"
102 | },
103 | "devDependencies": {
104 | "@commitlint/cli": "^19.7.1",
105 | "@lobehub/lint": "^1.25.7",
106 | "@testing-library/react": "^14.3.1",
107 | "@types/lodash-es": "^4.17.12",
108 | "@types/react": "^19.0.8",
109 | "@types/react-dom": "^19.0.3",
110 | "@vitest/coverage-v8": "~1.2.2",
111 | "babel-plugin-antd-style": "^1.0.4",
112 | "clean-package": "^2.2.0",
113 | "commitlint": "^19.7.1",
114 | "concurrently": "^9.1.2",
115 | "cross-env": "^7.0.3",
116 | "dumi": "^2.4.17",
117 | "dumi-theme-lobehub": "^1.15.0",
118 | "eslint": "^8.57.1",
119 | "father": "^4.5.2",
120 | "husky": "^9.1.7",
121 | "jsdom": "^26.0.0",
122 | "lint-staged": "^15.4.3",
123 | "prettier": "^3.4.2",
124 | "remark": "^15.0.1",
125 | "remark-cli": "^12.0.1",
126 | "semantic-release": "^21.1.2",
127 | "stylelint": "^15.11.0",
128 | "typescript": "^5.7.3",
129 | "vitest": "~1.2.2"
130 | },
131 | "publishConfig": {
132 | "access": "public",
133 | "registry": "https://registry.npmjs.org"
134 | },
135 | "pnpm": {
136 | "overrides": {
137 | "mdast-util-gfm": "3.0.0"
138 | }
139 | },
140 | "overrides": {
141 | "mdast-util-gfm": "3.0.0"
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-Agent: *
2 | Allow: /
3 |
4 | Host: https://readme-wizard.lobehub.com
5 | Sitemap: https://readme-wizard.lobehub.com/sitemap.xml
6 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "automerge": false,
4 | "dependencyDashboard": true,
5 | "ignoreDeps": [],
6 | "labels": ["dependencies"],
7 | "postUpdateOptions": ["yarnDedupeHighest"],
8 | "prConcurrentLimit": 30,
9 | "prHourlyLimit": 0,
10 | "rebaseWhen": "conflicted",
11 | "schedule": "on sunday before 6:00am",
12 | "timezone": "UTC"
13 | }
14 |
--------------------------------------------------------------------------------
/scripts/syncSimpleIconList.mjs:
--------------------------------------------------------------------------------
1 | import { writeFileSync } from 'node:fs';
2 | import { resolve } from 'node:path';
3 | import { getIconsData, titleToSlug } from 'simple-icons/sdk';
4 |
5 | const runIconSync = async () => {
6 | const data = await getIconsData();
7 | const list = data.map((icon) => titleToSlug(icon.title));
8 | writeFileSync(
9 | resolve('./src/const/icons.ts'),
10 | `export default ${JSON.stringify(Array.from(new Set(list.filter(Boolean))))} as const`,
11 | 'utf8',
12 | );
13 | };
14 |
15 | runIconSync();
16 |
--------------------------------------------------------------------------------
/src/Readme/share.ts:
--------------------------------------------------------------------------------
1 | import { folder } from 'leva';
2 | import { cloneDeep, pick } from 'lodash-es';
3 |
4 | import { shieldBaseControls } from '@/const/shieldBaseControls';
5 |
6 | export const defaultControls = {
7 | /* eslint-disable sort-keys-fix/sort-keys-fix */
8 | owner: 'lobehub',
9 | repo: 'lobe-chat',
10 | /* eslint-enable */
11 | };
12 |
13 | export const defaultControlsExtra = {
14 | ['⚒️']: folder(pick(cloneDeep(shieldBaseControls), ['color', 'labelColor', 'style']), {
15 | collapsed: true,
16 | }),
17 | };
18 |
--------------------------------------------------------------------------------
/src/ReadmeContributing/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav: components
3 | group: readme
4 | title: Contributing
5 | order: 6
6 | description: Contributing markdown generator
7 | ---
8 |
9 | ## Editor
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/ReadmeContributing/index.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, useCreateStore } from '@lobehub/ui';
2 | import { memo, useMemo } from 'react';
3 |
4 | import { defaultControls } from '@/Readme/share';
5 | import MarkdownStorybook from '@/components/MarkdownStorybook';
6 | import { genMarkdownContributing } from '@/services/genMarkdownContributing';
7 |
8 | const controls = {
9 | /* eslint-disable sort-keys-fix/sort-keys-fix */
10 | ...defaultControls,
11 | prWelcome: '🤯 PR WELCOME',
12 | backToTop: true,
13 | /* eslint-enable */
14 | };
15 |
16 | const Hero = memo(() => {
17 | const store = useCreateStore();
18 |
19 | const options = useControls(controls, { store });
20 |
21 | const md = useMemo(() => genMarkdownContributing(options), [options]);
22 |
23 | return {md.join('\n\n')} ;
24 | });
25 |
26 | export default Hero;
27 |
--------------------------------------------------------------------------------
/src/ReadmeCredits/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav: components
3 | group: readme
4 | title: Credits
5 | order: 6
6 | description: Credits markdown generator
7 | ---
8 |
9 | ## Editor
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/ReadmeCredits/index.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, useCreateStore } from '@lobehub/ui';
2 | import { memo, useMemo, useState } from 'react';
3 |
4 | import MarkdownEditor from '@/components/MarkdownEditor';
5 | import MarkdownStorybook from '@/components/MarkdownStorybook';
6 | import { creditsSample } from '@/const/sample';
7 | import { genMarkdownCredits } from '@/services/genMarkdownCredits';
8 |
9 | const controls = {
10 | /* eslint-disable sort-keys-fix/sort-keys-fix */
11 | title: 'Links',
12 | backToTop: true,
13 | /* eslint-enable */
14 | };
15 |
16 | const Credits = memo(() => {
17 | const [value, setValue] = useState(creditsSample);
18 | const store = useCreateStore();
19 |
20 | const options = useControls(controls, { store });
21 |
22 | const md = useMemo(() => genMarkdownCredits(options, value), [value, options]);
23 |
24 | return (
25 | <>
26 |
27 | {md.join('\n\n')}
28 | >
29 | );
30 | });
31 |
32 | export default Credits;
33 |
--------------------------------------------------------------------------------
/src/ReadmeDevelopment/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav: components
3 | group: readme
4 | title: Development
5 | order: 4
6 | description: Development markdown generator
7 | ---
8 |
9 | ## Editor
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/ReadmeDevelopment/index.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, useCreateStore } from '@lobehub/ui';
2 | import { memo, useMemo } from 'react';
3 |
4 | import { defaultControls } from '@/Readme/share';
5 | import MarkdownStorybook from '@/components/MarkdownStorybook';
6 | import { genMarkdownDevelopment } from '@/services/genMarkdownDevelopment';
7 |
8 | const controls = {
9 | ...defaultControls,
10 | backToTop: true,
11 | bun: true,
12 | };
13 |
14 | const Hero = memo(() => {
15 | const store = useCreateStore();
16 |
17 | const options = useControls(controls, { store });
18 |
19 | const md = useMemo(() => genMarkdownDevelopment(options), [options]);
20 |
21 | return {md.join('\n\n')} ;
22 | });
23 |
24 | export default Hero;
25 |
--------------------------------------------------------------------------------
/src/ReadmeFeatures/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav: components
3 | group: readme
4 | title: Features
5 | order: 2
6 | description: Features markdown generator
7 | ---
8 |
9 | ## Editor
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/ReadmeFeatures/index.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, useCreateStore } from '@lobehub/ui';
2 | import { memo, useMemo, useState } from 'react';
3 |
4 | import MarkdownEditor from '@/components/MarkdownEditor';
5 | import MarkdownStorybook from '@/components/MarkdownStorybook';
6 | import { featuresSample } from '@/const/sample';
7 | import { genMarkdownFeatures } from '@/services/genMarkdownFeatures';
8 |
9 | const controls = {
10 | /* eslint-disable sort-keys-fix/sort-keys-fix */
11 | title: 'Features',
12 | backToTop: true,
13 | /* eslint-enable */
14 | };
15 |
16 | const Features = memo(() => {
17 | const [value, setValue] = useState(featuresSample);
18 | const store = useCreateStore();
19 |
20 | const options = useControls(controls, { store });
21 |
22 | const md = useMemo(() => genMarkdownFeatures(options, value), [value, options]);
23 |
24 | return (
25 | <>
26 |
27 | {md.join('\n\n')}
28 | >
29 | );
30 | });
31 |
32 | export default Features;
33 |
--------------------------------------------------------------------------------
/src/ReadmeHero/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav: components
3 | group: readme
4 | title: Hero
5 | order: 1
6 | description: Hero markdown generator
7 | ---
8 |
9 | ## Editor
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/ReadmeHero/index.tsx:
--------------------------------------------------------------------------------
1 | import { folder, useControls, useCreateStore } from 'leva';
2 | import { memo, useMemo } from 'react';
3 |
4 | import { defaultControls, defaultControlsExtra } from '@/Readme/share';
5 | import MarkdownStorybook from '@/components/MarkdownStorybook';
6 | import { genMarkdownHero } from '@/services/genMarkdownHero';
7 |
8 | const controls = {
9 | /* eslint-disable sort-keys-fix/sort-keys-fix */
10 | logo: 'https://registry.npmmirror.com/@lobehub/assets-logo/1.0.0/files/assets/logo-3d.webp',
11 | logo2: '',
12 | title: 'Lobe Chat',
13 | description:
14 | 'LobeChat is a open-source, extensible , high-performance chatbot framework. It supports one-click free deployment of your private ChatGPT/LLM web application.',
15 | banner: 'https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png',
16 | backToTop: true,
17 | Github: folder({ ...defaultControls, branch: 'main', workflow: 'test,release' }),
18 | NPM: folder({
19 | packageName: '@lobehub/chat',
20 | }),
21 | ...defaultControlsExtra,
22 | /* eslint-enable */
23 | };
24 |
25 | const Hero = memo(() => {
26 | const store = useCreateStore();
27 |
28 | const options = useControls(controls, { store });
29 |
30 | const md = useMemo(() => genMarkdownHero(options), [options]);
31 |
32 | return {md.join('\n\n')} ;
33 | });
34 |
35 | export default Hero;
36 |
--------------------------------------------------------------------------------
/src/ReadmeInstallation/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav: components
3 | group: readme
4 | title: Installation
5 | order: 2
6 | description: Installation markdown generator
7 | ---
8 |
9 | ## Editor
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/ReadmeInstallation/index.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, useCreateStore } from '@lobehub/ui';
2 | import { memo, useMemo } from 'react';
3 |
4 | import MarkdownStorybook from '@/components/MarkdownStorybook';
5 | import { genMarkdownInstallation } from '@/services/genMarkdownInstallation';
6 |
7 | const controls = {
8 | /* eslint-disable sort-keys-fix/sort-keys-fix */
9 | packageName: '@lobehub/ui',
10 | esm: true,
11 | bun: true,
12 | nextjs: true,
13 | backToTop: true,
14 | /* eslint-enable */
15 | };
16 |
17 | const Installation = memo(() => {
18 | const store = useCreateStore();
19 |
20 | const options = useControls(controls, { store });
21 |
22 | const md = useMemo(() => genMarkdownInstallation(options), [options]);
23 |
24 | return {md.join('\n\n')} ;
25 | });
26 |
27 | export default Installation;
28 |
--------------------------------------------------------------------------------
/src/ReadmeLicense/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav: components
3 | group: readme
4 | title: License
5 | order: 7
6 | description: License markdown generator
7 | ---
8 |
9 | ## Editor
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/ReadmeLicense/index.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, useCreateStore } from '@lobehub/ui';
2 | import { memo, useMemo } from 'react';
3 |
4 | import { defaultControls } from '@/Readme/share';
5 | import MarkdownStorybook from '@/components/MarkdownStorybook';
6 | import { genMarkdownLicense } from '@/services/genMarkdownLicense';
7 |
8 | const controls = {
9 | /* eslint-disable sort-keys-fix/sort-keys-fix */
10 | ...defaultControls,
11 | license: 'MIT',
12 | /* eslint-enable */
13 | };
14 |
15 | const Hero = memo(() => {
16 | const store = useCreateStore();
17 |
18 | const options = useControls(controls, { store });
19 |
20 | const md = useMemo(() => genMarkdownLicense(options), [options]);
21 |
22 | return {md.join('\n\n')} ;
23 | });
24 |
25 | export default Hero;
26 |
--------------------------------------------------------------------------------
/src/ShieldShare/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav: components
3 | group: shields
4 | title: Share
5 | order: 7
6 | description: Share shields generator
7 | ---
8 |
9 | ## Editor
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/ShieldShare/index.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, useCreateStore } from '@lobehub/ui';
2 | import { folder } from 'leva';
3 | import { pick } from 'lodash-es';
4 | import { memo, useMemo } from 'react';
5 |
6 | import MarkdownStorybook from '@/components/MarkdownStorybook';
7 | import { shareShieldControlsPickList } from '@/const/shareShieldControls';
8 | import { shieldBaseControls } from '@/const/shieldBaseControls';
9 | import { genShareShields } from '@/services/genShareShield';
10 |
11 | const controls = {
12 | /* eslint-disable sort-keys-fix/sort-keys-fix */
13 | title: {
14 | value: 'Check this GitHub repository out 🤯 LobeChat',
15 | rows: 4,
16 | },
17 | desc: {
18 | value:
19 | 'An open-source, extensible (Function Calling), high-performance chatbot framework. It supports one-click free deployment of your private ChatGPT/LLM web application.',
20 | rows: 8,
21 | },
22 | hashtags: 'chatbot, chatGPT, openAI',
23 | url: 'https://github.com/lobehub/lobe-chat',
24 | ['⚒️']: folder(
25 | {
26 | ...pick(shieldBaseControls, ['style', 'color']),
27 | labelColor: {
28 | ...shieldBaseControls.labelColor,
29 | value: 'black',
30 | },
31 | logoColor: {
32 | ...shieldBaseControls.logoColor,
33 | value: 'white',
34 | },
35 | },
36 | {
37 | collapsed: true,
38 | },
39 | ),
40 | /* eslint-enable */
41 | };
42 | const pickControls = { ['✅']: folder(shareShieldControlsPickList, { collapsed: true }) };
43 |
44 | const Share = memo(() => {
45 | const store = useCreateStore();
46 |
47 | const options = useControls(controls, { store });
48 | const pickOptions = useControls(pickControls, { store });
49 |
50 | const md = useMemo(() => genShareShields(options, pickOptions), [options, pickOptions]);
51 |
52 | return {md.join('\n\n')} ;
53 | });
54 |
55 | export default Share;
56 |
--------------------------------------------------------------------------------
/src/ShieldsBilibili/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav: components
3 | group: shields
4 | title: Bilibili
5 | order: 6
6 | description: Bilibili shields generator
7 | ---
8 |
9 | ## Editor
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/ShieldsBilibili/index.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, useCreateStore } from '@lobehub/ui';
2 | import { LevaInputs, folder } from 'leva';
3 | import { pick } from 'lodash-es';
4 | import { memo, useMemo } from 'react';
5 |
6 | import MarkdownStorybook from '@/components/MarkdownStorybook';
7 | import { shieldBaseControls } from '@/const/shieldBaseControls';
8 | import { genBilibiliShield } from '@/services/genCustomShield';
9 |
10 | const controls = {
11 | /* eslint-disable sort-keys-fix/sort-keys-fix */
12 | uid: {
13 | value: '410372',
14 | type: LevaInputs.STRING,
15 | },
16 | label: 'followers',
17 | ['⚒️']: folder(
18 | {
19 | ...pick(shieldBaseControls, ['style', 'labelColor']),
20 | color: {
21 | ...shieldBaseControls.labelColor,
22 | value: 'fb7299',
23 | },
24 | logoColor: {
25 | ...shieldBaseControls.logoColor,
26 | value: 'white',
27 | },
28 | },
29 | {
30 | collapsed: true,
31 | },
32 | ),
33 | /* eslint-enable */
34 | };
35 |
36 | const Bilibili = memo(() => {
37 | const store = useCreateStore();
38 |
39 | const options = useControls(controls, { store });
40 |
41 | const md = useMemo(() => genBilibiliShield(options as any), [options]);
42 |
43 | return {md.join('\n\n')} ;
44 | });
45 |
46 | export default Bilibili;
47 |
--------------------------------------------------------------------------------
/src/ShieldsCustom/Double.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, useCreateStore } from '@lobehub/ui';
2 | import { memo, useMemo } from 'react';
3 |
4 | import MarkdownStorybook from '@/components/MarkdownStorybook';
5 | import { shieldBaseControls } from '@/const/shieldBaseControls';
6 | import { genCustomDoubleShield } from '@/services/genCustomShield';
7 |
8 | import { defaultControls } from './share';
9 |
10 | const controls = {
11 | /* eslint-disable sort-keys-fix/sort-keys-fix */
12 | content: 'LobeHub',
13 | labelColor: shieldBaseControls.labelColor,
14 | label: 'Readme Generator',
15 | color: {
16 | ...shieldBaseControls.color,
17 | value: 'white',
18 | },
19 | link: 'https://github.com/lobehub/lobe-readme-wizard',
20 | ...defaultControls,
21 | /* eslint-enable */
22 | };
23 |
24 | const CustomDouble = memo(() => {
25 | const store = useCreateStore();
26 |
27 | const options = useControls(controls, { store });
28 |
29 | const md = useMemo(() => genCustomDoubleShield(options), [options]);
30 |
31 | return {md.join('\n\n')} ;
32 | });
33 |
34 | export default CustomDouble;
35 |
--------------------------------------------------------------------------------
/src/ShieldsCustom/Single.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, useCreateStore } from '@lobehub/ui';
2 | import { memo, useMemo } from 'react';
3 |
4 | import MarkdownStorybook from '@/components/MarkdownStorybook';
5 | import { shieldBaseControls } from '@/const/shieldBaseControls';
6 | import { genCustomSingleShield } from '@/services/genCustomShield';
7 |
8 | import { defaultControls } from './share';
9 |
10 | const controls = {
11 | /* eslint-disable sort-keys-fix/sort-keys-fix */
12 | label: 'Readme Generator',
13 | color: {
14 | ...shieldBaseControls.color,
15 | value: 'black',
16 | },
17 | link: 'https://github.com/lobehub/lobe-readme-wizard',
18 | ...defaultControls,
19 | /* eslint-enable */
20 | };
21 |
22 | const CustomSingle = memo(() => {
23 | const store = useCreateStore();
24 |
25 | const options = useControls(controls, { store });
26 |
27 | const md = useMemo(() => genCustomSingleShield(options), [options]);
28 |
29 | return {md.join('\n\n')} ;
30 | });
31 |
32 | export default CustomSingle;
33 |
--------------------------------------------------------------------------------
/src/ShieldsCustom/Website.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, useCreateStore } from '@lobehub/ui';
2 | import { memo, useMemo } from 'react';
3 |
4 | import MarkdownStorybook from '@/components/MarkdownStorybook';
5 | import { shieldBaseControls } from '@/const/shieldBaseControls';
6 | import { genWebsiteShield } from '@/services/genCustomShield';
7 |
8 | import { defaultControls } from './share';
9 |
10 | const controls = {
11 | /* eslint-disable sort-keys-fix/sort-keys-fix */
12 | label: 'LobeChat',
13 | url: 'https://chat-preview.lobehub.com',
14 | up_message: 'online',
15 | down_message: 'offline',
16 | labelColor: {
17 | ...shieldBaseControls.labelColor,
18 | value: 'black',
19 | },
20 | ...defaultControls,
21 | /* eslint-enable */
22 | };
23 |
24 | const Website = memo(() => {
25 | const store = useCreateStore();
26 |
27 | const options = useControls(controls, { store });
28 |
29 | const md = useMemo(() => genWebsiteShield(options), [options]);
30 |
31 | return {md.join('\n\n')} ;
32 | });
33 |
34 | export default Website;
35 |
--------------------------------------------------------------------------------
/src/ShieldsCustom/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav: components
3 | group: shields
4 | title: Custom
5 | order: 1
6 | description: Shields generator
7 | ---
8 |
9 | ## Editor
10 |
11 | ### Custom Single
12 |
13 |
14 |
15 | ### Custom Double
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/ShieldsCustom/index.tsx:
--------------------------------------------------------------------------------
1 | export { default as ShieldsCustomDouble } from './Double';
2 | export { default as ShieldsCustomSingle } from './Single';
3 |
--------------------------------------------------------------------------------
/src/ShieldsCustom/share.ts:
--------------------------------------------------------------------------------
1 | import { folder } from 'leva';
2 | import { pick } from 'lodash-es';
3 |
4 | import { shieldBaseControls } from '@/const/shieldBaseControls';
5 |
6 | export const defaultControls = {
7 | ['⚒️']: folder(pick(shieldBaseControls, ['logo', 'logoColor', 'style']), {
8 | collapsed: true,
9 | }),
10 | };
11 |
--------------------------------------------------------------------------------
/src/ShieldsDiscord/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav: components
3 | group: shields
4 | title: Discord
5 | order: 5
6 | description: Discord shields generator
7 | ---
8 |
9 | ## Editor
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/ShieldsDiscord/index.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, useCreateStore } from '@lobehub/ui';
2 | import { LevaInputs, folder } from 'leva';
3 | import { pick } from 'lodash-es';
4 | import { memo, useMemo } from 'react';
5 |
6 | import MarkdownStorybook from '@/components/MarkdownStorybook';
7 | import { shieldBaseControls } from '@/const/shieldBaseControls';
8 | import { genDiscordShield } from '@/services/genCustomShield';
9 |
10 | const controls = {
11 | /* eslint-disable sort-keys-fix/sort-keys-fix */
12 | serverId: {
13 | value: '1127171173982154893',
14 | type: LevaInputs.STRING,
15 | },
16 | label: 'discord',
17 | link: 'https://discord.gg/AYFPHvv2jT',
18 | ['⚒️']: folder(
19 | {
20 | ...pick(shieldBaseControls, ['style', 'labelColor']),
21 | color: {
22 | ...shieldBaseControls.labelColor,
23 | value: '5865f2',
24 | },
25 | logoColor: {
26 | ...shieldBaseControls.logoColor,
27 | value: 'white',
28 | },
29 | },
30 | {
31 | collapsed: true,
32 | },
33 | ),
34 | /* eslint-enable */
35 | };
36 |
37 | const Discord = memo(() => {
38 | const store = useCreateStore();
39 |
40 | const options = useControls(controls, { store });
41 |
42 | const md = useMemo(() => genDiscordShield(options), [options]);
43 |
44 | return {md.join('\n\n')} ;
45 | });
46 |
47 | export default Discord;
48 |
--------------------------------------------------------------------------------
/src/ShieldsDocker/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav: components
3 | group: shields
4 | title: Docker
5 | order: 4
6 | description: Docker shields generator
7 | ---
8 |
9 | ## Editor
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/ShieldsDocker/index.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, useCreateStore } from '@lobehub/ui';
2 | import { folder } from 'leva';
3 | import { memo, useMemo } from 'react';
4 |
5 | import MarkdownStorybook from '@/components/MarkdownStorybook';
6 | import { dockerShieldControlsPickList } from '@/const/dockerShieldControls';
7 | import { genDockerShields } from '@/services/genDockerShield';
8 |
9 | import { defaultControlsExtra } from './share';
10 |
11 | const controls = defaultControlsExtra;
12 | const pickControls = { ['✅']: folder(dockerShieldControlsPickList, { collapsed: true }) };
13 |
14 | const Docker = memo(() => {
15 | const store = useCreateStore();
16 |
17 | const options = useControls(controls, { store });
18 | const pickOptions = useControls(pickControls, { store });
19 |
20 | const md = useMemo(() => genDockerShields(options, pickOptions), [options, pickOptions]);
21 |
22 | return {md.join('\n\n')} ;
23 | });
24 |
25 | export default Docker;
26 |
--------------------------------------------------------------------------------
/src/ShieldsDocker/share.ts:
--------------------------------------------------------------------------------
1 | import { folder } from 'leva';
2 | import { cloneDeep, pick } from 'lodash-es';
3 |
4 | import { shieldBaseControls } from '@/const/shieldBaseControls';
5 |
6 | export const defaultControls = {
7 | /* eslint-disable sort-keys-fix/sort-keys-fix */
8 | packageName: 'lobehub/lobe-chat',
9 | /* eslint-enable */
10 | };
11 |
12 | export const defaultControlsExtra = {
13 | ...defaultControls,
14 | ['⚒️']: folder(pick(cloneDeep(shieldBaseControls), ['color', 'labelColor', 'style']), {
15 | collapsed: true,
16 | }),
17 | };
18 |
--------------------------------------------------------------------------------
/src/ShieldsGithub/Action.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, useCreateStore } from '@lobehub/ui';
2 | import { memo, useMemo } from 'react';
3 |
4 | import MarkdownStorybook from '@/components/MarkdownStorybook';
5 | import { genGithubActionsShield } from '@/services/genGithubShield';
6 |
7 | import { defaultControlsExtra } from './share';
8 |
9 | const controls = {
10 | workflow: 'test,release',
11 | ...defaultControlsExtra,
12 | };
13 |
14 | const GithubAction = memo(() => {
15 | const store = useCreateStore();
16 |
17 | const options = useControls(controls, { store });
18 |
19 | const md = useMemo(() => genGithubActionsShield(options), [options]);
20 |
21 | return {md.join('\n\n')} ;
22 | });
23 |
24 | export default GithubAction;
25 |
--------------------------------------------------------------------------------
/src/ShieldsGithub/Codespace.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, useCreateStore } from '@lobehub/ui';
2 | import { memo, useMemo } from 'react';
3 |
4 | import MarkdownStorybook from '@/components/MarkdownStorybook';
5 | import { GenGithubCodespaceShield } from '@/services/genGithubShield';
6 |
7 | import { defaultControls } from './share';
8 |
9 | const controls = defaultControls;
10 |
11 | const GithubCodespace = memo(() => {
12 | const store = useCreateStore();
13 |
14 | const options = useControls(controls, { store });
15 |
16 | const md = useMemo(() => {
17 | return GenGithubCodespaceShield(options);
18 | }, [options]);
19 |
20 | return {md.join('\n\n')} ;
21 | });
22 |
23 | export default GithubCodespace;
24 |
--------------------------------------------------------------------------------
/src/ShieldsGithub/Contributors.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, useCreateStore } from '@lobehub/ui';
2 | import { memo, useMemo } from 'react';
3 |
4 | import MarkdownStorybook from '@/components/MarkdownStorybook';
5 | import { GenGithubContributorsShield } from '@/services/genGithubShield';
6 |
7 | import { defaultControls } from './share';
8 |
9 | const controls = defaultControls;
10 |
11 | const GithubContributors = memo(() => {
12 | const store = useCreateStore();
13 |
14 | const options = useControls(controls, { store });
15 |
16 | const md = useMemo(() => {
17 | return GenGithubContributorsShield(options);
18 | }, [options]);
19 |
20 | return {md.join('\n\n')} ;
21 | });
22 |
23 | export default GithubContributors;
24 |
--------------------------------------------------------------------------------
/src/ShieldsGithub/Release.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, useCreateStore } from '@lobehub/ui';
2 | import { folder } from 'leva';
3 | import { memo, useMemo } from 'react';
4 |
5 | import MarkdownStorybook from '@/components/MarkdownStorybook';
6 | import { githubShieldControlsPickList } from '@/const/githubShieldControls';
7 | import { genGithubReleaseShields } from '@/services/genGithubShield';
8 |
9 | import { defaultControlsExtra } from './share';
10 |
11 | const controls = defaultControlsExtra;
12 | const pickControls = { ['✅']: folder(githubShieldControlsPickList, { collapsed: true }) };
13 |
14 | const GithubRelease = memo(() => {
15 | const store = useCreateStore();
16 |
17 | const options = useControls(controls, { store });
18 | const pickOptions = useControls(pickControls, { store });
19 |
20 | const md = useMemo(() => genGithubReleaseShields(options, pickOptions), [options, pickOptions]);
21 |
22 | return {md.join('\n\n')} ;
23 | });
24 |
25 | export default GithubRelease;
26 |
--------------------------------------------------------------------------------
/src/ShieldsGithub/Social.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, useCreateStore } from '@lobehub/ui';
2 | import { folder } from 'leva';
3 | import { memo, useMemo } from 'react';
4 |
5 | import MarkdownStorybook from '@/components/MarkdownStorybook';
6 | import { githubSocialControlsPickList } from '@/const/githubShieldControls';
7 | import { genGithubSocialShields } from '@/services/genGithubShield';
8 |
9 | import { defaultControlsExtra } from './share';
10 |
11 | const controls = {
12 | branch: 'main',
13 | ...defaultControlsExtra,
14 | };
15 | const pickControls = { ['✅']: folder(githubSocialControlsPickList, { collapsed: true }) };
16 |
17 | const GithubSocial = memo(() => {
18 | const store = useCreateStore();
19 |
20 | const options = useControls(controls, { store });
21 | const pickOptions = useControls(pickControls, { store });
22 |
23 | const md = useMemo(() => genGithubSocialShields(options, pickOptions), [options, pickOptions]);
24 |
25 | return {md.join('\n\n')} ;
26 | });
27 |
28 | export default GithubSocial;
29 |
--------------------------------------------------------------------------------
/src/ShieldsGithub/StarHistory.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, useCreateStore } from '@lobehub/ui';
2 | import { memo, useMemo } from 'react';
3 |
4 | import MarkdownStorybook from '@/components/MarkdownStorybook';
5 | import { genGithubStarHistoryShield } from '@/services/genGithubShield';
6 |
7 | import { defaultControls } from './share';
8 |
9 | const controls = defaultControls;
10 |
11 | const GithubStarHistory = memo(() => {
12 | const store = useCreateStore();
13 |
14 | const options = useControls(controls, { store });
15 |
16 | const md = useMemo(() => {
17 | return genGithubStarHistoryShield(options);
18 | }, [options]);
19 |
20 | return {md} ;
21 | });
22 |
23 | export default GithubStarHistory;
24 |
--------------------------------------------------------------------------------
/src/ShieldsGithub/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav: components
3 | group: shields
4 | title: Github
5 | order: 2
6 | description: Github shields generator
7 | ---
8 |
9 | ## Editor
10 |
11 | ### Social
12 |
13 |
14 |
15 | ### Release
16 |
17 |
18 |
19 | ### Action
20 |
21 |
22 |
23 | ### Star History
24 |
25 |
26 |
27 | ### Codespace
28 |
29 |
30 |
31 | ### Contributors
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/ShieldsGithub/index.tsx:
--------------------------------------------------------------------------------
1 | export { default as ShieldsGithubAction } from './Action';
2 | export { default as ShieldsGithubCodespace } from './Codespace';
3 | export { default as ShieldsGithubContributors } from './Contributors';
4 | export { default as ShieldsGithubRelease } from './Release';
5 | export { default as ShieldsGithubSocial } from './Social';
6 | export { default as ShieldsGithubStarHistory } from './StarHistory';
7 |
--------------------------------------------------------------------------------
/src/ShieldsGithub/share.ts:
--------------------------------------------------------------------------------
1 | import { folder } from 'leva';
2 | import { cloneDeep, pick } from 'lodash-es';
3 |
4 | import { shieldBaseControls } from '@/const/shieldBaseControls';
5 |
6 | export const defaultControls = {
7 | /* eslint-disable sort-keys-fix/sort-keys-fix */
8 | owner: 'lobehub',
9 | repo: 'lobe-chat',
10 | /* eslint-enable */
11 | };
12 |
13 | export const defaultControlsExtra = {
14 | ...defaultControls,
15 | ['⚒️']: folder(pick(cloneDeep(shieldBaseControls), ['color', 'labelColor', 'style']), {
16 | collapsed: true,
17 | }),
18 | };
19 |
--------------------------------------------------------------------------------
/src/ShieldsNpm/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav: components
3 | group: shields
4 | title: NPM
5 | order: 3
6 | description: NPM shields generator
7 | ---
8 |
9 | ## Editor
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/ShieldsNpm/index.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, useCreateStore } from '@lobehub/ui';
2 | import { folder } from 'leva';
3 | import { memo, useMemo } from 'react';
4 |
5 | import MarkdownStorybook from '@/components/MarkdownStorybook';
6 | import { npmShieldControlsPickList } from '@/const/npmShieldControls';
7 | import { genNpmShields } from '@/services/genNpmShield';
8 |
9 | import { defaultControlsExtra } from './share';
10 |
11 | const controls = defaultControlsExtra;
12 | const pickControls = { ['✅']: folder(npmShieldControlsPickList, { collapsed: true }) };
13 |
14 | const Npm = memo(() => {
15 | const store = useCreateStore();
16 |
17 | const options = useControls(controls, { store });
18 | const pickOptions = useControls(pickControls, { store });
19 |
20 | const md = useMemo(() => genNpmShields(options, pickOptions), [options, pickOptions]);
21 |
22 | return {md.join('\n\n')} ;
23 | });
24 |
25 | export default Npm;
26 |
--------------------------------------------------------------------------------
/src/ShieldsNpm/share.ts:
--------------------------------------------------------------------------------
1 | import { folder } from 'leva';
2 | import { cloneDeep, pick } from 'lodash-es';
3 |
4 | import { shieldBaseControls } from '@/const/shieldBaseControls';
5 |
6 | export const defaultControls = {
7 | /* eslint-disable sort-keys-fix/sort-keys-fix */
8 | packageName: '@lobehub/ui',
9 | /* eslint-enable */
10 | };
11 |
12 | export const defaultControlsExtra = {
13 | ...defaultControls,
14 | ['⚒️']: folder(pick(cloneDeep(shieldBaseControls), ['color', 'labelColor', 'style']), {
15 | collapsed: true,
16 | }),
17 | };
18 |
--------------------------------------------------------------------------------
/src/ShieldsSocial/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav: components
3 | group: shields
4 | title: Social
5 | order: 8
6 | description: Social shields generator
7 | ---
8 |
9 | ## Editor
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/ShieldsSocial/index.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, useCreateStore } from '@lobehub/ui';
2 | import { folder } from 'leva';
3 | import { pick } from 'lodash-es';
4 | import { memo, useMemo } from 'react';
5 |
6 | import MarkdownStorybook from '@/components/MarkdownStorybook';
7 | import { shieldBaseControls } from '@/const/shieldBaseControls';
8 | import { socialShieldControlsPickList } from '@/const/socialShieldControls';
9 | import { genSocialShields } from '@/services/genSocialShield';
10 |
11 | const idControls = {
12 | /* eslint-disable sort-keys-fix/sort-keys-fix */
13 | qq: '40073838',
14 | wechat: '40073838',
15 | x: 'canisminor1990',
16 | weibo: 'Canis_Minor',
17 | discord: 'canisminor1990',
18 | steam: 'canisminor',
19 | /* eslint-enable */
20 | };
21 |
22 | const controls = {
23 | prefix: true,
24 | ['⚒️']: folder(
25 | {
26 | ...pick(shieldBaseControls, ['style', 'labelColor', 'color']),
27 | logoColor: {
28 | ...shieldBaseControls.logoColor,
29 | value: 'white',
30 | },
31 | },
32 | {
33 | collapsed: true,
34 | },
35 | ),
36 | };
37 | const pickControls = { ['✅']: folder(socialShieldControlsPickList, { collapsed: true }) };
38 |
39 | const Social = memo(() => {
40 | const store = useCreateStore();
41 |
42 | const idOptions = useControls(idControls, { store });
43 | const options = useControls(controls, { store });
44 | const pickOptions = useControls(pickControls, { store });
45 |
46 | const md = useMemo(
47 | () => genSocialShields(options, idOptions, pickOptions),
48 | [options, idOptions, pickOptions],
49 | );
50 |
51 | return {md.join('\n\n')} ;
52 | });
53 |
54 | export default Social;
55 |
--------------------------------------------------------------------------------
/src/ShieldsVercel/Deploy.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, useCreateStore } from '@lobehub/ui';
2 | import { memo, useMemo } from 'react';
3 |
4 | import MarkdownStorybook from '@/components/MarkdownStorybook';
5 | import { GenVercelDeployShield } from '@/services/genVercelShield';
6 |
7 | import { defaultControls } from './share';
8 |
9 | const controls = {
10 | /* eslint-disable sort-keys-fix/sort-keys-fix */
11 | ...defaultControls,
12 | env: 'OPENAI_API_KEY',
13 | envDescription: 'Find your OpenAI API Key by click the right Learn More button.',
14 | envLink: 'https://platform.openai.com/account/api-keys',
15 | /* eslint-enable */
16 | };
17 |
18 | const VercelDeploy = memo(() => {
19 | const store = useCreateStore();
20 |
21 | const options = useControls(controls, { store });
22 |
23 | const md = useMemo(() => {
24 | return GenVercelDeployShield(options);
25 | }, [options]);
26 |
27 | return {md.join('\n\n')} ;
28 | });
29 |
30 | export default VercelDeploy;
31 |
--------------------------------------------------------------------------------
/src/ShieldsVercel/Website.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, useCreateStore } from '@lobehub/ui';
2 | import { memo, useMemo } from 'react';
3 |
4 | import MarkdownStorybook from '@/components/MarkdownStorybook';
5 | import { genVercelWebsiteShield } from '@/services/genVercelShield';
6 |
7 | const controls = {
8 | label: '',
9 | url: 'https://lobe-readme-wizard.vercel.app',
10 | };
11 |
12 | const VercelWebsite = memo(() => {
13 | const store = useCreateStore();
14 |
15 | const options = useControls(controls, { store });
16 |
17 | const md = useMemo(
18 | () =>
19 | genVercelWebsiteShield({
20 | label: options.label || options.url.replaceAll('https://', '').replaceAll('http://', ''),
21 |
22 | url: options.url,
23 | }),
24 | [options],
25 | );
26 |
27 | return {md.join('\n\n')} ;
28 | });
29 |
30 | export default VercelWebsite;
31 |
--------------------------------------------------------------------------------
/src/ShieldsVercel/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav: components
3 | group: shields
4 | title: Vercel
5 | order: 4
6 | description: Vercel shields generator
7 | ---
8 |
9 | ## Editor
10 |
11 |
12 |
13 |
14 |
15 | ### Website
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/ShieldsVercel/index.tsx:
--------------------------------------------------------------------------------
1 | export { default as ShieldsVercelDeploy } from './Deploy';
2 | export { default as ShieldsVercelWebsite } from './Website';
3 |
--------------------------------------------------------------------------------
/src/ShieldsVercel/share.ts:
--------------------------------------------------------------------------------
1 | export const defaultControls = {
2 | /* eslint-disable sort-keys-fix/sort-keys-fix */
3 | owner: 'lobehub',
4 | repo: 'lobe-chat',
5 | /* eslint-enable */
6 | };
7 |
--------------------------------------------------------------------------------
/src/ShieldsWebsite/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav: components
3 | group: shields
4 | title: Website
5 | order: 1
6 | description: Website shields generator
7 | ---
8 |
9 | ## Editor
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/ShieldsWebsite/index.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, useCreateStore } from '@lobehub/ui';
2 | import { memo, useMemo } from 'react';
3 |
4 | import { defaultControls } from '@/ShieldsCustom/share';
5 | import MarkdownStorybook from '@/components/MarkdownStorybook';
6 | import { shieldBaseControls } from '@/const/shieldBaseControls';
7 | import { genWebsiteShield } from '@/services/genCustomShield';
8 |
9 | const controls = {
10 | /* eslint-disable sort-keys-fix/sort-keys-fix */
11 | label: 'LobeChat',
12 | url: 'https://chat-preview.lobehub.com',
13 | up_message: 'online',
14 | down_message: 'offline',
15 | labelColor: {
16 | ...shieldBaseControls.labelColor,
17 | value: 'black',
18 | },
19 | ...defaultControls,
20 | /* eslint-enable */
21 | };
22 |
23 | const Website = memo(() => {
24 | const store = useCreateStore();
25 |
26 | const options = useControls(controls, { store });
27 |
28 | const md = useMemo(() => genWebsiteShield(options), [options]);
29 |
30 | return {md.join('\n\n')} ;
31 | });
32 |
33 | export default Website;
34 |
--------------------------------------------------------------------------------
/src/Sponsor/Avatar.tsx:
--------------------------------------------------------------------------------
1 | import type { CSSProperties, FC } from 'react';
2 |
3 | import { theme } from './style';
4 |
5 | interface AvatarProps {
6 | name: string;
7 | size?: number;
8 | src?: string;
9 | style?: CSSProperties;
10 | themeMode?: 'light' | 'dark';
11 | }
12 |
13 | export const Avatar: FC = ({
14 | src,
15 | name,
16 | size = 64,
17 | style,
18 |
19 | themeMode,
20 | }) => {
21 | const styles = theme(themeMode);
22 | return (
23 |
38 | {src ? (
39 |
40 | ) : (
41 |
48 | {name.slice(0, 2).toUpperCase()}
49 |
50 | )}
51 |
52 | );
53 | };
54 |
--------------------------------------------------------------------------------
/src/Sponsor/const.ts:
--------------------------------------------------------------------------------
1 | import { CSSProperties } from 'react';
2 |
3 | export const DEFAULT_WIDTH = 800;
4 | export const DEFAULT_AVATAR_SIZE = 64;
5 |
6 | export interface MemberProfile {
7 | MemberId?: number;
8 | company?: string;
9 | createdAt?: string;
10 | currency?: string;
11 | description?: string;
12 | email?: string;
13 | github?: string;
14 | image?: string;
15 | isActive?: boolean;
16 | lastTransactionAmount?: number;
17 | lastTransactionAt?: string;
18 | name: string;
19 | profile?: string;
20 | role?: 'ADMIN' | 'HOST' | 'BACKER';
21 | tier?: string;
22 | totalAmountDonated: number;
23 | twitter?: string;
24 | type?: 'USER' | 'ORGANIZATION';
25 | website?: string;
26 | }
27 |
28 | export interface TierItem {
29 | amount: number;
30 | emoji: string;
31 | preset: 'backer' | 'sponsor';
32 | sort: number;
33 | style?: CSSProperties;
34 | title: string;
35 | }
36 |
37 | export const DEFAULT_GROUP: TierItem[] = [
38 | {
39 | amount: 250,
40 | emoji: '🥇',
41 | preset: 'sponsor',
42 | sort: 22,
43 | style: {
44 | backgroundImage: `linear-gradient(45deg, #F5E729 0%, #DC9A01 33%, #DC9A01 66%, #F5E729 100%)`,
45 | },
46 | title: '🥇 Gold Sponsor',
47 | },
48 | {
49 | amount: 100,
50 | emoji: '🥈',
51 | preset: 'sponsor',
52 | sort: 21,
53 | style: {
54 | backgroundImage: `linear-gradient(45deg, #D8D8D8 0%, #888888 33%, #888888 66%, #D8D8D8 100%)`,
55 | },
56 | title: '🥈 Silver Sponsor',
57 | },
58 | {
59 | amount: 50,
60 | emoji: '🥉',
61 | preset: 'sponsor',
62 | sort: 20,
63 | style: {
64 | backgroundImage: `linear-gradient(45deg, #D8974D 0%, #833204 33%, #833204 66%, #D8974D 100%)`,
65 | },
66 | title: '🥉 Bronze Sponsor',
67 | },
68 | {
69 | amount: 18,
70 | emoji: '💖',
71 | preset: 'backer',
72 | sort: 11,
73 | title: '💖 Generous Backer',
74 | },
75 | {
76 | amount: 6,
77 | emoji: '☕',
78 | preset: 'backer',
79 | sort: 10,
80 | title: '☕ Backer',
81 | },
82 | {
83 | amount: 1,
84 | emoji: '🌟',
85 | preset: 'backer',
86 | sort: 0,
87 | title: '🌟 One Time',
88 | },
89 | ];
90 |
--------------------------------------------------------------------------------
/src/Sponsor/demos/index.tsx:
--------------------------------------------------------------------------------
1 | import { Sponsor } from '@lobehub/readme-wizard';
2 | import { StoryBook, useControls, useCreateStore } from '@lobehub/ui';
3 | import { useThemeMode } from 'antd-style';
4 |
5 | import { DEFAULT_AVATAR_SIZE, DEFAULT_WIDTH } from '../const';
6 | import { caleHeight } from '../utils';
7 | import { data } from './data';
8 |
9 | export default () => {
10 | const { isDarkMode } = useThemeMode();
11 | const store = useCreateStore();
12 | const { width, avatarSize } = useControls(
13 | {
14 | avatarSize: {
15 | step: 1,
16 | value: DEFAULT_AVATAR_SIZE,
17 | },
18 |
19 | width: {
20 | step: 1,
21 | value: DEFAULT_WIDTH,
22 | },
23 | },
24 | { store },
25 | );
26 |
27 | return (
28 |
29 |
36 |
37 |
38 |
39 | );
40 | };
41 |
--------------------------------------------------------------------------------
/src/Sponsor/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav: components
3 | group: sponsor
4 | title: SponsorKit
5 | order: 1
6 | description: Sponsor shields generator
7 | ---
8 |
9 | ## Editor
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/Sponsor/index.tsx:
--------------------------------------------------------------------------------
1 | import type { CSSProperties, FC } from 'react';
2 |
3 | import { Sponsorship } from '@/services/sponsorkit/types';
4 |
5 | import { Avatar } from './Avatar';
6 | import { DEFAULT_AVATAR_SIZE } from './const';
7 | import { theme } from './style';
8 | import { formateSponsorData, getTier } from './utils';
9 |
10 | export interface SponsorProps {
11 | avatarSize?: number;
12 | data: Sponsorship[];
13 | height?: number;
14 | padding?: number;
15 | style?: CSSProperties;
16 | texts?: [string, string];
17 | themeMode?: 'light' | 'dark';
18 | width?: number;
19 | }
20 |
21 | const Sponsor: FC = ({
22 | style,
23 | data,
24 | avatarSize = DEFAULT_AVATAR_SIZE,
25 | texts = ['Become ❤️', 'LobeHub'],
26 | themeMode,
27 | }) => {
28 | const styles = theme(themeMode);
29 |
30 | const sponsorData = formateSponsorData(data);
31 |
32 | return (
33 |
43 | {sponsorData.map((item, index) => {
44 | const tierConfig = getTier(item.tier);
45 | const multiplier = Math.floor(item.totalAmountDonated / tierConfig.amount);
46 | return (
47 |
58 |
72 | {multiplier > 1 && (
73 |
90 | {`×${Math.floor(multiplier)}`}
91 |
92 | )}
93 |
109 | {tierConfig.emoji}
110 | {item?.name?.length > 8 ? item.name.slice(0, 7) + '...' : item.name}
111 |
112 |
113 | );
114 | })}
115 |
116 |
131 |
{texts[0]}
132 |
140 |
{texts[1]}
141 |
142 | S
143 | p
144 | o
145 | n
146 | s
147 | o
148 | r
149 |
150 |
151 |
152 |
153 | );
154 | };
155 |
156 | export default Sponsor;
157 |
--------------------------------------------------------------------------------
/src/Sponsor/style.ts:
--------------------------------------------------------------------------------
1 | interface Style {
2 | avatarBackgroundColor: string;
3 | avatarFontColor: string;
4 | backgroundColor: string;
5 | borderColor: string;
6 | fontColor: string;
7 | }
8 |
9 | export const theme = (themeMode?: 'light' | 'dark'): Style =>
10 | themeMode === 'dark'
11 | ? {
12 | avatarBackgroundColor: '#111',
13 | avatarFontColor: '#eee',
14 | backgroundColor: '#000',
15 | borderColor: '#333',
16 | fontColor: '#eee',
17 | }
18 | : {
19 | avatarBackgroundColor: '#f5f5f5',
20 | avatarFontColor: '#666',
21 | backgroundColor: '#fff',
22 | borderColor: '#e4e9ec',
23 | fontColor: '#333',
24 | };
25 |
--------------------------------------------------------------------------------
/src/Sponsor/utils.ts:
--------------------------------------------------------------------------------
1 | import { Sponsorship } from '@/services/sponsorkit/types';
2 |
3 | import {
4 | DEFAULT_AVATAR_SIZE,
5 | DEFAULT_GROUP,
6 | DEFAULT_WIDTH,
7 | MemberProfile,
8 | TierItem,
9 | } from './const';
10 |
11 | export const caleHeight = (
12 | data: Sponsorship[] = [],
13 | {
14 | width = DEFAULT_WIDTH,
15 | avatarSize = DEFAULT_AVATAR_SIZE,
16 | }: { avatarSize: number; multiplier?: number; width: number },
17 | ): number => {
18 | const length = data.length + 2;
19 | const col = width / (avatarSize * 1.4);
20 | return Math.ceil(length / Math.ceil(col)) * avatarSize * 1.4;
21 | };
22 |
23 | export const formateSponsorData = (
24 | json: Sponsorship[],
25 | groupBy: TierItem[] = DEFAULT_GROUP,
26 | fallbackTier: string = (DEFAULT_GROUP.at(-1) as TierItem).title,
27 | ): MemberProfile[] => {
28 | const tierSortMap = new Map(groupBy.map((item) => [item.title, item.sort]));
29 |
30 | const getValue = (item: Sponsorship) =>
31 | item?.raw?.totalDonations?.value || item?.raw?.amount?.value || item?.monthlyDollars;
32 |
33 | const sortByGroup = (a: Sponsorship, b: Sponsorship) => {
34 | const sortA = tierSortMap.get(a.tierName || fallbackTier) || 0;
35 | const sortB = tierSortMap.get(b.tierName || fallbackTier) || 0;
36 | if (sortA !== sortB) {
37 | return sortB - sortA;
38 | }
39 |
40 | return getValue(b) - getValue(a);
41 | };
42 | const filteredData = json.filter((item: Sponsorship) => {
43 | const dump = json
44 | .filter((i: Sponsorship) => item.sponsor.login === i.sponsor.login)
45 | .sort(sortByGroup);
46 | if (dump.length > 1 && item.tierName !== dump[0].tierName) return false;
47 | return getValue(item) > 0;
48 | });
49 |
50 | return filteredData.sort(sortByGroup).map((item) => {
51 | return {
52 | image: item.sponsor.avatarUrl,
53 | name: item.sponsor.name || 'Guest',
54 | tier: item.tierName || fallbackTier,
55 | totalAmountDonated: getValue(item),
56 | };
57 | });
58 | };
59 |
60 | export const getTier = (
61 | tier: string = (DEFAULT_GROUP.at(-1) as TierItem).title,
62 | groupBy: TierItem[] = DEFAULT_GROUP,
63 | ): TierItem => {
64 | return (
65 | groupBy.find((item) => item.title.toLowerCase() === tier.toLowerCase()) ||
66 | (groupBy.at(-1) as TierItem)
67 | );
68 | };
69 |
70 | export const fetchFonts = async () => {
71 | // Regular Font
72 | const fontFileRegular = await fetch(
73 | 'https://gw.alipayobjects.com/os/kitchen/BUfo9kyDYs/HarmonyOS_Sans_Regular.ttf',
74 | { cache: 'force-cache' },
75 | );
76 | const fontRegular: ArrayBuffer = await fontFileRegular.arrayBuffer();
77 |
78 | // Bold Font
79 | const fontFileBold = await fetch(
80 | 'https://gw.alipayobjects.com/os/kitchen/ywwdIaXDZa/HarmonyOS_Sans_Bold.ttf',
81 | { cache: 'force-cache' },
82 | );
83 | const fontBold: ArrayBuffer = await fontFileBold.arrayBuffer();
84 |
85 | return { fontBold, fontRegular };
86 | };
87 |
88 | export const getNumber = (value: string | null, defaultValue?: number) => {
89 | if (!value || value === null) return defaultValue;
90 | const parsed = Number.parseInt(value, 10);
91 | if (Number.isNaN(parsed)) return defaultValue;
92 | return parsed;
93 | };
94 |
--------------------------------------------------------------------------------
/src/components/Highlight/index.tsx:
--------------------------------------------------------------------------------
1 | import { Highlighter, HighlighterProps } from '@lobehub/ui';
2 | import { memo } from 'react';
3 | import useSWR from 'swr';
4 |
5 | import { remarkFormat } from '@/utils/remarkFormat';
6 |
7 | const Highlight = memo(({ children, ...props }) => {
8 | const { data, isLoading } = useSWR(children, () => remarkFormat(children));
9 |
10 | return (
11 |
12 | {isLoading ? '' : String(data)}
13 |
14 | );
15 | });
16 |
17 | export default Highlight;
18 |
--------------------------------------------------------------------------------
/src/components/Label/index.tsx:
--------------------------------------------------------------------------------
1 | import { LucideIcon } from 'lucide-react';
2 | import { memo } from 'react';
3 | import { Flexbox } from 'react-layout-kit';
4 |
5 | const Label = memo<{ icon?: LucideIcon; title: string }>(({ title, icon }) => {
6 | if (!icon) return title;
7 | const Render = icon;
8 | return (
9 |
10 |
11 | {title}
12 |
13 | );
14 | });
15 |
16 | export default Label;
17 |
--------------------------------------------------------------------------------
/src/components/Markdown/HighlightStyle.tsx:
--------------------------------------------------------------------------------
1 | import { FontLoader, genCdnUrl } from '@lobehub/ui';
2 | import { useThemeMode } from 'antd-style';
3 | import { memo } from 'react';
4 |
5 | const HighlightStyle = memo(() => {
6 | const { isDarkMode } = useThemeMode();
7 |
8 | return (
9 |
16 | );
17 | });
18 |
19 | export default HighlightStyle;
20 |
--------------------------------------------------------------------------------
/src/components/Markdown/index.tsx:
--------------------------------------------------------------------------------
1 | import { Typography } from 'antd';
2 | import { CSSProperties, memo } from 'react';
3 | import ReactMarkdown from 'react-markdown';
4 | import rehypeHighlight from 'rehype-highlight';
5 | import rehypeRaw from 'rehype-raw';
6 | import remarkGfm from 'remark-gfm';
7 | import remarkToc from 'remark-toc';
8 |
9 | import HighlightStyle from './HighlightStyle';
10 | import { useStyles } from './style';
11 |
12 | export interface MarkdownProps {
13 | children: string;
14 | className?: string;
15 | style?: CSSProperties;
16 | }
17 |
18 | const Markdown = memo(({ children, className, style, ...props }) => {
19 | const { styles, cx } = useStyles();
20 | const rehypePlugins = [rehypeRaw, [rehypeHighlight, { ignoreMissing: true }]] as any;
21 | const remarkPlugins = [remarkGfm, remarkToc];
22 |
23 | return (
24 |
25 |
26 |
27 |
33 | {children}
34 |
35 |
36 |
37 | );
38 | });
39 |
40 | export default Markdown;
41 |
--------------------------------------------------------------------------------
/src/components/Markdown/style.ts:
--------------------------------------------------------------------------------
1 | import { createStyles } from 'antd-style';
2 |
3 | export const useStyles = createStyles(({ css, isDarkMode, cx }) => {
4 | const scheme = isDarkMode
5 | ? css`
6 | --color-prettylights-syntax-comment: #8b949e;
7 | --color-prettylights-syntax-constant: #79c0ff;
8 | --color-prettylights-syntax-entity: #d2a8ff;
9 | --color-prettylights-syntax-storage-modifier-import: #c9d1d9;
10 | --color-prettylights-syntax-entity-tag: #7ee787;
11 | --color-prettylights-syntax-keyword: #ff7b72;
12 | --color-prettylights-syntax-string: #a5d6ff;
13 | --color-prettylights-syntax-variable: #ffa657;
14 | --color-prettylights-syntax-brackethighlighter-unmatched: #f85149;
15 | --color-prettylights-syntax-invalid-illegal-text: #f0f6fc;
16 | --color-prettylights-syntax-invalid-illegal-bg: #8e1519;
17 | --color-prettylights-syntax-carriage-return-text: #f0f6fc;
18 | --color-prettylights-syntax-carriage-return-bg: #b62324;
19 | --color-prettylights-syntax-string-regexp: #7ee787;
20 | --color-prettylights-syntax-markup-list: #f2cc60;
21 | --color-prettylights-syntax-markup-heading: #1f6feb;
22 | --color-prettylights-syntax-markup-italic: #c9d1d9;
23 | --color-prettylights-syntax-markup-bold: #c9d1d9;
24 | --color-prettylights-syntax-markup-deleted-text: #ffdcd7;
25 | --color-prettylights-syntax-markup-deleted-bg: #67060c;
26 | --color-prettylights-syntax-markup-inserted-text: #aff5b4;
27 | --color-prettylights-syntax-markup-inserted-bg: #033a16;
28 | --color-prettylights-syntax-markup-changed-text: #ffdfb6;
29 | --color-prettylights-syntax-markup-changed-bg: #5a1e02;
30 | --color-prettylights-syntax-markup-ignored-text: #c9d1d9;
31 | --color-prettylights-syntax-markup-ignored-bg: #1158c7;
32 | --color-prettylights-syntax-meta-diff-range: #d2a8ff;
33 | --color-prettylights-syntax-brackethighlighter-angle: #8b949e;
34 | --color-prettylights-syntax-sublimelinter-gutter-mark: #484f58;
35 | --color-prettylights-syntax-constant-other-reference-link: #a5d6ff;
36 | --color-fg-default: #c9d1d9;
37 | --color-fg-muted: #8b949e;
38 | --color-fg-subtle: #6e7681;
39 | --color-canvas-default: #0d1117;
40 | --color-canvas-subtle: #161b22;
41 | --color-border-default: #30363d;
42 | --color-border-muted: #21262d;
43 | --color-neutral-muted: rgba(110, 118, 129, 40%);
44 | --color-accent-fg: #58a6ff;
45 | --color-accent-emphasis: #1f6feb;
46 | --color-attention-subtle: rgba(187, 128, 9, 15%);
47 | --color-danger-fg: #f85149;
48 |
49 | color-scheme: dark;
50 | `
51 | : css`
52 | --color-prettylights-syntax-comment: #6e7781;
53 | --color-prettylights-syntax-constant: #0550ae;
54 | --color-prettylights-syntax-entity: #8250df;
55 | --color-prettylights-syntax-storage-modifier-import: #24292f;
56 | --color-prettylights-syntax-entity-tag: #116329;
57 | --color-prettylights-syntax-keyword: #cf222e;
58 | --color-prettylights-syntax-string: #0a3069;
59 | --color-prettylights-syntax-variable: #953800;
60 | --color-prettylights-syntax-brackethighlighter-unmatched: #82071e;
61 | --color-prettylights-syntax-invalid-illegal-text: #f6f8fa;
62 | --color-prettylights-syntax-invalid-illegal-bg: #82071e;
63 | --color-prettylights-syntax-carriage-return-text: #f6f8fa;
64 | --color-prettylights-syntax-carriage-return-bg: #cf222e;
65 | --color-prettylights-syntax-string-regexp: #116329;
66 | --color-prettylights-syntax-markup-list: #3b2300;
67 | --color-prettylights-syntax-markup-heading: #0550ae;
68 | --color-prettylights-syntax-markup-italic: #24292f;
69 | --color-prettylights-syntax-markup-bold: #24292f;
70 | --color-prettylights-syntax-markup-deleted-text: #82071e;
71 | --color-prettylights-syntax-markup-deleted-bg: #ffebe9;
72 | --color-prettylights-syntax-markup-inserted-text: #116329;
73 | --color-prettylights-syntax-markup-inserted-bg: #dafbe1;
74 | --color-prettylights-syntax-markup-changed-text: #953800;
75 | --color-prettylights-syntax-markup-changed-bg: #ffd8b5;
76 | --color-prettylights-syntax-markup-ignored-text: #eaeef2;
77 | --color-prettylights-syntax-markup-ignored-bg: #0550ae;
78 | --color-prettylights-syntax-meta-diff-range: #8250df;
79 | --color-prettylights-syntax-brackethighlighter-angle: #57606a;
80 | --color-prettylights-syntax-sublimelinter-gutter-mark: #8c959f;
81 | --color-prettylights-syntax-constant-other-reference-link: #0a3069;
82 | --color-fg-default: #24292f;
83 | --color-fg-muted: #57606a;
84 | --color-fg-subtle: #6e7781;
85 | --color-canvas-default: #fff;
86 | --color-canvas-subtle: #f6f8fa;
87 | --color-border-default: #d0d7de;
88 | --color-border-muted: hsla(210deg, 18%, 87%, 100%);
89 | --color-neutral-muted: rgba(175, 184, 193, 20%);
90 | --color-accent-fg: #0969da;
91 | --color-accent-emphasis: #0969da;
92 | --color-attention-subtle: #fff8c5;
93 | --color-danger-fg: #cf222e;
94 |
95 | color-scheme: light;
96 | `;
97 | return {
98 | container: css`
99 | position: relative;
100 | overflow: hidden;
101 | `,
102 | markdown: cx(
103 | 'markdown-body',
104 | css`
105 | position: relative;
106 | height: 100%;
107 | padding: 24px;
108 |
109 | [align='center'] {
110 | text-align: center !important;
111 |
112 | p {
113 | text-align: center !important;
114 | }
115 | }
116 |
117 | [align='right'] {
118 | text-align: right !important;
119 |
120 | p {
121 | text-align: right !important;
122 | }
123 | }
124 | `,
125 | ),
126 | scheme,
127 | };
128 | });
129 |
--------------------------------------------------------------------------------
/src/components/MarkdownEditor/index.tsx:
--------------------------------------------------------------------------------
1 | import { CodeEditor } from '@lobehub/ui';
2 | import { Segmented } from 'antd';
3 | import { memo, useState } from 'react';
4 | import { Flexbox } from 'react-layout-kit';
5 | import useControlledState from 'use-merge-value';
6 |
7 | import Markdown from '@/components/Markdown';
8 |
9 | import { useStyles } from './style';
10 |
11 | enum Tabs {
12 | Editor = 'editor',
13 | Preview = 'preview',
14 | Split = 'split',
15 | }
16 |
17 | interface MarkdownEditorProps {
18 | onChange: (text: string) => void;
19 | value: string;
20 | }
21 |
22 | const MarkdownEditor = memo(({ onChange, value }) => {
23 | const [currentValue, setCurrentValue] = useControlledState(value, {
24 | defaultValue: value,
25 | onChange,
26 | value,
27 | });
28 | const [tab, setTab] = useState(Tabs.Editor);
29 | const { styles } = useStyles();
30 |
31 | const editor = (
32 |
39 | );
40 |
41 | const preview = {currentValue} ;
42 |
43 | return (
44 |
45 |
64 | {tab === 'split' ? (
65 |
66 | {preview}
67 | {editor}
68 |
69 | ) : (
70 |
71 | {tab === Tabs.Preview && preview}
72 | {tab === Tabs.Editor && editor}
73 |
74 | )}
75 |
76 | );
77 | });
78 |
79 | export default MarkdownEditor;
80 |
--------------------------------------------------------------------------------
/src/components/MarkdownEditor/style.ts:
--------------------------------------------------------------------------------
1 | import { createStyles } from 'antd-style';
2 |
3 | export const useStyles = createStyles(({ css, token, responsive }) => {
4 | return {
5 | container: css`
6 | position: relative;
7 |
8 | overflow: hidden;
9 |
10 | width: 100%;
11 |
12 | background: ${token.colorBgContainer};
13 | border-radius: ${token.borderRadiusLG}px;
14 | box-shadow: 0 0 0 1px ${token.colorBorder};
15 | `,
16 | editor: css`
17 | flex: 1;
18 | padding: 16px;
19 | `,
20 | markdown: css`
21 | flex: 1;
22 | border-right: 1px solid ${token.colorBorder};
23 |
24 | ${responsive.mobile} {
25 | border-left: none;
26 | }
27 | `,
28 | };
29 | });
30 |
--------------------------------------------------------------------------------
/src/components/MarkdownPreivew/index.tsx:
--------------------------------------------------------------------------------
1 | import { memo } from 'react';
2 | import { Flexbox } from 'react-layout-kit';
3 |
4 | import Highlight from '@/components/Highlight';
5 | import Markdown from '@/components/Markdown';
6 |
7 | import { useStyles } from './style';
8 |
9 | const MarkdownEditor = memo<{ children: string }>(({ children }) => {
10 | const { styles } = useStyles();
11 |
12 | return (
13 |
14 | {children} ;
15 |
16 | {children}
17 |
18 |
19 | );
20 | });
21 |
22 | export default MarkdownEditor;
23 |
--------------------------------------------------------------------------------
/src/components/MarkdownPreivew/style.ts:
--------------------------------------------------------------------------------
1 | import { createStyles } from 'antd-style';
2 |
3 | export const useStyles = createStyles(({ css, token }) => {
4 | return {
5 | container: css`
6 | position: relative;
7 | overflow: hidden;
8 | width: 100%;
9 | height: 100%;
10 | `,
11 | markdown: css`
12 | overflow: hidden;
13 | flex: 1;
14 | height: 100%;
15 | border-bottom: 1px solid ${token.colorBorder};
16 | `,
17 | preview: css`
18 | flex: 1;
19 | border-radius: 0;
20 | `,
21 | };
22 | });
23 |
--------------------------------------------------------------------------------
/src/components/MarkdownStorybook/index.tsx:
--------------------------------------------------------------------------------
1 | import { StoryBook, StoryBookProps } from '@lobehub/ui';
2 | import { memo } from 'react';
3 | import { Flexbox } from 'react-layout-kit';
4 |
5 | import Highlight from '@/components/Highlight';
6 | import Markdown from '@/components/Markdown';
7 |
8 | import { useStyles } from './style';
9 |
10 | const MarkdownEditor = memo<{ children: string; levaStore: StoryBookProps['levaStore'] }>(
11 | ({ children, levaStore }) => {
12 | const { styles } = useStyles();
13 |
14 | return (
15 |
16 |
17 | {children}
18 |
19 |
20 | {children}
21 |
22 |
23 | );
24 | },
25 | );
26 |
27 | export default MarkdownEditor;
28 |
--------------------------------------------------------------------------------
/src/components/MarkdownStorybook/style.ts:
--------------------------------------------------------------------------------
1 | import { createStyles } from 'antd-style';
2 |
3 | export const useStyles = createStyles(({ css, token }) => {
4 | return {
5 | container: css`
6 | position: relative;
7 |
8 | overflow: hidden;
9 |
10 | width: 100%;
11 | margin: 16px 0 32px;
12 |
13 | border-radius: ${token.borderRadiusLG}px;
14 | box-shadow: 0 0 0 1px ${token.colorBorder};
15 | `,
16 | markdown: css`
17 | overflow-x: hidden;
18 | overflow-y: auto;
19 |
20 | width: 100%;
21 | height: 100%;
22 |
23 | background: ${token.colorBgContainer};
24 | `,
25 | preview: css`
26 | border-top: 1px solid ${token.colorBorder};
27 | border-radius: 0;
28 | `,
29 | };
30 | });
31 |
--------------------------------------------------------------------------------
/src/components/Title/index.tsx:
--------------------------------------------------------------------------------
1 | import { ActionIcon } from '@lobehub/ui';
2 | import { kebabCase } from 'lodash-es';
3 | import { Link } from 'lucide-react';
4 | import { memo } from 'react';
5 | import { Flexbox } from 'react-layout-kit';
6 |
7 | export interface TitleProps {
8 | children: string;
9 | link?: string;
10 | }
11 |
12 | const Title = memo(({ children, link }) => {
13 | const titleContent = {children} ;
14 | if (!link) return titleContent;
15 | return (
16 |
17 | {titleContent}
18 |
19 |
20 |
21 |
22 | );
23 | });
24 |
25 | export default Title;
26 |
--------------------------------------------------------------------------------
/src/const/dockerShieldControls.ts:
--------------------------------------------------------------------------------
1 | import urlJoin from 'url-join';
2 |
3 | import { colorOptions } from '@/const/shieldBaseControls';
4 | import { DOCKER_URL, SHIELD_DOCKER_URL } from '@/const/url';
5 | import { ShieldsBaseOptions } from '@/types/shields';
6 | import { genPickList } from '@/utils/genPickList';
7 |
8 | export interface DockerShieldControlItem extends Partial {
9 | genLink?: (packageName: string) => string | undefined;
10 | suffix?: string;
11 | url: string;
12 | }
13 |
14 | const genLink: DockerShieldControlItem['genLink'] = (packageName) =>
15 | urlJoin(DOCKER_URL, packageName);
16 |
17 | export const dockerShieldControls: {
18 | [key: string]: DockerShieldControlItem;
19 | } = {
20 | /* eslint-disable sort-keys-fix/sort-keys-fix */
21 | release: {
22 | logo: 'docker',
23 | logoColor: 'white',
24 | label: 'docker',
25 | color: colorOptions.geekblue,
26 | genLink,
27 | url: urlJoin(SHIELD_DOCKER_URL, 'v'),
28 | },
29 | size: {
30 | genLink,
31 | color: colorOptions.geekblue,
32 | url: urlJoin(SHIELD_DOCKER_URL, 'image-size'),
33 | },
34 | pulls: {
35 | genLink,
36 | color: '45cc11',
37 | url: urlJoin(SHIELD_DOCKER_URL, 'pulls'),
38 | },
39 | /* eslint-enable */
40 | };
41 |
42 | export const dockerShieldControlsPickList = genPickList(dockerShieldControls);
43 |
--------------------------------------------------------------------------------
/src/const/githubShieldControls.ts:
--------------------------------------------------------------------------------
1 | import urlJoin from 'url-join';
2 |
3 | import { colorOptions } from '@/const/shieldBaseControls';
4 | import { GITHUB_URL, SHIELD_GITHUB_URL } from '@/const/url';
5 | import { GithubShieldBaseOptions, ShieldsBaseOptions } from '@/types/shields';
6 | import { genPickList } from '@/utils/genPickList';
7 |
8 | export interface GithubShieldControlItem extends Partial {
9 | genLink?: (options: GithubShieldBaseOptions) => string | undefined;
10 | suffix?: string;
11 | url: string;
12 | }
13 |
14 | export const githubSocialControls: {
15 | [key: string]: GithubShieldControlItem;
16 | } = {
17 | /* eslint-disable sort-keys-fix/sort-keys-fix */
18 | contributors: {
19 | color: colorOptions.lime,
20 | genLink: ({ owner, repo }) => urlJoin(GITHUB_URL, owner, repo, 'graphs/contributors'),
21 | url: urlJoin(SHIELD_GITHUB_URL, 'contributors'),
22 | },
23 | forks: {
24 | color: colorOptions.blue,
25 | genLink: ({ owner, repo }) => urlJoin(GITHUB_URL, owner, repo, 'network/members'),
26 | url: urlJoin(SHIELD_GITHUB_URL, 'forks'),
27 | },
28 | stars: {
29 | color: colorOptions.gold,
30 | genLink: ({ owner, repo }) => urlJoin(GITHUB_URL, owner, repo, 'network/stargazers'),
31 | url: urlJoin(SHIELD_GITHUB_URL, 'stars'),
32 | },
33 | issues: {
34 | color: colorOptions.magenta,
35 | genLink: ({ owner, repo }) => urlJoin(GITHUB_URL, owner, repo, 'issues'),
36 | url: urlJoin(SHIELD_GITHUB_URL, 'issues'),
37 | },
38 | license: {
39 | color: colorOptions.white,
40 | genLink: ({ owner, repo, branch }) =>
41 | branch && urlJoin(GITHUB_URL, owner, repo, 'blob', branch, 'LICENSE'),
42 | url: urlJoin(SHIELD_GITHUB_URL, 'license'),
43 | },
44 | /* eslint-enable */
45 | };
46 |
47 | export const githubSocialControlsPickList = genPickList(githubSocialControls);
48 |
49 | export const githubShieldControls: {
50 | [key: string]: GithubShieldControlItem;
51 | } = {
52 | /* eslint-disable sort-keys-fix/sort-keys-fix */
53 | release: {
54 | logo: 'github',
55 | color: colorOptions.geekblue,
56 | genLink: ({ owner, repo }) => urlJoin(GITHUB_URL, owner, repo, 'releases'),
57 | url: urlJoin(SHIELD_GITHUB_URL, 'v/release'),
58 | },
59 | releaseDate: {
60 | genLink: ({ owner, repo }) => urlJoin(GITHUB_URL, owner, repo, 'releases'),
61 | url: urlJoin(SHIELD_GITHUB_URL, 'release-date'),
62 | },
63 | downloads: {
64 | genLink: ({ owner, repo }) => urlJoin(GITHUB_URL, owner, repo, 'releases'),
65 | suffix: 'total',
66 | url: urlJoin(SHIELD_GITHUB_URL, 'downloads'),
67 | },
68 | /* eslint-enable */
69 | };
70 |
71 | export const githubShieldControlsPickList = genPickList(githubShieldControls);
72 |
--------------------------------------------------------------------------------
/src/const/npmShieldControls.ts:
--------------------------------------------------------------------------------
1 | import urlJoin from 'url-join';
2 |
3 | import { colorOptions } from '@/const/shieldBaseControls';
4 | import { NPM_URL, SHIELD_NPM_URL } from '@/const/url';
5 | import { ShieldsBaseOptions } from '@/types/shields';
6 | import { genPickList } from '@/utils/genPickList';
7 |
8 | export interface NpmShieldControlItem extends Partial {
9 | genLink?: (packageName: string) => string | undefined;
10 | suffix?: string;
11 | url: string;
12 | }
13 |
14 | const genLink: NpmShieldControlItem['genLink'] = (packageName) => urlJoin(NPM_URL, packageName);
15 |
16 | export const npmShieldControls: {
17 | [key: string]: NpmShieldControlItem;
18 | } = {
19 | /* eslint-disable sort-keys-fix/sort-keys-fix */
20 | release: {
21 | logo: 'npm',
22 | logoColor: 'white',
23 | color: colorOptions.geekblue,
24 | genLink,
25 | url: urlJoin(SHIELD_NPM_URL, 'v'),
26 | },
27 | downloads: {
28 | genLink,
29 | url: urlJoin(SHIELD_NPM_URL, 'dt'),
30 | },
31 | types: {
32 | genLink,
33 | url: urlJoin(SHIELD_NPM_URL, 'types'),
34 | },
35 | /* eslint-enable */
36 | };
37 |
38 | export const npmShieldControlsPickList = genPickList(npmShieldControls);
39 |
--------------------------------------------------------------------------------
/src/const/sample.ts:
--------------------------------------------------------------------------------
1 | import urlJoin from 'url-join';
2 |
3 | import { GITHUB_URL, SHIELD_BADGE_URL } from '@/const/url';
4 | import { genShield } from '@/utils/genShield';
5 |
6 | export const featuresSample = `- [x] 💨 **Quick Deployment**: Using the Vercel platform, you can deploy with just one click and complete the process within 1 minute, without any complex configuration;
7 | - [x] 💎 **Exquisite UI Design**: With a carefully designed interface, it offers an elegant appearance and smooth interaction. It supports light and dark themes and is mobile-friendly. PWA support provides a more native-like experience;
8 | - [x] 🗣️ **Smooth Conversation Experience**: Fluid responses ensure a smooth conversation experience. It fully supports Markdown rendering, including code highlighting, LaTex formulas, Mermaid flowcharts, and more;
9 | `;
10 |
11 | export const creditsSample = `### More Products
12 |
13 | - **[🤖 Lobe Chat](https://github.com/lobehub/lobe-chat)** - An open-source, extensible (Function Calling), high-performance chatbot framework. It supports one-click free deployment of your private ChatGPT/LLM web application.
14 | - **[🤯 Lobe theme](https://github.com/lobehub/sd-webui-lobe-theme)** - The modern theme for stable diffusion webui, exquisite interface design, highly customizable UI, and efficiency boosting features.
15 |
16 | ### Credits
17 |
18 | - **remark** - https://github.com/remarkjs/remark
19 | - **shikiji** - https://github.com/antfu/shikiji
20 | `;
21 |
22 | export const bunShields = genShield(
23 | 'bun',
24 | urlJoin(SHIELD_BADGE_URL, '-speedup%20with%20bun-black?logo=bun&style=for-the-badge'),
25 | 'https://bun.sh',
26 | );
27 |
28 | export const prWelcomeShields = (prWelcome: string, owner: string, repo: string) =>
29 | genShield(
30 | 'pr-welcome',
31 | urlJoin(
32 | SHIELD_BADGE_URL,
33 | `${encodeURIComponent(prWelcome)}-%E2%86%92-ffcb47?labelColor=black&style=for-the-badge`,
34 | ),
35 | urlJoin(GITHUB_URL, owner, repo, 'pulls'),
36 | );
37 |
--------------------------------------------------------------------------------
/src/const/shareShieldControls.ts:
--------------------------------------------------------------------------------
1 | import { identity, pickBy } from 'lodash-es';
2 | import qs from 'query-string';
3 |
4 | import { ShieldsBaseOptions } from '@/types/shields';
5 | import { genPickList } from '@/utils/genPickList';
6 |
7 | export interface shareShieldControlsItem extends Partial {
8 | genLink?: (props: {
9 | desc?: string;
10 | hashtags?: string;
11 | title?: string;
12 | url?: string;
13 | }) => string | undefined;
14 | }
15 |
16 | const formateHashtags = (hashtags: string): string[] =>
17 | hashtags.replaceAll(',', ',').replaceAll(' ', '').split(',');
18 |
19 | const stringifyHashtags = (hashtags: string, joinfix: string = ',', prefix?: string) => {
20 | let tags = formateHashtags(hashtags.trim());
21 | if (prefix) tags = tags.map((tag) => prefix + tag);
22 | return tags.filter(Boolean).join(joinfix);
23 | };
24 |
25 | export const shareShieldControls: {
26 | [key: string]: shareShieldControlsItem;
27 | } = {
28 | /* eslint-disable sort-keys-fix/sort-keys-fix */
29 | x: {
30 | logo: 'x',
31 | logoColor: 'white',
32 | genLink: ({ url, title, desc, hashtags }) => {
33 | const query = pickBy(
34 | {
35 | text: [title, desc].filter(Boolean).join(' - '),
36 | url,
37 | hashtags: hashtags && stringifyHashtags(hashtags),
38 | },
39 | identity,
40 | ) as any;
41 | return qs.stringifyUrl({
42 | url: 'https://x.com/intent/tweet',
43 | query,
44 | });
45 | },
46 | },
47 | telegram: {
48 | logo: 'telegram',
49 | logoColor: 'white',
50 | genLink: ({ url, title, desc, hashtags }) => {
51 | const query = pickBy(
52 | {
53 | text: [
54 | [title, desc].filter(Boolean).join(' - '),
55 | hashtags && stringifyHashtags(hashtags, ' ', '#'),
56 | ]
57 | .filter(Boolean)
58 | .join(' '),
59 | url,
60 | },
61 | identity,
62 | ) as any;
63 | return qs.stringifyUrl({
64 | url: 'https://t.me/share/url"',
65 | query,
66 | });
67 | },
68 | },
69 | whatsapp: {
70 | logo: 'whatsapp',
71 | logoColor: 'white',
72 | genLink: ({ url, title, desc, hashtags }) => {
73 | const query = pickBy(
74 | {
75 | text: [
76 | [title, desc].filter(Boolean).join(' - '),
77 | url,
78 | hashtags && stringifyHashtags(hashtags, ' ', '#'),
79 | ]
80 | .filter(Boolean)
81 | .join(' '),
82 | },
83 | identity,
84 | ) as any;
85 | return qs.stringifyUrl({
86 | url: 'https://api.whatsapp.com/send',
87 | query,
88 | });
89 | },
90 | },
91 | reddit: {
92 | logo: 'reddit',
93 | logoColor: 'white',
94 | genLink: ({ url, title, desc, hashtags }) => {
95 | const query = pickBy(
96 | {
97 | title: [
98 | [title, desc].filter(Boolean).join(' - '),
99 | hashtags && stringifyHashtags(hashtags, ' ', '#'),
100 | ]
101 | .filter(Boolean)
102 | .join(' '),
103 | url,
104 | },
105 | identity,
106 | ) as any;
107 | return qs.stringifyUrl({
108 | url: 'https://www.reddit.com/submit',
109 | query,
110 | });
111 | },
112 | },
113 | weibo: {
114 | logo: 'sinaweibo',
115 | logoColor: 'white',
116 | genLink: ({ url, title, desc, hashtags }) => {
117 | const query = pickBy(
118 | {
119 | sharesource: 'weibo',
120 | title: [
121 | [title, desc].filter(Boolean).join(' - '),
122 | hashtags && stringifyHashtags(hashtags, ' ', '#'),
123 | ]
124 | .filter(Boolean)
125 | .join(' '),
126 | url,
127 | },
128 | identity,
129 | ) as any;
130 | return qs.stringifyUrl({
131 | url: 'http://service.weibo.com/share/share.php',
132 | query,
133 | });
134 | },
135 | },
136 | qq: {
137 | logo: 'tencentqq',
138 | logoColor: 'white',
139 | genLink: ({ url, title, desc, hashtags }) => {
140 | const query = pickBy(
141 | {
142 | title,
143 | desc: [desc, hashtags && stringifyHashtags(hashtags, ' ', '#')].filter(Boolean).join(' '),
144 | summary: [title, desc].filter(Boolean).join(' - '),
145 | url,
146 | sharesource: 'qzone',
147 | },
148 | identity,
149 | ) as any;
150 | return qs.stringifyUrl({
151 | url: 'http://connect.qq.com/widget/shareqq/index.html',
152 | query,
153 | });
154 | },
155 | },
156 | /* eslint-enable */
157 | };
158 |
159 | export const shareShieldControlsPickList = genPickList(shareShieldControls);
160 |
--------------------------------------------------------------------------------
/src/const/shieldBaseControls.ts:
--------------------------------------------------------------------------------
1 | import icons from './icons';
2 |
3 | export const colorOptions = {
4 | black: 'black',
5 | blue: '8ae8ff',
6 | cyan: '95f3d9',
7 | geekblue: '369eff',
8 | gold: 'ffcb47',
9 | green: '55b467',
10 | lime: 'c4f042',
11 | magenta: 'ff80eb',
12 | orange: 'ff802b',
13 | purple: 'B0A3FF',
14 | red: 'f04f88',
15 | volcano: 'ec5e41',
16 | white: 'white',
17 | yellow: 'ffef5c',
18 | } as const;
19 |
20 | export const shieldBaseControls = {
21 | /* eslint-disable sort-keys-fix/sort-keys-fix */
22 | label: '',
23 | color: {
24 | options: colorOptions,
25 | value: '',
26 | },
27 | labelColor: {
28 | options: colorOptions,
29 | value: 'black',
30 | },
31 | logo: {
32 | options: icons,
33 | value: '',
34 | },
35 | logoColor: {
36 | options: colorOptions,
37 | value: '',
38 | },
39 | style: {
40 | options: ['flat', 'flat-square', 'plastic', 'for-the-badge', 'social'],
41 | value: 'flat-square',
42 | },
43 | /* eslint-enable */
44 | } as const;
45 |
--------------------------------------------------------------------------------
/src/const/socialShieldControls.ts:
--------------------------------------------------------------------------------
1 | import urlJoin from 'url-join';
2 |
3 | import { colorOptions } from '@/const/shieldBaseControls';
4 | import { ShieldsBaseOptions } from '@/types/shields';
5 | import { genPickList } from '@/utils/genPickList';
6 |
7 | export interface socialShieldControlsItem extends Partial {
8 | genLink?: (id: string) => string | undefined;
9 | }
10 |
11 | export const socialShieldControls: {
12 | [key: string]: socialShieldControlsItem;
13 | } = {
14 | /* eslint-disable sort-keys-fix/sort-keys-fix */
15 | qq: {
16 | logo: 'tencentqq',
17 | logoColor: 'white',
18 | color: colorOptions.blue,
19 | },
20 | wechat: {
21 | logo: 'wechat',
22 | logoColor: 'white',
23 | color: colorOptions.lime,
24 | },
25 | discord: {
26 | logo: 'discord',
27 | logoColor: 'white',
28 | color: colorOptions.purple,
29 | },
30 | weibo: {
31 | logo: 'sinaweibo',
32 | logoColor: 'white',
33 | color: 'FF9F9F',
34 | genLink: (id) => urlJoin('https://weibo.com/n', id),
35 | },
36 | steam: {
37 | logo: 'steam',
38 | logoColor: 'white',
39 | color: 'ABCAFF',
40 | genLink: (id) => urlJoin('https://steamcommunity.com/id', id),
41 | },
42 | x: {
43 | logo: 'x',
44 | logoColor: 'white',
45 | color: colorOptions.white,
46 | genLink: (id) => urlJoin('https://x.com', id),
47 | },
48 | /* eslint-enable */
49 | };
50 |
51 | export const socialShieldControlsPickList = genPickList(socialShieldControls);
52 |
--------------------------------------------------------------------------------
/src/const/url.ts:
--------------------------------------------------------------------------------
1 | import urlJoin from 'url-join';
2 |
3 | export const GITHUB_URL = 'https://github.com';
4 | export const GITHUB_STAR_HISTORY_URL = 'https://api.star-history.com/svg';
5 | export const GITHUBE_CONTRIB_URL = 'https://contrib.rocks/image';
6 | export const NPM_URL = 'https://www.npmjs.com/package';
7 | export const DOCKER_URL = 'https://hub.docker.com/r';
8 | export const SHIELD_URL = 'https://img.shields.io';
9 | export const SHIELD_BADGE_URL = urlJoin(SHIELD_URL, 'badge');
10 | export const SHIELD_GITHUB_URL = urlJoin(SHIELD_URL, 'github');
11 | export const SHIELD_NPM_URL = urlJoin(SHIELD_URL, 'npm');
12 | export const SHIELD_DOCKER_URL = urlJoin(SHIELD_URL, 'docker');
13 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export { genSponsor } from './services/genSponsor';
2 | export { default as Sponsor, type SponsorProps } from './Sponsor';
3 |
--------------------------------------------------------------------------------
/src/services/genCustomShield.ts:
--------------------------------------------------------------------------------
1 | import { identity, pickBy } from 'lodash-es';
2 | import qs from 'query-string';
3 | import urlJoin from 'url-join';
4 |
5 | import { SHIELD_BADGE_URL, SHIELD_URL } from '@/const/url';
6 | import { ShieldsBaseOptions } from '@/types/shields';
7 | import { formatCustomLabel } from '@/utils/formatCustomLabel';
8 | import { genShield } from '@/utils/genShield';
9 |
10 | interface CustomSingleShieldOptions extends ShieldsBaseOptions {
11 | label: string;
12 | }
13 |
14 | export const genCustomSingleShield = (options: CustomSingleShieldOptions) => {
15 | const { link, label, color, ...config } = options;
16 | const url = qs.stringifyUrl({
17 | query: pickBy(config, identity) as any,
18 | url: urlJoin(
19 | SHIELD_BADGE_URL,
20 | formatCustomLabel({
21 | color: color as string,
22 | label,
23 | }),
24 | ),
25 | });
26 |
27 | return genShield(label, url, link);
28 | };
29 |
30 | interface CustomDoubleShieldOptions extends ShieldsBaseOptions {
31 | content: string;
32 | label: string;
33 | }
34 |
35 | export const genCustomDoubleShield = (options: CustomDoubleShieldOptions) => {
36 | const { content, link, label, color, ...config } = options;
37 | const url = qs.stringifyUrl({
38 | query: pickBy(config, identity) as any,
39 | url: urlJoin(
40 | SHIELD_BADGE_URL,
41 | formatCustomLabel({
42 | color: color as string,
43 | content,
44 | label,
45 | }),
46 | ),
47 | });
48 |
49 | return genShield(content || label, url, link);
50 | };
51 |
52 | interface WebsiteShieldOptions extends ShieldsBaseOptions {
53 | down_message: string;
54 | label: string;
55 | up_message: string;
56 | url: string;
57 | }
58 |
59 | export const genWebsiteShield = (options: WebsiteShieldOptions) => {
60 | const url = qs.stringifyUrl({
61 | query: pickBy(options, identity) as any,
62 | url: urlJoin(SHIELD_URL, 'website'),
63 | });
64 |
65 | return genShield(options.label, url, options.url);
66 | };
67 |
68 | interface DiscordShieldOptions extends ShieldsBaseOptions {
69 | label: string;
70 | serverId: string;
71 | }
72 |
73 | export const genDiscordShield = (options: DiscordShieldOptions) => {
74 | const { serverId, link, ...config } = options;
75 | const query = pickBy(config, identity) as any;
76 | const url = qs.stringifyUrl({
77 | query: { logo: 'discord', ...query },
78 | url: urlJoin(SHIELD_URL, 'discord', String(serverId)),
79 | });
80 |
81 | return genShield('discord', url, link);
82 | };
83 |
84 | interface BilibiliShieldOptions extends ShieldsBaseOptions {
85 | label: string;
86 | uid: string;
87 | }
88 |
89 | export const genBilibiliShield = (options: BilibiliShieldOptions) => {
90 | const { uid, labelColor, logoColor, ...config } = options;
91 | const query = pickBy(config, identity) as any;
92 | const url = qs.stringifyUrl({
93 | query: {
94 | label_color: labelColor,
95 | logo_color: logoColor,
96 | uid,
97 | ...query,
98 | },
99 | url: 'https://bilistats.lonelyion.com/followers',
100 | });
101 | const link = urlJoin('https://space.bilibili.com', uid);
102 |
103 | return genShield('bilibili', url, link);
104 | };
105 |
--------------------------------------------------------------------------------
/src/services/genDockerShield.ts:
--------------------------------------------------------------------------------
1 | import { identity, pickBy } from 'lodash-es';
2 | import qs from 'query-string';
3 | import urlJoin from 'url-join';
4 |
5 | import { DockerShieldControlItem, dockerShieldControls } from '@/const/dockerShieldControls';
6 | import { DockerShieldBaseOptions } from '@/types/shields';
7 | import { genShield } from '@/utils/genShield';
8 |
9 | interface DockerShieldOptions extends DockerShieldBaseOptions, DockerShieldControlItem {
10 | name: string;
11 | }
12 |
13 | export const genDockerShield = (options: DockerShieldOptions) => {
14 | const { packageName, url, suffix, name, genLink, ...config } = options;
15 |
16 | const formatUrl = [url, packageName, suffix].filter(Boolean) as string[];
17 | const defShield = qs.stringifyUrl({
18 | query: pickBy(config, identity) as any,
19 | url: urlJoin(...formatUrl),
20 | });
21 | const defLink = genLink?.(packageName);
22 |
23 | return genShield(`docker-${name}`, defShield, defLink);
24 | };
25 |
26 | export const genDockerShields = (
27 | options: Partial | any,
28 | pickOptions: { [key: string]: boolean },
29 | ) => {
30 | const defShields: string[] = [];
31 | const defLinks: string[] = [];
32 |
33 | for (const [name, config] of Object.entries(dockerShieldControls)) {
34 | if (!pickOptions[name]) continue;
35 | const data = genDockerShield({ name, ...options, ...config });
36 | defShields.push(data[0]);
37 | defLinks.push(data[1]);
38 | }
39 | return [defShields.join('\n'), defLinks.join('\n')];
40 | };
41 |
--------------------------------------------------------------------------------
/src/services/genGithubShield.ts:
--------------------------------------------------------------------------------
1 | import { identity, pickBy } from 'lodash-es';
2 | import qs from 'query-string';
3 | import urlJoin from 'url-join';
4 |
5 | import {
6 | GithubShieldControlItem,
7 | githubShieldControls,
8 | githubSocialControls,
9 | } from '@/const/githubShieldControls';
10 | import {
11 | GITHUBE_CONTRIB_URL,
12 | GITHUB_STAR_HISTORY_URL,
13 | GITHUB_URL,
14 | SHIELD_GITHUB_URL,
15 | } from '@/const/url';
16 | import { GithubShieldBaseOptions } from '@/types/shields';
17 | import { genShield } from '@/utils/genShield';
18 | import { genThemeModeImg } from '@/utils/genThemeModeImg';
19 |
20 | export interface GithubShieldOptions extends GithubShieldBaseOptions, GithubShieldControlItem {
21 | name: string;
22 | }
23 |
24 | export const genGithubShield = (options: GithubShieldOptions) => {
25 | const { owner, repo, branch, name, url, suffix, genLink, ...config } = options;
26 |
27 | const formatUrl = [url, owner, repo, suffix].filter(Boolean) as string[];
28 | const defShield = qs.stringifyUrl({
29 | query: pickBy(config, identity) as any,
30 | url: urlJoin(...formatUrl),
31 | });
32 | const defLink = genLink?.({ branch, owner, repo });
33 |
34 | return genShield(`github-${name}`, defShield, defLink);
35 | };
36 |
37 | export const genGithubSocialShields = (
38 | options: Partial | any,
39 | pickOptions: { [key: string]: boolean },
40 | ) => {
41 | const defShields: string[] = [];
42 | const defLinks: string[] = [];
43 |
44 | for (const [name, config] of Object.entries(githubSocialControls)) {
45 | if (!pickOptions[name]) continue;
46 | const data = genGithubShield({ name, ...options, ...config });
47 | defShields.push(data[0]);
48 | defLinks.push(data[1]);
49 | }
50 | return [defShields.join('\n'), defLinks.join('\n')];
51 | };
52 |
53 | export const genGithubReleaseShields = (
54 | options: Partial | any,
55 | pickOptions: { [key: string]: boolean },
56 | ) => {
57 | const defShields: string[] = [];
58 | const defLinks: string[] = [];
59 |
60 | for (const [name, config] of Object.entries(githubShieldControls)) {
61 | if (!pickOptions[name]) continue;
62 | const data = genGithubShield({ name, ...options, ...config });
63 | defShields.push(data[0]);
64 | defLinks.push(data[1]);
65 | }
66 | return [defShields.join('\n'), defLinks.join('\n')];
67 | };
68 |
69 | interface GithubActionShieldOptions extends GithubShieldBaseOptions {
70 | workflow: string;
71 | }
72 |
73 | export const genGithubActionShield = (options: GithubActionShieldOptions) => {
74 | const { owner, repo, workflow, ...config } = options;
75 |
76 | const query = pickBy(config, identity) as any;
77 |
78 | const defShield = qs.stringifyUrl({
79 | query: { label: workflow, logo: 'githubactions', logoColor: 'white', ...query },
80 | url: urlJoin(SHIELD_GITHUB_URL, 'actions/workflow/status', owner, repo, workflow + '.yml'),
81 | });
82 | const defLink = urlJoin(GITHUB_URL, owner, repo, 'actions/workflows', workflow + '.yml');
83 |
84 | return genShield(`github-action-${workflow}`, defShield, defLink);
85 | };
86 |
87 | export const genGithubActionsShield = (options: GithubActionShieldOptions) => {
88 | const { workflow, ...config } = options;
89 | const workflows = workflow.replaceAll(' ', '').replaceAll(',', ',').split(',');
90 | const defShields: string[] = [];
91 | const defLinks: string[] = [];
92 |
93 | for (const w of workflows) {
94 | const data = genGithubActionShield({ workflow: w, ...config });
95 | defShields.push(data[0]);
96 | defLinks.push(data[1]);
97 | }
98 |
99 | return [defShields.join('\n'), defLinks.join('\n')];
100 | };
101 |
102 | export const genGithubStarHistoryShield = (options: { owner: string; repo: string }) => {
103 | const light = qs.stringifyUrl({
104 | query: {
105 | repos: `${options.owner}/${options.repo}`,
106 | type: 'Date',
107 | },
108 | url: GITHUB_STAR_HISTORY_URL,
109 | });
110 | const dark = qs.stringifyUrl({
111 | query: {
112 | repos: `${options.owner}/${options.repo}`,
113 | theme: 'dark',
114 | type: 'Date',
115 | },
116 | url: GITHUB_STAR_HISTORY_URL,
117 | });
118 | return genThemeModeImg({ dark, light });
119 | };
120 | export const GenGithubContributorsShield = (options: { owner: string; repo: string }) => {
121 | const defShield = qs.stringifyUrl({
122 | query: {
123 | repo: `${options.owner}/${options.repo}`,
124 | },
125 | url: GITHUBE_CONTRIB_URL,
126 | });
127 | const defLink = urlJoin(GITHUB_URL, options.owner, options.repo, 'graphs/contributors');
128 | return genShield('github-contrib', defShield, defLink);
129 | };
130 |
131 | export const GenGithubCodespaceShield = (options: { owner: string; repo: string }) => {
132 | const defShield = 'https://github.com/codespaces/badge.svg';
133 | const defLink = urlJoin('https://codespaces.new', options.owner, options.repo);
134 | return genShield('github-codespace', defShield, defLink);
135 | };
136 |
--------------------------------------------------------------------------------
/src/services/genMarkdownContributing.ts:
--------------------------------------------------------------------------------
1 | import urlJoin from 'url-join';
2 |
3 | import { prWelcomeShields } from '@/const/sample';
4 | import { GITHUB_URL } from '@/const/url';
5 | import { GenGithubContributorsShield } from '@/services/genGithubShield';
6 | import { addBackToTop } from '@/utils/addBackTopTop';
7 |
8 | interface MarkdownContributingOptions {
9 | backToTop?: boolean;
10 | owner: string;
11 | prWelcome: string;
12 | repo: string;
13 | }
14 |
15 | export const genMarkdownContributing = (options: MarkdownContributingOptions) => {
16 | const { owner, repo, prWelcome, backToTop } = options;
17 |
18 | const [prShield, prLink] = prWelcomeShields(prWelcome, owner, repo);
19 | const [contributorsShield, contributorsLink] = GenGithubContributorsShield({ owner, repo });
20 |
21 | const md = `
22 | ## 🤝 Contributing
23 |
24 | Contributions of all types are more than welcome, if you are interested in contributing code, feel free to check out our GitHub [Issues][github-issues-link] to get stuck in to show us what you’re made of.
25 |
26 | ${prShield}
27 |
28 | ${contributorsShield}
29 | `;
30 | const ref = [
31 | `[github-issues-link]: ${urlJoin(GITHUB_URL, owner, repo, 'issues')}`,
32 | prLink,
33 | contributorsLink,
34 | ]
35 | .filter(Boolean)
36 | .join('\n');
37 |
38 | const content = [md, ref];
39 |
40 | return backToTop ? addBackToTop(content) : content;
41 | };
42 |
--------------------------------------------------------------------------------
/src/services/genMarkdownCredits.ts:
--------------------------------------------------------------------------------
1 | import { addBackToTop } from '@/utils/addBackTopTop';
2 |
3 | interface MarkdownCreditsOptions {
4 | backToTop?: boolean;
5 | title: string;
6 | }
7 |
8 | export const genMarkdownCredits = (options: MarkdownCreditsOptions, value: string) => {
9 | const { title, backToTop } = options;
10 |
11 | const content = [[`## 🔗 ${title}`, value].join('\n\n'), ''];
12 |
13 | return backToTop ? addBackToTop(content) : content;
14 | };
15 |
--------------------------------------------------------------------------------
/src/services/genMarkdownDevelopment.ts:
--------------------------------------------------------------------------------
1 | import { bunShields } from '@/const/sample';
2 | import { GenGithubCodespaceShield } from '@/services/genGithubShield';
3 | import { addBackToTop } from '@/utils/addBackTopTop';
4 |
5 | interface MarkdownDevelopmentOptions {
6 | backToTop?: boolean;
7 | bun: boolean;
8 | owner: string;
9 | repo: string;
10 | }
11 | export const genMarkdownDevelopment = (options: MarkdownDevelopmentOptions) => {
12 | const { owner, repo, bun, backToTop } = options;
13 |
14 | const [bunShield, bunLink] = bunShields;
15 |
16 | const [codespaceShield, codespaceLink] = GenGithubCodespaceShield({ owner, repo });
17 |
18 | const md = `
19 | ## ⌨️ Local Development
20 |
21 | You can use Github Codespaces for online development:
22 |
23 | ${codespaceShield}
24 |
25 | Or clone it for local development:
26 |
27 | ${bun ? bunShield : ''}
28 |
29 | \`\`\`bash
30 | $ git clone https://github.com/${owner}/${repo}.git
31 | $ cd ${repo}
32 | $ ${bun ? 'bun' : 'pnpm'} install
33 | $ ${bun ? 'bun' : 'pnpm'} dev
34 | \`\`\`
35 | `;
36 |
37 | const ref = [codespaceLink, bun && bunLink].filter(Boolean).join('\n');
38 |
39 | const content = [md, ref];
40 |
41 | return backToTop ? addBackToTop(content) : content;
42 | };
43 |
--------------------------------------------------------------------------------
/src/services/genMarkdownFeatures.ts:
--------------------------------------------------------------------------------
1 | import { addBackToTop } from '@/utils/addBackTopTop';
2 |
3 | interface MarkdownFeaturesOptions {
4 | backToTop?: boolean;
5 | title: string;
6 | }
7 |
8 | export const genMarkdownFeatures = (options: MarkdownFeaturesOptions, value: string) => {
9 | const { title, backToTop } = options;
10 |
11 | const content = [[`## ✨ ${title}`, value].join('\n\n'), ''];
12 |
13 | return backToTop ? addBackToTop(content) : content;
14 | };
15 |
--------------------------------------------------------------------------------
/src/services/genMarkdownHero.ts:
--------------------------------------------------------------------------------
1 | import { githubSocialControlsPickList } from '@/const/githubShieldControls';
2 | import {
3 | genGithubActionsShield,
4 | genGithubReleaseShields,
5 | genGithubSocialShields,
6 | } from '@/services/genGithubShield';
7 | import { genNpmShields } from '@/services/genNpmShield';
8 |
9 | interface MarkdownHeroOptions {
10 | backToTop?: boolean;
11 | banner: string;
12 | branch: string;
13 | description: string;
14 | logo: string;
15 | logo2?: string;
16 | owner: string;
17 | packageName: string;
18 | repo: string;
19 | title: string;
20 | workflow: string;
21 | }
22 | export const genMarkdownHero = (options: MarkdownHeroOptions) => {
23 | const {
24 | logo,
25 | logo2,
26 | title,
27 | description,
28 | banner,
29 | workflow,
30 | packageName,
31 | backToTop,
32 | owner,
33 | repo,
34 | branch,
35 | ...config
36 | } = options;
37 |
38 | const [releaseShields, releaseLinks] = genGithubReleaseShields(
39 | { owner, repo, ...config },
40 | {
41 | release: !packageName,
42 | releaseDate: true,
43 | },
44 | );
45 |
46 | const [npmShield, npmLinks] = packageName
47 | ? genNpmShields(
48 | { packageName, ...config },
49 | {
50 | release: true,
51 | },
52 | )
53 | : ['', ''];
54 |
55 | const [workflowShields, workflowLinks] = workflow
56 | ? genGithubActionsShield({ owner, repo, workflow, ...config })
57 | : ['', ''];
58 |
59 | const [socialShields, socialLinks] = genGithubSocialShields(
60 | { branch, owner, repo, ...config },
61 | githubSocialControlsPickList,
62 | );
63 |
64 | const logoGroup = logo2
65 | ? [
66 | ` `,
67 | ' ',
68 | ` `,
69 | ].join('\n')
70 | : ` `;
71 | const firstShieldRow = [npmShield, releaseShields, workflowShields].filter(Boolean).join('\n');
72 | const secondShieldRow = [socialShields].filter(Boolean).join('\n');
73 | const shieldRows = [`${firstShieldRow} `, secondShieldRow].filter(Boolean).join('\n');
74 |
75 | const md = [
76 | `${backToTop ? '
' : ''}`,
77 | logoGroup,
78 | `
${title} `,
79 | description,
80 | shieldRows,
81 | '[Changelog](./CHANGELOG.md) · [Report Bug][github-issues-link] · [Request Feature][github-issues-link]',
82 | ``,
83 | '
',
84 | ].join('\n\n');
85 |
86 | const ref = [npmLinks, releaseLinks, workflowLinks, socialLinks].join('\n');
87 |
88 | const content = [md, ref];
89 |
90 | return content;
91 | };
92 |
--------------------------------------------------------------------------------
/src/services/genMarkdownInstallation.ts:
--------------------------------------------------------------------------------
1 | import { bunShields } from '@/const/sample';
2 | import { addBackToTop } from '@/utils/addBackTopTop';
3 |
4 | interface MarkdownInstallationOptions {
5 | backToTop?: boolean;
6 | bun: boolean;
7 | esm: boolean;
8 | nextjs: boolean;
9 | packageName: string;
10 | }
11 |
12 | export const genMarkdownInstallation = (options: MarkdownInstallationOptions) => {
13 | const { esm, nextjs, packageName, bun, backToTop } = options;
14 |
15 | const [bunShield, bunLink] = bunShields;
16 |
17 | const esmBlock = `
18 | > [!IMPORTANT]\\
19 | > This package is [ESM only](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c).
20 | `;
21 |
22 | const installBlock = `
23 | To install \`${packageName}\`, run the following command:
24 |
25 | ${bun ? bunShield : ''}
26 |
27 | \`\`\`bash
28 | $ ${bun ? 'bun add' : 'pnpm install'} ${packageName}
29 | \`\`\`
30 | `;
31 |
32 | const nextjsBlock = `
33 | ### Compile with Next.js
34 |
35 | > [!NOTE]\\
36 | > By work correct with Next.js SSR, add \`transpilePackages: ['${packageName}']\` to \`next.config.js\`. For example:
37 |
38 | \`\`\`js
39 | const nextConfig = {
40 | transpilePackages: ['${packageName}'],
41 | };
42 | \`\`\`
43 | `;
44 |
45 | const md = ['## 📦 Installation', esm && esmBlock, installBlock, nextjs && nextjsBlock]
46 | .filter(Boolean)
47 | .join('\n\n');
48 | const ref = [bun && bunLink].filter(Boolean).join('\n');
49 |
50 | const content = [md, ref];
51 |
52 | return backToTop ? addBackToTop(content) : content;
53 | };
54 |
--------------------------------------------------------------------------------
/src/services/genMarkdownLicense.ts:
--------------------------------------------------------------------------------
1 | import urlJoin from 'url-join';
2 |
3 | import { GITHUB_URL } from '@/const/url';
4 |
5 | interface MarkdownLicenseOptions {
6 | license?: string;
7 | owner: string;
8 | repo: string;
9 | }
10 |
11 | export const genMarkdownLicense = (options: MarkdownLicenseOptions) => {
12 | const { owner, license } = options;
13 |
14 | const profileTitle = `[${owner === 'lobehub' ? 'LobeHub' : owner}][profile-link]`;
15 | const profileLink = `[profile-link]: ${urlJoin(GITHUB_URL, owner)}`;
16 |
17 | const md = `
18 | ---
19 |
20 | #### 📝 License
21 |
22 | Copyright © ${new Date().getFullYear()} ${profileTitle}.
23 | This project is [${license || 'MIT'}](./LICENSE) licensed.
24 | `;
25 | const ref = [profileLink].filter(Boolean).join('\n');
26 |
27 | const content = [md, ref];
28 |
29 | return content;
30 | };
31 |
--------------------------------------------------------------------------------
/src/services/genNpmShield.ts:
--------------------------------------------------------------------------------
1 | import { identity, pickBy } from 'lodash-es';
2 | import qs from 'query-string';
3 | import urlJoin from 'url-join';
4 |
5 | import { NpmShieldControlItem, npmShieldControls } from '@/const/npmShieldControls';
6 | import { NpmShieldBaseOptions } from '@/types/shields';
7 | import { genShield } from '@/utils/genShield';
8 |
9 | interface NpmShieldOptions extends NpmShieldBaseOptions, NpmShieldControlItem {
10 | name: string;
11 | }
12 |
13 | export const genNpmShield = (options: NpmShieldOptions) => {
14 | const { packageName, url, suffix, name, genLink, ...config } = options;
15 |
16 | const formatUrl = [url, packageName, suffix].filter(Boolean) as string[];
17 | const defShield = qs.stringifyUrl({
18 | query: pickBy(config, identity) as any,
19 | url: urlJoin(...formatUrl),
20 | });
21 | const defLink = genLink?.(packageName);
22 |
23 | return genShield(`npm-${name}`, defShield, defLink);
24 | };
25 |
26 | export const genNpmShields = (
27 | options: Partial | any,
28 | pickOptions: { [key: string]: boolean },
29 | ) => {
30 | const defShields: string[] = [];
31 | const defLinks: string[] = [];
32 |
33 | for (const [name, config] of Object.entries(npmShieldControls)) {
34 | if (!pickOptions[name]) continue;
35 | const data = genNpmShield({ name, ...options, ...config });
36 | defShields.push(data[0]);
37 | defLinks.push(data[1]);
38 | }
39 | return [defShields.join('\n'), defLinks.join('\n')];
40 | };
41 |
--------------------------------------------------------------------------------
/src/services/genShareShield.ts:
--------------------------------------------------------------------------------
1 | import { identity, pickBy } from 'lodash-es';
2 | import qs from 'query-string';
3 | import urlJoin from 'url-join';
4 |
5 | import { shareShieldControls, shareShieldControlsItem } from '@/const/shareShieldControls';
6 | import { SHIELD_BADGE_URL } from '@/const/url';
7 | import { ShieldsBaseOptions } from '@/types/shields';
8 | import { formatCustomLabel } from '@/utils/formatCustomLabel';
9 | import { genShield } from '@/utils/genShield';
10 |
11 | type ShareShieldOptions = ShieldsBaseOptions &
12 | shareShieldControlsItem & {
13 | desc?: string;
14 | hashtags?: string;
15 | name: string;
16 | title?: string;
17 | url?: string;
18 | };
19 |
20 | export const genShareShield = (options: ShareShieldOptions) => {
21 | const { name, genLink, title, desc, hashtags, url, color, label, ...config } = options;
22 |
23 | const defShield = qs.stringifyUrl({
24 | query: pickBy(config, identity) as any,
25 | url: urlJoin(
26 | SHIELD_BADGE_URL,
27 | formatCustomLabel({
28 | color: (color as string) || 'black',
29 | label: `share on ${label}`,
30 | }),
31 | ),
32 | });
33 | const defLink = genLink?.({ desc, hashtags, title, url });
34 |
35 | return genShield(`share-${name}`, defShield, defLink);
36 | };
37 |
38 | export const genShareShields = (
39 | options: Partial | any,
40 | pickOptions: { [key: string]: boolean },
41 | ) => {
42 | const defShields: string[] = [];
43 | const defLinks: string[] = [];
44 |
45 | for (const [name, config] of Object.entries(shareShieldControls)) {
46 | if (!pickOptions[name]) continue;
47 | const data = genShareShield({
48 | name,
49 | ...config,
50 | ...options,
51 | color: options.color || config.color,
52 | label: name,
53 | });
54 | defShields.push(data[0]);
55 | defLinks.push(data[1]);
56 | }
57 | return [defShields.join('\n'), defLinks.join('\n')];
58 | };
59 |
--------------------------------------------------------------------------------
/src/services/genSocialShield.ts:
--------------------------------------------------------------------------------
1 | import { identity, pickBy } from 'lodash-es';
2 | import qs from 'query-string';
3 | import urlJoin from 'url-join';
4 |
5 | import { socialShieldControls, socialShieldControlsItem } from '@/const/socialShieldControls';
6 | import { SHIELD_BADGE_URL } from '@/const/url';
7 | import { ShieldsBaseOptions } from '@/types/shields';
8 | import { formatCustomLabel } from '@/utils/formatCustomLabel';
9 | import { genShield } from '@/utils/genShield';
10 |
11 | interface SpcialIdOptions {
12 | discord?: string;
13 | qq?: string;
14 | steam?: string;
15 | wechat?: string;
16 | weibo?: string;
17 | x?: string;
18 | }
19 |
20 | type SocialShieldOptions = ShieldsBaseOptions &
21 | socialShieldControlsItem & {
22 | id: string;
23 | name: string;
24 | prefix: boolean;
25 | };
26 |
27 | export const genSocialShield = (options: SocialShieldOptions) => {
28 | const { name, genLink, id, color, prefix, ...config } = options;
29 |
30 | const defShield = qs.stringifyUrl({
31 | query: pickBy(config, identity) as any,
32 | url: urlJoin(
33 | SHIELD_BADGE_URL,
34 | formatCustomLabel({
35 | color: (color as string) || 'black',
36 | label: `${prefix ? '@' : ''}${id}`,
37 | }),
38 | ),
39 | });
40 | const defLink = genLink?.(id);
41 |
42 | return genShield(`social-${name}`, defShield, defLink);
43 | };
44 |
45 | export const genSocialShields = (
46 | options: Partial | any,
47 | idOptions: SpcialIdOptions,
48 | pickOptions: { [key: string]: boolean },
49 | ) => {
50 | const defShields: string[] = [];
51 | const defLinks: string[] = [];
52 |
53 | for (const [name, config] of Object.entries(socialShieldControls)) {
54 | if (!pickOptions[name]) continue;
55 | const data = genSocialShield({
56 | name,
57 | ...config,
58 | ...options,
59 | color: options.color || config.color,
60 | // @ts-ignore
61 | id: idOptions?.[name],
62 | });
63 | defShields.push(data[0]);
64 | defLinks.push(data[1]);
65 | }
66 | return [defShields.join('\n'), defLinks.join('\n')];
67 | };
68 |
--------------------------------------------------------------------------------
/src/services/genSponsor.tsx:
--------------------------------------------------------------------------------
1 | import { ImageResponse } from '@vercel/og';
2 |
3 | import Sponsor from '@/Sponsor';
4 | import { caleHeight, fetchFonts } from '@/Sponsor/utils';
5 | import { fetchSponsors } from '@/services/sponsorkit';
6 |
7 | export const MULTIPLE = 2;
8 |
9 | export const genSponsor = async ({
10 | avatarSize = 64 * MULTIPLE,
11 | width = 800 * MULTIPLE,
12 | themeMode = 'dark',
13 | }: {
14 | avatarSize?: number;
15 | themeMode: 'dark' | 'light';
16 | width?: number;
17 | }): Promise => {
18 | const data = await fetchSponsors({
19 | github: {
20 | login: process.env.SPONSORKIT_GITHUB_LOGIN || '',
21 | token: process.env.SPONSORKIT_GITHUB_TOKEN || '',
22 | type: process.env.SPONSORKIT_GITHUB_TYPE || 'organization',
23 | },
24 | includePastSponsors: true,
25 | opencollective: {
26 | key: process.env.SPONSORKIT_OPENCOLLECTIVE_KEY || '',
27 | slug: process.env.SPONSORKIT_OPENCOLLECTIVE_ID || 'lobehub',
28 | type: process.env.SPONSORKIT_OPENCOLLECTIVE_TYPE || 'collective',
29 | },
30 | });
31 |
32 | const { fontBold, fontRegular } = await fetchFonts();
33 | const height = caleHeight(data, { avatarSize, width } as any);
34 |
35 | return new ImageResponse(
36 | (
37 |
44 | ),
45 | {
46 | emoji: 'fluent',
47 | fonts: [
48 | {
49 | data: fontRegular,
50 | name: 'HarmonyOS Sans',
51 | style: 'normal',
52 | weight: 400,
53 | },
54 | {
55 | data: fontBold,
56 | name: 'HarmonyOS Sans',
57 | style: 'normal',
58 | weight: 600,
59 | },
60 | ],
61 | headers: {
62 | 'CDN-Cache-Control': 'public, s-maxage=120',
63 | 'Vercel-CDN-Cache-Control': 'public, s-maxage=3600',
64 | 'cache-control': 'public, max-age=120, s-maxage=120',
65 | 'content-type': 'image/png',
66 | },
67 | height,
68 | width,
69 | },
70 | );
71 | };
72 |
--------------------------------------------------------------------------------
/src/services/genVercelShield.ts:
--------------------------------------------------------------------------------
1 | import { identity, pickBy } from 'lodash-es';
2 | import qs from 'query-string';
3 | import urlJoin from 'url-join';
4 |
5 | import { GITHUB_URL, SHIELD_URL } from '@/const/url';
6 | import { genShield } from '@/utils/genShield';
7 |
8 | interface VercelDeployShieldOptions {
9 | env?: string;
10 | envDescription?: string;
11 | envLink?: string;
12 | owner: string;
13 | repo: string;
14 | }
15 |
16 | export const GenVercelDeployShield = (options: VercelDeployShieldOptions) => {
17 | const { owner, repo, env, envDescription, envLink } = options;
18 | const query = {
19 | env,
20 | envDescription,
21 | envLink,
22 | ['project-name']: repo,
23 | ['repository-link']: urlJoin(GITHUB_URL, owner, repo),
24 | ['repository-name']: repo,
25 | };
26 | const defShield = 'https://vercel.com/button';
27 | const defLink = qs.stringifyUrl({
28 | query: pickBy(query, identity),
29 | url: 'https://vercel.com/new/clone',
30 | });
31 | return genShield('vercel-deploy', defShield, defLink);
32 | };
33 |
34 | export const genVercelWebsiteShield = (options: { label: string; url: string }) => {
35 | const url = qs.stringifyUrl({
36 | query: pickBy(
37 | {
38 | down_message: 'offline',
39 | labelColor: 'black',
40 | logo: 'vercel',
41 | style: 'flat-square',
42 | up_message: 'online',
43 | ...options,
44 | },
45 | identity,
46 | ) as any,
47 | url: urlJoin(SHIELD_URL, 'website'),
48 | });
49 |
50 | return genShield('vercel-website', url, options.url);
51 | };
52 |
--------------------------------------------------------------------------------
/src/services/sponsorkit/github/index.ts:
--------------------------------------------------------------------------------
1 | import type { Provider, SponsorkitConfig, Sponsorship } from '../types';
2 | import { makeQuery } from './makeQuery';
3 |
4 | const API = 'https://api.github.com/graphql';
5 |
6 | export async function fetchGitHubSponsors(
7 | token: string,
8 | login: string,
9 | type: string,
10 | config: SponsorkitConfig,
11 | ): Promise {
12 | if (!token) throw new Error('GitHub token is required');
13 | if (!login) throw new Error('GitHub login is required');
14 | if (!['user', 'organization'].includes(type))
15 | throw new Error('GitHub type must be either `user` or `organization`');
16 |
17 | const sponsors: Sponsorship[] = [];
18 | let cursor;
19 |
20 | do {
21 | const query = makeQuery(login, type, !config.includePastSponsors, cursor);
22 | const res = await fetch(API, {
23 | body: JSON.stringify({ query }),
24 | headers: {
25 | 'Authorization': `bearer ${token}`,
26 | 'Content-Type': 'application/json',
27 | },
28 | method: 'POST',
29 | });
30 |
31 | const data = await res.json();
32 |
33 | if (!data) throw new Error(`Get no response on requesting ${API}`);
34 | else if (data.errors?.[0]?.type === 'INSUFFICIENT_SCOPES')
35 | throw new Error('Token is missing the `read:user` and/or `read:org` scopes');
36 | else if (data.errors?.length)
37 | throw new Error(`GitHub API error:\n${JSON.stringify(data.errors, null, 2)}`);
38 |
39 | sponsors.push(...(data.data[type].sponsorshipsAsMaintainer.nodes || []));
40 | if (data.data[type].sponsorshipsAsMaintainer.pageInfo.hasNextPage)
41 | cursor = data.data[type].sponsorshipsAsMaintainer.pageInfo.endCursor;
42 | else cursor = undefined;
43 | } while (cursor);
44 |
45 | const processed = sponsors.map(
46 | (raw: any): Sponsorship => ({
47 | createdAt: raw.createdAt,
48 | isOneTime: raw.tier.isOneTime,
49 | monthlyDollars: raw.tier.monthlyPriceInDollars,
50 | privacyLevel: raw.privacyLevel,
51 | sponsor: {
52 | ...raw.sponsorEntity,
53 | __typename: undefined,
54 | linkUrl: `https://github.com/${raw.sponsorEntity.login}`,
55 | type: raw.sponsorEntity.__typename,
56 | },
57 | tierName: raw.tier.name,
58 | }),
59 | );
60 |
61 | return processed;
62 | }
63 |
64 | export const GitHubProvider: Provider = {
65 | fetchSponsors(config) {
66 | return fetchGitHubSponsors(
67 | config.github?.token || config.token!,
68 | config.github?.login || config.login!,
69 | config.github?.type || 'user',
70 | config,
71 | );
72 | },
73 | name: 'github',
74 | };
75 |
--------------------------------------------------------------------------------
/src/services/sponsorkit/github/makeQuery.ts:
--------------------------------------------------------------------------------
1 | const graphql = String.raw;
2 |
3 | export function makeQuery(login: string, type: string, activeOnly = true, cursor?: string) {
4 | return graphql`{
5 | ${type}(login: "${login}") {
6 | sponsorshipsAsMaintainer(activeOnly: ${Boolean(activeOnly)}, first: 100${cursor ? ` after: "${cursor}"` : ''}) {
7 | totalCount
8 | pageInfo {
9 | endCursor
10 | hasNextPage
11 | }
12 | nodes {
13 | createdAt
14 | privacyLevel
15 | isActive
16 | tier {
17 | name
18 | isOneTime
19 | monthlyPriceInCents
20 | monthlyPriceInDollars
21 | }
22 | sponsorEntity {
23 | __typename
24 | ...on Organization {
25 | login
26 | name
27 | avatarUrl
28 | websiteUrl
29 | }
30 | ...on User {
31 | login
32 | name
33 | avatarUrl
34 | websiteUrl
35 | }
36 | }
37 | }
38 | }
39 | }
40 | }`;
41 | }
42 |
--------------------------------------------------------------------------------
/src/services/sponsorkit/index.ts:
--------------------------------------------------------------------------------
1 | import { GitHubProvider } from './github';
2 | import { OpenCollectiveProvider } from './opencollective';
3 | import type { Provider, ProviderName, SponsorkitConfig } from './types';
4 |
5 | export * from './github';
6 |
7 | export const ProvidersMap = {
8 | github: GitHubProvider,
9 | opencollective: OpenCollectiveProvider,
10 | };
11 |
12 | export function guessProviders(config: SponsorkitConfig) {
13 | const items: ProviderName[] = [];
14 | if (config.github && config.github.login) items.push('github');
15 |
16 | if (config.patreon && config.patreon.token) items.push('patreon');
17 |
18 | if (
19 | config.opencollective &&
20 | (config.opencollective.id || config.opencollective.slug || config.opencollective.githubHandle)
21 | )
22 | items.push('opencollective');
23 |
24 | if (config.afdian && config.afdian.userId && config.afdian.token) items.push('afdian');
25 |
26 | // fallback
27 | if (!items.length) items.push('github');
28 |
29 | return items;
30 | }
31 |
32 | export function resolveProviders(names: (ProviderName | Provider)[]) {
33 | return [...new Set(names)].map((i) => {
34 | if (typeof i === 'string') {
35 | // @ts-ignore
36 | const provider = ProvidersMap[i];
37 | if (!provider) throw new Error(`Unknown provider: ${i}`);
38 | return provider;
39 | }
40 | return i;
41 | });
42 | }
43 |
44 | export async function fetchSponsors(config: SponsorkitConfig) {
45 | const providers = resolveProviders(guessProviders(config));
46 | const sponsorships = await Promise.all(
47 | providers.map((provider) => provider.fetchSponsors(config)),
48 | );
49 |
50 | return sponsorships.flat(1);
51 | }
52 |
--------------------------------------------------------------------------------
/src/services/sponsorkit/opencollective/index.ts:
--------------------------------------------------------------------------------
1 | import type { Provider, Sponsorship } from '../types';
2 | import { makeSubscriptionsQuery, makeTransactionsQuery } from './makeQuery';
3 |
4 | interface SocialLink {
5 | type: string;
6 | url: string;
7 | }
8 |
9 | const API = 'https://api.opencollective.com/graphql/v2/';
10 |
11 | /**
12 | * Get the best URL from a list of social links.
13 | * The best URL is the first URL in a priority order,
14 | * with WEBSITE being the highest priority.
15 | * The rest of the order is somewhat arbitrary.
16 | *
17 | * @param socialLinks List of social links
18 | * @returns The best URL or `undefined` if no URL is found
19 | * @see makeQuery
20 | */
21 | function getBestUrl(socialLinks: SocialLink[]): string | undefined {
22 | const urls = socialLinks
23 | .filter(
24 | (i) =>
25 | i.type === 'WEBSITE' ||
26 | i.type === 'GITHUB' ||
27 | i.type === 'GITLAB' ||
28 | i.type === 'TWITTER' ||
29 | i.type === 'FACEBOOK' ||
30 | i.type === 'YOUTUBE' ||
31 | i.type === 'INSTAGRAM' ||
32 | i.type === 'LINKEDIN' ||
33 | i.type === 'DISCORD' ||
34 | i.type === 'TUMBLR',
35 | )
36 | .map((i) => i.url);
37 |
38 | return urls[0];
39 | }
40 |
41 | /**
42 | * Get the account type from the API values.
43 | *
44 | * @param type The type of the account from the API
45 | * @returns The account type
46 | */
47 | function getAccountType(type: string): 'User' | 'Organization' {
48 | switch (type) {
49 | case 'INDIVIDUAL': {
50 | return 'User';
51 | }
52 | case 'ORGANIZATION':
53 | case 'COLLECTIVE':
54 | case 'FUND':
55 | case 'PROJECT':
56 | case 'EVENT':
57 | case 'VENDOR':
58 | case 'BOT': {
59 | return 'Organization';
60 | }
61 | default: {
62 | throw new Error(`Unknown account type: ${type}`);
63 | }
64 | }
65 | }
66 |
67 | function createSponsorFromTransaction(
68 | transaction: any,
69 | excludeOrders: string[],
70 | ): [string, Sponsorship] | undefined {
71 | const slug = transaction.fromAccount.slug;
72 | if (slug === 'github-sponsors')
73 | // ignore github sponsors
74 | return undefined;
75 |
76 | if (excludeOrders.includes(transaction.order?.id)) return undefined;
77 |
78 | let monthlyDollars: number = transaction.amount.value;
79 | if (transaction.order?.status !== 'ACTIVE') {
80 | const firstDayOfCurrentMonth = new Date(
81 | new Date().getUTCFullYear(),
82 | new Date().getUTCMonth(),
83 | 1,
84 | );
85 | if (new Date(transaction.createdAt) < firstDayOfCurrentMonth) monthlyDollars = -1;
86 | } else if (transaction.order?.frequency === 'MONTHLY') {
87 | monthlyDollars = transaction.order?.amount.value;
88 | } else if (transaction.order?.frequency === 'YEARLY') {
89 | monthlyDollars = transaction.order?.amount.value / 12;
90 | }
91 |
92 | const sponsor: Sponsorship = {
93 | createdAt:
94 | transaction.order?.frequency === 'ONETIME'
95 | ? transaction.createdAt
96 | : transaction.order?.createdAt,
97 | isOneTime: transaction.order?.frequency === 'ONETIME',
98 | monthlyDollars,
99 | privacyLevel: transaction.fromAccount.isIncognito ? 'PRIVATE' : 'PUBLIC',
100 | raw: transaction,
101 | sponsor: {
102 | avatarUrl: transaction.fromAccount.imageUrl,
103 | linkUrl: `https://opencollective.com/${slug}`,
104 | login: slug,
105 | name: transaction.fromAccount.name,
106 | type: getAccountType(transaction.fromAccount.type),
107 | websiteUrl: getBestUrl(transaction.fromAccount.socialLinks),
108 | },
109 | tierName: transaction.tier?.name,
110 | };
111 |
112 | return [transaction.fromAccount.id, sponsor];
113 | }
114 |
115 | function createSponsorFromOrder(order: any): [string, Sponsorship] | undefined {
116 | const slug = order.fromAccount.slug;
117 | if (slug === 'github-sponsors')
118 | // ignore github sponsors
119 | return undefined;
120 |
121 | let monthlyDollars: number = order.amount.value;
122 | if (order.status !== 'ACTIVE') monthlyDollars = -1;
123 | else
124 | switch (order.frequency) {
125 | case 'MONTHLY': {
126 | monthlyDollars = order.amount.value;
127 | break;
128 | }
129 | case 'YEARLY': {
130 | monthlyDollars = order.amount.value / 12;
131 | break;
132 | }
133 | case 'ONETIME': {
134 | {
135 | monthlyDollars = order.amount.value;
136 | // No default
137 | }
138 | break;
139 | }
140 | }
141 |
142 | const sponsor: Sponsorship = {
143 | createdAt: order.frequency === 'ONETIME' ? order.createdAt : order.order?.createdAt,
144 | isOneTime: order.frequency === 'ONETIME',
145 | monthlyDollars,
146 | privacyLevel: order.fromAccount.isIncognito ? 'PRIVATE' : 'PUBLIC',
147 | raw: order,
148 | sponsor: {
149 | avatarUrl: order.fromAccount.imageUrl,
150 | linkUrl: `https://opencollective.com/${slug}`,
151 | login: slug,
152 | name: order.fromAccount.name,
153 | type: getAccountType(order.fromAccount.type),
154 | websiteUrl: getBestUrl(order.fromAccount.socialLinks),
155 | },
156 | tierName: order.tier?.name,
157 | };
158 |
159 | return [order.fromAccount.id, sponsor];
160 | }
161 |
162 | export async function fetchOpenCollectiveSponsors(
163 | key?: string,
164 | id?: string,
165 | slug?: string,
166 | githubHandle?: string,
167 | includePastSponsors?: boolean,
168 | ): Promise {
169 | if (!key) throw new Error('OpenCollective api key is required');
170 | if (!slug && !id && !githubHandle)
171 | throw new Error('OpenCollective collective id or slug or GitHub handle is required');
172 |
173 | const sponsors: any[] = [];
174 | const monthlyTransactions: any[] = [];
175 | let offset;
176 | offset = 0;
177 |
178 | do {
179 | const query = makeSubscriptionsQuery(id, slug, githubHandle, offset, !includePastSponsors);
180 | const res = await fetch(API, {
181 | body: JSON.stringify({ query }),
182 | headers: {
183 | 'Api-Key': `${key}`,
184 | 'Content-Type': 'application/json',
185 | },
186 | method: 'POST',
187 | });
188 | const data = await res.json();
189 | const nodes = data.data.account.orders.nodes;
190 | const totalCount = data.data.account.orders.totalCount;
191 |
192 | sponsors.push(...(nodes || []));
193 |
194 | if (nodes.length !== 0) {
195 | if (totalCount > offset + nodes.length) offset += nodes.length;
196 | else offset = undefined;
197 | } else {
198 | offset = undefined;
199 | }
200 | } while (offset);
201 |
202 | offset = 0;
203 | do {
204 | const now: Date = new Date();
205 | const dateFrom: Date | undefined = includePastSponsors
206 | ? undefined
207 | : new Date(now.getFullYear(), now.getMonth(), 1);
208 | const query = makeTransactionsQuery(id, slug, githubHandle, offset, dateFrom);
209 | const res = await fetch(API, {
210 | body: JSON.stringify({ query }),
211 | headers: {
212 | 'Api-Key': `${key}`,
213 | 'Content-Type': 'application/json',
214 | },
215 | method: 'POST',
216 | });
217 | const data = await res.json();
218 | const nodes = data.data.account.transactions.nodes;
219 | const totalCount = data.data.account.transactions.totalCount;
220 |
221 | monthlyTransactions.push(...(nodes || []));
222 | if (nodes.length !== 0) {
223 | if (totalCount > offset + nodes.length) offset += nodes.length;
224 | else offset = undefined;
225 | } else {
226 | offset = undefined;
227 | }
228 | } while (offset);
229 |
230 | const sponsorships: [string, Sponsorship][] = sponsors
231 | .map((element) => createSponsorFromOrder(element))
232 | .filter((sponsorship): sponsorship is [string, Sponsorship] => sponsorship !== null);
233 |
234 | const monthlySponsorships: [string, Sponsorship][] = monthlyTransactions
235 | .map((t) =>
236 | createSponsorFromTransaction(
237 | t,
238 | sponsorships.map((i) => i[1].raw.id),
239 | ),
240 | )
241 | .filter(
242 | (sponsorship): sponsorship is [string, Sponsorship] =>
243 | sponsorship !== null && sponsorship !== undefined,
244 | );
245 |
246 | const transactionsBySponsorId: Map = monthlySponsorships.reduce(
247 | (map, [id, sponsor]) => {
248 | const existingSponsor = map.get(id);
249 | if (existingSponsor) {
250 | const createdAt = new Date(sponsor.createdAt!);
251 | const existingSponsorCreatedAt = new Date(existingSponsor.createdAt!);
252 | if (createdAt >= existingSponsorCreatedAt) map.set(id, sponsor);
253 | else if (
254 | new Date(
255 | existingSponsorCreatedAt.getFullYear(),
256 | existingSponsorCreatedAt.getMonth(),
257 | 1,
258 | ) === new Date(createdAt.getFullYear(), createdAt.getMonth(), 1)
259 | )
260 | existingSponsor.monthlyDollars += sponsor.monthlyDollars;
261 | } else {
262 | map.set(id, sponsor);
263 | }
264 |
265 | return map;
266 | },
267 | new Map(),
268 | );
269 |
270 | const processed: Map = sponsorships.reduce((map, [id, sponsor]) => {
271 | const existingSponsor = map.get(id);
272 | if (existingSponsor) {
273 | const createdAt = new Date(sponsor.createdAt!);
274 | const existingSponsorCreatedAt = new Date(existingSponsor.createdAt!);
275 | if (createdAt >= existingSponsorCreatedAt) map.set(id, sponsor);
276 | } else {
277 | map.set(id, sponsor);
278 | }
279 | return map;
280 | }, new Map());
281 |
282 | const result: Sponsorship[] = [...processed.values(), ...transactionsBySponsorId.values()];
283 | return result;
284 | }
285 |
286 | export const OpenCollectiveProvider: Provider = {
287 | fetchSponsors(config) {
288 | return fetchOpenCollectiveSponsors(
289 | config.opencollective?.key,
290 | config.opencollective?.id,
291 | config.opencollective?.slug,
292 | config.opencollective?.githubHandle,
293 | config.includePastSponsors,
294 | );
295 | },
296 | name: 'opencollective',
297 | };
298 |
--------------------------------------------------------------------------------
/src/services/sponsorkit/opencollective/makeQuery.ts:
--------------------------------------------------------------------------------
1 | const graphql = String.raw;
2 |
3 | /**
4 | * Make a partial query for the OpenCollective API.
5 | * This is used to query for either a collective or an account.
6 | * If `id` is set, it will query for a collective.
7 | * If `slug` is set, it will query for an account.
8 | * If `githubHandle` is set, it will query for an account.
9 | * If none of the above are set, an error will be thrown.
10 | *
11 | * @param id Collective id
12 | * @param slug Collective slug
13 | * @param githubHandle GitHub handle
14 | * @returns The partial query
15 | * @see makeQuery
16 | * @see fetchOpenCollectiveSponsors
17 | */
18 | function makeAccountQueryPartial(id?: string, slug?: string, githubHandle?: string) {
19 | if (id) return `id: "${id}"`;
20 | else if (slug) return `slug: "${slug}"`;
21 | else if (githubHandle) return `githubHandle: "${githubHandle}"`;
22 | else throw new Error('OpenCollective collective id or slug or GitHub handle is required');
23 | }
24 |
25 | export function makeTransactionsQuery(
26 | id?: string,
27 | slug?: string,
28 | githubHandle?: string,
29 | offset?: number,
30 | dateFrom?: Date,
31 | dateTo?: Date,
32 | ) {
33 | const accountQueryPartial = makeAccountQueryPartial(id, slug, githubHandle);
34 | const dateFromParam = dateFrom ? `, dateFrom: "${dateFrom.toISOString()}"` : '';
35 | const dateToParam = dateTo ? `, dateTo: "${dateTo.toISOString()}"` : '';
36 | return graphql`{
37 | account(${accountQueryPartial}) {
38 | transactions(limit: 1000, offset:${offset}, type: CREDIT ${dateFromParam} ${dateToParam}) {
39 | offset
40 | limit
41 | totalCount
42 | nodes {
43 | type
44 | kind
45 | id
46 | order {
47 | id
48 | status
49 | frequency
50 | tier {
51 | name
52 | }
53 | amount {
54 | value
55 | }
56 | }
57 | createdAt
58 | amount {
59 | value
60 | }
61 | fromAccount {
62 | name
63 | id
64 | slug
65 | type
66 | socialLinks {
67 | url
68 | type
69 | }
70 | isIncognito
71 | imageUrl(height: 460, format: png)
72 | }
73 | }
74 | }
75 | }
76 | }`;
77 | }
78 |
79 | export function makeSubscriptionsQuery(
80 | id?: string,
81 | slug?: string,
82 | githubHandle?: string,
83 | offset?: number,
84 | activeOnly?: boolean,
85 | ) {
86 | const activeOrNot = activeOnly ? 'onlyActiveSubscriptions: true' : 'onlySubscriptions: true';
87 | return graphql`{
88 | account(${makeAccountQueryPartial(id, slug, githubHandle)}) {
89 | orders(limit: 1000, offset:${offset}, ${activeOrNot}, filter: INCOMING) {
90 | nodes {
91 | id
92 | createdAt
93 | frequency
94 | status
95 | tier {
96 | name
97 | }
98 | amount {
99 | value
100 | }
101 | totalDonations {
102 | value
103 | }
104 | createdAt
105 | fromAccount {
106 | name
107 | id
108 | slug
109 | type
110 | socialLinks {
111 | url
112 | type
113 | }
114 | isIncognito
115 | imageUrl(height: 460, format: png)
116 | }
117 | }
118 | }
119 | }
120 | }`;
121 | }
122 |
--------------------------------------------------------------------------------
/src/services/sponsorkit/types.ts:
--------------------------------------------------------------------------------
1 | export interface Provider {
2 | fetchSponsors: (config: SponsorkitConfig) => Promise;
3 | name: string;
4 | }
5 |
6 | export interface Sponsor {
7 | avatarUrl: string;
8 | avatarUrlHighRes?: string;
9 | avatarUrlLowRes?: string;
10 | avatarUrlMediumRes?: string;
11 | linkUrl?: string;
12 | login: string;
13 | name: string | null;
14 | type: 'User' | 'Organization';
15 | websiteUrl?: string;
16 | }
17 |
18 | export interface Sponsorship {
19 | createdAt?: string;
20 | expireAt?: string;
21 | isOneTime?: boolean;
22 | monthlyDollars: number;
23 | privacyLevel?: 'PUBLIC' | 'PRIVATE';
24 | provider?: ProviderName | string;
25 | /**
26 | * Raw data from provider
27 | */
28 | raw?: any;
29 | sponsor: Sponsor;
30 | tierName?: string;
31 | }
32 |
33 | export type OutputFormat = 'svg' | 'png' | 'json';
34 |
35 | export type ProviderName = 'github' | 'patreon' | 'opencollective' | 'afdian';
36 |
37 | export interface ProvidersConfig {
38 | afdian?: {
39 | /**
40 | * Exchange rate of USD to CNY
41 | *
42 | * @default 6.5
43 | */
44 | exechangeRate?: number;
45 | /**
46 | * Afdian Token that have access to your sponsorships.
47 | *
48 | * Will read from `SPONSORKIT_AFDIAN_TOKEN` environment variable if not set.
49 | *
50 | * @see https://afdian.net/dashboard/dev
51 | * @deprecated It's not recommended set this value directly, pass from env or use `.env` file.
52 | */
53 | token?: string;
54 | /**
55 | * The userId of your Afdian.
56 | *
57 | * Will read from `SPONSORKIT_AFDIAN_USER_ID` environment variable if not set.
58 | *
59 | * @see https://afdian.net/dashboard/dev
60 | */
61 | userId?: string;
62 | };
63 | github?: {
64 | /**
65 | * User id of your GitHub account.
66 | *
67 | * Will read from `SPONSORKIT_GITHUB_LOGIN` environment variable if not set.
68 | */
69 | login?: string;
70 | /**
71 | * GitHub Token that have access to your sponsorships.
72 | *
73 | * Will read from `SPONSORKIT_GITHUB_TOKEN` environment variable if not set.
74 | *
75 | * @deprecated It's not recommended set this value directly, pass from env or use `.env` file.
76 | */
77 | token?: string;
78 | /**
79 | * The account type for sponsorships.
80 | *
81 | * Possible values are `user`(default) and `organization`.
82 | * Will read from `SPONSORKIT_GITHUB_TYPE` environment variable if not set.
83 | */
84 | type?: string;
85 | };
86 | opencollective?: {
87 | /**
88 | * The GitHub handle of your account.
89 | *
90 | * Will read from `SPONSORKIT_OPENCOLLECTIVE_GH_HANDLE` environment variable if not set.
91 | */
92 | githubHandle?: string;
93 | /**
94 | * The id of your account.
95 | *
96 | * Will read from `SPONSORKIT_OPENCOLLECTIVE_ID` environment variable if not set.
97 | */
98 | id?: string;
99 | /**
100 | * Api key of your OpenCollective account.
101 | *
102 | * Will read from `SPONSORKIT_OPENCOLLECTIVE_KEY` environment variable if not set.
103 | *
104 | * @deprecated It's not recommended set this value directly, pass from env or use `.env` file.
105 | */
106 | key?: string;
107 | /**
108 | * The slug of your account.
109 | *
110 | * Will read from `SPONSORKIT_OPENCOLLECTIVE_SLUG` environment variable if not set.
111 | */
112 | slug?: string;
113 | /*
114 | * The type of your account. (`collective` or `individual`)
115 | *
116 | * Will read from `SPONSORKIT_OPENCOLLECTIVE_TYPE` environment variable if not set.
117 | */
118 | type?: string;
119 | };
120 | patreon?: {
121 | /**
122 | * Patreon Token that have access to your sponsorships.
123 | *
124 | * Will read from `SPONSORKIT_PATREON_TOKEN` environment variable if not set.
125 | *
126 | * @deprecated It's not recommended set this value directly, pass from env or use `.env` file.
127 | */
128 | token?: string;
129 | };
130 | }
131 |
132 | export interface SponsorkitConfig extends ProvidersConfig {
133 | /**
134 | * Filter of sponsorships to render in the final image.
135 | */
136 | filter?: (sponsor: Sponsorship, all: Sponsorship[]) => boolean | void;
137 |
138 | /**
139 | * By pass cache
140 | */
141 | force?: boolean;
142 |
143 | /**
144 | * Output formats
145 | *
146 | * @default ['json', 'svg', 'png']
147 | */
148 | formats?: OutputFormat[];
149 |
150 | /**
151 | * Whether to display the past sponsors
152 | * Currently only works with GitHub provider
153 | *
154 | * @default auto detect based on tiers
155 | */
156 | includePastSponsors?: boolean;
157 |
158 | /**
159 | * Whether to display the private sponsors
160 | *
161 | * @default false
162 | */
163 | includePrivate?: boolean;
164 |
165 | /**
166 | * @deprecated use `github.login` instead
167 | */
168 | login?: string;
169 |
170 | /**
171 | * Name of exported files
172 | *
173 | * @default 'sponsors'
174 | */
175 | name?: string | null;
176 |
177 | /**
178 | * Padding of image container
179 | */
180 | padding?: {
181 | bottom?: number;
182 | top?: number;
183 | };
184 |
185 | /**
186 | * @default auto detect based on the config provided
187 | */
188 | providers?: ProviderName[];
189 |
190 | /**
191 | * Inline CSS of generated SVG
192 | */
193 | svgInlineCSS?: string;
194 |
195 | /**
196 | * Tiers
197 | */
198 | tiers?: Tier[];
199 |
200 | /**
201 | * @deprecated use `github.token` instead
202 | */
203 | token?: string;
204 |
205 | /**
206 | * Width of the image.
207 | *
208 | * @default 700
209 | */
210 | width?: number;
211 | }
212 |
213 | export interface Tier {
214 | monthlyDollars?: number;
215 | padding?: {
216 | bottom?: number;
217 | top?: number;
218 | };
219 | title?: string;
220 | }
221 |
--------------------------------------------------------------------------------
/src/store/action.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lobehub/lobe-readme-wizard/6600570486025bd6733df0fdb0748c0d100ca992/src/store/action.ts
--------------------------------------------------------------------------------
/src/store/index.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lobehub/lobe-readme-wizard/6600570486025bd6733df0fdb0748c0d100ca992/src/store/index.ts
--------------------------------------------------------------------------------
/src/store/initialState.ts:
--------------------------------------------------------------------------------
1 | import { ThemeMode } from 'antd-style';
2 |
3 | export enum Tab {
4 | Readme = 'readme',
5 | Shields = 'shields',
6 | }
7 |
8 | export interface StoreState {
9 | activeTab: Tab;
10 | themeMode: ThemeMode;
11 | }
12 |
13 | export const initialState: StoreState = {
14 | activeTab: Tab.Readme,
15 | themeMode: 'auto',
16 | };
17 |
--------------------------------------------------------------------------------
/src/store/selectors.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lobehub/lobe-readme-wizard/6600570486025bd6733df0fdb0748c0d100ca992/src/store/selectors.ts
--------------------------------------------------------------------------------
/src/store/store.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lobehub/lobe-readme-wizard/6600570486025bd6733df0fdb0748c0d100ca992/src/store/store.ts
--------------------------------------------------------------------------------
/src/types/i18next.d.ts:
--------------------------------------------------------------------------------
1 | import 'i18next';
2 |
3 | import { NS } from '@/types/resources';
4 |
5 | declare module 'i18next' {
6 | interface CustomTypeOptions {
7 | defaultNS: 'common';
8 | resources: NS;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/types/shields.ts:
--------------------------------------------------------------------------------
1 | export interface ShieldsBaseOptions {
2 | color?: string;
3 | label?: string;
4 | labelColor?: string;
5 | link?: string;
6 | logo?: string;
7 | logoColor?: string;
8 | style: 'flat' | 'flat-square' | 'plastic' | 'for-the-badge' | 'social' | string;
9 | }
10 |
11 | export interface GithubShieldBaseOptions {
12 | branch?: string;
13 | owner: string;
14 | repo: string;
15 | }
16 |
17 | export interface NpmShieldBaseOptions {
18 | packageName: string;
19 | }
20 |
21 | export interface DockerShieldBaseOptions {
22 | packageName: string;
23 | }
24 |
--------------------------------------------------------------------------------
/src/utils/addBackTopTop.ts:
--------------------------------------------------------------------------------
1 | const backToTopShields = [
2 | ['', '[![][back-to-top]](#readme-top)', '
'].join('\n\n'),
3 | `[back-to-top]: https://img.shields.io/badge/-BACK_TO_TOP-black?style=flat-square`,
4 | ];
5 |
6 | export const addBackToTop = (content: string[]) => {
7 | const [md, ref] = content;
8 | const [shield, link] = backToTopShields;
9 | return [[md, shield].join('\n\n'), [ref, link].join('\n')];
10 | };
11 |
--------------------------------------------------------------------------------
/src/utils/formatCustomLabel.ts:
--------------------------------------------------------------------------------
1 | export const formatCustomLabel = ({
2 | content,
3 | label,
4 | color,
5 | }: {
6 | color?: string;
7 | content?: string;
8 | label: string;
9 | }) => {
10 | const data = encodeURIComponent([content, label, color || 'white'].filter(Boolean).join('-'));
11 | return content ? data : `-${data}`;
12 | };
13 |
--------------------------------------------------------------------------------
/src/utils/genPickList.ts:
--------------------------------------------------------------------------------
1 | export const genPickList = (obj: { [key: string]: any }): { [key: string]: boolean } => {
2 | const list: { [key: string]: boolean } = {};
3 |
4 | for (const key of Object.keys(obj)) {
5 | list[key] = true;
6 | }
7 |
8 | return list;
9 | };
10 |
--------------------------------------------------------------------------------
/src/utils/genShield.ts:
--------------------------------------------------------------------------------
1 | import { kebabCase } from 'lodash-es';
2 |
3 | export const genShield = (alt: string, url: string, link?: string) => {
4 | const shieldName = kebabCase([alt.toLowerCase(), 'shield'].filter(Boolean).join('-'));
5 | if (!link) return [`![][${shieldName}]`, `[${shieldName}]: ${url}`];
6 | const linkName = kebabCase([alt.toLowerCase(), 'link'].filter(Boolean).join('-'));
7 | return [
8 | `[![][${shieldName}]][${linkName}]`,
9 | [`[${shieldName}]: ${url}`, `[${linkName}]: ${link}`].join('\n'),
10 | ];
11 | };
12 |
--------------------------------------------------------------------------------
/src/utils/genSiteHeadTitle.ts:
--------------------------------------------------------------------------------
1 | export const genSiteHeadTitle = (title: string = 'Readme Generator') =>
2 | title ? `${title} · LobeHub` : 'LobeHub';
3 |
--------------------------------------------------------------------------------
/src/utils/genThemeModeImg.ts:
--------------------------------------------------------------------------------
1 | export const genThemeModeImg = ({ light, dark }: { dark: string; light: string }) => {
2 | return [
3 | '',
4 | ` `,
5 | ` `,
6 | ` `,
7 | ].join('\n');
8 | };
9 |
--------------------------------------------------------------------------------
/src/utils/remarkFormat.ts:
--------------------------------------------------------------------------------
1 | import { remark } from 'remark';
2 | import remarkGfm from 'remark-gfm';
3 |
4 | export const remarkFormat = async (md: string): Promise => {
5 | const data = await remark()
6 | .use(remarkGfm)
7 | .use({
8 | settings: {
9 | bullet: '-',
10 | emphasis: '*',
11 | fences: true,
12 | rule: '-',
13 | strong: '*',
14 | tightDefinitions: true,
15 | },
16 | })
17 | .process(md.trim());
18 |
19 | return data.toString();
20 | };
21 |
--------------------------------------------------------------------------------
/tests/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lobehub/lobe-readme-wizard/6600570486025bd6733df0fdb0748c0d100ca992/tests/.gitkeep
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "declaration": true,
5 | "downlevelIteration": true,
6 | "esModuleInterop": true,
7 | "jsx": "react-jsx",
8 | "lib": ["dom", "dom.iterable", "esnext"],
9 | "paths": {
10 | "@@/*": [".dumi/tmp/*"],
11 | "@/*": ["src/*"],
12 | "@lobehub/readme-wizard": ["src"],
13 | "@lobehub/readme-wizard/*": ["src/*"]
14 | },
15 | "resolveJsonModule": true,
16 | "skipLibCheck": true,
17 | "strict": true,
18 | "types": ["vitest/globals"]
19 | },
20 | "include": ["src", "docs", "tests", ".dumirc.ts", "*.ts"]
21 | }
22 |
--------------------------------------------------------------------------------
/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "installCommand": "/bun1/bun install"
3 | }
4 |
--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { resolve } from 'node:path';
2 | import { defineConfig } from 'vitest/config';
3 |
4 | export default defineConfig({
5 | test: {
6 | alias: {
7 | '@': resolve(__dirname, './src'),
8 | },
9 | coverage: {
10 | provider: 'v8',
11 | reporter: ['text', 'json', 'lcov', 'text-summary'],
12 | },
13 | environment: 'jsdom',
14 | globals: true,
15 | },
16 | });
17 |
--------------------------------------------------------------------------------