Compare commits
104 Commits
Author | SHA1 | Date | |
---|---|---|---|
7a82ba2250 | |||
445cfe69fc | |||
40e77fef15 | |||
348b5668fd | |||
60c751f340 | |||
024706f20f | |||
408618eceb | |||
9a7570b02f | |||
182c863eef | |||
![]() |
d422df5e42 | ||
![]() |
91215e6db9 | ||
![]() |
1286c42d4c | ||
![]() |
3467a6ee09 | ||
![]() |
b4f4c71f85 | ||
![]() |
39167fb193 | ||
![]() |
97b8d3c288 | ||
![]() |
b4c9407b06 | ||
![]() |
96ea8cd0db | ||
![]() |
550bb2fd91 | ||
![]() |
2092045b7f | ||
![]() |
12e4befa96 | ||
![]() |
80e0e3bc99 | ||
![]() |
4747f91749 | ||
![]() |
e04fd5a911 | ||
![]() |
9cd27e274e | ||
![]() |
81cad7ee84 | ||
![]() |
81612deab7 | ||
![]() |
282b546edc | ||
![]() |
1727be1eaf | ||
![]() |
57931b9db2 | ||
![]() |
d175557ab9 | ||
![]() |
8e12eaebd2 | ||
![]() |
90d887519d | ||
![]() |
c1232b59db | ||
![]() |
a40af9c545 | ||
![]() |
f3d827fef1 | ||
![]() |
92f2c4dee6 | ||
![]() |
74f25af839 | ||
![]() |
20d7a9d2db | ||
![]() |
64d493d692 | ||
![]() |
eda517cd61 | ||
![]() |
24635c54ed | ||
![]() |
87c37401ed | ||
![]() |
599d71b597 | ||
![]() |
0e02822add | ||
![]() |
1daa5afe39 | ||
![]() |
2e9e4b73f6 | ||
![]() |
4243a41e31 | ||
![]() |
ae828cfb56 | ||
![]() |
58c9144612 | ||
![]() |
1b8124f2a2 | ||
![]() |
8713688b57 | ||
![]() |
e65c433f13 | ||
![]() |
b1691e0d4f | ||
![]() |
4d8ff0ea0c | ||
![]() |
d757b48d99 | ||
![]() |
d927099b28 | ||
![]() |
9ebde2eb75 | ||
![]() |
bda261a767 | ||
![]() |
9419865d76 | ||
![]() |
be9323c408 | ||
![]() |
a756acf27c | ||
![]() |
e731016e04 | ||
![]() |
6cca063f8c | ||
![]() |
13b9528840 | ||
![]() |
31bec53520 | ||
![]() |
4e9acad585 | ||
![]() |
5eb2b2e67a | ||
![]() |
2b45a94879 | ||
![]() |
5a30338e83 | ||
![]() |
808891e47c | ||
![]() |
d3193e1739 | ||
![]() |
df91db5882 | ||
![]() |
26e8979245 | ||
![]() |
dd78ec7b3a | ||
![]() |
401c2787af | ||
![]() |
a4ab4b2da1 | ||
![]() |
e7450df251 | ||
![]() |
058fcaa147 | ||
![]() |
ed79c8cd49 | ||
![]() |
867315a19d | ||
![]() |
480875a5ec | ||
![]() |
440c5e93b8 | ||
![]() |
405e082651 | ||
![]() |
7335455185 | ||
![]() |
7da611b398 | ||
![]() |
554547af5a | ||
![]() |
380980d66f | ||
![]() |
68f281c6e7 | ||
![]() |
545f8da3cb | ||
![]() |
ee9bd1ab5a | ||
![]() |
e7fa160383 | ||
![]() |
f71cef4d9f | ||
![]() |
347358f6bc | ||
![]() |
8aff518e34 | ||
![]() |
21a3e59b5d | ||
![]() |
95d27a4a0a | ||
![]() |
b1ad58a121 | ||
![]() |
d51c19d6fd | ||
![]() |
398784efc4 | ||
![]() |
f020cb887b | ||
![]() |
6fa7992685 | ||
![]() |
a6493a41da | ||
![]() |
f5b4627083 |
@ -3,4 +3,7 @@ node_modules
|
||||
dist
|
||||
.history
|
||||
images
|
||||
docs
|
||||
docs
|
||||
Dockerfile
|
||||
README.md
|
||||
build.sh
|
||||
|
@ -1,6 +1,5 @@
|
||||
FROM mafgwo/wkhtmltopdf-nodejs:11.15.0
|
||||
|
||||
RUN mkdir -p /opt/stackedit
|
||||
WORKDIR /opt/stackedit
|
||||
|
||||
COPY package*json /opt/stackedit/
|
||||
|
105
README.md
@ -1,27 +1,59 @@
|
||||
# StackEdit中文版
|
||||
<h1 align="center" style="text-align:center;">
|
||||
<img src="chrome-app/icon-512.png" width="128" />
|
||||
<br />
|
||||
StackEdit中文版
|
||||
</h1>
|
||||
<p align="center">
|
||||
<strong>笔记利器,在线Markdown编辑器。</strong><br>
|
||||
项目clone自<a href="https://gitee.com/mafgwo/stackedit" target="_blank" title="豆萁">豆萁/stackedit</a>,如果你喜欢该项目,请过去点一下Star,您的肯定是作者最大的动力!
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://stackedit.cn/">https://stackedit.cn</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a target="_blank" href="https://www.apache.org/licenses/LICENSE-2.0.txt">
|
||||
<img src="https://img.shields.io/:license-Apache2-blue.svg" alt="Apache 2" />
|
||||
</a>
|
||||
<a target="_blank" href="https://hub.docker.com/r/mafgwo/stackedit">
|
||||
<img src="https://img.shields.io/docker/pulls/mafgwo/stackedit.svg" alt="Docker Pulls" />
|
||||
</a>
|
||||
<a target="_blank" href='https://gitee.com/mafgwo/stackedit/stargazers'>
|
||||
<img src='https://gitee.com/mafgwo/stackedit/badge/star.svg' alt='gitee star'/>
|
||||
</a>
|
||||
</p>
|
||||
<br/>
|
||||
<hr />
|
||||
1 笔记支持Gitee、GitHub、Gitea等Git仓库存储。<br>
|
||||
2 支持直接上传图片,也支持多种外部图床(GitHub、Gitea、SM.MS、自定义图床)粘贴或拖拽上传。<br>
|
||||
3 编辑区域支持选择主题或自定义,总有你喜欢的主题。<br>
|
||||
4 支持历史版本管理,不用担心编辑覆盖后无法回滚。<br>
|
||||
5 支持ChatGPT辅助写作。<br>
|
||||
6 支持KaTeX数学表达式、Mermaid UML图、乐谱等扩展。
|
||||
<hr />
|
||||
|
||||
**StackEdit中文版官方地址:https://stackedit.cn**
|
||||
## 说明
|
||||
|
||||
如果你喜欢该项目,请点一下Star,您的肯定是作者最大的动力!
|
||||
本项目为本人clone修改自用,如果你也喜欢,请至原作者处获取及交流。
|
||||
|
||||
StackEdit中文版的docker镜像地址:[mafgwo/stackedit](https://hub.docker.com/r/mafgwo/stackedit)
|
||||
## 截图
|
||||
|
||||
**示例截图-暗色主题**
|
||||

|
||||
**亮暗主题切换、编辑主题切换**
|
||||

|
||||
|
||||
**示例截图-亮色主题**
|
||||

|
||||
|
||||
**示例截图-支持的文档空间**
|
||||
**支持的文档空间**
|
||||

|
||||
|
||||
**示例截图-支持的图床**
|
||||

|
||||
**拖拽粘贴上传图片**
|
||||

|
||||
|
||||
**示例截图-支持文件搜索**
|
||||

|
||||
**支持文档搜索**
|
||||

|
||||
|
||||
**ChatGPT集成协助写作**
|
||||

|
||||
|
||||
## 相比国外开源版本的区别:
|
||||
|
||||
- 修复了Github授权登录问题
|
||||
- 支持了Gitee仓库(2022-05-25)
|
||||
- 支持了Gitea仓库(2022-05-25)
|
||||
@ -35,16 +67,32 @@ StackEdit中文版的docker镜像地址:[mafgwo/stackedit](https://hub.docker.
|
||||
- 编辑与预览区域样式优化(2022-08-10)
|
||||
- 左边栏文件资源管理支持搜索文件(2022-08-17)
|
||||
- 支持[TOC]目录(2022-09-04)
|
||||
- 发布支持填写提交信息[针对Gitee、GitHub、Gitea、Gitlab](2022-09-10)
|
||||
- 支持文档空间关闭自动同步[针对Gitee、GitHub、Gitea、Gitlab],关闭后可自定义提交信息(2022-09-23)
|
||||
- Gitea支持后端配置指定应用ID和Secret(2022-10-03)
|
||||
- 支持编辑区域选择主题样式(2022-10-06)
|
||||
- 支持图片直接存储到当前文档空间(2022-10-29)
|
||||
- 支持MD文档之间链接跳转(2022-11-20)
|
||||
- 支持预览区域选择主题样式(2022-12-04)
|
||||
- Gitlab的支持优化(2023-02-23)
|
||||
- 导出HTML、PDF支持带预览主题导出(2023-02-26)
|
||||
- 支持分享文档(2023-03-30)
|
||||
- 支持ChatGPT生成内容(2023-04-10)
|
||||
- GitLab授权接口调整(2023-08-26)
|
||||
- 主文档空间支持GitHub登录(2023-10-19)
|
||||
|
||||
## 国外开源版本弊端:
|
||||
- 作者已经不维护了
|
||||
- Github授权登录存在问题
|
||||
|
||||
- 作者已经不维护了或很少维护了
|
||||
- 不支持国内常用Gitee
|
||||
- 强依赖GoogleDrive,而Google Drive在国内不能正常访问
|
||||
|
||||
## 部署说明
|
||||
|
||||
> 建议docker-compose方式部署,其他部署方式如遇到问题欢迎提issue。
|
||||
|
||||
docker官方仓库下载太慢可以使用阿里云的镜像仓库,镜像仓库地址:registry.cn-hangzhou.aliyuncs.com/mafgwo/stackedit:【版本号】
|
||||
|
||||
`docker-compose.yml`如下:
|
||||
|
||||
```yaml
|
||||
@ -65,6 +113,12 @@ services:
|
||||
- GITEE_CLIENT_SECRET=【不需要支持则删掉】
|
||||
- GOOGLE_CLIENT_ID=【不需要支持则删掉】
|
||||
- GOOGLE_API_KEY=【不需要支持则删掉】
|
||||
- GITEA_CLIENT_ID=【不需要支持则删掉】
|
||||
- GITEA_CLIENT_SECRET=【不需要支持则删掉】
|
||||
- GITEA_URL=【不需要支持则删掉】
|
||||
- GITLAB_CLIENT_ID=【不需要支持则删掉】
|
||||
- GITLAB_CLIENT_SECRET=【不需要支持则删掉】
|
||||
- GITLAB_URL=【不需要支持则删掉】
|
||||
ports:
|
||||
- 8080:8080/tcp
|
||||
network_mode: bridge
|
||||
@ -72,6 +126,7 @@ services:
|
||||
```
|
||||
|
||||
docker-compose方式的启动或停止命令
|
||||
|
||||
```bash
|
||||
# 在 docker-compose.yml 文件目录下 启动命令
|
||||
docker-compose up -d
|
||||
@ -96,21 +151,29 @@ docker run -itd --name stackedit \
|
||||
-e GITEE_CLIENT_SECRET=【不需要支持则删掉】 \
|
||||
-e GOOGLE_CLIENT_ID=【不需要支持则删掉】 \
|
||||
-e GOOGLE_API_KEY=【不需要支持则删掉】 \
|
||||
-e GITEA_CLIENT_ID=【不需要支持则删掉】 \
|
||||
-e GITEA_CLIENT_SECRET=【不需要支持则删掉】 \
|
||||
-e GITEA_URL=【不需要支持则删掉】 \
|
||||
-e GITLAB_CLIENT_ID=【不需要支持则删掉】 \
|
||||
-e GITLAB_CLIENT_SECRET=【不需要支持则删掉】 \
|
||||
-e GITLAB_URL=【不需要支持则删掉】 \
|
||||
mafgwo/stackedit:【docker中央仓库找到最新版本】
|
||||
|
||||
```
|
||||
|
||||
## 如何创建三方平台应用
|
||||
|
||||
> 部署时,如果需要支持Gitee或GitHub,则需要自行到对应三方平台创建应用,获取到应用ID和秘钥,替换到以上的环境变量中,再启动应用。
|
||||
|
||||
- Gitee的环境变量:GITEE_CLIENT_ID、GITEE_CLIENT_SECRET,**[如何创建Gitee应用](./docs/部署之Gitee应用创建.md)**
|
||||
|
||||
- GitHub的环境变量:GITHUB_CLIENT_ID、GITEE_CLIENT_SECRET,**[如何创建GitHub应用](./docs/部署之GitHub应用创建.md)**
|
||||
- Gitea可选择性配置环境变量(未配置则在关联时前端指定,有配置则仅允许配置的应用信息):GITEA_CLIENT_ID、GITEA_CLIENT_SECRET、GITEA_URL,**[如何创建Gitea应用](./docs/部署之Gitea应用创建.md)**
|
||||
- Gitlab可选择性配置环境变量(未配置则在关联时前端指定,有配置则仅允许配置的应用信息):GITLAB_CLIENT_ID、GITLAB_CLIENT_SECRET、GITLAB_URL **如何创建Gitlab应用(待补充文档)**
|
||||
|
||||
- Gitea不需要配置环境变量,而是在关联Gitea账号时,需要填入应用ID和秘钥,**[如何创建Gitea应用](./docs/部署之Gitea应用创建.md)**
|
||||
|
||||
(特别说明:自建的Gitea、Gitlab要能接入stackedit必须支持跨域)
|
||||
|
||||
## 编译与运行
|
||||
|
||||
> 编译运行的nodejs版本选择11.15.0版本
|
||||
|
||||
```bash
|
||||
@ -126,7 +189,3 @@ npm run build
|
||||
# build for production and view the bundle analyzer report
|
||||
npm run build --report
|
||||
```
|
||||
|
||||
## 欢迎加群交流
|
||||
关于StackEdit,如果你有想法,或者使用中遇到了问题,可以提Issue,如果需要快速得到反馈,可以加QQ群如下(加群后可直接@群主):
|
||||

|
||||
|
37
build.sh
Normal file
@ -0,0 +1,37 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 检查参数是否提供版本号
|
||||
if [ -z "$1" ]; then
|
||||
echo "请提供版本号作为参数"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 定义版本号变量
|
||||
VERSION="$1"
|
||||
IMAGE_NAME="mafgwo/stackedit"
|
||||
|
||||
# 构建 Docker 镜像
|
||||
build_image() {
|
||||
docker build -t "$IMAGE_NAME" .
|
||||
}
|
||||
|
||||
# 标记 Docker 镜像
|
||||
tag_image() {
|
||||
docker tag "$IMAGE_NAME" "$IMAGE_NAME:$VERSION"
|
||||
docker tag "$IMAGE_NAME" "registry.cn-hangzhou.aliyuncs.com/$IMAGE_NAME:$VERSION"
|
||||
}
|
||||
|
||||
# 推送 Docker 镜像
|
||||
push_image() {
|
||||
docker push "$IMAGE_NAME"
|
||||
docker push "registry.cn-hangzhou.aliyuncs.com/$IMAGE_NAME"
|
||||
docker push "$IMAGE_NAME:$VERSION"
|
||||
docker push "registry.cn-hangzhou.aliyuncs.com/$IMAGE_NAME:$VERSION"
|
||||
}
|
||||
|
||||
# 执行构建、标记和推送
|
||||
build_image
|
||||
tag_image
|
||||
push_image
|
||||
|
||||
echo "操作完成"
|
@ -15,6 +15,11 @@ wordpressSecret: ""
|
||||
paypalReceiverEmail: ""
|
||||
awsAccessKeyId: ""
|
||||
awsSecretAccessKey: ""
|
||||
giteaClientId: ""
|
||||
giteaClientSecret: ""
|
||||
giteaUrl: ""
|
||||
gitlabClientId: ""
|
||||
gitlabUrl: ""
|
||||
|
||||
replicaCount: 1
|
||||
|
||||
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 491 B After Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 960 B After Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 7.6 KiB |
@ -1,28 +1,28 @@
|
||||
{
|
||||
"name": "StackEdit中文版",
|
||||
"description": "支持Gitee仓库/粘贴图片自动上传的浏览器内 Markdown 编辑器",
|
||||
"version": "5.15.12",
|
||||
"manifest_version": 2,
|
||||
"container" : "GITEE",
|
||||
"api_console_project_id" : "241271498917",
|
||||
"icons": {
|
||||
"16": "icon-16.png",
|
||||
"32": "icon-32.png",
|
||||
"64": "icon-64.png",
|
||||
"128": "icon-128.png",
|
||||
"256": "icon-256.png",
|
||||
"512": "icon-512.png"
|
||||
},
|
||||
"app": {
|
||||
"urls": [
|
||||
"https://stackedit.cn/"
|
||||
],
|
||||
"launch": {
|
||||
"web_url": "https://stackedit.cn/app"
|
||||
}
|
||||
},
|
||||
"offline_enabled": true,
|
||||
"permissions": [
|
||||
"unlimitedStorage"
|
||||
]
|
||||
}
|
||||
"name": "StackEdit中文版",
|
||||
"description": "支持Gitee仓库/粘贴图片自动上传的浏览器内 Markdown 编辑器",
|
||||
"version": "5.15.17",
|
||||
"manifest_version": 2,
|
||||
"container": "GITEE",
|
||||
"api_console_project_id": "241271498917",
|
||||
"icons": {
|
||||
"16": "icon-16.png",
|
||||
"32": "icon-32.png",
|
||||
"64": "icon-64.png",
|
||||
"128": "icon-128.png",
|
||||
"256": "icon-256.png",
|
||||
"512": "icon-512.png"
|
||||
},
|
||||
"app": {
|
||||
"urls": [
|
||||
"https://md.jonylee.top/"
|
||||
],
|
||||
"launch": {
|
||||
"web_url": "https://md.jonylee.top/app"
|
||||
}
|
||||
},
|
||||
"offline_enabled": true,
|
||||
"permissions": [
|
||||
"unlimitedStorage"
|
||||
]
|
||||
}
|
@ -9,4 +9,10 @@ module.exports = merge(prodEnv, {
|
||||
GITEE_CLIENT_ID: '"925ba7c78b85dec984f7877e4aca5cab10ae333c6d68e761bdb0b9dfb8f55672"',
|
||||
GITEE_CLIENT_SECRET: '"f05731066e42d307339dc8ebbb037a103881dafc7207a359a393b87749f1c562"',
|
||||
CLIENT_ID: '"thF3qCGLN39OtafjGnqHyj6n02WwE6xD"',
|
||||
// GITEA_CLIENT_ID: '"fe30f8f9-b1e8-4531-8f72-c1a5d3912805"',
|
||||
// GITEA_CLIENT_SECRET: '"lus7oMnb3H6M1hsChndphArE20Txr7erwJLf7SDBQWTw"',
|
||||
// GITEA_URL: '"https://gitea.test.com"',
|
||||
GITLAB_CLIENT_ID: '"074cd5103c62dea0f479dac861039656ac80935e304c8113a02cc64c629496ae"',
|
||||
GITLAB_CLIENT_SECRET: '"6f406f24216b686d55d28313dec1913c2a8e599afdb08380d5e8ce838e16e41e"',
|
||||
GITLAB_URL: '"http://gitlab.qicoder.com"',
|
||||
})
|
10
docs/大文档导出PDF方式.md
Normal file
@ -0,0 +1,10 @@
|
||||
# 大文档导出PDF方式说明
|
||||
> 由于大文档导出PDF,需要消费非常多的服务器资源,而且很容易导致导出超时,故导出PDF的MD文档过大时,可以使用 **[wkhtmltopdf](https://wkhtmltopdf.org/downloads.html)** 工具导出。
|
||||
|
||||
# 操作步骤
|
||||
- 先在 **[StackEdit中文版](https://stackedit.cn/app)** 中使用 `导出为HTML` 功能导出MD文档,导出后可以得到一个HTML文档。
|
||||
- 到 **[wkhtmltopdf](https://wkhtmltopdf.org/downloads.html)** 官网下载安装程序。
|
||||
- 使用 wkhtmltopdf 的导出PDF的命令 `wkhtmltopdf [GLOBAL OPTION]... [OBJECT]... <output file>` 把HTML导出为PDF,如简单的导出命令:`wkhtmltopdf test.html test.pdf`,具体的 `GLOBAL OPTION` 参数说明可以通过 `wkhtmltopdf -H` 查看帮助文档。
|
||||
|
||||
|
||||
|
@ -20,4 +20,16 @@
|
||||
|
||||
# Gitea跨域问题
|
||||
|
||||
由于StackEdit中文版是从浏览器直接访问Gitea接口,故个人部署的Gitea需要支持跨域,至于如何支持跨域,请参考官方文档:https://docs.gitea.io/en-us/config-cheat-sheet/#cors-cors
|
||||
由于StackEdit中文版是从浏览器直接访问Gitea接口,故个人部署的Gitea需要支持跨域,至于如何支持跨域,请参考官方文档:https://docs.gitea.io/en-us/config-cheat-sheet/#cors-cors (官方跨域的支持好像存在问题,我个人包括很多网友通过这个配置支持跨域都失败了,如果你也失败了,可以试试用nginx代理实现跨域)
|
||||
|
||||
nginx配置实现跨域的配置如下:
|
||||
|
||||
```
|
||||
add_header Access-Control-Allow-Headers *;
|
||||
add_header Access-Control-Allow-Origin $http_origin;
|
||||
add_header Access-Control-Allow-Methods GET,POST,PUT,DELETE,OPTIONS;
|
||||
|
||||
if ($request_method = 'OPTIONS') {
|
||||
return 204;
|
||||
}
|
||||
```
|
||||
|
BIN
images/chatgpt.gif
Normal file
After Width: | Height: | Size: 175 KiB |
BIN
images/search.gif
Normal file
After Width: | Height: | Size: 360 KiB |
BIN
images/theme.gif
Normal file
After Width: | Height: | Size: 937 KiB |
BIN
images/uploadimg.gif
Normal file
After Width: | Height: | Size: 761 KiB |
37
index.html
@ -1,28 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>StackEdit中文版</title>
|
||||
<link rel="canonical" href="https://stackedit.cn/app">
|
||||
<meta name="description" content="免费,开源,功能全面的Markdown编辑器。">
|
||||
<title>Markdown编辑器-JonyLee的设计导航</title>
|
||||
<link rel="canonical" href="https://md.jonylee.top">
|
||||
<meta name="description" content="StackEdit中文版,免费,开源,功能全面的Markdown编辑器。">
|
||||
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<script>
|
||||
var _hmt = _hmt || [];
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
<!-- baidu统计 -->
|
||||
<script>
|
||||
(function() {
|
||||
var hm = document.createElement("script");
|
||||
hm.src = "https://hm.baidu.com/hm.js?20a1e7a201b42702c49074c87a1f1035";
|
||||
var s = document.getElementsByTagName("script")[0];
|
||||
s.parentNode.insertBefore(hm, s);
|
||||
})();
|
||||
var _hmt = _hmt || [];
|
||||
(function() {
|
||||
var hm = document.createElement("script");
|
||||
hm.src = "https://hm.baidu.com/hm.js?dad4b4383b13eedea1ab45ee323df1c3";
|
||||
var s = document.getElementsByTagName("script")[0];
|
||||
s.parentNode.insertBefore(hm, s);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<!-- baidu统计结束 -->
|
||||
</body>
|
||||
|
||||
</html>
|
237
package-lock.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "stackedit",
|
||||
"version": "5.15.12",
|
||||
"version": "5.15.21",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@ -736,7 +736,8 @@
|
||||
"abab": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz",
|
||||
"integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w=="
|
||||
"integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w==",
|
||||
"dev": true
|
||||
},
|
||||
"abbrev": {
|
||||
"version": "1.1.1",
|
||||
@ -763,7 +764,8 @@
|
||||
"acorn": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-5.3.0.tgz",
|
||||
"integrity": "sha512-Yej+zOJ1Dm/IMZzzj78OntP/r3zHEaKcyNoU2lAaxPtrseM6rF0xwqoz5Q5ysAiED9hTjI2hgtvLXitlCN1/Ug=="
|
||||
"integrity": "sha512-Yej+zOJ1Dm/IMZzzj78OntP/r3zHEaKcyNoU2lAaxPtrseM6rF0xwqoz5Q5ysAiED9hTjI2hgtvLXitlCN1/Ug==",
|
||||
"dev": true
|
||||
},
|
||||
"acorn-dynamic-import": {
|
||||
"version": "2.0.2",
|
||||
@ -786,6 +788,7 @@
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.1.0.tgz",
|
||||
"integrity": "sha512-KjZwU26uG3u6eZcfGbTULzFcsoz6pegNKtHPksZPOUsiKo5bUmiBPa38FuHZ/Eun+XYh/JCCkS9AS3Lu4McQOQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"acorn": "^5.0.0"
|
||||
}
|
||||
@ -1021,7 +1024,8 @@
|
||||
"array-equal": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz",
|
||||
"integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM="
|
||||
"integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=",
|
||||
"dev": true
|
||||
},
|
||||
"array-filter": {
|
||||
"version": "0.0.1",
|
||||
@ -1237,7 +1241,8 @@
|
||||
"async-limiter": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
|
||||
"integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
|
||||
"integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==",
|
||||
"dev": true
|
||||
},
|
||||
"async-settle": {
|
||||
"version": "1.0.0",
|
||||
@ -1273,38 +1278,6 @@
|
||||
"postcss-value-parser": "^3.2.3"
|
||||
}
|
||||
},
|
||||
"aws-sdk": {
|
||||
"version": "2.317.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.317.0.tgz",
|
||||
"integrity": "sha512-X2Cd1Gb9Cf9WVgGOiBSW4TK6q5Mb6AYiGmEA9XikCgur4H8E4TgmgWbBWJnTzxssugclVLVoWQfw3RshNKJksg==",
|
||||
"requires": {
|
||||
"buffer": "4.9.1",
|
||||
"events": "1.1.1",
|
||||
"ieee754": "1.1.8",
|
||||
"jmespath": "0.15.0",
|
||||
"querystring": "0.2.0",
|
||||
"sax": "1.2.1",
|
||||
"url": "0.10.3",
|
||||
"uuid": "3.1.0",
|
||||
"xml2js": "0.4.19"
|
||||
},
|
||||
"dependencies": {
|
||||
"xml2js": {
|
||||
"version": "0.4.19",
|
||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz",
|
||||
"integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==",
|
||||
"requires": {
|
||||
"sax": ">=0.6.0",
|
||||
"xmlbuilder": "~9.0.1"
|
||||
}
|
||||
},
|
||||
"xmlbuilder": {
|
||||
"version": "9.0.7",
|
||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
|
||||
"integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0="
|
||||
}
|
||||
}
|
||||
},
|
||||
"aws-sign2": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
|
||||
@ -2346,7 +2319,8 @@
|
||||
"base64-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz",
|
||||
"integrity": "sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw=="
|
||||
"integrity": "sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw==",
|
||||
"dev": true
|
||||
},
|
||||
"bcrypt-pbkdf": {
|
||||
"version": "1.0.2",
|
||||
@ -2481,7 +2455,8 @@
|
||||
"browser-process-hrtime": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.2.tgz",
|
||||
"integrity": "sha1-Ql1opY00R/AqBKqJQYf86K+Le44="
|
||||
"integrity": "sha1-Ql1opY00R/AqBKqJQYf86K+Le44=",
|
||||
"dev": true
|
||||
},
|
||||
"browser-resolve": {
|
||||
"version": "1.11.3",
|
||||
@ -2593,6 +2568,7 @@
|
||||
"version": "4.9.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
|
||||
"integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"base64-js": "^1.0.2",
|
||||
"ieee754": "^1.1.4",
|
||||
@ -3904,12 +3880,14 @@
|
||||
"cssom": {
|
||||
"version": "0.3.4",
|
||||
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.4.tgz",
|
||||
"integrity": "sha512-+7prCSORpXNeR4/fUP3rL+TzqtiFfhMvTd7uEqMdgPvLPt4+uzFUeufx5RHjGTACCargg/DiEt/moMQmvnfkog=="
|
||||
"integrity": "sha512-+7prCSORpXNeR4/fUP3rL+TzqtiFfhMvTd7uEqMdgPvLPt4+uzFUeufx5RHjGTACCargg/DiEt/moMQmvnfkog==",
|
||||
"dev": true
|
||||
},
|
||||
"cssstyle": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.1.1.tgz",
|
||||
"integrity": "sha512-364AI1l/M5TYcFH83JnOH/pSqgaNnKmYgKrm0didZMGKWjQB60dymwWy1rKUgL3J1ffdq9xVi2yGLHdSjjSNog==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cssom": "0.3.x"
|
||||
}
|
||||
@ -4010,7 +3988,7 @@
|
||||
},
|
||||
"d3-collection": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz",
|
||||
"resolved": "https://registry.npmmirror.com/d3-collection/-/d3-collection-1.0.7.tgz",
|
||||
"integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A=="
|
||||
},
|
||||
"d3-color": {
|
||||
@ -4188,7 +4166,7 @@
|
||||
},
|
||||
"d3-voronoi": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.4.tgz",
|
||||
"resolved": "https://registry.npmmirror.com/d3-voronoi/-/d3-voronoi-1.1.4.tgz",
|
||||
"integrity": "sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg=="
|
||||
},
|
||||
"d3-zoom": {
|
||||
@ -4205,7 +4183,7 @@
|
||||
},
|
||||
"dagre": {
|
||||
"version": "0.8.5",
|
||||
"resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.5.tgz",
|
||||
"resolved": "https://registry.npmmirror.com/dagre/-/dagre-0.8.5.tgz",
|
||||
"integrity": "sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==",
|
||||
"requires": {
|
||||
"graphlib": "^2.1.8",
|
||||
@ -4214,14 +4192,14 @@
|
||||
"dependencies": {
|
||||
"lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"dagre-d3": {
|
||||
"version": "0.6.4",
|
||||
"resolved": "https://registry.npmjs.org/dagre-d3/-/dagre-d3-0.6.4.tgz",
|
||||
"resolved": "https://registry.npmmirror.com/dagre-d3/-/dagre-d3-0.6.4.tgz",
|
||||
"integrity": "sha512-e/6jXeCP7/ptlAM48clmX4xTZc5Ek6T6kagS7Oz2HrYSdqcLZFLqpAfh7ldbZRFfxCZVyh61NEPR08UQRVxJzQ==",
|
||||
"requires": {
|
||||
"d3": "^5.14",
|
||||
@ -4232,7 +4210,7 @@
|
||||
"dependencies": {
|
||||
"lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
}
|
||||
}
|
||||
@ -4249,6 +4227,7 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.0.1.tgz",
|
||||
"integrity": "sha512-0HdcMZzK6ubMUnsMmQmG0AcLQPvbvb47R0+7CCZQCYgcd8OUWG91CG7sM6GoXgjz+WLl4ArFzHtBMy/QqSF4eg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"abab": "^2.0.0",
|
||||
"whatwg-mimetype": "^2.1.0",
|
||||
@ -4259,6 +4238,7 @@
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz",
|
||||
"integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash.sortby": "^4.7.0",
|
||||
"tr46": "^1.0.1",
|
||||
@ -4317,7 +4297,8 @@
|
||||
"deep-is": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
|
||||
"integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ="
|
||||
"integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
|
||||
"dev": true
|
||||
},
|
||||
"default-compare": {
|
||||
"version": "1.0.0",
|
||||
@ -4579,6 +4560,7 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz",
|
||||
"integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"webidl-conversions": "^4.0.2"
|
||||
}
|
||||
@ -4592,6 +4574,11 @@
|
||||
"domelementtype": "1"
|
||||
}
|
||||
},
|
||||
"domino": {
|
||||
"version": "2.1.6",
|
||||
"resolved": "https://registry.npmmirror.com/domino/-/domino-2.1.6.tgz",
|
||||
"integrity": "sha512-3VdM/SXBZX2omc9JF9nOPCtDaYQ67BGp5CoLpIQlO2KCAPETs8TcDHacF26jXadGbvUteZzRTeos2fhID5+ucQ=="
|
||||
},
|
||||
"domutils": {
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.6.2.tgz",
|
||||
@ -4759,7 +4746,7 @@
|
||||
},
|
||||
"entity-decode": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/entity-decode/-/entity-decode-2.0.2.tgz",
|
||||
"resolved": "https://registry.npmmirror.com/entity-decode/-/entity-decode-2.0.2.tgz",
|
||||
"integrity": "sha512-5CCY/3ci4MC1m2jlumNjWd7VBFt4VfFnmSqSNmVcXq4gxM3Vmarxtt+SvmBnzwLS669MWdVuXboNVj1qN2esVg==",
|
||||
"requires": {
|
||||
"he": "^1.1.1"
|
||||
@ -4881,6 +4868,7 @@
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.0.tgz",
|
||||
"integrity": "sha512-IeMV45ReixHS53K/OmfKAIztN/igDHzTJUhZM3k1jMhIZWjk45SMwAtBsEXiJp3vSPmTcu6CXn7mDvFHRN66fw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esprima": "^3.1.3",
|
||||
"estraverse": "^4.2.0",
|
||||
@ -4892,12 +4880,14 @@
|
||||
"esprima": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz",
|
||||
"integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM="
|
||||
"integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=",
|
||||
"dev": true
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
@ -5349,12 +5339,14 @@
|
||||
"estraverse": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz",
|
||||
"integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM="
|
||||
"integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=",
|
||||
"dev": true
|
||||
},
|
||||
"esutils": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
|
||||
"integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs="
|
||||
"integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
|
||||
"dev": true
|
||||
},
|
||||
"etag": {
|
||||
"version": "1.8.1",
|
||||
@ -5370,7 +5362,8 @@
|
||||
"events": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
|
||||
"integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ="
|
||||
"integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=",
|
||||
"dev": true
|
||||
},
|
||||
"eventsource-polyfill": {
|
||||
"version": "0.9.6",
|
||||
@ -6060,7 +6053,8 @@
|
||||
"fast-levenshtein": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
|
||||
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc="
|
||||
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
|
||||
"dev": true
|
||||
},
|
||||
"fastparse": {
|
||||
"version": "1.1.1",
|
||||
@ -8642,16 +8636,6 @@
|
||||
"delegate": "^3.1.2"
|
||||
}
|
||||
},
|
||||
"google-id-token-verifier": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/google-id-token-verifier/-/google-id-token-verifier-0.2.3.tgz",
|
||||
"integrity": "sha1-nmt41FieLQUNqBYT+4kK26MKTqg=",
|
||||
"requires": {
|
||||
"request": "^2.65.0",
|
||||
"rsa-pem-from-mod-exp": "^0.8.4",
|
||||
"underscore": "^1.8.3"
|
||||
}
|
||||
},
|
||||
"graceful-fs": {
|
||||
"version": "4.1.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
|
||||
@ -8660,7 +8644,7 @@
|
||||
},
|
||||
"graphlib": {
|
||||
"version": "2.1.8",
|
||||
"resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz",
|
||||
"resolved": "https://registry.npmmirror.com/graphlib/-/graphlib-2.1.8.tgz",
|
||||
"integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==",
|
||||
"requires": {
|
||||
"lodash": "^4.17.15"
|
||||
@ -8668,7 +8652,7 @@
|
||||
"dependencies": {
|
||||
"lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
}
|
||||
}
|
||||
@ -9069,6 +9053,7 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz",
|
||||
"integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"whatwg-encoding": "^1.0.1"
|
||||
}
|
||||
@ -9639,7 +9624,8 @@
|
||||
"ieee754": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz",
|
||||
"integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q="
|
||||
"integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=",
|
||||
"dev": true
|
||||
},
|
||||
"iferr": {
|
||||
"version": "0.1.5",
|
||||
@ -11238,11 +11224,6 @@
|
||||
"url-regex": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"jmespath": {
|
||||
"version": "0.15.0",
|
||||
"resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz",
|
||||
"integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc="
|
||||
},
|
||||
"jpeg-js": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.2.0.tgz",
|
||||
@ -11298,6 +11279,7 @@
|
||||
"version": "11.12.0",
|
||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz",
|
||||
"integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"abab": "^2.0.0",
|
||||
"acorn": "^5.5.3",
|
||||
@ -11330,17 +11312,20 @@
|
||||
"acorn": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.1.tgz",
|
||||
"integrity": "sha512-d+nbxBUGKg7Arpsvbnlq61mc12ek3EY8EQldM3GPAhWJ1UVxC6TDGbIvUMNU6obBX3i1+ptCIzV4vq0gFPEGVQ=="
|
||||
"integrity": "sha512-d+nbxBUGKg7Arpsvbnlq61mc12ek3EY8EQldM3GPAhWJ1UVxC6TDGbIvUMNU6obBX3i1+ptCIzV4vq0gFPEGVQ==",
|
||||
"dev": true
|
||||
},
|
||||
"sax": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
|
||||
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
|
||||
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
|
||||
"dev": true
|
||||
},
|
||||
"whatwg-url": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz",
|
||||
"integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash.sortby": "^4.7.0",
|
||||
"tr46": "^1.0.1",
|
||||
@ -11351,6 +11336,7 @@
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz",
|
||||
"integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"async-limiter": "~1.0.0"
|
||||
}
|
||||
@ -11450,17 +11436,17 @@
|
||||
"dev": true
|
||||
},
|
||||
"katex": {
|
||||
"version": "0.13.0",
|
||||
"resolved": "https://registry.npmjs.org/katex/-/katex-0.13.0.tgz",
|
||||
"integrity": "sha512-6cHbzbegYgS9vvVGuH8UA+o97X+ZshtboSqJJCdq7trBYzuD75JNwr7Ef606xkUjecPPhFnyB+afx1dVafielg==",
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmmirror.com/katex/-/katex-0.16.2.tgz",
|
||||
"integrity": "sha512-70DJdQAyh9EMsthw3AaQlDyFf54X7nWEUIa5W+rq8XOpEk//w5Th7/8SqFqpvi/KZ2t6MHUj4f9wLmztBmAYQA==",
|
||||
"requires": {
|
||||
"commander": "^6.0.0"
|
||||
"commander": "^8.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
|
||||
"integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA=="
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/commander/-/commander-8.3.0.tgz",
|
||||
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -11549,7 +11535,8 @@
|
||||
"left-pad": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz",
|
||||
"integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="
|
||||
"integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==",
|
||||
"dev": true
|
||||
},
|
||||
"leven": {
|
||||
"version": "2.1.0",
|
||||
@ -11561,6 +11548,7 @@
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
|
||||
"integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"prelude-ls": "~1.1.2",
|
||||
"type-check": "~0.3.2"
|
||||
@ -12513,7 +12501,7 @@
|
||||
},
|
||||
"minify": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/minify/-/minify-4.1.3.tgz",
|
||||
"resolved": "https://registry.npmmirror.com/minify/-/minify-4.1.3.tgz",
|
||||
"integrity": "sha512-ykuscavxivSmVpcCzsXmsVTukWYLUUtPhHj0w2ILvHDGqC+hsuTCihBn9+PJBd58JNvWTNg9132J9nrrI2anzA==",
|
||||
"requires": {
|
||||
"clean-css": "^4.1.6",
|
||||
@ -12527,7 +12515,7 @@
|
||||
"dependencies": {
|
||||
"commander": {
|
||||
"version": "2.20.3",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||
"resolved": "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz",
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
|
||||
},
|
||||
"debug": {
|
||||
@ -12540,12 +12528,12 @@
|
||||
},
|
||||
"he": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||
"resolved": "https://registry.npmmirror.com/he/-/he-1.2.0.tgz",
|
||||
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
|
||||
},
|
||||
"html-minifier": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-4.0.0.tgz",
|
||||
"resolved": "https://registry.npmmirror.com/html-minifier/-/html-minifier-4.0.0.tgz",
|
||||
"integrity": "sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig==",
|
||||
"requires": {
|
||||
"camel-case": "^3.0.0",
|
||||
@ -12559,7 +12547,7 @@
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"uglify-js": {
|
||||
@ -13342,7 +13330,8 @@
|
||||
"nwsapi": {
|
||||
"version": "2.0.8",
|
||||
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.0.8.tgz",
|
||||
"integrity": "sha512-7RZ+qbFGiVc6v14Y8DSZjPN1wZPOaMbiiP4tzf5eNuyOITAeOIA3cMhjuKUypVIqBgCSg1KaSyAv8Ocq/0ZJ1A=="
|
||||
"integrity": "sha512-7RZ+qbFGiVc6v14Y8DSZjPN1wZPOaMbiiP4tzf5eNuyOITAeOIA3cMhjuKUypVIqBgCSg1KaSyAv8Ocq/0ZJ1A==",
|
||||
"dev": true
|
||||
},
|
||||
"oauth-sign": {
|
||||
"version": "0.9.0",
|
||||
@ -13677,6 +13666,7 @@
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
|
||||
"integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"deep-is": "~0.1.3",
|
||||
"fast-levenshtein": "~2.0.4",
|
||||
@ -13689,7 +13679,8 @@
|
||||
"wordwrap": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
|
||||
"integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus="
|
||||
"integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -14007,7 +13998,8 @@
|
||||
"parse5": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz",
|
||||
"integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA=="
|
||||
"integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==",
|
||||
"dev": true
|
||||
},
|
||||
"parseurl": {
|
||||
"version": "1.3.2",
|
||||
@ -14208,7 +14200,8 @@
|
||||
"pn": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz",
|
||||
"integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA=="
|
||||
"integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==",
|
||||
"dev": true
|
||||
},
|
||||
"pngjs": {
|
||||
"version": "3.3.3",
|
||||
@ -15111,7 +15104,8 @@
|
||||
"prelude-ls": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
|
||||
"integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ="
|
||||
"integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
|
||||
"dev": true
|
||||
},
|
||||
"prepend-http": {
|
||||
"version": "1.0.4",
|
||||
@ -15275,7 +15269,8 @@
|
||||
"punycode": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
|
||||
"integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0="
|
||||
"integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=",
|
||||
"dev": true
|
||||
},
|
||||
"q": {
|
||||
"version": "1.5.1",
|
||||
@ -15301,7 +15296,8 @@
|
||||
"querystring": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
|
||||
"integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA="
|
||||
"integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=",
|
||||
"dev": true
|
||||
},
|
||||
"querystring-es3": {
|
||||
"version": "0.2.1",
|
||||
@ -16250,6 +16246,7 @@
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz",
|
||||
"integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash": "^4.13.1"
|
||||
}
|
||||
@ -16258,6 +16255,7 @@
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.5.tgz",
|
||||
"integrity": "sha1-UoF3D2jgyXGeUWP9P6tIIhX0/aU=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"request-promise-core": "1.1.1",
|
||||
"stealthy-require": "^1.1.0",
|
||||
@ -16419,11 +16417,6 @@
|
||||
"inherits": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"rsa-pem-from-mod-exp": {
|
||||
"version": "0.8.4",
|
||||
"resolved": "https://registry.npmjs.org/rsa-pem-from-mod-exp/-/rsa-pem-from-mod-exp-0.8.4.tgz",
|
||||
"integrity": "sha1-NipCxtMEBW1JOz8SvOq7LGV2ptQ="
|
||||
},
|
||||
"rsvp": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/rsvp/-/rsvp-3.6.2.tgz",
|
||||
@ -17714,7 +17707,8 @@
|
||||
"sax": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz",
|
||||
"integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o="
|
||||
"integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=",
|
||||
"dev": true
|
||||
},
|
||||
"schema-utils": {
|
||||
"version": "0.3.0",
|
||||
@ -18448,7 +18442,8 @@
|
||||
"stealthy-require": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
|
||||
"integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks="
|
||||
"integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=",
|
||||
"dev": true
|
||||
},
|
||||
"stream-browserify": {
|
||||
"version": "2.0.1",
|
||||
@ -19607,7 +19602,8 @@
|
||||
"symbol-tree": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz",
|
||||
"integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY="
|
||||
"integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=",
|
||||
"dev": true
|
||||
},
|
||||
"table": {
|
||||
"version": "4.0.2",
|
||||
@ -20417,12 +20413,12 @@
|
||||
},
|
||||
"try-catch": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/try-catch/-/try-catch-2.0.1.tgz",
|
||||
"resolved": "https://registry.npmmirror.com/try-catch/-/try-catch-2.0.1.tgz",
|
||||
"integrity": "sha512-LsOrmObN/2WdM+y2xG+t16vhYrQsnV8wftXIcIOWZhQcBJvKGYuamJGwnU98A7Jxs2oZNkJztXlphEOoA0DWqg=="
|
||||
},
|
||||
"try-to-catch": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/try-to-catch/-/try-to-catch-1.1.1.tgz",
|
||||
"resolved": "https://registry.npmmirror.com/try-to-catch/-/try-to-catch-1.1.1.tgz",
|
||||
"integrity": "sha512-ikUlS+/BcImLhNYyIgZcEmq4byc31QpC+46/6Jm5ECWkVFhf8SM2Fp/0pMVXPX6vk45SMCwrP4Taxucne8I0VA=="
|
||||
},
|
||||
"tryer": {
|
||||
@ -20466,11 +20462,11 @@
|
||||
}
|
||||
},
|
||||
"turndown": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/turndown/-/turndown-4.0.2.tgz",
|
||||
"integrity": "sha512-pqZ6WrHFGnxXC9q2xJ3Qa7EoLAwrojgFRajWZjxTKwbz9vnNnyi8lLjiD5h86UTPOcMlEyHjm6NMhjEDdlc25A==",
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/turndown/-/turndown-7.1.1.tgz",
|
||||
"integrity": "sha512-BEkXaWH7Wh7e9bd2QumhfAXk5g34+6QUmmWx+0q6ThaVOLuLUqsnkq35HQ5SBHSaxjSfSM7US5o4lhJNH7B9MA==",
|
||||
"requires": {
|
||||
"jsdom": "^11.9.0"
|
||||
"domino": "^2.1.6"
|
||||
}
|
||||
},
|
||||
"tweetnacl": {
|
||||
@ -20489,6 +20485,7 @@
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
|
||||
"integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"prelude-ls": "~1.1.2"
|
||||
}
|
||||
@ -20568,7 +20565,8 @@
|
||||
"underscore": {
|
||||
"version": "1.8.3",
|
||||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz",
|
||||
"integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI="
|
||||
"integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=",
|
||||
"dev": true
|
||||
},
|
||||
"undertaker": {
|
||||
"version": "1.3.0",
|
||||
@ -20843,15 +20841,6 @@
|
||||
"integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=",
|
||||
"dev": true
|
||||
},
|
||||
"url": {
|
||||
"version": "0.10.3",
|
||||
"resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz",
|
||||
"integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=",
|
||||
"requires": {
|
||||
"punycode": "1.3.2",
|
||||
"querystring": "0.2.0"
|
||||
}
|
||||
},
|
||||
"url-loader": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/url-loader/-/url-loader-1.1.1.tgz",
|
||||
@ -20976,11 +20965,6 @@
|
||||
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=",
|
||||
"dev": true
|
||||
},
|
||||
"uuid": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",
|
||||
"integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g=="
|
||||
},
|
||||
"v8flags": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz",
|
||||
@ -21336,6 +21320,7 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz",
|
||||
"integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"browser-process-hrtime": "^0.1.2"
|
||||
}
|
||||
@ -21757,6 +21742,7 @@
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.4.tgz",
|
||||
"integrity": "sha512-vM9KWN6MP2mIHZ86ytcyIv7e8Cj3KTfO2nd2c8PFDqcI4bxFmQp83ibq4wadq7rL9l9sZV6o9B0LTt8ygGAAXg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"iconv-lite": "0.4.23"
|
||||
},
|
||||
@ -21765,6 +21751,7 @@
|
||||
"version": "0.4.23",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
|
||||
"integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
}
|
||||
@ -21774,7 +21761,8 @@
|
||||
"whatwg-mimetype": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.1.0.tgz",
|
||||
"integrity": "sha512-FKxhYLytBQiUKjkYteN71fAUA3g6KpNXoho1isLiLSB3N1G4F35Q5vUxWfKFhBwi5IWF27VE6WxhrnnC+m0Mew=="
|
||||
"integrity": "sha512-FKxhYLytBQiUKjkYteN71fAUA3g6KpNXoho1isLiLSB3N1G4F35Q5vUxWfKFhBwi5IWF27VE6WxhrnnC+m0Mew==",
|
||||
"dev": true
|
||||
},
|
||||
"whatwg-url": {
|
||||
"version": "6.4.0",
|
||||
@ -21953,7 +21941,8 @@
|
||||
"xml-name-validator": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz",
|
||||
"integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw=="
|
||||
"integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"xml-parse-from-string": {
|
||||
"version": "1.0.1",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "stackedit",
|
||||
"version": "5.15.12",
|
||||
"version": "5.15.21",
|
||||
"description": "免费, 开源, 功能齐全的 Markdown 编辑器",
|
||||
"author": "Benoit Schweblin, 豆萁",
|
||||
"license": "Apache-2.0",
|
||||
@ -27,7 +27,6 @@
|
||||
"dependencies": {
|
||||
"@vue/test-utils": "^1.0.0-beta.16",
|
||||
"abcjs": "^5.2.0",
|
||||
"aws-sdk": "^2.317.0",
|
||||
"babel-runtime": "^6.26.0",
|
||||
"bezier-easing": "^1.1.0",
|
||||
"body-parser": "^1.18.2",
|
||||
@ -35,11 +34,10 @@
|
||||
"compression": "^1.7.0",
|
||||
"diff-match-patch": "^1.0.0",
|
||||
"file-saver": "^1.3.8",
|
||||
"google-id-token-verifier": "^0.2.3",
|
||||
"handlebars": "^4.0.10",
|
||||
"indexeddbshim": "^3.6.2",
|
||||
"js-yaml": "^3.11.0",
|
||||
"katex": "^0.13.0",
|
||||
"katex": "^0.16.2",
|
||||
"markdown-it": "^8.4.1",
|
||||
"markdown-it-abbr": "^1.0.4",
|
||||
"markdown-it-deflist": "^2.0.2",
|
||||
@ -57,7 +55,7 @@
|
||||
"request": "^2.85.0",
|
||||
"serve-static": "^1.13.2",
|
||||
"tmp": "^0.0.33",
|
||||
"turndown": "^4.0.2",
|
||||
"turndown": "^7.1.1",
|
||||
"vue": "^2.5.16",
|
||||
"vuex": "^3.0.1"
|
||||
},
|
||||
|
@ -1,7 +1,5 @@
|
||||
const pandocPath = process.env.PANDOC_PATH || 'pandoc';
|
||||
const wkhtmltopdfPath = process.env.WKHTMLTOPDF_PATH || 'wkhtmltopdf';
|
||||
const userBucketName = process.env.USER_BUCKET_NAME || 'stackedit-users';
|
||||
const paypalUri = process.env.PAYPAL_URI || 'https://www.paypal.com/cgi-bin/webscr';
|
||||
const paypalReceiverEmail = process.env.PAYPAL_RECEIVER_EMAIL;
|
||||
|
||||
const dropboxAppKey = process.env.DROPBOX_APP_KEY;
|
||||
@ -13,12 +11,16 @@ const giteeClientSecret = process.env.GITEE_CLIENT_SECRET;
|
||||
const googleClientId = process.env.GOOGLE_CLIENT_ID;
|
||||
const googleApiKey = process.env.GOOGLE_API_KEY;
|
||||
const wordpressClientId = process.env.WORDPRESS_CLIENT_ID;
|
||||
const giteaClientId = process.env.GITEA_CLIENT_ID;
|
||||
const giteaClientSecret = process.env.GITEA_CLIENT_SECRET;
|
||||
const giteaUrl = process.env.GITEA_URL;
|
||||
const gitlabClientId = process.env.GITLAB_CLIENT_ID;
|
||||
const gitlabClientSecret = process.env.GITLAB_CLIENT_SECRET;
|
||||
const gitlabUrl = process.env.GITLAB_URL;
|
||||
|
||||
exports.values = {
|
||||
pandocPath,
|
||||
wkhtmltopdfPath,
|
||||
userBucketName,
|
||||
paypalUri,
|
||||
paypalReceiverEmail,
|
||||
dropboxAppKey,
|
||||
dropboxAppKeyFull,
|
||||
@ -29,6 +31,12 @@ exports.values = {
|
||||
googleClientId,
|
||||
googleApiKey,
|
||||
wordpressClientId,
|
||||
giteaClientId,
|
||||
giteaClientSecret,
|
||||
giteaUrl,
|
||||
gitlabClientId,
|
||||
gitlabClientSecret,
|
||||
gitlabUrl,
|
||||
};
|
||||
|
||||
exports.publicValues = {
|
||||
@ -39,4 +47,8 @@ exports.publicValues = {
|
||||
googleApiKey,
|
||||
wordpressClientId,
|
||||
allowSponsorship: !!paypalReceiverEmail,
|
||||
giteaClientId,
|
||||
giteaUrl,
|
||||
gitlabClientId,
|
||||
gitlabUrl,
|
||||
};
|
||||
|
40
server/gitea.js
Normal file
@ -0,0 +1,40 @@
|
||||
const request = require('request');
|
||||
const conf = require('./conf');
|
||||
|
||||
function giteaToken(queryParam) {
|
||||
return new Promise((resolve, reject) => {
|
||||
request({
|
||||
method: 'POST',
|
||||
url: `${conf.values.giteaUrl}/login/oauth/access_token`,
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
json: true,
|
||||
body: {
|
||||
...queryParam,
|
||||
client_id: conf.values.giteaClientId,
|
||||
client_secret: conf.values.giteaClientSecret,
|
||||
},
|
||||
}, (err, res, body) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
const token = body.access_token;
|
||||
if (token) {
|
||||
resolve(body);
|
||||
} else {
|
||||
reject(res.statusCode + ',body:' + JSON.stringify(body));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
exports.giteaToken = (req, res) => {
|
||||
giteaToken(req.query)
|
||||
.then(
|
||||
tokenBody => res.send(tokenBody),
|
||||
err => res
|
||||
.status(400)
|
||||
.send(err ? err.message || err.toString() : 'bad_code'),
|
||||
);
|
||||
};
|
40
server/gitlab.js
Normal file
@ -0,0 +1,40 @@
|
||||
const request = require('request');
|
||||
const conf = require('./conf');
|
||||
|
||||
function gitlabToken(queryParam) {
|
||||
return new Promise((resolve, reject) => {
|
||||
request({
|
||||
method: 'POST',
|
||||
url: `${conf.values.gitlabUrl}/oauth/token`,
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
json: true,
|
||||
qs: {
|
||||
...queryParam,
|
||||
client_id: conf.values.gitlabClientId,
|
||||
client_secret: conf.values.gitlabClientSecret,
|
||||
},
|
||||
}, (err, res, body) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
const token = body.access_token;
|
||||
if (token) {
|
||||
resolve(body);
|
||||
} else {
|
||||
reject(res.statusCode + ',body:' + JSON.stringify(body));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
exports.gitlabToken = (req, res) => {
|
||||
gitlabToken(req.query)
|
||||
.then(
|
||||
tokenBody => res.send(tokenBody),
|
||||
err => res
|
||||
.status(400)
|
||||
.send(err ? err.message || err.toString() : 'bad_code'),
|
||||
);
|
||||
};
|
@ -2,9 +2,10 @@ const compression = require('compression');
|
||||
const serveStatic = require('serve-static');
|
||||
const bodyParser = require('body-parser');
|
||||
const path = require('path');
|
||||
const user = require('./user');
|
||||
const github = require('./github');
|
||||
const gitee = require('./gitee');
|
||||
const gitea = require('./gitea');
|
||||
const gitlab = require('./gitlab');
|
||||
const pdf = require('./pdf');
|
||||
const pandoc = require('./pandoc');
|
||||
const conf = require('./conf');
|
||||
@ -27,13 +28,11 @@ module.exports = (app) => {
|
||||
|
||||
app.get('/oauth2/githubToken', github.githubToken);
|
||||
app.get('/oauth2/giteeToken', gitee.giteeToken);
|
||||
app.get('/oauth2/giteaToken', gitea.giteaToken);
|
||||
app.get('/oauth2/gitlabToken', gitlab.gitlabToken);
|
||||
app.get('/conf', (req, res) => res.send(conf.publicValues));
|
||||
app.get('/userInfo', user.userInfo);
|
||||
app.post('/pdfExport', pdf.generate);
|
||||
app.post('/pandocExport', pandoc.generate);
|
||||
app.post('/paypalIpn', bodyParser.urlencoded({
|
||||
extended: false,
|
||||
}), user.paypalIpn);
|
||||
app.get('/giteeClientId', (req, res) => {
|
||||
const giteeClientIds = conf.values.giteeClientId.split(',');
|
||||
// 仅一个 则直接返回
|
||||
@ -62,17 +61,24 @@ module.exports = (app) => {
|
||||
// Google Drive action receiver
|
||||
app.get('/googleDriveAction', (req, res) =>
|
||||
res.redirect(`./app#providerId=googleDrive&state=${encodeURIComponent(req.query.state)}`));
|
||||
// Serve the static folder with 30 day max-age
|
||||
app.use('/themes', serveStatic(resolvePath('static/themes'), {
|
||||
maxAge: '5d',
|
||||
}));
|
||||
|
||||
// Serve style.css with 1 day max-age
|
||||
app.get('/style.css', (req, res) => res.sendFile(resolvePath('dist/style.css'), {
|
||||
maxAge: '1d',
|
||||
}));
|
||||
// Serve share.html
|
||||
app.get('/share.html', (req, res) => res.sendFile(resolvePath('static/landing/share.html')));
|
||||
app.get('/gistshare.html', (req, res) => res.sendFile(resolvePath('static/landing/gistshare.html')));
|
||||
|
||||
// Serve static resources
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
// Serve index.html in /app
|
||||
app.get('/app', (req, res) => res.sendFile(resolvePath('dist/index.html')));
|
||||
|
||||
// Serve style.css with 1 day max-age
|
||||
app.get('/style.css', (req, res) => res.sendFile(resolvePath('dist/style.css'), {
|
||||
maxAge: '1d',
|
||||
}));
|
||||
|
||||
// Serve the static folder with 1 year max-age
|
||||
app.use('/static', serveStatic(resolvePath('dist/static'), {
|
||||
maxAge: '1y',
|
||||
|
116
server/user.js
@ -1,116 +0,0 @@
|
||||
const request = require('request');
|
||||
const AWS = require('aws-sdk');
|
||||
const verifier = require('google-id-token-verifier');
|
||||
const conf = require('./conf');
|
||||
|
||||
const s3Client = new AWS.S3();
|
||||
|
||||
const cb = (resolve, reject) => (err, res) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(res);
|
||||
}
|
||||
};
|
||||
|
||||
exports.getUser = id => new Promise((resolve, reject) => {
|
||||
s3Client.getObject({
|
||||
Bucket: conf.values.userBucketName,
|
||||
Key: id,
|
||||
}, cb(resolve, reject));
|
||||
})
|
||||
.then(
|
||||
res => JSON.parse(`${res.Body}`),
|
||||
(err) => {
|
||||
if (err.code !== 'NoSuchKey') {
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
exports.putUser = (id, user) => new Promise((resolve, reject) => {
|
||||
s3Client.putObject({
|
||||
Bucket: conf.values.userBucketName,
|
||||
Key: id,
|
||||
Body: JSON.stringify(user),
|
||||
}, cb(resolve, reject));
|
||||
});
|
||||
|
||||
exports.removeUser = id => new Promise((resolve, reject) => {
|
||||
s3Client.deleteObject({
|
||||
Bucket: conf.values.userBucketName,
|
||||
Key: id,
|
||||
}, cb(resolve, reject));
|
||||
});
|
||||
|
||||
exports.getUserFromToken = idToken => new Promise((resolve, reject) => verifier
|
||||
.verify(idToken, conf.values.googleClientId, cb(resolve, reject)))
|
||||
.then(tokenInfo => exports.getUser(tokenInfo.sub));
|
||||
|
||||
exports.userInfo = (req, res) => exports.getUserFromToken(req.query.idToken)
|
||||
.then(
|
||||
user => res.send(Object.assign({
|
||||
sponsorUntil: 0,
|
||||
}, user)),
|
||||
err => res
|
||||
.status(400)
|
||||
.send(err ? err.message || err.toString() : 'invalid_token'),
|
||||
);
|
||||
|
||||
exports.paypalIpn = (req, res, next) => Promise.resolve()
|
||||
.then(() => {
|
||||
const userId = req.body.custom;
|
||||
const paypalEmail = req.body.payer_email;
|
||||
const gross = parseFloat(req.body.mc_gross);
|
||||
let sponsorUntil;
|
||||
if (gross === 5) {
|
||||
sponsorUntil = Date.now() + (3 * 31 * 24 * 60 * 60 * 1000); // 3 months
|
||||
} else if (gross === 15) {
|
||||
sponsorUntil = Date.now() + (366 * 24 * 60 * 60 * 1000); // 1 year
|
||||
} else if (gross === 25) {
|
||||
sponsorUntil = Date.now() + (2 * 366 * 24 * 60 * 60 * 1000); // 2 years
|
||||
} else if (gross === 50) {
|
||||
sponsorUntil = Date.now() + (5 * 366 * 24 * 60 * 60 * 1000); // 5 years
|
||||
}
|
||||
if (
|
||||
req.body.receiver_email !== conf.values.paypalReceiverEmail ||
|
||||
req.body.payment_status !== 'Completed' ||
|
||||
req.body.mc_currency !== 'USD' ||
|
||||
(req.body.txn_type !== 'web_accept' && req.body.txn_type !== 'subscr_payment') ||
|
||||
!userId || !sponsorUntil
|
||||
) {
|
||||
// Ignoring PayPal IPN
|
||||
return res.end();
|
||||
}
|
||||
// Processing PayPal IPN
|
||||
req.body.cmd = '_notify-validate';
|
||||
return new Promise((resolve, reject) => request.post({
|
||||
uri: conf.values.paypalUri,
|
||||
form: req.body,
|
||||
}, (err, response, body) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else if (body !== 'VERIFIED') {
|
||||
reject(new Error('PayPal IPN unverified'));
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
}))
|
||||
.then(() => exports.putUser(userId, {
|
||||
paypalEmail,
|
||||
sponsorUntil,
|
||||
}))
|
||||
.then(() => res.end());
|
||||
})
|
||||
.catch(next);
|
||||
|
||||
exports.checkSponsor = (idToken) => {
|
||||
if (!conf.publicValues.allowSponsorship) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
if (!idToken) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
return exports.getUserFromToken(idToken)
|
||||
.then(userInfo => userInfo && userInfo.sponsorUntil > Date.now(), () => false);
|
||||
};
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 28 KiB |
@ -22,6 +22,8 @@ import networkSvc from '../services/networkSvc';
|
||||
import tempFileSvc from '../services/tempFileSvc';
|
||||
import store from '../store';
|
||||
import './common/vueGlobals';
|
||||
import utils from '../services/utils';
|
||||
import providerRegistry from '../services/providers/common/providerRegistry';
|
||||
|
||||
const themeClasses = {
|
||||
light: ['app--light'],
|
||||
@ -49,11 +51,49 @@ export default {
|
||||
close() {
|
||||
tempFileSvc.close();
|
||||
},
|
||||
// 通过路径查看文件 支持相对路径
|
||||
viewFileByPath(path) {
|
||||
// 如果是md结尾
|
||||
if (!path) {
|
||||
return;
|
||||
}
|
||||
const currDirNode = store.getters['explorer/selectedNodeFolder'];
|
||||
if (path.slice(-3) === '.md') {
|
||||
const rootNode = store.getters['explorer/rootNode'];
|
||||
const node = utils.findNodeByPath(rootNode, currDirNode, path);
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
store.commit('explorer/setSelectedId', node.item.id);
|
||||
// Prevent from freezing the UI while loading the file
|
||||
setTimeout(() => {
|
||||
store.commit('file/setCurrentId', node.item.id);
|
||||
}, 10);
|
||||
} else {
|
||||
const workspace = store.getters['workspace/currentWorkspace'];
|
||||
const provider = providerRegistry.providersById[workspace.providerId];
|
||||
if (provider == null) {
|
||||
return;
|
||||
}
|
||||
const absolutePath = utils.getAbsoluteFilePath(currDirNode, path);
|
||||
const url = provider.getFilePathUrl(absolutePath);
|
||||
if (url) {
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
async created() {
|
||||
window.viewFileByPath = this.viewFileByPath;
|
||||
try {
|
||||
await syncSvc.init();
|
||||
await networkSvc.init();
|
||||
// store 编辑主题
|
||||
const editTheme = localStorage.getItem('theme/currEditTheme');
|
||||
store.dispatch('theme/setEditTheme', editTheme || 'default');
|
||||
// store 预览主题
|
||||
const previewTheme = localStorage.getItem('theme/currPreviewTheme');
|
||||
store.dispatch('theme/setPreviewTheme', previewTheme || 'default');
|
||||
this.ready = true;
|
||||
tempFileSvc.setReady();
|
||||
} catch (err) {
|
||||
|
@ -7,11 +7,12 @@ import Prism from 'prismjs';
|
||||
import cledit from '../services/editor/cledit';
|
||||
|
||||
export default {
|
||||
props: ['value', 'lang', 'disabled'],
|
||||
props: ['value', 'lang', 'disabled', 'scrollClass'],
|
||||
mounted() {
|
||||
const preElt = this.$el;
|
||||
let scrollElt = preElt;
|
||||
while (scrollElt && !scrollElt.classList.contains('modal')) {
|
||||
const scrollCls = this.scrollClass || 'modal';
|
||||
while (scrollElt && !scrollElt.classList.contains(scrollCls)) {
|
||||
scrollElt = scrollElt.parentNode;
|
||||
}
|
||||
if (scrollElt) {
|
||||
|
@ -14,6 +14,8 @@ import CommentList from './gutters/CommentList';
|
||||
import EditorNewDiscussionButton from './gutters/EditorNewDiscussionButton';
|
||||
import store from '../store';
|
||||
import editorSvc from '../services/editorSvc';
|
||||
import imageSvc from '../services/imageSvc';
|
||||
import utils from '../services/utils';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -32,7 +34,7 @@ export default {
|
||||
]),
|
||||
},
|
||||
methods: {
|
||||
setImgAndDoClick(items) {
|
||||
async processUpload(items) {
|
||||
let file = null;
|
||||
if (!items || items.length === 0) {
|
||||
return;
|
||||
@ -46,8 +48,23 @@ export default {
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
store.dispatch('img/setImg', file);
|
||||
editorSvc.pagedownEditor.uiManager.doClick('image');
|
||||
const imgId = utils.uid();
|
||||
store.dispatch('img/setCurrImgId', imgId);
|
||||
editorSvc.pagedownEditor.uiManager.doClick('imageUploading');
|
||||
try {
|
||||
const { url, error } = await imageSvc.updateImg(file);
|
||||
// 存在错误
|
||||
if (error) {
|
||||
editorSvc.clEditor.replaceAll(`[图片上传中...(image-${imgId})]`, `[图片上传失败...(image-${imgId})]`);
|
||||
store.dispatch('notification/error', error);
|
||||
return;
|
||||
}
|
||||
editorSvc.clEditor.replaceAll(`[图片上传中...(image-${imgId})]`, ``);
|
||||
} catch (err) {
|
||||
console.error(err); // eslint-disable-line no-console
|
||||
editorSvc.clEditor.replaceAll(`[图片上传中...(image-${imgId})]`, `[图片上传失败...(image-${imgId})]`);
|
||||
store.dispatch('notification/error', err);
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
@ -56,6 +73,11 @@ export default {
|
||||
if (currImgStorageStr) {
|
||||
store.commit('img/changeCheckedStorage', JSON.parse(currImgStorageStr));
|
||||
}
|
||||
// 当前本地图片路径配置
|
||||
const workspaceImgPath = localStorage.getItem('img/workspaceImgPath');
|
||||
if (workspaceImgPath) {
|
||||
store.commit('img/setWorkspaceImgPath', JSON.parse(workspaceImgPath));
|
||||
}
|
||||
const editorElt = this.$el.querySelector('.editor__inner');
|
||||
const onDiscussionEvt = cb => (evt) => {
|
||||
let elt = evt.target;
|
||||
@ -83,11 +105,11 @@ export default {
|
||||
|
||||
editorElt.addEventListener('drop', (event) => {
|
||||
const transItems = event.dataTransfer.items;
|
||||
this.setImgAndDoClick(transItems);
|
||||
this.processUpload(transItems);
|
||||
});
|
||||
editorElt.addEventListener('paste', (event) => {
|
||||
const pasteItems = (event.clipboardData || window.clipboardData).items;
|
||||
this.setImgAndDoClick(pasteItems);
|
||||
this.processUpload(pasteItems);
|
||||
});
|
||||
|
||||
this.$watch(
|
||||
|
197
src/components/EditorInPageButtons.vue
Normal file
@ -0,0 +1,197 @@
|
||||
<template>
|
||||
<div class="editor-in-page-buttons">
|
||||
<ul>
|
||||
<li :title="`查找 ${mod}+F`">
|
||||
<a @click="showFind"><icon-search></icon-search></a>
|
||||
</li>
|
||||
<li :title="`替换 ${mod}+Alt+F`">
|
||||
<a @click="showFindReplace"><icon-find-replace></icon-find-replace></a>
|
||||
</li>
|
||||
<li title="切换编辑主题">
|
||||
<dropdown-menu :selected="selectedTheme" :options="allThemes" :closeOnItemClick="false" @change="changeTheme">
|
||||
<icon-select-theme></icon-select-theme>
|
||||
</dropdown-menu>
|
||||
</li>
|
||||
<li class="after">
|
||||
<icon-ellipsis></icon-ellipsis>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import store from '../store';
|
||||
import editorSvc from '../services/editorSvc';
|
||||
import DropdownMenu from './common/DropdownMenu';
|
||||
|
||||
const mod = /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? 'Meta' : 'Ctrl';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DropdownMenu,
|
||||
},
|
||||
data: () => ({
|
||||
mod,
|
||||
allThemes: [{
|
||||
name: '默认主题',
|
||||
value: 'default',
|
||||
}, {
|
||||
name: '天蓝黑',
|
||||
value: 'azure',
|
||||
}, {
|
||||
name: '冰山黑',
|
||||
value: 'iceberg_contrast',
|
||||
}, {
|
||||
name: '黎明白',
|
||||
value: 'dawn',
|
||||
}, {
|
||||
name: '孔雀黑',
|
||||
value: 'peacock',
|
||||
}, {
|
||||
name: '薄荷黑',
|
||||
value: 'mintchoc',
|
||||
}, {
|
||||
name: '薄荷绿',
|
||||
value: 'spearmint',
|
||||
}, {
|
||||
name: '暗蓝黑',
|
||||
value: 'slate',
|
||||
}, {
|
||||
name: '文墨黑',
|
||||
value: 'carbonight',
|
||||
}, {
|
||||
name: '日光白',
|
||||
value: 'solarized_light',
|
||||
}, {
|
||||
name: '咖啡黑',
|
||||
value: 'espresso_libre',
|
||||
}, {
|
||||
name: '薰衣草黑',
|
||||
value: 'lavender',
|
||||
}, {
|
||||
name: '耀斑黑',
|
||||
value: 'solarflare',
|
||||
}, {
|
||||
name: 'Clouds白',
|
||||
value: 'clouds',
|
||||
}, {
|
||||
name: 'Clouds黑',
|
||||
value: 'clouds_midnight',
|
||||
}, {
|
||||
name: 'GitHub白',
|
||||
value: 'github',
|
||||
}, {
|
||||
name: '自定义',
|
||||
value: 'custom',
|
||||
}],
|
||||
}),
|
||||
computed: {
|
||||
...mapGetters('theme', [
|
||||
'currEditTheme',
|
||||
'customEditThemeStyle',
|
||||
]),
|
||||
selectedTheme() {
|
||||
return {
|
||||
value: this.currEditTheme || 'default',
|
||||
};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions('data', [
|
||||
'toggleSideBar',
|
||||
]),
|
||||
showFind() {
|
||||
store.dispatch('findReplace/open', {
|
||||
type: 'find',
|
||||
findText: editorSvc.clEditor.selectionMgr.hasFocus() &&
|
||||
editorSvc.clEditor.selectionMgr.getSelectedText(),
|
||||
});
|
||||
},
|
||||
showFindReplace() {
|
||||
store.dispatch('findReplace/open', {
|
||||
type: 'replace',
|
||||
findText: editorSvc.clEditor.selectionMgr.hasFocus() &&
|
||||
editorSvc.clEditor.selectionMgr.getSelectedText(),
|
||||
});
|
||||
},
|
||||
async changeTheme(item) {
|
||||
await store.dispatch('theme/setEditTheme', item.value);
|
||||
// 如果自定义主题没内容 则弹出编辑区域
|
||||
if (item.value === 'custom' && !this.customEditThemeStyle) {
|
||||
this.toggleSideBar(true);
|
||||
store.dispatch('data/setSideBarPanel', 'editTheme');
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../styles/variables.scss';
|
||||
|
||||
.editor-in-page-buttons {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -108px;
|
||||
height: 34px;
|
||||
padding: 5px;
|
||||
background-color: rgba(84, 96, 114, 0.4);
|
||||
border-radius: $border-radius-base;
|
||||
transition: 0.5s;
|
||||
display: flex;
|
||||
|
||||
.dropdown-menu {
|
||||
display: none;
|
||||
|
||||
.dropdown-menu-items {
|
||||
right: unset;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&:active,
|
||||
&:focus,
|
||||
&:hover {
|
||||
left: 0;
|
||||
transition: 0.5s;
|
||||
background-color: #546072;
|
||||
|
||||
.dropdown-menu {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
padding: 0;
|
||||
margin-left: 10px;
|
||||
line-height: 20px;
|
||||
|
||||
li {
|
||||
width: 16px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
list-style: none;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
color: #dea731;
|
||||
opacity: 0.7;
|
||||
|
||||
&:active,
|
||||
&:focus,
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.after {
|
||||
margin-left: 0;
|
||||
margin-right: -6px;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -14,6 +14,7 @@
|
||||
</div>
|
||||
<explorer-node v-for="node in node.files" :key="node.item.id" :node="node" :depth="depth + 1"></explorer-node>
|
||||
</div>
|
||||
<button ref="copyId" v-clipboard="copyPath()" @click="info('路径已复制到剪切板!')" style="display: none;"></button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -23,6 +24,7 @@ import workspaceSvc from '../services/workspaceSvc';
|
||||
import explorerSvc from '../services/explorerSvc';
|
||||
import store from '../store';
|
||||
import badgeSvc from '../services/badgeSvc';
|
||||
import utils from '../services/utils';
|
||||
|
||||
export default {
|
||||
name: 'explorer-node', // Required for recursivity
|
||||
@ -80,6 +82,9 @@ export default {
|
||||
...mapActions('explorer', [
|
||||
'setDragTarget',
|
||||
]),
|
||||
...mapActions('notification', [
|
||||
'info',
|
||||
]),
|
||||
select(id = this.node.item.id, doOpen = true) {
|
||||
const node = store.getters['explorer/nodeMap'][id];
|
||||
if (!node) {
|
||||
@ -144,6 +149,11 @@ export default {
|
||||
// See https://stackoverflow.com/a/3977637/1333165
|
||||
evt.dataTransfer.setData('Text', '');
|
||||
},
|
||||
copyPath() {
|
||||
let path = utils.getAbsoluteDir(this.node).replaceAll(' ', '%20');
|
||||
path = path.indexOf('/') === 0 ? path : `/${path}`;
|
||||
return this.node.isFolder ? path : `${path}.md`;
|
||||
},
|
||||
onDrop() {
|
||||
const sourceNode = store.getters['explorer/dragSourceNode'];
|
||||
const targetNode = store.getters['explorer/dragTargetNodeFolder'];
|
||||
@ -169,22 +179,26 @@ export default {
|
||||
top: evt.clientY,
|
||||
},
|
||||
items: [{
|
||||
name: 'New file',
|
||||
name: '新建文件',
|
||||
disabled: !this.node.isFolder || this.node.isTrash,
|
||||
perform: () => explorerSvc.newItem(false),
|
||||
}, {
|
||||
name: 'New folder',
|
||||
name: '新建文件夹',
|
||||
disabled: !this.node.isFolder || this.node.isTrash || this.node.isTemp,
|
||||
perform: () => explorerSvc.newItem(true),
|
||||
}, {
|
||||
type: 'separator',
|
||||
}, {
|
||||
name: 'Rename',
|
||||
name: '重命名',
|
||||
disabled: this.node.isTrash || this.node.isTemp,
|
||||
perform: () => this.setEditingId(this.node.item.id),
|
||||
}, {
|
||||
name: 'Delete',
|
||||
name: '删除',
|
||||
perform: () => explorerSvc.deleteItem(),
|
||||
}, {
|
||||
name: '复制路径',
|
||||
disabled: this.node.isTrash || this.node.isTemp,
|
||||
perform: () => this.$refs.copyId.click(),
|
||||
}],
|
||||
});
|
||||
if (item) {
|
||||
|
@ -383,6 +383,10 @@ export default {
|
||||
.find-replace-highlighting {
|
||||
background-color: $highlighting-color;
|
||||
color: $editor-color-light !important;
|
||||
|
||||
.app--dark & {
|
||||
background-color: $dark-highlighting-color;
|
||||
}
|
||||
}
|
||||
|
||||
.find-replace-selection {
|
||||
|
@ -9,11 +9,12 @@
|
||||
<navigation-bar></navigation-bar>
|
||||
</div>
|
||||
<div class="layout__panel flex flex--row" :style="{height: styles.innerHeight + 'px'}">
|
||||
<div class="layout__panel layout__panel--editor" v-show="styles.showEditor" :style="{width: (styles.editorWidth + styles.editorGutterWidth) + 'px', fontSize: styles.fontSize + 'px'}">
|
||||
<div class="layout__panel layout__panel--editor" :class="editTheme" v-show="styles.showEditor" :style="{width: (styles.editorWidth + styles.editorGutterWidth) + 'px', fontSize: styles.fontSize + 'px'}">
|
||||
<div class="gutter" :style="{left: styles.editorGutterLeft + 'px'}">
|
||||
<div class="gutter__background" v-if="styles.editorGutterWidth" :style="{width: styles.editorGutterWidth + 'px'}"></div>
|
||||
</div>
|
||||
<editor></editor>
|
||||
<editor-in-page-buttons v-if="editorShowInPageButtons"></editor-in-page-buttons>
|
||||
<div class="gutter" :style="{left: styles.editorGutterLeft + 'px'}">
|
||||
<sticky-comment v-if="styles.editorGutterWidth && stickyComment === 'top'"></sticky-comment>
|
||||
<current-discussion v-if="styles.editorGutterWidth"></current-discussion>
|
||||
@ -27,6 +28,7 @@
|
||||
<div class="gutter__background" v-if="styles.previewGutterWidth" :style="{width: styles.previewGutterWidth + 'px'}"></div>
|
||||
</div>
|
||||
<preview></preview>
|
||||
<preview-in-page-buttons></preview-in-page-buttons>
|
||||
<div class="gutter" :style="{left: styles.previewGutterLeft + 'px'}">
|
||||
<sticky-comment v-if="styles.previewGutterWidth && stickyComment === 'top'"></sticky-comment>
|
||||
<current-discussion v-if="styles.previewGutterWidth"></current-discussion>
|
||||
@ -58,6 +60,8 @@ import SideBar from './SideBar';
|
||||
import Editor from './Editor';
|
||||
import Preview from './Preview';
|
||||
import Tour from './Tour';
|
||||
import EditorInPageButtons from './EditorInPageButtons';
|
||||
import PreviewInPageButtons from './PreviewInPageButtons';
|
||||
import StickyComment from './gutters/StickyComment';
|
||||
import CurrentDiscussion from './gutters/CurrentDiscussion';
|
||||
import FindReplace from './FindReplace';
|
||||
@ -75,6 +79,8 @@ export default {
|
||||
Editor,
|
||||
Preview,
|
||||
Tour,
|
||||
EditorInPageButtons,
|
||||
PreviewInPageButtons,
|
||||
StickyComment,
|
||||
CurrentDiscussion,
|
||||
FindReplace,
|
||||
@ -96,9 +102,18 @@ export default {
|
||||
...mapGetters('data', [
|
||||
'layoutSettings',
|
||||
]),
|
||||
...mapGetters('theme', [
|
||||
'currEditTheme',
|
||||
]),
|
||||
editTheme() {
|
||||
return `edit-theme--${this.currEditTheme || 'default'}`;
|
||||
},
|
||||
showFindReplace() {
|
||||
return !!store.state.findReplace.type;
|
||||
},
|
||||
editorShowInPageButtons() {
|
||||
return store.getters['data/computedSettings'].editor.showInPageButtons;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions('layout', [
|
||||
|
@ -10,6 +10,7 @@
|
||||
<div class="modal__button-bar">
|
||||
<button class="button" v-if="simpleModal.rejectText" @click="config.reject()">{{simpleModal.rejectText}}</button>
|
||||
<button class="button button--resolve" v-if="simpleModal.resolveText" @click="config.resolve()">{{simpleModal.resolveText}}</button>
|
||||
<button v-for="(item, idx) in (simpleModal.resolveArray || [])" class="button button--resolve" @click="config.resolve(item.value)">{{item.text}}</button>
|
||||
</div>
|
||||
</modal-inner>
|
||||
</div>
|
||||
@ -39,6 +40,9 @@ import WorkspaceManagementModal from './modals/WorkspaceManagementModal';
|
||||
import AccountManagementModal from './modals/AccountManagementModal';
|
||||
import BadgeManagementModal from './modals/BadgeManagementModal';
|
||||
import SponsorModal from './modals/SponsorModal';
|
||||
import CommitMessageModal from './modals/CommitMessageModal';
|
||||
import WorkspaceImgPathModal from './modals/WorkspaceImgPathModal';
|
||||
import ChatGptModal from './modals/ChatGptModal';
|
||||
|
||||
// Providers
|
||||
import GooglePhotoModal from './modals/providers/GooglePhotoModal';
|
||||
@ -62,6 +66,8 @@ import GiteeOpenModal from './modals/providers/GiteeOpenModal';
|
||||
import GiteeSaveModal from './modals/providers/GiteeSaveModal';
|
||||
import GiteeWorkspaceModal from './modals/providers/GiteeWorkspaceModal';
|
||||
import GiteePublishModal from './modals/providers/GiteePublishModal';
|
||||
import GiteeGistSyncModal from './modals/providers/GiteeGistSyncModal';
|
||||
import GiteeGistPublishModal from './modals/providers/GiteeGistPublishModal';
|
||||
import GitlabAccountModal from './modals/providers/GitlabAccountModal';
|
||||
import GitlabOpenModal from './modals/providers/GitlabOpenModal';
|
||||
import GitlabPublishModal from './modals/providers/GitlabPublishModal';
|
||||
@ -105,6 +111,9 @@ export default {
|
||||
AccountManagementModal,
|
||||
BadgeManagementModal,
|
||||
SponsorModal,
|
||||
CommitMessageModal,
|
||||
WorkspaceImgPathModal,
|
||||
ChatGptModal,
|
||||
// Providers
|
||||
GooglePhotoModal,
|
||||
GoogleDriveAccountModal,
|
||||
@ -127,6 +136,8 @@ export default {
|
||||
GiteeSaveModal,
|
||||
GiteeWorkspaceModal,
|
||||
GiteePublishModal,
|
||||
GiteeGistSyncModal,
|
||||
GiteeGistPublishModal,
|
||||
GitlabAccountModal,
|
||||
GitlabOpenModal,
|
||||
GitlabPublishModal,
|
||||
@ -177,6 +188,7 @@ export default {
|
||||
// User has to sign in
|
||||
await store.dispatch('modal/open', 'signInForSponsorship');
|
||||
await giteeHelper.signin();
|
||||
await syncSvc.afterSignIn();
|
||||
syncSvc.requestSync();
|
||||
}
|
||||
if (!store.getters.isSponsor) {
|
||||
|
@ -114,7 +114,8 @@ export default {
|
||||
publishLocations: 'current',
|
||||
}),
|
||||
pagedownButtons() {
|
||||
return pagedownButtons.map(button => ({
|
||||
const buttonShowObj = store.getters['data/computedSettings'].editor.headButtons;
|
||||
return pagedownButtons.filter(it => buttonShowObj[it.method]).map(button => ({
|
||||
...button,
|
||||
titleWithShortcut: `${button.title}${getShortcut(button.method)}`,
|
||||
iconClass: `icon-${button.icon}`,
|
||||
|
@ -10,10 +10,10 @@
|
||||
{{item.content}}
|
||||
</div>
|
||||
<button class="notification__button button" v-if="item.type === 'confirm'" @click="item.reject">
|
||||
No
|
||||
否
|
||||
</button>
|
||||
<button class="notification__button button" v-if="item.type === 'confirm'" @click="item.resolve">
|
||||
Yes
|
||||
是
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="preview">
|
||||
<div class="preview__inner-1" @click="onClick" @scroll="onScroll">
|
||||
<div class="preview__inner-2" :style="{padding: styles.previewPadding}">
|
||||
<div class="preview__inner-2" :class="previewTheme" :style="{padding: styles.previewPadding}">
|
||||
</div>
|
||||
<div class="gutter" :style="{left: styles.previewGutterLeft + 'px'}">
|
||||
<comment-list v-if="styles.previewGutterWidth"></comment-list>
|
||||
@ -37,9 +37,15 @@ export default {
|
||||
...mapGetters('file', [
|
||||
'isCurrentTemp',
|
||||
]),
|
||||
...mapGetters('theme', [
|
||||
'currPreviewTheme',
|
||||
]),
|
||||
...mapGetters('layout', [
|
||||
'styles',
|
||||
]),
|
||||
previewTheme() {
|
||||
return `preview-theme--${this.currPreviewTheme || 'default'}`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions('data', [
|
||||
|
212
src/components/PreviewInPageButtons.vue
Normal file
@ -0,0 +1,212 @@
|
||||
<template>
|
||||
<div class="preview-in-page-buttons">
|
||||
<ul>
|
||||
<li class="before">
|
||||
<icon-ellipsis></icon-ellipsis>
|
||||
</li>
|
||||
<li title="分享">
|
||||
<a href="javascript:void(0)" @click="share"><icon-share></icon-share></a>
|
||||
</li>
|
||||
<li title="切换预览主题">
|
||||
<dropdown-menu :selected="selectedTheme" :options="allThemes" :closeOnItemClick="false" @change="changeTheme">
|
||||
<icon-select-theme></icon-select-theme>
|
||||
</dropdown-menu>
|
||||
</li>
|
||||
<li title="Markdown语法帮助">
|
||||
<a href="javascript:void(0)" @click="showHelp"><icon-help-circle></icon-help-circle></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
// import juice from 'juice';
|
||||
import store from '../store';
|
||||
import DropdownMenu from './common/DropdownMenu';
|
||||
import publishSvc from '../services/publishSvc';
|
||||
import giteeGistProvider from '../services/providers/giteeGistProvider';
|
||||
import gistProvider from '../services/providers/gistProvider';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DropdownMenu,
|
||||
},
|
||||
data: () => ({
|
||||
allThemes: [{
|
||||
name: '默认主题',
|
||||
value: 'default',
|
||||
}, {
|
||||
name: '凝夜紫',
|
||||
value: 'ningyezi',
|
||||
}, {
|
||||
name: '草原绿',
|
||||
value: 'caoyuangreen',
|
||||
}, {
|
||||
name: '雁栖湖',
|
||||
value: 'yanqihu',
|
||||
}, {
|
||||
name: '灵动蓝',
|
||||
value: 'activeblue',
|
||||
}, {
|
||||
name: '极客黑',
|
||||
value: 'jikebrack',
|
||||
}, {
|
||||
name: '极简黑',
|
||||
value: 'simplebrack',
|
||||
}, {
|
||||
name: '全栈蓝',
|
||||
value: 'allblue',
|
||||
}, {
|
||||
name: '自定义',
|
||||
value: 'custom',
|
||||
}],
|
||||
baseCss: '',
|
||||
sharing: false,
|
||||
}),
|
||||
computed: {
|
||||
...mapGetters('theme', [
|
||||
'currPreviewTheme',
|
||||
'customPreviewThemeStyle',
|
||||
]),
|
||||
...mapGetters('publishLocation', {
|
||||
publishLocations: 'current',
|
||||
}),
|
||||
selectedTheme() {
|
||||
return {
|
||||
value: this.currPreviewTheme || 'default',
|
||||
};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions('data', [
|
||||
'toggleSideBar',
|
||||
]),
|
||||
async changeTheme(item) {
|
||||
await store.dispatch('theme/setPreviewTheme', item.value);
|
||||
// 如果自定义主题没内容 则弹出编辑区域
|
||||
if (item.value === 'custom' && !this.customPreviewThemeStyle) {
|
||||
this.toggleSideBar(true);
|
||||
store.dispatch('data/setSideBarPanel', 'previewTheme');
|
||||
}
|
||||
},
|
||||
showHelp() {
|
||||
this.toggleSideBar(true);
|
||||
store.dispatch('data/setSideBarPanel', 'help');
|
||||
},
|
||||
async share() {
|
||||
if (this.sharing) {
|
||||
store.dispatch('notification/info', '分享链接创建中...请稍后再试');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const currentFile = store.getters['file/current'];
|
||||
await store.dispatch('modal/open', { type: 'shareHtmlPre', name: currentFile.name });
|
||||
this.sharing = true;
|
||||
const mainToken = store.getters['workspace/mainWorkspaceToken'];
|
||||
if (!mainToken) {
|
||||
store.dispatch('notification/info', '登录主文档空间之后才可使用分享功能!');
|
||||
return;
|
||||
}
|
||||
let tempGistId = null;
|
||||
const isGithub = mainToken.providerId === 'githubAppData';
|
||||
const gistProviderId = isGithub ? 'gist' : 'giteegist';
|
||||
const filterLocations = this.publishLocations.filter(it => it.providerId === gistProviderId
|
||||
&& it.url && it.gistId);
|
||||
if (filterLocations.length > 0) {
|
||||
tempGistId = filterLocations[0].gistId;
|
||||
}
|
||||
const location = (isGithub ? gistProvider : giteeGistProvider).makeLocation(
|
||||
mainToken,
|
||||
`分享-${currentFile.name}`,
|
||||
true,
|
||||
null,
|
||||
);
|
||||
location.templateId = 'styledHtmlWithTheme';
|
||||
location.fileId = currentFile.id;
|
||||
location.gistId = tempGistId;
|
||||
const { gistId } = await publishSvc.publishLocationAndStore(location);
|
||||
const sharePage = mainToken.providerId === 'githubAppData' ? 'gistshare.html' : 'share.html';
|
||||
const url = `${window.location.protocol}//${window.location.host}/${sharePage}?id=${gistId}`;
|
||||
await store.dispatch('modal/open', { type: 'shareHtml', name: currentFile.name, url });
|
||||
} catch (err) {
|
||||
if (err) {
|
||||
store.dispatch('notification/error', err);
|
||||
}
|
||||
} finally {
|
||||
this.sharing = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../styles/variables.scss';
|
||||
|
||||
.preview-in-page-buttons {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
right: -98px;
|
||||
height: 34px;
|
||||
padding: 5px;
|
||||
background-color: rgba(84, 96, 114, 0.4);
|
||||
border-radius: $border-radius-base;
|
||||
transition: 0.5s;
|
||||
display: flex;
|
||||
|
||||
.dropdown-menu {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:active,
|
||||
&:focus,
|
||||
&:hover {
|
||||
right: 0;
|
||||
transition: 0.5s;
|
||||
background-color: #546072;
|
||||
|
||||
.dropdown-menu {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu-items {
|
||||
bottom: 100%;
|
||||
top: unset;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding: 0;
|
||||
margin-left: 10px;
|
||||
line-height: 20px;
|
||||
|
||||
li {
|
||||
line-height: 16px;
|
||||
width: 16px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
list-style: none;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
margin-right: 10px;
|
||||
|
||||
.icon {
|
||||
color: #fff;
|
||||
opacity: 0.7;
|
||||
|
||||
&:active,
|
||||
&:focus,
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.before {
|
||||
margin-left: -16px;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -23,6 +23,8 @@
|
||||
<div v-else-if="panel === 'help'" class="side-bar__panel side-bar__panel--help">
|
||||
<pre class="markdown-highlighting" v-html="markdownSample"></pre>
|
||||
</div>
|
||||
<edit-theme-menu v-else-if="panel === 'editTheme'"></edit-theme-menu>
|
||||
<preview-theme-menu v-else-if="panel === 'previewTheme'"></preview-theme-menu>
|
||||
<div class="side-bar__panel side-bar__panel--toc" :class="{'side-bar__panel--hidden': panel !== 'toc'}">
|
||||
<toc>
|
||||
</toc>
|
||||
@ -41,6 +43,8 @@ import PublishMenu from './menus/PublishMenu';
|
||||
import HistoryMenu from './menus/HistoryMenu';
|
||||
import ImportExportMenu from './menus/ImportExportMenu';
|
||||
import WorkspaceBackupMenu from './menus/WorkspaceBackupMenu';
|
||||
import EditThemeMenu from './menus/EditThemeMenu';
|
||||
import PreviewThemeMenu from './menus/PreviewThemeMenu';
|
||||
import markdownSample from '../data/markdownSample.md';
|
||||
import markdownConversionSvc from '../services/markdownConversionSvc';
|
||||
import store from '../store';
|
||||
@ -55,6 +59,8 @@ const panelNames = {
|
||||
history: '文件历史',
|
||||
importExport: '导入/导出',
|
||||
workspaceBackups: '文档空间备份',
|
||||
editTheme: '编辑区主题',
|
||||
previewTheme: '预览区主题',
|
||||
};
|
||||
|
||||
export default {
|
||||
@ -67,6 +73,8 @@ export default {
|
||||
HistoryMenu,
|
||||
ImportExportMenu,
|
||||
WorkspaceBackupMenu,
|
||||
EditThemeMenu,
|
||||
PreviewThemeMenu,
|
||||
},
|
||||
data: () => ({
|
||||
markdownSample: markdownConversionSvc.highlight(markdownSample),
|
||||
|
137
src/components/common/DropdownMenu.vue
Normal file
@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<span class="dropdown-menu">
|
||||
<span ref="slotInfo" @click="toggleMenu()" class="dropdown-toggle">
|
||||
<slot></slot>
|
||||
</span>
|
||||
<ul class="dropdown-menu-items" :style="dropdownStyle" v-if="showMenu">
|
||||
<li v-for="(option, idx) in options" :key="idx">
|
||||
<a href="javascript:void(0)" :class="{selected: option.value === selectedOption.value}" @click="updateOption(option)">
|
||||
{{ option.name }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import store from '../../store';
|
||||
|
||||
export default {
|
||||
data: () => ({
|
||||
selectedOption: {
|
||||
value: '',
|
||||
name: '',
|
||||
},
|
||||
showMenu: false,
|
||||
}),
|
||||
props: {
|
||||
options: {
|
||||
type: [Array, Object],
|
||||
},
|
||||
selected: {},
|
||||
closeOnOutsideClick: {
|
||||
type: [Boolean],
|
||||
default: true,
|
||||
},
|
||||
closeOnItemClick: {
|
||||
type: [Boolean],
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.selectedOption = this.selected;
|
||||
if (this.closeOnOutsideClick) {
|
||||
document.addEventListener('click', this.clickHandler);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener('click', this.clickHandler);
|
||||
},
|
||||
computed: {
|
||||
dropdownStyle() {
|
||||
const height = store.state.layout.bodyHeight;
|
||||
return `max-height: ${height * 0.7}px;`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateOption(option) {
|
||||
this.selectedOption = option;
|
||||
if (this.closeOnItemClick) {
|
||||
this.showMenu = false;
|
||||
}
|
||||
this.$emit('change', option);
|
||||
},
|
||||
toggleMenu() {
|
||||
this.showMenu = !this.showMenu;
|
||||
},
|
||||
clickHandler(event) {
|
||||
const { target } = event;
|
||||
const { $el } = this;
|
||||
if (!$el.contains(target)) {
|
||||
this.showMenu = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
selected(val) {
|
||||
this.selectedOption = val;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.dropdown-menu {
|
||||
.dropdown-menu-items {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0;
|
||||
float: left;
|
||||
min-width: 160px;
|
||||
max-height: 450px;
|
||||
overflow-y: scroll;
|
||||
padding: 5px 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
font-size: 15px;
|
||||
background-color: #666;
|
||||
border: 1px solid #666;
|
||||
border-radius: 4px;
|
||||
-webkit-box-shadow: 0 6px 12px rgb(0, 0, 0 / 18%);
|
||||
box-shadow: 0 6px 12px rgb(0, 0, 0 / 18%);
|
||||
background-clip: padding-box;
|
||||
|
||||
li {
|
||||
width: 100%;
|
||||
display: list-item;
|
||||
margin: 0;
|
||||
text-align: -webkit-match-parent;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
clear: both;
|
||||
font-weight: normal;
|
||||
line-height: 1.45;
|
||||
white-space: nowrap;
|
||||
color: #eee;
|
||||
padding: 5px 20px;
|
||||
border-top: 1px solid transparent;
|
||||
border-bottom: 1px solid transparent;
|
||||
text-decoration: none;
|
||||
|
||||
&:active,
|
||||
&:focus,
|
||||
&:hover {
|
||||
background-color: rgb(82, 82, 82);
|
||||
}
|
||||
}
|
||||
|
||||
a.selected {
|
||||
background: #74b936 !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
116
src/components/menus/EditThemeMenu.vue
Normal file
@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<div class="edit-theme side-bar__panel side-bar__panel--menu">
|
||||
<div class="side-bar__info">
|
||||
<div class="menu-entry menu-entry--info flex flex--row flex--align-center">
|
||||
<span v-if="currEditTheme==='custom'">
|
||||
下面的自定义主题样式可编辑,可参考其他主题样式填入自己喜欢的编辑样式。<br>
|
||||
主题class为:edit-theme--custom
|
||||
</span>
|
||||
<span v-else>
|
||||
下面的主题样式不可编辑。
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="side-bar__content">
|
||||
<template v-if="currEditTheme === 'default'">
|
||||
默认主题无额外样式,请选择其他主题。
|
||||
</template>
|
||||
<template v-else>
|
||||
<code-editor v-for="(value, index) in styleEles" :key="index"
|
||||
v-if="value.id === `edit-theme-${currEditTheme}`" lang="css" :value="value.innerHTML"
|
||||
:disabled="value.id!=='edit-theme-custom'" @changed="changeText" scrollClass="side-bar__inner"></code-editor>
|
||||
</template>
|
||||
</div>
|
||||
<div class="flex flex--row flex--end" v-if="currEditTheme==='custom'">
|
||||
<button class="edit-theme__button button" @click="saveStyleText">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import MenuEntry from './common/MenuEntry';
|
||||
import CodeEditor from '../CodeEditor';
|
||||
import store from '../../store';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MenuEntry,
|
||||
CodeEditor,
|
||||
},
|
||||
data: () => ({
|
||||
themeStyleText: '',
|
||||
styleEles: [],
|
||||
}),
|
||||
computed: {
|
||||
...mapGetters('theme', [
|
||||
'currEditTheme',
|
||||
]),
|
||||
},
|
||||
methods: {
|
||||
saveStyleText() {
|
||||
const typeEle = this.findByTheme(this.currEditTheme);
|
||||
if (!typeEle || !this.themeStyleText) {
|
||||
return;
|
||||
}
|
||||
typeEle.innerHTML = this.themeStyleText;
|
||||
store.dispatch('theme/setCustomEditThemeStyle', this.themeStyleText);
|
||||
store.dispatch('notification/info', '保存自定义主题样式成功!');
|
||||
},
|
||||
findByTheme(theme) {
|
||||
const findEles = this.styleEles.filter(it => it.id === `edit-theme-${theme}`);
|
||||
return findEles.length ? findEles[0] : null;
|
||||
},
|
||||
changeText(text) {
|
||||
this.themeStyleText = text;
|
||||
},
|
||||
close() {
|
||||
store.dispatch('data/setSideBarPanel', 'menu');
|
||||
},
|
||||
initStyle(theme) {
|
||||
if (theme === 'default') {
|
||||
return;
|
||||
}
|
||||
const value = theme || this.currEditTheme;
|
||||
if (this.findByTheme(value)) {
|
||||
return;
|
||||
}
|
||||
const styleId = `edit-theme-${value}`;
|
||||
const styleEle = document.getElementById(styleId);
|
||||
if (!styleEle) {
|
||||
setTimeout(() => this.initStyle(value), 1000);
|
||||
return;
|
||||
}
|
||||
this.styleEles.push(styleEle);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
currEditTheme: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
this.initStyle(val);
|
||||
},
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.initStyle();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.side-bar__panel--menu {
|
||||
.side-bar__content {
|
||||
.code-editor {
|
||||
min-height: 400px !important;
|
||||
max-height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-theme__button {
|
||||
font-size: 14px;
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -8,7 +8,7 @@
|
||||
</option>
|
||||
</select>
|
||||
</p>
|
||||
<p v-if="!historyContext">同步 <b>{{currentFileName}}</b> 以启用修订历史 或者 <a href="javascript:void(0)" @click="signin">登录 Gitee</a> 以同步您的主文档空间。</p>
|
||||
<p v-if="!historyContext">同步 <b>{{currentFileName}}</b> 以启用修订历史 或者 <a href="javascript:void(0)" @click="signin">登录 Gitee</a> 或 <a href="javascript:void(0)" @click="signinWithGithub">登录 GitHub</a> 以同步您的主文档空间。</p>
|
||||
<p v-else-if="loading">历史版本加载中…</p>
|
||||
<p v-else-if="!revisionsWithSpacer.length"><b>{{currentFileName}}</b> 没有历史版本.</p>
|
||||
<div class="menu-entry menu-entry--info flex flex--row flex--align-center" v-else>
|
||||
@ -33,6 +33,7 @@
|
||||
<div class="revision__header flex flex--column">
|
||||
<user-name :user-id="revision.sub"></user-name>
|
||||
<div class="revision__created">{{revision.created | formatTime}}</div>
|
||||
<div class="revision__msg">{{revision.message}}</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
@ -54,6 +55,7 @@ import EditorClassApplier from '../common/EditorClassApplier';
|
||||
import PreviewClassApplier from '../common/PreviewClassApplier';
|
||||
import utils from '../../services/utils';
|
||||
import giteeHelper from '../../services/providers/helpers/giteeHelper';
|
||||
import githubHelper from '../../services/providers/helpers/githubHelper';
|
||||
import syncSvc from '../../services/syncSvc';
|
||||
import store from '../../store';
|
||||
import badgeSvc from '../../services/badgeSvc';
|
||||
@ -167,6 +169,16 @@ export default {
|
||||
async signin() {
|
||||
try {
|
||||
await giteeHelper.signin();
|
||||
await syncSvc.afterSignIn();
|
||||
syncSvc.requestSync();
|
||||
} catch (e) {
|
||||
// Cancel
|
||||
}
|
||||
},
|
||||
async signinWithGithub() {
|
||||
try {
|
||||
await githubHelper.signin();
|
||||
await syncSvc.afterSignIn();
|
||||
syncSvc.requestSync();
|
||||
} catch (e) {
|
||||
// Cancel
|
||||
@ -412,6 +424,14 @@ export default {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.revision__msg {
|
||||
font-size: 0.75em;
|
||||
opacity: 0.6;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.layout--revision {
|
||||
.cledit-section *,
|
||||
.cl-preview-section * {
|
||||
|
@ -14,6 +14,9 @@
|
||||
<span v-if="currentWorkspace.providerId === 'giteeAppData'">
|
||||
<b>{{currentWorkspace.name}}</b> 与您的 Gitee 默认文档空间仓库同步。
|
||||
</span>
|
||||
<span v-else-if="currentWorkspace.providerId === 'githubAppData'">
|
||||
<b>{{currentWorkspace.name}}</b> 与您的 GitHub 默认文档空间仓库同步。
|
||||
</span>
|
||||
<span v-else-if="currentWorkspace.providerId === 'googleDriveWorkspace'">
|
||||
<b>{{currentWorkspace.name}}</b> 与 <a :href="workspaceLocationUrl" target="_blank">Google Drive 文件夹</a>同步。
|
||||
</span>
|
||||
@ -21,10 +24,10 @@
|
||||
<b>{{currentWorkspace.name}}</b> 与 <a :href="workspaceLocationUrl" target="_blank">CouchDB 数据库</a>同步。
|
||||
</span>
|
||||
<span v-else-if="currentWorkspace.providerId === 'githubWorkspace'">
|
||||
<b>{{currentWorkspace.name}}</b> 与 <a :href="workspaceLocationUrl" target="_blank">GitHub repo</a> 同步。
|
||||
<b>{{currentWorkspace.name}}</b> 与 <a :href="workspaceLocationUrl" target="_blank">GitHub 仓库</a> 同步。
|
||||
</span>
|
||||
<span v-else-if="currentWorkspace.providerId === 'giteeWorkspace'">
|
||||
<b>{{currentWorkspace.name}}</b> 与 <a :href="workspaceLocationUrl" target="_blank">Gitee repo</a> 同步。
|
||||
<b>{{currentWorkspace.name}}</b> 与 <a :href="workspaceLocationUrl" target="_blank">Gitee 仓库</a> 同步。
|
||||
</span>
|
||||
<span v-else-if="currentWorkspace.providerId === 'gitlabWorkspace'">
|
||||
<b>{{currentWorkspace.name}}</b> 与 <a :href="workspaceLocationUrl" target="_blank">GitLab 项目</a>同步。
|
||||
@ -45,6 +48,11 @@
|
||||
<div>使用 Gitee 登录</div>
|
||||
<span>同步您的主文档空间并解锁功能。</span>
|
||||
</menu-entry>
|
||||
<menu-entry v-if="!loginToken" @click.native="signinWithGithub">
|
||||
<icon-login slot="icon"></icon-login>
|
||||
<div>使用 GitHub 登录</div>
|
||||
<span>同步您的主文档空间并解锁功能。</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="setPanel('workspaces')">
|
||||
<icon-database slot="icon"></icon-database>
|
||||
<div><div class="menu-entry__label menu-entry__label--count" v-if="workspaceCount">{{workspaceCount}}</div> 文档空间</div>
|
||||
@ -105,6 +113,16 @@
|
||||
<div><div class="menu-entry__label menu-entry__label--count">{{templateCount}}</div> 模板</div>
|
||||
<span>为您的导出配置 Handlebars 模板。</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="setPanel('editTheme')">
|
||||
<icon-select-theme slot="icon"></icon-select-theme>
|
||||
编辑区主题
|
||||
<span>编辑区主题样式(自定义主题可编辑)。</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="setPanel('previewTheme')">
|
||||
<icon-select-theme slot="icon"></icon-select-theme>
|
||||
预览区主题
|
||||
<span>预览区主题样式(自定义主题可编辑)。</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="settings">
|
||||
<icon-settings slot="icon"></icon-settings>
|
||||
<div>配置</div>
|
||||
@ -132,6 +150,7 @@ import MenuEntry from './common/MenuEntry';
|
||||
import providerRegistry from '../../services/providers/common/providerRegistry';
|
||||
import UserImage from '../UserImage';
|
||||
import giteeHelper from '../../services/providers/helpers/giteeHelper';
|
||||
import githubHelper from '../../services/providers/helpers/githubHelper';
|
||||
import syncSvc from '../../services/syncSvc';
|
||||
import userSvc from '../../services/userSvc';
|
||||
import store from '../../store';
|
||||
@ -184,6 +203,16 @@ export default {
|
||||
async signin() {
|
||||
try {
|
||||
await giteeHelper.signin();
|
||||
await syncSvc.afterSignIn();
|
||||
syncSvc.requestSync();
|
||||
} catch (e) {
|
||||
// Cancel
|
||||
}
|
||||
},
|
||||
async signinWithGithub() {
|
||||
try {
|
||||
await githubHelper.signin();
|
||||
await syncSvc.afterSignIn();
|
||||
syncSvc.requestSync();
|
||||
} catch (e) {
|
||||
// Cancel
|
||||
|
116
src/components/menus/PreviewThemeMenu.vue
Normal file
@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<div class="preview-theme side-bar__panel side-bar__panel--menu">
|
||||
<div class="side-bar__info">
|
||||
<div class="menu-entry menu-entry--info flex flex--row flex--align-center">
|
||||
<span v-if="currPreviewTheme==='custom'">
|
||||
下面的自定义主题样式可编辑,可参考其他主题样式填入自己喜欢的预览样式。<br>
|
||||
主题class为:preview-theme--custom
|
||||
</span>
|
||||
<span v-else>
|
||||
下面的主题样式不可编辑。
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="side-bar__content">
|
||||
<template v-if="currPreviewTheme === 'default'">
|
||||
默认主题无额外样式,请选择其他主题。
|
||||
</template>
|
||||
<template v-else>
|
||||
<code-editor v-for="(value, index) in styleEles" :key="index"
|
||||
v-if="value.id === `preview-theme-${currPreviewTheme}`" lang="css" :value="value.innerHTML"
|
||||
:disabled="value.id!=='preview-theme-custom'" @changed="changeText" scrollClass="side-bar__inner"></code-editor>
|
||||
</template>
|
||||
</div>
|
||||
<div class="flex flex--row flex--end" v-if="currPreviewTheme==='custom'">
|
||||
<button class="preview-theme__button button" @click="saveStyleText">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import MenuEntry from './common/MenuEntry';
|
||||
import CodeEditor from '../CodeEditor';
|
||||
import store from '../../store';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MenuEntry,
|
||||
CodeEditor,
|
||||
},
|
||||
data: () => ({
|
||||
themeStyleText: '',
|
||||
styleEles: [],
|
||||
}),
|
||||
computed: {
|
||||
...mapGetters('theme', [
|
||||
'currPreviewTheme',
|
||||
]),
|
||||
},
|
||||
methods: {
|
||||
saveStyleText() {
|
||||
const typeEle = this.findByTheme(this.currPreviewTheme);
|
||||
if (!typeEle || !this.themeStyleText) {
|
||||
return;
|
||||
}
|
||||
typeEle.innerHTML = this.themeStyleText;
|
||||
store.dispatch('theme/setCustomPreviewThemeStyle', this.themeStyleText);
|
||||
store.dispatch('notification/info', '保存自定义主题样式成功!');
|
||||
},
|
||||
findByTheme(theme) {
|
||||
const findEles = this.styleEles.filter(it => it.id === `preview-theme-${theme}`);
|
||||
return findEles.length ? findEles[0] : null;
|
||||
},
|
||||
changeText(text) {
|
||||
this.themeStyleText = text;
|
||||
},
|
||||
close() {
|
||||
store.dispatch('data/setSideBarPanel', 'menu');
|
||||
},
|
||||
initStyle(theme) {
|
||||
if (theme === 'default') {
|
||||
return;
|
||||
}
|
||||
const value = theme || this.currPreviewTheme;
|
||||
if (this.findByTheme(value)) {
|
||||
return;
|
||||
}
|
||||
const styleId = `preview-theme-${value}`;
|
||||
const styleEle = document.getElementById(styleId);
|
||||
if (!styleEle) {
|
||||
setTimeout(() => this.initStyle(value), 1000);
|
||||
return;
|
||||
}
|
||||
this.styleEles.push(styleEle);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
currPreviewTheme: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
this.initStyle(val);
|
||||
},
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.initStyle();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.side-bar__panel--menu {
|
||||
.side-bar__content {
|
||||
.code-editor {
|
||||
min-height: 400px !important;
|
||||
max-height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-theme__button {
|
||||
font-size: 14px;
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -43,7 +43,7 @@
|
||||
<div v-for="token in githubTokens" :key="token.sub">
|
||||
<menu-entry @click.native="publishGist(token)">
|
||||
<icon-provider slot="icon" provider-id="gist"></icon-provider>
|
||||
<div>发布到 Gist</div>
|
||||
<div>发布到 GitHubGist</div>
|
||||
<span>{{token.name}}</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="publishGithub(token)">
|
||||
@ -53,6 +53,11 @@
|
||||
</menu-entry>
|
||||
</div>
|
||||
<div v-for="token in giteeTokens" :key="token.sub">
|
||||
<menu-entry @click.native="publishGiteeGist(token)">
|
||||
<icon-provider slot="icon" provider-id="giteegist"></icon-provider>
|
||||
<div>发布到 GiteeGist</div>
|
||||
<span>{{token.name}}</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="publishGitee(token)">
|
||||
<icon-provider slot="icon" provider-id="gitee"></icon-provider>
|
||||
<div>发布到 Gitee</div>
|
||||
@ -258,8 +263,8 @@ export default {
|
||||
},
|
||||
async addGitlabAccount() {
|
||||
try {
|
||||
const { serverUrl, applicationId } = await store.dispatch('modal/open', { type: 'gitlabAccount' });
|
||||
await gitlabHelper.addAccount(serverUrl, applicationId);
|
||||
const { serverUrl, applicationId, applicationSecret } = await store.dispatch('modal/open', { type: 'gitlabAccount' });
|
||||
await gitlabHelper.addAccount(serverUrl, applicationId, applicationSecret);
|
||||
} catch (e) { /* cancel */ }
|
||||
},
|
||||
async addGiteaAccount() {
|
||||
@ -289,8 +294,9 @@ export default {
|
||||
publishBloggerPage: publishModalOpener('bloggerPagePublish', 'publishToBloggerPage'),
|
||||
publishDropbox: publishModalOpener('dropboxPublish', 'publishToDropbox'),
|
||||
publishGithub: publishModalOpener('githubPublish', 'publishToGithub'),
|
||||
publishGitee: publishModalOpener('giteePublish', 'publishToGitee'),
|
||||
publishGist: publishModalOpener('gistPublish', 'publishToGist'),
|
||||
publishGitee: publishModalOpener('giteePublish', 'publishToGitee'),
|
||||
publishGiteeGist: publishModalOpener('giteeGistPublish', 'publishGiteeGist'),
|
||||
publishGitlab: publishModalOpener('gitlabPublish', 'publishToGitlab'),
|
||||
publishGitea: publishModalOpener('giteaPublish', 'publishToGitea'),
|
||||
publishGoogleDrive: publishModalOpener('googleDrivePublish', 'publishToGoogleDrive'),
|
||||
|
@ -46,7 +46,7 @@
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="saveGist(token)">
|
||||
<icon-provider slot="icon" provider-id="gist"></icon-provider>
|
||||
<div>在Gist上保存</div>
|
||||
<div>在GitHubGist上保存</div>
|
||||
<span>{{token.name}}</span>
|
||||
</menu-entry>
|
||||
</div>
|
||||
@ -61,6 +61,11 @@
|
||||
<div>在Gitee上保存</div>
|
||||
<span>{{token.name}}</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="saveGiteeGist(token)">
|
||||
<icon-provider slot="icon" provider-id="giteegist"></icon-provider>
|
||||
<div>在GiteeGist上保存</div>
|
||||
<span>{{token.name}}</span>
|
||||
</menu-entry>
|
||||
</div>
|
||||
<div v-for="token in gitlabTokens" :key="token.sub">
|
||||
<menu-entry @click.native="openGitlab(token)">
|
||||
@ -234,8 +239,8 @@ export default {
|
||||
},
|
||||
async addGitlabAccount() {
|
||||
try {
|
||||
const { serverUrl, applicationId } = await store.dispatch('modal/open', { type: 'gitlabAccount' });
|
||||
await gitlabHelper.addAccount(serverUrl, applicationId);
|
||||
const { serverUrl, applicationId, applicationSecret } = await store.dispatch('modal/open', { type: 'gitlabAccount' });
|
||||
await gitlabHelper.addAccount(serverUrl, applicationId, applicationSecret);
|
||||
} catch (e) { /* cancel */ }
|
||||
},
|
||||
async addGiteaAccount() {
|
||||
@ -330,6 +335,12 @@ export default {
|
||||
badgeSvc.addBadge('saveOnGist');
|
||||
} catch (e) { /* cancel */ }
|
||||
},
|
||||
async saveGiteeGist(token) {
|
||||
try {
|
||||
await openSyncModal(token, 'giteeGistSync');
|
||||
badgeSvc.addBadge('saveOnGiteeGist');
|
||||
} catch (e) { /* cancel */ }
|
||||
},
|
||||
async openGitlab(token) {
|
||||
try {
|
||||
const syncLocation = await store.dispatch('modal/open', {
|
||||
|
@ -8,7 +8,8 @@
|
||||
<hr>
|
||||
<div class="workspace" v-for="(workspace, id) in workspacesById" :key="id">
|
||||
<menu-entry :href="workspace.url" target="_blank">
|
||||
<icon-provider slot="icon" :provider-id="workspace.providerId"></icon-provider>
|
||||
<icon-provider v-if="id === 'main' && !workspace.sub" slot="icon" :provider-id="'stackedit'"></icon-provider>
|
||||
<icon-provider v-else slot="icon" :provider-id="workspace.providerId"></icon-provider>
|
||||
<div class="workspace__name"><div class="menu-entry__label" v-if="currentWorkspace === workspace">当前</div>{{workspace.name}}</div>
|
||||
</menu-entry>
|
||||
</div>
|
||||
@ -85,8 +86,8 @@ export default {
|
||||
},
|
||||
async addGitlabWorkspace() {
|
||||
try {
|
||||
const { serverUrl, applicationId } = await store.dispatch('modal/open', { type: 'gitlabAccount' });
|
||||
const token = await gitlabHelper.addAccount(serverUrl, applicationId);
|
||||
const { serverUrl, applicationId, applicationSecret } = await store.dispatch('modal/open', { type: 'gitlabAccount' });
|
||||
const token = await gitlabHelper.addAccount(serverUrl, applicationId, applicationSecret);
|
||||
store.dispatch('modal/open', {
|
||||
type: 'gitlabWorkspace',
|
||||
token,
|
||||
|
@ -248,8 +248,8 @@ export default {
|
||||
},
|
||||
async addGitlabAccount() {
|
||||
try {
|
||||
const { serverUrl, applicationId } = await store.dispatch('modal/open', { type: 'gitlabAccount' });
|
||||
await gitlabHelper.addAccount(serverUrl, applicationId);
|
||||
const { serverUrl, applicationId, applicationSecret } = await store.dispatch('modal/open', { type: 'gitlabAccount' });
|
||||
await gitlabHelper.addAccount(serverUrl, applicationId, applicationSecret);
|
||||
} catch (e) { /* cancel */ }
|
||||
},
|
||||
async addGiteaAccount() {
|
||||
|
131
src/components/modals/ChatGptModal.vue
Normal file
@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<modal-inner class="modal__inner-1--chatgpt" aria-label="chatgpt">
|
||||
<div class="modal__content">
|
||||
<div class="modal__image">
|
||||
<icon-chat-gpt></icon-chat-gpt>
|
||||
</div>
|
||||
<p><b>ChatGPT内容生成</b><br>生成时长受ChatGPT服务响应与网络响应时长影响,时间可能较长</p>
|
||||
<form-entry label="生成内容要求详细描述" error="content">
|
||||
<textarea slot="field" class="text-input" type="text" placeholder="输入内容(支持换行)" v-model.trim="content" :disabled="generating"></textarea>
|
||||
<div class="form-entry__info">
|
||||
使用 <a href="https://api35.pxj123.cn/" target="_blank">api35.pxj123.cn</a> 的免费接口生成内容,AI模型是:GPT-3.5 Turbo。
|
||||
</div>
|
||||
</form-entry>
|
||||
<div class="modal__result">
|
||||
<pre class="result_pre" v-if="generating && !result">(等待生成中...)</pre>
|
||||
<pre class="result_pre" v-else v-text="result"></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal__button-bar">
|
||||
<button class="button" @click="reject()">{{ generating ? '停止' : '关闭' }}</button>
|
||||
<button class="button button--resolve" @click="generate" v-if="!generating && !!content">{{ !!result ? '重新生成' : '开始生成' }}</button>
|
||||
<button class="button button--resolve" @click="resolve" v-if="!generating && !!result">确认插入</button>
|
||||
</div>
|
||||
</modal-inner>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import modalTemplate from './common/modalTemplate';
|
||||
import chatGptSvc from '../../services/chatGptSvc';
|
||||
import store from '../../store';
|
||||
|
||||
export default modalTemplate({
|
||||
data: () => ({
|
||||
generating: false,
|
||||
content: '',
|
||||
result: '',
|
||||
xhr: null,
|
||||
}),
|
||||
methods: {
|
||||
resolve(evt) {
|
||||
evt.preventDefault();
|
||||
const { callback } = this.config;
|
||||
this.config.resolve();
|
||||
callback(this.result);
|
||||
},
|
||||
process({ done, content, error }) {
|
||||
if (done) {
|
||||
this.generating = false;
|
||||
// 已结束
|
||||
} else if (content) {
|
||||
this.result = this.result + content;
|
||||
const container = document.querySelector('.result_pre');
|
||||
container.scrollTo(0, container.scrollHeight); // 滚动到最底部
|
||||
} else if (error) {
|
||||
this.generating = false;
|
||||
}
|
||||
},
|
||||
generate() {
|
||||
this.generating = true;
|
||||
this.result = '';
|
||||
try {
|
||||
this.xhr = chatGptSvc.chat({
|
||||
content: `${this.content}\n(使用Markdown方式输出结果)`,
|
||||
}, this.process);
|
||||
} catch (err) {
|
||||
this.generating = false;
|
||||
store.dispatch('notification/error', err);
|
||||
}
|
||||
},
|
||||
reject() {
|
||||
if (this.generating) {
|
||||
if (this.xhr) {
|
||||
this.xhr.abort();
|
||||
this.generating = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
const { callback } = this.config;
|
||||
this.config.reject();
|
||||
callback(null);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
const script = document.createElement('script');
|
||||
script.src = `https://api35.pxj123.cn/js/chat.js?t=${new Date().getTime()}`;
|
||||
script.onload = () => {
|
||||
/* eslint-disable */
|
||||
console.log('加载外部chatgpt的js成功!');
|
||||
};
|
||||
this.$el.appendChild(script);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../styles/variables.scss';
|
||||
|
||||
.modal__inner-1.modal__inner-1--chatgpt {
|
||||
max-width: 560px;
|
||||
|
||||
.result_pre {
|
||||
font-size: 0.9em;
|
||||
font-variant-ligatures: no-common-ligatures;
|
||||
line-height: 1.25;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
word-wrap: break-word;
|
||||
height: 300px;
|
||||
border: 1px solid rgb(126, 126, 126);
|
||||
border-radius: $border-radius-base;
|
||||
padding: 10px;
|
||||
overflow-y: scroll; /* 开启垂直滚动条 */
|
||||
}
|
||||
|
||||
.result_pre::-webkit-scrollbar {
|
||||
display: none; /* 隐藏滚动条 */
|
||||
}
|
||||
|
||||
.result_pre.scroll-bottom {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
.config-warning {
|
||||
color: #f00;
|
||||
}
|
||||
|
||||
.text-input {
|
||||
min-height: 60px;
|
||||
}
|
||||
}
|
||||
</style>
|
43
src/components/modals/CommitMessageModal.vue
Normal file
@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<modal-inner aria-label="提交信息">
|
||||
<p>自定义 <b>{{ config.name }}</b> 提交信息。</p>
|
||||
<div class="modal__content">
|
||||
<div class="form-entry">
|
||||
<label class="form-entry__label">提交信息</label>
|
||||
<div class="form-entry__field">
|
||||
<input slot="field" class="textfield" placeholder="提交信息非必填" type="text" v-model.trim="commitMessage" @keydown.enter="resolve()">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal__button-bar">
|
||||
<button class="button" @click="config.reject()">取消</button>
|
||||
<button class="button button--resolve" @click="resolve()">确认</button>
|
||||
</div>
|
||||
</modal-inner>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import ModalInner from './common/ModalInner';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ModalInner,
|
||||
},
|
||||
data: () => ({
|
||||
commitMessage: '',
|
||||
}),
|
||||
computed: {
|
||||
...mapGetters('modal', [
|
||||
'config',
|
||||
]),
|
||||
},
|
||||
methods: {
|
||||
resolve() {
|
||||
this.config.resolve({
|
||||
commitMessage: this.commitMessage,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
@ -1,18 +1,35 @@
|
||||
<template>
|
||||
<modal-inner aria-label="插入图像">
|
||||
<div class="modal__content">
|
||||
<p v-if="hasFile">
|
||||
<span v-if="uploading">粘贴/拖拽图片上传中...</span>
|
||||
<span v-if="!this.uploading && url">
|
||||
<img :src="url">
|
||||
</span>
|
||||
<span v-if="!this.uploading && !url">图片上传失败,如未添加图床请先添加并选择,之后关闭窗口再重试!</span>
|
||||
</p>
|
||||
<p v-if="!hasFile">请为您的图像提供<b> url </b>。</p>
|
||||
<form-entry v-if="!hasFile" label="URL" error="url">
|
||||
<p>请为您的图像提供<b> url </b>。<span v-if="uploading">(图片上传中...)</span></p>
|
||||
<form-entry label="URL" error="url">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="url" @keydown.enter="resolve">
|
||||
</form-entry>
|
||||
<p>添加并选择图床后可实现粘贴/拖拽自动上传图片</p>
|
||||
</div>
|
||||
<div class="modal__button-bar">
|
||||
<input class="hidden-file" id="upload-image-file-input" type="file" accept="image/*" :disabled="uploading" @change="uploadImage">
|
||||
<label for="upload-image-file-input"><a class="button">上传图片</a></label>
|
||||
<button class="button" @click="reject()">取消</button>
|
||||
<button class="button button--resolve" @click="resolve" :disabled="uploading">确认</button>
|
||||
</div>
|
||||
<div>
|
||||
<hr />
|
||||
<p>添加并选择图床后可在编辑区中粘贴/拖拽图片自动上传</p>
|
||||
|
||||
<menu-entry @click.native="checkedImgDest(path)" v-for="path in workspaceImgPath" :key="path">
|
||||
<icon-check-circle v-if="checkedStorage.sub === path" slot="icon"></icon-check-circle>
|
||||
<icon-check-circle-un v-if="checkedStorage.sub !== path" slot="icon"></icon-check-circle-un>
|
||||
<menu-item>
|
||||
<icon-provider slot="icon" :provider-id="currentWorkspace.providerId"></icon-provider>
|
||||
<div>
|
||||
当前文档空间图片路径
|
||||
<button class="menu-item__button button" @click.stop="removeByPath(path)" v-title="'删除'">
|
||||
<icon-delete></icon-delete>
|
||||
</button>
|
||||
</div>
|
||||
<span>路径:{{path}}</span>
|
||||
</menu-item>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="checkedImgDest(token.sub, token.providerId)" v-for="token in imageTokens" :key="token.sub">
|
||||
<icon-check-circle v-if="checkedStorage.sub === token.sub" slot="icon"></icon-check-circle>
|
||||
<icon-check-circle-un v-if="checkedStorage.sub !== token.sub" slot="icon"></icon-check-circle-un>
|
||||
@ -30,9 +47,9 @@
|
||||
<span class="line-entry" v-if="token.params">自定义Form参数:{{token.params}}</span>
|
||||
</menu-item>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="checkedImgDest(tokenStorage.sid, tokenStorage.providerId)" v-for="tokenStorage in tokensImgStorages" :key="tokenStorage.sid">
|
||||
<icon-check-circle v-if="checkedStorage.sub === tokenStorage.sid" slot="icon"></icon-check-circle>
|
||||
<icon-check-circle-un v-if="checkedStorage.sub !== tokenStorage.sid" slot="icon"></icon-check-circle-un>
|
||||
<menu-entry @click.native="checkedImgDest(tokenStorage.token.sub, tokenStorage.providerId, tokenStorage.sid)" v-for="tokenStorage in tokensImgStorages" :key="tokenStorage.sid">
|
||||
<icon-check-circle v-if="checkedStorage.sid === tokenStorage.sid" slot="icon"></icon-check-circle>
|
||||
<icon-check-circle-un v-if="checkedStorage.sid !== tokenStorage.sid" slot="icon"></icon-check-circle-un>
|
||||
<menu-item>
|
||||
<icon-provider slot="icon" :provider-id="tokenStorage.providerId"></icon-provider>
|
||||
<div>{{tokenStorage.providerName}}
|
||||
@ -43,12 +60,10 @@
|
||||
<span> {{tokenStorage.uname}}, 仓库URL: {{tokenStorage.repoUrl}}, 路径: {{tokenStorage.path}}, 分支: {{tokenStorage.branch}}</span>
|
||||
</menu-item>
|
||||
</menu-entry>
|
||||
</div>
|
||||
<div class="modal__button-bar">
|
||||
<button class="button" @click="reject()">取消</button>
|
||||
<button class="button button--resolve" @click="resolve" :disabled="uploading">确认</button>
|
||||
</div>
|
||||
<div>
|
||||
<menu-entry @click.native="addWorkspaceImgPath">
|
||||
<icon-provider slot="icon" :provider-id="currentWorkspace.providerId"></icon-provider>
|
||||
<span>添加当前文档空间图片路径</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="addSmmsAccount">
|
||||
<icon-provider slot="icon" provider-id="smms"></icon-provider>
|
||||
<span>添加SM.MS图床账号</span>
|
||||
@ -70,6 +85,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import modalTemplate from './common/modalTemplate';
|
||||
import MenuEntry from '../menus/common/MenuEntry';
|
||||
import MenuItem from '../menus/common/MenuItem';
|
||||
@ -79,6 +95,7 @@ import giteaHelper from '../../services/providers/helpers/giteaHelper';
|
||||
import githubHelper from '../../services/providers/helpers/githubHelper';
|
||||
import customHelper from '../../services/providers/helpers/customHelper';
|
||||
import utils from '../../services/utils';
|
||||
import imageSvc from '../../services/imageSvc';
|
||||
|
||||
export default modalTemplate({
|
||||
components: {
|
||||
@ -86,14 +103,24 @@ export default modalTemplate({
|
||||
MenuItem,
|
||||
},
|
||||
data: () => ({
|
||||
hasFile: false,
|
||||
uploading: false,
|
||||
url: '',
|
||||
}),
|
||||
computed: {
|
||||
...mapGetters('workspace', [
|
||||
'currentWorkspace',
|
||||
'currentWorkspaceIsGit',
|
||||
]),
|
||||
checkedStorage() {
|
||||
return store.getters['img/getCheckedStorage'];
|
||||
},
|
||||
workspaceImgPath() {
|
||||
if (!this.currentWorkspaceIsGit) {
|
||||
return [];
|
||||
}
|
||||
const workspaceImgPath = store.getters['img/getWorkspaceImgPath'];
|
||||
return Object.keys(workspaceImgPath || {});
|
||||
},
|
||||
imageTokens() {
|
||||
return [
|
||||
...Object.values(store.getters['data/smmsTokensBySub']).map(token => ({
|
||||
@ -137,92 +164,12 @@ export default modalTemplate({
|
||||
uname: it.token.name,
|
||||
providerId: it.providerId,
|
||||
providerName: it.providerName,
|
||||
repoUrl: it.providerId === 'gitea' ? `${it.serverUrl}/${storage.repoUri}` : `${storage.owner}/${storage.repo}`,
|
||||
repoUrl: it.providerId === 'gitea' ? `${it.token.serverUrl}/${storage.repoUri}` : `${storage.owner}/${storage.repo}`,
|
||||
}));
|
||||
});
|
||||
return imgStorages;
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
this.hasFile = false;
|
||||
const imgFile = store.getters['img/getImg'];
|
||||
if (imgFile) {
|
||||
this.hasFile = true;
|
||||
this.uploading = true;
|
||||
try {
|
||||
// 操作图片上传
|
||||
// 找到对应的provider 目前仅smms
|
||||
const currStorage = this.checkedStorage;
|
||||
if (!currStorage) {
|
||||
store.dispatch('notification/info', '暂无已选择的图床,未自动上传图片!请选择图床后重新粘贴/拖拽图片!');
|
||||
return;
|
||||
}
|
||||
if (currStorage.provider === 'smms' || currStorage.provider === 'custom') {
|
||||
const filterTokens = this.imageTokens.filter(it => it.sub === currStorage.sub);
|
||||
if (!filterTokens.length) {
|
||||
store.dispatch('notification/info', '图床已失效,未自动上传图片!请选择图床后重新粘贴/拖拽图片!');
|
||||
return;
|
||||
}
|
||||
const token = filterTokens[0];
|
||||
const helper = currStorage.provider === 'smms' ? smmsHelper : customHelper;
|
||||
try {
|
||||
this.url = await helper.uploadFile({
|
||||
token,
|
||||
file: imgFile,
|
||||
});
|
||||
} catch (err) {
|
||||
store.dispatch('notification/error', err);
|
||||
}
|
||||
} else if (currStorage.provider === 'gitea' || currStorage.provider === 'github') {
|
||||
const filterTokenStorages = this.tokensImgStorages
|
||||
.filter(it => it.sid === currStorage.sub);
|
||||
if (!filterTokenStorages.length) {
|
||||
store.dispatch('notification/info', 'Gitea图床已失效,未自动上传图片!请选择图床后重新粘贴/拖拽图片!');
|
||||
return;
|
||||
}
|
||||
const tokenStorage = filterTokenStorages[0];
|
||||
const time = new Date();
|
||||
const date = time.getDate();
|
||||
const month = time.getMonth() + 1;
|
||||
const year = time.getFullYear();
|
||||
let path = tokenStorage.path.replace('{YYYY}', year)
|
||||
.replace('{MM}', `0${month}`.slice(-2)).replace('{DD}', `0${date}`.slice(-2));
|
||||
path = `${path}${path.endsWith('/') ? '' : '/'}${utils.uid()}.${imgFile.type.split('/')[1]}`;
|
||||
try {
|
||||
if (currStorage.provider === 'gitea') {
|
||||
const result = await giteaHelper.uploadFile({
|
||||
token: tokenStorage.token,
|
||||
projectId: tokenStorage.repoUri,
|
||||
branch: tokenStorage.branch,
|
||||
path,
|
||||
content: imgFile,
|
||||
isFile: true,
|
||||
});
|
||||
this.url = result.content.download_url;
|
||||
} else if (currStorage.provider === 'github') {
|
||||
const result = await githubHelper.uploadFile({
|
||||
token: tokenStorage.token,
|
||||
owner: tokenStorage.owner,
|
||||
repo: tokenStorage.repo,
|
||||
branch: tokenStorage.branch,
|
||||
path,
|
||||
content: imgFile,
|
||||
isFile: true,
|
||||
});
|
||||
this.url = result.content.download_url;
|
||||
}
|
||||
} catch (err) {
|
||||
store.dispatch('notification/error', err);
|
||||
}
|
||||
} else {
|
||||
store.dispatch('notification/info', '暂无已选择的图床,未自动上传图片!请选择图床后重新粘贴/拖拽图片!');
|
||||
}
|
||||
} finally {
|
||||
store.dispatch('img/clearImg');
|
||||
this.uploading = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resolve(evt) {
|
||||
evt.preventDefault(); // Fixes https://github.com/mafgwo/stackedit/issues/1503
|
||||
@ -239,6 +186,27 @@ export default modalTemplate({
|
||||
this.config.reject();
|
||||
callback(null);
|
||||
},
|
||||
async uploadImage(evt) {
|
||||
if (!evt.target.files || !evt.target.files.length) {
|
||||
return;
|
||||
}
|
||||
const imgFile = evt.target.files[0];
|
||||
try {
|
||||
this.uploading = true;
|
||||
const { url, error } = await imageSvc.updateImg(imgFile);
|
||||
if (error) {
|
||||
store.dispatch('notification/error', error);
|
||||
return;
|
||||
}
|
||||
this.url = url;
|
||||
} catch (err) {
|
||||
store.dispatch('notification/error', err);
|
||||
} finally {
|
||||
this.uploading = false;
|
||||
// 上传后清空
|
||||
evt.target.value = '';
|
||||
}
|
||||
},
|
||||
async remove(proivderId, item) {
|
||||
try {
|
||||
await store.dispatch('modal/open', 'imgStorageDeletion');
|
||||
@ -258,6 +226,13 @@ export default modalTemplate({
|
||||
// Cancel
|
||||
}
|
||||
},
|
||||
async removeByPath(path) {
|
||||
store.dispatch('img/removeWorkspaceImgPath', path);
|
||||
},
|
||||
async addWorkspaceImgPath() {
|
||||
const { path } = await store.dispatch('modal/open', { type: 'workspaceImgPath' });
|
||||
store.dispatch('img/addWorkspaceImgPath', path);
|
||||
},
|
||||
async addSmmsAccount() {
|
||||
const { proxyUrl, apiSecretToken } = await store.dispatch('modal/open', { type: 'smmsAccount' });
|
||||
await smmsHelper.addAccount(proxyUrl, apiSecretToken);
|
||||
@ -288,15 +263,19 @@ export default modalTemplate({
|
||||
githubHelper.updateToken(token, imgStorageInfo);
|
||||
} catch (e) { /* Cancel */ }
|
||||
},
|
||||
async checkedImgDest(sub, provider) {
|
||||
async checkedImgDest(sub, provider, sid) {
|
||||
let type = 'token';
|
||||
if (provider === 'gitea' || provider === 'github') {
|
||||
// 当前文档空间存储
|
||||
if (!provider) {
|
||||
type = 'workspace';
|
||||
} else if (provider === 'gitea' || provider === 'github') {
|
||||
type = 'tokenRepo';
|
||||
}
|
||||
store.dispatch('img/changeCheckedStorage', {
|
||||
type,
|
||||
provider,
|
||||
sub,
|
||||
sid,
|
||||
});
|
||||
// const { callback } = this.config;
|
||||
// this.config.reject();
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<modal-inner aria-label="导出到PDF">
|
||||
<div class="modal__content">
|
||||
<p>请为您的<b> pdf导出</b>选择模板。</p>
|
||||
<p>请为您的<b> pdf导出</b>选择模板。(该导出很消耗服务器资源,文档太大或图片太多可能会导出超时失败!可参考 <a href="https://gitee.com/mafgwo/stackedit/blob/master/docs/大文档导出PDF方式.md" target="_blank">大文档导出PDF方式</a> 自行导出大文档!)</p>
|
||||
<form-entry label="模板">
|
||||
<select class="textfield" slot="field" v-model="selectedTemplate" @keydown.enter="resolve()">
|
||||
<option v-for="(template, id) in allTemplatesById" :key="id" :value="id">
|
||||
|
@ -4,8 +4,8 @@
|
||||
<div class="modal__image">
|
||||
<icon-upload></icon-upload>
|
||||
</div>
|
||||
<p v-if="publishLocations.length"><b>{{currentFileName}}</b> is published to the following location(s):</p>
|
||||
<p v-else><b>{{currentFileName}}</b> is not published yet.</p>
|
||||
<p v-if="publishLocations.length"><b>{{currentFileName}}</b> 被发布到了以下位置:</p>
|
||||
<p v-else><b>{{currentFileName}}</b> 还没有被发布.</p>
|
||||
<div>
|
||||
<div class="publish-entry flex flex--column" v-for="location in publishLocations" :key="location.id">
|
||||
<div class="publish-entry__header flex flex--row flex--align-center">
|
||||
@ -26,7 +26,7 @@
|
||||
{{location.url}}
|
||||
</div>
|
||||
<div class="publish-entry__buttons flex flex--row flex--center" v-if="location.url">
|
||||
<button class="publish-entry__button button" v-clipboard="location.url" @click="info('Location URL copied to clipboard!')" v-title="'复制URL'">
|
||||
<button class="publish-entry__button button" v-clipboard="location.url" @click="info('位置URL已复制到剪贴板!')" v-title="'复制URL'">
|
||||
<icon-content-copy></icon-content-copy>
|
||||
</button>
|
||||
<a class="publish-entry__button button" v-if="location.url" :href="location.url" target="_blank" v-title="'打开位置'">
|
||||
@ -34,6 +34,19 @@
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="publish-entry__row flex flex--row flex--align-center" v-if="shareUrl(location)">
|
||||
<div class="publish-entry__url">
|
||||
分享链接: {{shareUrl(location)}}
|
||||
</div>
|
||||
<div class="publish-entry__buttons flex flex--row flex--center">
|
||||
<button class="publish-entry__button button" v-clipboard="shareUrl(location)" @click="info('分享URL已复制到剪贴板!')" v-title="'复制分享URL'">
|
||||
<icon-content-copy></icon-content-copy>
|
||||
</button>
|
||||
<a class="publish-entry__button button" :href="shareUrl(location)" target="_blank" v-title="'打开分享'">
|
||||
<icon-open-in-new></icon-open-in-new>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal__info" v-if="publishLocations.length">
|
||||
@ -75,6 +88,16 @@ export default {
|
||||
store.commit('publishLocation/deleteItem', location.id);
|
||||
badgeSvc.addBadge('removePublishLocation');
|
||||
},
|
||||
shareUrl(location) {
|
||||
if (location.providerId !== 'giteegist' && location.providerId !== 'gist') {
|
||||
return null;
|
||||
}
|
||||
if (!location.url || !location.gistId) {
|
||||
return null;
|
||||
}
|
||||
const sharePage = location.providerId === 'gist' ? 'gistshare.html' : 'share.html';
|
||||
return `${window.location.protocol}//${window.location.host}/${sharePage}?id=${location.gistId}`;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
48
src/components/modals/WorkspaceImgPathModal.vue
Normal file
@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<modal-inner aria-label="文档空间图片路径">
|
||||
<div class="modal__content">
|
||||
<div class="modal__image">
|
||||
<icon-provider :provider-id="currentWorkspace.providerId"></icon-provider>
|
||||
</div>
|
||||
<p>在当前文档空间增加图片上传路径。</p>
|
||||
<form-entry label="图片上传路径" error="path">
|
||||
<input slot="field" class="textfield" type="text" placeholder="如:/imgs/{YYYY}-{MM}-{DD}" v-model.trim="path" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
如果不提供,默认为 /imgs/{YYYY}-{MM}-{DD}。<br/>
|
||||
支持相对路径,如 ./imgs、../imgs 或 imgs 都是相对当前编辑中文档的路径。<br/>
|
||||
变量说明:{YYYY}为年变量、{MM}为月变量、{DD}为日变量、{MDNAME}为当前文档名称
|
||||
</div>
|
||||
</form-entry>
|
||||
</div>
|
||||
<div class="modal__button-bar">
|
||||
<button class="button" @click="config.reject()">取消</button>
|
||||
<button class="button button--resolve" @click="resolve()">确认</button>
|
||||
</div>
|
||||
</modal-inner>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import modalTemplate from './common/modalTemplate';
|
||||
|
||||
export default modalTemplate({
|
||||
computedLocalSettings: {
|
||||
path: '',
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('workspace', [
|
||||
'currentWorkspace',
|
||||
]),
|
||||
},
|
||||
methods: {
|
||||
resolve() {
|
||||
if (!this.path) {
|
||||
this.setError('path');
|
||||
}
|
||||
this.config.resolve({
|
||||
path: this.path || '/imgs/{YYYY}-{MM}-{DD}',
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
@ -9,7 +9,8 @@
|
||||
<div class="flex flex--column">
|
||||
<div class="workspace-entry__header flex flex--row flex--align-center">
|
||||
<div class="workspace-entry__icon">
|
||||
<icon-provider :provider-id="workspace.providerId"></icon-provider>
|
||||
<icon-provider v-if="id === 'main' && !workspace.sub" :provider-id="'stackedit'"></icon-provider>
|
||||
<icon-provider v-else :provider-id="workspace.providerId"></icon-provider>
|
||||
</div>
|
||||
<input class="text-input" type="text" v-if="editedId === id" v-focus @blur="submitEdit()" @keydown.enter="submitEdit()" @keydown.esc.stop="submitEdit(true)" v-model="editingName">
|
||||
<div class="workspace-entry__name" v-else>{{workspace.name}}</div>
|
||||
@ -17,6 +18,15 @@
|
||||
<button class="workspace-entry__button button" @click="edit(id)" v-title="'编辑名称'">
|
||||
<icon-pen></icon-pen>
|
||||
</button>
|
||||
<template v-if="workspace.providerId === 'giteeAppData' || workspace.providerId === 'githubAppData' || workspace.providerId === 'githubWorkspace'
|
||||
|| workspace.providerId === 'giteeWorkspace' || workspace.providerId === 'gitlabWorkspace' || workspace.providerId === 'giteaWorkspace'">
|
||||
<button class="workspace-entry__button button" @click="stopAutoSync(id)" v-if="workspace.autoSync == undefined || workspace.autoSync" v-title="'关闭自动同步'">
|
||||
<icon-sync-auto></icon-sync-auto>
|
||||
</button>
|
||||
<button class="workspace-entry__button button" @click="startAutoSync(id)" v-if="workspace.autoSync != undefined && !workspace.autoSync" v-title="'启动自动同步'">
|
||||
<icon-sync-stop></icon-sync-stop>
|
||||
</button>
|
||||
</template>
|
||||
<button class="workspace-entry__button button" @click="remove(id)" v-title="'删除'">
|
||||
<icon-delete></icon-delete>
|
||||
</button>
|
||||
@ -122,12 +132,53 @@ export default {
|
||||
this.info('请先关闭文档空间,然后再将其删除。');
|
||||
} else {
|
||||
try {
|
||||
await store.dispatch('modal/open', 'removeWorkspace');
|
||||
const workspace = this.workspacesById[id];
|
||||
if (!workspace) {
|
||||
return;
|
||||
}
|
||||
await store.dispatch('modal/open', {
|
||||
type: 'removeWorkspace',
|
||||
name: workspace.name,
|
||||
});
|
||||
workspaceSvc.removeWorkspace(id);
|
||||
badgeSvc.addBadge('removeWorkspace');
|
||||
} catch (e) { /* Cancel */ }
|
||||
}
|
||||
},
|
||||
async stopAutoSync(id) {
|
||||
const workspace = this.workspacesById[id];
|
||||
if (!workspace) {
|
||||
return;
|
||||
}
|
||||
await store.dispatch('modal/open', {
|
||||
type: 'stopAutoSyncWorkspace',
|
||||
name: workspace.name,
|
||||
});
|
||||
store.dispatch('workspace/patchWorkspacesById', {
|
||||
[id]: {
|
||||
...workspace,
|
||||
autoSync: false,
|
||||
},
|
||||
});
|
||||
badgeSvc.addBadge('stopAutoSyncWorkspace');
|
||||
},
|
||||
async startAutoSync(id) {
|
||||
const workspace = this.workspacesById[id];
|
||||
if (!workspace) {
|
||||
return;
|
||||
}
|
||||
await store.dispatch('modal/open', {
|
||||
type: 'autoSyncWorkspace',
|
||||
name: workspace.name,
|
||||
});
|
||||
store.dispatch('workspace/patchWorkspacesById', {
|
||||
[id]: {
|
||||
...workspace,
|
||||
autoSync: true,
|
||||
},
|
||||
});
|
||||
badgeSvc.addBadge('autoSyncWorkspace');
|
||||
},
|
||||
},
|
||||
created() {
|
||||
Object.keys(this.workspacesById).forEach(async (workspaceId) => {
|
||||
|
@ -17,7 +17,7 @@ export default {
|
||||
uid: utils.uid(),
|
||||
}),
|
||||
mounted() {
|
||||
this.$el.querySelector('input,select').id = this.uid;
|
||||
this.$el.querySelector('input,select,textarea').id = this.uid;
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<modal-inner aria-label="发布到Gist">
|
||||
<modal-inner aria-label="发布到GitHubGist">
|
||||
<div class="modal__content">
|
||||
<div class="modal__image">
|
||||
<icon-provider provider-id="gist"></icon-provider>
|
||||
</div>
|
||||
<p>发布<b> {{CurrentFileName}} </b>到<b>Gist</b>。</p>
|
||||
<form-entry label="Filename" error="filename">
|
||||
<p>发布<b> {{CurrentFileName}} </b>到<b>GitHubGist</b>。</p>
|
||||
<form-entry label="文件名" error="filename">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="filename" @keydown.enter="resolve()">
|
||||
</form-entry>
|
||||
<div class="form-entry">
|
||||
@ -15,10 +15,10 @@
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<form-entry label="Existing Gist ID" info="可选的">
|
||||
<form-entry label="存在Gist ID" info="可选的">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="gistId" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
如果文件存在于Gist中,则将被覆盖。
|
||||
如果文件存在于GitHubGist中,则将被覆盖。
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="Template">
|
||||
|
@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<modal-inner aria-label="与 Gist 同步">
|
||||
<modal-inner aria-label="与 GitHubGist 同步">
|
||||
<div class="modal__content">
|
||||
<div class="modal__image">
|
||||
<icon-provider provider-id="gist"></icon-provider>
|
||||
</div>
|
||||
<p>将<b> {{currentFileName}} </b>保存到<b>Gist</b>并保持同步。</p>
|
||||
<form-entry label="Filename" error="filename">
|
||||
<p>将<b> {{currentFileName}} </b>保存到<b>GitHubGist</b>并保持同步。</p>
|
||||
<form-entry label="文件名" error="filename">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="filename" @keydown.enter="resolve()">
|
||||
</form-entry>
|
||||
<div class="form-entry">
|
||||
@ -15,10 +15,10 @@
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<form-entry label="Existing Gist ID" info="可选的">
|
||||
<form-entry label="存在Gist ID" info="可选的">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="gistId" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
如果文件存在于Gist中,则将被覆盖。
|
||||
如果文件存在于GitHubGist中,则将被覆盖。
|
||||
</div>
|
||||
</form-entry>
|
||||
</div>
|
||||
|
@ -5,28 +5,30 @@
|
||||
<icon-provider provider-id="gitea"></icon-provider>
|
||||
</div>
|
||||
<p>将您的<b>Gitea</b>链接到<b>StackEdit中文版</b>。</p>
|
||||
<form-entry label="Gitea URL" error="serverUrl">
|
||||
<input v-if="config.forceServerUrl" slot="field" class="textfield" type="text" disabled="disabled" v-model="config.forceServerUrl">
|
||||
<input v-else slot="field" class="textfield" type="text" v-model.trim="serverUrl" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
<b>例如:</b> https://gitea.example.com/
|
||||
<span v-if="httpAppUrl">
|
||||
,非https的URL,请跳转到 <a :href="httpAppUrl" target="_blank">HTTP链接</a> 添加Gitea。
|
||||
</span>
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="Application ID" error="applicationId">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="applicationId" @keydown.enter="resolve()">
|
||||
</form-entry>
|
||||
<form-entry label="Application Secret" error="applicationSecret">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="applicationSecret" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
您必须使用重定向url <b>{{redirectUrl}}</b>配置OAuth2应用程序
|
||||
</div>
|
||||
<div class="form-entry__actions">
|
||||
<a href="https://docs.gitea.io/en-us/oauth2-provider/" target="_blank">更多信息</a>
|
||||
</div>
|
||||
</form-entry>
|
||||
<template v-if="!useServerConf">
|
||||
<form-entry label="Gitea URL" error="serverUrl">
|
||||
<input v-if="config.forceServerUrl" slot="field" class="textfield" type="text" disabled="disabled" v-model="config.forceServerUrl">
|
||||
<input v-else slot="field" class="textfield" type="text" v-model.trim="serverUrl" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
<b>例如:</b> https://gitea.example.com/
|
||||
<span v-if="httpAppUrl">
|
||||
,非https的URL,请跳转到 <a :href="httpAppUrl" target="_blank">HTTP链接</a> 添加Gitea。
|
||||
</span>
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="Application ID" error="applicationId">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="applicationId" @keydown.enter="resolve()">
|
||||
</form-entry>
|
||||
<form-entry label="Application Secret" error="applicationSecret">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="applicationSecret" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
您必须使用重定向url <b>{{redirectUrl}}</b>配置OAuth2应用程序
|
||||
</div>
|
||||
<div class="form-entry__actions">
|
||||
<a href="https://docs.gitea.io/en-us/oauth2-provider/" target="_blank">更多信息</a>
|
||||
</div>
|
||||
</form-entry>
|
||||
</template>
|
||||
</div>
|
||||
<div class="modal__button-bar">
|
||||
<button class="button" @click="config.reject()">取消</button>
|
||||
@ -38,6 +40,8 @@
|
||||
<script>
|
||||
import modalTemplate from '../common/modalTemplate';
|
||||
import constants from '../../../data/constants';
|
||||
import store from '../../../store';
|
||||
import networkSvc from '../../../services/networkSvc';
|
||||
|
||||
export default modalTemplate({
|
||||
data: () => ({
|
||||
@ -55,9 +59,22 @@ export default modalTemplate({
|
||||
}
|
||||
return null;
|
||||
},
|
||||
// 是否使用服务端配置
|
||||
useServerConf() {
|
||||
const confClientId = store.getters['data/serverConf'].giteaClientId;
|
||||
const confServerUrl = store.getters['data/serverConf'].giteaUrl;
|
||||
return !!confClientId && !!confServerUrl;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
networkSvc.getServerConf();
|
||||
},
|
||||
methods: {
|
||||
resolve() {
|
||||
if (this.useServerConf) {
|
||||
this.config.resolve({});
|
||||
return;
|
||||
}
|
||||
const serverUrl = this.config.forceServerUrl || this.serverUrl;
|
||||
if (!serverUrl) {
|
||||
this.setError('serverUrl');
|
||||
|
@ -12,9 +12,10 @@
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="文件夹路径" info="可选的">
|
||||
<input slot="field" class="textfield" type="text" placeholder="如:imgs/{YYYY}/{MM}" v-model.trim="path" @keydown.enter="resolve()">
|
||||
<input slot="field" class="textfield" type="text" placeholder="如:imgs/{YYYY}-{MM}-{DD}" v-model.trim="path" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
如果不提供,默认为 {YYYY}/{MM}/{DD} ,其中{YYYY}为年变量、{MM}为月变量、{DD}为日变量。
|
||||
如果不提供,默认为 imgs/{YYYY}-{MM}-{DD} 。<br/>
|
||||
变量说明:{YYYY}为年变量、{MM}为月变量、{DD}为日变量、{MDNAME}为当前文档名称。
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="分支" info="可选的">
|
||||
@ -62,7 +63,7 @@ export default modalTemplate({
|
||||
const path = this.path && this.path.replace(/^\//, '');
|
||||
this.config.resolve({
|
||||
repoUri: projectPath,
|
||||
path: path || '{YYYY}/{MM}/{DD}',
|
||||
path: path || 'imgs/{YYYY}-{MM}-{DD}',
|
||||
branch: this.branch || 'master',
|
||||
});
|
||||
} catch (err) {
|
||||
|
@ -4,11 +4,11 @@
|
||||
<div class="modal__image">
|
||||
<icon-provider provider-id="gitea"></icon-provider>
|
||||
</div>
|
||||
<p>向您的<b> Gitea </b>项目发布<b> {{CurrentFileName}} </b>。</p>
|
||||
<p>向您的<b> Gitea </b>项目发布<b> {{ currentFileName }} </b>。</p>
|
||||
<form-entry label="Project URL" error="projectUrl">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="projectUrl" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
<b>例如:</b> {{config.token.serverUrl}}/path/to/project
|
||||
<b>例如:</b> {{ config.token.serverUrl }}/path/to/project
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="File path" error="path">
|
||||
|
79
src/components/modals/providers/GiteeGistPublishModal.vue
Normal file
@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<modal-inner aria-label="发布到GiteeGist">
|
||||
<div class="modal__content">
|
||||
<div class="modal__image">
|
||||
<icon-provider provider-id="giteegist"></icon-provider>
|
||||
</div>
|
||||
<p>发布<b> {{CurrentFileName}} </b>到<b>GiteeGist</b>。</p>
|
||||
<form-entry label="文件名" error="filename">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="filename" @keydown.enter="resolve()">
|
||||
</form-entry>
|
||||
<div class="form-entry">
|
||||
<div class="form-entry__checkbox">
|
||||
<label>
|
||||
<input type="checkbox" v-model="isPublic"> 公开的
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<form-entry label="存在Gist ID" info="可选的">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="gistId" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
如果文件存在于GiteeGist中,则将被覆盖。
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="Template">
|
||||
<select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()">
|
||||
<option v-for="(template, id) in allTemplatesById" :key="id" :value="id">
|
||||
{{ template.name }}
|
||||
</option>
|
||||
</select>
|
||||
<div class="form-entry__actions">
|
||||
<a href="javascript:void(0)" @click="configureTemplates">配置模板</a>
|
||||
</div>
|
||||
</form-entry>
|
||||
<div class="modal__info">
|
||||
<b>ProTip:</b> You can provide a value for <code>title</code> in the <a href="javascript:void(0)" @click="openFileProperties">file properties</a>.
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal__button-bar">
|
||||
<button class="button" @click="config.reject()">取消</button>
|
||||
<button class="button button--resolve" @click="resolve()">确认</button>
|
||||
</div>
|
||||
</modal-inner>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import giteeGistProvider from '../../../services/providers/giteeGistProvider';
|
||||
import modalTemplate from '../common/modalTemplate';
|
||||
|
||||
export default modalTemplate({
|
||||
data: () => ({
|
||||
filename: '',
|
||||
gistId: '',
|
||||
}),
|
||||
computedLocalSettings: {
|
||||
isPublic: 'gistIsPublic',
|
||||
selectedTemplate: 'gistPublishTemplate',
|
||||
},
|
||||
created() {
|
||||
this.filename = `${this.currentFileName}.md`;
|
||||
},
|
||||
methods: {
|
||||
resolve() {
|
||||
if (!this.filename) {
|
||||
this.setError('filename');
|
||||
} else {
|
||||
// Return new location
|
||||
const location = giteeGistProvider.makeLocation(
|
||||
this.config.token,
|
||||
this.filename,
|
||||
this.isPublic,
|
||||
this.gistId,
|
||||
);
|
||||
location.templateId = this.selectedTemplate;
|
||||
this.config.resolve(location);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
64
src/components/modals/providers/GiteeGistSyncModal.vue
Normal file
@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<modal-inner aria-label="与 GiteeGist 同步">
|
||||
<div class="modal__content">
|
||||
<div class="modal__image">
|
||||
<icon-provider provider-id="giteegist"></icon-provider>
|
||||
</div>
|
||||
<p>将<b> {{currentFileName}} </b>保存到<b>GiteeGist</b>并保持同步。</p>
|
||||
<form-entry label="文件名" error="filename">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="filename" @keydown.enter="resolve()">
|
||||
</form-entry>
|
||||
<div class="form-entry">
|
||||
<div class="form-entry__checkbox">
|
||||
<label>
|
||||
<input type="checkbox" v-model="isPublic"> 公开的
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<form-entry label="存在Gist ID" info="可选的">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="gistId" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
如果文件存在于GiteeGist中,则将被覆盖。
|
||||
</div>
|
||||
</form-entry>
|
||||
</div>
|
||||
<div class="modal__button-bar">
|
||||
<button class="button" @click="config.reject()">取消</button>
|
||||
<button class="button button--resolve" @click="resolve()">确认</button>
|
||||
</div>
|
||||
</modal-inner>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import giteeGistProvider from '../../../services/providers/giteeGistProvider';
|
||||
import modalTemplate from '../common/modalTemplate';
|
||||
|
||||
export default modalTemplate({
|
||||
data: () => ({
|
||||
filename: '',
|
||||
gistId: '',
|
||||
}),
|
||||
computedLocalSettings: {
|
||||
isPublic: 'gistIsPublic',
|
||||
},
|
||||
created() {
|
||||
this.filename = `${this.currentFileName}.md`;
|
||||
},
|
||||
methods: {
|
||||
resolve() {
|
||||
if (!this.filename) {
|
||||
this.setError('filename');
|
||||
} else {
|
||||
// Return new location
|
||||
const location = giteeGistProvider.makeLocation(
|
||||
this.config.token,
|
||||
this.filename,
|
||||
this.isPublic,
|
||||
this.gistId,
|
||||
);
|
||||
this.config.resolve(location);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
@ -4,7 +4,7 @@
|
||||
<div class="modal__image">
|
||||
<icon-provider provider-id="gitee"></icon-provider>
|
||||
</div>
|
||||
<p>Publish <b>{{currentFileName}}</b> to your <b>Gitee</b> repository.</p>
|
||||
<p>发布 <b>{{currentFileName}}</b> 到您的 <b>Gitee</b> 仓库.</p>
|
||||
<form-entry label="仓库URL" error="repoUrl">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="repoUrl" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
|
@ -4,7 +4,7 @@
|
||||
<div class="modal__image">
|
||||
<icon-provider provider-id="gitee"></icon-provider>
|
||||
</div>
|
||||
<p>Save <b>{{currentFileName}}</b> to your <b>Gitee</b> repository and keep it synced.</p>
|
||||
<p>保存 <b>{{currentFileName}}</b> 并与您的 <b>Gitee</b> 仓库保持同步.</p>
|
||||
<form-entry label="仓库URL" error="repoUrl">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="repoUrl" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
|
@ -12,9 +12,10 @@
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="文件夹路径" info="可选的">
|
||||
<input slot="field" class="textfield" type="text" placeholder="如:imgs/{YYYY}/{MM}" v-model.trim="path" @keydown.enter="resolve()">
|
||||
<input slot="field" class="textfield" type="text" placeholder="如:imgs/{YYYY}-{MM}-{DD}" v-model.trim="path" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
如果不提供,默认为 {YYYY}/{MM}/{DD} ,其中{YYYY}为年变量、{MM}为月变量、{DD}为日变量。
|
||||
如果不提供,默认为 imgs/{YYYY}-{MM}-{DD} 。<br/>
|
||||
变量说明:{YYYY}为年变量、{MM}为月变量、{DD}为日变量、{MDNAME}为当前文档名称。
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="分支" info="可选的">
|
||||
@ -63,7 +64,7 @@ export default modalTemplate({
|
||||
this.config.resolve({
|
||||
owner,
|
||||
repo,
|
||||
path: path || '{YYYY}/{MM}/{DD}',
|
||||
path: path || 'imgs/{YYYY}-{MM}-{DD}',
|
||||
branch: this.branch || 'master',
|
||||
});
|
||||
} catch (err) {
|
||||
|
@ -4,7 +4,7 @@
|
||||
<div class="modal__image">
|
||||
<icon-provider provider-id="github"></icon-provider>
|
||||
</div>
|
||||
<p>Publish <b>{{currentFileName}}</b> to your <b>GitHub</b> repository.</p>
|
||||
<p>发布 <b>{{currentFileName}}</b> 到您的 <b>GitHub</b> 仓库.</p>
|
||||
<form-entry label="仓库URL" error="repoUrl">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="repoUrl" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
|
@ -5,22 +5,30 @@
|
||||
<icon-provider provider-id="gitlab"></icon-provider>
|
||||
</div>
|
||||
<p>将您的<b>GitLab</b>链接到<b>StackEdit中文版</b>。</p>
|
||||
<form-entry label="GitLab URL" error="serverUrl">
|
||||
<input v-if="config.forceServerUrl" slot="field" class="textfield" type="text" disabled="disabled" v-model="config.forceServerUrl">
|
||||
<input v-else slot="field" class="textfield" type="text" v-model.trim="serverUrl" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
<b>例如:</b> https://gitlab.example.com/
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="Application ID" error="applicationId">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="applicationId" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
您必须使用重定向url <b>{{redirectUrl}}</b>配置OAuth2应用程序
|
||||
</div>
|
||||
<div class="form-entry__actions">
|
||||
<a href="https://docs.gitlab.com/ee/integration/oauth_provider.html" target="_blank">更多信息</a>
|
||||
</div>
|
||||
</form-entry>
|
||||
<template v-if="!useServerConf">
|
||||
<form-entry label="GitLab URL" error="serverUrl">
|
||||
<input v-if="config.forceServerUrl" slot="field" class="textfield" type="text" disabled="disabled" v-model="config.forceServerUrl">
|
||||
<input v-else slot="field" class="textfield" type="text" v-model.trim="serverUrl" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
<b>例如:</b> https://gitlab.example.com/
|
||||
<span v-if="httpAppUrl">
|
||||
,非https的URL,请跳转到 <a :href="httpAppUrl" target="_blank">HTTP链接</a> 添加Gitlab。
|
||||
</span>
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="Application ID" error="applicationId">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="applicationId" @keydown.enter="resolve()">
|
||||
</form-entry>
|
||||
<form-entry label="Application Secret" error="applicationSecret">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="applicationSecret" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
您必须使用重定向url <b>{{redirectUrl}}</b>配置OAuth2应用程序
|
||||
</div>
|
||||
<div class="form-entry__actions">
|
||||
<a href="https://docs.gitlab.com/ee/integration/oauth_provider.html" target="_blank">更多信息</a>
|
||||
</div>
|
||||
</form-entry>
|
||||
</template>
|
||||
</div>
|
||||
<div class="modal__button-bar">
|
||||
<button class="button" @click="config.reject()">取消</button>
|
||||
@ -32,6 +40,8 @@
|
||||
<script>
|
||||
import modalTemplate from '../common/modalTemplate';
|
||||
import constants from '../../../data/constants';
|
||||
import store from '../../../store';
|
||||
import networkSvc from '../../../services/networkSvc';
|
||||
|
||||
export default modalTemplate({
|
||||
data: () => ({
|
||||
@ -40,9 +50,31 @@ export default modalTemplate({
|
||||
computedLocalSettings: {
|
||||
serverUrl: 'gitlabServerUrl',
|
||||
applicationId: 'gitlabApplicationId',
|
||||
applicationSecret: 'gitlabApplicationSecret',
|
||||
},
|
||||
computed: {
|
||||
httpAppUrl() {
|
||||
if (constants.origin.indexOf('https://') === 0 && this.serverUrl.indexOf('http://') === 0) {
|
||||
return `${constants.origin.replace('https://', 'http://')}/app`;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
// 是否使用服务端配置
|
||||
useServerConf() {
|
||||
const confClientId = store.getters['data/serverConf'].gitlabClientId;
|
||||
const confServerUrl = store.getters['data/serverConf'].gitlabUrl;
|
||||
return !!confClientId && !!confServerUrl;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
networkSvc.getServerConf();
|
||||
},
|
||||
methods: {
|
||||
resolve() {
|
||||
if (this.useServerConf) {
|
||||
this.config.resolve({});
|
||||
return;
|
||||
}
|
||||
const serverUrl = this.config.forceServerUrl || this.serverUrl;
|
||||
if (!serverUrl) {
|
||||
this.setError('serverUrl');
|
||||
@ -50,14 +82,18 @@ export default modalTemplate({
|
||||
if (!this.applicationId) {
|
||||
this.setError('applicationId');
|
||||
}
|
||||
if (serverUrl && this.applicationId) {
|
||||
const parsedUrl = serverUrl.match(/^(https:\/\/[^/]+)/);
|
||||
if (!this.applicationSecret) {
|
||||
this.setError('applicationSecret');
|
||||
}
|
||||
if (serverUrl && this.applicationId && this.applicationSecret) {
|
||||
const parsedUrl = serverUrl.match(/^(http[s]?:\/\/[^/]+)/);
|
||||
if (!parsedUrl) {
|
||||
this.setError('serverUrl');
|
||||
} else {
|
||||
this.config.resolve({
|
||||
serverUrl: parsedUrl[1],
|
||||
applicationId: this.applicationId,
|
||||
applicationSecret: this.applicationSecret,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
<div class="modal__image">
|
||||
<icon-provider provider-id="gitlab"></icon-provider>
|
||||
</div>
|
||||
<p>Publish <b>{{currentFileName}}</b> to your <b>GitLab</b> project.</p>
|
||||
<p>发布 <b>{{currentFileName}}</b> 到您的 <b>GitLab</b> 仓库.</p>
|
||||
<form-entry label="Project URL" error="projectUrl">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="projectUrl" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
|
@ -4,7 +4,7 @@
|
||||
<div class="modal__image">
|
||||
<icon-provider provider-id="googleDrive"></icon-provider>
|
||||
</div>
|
||||
<p>Publish <b>{{currentFileName}}</b> to your <b>Google Drive</b> account.</p>
|
||||
<p>发布 <b>{{currentFileName}}</b> 到您的 <b>Google Drive</b> 账号.</p>
|
||||
<form-entry label="Folder ID" info="可选的">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="folderId" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
|
@ -22,6 +22,6 @@ export default {
|
||||
'badgeCreations',
|
||||
'serverConf',
|
||||
],
|
||||
textMaxLength: 250000,
|
||||
textMaxLength: 10000000,
|
||||
defaultName: 'Untitled',
|
||||
};
|
||||
|
@ -24,6 +24,7 @@ export default () => ({
|
||||
giteePublishTemplate: 'jekyllSite',
|
||||
gitlabServerUrl: '',
|
||||
gitlabApplicationId: '',
|
||||
gitlabApplicationSecret: '',
|
||||
gitlabProjectUrl: '',
|
||||
gitlabWorkspaceProjectUrl: '',
|
||||
gitlabPublishTemplate: 'plainText',
|
||||
|
@ -15,6 +15,36 @@ editor:
|
||||
inlineImages: true
|
||||
# Use monospaced font only
|
||||
monospacedFontOnly: false
|
||||
# 是否显示右上角图标
|
||||
showInPageButtons: true
|
||||
# 头部的按钮是否显示独立设置
|
||||
headButtons:
|
||||
# 加粗
|
||||
bold: true
|
||||
# 斜体
|
||||
italic: true
|
||||
# 标题
|
||||
heading: true
|
||||
# 删除线
|
||||
strikethrough: true
|
||||
# 无序列表
|
||||
ulist: true
|
||||
# 有序列表
|
||||
olist: true
|
||||
# 可选列表
|
||||
clist: true
|
||||
# 块引用
|
||||
quote: true
|
||||
# 代码
|
||||
code: true
|
||||
# 表格
|
||||
table: true
|
||||
# 链接
|
||||
link: true
|
||||
# 图片
|
||||
image: true
|
||||
# ChatGPT
|
||||
chatgpt: true
|
||||
|
||||
# Keyboard shortcuts
|
||||
# See https://craig.is/killing/mice
|
||||
@ -29,6 +59,7 @@ shortcuts:
|
||||
mod+shift+h: heading
|
||||
mod+shift+r: hr
|
||||
mod+shift+g: image
|
||||
mod+shift+p: chatgpt
|
||||
mod+shift+i: italic
|
||||
mod+shift+l: link
|
||||
mod+shift+o: olist
|
||||
@ -36,6 +67,9 @@ shortcuts:
|
||||
mod+shift+s: strikethrough
|
||||
mod+shift+t: table
|
||||
mod+shift+u: ulist
|
||||
mod+shift+f: inlineformula
|
||||
# 切换编辑与预览模式
|
||||
mod+shift+e: toggleeditor
|
||||
'= = > space':
|
||||
method: expand
|
||||
params:
|
||||
@ -79,16 +113,16 @@ turndown:
|
||||
|
||||
# GitHub/GitLab/Gitee/Gitea commit messages
|
||||
git:
|
||||
createFileMessage: '{{path}} created from https://stackedit.cn/'
|
||||
updateFileMessage: '{{path}} updated from https://stackedit.cn/'
|
||||
deleteFileMessage: '{{path}} deleted from https://stackedit.cn/'
|
||||
createFileMessage: '{{path}} created from https://md.jonylee.top/'
|
||||
updateFileMessage: '{{path}} updated from https://md.jonylee.top/'
|
||||
deleteFileMessage: '{{path}} deleted from https://md.jonylee.top/'
|
||||
|
||||
# Default content for new files
|
||||
newFileContent: |
|
||||
|
||||
|
||||
|
||||
> Written with [StackEdit中文版](https://stackedit.cn/).
|
||||
> Written with [Markdown编辑器-StackEdit中文版](https://md.jonylee.top/).
|
||||
|
||||
# Default properties for new files
|
||||
newFileProperties: |
|
||||
|
@ -158,7 +158,19 @@ export default [
|
||||
new Feature(
|
||||
'sponsor',
|
||||
'赞助',
|
||||
'使用 Google 登录并赞助 StackEdit 以解锁 PDF 和 Pandoc 导出。(暂不支持赞助)',
|
||||
'使用 Gitee 登录并赞助 StackEdit 以解锁 PDF 和 Pandoc 导出。(暂不支持赞助)',
|
||||
),
|
||||
],
|
||||
),
|
||||
new Feature(
|
||||
'githubSignIn',
|
||||
'登录',
|
||||
'使用 Gitee 登录,同步您的主文档空间并解锁功能。',
|
||||
[
|
||||
new Feature(
|
||||
'githubSyncMainWorkspace',
|
||||
'主文档空间已同步',
|
||||
'使用 GitHub 登录以将您的主文档空间与您的默认空间stackedit-app-data仓库数据同步。',
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -207,6 +219,16 @@ export default [
|
||||
'文档空间删除',
|
||||
'使用“管理文档空间”对话框在本地删除文档空间。',
|
||||
),
|
||||
new Feature(
|
||||
'autoSyncWorkspace',
|
||||
'文档空间启用自动同步',
|
||||
'使用“管理文档空间”对话框启用自动同步。',
|
||||
),
|
||||
new Feature(
|
||||
'stopAutoSyncWorkspace',
|
||||
'文档空间关闭自动同步',
|
||||
'使用“管理文档空间”对话框关闭自动同步。',
|
||||
),
|
||||
],
|
||||
),
|
||||
new Feature(
|
||||
@ -306,6 +328,11 @@ export default [
|
||||
'GitHub保存',
|
||||
'使用“同步”菜单将文件保存在GitHub仓库中。',
|
||||
),
|
||||
new Feature(
|
||||
'saveOnGist',
|
||||
'GitHubGist保存',
|
||||
'使用“同步”菜单将文件保存在GitHubGist中。',
|
||||
),
|
||||
new Feature(
|
||||
'openFromGitee',
|
||||
'Gitee阅读器',
|
||||
@ -317,9 +344,9 @@ export default [
|
||||
'使用“同步”菜单将文件保存在Gitee仓库中。',
|
||||
),
|
||||
new Feature(
|
||||
'saveOnGist',
|
||||
'Gist保存',
|
||||
'使用“同步”菜单将文件保存在GIST中。',
|
||||
'saveOnGiteeGist',
|
||||
'GiteeGist保存',
|
||||
'使用“同步”菜单将文件保存在GiteeGist中。',
|
||||
),
|
||||
new Feature(
|
||||
'openFromGitlab',
|
||||
@ -395,14 +422,19 @@ export default [
|
||||
),
|
||||
new Feature(
|
||||
'publishToGist',
|
||||
'Gist发布',
|
||||
'使用“发布”菜单将文件发布到GIST。',
|
||||
'GitHubGist发布',
|
||||
'使用“发布”菜单将文件发布到GitHubGist。',
|
||||
),
|
||||
new Feature(
|
||||
'publishToGitee',
|
||||
'Gitee发布',
|
||||
'使用“发布”菜单将文件发布到Gitee仓库。',
|
||||
),
|
||||
new Feature(
|
||||
'publishToGiteeGist',
|
||||
'GiteeGist发布',
|
||||
'使用“发布”菜单将文件发布到GiteeGist。',
|
||||
),
|
||||
new Feature(
|
||||
'publishToGitlab',
|
||||
'GitLab发布',
|
||||
|
@ -1,78 +1,78 @@
|
||||
Headers
|
||||
标题
|
||||
---------------------------
|
||||
|
||||
# Header 1
|
||||
# 标题1
|
||||
|
||||
## Header 2
|
||||
## 标题2
|
||||
|
||||
### Header 3
|
||||
### 标题3
|
||||
|
||||
|
||||
|
||||
Styling
|
||||
样式
|
||||
---------------------------
|
||||
|
||||
*Emphasize* _emphasize_
|
||||
*强调* _强调_
|
||||
|
||||
**Strong** __strong__
|
||||
**加粗** __加粗__
|
||||
|
||||
==Marked text.==
|
||||
==标记文本==
|
||||
|
||||
~~Mistaken text.~~
|
||||
~~删除线文本~~
|
||||
|
||||
> Quoted text.
|
||||
> 块引用文本
|
||||
|
||||
H~2~O is a liquid.
|
||||
H~2~O是一种液体
|
||||
|
||||
2^10^ is 1024.
|
||||
2^10^是1024
|
||||
|
||||
|
||||
|
||||
Lists
|
||||
列表
|
||||
---------------------------
|
||||
|
||||
- Item
|
||||
* Item
|
||||
+ Item
|
||||
- 列表项
|
||||
* 列表项
|
||||
+ 列表项
|
||||
|
||||
1. Item 1
|
||||
2. Item 2
|
||||
3. Item 3
|
||||
1. 列表项 1
|
||||
2. 列表项 2
|
||||
3. 列表项 3
|
||||
|
||||
- [ ] Incomplete item
|
||||
- [x] Complete item
|
||||
- [ ] 未完成项
|
||||
- [x] 已完成项
|
||||
|
||||
|
||||
|
||||
Links
|
||||
链接
|
||||
---------------------------
|
||||
|
||||
A [link](http://example.com).
|
||||
一个[链接](http://example.com).
|
||||
|
||||
An image: 
|
||||
一张图片: 
|
||||
|
||||
A sized image: 
|
||||
一张调整大小的图片: 
|
||||
|
||||
|
||||
|
||||
Code
|
||||
代码
|
||||
---------------------------
|
||||
|
||||
Some `inline code`.
|
||||
一些`行内代码`.
|
||||
|
||||
```
|
||||
// A code block
|
||||
// 一个代码块
|
||||
var foo = 'bar';
|
||||
```
|
||||
|
||||
```javascript
|
||||
// An highlighted block
|
||||
// 一个高亮代码块
|
||||
var foo = 'bar';
|
||||
```
|
||||
|
||||
|
||||
|
||||
Tables
|
||||
表格
|
||||
---------------------------
|
||||
|
||||
Item | Value
|
||||
@ -88,41 +88,41 @@ Pipe | $1
|
||||
|
||||
|
||||
|
||||
Definition lists
|
||||
定义列表
|
||||
---------------------------
|
||||
|
||||
Markdown
|
||||
: Text-to-HTML conversion tool
|
||||
: 文本到HTML转换工具
|
||||
|
||||
Authors
|
||||
: John
|
||||
: Luke
|
||||
作者
|
||||
: 张三
|
||||
: 李四
|
||||
|
||||
|
||||
|
||||
Footnotes
|
||||
脚注
|
||||
---------------------------
|
||||
|
||||
Some text with a footnote.[^1]
|
||||
一些带有脚注的文本。[^1]
|
||||
|
||||
[^1]: The footnote.
|
||||
[^1]: 脚注内容。
|
||||
|
||||
|
||||
|
||||
Abbreviations
|
||||
缩写
|
||||
---------------------------
|
||||
|
||||
Markdown converts text to HTML.
|
||||
Markdown将文本转换为 HTML。
|
||||
|
||||
*[HTML]: HyperText Markup Language
|
||||
*[HTML]: 超文本标记语言
|
||||
|
||||
|
||||
|
||||
LaTeX math
|
||||
LaTeX数学表达式
|
||||
---------------------------
|
||||
|
||||
The Gamma function satisfying $\Gamma(n) = (n-1)!\quad\forall
|
||||
n\in\mathbb N$ is via the Euler integral
|
||||
满足 $\Gamma(n) = (n-1)!\quad\forall
|
||||
n\in\mathbb N$ 的Gamma函数是通过欧拉积分
|
||||
|
||||
$$
|
||||
\Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,.
|
||||
|
@ -15,7 +15,7 @@ export default [{
|
||||
method: 'strikethrough',
|
||||
title: '删除线',
|
||||
icon: 'format-strikethrough',
|
||||
}, {
|
||||
// }, {
|
||||
}, {
|
||||
method: 'ulist',
|
||||
title: '无序列表',
|
||||
@ -28,7 +28,7 @@ export default [{
|
||||
method: 'clist',
|
||||
title: '可选列表',
|
||||
icon: 'format-list-checks',
|
||||
}, {
|
||||
// }, {
|
||||
}, {
|
||||
method: 'quote',
|
||||
title: '块引用',
|
||||
@ -49,4 +49,8 @@ export default [{
|
||||
method: 'image',
|
||||
title: '图片',
|
||||
icon: 'file-image',
|
||||
}, {
|
||||
method: 'chatgpt',
|
||||
title: 'ChatGPT',
|
||||
icon: 'chat-gpt',
|
||||
}];
|
||||
|
@ -1,11 +1,17 @@
|
||||
const simpleModal = (contentHtml, rejectText, resolveText) => ({
|
||||
const simpleModal = (contentHtml, rejectText, resolveText, resolveArray) => ({
|
||||
contentHtml: typeof contentHtml === 'function' ? contentHtml : () => contentHtml,
|
||||
rejectText,
|
||||
resolveArray,
|
||||
resolveText,
|
||||
});
|
||||
|
||||
/* eslint sort-keys: "error" */
|
||||
export default {
|
||||
autoSyncWorkspace: simpleModal(
|
||||
config => `<p>您将启动文档空间 <b>${config.name}</b >的自动同步。<br>启动后无法自定义提交信息。<br>你确定吗?</p>`,
|
||||
'取消',
|
||||
'确认启动',
|
||||
),
|
||||
commentDeletion: simpleModal(
|
||||
'<p>您将要删除评论。你确定吗?</p>',
|
||||
'取消',
|
||||
@ -46,7 +52,7 @@ export default {
|
||||
'确认跳转',
|
||||
),
|
||||
removeWorkspace: simpleModal(
|
||||
'<p>您将要在本地删除文档空间ß。你确定吗?</p>',
|
||||
config => `<p>您将要在本地删除文档空间<b>${config.name}</b>。你确定吗?</p>`,
|
||||
'取消',
|
||||
'确认删除',
|
||||
),
|
||||
@ -55,22 +61,50 @@ export default {
|
||||
'取消',
|
||||
'确认清理',
|
||||
),
|
||||
shareHtml: simpleModal(
|
||||
config => `<p>给文档 "${config.name}" 创建了分享链接如下:<br/><a href="${config.url}" target="_blank">${config.url}</a><br/>关闭该窗口后可以到发布中查看分享链接。</p>`,
|
||||
'关闭窗口',
|
||||
),
|
||||
shareHtmlPre: simpleModal(
|
||||
config => `<p>将给文档 "${config.name}" 创建分享链接,创建后将会把文档公开发布到默认空间账号的Gist中。您确定吗?</p>`,
|
||||
'取消',
|
||||
'确认分享',
|
||||
),
|
||||
signInForComment: simpleModal(
|
||||
`<p>您必须使用 Google 登录才能开始评论。</p>
|
||||
`<p>您必须使用 Gitee或GitHub 登录默认文档空间后才能开始评论。</p>
|
||||
<div class="modal__info"><b>注意:</b> 这将同步您的主文档空间。</div>`,
|
||||
'取消',
|
||||
'确认登录',
|
||||
'',
|
||||
[{
|
||||
text: 'Gitee登录',
|
||||
value: 'gitee',
|
||||
}, {
|
||||
text: 'GitHub登录',
|
||||
value: 'github',
|
||||
}],
|
||||
),
|
||||
signInForSponsorship: simpleModal(
|
||||
`<p>您必须使用 Google 登录才能赞助。</p>
|
||||
`<p>您必须使用 Gitee或GitHub 登录才能赞助。</p>
|
||||
<div class="modal__info"><b>注意:</b> 这将同步您的主文档空间。</div>`,
|
||||
'取消',
|
||||
'确认登录',
|
||||
'',
|
||||
[{
|
||||
text: 'Gitee登录',
|
||||
value: 'gitee',
|
||||
}, {
|
||||
text: 'GitHub登录',
|
||||
value: 'github',
|
||||
}],
|
||||
),
|
||||
sponsorOnly: simpleModal(
|
||||
'<p>此功能仅限于赞助商,因为它依赖于服务器资源。</p>',
|
||||
'好的,我明白了',
|
||||
),
|
||||
stopAutoSyncWorkspace: simpleModal(
|
||||
config => `<p>您将关闭文档空间 <b>${config.name}</b> 的自动同步。<br>关闭后您需要手动触发同步,但可以自定义提交信息。<br>你确定吗?</p>`,
|
||||
'取消',
|
||||
'确认关闭',
|
||||
),
|
||||
stripName: simpleModal(
|
||||
config => `<p><b>${config.item.name}</b>包含非法字符。你想去掉它们吗?</p>`,
|
||||
'取消',
|
||||
|
43
src/data/templates/styledHtmlWithThemeAndTocTemplate.html
Normal file
@ -0,0 +1,43 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{files.0.name}}</title>
|
||||
<link rel="stylesheet" href="https://stackedit.cn/style.css" />
|
||||
<style type="text/css">
|
||||
.app--dark {
|
||||
background-color: #444;
|
||||
}
|
||||
.app--dark .stackedit__html {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
.app--dark .stackedit__content {
|
||||
padding: 1px 20px 20px;
|
||||
}
|
||||
{{{files.0.content.themeStyleContent}}}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
{{#if pdf}}
|
||||
<body class="stackedit stackedit--pdf">
|
||||
{{else}}
|
||||
<body class="stackedit {{{files.0.content.colorThemeClass}}}">
|
||||
{{/if}}
|
||||
<div class="stackedit__left">
|
||||
<div class="stackedit__toc">
|
||||
{{#tocToHtml files.0.content.toc 2}}{{/tocToHtml}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="stackedit__right">
|
||||
<div class="stackedit__html">
|
||||
<div class="stackedit__content {{{files.0.content.themeClass}}}">
|
||||
{{{files.0.content.html}}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
36
src/data/templates/styledHtmlWithThemeTemplate.html
Normal file
@ -0,0 +1,36 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{files.0.name}}</title>
|
||||
<link rel="stylesheet" href="https://stackedit.cn/style.css" />
|
||||
<style type="text/css">
|
||||
.app--dark {
|
||||
background-color: #444;
|
||||
}
|
||||
.app--dark .stackedit__html {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
.app--dark .stackedit__content {
|
||||
padding: 1px 20px 20px;
|
||||
}
|
||||
{{{files.0.content.themeStyleContent}}}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
{{#if pdf}}
|
||||
<body class="stackedit stackedit--pdf">
|
||||
{{else}}
|
||||
<body class="stackedit {{{files.0.content.colorThemeClass}}}">
|
||||
{{/if}}
|
||||
<div class="stackedit__html">
|
||||
<div class="stackedit__content {{{files.0.content.themeClass}}}">
|
||||
{{{files.0.content.html}}}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -56,6 +56,7 @@ export default (md) => {
|
||||
|
||||
// According to http://pandoc.org/README.html#extension-auto_identifiers
|
||||
let slug = headingContent
|
||||
.replace(/<img[^>]*>/g, '') // Replace image to empty
|
||||
.replace(/\s/g, '-') // Replace all spaces and newlines with hyphens
|
||||
.replace(/[\0-,/:-@[-^`{-~]/g, '') // Remove all punctuation, except underscores, hyphens, and periods
|
||||
.toLowerCase(); // Convert all alphabetic characters to lowercase
|
||||
@ -64,7 +65,9 @@ export default (md) => {
|
||||
let i;
|
||||
for (i = 0; i < slug.length; i += 1) {
|
||||
const charCode = slug.charCodeAt(i);
|
||||
if ((charCode >= 0x61 && charCode <= 0x7A) || charCode > 0x7E) {
|
||||
if ((charCode >= 0x30 && charCode <= 0x39) || // 0-9
|
||||
(charCode >= 0x61 && charCode <= 0x7A) || // a-z
|
||||
charCode > 0x7E) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -98,7 +101,7 @@ export default (md) => {
|
||||
return result;
|
||||
}, '');
|
||||
}
|
||||
if (token.type === 'inline' && token.content === '[TOC]') {
|
||||
if (token.type === 'inline' && (token.content === '[TOC]' || token.content === '[toc]')) {
|
||||
tocTokens.push(token);
|
||||
}
|
||||
});
|
||||
|
@ -23,11 +23,21 @@ function texMath(state, silent) {
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const endMarkerPos = state.src.indexOf(endMarker, startMathPos);
|
||||
if (endMarkerPos === -1) {
|
||||
return false;
|
||||
function getIndex(tempStartMathPos) {
|
||||
const tempEndMarkerPos = state.src.indexOf(endMarker, tempStartMathPos);
|
||||
if (tempEndMarkerPos === -1) {
|
||||
return tempEndMarkerPos;
|
||||
}
|
||||
if (state.src.charCodeAt(tempEndMarkerPos - 1) === 0x5C /* \ */) {
|
||||
if (state.src.length - 1 > tempEndMarkerPos) {
|
||||
return getIndex(tempEndMarkerPos + 1);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
return tempEndMarkerPos;
|
||||
}
|
||||
if (state.src.charCodeAt(endMarkerPos - 1) === 0x5C /* \ */) {
|
||||
const endMarkerPos = getIndex(startMathPos);
|
||||
if (endMarkerPos === -1) {
|
||||
return false;
|
||||
}
|
||||
const nextPos = endMarkerPos + endMarker.length;
|
||||
|
3
src/icons/ChatGpt.vue
Normal file
@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<svg class="icon" width="24px" height="24px" viewBox="140 140 520 520"><defs><linearGradient id="linear" x1="100%" y1="22%" x2="0%" y2="78%"><stop offset="0%" stop-color="rgb(131,211,231)"></stop><stop offset="2%" stop-color="rgb(127,203,229)"></stop><stop offset="25%" stop-color="rgb(86,115,217)"></stop><stop offset="49%" stop-color="rgb(105,80,190)"></stop><stop offset="98%" stop-color="rgb(197,59,119)"></stop><stop offset="100%" stop-color="rgb(197,59,119)"></stop></linearGradient></defs><path id="logo" d="m617.24 354a126.36 126.36 0 0 0 -10.86-103.79 127.8 127.8 0 0 0 -137.65-61.32 126.36 126.36 0 0 0 -95.31-42.49 127.81 127.81 0 0 0 -121.92 88.49 126.4 126.4 0 0 0 -84.5 61.3 127.82 127.82 0 0 0 15.72 149.86 126.36 126.36 0 0 0 10.86 103.79 127.81 127.81 0 0 0 137.65 61.32 126.36 126.36 0 0 0 95.31 42.49 127.81 127.81 0 0 0 121.96-88.54 126.4 126.4 0 0 0 84.5-61.3 127.82 127.82 0 0 0 -15.76-149.81zm-190.66 266.49a94.79 94.79 0 0 1 -60.85-22c.77-.42 2.12-1.16 3-1.7l101-58.34a16.42 16.42 0 0 0 8.3-14.37v-142.39l42.69 24.65a1.52 1.52 0 0 1 .83 1.17v117.92a95.18 95.18 0 0 1 -94.97 95.06zm-204.24-87.23a94.74 94.74 0 0 1 -11.34-63.7c.75.45 2.06 1.25 3 1.79l101 58.34a16.44 16.44 0 0 0 16.59 0l123.31-71.2v49.3a1.53 1.53 0 0 1 -.61 1.31l-102.1 58.95a95.16 95.16 0 0 1 -129.85-34.79zm-26.57-220.49a94.71 94.71 0 0 1 49.48-41.68c0 .87-.05 2.41-.05 3.48v116.68a16.41 16.41 0 0 0 8.29 14.36l123.31 71.19-42.69 24.65a1.53 1.53 0 0 1 -1.44.13l-102.11-59a95.16 95.16 0 0 1 -34.79-129.81zm350.74 81.62-123.31-71.2 42.69-24.64a1.53 1.53 0 0 1 1.44-.13l102.11 58.95a95.08 95.08 0 0 1 -14.69 171.55c0-.88 0-2.42 0-3.49v-116.68a16.4 16.4 0 0 0 -8.24-14.36zm42.49-63.95c-.75-.46-2.06-1.25-3-1.79l-101-58.34a16.46 16.46 0 0 0 -16.59 0l-123.31 71.2v-49.3a1.53 1.53 0 0 1 .61-1.31l102.1-58.9a95.07 95.07 0 0 1 141.19 98.44zm-267.11 87.87-42.7-24.65a1.52 1.52 0 0 1 -.83-1.17v-117.92a95.07 95.07 0 0 1 155.9-73c-.77.42-2.11 1.16-3 1.7l-101 58.34a16.41 16.41 0 0 0 -8.3 14.36zm23.19-50 54.92-31.72 54.92 31.7v63.42l-54.92 31.7-54.92-31.7z" fill="#202123"></path></svg>
|
||||
</template>
|
3
src/icons/Copy.vue
Normal file
@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<svg t="1669462755056" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6069" width="32" height="32"><path d="M704 202.666667a96 96 0 0 1 96 96v554.666666a96 96 0 0 1-96 96H213.333333A96 96 0 0 1 117.333333 853.333333V298.666667A96 96 0 0 1 213.333333 202.666667h490.666667z m0 64H213.333333A32 32 0 0 0 181.333333 298.666667v554.666666a32 32 0 0 0 32 32h490.666667a32 32 0 0 0 32-32V298.666667a32 32 0 0 0-32-32z" fill="#212121" p-id="6070"></path><path d="M277.333333 362.666667m32 0l298.666667 0q32 0 32 32l0 0q0 32-32 32l-298.666667 0q-32 0-32-32l0 0q0-32 32-32Z" fill="#212121" p-id="6071"></path><path d="M277.333333 512m32 0l298.666667 0q32 0 32 32l0 0q0 32-32 32l-298.666667 0q-32 0-32-32l0 0q0-32 32-32Z" fill="#212121" p-id="6072"></path><path d="M277.333333 661.333333m32 0l170.666667 0q32 0 32 32l0 0q0 32-32 32l-170.666667 0q-32 0-32-32l0 0q0-32 32-32Z" fill="#212121" p-id="6073"></path><path d="M320 138.666667h512A32 32 0 0 1 864 170.666667v576a32 32 0 0 0 64 0V170.666667A96 96 0 0 0 832 74.666667H320a32 32 0 0 0 0 64z" fill="#212121" p-id="6074"></path></svg>
|
||||
</template>
|
3
src/icons/Ellipsis.vue
Normal file
@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<svg t="1669464773854" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8666" width="32" height="32"><path d="M621.714286 713.142857l0 109.714286q0 22.820571-16.018286 38.838857t-38.838857 16.018286l-109.714286 0q-22.820571 0-38.838857-16.018286t-16.018286-38.838857l0-109.714286q0-22.820571 16.018286-38.838857t38.838857-16.018286l109.714286 0q22.820571 0 38.838857 16.018286t16.018286 38.838857zM621.714286 420.571429l0 109.714286q0 22.820571-16.018286 38.838857t-38.838857 16.018286l-109.714286 0q-22.820571 0-38.838857-16.018286t-16.018286-38.838857l0-109.714286q0-22.820571 16.018286-38.838857t38.838857-16.018286l109.714286 0q22.820571 0 38.838857 16.018286t16.018286 38.838857zM621.714286 128l0 109.714286q0 22.820571-16.018286 38.838857t-38.838857 16.018286l-109.714286 0q-22.820571 0-38.838857-16.018286t-16.018286-38.838857l0-109.714286q0-22.820571 16.018286-38.838857t38.838857-16.018286l109.714286 0q22.820571 0 38.838857 16.018286t16.018286 38.838857z" p-id="8667"></path></svg>
|
||||
</template>
|
3
src/icons/FindReplace.vue
Normal file
@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<svg t="1664799557150" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8995" width="32" height="32"><path d="M781.702095 712.752762l208.847238 208.798476-68.998095 68.998095-208.798476-208.847238A436.906667 436.906667 0 0 1 438.857143 877.714286c-242.249143 0-438.857143-196.608-438.857143-438.857143s196.608-438.857143 438.857143-438.857143 438.857143 196.608 438.857143 438.857143a436.906667 436.906667 0 0 1-96.012191 273.895619zM714.800762 341.333333A292.571429 292.571429 0 0 0 438.857143 146.285714C277.211429 146.285714 146.285714 277.211429 146.285714 438.857143h97.52381a195.096381 195.096381 0 0 1 288.182857-171.398095L487.619048 341.333333h227.181714zM731.428571 438.857143h-97.523809a195.096381 195.096381 0 0 1-288.182857 171.398095L390.095238 536.380952H162.913524A292.571429 292.571429 0 0 0 438.857143 731.428571c161.645714 0 292.571429-130.925714 292.571428-292.571428z" p-id="8996"></path></svg>
|
||||
</template>
|
@ -15,6 +15,7 @@ export default {
|
||||
return 'google-drive';
|
||||
case 'googlePhotos':
|
||||
return 'google-photos';
|
||||
case 'githubAppData':
|
||||
case 'githubWorkspace':
|
||||
return 'github';
|
||||
case 'gist':
|
||||
@ -29,7 +30,10 @@ export default {
|
||||
return 'couchdb';
|
||||
case 'giteeAppData':
|
||||
case 'giteeWorkspace':
|
||||
case 'giteegist':
|
||||
return 'gitee';
|
||||
case 'stackedit':
|
||||
return 'stackedit';
|
||||
default:
|
||||
return this.providerId;
|
||||
}
|
||||
|
@ -1,3 +1,3 @@
|
||||
<template>
|
||||
<svg t="1660710445280" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2419" width="32" height="32"><path d="M576 672a192 192 0 0 1 192-192 182.72 182.72 0 0 1 48 6.4v-106.56a48 48 0 0 0-13.12-32L565.76 96a48 48 0 0 0-34.88-15.04H128A48 48 0 0 0 80 128v768A48 48 0 0 0 128 944h640a48 48 0 0 0 48-48v-38.4a184.64 184.64 0 0 1-48 6.4 192 192 0 0 1-192-192z m-64-326.72V112l272 272h-233.28A38.72 38.72 0 0 1 512 345.28z" p-id="2420"></path><path d="M957.76 774.4l-71.36-53.76A128 128 0 1 0 768 800a125.76 125.76 0 0 0 79.36-28.48l72 54.08zM768 736a64 64 0 1 1 64-64 64 64 0 0 1-64 64z" p-id="2421"></path></svg>
|
||||
<svg t="1664775684106" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6828" width="32" height="32"><path d="M694.857143 475.428571q0-105.714286-75.142857-180.857143t-180.857143-75.142857-180.857143 75.142857-75.142857 180.857143 75.142857 180.857143 180.857143 75.142857 180.857143-75.142857 75.142857-180.857143zm292.571429 475.428571q0 29.714286-21.714286 51.428571t-51.428571 21.714286q-30.857143 0-51.428571-21.714286l-196-195.428571q-102.285714 70.857143-228 70.857143-81.714286 0-156.285714-31.714286t-128.571429-85.714286-85.714286-128.571429-31.714286-156.285714 31.714286-156.285714 85.714286-128.571429 128.571429-85.714286 156.285714-31.714286 156.285714 31.714286 128.571429 85.714286 85.714286 128.571429 31.714286 156.285714q0 125.714286-70.857143 228l196 196q21.142857 21.142857 21.142857 51.428571z" p-id="6829"></path></svg>
|
||||
</template>
|
||||
|
3
src/icons/SelectTheme.vue
Normal file
@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<svg t="1664776491043" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7365" width="32" height="32"><path d="M384.096 222.4L238.08 363.072l590.4 606.048 155.584-173.92-599.968-572.8z m442.368 652.96L328.832 364.512l55.456-53.408L895.072 798.72l-68.608 76.64zM205.312 321.856l140.384-134.08L286.304 129.6c-36.384-35.552-98.944-35.264-134.88 0.608a96.384 96.384 0 0 0-1.184 134.88l55.072 56.768zM416 64h64v64h-64zM320 672h64v64h-64zM32 352h64v64H32zM232.672 492.192l-60.48-20.864-16.512 47.776-48.768-15.136-18.944 61.088 46.848 14.56-15.488 44.928 60.48 20.864 16.16-46.784 45.824 14.208 18.976-61.12-43.904-13.632zM632.16 277.472l61.312 18.368 14.72-49.152 45.6 14.144 18.976-61.088-46.176-14.368 14.048-46.848-61.312-18.368-13.888 46.24-46.496-14.432-19.008 61.088 47.136 14.656z" p-id="7366"></path></svg>
|
||||
</template>
|
3
src/icons/Share.vue
Normal file
@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<svg t="1680140298859" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2766" width="32" height="32"><path d="M769.14815 670.390403c-44.430932 0-84.182284 19.999496-110.768803 51.471278L389.219117 565.736878c6.597255-16.571421 10.228969-34.653241 10.228969-53.594639 0-17.006326-2.940982-33.332153-8.320503-48.497551l270.88143-157.119457c26.511817 29.069059 64.702628 47.312562 107.138112 47.312562 80.055291 0 144.95337-64.899102 144.95337-144.95337 0-80.055291-64.898079-144.954393-144.95337-144.954393s-144.95337 64.899102-144.95337 144.954393c0 15.991206 2.600221 31.386848 7.382131 45.776579L359.655801 412.377048c-26.417673-27.833929-63.756069-45.181015-105.161085-45.181015-80.055291 0-144.954393 64.890916-144.954393 144.946206 0 80.055291 64.898079 144.967696 144.954393 144.967696 39.409568 0 75.128071-15.741519 101.256148-41.24845l274.172383 159.024853c-3.725858 12.8384-5.729491 26.409486-5.729491 40.457434 0 80.0645 64.898079 144.954393 144.95337 144.954393s144.95337-64.889893 144.95337-144.954393C914.101519 735.297692 849.20344 670.390403 769.14815 670.390403z" p-id="2767"></path></svg>
|
||||
</template>
|
5
src/icons/SyncAuto.vue
Normal file
@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24">
|
||||
<path d="M 12 18 C 8.69 18 6 15.31 6 12 C 6 11 6.25 10.03 6.7 9.2 L 5.24 7.74 C 4.46 8.97 4 10.43 4 12 C 4 16.42 7.58 20 12 20 V 23 L 16 19 L 12 15 M 12 4 V 1 L 8 5 L 12 9 V 6 C 15.31 6 18 8.69 18 12 C 18 13 17.75 13.97 17.3 14.8 L 18.76 16.26 C 19.54 15.03 20 13.57 20 12 C 20 7.58 16.42 4 12 4 Z M 11 8 L 11 13 L 16 13 L 16 11 L 13 11 L 13 8 L 11 8 Z" />
|
||||
</svg>
|
||||
</template>
|
5
src/icons/SyncStop.vue
Normal file
@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24">
|
||||
<path d="M 12 18 C 8.69 18 6 15.31 6 12 C 6 11 6.25 10.03 6.7 9.2 L 5.24 7.74 C 4.46 8.97 4 10.43 4 12 C 4 16.42 7.58 20 12 20 V 23 L 16 19 L 12 15 M 12 4 V 1 L 8 5 L 12 9 V 6 C 15.31 6 18 8.69 18 12 C 18 13 17.75 13.97 17.3 14.8 L 18.76 16.26 C 19.54 15.03 20 13.57 20 12 C 20 7.58 16.42 4 12 4 Z M 9 9 L 9 15 L 11 15 L 11 9 L 9 9 Z M 13 9 L 13 15 L 15 15 L 15 9 L 13 9 Z" />
|
||||
</svg>
|
||||
</template>
|
@ -30,6 +30,8 @@ import Login from './Login';
|
||||
import Logout from './Logout';
|
||||
import Sync from './Sync';
|
||||
import SyncOff from './SyncOff';
|
||||
import SyncAuto from './SyncAuto';
|
||||
import SyncStop from './SyncStop';
|
||||
import Upload from './Upload';
|
||||
import ViewList from './ViewList';
|
||||
import Download from './Download';
|
||||
@ -57,6 +59,13 @@ import Key from './Key';
|
||||
import DotsHorizontal from './DotsHorizontal';
|
||||
import Seal from './Seal';
|
||||
import SwitchTheme from './SwitchTheme';
|
||||
import Search from './Search';
|
||||
import FindReplace from './FindReplace';
|
||||
import SelectTheme from './SelectTheme';
|
||||
import Copy from './Copy';
|
||||
import Ellipsis from './Ellipsis';
|
||||
import Share from './Share';
|
||||
import ChatGpt from './ChatGpt';
|
||||
|
||||
Vue.component('iconProvider', Provider);
|
||||
Vue.component('iconFormatBold', FormatBold);
|
||||
@ -89,6 +98,8 @@ Vue.component('iconLogin', Login);
|
||||
Vue.component('iconLogout', Logout);
|
||||
Vue.component('iconSync', Sync);
|
||||
Vue.component('iconSyncOff', SyncOff);
|
||||
Vue.component('iconSyncAuto', SyncAuto);
|
||||
Vue.component('iconSyncStop', SyncStop);
|
||||
Vue.component('iconUpload', Upload);
|
||||
Vue.component('iconViewList', ViewList);
|
||||
Vue.component('iconDownload', Download);
|
||||
@ -116,3 +127,10 @@ Vue.component('iconKey', Key);
|
||||
Vue.component('iconDotsHorizontal', DotsHorizontal);
|
||||
Vue.component('iconSeal', Seal);
|
||||
Vue.component('iconSwitchTheme', SwitchTheme);
|
||||
Vue.component('iconSearch', Search);
|
||||
Vue.component('iconFindReplace', FindReplace);
|
||||
Vue.component('iconSelectTheme', SelectTheme);
|
||||
Vue.component('iconCopy', Copy);
|
||||
Vue.component('iconEllipsis', Ellipsis);
|
||||
Vue.component('iconShare', Share);
|
||||
Vue.component('iconChatGpt', ChatGpt);
|
||||
|
@ -6,41 +6,43 @@ var util = {},
|
||||
|
||||
var defaultsStrings = {
|
||||
bold: "Strong <strong> Ctrl/Cmd+B",
|
||||
boldexample: "strong text",
|
||||
boldexample: "加粗文本",
|
||||
|
||||
italic: "Emphasis <em> Ctrl/Cmd+I",
|
||||
italicexample: "emphasized text",
|
||||
italicexample: "强调文本",
|
||||
|
||||
strikethrough: "Strikethrough <s> Ctrl/Cmd+I",
|
||||
strikethroughexample: "strikethrough text",
|
||||
strikethroughexample: "删除线文本",
|
||||
|
||||
link: "Hyperlink <a> Ctrl/Cmd+L",
|
||||
linkdescription: "enter link description here",
|
||||
linkdescription: "这里输入链接描述",
|
||||
linkdialog: "<p><b>Insert Hyperlink</b></p><p>http://example.com/ \"optional title\"</p>",
|
||||
|
||||
quote: "Blockquote <blockquote> Ctrl/Cmd+Q",
|
||||
quoteexample: "Blockquote",
|
||||
quoteexample: "块引用",
|
||||
|
||||
code: "Code Sample <pre><code> Ctrl/Cmd+K",
|
||||
codeexample: "enter code here",
|
||||
codeexample: "这里输入代码",
|
||||
|
||||
image: "Image <img> Ctrl/Cmd+G",
|
||||
imagedescription: "",
|
||||
imagedescription: "输入图片说明",
|
||||
imagedialog: "<p><b>Insert Image</b></p><p>http://example.com/images/diagram.jpg \"optional title\"<br><br>Need <a href='http://www.google.com/search?q=free+image+hosting' target='_blank'>free image hosting?</a></p>",
|
||||
|
||||
olist: "Numbered List <ol> Ctrl/Cmd+O",
|
||||
ulist: "Bulleted List <ul> Ctrl/Cmd+U",
|
||||
litem: "List item",
|
||||
litem: "这里是列表文本",
|
||||
|
||||
heading: "Heading <h1>/<h2> Ctrl/Cmd+H",
|
||||
headingexample: "Heading",
|
||||
headingexample: "标题",
|
||||
|
||||
hr: "Horizontal Rule <hr> Ctrl/Cmd+R",
|
||||
|
||||
undo: "Undo - Ctrl/Cmd+Z",
|
||||
redo: "Redo - Ctrl/Cmd+Y",
|
||||
|
||||
help: "Markdown Editing Help"
|
||||
help: "Markdown Editing Help",
|
||||
|
||||
formulaexample: "这里输入Latex表达式",
|
||||
};
|
||||
|
||||
// options, if given, can have the following properties:
|
||||
@ -120,11 +122,14 @@ function Pagedown(options) {
|
||||
hooks.addNoop("onPreviewRefresh"); // called with no arguments after the preview has been refreshed
|
||||
hooks.addNoop("postBlockquoteCreation"); // called with the user's selection *after* the blockquote was created; should return the actual to-be-inserted text
|
||||
hooks.addFalse("insertImageDialog");
|
||||
hooks.addFalse("insertChatGptDialog");
|
||||
/* called with one parameter: a callback to be called with the URL of the image. If the application creates
|
||||
* its own image insertion dialog, this hook should return true, and the callback should be called with the chosen
|
||||
* image url (or null if the user cancelled). If this hook returns false, the default dialog will be used.
|
||||
*/
|
||||
hooks.addFalse("insertLinkDialog");
|
||||
// 插入图片占位字符
|
||||
hooks.addFalse("insertImageUploading");
|
||||
|
||||
var that = this,
|
||||
input;
|
||||
@ -463,6 +468,8 @@ function UIManager(input, commandManager) {
|
||||
buttons.bold = bindCommand("doBold");
|
||||
buttons.italic = bindCommand("doItalic");
|
||||
buttons.strikethrough = bindCommand("doStrikethrough");
|
||||
buttons.inlineformula = bindCommand("doInlinkeFormula");
|
||||
buttons.imageUploading = bindCommand("doImageUploading");
|
||||
buttons.link = bindCommand(function (chunk, postProcessing) {
|
||||
return this.doLinkOrImage(chunk, postProcessing, false);
|
||||
});
|
||||
@ -471,6 +478,7 @@ function UIManager(input, commandManager) {
|
||||
buttons.image = bindCommand(function (chunk, postProcessing) {
|
||||
return this.doLinkOrImage(chunk, postProcessing, true);
|
||||
});
|
||||
buttons.chatgpt = bindCommand("doChatGpt");
|
||||
buttons.olist = bindCommand(function (chunk, postProcessing) {
|
||||
this.doList(chunk, postProcessing, true);
|
||||
});
|
||||
@ -615,6 +623,60 @@ commandProto.doStrikethrough = function (chunk, postProcessing) {
|
||||
return;
|
||||
};
|
||||
|
||||
commandProto.doInlinkeFormula = function (chunk, postProcessing) {
|
||||
|
||||
// Get rid of whitespace and fixup newlines.
|
||||
chunk.trimWhitespace();
|
||||
chunk.selection = chunk.selection.replace(/\n{2,}/g, "\n");
|
||||
|
||||
// Look for stars before and after. Is the chunk already marked up?
|
||||
// note that these regex matches cannot fail
|
||||
var starsBefore = /(\$*$)/.exec(chunk.before)[0];
|
||||
var starsAfter = /(^\$*)/.exec(chunk.after)[0];
|
||||
|
||||
var prevStars = Math.min(starsBefore.length, starsAfter.length);
|
||||
|
||||
var nStars = 2;
|
||||
|
||||
// Remove stars if we have to since the button acts as a toggle.
|
||||
if ((prevStars >= nStars) && (prevStars != 2 || nStars != 1)) {
|
||||
chunk.before = chunk.before.replace(re("[\$]{" + nStars + "}$", ""), "");
|
||||
chunk.after = chunk.after.replace(re("^[\$]{" + nStars + "}", ""), "");
|
||||
} else if (!chunk.selection && starsAfter) {
|
||||
// It's not really clear why this code is necessary. It just moves
|
||||
// some arbitrary stuff around.
|
||||
chunk.after = chunk.after.replace(/^(\$*)/, "");
|
||||
chunk.before = chunk.before.replace(/(\s?)$/, "");
|
||||
var whitespace = re.$1;
|
||||
chunk.before = chunk.before + starsAfter + whitespace;
|
||||
} else {
|
||||
|
||||
// In most cases, if you don't have any selected text and click the button
|
||||
// you'll get a selected, marked up region with the default text inserted.
|
||||
if (!chunk.selection && !starsAfter) {
|
||||
chunk.selection = this.getString("formulaexample");
|
||||
}
|
||||
|
||||
// Add the true markup.
|
||||
var markup = "$"; // shouldn't the test be = ?
|
||||
chunk.before = chunk.before + markup;
|
||||
chunk.after = markup + chunk.after;
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
commandProto.doImageUploading = function (chunk, postProcessing) {
|
||||
var enteredCallback = function (imgId) {
|
||||
if (imgId !== null) {
|
||||
chunk.before = `${chunk.before}[图片上传中...(image-${imgId})]`;
|
||||
chunk.selection = '';
|
||||
}
|
||||
postProcessing();
|
||||
};
|
||||
this.hooks.insertImageUploading(enteredCallback);
|
||||
}
|
||||
|
||||
commandProto.stripLinkDefs = function (text, defsToAdd) {
|
||||
|
||||
text = text.replace(/^[ ]{0,3}\[(\d+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|$)/gm,
|
||||
@ -786,6 +848,17 @@ commandProto.doLinkOrImage = function (chunk, postProcessing, isImage) {
|
||||
}
|
||||
};
|
||||
|
||||
commandProto.doChatGpt = function (chunk, postProcessing) {
|
||||
var enteredCallback = function (content) {
|
||||
if (content !== null) {
|
||||
chunk.before = `${chunk.before}${content}`;
|
||||
chunk.selection = '';
|
||||
}
|
||||
postProcessing();
|
||||
};
|
||||
this.hooks.insertChatGptDialog(enteredCallback);
|
||||
};
|
||||
|
||||
// When making a list, hitting shift-enter will put your cursor on the next line
|
||||
// at the current indent level.
|
||||
commandProto.doAutoindent = function (chunk) {
|
||||
@ -983,37 +1056,15 @@ commandProto.doCode = function (chunk) {
|
||||
// Use 'four space' markdown if the selection is on its own
|
||||
// line or is multiline.
|
||||
if ((!hasTextAfter && !hasTextBefore) || /\n/.test(chunk.selection)) {
|
||||
|
||||
chunk.before = chunk.before.replace(/[ ]{4}$/,
|
||||
function (totalMatch) {
|
||||
chunk.selection = totalMatch + chunk.selection;
|
||||
return "";
|
||||
});
|
||||
|
||||
var nLinesBack = 1;
|
||||
var nLinesForward = 1;
|
||||
|
||||
if (/(\n|^)(\t|[ ]{4,}).*\n$/.test(chunk.before)) {
|
||||
nLinesBack = 0;
|
||||
}
|
||||
if (/^\n(\t|[ ]{4,})/.test(chunk.after)) {
|
||||
nLinesForward = 0;
|
||||
}
|
||||
|
||||
chunk.skipLines(nLinesBack, nLinesForward);
|
||||
|
||||
if (!chunk.selection) {
|
||||
chunk.startTag = " ";
|
||||
chunk.selection = this.getString("codeexample");
|
||||
if (/[\n]+```\n$/.test(chunk.before) && /^\n```[ ]*\n/.test(chunk.after)) {
|
||||
chunk.before = chunk.before.replace(/```\n$/, "");
|
||||
chunk.after = chunk.after.replace(/^\n```/, "");
|
||||
} else {
|
||||
if (/^[ ]{0,3}\S/m.test(chunk.selection)) {
|
||||
if (/\n/.test(chunk.selection))
|
||||
chunk.selection = chunk.selection.replace(/^/gm, " ");
|
||||
else // if it's not multiline, do not select the four added spaces; this is more consistent with the doList behavior
|
||||
chunk.before += " ";
|
||||
} else {
|
||||
chunk.selection = chunk.selection.replace(/^(?:[ ]{4}|[ ]{0,3}\t)/gm, "");
|
||||
}
|
||||
chunk.before += '```\n';
|
||||
chunk.after = '\n```' + chunk.after;
|
||||
}
|
||||
if (!chunk.selection) {
|
||||
chunk.selection = this.getString("codeexample");
|
||||
}
|
||||
} else {
|
||||
// Use backticks (`) to delimit the code block.
|
||||
|
42
src/services/chatGptSvc.js
Normal file
@ -0,0 +1,42 @@
|
||||
import store from '../store';
|
||||
|
||||
export default {
|
||||
chat({ content }, callback) {
|
||||
const xhr = new XMLHttpRequest();
|
||||
const url = 'https://api.openai-proxy.com/v1/chat/completions';
|
||||
xhr.open('POST', url);
|
||||
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||
xhr.setRequestHeader('Authorization', `Bearer ${window.my_api_key}`);
|
||||
xhr.send(JSON.stringify({
|
||||
model: 'gpt-3.5-turbo',
|
||||
max_tokens: 3000,
|
||||
top_p: 0,
|
||||
temperature: 0.9,
|
||||
frequency_penalty: 0,
|
||||
presence_penalty: 0,
|
||||
messages: [{ role: 'user', content }],
|
||||
stream: true,
|
||||
}));
|
||||
let lastRespLen = 0;
|
||||
xhr.onprogress = () => {
|
||||
const responseText = xhr.response.substr(lastRespLen);
|
||||
lastRespLen = xhr.response.length;
|
||||
responseText.split('\n\n')
|
||||
.filter(l => l.length > 0)
|
||||
.forEach((text) => {
|
||||
const item = text.substr(6);
|
||||
if (item === '[DONE]') {
|
||||
callback({ done: true });
|
||||
} else {
|
||||
const data = JSON.parse(item);
|
||||
callback({ content: data.choices[0].delta.content });
|
||||
}
|
||||
});
|
||||
};
|
||||
xhr.onerror = () => {
|
||||
store.dispatch('notification/error', 'ChatGPT接口请求异常!');
|
||||
callback({ error: 'ChatGPT接口请求异常!' });
|
||||
};
|
||||
return xhr;
|
||||
},
|
||||
};
|
@ -186,6 +186,8 @@ function mergeContent(serverContent, clientContent, lastMergedContent = {}) {
|
||||
clientContent.comments,
|
||||
lastMergedContent.comments,
|
||||
),
|
||||
// 服务端和本地都变更了
|
||||
mergeFlag: isServerTextChanges && isClientTextChanges,
|
||||
};
|
||||
restoreDiscussionOffsets(result, markerKeys);
|
||||
return result;
|
||||
|
@ -2,6 +2,7 @@ import Vue from 'vue';
|
||||
import DiffMatchPatch from 'diff-match-patch';
|
||||
import Prism from 'prismjs';
|
||||
import markdownItPandocRenderer from 'markdown-it-pandoc-renderer';
|
||||
import md5 from 'js-md5';
|
||||
import cledit from './editor/cledit';
|
||||
import pagedown from '../libs/pagedown';
|
||||
import htmlSanitizer from '../libs/htmlSanitizer';
|
||||
@ -13,6 +14,9 @@ import editorSvcDiscussions from './editor/editorSvcDiscussions';
|
||||
import editorSvcUtils from './editor/editorSvcUtils';
|
||||
import utils from './utils';
|
||||
import store from '../store';
|
||||
import syncSvc from './syncSvc';
|
||||
import constants from '../data/constants';
|
||||
import localDbSvc from './localDbSvc';
|
||||
|
||||
const allowDebounce = (action, wait) => {
|
||||
let timeoutId;
|
||||
@ -40,6 +44,33 @@ class SectionDesc {
|
||||
}
|
||||
}
|
||||
|
||||
const pathUrlMap = Object.create(null);
|
||||
|
||||
const getImgUrl = async (uri) => {
|
||||
if (uri.indexOf('http://') !== 0 && uri.indexOf('https://') !== 0) {
|
||||
const currDirNode = store.getters['explorer/selectedNodeFolder'];
|
||||
const absoluteImgPath = utils.getAbsoluteFilePath(currDirNode, uri);
|
||||
if (pathUrlMap[absoluteImgPath]) {
|
||||
return pathUrlMap[absoluteImgPath];
|
||||
}
|
||||
const md5Id = md5(absoluteImgPath);
|
||||
let imgItem = await localDbSvc.getImgItem(md5Id);
|
||||
if (!imgItem) {
|
||||
await syncSvc.syncImg(absoluteImgPath);
|
||||
imgItem = await localDbSvc.getImgItem(md5Id);
|
||||
}
|
||||
if (imgItem) {
|
||||
// imgItem 如果不存在 则加载 TODO
|
||||
const imgFile = utils.base64ToBlob(imgItem.content, uri);
|
||||
const url = URL.createObjectURL(imgFile);
|
||||
pathUrlMap[absoluteImgPath] = url;
|
||||
return url;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
return uri;
|
||||
};
|
||||
|
||||
// Use a vue instance as an event bus
|
||||
const editorSvc = Object.assign(new Vue(), editorSvcDiscussions, editorSvcUtils, {
|
||||
// Elements
|
||||
@ -172,11 +203,28 @@ const editorSvc = Object.assign(new Vue(), editorSvcDiscussions, editorSvcUtils,
|
||||
this.previewElt.appendChild(sectionPreviewElt);
|
||||
}
|
||||
extensionSvc.sectionPreview(sectionPreviewElt, this.options, true);
|
||||
const imgs = Array.prototype.slice.call(sectionPreviewElt.getElementsByTagName('img')).map((imgElt) => {
|
||||
if (imgElt.src.indexOf(constants.origin) >= 0) {
|
||||
const uri = decodeURIComponent(imgElt.attributes.src.nodeValue);
|
||||
imgElt.removeAttribute('src');
|
||||
return { imgElt, uri };
|
||||
}
|
||||
return { imgElt };
|
||||
});
|
||||
loadingImages = [
|
||||
...loadingImages,
|
||||
...Array.prototype.slice.call(sectionPreviewElt.getElementsByTagName('img')),
|
||||
...imgs,
|
||||
];
|
||||
|
||||
Array.prototype.slice.call(sectionPreviewElt.getElementsByTagName('a')).forEach((aElt) => {
|
||||
const url = aElt.attributes && aElt.attributes.href && aElt.attributes.href.nodeValue;
|
||||
if (!url || url.indexOf('http://') >= 0 || url.indexOf('https://') >= 0 || url.indexOf('#') >= 0) {
|
||||
return;
|
||||
}
|
||||
aElt.href = 'javascript:void(0);'; // eslint-disable-line no-script-url
|
||||
aElt.setAttribute('onclick', `window.viewFileByPath('${utils.decodeUrlPath(url)}')`);
|
||||
});
|
||||
|
||||
// Create TOC section element
|
||||
sectionTocElt = document.createElement('div');
|
||||
sectionTocElt.className = 'cl-toc-section';
|
||||
@ -185,6 +233,21 @@ const editorSvc = Object.assign(new Vue(), editorSvcDiscussions, editorSvcUtils,
|
||||
const clonedElt = headingElt.cloneNode(true);
|
||||
clonedElt.removeAttribute('id');
|
||||
sectionTocElt.appendChild(clonedElt);
|
||||
// 创建一个新的 <span> 元素
|
||||
const contentElt = document.createElement('span');
|
||||
contentElt.className = 'content';
|
||||
// 将原始内容移动到新的 <span> 元素中
|
||||
while (headingElt.firstChild) {
|
||||
contentElt.appendChild(headingElt.firstChild);
|
||||
}
|
||||
const prefixElt = document.createElement('span');
|
||||
prefixElt.className = 'prefix';
|
||||
headingElt.insertBefore(prefixElt, headingElt.firstChild);
|
||||
// 将新的 <span> 元素替换原始元素
|
||||
headingElt.appendChild(contentElt);
|
||||
const suffixElt = document.createElement('span');
|
||||
suffixElt.className = 'suffix';
|
||||
headingElt.appendChild(suffixElt);
|
||||
}
|
||||
if (insertBeforeTocElt) {
|
||||
this.tocElt.insertBefore(sectionTocElt, insertBeforeTocElt);
|
||||
@ -212,15 +275,22 @@ const editorSvc = Object.assign(new Vue(), editorSvcDiscussions, editorSvcUtils,
|
||||
this.makeTextToPreviewDiffs();
|
||||
|
||||
// Wait for images to load
|
||||
const loadedPromises = loadingImages.map(imgElt => new Promise((resolve) => {
|
||||
if (!imgElt.src) {
|
||||
const loadedPromises = loadingImages.map(it => new Promise((resolve, reject) => {
|
||||
if (!it.imgElt.src && it.uri) {
|
||||
getImgUrl(it.uri).then((newUrl) => {
|
||||
it.imgElt.src = newUrl;
|
||||
resolve();
|
||||
}, () => reject(new Error('加载当前空间图片出错')));
|
||||
return;
|
||||
}
|
||||
if (!it.imgElt.src) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
const img = new window.Image();
|
||||
img.onload = resolve;
|
||||
img.onerror = resolve;
|
||||
img.src = imgElt.src;
|
||||
img.src = it.imgElt.src;
|
||||
}));
|
||||
await Promise.all(loadedPromises);
|
||||
|
||||
@ -383,7 +453,17 @@ const editorSvc = Object.assign(new Vue(), editorSvcDiscussions, editorSvcUtils,
|
||||
});
|
||||
return true;
|
||||
});
|
||||
|
||||
this.pagedownEditor.hooks.set('insertChatGptDialog', (callback) => {
|
||||
store.dispatch('modal/open', {
|
||||
type: 'chatGpt',
|
||||
callback,
|
||||
});
|
||||
return true;
|
||||
});
|
||||
this.pagedownEditor.hooks.set('insertImageUploading', (callback) => {
|
||||
callback(store.getters['img/currImgId']);
|
||||
return true;
|
||||
});
|
||||
this.editorElt.parentNode.addEventListener('scroll', () => this.saveContentState(true));
|
||||
this.previewElt.parentNode.addEventListener('scroll', () => this.saveContentState(true));
|
||||
|
||||
@ -468,6 +548,7 @@ const editorSvc = Object.assign(new Vue(), editorSvcDiscussions, editorSvcUtils,
|
||||
let imgEltsToCache = [];
|
||||
if (store.getters['data/computedSettings'].editor.inlineImages) {
|
||||
this.clEditor.highlighter.on('sectionHighlighted', (section) => {
|
||||
const loadImgs = [];
|
||||
section.elt.getElementsByClassName('token img').cl_each((imgTokenElt) => {
|
||||
const srcElt = imgTokenElt.querySelector('.token.cl-src');
|
||||
if (srcElt) {
|
||||
@ -493,6 +574,10 @@ const editorSvc = Object.assign(new Vue(), editorSvcDiscussions, editorSvcUtils,
|
||||
}
|
||||
}
|
||||
imgEltsToCache.push(imgElt);
|
||||
if (imgElt.src.indexOf(origin) >= 0) {
|
||||
imgElt.removeAttribute('src');
|
||||
loadImgs.push({ imgElt, uri: decodeURIComponent(uri) });
|
||||
}
|
||||
}
|
||||
const imgTokenWrapper = document.createElement('span');
|
||||
imgTokenWrapper.className = 'token img-wrapper';
|
||||
@ -501,9 +586,18 @@ const editorSvc = Object.assign(new Vue(), editorSvcDiscussions, editorSvcUtils,
|
||||
imgTokenWrapper.appendChild(imgTokenElt);
|
||||
}
|
||||
});
|
||||
if (loadImgs.length) {
|
||||
// Wait for images to load
|
||||
const loadWorkspaceImg = loadImgs.map(it => new Promise((resolve, reject) => {
|
||||
getImgUrl(it.uri).then((newUrl) => {
|
||||
it.imgElt.src = newUrl;
|
||||
resolve();
|
||||
}, () => reject(new Error(`加载当前空间图片出错,uri:${it.uri}`)));
|
||||
}));
|
||||
Promise.all(loadWorkspaceImg).then();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.clEditor.highlighter.on('highlighted', () => {
|
||||
imgEltsToCache.forEach((imgElt) => {
|
||||
const cachedImgElt = getFromImgCache(imgElt);
|
||||
|
@ -1,3 +1,4 @@
|
||||
import md5 from 'js-md5';
|
||||
import FileSaver from 'file-saver';
|
||||
import TemplateWorker from 'worker-loader!./templateWorker.js'; // eslint-disable-line
|
||||
import localDbSvc from './localDbSvc';
|
||||
@ -34,6 +35,24 @@ function groupHeadings(headings, level = 1) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const getImgBase64 = async (uri) => {
|
||||
if (uri.indexOf('http://') !== 0 && uri.indexOf('https://') !== 0) {
|
||||
const currDirNode = store.getters['explorer/selectedNodeFolder'];
|
||||
const absoluteImgPath = utils.getAbsoluteFilePath(currDirNode, uri);
|
||||
const md5Id = md5(absoluteImgPath);
|
||||
const imgItem = await localDbSvc.getImgItem(md5Id);
|
||||
if (imgItem) {
|
||||
const potIdx = uri.lastIndexOf('.');
|
||||
const suffix = potIdx > -1 ? uri.substring(potIdx + 1) : 'png';
|
||||
const mime = `image/${suffix}`;
|
||||
return `data:${mime};base64,${imgItem.content}`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
return uri;
|
||||
};
|
||||
|
||||
|
||||
const containerElt = document.createElement('div');
|
||||
containerElt.className = 'hidden-rendering-container';
|
||||
document.body.appendChild(containerElt);
|
||||
@ -54,6 +73,13 @@ export default {
|
||||
const parsingCtx = markdownConversionSvc.parseSections(converter, content.text);
|
||||
const conversionCtx = markdownConversionSvc.convert(parsingCtx);
|
||||
const html = conversionCtx.htmlSectionList.map(htmlSanitizer.sanitizeHtml).join('');
|
||||
const colorThemeClass = `app--${store.getters['data/computedSettings'].colorTheme}`;
|
||||
const themeClass = `preview-theme--${store.state.theme.currPreviewTheme}`;
|
||||
let themeStyleContent = '';
|
||||
const themeStyleEle = document.getElementById(`preview-theme-${store.state.theme.currPreviewTheme}`);
|
||||
if (themeStyleEle) {
|
||||
themeStyleContent = themeStyleEle.innerText;
|
||||
}
|
||||
containerElt.innerHTML = html;
|
||||
extensionSvc.sectionPreview(containerElt, options);
|
||||
|
||||
@ -65,8 +91,48 @@ export default {
|
||||
wrapperElt.parentNode.removeChild(wrapperElt);
|
||||
});
|
||||
|
||||
// 替换相对路径图片为blob图片
|
||||
const imgs = Array.prototype.slice.call(containerElt.getElementsByTagName('img')).map((imgElt) => {
|
||||
let uri = imgElt.attributes && imgElt.attributes.src && imgElt.attributes.src.nodeValue;
|
||||
if (uri && uri.indexOf('http://') !== 0 && uri.indexOf('https://') !== 0) {
|
||||
uri = decodeURIComponent(uri);
|
||||
imgElt.removeAttribute('src');
|
||||
return { imgElt, uri };
|
||||
}
|
||||
return { imgElt };
|
||||
});
|
||||
const loadedPromises = imgs.map(it => new Promise((resolve, reject) => {
|
||||
if (!it.imgElt.src && it.uri) {
|
||||
getImgBase64(it.uri).then((newUrl) => {
|
||||
it.imgElt.src = newUrl;
|
||||
resolve();
|
||||
}, () => reject(new Error('加载当前空间图片出错')));
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
}));
|
||||
await Promise.all(loadedPromises);
|
||||
|
||||
// Make TOC
|
||||
const headings = containerElt.querySelectorAll('h1,h2,h3,h4,h5,h6').cl_map(headingElt => ({
|
||||
const allHeaders = containerElt.querySelectorAll('h1,h2,h3,h4,h5,h6');
|
||||
Array.prototype.slice.call(allHeaders).forEach((headingElt) => {
|
||||
// 创建一个新的 <span> 元素
|
||||
const contentElt = document.createElement('span');
|
||||
contentElt.className = 'content';
|
||||
// 将原始内容移动到新的 <span> 元素中
|
||||
while (headingElt.firstChild) {
|
||||
contentElt.appendChild(headingElt.firstChild);
|
||||
}
|
||||
const prefixElt = document.createElement('span');
|
||||
prefixElt.className = 'prefix';
|
||||
headingElt.insertBefore(prefixElt, headingElt.firstChild);
|
||||
// 将新的 <span> 元素替换原始元素
|
||||
headingElt.appendChild(contentElt);
|
||||
const suffixElt = document.createElement('span');
|
||||
suffixElt.className = 'suffix';
|
||||
headingElt.appendChild(suffixElt);
|
||||
});
|
||||
const headings = allHeaders.cl_map(headingElt => ({
|
||||
title: headingElt.textContent,
|
||||
anchor: headingElt.id,
|
||||
level: parseInt(headingElt.tagName.slice(1), 10),
|
||||
@ -83,6 +149,9 @@ export default {
|
||||
yamlProperties: content.properties,
|
||||
html: containerElt.innerHTML,
|
||||
toc,
|
||||
colorThemeClass,
|
||||
themeClass,
|
||||
themeStyleContent,
|
||||
},
|
||||
}],
|
||||
};
|
||||
|
94
src/services/imageSvc.js
Normal file
@ -0,0 +1,94 @@
|
||||
import md5 from 'js-md5';
|
||||
import store from '../store';
|
||||
import utils from './utils';
|
||||
import localDbSvc from './localDbSvc';
|
||||
import smmsHelper from '../services/providers/helpers/smmsHelper';
|
||||
import giteaHelper from '../services/providers/helpers/giteaHelper';
|
||||
import githubHelper from '../services/providers/helpers/githubHelper';
|
||||
import customHelper from '../services/providers/helpers/customHelper';
|
||||
|
||||
const getImagePath = (confPath, imgType) => {
|
||||
const time = new Date();
|
||||
const date = time.getDate();
|
||||
const month = time.getMonth() + 1;
|
||||
const year = time.getFullYear();
|
||||
const path = confPath.replace('{YYYY}', year).replace('{MM}', `0${month}`.slice(-2))
|
||||
.replace('{DD}', `0${date}`.slice(-2)).replace('{MDNAME}', store.getters['file/current'].name);
|
||||
return `${path}${path.endsWith('/') ? '' : '/'}${utils.uid()}.${imgType.split('/')[1]}`;
|
||||
};
|
||||
|
||||
export default {
|
||||
// 上传图片 返回图片链接
|
||||
// { url: 'http://xxxx', error: 'xxxxxx'}
|
||||
async updateImg(imgFile) {
|
||||
// 操作图片上传
|
||||
const currStorage = store.getters['img/getCheckedStorage'];
|
||||
if (!currStorage) {
|
||||
return { error: '暂无已选择的图床!' };
|
||||
}
|
||||
// 判断是否文档空间路径
|
||||
if (currStorage.type === 'workspace') {
|
||||
// 如果不是git仓库 则提示不支持
|
||||
if (!store.getters['workspace/currentWorkspaceIsGit']) {
|
||||
return { error: '暂无已选择的图床!' };
|
||||
}
|
||||
const path = getImagePath(currStorage.sub, imgFile.type);
|
||||
// 保存到indexeddb
|
||||
const base64 = await utils.encodeFiletoBase64(imgFile);
|
||||
const currDirNode = store.getters['explorer/selectedNodeFolder'];
|
||||
const absolutePath = utils.getAbsoluteFilePath(currDirNode, path);
|
||||
await localDbSvc.saveImg({
|
||||
id: md5(absolutePath),
|
||||
path: absolutePath,
|
||||
content: base64,
|
||||
});
|
||||
return { url: path.replaceAll(' ', '%20') };
|
||||
}
|
||||
if (!currStorage.provider) {
|
||||
return { error: '暂无已选择的图床!' };
|
||||
}
|
||||
const token = store.getters[`data/${currStorage.provider}TokensBySub`][currStorage.sub];
|
||||
if (!token) {
|
||||
return { error: '暂无已选择的图床!' };
|
||||
}
|
||||
let url = '';
|
||||
// token图床类型
|
||||
if (currStorage.type === 'token') {
|
||||
const helper = currStorage.provider === 'smms' ? smmsHelper : customHelper;
|
||||
url = await helper.uploadFile({
|
||||
token,
|
||||
file: imgFile,
|
||||
});
|
||||
} else if (currStorage.type === 'tokenRepo') { // git repo图床类型
|
||||
const checkStorages = token.imgStorages.filter(it => it.sid === currStorage.sid);
|
||||
if (!checkStorages || checkStorages.length === 0) {
|
||||
return { error: '暂无已选择的图床!' };
|
||||
}
|
||||
const checkStorage = checkStorages[0];
|
||||
const path = getImagePath(checkStorage.path, imgFile.type);
|
||||
if (currStorage.provider === 'gitea') {
|
||||
const result = await giteaHelper.uploadFile({
|
||||
token,
|
||||
projectId: checkStorage.repoUri,
|
||||
branch: checkStorage.branch,
|
||||
path,
|
||||
content: imgFile,
|
||||
isImg: true,
|
||||
});
|
||||
url = result.content.download_url;
|
||||
} else if (currStorage.provider === 'github') {
|
||||
const result = await githubHelper.uploadFile({
|
||||
token,
|
||||
owner: checkStorage.owner,
|
||||
repo: checkStorage.repo,
|
||||
branch: checkStorage.branch,
|
||||
path,
|
||||
content: imgFile,
|
||||
isImg: true,
|
||||
});
|
||||
url = result.content.download_url;
|
||||
}
|
||||
}
|
||||
return { url };
|
||||
},
|
||||
};
|