93 Commits

Author SHA1 Message Date
renovate[bot]
5a11bc6b4d fix(deps): update react monorepo to v19 2025-03-28 21:11:57 +00:00
jaywcjlove
8991adcd15 ci: update workflows config. 2024-08-05 00:58:24 +08:00
jaywcjlove
1b2e3b534d released v2.4.0 2024-08-05 00:56:10 +08:00
jaywcjlove
d85368cb4f type: fix type error. 2024-08-05 00:44:13 +08:00
jaywcjlove
8b4194f5ae ci: update workflows config. 2024-08-05 00:03:05 +08:00
renovate[bot]
ab3423a697 fix(deps): update dependency rehype-prism-plus to v2 (#47)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-05 00:02:54 +08:00
renovate[bot]
504259b862 fix(deps): update remark (#48)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-05 00:02:27 +08:00
renovate[bot]
b05d90e15b fix(deps): update dependency rehype-stringify to v10 (#31)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-05 00:00:33 +08:00
renovate[bot]
f6e71388f1 fix(deps): update dependency rehype-ignore to v2 (#46)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-05 00:00:14 +08:00
renovate[bot]
1c9a7a8668 fix(deps): update dependency rehype-raw to v7 (#30)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-04 23:59:33 +08:00
renovate[bot]
314f47f8af fix(deps): update dependency rehype-attr to v3 (#36)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-04 23:59:14 +08:00
renovate[bot]
be06f694a5 fix(deps): update dependency unified to v11 (#29)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-04 23:58:31 +08:00
renovate[bot]
c23ada95ff chore(deps): update dependency tsbb to ~4.4.0 (#45)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-04 23:37:41 +08:00
renovate[bot]
2632cb1938 fix(deps): update dependency @uiw/react-markdown-editor to v6 (#43)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-04 23:36:07 +08:00
jaywcjlove
77616468a4 fix: fix copy issue. #44 2024-08-04 23:34:33 +08:00
jaywcjlove
41eb86cd2b ci: update workflows config. 2024-06-29 22:20:31 +08:00
jaywcjlove
5fe5ddfa61 ci: update workflow config. 2024-04-03 16:34:12 +08:00
renovate[bot]
82cc2a3df4 chore(deps): update lerna monorepo to v8 (#40)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-03 16:26:12 +08:00
renovate[bot]
b5596d76b6 fix(deps): update dependency styled-components to ~6.1.0 (#39)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-03 16:25:25 +08:00
renovate[bot]
d2c2746420 chore(deps): update dependency tsbb to ~4.2.0 (#34)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-03 16:17:38 +08:00
jaywcjlove
ef1ed54be7 chore: add sponsor badge. 2023-11-25 21:04:59 +08:00
jaywcjlove
e3a3cf5ff6 chore: update .github/workflows/ci.yml 2023-08-25 11:51:58 +08:00
jaywcjlove
4f4ad71d09 chore: update .github/workflows/ci.yml 2023-08-25 11:29:03 +08:00
jaywcjlove
3b32e76f65 style: modify theme style. 2023-08-25 11:28:47 +08:00
jaywcjlove
056b792519 chore: update .github/workflows/ci.yml 2023-08-25 11:14:20 +08:00
jaywcjlove
de98442b3c chore: update workflows config. 2023-08-25 00:03:40 +08:00
jaywcjlove
8bf24877bf fix: Fix {name} does not correspond to supportd language and throw an error. 2023-08-22 17:44:56 +08:00
jaywcjlove
ad546bfb6c chore(deps): update dependencies. 2023-08-22 17:42:24 +08:00
renovate[bot]
bac929d894 chore(deps): update dependency tsbb to ~4.1.0 (#20)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-04 15:58:05 +08:00
jaywcjlove
ab1c043931 chore(deps): update dependency tsbb to v4 #20
https://github.com/jaywcjlove/tsbb/issues/439
2023-03-30 21:51:54 +08:00
jaywcjlove
5afb09a715 style: modify underscore themes. 2022-10-24 23:00:59 +08:00
jaywcjlove
df79dcf694 chore(deps): Update @uiw/react-markdown-editor dependency to ^5.10.0 2022-09-22 14:15:54 +08:00
jaywcjlove
33a60420a4 released v2.3.3 2022-09-17 11:32:18 +08:00
jaywcjlove
c7dba6d5de website: update commamnd style. 2022-09-17 11:31:43 +08:00
jaywcjlove
ed596a7403 chore: update workflows config. 2022-09-13 12:40:35 +08:00
jaywcjlove
7f28e6ada1 released v2.3.2 2022-09-13 11:29:17 +08:00
renovate[bot]
df10f96a65 chore(deps): update dependency electron to v20 (#8) 2022-09-13 11:01:04 +08:00
jaywcjlove
02cb33cfcd released v2.3.1 2022-09-12 17:57:06 +08:00
jaywcjlove
13a96916d7 doc: Update README.md 2022-09-12 17:56:27 +08:00
jaywcjlove
245d54e511 doc: Update README.md 2022-09-12 17:55:42 +08:00
jaywcjlove
06e216aa22 doc: Update README.md 2022-09-12 17:54:26 +08:00
jaywcjlove
a50acf3888 chore: update workflows config. 2022-09-12 17:53:51 +08:00
jaywcjlove
195d2ce8d0 chore: update workflows config. 2022-09-12 17:51:09 +08:00
renovate[bot]
9adcd7eaa7 chore(deps): update dependency lerna to v5.5.1 (#9) 2022-09-12 17:40:10 +08:00
jaywcjlove
193ec20a22 released v2.3.0 2022-09-06 00:38:15 +08:00
jaywcjlove
8f62d41020 feat: add color palette. 2022-09-06 00:08:46 +08:00
jaywcjlove
8720638f9d style: modify theme style. 2022-09-06 00:08:46 +08:00
renovate[bot]
8d33e4dab2 chore(deps): update dependency electron-builder to v23.3.3 (#6) 2022-09-05 10:47:03 +08:00
jaywcjlove
1c79ec8b0a chore: update workflows config. 2022-09-05 09:39:17 +08:00
jaywcjlove
f3337f064e website: modify preview width. 2022-09-05 09:31:41 +08:00
renovate[bot]
c86d5bbc0c chore(deps): update dependency lerna to v5.5.0 (#7)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-05 08:15:30 +08:00
jaywcjlove
55c369f23c chore: update workflows config. 2022-09-05 00:52:05 +08:00
jaywcjlove
6aad6713d2 released v2.2.0 2022-09-05 00:21:56 +08:00
jaywcjlove
5c10978fe9 chore: update workflows config. 2022-09-05 00:07:21 +08:00
jaywcjlove
8d17f7532e feat: build windows & linux app. 2022-09-04 23:52:47 +08:00
jaywcjlove
7f997282fc chore: update workflows config. 2022-09-04 22:57:14 +08:00
jaywcjlove
60b32b3ca8 fix: fix dockerfile config error. 2022-09-04 22:53:51 +08:00
jaywcjlove
83e834d6cf chore: update workflows config. 2022-09-04 22:50:38 +08:00
jaywcjlove
5cc2758073 fix: fix docker image build error. 2022-09-04 22:49:27 +08:00
jaywcjlove
d84ad70345 fix: Fix dockerfile config error. 2022-09-04 22:40:55 +08:00
jaywcjlove
ba5eb6115c chore: upate workflows config. 2022-09-04 22:22:52 +08:00
renovate[bot]
57e604c195 chore(deps): update dependency cpy-cli to v4.2.0 (#5) 2022-09-04 22:10:33 +08:00
jaywcjlove
76a6f48d0a feat: add electron app. 2022-09-04 22:09:04 +08:00
jaywcjlove
a66e906eef doc: Update README.md 2022-09-03 17:19:11 +08:00
jaywcjlove
b694d61bf0 doc: Update README.md 2022-09-03 17:17:06 +08:00
jaywcjlove
fd6ad59d5c feat: add api request loading animation. 2022-09-03 17:12:31 +08:00
jaywcjlove
b1dc77e98b feat: add url parameter to load markdown content. 2022-09-03 16:40:11 +08:00
jaywcjlove
57b719c163 feat: add documemt. 2022-09-03 15:39:06 +08:00
jaywcjlove
d806339c26 released v2.1.0 2022-09-03 15:08:45 +08:00
jaywcjlove
6a2279d7b2 feat: add theme url parameter. 2022-09-03 15:07:58 +08:00
jaywcjlove
ba0b158a47 doc: Update README.md 2022-09-03 13:40:40 +08:00
jaywcjlove
3bf313ffc3 style: modify theme style. 2022-09-03 13:33:25 +08:00
jaywcjlove
f245c33973 feat: add base theme. 2022-09-03 12:50:18 +08:00
jaywcjlove
1c7167bcf9 feat: add theme editor. 2022-09-03 12:19:13 +08:00
jaywcjlove
0580011f7b feat: add preview themes. 2022-09-03 01:11:22 +08:00
jaywcjlove
d007e11f20 chore: format code & add format tools. 2022-09-02 22:45:28 +08:00
jaywcjlove
6da12fcb10 style: modify select style. 2022-09-02 21:41:09 +08:00
jaywcjlove
e0fed4783a style: modify image style. 2022-09-02 17:36:05 +08:00
jaywcjlove
e9b3b60f6d feat: add editor theme switch. 2022-09-02 17:06:30 +08:00
jaywcjlove
cbaad4e0fc style: update footnotes style. 2022-09-02 11:58:45 +08:00
jaywcjlove
3abeaf355c feat: support custom style & ignore content syntax. 2022-09-02 11:44:25 +08:00
jaywcjlove
709849e048 feat: support docker deplay. 2022-09-02 01:46:24 +08:00
jaywcjlove
9b08674a5a released v2.0.0 2022-09-02 01:34:27 +08:00
jaywcjlove
61649a29da chore: update renovate.json 2022-09-02 01:32:48 +08:00
jaywcjlove
92db8dd11a style: update preview styles. 2022-09-02 01:31:20 +08:00
jaywcjlove
3d11eb8479 doc: Update README.md 2022-09-02 01:26:59 +08:00
jaywcjlove
cce03c115b chore: udpate workflows config. 2022-09-02 01:26:25 +08:00
jaywcjlove
bebe6a549c doc: Update README.md 2022-09-02 01:26:25 +08:00
小弟调调™
cd23add2ce Create LICENSE 2022-09-02 01:19:05 +08:00
renovate[bot]
79f9d0d9b4 chore(deps): add renovate.json (#1) 2022-09-02 01:18:26 +08:00
jaywcjlove
d0364a0bee feat: init web app. 2022-09-02 01:16:22 +08:00
jaywcjlove
ef01ce998e Add .gitignore file. 2020-08-09 22:07:00 +08:00
jaywcjlove
158a032fcb update README.md 2016-07-14 11:06:59 +08:00
80 changed files with 3108 additions and 127 deletions

3
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
ko_fi: jaywcjlove
buy_me_a_coffee: jaywcjlove
custom: ["https://www.paypal.me/kennyiseeyou", "https://jaywcjlove.github.io/#/sponsor"]

314
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,314 @@
name: CI
on:
push:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
registry-url: 'https://registry.npmjs.org'
- run: npm install
- run: npm run build
- run: npm run doc
- uses: actions/upload-artifact@v4
with:
name: webiste
path: |
website/build/**
- name: Generate Contributors Images
uses: jaywcjlove/github-action-contributors@main
with:
filter-author: (renovate\[bot\]|renovate-bot|dependabot\[bot\])
output: website/build/CONTRIBUTORS.svg
avatarSize: 42
- name: Create Tag
id: create_tag
uses: jaywcjlove/create-tag-action@main
with:
package-path: ./website/package.json
- name: get tag version
id: tag_version
uses: jaywcjlove/changelog-generator@main
- name: Deploy
uses: peaceiris/actions-gh-pages@v4
with:
commit_message: ${{ github.event.head_commit.message }} ${{steps.tag_version.outputs.tag}}
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./website/build
- name: Generate Changelog
id: changelog
uses: jaywcjlove/changelog-generator@main
with:
head-ref: ${{steps.create_tag.outputs.version}}
filter-author: (renovate-bot|Renovate Bot)
filter: '[R|r]elease[d]\s+[v|V]\d(\.\d+){0,2}'
outputs:
version: ${{ steps.changelog.outputs.version }}
create_tag_version: ${{ steps.create_tag.outputs.version }}
create_tag_versionNumber: ${{ steps.create_tag.outputs.versionNumber }}
tag: ${{ steps.changelog.outputs.tag }}
successful: ${{steps.create_tag.outputs.successful }}
gh-pages-short-hash: ${{ steps.changelog.outputs.gh-pages-short-hash }}
docker:
runs-on: ubuntu-latest
needs: [build]
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: webiste
path: website/build
- run: echo "outputs.version - ${{ needs.build.outputs.version }}"
- run: echo "outputs.create_tag_version - ${{ needs.build.outputs.create_tag_version }}"
- run: echo "outputs.create_tag_versionNumber - ${{ needs.build.outputs.create_tag_versionNumber }}"
- run: echo "outputs.tag - ${{ needs.build.outputs.tag }}"
# Create Docker Image
- name: Docker login
run: docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PASSWORD }}
- name: Build wxmp image
working-directory: website
run: docker image build -t wxmp .
- name: Tags & Push image (latest)
run: |
echo "outputs.tag - ${{ needs.build.outputs.version }}"
docker tag wxmp ${{ secrets.DOCKER_USER }}/wxmp:latest
docker push ${{ secrets.DOCKER_USER }}/wxmp:latest
- name: Tags & Push image
if: needs.build.outputs.successful
run: |
echo "outputs.tag - ${{ needs.build.outputs.version }}"
docker tag wxmp ${{ secrets.DOCKER_USER }}/wxmp:${{needs.build.outputs.version}}
docker push ${{ secrets.DOCKER_USER }}/wxmp:${{needs.build.outputs.version}}
# # Create Docker Image in GitHub
# - name: Login to GitHub registry
# run: echo ${{ github.token }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
# - name: Build docker image
# working-directory: website
# run: docker build -t ghcr.io/jaywcjlove/wxmp:latest .
# - name: Publish to GitHub registry
# run: docker push ghcr.io/jaywcjlove/wxmp:latest
# - name: Tag docker image (beta) and publish to GitHub registry
# if: needs.build.outputs.successful
# run: |
# echo "version: v${{ needs.build.outputs.version }}"
# docker tag ghcr.io/jaywcjlove/wxmp:latest ghcr.io/jaywcjlove/wxmp:${{needs.build.outputs.version}}
# docker push ghcr.io/jaywcjlove/wxmp:${{needs.build.outputs.version}}
build_windows:
needs: [build]
runs-on: windows-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Install
run: npm install --build-from-source
- run: npm run hoist
- run: npm run build
# - run: npm run electron
- uses: actions/download-artifact@v4
with:
name: webiste
path: website/build
- name: electron-builder install-app-deps
working-directory: electron/app
run: npm run deps
- run: npm run build:app
- working-directory: electron/app/dist
run: ls -R
- uses: actions/upload-artifact@v4
if: needs.build.outputs.successful == 'true'
with:
name: wxmp-windows
path: |
electron\app\dist\*.exe
build_macos:
needs: [build]
runs-on: macos-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm install
- run: npm run hoist
- run: npm run build
- uses: actions/download-artifact@v4
with:
name: webiste
path: website/build
- name: electron-builder install-app-deps
working-directory: electron/app
run: npm run deps
- run: npm run build:app
- working-directory: electron/app/dist
run: ls -R
- uses: actions/upload-artifact@v4
if: needs.build.outputs.successful == 'true'
with:
name: wxmp-macos
path: |
electron/app/dist/*.zip
build_linux:
needs: [build]
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm install
- run: npm run hoist
- run: npm run build
# - run: npm run electron
- uses: actions/download-artifact@v4
with:
name: webiste
path: website/build
- name: electron-builder install-app-deps
working-directory: electron/app
run: npm run deps
- run: npm run build:app
- working-directory: electron/app/dist
run: ls -R
- uses: actions/upload-artifact@v4
if: needs.build.outputs.successful == 'true'
with:
name: wxmp-linux
path: |
electron/app/dist/*.deb
electron/app/dist/*.rpm
create_release:
needs: [build, build_windows, build_macos, build_linux]
if: needs.build.outputs.successful == 'true'
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- uses: actions/download-artifact@v4
with:
name: wxmp-linux
path: dist/linux
- uses: actions/download-artifact@v4
with:
name: wxmp-macos
path: dist/macos
- uses: actions/download-artifact@v4
with:
name: wxmp-windows
path: dist/windows
- name: Display structure of downloaded files
working-directory: dist
run: ls -R
- name: Generate Changelog
id: changelog
uses: jaywcjlove/changelog-generator@main
with:
token: ${{ secrets.GITHUB_TOKEN }}
filter-author: (jaywcjlove|小弟调调™|dependabot\[bot\]|Renovate Bot)
filter: (^[\s]+?[R|r]elease)|(^[R|r]elease)
- name: Create Release
uses: ncipollo/release-action@v1
if: needs.build.outputs.successful == 'true'
with:
allowUpdates: true
token: ${{ secrets.GITHUB_TOKEN }}
name: ${{ steps.changelog.outputs.tag }}
tag: ${{ steps.changelog.outputs.tag }}
artifacts: "dist/linux/*.rpm,dist/linux/*.deb,dist/macos/*.zip,dist/macos/*.dmg,dist/windows/*.exe"
body: |
Documentation ${{ steps.changelog.outputs.tag }}: https://raw.githack.com/jaywcjlove/wxmp/${{ steps.changelog.outputs.gh-pages-short-hash }}/index.html
Comparing Changes: ${{ steps.changelog.outputs.compareurl }}
${{ steps.changelog.outputs.changelog }}
```bash
docker pull wcjiang/wxmp:${{needs.build.outputs.create_tag_versionNumber}}
```
```bash
docker run --name wxmp --rm -d -p 9666:3000 wcjiang/wxmp:${{ needs.build.outputs.create_tag_versionNumber }}
# Or
docker run --name wxmp -itd -p 9666:3000 wcjiang/wxmp:${{ needs.build.outputs.create_tag_versionNumber }}
```
Visit the following URL in your browser
```bash
http://localhost:9666/
```
roll_back:
if: failure()
needs: [build, create_release]
runs-on: ubuntu-latest
timeout-minutes: 4
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
- run: echo "outputs.version - ${{ needs.build.outputs.create_tag_version }}"
- uses: dev-drprasad/delete-tag-and-release@v1.1
if: needs.build.outputs.successful == 'true'
with:
delete_release: true
repo: jaywcjlove/wxmp
tag_name: '${{ needs.build.outputs.create_tag_version }}'
github_token: ${{ secrets.GITHUB_TOKEN }}

31
.gitignore vendored Normal file
View File

@@ -0,0 +1,31 @@
dist
build
lib
cjs
esm
node_modules
npm-debug.log*
lerna-debug.log
yarn-error.log
package-lock.json
.DS_Store
.cache
.vscode
.idea
.env
*.mpassword
*.bak
*.tem
*.temp
#.swp
*.*~
~*.*
# IDEA
*.iml
*.ipr
*.iws
.idea/

4
.husky/pre-commit Executable file
View File

@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx pretty-quick --staged

13
.prettierignore Normal file
View File

@@ -0,0 +1,13 @@
**/*.md
**/*.svg
**/*.ejs
**/*.html
**/*.yml
package.json
node_modules
dist
build
coverage
lib
esm
test

11
.prettierrc Normal file
View File

@@ -0,0 +1,11 @@
{
"singleQuote": true,
"trailingComma": "all",
"printWidth": 120,
"overrides": [
{
"files": ".prettierrc",
"options": { "parser": "json" }
}
]
}

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 小弟调调™
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

235
README.md
View File

@@ -1,36 +1,221 @@
# Wxmp
<div align="center">
<h1 align="center">微信公众号 Markdown 编辑器</h1>
</div>
chrome 小插件,优化在微信公众账号中发文章,因复制粘贴带过去的`font-family` CSS 样式导致被微信过滤样式全无。使用此插件删除提交文章上所有HTML节点上的`font-family`,让复制过去的样式保持一致。
[![Buy me a coffee](https://img.shields.io/badge/Buy%20me%20a%20coffee-048754?logo=buymeacoffee)](https://jaywcjlove.github.io/#/sponsor)
[![CI](https://github.com/jaywcjlove/wxmp/actions/workflows/ci.yml/badge.svg)](https://github.com/jaywcjlove/wxmp/actions/workflows/ci.yml)
目前删除这些标签上的`font-family`样式
[![微信公众号 Markdown 编辑器](https://user-images.githubusercontent.com/1680273/188264183-a6b8cb6a-92e1-4a73-afc5-4f0234b26ed3.png)](https://jaywcjlove.github.io/wxmp)
> `code`,`pre`,`h1`,`h2`,`h3`,`h4`,`h5`,`h6`,`p`,`div`,`span`
微信公众号文章 Markdown 在线编辑器,使用 markdown 语法创建一篇简介美观大方的微信公众号图文。由于发版本麻烦,和一些功能无法扩展停滞开发了,未来不再开发 Chrome 的插件(暂存在 chrome 分支),通过 web 版本定制更丰富的功能。
![界面预览](https://raw.githubusercontent.com/jaywcjlove/wxmp/master/wxmq.png)
[![Markdown 编辑器桌面应用](https://user-images.githubusercontent.com/1680273/188407235-ead43d61-2ef8-416a-926f-396d8b824b33.png)](https://github.com/jaywcjlove/wxmp/releases)
## 功能特性
开发计划和一些功能介绍,有需求可以在 issue 中提,使得工具变得更加完善。下面示例用于 web 应用中效果展示。
- [x] 支持 Markdown 所有基础语法
- [x] 支持自定义 CSS 样式
- [x] 支持主题选择 & 编辑预览。
- [x] 支持明暗两种主题预览。
- [ ] 支持代码块主题样式选择。
- [x] 支持色盘取色,快速替换文章整体色调
- [x] 支持 URL 参数加载 Markdown 内容。
- [x] 支持 URL 参数选择预览主题。
- [x] CI 自动生成 Electron 桌面应用。
- [ ] ~~支持全局字号大小选择。~~
### 支持代码块样式
下面是 `jsx` 代码块展示示例,并高亮代码,用于 web 应用中效果展示。
```jsx
function Demo() {
return <div className="demo">Hello World!</div>
}
```
下面是 `css` 代码块展示示例,并高亮代码,用于 web 应用中效果展示。
```css
li {
font-size: 16px;
margin: 0;
line-height: 26px;
color: rgb(30 41 59);
font-family:-apple-system-font,BlinkMacSystemFont, Helvetica Neue, PingFang SC, Hiragino Sans GB , Microsoft YaHei UI , Microsoft YaHei ,Arial,sans-serif;
}
```
### 支持内联代码
> 用于 web 应用中效果展示。
Inline Code `{code: 0}`
### 支持表格
表格无法使用自定义样式,暂时没找到解决途径
| Header 1 | Header 2 |
| --- | --- |
| Key 1 | Value 1 |
| Key 2 | Value 2 |
| Key 3 | Value 3 |
### 支持 GFM 脚注
这是一个简单的 Markdown[^1] 语法的脚注[^2]。 页面最后有一些额外的文字描述。注意这不是完整的注脚[^3]特性。
[^1]: GitHub 风格的 Markdown 规范 https://github.github.com/gfm/
[^2]: 脚注 https://github.blog/changelog/2021-09-30-footnotes-now-supported-in-markdown-fields/
[^3]: 微信文章不支持锚点跳转和打开第三方 URL 超链接,所以不支持完整的注脚特性。
### 支持注释
```html
<ruby>
<rt>Han</rt>
</ruby>
```
汉字注音效果:
<ruby>
汉 <rt>Han</rt>
字 <rt>zi</rt>
拼 <rt>pin</rt>
音 <rt>yin</rt>
注 <rt>zhu</rt>
音 <rt>yin</rt>
</ruby>
### 支持自定义样式
<!--rehype:style=color: red;-->
在 Markdown 中 HTML 注释也可以用在 markdown 中,利用这一特点,为一些内容自定一样式。使用 HTML 注释 `<!--rehype:xxx-->`<!--rehype:style=color: red;background: #ff000033;--> 让 Markdown 支持样式自定义。
```markdown
## 定义标题样式
<!--rehype:style=display: flex; height: 230px; align-items: center; justify-content: center; font-size: 38px;-->
支持对某些文字变更样式如_文字颜色_<!--rehype:style=color: red;-->,文字颜色将被设置为红色(red)。
```
⚠️ 注意:这一特性可能适用于有一定 css 前端基础知识的用户,不过它也非常简单,使用 `<!--rehype:style=` 开始,`-->` 结束,中间包裹 css 样式,如 `color: red;` 设置文字红色。
## 已经实现功能
### 标记忽略内容
- [x] 过滤 `font-family`
- [x] 代码高亮区域有背景颜色;
- [x] 代码高亮区域有横向滚动条强制不换行;
- [x] 增加iOS滚动滚动弹性
- [ ] 添加设置标题工具;
- [ ] 添加字段高亮工具;
- [ ] 添加删除线工具,如:<del>删除线</del>
此特性利用 HTML 注释在 markdown 中被忽略的特性,标记需要忽略的内容,标记开始 `<!--rehype:ignore:start-->`,标记结束 `<!--rehype:ignore:end-->`,被标记的内容在微信 Markdown 编辑器预览中不显示。在其它预览工具中展示内容,比如 GitHub 中能展示。
```markdown
# 注释忽略
<!--rehype:ignore:start-->内容在微信 Markdown 编辑器预览中不显示。在其它预览工具中展示内容。<!--rehype:ignore:end-->
```
### 支持 URL 参数加载 Markdown 内容
```
https://<URL>?md=<Markdown 资源 URL>
```
加载 Markdown 内容的示例 URL
```
https://jaywcjlove.github.io/wxmp/#/?theme=underscore&md=https://raw.githubusercontent.com/jaywcjlove/c-tutorial/master/README.md
Markdown URL 地址: https://raw.githubusercontent.com/jaywcjlove/c-tutorial/master/README.md
```
## 主题定制
在目录 `website/src/themes` 中存放默认主题,在 `website/src/store/context.tsx` 中配置主题,主题使用 `css` 定义样式,不支持复杂的选择器。提供在线主题编辑器,欢迎修改并 `PR` 进仓库供大家使用。
```css
/* 1~6 标题样式定义 */
h1 {} h2 {} h3 {} h4 {} h5 {} h6 {}
a { color: red; } /* 超链接样式定义 */
strong {} /* 加粗样式定义 */
del {} /* 删除线样式定义 */
em {} /* 下划线样式定义 */
u {} /* 下划线样式定义 */
p {} /* 段落样式定义 */
ul {} /* 无序列表样式定义 */
ol {} /* 有序列表样式定义 */
li {} /* 列表条目样式定义 */
blockquote {} /* 块级引用样式定义 */
table {}
td {}
th {}
pre {} /* 样式定义 */
.code-highlight {} /* 代码块样式定义 */
.code-line {} /* 代码块行样式定义 */
.code-spans {} /* 代码块行样式定义 */
sup {} /* GFM 脚注样式定义 */
.footnotes-title {} /* GFM 脚注,参考标题样式定义 */
.footnotes-list {} /* GFM 脚注,参考列表样式定义 */
.image-warpper {} /* 图片父节点样式定义 */
.image {} /* 图片样式定义 */
/* 部分代码高亮样式 */
.comment {}
.property {}
.function {}
.keyword {}
.punctuation {}
.unit {}
.tag {}
.color {}
.selector {}
.quote {}
.number {}
.attr-name {}
.attr-value {}
```
## 部署
[![Docker Image Version (latest by date)](https://img.shields.io/docker/v/wcjiang/wxmp?logo=docker)](https://hub.docker.com/r/wcjiang/wxmp) [![Docker Image Size (latest by date)](https://img.shields.io/docker/image-size/wcjiang/wxmp?logo=docker)](https://hub.docker.com/r/wcjiang/wxmp) [![Docker Pulls](https://img.shields.io/docker/pulls/wcjiang/wxmp?logo=docker)](https://hub.docker.com/r/wcjiang/wxmp)
轻松通过 docker 部署《微信公众号 Markdown 编辑器》网站应用。
```bash
docker pull wcjiang/wxmp
# Or
docker pull ghcr.io/jaywcjlove/wxmp:latest
```
```bash
docker run --name wxmp --rm -d -p 8113:3000 wcjiang/wxmp:latest
# Or
docker run --name wxmp -itd -p 8113:3000 wcjiang/wxmp:latest
# Or
docker run --name wxmp -itd -p 8113:3000 ghcr.io/jaywcjlove/wxmp:latest
```
在浏览器中访问以下 URL
```
http://localhost:96611/
```
## 贡献者
一如既往,感谢我们出色的贡献者!
<a href="https://github.com/jaywcjlove/wxmp/graphs/contributors">
<img src="https://jaywcjlove.github.io/wxmp/CONTRIBUTORS.svg" />
</a>
上图贡献者列表,由 [action-contributors](https://github.com/jaywcjlove/github-action-contributors)[^4] 自动生成贡献者图片。
# 插件安装
[^4]: Action Contributors https://github.com/jaywcjlove/github-action-contributors
1. 下载文件压缩包解压
2. 在chrome里面器地址输入`chrome://extensions/` 打开插件界面
3. 点击`加载已解压的扩展程序...`
4. 选择插件所在的目录
## License
# 使用方法
1. 打开微信公众平台,新建图文消息,复制文章到编辑器中
2. 在右上角点击微信图标
3. 点击弹出的模态框上的删除按钮
4. 如果成功会在按钮后面提示`更改成功!!`
根据 MIT 许可证获得许可。

View File

@@ -1,7 +0,0 @@
ul,li{margin: 0;padding: 0;}
.warpper{width: 200px; min-height: 23px;}
.warpper ul {}
.warpper ul li{}
.warpper ul li a{display: block;box-shadow: 0 1px 1px 0 #D0D0D0;line-height: 23px;border:1px solid #D0D0D0;border-radius: 4px;padding: 0 5px;color:#333;}
.warpper ul li a:hover{box-shadow: 0 1px 1px 0 #4A90E2;border: 1px solid #4A90E2;color:#4A90E2;}
.warpper ul li a:active{box-shadow: 0 1px 1px 0 #ABABAB;border: 1px solid #ABABAB;color:#ABABAB;}

2
electron/app/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
dist
website

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
</dict>
</plist>

35
electron/app/config.json Normal file
View File

@@ -0,0 +1,35 @@
{
"productName": "wxmp",
"appId": "com.wangchujiang.wxmp",
"asar": true,
"directories": {
"output": "dist"
},
"mac": {
"icon": "tools.icns",
"target": {
"target": "default",
"arch": ["arm64", "x64"]
},
"category": "public.app-category.developer-tools",
"type": "distribution",
"entitlements": "assets/entitlements.mac.plist",
"entitlementsInherit": "assets/entitlements.mac.plist"
},
"linux": {
"icon": "tools.icns",
"description": "微信公众号 Markdown 编辑器",
"category": "Development",
"target": ["deb", "rpm"],
"desktop": {
"Name": "Web Tools"
}
},
"win": {
"icon": "tools.ico",
"target": {
"target": "nsis",
"arch": ["x64", "ia32"]
}
}
}

BIN
electron/app/icon.icns Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

BIN
electron/app/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

15
electron/app/main.js Normal file
View File

@@ -0,0 +1,15 @@
const path = require('path');
const { App } = require('@wcj/wxmp-main');
(async () => {
const options = {};
if (process.env.NODE_ENV === 'development') {
options.preload = require.resolve('@wcj/wxmp-preload');
options.webpath = require.resolve('website/build/index.html');
} else {
options.preload = path.resolve(__dirname, 'website/index.js');
options.webpath = 'website/index.html';
}
const app = new App();
await app.createWindow(options);
})();

30
electron/app/package.json Normal file
View File

@@ -0,0 +1,30 @@
{
"name": "wxmp",
"description": "微信公众号 Markdown 编辑器",
"homepage": "https://github.com/jaywcjlove/wxmp.git",
"version": "2.4.0",
"main": "main.js",
"author": "Kenny Wong <398188662@qq.com>",
"private": true,
"scripts": {
"deps": "electron-builder install-app-deps",
"start": "cross-env NODE_ENV=development ELECTRON_DISABLE_SECURITY_WARNINGS=true electron .",
"start:production": "cross-env NODE_ENV=production ELECTRON_DISABLE_SECURITY_WARNINGS=true electron .",
"dist-win32": "electron-builder --win --ia32 --config config.json",
"dist-win64": "electron-builder --win --x64 --config config.json",
"dist-mac": "electron-builder --mac --universal --config config.json",
"dist-linux": "electron-builder --linux --config config.json",
"copy": "cpy './node_modules/@wcj/wxmp-preload/lib/*.js' './node_modules/website/build/**' website",
"build": "npm run copy && cross-env NODE_ENV=production electron-builder build --publish=never --config config.json"
},
"dependencies": {
"@wcj/wxmp-main": "2.4.0"
},
"devDependencies": {
"@wcj/wxmp-preload": "2.4.0",
"cpy-cli": "^5.0.0",
"electron": "20.1.3",
"electron-builder": "23.3.3",
"website": "2.4.0"
}
}

1
electron/main/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
lib

View File

@@ -0,0 +1,16 @@
{
"name": "@wcj/wxmp-main",
"version": "2.4.0",
"main": "./lib/index.js",
"private": true,
"scripts": {
"build": "tsbb build",
"watch": "tsbb watch"
},
"files": [
"lib"
],
"devDependencies": {
"electron": "20.1.3"
}
}

56
electron/main/src/Menu.ts Normal file
View File

@@ -0,0 +1,56 @@
import { app, Menu, MenuItem, MenuItemConstructorOptions } from 'electron';
const isMac = process.platform === 'darwin';
const template = [
// { role: 'appMenu' }
...(isMac
? [
{
label: app.name,
submenu: [
{ role: 'about' },
{ type: 'separator' },
{ role: 'services' },
{ type: 'separator' },
{ role: 'hide' },
{ role: 'hideOthers' },
{ role: 'unhide' },
{ type: 'separator' },
{ role: 'quit' },
],
},
]
: []),
{ role: 'editMenu' },
{
label: 'Window',
submenu: [
{ role: 'minimize' },
{ role: 'zoom' },
...(isMac
? [{ type: 'separator' }, { role: 'front' }, { type: 'separator' }, { role: 'window' }]
: [{ role: 'close' }]),
],
},
{
role: 'help',
submenu: [
{
label: 'Open Source for Github',
click: async () => {
const { shell } = require('electron');
await shell.openExternal('https://github.com/jaywcjlove/wxmp');
},
},
{
label: 'Online Website',
click: async () => {
const { shell } = require('electron');
await shell.openExternal('https://jaywcjlove.github.io/wxmp');
},
},
],
},
];
const menu = Menu.buildFromTemplate(template as Array<MenuItem | MenuItemConstructorOptions>);
Menu.setApplicationMenu(menu);

61
electron/main/src/app.ts Normal file
View File

@@ -0,0 +1,61 @@
import { app, shell, BrowserWindow } from 'electron';
import './Menu';
export interface Options extends Electron.BrowserWindowConstructorOptions {
preload?: string;
webpath?: string;
}
export class App {
app = app;
win?: BrowserWindow;
isLogin: boolean = false;
/** 创建主进程窗口 */
async createWindow(options: Options = {}, loadURL?: string) {
await app.whenReady();
const opts: Options = {
// titleBarStyle: 'hiddenInset', // 无标题栏
// frame: false, // 创建无边窗口
width: 850,
height: 600,
minWidth: 850,
minHeight: 600,
center: true,
// maximizable: true,
// minimizable: true,
// resizable: true,
webPreferences: {
// 多线程
nodeIntegrationInWorker: true,
nodeIntegration: true,
contextIsolation: false,
},
...options,
};
if (options.preload) {
opts.webPreferences.preload = options.preload;
}
this.win = new BrowserWindow(opts);
if (process.env.NODE_ENV === 'development') {
this.win.loadURL(loadURL || 'http://localhost:3000/');
// 打开开发者工具,默认不打开
this.win.webContents.openDevTools();
} else {
this.win.loadFile(options.webpath);
}
this.win.webContents.setWindowOpenHandler(({ url }) => {
if (/^https?:\/\//.test(url)) {
shell.openExternal(url);
return { action: 'deny' };
}
return {
action: 'allow',
overrideBrowserWindowOptions: {
modal: true,
},
};
});
return this.win;
}
}

View File

@@ -0,0 +1 @@
export * from './app';

View File

@@ -0,0 +1,19 @@
{
"compilerOptions": {
"module": "commonjs",
"esModuleInterop": true,
"declaration": true,
"target": "es2017",
"noImplicitAny": true,
"resolveJsonModule": true,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"sourceMap": false,
"strict": false,
"skipLibCheck": true,
"outDir": "lib",
"baseUrl": "."
},
"include": ["src"]
}

1
electron/preload/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
lib

View File

@@ -0,0 +1,16 @@
{
"name": "@wcj/wxmp-preload",
"version": "2.4.0",
"main": "./lib/index.js",
"private": true,
"scripts": {
"build": "tsbb build",
"watch": "tsbb watch"
},
"files": [
"lib"
],
"devDependencies": {
"electron": "20.1.3"
}
}

View File

@@ -0,0 +1,10 @@
const styleStr = `.header .logo {}`;
document.addEventListener('DOMContentLoaded', () => {
const head = document.querySelector('head');
const style = document.createElement('style');
style.textContent = styleStr;
if (head) {
head.append(style);
}
});

View File

@@ -0,0 +1,27 @@
{
"compilerOptions": {
"module": "commonjs",
"esModuleInterop": true,
"declaration": true,
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"sourceMap": false,
"noImplicitAny": true,
"resolveJsonModule": true,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"outDir": "lib",
"baseUrl": "."
// "esModuleInterop": true,
// "allowSyntheticDefaultImports": true,
// "forceConsistentCasingInFileNames": true,
// "noFallthroughCasesInSwitch": true,
// "isolatedModules": false,
},
"include": ["src"]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

View File

@@ -1,37 +0,0 @@
chrome.extension.onRequest.addListener(
function (request, sender, sendResponse) {
if (request.hello == "btn_del_family") {
var bodys = document.querySelectorAll('iframe#ueditor_0')
if(bodys&&bodys.length>0&&bodys[0].contentDocument){
var body = bodys[0].contentDocument;
if(body){
var elms = body.querySelectorAll('pre code,pre,h1,h2,h3,h4,h5,h6,p,div,span');
for (var i = 0; i < elms.length; i++) {
var styl = elms[i].getAttribute('style');
if(elms[i].tagName === 'PRE'){
elms[i].setAttribute('style','box-sizing: border-box; overflow: auto;font-size: 0.93em; padding: 1em; margin-top: 1.5em; margin-bottom: 1.5em; line-height: 1.3; word-break: break-all; word-wrap: break-word; color: rgb(51, 51, 51); border: none; border-radius: 3px; max-height: 35em; position: relative;background-color:#EDEDED;word-wrap: initial!important;-webkit-overflow-scrolling: touch;')
}else if(elms[i].tagName === 'CODE'){
elms[i].setAttribute('style','box-sizing: border-box;font-size: 1em; color: inherit; border-radius: 0px; white-space: inherit; overflow-wrap: normal; background: none;word-wrap:normal!important;')
}else{
if(styl){
styl = styl.replace(/font-family\:[\s\S]*?\;/g,'');
if(elms[i].tagName === 'SPAN') styl += 'word-wrap:normal!important;';
elms[i].setAttribute('style',styl);
}
}
}
var elms = body.querySelectorAll('p code');
for (var i = 0; i < elms.length; i++) {
if(elms[i].tagName === 'CODE'){
elms[i].setAttribute('style','color: #c7254e;background-color: #f9f2f4;padding: 2px 4px;border-radius: 3px;')
}
}
}
}
sendResponse({ msg: '更改完毕!!' });
}
}
);

4
js/jq.3.0.0.min.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -1,20 +0,0 @@
$('.warpper a.btn_del_family').on('click',function(){
var self = this
chrome.tabs.getSelected(null, function (tab) {
chrome.tabs.sendRequest(tab.id, {"hello": "btn_del_family"}, function (response) {
$(self).find('span').html(response.msg);
setTimeout(function(){
$(self).find('span').html('');
}, 2000);
});
});
})
$('.warpper a.btn_del_fonts').on('click',function(){
console.log("btn_del_fonts");
chrome.tabs.getSelected(null, function (tab) {
chrome.tabs.sendRequest(tab.id, {"hello": "btn_del_fonts"}, function (response) {
console.log("response",response);
});
});
})

4
lerna.json Normal file
View File

@@ -0,0 +1,4 @@
{
"version": "2.4.0",
"packages": ["website", "electron/*"]
}

View File

@@ -1,16 +0,0 @@
{
"name": "Wxmp",
"version": "1.0",
"description": "微信公众账号发文章优化文章插件",
"icons": { "128": "img/icon.png" },
"permissions": ["*://*/*","declarativeContent","tabs", "unlimitedStorage"],
"browser_action": {
"default_title": "",
"default_icon": "img/icon.png",
"default_popup": "popup.html"
},
"manifest_version": 2,
"content_scripts": [{"matches": ["*://*/*"],"js": ["js/bg.js"]}],
"permissions": ["*://*/*","tabs"]
}

35
package.json Normal file
View File

@@ -0,0 +1,35 @@
{
"private": true,
"scripts": {
"build": "lerna exec --scope @wcj/* --ignore wxmp -- npm run build",
"doc": "npm run-script build --workspace website",
"start": "npm run-script start --workspace website",
"build:app": "npm run-script build --workspace wxmp",
"⬆️⬆️⬆️⬆️⬆️ package ⬆️⬆️⬆️⬆️⬆️": "▲▲▲▲▲ package ▲▲▲▲▲",
"version": "lerna version --exact --force-publish --no-push --no-git-tag-version",
"prepare": "husky install",
"prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'",
"hoist": "lerna bootstrap --hoist",
"clean": "lerna clean --yes"
},
"license": "MIT",
"devDependencies": {
"@lerna/legacy-package-management": "^8.0.0",
"cross-env": "^7.0.3",
"husky": "^8.0.1",
"lerna": "^8.0.0",
"prettier": "^3.0.2",
"react": "~19.1.0",
"react-dom": "~19.1.0",
"tsbb": "~4.4.0"
},
"workspaces": {
"packages": [
"electron/**",
"website"
]
},
"engines": {
"node": ">=16.0.0"
}
}

View File

@@ -1,18 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>弹出层</title>
<link rel="stylesheet" type="text/css" href="css/popup.css">
</head>
<body>
<div class="warpper">
<ul>
<li><a class="btn_del_family" href="#">优化文章样式<span></span></a></li>
<!-- <li><a class="btn_del_fonts" href="#">删除family样式</a></li> -->
</ul>
</div>
<script type="text/javascript" src="js/jq.3.0.0.min.js"></script>
<script type="text/javascript" src="js/popup.js"></script>
</body>
</html>

10
renovate.json Normal file
View File

@@ -0,0 +1,10 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:base"],
"packageRules": [
{
"matchPackagePatterns": ["*"],
"rangeStrategy": "replace"
}
]
}

4
website/.dockerignore Normal file
View File

@@ -0,0 +1,4 @@
node_modules
public
src
.git

48
website/.kktrc.ts Normal file
View File

@@ -0,0 +1,48 @@
import webpack, { Configuration } from 'webpack';
import lessModules from '@kkt/less-modules';
import { mdCodeModulesLoader } from 'markdown-react-code-preview-loader';
import { disableScopePlugin } from '@kkt/scope-plugin-options';
import { LoaderConfOptions } from 'kkt';
import raw from '@kkt/raw-modules';
import pkg from './package.json';
export default (conf: Configuration, env: 'development' | 'production', options: LoaderConfOptions) => {
conf = lessModules(conf, env, options);
conf = mdCodeModulesLoader(conf);
conf = raw(conf, env, {
...options,
test: /\.(md.css)$/i,
});
conf = disableScopePlugin(conf);
conf.plugins!.push(
new webpack.DefinePlugin({
VERSION: JSON.stringify(pkg.version),
}),
);
/** https://github.com/kktjs/kkt/issues/446 */
conf.ignoreWarnings = [{ module: /node_modules[\\/]parse5[\\/]/ }];
conf.module!.exprContextCritical = false;
if (env === 'production') {
conf.output = { ...conf.output, publicPath: './' };
conf.optimization = {
...conf.optimization,
splitChunks: {
cacheGroups: {
reactvendor: {
test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
name: 'react-vendor',
chunks: 'all',
},
refractor: {
test: /[\\/]node_modules[\\/](refractor)[\\/]/,
name: 'refractor-prismjs-vendor',
chunks: 'all',
},
},
},
};
}
return conf;
};

7
website/Dockerfile Normal file
View File

@@ -0,0 +1,7 @@
# https://lipanski.com/posts/smallest-docker-image-static-website
# https://github.com/lipanski/docker-static-website
FROM lipanski/docker-static-website:latest
# Copy the static website
# Use the .dockerignore file to control what ends up inside the image!
COPY ./build .

74
website/package.json Normal file
View File

@@ -0,0 +1,74 @@
{
"name": "website",
"version": "2.4.0",
"private": true,
"scripts": {
"start": "kkt start",
"build": "kkt build"
},
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.18.9",
"@tanstack/react-query": "^4.2.3",
"@uiw/codemirror-theme-abcdef": "^4.11.6",
"@uiw/codemirror-theme-androidstudio": "^4.11.6",
"@uiw/codemirror-theme-atomone": "^4.11.6",
"@uiw/codemirror-theme-bbedit": "^4.11.6",
"@uiw/codemirror-theme-bespin": "^4.11.6",
"@uiw/codemirror-theme-darcula": "^4.11.6",
"@uiw/codemirror-theme-dracula": "^4.11.6",
"@uiw/codemirror-theme-duotone": "^4.11.6",
"@uiw/codemirror-theme-eclipse": "^4.11.6",
"@uiw/codemirror-theme-github": "^4.11.6",
"@uiw/codemirror-theme-okaidia": "^4.11.6",
"@uiw/codemirror-theme-sublime": "^4.11.6",
"@uiw/codemirror-theme-xcode": "^4.11.6",
"@uiw/react-back-to-top": "^1.2.0",
"@uiw/react-github-corners": "^1.5.15",
"@uiw/react-markdown-editor": "^6.0.0",
"@wcj/dark-mode": "^1.0.15",
"css-tree": "^2.2.1",
"react": "~19.1.0",
"react-dom": "~19.1.0",
"react-hot-toast": "^2.3.0",
"react-router-dom": "^6.3.0",
"rehype-attr": "^3.0.0",
"rehype-ignore": "^2.0.0",
"rehype-prism-plus": "^2.0.0",
"rehype-raw": "^7.0.0",
"rehype-stringify": "^10.0.0",
"remark-gfm": "^4.0.0",
"remark-parse": "^11.0.0",
"remark-rehype": "^11.0.0",
"styled-components": "~6.1.0",
"unified": "^11.0.0"
},
"devDependencies": {
"@kkt/less-modules": "^7.2.0",
"@kkt/raw-modules": "^7.2.0",
"@kkt/scope-plugin-options": "^7.2.0",
"@types/css-tree": "^2.3.1",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"kkt": "^7.2.0",
"markdown-react-code-preview-loader": "^2.1.2"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

BIN
website/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

24
website/public/index.html Normal file
View File

@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport">
<meta name="theme-color" content="#000000" />
<title>微信公众号 Markdown 编辑器</title>
<meta name="keywords" content="react,simple,monorepo,template,component,project,package,development" />
<meta name="description" content="Simple React package development project example template." />
<link rel="icon" href="%PUBLIC_URL%favicon.ico" />
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

17
website/src/App.tsx Normal file
View File

@@ -0,0 +1,17 @@
import { Routes, Route } from 'react-router-dom';
import { Layout } from './components/Layout';
import { HomePage } from './pages/home';
import { EditorPage } from './pages/theme/editor';
import { DocsPage } from './pages/docs';
export default function App() {
return (
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<HomePage />} />
<Route path="/editor/theme" element={<EditorPage />} />
<Route path="/doc" element={<DocsPage />} />
</Route>
</Routes>
);
}

View File

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g id="Layer_2">
<g id="color-palette">
<path d="M19.54 5.08A10.61 10.61 0 0 0 11.91 2a10 10 0 0 0-.05 20 2.58 2.58 0 0 0 2.53-1.89 2.52 2.52 0 0 0-.57-2.28.5.5 0 0 1 .37-.83h1.65A6.15 6.15 0 0 0 22 11.33a8.48 8.48 0 0 0-2.46-6.25Zm-12.7 9.66a1.5 1.5 0 1 1 .4-2.08 1.49 1.49 0 0 1-.4 2.08ZM8.3 9.25a1.5 1.5 0 1 1-.55-2 1.5 1.5 0 0 1 .55 2ZM11 7a1.5 1.5 0 1 1 1.5-1.5A1.5 1.5 0 0 1 11 7Zm5.75.8a1.5 1.5 0 1 1 .55-2 1.5 1.5 0 0 1-.55 2Z" style="fill:#231f20" id="color-palette-2"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 576 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 1024 1024" height="1em" width="1em">
<path d="M511.6 76.3C264.3 76.2 64 276.4 64 523.5 64 718.9 189.3 885 363.8 946c23.5 5.9 19.9-10.8 19.9-22.2v-77.5c-135.7 15.9-141.2-73.9-150.3-88.9C215 726 171.5 718 184.5 703c30.9-15.9 62.4 4 98.9 57.9 26.4 39.1 77.9 32.5 104 26 5.7-23.5 17.9-44.5 34.7-60.8-140.6-25.2-199.2-111-199.2-213 0-49.5 16.3-95 48.3-131.7-20.4-60.5 1.9-112.3 4.9-120 58.1-5.2 118.5 41.6 123.2 45.3 33-8.9 70.7-13.6 112.9-13.6 42.4 0 80.2 4.9 113.5 13.9 11.3-8.6 67.3-48.8 121.3-43.9 2.9 7.7 24.7 58.3 5.5 118 32.4 36.8 48.9 82.7 48.9 132.3 0 102.2-59 188.1-200 212.9a127.5 127.5 0 0 1 38.1 91v112.5c.8 9 0 17.9 15 17.9 177.1-59.7 304.6-227 304.6-424.1 0-247.2-200.4-447.3-447.5-447.3z"/>
</svg>

After

Width:  |  Height:  |  Size: 784 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="#1aad1a" viewBox="0 0 1024 1024" height="1em" width="1em">
<path d="M690.1 377.4c5.9 0 11.8.2 17.6.5-24.4-128.7-158.3-227.1-319.9-227.1C209 150.8 64 271.4 64 420.2c0 81.1 43.6 154.2 111.9 203.6a21.5 21.5 0 0 1 9.1 17.6c0 2.4-.5 4.6-1.1 6.9-5.5 20.3-14.2 52.8-14.6 54.3-.7 2.6-1.7 5.2-1.7 7.9 0 5.9 4.8 10.8 10.8 10.8 2.3 0 4.2-.9 6.2-2l70.9-40.9c5.3-3.1 11-5 17.2-5 3.2 0 6.4.5 9.5 1.4 33.1 9.5 68.8 14.8 105.7 14.8 6 0 11.9-.1 17.8-.4-7.1-21-10.9-43.1-10.9-66 0-135.8 132.2-245.8 295.3-245.8zm-194.3-86.5c23.8 0 43.2 19.3 43.2 43.1s-19.3 43.1-43.2 43.1c-23.8 0-43.2-19.3-43.2-43.1s19.4-43.1 43.2-43.1zm-215.9 86.2c-23.8 0-43.2-19.3-43.2-43.1s19.3-43.1 43.2-43.1 43.2 19.3 43.2 43.1-19.4 43.1-43.2 43.1zm586.8 415.6c56.9-41.2 93.2-102 93.2-169.7 0-124-120.8-224.5-269.9-224.5-149 0-269.9 100.5-269.9 224.5S540.9 847.5 690 847.5c30.8 0 60.6-4.4 88.1-12.3 2.6-.8 5.2-1.2 7.9-1.2 5.2 0 9.9 1.6 14.3 4.1l59.1 34c1.7 1 3.3 1.7 5.2 1.7a9 9 0 0 0 6.4-2.6 9 9 0 0 0 2.6-6.4c0-2.2-.9-4.4-1.4-6.6-.3-1.2-7.6-28.3-12.2-45.3-.5-1.9-.9-3.8-.9-5.7.1-5.9 3.1-11.2 7.6-14.5zM600.2 587.2c-19.9 0-36-16.1-36-35.9 0-19.8 16.1-35.9 36-35.9s36 16.1 36 35.9c0 19.8-16.2 35.9-36 35.9zm179.9 0c-19.9 0-36-16.1-36-35.9 0-19.8 16.1-35.9 36-35.9s36 16.1 36 35.9a36.08 36.08 0 0 1-36 35.9z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,29 @@
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 105 105" fill="currentColor">
<circle cx="12.5" cy="12.5" r="12.5">
<animate attributeName="fill-opacity" begin="0s" dur="1s" values="1;.2;1" calcMode="linear" repeatCount="indefinite"/>
</circle>
<circle cx="12.5" cy="52.5" r="12.5" fill-opacity=".5">
<animate attributeName="fill-opacity" begin="100ms" dur="1s" values="1;.2;1" calcMode="linear" repeatCount="indefinite"/>
</circle>
<circle cx="52.5" cy="12.5" r="12.5">
<animate attributeName="fill-opacity" begin="300ms" dur="1s" values="1;.2;1" calcMode="linear" repeatCount="indefinite"/>
</circle>
<circle cx="52.5" cy="52.5" r="12.5">
<animate attributeName="fill-opacity" begin="600ms" dur="1s" values="1;.2;1" calcMode="linear" repeatCount="indefinite"/>
</circle>
<circle cx="92.5" cy="12.5" r="12.5">
<animate attributeName="fill-opacity" begin="800ms" dur="1s" values="1;.2;1" calcMode="linear" repeatCount="indefinite"/>
</circle>
<circle cx="92.5" cy="52.5" r="12.5">
<animate attributeName="fill-opacity" begin="400ms" dur="1s" values="1;.2;1" calcMode="linear" repeatCount="indefinite"/>
</circle>
<circle cx="12.5" cy="92.5" r="12.5">
<animate attributeName="fill-opacity" begin="700ms" dur="1s" values="1;.2;1" calcMode="linear" repeatCount="indefinite"/>
</circle>
<circle cx="52.5" cy="92.5" r="12.5">
<animate attributeName="fill-opacity" begin="500ms" dur="1s" values="1;.2;1" calcMode="linear" repeatCount="indefinite"/>
</circle>
<circle cx="92.5" cy="92.5" r="12.5">
<animate attributeName="fill-opacity" begin="200ms" dur="1s" values="1;.2;1" calcMode="linear" repeatCount="indefinite"/>
</circle>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,42 @@
import React, { useContext } from 'react';
import { ICommand } from '@uiw/react-markdown-editor';
import styled from 'styled-components';
import { Context } from '../store/context';
const Input = styled.input`
position: absolute;
opacity: 0;
height: 20px;
width: 20px;
`;
const Button = styled.button``;
const ColorView: React.FC<{}> = (props) => {
const { preColor, setPreColor } = useContext(Context);
const handleChange = (evn: React.ChangeEvent<HTMLInputElement>) => {
setPreColor(evn.target.value);
};
const color = preColor ? preColor : 'currentColor';
return (
<Button type="button">
<svg viewBox="0 0 24 24" fill="none" height="16" width="16">
<path
fill="currentColor"
fillRule="evenodd"
clipRule="evenodd"
d="M8.203 2.004c1.261 0 2.304 1.103 2.476 2.538l8.483 8.484-7.778 7.778a3 3 0 0 1-4.243 0L2.9 16.562a3 3 0 0 1 0-4.243l2.804-2.805V4.961c0-1.633 1.12-2.957 2.5-2.957Zm.5 2.957v1.553l-1 1V4.961c0-.327.224-.591.5-.591.277 0 .5.264.5.591Zm0 5.914V9.342l-4.39 4.391a1 1 0 0 0 0 1.414l4.243 4.243a1 1 0 0 0 1.414 0l6.364-6.364-5.63-5.63v3.48l-.003.128h-2.01a.698.698 0 0 0 .012-.129Z"
/>
<path d="M16.859 16.875a3 3 0 1 0 4.242 0l-2.121-2.121-2.121 2.12Z" fill={color} />
</svg>
<Input type="color" value={preColor} onChange={handleChange} />
</Button>
);
};
export const colorCommand: ICommand = {
name: 'color',
keyCommand: 'color',
button: () => <ColorView />,
};

View File

@@ -0,0 +1,50 @@
import React from 'react';
import { ICommand, IMarkdownEditor, ToolBarProps } from '@uiw/react-markdown-editor';
import toast from 'react-hot-toast';
import styled from 'styled-components';
const Button = styled.button`
/* white-space: nowrap;
width: initial !important;
display: flex;
align-items: center; */
`;
const CopyView: React.FC<{ command: ICommand; editorProps: IMarkdownEditor & ToolBarProps }> = (props) => {
const { editorProps } = props;
const handleClick = () => {
const dom: HTMLDivElement | null = editorProps.preview.current;
if (!dom) {
toast.error(<div>dom is null</div>);
return;
}
dom.focus();
const htmlContent = dom.innerHTML;
navigator.clipboard
.writeText(htmlContent)
.then(() => {
toast.success(<div></div>);
})
.catch((err) => {
toast.error(<div>{JSON.stringify(err)}</div>);
console.error('Failed to copy: ', err);
});
};
return (
<Button type="button" onClick={handleClick}>
{props.command.icon}
</Button>
);
};
export const copy: ICommand = {
name: 'copy',
keyCommand: 'copy',
button: (command, props, opts) => <CopyView command={command} editorProps={{ ...props, ...opts }} />,
icon: (
<svg fill="currentColor" viewBox="0 0 24 24" height="16" width="16">
<path d="M20 2H10a2 2 0 0 0-2 2v2h8a2 2 0 0 1 2 2v8h2a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2z" />
<path d="M4 22h10c1.103 0 2-.897 2-2V10c0-1.103-.897-2-2-2H4c-1.103 0-2 .897-2 2v10c0 1.103.897 2 2 2zm2-10h6v2H6v-2zm0 4h6v2H6v-2z" />
</svg>
),
};

View File

@@ -0,0 +1,27 @@
import { NavLink } from 'react-router-dom';
import { ICommand } from '@uiw/react-markdown-editor';
import styled from 'styled-components';
const Link = styled(NavLink)`
font-size: 0.8rem;
line-height: 0.7rem;
text-decoration: none;
padding: 0.18rem 0.3rem;
&:hover {
color: var(--color-accent-fg);
background-color: var(--color-neutral-muted);
border-radius: 0.2rem;
}
`;
export const cssCommand: ICommand = {
name: 'previewTtheme',
keyCommand: 'previewTtheme',
button: () => <Link to="/editor/theme"></Link>,
};
export const previousCommand: ICommand = {
name: 'previous',
keyCommand: 'previous',
button: () => <Link to="/"></Link>,
};

View File

@@ -0,0 +1,72 @@
import React, { useContext } from 'react';
import { ICommand, IMarkdownEditor, ToolBarProps } from '@uiw/react-markdown-editor';
import styled from 'styled-components';
import { Context, previewThemes, PreviewThemeValue, themes as editorThemes, ThemeValue } from '../store/context';
const Select = styled.select`
max-width: 4rem;
padding: 0;
appearance: none;
background-color: var(--color-border-muted);
border: none;
padding: 0 0.2rem 0 0.2rem;
margin: 0;
font-family: inherit;
font-size: 0.7rem;
outline: none;
height: 1.15rem;
cursor: inherit;
line-height: inherit;
border-radius: 0.2rem;
&::-ms-expand {
display: none;
}
`;
const ThemeView: React.FC<{ command: ICommand; editorProps: IMarkdownEditor & ToolBarProps }> = (props) => {
const { theme, setTheme } = useContext(Context);
const handleChange = (ev: React.ChangeEvent<HTMLSelectElement>) => setTheme(ev.target.value as any);
return (
<Select value={theme} onChange={handleChange}>
{(Object.keys(editorThemes) as Array<ThemeValue>).map((name, key) => {
return (
<option key={key} value={name}>
{editorThemes[name].label}
</option>
);
})}
</Select>
);
};
export const theme: ICommand = {
name: 'theme',
keyCommand: 'theme',
button: (command, props, opts) => <ThemeView command={command} editorProps={{ ...props, ...opts }} />,
};
const ThemePreviewView: React.FC<{}> = () => {
const { setCss, previewTheme, setPreviewTheme } = useContext(Context);
const handleChange = (ev: React.ChangeEvent<HTMLSelectElement>) => {
const value = ev.target.value as PreviewThemeValue;
setPreviewTheme(value);
setCss(previewThemes[value].value);
};
return (
<Select value={previewTheme} onChange={handleChange}>
{(Object.keys(previewThemes) as Array<PreviewThemeValue>).map((name, key) => {
return (
<option value={name} key={key}>
{previewThemes[name].label}
</option>
);
})}
</Select>
);
};
export const previeTheme: ICommand = {
name: 'previewTtheme',
keyCommand: 'previewTtheme',
button: () => <ThemePreviewView />,
};

View File

@@ -0,0 +1,25 @@
import React from 'react';
import { ICommand } from '@uiw/react-markdown-editor';
import styled from 'styled-components';
import { ReactComponent as ColorIcon } from '../assets/color.svg';
const Title = styled.div`
font-size: 0.9rem;
font-weight: bold;
display: flex;
align-items: center;
line-height: 1;
padding-right: 0.5rem;
padding-left: 0.2rem;
`;
export const themeTitle: ICommand = {
name: 'themeTitle',
keyCommand: 'themeTitle',
button: () => (
<Title>
<ColorIcon width={16} height={16} />
</Title>
),
};

View File

@@ -0,0 +1,119 @@
import styled from 'styled-components';
import { Outlet, NavLink } from 'react-router-dom';
import '@wcj/dark-mode';
import { useContext } from 'react';
import { ReactComponent as LogoIcon } from '../assets/logo.svg';
import { ReactComponent as GithubIcon } from '../assets/github.svg';
import { ReactComponent as Loading } from '../assets/tail-spin.svg';
import { Context } from '../store/context';
const Warpper = styled.div``;
const HeaderPlace = styled.div`
position: relative;
height: 2.8rem;
`;
const Header = styled.header`
-webkit-app-region: drag;
display: flex;
flex-direction: row;
justify-content: space-between;
background: var(--color-canvas-default);
border-bottom: 1px solid var(--color-border-muted);
padding: 0.5rem 0.6rem 0.5rem 0.8rem;
position: fixed;
width: 100%;
z-index: 9;
`;
const Article = styled.article`
display: flex;
flex-direction: row;
align-items: center;
gap: 0.6rem;
`;
const Logo = styled(LogoIcon)`
max-width: 3.6rem;
`;
const Title = styled.h1`
font-size: 1rem;
margin: 0;
display: flex;
align-items: center;
user-select: none;
sup {
color: var(--color-fg-subtle);
margin-left: 0.4rem;
background-color: var(--color-border-muted);
border-radius: 0.1rem;
padding: 0 0.2rem 0 0.1rem;
font-weight: normal;
font-size: 0.7rem;
letter-spacing: -0.1rem;
}
`;
const Section = styled.section`
display: flex;
align-items: center;
gap: 0.5rem;
dark-mode {
font-size: 1.05rem;
display: block;
line-height: 12px;
margin-left: 0.6rem;
}
a svg {
display: block;
}
a {
text-decoration: none;
color: var(--color-theme-text);
padding: 0.1rem 0.3rem;
transition: all 0.3s;
font-size: 0.9rem;
border-radius: 0.2rem;
&.active {
background-color: var(--color-accent-fg);
box-shadow: inset 0 -0.3rem 0 var(--color-accent-fg);
color: #fff;
}
&:hover:not(.active):not(:last-child) {
background-color: var(--color-accent-fg);
color: #fff;
border-radius: 0.2rem;
}
}
`;
export function Layout() {
const { isLoading } = useContext(Context);
return (
<Warpper className="wmde-markdown-color">
<HeaderPlace>
<Header className="header">
<Article className="logo">
<Logo width={28} height={28} />
<Title>
<sup> v{VERSION} </sup>
</Title>
{isLoading && <Loading />}
</Article>
<Section>
<NavLink to="/"></NavLink>
<NavLink to="/editor/theme"></NavLink>
<NavLink to="/doc"></NavLink>
<dark-mode permanent dark="Dark" light="Light" />
<a href="https://github.com/jaywcjlove/wxmp" target="__blank">
<GithubIcon width={23} height={23} />
</a>
</Section>
</Header>
</HeaderPlace>
<Outlet />
</Warpper>
);
}

72
website/src/index.tsx Normal file
View File

@@ -0,0 +1,72 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import { HashRouter } from 'react-router-dom';
import BackToUp from '@uiw/react-back-to-top';
import { Toaster } from 'react-hot-toast';
import { createGlobalStyle } from 'styled-components';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import App from './App';
import { Provider } from './store/Provider';
export const GlobalStyle = createGlobalStyle`
[data-color-mode*='dark'], [data-color-mode*='dark'] body {
--color-fg-default: #c9d1d9;
--color-fg-muted: #8b949e;
--color-fg-subtle: #484f58;
--color-canvas-default: #0d1117;
--color-canvas-subtle: #161b22;
--color-border-default: #30363d;
--color-border-muted: #21262d;
--color-neutral-muted: rgba(110,118,129,0.4);
--color-accent-fg: #58a6ff;
--color-accent-emphasis: #1f6feb;
--color-attention-subtle: rgba(187,128,9,0.15);
--color-danger-fg: #f85149;
}
[data-color-mode*='light'], [data-color-mode*='light'] body {
--color-fg-default: #24292f;
--color-fg-muted: #57606a;
--color-fg-subtle: #6e7781;
--color-canvas-default: #ffffff;
--color-canvas-subtle: #f6f8fa;
--color-border-default: #d0d7de;
--color-border-muted: hsla(210,18%,87%,1);
--color-neutral-muted: rgba(175,184,193,0.2);
--color-accent-fg: #0969da;
--color-accent-emphasis: #0969da;
--color-attention-subtle: #fff8c5;
--color-danger-fg: #cf222e;
}
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
`;
const queryClient = new QueryClient();
const style: React.CSSProperties = { zIndex: 999 };
const container = document.getElementById('root');
const root = createRoot(container!);
root.render(
<HashRouter>
<Toaster />
<BackToUp style={style}>Top</BackToUp>
<GlobalStyle />
<QueryClientProvider client={queryClient}>
<Provider>
<App />
</Provider>
</QueryClientProvider>
</HashRouter>,
);

View File

@@ -0,0 +1,17 @@
import MarkdownEditor from '@uiw/react-markdown-editor';
import styled from 'styled-components';
import { markdownString } from '../../store/context';
const Warpper = styled.div`
max-width: 59rem;
margin: 0 auto 0 auto;
padding: 0 1rem 3rem 1rem;
`;
export const DocsPage = () => {
return (
<Warpper>
<MarkdownEditor.Markdown source={markdownString} />
</Warpper>
);
};

View File

@@ -0,0 +1,20 @@
import { MarkdownPreviewProps } from '@uiw/react-markdown-preview';
import styled from 'styled-components';
import { useContext } from 'react';
import { Context } from '../../store/context';
import { markdownToHTML } from '../../utils/markdownToHTML';
export const Warpper = styled.div`
width: 375px;
padding: 20px;
box-shadow: 0 0 60px rgb(0 0 0 / 10%);
min-height: 100%;
font-size: 17px;
`;
export const Preview = (props: MarkdownPreviewProps) => {
const { css, preColor, previewTheme } = useContext(Context);
const html = markdownToHTML(props.source || '', css, { preColor, previewTheme });
return <Warpper contentEditable spellCheck={false} dangerouslySetInnerHTML={{ __html: html }} />;
};

View File

@@ -0,0 +1,32 @@
import MarkdownEditor, { getCommands } from '@uiw/react-markdown-editor';
import { useContext } from 'react';
// @ts-ignore
import { EditorView } from '@codemirror/view';
import { Preview } from './Preview';
import { copy } from '../../commands/copy';
import { colorCommand } from '../../commands/color';
import { theme as themeCommand, previeTheme } from '../../commands/theme';
import { cssCommand } from '../../commands/css';
import { Context, themes } from '../../store/context';
export const HomePage = () => {
const commands = [...getCommands(), themeCommand];
const { theme, markdown, isLoading, setMarkdown } = useContext(Context);
const themeValue = themes[theme].value;
const handleChange = (value: string) => setMarkdown(value);
return (
<MarkdownEditor
value={markdown}
toolbars={commands}
theme={themeValue}
readOnly={isLoading}
toolbarsMode={[cssCommand, previeTheme, copy, colorCommand, 'fullscreen', 'preview']}
extensions={[EditorView.lineWrapping]}
renderPreview={Preview}
previewWidth="420px"
onChange={handleChange}
visible={true}
height="calc(100vh - 4.6rem)"
/>
);
};

View File

@@ -0,0 +1,11 @@
import { useContext } from 'react';
import { Context } from '../../store/context';
import { markdownToHTML } from '../../utils/markdownToHTML';
import { Warpper } from '../home/Preview';
export const Preview = () => {
const { css, markdown, preColor, previewTheme } = useContext(Context);
const html = markdownToHTML(markdown, css, { preColor, previewTheme });
return <Warpper contentEditable spellCheck={false} dangerouslySetInnerHTML={{ __html: html }} />;
};

View File

@@ -0,0 +1,33 @@
import MarkdownEditor, { IMarkdownEditor } from '@uiw/react-markdown-editor';
import { useContext } from 'react';
import { EditorView } from '@codemirror/view';
import { css as cssLang } from '@codemirror/lang-css';
import { Preview } from './Preview';
import { copy } from '../../commands/copy';
import { previousCommand } from '../../commands/css';
import { themeTitle } from '../../commands/title';
import { theme as themeCommand, previeTheme } from '../../commands/theme';
import { Context, themes } from '../../store/context';
export const EditorPage = () => {
const commands = [themeTitle, themeCommand, previousCommand];
const toolbarsMode: IMarkdownEditor['toolbarsMode'] = [previeTheme, copy, 'fullscreen', 'preview'];
const { theme, css, setCss, isLoading } = useContext(Context);
const value = themes[theme].value;
const handleChange = (value: string) => setCss(value);
return (
<MarkdownEditor
value={css}
theme={value}
readOnly={isLoading}
toolbars={commands}
toolbarsMode={toolbarsMode}
reExtensions={[EditorView.lineWrapping, cssLang()]}
renderPreview={Preview}
previewWidth="420px"
onChange={handleChange}
visible={true}
height="calc(100vh - 4.6rem)"
/>
);
};

19
website/src/react-app-env.d.ts vendored Normal file
View File

@@ -0,0 +1,19 @@
/// <reference types="react-scripts" />
declare module '*.less' {
const classes: { readonly [key: string]: string };
export default classes;
}
declare var VERSION: string;
declare module '*.md' {
import { CodeBlockData } from 'markdown-react-code-preview-loader';
const src: CodeBlockData;
export default src;
}
declare module '*.md.css' {
const src: string;
export default src;
}

View File

@@ -0,0 +1,53 @@
import React, { useEffect } from 'react';
import { useSearchParams } from 'react-router-dom';
import { PreviewThemeValue, previewThemes, ThemeValue, Context, markdownString } from './context';
import { useMdSource } from './getMdSource';
export const Provider: React.FC<React.PropsWithChildren> = ({ children }) => {
const [searchParams, setSearchParams] = useSearchParams();
const paramPreviewTheme = searchParams.get('theme') as PreviewThemeValue;
const initPreviewTheme = paramPreviewTheme || 'underscore';
const mdurl = searchParams.get('md');
const [markdown, setMarkdown] = React.useState<string>(mdurl ? '' : markdownString);
const [css, setCss] = React.useState<string>(previewThemes[initPreviewTheme].value);
const [previewTheme, setPreviewTheme] = React.useState<PreviewThemeValue>(initPreviewTheme);
const [theme, setTheme] = React.useState<ThemeValue>('default');
const [preColor, setPreColor] = React.useState<string>(
previewThemes[initPreviewTheme] ? previewThemes[initPreviewTheme].color : '',
);
const [isLoading, setIsLoading] = React.useState<boolean>(true);
const { data: mddata, isLoading: loading } = useMdSource(mdurl);
useEffect(() => {
if (paramPreviewTheme !== previewTheme) {
searchParams.set('theme', previewTheme);
setSearchParams(searchParams);
}
}, [paramPreviewTheme, previewTheme, searchParams, setSearchParams]);
useEffect(() => {
if (mdurl) {
setMarkdown(mddata || '');
}
}, [mddata, mdurl]);
useEffect(() => setIsLoading(loading), [loading]);
useEffect(() => setPreColor(previewThemes[initPreviewTheme].color), [initPreviewTheme]);
return (
<Context.Provider
value={{
preColor,
setPreColor,
isLoading,
setIsLoading,
markdown,
setMarkdown,
css,
setCss,
previewTheme,
setPreviewTheme,
theme,
setTheme,
}}
>
{children}
</Context.Provider>
);
};

View File

@@ -0,0 +1,183 @@
import React from 'react';
import { defaultTheme } from '@uiw/react-markdown-editor';
import { abcdef } from '@uiw/codemirror-theme-abcdef';
import { androidstudio } from '@uiw/codemirror-theme-androidstudio';
import { atomone } from '@uiw/codemirror-theme-atomone';
import { bbedit } from '@uiw/codemirror-theme-bbedit';
import { bespin } from '@uiw/codemirror-theme-bespin';
import { darcula } from '@uiw/codemirror-theme-darcula';
import { dracula } from '@uiw/codemirror-theme-dracula';
import { duotoneLight, duotoneDark } from '@uiw/codemirror-theme-duotone';
import { eclipse } from '@uiw/codemirror-theme-eclipse';
import { githubLight, githubDark } from '@uiw/codemirror-theme-github';
import { okaidia } from '@uiw/codemirror-theme-okaidia';
import { sublime } from '@uiw/codemirror-theme-sublime';
import { xcodeLight, xcodeDark } from '@uiw/codemirror-theme-xcode';
import defStyle from '../themes/default.md.css';
import simpleStyle from '../themes/simple.md.css';
import underscoreStyle from '../themes/underscore.md.css';
import baseStyle from '../themes/base.md.css';
import data from '../../../README.md';
export const markdownString = data.source;
export const themes = {
default: {
label: '默认主题',
value: defaultTheme,
},
abcdef: {
label: 'Abcdef Theme',
value: abcdef,
},
androidstudio: {
label: 'Android Studio Theme',
value: androidstudio,
},
atomone: {
label: 'Atomone Theme',
value: atomone,
},
bbedit: {
label: 'Bbedit Theme',
value: bbedit,
},
bespin: {
label: 'Bespin Theme',
value: bespin,
},
darcula: {
label: 'Darcula Theme',
value: darcula,
},
dracula: {
label: 'Dracula Theme',
value: dracula,
},
duotoneLight: {
label: 'Duotone Light Theme',
value: duotoneLight,
},
duotoneDark: {
label: 'Duotone Dark Theme',
value: duotoneDark,
},
eclipse: {
label: 'Eclipse Theme',
value: eclipse,
},
githubLight: {
label: 'Github Light Theme',
value: githubLight,
},
githubDark: {
label: 'Github Dark Theme',
value: githubDark,
},
okaidia: {
label: 'Okaidia Theme',
value: okaidia,
},
sublime: {
label: 'Sublime Theme',
value: sublime,
},
xcodeLight: {
label: 'Xcode Light Theme',
value: xcodeLight,
},
xcodeDark: {
label: 'Xcode Dark Theme',
value: xcodeDark,
},
};
export const previewThemes = {
default: {
label: '翡翠绿',
value: defStyle,
color: '#009874',
},
simple: {
label: '简洁蓝',
value: simpleStyle,
color: '#0f4c81',
},
underscore: {
label: '下划线黄',
value: underscoreStyle,
color: '#ffb11b',
},
base: {
label: '简洁',
value: baseStyle,
color: '',
},
};
/** 用于全局主题替换样式 */
export const replaceData: Record<PreviewThemeValue, ReplaceData[]> = {
underscore: [
{ select: 'a', name: 'color', value: '{{color}}' },
{ select: 'h1', name: 'box-shadow', value: 'inset 0 -0.9rem 0 0 {{color}}' },
{ select: 'h2', name: 'box-shadow', value: 'inset 0 -0.7rem 0 0 {{color}}' },
{ select: 'h3', name: 'border-left', value: '5px solid {{color}}' },
],
default: [
{ select: 'a', name: 'color', value: '{{color}}' },
{ select: 'h1', name: 'border-bottom', value: '3px solid {{color}}' },
{ select: 'h2', name: 'background', value: '{{color}}' },
{ select: 'h3', name: 'border-left', value: '5px solid {{color}}' },
],
simple: [
{ select: 'a', name: 'color', value: '{{color}}' },
{ select: 'h1', name: 'border-bottom', value: '3px solid {{color}}' },
{ select: 'h2', name: 'background', value: '{{color}}' },
{ select: 'h3', name: 'border-left', value: '5px solid {{color}}' },
{ select: '.code-spans', name: 'color', value: '{{color}}' },
],
base: [],
};
export type ReplaceData = {
select: string;
name: string;
value: string;
};
export const colors = (Object.keys(previewThemes) as Array<keyof typeof previewThemes>).map(
(key) => previewThemes[key].color,
);
export type ThemeValue = keyof typeof themes;
export type PreviewThemeValue = keyof typeof previewThemes;
export interface CreateContext {
preColor: string;
setPreColor: React.Dispatch<React.SetStateAction<string>>;
isLoading: boolean;
setIsLoading: React.Dispatch<React.SetStateAction<boolean>>;
markdown: string;
setMarkdown: React.Dispatch<React.SetStateAction<string>>;
css: string;
setCss: React.Dispatch<React.SetStateAction<string>>;
previewTheme: PreviewThemeValue;
setPreviewTheme: React.Dispatch<React.SetStateAction<PreviewThemeValue>>;
theme: ThemeValue;
setTheme: React.Dispatch<React.SetStateAction<ThemeValue>>;
}
export const Context = React.createContext<CreateContext>({
preColor: '',
setPreColor: () => {},
isLoading: true,
setIsLoading: () => {},
markdown: data.source,
setMarkdown: () => {},
css: previewThemes['underscore'].value,
setCss: () => {},
previewTheme: 'underscore',
setPreviewTheme: () => {},
theme: 'default',
setTheme: () => {},
});

View File

@@ -0,0 +1,25 @@
import { useQuery } from '@tanstack/react-query';
import toast from 'react-hot-toast';
import styled from 'styled-components';
const Warpper = styled.div`
font-size: 0.8rem;
`;
export const useMdSource = (url: string | null) => {
return useQuery(['database-list', url], () => {
if (!url) return Promise.resolve('');
return fetch(url)
.then((response) => response.text())
.then((data) => {
return data;
})
.catch((err) => {
toast.error(
<Warpper>
<a href={url}>URL</a>
</Warpper>,
);
});
});
};

View File

@@ -0,0 +1,186 @@
a {
color: inherit;
text-decoration: none;
}
h1 {
color: inherit;
font-size: 18px;
font-weight: bold;
}
h2 {
color: inherit;
margin: 2.5rem 0 1rem 0;
font-size: 16px;
font-weight: bold;
}
h3 {
color: inherit;
margin: 1em 0 1em 0;
font-weight: bold;
font-size: 14px;
}
h4 {
color: inherit;
margin: 0.6em 0 0.6em 0;
font-weight: bold;
font-size: 12px;
}
p {
color: initial;
font-size: 16px;
line-height: 1.5em;
}
ul {
padding-left: 1.2em;
}
ol {
padding-left: 1.2em;
}
li {
margin: 0;
font-size: 14px;
line-height: 1.5em;
}
blockquote {
font-style: normal;
border-left: none;
margin: 1em 0;
line-height: 1.5em;
}
pre {
display: block;
overflow-x: auto;
padding: 1em;
color: rgb(51, 51, 51);
background: rgb(248, 248, 248);
font-size: 14px;
font-weight: 400;
letter-spacing: normal;
word-spacing: 0px;
border-radius: 5px;
margin: 0.9rem 0;
white-space: pre;
}
table {
width: 100% !important;
border-collapse: collapse;
line-height: 1.35;
font-size: 14px;
}
td {
border: 1px solid #ddd;
padding: 0.25em 0.5em;
}
th {
background: rgb(0 0 0 / 5%);
border: 1px solid #ddd;
padding: 0.25em 0.5em;
}
.code-highlight {
text-align: left;
font-family: Menlo, 'Operator Mono', Consolas, Monaco, monospace;
font-size: 14px;
margin: 0px;
white-space: nowrap;
}
.code-line {
display: block;
line-height: 1.3;
}
.code-spans {
text-align: left;
line-height: 1;
white-space: initial;
background: rgba(27, 31, 35, 0.05);
padding: 0.1em 0.3em;
border-radius: 0.3em;
font-weight: bold;
font-size: 1em;
top: -0.1em;
position: relative;
}
.footnotes-title {
display: table;
font-family: -apple-system-font, BlinkMacSystemFont, 'Helvetica Neue', 'PingFang SC', 'Hiragino Sans GB',
'Microsoft YaHei UI', 'Microsoft YaHei', Arial, sans-serif;
font-size: 14px;
font-weight: bold;
margin: 3rem 0 0.6rem 0;
padding-left: 0.2rem;
}
.footnotes-list {
font-size: 10px;
font-style: italic;
line-height: 1.2;
margin: 0.4rem 0;
}
figure {
margin: 0;
}
.image-warpper {
text-align: center;
margin-bottom: 0rem;
visibility: visible;
}
.image {
display: initial;
max-width: 100%;
}
.comment {
color: #6a737d;
}
.property {
color: #6f42c1;
}
.function {
color: #6f42c1;
}
.keyword {
color: #d73a49;
}
.punctuation {
color: #0550ae;
}
.unit {
color: #0550ae;
}
.tag {
color: #22863a;
}
.selector {
color: #22863a;
}
.quote {
color: #22863a;
}
.number {
color: #005cc5;
}
.attr-name {
color: #005cc5;
}
.attr-value {
color: #005cc5;
}

View File

@@ -0,0 +1,226 @@
a {
color: #009874;
text-decoration: none;
font-size: 14px;
}
h1 {
display: table;
text-align: center;
color: #3f3f3f;
line-height: 1.75;
font-family: -apple-system-font, BlinkMacSystemFont, 'Helvetica Neue', 'PingFang SC', 'Hiragino Sans GB',
'Microsoft YaHei UI', 'Microsoft YaHei', Arial, sans-serif;
font-size: 18px;
font-weight: bold;
margin: 2em auto 1em;
padding: 0 1em;
border-bottom: 3px solid #009874;
margin-top: 0;
}
h2 {
display: table;
text-align: center;
color: #fff;
line-height: 1.75;
font-family: -apple-system-font, BlinkMacSystemFont, 'Helvetica Neue', 'PingFang SC', 'Hiragino Sans GB',
'Microsoft YaHei UI', 'Microsoft YaHei', Arial, sans-serif;
font-size: 16px;
font-weight: bold;
margin: 4em auto 2em;
padding: 0 0.3em;
border-radius: 0.3em;
background: #009874;
}
h3 {
text-align: left;
color: #3f3f3f;
line-height: 1.2;
font-family: -apple-system-font, BlinkMacSystemFont, 'Helvetica Neue', 'PingFang SC', 'Hiragino Sans GB',
'Microsoft YaHei UI', 'Microsoft YaHei', Arial, sans-serif;
font-size: 14px;
font-weight: bold;
margin: 2em 8px 0.75em 0;
padding-left: 8px;
border-left: 5px solid #009874;
}
ul {
padding-left: 1.2em;
}
ol {
padding-left: 1.2em;
}
li {
margin: 0;
line-height: 1.5em;
font-size: 14px;
line-height: 1.5em;
}
p {
font-size: 16px;
line-height: 1.5em;
padding: 0.5em 0 !important;
margin-bottom: 0 !important;
margin-top: 0 !important;
}
blockquote {
text-align: left;
font-family: -apple-system-font, BlinkMacSystemFont, 'Helvetica Neue', 'PingFang SC', 'Hiragino Sans GB',
'Microsoft YaHei UI', 'Microsoft YaHei', Arial, sans-serif;
font-size: 14px;
font-style: normal;
border-left: none;
padding: 0.5em 1em;
border-radius: 4px;
background: rgba(27, 31, 35, 0.05);
margin: 1em 0;
}
pre {
display: block;
overflow-x: auto;
padding: 1em;
color: rgb(51, 51, 51);
background: rgb(248, 248, 248);
font-style: normal;
font-variant-ligatures: normal;
font-variant-caps: normal;
font-weight: 400;
letter-spacing: normal;
orphans: 2;
text-indent: 0px;
text-transform: none;
widows: 2;
word-spacing: 0px;
text-decoration-style: initial;
text-decoration-color: initial;
text-align: left;
line-height: 1.5;
font-family: -apple-system-font, BlinkMacSystemFont, 'Helvetica Neue', 'PingFang SC', 'Hiragino Sans GB',
'Microsoft YaHei UI', 'Microsoft YaHei', Arial, sans-serif;
border-radius: 0.3em;
margin: 0.9rem 0;
white-space: pre;
}
table {
width: 100% !important;
border-collapse: collapse;
line-height: 1.35;
font-size: 14px;
}
td {
border: 1px solid #ddd;
padding: 0.25em 0.5em;
}
th {
background: rgb(0 0 0 / 5%);
border: 1px solid #ddd;
padding: 0.25em 0.5em;
}
.code-highlight {
text-align: left;
line-height: 1.75;
font-family: Menlo, 'Operator Mono', Consolas, Monaco, monospace;
font-size: 14px;
margin: 0px;
white-space: nowrap;
}
.code-line {
display: block;
line-height: 1.3;
}
.code-spans {
text-align: left;
line-height: 1;
white-space: initial;
color: #333;
background: rgba(27, 31, 35, 0.05);
padding: 0.1em 0.3em;
border-radius: 0.3em;
font-weight: bold;
font-size: 1em;
top: -0.1em;
position: relative;
}
.footnotes-title {
display: table;
font-family: -apple-system-font, BlinkMacSystemFont, 'Helvetica Neue', 'PingFang SC', 'Hiragino Sans GB',
'Microsoft YaHei UI', 'Microsoft YaHei', Arial, sans-serif;
font-size: 14px;
font-weight: bold;
margin: 3em 0 0.6em 0;
padding-left: 0.2em;
}
.footnotes-list {
font-size: 10px;
font-style: italic;
line-height: 1.2;
margin: 0.4rem 0;
}
figure {
margin: 0;
}
.image-warpper {
text-align: center;
margin-bottom: 0rem;
visibility: visible;
}
.image {
display: initial;
max-width: 100%;
}
.comment {
color: #6a737d;
}
.property {
color: #6f42c1;
}
.function {
color: #6f42c1;
}
.keyword {
color: #d73a49;
}
.punctuation {
color: #0550ae;
}
.unit {
color: #0550ae;
}
.tag {
color: #22863a;
}
.selector {
color: #22863a;
}
.quote {
color: #22863a;
}
.number {
color: #005cc5;
}
.attr-name {
color: #005cc5;
}
.attr-value {
color: #005cc5;
}

View File

@@ -0,0 +1,226 @@
a {
color: #0f4c81;
text-decoration: none;
font-size: 14px;
}
h1 {
display: table;
text-align: center;
color: #3f3f3f;
line-height: 1.75;
font-family: -apple-system-font, BlinkMacSystemFont, 'Helvetica Neue', 'PingFang SC', 'Hiragino Sans GB',
'Microsoft YaHei UI', 'Microsoft YaHei', Arial, sans-serif;
font-size: 18px;
font-weight: bold;
margin: 2em auto 1em;
padding: 0 1em;
border-bottom: 3px solid #0f4c81;
margin-top: 0;
}
h2 {
display: table;
text-align: center;
color: #fff;
line-height: 1.75;
font-family: -apple-system-font, BlinkMacSystemFont, 'Helvetica Neue', 'PingFang SC', 'Hiragino Sans GB',
'Microsoft YaHei UI', 'Microsoft YaHei', Arial, sans-serif;
font-size: 16px;
font-weight: bold;
margin: 4em auto 2em;
padding: 0 0.3em;
border-radius: 0.3rem;
background: #0f4c81;
}
h3 {
text-align: left;
color: #3f3f3f;
line-height: 1.2;
font-family: -apple-system-font, BlinkMacSystemFont, 'Helvetica Neue', 'PingFang SC', 'Hiragino Sans GB',
'Microsoft YaHei UI', 'Microsoft YaHei', Arial, sans-serif;
font-size: 14px;
font-weight: bold;
margin: 2em 8px 0.75em 0;
padding-left: 8px;
border-left: 5px solid #0f4c81;
}
ul {
padding-left: 1.2em;
}
ol {
padding-left: 1.2em;
}
li {
margin: 0;
line-height: 1.5em;
font-size: 14px;
line-height: 1.5em;
}
p {
font-size: 16px;
line-height: 1.5em;
padding: 0.5em 0 !important;
margin-bottom: 0 !important;
margin-top: 0 !important;
}
blockquote {
text-align: left;
font-family: -apple-system-font, BlinkMacSystemFont, 'Helvetica Neue', 'PingFang SC', 'Hiragino Sans GB',
'Microsoft YaHei UI', 'Microsoft YaHei', Arial, sans-serif;
font-size: 14px;
font-style: normal;
border-left: none;
padding: 0.5em 1em;
border-radius: 4px;
background: rgba(27, 31, 35, 0.05);
margin: 1em 0;
}
pre {
display: block;
overflow-x: auto;
padding: 1em;
color: rgb(51, 51, 51);
background: rgb(248, 248, 248);
font-style: normal;
font-variant-ligatures: normal;
font-variant-caps: normal;
font-weight: 400;
letter-spacing: normal;
orphans: 2;
text-indent: 0px;
text-transform: none;
widows: 2;
word-spacing: 0px;
text-decoration-style: initial;
text-decoration-color: initial;
text-align: left;
line-height: 1.5;
font-family: -apple-system-font, BlinkMacSystemFont, 'Helvetica Neue', 'PingFang SC', 'Hiragino Sans GB',
'Microsoft YaHei UI', 'Microsoft YaHei', Arial, sans-serif;
border-radius: 0.3em;
margin: 0.9rem 0;
white-space: pre;
}
table {
width: 100% !important;
border-collapse: collapse;
line-height: 1.35;
font-size: 14px;
}
td {
border: 1px solid #ddd;
padding: 0.25em 0.5em;
}
th {
background: rgb(0 0 0 / 5%);
border: 1px solid #ddd;
padding: 0.25em 0.5em;
}
.code-highlight {
text-align: left;
line-height: 1.75;
font-family: Menlo, 'Operator Mono', Consolas, Monaco, monospace;
font-size: 14px;
margin: 0px;
white-space: nowrap;
}
.code-line {
display: block;
line-height: 1.3;
}
.code-spans {
text-align: left;
line-height: 1;
white-space: initial;
color: #0f4c81;
background: rgba(27, 31, 35, 0.05);
padding: 0.1em 0.3em;
border-radius: 0.3em;
font-weight: bold;
font-size: 14px;
top: -0.1em;
position: relative;
}
.footnotes-title {
display: table;
font-family: -apple-system-font, BlinkMacSystemFont, 'Helvetica Neue', 'PingFang SC', 'Hiragino Sans GB',
'Microsoft YaHei UI', 'Microsoft YaHei', Arial, sans-serif;
font-size: 14px;
font-weight: bold;
margin: 3em 0 0.6em 0;
padding-left: 0.2em;
}
.footnotes-list {
font-size: 10px;
font-style: italic;
line-height: 1.2;
margin: 0.4rem 0;
}
figure {
margin: 0;
}
.image-warpper {
text-align: center;
margin-bottom: 0rem;
visibility: visible;
}
.image {
display: initial;
max-width: 100%;
}
.comment {
color: #6a737d;
}
.property {
color: #6f42c1;
}
.function {
color: #6f42c1;
}
.keyword {
color: #d73a49;
}
.punctuation {
color: #0550ae;
}
.unit {
color: #0550ae;
}
.tag {
color: #22863a;
}
.selector {
color: #22863a;
}
.quote {
color: #22863a;
}
.number {
color: #005cc5;
}
.attr-name {
color: #005cc5;
}
.attr-value {
color: #005cc5;
}

View File

@@ -0,0 +1,223 @@
a {
color: #ffb11b;
text-decoration: none;
font-size: 14px;
}
h1 {
display: table;
text-align: center;
color: #3f3f3f;
line-height: 1.15;
font-family: -apple-system-font, BlinkMacSystemFont, 'Helvetica Neue', 'PingFang SC', 'Hiragino Sans GB',
'Microsoft YaHei UI', 'Microsoft YaHei', Arial, sans-serif;
font-size: 18px;
font-weight: bold;
margin: 2em auto 1em;
padding: 0 1em 0.3em 1em;
margin-top: 0;
box-shadow: inset 0 -0.9rem 0 0 #ffb11b;
}
h2 {
display: table;
line-height: 1.35;
font-family: -apple-system-font, BlinkMacSystemFont, 'Helvetica Neue', 'PingFang SC', 'Hiragino Sans GB',
'Microsoft YaHei UI', 'Microsoft YaHei', Arial, sans-serif;
font-size: 16px;
font-weight: bold;
padding: 0 0.3em;
margin: 2em 0 1em 0;
box-shadow: inset 0 -0.7rem 0 0 #ffb11b;
}
h3 {
text-align: left;
color: #3f3f3f;
line-height: 1.2;
font-family: -apple-system-font, BlinkMacSystemFont, 'Helvetica Neue', 'PingFang SC', 'Hiragino Sans GB',
'Microsoft YaHei UI', 'Microsoft YaHei', Arial, sans-serif;
font-size: 14px;
font-weight: bold;
margin: 2em 8px 0.75em 0;
padding-left: 8px;
border-left: 5px solid #ffb11b;
}
ul {
padding-left: 1.2em;
}
ol {
padding-left: 1.2em;
}
li {
margin: 0;
line-height: 1.5em;
font-size: 14px;
}
p {
font-size: 16px;
line-height: 1.5em;
padding: 0.5em 0 !important;
margin-bottom: 0 !important;
margin-top: 0 !important;
}
blockquote {
text-align: left;
line-height: 1.5em;
font-family: -apple-system-font, BlinkMacSystemFont, 'Helvetica Neue', 'PingFang SC', 'Hiragino Sans GB',
'Microsoft YaHei UI', 'Microsoft YaHei', Arial, sans-serif;
font-size: 14px;
font-style: normal;
border-left: none;
padding: 0.5em 1em;
border-radius: 4px;
background: rgba(27, 31, 35, 0.05);
margin: 1em 0;
}
pre {
display: block;
overflow-x: auto;
padding: 1em;
color: rgb(51, 51, 51);
background: rgb(248, 248, 248);
font-style: normal;
font-variant-ligatures: normal;
font-variant-caps: normal;
font-weight: 400;
letter-spacing: normal;
orphans: 2;
text-indent: 0px;
text-transform: none;
widows: 2;
word-spacing: 0px;
text-decoration-style: initial;
text-decoration-color: initial;
text-align: left;
line-height: 1.5;
font-family: -apple-system-font, BlinkMacSystemFont, 'Helvetica Neue', 'PingFang SC', 'Hiragino Sans GB',
'Microsoft YaHei UI', 'Microsoft YaHei', Arial, sans-serif;
border-radius: 0.3em;
margin: 0.9rem 0;
white-space: pre;
}
table {
width: 100% !important;
border-collapse: collapse;
line-height: 1.35;
font-size: 14px;
}
td {
border: 1px solid #ddd;
padding: 0.25em 0.5em;
}
th {
background: rgb(0 0 0 / 5%);
border: 1px solid #ddd;
padding: 0.25em 0.5em;
}
.code-highlight {
text-align: left;
line-height: 1.75;
font-family: Menlo, 'Operator Mono', Consolas, Monaco, monospace;
font-size: 14px;
margin: 0px;
white-space: nowrap;
}
.code-line {
display: block;
line-height: 1.3;
}
.code-spans {
text-align: left;
line-height: 1;
white-space: initial;
color: #333;
background: rgba(27, 31, 35, 0.05);
padding: 0.1em 0.3em;
border-radius: 0.3em;
font-weight: bold;
font-size: 14px;
top: -0.1em;
position: relative;
}
.footnotes-title {
display: table;
font-family: -apple-system-font, BlinkMacSystemFont, 'Helvetica Neue', 'PingFang SC', 'Hiragino Sans GB',
'Microsoft YaHei UI', 'Microsoft YaHei', Arial, sans-serif;
font-size: 14px;
font-weight: bold;
margin: 3em 0 0.6em 0;
padding-left: 0.2em;
}
.footnotes-list {
font-size: 10px;
font-style: italic;
line-height: 1.2;
margin: 0.4rem 0;
}
figure {
margin: 0;
}
.image-warpper {
text-align: center;
margin-bottom: 0rem;
visibility: visible;
}
.image {
display: initial;
max-width: 100%;
}
.comment {
color: #6a737d;
}
.property {
color: #6f42c1;
}
.function {
color: #6f42c1;
}
.keyword {
color: #d73a49;
}
.punctuation {
color: #0550ae;
}
.unit {
color: #0550ae;
}
.tag {
color: #22863a;
}
.selector {
color: #22863a;
}
.quote {
color: #22863a;
}
.number {
color: #005cc5;
}
.attr-name {
color: #005cc5;
}
.attr-value {
color: #005cc5;
}

147
website/src/utils/css.ts Normal file
View File

@@ -0,0 +1,147 @@
import { RootContent, Element, Text, Root } from 'hast';
import { PreviewThemeValue, replaceData, ReplaceData } from '../store/context';
/**
* {
* "replace": [
* { select: 'a', name: 'color', value: 'red' },
* { select: 'h1', name: 'box-shadow', value: 'red' },
* { select: 'h2', name: 'box-shadow', value: 'red' },
* { select: 'h3', name: 'border-left', value: 'red' },
* { select: 'h3', name: 'color', value: 'red' },
* ]
* }
*/
type BlockOption = {
replace?: Array<ReplaceData>;
};
export const getBlock = (data: any, str: string = '', opts: BlockOption = {}) => {
const { replace } = opts;
if (data && data.data && data.data.type === 'Declaration') {
const value = replace?.find((m) => m.name === data.data.property)?.value || data.data.value.value;
// console.log(value)
str = `${data.data.property}: ${value}${data.data.important ? ' !important' : ''};`;
if (data.next) {
str += getBlock(data.next, '', opts);
}
}
return str;
};
type Cssdata = {
theme?: PreviewThemeValue;
color?: string;
};
export const cssdata = (list: any, result: Record<string, string> = {}, opts: Cssdata = {}) => {
if (list.data && list.data.type === 'Rule') {
const selector = list.data.prelude.value;
const options: BlockOption = {};
// console.log('opts:', opts)
if (opts.color && opts.theme && replaceData[opts.theme]) {
options.replace = replaceData[opts.theme]
.filter((m) => m.select === selector)
.map((m) => ({
...m,
value: m.value.replace('{{color}}', opts.color!),
}));
}
result[selector] = getBlock(list.data.block.children.head, '', options);
if (list.next) {
result = cssdata(list.next, { ...result }, opts);
}
}
return result;
};
export const spaceEscape = (node: RootContent) => {
if (node.type === 'element' && node.children) {
const className = node.properties?.className as string[];
if (className) {
if (!node.properties) {
node.properties = {};
}
node.properties.className = className.filter((str: string) => !/(token|control-flow)/.test(str));
}
node.children.map((elm) => {
if (elm.type === 'element' && elm.children) {
spaceEscape(elm);
}
if (elm.type === 'text') {
elm.value = elm.value.replace(/\s/g, '\u00A0');
}
return elm;
});
}
};
type ChildContent = Element | Text;
const getNodeText = (node: ChildContent[]) => {
let str = '';
node.forEach((item) => {
if (item.type === 'text') str += item.value;
else if (item.type === 'element') {
str += getNodeText(item.children as ChildContent[]);
}
});
return str.replace(/↩/, '');
};
export const footnotes = (node: Element) => {
node.children.map((item) => {
if (item.type === 'element' && item.tagName === 'h2') {
if (!item.properties) item.properties = {};
item.properties.className = ['footnotes-title'];
item.children = [
{
type: 'text',
value: '参考',
},
];
}
if (item.type === 'element' && item.tagName === 'ol') {
item.children.map((li) => {
if (li.type === 'element' && li.tagName === 'li') {
if (!li.properties) li.properties = {};
li.properties.className = ['footnotes-list'];
li.children = [
{
type: 'text',
value: getNodeText(li.children as ChildContent[]),
},
];
}
return li;
});
}
return item;
});
};
export const footnotesLabel = (node: Element) => {
const label = getNodeText(node.children as ChildContent[]);
node.children = [
{
type: 'text',
value: `[${label}]`,
},
];
};
export const imagesStyle = (node: Element, parent: Root | Element | undefined) => {
if (
parent?.type === 'element' &&
/(p|a)/.test(parent.tagName) &&
node?.type === 'element' &&
node.tagName === 'img'
) {
if (parent.tagName === 'p') {
parent.tagName = 'figure';
}
if (!parent.properties) parent.properties = {};
parent.properties.className = ['image-warpper'];
if (!node.properties) node.properties = {};
node.properties.className = ['image'];
}
};

View File

@@ -0,0 +1,108 @@
import { VFile } from 'vfile';
import { unified } from 'unified';
import * as csstree from 'css-tree';
import { Element } from 'hast';
import remarkParse from 'remark-parse';
import remarkGfm from 'remark-gfm';
import remarkRehype from 'remark-rehype';
import rehypePrism from 'rehype-prism-plus';
import rehypeRaw from 'rehype-raw';
import rehypeAttrs from 'rehype-attr';
import rehypeIgnore from 'rehype-ignore';
import rehypeRewrite from 'rehype-rewrite';
import stringify from 'rehype-stringify';
import { cssdata, spaceEscape, footnotes, footnotesLabel, imagesStyle } from './css';
export type MarkdownToHTMLOptions = {
preColor?: string;
previewTheme?: string;
};
export function markdownToHTML(md: string, css: string, opts: MarkdownToHTMLOptions = {}) {
const ast = csstree.parse(css, {
parseAtrulePrelude: false,
parseRulePrelude: false,
parseValue: false,
parseCustomProperty: false,
positions: false,
});
// @ts-ignore
const data = cssdata(ast.children.head, {}, { color: opts.preColor, theme: opts.previewTheme });
const processor = unified()
.use(remarkParse)
.use(remarkGfm)
.use(remarkRehype, { allowDangerousHtml: true })
.use(rehypeRaw)
.use(rehypePrism, {
ignoreMissing: true,
})
.use(rehypeIgnore, {})
.use(rehypeAttrs, { properties: 'attr' })
.use(rehypeRewrite, {
rewrite: (node, _index, parent) => {
if (
node?.type === 'element' &&
node?.tagName === 'code' &&
parent?.type === 'element' &&
parent?.tagName === 'pre'
) {
spaceEscape(node);
}
if (
node?.type === 'element' &&
node.tagName === 'section' &&
(node?.properties?.className as string[]).includes('footnotes')
) {
footnotes(node);
}
if (node?.type === 'element' && node.tagName === 'sup') {
footnotesLabel(node);
}
if (node?.type === 'element' && node.tagName === 'img') {
imagesStyle(node, parent);
}
// Code Spans style
if (
node?.type === 'element' &&
node?.tagName === 'code' &&
parent?.type === 'element' &&
parent?.tagName !== 'pre'
) {
if (!node.properties) node.properties = {};
node.properties!.className = ['code-spans'];
}
// List TODO style
if (parent?.type === 'element' && node?.type === 'element' && node?.tagName === 'input') {
if (parent && parent.type === 'element') {
parent.children = parent?.children.filter((elm) => (elm as Element).tagName !== 'input');
}
return;
}
// Support *.md.css
if (node?.type === 'element') {
if (!node.properties) {
node.properties = {};
}
const className = node.properties?.className as string[];
let style = '';
if (className) {
className.forEach((name) => {
if (data[`.${name}`]) {
style = data[`.${name}`];
}
});
}
if (!style) style = data[node.tagName];
if (style) {
node.properties.style = style + (node.properties.style || '');
}
}
},
})
.use(stringify);
const file = new VFile();
file.value = md;
const hastNode = processor.runSync(processor.parse(file), file);
return String(processor.stringify(hastNode, file));
}

22
website/tsconfig.json Normal file
View File

@@ -0,0 +1,22 @@
{
"include": [".kktrc.ts", "src"],
"compilerOptions": {
"jsx": "react-jsx",
"target": "esnext",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"declaration": true,
"baseUrl": "./src",
"noFallthroughCasesInSwitch": true,
"noEmit": true
}
}

BIN
wxmq.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB