├── .github └── workflows │ └── website.yaml ├── .gitignore ├── .prettierrc ├── .vscode └── settings.json ├── README.md ├── package.json ├── packages ├── v1 │ ├── .storybook │ │ ├── main.js │ │ └── preview.js │ ├── README.md │ ├── index.html │ ├── package.json │ ├── public │ │ ├── CNAME │ │ ├── bg │ │ │ ├── 01.jpg │ │ │ ├── 02.jpg │ │ │ ├── 03.jpg │ │ │ ├── 04.jpg │ │ │ ├── 05.jpg │ │ │ ├── 06.jpg │ │ │ ├── 07.jpg │ │ │ ├── 08.jpg │ │ │ ├── 09.jpg │ │ │ └── 10.jpg │ │ └── images │ │ │ ├── greasemonkey.svg │ │ │ ├── joplin-api.svg │ │ │ ├── joplin-batch.svg │ │ │ ├── joplin-blog.svg │ │ │ ├── joplin-search-integration.svg │ │ │ ├── joplin-vscode-plugin.svg │ │ │ ├── joplin.svg │ │ │ ├── liuli-tools.png │ │ │ ├── liuli-utils.png │ │ │ └── vite-jetbrains-plugin.svg │ ├── src │ │ ├── App.tsx │ │ ├── components │ │ │ ├── Space.stories.tsx │ │ │ └── Space.tsx │ │ ├── constants │ │ │ └── router.ts │ │ ├── main.css │ │ ├── main.tsx │ │ ├── utils │ │ │ ├── RandomUtil.ts │ │ │ ├── index.ts │ │ │ └── story.ts │ │ ├── views │ │ │ ├── 404 │ │ │ │ ├── NotMatchPage.module.css │ │ │ │ ├── NotMatchPage.stories.tsx │ │ │ │ ├── NotMatchPage.tsx │ │ │ │ ├── index.ts │ │ │ │ └── util │ │ │ │ │ ├── __tests__ │ │ │ │ │ └── randomImage.test.ts │ │ │ │ │ └── randomImage.ts │ │ │ ├── about │ │ │ │ ├── AboutPage.module.css │ │ │ │ ├── AboutPage.tsx │ │ │ │ ├── about.md │ │ │ │ └── index.ts │ │ │ └── home │ │ │ │ ├── HomePage.tsx │ │ │ │ ├── api │ │ │ │ └── CreatorApi.ts │ │ │ │ ├── component │ │ │ │ ├── Banner.module.css │ │ │ │ ├── Banner.tsx │ │ │ │ ├── Content.module.css │ │ │ │ ├── Content.tsx │ │ │ │ ├── ContentItem.module.css │ │ │ │ ├── ContentItem.stories.tsx │ │ │ │ ├── ContentItem.tsx │ │ │ │ ├── Header.module.css │ │ │ │ └── Header.tsx │ │ │ │ └── index.ts │ │ └── vite-env.d.ts │ ├── tsconfig.json │ └── vite.config.ts ├── v2 │ ├── .gitignore │ ├── index.html │ ├── package.json │ ├── public │ │ ├── .nojekyll │ │ ├── CNAME │ │ └── ads.txt │ ├── readme.md │ ├── src │ │ ├── App.module.css │ │ ├── App.tsx │ │ ├── assets │ │ │ ├── avatar.jpg │ │ │ ├── avatar.webp │ │ │ ├── blog.svg │ │ │ ├── folder.svg │ │ │ ├── github.svg │ │ │ ├── joplin-vscode-plugin-cover.png │ │ │ ├── joplin-vscode-plugin-cover.webp │ │ │ ├── logo.svg │ │ │ ├── mami.drawio.svg │ │ │ ├── open.svg │ │ │ ├── pixiv.svg │ │ │ ├── telegram.svg │ │ │ └── twitter.svg │ │ ├── components │ │ │ ├── LayoutNavbar.module.css │ │ │ ├── LayoutNavbar.tsx │ │ │ ├── LeftLinks.module.css │ │ │ ├── LeftLinks.tsx │ │ │ ├── LinkButton.module.css │ │ │ ├── LinkButton.tsx │ │ │ ├── LinkIcon.module.css │ │ │ ├── LinkIcon.tsx │ │ │ ├── RightMail.module.css │ │ │ ├── RightMail.tsx │ │ │ ├── Tabs.module.css │ │ │ ├── Tabs.tsx │ │ │ ├── TransitionGroup.module.css │ │ │ └── TransitionGroup.tsx │ │ ├── constants │ │ │ ├── __tests__ │ │ │ │ └── i18n.test.ts │ │ │ ├── i18n.ts │ │ │ └── useI18n.tsx │ │ ├── hooks │ │ │ ├── useInView.ts │ │ │ └── useWindowScroll.ts │ │ ├── i18n │ │ │ ├── en-US.json │ │ │ ├── ja-JP.json │ │ │ └── zh-CN.json │ │ ├── index.css │ │ ├── main.tsx │ │ ├── preact.d.ts │ │ ├── views │ │ │ ├── AboutView.module.css │ │ │ ├── AboutView.tsx │ │ │ ├── ConcatView.module.css │ │ │ ├── ConcatView.tsx │ │ │ ├── ExperienceView.module.css │ │ │ ├── ExperienceView.tsx │ │ │ ├── FooterView.module.css │ │ │ ├── FooterView.tsx │ │ │ ├── HomeView.module.css │ │ │ ├── HomeView.tsx │ │ │ ├── WorkView.module.css │ │ │ ├── WorkView.tsx │ │ │ └── works │ │ │ │ ├── joplin-utils.en-US.md │ │ │ │ ├── joplin-utils.ja-JP.md │ │ │ │ ├── joplin-utils.zh-CN.md │ │ │ │ ├── mami.en-US.md │ │ │ │ ├── mami.ja-JP.md │ │ │ │ └── mami.zh-CN.md │ │ └── vite-env.d.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts ├── v3 │ ├── .gitignore │ ├── index.html │ ├── package.json │ ├── public │ │ ├── .nojekyll │ │ ├── CNAME │ │ └── ads.txt │ ├── src │ │ ├── assets │ │ │ ├── avatar.jpg │ │ │ ├── banner.jpg │ │ │ ├── blog.svg │ │ │ ├── github.svg │ │ │ ├── pixiv.svg │ │ │ ├── telegram.svg │ │ │ └── twitter.svg │ │ ├── index.tsx │ │ └── style.css │ ├── tailwind.config.js │ ├── tsconfig.json │ └── vite.config.ts ├── v4 │ ├── .gitignore │ ├── .npmrc │ ├── .prettierrc │ ├── .vscode │ │ └── settings.json │ ├── README.md │ ├── components.json │ ├── eslint.config.js │ ├── package.json │ ├── postcss.config.js │ ├── src │ │ ├── app.css │ │ ├── app.d.ts │ │ ├── app.html │ │ ├── index.test.ts │ │ ├── lib │ │ │ ├── components │ │ │ │ ├── logic │ │ │ │ │ ├── footer.svelte │ │ │ │ │ └── navbar.svelte │ │ │ │ └── ui │ │ │ │ │ └── button │ │ │ │ │ ├── button.svelte │ │ │ │ │ └── index.ts │ │ │ ├── i18n │ │ │ │ ├── store.ts │ │ │ │ └── translations.ts │ │ │ ├── index.ts │ │ │ └── utils.ts │ │ └── routes │ │ │ ├── +layout.server.ts │ │ │ ├── +layout.svelte │ │ │ ├── +page.svelte │ │ │ ├── +page.ts │ │ │ ├── components │ │ │ ├── about.svelte │ │ │ ├── hero.svelte │ │ │ ├── life.svelte │ │ │ ├── navbar.svelte │ │ │ └── works.svelte │ │ │ ├── ping │ │ │ └── privacy │ │ │ │ ├── +page.svelte │ │ │ │ └── privacy.md │ │ │ └── webstore │ │ │ └── privacy │ │ │ ├── +page.svelte │ │ │ └── privacy.md │ ├── static │ │ ├── .nojekyll │ │ ├── CNAME │ │ ├── ads.txt │ │ └── favicon.jpg │ ├── svelte.config.js │ ├── tailwind.config.ts │ ├── tsconfig.json │ └── vite.config.ts └── v5 │ ├── .cta.json │ ├── .cursorrules │ ├── .gitignore │ ├── .vscode │ └── settings.json │ ├── README.md │ ├── app.config.timestamp_1748251366325.js │ ├── app.config.ts │ ├── components.json │ ├── count.txt │ ├── package.json │ ├── public │ ├── favicon.png │ ├── images │ │ └── projects │ │ │ ├── bilibili-markdown.jpg │ │ │ ├── clean-twitter.jpg │ │ │ ├── cors-unblock.jpg │ │ │ ├── idbport.jpg │ │ │ ├── joplin-vscode-plugin.jpg │ │ │ ├── mass-block-twitter.jpg │ │ │ ├── myunzip.jpg │ │ │ ├── ping.jpg │ │ │ ├── ponytab.jpg │ │ │ ├── redirector.jpg │ │ │ ├── window-resizer-preferences.jpg │ │ │ ├── window-resizer-tray-menu.jpg │ │ │ └── window-resizer.jpg │ ├── logo.png │ ├── logo192.jpg │ ├── logo512.jpg │ ├── logo96.jpg │ ├── manifest.json │ └── robots.txt │ ├── scripts │ └── generate-sitemap.ts │ ├── src │ ├── api.ts │ ├── client.tsx │ ├── components │ │ ├── Header.tsx │ │ ├── MarkdownView.tsx │ │ ├── NotFound.tsx │ │ ├── ThemeProvider.tsx │ │ ├── ThemeToggle.tsx │ │ ├── blog │ │ │ ├── BlogPostCard.tsx │ │ │ └── Outline.tsx │ │ ├── layout │ │ │ ├── footer.tsx │ │ │ ├── header.tsx │ │ │ └── root-layout.tsx │ │ ├── project │ │ │ ├── project-card.tsx │ │ │ └── project-icon.tsx │ │ ├── seo.tsx │ │ └── ui │ │ │ ├── badge.tsx │ │ │ ├── button.tsx │ │ │ ├── card.tsx │ │ │ ├── dropdown-menu.tsx │ │ │ ├── input.tsx │ │ │ ├── select.tsx │ │ │ ├── separator.tsx │ │ │ └── tabs.tsx │ ├── data │ │ ├── blog.ts │ │ ├── blog │ │ │ ├── convert-chrome-extension-to-safari.md │ │ │ ├── extracting-large-zip-files-with-directory-structure-in-web-browsers.md │ │ │ ├── intercepting-network-requests-in-chrome-extensions.md │ │ │ ├── journey-to-optimize-cloudflare-d1-database-queries.md │ │ │ ├── recording-a-migration-from-prisma-to-drizzle.md │ │ │ └── svelte5-a-less-favorable-vue3.md │ │ ├── privacy │ │ │ ├── ping.md │ │ │ └── webstore.md │ │ ├── projects.ts │ │ └── projects │ │ │ ├── cors-unblock.md │ │ │ ├── idbport.md │ │ │ ├── mass-block-twitter.md │ │ │ ├── myunzip.md │ │ │ ├── ping.md │ │ │ ├── ponytab.md │ │ │ ├── redirector.md │ │ │ └── window-resizer.md │ ├── lib │ │ ├── ga.ts │ │ └── utils.ts │ ├── routeTree.gen.ts │ ├── router.tsx │ ├── routes │ │ ├── __root.tsx │ │ ├── about.tsx │ │ ├── api.demo-names.ts │ │ ├── blog.$slug.tsx │ │ ├── blog.index.tsx │ │ ├── demo.start.api-request.tsx │ │ ├── demo.start.server-funcs.tsx │ │ ├── index.tsx │ │ ├── ping.privacy.tsx │ │ ├── project.$slug.tsx │ │ ├── project.index.tsx │ │ └── webstore.privacy.tsx │ ├── ssr.tsx │ ├── styles.css │ ├── styles │ │ └── prism-custom.css │ └── types │ │ ├── blog.ts │ │ ├── markdown.d.ts │ │ └── project.ts │ └── tsconfig.json ├── pnpm-lock.yaml └── pnpm-workspace.yaml /.github/workflows/website.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | # Runs on pushes targeting the `main` branch. Change this to `master` if you're 3 | # using the `master` branch as the default branch. 4 | push: 5 | branches: [main] 6 | paths: 7 | - 'packages/v5/**' 8 | # Allows you to run this workflow manually from the Actions tab 9 | workflow_dispatch: 10 | 11 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 12 | permissions: 13 | contents: read 14 | pages: write 15 | id-token: write 16 | 17 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 18 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 19 | concurrency: 20 | group: pages 21 | cancel-in-progress: false 22 | 23 | jobs: 24 | # Build job 25 | build: 26 | runs-on: ubuntu-latest 27 | steps: 28 | - name: Checkout 29 | uses: actions/checkout@v3 30 | with: 31 | fetch-depth: 0 # Not needed if lastUpdated is not enabled 32 | # - uses: pnpm/action-setup@v2 # Uncomment this if you're using pnpm 33 | # - uses: oven-sh/setup-bun@v1 # Uncomment this if you're using Bun 34 | - name: Setup pnpm 35 | uses: pnpm/action-setup@v4 36 | - name: Setup Node 37 | uses: actions/setup-node@v4 38 | with: 39 | node-version: 22 40 | cache: pnpm 41 | - name: Setup Pages 42 | uses: actions/configure-pages@v3 43 | - name: Install dependencies 44 | run: pnpm i # or pnpm install / yarn install / bun install 45 | - name: Build 46 | run: cd packages/v5 && pnpm build && cd ../.. 47 | - name: Upload artifact 48 | uses: actions/upload-pages-artifact@v3 49 | with: 50 | path: packages/v5/.output/public 51 | 52 | # Deployment job 53 | deploy: 54 | environment: 55 | name: github-pages 56 | url: ${{ steps.deployment.outputs.page_url }} 57 | needs: build 58 | runs-on: ubuntu-latest 59 | name: Deploy 60 | steps: 61 | - name: Deploy to GitHub Pages 62 | id: deployment 63 | uses: actions/deploy-pages@v4 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ----------------------------- Directories -------------------------- 2 | node_modules 3 | dist 4 | tests/server/static 5 | tests/server/static/upload 6 | .idea 7 | 8 | # ----------------------------- File Types -------------------------- 9 | *.suo 10 | *.ntvs* 11 | *.njsproj 12 | *.sln 13 | *.sw? 14 | *~ 15 | 16 | # ----------------------------- Files ------------------------------- 17 | .DS_Store 18 | # local env files 19 | .env.local 20 | .env.*.local 21 | # log files 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | lerna-debug.log* 26 | **/__tests__/temp/ 27 | *.tgz 28 | .vscode/* 29 | !.vscode/settings.json 30 | !.vscode/tasks.json 31 | !.vscode/launch.json 32 | !.vscode/extensions.json 33 | **/i18n/index.d.ts 34 | stats.html 35 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "printWidth": 120, 4 | "semi": false, 5 | "singleQuote": true, 6 | "trailingComma": "all", 7 | "arrowParens": "always", 8 | "endOfLine": "crlf", 9 | "plugins": ["prettier-plugin-svelte"], 10 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "i18n-ally.localesPaths": [ 3 | "packages/v2/src/i18n", 4 | "packages/v4/src/lib/i18n" 5 | ] 6 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## rxliuli(琉璃) 👨‍💻 2 | 3 | 吾辈是 rxliuli(中文名是 **琉璃**),喜欢现代前端的开发者(曾经的后端开发)。 4 | 5 | ### 关于 6 | 7 | - **工作 :** Web 前端开发 8 | - **学习 :** Full-Stack :zap: | Open-Source :fire: 9 | - **兴趣 :** Books :books: Kiger 😄 10 | - **帮助 :** 如果遇到任何 `TypeScript/Jetbrains IDE/Windows 工具` 的问题可以问吾辈!:v: 11 | - **昵称 :** rxliuli/琉璃 :innocent: 12 | 13 | --- 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rxliuli/website", 3 | "version": "0.0.0", 4 | "devDependencies": { 5 | "@commitlint/cli": "^16.2.1", 6 | "@commitlint/config-conventional": "^16.2.1", 7 | "@liuli-util/commitlint-standard-config": "^0.1.6", 8 | "@liuli-util/eslint-config-ts": "^0.4.0", 9 | "@liuli-util/prettier-standard-config": "^0.2.0", 10 | "create-preact": "link:/Users/rxliuli/code/web/create-preact/", 11 | "lint-staged": "^12.3.4", 12 | "prettier": "^2.3.2", 13 | "prettier-plugin-svelte": "^3.1.2", 14 | "simple-git-hooks": "^2.5.1" 15 | }, 16 | "eslintConfig": { 17 | "extends": [ 18 | "@liuli-util/eslint-config-ts" 19 | ] 20 | }, 21 | "simple-git-hooks": { 22 | "commit-msg": "pnpm commitlint --edit $1", 23 | "pre-commit": "pnpm lint-staged" 24 | }, 25 | "commitlint": { 26 | "extends": [ 27 | "@liuli-util/commitlint-standard-config" 28 | ] 29 | }, 30 | "lint-staged": { 31 | "src/**/*.{ts,tsx,js,jsx,css,vue}": [ 32 | "prettier --write" 33 | ], 34 | "src/**/*.{ts,tsx,js,jsx}": [ 35 | "eslint --fix --quiet" 36 | ] 37 | }, 38 | "scripts": { 39 | "postinstall": "pnpm simple-git-hooks" 40 | }, 41 | "packageManager": "pnpm@10.5.2+sha512.da9dc28cd3ff40d0592188235ab25d3202add8a207afbedc682220e4a0029ffbff4562102b9e6e46b4e3f9e8bd53e6d05de48544b0c57d4b0179e22c76d1199b" 42 | } 43 | -------------------------------------------------------------------------------- /packages/v1/.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "stories": [ 3 | "../src/**/*.stories.mdx", 4 | "../src/**/*.stories.@(js|jsx|ts|tsx)" 5 | ], 6 | "addons": [ 7 | "@storybook/addon-links", 8 | "@storybook/addon-essentials" 9 | ] 10 | } -------------------------------------------------------------------------------- /packages/v1/.storybook/preview.js: -------------------------------------------------------------------------------- 1 | import '@fortawesome/fontawesome-free/js/brands' 2 | import '@fortawesome/fontawesome-free/js/solid' 3 | import '@fortawesome/fontawesome-free/js/fontawesome' 4 | 5 | export const parameters = { 6 | actions: { argTypesRegex: '^on[A-Z].*' }, 7 | controls: { 8 | matchers: { 9 | color: /(background|color)$/i, 10 | date: /Date$/, 11 | }, 12 | }, 13 | } 14 | -------------------------------------------------------------------------------- /packages/v1/README.md: -------------------------------------------------------------------------------- 1 | # homepage 2 | -------------------------------------------------------------------------------- /packages/v1/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 琉璃的个人主页 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/v1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rxliuli/v1", 3 | "scripts": { 4 | "dev": "vite", 5 | "build": "vite build", 6 | "deploy": "liuli-cli deploy", 7 | "storybook": "start-storybook -p 6006", 8 | "build-storybook": "build-storybook" 9 | }, 10 | "deploy": { 11 | "type": "gh-pages", 12 | "dist": "dist", 13 | "dest": "/v1" 14 | }, 15 | "dependencies": { 16 | "@fortawesome/fontawesome-free": "^6.0.0", 17 | "@liuli-util/react-router": "^0.3.1", 18 | "mockjs": "^1.1.0", 19 | "normalize.css": "^8.0.1", 20 | "rc-util": "^5.18.1", 21 | "react": "^17.0.2", 22 | "react-dom": "^17.0.2", 23 | "react-markdown": "^8.0.0", 24 | "react-use": "^17.3.2" 25 | }, 26 | "jest": { 27 | "preset": "ts-jest" 28 | }, 29 | "eslintConfig": { 30 | "extends": [ 31 | "@liuli-util/eslint-config-react-ts" 32 | ] 33 | }, 34 | "devDependencies": { 35 | "@babel/core": "^7.17.5", 36 | "@liuli-util/cli": "^3.21.0", 37 | "@liuli-util/eslint-config-react-ts": "^0.2.1", 38 | "@mdx-js/react": "^2.0.0", 39 | "@storybook/addon-actions": "^6.4.19", 40 | "@storybook/addon-docs": "^6.4.19", 41 | "@storybook/addon-essentials": "^6.4.19", 42 | "@storybook/addon-links": "^6.4.19", 43 | "@storybook/addons": "^6.4.19", 44 | "@storybook/react": "^6.4.19", 45 | "@types/jest": "^27.4.1", 46 | "@types/mockjs": "^1.0.6", 47 | "@types/react": "^17.0.39", 48 | "@types/react-dom": "^17.0.11", 49 | "@vitejs/plugin-react-refresh": "^1.3.6", 50 | "babel-loader": "^8.2.3", 51 | "gh-pages": "^3.2.3", 52 | "jest": "^27.5.1", 53 | "rollup-plugin-visualizer": "^5.6.0", 54 | "ts-jest": "^27.1.3", 55 | "typescript": "^4.5.5", 56 | "vite": "^2.9.14" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /packages/v1/public/CNAME: -------------------------------------------------------------------------------- 1 | rxliuli.com -------------------------------------------------------------------------------- /packages/v1/public/bg/01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rxliuli/site/b281f05e69b0d562c20e4c040be1e4d04ce90b72/packages/v1/public/bg/01.jpg -------------------------------------------------------------------------------- /packages/v1/public/bg/02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rxliuli/site/b281f05e69b0d562c20e4c040be1e4d04ce90b72/packages/v1/public/bg/02.jpg -------------------------------------------------------------------------------- /packages/v1/public/bg/03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rxliuli/site/b281f05e69b0d562c20e4c040be1e4d04ce90b72/packages/v1/public/bg/03.jpg -------------------------------------------------------------------------------- /packages/v1/public/bg/04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rxliuli/site/b281f05e69b0d562c20e4c040be1e4d04ce90b72/packages/v1/public/bg/04.jpg -------------------------------------------------------------------------------- /packages/v1/public/bg/05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rxliuli/site/b281f05e69b0d562c20e4c040be1e4d04ce90b72/packages/v1/public/bg/05.jpg -------------------------------------------------------------------------------- /packages/v1/public/bg/06.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rxliuli/site/b281f05e69b0d562c20e4c040be1e4d04ce90b72/packages/v1/public/bg/06.jpg -------------------------------------------------------------------------------- /packages/v1/public/bg/07.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rxliuli/site/b281f05e69b0d562c20e4c040be1e4d04ce90b72/packages/v1/public/bg/07.jpg -------------------------------------------------------------------------------- /packages/v1/public/bg/08.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rxliuli/site/b281f05e69b0d562c20e4c040be1e4d04ce90b72/packages/v1/public/bg/08.jpg -------------------------------------------------------------------------------- /packages/v1/public/bg/09.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rxliuli/site/b281f05e69b0d562c20e4c040be1e4d04ce90b72/packages/v1/public/bg/09.jpg -------------------------------------------------------------------------------- /packages/v1/public/bg/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rxliuli/site/b281f05e69b0d562c20e4c040be1e4d04ce90b72/packages/v1/public/bg/10.jpg -------------------------------------------------------------------------------- /packages/v1/public/images/liuli-tools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rxliuli/site/b281f05e69b0d562c20e4c040be1e4d04ce90b72/packages/v1/public/images/liuli-tools.png -------------------------------------------------------------------------------- /packages/v1/public/images/liuli-utils.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rxliuli/site/b281f05e69b0d562c20e4c040be1e4d04ce90b72/packages/v1/public/images/liuli-utils.png -------------------------------------------------------------------------------- /packages/v1/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import 'normalize.css' 3 | import { RouterView } from '@liuli-util/react-router' 4 | import { Header } from './views/home/component/Header' 5 | 6 | const App: React.FC = () => { 7 | return ( 8 | <> 9 |
10 | 11 | 12 | ) 13 | } 14 | 15 | export default App 16 | -------------------------------------------------------------------------------- /packages/v1/src/components/Space.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { story } from '../utils' 3 | import { Space } from './Space' 4 | 5 | const { meta, of } = story(Space) 6 | export default meta({ 7 | title: '组件/Space', 8 | }) 9 | 10 | export const Basic = of({ 11 | storyName: '基本示例', 12 | args: { 13 | children: ( 14 | <> 15 | 16 | 17 | 18 | ), 19 | }, 20 | }) 21 | -------------------------------------------------------------------------------- /packages/v1/src/components/Space.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { ReactNode } from 'react' 3 | import toArray from 'rc-util/es/Children/toArray' 4 | 5 | type SpaceProps = { 6 | size?: number 7 | children: ReactNode 8 | } 9 | 10 | export const Space: React.FC = (props) => { 11 | return ( 12 |
'max-content') 17 | .join(' '), 18 | gridGap: props.size ?? 4, 19 | }} 20 | > 21 | {props.children} 22 |
23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /packages/v1/src/constants/router.ts: -------------------------------------------------------------------------------- 1 | import { createHashHistory, RouteConfig } from '@liuli-util/react-router' 2 | import HomeView from '../views/home' 3 | import AboutView from '../views/about' 4 | import NotMatchPage from '../views/404' 5 | import App from '../App' 6 | 7 | export const history = createHashHistory() 8 | 9 | export const routes: RouteConfig[] = [ 10 | { 11 | path: '/', 12 | component: App, 13 | children: [ 14 | { path: '/', component: HomeView }, 15 | { path: '/about', component: AboutView }, 16 | { path: '*', component: NotMatchPage }, 17 | ], 18 | }, 19 | ] 20 | -------------------------------------------------------------------------------- /packages/v1/src/main.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | -------------------------------------------------------------------------------- /packages/v1/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import '@fortawesome/fontawesome-free/js/brands' 4 | import '@fortawesome/fontawesome-free/js/solid' 5 | import '@fortawesome/fontawesome-free/js/fontawesome' 6 | import './main.css' 7 | import { ReactRouter } from '@liuli-util/react-router' 8 | import { history, routes } from './constants/router' 9 | 10 | ReactDOM.render( 11 | 12 | 13 | , 14 | document.getElementById('root'), 15 | ) 16 | -------------------------------------------------------------------------------- /packages/v1/src/utils/RandomUtil.ts: -------------------------------------------------------------------------------- 1 | export class RandomUtil { 2 | static image(size: number): string 3 | static image(x: number, y: number): string 4 | /** 5 | * 随机生成一张图片 6 | * @param x 7 | * @param y 8 | */ 9 | static image(x: number, y?: number): string { 10 | y = y ?? x 11 | return `https://picsum.photos/${x}/${y}` 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/v1/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './story' 2 | export * from './RandomUtil' 3 | -------------------------------------------------------------------------------- /packages/v1/src/utils/story.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { createElement } from 'react' 3 | import { Annotations, BaseStory } from '@storybook/addons' 4 | import { Meta, Story } from '@storybook/react' 5 | import { StoryFnReactReturnType } from '@storybook/react/dist/ts3.9/client/preview/types' 6 | 7 | interface StoryReturnType { 8 | meta(meta: Meta): Meta 9 | of( 10 | annotations?: Annotations & 11 | Pick, 'storyName'>, 12 | ): Story 13 | } 14 | 15 | /** 16 | * Create a typed story of a given component 17 | * 18 | * @example 19 | * const { meta, of } = story(Button) 20 | * export default meta({}) 21 | * export const story1 = of({}) 22 | * export const story2 = of({}) 23 | */ 24 | export function story( 25 | Component: React.ComponentType, 26 | ): StoryReturnType { 27 | return { 28 | meta(meta: Meta) { 29 | return { ...meta, component: Component } 30 | }, 31 | of( 32 | annotations: Annotations & 33 | Pick, 'storyName'> = {}, 34 | ) { 35 | const copy: Story = (props: T) => createElement(Component, props) 36 | Object.assign(copy, annotations) 37 | return copy 38 | }, 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/v1/src/views/404/NotMatchPage.module.css: -------------------------------------------------------------------------------- 1 | .notMatchPage { 2 | position: fixed; 3 | left: 0; 4 | right: 0; 5 | top: 0; 6 | bottom: 0; 7 | width: 100%; 8 | height: 100%; 9 | background: #ffffff center center / cover no-repeat; 10 | } 11 | 12 | .mask { 13 | width: 100%; 14 | height: 100%; 15 | background-color: rgba(0, 0, 0, 0.5); 16 | display: flex; 17 | align-items: center; 18 | justify-content: center; 19 | } 20 | 21 | .content { 22 | text-align: center; 23 | color: #ffffff; 24 | } 25 | 26 | .logo { 27 | border: 3px solid #fff; 28 | box-shadow: rgba(6, 6, 6, 0.3) 0 0 1px 1px; 29 | width: 160px; 30 | height: 160px; 31 | border-radius: 50%; 32 | transition: all.5s; 33 | } 34 | 35 | .logo:hover { 36 | transform: rotate(360deg); 37 | } 38 | 39 | footer { 40 | color: #b3b3b3; 41 | position: absolute; 42 | bottom: 16px; 43 | left: 0; 44 | right: 0; 45 | text-align: center; 46 | } 47 | -------------------------------------------------------------------------------- /packages/v1/src/views/404/NotMatchPage.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { story } from '../../utils' 3 | import { NotMatchPage } from './NotMatchPage' 4 | 5 | const { meta, of } = story(NotMatchPage) 6 | 7 | export default meta({ 8 | title: '组件/NotMatchPage', 9 | }) 10 | 11 | export const Basic = of({ 12 | storyName: '基本示例', 13 | }) 14 | -------------------------------------------------------------------------------- /packages/v1/src/views/404/NotMatchPage.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from '@liuli-util/react-router' 2 | import * as React from 'react' 3 | import css from './NotMatchPage.module.css' 4 | import { randomImage } from './util/randomImage' 5 | 6 | export const NotMatchPage: React.FC = () => { 7 | return ( 8 |
14 |
15 |
16 | 17 | {'avatar'} 22 | 23 |

对不起,您要找的页面走丢了 (~ ̄▽ ̄)~

24 |
25 |
MIT Licensed | Copyright © 2021-present rxliuli
26 |
27 |
28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /packages/v1/src/views/404/index.ts: -------------------------------------------------------------------------------- 1 | export { NotMatchPage as default } from './NotMatchPage' 2 | -------------------------------------------------------------------------------- /packages/v1/src/views/404/util/__tests__/randomImage.test.ts: -------------------------------------------------------------------------------- 1 | import { randomImage } from '../randomImage' 2 | 3 | it('测试 randomImage', () => { 4 | const res = randomImage() 5 | console.log(Array(10).fill(0).map(randomImage)) 6 | expect(res.length).toBe(10) 7 | }) 8 | -------------------------------------------------------------------------------- /packages/v1/src/views/404/util/randomImage.ts: -------------------------------------------------------------------------------- 1 | import { Random } from 'mockjs' 2 | 3 | export function randomImage(): string { 4 | const i = Random.integer(1, 10) 5 | const s = i.toString() 6 | const n = 2 7 | return '/v1/bg/' + '0'.repeat(n - s.length) + s + '.jpg' 8 | } 9 | -------------------------------------------------------------------------------- /packages/v1/src/views/about/AboutPage.module.css: -------------------------------------------------------------------------------- 1 | .content { 2 | max-width: 800px; 3 | margin: auto; 4 | } 5 | @media (max-width: 576px) { 6 | .content { 7 | padding: 0 8px; 8 | } 9 | } 10 | .content img { 11 | max-width: 100%; 12 | } 13 | 14 | .icon { 15 | color: #ffffff; 16 | font-size: 24px; 17 | text-align: center; 18 | vertical-align: middle; 19 | width: 32px; 20 | height: 32px; 21 | line-height: 32px; 22 | } 23 | -------------------------------------------------------------------------------- /packages/v1/src/views/about/AboutPage.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import ReactMarkdown from 'react-markdown' 3 | import css from './AboutPage.module.css' 4 | 5 | const Icon: React.FC<{ 6 | href: string 7 | }> = (props) => ( 8 | 9 | {props.children} 10 | 11 | ) 12 | 13 | function AboutBanner() { 14 | return ( 15 | 16 |

关于吾辈

17 |

吾辈是琉璃,喜欢现代前端的全沾开发者

18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
36 | ) 37 | } 38 | 39 | import aboutContent from './about.md?raw' 40 | import { Space } from '../../components/Space' 41 | import { Banner } from '../home/component/Banner' 42 | 43 | export const AboutPage: React.FC = () => { 44 | return ( 45 |
46 | 47 | {aboutContent} 48 |
49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /packages/v1/src/views/about/about.md: -------------------------------------------------------------------------------- 1 | ## rxliuli(琉璃)👨‍💻 2 | 3 | - **工作 :** Web 前端开发 4 | - **学习 :** Full-Stack ⚡ | Open-Source 🔥 5 | - **兴趣 :** Books 📚 预备 Kiger 😄 6 | - **帮助 :** 如果遇到任何 `TypeScript/Jetbrains IDE/Windows 工具` 的问题可以问吾辈!✌️ 7 | - **代称 :** rxliuli/琉璃 😇 8 | 9 | ![github stats](https://github-readme-stats.vercel.app/api?username=rxliuli&show_icons=true&theme=tokyonight&line_height=40&v=5) 10 | ![github used languages](https://github-readme-stats.vercel.app/api/top-langs/?username=rxliuli&theme=tokyonight&hide=python,shell) 11 | -------------------------------------------------------------------------------- /packages/v1/src/views/about/index.ts: -------------------------------------------------------------------------------- 1 | export { AboutPage as default } from './AboutPage' 2 | -------------------------------------------------------------------------------- /packages/v1/src/views/home/HomePage.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { Content } from './component/Content' 3 | import { useAsync } from 'react-use' 4 | import { creatorApi } from './api/CreatorApi' 5 | import { Banner } from './component/Banner' 6 | 7 | export const HomePage: React.FC = () => { 8 | const listState = useAsync(creatorApi.list) 9 | return ( 10 |
11 | 12 |

创造物

13 |

明灯不熄的熊熊烈火才正是最为纯粹的生命本质!

14 |
15 | {listState.value && } 16 |
17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /packages/v1/src/views/home/api/CreatorApi.ts: -------------------------------------------------------------------------------- 1 | export interface CreatorConfig { 2 | img: string 3 | title: string 4 | content: string 5 | github?: string 6 | home?: string 7 | } 8 | 9 | const configList: CreatorConfig[] = [ 10 | { 11 | img: '/v1/images/joplin-vscode-plugin.svg', 12 | title: 'joplin-vscode-plugin', 13 | content: 14 | 'joplin-vscode-plugin 提供在 vscode 中管理 joplin 笔记的功能,包括常见的查看、编辑笔记,管理笔记的标签,添加、修改附件,内部链接、搜索等功能。', 15 | github: 16 | 'https://github.com/rxliuli/joplin-utils/tree/master/apps/joplin-vscode-plugin', 17 | home: 'https://joplin-utils.rxliuli.com/joplin-vscode-plugin/', 18 | }, 19 | { 20 | img: '/v1/images/liuli-tools.png', 21 | title: 'liuli-tools', 22 | content: 23 | '一些项目级别的工具集,主要尝试研发提效,欢迎来 [讨论区](https://github.com/rxliuli/liuli-tools/discussions) 交流想法。', 24 | github: 'https://github.com/rxliuli/liuli-tools', 25 | home: 'https://liuli.dev/', 26 | }, 27 | { 28 | img: '/v1/images/liuli-utils.png', 29 | title: 'liuli-utils', 30 | content: 31 | '吾辈使用的工具函数库,按照功能分割成不同的模块,都发布在 [@liuli-util](https://www.npmjs.com/org/liuli-util) 组织下,可以单独引入指定模块。', 32 | github: 'https://github.com/rxliuli/liuli-utils', 33 | home: 'https://utils.liuli.dev/', 34 | }, 35 | { 36 | img: '/v1/images/joplin-blog.svg', 37 | title: 'joplin-blog', 38 | content: 39 | '将 Joplin 笔记发布为静态网站的 CLI 工具,目前支持 blog/wiki 两种形式,框架支持 hexo/vuepress/docsify。', 40 | github: 41 | 'https://github.com/rxliuli/joplin-utils/tree/master/apps/joplin-blog', 42 | home: 'https://www.npmjs.com/package/joplin-blog', 43 | }, 44 | { 45 | img: '/v1/images/vite-jetbrains-plugin.svg', 46 | title: 'vite-jetbrains-plugin', 47 | content: 48 | 'Vite 脚手架在 JetBrains IDE 中的集成,可以直接使用 IDE 的引导面板创建一个项目。', 49 | github: 50 | 'https://github.com/rxliuli/liuli-tools/blob/master/jetbrains-plugins/vite-jetbrains-plugin/', 51 | home: 'https://plugins.jetbrains.com/plugin/16897', 52 | }, 53 | { 54 | img: '/v1/images/joplin-api.svg', 55 | title: 'joplin-api', 56 | content: 57 | 'Joplin api 的 js 封装,使用 ts 编写,支持浏览器、nodejs,包含目前所有在文档中公开的 api', 58 | github: 59 | 'https://github.com/rxliuli/joplin-utils/tree/master/libs/joplin-api', 60 | home: 'https://joplin-utils.rxliuli.com/joplin-api//', 61 | }, 62 | { 63 | img: '/v1/images/joplin-search-integration.svg', 64 | title: 'joplin-search-integration', 65 | content: '使用搜索时,相关的乔普林笔记也会显示在搜索结果中。', 66 | github: 67 | 'https://github.com/rxliuli/joplin-utils/tree/master/apps/joplin-search-integration', 68 | home: 'https://chrome.google.com/webstore/detail/mcjkdcifkhjenpfjacnbhpdcnjknjkhj', 69 | }, 70 | { 71 | img: '/v1/images/joplin-batch.svg', 72 | title: 'joplin-batch-web', 73 | content: 74 | '处理一些 Joplin 本身不支持的批量操作,以可视化界面的形式展现出来。', 75 | github: 76 | 'https://github.com/rxliuli/joplin-utils/tree/master/apps/joplin-batch-web', 77 | home: 'https://joplin-utils.rxliuli.com/joplin-batch-web/', 78 | }, 79 | { 80 | img: '/v1/images/greasemonkey.svg', 81 | title: 'user.js', 82 | content: '日常需要用到便自行实现的油猴脚本', 83 | github: 'https://github.com/rxliuli/userjs', 84 | }, 85 | ] 86 | 87 | class CreatorApi { 88 | async list(): Promise { 89 | return configList 90 | } 91 | } 92 | 93 | export const creatorApi = new CreatorApi() 94 | -------------------------------------------------------------------------------- /packages/v1/src/views/home/component/Banner.module.css: -------------------------------------------------------------------------------- 1 | .banner { 2 | background: url('https://blog.rxliuli.com/medias/banner/default.jpg') center 3 | center / cover no-repeat; 4 | } 5 | .mask { 6 | background-color: rgba(0, 0, 0, 0.3); 7 | height: 240px; 8 | display: grid; 9 | align-items: center; 10 | } 11 | .content { 12 | width: 100%; 13 | color: #ffffff; 14 | max-width: 1200px; 15 | margin: auto; 16 | } 17 | @media (max-width: 576px) { 18 | .content { 19 | padding: 0 8px; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/v1/src/views/home/component/Banner.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import css from './Banner.module.css' 3 | 4 | export const Banner: React.FC = (props) => { 5 | return ( 6 |
7 |
8 |
{props.children}
9 |
10 |
11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /packages/v1/src/views/home/component/Content.module.css: -------------------------------------------------------------------------------- 1 | .content { 2 | margin: 48px auto; 3 | max-width: 1200px; 4 | display: grid; 5 | justify-content: center; 6 | grid-template-columns: repeat(3, auto); 7 | grid-gap: 24px; 8 | } 9 | 10 | @media (min-width: 0) and (max-width: 576px) { 11 | .content { 12 | grid-template-columns: repeat(1, 1fr); 13 | } 14 | .content > * { 15 | max-width: 100%; 16 | } 17 | } 18 | @media (min-width: 576px) and (max-width: 768px) { 19 | .content { 20 | grid-template-columns: repeat(2, 1fr); 21 | } 22 | } 23 | @media (min-width: 768px) { 24 | .content { 25 | grid-template-columns: repeat(3, 1fr); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/v1/src/views/home/component/Content.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { ContentItem } from './ContentItem' 3 | import css from './Content.module.css' 4 | import { CreatorConfig } from '../api/CreatorApi' 5 | 6 | type ContentProps = { 7 | list: CreatorConfig[] 8 | } 9 | 10 | export const Content: React.FC = (props) => { 11 | return ( 12 |
13 | {props.list.map((item) => ( 14 | 15 | ))} 16 |
17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /packages/v1/src/views/home/component/ContentItem.module.css: -------------------------------------------------------------------------------- 1 | .contentItem { 2 | height: 100%; 3 | padding: 16px; 4 | border-radius: 16px; 5 | background: #ffffff; 6 | box-shadow: rgba(110, 121, 140, 0.5) 0 0 2px; 7 | display: grid; 8 | grid-template-rows: auto auto 1fr auto; 9 | } 10 | .contentItem h4 { 11 | margin-bottom: 0; 12 | } 13 | .contentItem > img { 14 | width: 100%; 15 | height: 200px; 16 | object-fit: contain; 17 | } 18 | 19 | .icon { 20 | color: #222222; 21 | font-size: 24px; 22 | text-align: center; 23 | vertical-align: middle; 24 | width: 32px; 25 | height: 32px; 26 | line-height: 32px; 27 | } 28 | -------------------------------------------------------------------------------- /packages/v1/src/views/home/component/ContentItem.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { story } from '../../../utils' 3 | import { ContentItem } from './ContentItem' 4 | 5 | const { meta, of } = story(ContentItem) 6 | 7 | export default meta({ 8 | title: '/组件/ContentItem', 9 | }) 10 | 11 | export const Basic = of({ 12 | storyName: '基本示例', 13 | args: { 14 | item: { 15 | title: 'liuli-utils', 16 | content: 17 | '吾辈使用的工具函数库,按照功能分割成不同的模块,都发布在 [@liuli-util](https://www.npmjs.com/org/liuli-util) 组织下,可以单独引入指定模块', 18 | img: 'https://picsum.photos/500/800', 19 | home: 'https://liuli-utils.rxliuli.com', 20 | github: 'https://github.com/rxliuli/liuli-util', 21 | }, 22 | }, 23 | 24 | decorators: [ 25 | (Story) => ( 26 |
31 |
36 | 37 |
38 |
39 | ), 40 | ], 41 | }) 42 | -------------------------------------------------------------------------------- /packages/v1/src/views/home/component/ContentItem.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import ReactMarkdown from 'react-markdown' 3 | import css from './ContentItem.module.css' 4 | import { Space } from '../../../components/Space' 5 | import { CreatorConfig } from '../api/CreatorApi' 6 | 7 | type ContentItemProps = { 8 | item: CreatorConfig 9 | } 10 | 11 | const Icon: React.FC<{ href: string; title: string }> = (props) => ( 12 | 13 | {props.children} 14 | 15 | ) 16 | 17 | export const ContentItem: React.FC = (props) => { 18 | return ( 19 |
20 | {'img'} 21 |

{props.item.title}

22 | {props.item.content} 23 | 24 | {props.item.home && ( 25 | 26 | 27 | 28 | )}{' '} 29 | {props.item.github && ( 30 | 31 | 32 | 33 | )} 34 | 35 |
36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /packages/v1/src/views/home/component/Header.module.css: -------------------------------------------------------------------------------- 1 | .placeholder { 2 | height: 36px; 3 | } 4 | .header { 5 | background-color: #222; 6 | position: fixed; 7 | left: 0; 8 | right: 0; 9 | top: 0; 10 | } 11 | nav { 12 | max-width: 1200px; 13 | margin: auto; 14 | height: 36px; 15 | line-height: 36px; 16 | display: grid; 17 | grid-template-columns: auto 1fr auto; 18 | grid-column-gap: 16px; 19 | } 20 | @media (max-width: 576px) { 21 | nav { 22 | padding: 0 8px; 23 | } 24 | } 25 | nav a { 26 | color: #9f9990; 27 | text-decoration: none; 28 | } 29 | nav a:hover { 30 | color: #ffffff; 31 | } 32 | 33 | .logo { 34 | display: grid; 35 | grid-template-columns: repeat(2, auto); 36 | align-items: center; 37 | } 38 | 39 | .avatar { 40 | display: inline-block; 41 | height: 24px; 42 | border-radius: 50%; 43 | margin-right: 4px; 44 | } 45 | -------------------------------------------------------------------------------- /packages/v1/src/views/home/component/Header.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from '@liuli-util/react-router' 2 | import * as React from 'react' 3 | import css from './Header.module.css' 4 | 5 | export const Header: React.FC = () => { 6 | return ( 7 | <> 8 |
9 | 21 |
22 |
23 | 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /packages/v1/src/views/home/index.ts: -------------------------------------------------------------------------------- 1 | export { HomePage as default } from './HomePage' 2 | -------------------------------------------------------------------------------- /packages/v1/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /packages/v1/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 5 | "allowJs": false, 6 | "skipLibCheck": true, 7 | "esModuleInterop": false, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "module": "ESNext", 12 | "moduleResolution": "Node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | "jsx": "react" 17 | }, 18 | "include": ["./src"] 19 | } 20 | -------------------------------------------------------------------------------- /packages/v1/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import reactRefresh from '@vitejs/plugin-react-refresh' 3 | import { visualizer } from 'rollup-plugin-visualizer' 4 | 5 | export default defineConfig({ 6 | base: '/v1/', 7 | plugins: [reactRefresh(), visualizer()], 8 | }) 9 | -------------------------------------------------------------------------------- /packages/v2/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /packages/v2/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 琉璃的个人主页 v2 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/v2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "v2", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "vite build", 8 | "deploy": "gh-pages -d dist --dotfiles -e v2" 9 | }, 10 | "dependencies": { 11 | "classnames": "^2.3.1", 12 | "preact": "^10.5.15" 13 | }, 14 | "devDependencies": { 15 | "@liuli-util/rollup-plugin-i18next-dts-gen": "^0.4.4", 16 | "@preact/preset-vite": "^2.3.0", 17 | "@types/markdown-it": "^12.2.3", 18 | "@types/marked": "^4.0.2", 19 | "gh-pages": "^3.2.3", 20 | "happy-dom": "^11.2.0", 21 | "markdown-it": "^12.3.2", 22 | "rollup-plugin-visualizer": "^5.6.0", 23 | "typescript": "^4.5.4", 24 | "vite": "^2.9.14", 25 | "vite-plugin-markdown": "^2.0.2", 26 | "vitest": "^0.34.4" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/v2/public/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rxliuli/site/b281f05e69b0d562c20e4c040be1e4d04ce90b72/packages/v2/public/.nojekyll -------------------------------------------------------------------------------- /packages/v2/public/CNAME: -------------------------------------------------------------------------------- 1 | rxliuli.com -------------------------------------------------------------------------------- /packages/v2/public/ads.txt: -------------------------------------------------------------------------------- 1 | google.com, pub-9997051001110405, DIRECT, f08c47fec0942fa0 -------------------------------------------------------------------------------- /packages/v2/readme.md: -------------------------------------------------------------------------------- 1 | # 个人网站 v2 2 | 3 | 主要是期望提升个人的设计水平,而第一步往往需要模仿,并且动手实践。因此将会依照设计 重新实现个人网站的第二个版本。 4 | -------------------------------------------------------------------------------- /packages/v2/src/App.module.css: -------------------------------------------------------------------------------- 1 | .main { 2 | padding: 0 100px; 3 | } 4 | 5 | .main.filter { 6 | filter: blur(10px); 7 | pointer-events: none; 8 | } 9 | 10 | @media (max-width: 768px) { 11 | .main { 12 | padding: 0 50px; 13 | } 14 | } 15 | 16 | @media (max-width: 480px) { 17 | .main { 18 | padding: 0 25px; 19 | } 20 | } 21 | 22 | .mask { 23 | position: fixed; 24 | top: 0; 25 | bottom: 0; 26 | left: 0; 27 | right: 0; 28 | display: none; 29 | } 30 | .mask.show { 31 | display: block; 32 | } 33 | -------------------------------------------------------------------------------- /packages/v2/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { HomeView } from './views/HomeView' 2 | import { ExperienceView } from './views/ExperienceView' 3 | import { WorkView } from './views/WorkView' 4 | import { ConcatView } from './views/ConcatView' 5 | import { LeftLinks } from './components/LeftLinks' 6 | import { RightMail } from './components/RightMail' 7 | import { AboutView } from './views/AboutView' 8 | import { FooterView } from './views/FooterView' 9 | import { LayoutNavbar } from './components/LayoutNavbar' 10 | import css from './App.module.css' 11 | import { useState } from 'preact/hooks' 12 | import classNames from 'classnames' 13 | import { LocaleProvider } from './constants/useI18n' 14 | 15 | export const App = () => { 16 | const [sidebar, setSidebar] = useState(false) 17 | 18 | function onToggle() { 19 | document.body.style.overflowY = sidebar ? 'auto' : 'hidden' 20 | setSidebar(!sidebar) 21 | } 22 | 23 | return ( 24 | 25 |
26 | 27 | 28 | 29 |
34 | 35 | 36 | 37 | 38 | 39 |
40 |
{ 43 | console.log('sidebar && onToggle(): ', sidebar) 44 | sidebar && onToggle() 45 | }} 46 | /> 47 | 48 |
49 | 50 | ) 51 | } 52 | -------------------------------------------------------------------------------- /packages/v2/src/assets/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rxliuli/site/b281f05e69b0d562c20e4c040be1e4d04ce90b72/packages/v2/src/assets/avatar.jpg -------------------------------------------------------------------------------- /packages/v2/src/assets/avatar.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rxliuli/site/b281f05e69b0d562c20e4c040be1e4d04ce90b72/packages/v2/src/assets/avatar.webp -------------------------------------------------------------------------------- /packages/v2/src/assets/blog.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | blog 15 | 19 | 20 | -------------------------------------------------------------------------------- /packages/v2/src/assets/folder.svg: -------------------------------------------------------------------------------- 1 | 3 | Folder 4 | 5 | -------------------------------------------------------------------------------- /packages/v2/src/assets/github.svg: -------------------------------------------------------------------------------- 1 | GitHub 2 | -------------------------------------------------------------------------------- /packages/v2/src/assets/joplin-vscode-plugin-cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rxliuli/site/b281f05e69b0d562c20e4c040be1e4d04ce90b72/packages/v2/src/assets/joplin-vscode-plugin-cover.png -------------------------------------------------------------------------------- /packages/v2/src/assets/joplin-vscode-plugin-cover.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rxliuli/site/b281f05e69b0d562c20e4c040be1e4d04ce90b72/packages/v2/src/assets/joplin-vscode-plugin-cover.webp -------------------------------------------------------------------------------- /packages/v2/src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 92 | -------------------------------------------------------------------------------- /packages/v2/src/assets/open.svg: -------------------------------------------------------------------------------- 1 | External Link 2 | -------------------------------------------------------------------------------- /packages/v2/src/assets/pixiv.svg: -------------------------------------------------------------------------------- 1 | 3 | Pixiv 4 | 7 | -------------------------------------------------------------------------------- /packages/v2/src/assets/telegram.svg: -------------------------------------------------------------------------------- 1 | Telegram 2 | -------------------------------------------------------------------------------- /packages/v2/src/assets/twitter.svg: -------------------------------------------------------------------------------- 1 | 3 | Twitter 4 | 6 | 7 | -------------------------------------------------------------------------------- /packages/v2/src/components/LeftLinks.module.css: -------------------------------------------------------------------------------- 1 | @media (max-width: 768px) { 2 | .LeftLinks { 3 | display: none; 4 | } 5 | } 6 | 7 | .LeftLinks { 8 | position: fixed; 9 | left: 0; 10 | bottom: 0; 11 | margin-left: 40px; 12 | } 13 | 14 | .LeftLinks:after { 15 | content: ''; 16 | display: block; 17 | width: 1px; 18 | height: 90px; 19 | margin: 30px auto 0; 20 | background-color: var(--light-slate); 21 | } 22 | 23 | .LeftLinks ul { 24 | list-style: none; 25 | margin: 0; 26 | padding: 0; 27 | } 28 | -------------------------------------------------------------------------------- /packages/v2/src/components/LeftLinks.tsx: -------------------------------------------------------------------------------- 1 | import css from './LeftLinks.module.css' 2 | import blog from '../assets/blog.svg?raw' 3 | import telegram from '../assets/telegram.svg?raw' 4 | import github from '../assets/github.svg?raw' 5 | import twitter from '../assets/twitter.svg?raw' 6 | import pixiv from '../assets/pixiv.svg?raw' 7 | import { LinkIcon, LinkIconItem } from './LinkIcon' 8 | import { useInView } from '../hooks/useInView' 9 | import transition from '../components/TransitionGroup.module.css' 10 | import classNames from 'classnames' 11 | 12 | const links: LinkIconItem[] = [ 13 | { title: 'github', link: 'https://github.com/rxliuli', icon: github }, 14 | { title: 'twitter', link: 'https://twitter.com/rxliuli', icon: twitter }, 15 | { title: 'telegram', link: 'https://t.me/rxliuli', icon: telegram }, 16 | { title: 'blog', link: 'https://blog.rxliuli.com/', icon: blog }, 17 | { title: 'pixiv', link: 'https://www.pixiv.net/users/16247572', icon: pixiv }, 18 | ] 19 | 20 | export const LeftLinks = () => { 21 | const { ref, inView } = useInView({ timeout: 1000 }) 22 | return ( 23 |
33 |
    34 | {links.map((item) => ( 35 |
  • 36 | 37 |
  • 38 | ))} 39 |
40 |
41 | ) 42 | } 43 | 44 | -------------------------------------------------------------------------------- /packages/v2/src/components/LinkButton.module.css: -------------------------------------------------------------------------------- 1 | .LinkButton { 2 | color: var(--green); 3 | text-decoration: none; 4 | display: inline-block; 5 | margin-top: 50px; 6 | padding: 20px 28px; 7 | border: solid 1px var(--green); 8 | border-radius: 5px; 9 | transition: all 0.3s; 10 | } 11 | 12 | .LinkButton:hover { 13 | background-color: #133040; 14 | } 15 | -------------------------------------------------------------------------------- /packages/v2/src/components/LinkButton.tsx: -------------------------------------------------------------------------------- 1 | import { FunctionalComponent, JSX } from 'preact' 2 | import css from './LinkButton.module.css' 3 | 4 | export const LinkButton: FunctionalComponent<{ href: string; style?: JSX.CSSProperties }> = (props) => { 5 | return ( 6 | 7 | {props.children} 8 | 9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /packages/v2/src/components/LinkIcon.module.css: -------------------------------------------------------------------------------- 1 | .LinkIcon { 2 | display: block; 3 | padding: 10px; 4 | transition: all 0.3s; 5 | color: var(--light-slate); 6 | } 7 | 8 | .LinkIcon:hover { 9 | color: var(--green); 10 | transform: translateY(-4px); 11 | } 12 | 13 | .LinkIcon svg { 14 | width: 20px; 15 | height: 20px; 16 | } 17 | -------------------------------------------------------------------------------- /packages/v2/src/components/LinkIcon.tsx: -------------------------------------------------------------------------------- 1 | import { FunctionalComponent } from 'preact' 2 | import css from './LinkIcon.module.css' 3 | 4 | export interface LinkIconItem { 5 | title: string 6 | link: string 7 | icon: string 8 | } 9 | 10 | export const LinkIcon: FunctionalComponent<{ item: LinkIconItem }> = ({ item }) => { 11 | return 12 | } 13 | -------------------------------------------------------------------------------- /packages/v2/src/components/RightMail.module.css: -------------------------------------------------------------------------------- 1 | .RightMail { 2 | position: fixed; 3 | right: 0; 4 | bottom: 0; 5 | margin-right: 40px; 6 | } 7 | 8 | .RightMail:after { 9 | content: ''; 10 | display: block; 11 | width: 1px; 12 | height: 90px; 13 | margin: 30px auto 0; 14 | background-color: var(--light-slate); 15 | } 16 | 17 | .RightMail a { 18 | text-decoration: none; 19 | transition: all 0.3s; 20 | color: var(--light-slate); 21 | writing-mode: vertical-lr; 22 | } 23 | 24 | .RightMail a:hover { 25 | color: var(--green); 26 | transform: translateY(-4px); 27 | } 28 | 29 | @media (max-width: 768px) { 30 | .RightMail { 31 | display: none; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/v2/src/components/RightMail.tsx: -------------------------------------------------------------------------------- 1 | import css from './RightMail.module.css' 2 | import transition from '../components/TransitionGroup.module.css' 3 | import classNames from 'classnames' 4 | import { useInView } from '../hooks/useInView' 5 | 6 | export const RightMail = () => { 7 | const { ref, inView } = useInView({ timeout: 1000 }) 8 | return ( 9 | 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /packages/v2/src/components/Tabs.module.css: -------------------------------------------------------------------------------- 1 | .tab { 2 | display: grid; 3 | grid-template-columns: auto 1fr; 4 | gap: 40px; 5 | } 6 | 7 | .tab > nav { 8 | position: relative; 9 | } 10 | 11 | .tab > nav > ul, 12 | .tab > ul { 13 | list-style: none; 14 | padding: 0; 15 | margin: 0; 16 | } 17 | 18 | .tab > nav button { 19 | border: none; 20 | background-color: transparent; 21 | color: var(--light-slate); 22 | display: block; 23 | width: 100%; 24 | height: 42px; 25 | text-align: left; 26 | padding: 0 20px; 27 | border-left: 2px solid #233554; 28 | transition: var(--transition); 29 | cursor: pointer; 30 | } 31 | 32 | .tab nav button:hover { 33 | color: var(--green); 34 | background-color: #112240; 35 | } 36 | 37 | .tab nav button.active { 38 | color: var(--green); 39 | border-left: 2px solid var(--green); 40 | } 41 | 42 | .bar { 43 | content: ''; 44 | display: block; 45 | position: absolute; 46 | background-color: var(--green); 47 | transition: var(--transition); 48 | transition-property: left, top; 49 | } 50 | 51 | @media (max-width: 768px) { 52 | .tab { 53 | grid-template-rows: auto 1fr; 54 | grid-template-columns: 1fr; 55 | } 56 | .tab > nav > ul { 57 | width: 100%; 58 | overflow-x: auto; 59 | display: flex; 60 | } 61 | /* 使用权重更高的 css 强制设置避免 github pages 返回的 css 有奇怪的问题 */ 62 | .tab > nav > ul button { 63 | border-left: none; 64 | border-bottom: 2px solid #233554; 65 | } 66 | 67 | .tab > nav > ul button.active { 68 | border-left: none; 69 | border-bottom: 2px solid var(--green); 70 | } 71 | .bar { 72 | bottom: 0; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /packages/v2/src/components/Tabs.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames' 2 | import { ComponentChildren, FunctionComponent, JSX, Key, VNode } from 'preact' 3 | import { useEffect, useRef, useState } from 'preact/hooks' 4 | import css from './Tabs.module.css' 5 | 6 | interface TabItemProps { 7 | title: string 8 | children: ComponentChildren 9 | } 10 | 11 | const TabItem: FunctionComponent = (props: TabItemProps) => { 12 | return <>{props.children} 13 | } 14 | 15 | export interface TabsProps { 16 | active: K 17 | onChange: (key: K) => void 18 | children: VNode | VNode[] 19 | } 20 | 21 | export function _Tabs(props: TabsProps) { 22 | const children = Array.isArray(props.children) 23 | ? props.children 24 | : [props.children] 25 | const $list = useRef(null) 26 | const [barStyle, setBarStyle] = useState() 27 | const aligh = useRef<'horizontal' | 'portrait'>( 28 | window.innerWidth > 768 ? 'portrait' : 'horizontal', 29 | ) 30 | 31 | function calcStyle() { 32 | if (!$list.current) { 33 | return 34 | } 35 | const i = children.findIndex((item) => item.key === props.active) 36 | if (i === -1) { 37 | return 38 | } 39 | const $el = [...$list.current.children][i] as HTMLLIElement 40 | if (aligh.current === 'portrait') { 41 | console.log('$el.portrait: ', $el.offsetTop, $el.offsetHeight) 42 | setBarStyle({ 43 | top: $el.offsetTop, 44 | width: 2, 45 | height: $el.offsetHeight, 46 | }) 47 | } else if (aligh.current === 'horizontal') { 48 | console.log('$el.horizontal: ', $el.offsetLeft, $el.offsetWidth) 49 | setBarStyle({ 50 | left: $el.offsetLeft, 51 | width: $el.offsetWidth, 52 | height: 2, 53 | }) 54 | } 55 | } 56 | 57 | useEffect(calcStyle, [children, props.active, $list]) 58 | useEffect(() => { 59 | const listener = () => { 60 | aligh.current = window.innerWidth > 768 ? 'portrait' : 'horizontal' 61 | calcStyle() 62 | } 63 | window.addEventListener('resize', listener) 64 | return () => window.removeEventListener('resize', listener) 65 | }, [props.active]) 66 | 67 | return ( 68 |
69 | 85 |
    86 | {children.map((item) => ( 87 |
  • 93 | {item} 94 |
  • 95 | ))} 96 |
97 |
98 | ) 99 | } 100 | 101 | export const Tabs = Object.assign(_Tabs, { 102 | Item: TabItem, 103 | }) 104 | -------------------------------------------------------------------------------- /packages/v2/src/components/TransitionGroup.module.css: -------------------------------------------------------------------------------- 1 | .fadeupEnter > * { 2 | --delay: 100ms; 3 | opacity: 0.01; 4 | transform: translateY(20px); 5 | transition: opacity 300ms ease-in, transform 300ms ease-in; 6 | } 7 | 8 | .fadeupEnterActive > * { 9 | opacity: 1; 10 | transform: translateY(0px); 11 | transition: opacity 300ms ease-in, transform 300ms ease-in; 12 | } 13 | 14 | .fadedownEnter > * { 15 | opacity: 0.01; 16 | transform: translateY(-20px); 17 | transition: opacity 300ms var(--easing), transform 300ms var(--easing); 18 | } 19 | 20 | .fadedownEnterActive > * { 21 | opacity: 1; 22 | transform: translateY(0px); 23 | transition: opacity 300ms var(--easing), transform 300ms var(--easing); 24 | } 25 | 26 | .fadeEnter { 27 | opacity: 0; 28 | } 29 | .fadeEnterActive { 30 | opacity: 1; 31 | transition: opacity 300ms var(--easing); 32 | } 33 | -------------------------------------------------------------------------------- /packages/v2/src/components/TransitionGroup.tsx: -------------------------------------------------------------------------------- 1 | import { VNode } from 'preact'; 2 | import { cloneElement } from 'preact/compat' 3 | 4 | export function TransitionGroup(props: { children: VNode | VNode[]; timeout?: number }) { 5 | const children = (Array.isArray(props.children) ? props.children : [props.children]) as unknown as VNode<{ 6 | style?: JSX.CSSProperties 7 | children: VNode 8 | }>[] 9 | return ( 10 | <> 11 | {children.map((item, i) => 12 | cloneElement( 13 | item, 14 | { 15 | ...item.props, 16 | key: i, 17 | style: { 18 | transitionDelay: `${(props.timeout ?? 0) + i * 100}ms`, 19 | ...item.props.style, 20 | }, 21 | }, 22 | item.props.children, 23 | ), 24 | )} 25 | 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /packages/v2/src/constants/__tests__/i18n.test.ts: -------------------------------------------------------------------------------- 1 | // @vitest-environment happy-dom 2 | import { it, expect } from 'vitest' 3 | import { createI18n, locales } from '../i18n' 4 | 5 | it('should be able to import i18n', () => { 6 | const { t } = createI18n(locales, 'en-US') 7 | expect(t('example')).eq('example') 8 | }) 9 | -------------------------------------------------------------------------------- /packages/v2/src/constants/i18n.ts: -------------------------------------------------------------------------------- 1 | import enUS from '../i18n/en-US.json' 2 | import zhCN from '../i18n/zh-CN.json' 3 | import jaJP from '../i18n/ja-JP.json' 4 | import { TranslateType } from '../i18n' 5 | 6 | export const locales = { 7 | 'en-US': enUS, 8 | 'zh-CN': zhCN, 9 | 'ja-JP': jaJP, 10 | } as const 11 | 12 | const DefaultLang = 'en-US' 13 | export function getLanguage(): keyof typeof locales { 14 | return 'zh-CN' 15 | const lang = localStorage.getItem('language') ?? navigator.language 16 | if (lang in locales) { 17 | return lang as keyof typeof locales 18 | } 19 | return DefaultLang 20 | } 21 | 22 | export function createI18n>>( 23 | locales: T, 24 | lang: keyof T, 25 | ) { 26 | return { 27 | get lang() { 28 | return getLanguage() 29 | }, 30 | setLang(lang: keyof T) { 31 | localStorage.setItem('language', lang as string) 32 | }, 33 | t( 34 | ...args: TranslateType[K]['params'] 35 | ): string { 36 | const [key, params] = args as any 37 | const template = ((locales[lang] as any)[key] ?? 38 | (locales[DefaultLang] as any)[key]) as string | undefined 39 | if (!template) { 40 | throw new Error(`Cannot find template for key: ${key}`) 41 | } 42 | if (!params) { 43 | return template 44 | } 45 | return template.replace(/\{\{(\w+)\}\}/g, (match, key) => { 46 | return params[key] !== undefined ? String(params[key]) : match 47 | }) 48 | }, 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/v2/src/constants/useI18n.tsx: -------------------------------------------------------------------------------- 1 | import { createContext } from 'preact' 2 | import { createI18n, getLanguage, locales } from './i18n' 3 | import { useContext, useEffect, useMemo, useState } from 'preact/hooks' 4 | import React from 'preact/compat' 5 | 6 | const LocaleContext = createContext(createI18n(locales, getLanguage())) 7 | 8 | export function useLocale() { 9 | const context = useContext(LocaleContext) 10 | if (!context) { 11 | throw new Error('useLocale must be used within a LocaleProvider') 12 | } 13 | return context 14 | } 15 | 16 | export const LocaleProvider: React.FC = (props) => { 17 | const [lang, setLang] = useState(getLanguage()) 18 | const context = useMemo(() => { 19 | const r = createI18n(locales, lang) 20 | return { 21 | ...r, 22 | setLang(lang: keyof typeof locales) { 23 | setLang(lang) 24 | r.setLang(lang) 25 | }, 26 | } 27 | }, [lang]) 28 | useEffect(() => { 29 | // @ts-expect-error 30 | globalThis.context = context 31 | }) 32 | return ( 33 | 34 | {props.children} 35 | 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /packages/v2/src/hooks/useInView.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from 'preact/hooks' 2 | 3 | interface Options extends IntersectionObserverInit { 4 | timeout?: number 5 | } 6 | 7 | export function useInView(options?: Options) { 8 | const { timeout, ...other }: Options = { threshold: 0.5, ...options } 9 | const html = useRef(null) 10 | const [inView, setInView] = useState(false) 11 | useEffect(() => { 12 | let n: any 13 | const observer = new IntersectionObserver((entries) => { 14 | const intersectionRatio = entries[0].intersectionRatio 15 | // console.log('entries: ', entries, intersectionRatio, intersectionRatio > 0 && intersectionRatio <= 1) 16 | if (intersectionRatio > 0 && intersectionRatio <= 1) { 17 | if (timeout === undefined) { 18 | setInView(true) 19 | } else { 20 | n = setTimeout(() => setInView(true), timeout) 21 | } 22 | observer.unobserve(html.current!) 23 | } 24 | }, other) 25 | observer.observe(html.current!) 26 | return () => { 27 | observer.unobserve(html.current!) 28 | clearTimeout(n) 29 | } 30 | }, [timeout]) 31 | return { 32 | ref: html, 33 | inView, 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/v2/src/hooks/useWindowScroll.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'preact/hooks' 2 | 3 | export function useWindowScroll() { 4 | const [dir, setDir] = useState<'up' | 'down'>() 5 | const [scrollY, setScrollY] = useState(0) 6 | useEffect(() => { 7 | const listener = () => { 8 | const dir = scrollY > window.scrollY ? 'up' : 'down' 9 | setDir(dir) 10 | setScrollY(window.scrollY) 11 | } 12 | window.addEventListener('scroll', listener) 13 | return () => window.removeEventListener('scroll', listener) 14 | }, [scrollY]) 15 | return { dir, scrollY } 16 | } 17 | -------------------------------------------------------------------------------- /packages/v2/src/i18n/zh-CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "about.title": "关于", 3 | "experience.title": "经历", 4 | "work.title": "作品", 5 | "concat.title": "联系", 6 | "navbar.source": "源代码", 7 | "home.hello": "你好,吾辈名为", 8 | "home.name": "琉璃", 9 | "home.title": "吾辈基于 web 技术创造各种东西", 10 | "home.desc": "现在是一个前端开发工程师,喜欢折腾有趣的技术、二次元、开源和分享,目前专注于", 11 | "home.point": "前端工程化", 12 | "home.example": "看看 joplin-utils", 13 | "about.desc1": "你好,吾辈是 rxliuli,中文名是琉璃。吾辈曾经是一个 Java CURD 研发工程师,后面感觉现代前端日新月异,有许多新奇的东西,所以在 2019 年正式转换为前端工作。", 14 | "about.desc2": "快进到今天,吾辈现在在一个做着类似于 webos 东西的公司,可以花更多的时间来做一些长线的事情,像是各种前端工程化的基建,以及基于 web 的应用平台研发。", 15 | "about.desc3": "虽然公司使用了 vue3+typescript,但吾辈更喜欢 react,所以社区项目仍然继续使用它开发。虽然公司使用了 vue3+typescript,但吾辈更喜欢 react,所以社区项目仍然继续使用它开发。", 16 | "about.recent": "以下是最近一直在使用的一些技术:", 17 | "about.picture": "我的照片", 18 | "experience.pinefield.name": "北京奇岱松", 19 | "experience.pinefield.jobTitle": "前端开发工程师", 20 | "experience.pinefield.item1": "前端工程化和基础建设,包括各种 cli、插件和 library 等", 21 | "experience.pinefield.item2": "webos 子应用系统的设计实现,协助其他人开发子应用,多线程模型、通信、系统 api", 22 | "experience.pinefield.item3": "实践和确定了众多技术的可行性,大型 monorepo、vite、esbuild、vue3、pnpm", 23 | "experience.ibingli.name": "广州秉理", 24 | "experience.ibingli.jobTitle": "前端开发工程师", 25 | "experience.ibingli.item1": "前端项目的基建和大型重构,保证项目在超过 4w 行代码、20 个模块时仍然保持可维护性", 26 | "experience.ibingli.item2": "lerna 的具体实践和落地,让前端工程演进为 monorepo 形式,支持了上面的大型前端项目", 27 | "experience.ibingli.item3": "项目规范的编写和推进,使用 `eslint/prettier/git hooks/code review` 的形式校验代码", 28 | "experience.ibingli.item4": "在前端日志方面做出探索,实现了在部署到医院内网之后前端可以通过日志查找问题", 29 | "experience.ibingli.item5": "领域限定硬件的调研/第三方服务的接入", 30 | "experience.zx-soft.name": "广州智晓", 31 | "experience.zx-soft.jobTitle": "Java 后端开发", 32 | "experience.zx-soft.item1": "学习并在后端团队内推广 vue 这种现代前端框架", 33 | "experience.zx-soft.item2": "使用 Java Spring Boot 开发各种后台管理系统", 34 | "experience.zx-soft.item3": "认识到了很好的同事,至今仍然在通过邮件继续联系", 35 | "experience.time.now": "至今", 36 | "work.primary.title": "特色项目", 37 | "work.other.title": "其他值得注意的项目", 38 | "work.other.viewList": "查看列表", 39 | "work.liuli-tools.desc": "一系列与开发者相关的模块,包括通用函数库、cli 以及 esbuild/vite 的插件,旨在解决开发中遇到的各种通用问题。", 40 | "work.new-project.desc": "这是一个 vscode 可视化创建项目的插件,尝试在 vscode 中提供类似于 jetbrains ide 的创建项目的面板。目前仅支持使用 vite/create-react-app/angular/svelte 创建项目,但支持自定义生成器。", 41 | "work.tsx.desc": "esbuild-kit/tsx 的 vscode 集成,可以运行任何 js/ts/jsx/tsx 代码,支持 esm/cjs 模块。", 42 | "work.vite-integrated.desc": "Vite 脚手架在 JetBrains IDE 中的集成,主要负责直接使用 IDE 的引导面板创建一个项目。", 43 | "work.react-router.desc": "封装 react-router 为集中式的 js 路由配置,组件仅暴露必要的 props,并且默认支持在 react 组件外使用路由。", 44 | "work.magia.desc": "一个复刻的可爱的动画网站(魔法少女小圆),源项目是 https://magia.cyris.moe/。", 45 | "work.saki.desc": "想知道基于 golang 编写 cli 能够提高多少性能,所以尝试使用 golang 编写了这个 cli 应用。", 46 | "work.mdbook.desc": "一个基于 pandoc 的 markdown => epub 的构建工具,支持使用配置而非命令行参数的形式指定章节文件(也是后面整理和发布小说 epub 版本的前提)", 47 | "work.tts.title": "魔法少女小圆 飞向星空", 48 | "work.tts.desc": "“丘比承诺说人类总有一天也能到达那遥远的星空。但它们很明智地没有说出来,人类将会在那里遇到什么。”—— 引言", 49 | "work.tts.topic1": "二次元", 50 | "work.tts.topic2": "同人", 51 | "work.tts.topic3": "硬科幻", 52 | "concat.next": "下一步是什么?", 53 | "concat.subtitle": "保持联系", 54 | "concat.desc": "虽然吾辈目前没有寻找任何新的机会,但吾辈的邮箱始终是打开的。无论您有任何问题或只是想打个招呼,不管是二次元还是开发者,吾辈都会尽力回复您!", 55 | "concat.hello": "打个招呼", 56 | "concat.footer": "由 bchiang7 设计,但吾辈重新实现了它。", 57 | "example": "示例" 58 | } 59 | -------------------------------------------------------------------------------- /packages/v2/src/index.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | background-color: #0a192f; 6 | color: var(--light-slate); 7 | scroll-behavior: smooth; 8 | } 9 | 10 | * { 11 | box-sizing: border-box; 12 | } 13 | 14 | :root { 15 | --green: rgb(100, 255, 218); 16 | --light-slate: #8892b0; 17 | --lightest-slate: #ccd6f6; 18 | --transition: all 0.25s cubic-bezier(0.645, 0.045, 0.355, 1); 19 | --background: #0a192f; 20 | --easing: cubic-bezier(0.645, 0.045, 0.355, 1); 21 | } 22 | 23 | /* 滚动槽 */ 24 | body::-webkit-scrollbar { 25 | width: 12px; 26 | } 27 | body::-webkit-scrollbar-track { 28 | background-color: var(--background); 29 | } 30 | /* 滚动条滑块 */ 31 | body::-webkit-scrollbar-thumb { 32 | background: #495670; 33 | border: 3px solid var(--background); 34 | border-radius: 10px; 35 | /*-webkit-box-shadow: inset 0 0 10px #495670;*/ 36 | } 37 | -------------------------------------------------------------------------------- /packages/v2/src/main.tsx: -------------------------------------------------------------------------------- 1 | // if (import.meta.env.DEV) { 2 | // // @ts-ignore 3 | // await import('preact/debug') 4 | // } 5 | 6 | import { render } from 'preact' 7 | import { App } from './App' 8 | import './index.css' 9 | 10 | render(, document.getElementById('app')!) 11 | -------------------------------------------------------------------------------- /packages/v2/src/preact.d.ts: -------------------------------------------------------------------------------- 1 | import JSX = preact.JSX 2 | -------------------------------------------------------------------------------- /packages/v2/src/views/AboutView.module.css: -------------------------------------------------------------------------------- 1 | .AboutView { 2 | max-width: 900px; 3 | margin: auto; 4 | display: grid; 5 | grid-template-rows: auto 1fr; 6 | grid-template-columns: 1fr 1fr; 7 | grid-template-areas: 8 | 'header header' 9 | '. .'; 10 | grid-column-gap: 90px; 11 | padding: 100px 0; 12 | } 13 | 14 | .header { 15 | grid-area: header; 16 | font-size: 32px; 17 | margin: 10px 0 40px; 18 | display: flex; 19 | align-items: center; 20 | color: var(--lightest-slate); 21 | white-space: nowrap; 22 | } 23 | 24 | .header span { 25 | font-size: 20px; 26 | color: var(--green); 27 | margin-right: 10px; 28 | } 29 | .header:after { 30 | content: ''; 31 | display: block; 32 | width: 300px; 33 | height: 1px; 34 | margin-left: 20px; 35 | background: var(--lightest-slate); 36 | } 37 | 38 | .tab { 39 | font-size: 20px; 40 | } 41 | 42 | .skills { 43 | display: grid; 44 | grid-template-columns: 1fr 1fr; 45 | list-style: none; 46 | padding-left: 0; 47 | gap: 10px; 48 | } 49 | 50 | .skills li { 51 | display: flex; 52 | align-items: center; 53 | } 54 | .skills li:before { 55 | content: '▹'; 56 | color: var(--green); 57 | font-size: 12px; 58 | display: inline-block; 59 | width: 20px; 60 | } 61 | 62 | .avatar { 63 | position: relative; 64 | width: 300px; 65 | height: 300px; 66 | } 67 | 68 | .avatar img { 69 | position: relative; 70 | border-radius: 5px; 71 | width: 100%; 72 | height: 100%; 73 | } 74 | 75 | .avatar:after { 76 | content: ''; 77 | display: block; 78 | position: absolute; 79 | width: 100%; 80 | height: 100%; 81 | left: 0; 82 | top: 0; 83 | border-radius: 5px; 84 | background-color: var(--green); 85 | opacity: 0.5; 86 | transition: var(--transition); 87 | } 88 | .avatar:hover:after { 89 | opacity: 0; 90 | } 91 | 92 | .avatar:before { 93 | content: ''; 94 | display: block; 95 | position: absolute; 96 | width: 100%; 97 | height: 100%; 98 | left: 20px; 99 | top: 20px; 100 | border: solid 2px var(--green); 101 | border-radius: 5px; 102 | transition: var(--transition); 103 | } 104 | 105 | .avatar:hover:before { 106 | transform: translate(-10px, -10px); 107 | } 108 | 109 | @media (max-width: 768px) { 110 | .AboutView { 111 | display: block; 112 | } 113 | .header:after { 114 | width: 100%; 115 | } 116 | .avatar { 117 | margin-top: 50px; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /packages/v2/src/views/AboutView.tsx: -------------------------------------------------------------------------------- 1 | import css from './AboutView.module.css' 2 | import avatar from '../assets/avatar.webp' 3 | import { FunctionalComponent, JSX } from 'preact' 4 | import classNames from 'classnames' 5 | import transition from '../components/TransitionGroup.module.css' 6 | import { useInView } from '../hooks/useInView' 7 | import { useLocale } from '../constants/useI18n' 8 | 9 | export const Header: FunctionalComponent<{ 10 | order: string 11 | style?: JSX.CSSProperties 12 | }> = (props) => { 13 | return ( 14 |

15 | {props.order} 16 | {props.children} 17 |

18 | ) 19 | } 20 | 21 | const skillList = ['TypeScript', 'React', 'Vue3', 'Golang', 'DX'] 22 | 23 | export const AboutView = () => { 24 | const { ref, inView } = useInView() 25 | const { t } = useLocale() 26 | return ( 27 |
38 |
{t('about.title')}
39 |
40 |

{t('about.desc1')}

41 |

{t('about.desc2')}

42 |

{t('about.desc3')}

43 |

{t('about.recent')}

44 |
    45 | {skillList.map((item) => ( 46 |
  • {item}
  • 47 | ))} 48 |
49 |
50 |
51 | {t('about.picture')} 52 |
53 |
54 | ) 55 | } 56 | -------------------------------------------------------------------------------- /packages/v2/src/views/ConcatView.module.css: -------------------------------------------------------------------------------- 1 | .ConcatView { 2 | max-width: 600px; 3 | padding: 100px 0; 4 | margin: auto auto 100px; 5 | text-align: center; 6 | } 7 | 8 | .ConcatView > header > h2:first-child { 9 | font-size: 16px; 10 | margin: 10px 0 20px; 11 | color: var(--green); 12 | } 13 | 14 | .ConcatView > header > h2:last-child { 15 | font-size: 60px; 16 | color: var(--lightest-slate); 17 | margin: 0 auto 10px; 18 | } 19 | -------------------------------------------------------------------------------- /packages/v2/src/views/ConcatView.tsx: -------------------------------------------------------------------------------- 1 | import { LinkButton } from '../components/LinkButton' 2 | import { useInView } from '../hooks/useInView' 3 | import css from './ConcatView.module.css' 4 | import transition from '../components/TransitionGroup.module.css' 5 | import classNames from 'classnames' 6 | import { useLocale } from '../constants/useI18n' 7 | 8 | export const ConcatView = () => { 9 | const { ref, inView } = useInView() 10 | const { t } = useLocale() 11 | return ( 12 |
23 |
24 |

25 | 04. {t('concat.next')} 26 |

27 |

{t('concat.subtitle')}

28 |
29 |
{t('concat.desc')}
30 | 31 | {t('concat.hello')} 32 | 33 |
34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /packages/v2/src/views/ExperienceView.module.css: -------------------------------------------------------------------------------- 1 | .ExperienceView { 2 | max-width: 700px; 3 | margin: auto; 4 | padding: 100px 0; 5 | } 6 | 7 | .content h3 { 8 | font-size: 22px; 9 | color: var(--lightest-slate); 10 | margin-bottom: 2px; 11 | margin-top: 0; 12 | } 13 | 14 | .content h3 a { 15 | color: var(--green); 16 | text-decoration: none; 17 | } 18 | 19 | .content p { 20 | margin-top: 0; 21 | } 22 | 23 | .content ul { 24 | list-style: none; 25 | padding-left: 0; 26 | } 27 | 28 | .content ul li { 29 | position: relative; 30 | padding-left: 30px; 31 | margin-bottom: 10px; 32 | } 33 | 34 | .content ul li:before { 35 | content: '▹'; 36 | color: var(--green); 37 | position: absolute; 38 | left: 0; 39 | width: 20px; 40 | } 41 | -------------------------------------------------------------------------------- /packages/v2/src/views/FooterView.module.css: -------------------------------------------------------------------------------- 1 | .FooterView { 2 | padding: 15px; 3 | text-align: center; 4 | } 5 | 6 | .FooterView a { 7 | text-decoration: none; 8 | color: var(--green); 9 | } 10 | -------------------------------------------------------------------------------- /packages/v2/src/views/FooterView.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames' 2 | import { useInView } from '../hooks/useInView' 3 | import css from './FooterView.module.css' 4 | import transition from '../components/TransitionGroup.module.css' 5 | import { useLocale } from '../constants/useI18n' 6 | 7 | export const FooterView = () => { 8 | const { ref, inView } = useInView() 9 | const { t } = useLocale() 10 | return ( 11 |
22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /packages/v2/src/views/HomeView.module.css: -------------------------------------------------------------------------------- 1 | .HomeView { 2 | height: 100vh; 3 | display: flex; 4 | align-items: center; 5 | } 6 | 7 | .content { 8 | max-width: 1000px; 9 | margin: auto; 10 | transition-delay: 500ms; 11 | } 12 | 13 | .content > h1 { 14 | font-size: 16px; 15 | color: var(--green); 16 | } 17 | 18 | .content > h2 { 19 | color: #ccd6f6; 20 | font-size: clamp(40px, 8vw, 80px); 21 | margin: 0; 22 | } 23 | 24 | .content > h2:nth-of-type(2) { 25 | color: var(--light-slate); 26 | } 27 | 28 | .content a { 29 | color: var(--green); 30 | text-decoration: none; 31 | } 32 | -------------------------------------------------------------------------------- /packages/v2/src/views/HomeView.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames' 2 | import { LinkButton } from '../components/LinkButton' 3 | import { TransitionGroup } from '../components/TransitionGroup' 4 | import { useInView } from '../hooks/useInView' 5 | import css from './HomeView.module.css' 6 | import transition from '../components/TransitionGroup.module.css' 7 | import { useWindowScroll } from '../hooks/useWindowScroll' 8 | import { useLocale } from '../constants/useI18n' 9 | 10 | export const HomeView = () => { 11 | const { ref, inView } = useInView({ threshold: 0.1 }) 12 | const { dir } = useWindowScroll() 13 | const { t } = useLocale() 14 | return ( 15 |
16 |
25 | 26 |

{t('home.hello')}

27 |

{t('home.name')}

28 |

{t('home.title')}

29 |

30 | {t('home.desc')}{' '} 31 | 35 | {t('home.point')} 36 | {' '} 37 | 。 38 |

39 | 40 | {t('home.example')} 41 | 42 |
43 |
44 |
45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /packages/v2/src/views/works/joplin-utils.en-US.md: -------------------------------------------------------------------------------- 1 | Community tools based on Joplin. The [joplin-vscode-plugin](https://marketplace.visualstudio.com/items?itemName=rxliuli.joplin-vscode-plugin) offers functionality to manage Joplin notes within VSCode, leveraging the powerful editor and its ecosystem of VSCode. [joplin-blog](https://www.npmjs.com/package/joplin-blog) publishes notes with specific tags as online websites, allowing users to choose between blog or wiki formats. There are also some developer-related toolsets, including [joplin-api](https://www.npmjs.com/package/joplin-api) and [joplin-plugin-cli](https://www.npmjs.com/package/joplin-plugin-cli). -------------------------------------------------------------------------------- /packages/v2/src/views/works/joplin-utils.ja-JP.md: -------------------------------------------------------------------------------- 1 | Joplin の周辺コミュニティツールに基づいています。[joplin-vscode-plugin](https://marketplace.visualstudio.com/items?itemName=rxliuli.joplin-vscode-plugin) は、vscode内でJoplinのノートを管理する機能を提供しており、既存のvscodeの強力なエディタとそのエコシステムと組み合わせて使用されます。[joplin-blog](https://www.npmjs.com/package/joplin-blog) は、指定されたタグのノートをオンラインのウェブサイトとして公開し、blogまたはwikiの形式を選択することができます。開発者向けのツールセットもあり、[joplin-api](https://www.npmjs.com/package/joplin-api)/[joplin-plugin-cli](https://www.npmjs.com/package/joplin-plugin-cli) を含んでいます。 2 | -------------------------------------------------------------------------------- /packages/v2/src/views/works/joplin-utils.zh-CN.md: -------------------------------------------------------------------------------- 1 | 基于 Joplin 的周边社区工具。[joplin-vscode-plugin](https://marketplace.visualstudio.com/items?itemName=rxliuli.joplin-vscode-plugin) 提供在 vscode 中管理 joplin 笔记的功能,结合 vscode 现有的强大编辑器及其生态。[joplin-blog](https://www.npmjs.com/package/joplin-blog) 将指定标签的笔记发布为在线网站,可以选择 blog 或 wiki 的形式。还有开发者相关的一些工具集,包括 [joplin-api](https://www.npmjs.com/package/joplin-api)/[joplin-plugin-cli](https://www.npmjs.com/package/joplin-plugin-cli)。 -------------------------------------------------------------------------------- /packages/v2/src/views/works/mami.en-US.md: -------------------------------------------------------------------------------- 1 | Mami is a conversion tool that can connect various markdown-based frameworks and tools. It can convert data from one tool to another, which is very helpful for cross-application migration and multi-platform publishing. Currently, it supports joplin, obsidian, hexo, hugo, and raw, with plans to support docsify and vuepress in the future. -------------------------------------------------------------------------------- /packages/v2/src/views/works/mami.ja-JP.md: -------------------------------------------------------------------------------- 1 | mami は変換ツールであり、異なる markdown ベースのフレームワークやツールに接続することができます。一つのツールのデータを別のツールに変換することができ、アプリ間の移行や複数のプラットフォームでの公開に非常に役立ちます。現在、joplin/obsidian/hexo/hugo/raw/docsify/vuepress がサポートされています。 2 | -------------------------------------------------------------------------------- /packages/v2/src/views/works/mami.zh-CN.md: -------------------------------------------------------------------------------- 1 | mami 是一个转换工具,可以连接不同的基于 markdown 框架和工具,能将一种工具的数据转换到另一种工具,这对于跨应用迁移以及多平台发布很有帮助,目前已支持 joplin/obsidian/hexo/hugo/raw/docsify/vuepress。 -------------------------------------------------------------------------------- /packages/v2/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '*.md' { 4 | export const html: string 5 | } 6 | -------------------------------------------------------------------------------- /packages/v2/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "skipDefaultLibCheck": true, 9 | "esModuleInterop": false, 10 | "allowSyntheticDefaultImports": true, 11 | "strict": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "module": "ESNext", 14 | "moduleResolution": "Node", 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "noEmit": true, 18 | "jsx": "preserve", 19 | "jsxFactory": "h", 20 | "jsxFragmentFactory": "Fragment" 21 | }, 22 | "include": ["src"], 23 | "references": [{ "path": "./tsconfig.node.json" }] 24 | } 25 | -------------------------------------------------------------------------------- /packages/v2/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "esModuleInterop": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/v2/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import preact from '@preact/preset-vite' 3 | import { visualizer } from 'rollup-plugin-visualizer' 4 | import { Mode, plugin } from 'vite-plugin-markdown' 5 | import MarkdownIt from 'markdown-it' 6 | import { i18nextDtsGen } from '@liuli-util/rollup-plugin-i18next-dts-gen' 7 | 8 | /** 9 | * @link https://github.com/markdown-it/markdown-it/blob/master/docs/architecture.md#renderer 10 | * @param md 11 | */ 12 | function linkTarget(md: MarkdownIt) { 13 | // Remember old renderer, if overridden, or proxy to default renderer 14 | var defaultRender = 15 | md.renderer.rules.link_open || 16 | function (tokens, idx, options, env, self) { 17 | return self.renderToken(tokens, idx, options) 18 | } 19 | 20 | md.renderer.rules.link_open = function (tokens, idx, options, env, self) { 21 | // If you are sure other plugins can't add `target` - drop check below 22 | var aIndex = tokens[idx].attrIndex('target') 23 | 24 | if (aIndex < 0) { 25 | tokens[idx].attrPush(['target', '_blank']) // add new attribute 26 | } else { 27 | tokens[idx].attrs[aIndex][1] = '_blank' // replace value of existing attr 28 | } 29 | 30 | // pass token to default renderer. 31 | return defaultRender(tokens, idx, options, env, self) 32 | } 33 | } 34 | const md = new MarkdownIt() 35 | linkTarget(md) 36 | 37 | export default defineConfig({ 38 | base: '/v2/', 39 | plugins: [ 40 | preact(), 41 | plugin({ 42 | mode: [Mode.HTML], 43 | markdownIt: md, 44 | }), 45 | visualizer() as any, 46 | i18nextDtsGen({ 47 | dirs: ['./src/i18n'], 48 | }), 49 | ], 50 | }) 51 | -------------------------------------------------------------------------------- /packages/v3/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /packages/v3/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 琉璃的个人主页 v3 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/v3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "v3", 3 | "private": true, 4 | "type": "module", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "vite build", 8 | "preview": "vite preview", 9 | "deploy": "gh-pages -d dist --dotfiles -a" 10 | }, 11 | "devDependencies": { 12 | "@preact/preset-vite": "^2.5.0", 13 | "autoprefixer": "^10.4.17", 14 | "clsx": "^2.1.0", 15 | "gh-pages": "^3.2.3", 16 | "postcss": "^8.4.33", 17 | "preact": "^10.19.3", 18 | "preact-iso": "^2.3.2", 19 | "preact-render-to-string": "^6.3.1", 20 | "tailwindcss": "^3.4.1", 21 | "typescript": "^5.3.3", 22 | "vite": "^5.0.12" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/v3/public/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rxliuli/site/b281f05e69b0d562c20e4c040be1e4d04ce90b72/packages/v3/public/.nojekyll -------------------------------------------------------------------------------- /packages/v3/public/CNAME: -------------------------------------------------------------------------------- 1 | rxliuli.com -------------------------------------------------------------------------------- /packages/v3/public/ads.txt: -------------------------------------------------------------------------------- 1 | google.com, pub-9997051001110405, DIRECT, f08c47fec0942fa0 -------------------------------------------------------------------------------- /packages/v3/src/assets/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rxliuli/site/b281f05e69b0d562c20e4c040be1e4d04ce90b72/packages/v3/src/assets/avatar.jpg -------------------------------------------------------------------------------- /packages/v3/src/assets/banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rxliuli/site/b281f05e69b0d562c20e4c040be1e4d04ce90b72/packages/v3/src/assets/banner.jpg -------------------------------------------------------------------------------- /packages/v3/src/assets/blog.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/v3/src/assets/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/v3/src/assets/pixiv.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/v3/src/assets/telegram.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/v3/src/assets/twitter.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/v3/src/style.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | .icon { 6 | transition: all 0.3s; 7 | } 8 | 9 | .icon:hover { 10 | color: var(--green); 11 | transform: translateY(-4px); 12 | } 13 | 14 | .icon svg { 15 | @apply w-6 h-6; 16 | } 17 | -------------------------------------------------------------------------------- /packages/v3/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | } 9 | -------------------------------------------------------------------------------- /packages/v3/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "ESNext", 5 | "moduleResolution": "bundler", 6 | "noEmit": true, 7 | "allowJs": true, 8 | "checkJs": true, 9 | 10 | /* Preact Config */ 11 | "jsx": "react-jsx", 12 | "jsxImportSource": "preact", 13 | "skipLibCheck": true, 14 | "paths": { 15 | "react": ["./node_modules/preact/compat/"], 16 | "react-dom": ["./node_modules/preact/compat/"] 17 | } 18 | }, 19 | "include": ["node_modules/vite/client.d.ts", "**/*"] 20 | } 21 | -------------------------------------------------------------------------------- /packages/v3/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import preact from '@preact/preset-vite' 3 | import tailwindcss from 'tailwindcss' 4 | import autoprefixer from 'autoprefixer' 5 | 6 | export default defineConfig({ 7 | plugins: [ 8 | preact({ 9 | prerender: { 10 | enabled: true, 11 | renderTarget: '#app', 12 | }, 13 | }), 14 | ], 15 | css: { 16 | postcss: { 17 | plugins: [tailwindcss, autoprefixer], 18 | }, 19 | }, 20 | }) 21 | -------------------------------------------------------------------------------- /packages/v4/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | # Output 4 | .output 5 | .vercel 6 | /.svelte-kit 7 | /build 8 | 9 | # OS 10 | .DS_Store 11 | Thumbs.db 12 | 13 | # Env 14 | .env 15 | .env.* 16 | !.env.example 17 | !.env.test 18 | 19 | # Vite 20 | vite.config.js.timestamp-* 21 | vite.config.ts.timestamp-* 22 | -------------------------------------------------------------------------------- /packages/v4/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /packages/v4/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "printWidth": 120, 4 | "semi": false, 5 | "singleQuote": true, 6 | "trailingComma": "all", 7 | "arrowParens": "always", 8 | "endOfLine": "crlf", 9 | "plugins": ["prettier-plugin-svelte"], 10 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 11 | } 12 | -------------------------------------------------------------------------------- /packages/v4/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "i18n-ally.localesPaths": [ 3 | "src/lib/i18n" 4 | ] 5 | } -------------------------------------------------------------------------------- /packages/v4/README.md: -------------------------------------------------------------------------------- 1 | # create-svelte 2 | 3 | Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte). 4 | 5 | ## Creating a project 6 | 7 | If you're seeing this, you've probably already done this step. Congrats! 8 | 9 | ```bash 10 | # create a new project in the current directory 11 | npm create svelte@latest 12 | 13 | # create a new project in my-app 14 | npm create svelte@latest my-app 15 | ``` 16 | 17 | ## Developing 18 | 19 | Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: 20 | 21 | ```bash 22 | npm run dev 23 | 24 | # or start the server and open the app in a new browser tab 25 | npm run dev -- --open 26 | ``` 27 | 28 | ## Building 29 | 30 | To create a production version of your app: 31 | 32 | ```bash 33 | npm run build 34 | ``` 35 | 36 | You can preview the production build with `npm run preview`. 37 | 38 | > To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment. 39 | -------------------------------------------------------------------------------- /packages/v4/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://shadcn-svelte.com/schema.json", 3 | "style": "default", 4 | "tailwind": { 5 | "config": "tailwind.config.ts", 6 | "css": "src/app.css", 7 | "baseColor": "slate" 8 | }, 9 | "aliases": { 10 | "components": "$lib/components", 11 | "utils": "$lib/utils" 12 | }, 13 | "typescript": true 14 | } -------------------------------------------------------------------------------- /packages/v4/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js'; 2 | import ts from 'typescript-eslint'; 3 | import svelte from 'eslint-plugin-svelte'; 4 | import prettier from 'eslint-config-prettier'; 5 | import globals from 'globals'; 6 | 7 | /** @type {import('eslint').Linter.Config[]} */ 8 | export default [ 9 | js.configs.recommended, 10 | ...ts.configs.recommended, 11 | ...svelte.configs['flat/recommended'], 12 | prettier, 13 | ...svelte.configs['flat/prettier'], 14 | { 15 | languageOptions: { 16 | globals: { 17 | ...globals.browser, 18 | ...globals.node 19 | } 20 | } 21 | }, 22 | { 23 | files: ['**/*.svelte'], 24 | languageOptions: { 25 | parserOptions: { 26 | parser: ts.parser 27 | } 28 | } 29 | }, 30 | { 31 | ignores: ['build/', '.svelte-kit/', 'dist/'] 32 | } 33 | ]; 34 | -------------------------------------------------------------------------------- /packages/v4/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "v4", 3 | "version": "0.0.1", 4 | "type": "module", 5 | "private": true, 6 | "scripts": { 7 | "dev": "vite dev", 8 | "build": "vite build", 9 | "preview": "vite preview", 10 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 11 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 12 | "test": "vitest", 13 | "lint": "prettier --check . && eslint .", 14 | "format": "prettier --write ." 15 | }, 16 | "devDependencies": { 17 | "@sveltejs/adapter-auto": "^3.0.0", 18 | "@sveltejs/adapter-static": "^3.0.4", 19 | "@sveltejs/kit": "^2.0.0", 20 | "@sveltejs/vite-plugin-svelte": "^3.0.0", 21 | "@tailwindcss/typography": "^0.5.14", 22 | "@types/eslint": "^9.6.0", 23 | "autoprefixer": "^10.4.20", 24 | "eslint": "^9.0.0", 25 | "eslint-config-prettier": "^9.1.0", 26 | "eslint-plugin-svelte": "^2.36.0", 27 | "globals": "^15.0.0", 28 | "prettier": "^3.1.1", 29 | "prettier-plugin-svelte": "^3.1.2", 30 | "prettier-plugin-tailwindcss": "^0.6.5", 31 | "svelte": "^4.2.7", 32 | "svelte-check": "^4.0.0", 33 | "tailwindcss": "^3.4.9", 34 | "typescript": "^5.0.0", 35 | "typescript-eslint": "^8.0.0", 36 | "vite": "^5.0.3", 37 | "vite-plugin-markdown": "^2.0.2", 38 | "vitest": "^2.0.0" 39 | }, 40 | "dependencies": { 41 | "@fontsource/lxgw-wenkai": "^5.1.0", 42 | "bits-ui": "^0.21.13", 43 | "clsx": "^2.1.0", 44 | "mode-watcher": "^0.4.1", 45 | "svelte-radix": "^1.1.1", 46 | "sveltekit-i18n": "^2.4.2", 47 | "tailwind-merge": "^2.5.2", 48 | "tailwind-variants": "^0.2.1" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/v4/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {} 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /packages/v4/src/app.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 240 10% 3.9%; 9 | --card: 0 0% 100%; 10 | --card-foreground: 240 10% 3.9%; 11 | --popover: 0 0% 100%; 12 | --popover-foreground: 240 10% 3.9%; 13 | --primary: 240 5.9% 10%; 14 | --primary-foreground: 0 0% 98%; 15 | --secondary: 240 4.8% 95.9%; 16 | --secondary-foreground: 240 5.9% 10%; 17 | --muted: 240 4.8% 95.9%; 18 | --muted-foreground: 240 3.8% 46.1%; 19 | --accent: 240 4.8% 95.9%; 20 | --accent-foreground: 240 5.9% 10%; 21 | --destructive: 0 84.2% 60.2%; 22 | --destructive-foreground: 0 0% 98%; 23 | --border: 240 5.9% 90%; 24 | --input: 240 5.9% 90%; 25 | --ring: 240 5.9% 10%; 26 | --radius: 0.5rem; 27 | --chart-1: 12 76% 61%; 28 | --chart-2: 173 58% 39%; 29 | --chart-3: 197 37% 24%; 30 | --chart-4: 43 74% 66%; 31 | --chart-5: 27 87% 67%; 32 | } 33 | 34 | .dark { 35 | --background: 240 10% 3.9%; 36 | --foreground: 0 0% 98%; 37 | --card: 240 10% 3.9%; 38 | --card-foreground: 0 0% 98%; 39 | --popover: 240 10% 3.9%; 40 | --popover-foreground: 0 0% 98%; 41 | --primary: 0 0% 98%; 42 | --primary-foreground: 240 5.9% 10%; 43 | --secondary: 240 3.7% 15.9%; 44 | --secondary-foreground: 0 0% 98%; 45 | --muted: 240 3.7% 15.9%; 46 | --muted-foreground: 240 5% 64.9%; 47 | --accent: 240 3.7% 15.9%; 48 | --accent-foreground: 0 0% 98%; 49 | --destructive: 0 62.8% 30.6%; 50 | --destructive-foreground: 0 0% 98%; 51 | --border: 240 3.7% 15.9%; 52 | --input: 240 3.7% 15.9%; 53 | --ring: 240 4.9% 83.9%; 54 | --chart-1: 220 70% 50%; 55 | --chart-2: 160 60% 45%; 56 | --chart-3: 30 80% 55%; 57 | --chart-4: 280 65% 60%; 58 | --chart-5: 340 75% 55%; 59 | } 60 | } 61 | 62 | @layer base { 63 | * { 64 | @apply border-border; 65 | } 66 | body { 67 | @apply bg-background text-foreground; 68 | font-family: 'LXGW WenKai'; 69 | font-weight: normal; 70 | } 71 | .dark .prose { 72 | @apply prose-dark; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /packages/v4/src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://kit.svelte.dev/docs/types#app 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface PageState {} 9 | // interface Platform {} 10 | } 11 | 12 | declare module '*.md' { 13 | // "unknown" would be more detailed depends on how you structure frontmatter 14 | const attributes: Record 15 | 16 | // When "Mode.TOC" is requested 17 | const toc: { level: string; content: string }[] 18 | 19 | // When "Mode.HTML" is requested 20 | const html: string 21 | 22 | // When "Mode.RAW" is requested 23 | const raw: string 24 | 25 | // When "Mode.React" is requested. VFC could take a generic like React.VFC<{ MyComponent: TypeOfMyComponent }> 26 | import React from 'react' 27 | const ReactComponent: React.VFC 28 | 29 | // When "Mode.Vue" is requested 30 | import { ComponentOptions, Component } from 'vue' 31 | const VueComponent: ComponentOptions 32 | const VueComponentWith: (components: Record) => ComponentOptions 33 | 34 | // Modify below per your usage 35 | export { attributes, toc, html, ReactComponent, VueComponent, VueComponentWith } 36 | } 37 | } 38 | 39 | export {} 40 | 41 | -------------------------------------------------------------------------------- /packages/v4/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | %sveltekit.head% 9 | 10 | 11 |
%sveltekit.body%
12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/v4/src/index.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | 3 | describe('sum test', () => { 4 | it('adds 1 + 2 to equal 3', () => { 5 | expect(1 + 2).toBe(3); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /packages/v4/src/lib/components/logic/footer.svelte: -------------------------------------------------------------------------------- 1 | 46 | 47 |
48 |
49 | 50 |
51 |
{$t('home.footer.title')}
52 | 53 | {#each links as group} 54 |
55 |

{group.name}

56 | {#each group.links as link} 57 | {link.name} 58 | {/each} 59 |
60 | {/each} 61 |
62 | 63 |
64 |

© 2024 rxliuli

65 |
66 |
67 | -------------------------------------------------------------------------------- /packages/v4/src/lib/components/logic/navbar.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 |
11 | 12 | logo 13 | 14 |
15 | 16 |
17 | 22 |
23 |
24 | -------------------------------------------------------------------------------- /packages/v4/src/lib/components/ui/button/button.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /packages/v4/src/lib/components/ui/button/index.ts: -------------------------------------------------------------------------------- 1 | import { type VariantProps, tv } from "tailwind-variants"; 2 | import type { Button as ButtonPrimitive } from "bits-ui"; 3 | import Root from "./button.svelte"; 4 | 5 | const buttonVariants = tv({ 6 | base: "ring-offset-background focus-visible:ring-ring inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", 7 | variants: { 8 | variant: { 9 | default: "bg-primary text-primary-foreground hover:bg-primary/90", 10 | destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", 11 | outline: 12 | "border-input bg-background hover:bg-accent hover:text-accent-foreground border", 13 | secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", 14 | ghost: "hover:bg-accent hover:text-accent-foreground", 15 | link: "text-primary underline-offset-4 hover:underline", 16 | }, 17 | size: { 18 | default: "h-10 px-4 py-2", 19 | sm: "h-9 rounded-md px-3", 20 | lg: "h-11 rounded-md px-8", 21 | icon: "h-10 w-10", 22 | }, 23 | }, 24 | defaultVariants: { 25 | variant: "default", 26 | size: "default", 27 | }, 28 | }); 29 | 30 | type Variant = VariantProps["variant"]; 31 | type Size = VariantProps["size"]; 32 | 33 | type Props = ButtonPrimitive.Props & { 34 | variant?: Variant; 35 | size?: Size; 36 | }; 37 | 38 | type Events = ButtonPrimitive.Events; 39 | 40 | export { 41 | Root, 42 | type Props, 43 | type Events, 44 | // 45 | Root as Button, 46 | type Props as ButtonProps, 47 | type Events as ButtonEvents, 48 | buttonVariants, 49 | }; 50 | -------------------------------------------------------------------------------- /packages/v4/src/lib/i18n/store.ts: -------------------------------------------------------------------------------- 1 | import { writable, derived } from 'svelte/store' 2 | import { translations, type Locale, type TranslationKey } from './translations' 3 | 4 | function createI18nStore() { 5 | const { subscribe, set } = writable('en-US') 6 | 7 | return { 8 | subscribe, 9 | setLocale: (locale: Locale) => set(locale), 10 | } 11 | } 12 | 13 | export const i18n = createI18nStore() 14 | 15 | export const t = derived(i18n, ($i18n) => (key: TranslationKey) => translations[$i18n][key] || key) 16 | 17 | -------------------------------------------------------------------------------- /packages/v4/src/lib/index.ts: -------------------------------------------------------------------------------- 1 | // place files you want to import through the `$lib` alias in this folder. 2 | -------------------------------------------------------------------------------- /packages/v4/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | import { cubicOut } from "svelte/easing"; 4 | import type { TransitionConfig } from "svelte/transition"; 5 | 6 | export function cn(...inputs: ClassValue[]) { 7 | return twMerge(clsx(inputs)); 8 | } 9 | 10 | type FlyAndScaleParams = { 11 | y?: number; 12 | x?: number; 13 | start?: number; 14 | duration?: number; 15 | }; 16 | 17 | export const flyAndScale = ( 18 | node: Element, 19 | params: FlyAndScaleParams = { y: -8, x: 0, start: 0.95, duration: 150 } 20 | ): TransitionConfig => { 21 | const style = getComputedStyle(node); 22 | const transform = style.transform === "none" ? "" : style.transform; 23 | 24 | const scaleConversion = ( 25 | valueA: number, 26 | scaleA: [number, number], 27 | scaleB: [number, number] 28 | ) => { 29 | const [minA, maxA] = scaleA; 30 | const [minB, maxB] = scaleB; 31 | 32 | const percentage = (valueA - minA) / (maxA - minA); 33 | const valueB = percentage * (maxB - minB) + minB; 34 | 35 | return valueB; 36 | }; 37 | 38 | const styleToString = ( 39 | style: Record 40 | ): string => { 41 | return Object.keys(style).reduce((str, key) => { 42 | if (style[key] === undefined) return str; 43 | return str + `${key}:${style[key]};`; 44 | }, ""); 45 | }; 46 | 47 | return { 48 | duration: params.duration ?? 200, 49 | delay: 0, 50 | css: (t) => { 51 | const y = scaleConversion(t, [0, 1], [params.y ?? 5, 0]); 52 | const x = scaleConversion(t, [0, 1], [params.x ?? 0, 0]); 53 | const scale = scaleConversion(t, [0, 1], [params.start ?? 0.95, 1]); 54 | 55 | return styleToString({ 56 | transform: `${transform} translate3d(${x}px, ${y}px, 0) scale(${scale})`, 57 | opacity: t 58 | }); 59 | }, 60 | easing: cubicOut 61 | }; 62 | }; -------------------------------------------------------------------------------- /packages/v4/src/routes/+layout.server.ts: -------------------------------------------------------------------------------- 1 | import type { LayoutServerLoad } from './$types' 2 | 3 | export const load: LayoutServerLoad = async ({ request }) => { 4 | const acceptLanguage = request.headers.get('accept-language') || 'en-US' 5 | const language = acceptLanguage.split(',')[0].split('-')[0] 6 | const locale = language.includes('zh') ? 'zh-CN' : 'en-US' 7 | 8 | return { locale } 9 | } 10 | 11 | export const prerender = true 12 | -------------------------------------------------------------------------------- /packages/v4/src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/v4/src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 | -------------------------------------------------------------------------------- /packages/v4/src/routes/+page.ts: -------------------------------------------------------------------------------- 1 | import { error } from '@sveltejs/kit'; 2 | import type { PageLoad } from './$types'; 3 | 4 | export const load: PageLoad = () => { 5 | if (0 === 0) { 6 | return { title: 'Hello world!', content: 'Welcome to our blog. Lorem ipsum dolor sit amet...' }; 7 | } 8 | error(404, 'Not found'); 9 | }; 10 | -------------------------------------------------------------------------------- /packages/v4/src/routes/components/about.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 |
7 |

{$t('home.about.title')}

8 |
9 |
10 |

11 | {$t('home.about.section1')} 12 |

13 |

14 | {$t('home.about.section2')} 15 |

16 |

17 | {$t('home.about.section3')} 18 |

19 |

20 | {$t('home.about.section4')} 21 |

22 |
23 |
24 | {'profile 25 |
26 |
27 |
28 |
29 | -------------------------------------------------------------------------------- /packages/v4/src/routes/components/hero.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 |

8 | {$t('home.hero.title')} 9 |

10 |

11 | {$t('home.hero.description')} 12 |

13 | 14 | 15 | 16 |
17 | -------------------------------------------------------------------------------- /packages/v4/src/routes/components/navbar.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | {$t('home.navbar.about')} 8 | {$t('home.navbar.work')} 9 | {$t('home.navbar.life')} 10 | {$t('home.navbar.contact')} 11 | 12 | -------------------------------------------------------------------------------- /packages/v4/src/routes/components/works.svelte: -------------------------------------------------------------------------------- 1 | 37 | 38 |
39 |
40 |

41 | {$t('home.footer.works.title')} 42 |

43 |
46 | {#each works as it} 47 | 48 |

{it.name}

49 |

{it.description}

50 |
51 | {/each} 52 |
53 |
54 |
55 | -------------------------------------------------------------------------------- /packages/v4/src/routes/ping/privacy/+page.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 |
9 | {@html html} 10 |
11 |