Compare commits
93 Commits
v1.0.0
...
renovate/r
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5a6799e94 | ||
|
|
8991adcd15 | ||
|
|
1b2e3b534d | ||
|
|
d85368cb4f | ||
|
|
8b4194f5ae | ||
|
|
ab3423a697 | ||
|
|
504259b862 | ||
|
|
b05d90e15b | ||
|
|
f6e71388f1 | ||
|
|
1c9a7a8668 | ||
|
|
314f47f8af | ||
|
|
be06f694a5 | ||
|
|
c23ada95ff | ||
|
|
2632cb1938 | ||
|
|
77616468a4 | ||
|
|
41eb86cd2b | ||
|
|
5fe5ddfa61 | ||
|
|
82cc2a3df4 | ||
|
|
b5596d76b6 | ||
|
|
d2c2746420 | ||
|
|
ef1ed54be7 | ||
|
|
e3a3cf5ff6 | ||
|
|
4f4ad71d09 | ||
|
|
3b32e76f65 | ||
|
|
056b792519 | ||
|
|
de98442b3c | ||
|
|
8bf24877bf | ||
|
|
ad546bfb6c | ||
|
|
bac929d894 | ||
|
|
ab1c043931 | ||
|
|
5afb09a715 | ||
|
|
df79dcf694 | ||
|
|
33a60420a4 | ||
|
|
c7dba6d5de | ||
|
|
ed596a7403 | ||
|
|
7f28e6ada1 | ||
|
|
df10f96a65 | ||
|
|
02cb33cfcd | ||
|
|
13a96916d7 | ||
|
|
245d54e511 | ||
|
|
06e216aa22 | ||
|
|
a50acf3888 | ||
|
|
195d2ce8d0 | ||
|
|
9adcd7eaa7 | ||
|
|
193ec20a22 | ||
|
|
8f62d41020 | ||
|
|
8720638f9d | ||
|
|
8d33e4dab2 | ||
|
|
1c79ec8b0a | ||
|
|
f3337f064e | ||
|
|
c86d5bbc0c | ||
|
|
55c369f23c | ||
|
|
6aad6713d2 | ||
|
|
5c10978fe9 | ||
|
|
8d17f7532e | ||
|
|
7f997282fc | ||
|
|
60b32b3ca8 | ||
|
|
83e834d6cf | ||
|
|
5cc2758073 | ||
|
|
d84ad70345 | ||
|
|
ba5eb6115c | ||
|
|
57e604c195 | ||
|
|
76a6f48d0a | ||
|
|
a66e906eef | ||
|
|
b694d61bf0 | ||
|
|
fd6ad59d5c | ||
|
|
b1dc77e98b | ||
|
|
57b719c163 | ||
|
|
d806339c26 | ||
|
|
6a2279d7b2 | ||
|
|
ba0b158a47 | ||
|
|
3bf313ffc3 | ||
|
|
f245c33973 | ||
|
|
1c7167bcf9 | ||
|
|
0580011f7b | ||
|
|
d007e11f20 | ||
|
|
6da12fcb10 | ||
|
|
e0fed4783a | ||
|
|
e9b3b60f6d | ||
|
|
cbaad4e0fc | ||
|
|
3abeaf355c | ||
|
|
709849e048 | ||
|
|
9b08674a5a | ||
|
|
61649a29da | ||
|
|
92db8dd11a | ||
|
|
3d11eb8479 | ||
|
|
cce03c115b | ||
|
|
bebe6a549c | ||
|
|
cd23add2ce | ||
|
|
79f9d0d9b4 | ||
|
|
d0364a0bee | ||
|
|
ef01ce998e | ||
|
|
158a032fcb |
3
.github/FUNDING.yml
vendored
Normal 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
@@ -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
@@ -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
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx pretty-quick --staged
|
||||
13
.prettierignore
Normal file
@@ -0,0 +1,13 @@
|
||||
**/*.md
|
||||
**/*.svg
|
||||
**/*.ejs
|
||||
**/*.html
|
||||
**/*.yml
|
||||
package.json
|
||||
node_modules
|
||||
dist
|
||||
build
|
||||
coverage
|
||||
lib
|
||||
esm
|
||||
test
|
||||
11
.prettierrc
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"printWidth": 120,
|
||||
"overrides": [
|
||||
{
|
||||
"files": ".prettierrc",
|
||||
"options": { "parser": "json" }
|
||||
}
|
||||
]
|
||||
}
|
||||
21
LICENSE
Normal 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
@@ -1,36 +1,221 @@
|
||||
# Wxmp
|
||||
<div align="center">
|
||||
<h1 align="center">微信公众号 Markdown 编辑器</h1>
|
||||
</div>
|
||||
|
||||
chrome 小插件,优化在微信公众账号中发文章,因复制粘贴带过去的`font-family` CSS 样式,导致被微信过滤样式全无。使用此插件删除提交文章上所有HTML节点上的`font-family`,让复制过去的样式保持一致。
|
||||
[](https://jaywcjlove.github.io/#/sponsor)
|
||||
[](https://github.com/jaywcjlove/wxmp/actions/workflows/ci.yml)
|
||||
|
||||
目前删除这些标签上的`font-family`样式
|
||||
[](https://jaywcjlove.github.io/wxmp)
|
||||
|
||||
> `code`,`pre`,`h1`,`h2`,`h3`,`h4`,`h5`,`h6`,`p`,`div`,`span`
|
||||
微信公众号文章 Markdown 在线编辑器,使用 markdown 语法创建一篇简介美观大方的微信公众号图文。由于发版本麻烦,和一些功能无法扩展停滞开发了,未来不再开发 Chrome 的插件(暂存在 chrome 分支),通过 web 版本定制更丰富的功能。
|
||||
|
||||

|
||||
[](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 {}
|
||||
```
|
||||
|
||||
## 部署
|
||||
|
||||
[](https://hub.docker.com/r/wcjiang/wxmp) [](https://hub.docker.com/r/wcjiang/wxmp) [](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 许可证获得许可。
|
||||
|
||||
@@ -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
@@ -0,0 +1,2 @@
|
||||
dist
|
||||
website
|
||||
8
electron/app/assets/entitlements.mac.plist
Normal 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
@@ -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
BIN
electron/app/icons.iconset/icon_128x128.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
electron/app/icons.iconset/icon_128x128@2x.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
electron/app/icons.iconset/icon_16x16.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
electron/app/icons.iconset/icon_16x16@2x.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
electron/app/icons.iconset/icon_256x256.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
electron/app/icons.iconset/icon_256x256@2x.png
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
electron/app/icons.iconset/icon_32x32.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
electron/app/icons.iconset/icon_32x32@2x.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
electron/app/icons.iconset/icon_512x512.png
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
electron/app/icons.iconset/icon_512x512@2x.png
Normal file
|
After Width: | Height: | Size: 275 KiB |
BIN
electron/app/logo.png
Normal file
|
After Width: | Height: | Size: 82 KiB |
15
electron/app/main.js
Normal 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
@@ -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
@@ -0,0 +1 @@
|
||||
lib
|
||||
16
electron/main/package.json
Normal 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
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
1
electron/main/src/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './app';
|
||||
19
electron/main/tsconfig.json
Normal 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
@@ -0,0 +1 @@
|
||||
lib
|
||||
16
electron/preload/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
10
electron/preload/src/index.ts
Normal 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);
|
||||
}
|
||||
});
|
||||
27
electron/preload/tsconfig.json
Normal 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"]
|
||||
}
|
||||
BIN
img/icon.png
|
Before Width: | Height: | Size: 6.4 KiB |
37
js/bg.js
@@ -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
20
js/popup.js
@@ -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
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"version": "2.4.0",
|
||||
"packages": ["website", "electron/*"]
|
||||
}
|
||||
@@ -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
@@ -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": "~18.3.0",
|
||||
"react-dom": "~18.3.0",
|
||||
"tsbb": "~4.4.0"
|
||||
},
|
||||
"workspaces": {
|
||||
"packages": [
|
||||
"electron/**",
|
||||
"website"
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
}
|
||||
18
popup.html
@@ -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
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["config:base"],
|
||||
"packageRules": [
|
||||
{
|
||||
"matchPackagePatterns": ["*"],
|
||||
"rangeStrategy": "replace"
|
||||
}
|
||||
]
|
||||
}
|
||||
4
website/.dockerignore
Normal file
@@ -0,0 +1,4 @@
|
||||
node_modules
|
||||
public
|
||||
src
|
||||
.git
|
||||
48
website/.kktrc.ts
Normal 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
@@ -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
@@ -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": "~18.3.0",
|
||||
"react-dom": "~18.3.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": "^18.0.17",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"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
|
After Width: | Height: | Size: 15 KiB |
24
website/public/index.html
Normal 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
@@ -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>
|
||||
);
|
||||
}
|
||||
7
website/src/assets/color.svg
Normal 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 |
3
website/src/assets/github.svg
Normal 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 |
3
website/src/assets/logo.svg
Normal 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 |
29
website/src/assets/tail-spin.svg
Normal 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 |
42
website/src/commands/color.tsx
Normal 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 />,
|
||||
};
|
||||
50
website/src/commands/copy.tsx
Normal 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>
|
||||
),
|
||||
};
|
||||
27
website/src/commands/css.tsx
Normal 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>,
|
||||
};
|
||||
72
website/src/commands/theme.tsx
Normal 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 />,
|
||||
};
|
||||
25
website/src/commands/title.tsx
Normal 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>
|
||||
),
|
||||
};
|
||||
119
website/src/components/Layout.tsx
Normal 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
@@ -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>,
|
||||
);
|
||||
17
website/src/pages/docs/index.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
20
website/src/pages/home/Preview.tsx
Normal 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 }} />;
|
||||
};
|
||||
32
website/src/pages/home/index.tsx
Normal 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)"
|
||||
/>
|
||||
);
|
||||
};
|
||||
11
website/src/pages/theme/Preview.tsx
Normal 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 }} />;
|
||||
};
|
||||
33
website/src/pages/theme/editor.tsx
Normal 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
@@ -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;
|
||||
}
|
||||
53
website/src/store/Provider.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
183
website/src/store/context.tsx
Normal 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: () => {},
|
||||
});
|
||||
25
website/src/store/getMdSource.tsx
Normal 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>,
|
||||
);
|
||||
});
|
||||
});
|
||||
};
|
||||
186
website/src/themes/base.md.css
Normal 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;
|
||||
}
|
||||
226
website/src/themes/default.md.css
Normal 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;
|
||||
}
|
||||
226
website/src/themes/simple.md.css
Normal 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;
|
||||
}
|
||||
223
website/src/themes/underscore.md.css
Normal 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
@@ -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'];
|
||||
}
|
||||
};
|
||||
108
website/src/utils/markdownToHTML.ts
Normal 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
@@ -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
|
||||
}
|
||||
}
|
||||