20 Commits

Author SHA1 Message Date
jaywcjlove
1c79ec8b0a chore: update workflows config. 2022-09-05 09:39:17 +08:00
jaywcjlove
f3337f064e website: modify preview width. 2022-09-05 09:31:41 +08:00
renovate[bot]
c86d5bbc0c chore(deps): update dependency lerna to v5.5.0 (#7)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-05 08:15:30 +08:00
jaywcjlove
55c369f23c chore: update workflows config. 2022-09-05 00:52:05 +08:00
jaywcjlove
6aad6713d2 released v2.2.0 2022-09-05 00:21:56 +08:00
jaywcjlove
5c10978fe9 chore: update workflows config. 2022-09-05 00:07:21 +08:00
jaywcjlove
8d17f7532e feat: build windows & linux app. 2022-09-04 23:52:47 +08:00
jaywcjlove
7f997282fc chore: update workflows config. 2022-09-04 22:57:14 +08:00
jaywcjlove
60b32b3ca8 fix: fix dockerfile config error. 2022-09-04 22:53:51 +08:00
jaywcjlove
83e834d6cf chore: update workflows config. 2022-09-04 22:50:38 +08:00
jaywcjlove
5cc2758073 fix: fix docker image build error. 2022-09-04 22:49:27 +08:00
jaywcjlove
d84ad70345 fix: Fix dockerfile config error. 2022-09-04 22:40:55 +08:00
jaywcjlove
ba5eb6115c chore: upate workflows config. 2022-09-04 22:22:52 +08:00
renovate[bot]
57e604c195 chore(deps): update dependency cpy-cli to v4.2.0 (#5) 2022-09-04 22:10:33 +08:00
jaywcjlove
76a6f48d0a feat: add electron app. 2022-09-04 22:09:04 +08:00
jaywcjlove
a66e906eef doc: Update README.md 2022-09-03 17:19:11 +08:00
jaywcjlove
b694d61bf0 doc: Update README.md 2022-09-03 17:17:06 +08:00
jaywcjlove
fd6ad59d5c feat: add api request loading animation. 2022-09-03 17:12:31 +08:00
jaywcjlove
b1dc77e98b feat: add url parameter to load markdown content. 2022-09-03 16:40:11 +08:00
jaywcjlove
57b719c163 feat: add documemt. 2022-09-03 15:39:06 +08:00
64 changed files with 847 additions and 177 deletions

View File

@@ -5,7 +5,7 @@ on:
- master
jobs:
build-deploy:
build:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
@@ -15,19 +15,25 @@ jobs:
- run: npm install
- run: npm run build
- run: npm run doc
- uses: actions/upload-artifact@v3
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: build/CONTRIBUTORS.svg
output: website/build/CONTRIBUTORS.svg
avatarSize: 42
- name: Create Tag
id: create_tag
uses: jaywcjlove/create-tag-action@main
with:
package-path: ./package.json
package-path: ./website/package.json
- name: get tag version
id: tag_version
@@ -38,7 +44,7 @@ jobs:
with:
commit_message: ${{ github.event.head_commit.message }} ${{steps.tag_version.outputs.tag}}
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./build
publish_dir: ./website/build
- name: Generate Changelog
id: changelog
@@ -48,52 +54,234 @@ jobs:
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 }}
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 }}
${{ steps.changelog.outputs.changelog }}
docker:
runs-on: ubuntu-latest
needs: [build]
steps:
- uses: actions/checkout@v3
- uses: actions/download-artifact@v3
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 Awesome Mac image
- name: Build wxmp image
working-directory: website
run: docker image build -t wxmp .
- name: Tags & Push image (latest)
run: |
echo "outputs.tag - ${{ steps.changelog.outputs.version }}"
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: steps.create_tag.outputs.successful
if: needs.build.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}}
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: steps.create_tag.outputs.successful
if: needs.build.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}}
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@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- name: Install
run: npm install --build-from-source
- run: npm run hoist
- run: npm run build
# - run: npm run electron
- uses: actions/download-artifact@v3
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
- uses: actions/upload-artifact@v3
with:
name: wxmp-windows
path: |
electron\app\dist\*.exe
build_macos:
needs: [build]
runs-on: macos-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- run: npm install
- run: npm run hoist
- run: npm run build
- uses: actions/download-artifact@v3
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
- uses: actions/upload-artifact@v3
with:
name: wxmp-macos
path: |
electron/app/dist/*.zip
build_linux:
needs: [build]
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- run: npm install
- run: npm run hoist
- run: npm run build
# - run: npm run electron
- uses: actions/download-artifact@v3
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
- uses: actions/upload-artifact@v3
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
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- uses: actions/download-artifact@v3
with:
name: wxmp-linux
path: dist/linux
- uses: actions/download-artifact@v3
with:
name: wxmp-macos
path: dist/macos
- uses: actions/download-artifact@v3
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@v1.5.7
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
with:
token: ${{ secrets.GITHUB_TOKEN }}
name: ${{ steps.changelog.outputs.tag }}
tag: ${{ steps.changelog.outputs.tag }}
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 }}
roll_back:
if: failure()
needs: [build, create_release]
runs-on: ubuntu-latest
timeout-minutes: 4
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.head_ref }}
- run: echo "outputs.version - ${{ needs.build.outputs.create_tag_version }}"
- uses: dev-drprasad/delete-tag-and-release@v0.2.0
if: needs.build.outputs.successful
with:
delete_release: true
repo: jaywcjlove/wxmp
tag_name: '${{ needs.build.outputs.create_tag_version }}'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -2,7 +2,7 @@
<h1 align="center">微信公众号 Markdown 编辑器</h1>
</div>
![微信公众号 Markdown 编辑器](https://user-images.githubusercontent.com/1680273/188257498-529e42aa-44b0-441f-bcdd-049efa174b78.png)
![微信公众号 Markdown 编辑器](https://user-images.githubusercontent.com/1680273/188264183-a6b8cb6a-92e1-4a73-afc5-4f0234b26ed3.png)
微信公众号文章 Markdown 在线编辑器,使用 markdown 语法创建一篇简介美观大方的微信公众号图文。由于发版本麻烦,和一些功能无法扩展停滞开发了,未来不再开发 Chrome 的插件(暂存在 chrome 分支),通过 web 版本定制更丰富的功能。
@@ -14,9 +14,12 @@
- [x] 支持自定义 CSS 样式
- [x] 支持主题选择 & 编辑预览。
- [x] 支持明暗两种主题预览。
- [ ] 支持代码块主题样式选择。
- [ ] 支持全局字号大小选择。
- [ ] 支持色盘取色,快速替换文章整体色调
- [ ] 支持 URL 参数加载 Markdown 内容。
- [x] 支持 URL 参数加载 Markdown 内容。
- [x] 支持 URL 参数选择预览主题。
- [x] 使用 electron 生成桌面应用。
### 支持代码块样式
@@ -66,13 +69,20 @@ Inline Code `{code: 0}`
### 支持注释
```html
<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>
<rt>Han</rt>
</ruby>
```
汉字注音效果:
<ruby>
汉 <rt>Han</rt>
字 <rt>zi</rt>
拼 <rt>pin</rt>
音 <rt>yin</rt>
注 <rt>zhu</rt>
音 <rt>yin</rt>
</ruby>
### 支持自定义样式
@@ -100,6 +110,20 @@ Inline Code `{code: 0}`
<!--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
```
## 主题定制
在目录 `src/themes` 中存放默认主题,在 `src/store/context.tsx` 中配置主题,主题使用 css 定义样式,不支持复杂的选择器。提供在线主题编辑器,欢迎修改并 PR 进仓库供大家使用。
@@ -161,11 +185,11 @@ docker pull ghcr.io/jaywcjlove/wxmp:latest
```
```bash
docker run --name wxmp --rm -d -p 96611:3000 wcjiang/wxmp:latest
docker run --name wxmp --rm -d -p 8113:3000 wcjiang/wxmp:latest
# Or
docker run --name wxmp -itd -p 96611:3000 wcjiang/wxmp:latest
docker run --name wxmp -itd -p 8113:3000 wcjiang/wxmp:latest
# Or
docker run --name wxmp -itd -p 96611:3000 ghcr.io/jaywcjlove/wxmp:latest
docker run --name wxmp -itd -p 8113:3000 ghcr.io/jaywcjlove/wxmp:latest
```
在浏览器中访问以下 URL

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

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

View File

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

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

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

BIN
electron/app/icon.icns Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

BIN
electron/app/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

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

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

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

@@ -0,0 +1,30 @@
{
"name": "wxmp",
"description": "微信公众号 Markdown 编辑器",
"homepage": "https://github.com/jaywcjlove/wxmp.git",
"version": "2.2.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.2.0"
},
"devDependencies": {
"@wcj/wxmp-preload": "2.2.0",
"cpy-cli": "4.2.0",
"electron": "19.0.5",
"electron-builder": "23.1.0",
"website": "2.2.0"
}
}

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

@@ -0,0 +1 @@
lib

View File

@@ -0,0 +1,16 @@
{
"name": "@wcj/wxmp-main",
"version": "2.2.0",
"main": "./lib/index.js",
"private": true,
"scripts": {
"build": "tsbb build --disable-babel --file-names src/index.ts",
"watch": "tsbb watch --disable-babel --file-names src/index.ts"
},
"files": [
"lib"
],
"devDependencies": {
"electron": "19.0.5"
}
}

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

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

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

@@ -0,0 +1,60 @@
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: 'hidden', // 无标题栏
frame: false, // 创建无边窗口
width: 800,
height: 600,
minWidth: 850,
center: true,
maximizable: true,
minimizable: true,
resizable: true,
webPreferences: {
// 多线程
nodeIntegrationInWorker: true,
nodeIntegration: true,
contextIsolation: false,
},
...options,
};
if (options.preload) {
opts.webPreferences.preload = options.preload;
}
this.win = new BrowserWindow(opts);
if (process.env.NODE_ENV === 'development') {
this.win.loadURL(loadURL || 'http://localhost:3000/');
// 打开开发者工具,默认不打开
this.win.webContents.openDevTools();
} else {
this.win.loadFile(options.webpath);
}
this.win.webContents.setWindowOpenHandler(({ url }) => {
if (/^https?:\/\//.test(url)) {
shell.openExternal(url);
return { action: 'deny' };
}
return {
action: 'allow',
overrideBrowserWindowOptions: {
modal: true,
},
};
});
return this.win;
}
}

View File

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

View File

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

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

@@ -0,0 +1 @@
lib

View File

@@ -0,0 +1,16 @@
{
"name": "@wcj/wxmp-preload",
"version": "2.2.0",
"main": "./lib/index.js",
"private": true,
"scripts": {
"build": "tsbb build --disable-babel --file-names src/index.ts",
"watch": "tsbb watch --disable-babel --file-names src/index.ts"
},
"files": [
"lib"
],
"devDependencies": {
"electron": "19.0.5"
}
}

View File

@@ -0,0 +1,24 @@
const styleStr = `.siderbar {
-webkit-app-region: drag;
}
.siderbar header h1 a, github-corners {
display: none;
}
article.content:before {
-webkit-app-region: drag;
position: absolute;
content: ' ';
display: block;
height: 41px;
width: 100%;
z-index: -1;
}`;
document.addEventListener('DOMContentLoaded', () => {
const head = document.querySelector('head');
const style = document.createElement('style');
style.textContent = styleStr;
if (head) {
head.append(style);
}
});

View File

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

4
lerna.json Normal file
View File

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

View File

@@ -1,81 +1,34 @@
{
"name": "website",
"version": "2.1.0",
"private": true,
"scripts": {
"start": "kkt start",
"build": "kkt build",
"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}'",
"pretty-quick": "pretty-quick --staged"
"pretty-quick": "pretty-quick --staged",
"hoist": "lerna bootstrap --hoist",
"clean": "lerna clean --yes"
},
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.18.9",
"@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": "^5.6.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-attr": "^2.0.8",
"rehype-ignore": "^1.0.1",
"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",
"cross-env": "^7.0.3",
"husky": "^8.0.1",
"lerna": "5.5.0",
"prettier": "^2.7.1",
"pretty-quick": "~3.1.3",
"kkt": "^7.2.0",
"markdown-react-code-preview-loader": "^2.1.2"
"tsbb": "^3.7.5"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
"workspaces": {
"packages": [
"electron/**",
"website"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
"engines": {
"node": ">=16.0.0"
}
}

4
website/.dockerignore Normal file
View File

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

View File

@@ -1,8 +1,7 @@
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 { disableScopePlugin } from '@kkt/scope-plugin-options';
import { LoaderConfOptions } from 'kkt';
import raw from '@kkt/raw-modules';
import pkg from './package.json';
@@ -14,10 +13,7 @@ export default (conf: Configuration, env: 'development' | 'production', options:
...options,
test: /\.(md.css)$/i,
});
conf = scopePluginOptions(conf, env, {
...options,
allowedFiles: [path.resolve(process.cwd(), 'README.md'), path.resolve(process.cwd(), 'src')],
});
conf = disableScopePlugin(conf);
conf.plugins!.push(
new webpack.DefinePlugin({
VERSION: JSON.stringify(pkg.version),

76
website/package.json Normal file
View File

@@ -0,0 +1,76 @@
{
"name": "website",
"version": "2.2.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": "^5.7.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-attr": "^2.0.8",
"rehype-ignore": "^1.0.1",
"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

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -2,6 +2,7 @@ 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 (
@@ -9,6 +10,7 @@ export default function App() {
<Route path="/" element={<Layout />}>
<Route index element={<HomePage />} />
<Route path="/editor/theme" element={<EditorPage />} />
<Route path="/doc" element={<DocsPage />} />
</Route>
</Routes>
);

View File

Before

Width:  |  Height:  |  Size: 576 B

After

Width:  |  Height:  |  Size: 576 B

View File

Before

Width:  |  Height:  |  Size: 784 B

After

Width:  |  Height:  |  Size: 784 B

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

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

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -12,7 +12,7 @@ const Select = styled.select`
padding: 0 0.2rem 0 0.2rem;
margin: 0;
font-family: inherit;
font-size: 0.3rem;
font-size: 0.8rem;
outline: none;
height: 1.15rem;
cursor: inherit;

View File

@@ -1,12 +1,16 @@
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 Header = styled.header`
-webkit-app-region: drag;
display: flex;
flex-direction: row;
justify-content: space-between;
@@ -36,9 +40,9 @@ const Title = styled.h1`
margin-left: 0.4rem;
background-color: var(--color-border-muted);
border-radius: 0.1rem;
padding: 0 0.2rem;
padding: 0 0.2rem 0 0.1rem;
font-weight: normal;
font-size: 0.1rem;
font-size: 0.7rem;
letter-spacing: -0.1rem;
}
`;
@@ -75,6 +79,7 @@ const Section = styled.section`
`;
export function Layout() {
const { isLoading } = useContext(Context);
return (
<Warpper className="wmde-markdown-color">
<Header>
@@ -84,10 +89,12 @@ export function Layout() {
<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} />

View File

@@ -4,8 +4,9 @@ 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/context';
import { Provider } from './store/Provider';
export const GlobalStyle = createGlobalStyle`
[data-color-mode*='dark'], [data-color-mode*='dark'] body {
@@ -52,15 +53,20 @@ export const GlobalStyle = createGlobalStyle`
}
`;
const queryClient = new QueryClient();
const style: React.CSSProperties = { zIndex: 999 };
const container = document.getElementById('root');
const root = createRoot(container!);
root.render(
<HashRouter>
<Toaster />
<BackToUp>Top</BackToUp>
<BackToUp style={style}>Top</BackToUp>
<GlobalStyle />
<Provider>
<App />
</Provider>
<QueryClientProvider client={queryClient}>
<Provider>
<App />
</Provider>
</QueryClientProvider>
</HashRouter>,
);

View File

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

View File

@@ -8,13 +8,13 @@ import { theme as themeCommand, previeTheme } from '../../commands/theme';
import { cssCommand } from '../../commands/css';
import { Context, themes } from '../../store/context';
const Warpper = styled.div`
export const Warpper = styled.div`
height: calc(100vh - 2.9rem);
`;
export const HomePage = () => {
const commands = [...getCommands(), themeCommand];
const { theme, markdown, setMarkdown } = useContext(Context);
const { theme, markdown, isLoading, setMarkdown } = useContext(Context);
const themeValue = themes[theme].value;
const handleChange = (value: string) => setMarkdown(value);
return (
@@ -23,12 +23,14 @@ export const HomePage = () => {
value={markdown}
toolbars={commands}
theme={themeValue}
toolbarsMode={[cssCommand, previeTheme, copy, 'preview', 'fullscreen']}
readOnly={isLoading}
toolbarsMode={[cssCommand, previeTheme, copy, 'fullscreen', 'preview']}
extensions={[EditorView.lineWrapping]}
renderPreview={Preview}
previewWidth="420px"
onChange={handleChange}
visible={true}
height="calc(100vh - 4.92rem)"
height="calc(100vh - 4.70rem)"
/>
</Warpper>
);

View File

@@ -1,7 +1,6 @@
import MarkdownEditor, { IMarkdownEditor } from '@uiw/react-markdown-editor';
import { useContext } from 'react';
import { EditorView } from '@codemirror/view';
import styled from 'styled-components';
import { css as cssLang } from '@codemirror/lang-css';
import { Preview } from './Preview';
import { copy } from '../../commands/copy';
@@ -9,15 +8,12 @@ import { previousCommand } from '../../commands/css';
import { themeTitle } from '../../commands/title';
import { theme as themeCommand, previeTheme } from '../../commands/theme';
import { Context, themes } from '../../store/context';
const Warpper = styled.div`
height: calc(100vh - 2.9rem);
`;
import { Warpper } from '../home';
export const EditorPage = () => {
const commands = [themeTitle, themeCommand, previousCommand];
const toolbarsMode: IMarkdownEditor['toolbarsMode'] = [previeTheme, copy, 'preview', 'fullscreen'];
const { theme, css, setCss } = useContext(Context);
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 (
@@ -25,10 +21,12 @@ export const EditorPage = () => {
<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.92rem)"

View File

@@ -0,0 +1,47 @@
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 [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]);
return (
<Context.Provider
value={{
isLoading,
setIsLoading,
markdown,
setMarkdown,
css,
setCss,
previewTheme,
setPreviewTheme,
theme,
setTheme,
}}
>
{children}
</Context.Provider>
);
};

View File

@@ -1,5 +1,4 @@
import React, { useEffect } from 'react';
import { useSearchParams } from 'react-router-dom';
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';
@@ -19,7 +18,9 @@ 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';
import data from '../../../README.md';
export const markdownString = data.source;
export const themes = {
default: {
@@ -115,6 +116,8 @@ export type ThemeValue = keyof typeof themes;
export type PreviewThemeValue = keyof typeof previewThemes;
export interface CreateContext {
isLoading: boolean;
setIsLoading: React.Dispatch<React.SetStateAction<boolean>>;
markdown: string;
setMarkdown: React.Dispatch<React.SetStateAction<string>>;
css: string;
@@ -126,6 +129,8 @@ export interface CreateContext {
}
export const Context = React.createContext<CreateContext>({
isLoading: true,
setIsLoading: () => {},
markdown: data.source,
setMarkdown: () => {},
css: previewThemes['underscore'].value,
@@ -135,35 +140,3 @@ export const Context = React.createContext<CreateContext>({
theme: 'default',
setTheme: () => {},
});
export const Provider: React.FC<React.PropsWithChildren> = ({ children }) => {
const [searchParams, setSearchParams] = useSearchParams();
const paramPreviewTheme = searchParams.get('theme') as PreviewThemeValue;
const initPreviewTheme = paramPreviewTheme || 'underscore';
const [markdown, setMarkdown] = React.useState<string>(data.source);
const [css, setCss] = React.useState<string>(previewThemes[initPreviewTheme].value);
const [previewTheme, setPreviewTheme] = React.useState<PreviewThemeValue>(initPreviewTheme);
const [theme, setTheme] = React.useState<ThemeValue>('default');
useEffect(() => {
if (paramPreviewTheme !== previewTheme) {
searchParams.set('theme', previewTheme);
setSearchParams(searchParams);
}
}, [paramPreviewTheme, previewTheme, searchParams, setSearchParams]);
return (
<Context.Provider
value={{
markdown,
setMarkdown,
css,
setCss,
previewTheme,
setPreviewTheme,
theme,
setTheme,
}}
>
{children}
</Context.Provider>
);
};

View File

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

View File

@@ -103,12 +103,14 @@ th {
.code-spans {
text-align: left;
line-height: 1;
white-space: pre;
white-space: initial;
background: rgba(27, 31, 35, 0.05);
padding: 0.2em 0.6em;
border-radius: 0.6em;
padding: 0.1em 0.3em;
border-radius: 0.3em;
font-weight: bold;
font-size: 0.45em;
font-size: 1em;
top: -0.1em;
position: relative;
}
.footnotes-title {

View File

@@ -142,13 +142,15 @@ th {
.code-spans {
text-align: left;
line-height: 1;
white-space: pre;
white-space: initial;
color: #009874;
background: rgba(27, 31, 35, 0.05);
padding: 0.2em 0.6em;
border-radius: 0.6em;
padding: 0.1em 0.3em;
border-radius: 0.3em;
font-weight: bold;
font-size: 0.45em;
font-size: 1em;
top: -0.1em;
position: relative;
}
.footnotes-title {

View File

@@ -142,13 +142,15 @@ th {
.code-spans {
text-align: left;
line-height: 1;
white-space: pre;
white-space: initial;
color: #0f4c81;
background: rgba(27, 31, 35, 0.05);
padding: 0.2em 0.6em;
border-radius: 0.6em;
padding: 0.1em 0.3em;
border-radius: 0.3em;
font-weight: bold;
font-size: 0.45em;
font-size: 1em;
top: -0.1em;
position: relative;
}
.footnotes-title {

View File

@@ -139,13 +139,15 @@ th {
.code-spans {
text-align: left;
line-height: 1;
white-space: pre;
white-space: initial;
color: #ffb11b;
background: rgba(27, 31, 35, 0.05);
padding: 0.2em 0.6em;
border-radius: 0.6em;
padding: 0.1em 0.3em;
border-radius: 0.3em;
font-weight: bold;
font-size: 0.45em;
font-size: 1em;
top: -0.1em;
position: relative;
}
.footnotes-title {