10 Commits

Author SHA1 Message Date
jaywcjlove
709849e048 feat: support docker deplay. 2022-09-02 01:46:24 +08:00
jaywcjlove
9b08674a5a released v2.0.0 2022-09-02 01:34:27 +08:00
jaywcjlove
61649a29da chore: update renovate.json 2022-09-02 01:32:48 +08:00
jaywcjlove
92db8dd11a style: update preview styles. 2022-09-02 01:31:20 +08:00
jaywcjlove
3d11eb8479 doc: Update README.md 2022-09-02 01:26:59 +08:00
jaywcjlove
cce03c115b chore: udpate workflows config. 2022-09-02 01:26:25 +08:00
jaywcjlove
bebe6a549c doc: Update README.md 2022-09-02 01:26:25 +08:00
小弟调调™
cd23add2ce Create LICENSE 2022-09-02 01:19:05 +08:00
renovate[bot]
79f9d0d9b4 chore(deps): add renovate.json (#1) 2022-09-02 01:18:26 +08:00
jaywcjlove
d0364a0bee feat: init web app. 2022-09-02 01:16:22 +08:00
31 changed files with 1026 additions and 130 deletions

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

@@ -0,0 +1,99 @@
name: CI
on:
push:
branches:
- master
jobs:
build-deploy:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- run: npm install
- run: npm run build
- name: Generate Contributors Images
uses: jaywcjlove/github-action-contributors@main
with:
filter-author: (renovate\[bot\]|renovate-bot|dependabot\[bot\])
output: build/CONTRIBUTORS.svg
avatarSize: 42
- name: Create Tag
id: create_tag
uses: jaywcjlove/create-tag-action@main
with:
package-path: ./package.json
- name: get tag version
id: tag_version
uses: jaywcjlove/changelog-generator@main
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
commit_message: ${{ github.event.head_commit.message }} ${{steps.tag_version.outputs.tag}}
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./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}'
- name: Create Release
uses: ncipollo/release-action@v1
if: steps.create_tag.outputs.successful
with:
token: ${{ secrets.GITHUB_TOKEN }}
name: ${{ steps.create_tag.outputs.version }}
tag: ${{ steps.create_tag.outputs.version }}
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 }}
# Create Docker Image
- name: Docker login
run: docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PASSWORD }}
- name: Build Awesome Mac image
run: docker image build -t wxmp .
- name: Tags & Push image (latest)
run: |
echo "outputs.tag - ${{ steps.changelog.outputs.version }}"
docker tag wxmp ${{ secrets.DOCKER_USER }}/wxmp:latest
docker push ${{ secrets.DOCKER_USER }}/wxmp:latest
- name: Tags & Push image
if: steps.create_tag.outputs.successful
run: |
echo "outputs.tag - ${{ steps.changelog.outputs.version }}"
docker tag wxmp ${{ secrets.DOCKER_USER }}/wxmp:${{steps.changelog.outputs.version}}
docker push ${{ secrets.DOCKER_USER }}/wxmp:${{steps.changelog.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
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: steps.create_tag.outputs.successful
run: |
echo "version: v${{ steps.changelog.outputs.version }}"
docker tag ghcr.io/jaywcjlove/wxmp:latest ghcr.io/jaywcjlove/wxmp:${{steps.changelog.outputs.version}}
docker push ghcr.io/jaywcjlove/wxmp:${{steps.changelog.outputs.version}}

22
.gitignore vendored
View File

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

50
.kktrc.ts Normal file
View File

@@ -0,0 +1,50 @@
import path from 'path';
import webpack, { Configuration } from 'webpack';
import lessModules from '@kkt/less-modules';
import { mdCodeModulesLoader } from 'markdown-react-code-preview-loader';
import scopePluginOptions 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 = scopePluginOptions(conf, env, {
...options,
allowedFiles: [path.resolve(process.cwd(), 'README.md'), path.resolve(process.cwd(), 'src')],
});
conf.plugins!.push(
new webpack.DefinePlugin({
VERSION: JSON.stringify(pkg.version),
}),
);
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
Dockerfile Normal file
View File

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

21
LICENSE Normal file
View File

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

126
README.md
View File

@@ -1,41 +1,113 @@
# Wxmp <div align="center">
chrome 小插件,优化在微信公众账号中发文章,因复制粘贴带过去的`font-family` CSS 样式导致被微信过滤样式全无。使用此插件删除提交文章上所有HTML节点上的`font-family`,让复制过去的样式保持一致。 <h1 align="center">微信公众号 Markdown 编辑器</h1>
目前删除这些标签上的`font-family`样式 </div>
> `code`,`pre`,`h1`,`h2`,`h3`,`h4`,`h5`,`h6`,`p`,`div`,`span` 微信公众号文章 Markdown 编辑器,使用 markdown 语法创建一篇简介美观大方的微信公众号图文。由于发版本麻烦,和一些功能无法扩展停滞开发了,未来不再开发 Chrome 的工具(暂存在 chrome 分支),通过 web 版本定制更丰富的功能。
![界面预览](https://raw.githubusercontent.com/jaywcjlove/wxmp/master/wxmq.png) ## 功能特性
开发计划和一些功能介绍,有需求可以在 issue 中提,使得工具变得更加完善。下面示例用于 web 应用中效果展示。
## 已经实现功能 - [x] 支持 Markdown 所有基础语法
- [x] 支持自定义 CSS 样式
- [ ] 支持主题选择 & 配置。
- [x] 支持明暗两种主题预览。
- [ ] 支持色盘取色,快速替换文章整体色调
- [x] 过滤 `font-family` ### 支持代码块样式
- [x] 代码高亮区域有背景颜色;
- [x] 代码高亮区域有横向滚动条强制不换行;
- [x] 增加iOS滚动滚动弹性
- [ ] 添加设置标题工具;
- [ ] 添加字段高亮工具;
- [ ] 添加删除线工具,如:<del>删除线</del>
# 直接安装 下面是 `jsx` 代码块展示示例,并高亮代码,用于 web 应用中效果展示。
1. 下载扩展程序[Wxmp.crx](https://github.com/jaywcjlove/wxmp/releases) 文件 ```jsx
2. 在chrome里面器地址输入`chrome://extensions/` 打开插件界面 function Demo() {
3.`Wxmp.crx`文件拖入chrome浏览器的扩展程序列表中 return <div className="demo">Hello World!</div>
}
```
# 开发模式插件安装 下面是 `css` 代码块展示示例,并高亮代码,用于 web 应用中效果展示。
1. 下载文件压缩包解压 ```css
2. 在chrome里面器地址输入`chrome://extensions/` 打开插件界面 li {
3. 点击`加载已解压的扩展程序...` font-size: 16px;
4. 选择插件所在的目录 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 应用中效果展示。
1. 打开微信公众平台,新建图文消息,复制文章到编辑器中 Inline Code `{code: 0}`
2. 在右上角点击微信图标
3. 点击弹出的模态框上的删除按钮 ### 支持表格
4. 如果成功会在按钮后面提示`更改成功!!`
表格无法使用自定义样式,暂时没找到解决途径
| Header 1 | Header 2 |
| --- | --- |
| Key 1 | Value 1 |
| Key 2 | Value 2 |
| Key 3 | Value 3 |
### 支持 GFM 脚注
这是一个简单的脚注[^1]。 页面最后有一些额外的文字描述。注意这不是完整的注脚[^2]。
[^1]: https://github.github.com/gfm/
[^2]: 微信文章不支持锚点跳转和打开第三方 URL 超链接,所以不支持完整的注脚
### 支持注释
<ruby>
汉 <rp></rp><rt>Han</rt><rp></rp>
字 <rp></rp><rt>zi</rt><rp></rp>
拼 <rp></rp><rt>pin</rt><rp></rp>
音 <rp></rp><rt>yin</rt><rp></rp>
注 <rp></rp><rt>zhu</rt><rp></rp>
音 <rp></rp><rt>yin</rt><rp></rp>
</ruby>
## 部署
[![Docker Image Version (latest by date)](https://img.shields.io/docker/v/wcjiang/wxmp?logo=docker)](https://hub.docker.com/r/wcjiang/wxmp) [![Docker Image Size (latest by date)](https://img.shields.io/docker/image-size/wcjiang/wxmp?logo=docker)](https://hub.docker.com/r/wcjiang/wxmp) [![Docker Pulls](https://img.shields.io/docker/pulls/wcjiang/wxmp?logo=docker)](https://hub.docker.com/r/wcjiang/wxmp)
轻松通过 docker 部署《微信公众号 Markdown 编辑器》网站应用。
```bash
docker pull wcjiang/wxmp
# Or
docker pull ghcr.io/jaywcjlove/wxmp:latest
```
```bash
docker run --name wxmp --rm -d -p 96611:3000 wcjiang/wxmp:latest
# Or
docker run --name wxmp -itd -p 96611:3000 wcjiang/wxmp:latest
# Or
docker run --name wxmp -itd -p 96611:3000 ghcr.io/jaywcjlove/wxmp:latest
```
在浏览器中访问以下 URL
```
http://localhost:96611/
```
## Contributors
As always, thanks to our amazing contributors!
<a href="https://github.com/jaywcjlove/wxmp/graphs/contributors">
<img src="https://jaywcjlove.github.io/wxmp/CONTRIBUTORS.svg" />
</a>
Made with [github-action-contributors](https://github.com/jaywcjlove/github-action-contributors).
## License
Licensed under the MIT License.

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

View File

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

4
js/jq.3.0.0.min.js vendored

File diff suppressed because one or more lines are too long

View File

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

View File

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

61
package.json Normal file
View File

@@ -0,0 +1,61 @@
{
"name": "website",
"version": "2.0.0",
"private": true,
"scripts": {
"start": "kkt start",
"build": "kkt build"
},
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.18.9",
"@uiw/react-back-to-top": "^1.2.0",
"@uiw/react-github-corners": "^1.5.15",
"@uiw/react-markdown-editor": "^5.3.2",
"@uiw/react-markdown-preview": "^4.1.0",
"@wcj/dark-mode": "^1.0.15",
"css-tree": "^2.2.1",
"react": "^18.2.0",
"react-code-preview-layout": "^2.0.4",
"react-dom": "^18.2.0",
"react-hot-toast": "^2.3.0",
"react-router-dom": "^6.3.0",
"rehype-prism-plus": "^1.5.0",
"rehype-raw": "^6.1.1",
"rehype-stringify": "^9.0.3",
"remark-gfm": "^3.0.1",
"remark-parse": "^10.0.1",
"remark-rehype": "^10.1.0",
"styled-components": "^5.3.5",
"unified": "^10.1.2"
},
"devDependencies": {
"@kkt/less-modules": "^7.2.0",
"@kkt/raw-modules": "^7.2.0",
"@kkt/scope-plugin-options": "^7.2.0",
"@types/css-tree": "^1.0.7",
"@types/react": "^18.0.17",
"@types/react-dom": "^18.0.6",
"@types/styled-components": "^5.1.25",
"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"
]
}
}

View File

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

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

24
public/index.html Normal file
View File

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

10
renovate.json Normal file
View File

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

13
src/App.tsx Normal file
View File

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

3
src/assets/github.svg Normal file
View File

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

After

Width:  |  Height:  |  Size: 784 B

3
src/assets/logo.svg Normal file
View File

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

After

Width:  |  Height:  |  Size: 1.3 KiB

77
src/components/Layout.tsx Normal file
View File

@@ -0,0 +1,77 @@
import styled from 'styled-components';
import { Outlet } from 'react-router-dom';
import '@wcj/dark-mode';
import { ReactComponent as LogoIcon } from '../assets/logo.svg';
import { ReactComponent as GithubIcon } from '../assets/github.svg';
const Warpper = styled.div`
`;
const Header = styled.header`
display: flex;
flex-direction: row;
justify-content: space-between;
border-bottom: 1px solid var(--color-border-muted);
padding: 0.5rem 1rem 0.5rem 1rem;
`;
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: 1.0rem;
margin: 0;
display: flex;
align-items: center;
sup {
color: var(--color-fg-subtle);
margin-left: 0.4rem;
background-color: var(--color-border-muted);
border-radius: 0.1rem;
padding: 0 0.2rem;
font-weight: normal;
}
`;
const Section = styled.section`
display: flex;
align-items: center;
gap: 0.8rem;
dark-mode {
font-size: 1.4rem;
}
a svg {
display: block;
}
`;
export function Layout() {
return (
<Warpper className="wmde-markdown-color">
<Header>
<Article>
<Logo width={28} height={28} />
<Title>
<sup> v{VERSION} </sup>
</Title>
</Article>
<Section>
<dark-mode permanent dark="Dark" light="Light" />
<a href="https://github.com/jaywcjlove/wxmp" target="__blank">
<GithubIcon width={28} height={28} />
</a>
</Section>
</Header>
<Outlet />
</Warpper>
);
}

177
src/conf/default.md.css Normal file
View File

@@ -0,0 +1,177 @@
a {
color: #576b95;
text-decoration: none;
font-size: 14px;
}
h1 {
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:1.2em;
font-weight:bold;
display:table;
margin:2em auto 1em;
padding:0 1em;
border-bottom:2px solid #009874;
margin-top: 0;
}
h2 {
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:1.2em;
font-weight:bold;
display:table;
margin:4em auto 2em;
padding:0 0.3em;
border-radius: 0.3rem;
background:#009874;
}
p {
font-size: 14px;
}
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:1.1em;
font-weight:bold;
margin:2em 8px 0.75em 0;
padding-left:8px;
border-left:3px solid #009874;
}
ul {
padding-left: 1.2em;
font-size: 14px;
}
ol {
padding-left: 1.2em;
font-size: 14px;
}
li {
font-size: 16px;
margin: 0;
line-height: 26px;
color: rgb(30 41 59);
font-size: 14px;
}
blockquote {
text-align:left;
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:14px;
font-style:normal;
border-left:none;
padding:0.1rem 1rem;
border-radius:4px;
background:rgba(27,31,35,.05);
margin: 1rem 0;
}
pre {
display: block;
overflow-x: auto;
padding: 1em;
color: rgb(51, 51, 51);
background: rgb(248, 248, 248);
font-size: 14px;
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: 5px;
margin: 0.9rem 0;
white-space: pre;
}
table {
width: 100% !important;
border-collapse: collapse;
line-height: 1.35;
font-size: 90%;
}
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:pre;
color: #009874;
background:rgba(27,31,35,.05);
padding: 0.2rem 0.3rem;
border-radius:4px;
font-weight: bold;
font-size: 14px;
}
.footnotes-title {
font-family:-apple-system-font,BlinkMacSystemFont,"Helvetica Neue","PingFang SC","Hiragino Sans GB","Microsoft YaHei UI","Microsoft YaHei",Arial,sans-serif;
font-size:1.0rem;
font-weight:bold;
display:table;
margin:3rem 0 0.6rem 0;
padding-left: 0.2rem;
}
.footnotes-list {
font-size: 12px;
}
.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; }

61
src/index.tsx Normal file
View File

@@ -0,0 +1,61 @@
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 App from './App';
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;
}
* {
box-sizing: border-box;
}
`;
const container = document.getElementById('root');
const root = createRoot(container!);
root.render(
<HashRouter>
<Toaster />
<BackToUp>Top</BackToUp>
<GlobalStyle />
<App />
</HashRouter>,
);

View File

@@ -0,0 +1,17 @@
import { MarkdownPreviewProps } from '@uiw/react-markdown-preview';
import styled from 'styled-components';
import def from '../../conf/default.md.css';
import { markdownToHTML } from '../../utils/markdownToHTML';
const Warpper = styled.div`
width: 375px;
padding: 20px;
box-shadow: 0 0 60px rgb(0 0 0 / 10%);
min-height: 100%;
`;
export const Preview = (props: MarkdownPreviewProps, visible: boolean) => {
const html = markdownToHTML(props.source || '', def);
return <Warpper dangerouslySetInnerHTML={{ __html: html }} />;
}

45
src/pages/home/copy.tsx Normal file
View File

@@ -0,0 +1,45 @@
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;
padding: 0 0.4rem !important;
`;
const CopyView: React.FC<{ command: ICommand; editorProps: IMarkdownEditor & ToolBarProps }> = (props) => {
const { editorProps } = props;
const handleClick = () => {
const dom = editorProps.preview.current;
dom?.focus();
window.getSelection()?.removeAllRanges();
let range = document.createRange();
range.setStartBefore(dom?.firstChild!);
range.setEndAfter(dom?.lastChild!);
window.getSelection()?.addRange(range);
document.execCommand(`copy`);
window.getSelection()?.removeAllRanges();
toast.success(<div></div>);
}
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
src/pages/home/index.tsx Normal file
View File

@@ -0,0 +1,27 @@
import MarkdownEditor, { getCommands } from '@uiw/react-markdown-editor';
import { EditorView } from "@codemirror/view";
import styled from 'styled-components';
import data from '../../../README.md';
import { Preview } from './Preview';
import { copy } from './copy'
const Warpper = styled.div`
height: calc(100vh - 2.9rem);
`;
export const HomePage = () => {
const commands = getCommands();
return (
<Warpper>
<MarkdownEditor
value={data.source}
toolbars={commands}
toolbarsMode={[copy, 'preview', 'fullscreen']}
extensions={[EditorView.lineWrapping]}
renderPreview={Preview}
visible={true}
height="calc(100vh - 5.0rem)"
/>
</Warpper>
);
}

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

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

90
src/utils/css.ts Normal file
View File

@@ -0,0 +1,90 @@
import { RootContent, Element, Text } from 'hast';
export const getBlock = (data: any, str: string = '') => {
if (data && data.data && data.data.type === 'Declaration') {
str = `${data.data.property}: ${data.data.value.value}${data.data.important ? ' !important' : ''};`;
if (data.next) {
str += getBlock(data.next)
}
}
return str;
}
export const cssdata = (list: any, result: Record<string, string> = {}) => {
if (list.data && list.data.type === 'Rule') {
result[list.data.prelude.value] = getBlock(list.data.block.children.head);
if (list.next) {
result = cssdata(list.next, {...result})
}
}
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}]`
}];
}

View File

@@ -0,0 +1,80 @@
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 rehypeRewrite from 'rehype-rewrite';
import stringify from 'rehype-stringify';
import { cssdata, spaceEscape, footnotes, footnotesLabel } from './css';
export type MarkdownToHTMLOptions = {
}
export function markdownToHTML(md: string, css: string, options: MarkdownToHTMLOptions = {}) {
const ast = csstree.parse(css, {
parseAtrulePrelude: false,
parseRulePrelude: false,
parseValue: false,
parseCustomProperty: false,
positions: false
});
// @ts-ignore
const data = cssdata(ast.children.head);
const processor = unified()
.use(remarkParse)
.use(remarkRehype, { allowDangerousHtml: true })
.use(rehypePrism)
.use(remarkGfm)
.use(rehypeRaw)
.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 === 'code' && parent?.type === 'element' && parent?.tagName !== 'pre') {
if (!node.properties) node.properties = {}
node.properties!.className = ['code-spans'];
}
if (node?.type === 'element') {
if (node.tagName === 'input' && parent?.type === 'element') {
if (parent && parent.type === 'element') {
parent.children = parent?.children.filter(elm => (elm as Element).tagName !== 'input')
}
return;
}
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;
}
}
}
})
.use(stringify);
const file = new VFile();
file.value = md;
const hastNode = processor.runSync(processor.parse(file), file);
return String(processor.stringify(hastNode, file));
}

22
tsconfig.json Normal file
View File

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

BIN
wxmq.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB