Compare commits

..

104 Commits

Author SHA1 Message Date
7a82ba2250 更新:README文件 2024-09-09 15:14:26 +08:00
445cfe69fc 更新:README文档 2024-09-09 14:50:22 +08:00
40e77fef15 更新gitee分享文件位置链接 2024-08-05 10:23:12 +08:00
348b5668fd 更新git后缀链接 2024-08-05 10:03:03 +08:00
60c751f340 更新站点icon图标 2024-08-04 17:36:02 +08:00
024706f20f 更新隐私政策 2024-08-04 17:35:16 +08:00
408618eceb 更新站点链接 2024-08-04 17:34:56 +08:00
9a7570b02f 更新首页信息 2024-08-04 17:34:01 +08:00
182c863eef 更新自述 2024-08-04 17:32:02 +08:00
xiaoqi.cxq
d422df5e42 去掉英文版入口 2024-03-22 16:14:05 +08:00
xiaoqi.cxq
91215e6db9 截断字符长度从25万改成1000万 2024-01-04 08:24:26 +08:00
xiaoqi.cxq
1286c42d4c 支持复制路径 2023-12-25 11:26:15 +08:00
xiaoqi.cxq
3467a6ee09 github作为主空间时的bug修复 2023-12-20 11:21:01 +08:00
xiaoqi.cxq
b4f4c71f85 快捷键会失效的问题修复 2023-10-20 13:50:33 +08:00
xiaoqi.cxq
39167fb193 主文档空间支持GitHub登录 2023-10-19 18:20:34 +08:00
xiaoqi.cxq
97b8d3c288 优化导出中的header生成 2023-10-18 10:09:46 +08:00
xiaoqi.cxq
b4c9407b06 优化预览header生成 2023-10-18 09:40:43 +08:00
xiaoqi.cxq
96ea8cd0db 避免undefined文件提交 2023-10-18 09:09:27 +08:00
xiaoqi.cxq
550bb2fd91 分享页面请求逻辑优化 2023-09-19 09:29:28 +08:00
xiaoqi.cxq
2092045b7f gitea publish bugfix 2023-09-08 14:40:22 +08:00
xiaoqi.cxq
12e4befa96 GitLab授权调整 2023-08-26 01:15:54 +08:00
xiaoqi.cxq
80e0e3bc99 修改chatgpt的api代理地址 2023-08-26 01:11:16 +08:00
xiaoqi.cxq
4747f91749 更改chatgpt接口地址 2023-08-08 13:25:43 +08:00
xiaoqi.cxq
e04fd5a911 chatgpt优化 2023-07-01 19:40:16 +08:00
xiaoqi.cxq
9cd27e274e 导出标题标签优化 2023-06-30 11:29:41 +08:00
xiaoqi.cxq
81cad7ee84 路径支持反斜杠 2023-06-30 11:28:26 +08:00
xiaoqi.cxq
81612deab7 标题锚点可以是数字开头 2023-06-30 11:28:00 +08:00
xiaoqi.cxq
282b546edc 修复h标签渲染包含图片锚点错误并图片渲染不了的问题 2023-06-30 09:40:52 +08:00
xiaoqi.cxq
1727be1eaf 增加Docker构建的脚本 2023-06-29 22:25:01 +08:00
xiaoqi.cxq
57931b9db2 渲染问题bugfix 2023-06-29 22:24:07 +08:00
xiaoqi.cxq
d175557ab9 图片加载bugfix 2023-06-29 21:49:16 +08:00
xiaoqi.cxq
8e12eaebd2 替换ChatGPT接口为其他免费接口 2023-06-29 21:49:02 +08:00
xiaoqi.cxq
90d887519d chatgpt调整 2023-06-13 18:32:49 +08:00
xiaoqi.cxq
c1232b59db chatgpt优化 2023-05-23 10:44:26 +08:00
xiaoqi.cxq
a40af9c545 更新隐私策略 2023-05-12 15:19:36 +08:00
xiaoqi.cxq
f3d827fef1 update google api scope 2023-05-09 11:37:12 +08:00
xiaoqi.cxq
92f2c4dee6 latex \$ bugfix 2023-04-11 11:28:09 +08:00
xiaoqi.cxq
74f25af839 删除非必要代码 2023-04-11 09:58:25 +08:00
xiaoqi.cxq
20d7a9d2db ChatGPT辅助写作窗口控制台报错bugfix 2023-04-11 09:30:15 +08:00
xiaoqi.cxq
64d493d692 优化文案 2023-04-10 19:57:33 +08:00
xiaoqi.cxq
eda517cd61 bugfix 2023-04-10 19:57:11 +08:00
xiaoqi.cxq
24635c54ed update readme 2023-04-10 14:53:39 +08:00
xiaoqi.cxq
87c37401ed update readme 2023-04-10 12:59:26 +08:00
xiaoqi.cxq
599d71b597 支持ChatGPT生成内容 2023-04-10 10:24:22 +08:00
xiaoqi.cxq
0e02822add 优化share 2023-04-09 21:35:55 +08:00
xiaoqi.cxq
1daa5afe39 update styles 2023-04-06 14:00:15 +08:00
xiaoqi.cxq
2e9e4b73f6 homepage update 2023-03-31 09:39:24 +08:00
xiaoqi.cxq
4243a41e31 update share.html 2023-03-30 18:21:22 +08:00
xiaoqi.cxq
ae828cfb56 支持分享功能 2023-03-30 15:56:24 +08:00
xiaoqi.cxq
58c9144612 更新文案 2023-02-27 08:29:31 +08:00
xiaoqi.cxq
1b8124f2a2 导出HTML、PDF支持带预览主题导出 2023-02-26 11:29:25 +08:00
xiaoqi.cxq
8713688b57 update readme 2023-02-24 09:37:28 +08:00
xiaoqi.cxq
e65c433f13 update readme 2023-02-24 09:22:50 +08:00
xiaoqi.cxq
b1691e0d4f Gitlab支持优化 2023-02-23 15:33:55 +08:00
xiaoqi.cxq
4d8ff0ea0c 代码块不拼写检查 2022-12-08 23:08:55 +08:00
xiaoqi.cxq
d757b48d99 update readme 2022-12-05 09:10:24 +08:00
xiaoqi.cxq
d927099b28 支持预览区主题 2022-12-04 21:40:54 +08:00
xiaoqi.cxq
9ebde2eb75 调整a标签跳转 2022-11-25 20:29:01 +08:00
xiaoqi.cxq
bda261a767 优化pdf导出 2022-11-25 20:12:51 +08:00
xiaoqi.cxq
9419865d76 导出pdf文案调整 2022-11-24 16:49:41 +08:00
xiaoqi.cxq
be9323c408 编辑器头部按钮显示与否支持配置 2022-11-24 09:06:07 +08:00
xiaoqi.cxq
a756acf27c update readme 2022-11-20 18:02:31 +08:00
xiaoqi.cxq
e731016e04 update version 2022-11-20 15:18:28 +08:00
xiaoqi.cxq
6cca063f8c update readme 2022-11-20 15:14:51 +08:00
xiaoqi.cxq
13b9528840 支持笔记之间双链 2022-11-20 15:11:31 +08:00
xiaoqi.cxq
31bec53520 校验在线的js改成外部的 2022-11-17 20:19:00 +08:00
xiaoqi.cxq
4e9acad585 当前文档空间上传图片优化 2022-11-13 15:16:46 +08:00
xiaoqi.cxq
5eb2b2e67a 更新判断在线与否的外部js 2022-11-12 17:17:07 +08:00
xiaoqi.cxq
2b45a94879 update readme 2022-11-12 11:51:50 +08:00
xiaoqi.cxq
5a30338e83 路径替换bugfix 2022-11-12 10:02:48 +08:00
xiaoqi.cxq
808891e47c 当前文档空间路径上传图片绝对路径计算bugfix,支持数学表达式输入快捷键 2022-11-11 17:01:41 +08:00
xiaoqi.cxq
d3193e1739 文案调整 2022-11-05 14:15:06 +08:00
xiaoqi.cxq
df91db5882 当前文档空间图片路径优化 2022-11-05 12:37:03 +08:00
xiaoqi.cxq
26e8979245 update readme 2022-10-31 12:57:12 +08:00
xiaoqi.cxq
dd78ec7b3a 存储图片路径为相对路径时显示的bugfix 2022-10-29 16:37:26 +08:00
xiaoqi.cxq
401c2787af update readme 2022-10-29 15:48:34 +08:00
xiaoqi.cxq
a4ab4b2da1 图片支持相对本地空间的路径存储 2022-10-29 15:46:57 +08:00
xiaoqi.cxq
e7450df251 粘贴拖拽图片体验优化 2022-10-22 15:43:07 +08:00
xiaoqi.cxq
058fcaa147 update katex version 2022-10-21 11:23:36 +08:00
xiaoqi.cxq
ed79c8cd49 解决文档空间链接复制授权登录无法进入的bug 2022-10-13 13:53:06 +08:00
xiaoqi.cxq
867315a19d 增加冲突自动合并提醒 2022-10-13 13:22:42 +08:00
xiaoqi.cxq
480875a5ec 调整首页 2022-10-10 18:11:25 +08:00
xiaoqi.cxq
440c5e93b8 update readme 2022-10-10 00:34:49 +08:00
xiaoqi.cxq
405e082651 update readme 2022-10-10 00:33:17 +08:00
xiaoqi.cxq
7335455185 update readme 2022-10-10 00:27:55 +08:00
xiaoqi.cxq
7da611b398 update readme 2022-10-10 00:14:33 +08:00
xiaoqi.cxq
554547af5a 编辑区域右上角图标支持隐藏 2022-10-09 18:41:51 +08:00
xiaoqi.cxq
380980d66f update gitea应用创建说明 2022-10-09 10:13:44 +08:00
xiaoqi.cxq
68f281c6e7 bugfix 2022-10-07 15:25:56 +08:00
xiaoqi.cxq
545f8da3cb 支持编辑区自定义主题/添加编辑区主题 2022-10-07 15:17:06 +08:00
xiaoqi.cxq
ee9bd1ab5a 编辑主题缓存时间调整为一天 2022-10-06 04:23:34 +08:00
xiaoqi.cxq
e7fa160383 主文档空间文档历史revision显示bugfix 2022-10-06 04:10:25 +08:00
xiaoqi.cxq
f71cef4d9f update readme 2022-10-06 03:49:25 +08:00
xiaoqi.cxq
347358f6bc 支持编辑区域主题选择 2022-10-06 03:45:51 +08:00
xiaoqi.cxq
8aff518e34 update readme 2022-10-02 00:24:01 +08:00
xiaoqi.cxq
21a3e59b5d gitea支持启动时指定clientId和接口地址等 2022-10-02 00:20:01 +08:00
xiaoqi.cxq
95d27a4a0a 文档版本显示提交信息 2022-09-23 23:18:15 +08:00
xiaoqi.cxq
b1ad58a121 update readme 2022-09-23 19:39:09 +08:00
xiaoqi.cxq
d51c19d6fd update version 2022-09-23 19:36:12 +08:00
xiaoqi.cxq
398784efc4 仓库支持关闭自动同步 2022-09-23 19:33:25 +08:00
xiaoqi.cxq
f020cb887b update readme 2022-09-10 20:54:39 +08:00
xiaoqi.cxq
6fa7992685 发布支持自定义提交信息 2022-09-10 19:48:28 +08:00
xiaoqi.cxq
a6493a41da 修复暗色主题下find高亮时光标看不清的问题 2022-09-08 00:12:47 +08:00
xiaoqi.cxq
f5b4627083 toc支持小写占位符 2022-09-04 18:28:25 +08:00
164 changed files with 8066 additions and 1086 deletions

View File

@ -4,3 +4,6 @@ dist
.history .history
images images
docs docs
Dockerfile
README.md
build.sh

View File

@ -1,6 +1,5 @@
FROM mafgwo/wkhtmltopdf-nodejs:11.15.0 FROM mafgwo/wkhtmltopdf-nodejs:11.15.0
RUN mkdir -p /opt/stackedit
WORKDIR /opt/stackedit WORKDIR /opt/stackedit
COPY package*json /opt/stackedit/ COPY package*json /opt/stackedit/

105
README.md
View File

@ -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) ## 截图
**示例截图-暗色主题** **亮暗主题切换、编辑主题切换**
![](./images/dark.png) ![](./images/theme.gif)
**示例截图-亮色主题** **支持的文档空间**
![](./images/light.png)
**示例截图-支持的文档空间**
![](./images/workspace.png) ![](./images/workspace.png)
**示例截图-支持的图床** **拖拽粘贴上传图片**
![](./images/imageBed.png) ![](./images/uploadimg.gif)
**示例截图-支持文件搜索** **支持文档搜索**
![](./images/fileSearch.png) ![](./images/search.gif)
**ChatGPT集成协助写作**
![](./images/chatgpt.gif)
## 相比国外开源版本的区别: ## 相比国外开源版本的区别:
- 修复了Github授权登录问题 - 修复了Github授权登录问题
- 支持了Gitee仓库2022-05-25 - 支持了Gitee仓库2022-05-25
- 支持了Gitea仓库2022-05-25 - 支持了Gitea仓库2022-05-25
@ -35,16 +67,32 @@ StackEdit中文版的docker镜像地址[mafgwo/stackedit](https://hub.docker.
- 编辑与预览区域样式优化2022-08-10 - 编辑与预览区域样式优化2022-08-10
- 左边栏文件资源管理支持搜索文件2022-08-17 - 左边栏文件资源管理支持搜索文件2022-08-17
- 支持[TOC]目录2022-09-04 - 支持[TOC]目录2022-09-04
- 发布支持填写提交信息[针对Gitee、GitHub、Gitea、Gitlab]2022-09-10
- 支持文档空间关闭自动同步[针对Gitee、GitHub、Gitea、Gitlab]关闭后可自定义提交信息2022-09-23
- Gitea支持后端配置指定应用ID和Secret2022-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 - 不支持国内常用Gitee
- 强依赖GoogleDrive而Google Drive在国内不能正常访问 - 强依赖GoogleDrive而Google Drive在国内不能正常访问
## 部署说明 ## 部署说明
> 建议docker-compose方式部署其他部署方式如遇到问题欢迎提issue。 > 建议docker-compose方式部署其他部署方式如遇到问题欢迎提issue。
docker官方仓库下载太慢可以使用阿里云的镜像仓库镜像仓库地址registry.cn-hangzhou.aliyuncs.com/mafgwo/stackedit:【版本号】
`docker-compose.yml`如下: `docker-compose.yml`如下:
```yaml ```yaml
@ -65,6 +113,12 @@ services:
- GITEE_CLIENT_SECRET=【不需要支持则删掉】 - GITEE_CLIENT_SECRET=【不需要支持则删掉】
- GOOGLE_CLIENT_ID=【不需要支持则删掉】 - GOOGLE_CLIENT_ID=【不需要支持则删掉】
- GOOGLE_API_KEY=【不需要支持则删掉】 - GOOGLE_API_KEY=【不需要支持则删掉】
- GITEA_CLIENT_ID=【不需要支持则删掉】
- GITEA_CLIENT_SECRET=【不需要支持则删掉】
- GITEA_URL=【不需要支持则删掉】
- GITLAB_CLIENT_ID=【不需要支持则删掉】
- GITLAB_CLIENT_SECRET=【不需要支持则删掉】
- GITLAB_URL=【不需要支持则删掉】
ports: ports:
- 8080:8080/tcp - 8080:8080/tcp
network_mode: bridge network_mode: bridge
@ -72,6 +126,7 @@ services:
``` ```
docker-compose方式的启动或停止命令 docker-compose方式的启动或停止命令
```bash ```bash
# 在 docker-compose.yml 文件目录下 启动命令 # 在 docker-compose.yml 文件目录下 启动命令
docker-compose up -d docker-compose up -d
@ -96,21 +151,29 @@ docker run -itd --name stackedit \
-e GITEE_CLIENT_SECRET=【不需要支持则删掉】 \ -e GITEE_CLIENT_SECRET=【不需要支持则删掉】 \
-e GOOGLE_CLIENT_ID=【不需要支持则删掉】 \ -e GOOGLE_CLIENT_ID=【不需要支持则删掉】 \
-e GOOGLE_API_KEY=【不需要支持则删掉】 \ -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中央仓库找到最新版本】 mafgwo/stackedit:【docker中央仓库找到最新版本】
``` ```
## 如何创建三方平台应用 ## 如何创建三方平台应用
> 部署时如果需要支持Gitee或GitHub则需要自行到对应三方平台创建应用获取到应用ID和秘钥替换到以上的环境变量中再启动应用。 > 部署时如果需要支持Gitee或GitHub则需要自行到对应三方平台创建应用获取到应用ID和秘钥替换到以上的环境变量中再启动应用。
- Gitee的环境变量GITEE_CLIENT_ID、GITEE_CLIENT_SECRET**[如何创建Gitee应用](./docs/部署之Gitee应用创建.md)** - Gitee的环境变量GITEE_CLIENT_ID、GITEE_CLIENT_SECRET**[如何创建Gitee应用](./docs/部署之Gitee应用创建.md)**
- GitHub的环境变量GITHUB_CLIENT_ID、GITEE_CLIENT_SECRET**[如何创建GitHub应用](./docs/部署之GitHub应用创建.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版本 > 编译运行的nodejs版本选择11.15.0版本
```bash ```bash
@ -126,7 +189,3 @@ npm run build
# build for production and view the bundle analyzer report # build for production and view the bundle analyzer report
npm run build --report npm run build --report
``` ```
## 欢迎加群交流
关于StackEdit如果你有想法或者使用中遇到了问题可以提Issue如果需要快速得到反馈可以加QQ群如下加群后可直接@群主):
![](./images/qq.jpeg)

37
build.sh Normal file
View 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 "操作完成"

View File

@ -15,6 +15,11 @@ wordpressSecret: ""
paypalReceiverEmail: "" paypalReceiverEmail: ""
awsAccessKeyId: "" awsAccessKeyId: ""
awsSecretAccessKey: "" awsSecretAccessKey: ""
giteaClientId: ""
giteaClientSecret: ""
giteaUrl: ""
gitlabClientId: ""
gitlabUrl: ""
replicaCount: 1 replicaCount: 1

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 491 B

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 960 B

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -1,10 +1,10 @@
{ {
"name": "StackEdit中文版", "name": "StackEdit中文版",
"description": "支持Gitee仓库/粘贴图片自动上传的浏览器内 Markdown 编辑器", "description": "支持Gitee仓库/粘贴图片自动上传的浏览器内 Markdown 编辑器",
"version": "5.15.12", "version": "5.15.17",
"manifest_version": 2, "manifest_version": 2,
"container" : "GITEE", "container": "GITEE",
"api_console_project_id" : "241271498917", "api_console_project_id": "241271498917",
"icons": { "icons": {
"16": "icon-16.png", "16": "icon-16.png",
"32": "icon-32.png", "32": "icon-32.png",
@ -15,10 +15,10 @@
}, },
"app": { "app": {
"urls": [ "urls": [
"https://stackedit.cn/" "https://md.jonylee.top/"
], ],
"launch": { "launch": {
"web_url": "https://stackedit.cn/app" "web_url": "https://md.jonylee.top/app"
} }
}, },
"offline_enabled": true, "offline_enabled": true,

View File

@ -9,4 +9,10 @@ module.exports = merge(prodEnv, {
GITEE_CLIENT_ID: '"925ba7c78b85dec984f7877e4aca5cab10ae333c6d68e761bdb0b9dfb8f55672"', GITEE_CLIENT_ID: '"925ba7c78b85dec984f7877e4aca5cab10ae333c6d68e761bdb0b9dfb8f55672"',
GITEE_CLIENT_SECRET: '"f05731066e42d307339dc8ebbb037a103881dafc7207a359a393b87749f1c562"', GITEE_CLIENT_SECRET: '"f05731066e42d307339dc8ebbb037a103881dafc7207a359a393b87749f1c562"',
CLIENT_ID: '"thF3qCGLN39OtafjGnqHyj6n02WwE6xD"', 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"',
}) })

View 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` 查看帮助文档。

View File

@ -20,4 +20,16 @@
# Gitea跨域问题 # 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

BIN
images/search.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 KiB

BIN
images/theme.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 937 KiB

BIN
images/uploadimg.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 761 KiB

View File

@ -1,28 +1,31 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head>
<head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>StackEdit中文版</title> <title>Markdown编辑器-JonyLee的设计导航</title>
<link rel="canonical" href="https://stackedit.cn/app"> <link rel="canonical" href="https://md.jonylee.top">
<meta name="description" content="免费开源功能全面的Markdown编辑器。"> <meta name="description" content="StackEdit中文版免费开源功能全面的Markdown编辑器。">
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"> <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="mobile-web-app-capable" content="yes">
<meta name="apple-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"> <meta name="apple-mobile-web-app-status-bar-style" content="black">
<script> </head>
var _hmt = _hmt || [];
</script> <body>
</head>
<body>
<div id="app"></div> <div id="app"></div>
<!-- built files will be auto injected --> <!-- built files will be auto injected -->
<!-- baidu统计 -->
<script> <script>
var _hmt = _hmt || [];
(function() { (function() {
var hm = document.createElement("script"); var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?20a1e7a201b42702c49074c87a1f1035"; hm.src = "https://hm.baidu.com/hm.js?dad4b4383b13eedea1ab45ee323df1c3";
var s = document.getElementsByTagName("script")[0]; var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s); s.parentNode.insertBefore(hm, s);
})(); })();
</script> </script>
</body> <!-- baidu统计结束 -->
</body>
</html> </html>

237
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "stackedit", "name": "stackedit",
"version": "5.15.12", "version": "5.15.21",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -736,7 +736,8 @@
"abab": { "abab": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz",
"integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w==" "integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w==",
"dev": true
}, },
"abbrev": { "abbrev": {
"version": "1.1.1", "version": "1.1.1",
@ -763,7 +764,8 @@
"acorn": { "acorn": {
"version": "5.3.0", "version": "5.3.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-5.3.0.tgz", "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": { "acorn-dynamic-import": {
"version": "2.0.2", "version": "2.0.2",
@ -786,6 +788,7 @@
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.1.0.tgz", "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.1.0.tgz",
"integrity": "sha512-KjZwU26uG3u6eZcfGbTULzFcsoz6pegNKtHPksZPOUsiKo5bUmiBPa38FuHZ/Eun+XYh/JCCkS9AS3Lu4McQOQ==", "integrity": "sha512-KjZwU26uG3u6eZcfGbTULzFcsoz6pegNKtHPksZPOUsiKo5bUmiBPa38FuHZ/Eun+XYh/JCCkS9AS3Lu4McQOQ==",
"dev": true,
"requires": { "requires": {
"acorn": "^5.0.0" "acorn": "^5.0.0"
} }
@ -1021,7 +1024,8 @@
"array-equal": { "array-equal": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz",
"integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=" "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=",
"dev": true
}, },
"array-filter": { "array-filter": {
"version": "0.0.1", "version": "0.0.1",
@ -1237,7 +1241,8 @@
"async-limiter": { "async-limiter": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", "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": { "async-settle": {
"version": "1.0.0", "version": "1.0.0",
@ -1273,38 +1278,6 @@
"postcss-value-parser": "^3.2.3" "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": { "aws-sign2": {
"version": "0.7.0", "version": "0.7.0",
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
@ -2346,7 +2319,8 @@
"base64-js": { "base64-js": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz",
"integrity": "sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw==" "integrity": "sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw==",
"dev": true
}, },
"bcrypt-pbkdf": { "bcrypt-pbkdf": {
"version": "1.0.2", "version": "1.0.2",
@ -2481,7 +2455,8 @@
"browser-process-hrtime": { "browser-process-hrtime": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.2.tgz", "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": { "browser-resolve": {
"version": "1.11.3", "version": "1.11.3",
@ -2593,6 +2568,7 @@
"version": "4.9.1", "version": "4.9.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
"integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
"dev": true,
"requires": { "requires": {
"base64-js": "^1.0.2", "base64-js": "^1.0.2",
"ieee754": "^1.1.4", "ieee754": "^1.1.4",
@ -3904,12 +3880,14 @@
"cssom": { "cssom": {
"version": "0.3.4", "version": "0.3.4",
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.4.tgz", "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": { "cssstyle": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.1.1.tgz", "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.1.1.tgz",
"integrity": "sha512-364AI1l/M5TYcFH83JnOH/pSqgaNnKmYgKrm0didZMGKWjQB60dymwWy1rKUgL3J1ffdq9xVi2yGLHdSjjSNog==", "integrity": "sha512-364AI1l/M5TYcFH83JnOH/pSqgaNnKmYgKrm0didZMGKWjQB60dymwWy1rKUgL3J1ffdq9xVi2yGLHdSjjSNog==",
"dev": true,
"requires": { "requires": {
"cssom": "0.3.x" "cssom": "0.3.x"
} }
@ -4010,7 +3988,7 @@
}, },
"d3-collection": { "d3-collection": {
"version": "1.0.7", "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==" "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A=="
}, },
"d3-color": { "d3-color": {
@ -4188,7 +4166,7 @@
}, },
"d3-voronoi": { "d3-voronoi": {
"version": "1.1.4", "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==" "integrity": "sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg=="
}, },
"d3-zoom": { "d3-zoom": {
@ -4205,7 +4183,7 @@
}, },
"dagre": { "dagre": {
"version": "0.8.5", "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==", "integrity": "sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==",
"requires": { "requires": {
"graphlib": "^2.1.8", "graphlib": "^2.1.8",
@ -4214,14 +4192,14 @@
"dependencies": { "dependencies": {
"lodash": { "lodash": {
"version": "4.17.21", "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==" "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
} }
} }
}, },
"dagre-d3": { "dagre-d3": {
"version": "0.6.4", "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==", "integrity": "sha512-e/6jXeCP7/ptlAM48clmX4xTZc5Ek6T6kagS7Oz2HrYSdqcLZFLqpAfh7ldbZRFfxCZVyh61NEPR08UQRVxJzQ==",
"requires": { "requires": {
"d3": "^5.14", "d3": "^5.14",
@ -4232,7 +4210,7 @@
"dependencies": { "dependencies": {
"lodash": { "lodash": {
"version": "4.17.21", "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==" "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
} }
} }
@ -4249,6 +4227,7 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.0.1.tgz", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.0.1.tgz",
"integrity": "sha512-0HdcMZzK6ubMUnsMmQmG0AcLQPvbvb47R0+7CCZQCYgcd8OUWG91CG7sM6GoXgjz+WLl4ArFzHtBMy/QqSF4eg==", "integrity": "sha512-0HdcMZzK6ubMUnsMmQmG0AcLQPvbvb47R0+7CCZQCYgcd8OUWG91CG7sM6GoXgjz+WLl4ArFzHtBMy/QqSF4eg==",
"dev": true,
"requires": { "requires": {
"abab": "^2.0.0", "abab": "^2.0.0",
"whatwg-mimetype": "^2.1.0", "whatwg-mimetype": "^2.1.0",
@ -4259,6 +4238,7 @@
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz",
"integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==", "integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==",
"dev": true,
"requires": { "requires": {
"lodash.sortby": "^4.7.0", "lodash.sortby": "^4.7.0",
"tr46": "^1.0.1", "tr46": "^1.0.1",
@ -4317,7 +4297,8 @@
"deep-is": { "deep-is": {
"version": "0.1.3", "version": "0.1.3",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", "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": { "default-compare": {
"version": "1.0.0", "version": "1.0.0",
@ -4579,6 +4560,7 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz",
"integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==",
"dev": true,
"requires": { "requires": {
"webidl-conversions": "^4.0.2" "webidl-conversions": "^4.0.2"
} }
@ -4592,6 +4574,11 @@
"domelementtype": "1" "domelementtype": "1"
} }
}, },
"domino": {
"version": "2.1.6",
"resolved": "https://registry.npmmirror.com/domino/-/domino-2.1.6.tgz",
"integrity": "sha512-3VdM/SXBZX2omc9JF9nOPCtDaYQ67BGp5CoLpIQlO2KCAPETs8TcDHacF26jXadGbvUteZzRTeos2fhID5+ucQ=="
},
"domutils": { "domutils": {
"version": "1.6.2", "version": "1.6.2",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.6.2.tgz", "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.6.2.tgz",
@ -4759,7 +4746,7 @@
}, },
"entity-decode": { "entity-decode": {
"version": "2.0.2", "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==", "integrity": "sha512-5CCY/3ci4MC1m2jlumNjWd7VBFt4VfFnmSqSNmVcXq4gxM3Vmarxtt+SvmBnzwLS669MWdVuXboNVj1qN2esVg==",
"requires": { "requires": {
"he": "^1.1.1" "he": "^1.1.1"
@ -4881,6 +4868,7 @@
"version": "1.11.0", "version": "1.11.0",
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.0.tgz", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.0.tgz",
"integrity": "sha512-IeMV45ReixHS53K/OmfKAIztN/igDHzTJUhZM3k1jMhIZWjk45SMwAtBsEXiJp3vSPmTcu6CXn7mDvFHRN66fw==", "integrity": "sha512-IeMV45ReixHS53K/OmfKAIztN/igDHzTJUhZM3k1jMhIZWjk45SMwAtBsEXiJp3vSPmTcu6CXn7mDvFHRN66fw==",
"dev": true,
"requires": { "requires": {
"esprima": "^3.1.3", "esprima": "^3.1.3",
"estraverse": "^4.2.0", "estraverse": "^4.2.0",
@ -4892,12 +4880,14 @@
"esprima": { "esprima": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz",
"integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=" "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=",
"dev": true
}, },
"source-map": { "source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"optional": true "optional": true
} }
} }
@ -5349,12 +5339,14 @@
"estraverse": { "estraverse": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz",
"integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=" "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=",
"dev": true
}, },
"esutils": { "esutils": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
"integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
"dev": true
}, },
"etag": { "etag": {
"version": "1.8.1", "version": "1.8.1",
@ -5370,7 +5362,8 @@
"events": { "events": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
"integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=",
"dev": true
}, },
"eventsource-polyfill": { "eventsource-polyfill": {
"version": "0.9.6", "version": "0.9.6",
@ -6060,7 +6053,8 @@
"fast-levenshtein": { "fast-levenshtein": {
"version": "2.0.6", "version": "2.0.6",
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
"dev": true
}, },
"fastparse": { "fastparse": {
"version": "1.1.1", "version": "1.1.1",
@ -8642,16 +8636,6 @@
"delegate": "^3.1.2" "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": { "graceful-fs": {
"version": "4.1.11", "version": "4.1.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
@ -8660,7 +8644,7 @@
}, },
"graphlib": { "graphlib": {
"version": "2.1.8", "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==", "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==",
"requires": { "requires": {
"lodash": "^4.17.15" "lodash": "^4.17.15"
@ -8668,7 +8652,7 @@
"dependencies": { "dependencies": {
"lodash": { "lodash": {
"version": "4.17.21", "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==" "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
} }
} }
@ -9069,6 +9053,7 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz",
"integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==",
"dev": true,
"requires": { "requires": {
"whatwg-encoding": "^1.0.1" "whatwg-encoding": "^1.0.1"
} }
@ -9639,7 +9624,8 @@
"ieee754": { "ieee754": {
"version": "1.1.8", "version": "1.1.8",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz",
"integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=" "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=",
"dev": true
}, },
"iferr": { "iferr": {
"version": "0.1.5", "version": "0.1.5",
@ -11238,11 +11224,6 @@
"url-regex": "^3.0.0" "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": { "jpeg-js": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.2.0.tgz", "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.2.0.tgz",
@ -11298,6 +11279,7 @@
"version": "11.12.0", "version": "11.12.0",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz",
"integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==",
"dev": true,
"requires": { "requires": {
"abab": "^2.0.0", "abab": "^2.0.0",
"acorn": "^5.5.3", "acorn": "^5.5.3",
@ -11330,17 +11312,20 @@
"acorn": { "acorn": {
"version": "5.7.1", "version": "5.7.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.1.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.1.tgz",
"integrity": "sha512-d+nbxBUGKg7Arpsvbnlq61mc12ek3EY8EQldM3GPAhWJ1UVxC6TDGbIvUMNU6obBX3i1+ptCIzV4vq0gFPEGVQ==" "integrity": "sha512-d+nbxBUGKg7Arpsvbnlq61mc12ek3EY8EQldM3GPAhWJ1UVxC6TDGbIvUMNU6obBX3i1+ptCIzV4vq0gFPEGVQ==",
"dev": true
}, },
"sax": { "sax": {
"version": "1.2.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", "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": { "whatwg-url": {
"version": "6.5.0", "version": "6.5.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz",
"integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==",
"dev": true,
"requires": { "requires": {
"lodash.sortby": "^4.7.0", "lodash.sortby": "^4.7.0",
"tr46": "^1.0.1", "tr46": "^1.0.1",
@ -11351,6 +11336,7 @@
"version": "5.2.2", "version": "5.2.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz",
"integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==",
"dev": true,
"requires": { "requires": {
"async-limiter": "~1.0.0" "async-limiter": "~1.0.0"
} }
@ -11450,17 +11436,17 @@
"dev": true "dev": true
}, },
"katex": { "katex": {
"version": "0.13.0", "version": "0.16.2",
"resolved": "https://registry.npmjs.org/katex/-/katex-0.13.0.tgz", "resolved": "https://registry.npmmirror.com/katex/-/katex-0.16.2.tgz",
"integrity": "sha512-6cHbzbegYgS9vvVGuH8UA+o97X+ZshtboSqJJCdq7trBYzuD75JNwr7Ef606xkUjecPPhFnyB+afx1dVafielg==", "integrity": "sha512-70DJdQAyh9EMsthw3AaQlDyFf54X7nWEUIa5W+rq8XOpEk//w5Th7/8SqFqpvi/KZ2t6MHUj4f9wLmztBmAYQA==",
"requires": { "requires": {
"commander": "^6.0.0" "commander": "^8.0.0"
}, },
"dependencies": { "dependencies": {
"commander": { "commander": {
"version": "6.2.1", "version": "8.3.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", "resolved": "https://registry.npmmirror.com/commander/-/commander-8.3.0.tgz",
"integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==" "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="
} }
} }
}, },
@ -11549,7 +11535,8 @@
"left-pad": { "left-pad": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz",
"integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==" "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==",
"dev": true
}, },
"leven": { "leven": {
"version": "2.1.0", "version": "2.1.0",
@ -11561,6 +11548,7 @@
"version": "0.3.0", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
"integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
"dev": true,
"requires": { "requires": {
"prelude-ls": "~1.1.2", "prelude-ls": "~1.1.2",
"type-check": "~0.3.2" "type-check": "~0.3.2"
@ -12513,7 +12501,7 @@
}, },
"minify": { "minify": {
"version": "4.1.3", "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==", "integrity": "sha512-ykuscavxivSmVpcCzsXmsVTukWYLUUtPhHj0w2ILvHDGqC+hsuTCihBn9+PJBd58JNvWTNg9132J9nrrI2anzA==",
"requires": { "requires": {
"clean-css": "^4.1.6", "clean-css": "^4.1.6",
@ -12527,7 +12515,7 @@
"dependencies": { "dependencies": {
"commander": { "commander": {
"version": "2.20.3", "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==" "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
}, },
"debug": { "debug": {
@ -12540,12 +12528,12 @@
}, },
"he": { "he": {
"version": "1.2.0", "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==" "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
}, },
"html-minifier": { "html-minifier": {
"version": "4.0.0", "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==", "integrity": "sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig==",
"requires": { "requires": {
"camel-case": "^3.0.0", "camel-case": "^3.0.0",
@ -12559,7 +12547,7 @@
}, },
"ms": { "ms": {
"version": "2.1.2", "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==" "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}, },
"uglify-js": { "uglify-js": {
@ -13342,7 +13330,8 @@
"nwsapi": { "nwsapi": {
"version": "2.0.8", "version": "2.0.8",
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.0.8.tgz", "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": { "oauth-sign": {
"version": "0.9.0", "version": "0.9.0",
@ -13677,6 +13666,7 @@
"version": "0.8.2", "version": "0.8.2",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
"integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=",
"dev": true,
"requires": { "requires": {
"deep-is": "~0.1.3", "deep-is": "~0.1.3",
"fast-levenshtein": "~2.0.4", "fast-levenshtein": "~2.0.4",
@ -13689,7 +13679,8 @@
"wordwrap": { "wordwrap": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
"integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=",
"dev": true
} }
} }
}, },
@ -14007,7 +13998,8 @@
"parse5": { "parse5": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", "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": { "parseurl": {
"version": "1.3.2", "version": "1.3.2",
@ -14208,7 +14200,8 @@
"pn": { "pn": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", "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": { "pngjs": {
"version": "3.3.3", "version": "3.3.3",
@ -15111,7 +15104,8 @@
"prelude-ls": { "prelude-ls": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", "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": { "prepend-http": {
"version": "1.0.4", "version": "1.0.4",
@ -15275,7 +15269,8 @@
"punycode": { "punycode": {
"version": "1.3.2", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
"integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=",
"dev": true
}, },
"q": { "q": {
"version": "1.5.1", "version": "1.5.1",
@ -15301,7 +15296,8 @@
"querystring": { "querystring": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
"integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=",
"dev": true
}, },
"querystring-es3": { "querystring-es3": {
"version": "0.2.1", "version": "0.2.1",
@ -16250,6 +16246,7 @@
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz",
"integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=", "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=",
"dev": true,
"requires": { "requires": {
"lodash": "^4.13.1" "lodash": "^4.13.1"
} }
@ -16258,6 +16255,7 @@
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.5.tgz", "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.5.tgz",
"integrity": "sha1-UoF3D2jgyXGeUWP9P6tIIhX0/aU=", "integrity": "sha1-UoF3D2jgyXGeUWP9P6tIIhX0/aU=",
"dev": true,
"requires": { "requires": {
"request-promise-core": "1.1.1", "request-promise-core": "1.1.1",
"stealthy-require": "^1.1.0", "stealthy-require": "^1.1.0",
@ -16419,11 +16417,6 @@
"inherits": "^2.0.1" "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": { "rsvp": {
"version": "3.6.2", "version": "3.6.2",
"resolved": "https://registry.npmjs.org/rsvp/-/rsvp-3.6.2.tgz", "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-3.6.2.tgz",
@ -17714,7 +17707,8 @@
"sax": { "sax": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz",
"integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=",
"dev": true
}, },
"schema-utils": { "schema-utils": {
"version": "0.3.0", "version": "0.3.0",
@ -18448,7 +18442,8 @@
"stealthy-require": { "stealthy-require": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", "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": { "stream-browserify": {
"version": "2.0.1", "version": "2.0.1",
@ -19607,7 +19602,8 @@
"symbol-tree": { "symbol-tree": {
"version": "3.2.2", "version": "3.2.2",
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz",
"integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=" "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=",
"dev": true
}, },
"table": { "table": {
"version": "4.0.2", "version": "4.0.2",
@ -20417,12 +20413,12 @@
}, },
"try-catch": { "try-catch": {
"version": "2.0.1", "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==" "integrity": "sha512-LsOrmObN/2WdM+y2xG+t16vhYrQsnV8wftXIcIOWZhQcBJvKGYuamJGwnU98A7Jxs2oZNkJztXlphEOoA0DWqg=="
}, },
"try-to-catch": { "try-to-catch": {
"version": "1.1.1", "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==" "integrity": "sha512-ikUlS+/BcImLhNYyIgZcEmq4byc31QpC+46/6Jm5ECWkVFhf8SM2Fp/0pMVXPX6vk45SMCwrP4Taxucne8I0VA=="
}, },
"tryer": { "tryer": {
@ -20466,11 +20462,11 @@
} }
}, },
"turndown": { "turndown": {
"version": "4.0.2", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/turndown/-/turndown-4.0.2.tgz", "resolved": "https://registry.npmmirror.com/turndown/-/turndown-7.1.1.tgz",
"integrity": "sha512-pqZ6WrHFGnxXC9q2xJ3Qa7EoLAwrojgFRajWZjxTKwbz9vnNnyi8lLjiD5h86UTPOcMlEyHjm6NMhjEDdlc25A==", "integrity": "sha512-BEkXaWH7Wh7e9bd2QumhfAXk5g34+6QUmmWx+0q6ThaVOLuLUqsnkq35HQ5SBHSaxjSfSM7US5o4lhJNH7B9MA==",
"requires": { "requires": {
"jsdom": "^11.9.0" "domino": "^2.1.6"
} }
}, },
"tweetnacl": { "tweetnacl": {
@ -20489,6 +20485,7 @@
"version": "0.3.2", "version": "0.3.2",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
"integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
"dev": true,
"requires": { "requires": {
"prelude-ls": "~1.1.2" "prelude-ls": "~1.1.2"
} }
@ -20568,7 +20565,8 @@
"underscore": { "underscore": {
"version": "1.8.3", "version": "1.8.3",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz",
"integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=",
"dev": true
}, },
"undertaker": { "undertaker": {
"version": "1.3.0", "version": "1.3.0",
@ -20843,15 +20841,6 @@
"integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=",
"dev": true "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": { "url-loader": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/url-loader/-/url-loader-1.1.1.tgz", "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-1.1.1.tgz",
@ -20976,11 +20965,6 @@
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=",
"dev": true "dev": true
}, },
"uuid": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",
"integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g=="
},
"v8flags": { "v8flags": {
"version": "3.2.0", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz",
@ -21336,6 +21320,7 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz",
"integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=",
"dev": true,
"requires": { "requires": {
"browser-process-hrtime": "^0.1.2" "browser-process-hrtime": "^0.1.2"
} }
@ -21757,6 +21742,7 @@
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.4.tgz", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.4.tgz",
"integrity": "sha512-vM9KWN6MP2mIHZ86ytcyIv7e8Cj3KTfO2nd2c8PFDqcI4bxFmQp83ibq4wadq7rL9l9sZV6o9B0LTt8ygGAAXg==", "integrity": "sha512-vM9KWN6MP2mIHZ86ytcyIv7e8Cj3KTfO2nd2c8PFDqcI4bxFmQp83ibq4wadq7rL9l9sZV6o9B0LTt8ygGAAXg==",
"dev": true,
"requires": { "requires": {
"iconv-lite": "0.4.23" "iconv-lite": "0.4.23"
}, },
@ -21765,6 +21751,7 @@
"version": "0.4.23", "version": "0.4.23",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
"integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
"dev": true,
"requires": { "requires": {
"safer-buffer": ">= 2.1.2 < 3" "safer-buffer": ">= 2.1.2 < 3"
} }
@ -21774,7 +21761,8 @@
"whatwg-mimetype": { "whatwg-mimetype": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.1.0.tgz", "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": { "whatwg-url": {
"version": "6.4.0", "version": "6.4.0",
@ -21953,7 +21941,8 @@
"xml-name-validator": { "xml-name-validator": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", "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": { "xml-parse-from-string": {
"version": "1.0.1", "version": "1.0.1",

View File

@ -1,6 +1,6 @@
{ {
"name": "stackedit", "name": "stackedit",
"version": "5.15.12", "version": "5.15.21",
"description": "免费, 开源, 功能齐全的 Markdown 编辑器", "description": "免费, 开源, 功能齐全的 Markdown 编辑器",
"author": "Benoit Schweblin, 豆萁", "author": "Benoit Schweblin, 豆萁",
"license": "Apache-2.0", "license": "Apache-2.0",
@ -27,7 +27,6 @@
"dependencies": { "dependencies": {
"@vue/test-utils": "^1.0.0-beta.16", "@vue/test-utils": "^1.0.0-beta.16",
"abcjs": "^5.2.0", "abcjs": "^5.2.0",
"aws-sdk": "^2.317.0",
"babel-runtime": "^6.26.0", "babel-runtime": "^6.26.0",
"bezier-easing": "^1.1.0", "bezier-easing": "^1.1.0",
"body-parser": "^1.18.2", "body-parser": "^1.18.2",
@ -35,11 +34,10 @@
"compression": "^1.7.0", "compression": "^1.7.0",
"diff-match-patch": "^1.0.0", "diff-match-patch": "^1.0.0",
"file-saver": "^1.3.8", "file-saver": "^1.3.8",
"google-id-token-verifier": "^0.2.3",
"handlebars": "^4.0.10", "handlebars": "^4.0.10",
"indexeddbshim": "^3.6.2", "indexeddbshim": "^3.6.2",
"js-yaml": "^3.11.0", "js-yaml": "^3.11.0",
"katex": "^0.13.0", "katex": "^0.16.2",
"markdown-it": "^8.4.1", "markdown-it": "^8.4.1",
"markdown-it-abbr": "^1.0.4", "markdown-it-abbr": "^1.0.4",
"markdown-it-deflist": "^2.0.2", "markdown-it-deflist": "^2.0.2",
@ -57,7 +55,7 @@
"request": "^2.85.0", "request": "^2.85.0",
"serve-static": "^1.13.2", "serve-static": "^1.13.2",
"tmp": "^0.0.33", "tmp": "^0.0.33",
"turndown": "^4.0.2", "turndown": "^7.1.1",
"vue": "^2.5.16", "vue": "^2.5.16",
"vuex": "^3.0.1" "vuex": "^3.0.1"
}, },

View File

@ -1,7 +1,5 @@
const pandocPath = process.env.PANDOC_PATH || 'pandoc'; const pandocPath = process.env.PANDOC_PATH || 'pandoc';
const wkhtmltopdfPath = process.env.WKHTMLTOPDF_PATH || 'wkhtmltopdf'; 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 paypalReceiverEmail = process.env.PAYPAL_RECEIVER_EMAIL;
const dropboxAppKey = process.env.DROPBOX_APP_KEY; 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 googleClientId = process.env.GOOGLE_CLIENT_ID;
const googleApiKey = process.env.GOOGLE_API_KEY; const googleApiKey = process.env.GOOGLE_API_KEY;
const wordpressClientId = process.env.WORDPRESS_CLIENT_ID; 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 = { exports.values = {
pandocPath, pandocPath,
wkhtmltopdfPath, wkhtmltopdfPath,
userBucketName,
paypalUri,
paypalReceiverEmail, paypalReceiverEmail,
dropboxAppKey, dropboxAppKey,
dropboxAppKeyFull, dropboxAppKeyFull,
@ -29,6 +31,12 @@ exports.values = {
googleClientId, googleClientId,
googleApiKey, googleApiKey,
wordpressClientId, wordpressClientId,
giteaClientId,
giteaClientSecret,
giteaUrl,
gitlabClientId,
gitlabClientSecret,
gitlabUrl,
}; };
exports.publicValues = { exports.publicValues = {
@ -39,4 +47,8 @@ exports.publicValues = {
googleApiKey, googleApiKey,
wordpressClientId, wordpressClientId,
allowSponsorship: !!paypalReceiverEmail, allowSponsorship: !!paypalReceiverEmail,
giteaClientId,
giteaUrl,
gitlabClientId,
gitlabUrl,
}; };

40
server/gitea.js Normal file
View 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
View 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'),
);
};

View File

@ -2,9 +2,10 @@ const compression = require('compression');
const serveStatic = require('serve-static'); const serveStatic = require('serve-static');
const bodyParser = require('body-parser'); const bodyParser = require('body-parser');
const path = require('path'); const path = require('path');
const user = require('./user');
const github = require('./github'); const github = require('./github');
const gitee = require('./gitee'); const gitee = require('./gitee');
const gitea = require('./gitea');
const gitlab = require('./gitlab');
const pdf = require('./pdf'); const pdf = require('./pdf');
const pandoc = require('./pandoc'); const pandoc = require('./pandoc');
const conf = require('./conf'); const conf = require('./conf');
@ -27,13 +28,11 @@ module.exports = (app) => {
app.get('/oauth2/githubToken', github.githubToken); app.get('/oauth2/githubToken', github.githubToken);
app.get('/oauth2/giteeToken', gitee.giteeToken); 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('/conf', (req, res) => res.send(conf.publicValues));
app.get('/userInfo', user.userInfo);
app.post('/pdfExport', pdf.generate); app.post('/pdfExport', pdf.generate);
app.post('/pandocExport', pandoc.generate); app.post('/pandocExport', pandoc.generate);
app.post('/paypalIpn', bodyParser.urlencoded({
extended: false,
}), user.paypalIpn);
app.get('/giteeClientId', (req, res) => { app.get('/giteeClientId', (req, res) => {
const giteeClientIds = conf.values.giteeClientId.split(','); const giteeClientIds = conf.values.giteeClientId.split(',');
// 仅一个 则直接返回 // 仅一个 则直接返回
@ -62,16 +61,23 @@ module.exports = (app) => {
// Google Drive action receiver // Google Drive action receiver
app.get('/googleDriveAction', (req, res) => app.get('/googleDriveAction', (req, res) =>
res.redirect(`./app#providerId=googleDrive&state=${encodeURIComponent(req.query.state)}`)); res.redirect(`./app#providerId=googleDrive&state=${encodeURIComponent(req.query.state)}`));
// Serve the static folder with 30 day max-age
// Serve static resources app.use('/themes', serveStatic(resolvePath('static/themes'), {
if (process.env.NODE_ENV === 'production') { maxAge: '5d',
// Serve index.html in /app }));
app.get('/app', (req, res) => res.sendFile(resolvePath('dist/index.html')));
// Serve style.css with 1 day max-age // Serve style.css with 1 day max-age
app.get('/style.css', (req, res) => res.sendFile(resolvePath('dist/style.css'), { app.get('/style.css', (req, res) => res.sendFile(resolvePath('dist/style.css'), {
maxAge: '1d', 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 the static folder with 1 year max-age // Serve the static folder with 1 year max-age
app.use('/static', serveStatic(resolvePath('dist/static'), { app.use('/static', serveStatic(resolvePath('dist/static'), {

View File

@ -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);
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -22,6 +22,8 @@ import networkSvc from '../services/networkSvc';
import tempFileSvc from '../services/tempFileSvc'; import tempFileSvc from '../services/tempFileSvc';
import store from '../store'; import store from '../store';
import './common/vueGlobals'; import './common/vueGlobals';
import utils from '../services/utils';
import providerRegistry from '../services/providers/common/providerRegistry';
const themeClasses = { const themeClasses = {
light: ['app--light'], light: ['app--light'],
@ -49,11 +51,49 @@ export default {
close() { close() {
tempFileSvc.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() { async created() {
window.viewFileByPath = this.viewFileByPath;
try { try {
await syncSvc.init(); await syncSvc.init();
await networkSvc.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; this.ready = true;
tempFileSvc.setReady(); tempFileSvc.setReady();
} catch (err) { } catch (err) {

View File

@ -7,11 +7,12 @@ import Prism from 'prismjs';
import cledit from '../services/editor/cledit'; import cledit from '../services/editor/cledit';
export default { export default {
props: ['value', 'lang', 'disabled'], props: ['value', 'lang', 'disabled', 'scrollClass'],
mounted() { mounted() {
const preElt = this.$el; const preElt = this.$el;
let scrollElt = preElt; let scrollElt = preElt;
while (scrollElt && !scrollElt.classList.contains('modal')) { const scrollCls = this.scrollClass || 'modal';
while (scrollElt && !scrollElt.classList.contains(scrollCls)) {
scrollElt = scrollElt.parentNode; scrollElt = scrollElt.parentNode;
} }
if (scrollElt) { if (scrollElt) {

View File

@ -14,6 +14,8 @@ import CommentList from './gutters/CommentList';
import EditorNewDiscussionButton from './gutters/EditorNewDiscussionButton'; import EditorNewDiscussionButton from './gutters/EditorNewDiscussionButton';
import store from '../store'; import store from '../store';
import editorSvc from '../services/editorSvc'; import editorSvc from '../services/editorSvc';
import imageSvc from '../services/imageSvc';
import utils from '../services/utils';
export default { export default {
components: { components: {
@ -32,7 +34,7 @@ export default {
]), ]),
}, },
methods: { methods: {
setImgAndDoClick(items) { async processUpload(items) {
let file = null; let file = null;
if (!items || items.length === 0) { if (!items || items.length === 0) {
return; return;
@ -46,8 +48,23 @@ export default {
if (!file) { if (!file) {
return; return;
} }
store.dispatch('img/setImg', file); const imgId = utils.uid();
editorSvc.pagedownEditor.uiManager.doClick('image'); 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})]`, `![输入图片说明](${url})`);
} catch (err) {
console.error(err); // eslint-disable-line no-console
editorSvc.clEditor.replaceAll(`[图片上传中...(image-${imgId})]`, `[图片上传失败...(image-${imgId})]`);
store.dispatch('notification/error', err);
}
}, },
}, },
mounted() { mounted() {
@ -56,6 +73,11 @@ export default {
if (currImgStorageStr) { if (currImgStorageStr) {
store.commit('img/changeCheckedStorage', JSON.parse(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 editorElt = this.$el.querySelector('.editor__inner');
const onDiscussionEvt = cb => (evt) => { const onDiscussionEvt = cb => (evt) => {
let elt = evt.target; let elt = evt.target;
@ -83,11 +105,11 @@ export default {
editorElt.addEventListener('drop', (event) => { editorElt.addEventListener('drop', (event) => {
const transItems = event.dataTransfer.items; const transItems = event.dataTransfer.items;
this.setImgAndDoClick(transItems); this.processUpload(transItems);
}); });
editorElt.addEventListener('paste', (event) => { editorElt.addEventListener('paste', (event) => {
const pasteItems = (event.clipboardData || window.clipboardData).items; const pasteItems = (event.clipboardData || window.clipboardData).items;
this.setImgAndDoClick(pasteItems); this.processUpload(pasteItems);
}); });
this.$watch( this.$watch(

View 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>

View File

@ -14,6 +14,7 @@
</div> </div>
<explorer-node v-for="node in node.files" :key="node.item.id" :node="node" :depth="depth + 1"></explorer-node> <explorer-node v-for="node in node.files" :key="node.item.id" :node="node" :depth="depth + 1"></explorer-node>
</div> </div>
<button ref="copyId" v-clipboard="copyPath()" @click="info('路径已复制到剪切板!')" style="display: none;"></button>
</div> </div>
</template> </template>
@ -23,6 +24,7 @@ import workspaceSvc from '../services/workspaceSvc';
import explorerSvc from '../services/explorerSvc'; import explorerSvc from '../services/explorerSvc';
import store from '../store'; import store from '../store';
import badgeSvc from '../services/badgeSvc'; import badgeSvc from '../services/badgeSvc';
import utils from '../services/utils';
export default { export default {
name: 'explorer-node', // Required for recursivity name: 'explorer-node', // Required for recursivity
@ -80,6 +82,9 @@ export default {
...mapActions('explorer', [ ...mapActions('explorer', [
'setDragTarget', 'setDragTarget',
]), ]),
...mapActions('notification', [
'info',
]),
select(id = this.node.item.id, doOpen = true) { select(id = this.node.item.id, doOpen = true) {
const node = store.getters['explorer/nodeMap'][id]; const node = store.getters['explorer/nodeMap'][id];
if (!node) { if (!node) {
@ -144,6 +149,11 @@ export default {
// See https://stackoverflow.com/a/3977637/1333165 // See https://stackoverflow.com/a/3977637/1333165
evt.dataTransfer.setData('Text', ''); 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() { onDrop() {
const sourceNode = store.getters['explorer/dragSourceNode']; const sourceNode = store.getters['explorer/dragSourceNode'];
const targetNode = store.getters['explorer/dragTargetNodeFolder']; const targetNode = store.getters['explorer/dragTargetNodeFolder'];
@ -169,22 +179,26 @@ export default {
top: evt.clientY, top: evt.clientY,
}, },
items: [{ items: [{
name: 'New file', name: '新建文件',
disabled: !this.node.isFolder || this.node.isTrash, disabled: !this.node.isFolder || this.node.isTrash,
perform: () => explorerSvc.newItem(false), perform: () => explorerSvc.newItem(false),
}, { }, {
name: 'New folder', name: '新建文件夹',
disabled: !this.node.isFolder || this.node.isTrash || this.node.isTemp, disabled: !this.node.isFolder || this.node.isTrash || this.node.isTemp,
perform: () => explorerSvc.newItem(true), perform: () => explorerSvc.newItem(true),
}, { }, {
type: 'separator', type: 'separator',
}, { }, {
name: 'Rename', name: '重命名',
disabled: this.node.isTrash || this.node.isTemp, disabled: this.node.isTrash || this.node.isTemp,
perform: () => this.setEditingId(this.node.item.id), perform: () => this.setEditingId(this.node.item.id),
}, { }, {
name: 'Delete', name: '删除',
perform: () => explorerSvc.deleteItem(), perform: () => explorerSvc.deleteItem(),
}, {
name: '复制路径',
disabled: this.node.isTrash || this.node.isTemp,
perform: () => this.$refs.copyId.click(),
}], }],
}); });
if (item) { if (item) {

View File

@ -383,6 +383,10 @@ export default {
.find-replace-highlighting { .find-replace-highlighting {
background-color: $highlighting-color; background-color: $highlighting-color;
color: $editor-color-light !important; color: $editor-color-light !important;
.app--dark & {
background-color: $dark-highlighting-color;
}
} }
.find-replace-selection { .find-replace-selection {

View File

@ -9,11 +9,12 @@
<navigation-bar></navigation-bar> <navigation-bar></navigation-bar>
</div> </div>
<div class="layout__panel flex flex--row" :style="{height: styles.innerHeight + 'px'}"> <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" :style="{left: styles.editorGutterLeft + 'px'}">
<div class="gutter__background" v-if="styles.editorGutterWidth" :style="{width: styles.editorGutterWidth + 'px'}"></div> <div class="gutter__background" v-if="styles.editorGutterWidth" :style="{width: styles.editorGutterWidth + 'px'}"></div>
</div> </div>
<editor></editor> <editor></editor>
<editor-in-page-buttons v-if="editorShowInPageButtons"></editor-in-page-buttons>
<div class="gutter" :style="{left: styles.editorGutterLeft + 'px'}"> <div class="gutter" :style="{left: styles.editorGutterLeft + 'px'}">
<sticky-comment v-if="styles.editorGutterWidth && stickyComment === 'top'"></sticky-comment> <sticky-comment v-if="styles.editorGutterWidth && stickyComment === 'top'"></sticky-comment>
<current-discussion v-if="styles.editorGutterWidth"></current-discussion> <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 class="gutter__background" v-if="styles.previewGutterWidth" :style="{width: styles.previewGutterWidth + 'px'}"></div>
</div> </div>
<preview></preview> <preview></preview>
<preview-in-page-buttons></preview-in-page-buttons>
<div class="gutter" :style="{left: styles.previewGutterLeft + 'px'}"> <div class="gutter" :style="{left: styles.previewGutterLeft + 'px'}">
<sticky-comment v-if="styles.previewGutterWidth && stickyComment === 'top'"></sticky-comment> <sticky-comment v-if="styles.previewGutterWidth && stickyComment === 'top'"></sticky-comment>
<current-discussion v-if="styles.previewGutterWidth"></current-discussion> <current-discussion v-if="styles.previewGutterWidth"></current-discussion>
@ -58,6 +60,8 @@ import SideBar from './SideBar';
import Editor from './Editor'; import Editor from './Editor';
import Preview from './Preview'; import Preview from './Preview';
import Tour from './Tour'; import Tour from './Tour';
import EditorInPageButtons from './EditorInPageButtons';
import PreviewInPageButtons from './PreviewInPageButtons';
import StickyComment from './gutters/StickyComment'; import StickyComment from './gutters/StickyComment';
import CurrentDiscussion from './gutters/CurrentDiscussion'; import CurrentDiscussion from './gutters/CurrentDiscussion';
import FindReplace from './FindReplace'; import FindReplace from './FindReplace';
@ -75,6 +79,8 @@ export default {
Editor, Editor,
Preview, Preview,
Tour, Tour,
EditorInPageButtons,
PreviewInPageButtons,
StickyComment, StickyComment,
CurrentDiscussion, CurrentDiscussion,
FindReplace, FindReplace,
@ -96,9 +102,18 @@ export default {
...mapGetters('data', [ ...mapGetters('data', [
'layoutSettings', 'layoutSettings',
]), ]),
...mapGetters('theme', [
'currEditTheme',
]),
editTheme() {
return `edit-theme--${this.currEditTheme || 'default'}`;
},
showFindReplace() { showFindReplace() {
return !!store.state.findReplace.type; return !!store.state.findReplace.type;
}, },
editorShowInPageButtons() {
return store.getters['data/computedSettings'].editor.showInPageButtons;
},
}, },
methods: { methods: {
...mapActions('layout', [ ...mapActions('layout', [

View File

@ -10,6 +10,7 @@
<div class="modal__button-bar"> <div class="modal__button-bar">
<button class="button" v-if="simpleModal.rejectText" @click="config.reject()">{{simpleModal.rejectText}}</button> <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 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> </div>
</modal-inner> </modal-inner>
</div> </div>
@ -39,6 +40,9 @@ import WorkspaceManagementModal from './modals/WorkspaceManagementModal';
import AccountManagementModal from './modals/AccountManagementModal'; import AccountManagementModal from './modals/AccountManagementModal';
import BadgeManagementModal from './modals/BadgeManagementModal'; import BadgeManagementModal from './modals/BadgeManagementModal';
import SponsorModal from './modals/SponsorModal'; import SponsorModal from './modals/SponsorModal';
import CommitMessageModal from './modals/CommitMessageModal';
import WorkspaceImgPathModal from './modals/WorkspaceImgPathModal';
import ChatGptModal from './modals/ChatGptModal';
// Providers // Providers
import GooglePhotoModal from './modals/providers/GooglePhotoModal'; import GooglePhotoModal from './modals/providers/GooglePhotoModal';
@ -62,6 +66,8 @@ import GiteeOpenModal from './modals/providers/GiteeOpenModal';
import GiteeSaveModal from './modals/providers/GiteeSaveModal'; import GiteeSaveModal from './modals/providers/GiteeSaveModal';
import GiteeWorkspaceModal from './modals/providers/GiteeWorkspaceModal'; import GiteeWorkspaceModal from './modals/providers/GiteeWorkspaceModal';
import GiteePublishModal from './modals/providers/GiteePublishModal'; 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 GitlabAccountModal from './modals/providers/GitlabAccountModal';
import GitlabOpenModal from './modals/providers/GitlabOpenModal'; import GitlabOpenModal from './modals/providers/GitlabOpenModal';
import GitlabPublishModal from './modals/providers/GitlabPublishModal'; import GitlabPublishModal from './modals/providers/GitlabPublishModal';
@ -105,6 +111,9 @@ export default {
AccountManagementModal, AccountManagementModal,
BadgeManagementModal, BadgeManagementModal,
SponsorModal, SponsorModal,
CommitMessageModal,
WorkspaceImgPathModal,
ChatGptModal,
// Providers // Providers
GooglePhotoModal, GooglePhotoModal,
GoogleDriveAccountModal, GoogleDriveAccountModal,
@ -127,6 +136,8 @@ export default {
GiteeSaveModal, GiteeSaveModal,
GiteeWorkspaceModal, GiteeWorkspaceModal,
GiteePublishModal, GiteePublishModal,
GiteeGistSyncModal,
GiteeGistPublishModal,
GitlabAccountModal, GitlabAccountModal,
GitlabOpenModal, GitlabOpenModal,
GitlabPublishModal, GitlabPublishModal,
@ -177,6 +188,7 @@ export default {
// User has to sign in // User has to sign in
await store.dispatch('modal/open', 'signInForSponsorship'); await store.dispatch('modal/open', 'signInForSponsorship');
await giteeHelper.signin(); await giteeHelper.signin();
await syncSvc.afterSignIn();
syncSvc.requestSync(); syncSvc.requestSync();
} }
if (!store.getters.isSponsor) { if (!store.getters.isSponsor) {

View File

@ -114,7 +114,8 @@ export default {
publishLocations: 'current', publishLocations: 'current',
}), }),
pagedownButtons() { pagedownButtons() {
return pagedownButtons.map(button => ({ const buttonShowObj = store.getters['data/computedSettings'].editor.headButtons;
return pagedownButtons.filter(it => buttonShowObj[it.method]).map(button => ({
...button, ...button,
titleWithShortcut: `${button.title}${getShortcut(button.method)}`, titleWithShortcut: `${button.title}${getShortcut(button.method)}`,
iconClass: `icon-${button.icon}`, iconClass: `icon-${button.icon}`,

View File

@ -10,10 +10,10 @@
{{item.content}} {{item.content}}
</div> </div>
<button class="notification__button button" v-if="item.type === 'confirm'" @click="item.reject"> <button class="notification__button button" v-if="item.type === 'confirm'" @click="item.reject">
No
</button> </button>
<button class="notification__button button" v-if="item.type === 'confirm'" @click="item.resolve"> <button class="notification__button button" v-if="item.type === 'confirm'" @click="item.resolve">
Yes
</button> </button>
</div> </div>
</div> </div>

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="preview"> <div class="preview">
<div class="preview__inner-1" @click="onClick" @scroll="onScroll"> <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>
<div class="gutter" :style="{left: styles.previewGutterLeft + 'px'}"> <div class="gutter" :style="{left: styles.previewGutterLeft + 'px'}">
<comment-list v-if="styles.previewGutterWidth"></comment-list> <comment-list v-if="styles.previewGutterWidth"></comment-list>
@ -37,9 +37,15 @@ export default {
...mapGetters('file', [ ...mapGetters('file', [
'isCurrentTemp', 'isCurrentTemp',
]), ]),
...mapGetters('theme', [
'currPreviewTheme',
]),
...mapGetters('layout', [ ...mapGetters('layout', [
'styles', 'styles',
]), ]),
previewTheme() {
return `preview-theme--${this.currPreviewTheme || 'default'}`;
},
}, },
methods: { methods: {
...mapActions('data', [ ...mapActions('data', [

View 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>

View File

@ -23,6 +23,8 @@
<div v-else-if="panel === 'help'" class="side-bar__panel side-bar__panel--help"> <div v-else-if="panel === 'help'" class="side-bar__panel side-bar__panel--help">
<pre class="markdown-highlighting" v-html="markdownSample"></pre> <pre class="markdown-highlighting" v-html="markdownSample"></pre>
</div> </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'}"> <div class="side-bar__panel side-bar__panel--toc" :class="{'side-bar__panel--hidden': panel !== 'toc'}">
<toc> <toc>
</toc> </toc>
@ -41,6 +43,8 @@ import PublishMenu from './menus/PublishMenu';
import HistoryMenu from './menus/HistoryMenu'; import HistoryMenu from './menus/HistoryMenu';
import ImportExportMenu from './menus/ImportExportMenu'; import ImportExportMenu from './menus/ImportExportMenu';
import WorkspaceBackupMenu from './menus/WorkspaceBackupMenu'; import WorkspaceBackupMenu from './menus/WorkspaceBackupMenu';
import EditThemeMenu from './menus/EditThemeMenu';
import PreviewThemeMenu from './menus/PreviewThemeMenu';
import markdownSample from '../data/markdownSample.md'; import markdownSample from '../data/markdownSample.md';
import markdownConversionSvc from '../services/markdownConversionSvc'; import markdownConversionSvc from '../services/markdownConversionSvc';
import store from '../store'; import store from '../store';
@ -55,6 +59,8 @@ const panelNames = {
history: '文件历史', history: '文件历史',
importExport: '导入/导出', importExport: '导入/导出',
workspaceBackups: '文档空间备份', workspaceBackups: '文档空间备份',
editTheme: '编辑区主题',
previewTheme: '预览区主题',
}; };
export default { export default {
@ -67,6 +73,8 @@ export default {
HistoryMenu, HistoryMenu,
ImportExportMenu, ImportExportMenu,
WorkspaceBackupMenu, WorkspaceBackupMenu,
EditThemeMenu,
PreviewThemeMenu,
}, },
data: () => ({ data: () => ({
markdownSample: markdownConversionSvc.highlight(markdownSample), markdownSample: markdownConversionSvc.highlight(markdownSample),

View 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>

View 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>

View File

@ -8,7 +8,7 @@
</option> </option>
</select> </select>
</p> </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="loading">历史版本加载中</p>
<p v-else-if="!revisionsWithSpacer.length"><b>{{currentFileName}}</b> 没有历史版本.</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> <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"> <div class="revision__header flex flex--column">
<user-name :user-id="revision.sub"></user-name> <user-name :user-id="revision.sub"></user-name>
<div class="revision__created">{{revision.created | formatTime}}</div> <div class="revision__created">{{revision.created | formatTime}}</div>
<div class="revision__msg">{{revision.message}}</div>
</div> </div>
</a> </a>
</div> </div>
@ -54,6 +55,7 @@ import EditorClassApplier from '../common/EditorClassApplier';
import PreviewClassApplier from '../common/PreviewClassApplier'; import PreviewClassApplier from '../common/PreviewClassApplier';
import utils from '../../services/utils'; import utils from '../../services/utils';
import giteeHelper from '../../services/providers/helpers/giteeHelper'; import giteeHelper from '../../services/providers/helpers/giteeHelper';
import githubHelper from '../../services/providers/helpers/githubHelper';
import syncSvc from '../../services/syncSvc'; import syncSvc from '../../services/syncSvc';
import store from '../../store'; import store from '../../store';
import badgeSvc from '../../services/badgeSvc'; import badgeSvc from '../../services/badgeSvc';
@ -167,6 +169,16 @@ export default {
async signin() { async signin() {
try { try {
await giteeHelper.signin(); await giteeHelper.signin();
await syncSvc.afterSignIn();
syncSvc.requestSync();
} catch (e) {
// Cancel
}
},
async signinWithGithub() {
try {
await githubHelper.signin();
await syncSvc.afterSignIn();
syncSvc.requestSync(); syncSvc.requestSync();
} catch (e) { } catch (e) {
// Cancel // Cancel
@ -412,6 +424,14 @@ export default {
opacity: 0.6; 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 { .layout--revision {
.cledit-section *, .cledit-section *,
.cl-preview-section * { .cl-preview-section * {

View File

@ -14,6 +14,9 @@
<span v-if="currentWorkspace.providerId === 'giteeAppData'"> <span v-if="currentWorkspace.providerId === 'giteeAppData'">
<b>{{currentWorkspace.name}}</b> 与您的 Gitee 默认文档空间仓库同步 <b>{{currentWorkspace.name}}</b> 与您的 Gitee 默认文档空间仓库同步
</span> </span>
<span v-else-if="currentWorkspace.providerId === 'githubAppData'">
<b>{{currentWorkspace.name}}</b> 与您的 GitHub 默认文档空间仓库同步
</span>
<span v-else-if="currentWorkspace.providerId === 'googleDriveWorkspace'"> <span v-else-if="currentWorkspace.providerId === 'googleDriveWorkspace'">
<b>{{currentWorkspace.name}}</b> <a :href="workspaceLocationUrl" target="_blank">Google Drive 文件夹</a>同步 <b>{{currentWorkspace.name}}</b> <a :href="workspaceLocationUrl" target="_blank">Google Drive 文件夹</a>同步
</span> </span>
@ -21,10 +24,10 @@
<b>{{currentWorkspace.name}}</b> <a :href="workspaceLocationUrl" target="_blank">CouchDB 数据库</a>同步 <b>{{currentWorkspace.name}}</b> <a :href="workspaceLocationUrl" target="_blank">CouchDB 数据库</a>同步
</span> </span>
<span v-else-if="currentWorkspace.providerId === 'githubWorkspace'"> <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>
<span v-else-if="currentWorkspace.providerId === 'giteeWorkspace'"> <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>
<span v-else-if="currentWorkspace.providerId === 'gitlabWorkspace'"> <span v-else-if="currentWorkspace.providerId === 'gitlabWorkspace'">
<b>{{currentWorkspace.name}}</b> <a :href="workspaceLocationUrl" target="_blank">GitLab 项目</a>同步 <b>{{currentWorkspace.name}}</b> <a :href="workspaceLocationUrl" target="_blank">GitLab 项目</a>同步
@ -45,6 +48,11 @@
<div>使用 Gitee 登录</div> <div>使用 Gitee 登录</div>
<span>同步您的主文档空间并解锁功能</span> <span>同步您的主文档空间并解锁功能</span>
</menu-entry> </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')"> <menu-entry @click.native="setPanel('workspaces')">
<icon-database slot="icon"></icon-database> <icon-database slot="icon"></icon-database>
<div><div class="menu-entry__label menu-entry__label--count" v-if="workspaceCount">{{workspaceCount}}</div> 文档空间</div> <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> <div><div class="menu-entry__label menu-entry__label--count">{{templateCount}}</div> 模板</div>
<span>为您的导出配置 Handlebars 模板</span> <span>为您的导出配置 Handlebars 模板</span>
</menu-entry> </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"> <menu-entry @click.native="settings">
<icon-settings slot="icon"></icon-settings> <icon-settings slot="icon"></icon-settings>
<div>配置</div> <div>配置</div>
@ -132,6 +150,7 @@ import MenuEntry from './common/MenuEntry';
import providerRegistry from '../../services/providers/common/providerRegistry'; import providerRegistry from '../../services/providers/common/providerRegistry';
import UserImage from '../UserImage'; import UserImage from '../UserImage';
import giteeHelper from '../../services/providers/helpers/giteeHelper'; import giteeHelper from '../../services/providers/helpers/giteeHelper';
import githubHelper from '../../services/providers/helpers/githubHelper';
import syncSvc from '../../services/syncSvc'; import syncSvc from '../../services/syncSvc';
import userSvc from '../../services/userSvc'; import userSvc from '../../services/userSvc';
import store from '../../store'; import store from '../../store';
@ -184,6 +203,16 @@ export default {
async signin() { async signin() {
try { try {
await giteeHelper.signin(); await giteeHelper.signin();
await syncSvc.afterSignIn();
syncSvc.requestSync();
} catch (e) {
// Cancel
}
},
async signinWithGithub() {
try {
await githubHelper.signin();
await syncSvc.afterSignIn();
syncSvc.requestSync(); syncSvc.requestSync();
} catch (e) { } catch (e) {
// Cancel // Cancel

View 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>

View File

@ -43,7 +43,7 @@
<div v-for="token in githubTokens" :key="token.sub"> <div v-for="token in githubTokens" :key="token.sub">
<menu-entry @click.native="publishGist(token)"> <menu-entry @click.native="publishGist(token)">
<icon-provider slot="icon" provider-id="gist"></icon-provider> <icon-provider slot="icon" provider-id="gist"></icon-provider>
<div>发布到 Gist</div> <div>发布到 GitHubGist</div>
<span>{{token.name}}</span> <span>{{token.name}}</span>
</menu-entry> </menu-entry>
<menu-entry @click.native="publishGithub(token)"> <menu-entry @click.native="publishGithub(token)">
@ -53,6 +53,11 @@
</menu-entry> </menu-entry>
</div> </div>
<div v-for="token in giteeTokens" :key="token.sub"> <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)"> <menu-entry @click.native="publishGitee(token)">
<icon-provider slot="icon" provider-id="gitee"></icon-provider> <icon-provider slot="icon" provider-id="gitee"></icon-provider>
<div>发布到 Gitee</div> <div>发布到 Gitee</div>
@ -258,8 +263,8 @@ export default {
}, },
async addGitlabAccount() { async addGitlabAccount() {
try { try {
const { serverUrl, applicationId } = await store.dispatch('modal/open', { type: 'gitlabAccount' }); const { serverUrl, applicationId, applicationSecret } = await store.dispatch('modal/open', { type: 'gitlabAccount' });
await gitlabHelper.addAccount(serverUrl, applicationId); await gitlabHelper.addAccount(serverUrl, applicationId, applicationSecret);
} catch (e) { /* cancel */ } } catch (e) { /* cancel */ }
}, },
async addGiteaAccount() { async addGiteaAccount() {
@ -289,8 +294,9 @@ export default {
publishBloggerPage: publishModalOpener('bloggerPagePublish', 'publishToBloggerPage'), publishBloggerPage: publishModalOpener('bloggerPagePublish', 'publishToBloggerPage'),
publishDropbox: publishModalOpener('dropboxPublish', 'publishToDropbox'), publishDropbox: publishModalOpener('dropboxPublish', 'publishToDropbox'),
publishGithub: publishModalOpener('githubPublish', 'publishToGithub'), publishGithub: publishModalOpener('githubPublish', 'publishToGithub'),
publishGitee: publishModalOpener('giteePublish', 'publishToGitee'),
publishGist: publishModalOpener('gistPublish', 'publishToGist'), publishGist: publishModalOpener('gistPublish', 'publishToGist'),
publishGitee: publishModalOpener('giteePublish', 'publishToGitee'),
publishGiteeGist: publishModalOpener('giteeGistPublish', 'publishGiteeGist'),
publishGitlab: publishModalOpener('gitlabPublish', 'publishToGitlab'), publishGitlab: publishModalOpener('gitlabPublish', 'publishToGitlab'),
publishGitea: publishModalOpener('giteaPublish', 'publishToGitea'), publishGitea: publishModalOpener('giteaPublish', 'publishToGitea'),
publishGoogleDrive: publishModalOpener('googleDrivePublish', 'publishToGoogleDrive'), publishGoogleDrive: publishModalOpener('googleDrivePublish', 'publishToGoogleDrive'),

View File

@ -46,7 +46,7 @@
</menu-entry> </menu-entry>
<menu-entry @click.native="saveGist(token)"> <menu-entry @click.native="saveGist(token)">
<icon-provider slot="icon" provider-id="gist"></icon-provider> <icon-provider slot="icon" provider-id="gist"></icon-provider>
<div>在Gist上保存</div> <div>在GitHubGist上保存</div>
<span>{{token.name}}</span> <span>{{token.name}}</span>
</menu-entry> </menu-entry>
</div> </div>
@ -61,6 +61,11 @@
<div>在Gitee上保存</div> <div>在Gitee上保存</div>
<span>{{token.name}}</span> <span>{{token.name}}</span>
</menu-entry> </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>
<div v-for="token in gitlabTokens" :key="token.sub"> <div v-for="token in gitlabTokens" :key="token.sub">
<menu-entry @click.native="openGitlab(token)"> <menu-entry @click.native="openGitlab(token)">
@ -234,8 +239,8 @@ export default {
}, },
async addGitlabAccount() { async addGitlabAccount() {
try { try {
const { serverUrl, applicationId } = await store.dispatch('modal/open', { type: 'gitlabAccount' }); const { serverUrl, applicationId, applicationSecret } = await store.dispatch('modal/open', { type: 'gitlabAccount' });
await gitlabHelper.addAccount(serverUrl, applicationId); await gitlabHelper.addAccount(serverUrl, applicationId, applicationSecret);
} catch (e) { /* cancel */ } } catch (e) { /* cancel */ }
}, },
async addGiteaAccount() { async addGiteaAccount() {
@ -330,6 +335,12 @@ export default {
badgeSvc.addBadge('saveOnGist'); badgeSvc.addBadge('saveOnGist');
} catch (e) { /* cancel */ } } catch (e) { /* cancel */ }
}, },
async saveGiteeGist(token) {
try {
await openSyncModal(token, 'giteeGistSync');
badgeSvc.addBadge('saveOnGiteeGist');
} catch (e) { /* cancel */ }
},
async openGitlab(token) { async openGitlab(token) {
try { try {
const syncLocation = await store.dispatch('modal/open', { const syncLocation = await store.dispatch('modal/open', {

View File

@ -8,7 +8,8 @@
<hr> <hr>
<div class="workspace" v-for="(workspace, id) in workspacesById" :key="id"> <div class="workspace" v-for="(workspace, id) in workspacesById" :key="id">
<menu-entry :href="workspace.url" target="_blank"> <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> <div class="workspace__name"><div class="menu-entry__label" v-if="currentWorkspace === workspace">当前</div>{{workspace.name}}</div>
</menu-entry> </menu-entry>
</div> </div>
@ -85,8 +86,8 @@ export default {
}, },
async addGitlabWorkspace() { async addGitlabWorkspace() {
try { try {
const { serverUrl, applicationId } = await store.dispatch('modal/open', { type: 'gitlabAccount' }); const { serverUrl, applicationId, applicationSecret } = await store.dispatch('modal/open', { type: 'gitlabAccount' });
const token = await gitlabHelper.addAccount(serverUrl, applicationId); const token = await gitlabHelper.addAccount(serverUrl, applicationId, applicationSecret);
store.dispatch('modal/open', { store.dispatch('modal/open', {
type: 'gitlabWorkspace', type: 'gitlabWorkspace',
token, token,

View File

@ -248,8 +248,8 @@ export default {
}, },
async addGitlabAccount() { async addGitlabAccount() {
try { try {
const { serverUrl, applicationId } = await store.dispatch('modal/open', { type: 'gitlabAccount' }); const { serverUrl, applicationId, applicationSecret } = await store.dispatch('modal/open', { type: 'gitlabAccount' });
await gitlabHelper.addAccount(serverUrl, applicationId); await gitlabHelper.addAccount(serverUrl, applicationId, applicationSecret);
} catch (e) { /* cancel */ } } catch (e) { /* cancel */ }
}, },
async addGiteaAccount() { async addGiteaAccount() {

View 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>

View 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>

View File

@ -1,18 +1,35 @@
<template> <template>
<modal-inner aria-label="插入图像"> <modal-inner aria-label="插入图像">
<div class="modal__content"> <div class="modal__content">
<p v-if="hasFile"> <p>请为您的图像提供<b> url </b><span v-if="uploading">(图片上传中...)</span></p>
<span v-if="uploading">粘贴/拖拽图片上传中...</span> <form-entry label="URL" error="url">
<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">
<input slot="field" class="textfield" type="text" v-model.trim="url" @keydown.enter="resolve"> <input slot="field" class="textfield" type="text" v-model.trim="url" @keydown.enter="resolve">
</form-entry> </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"> <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 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> <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> <span class="line-entry" v-if="token.params">自定义Form参数{{token.params}}</span>
</menu-item> </menu-item>
</menu-entry> </menu-entry>
<menu-entry @click.native="checkedImgDest(tokenStorage.sid, tokenStorage.providerId)" v-for="tokenStorage in tokensImgStorages" :key="tokenStorage.sid"> <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.sub === tokenStorage.sid" slot="icon"></icon-check-circle> <icon-check-circle v-if="checkedStorage.sid === tokenStorage.sid" slot="icon"></icon-check-circle>
<icon-check-circle-un v-if="checkedStorage.sub !== tokenStorage.sid" slot="icon"></icon-check-circle-un> <icon-check-circle-un v-if="checkedStorage.sid !== tokenStorage.sid" slot="icon"></icon-check-circle-un>
<menu-item> <menu-item>
<icon-provider slot="icon" :provider-id="tokenStorage.providerId"></icon-provider> <icon-provider slot="icon" :provider-id="tokenStorage.providerId"></icon-provider>
<div>{{tokenStorage.providerName}} <div>{{tokenStorage.providerName}}
@ -43,12 +60,10 @@
<span> {{tokenStorage.uname}}, 仓库URL: {{tokenStorage.repoUrl}}, 路径: {{tokenStorage.path}}, 分支: {{tokenStorage.branch}}</span> <span> {{tokenStorage.uname}}, 仓库URL: {{tokenStorage.repoUrl}}, 路径: {{tokenStorage.path}}, 分支: {{tokenStorage.branch}}</span>
</menu-item> </menu-item>
</menu-entry> </menu-entry>
</div> <menu-entry @click.native="addWorkspaceImgPath">
<div class="modal__button-bar"> <icon-provider slot="icon" :provider-id="currentWorkspace.providerId"></icon-provider>
<button class="button" @click="reject()">取消</button> <span>添加当前文档空间图片路径</span>
<button class="button button--resolve" @click="resolve" :disabled="uploading">确认</button> </menu-entry>
</div>
<div>
<menu-entry @click.native="addSmmsAccount"> <menu-entry @click.native="addSmmsAccount">
<icon-provider slot="icon" provider-id="smms"></icon-provider> <icon-provider slot="icon" provider-id="smms"></icon-provider>
<span>添加SM.MS图床账号</span> <span>添加SM.MS图床账号</span>
@ -70,6 +85,7 @@
</template> </template>
<script> <script>
import { mapGetters } from 'vuex';
import modalTemplate from './common/modalTemplate'; import modalTemplate from './common/modalTemplate';
import MenuEntry from '../menus/common/MenuEntry'; import MenuEntry from '../menus/common/MenuEntry';
import MenuItem from '../menus/common/MenuItem'; 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 githubHelper from '../../services/providers/helpers/githubHelper';
import customHelper from '../../services/providers/helpers/customHelper'; import customHelper from '../../services/providers/helpers/customHelper';
import utils from '../../services/utils'; import utils from '../../services/utils';
import imageSvc from '../../services/imageSvc';
export default modalTemplate({ export default modalTemplate({
components: { components: {
@ -86,14 +103,24 @@ export default modalTemplate({
MenuItem, MenuItem,
}, },
data: () => ({ data: () => ({
hasFile: false,
uploading: false, uploading: false,
url: '', url: '',
}), }),
computed: { computed: {
...mapGetters('workspace', [
'currentWorkspace',
'currentWorkspaceIsGit',
]),
checkedStorage() { checkedStorage() {
return store.getters['img/getCheckedStorage']; return store.getters['img/getCheckedStorage'];
}, },
workspaceImgPath() {
if (!this.currentWorkspaceIsGit) {
return [];
}
const workspaceImgPath = store.getters['img/getWorkspaceImgPath'];
return Object.keys(workspaceImgPath || {});
},
imageTokens() { imageTokens() {
return [ return [
...Object.values(store.getters['data/smmsTokensBySub']).map(token => ({ ...Object.values(store.getters['data/smmsTokensBySub']).map(token => ({
@ -137,92 +164,12 @@ export default modalTemplate({
uname: it.token.name, uname: it.token.name,
providerId: it.providerId, providerId: it.providerId,
providerName: it.providerName, 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; 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: { methods: {
resolve(evt) { resolve(evt) {
evt.preventDefault(); // Fixes https://github.com/mafgwo/stackedit/issues/1503 evt.preventDefault(); // Fixes https://github.com/mafgwo/stackedit/issues/1503
@ -239,6 +186,27 @@ export default modalTemplate({
this.config.reject(); this.config.reject();
callback(null); 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) { async remove(proivderId, item) {
try { try {
await store.dispatch('modal/open', 'imgStorageDeletion'); await store.dispatch('modal/open', 'imgStorageDeletion');
@ -258,6 +226,13 @@ export default modalTemplate({
// Cancel // 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() { async addSmmsAccount() {
const { proxyUrl, apiSecretToken } = await store.dispatch('modal/open', { type: 'smmsAccount' }); const { proxyUrl, apiSecretToken } = await store.dispatch('modal/open', { type: 'smmsAccount' });
await smmsHelper.addAccount(proxyUrl, apiSecretToken); await smmsHelper.addAccount(proxyUrl, apiSecretToken);
@ -288,15 +263,19 @@ export default modalTemplate({
githubHelper.updateToken(token, imgStorageInfo); githubHelper.updateToken(token, imgStorageInfo);
} catch (e) { /* Cancel */ } } catch (e) { /* Cancel */ }
}, },
async checkedImgDest(sub, provider) { async checkedImgDest(sub, provider, sid) {
let type = 'token'; let type = 'token';
if (provider === 'gitea' || provider === 'github') { //
if (!provider) {
type = 'workspace';
} else if (provider === 'gitea' || provider === 'github') {
type = 'tokenRepo'; type = 'tokenRepo';
} }
store.dispatch('img/changeCheckedStorage', { store.dispatch('img/changeCheckedStorage', {
type, type,
provider, provider,
sub, sub,
sid,
}); });
// const { callback } = this.config; // const { callback } = this.config;
// this.config.reject(); // this.config.reject();

View File

@ -1,7 +1,7 @@
<template> <template>
<modal-inner aria-label="导出到PDF"> <modal-inner aria-label="导出到PDF">
<div class="modal__content"> <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="模板"> <form-entry label="模板">
<select class="textfield" slot="field" v-model="selectedTemplate" @keydown.enter="resolve()"> <select class="textfield" slot="field" v-model="selectedTemplate" @keydown.enter="resolve()">
<option v-for="(template, id) in allTemplatesById" :key="id" :value="id"> <option v-for="(template, id) in allTemplatesById" :key="id" :value="id">

View File

@ -4,8 +4,8 @@
<div class="modal__image"> <div class="modal__image">
<icon-upload></icon-upload> <icon-upload></icon-upload>
</div> </div>
<p v-if="publishLocations.length"><b>{{currentFileName}}</b> is published to the following location(s):</p> <p v-if="publishLocations.length"><b>{{currentFileName}}</b> 被发布到了以下位置:</p>
<p v-else><b>{{currentFileName}}</b> is not published yet.</p> <p v-else><b>{{currentFileName}}</b> 还没有被发布.</p>
<div> <div>
<div class="publish-entry flex flex--column" v-for="location in publishLocations" :key="location.id"> <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"> <div class="publish-entry__header flex flex--row flex--align-center">
@ -26,7 +26,7 @@
{{location.url}} {{location.url}}
</div> </div>
<div class="publish-entry__buttons flex flex--row flex--center" v-if="location.url"> <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> <icon-content-copy></icon-content-copy>
</button> </button>
<a class="publish-entry__button button" v-if="location.url" :href="location.url" target="_blank" v-title="'打开位置'"> <a class="publish-entry__button button" v-if="location.url" :href="location.url" target="_blank" v-title="'打开位置'">
@ -34,6 +34,19 @@
</a> </a>
</div> </div>
</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> </div>
<div class="modal__info" v-if="publishLocations.length"> <div class="modal__info" v-if="publishLocations.length">
@ -75,6 +88,16 @@ export default {
store.commit('publishLocation/deleteItem', location.id); store.commit('publishLocation/deleteItem', location.id);
badgeSvc.addBadge('removePublishLocation'); 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> </script>

View 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>

View File

@ -9,7 +9,8 @@
<div class="flex flex--column"> <div class="flex flex--column">
<div class="workspace-entry__header flex flex--row flex--align-center"> <div class="workspace-entry__header flex flex--row flex--align-center">
<div class="workspace-entry__icon"> <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> </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"> <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> <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="'编辑名称'"> <button class="workspace-entry__button button" @click="edit(id)" v-title="'编辑名称'">
<icon-pen></icon-pen> <icon-pen></icon-pen>
</button> </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="'删除'"> <button class="workspace-entry__button button" @click="remove(id)" v-title="'删除'">
<icon-delete></icon-delete> <icon-delete></icon-delete>
</button> </button>
@ -122,12 +132,53 @@ export default {
this.info('请先关闭文档空间,然后再将其删除。'); this.info('请先关闭文档空间,然后再将其删除。');
} else { } else {
try { 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); workspaceSvc.removeWorkspace(id);
badgeSvc.addBadge('removeWorkspace'); badgeSvc.addBadge('removeWorkspace');
} catch (e) { /* Cancel */ } } 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() { created() {
Object.keys(this.workspacesById).forEach(async (workspaceId) => { Object.keys(this.workspacesById).forEach(async (workspaceId) => {

View File

@ -17,7 +17,7 @@ export default {
uid: utils.uid(), uid: utils.uid(),
}), }),
mounted() { mounted() {
this.$el.querySelector('input,select').id = this.uid; this.$el.querySelector('input,select,textarea').id = this.uid;
}, },
}; };
</script> </script>

View File

@ -1,11 +1,11 @@
<template> <template>
<modal-inner aria-label="发布到Gist"> <modal-inner aria-label="发布到GitHubGist">
<div class="modal__content"> <div class="modal__content">
<div class="modal__image"> <div class="modal__image">
<icon-provider provider-id="gist"></icon-provider> <icon-provider provider-id="gist"></icon-provider>
</div> </div>
<p>发布<b> {{CurrentFileName}} </b><b>Gist</b></p> <p>发布<b> {{CurrentFileName}} </b><b>GitHubGist</b></p>
<form-entry label="Filename" error="filename"> <form-entry label="文件名" error="filename">
<input slot="field" class="textfield" type="text" v-model.trim="filename" @keydown.enter="resolve()"> <input slot="field" class="textfield" type="text" v-model.trim="filename" @keydown.enter="resolve()">
</form-entry> </form-entry>
<div class="form-entry"> <div class="form-entry">
@ -15,10 +15,10 @@
</label> </label>
</div> </div>
</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()"> <input slot="field" class="textfield" type="text" v-model.trim="gistId" @keydown.enter="resolve()">
<div class="form-entry__info"> <div class="form-entry__info">
如果文件存在于Gist中则将被覆盖 如果文件存在于GitHubGist中则将被覆盖
</div> </div>
</form-entry> </form-entry>
<form-entry label="Template"> <form-entry label="Template">

View File

@ -1,11 +1,11 @@
<template> <template>
<modal-inner aria-label=" Gist 同步"> <modal-inner aria-label=" GitHubGist 同步">
<div class="modal__content"> <div class="modal__content">
<div class="modal__image"> <div class="modal__image">
<icon-provider provider-id="gist"></icon-provider> <icon-provider provider-id="gist"></icon-provider>
</div> </div>
<p><b> {{currentFileName}} </b>保存到<b>Gist</b>并保持同步</p> <p><b> {{currentFileName}} </b>保存到<b>GitHubGist</b>并保持同步</p>
<form-entry label="Filename" error="filename"> <form-entry label="文件名" error="filename">
<input slot="field" class="textfield" type="text" v-model.trim="filename" @keydown.enter="resolve()"> <input slot="field" class="textfield" type="text" v-model.trim="filename" @keydown.enter="resolve()">
</form-entry> </form-entry>
<div class="form-entry"> <div class="form-entry">
@ -15,10 +15,10 @@
</label> </label>
</div> </div>
</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()"> <input slot="field" class="textfield" type="text" v-model.trim="gistId" @keydown.enter="resolve()">
<div class="form-entry__info"> <div class="form-entry__info">
如果文件存在于Gist中则将被覆盖 如果文件存在于GitHubGist中则将被覆盖
</div> </div>
</form-entry> </form-entry>
</div> </div>

View File

@ -5,6 +5,7 @@
<icon-provider provider-id="gitea"></icon-provider> <icon-provider provider-id="gitea"></icon-provider>
</div> </div>
<p>将您的<b>Gitea</b>链接到<b>StackEdit中文版</b></p> <p>将您的<b>Gitea</b>链接到<b>StackEdit中文版</b></p>
<template v-if="!useServerConf">
<form-entry label="Gitea URL" error="serverUrl"> <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-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()"> <input v-else slot="field" class="textfield" type="text" v-model.trim="serverUrl" @keydown.enter="resolve()">
@ -27,6 +28,7 @@
<a href="https://docs.gitea.io/en-us/oauth2-provider/" target="_blank">更多信息</a> <a href="https://docs.gitea.io/en-us/oauth2-provider/" target="_blank">更多信息</a>
</div> </div>
</form-entry> </form-entry>
</template>
</div> </div>
<div class="modal__button-bar"> <div class="modal__button-bar">
<button class="button" @click="config.reject()">取消</button> <button class="button" @click="config.reject()">取消</button>
@ -38,6 +40,8 @@
<script> <script>
import modalTemplate from '../common/modalTemplate'; import modalTemplate from '../common/modalTemplate';
import constants from '../../../data/constants'; import constants from '../../../data/constants';
import store from '../../../store';
import networkSvc from '../../../services/networkSvc';
export default modalTemplate({ export default modalTemplate({
data: () => ({ data: () => ({
@ -55,9 +59,22 @@ export default modalTemplate({
} }
return null; return null;
}, },
// 使
useServerConf() {
const confClientId = store.getters['data/serverConf'].giteaClientId;
const confServerUrl = store.getters['data/serverConf'].giteaUrl;
return !!confClientId && !!confServerUrl;
},
},
mounted() {
networkSvc.getServerConf();
}, },
methods: { methods: {
resolve() { resolve() {
if (this.useServerConf) {
this.config.resolve({});
return;
}
const serverUrl = this.config.forceServerUrl || this.serverUrl; const serverUrl = this.config.forceServerUrl || this.serverUrl;
if (!serverUrl) { if (!serverUrl) {
this.setError('serverUrl'); this.setError('serverUrl');

View File

@ -12,9 +12,10 @@
</div> </div>
</form-entry> </form-entry>
<form-entry label="文件夹路径" info="可选的"> <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"> <div class="form-entry__info">
如果不提供默认为 {YYYY}/{MM}/{DD} 其中{YYYY}为年变量{MM}为月变量{DD}为日变量 如果不提供默认为 imgs/{YYYY}-{MM}-{DD} <br/>
变量说明{YYYY}为年变量{MM}为月变量{DD}为日变量{MDNAME}为当前文档名称
</div> </div>
</form-entry> </form-entry>
<form-entry label="分支" info="可选的"> <form-entry label="分支" info="可选的">
@ -62,7 +63,7 @@ export default modalTemplate({
const path = this.path && this.path.replace(/^\//, ''); const path = this.path && this.path.replace(/^\//, '');
this.config.resolve({ this.config.resolve({
repoUri: projectPath, repoUri: projectPath,
path: path || '{YYYY}/{MM}/{DD}', path: path || 'imgs/{YYYY}-{MM}-{DD}',
branch: this.branch || 'master', branch: this.branch || 'master',
}); });
} catch (err) { } catch (err) {

View File

@ -4,11 +4,11 @@
<div class="modal__image"> <div class="modal__image">
<icon-provider provider-id="gitea"></icon-provider> <icon-provider provider-id="gitea"></icon-provider>
</div> </div>
<p>向您的<b> Gitea </b>项目发布<b> {{CurrentFileName}} </b></p> <p>向您的<b> Gitea </b>项目发布<b> {{ currentFileName }} </b></p>
<form-entry label="Project URL" error="projectUrl"> <form-entry label="Project URL" error="projectUrl">
<input slot="field" class="textfield" type="text" v-model.trim="projectUrl" @keydown.enter="resolve()"> <input slot="field" class="textfield" type="text" v-model.trim="projectUrl" @keydown.enter="resolve()">
<div class="form-entry__info"> <div class="form-entry__info">
<b>例如:</b> {{config.token.serverUrl}}/path/to/project <b>例如:</b> {{ config.token.serverUrl }}/path/to/project
</div> </div>
</form-entry> </form-entry>
<form-entry label="File path" error="path"> <form-entry label="File path" error="path">

View 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>

View 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>

View File

@ -4,7 +4,7 @@
<div class="modal__image"> <div class="modal__image">
<icon-provider provider-id="gitee"></icon-provider> <icon-provider provider-id="gitee"></icon-provider>
</div> </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"> <form-entry label="仓库URL" error="repoUrl">
<input slot="field" class="textfield" type="text" v-model.trim="repoUrl" @keydown.enter="resolve()"> <input slot="field" class="textfield" type="text" v-model.trim="repoUrl" @keydown.enter="resolve()">
<div class="form-entry__info"> <div class="form-entry__info">

View File

@ -4,7 +4,7 @@
<div class="modal__image"> <div class="modal__image">
<icon-provider provider-id="gitee"></icon-provider> <icon-provider provider-id="gitee"></icon-provider>
</div> </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"> <form-entry label="仓库URL" error="repoUrl">
<input slot="field" class="textfield" type="text" v-model.trim="repoUrl" @keydown.enter="resolve()"> <input slot="field" class="textfield" type="text" v-model.trim="repoUrl" @keydown.enter="resolve()">
<div class="form-entry__info"> <div class="form-entry__info">

View File

@ -12,9 +12,10 @@
</div> </div>
</form-entry> </form-entry>
<form-entry label="文件夹路径" info="可选的"> <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"> <div class="form-entry__info">
如果不提供默认为 {YYYY}/{MM}/{DD} 其中{YYYY}为年变量{MM}为月变量{DD}为日变量 如果不提供默认为 imgs/{YYYY}-{MM}-{DD} <br/>
变量说明{YYYY}为年变量{MM}为月变量{DD}为日变量{MDNAME}为当前文档名称
</div> </div>
</form-entry> </form-entry>
<form-entry label="分支" info="可选的"> <form-entry label="分支" info="可选的">
@ -63,7 +64,7 @@ export default modalTemplate({
this.config.resolve({ this.config.resolve({
owner, owner,
repo, repo,
path: path || '{YYYY}/{MM}/{DD}', path: path || 'imgs/{YYYY}-{MM}-{DD}',
branch: this.branch || 'master', branch: this.branch || 'master',
}); });
} catch (err) { } catch (err) {

View File

@ -4,7 +4,7 @@
<div class="modal__image"> <div class="modal__image">
<icon-provider provider-id="github"></icon-provider> <icon-provider provider-id="github"></icon-provider>
</div> </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"> <form-entry label="仓库URL" error="repoUrl">
<input slot="field" class="textfield" type="text" v-model.trim="repoUrl" @keydown.enter="resolve()"> <input slot="field" class="textfield" type="text" v-model.trim="repoUrl" @keydown.enter="resolve()">
<div class="form-entry__info"> <div class="form-entry__info">

View File

@ -5,15 +5,22 @@
<icon-provider provider-id="gitlab"></icon-provider> <icon-provider provider-id="gitlab"></icon-provider>
</div> </div>
<p>将您的<b>GitLab</b>链接到<b>StackEdit中文版</b></p> <p>将您的<b>GitLab</b>链接到<b>StackEdit中文版</b></p>
<template v-if="!useServerConf">
<form-entry label="GitLab URL" error="serverUrl"> <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-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()"> <input v-else slot="field" class="textfield" type="text" v-model.trim="serverUrl" @keydown.enter="resolve()">
<div class="form-entry__info"> <div class="form-entry__info">
<b>例如:</b> https://gitlab.example.com/ <b>例如:</b> https://gitlab.example.com/
<span v-if="httpAppUrl">
非https的URL请跳转到 <a :href="httpAppUrl" target="_blank">HTTP链接</a> 添加Gitlab
</span>
</div> </div>
</form-entry> </form-entry>
<form-entry label="Application ID" error="applicationId"> <form-entry label="Application ID" error="applicationId">
<input slot="field" class="textfield" type="text" v-model.trim="applicationId" @keydown.enter="resolve()"> <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"> <div class="form-entry__info">
您必须使用重定向url <b>{{redirectUrl}}</b>配置OAuth2应用程序 您必须使用重定向url <b>{{redirectUrl}}</b>配置OAuth2应用程序
</div> </div>
@ -21,6 +28,7 @@
<a href="https://docs.gitlab.com/ee/integration/oauth_provider.html" target="_blank">更多信息</a> <a href="https://docs.gitlab.com/ee/integration/oauth_provider.html" target="_blank">更多信息</a>
</div> </div>
</form-entry> </form-entry>
</template>
</div> </div>
<div class="modal__button-bar"> <div class="modal__button-bar">
<button class="button" @click="config.reject()">取消</button> <button class="button" @click="config.reject()">取消</button>
@ -32,6 +40,8 @@
<script> <script>
import modalTemplate from '../common/modalTemplate'; import modalTemplate from '../common/modalTemplate';
import constants from '../../../data/constants'; import constants from '../../../data/constants';
import store from '../../../store';
import networkSvc from '../../../services/networkSvc';
export default modalTemplate({ export default modalTemplate({
data: () => ({ data: () => ({
@ -40,9 +50,31 @@ export default modalTemplate({
computedLocalSettings: { computedLocalSettings: {
serverUrl: 'gitlabServerUrl', serverUrl: 'gitlabServerUrl',
applicationId: 'gitlabApplicationId', 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: { methods: {
resolve() { resolve() {
if (this.useServerConf) {
this.config.resolve({});
return;
}
const serverUrl = this.config.forceServerUrl || this.serverUrl; const serverUrl = this.config.forceServerUrl || this.serverUrl;
if (!serverUrl) { if (!serverUrl) {
this.setError('serverUrl'); this.setError('serverUrl');
@ -50,14 +82,18 @@ export default modalTemplate({
if (!this.applicationId) { if (!this.applicationId) {
this.setError('applicationId'); this.setError('applicationId');
} }
if (serverUrl && this.applicationId) { if (!this.applicationSecret) {
const parsedUrl = serverUrl.match(/^(https:\/\/[^/]+)/); this.setError('applicationSecret');
}
if (serverUrl && this.applicationId && this.applicationSecret) {
const parsedUrl = serverUrl.match(/^(http[s]?:\/\/[^/]+)/);
if (!parsedUrl) { if (!parsedUrl) {
this.setError('serverUrl'); this.setError('serverUrl');
} else { } else {
this.config.resolve({ this.config.resolve({
serverUrl: parsedUrl[1], serverUrl: parsedUrl[1],
applicationId: this.applicationId, applicationId: this.applicationId,
applicationSecret: this.applicationSecret,
}); });
} }
} }

View File

@ -4,7 +4,7 @@
<div class="modal__image"> <div class="modal__image">
<icon-provider provider-id="gitlab"></icon-provider> <icon-provider provider-id="gitlab"></icon-provider>
</div> </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"> <form-entry label="Project URL" error="projectUrl">
<input slot="field" class="textfield" type="text" v-model.trim="projectUrl" @keydown.enter="resolve()"> <input slot="field" class="textfield" type="text" v-model.trim="projectUrl" @keydown.enter="resolve()">
<div class="form-entry__info"> <div class="form-entry__info">

View File

@ -4,7 +4,7 @@
<div class="modal__image"> <div class="modal__image">
<icon-provider provider-id="googleDrive"></icon-provider> <icon-provider provider-id="googleDrive"></icon-provider>
</div> </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="可选的"> <form-entry label="Folder ID" info="可选的">
<input slot="field" class="textfield" type="text" v-model.trim="folderId" @keydown.enter="resolve()"> <input slot="field" class="textfield" type="text" v-model.trim="folderId" @keydown.enter="resolve()">
<div class="form-entry__info"> <div class="form-entry__info">

View File

@ -22,6 +22,6 @@ export default {
'badgeCreations', 'badgeCreations',
'serverConf', 'serverConf',
], ],
textMaxLength: 250000, textMaxLength: 10000000,
defaultName: 'Untitled', defaultName: 'Untitled',
}; };

View File

@ -24,6 +24,7 @@ export default () => ({
giteePublishTemplate: 'jekyllSite', giteePublishTemplate: 'jekyllSite',
gitlabServerUrl: '', gitlabServerUrl: '',
gitlabApplicationId: '', gitlabApplicationId: '',
gitlabApplicationSecret: '',
gitlabProjectUrl: '', gitlabProjectUrl: '',
gitlabWorkspaceProjectUrl: '', gitlabWorkspaceProjectUrl: '',
gitlabPublishTemplate: 'plainText', gitlabPublishTemplate: 'plainText',

View File

@ -15,6 +15,36 @@ editor:
inlineImages: true inlineImages: true
# Use monospaced font only # Use monospaced font only
monospacedFontOnly: false 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 # Keyboard shortcuts
# See https://craig.is/killing/mice # See https://craig.is/killing/mice
@ -29,6 +59,7 @@ shortcuts:
mod+shift+h: heading mod+shift+h: heading
mod+shift+r: hr mod+shift+r: hr
mod+shift+g: image mod+shift+g: image
mod+shift+p: chatgpt
mod+shift+i: italic mod+shift+i: italic
mod+shift+l: link mod+shift+l: link
mod+shift+o: olist mod+shift+o: olist
@ -36,6 +67,9 @@ shortcuts:
mod+shift+s: strikethrough mod+shift+s: strikethrough
mod+shift+t: table mod+shift+t: table
mod+shift+u: ulist mod+shift+u: ulist
mod+shift+f: inlineformula
# 切换编辑与预览模式
mod+shift+e: toggleeditor
'= = > space': '= = > space':
method: expand method: expand
params: params:
@ -79,16 +113,16 @@ turndown:
# GitHub/GitLab/Gitee/Gitea commit messages # GitHub/GitLab/Gitee/Gitea commit messages
git: git:
createFileMessage: '{{path}} created from https://stackedit.cn/' createFileMessage: '{{path}} created from https://md.jonylee.top/'
updateFileMessage: '{{path}} updated from https://stackedit.cn/' updateFileMessage: '{{path}} updated from https://md.jonylee.top/'
deleteFileMessage: '{{path}} deleted from https://stackedit.cn/' deleteFileMessage: '{{path}} deleted from https://md.jonylee.top/'
# Default content for new files # Default content for new files
newFileContent: | newFileContent: |
> Written with [StackEdit中文版](https://stackedit.cn/). > Written with [Markdown编辑器-StackEdit中文版](https://md.jonylee.top/).
# Default properties for new files # Default properties for new files
newFileProperties: | newFileProperties: |

View File

@ -158,7 +158,19 @@ export default [
new Feature( new Feature(
'sponsor', '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( new Feature(
@ -306,6 +328,11 @@ export default [
'GitHub保存', 'GitHub保存',
'使用“同步”菜单将文件保存在GitHub仓库中。', '使用“同步”菜单将文件保存在GitHub仓库中。',
), ),
new Feature(
'saveOnGist',
'GitHubGist保存',
'使用“同步”菜单将文件保存在GitHubGist中。',
),
new Feature( new Feature(
'openFromGitee', 'openFromGitee',
'Gitee阅读器', 'Gitee阅读器',
@ -317,9 +344,9 @@ export default [
'使用“同步”菜单将文件保存在Gitee仓库中。', '使用“同步”菜单将文件保存在Gitee仓库中。',
), ),
new Feature( new Feature(
'saveOnGist', 'saveOnGiteeGist',
'Gist保存', 'GiteeGist保存',
'使用“同步”菜单将文件保存在GIST中。', '使用“同步”菜单将文件保存在GiteeGist中。',
), ),
new Feature( new Feature(
'openFromGitlab', 'openFromGitlab',
@ -395,14 +422,19 @@ export default [
), ),
new Feature( new Feature(
'publishToGist', 'publishToGist',
'Gist发布', 'GitHubGist发布',
'使用“发布”菜单将文件发布到GIST。', '使用“发布”菜单将文件发布到GitHubGist。',
), ),
new Feature( new Feature(
'publishToGitee', 'publishToGitee',
'Gitee发布', 'Gitee发布',
'使用“发布”菜单将文件发布到Gitee仓库。', '使用“发布”菜单将文件发布到Gitee仓库。',
), ),
new Feature(
'publishToGiteeGist',
'GiteeGist发布',
'使用“发布”菜单将文件发布到GiteeGist。',
),
new Feature( new Feature(
'publishToGitlab', 'publishToGitlab',
'GitLab发布', 'GitLab发布',

View File

@ -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 1. 列表项 1
2. Item 2 2. 列表项 2
3. Item 3 3. 列表项 3
- [ ] Incomplete item - [ ] 未完成项
- [x] Complete item - [x] 已完成项
Links 链接
--------------------------- ---------------------------
A [link](http://example.com). 一个[链接](http://example.com).
An image: ![Alt](img.jpg) 一张图片: ![图片描述](img.jpg)
A sized image: ![Alt](img.jpg =60x50) 一张调整大小的图片: ![图片描述](img.jpg =60x50)
Code 代码
--------------------------- ---------------------------
Some `inline code`. 一些`行内代码`.
``` ```
// A code block // 一个代码块
var foo = 'bar'; var foo = 'bar';
``` ```
```javascript ```javascript
// An highlighted block // 一个高亮代码块
var foo = 'bar'; var foo = 'bar';
``` ```
Tables 表格
--------------------------- ---------------------------
Item | Value Item | Value
@ -88,41 +88,41 @@ Pipe | $1
Definition lists 定义列表
--------------------------- ---------------------------
Markdown 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 满足 $\Gamma(n) = (n-1)!\quad\forall
n\in\mathbb N$ is via the Euler integral n\in\mathbb N$ 的Gamma函数是通过欧拉积分
$$ $$
\Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,. \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,.

View File

@ -15,7 +15,7 @@ export default [{
method: 'strikethrough', method: 'strikethrough',
title: '删除线', title: '删除线',
icon: 'format-strikethrough', icon: 'format-strikethrough',
}, { // }, {
}, { }, {
method: 'ulist', method: 'ulist',
title: '无序列表', title: '无序列表',
@ -28,7 +28,7 @@ export default [{
method: 'clist', method: 'clist',
title: '可选列表', title: '可选列表',
icon: 'format-list-checks', icon: 'format-list-checks',
}, { // }, {
}, { }, {
method: 'quote', method: 'quote',
title: '块引用', title: '块引用',
@ -49,4 +49,8 @@ export default [{
method: 'image', method: 'image',
title: '图片', title: '图片',
icon: 'file-image', icon: 'file-image',
}, {
method: 'chatgpt',
title: 'ChatGPT',
icon: 'chat-gpt',
}]; }];

View File

@ -1,11 +1,17 @@
const simpleModal = (contentHtml, rejectText, resolveText) => ({ const simpleModal = (contentHtml, rejectText, resolveText, resolveArray) => ({
contentHtml: typeof contentHtml === 'function' ? contentHtml : () => contentHtml, contentHtml: typeof contentHtml === 'function' ? contentHtml : () => contentHtml,
rejectText, rejectText,
resolveArray,
resolveText, resolveText,
}); });
/* eslint sort-keys: "error" */ /* eslint sort-keys: "error" */
export default { export default {
autoSyncWorkspace: simpleModal(
config => `<p>您将启动文档空间 <b>${config.name}</b >的自动同步。<br>启动后无法自定义提交信息。<br>你确定吗?</p>`,
'取消',
'确认启动',
),
commentDeletion: simpleModal( commentDeletion: simpleModal(
'<p>您将要删除评论。你确定吗?</p>', '<p>您将要删除评论。你确定吗?</p>',
'取消', '取消',
@ -46,7 +52,7 @@ export default {
'确认跳转', '确认跳转',
), ),
removeWorkspace: simpleModal( 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( signInForComment: simpleModal(
`<p>您必须使用 Google 登录才能开始评论。</p> `<p>您必须使用 Gitee或GitHub 登录默认文档空间后才能开始评论。</p>
<div class="modal__info"><b>注意:</b> </div>`, <div class="modal__info"><b>注意:</b> </div>`,
'取消', '取消',
'确认登录', '',
[{
text: 'Gitee登录',
value: 'gitee',
}, {
text: 'GitHub登录',
value: 'github',
}],
), ),
signInForSponsorship: simpleModal( signInForSponsorship: simpleModal(
`<p>您必须使用 Google 登录才能赞助。</p> `<p>您必须使用 Gitee或GitHub 登录才能赞助。</p>
<div class="modal__info"><b>注意:</b> </div>`, <div class="modal__info"><b>注意:</b> </div>`,
'取消', '取消',
'确认登录', '',
[{
text: 'Gitee登录',
value: 'gitee',
}, {
text: 'GitHub登录',
value: 'github',
}],
), ),
sponsorOnly: simpleModal( sponsorOnly: simpleModal(
'<p>此功能仅限于赞助商,因为它依赖于服务器资源。</p>', '<p>此功能仅限于赞助商,因为它依赖于服务器资源。</p>',
'好的,我明白了', '好的,我明白了',
), ),
stopAutoSyncWorkspace: simpleModal(
config => `<p>您将关闭文档空间 <b>${config.name}</b> 的自动同步。<br>关闭后您需要手动触发同步,但可以自定义提交信息。<br>你确定吗?</p>`,
'取消',
'确认关闭',
),
stripName: simpleModal( stripName: simpleModal(
config => `<p><b>${config.item.name}</b>包含非法字符。你想去掉它们吗?</p>`, config => `<p><b>${config.item.name}</b>包含非法字符。你想去掉它们吗?</p>`,
'取消', '取消',

View 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>

View 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>

View File

@ -56,6 +56,7 @@ export default (md) => {
// According to http://pandoc.org/README.html#extension-auto_identifiers // According to http://pandoc.org/README.html#extension-auto_identifiers
let slug = headingContent let slug = headingContent
.replace(/<img[^>]*>/g, '') // Replace image to empty
.replace(/\s/g, '-') // Replace all spaces and newlines with hyphens .replace(/\s/g, '-') // Replace all spaces and newlines with hyphens
.replace(/[\0-,/:-@[-^`{-~]/g, '') // Remove all punctuation, except underscores, hyphens, and periods .replace(/[\0-,/:-@[-^`{-~]/g, '') // Remove all punctuation, except underscores, hyphens, and periods
.toLowerCase(); // Convert all alphabetic characters to lowercase .toLowerCase(); // Convert all alphabetic characters to lowercase
@ -64,7 +65,9 @@ export default (md) => {
let i; let i;
for (i = 0; i < slug.length; i += 1) { for (i = 0; i < slug.length; i += 1) {
const charCode = slug.charCodeAt(i); 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; break;
} }
} }
@ -98,7 +101,7 @@ export default (md) => {
return result; return result;
}, ''); }, '');
} }
if (token.type === 'inline' && token.content === '[TOC]') { if (token.type === 'inline' && (token.content === '[TOC]' || token.content === '[toc]')) {
tocTokens.push(token); tocTokens.push(token);
} }
}); });

View File

@ -23,11 +23,21 @@ function texMath(state, silent) {
) { ) {
return false; return false;
} }
const endMarkerPos = state.src.indexOf(endMarker, startMathPos); function getIndex(tempStartMathPos) {
if (endMarkerPos === -1) { const tempEndMarkerPos = state.src.indexOf(endMarker, tempStartMathPos);
return false; if (tempEndMarkerPos === -1) {
return tempEndMarkerPos;
} }
if (state.src.charCodeAt(endMarkerPos - 1) === 0x5C /* \ */) { if (state.src.charCodeAt(tempEndMarkerPos - 1) === 0x5C /* \ */) {
if (state.src.length - 1 > tempEndMarkerPos) {
return getIndex(tempEndMarkerPos + 1);
}
return -1;
}
return tempEndMarkerPos;
}
const endMarkerPos = getIndex(startMathPos);
if (endMarkerPos === -1) {
return false; return false;
} }
const nextPos = endMarkerPos + endMarker.length; const nextPos = endMarkerPos + endMarker.length;

3
src/icons/ChatGpt.vue Normal file
View 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
View 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
View 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>

View 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>

View File

@ -15,6 +15,7 @@ export default {
return 'google-drive'; return 'google-drive';
case 'googlePhotos': case 'googlePhotos':
return 'google-photos'; return 'google-photos';
case 'githubAppData':
case 'githubWorkspace': case 'githubWorkspace':
return 'github'; return 'github';
case 'gist': case 'gist':
@ -29,7 +30,10 @@ export default {
return 'couchdb'; return 'couchdb';
case 'giteeAppData': case 'giteeAppData':
case 'giteeWorkspace': case 'giteeWorkspace':
case 'giteegist':
return 'gitee'; return 'gitee';
case 'stackedit':
return 'stackedit';
default: default:
return this.providerId; return this.providerId;
} }

View File

@ -1,3 +1,3 @@
<template> <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> </template>

View 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
View 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
View 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
View 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>

View File

@ -30,6 +30,8 @@ import Login from './Login';
import Logout from './Logout'; import Logout from './Logout';
import Sync from './Sync'; import Sync from './Sync';
import SyncOff from './SyncOff'; import SyncOff from './SyncOff';
import SyncAuto from './SyncAuto';
import SyncStop from './SyncStop';
import Upload from './Upload'; import Upload from './Upload';
import ViewList from './ViewList'; import ViewList from './ViewList';
import Download from './Download'; import Download from './Download';
@ -57,6 +59,13 @@ import Key from './Key';
import DotsHorizontal from './DotsHorizontal'; import DotsHorizontal from './DotsHorizontal';
import Seal from './Seal'; import Seal from './Seal';
import SwitchTheme from './SwitchTheme'; 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('iconProvider', Provider);
Vue.component('iconFormatBold', FormatBold); Vue.component('iconFormatBold', FormatBold);
@ -89,6 +98,8 @@ Vue.component('iconLogin', Login);
Vue.component('iconLogout', Logout); Vue.component('iconLogout', Logout);
Vue.component('iconSync', Sync); Vue.component('iconSync', Sync);
Vue.component('iconSyncOff', SyncOff); Vue.component('iconSyncOff', SyncOff);
Vue.component('iconSyncAuto', SyncAuto);
Vue.component('iconSyncStop', SyncStop);
Vue.component('iconUpload', Upload); Vue.component('iconUpload', Upload);
Vue.component('iconViewList', ViewList); Vue.component('iconViewList', ViewList);
Vue.component('iconDownload', Download); Vue.component('iconDownload', Download);
@ -116,3 +127,10 @@ Vue.component('iconKey', Key);
Vue.component('iconDotsHorizontal', DotsHorizontal); Vue.component('iconDotsHorizontal', DotsHorizontal);
Vue.component('iconSeal', Seal); Vue.component('iconSeal', Seal);
Vue.component('iconSwitchTheme', SwitchTheme); 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);

View File

@ -6,41 +6,43 @@ var util = {},
var defaultsStrings = { var defaultsStrings = {
bold: "Strong <strong> Ctrl/Cmd+B", bold: "Strong <strong> Ctrl/Cmd+B",
boldexample: "strong text", boldexample: "加粗文本",
italic: "Emphasis <em> Ctrl/Cmd+I", italic: "Emphasis <em> Ctrl/Cmd+I",
italicexample: "emphasized text", italicexample: "强调文本",
strikethrough: "Strikethrough <s> Ctrl/Cmd+I", strikethrough: "Strikethrough <s> Ctrl/Cmd+I",
strikethroughexample: "strikethrough text", strikethroughexample: "删除线文本",
link: "Hyperlink <a> Ctrl/Cmd+L", 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>", linkdialog: "<p><b>Insert Hyperlink</b></p><p>http://example.com/ \"optional title\"</p>",
quote: "Blockquote <blockquote> Ctrl/Cmd+Q", quote: "Blockquote <blockquote> Ctrl/Cmd+Q",
quoteexample: "Blockquote", quoteexample: "块引用",
code: "Code Sample <pre><code> Ctrl/Cmd+K", code: "Code Sample <pre><code> Ctrl/Cmd+K",
codeexample: "enter code here", codeexample: "这里输入代码",
image: "Image <img> Ctrl/Cmd+G", 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>", 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", olist: "Numbered List <ol> Ctrl/Cmd+O",
ulist: "Bulleted List <ul> Ctrl/Cmd+U", ulist: "Bulleted List <ul> Ctrl/Cmd+U",
litem: "List item", litem: "这里是列表文本",
heading: "Heading <h1>/<h2> Ctrl/Cmd+H", heading: "Heading <h1>/<h2> Ctrl/Cmd+H",
headingexample: "Heading", headingexample: "标题",
hr: "Horizontal Rule <hr> Ctrl/Cmd+R", hr: "Horizontal Rule <hr> Ctrl/Cmd+R",
undo: "Undo - Ctrl/Cmd+Z", undo: "Undo - Ctrl/Cmd+Z",
redo: "Redo - Ctrl/Cmd+Y", redo: "Redo - Ctrl/Cmd+Y",
help: "Markdown Editing Help" help: "Markdown Editing Help",
formulaexample: "这里输入Latex表达式",
}; };
// options, if given, can have the following properties: // 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("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.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("insertImageDialog");
hooks.addFalse("insertChatGptDialog");
/* called with one parameter: a callback to be called with the URL of the image. If the application creates /* 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 * 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. * image url (or null if the user cancelled). If this hook returns false, the default dialog will be used.
*/ */
hooks.addFalse("insertLinkDialog"); hooks.addFalse("insertLinkDialog");
// 插入图片占位字符
hooks.addFalse("insertImageUploading");
var that = this, var that = this,
input; input;
@ -463,6 +468,8 @@ function UIManager(input, commandManager) {
buttons.bold = bindCommand("doBold"); buttons.bold = bindCommand("doBold");
buttons.italic = bindCommand("doItalic"); buttons.italic = bindCommand("doItalic");
buttons.strikethrough = bindCommand("doStrikethrough"); buttons.strikethrough = bindCommand("doStrikethrough");
buttons.inlineformula = bindCommand("doInlinkeFormula");
buttons.imageUploading = bindCommand("doImageUploading");
buttons.link = bindCommand(function (chunk, postProcessing) { buttons.link = bindCommand(function (chunk, postProcessing) {
return this.doLinkOrImage(chunk, postProcessing, false); return this.doLinkOrImage(chunk, postProcessing, false);
}); });
@ -471,6 +478,7 @@ function UIManager(input, commandManager) {
buttons.image = bindCommand(function (chunk, postProcessing) { buttons.image = bindCommand(function (chunk, postProcessing) {
return this.doLinkOrImage(chunk, postProcessing, true); return this.doLinkOrImage(chunk, postProcessing, true);
}); });
buttons.chatgpt = bindCommand("doChatGpt");
buttons.olist = bindCommand(function (chunk, postProcessing) { buttons.olist = bindCommand(function (chunk, postProcessing) {
this.doList(chunk, postProcessing, true); this.doList(chunk, postProcessing, true);
}); });
@ -615,6 +623,60 @@ commandProto.doStrikethrough = function (chunk, postProcessing) {
return; 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) { commandProto.stripLinkDefs = function (text, defsToAdd) {
text = text.replace(/^[ ]{0,3}\[(\d+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|$)/gm, 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 // When making a list, hitting shift-enter will put your cursor on the next line
// at the current indent level. // at the current indent level.
commandProto.doAutoindent = function (chunk) { commandProto.doAutoindent = function (chunk) {
@ -983,37 +1056,15 @@ commandProto.doCode = function (chunk) {
// Use 'four space' markdown if the selection is on its own // Use 'four space' markdown if the selection is on its own
// line or is multiline. // line or is multiline.
if ((!hasTextAfter && !hasTextBefore) || /\n/.test(chunk.selection)) { if ((!hasTextAfter && !hasTextBefore) || /\n/.test(chunk.selection)) {
if (/[\n]+```\n$/.test(chunk.before) && /^\n```[ ]*\n/.test(chunk.after)) {
chunk.before = chunk.before.replace(/[ ]{4}$/, chunk.before = chunk.before.replace(/```\n$/, "");
function (totalMatch) { chunk.after = chunk.after.replace(/^\n```/, "");
chunk.selection = totalMatch + chunk.selection; } else {
return ""; chunk.before += '```\n';
}); chunk.after = '\n```' + chunk.after;
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) { if (!chunk.selection) {
chunk.startTag = " ";
chunk.selection = this.getString("codeexample"); chunk.selection = this.getString("codeexample");
} 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, "");
}
} }
} else { } else {
// Use backticks (`) to delimit the code block. // Use backticks (`) to delimit the code block.

View 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;
},
};

View File

@ -186,6 +186,8 @@ function mergeContent(serverContent, clientContent, lastMergedContent = {}) {
clientContent.comments, clientContent.comments,
lastMergedContent.comments, lastMergedContent.comments,
), ),
// 服务端和本地都变更了
mergeFlag: isServerTextChanges && isClientTextChanges,
}; };
restoreDiscussionOffsets(result, markerKeys); restoreDiscussionOffsets(result, markerKeys);
return result; return result;

View File

@ -2,6 +2,7 @@ import Vue from 'vue';
import DiffMatchPatch from 'diff-match-patch'; import DiffMatchPatch from 'diff-match-patch';
import Prism from 'prismjs'; import Prism from 'prismjs';
import markdownItPandocRenderer from 'markdown-it-pandoc-renderer'; import markdownItPandocRenderer from 'markdown-it-pandoc-renderer';
import md5 from 'js-md5';
import cledit from './editor/cledit'; import cledit from './editor/cledit';
import pagedown from '../libs/pagedown'; import pagedown from '../libs/pagedown';
import htmlSanitizer from '../libs/htmlSanitizer'; import htmlSanitizer from '../libs/htmlSanitizer';
@ -13,6 +14,9 @@ import editorSvcDiscussions from './editor/editorSvcDiscussions';
import editorSvcUtils from './editor/editorSvcUtils'; import editorSvcUtils from './editor/editorSvcUtils';
import utils from './utils'; import utils from './utils';
import store from '../store'; import store from '../store';
import syncSvc from './syncSvc';
import constants from '../data/constants';
import localDbSvc from './localDbSvc';
const allowDebounce = (action, wait) => { const allowDebounce = (action, wait) => {
let timeoutId; 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 // Use a vue instance as an event bus
const editorSvc = Object.assign(new Vue(), editorSvcDiscussions, editorSvcUtils, { const editorSvc = Object.assign(new Vue(), editorSvcDiscussions, editorSvcUtils, {
// Elements // Elements
@ -172,11 +203,28 @@ const editorSvc = Object.assign(new Vue(), editorSvcDiscussions, editorSvcUtils,
this.previewElt.appendChild(sectionPreviewElt); this.previewElt.appendChild(sectionPreviewElt);
} }
extensionSvc.sectionPreview(sectionPreviewElt, this.options, true); 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 = [
...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 // Create TOC section element
sectionTocElt = document.createElement('div'); sectionTocElt = document.createElement('div');
sectionTocElt.className = 'cl-toc-section'; sectionTocElt.className = 'cl-toc-section';
@ -185,6 +233,21 @@ const editorSvc = Object.assign(new Vue(), editorSvcDiscussions, editorSvcUtils,
const clonedElt = headingElt.cloneNode(true); const clonedElt = headingElt.cloneNode(true);
clonedElt.removeAttribute('id'); clonedElt.removeAttribute('id');
sectionTocElt.appendChild(clonedElt); 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) { if (insertBeforeTocElt) {
this.tocElt.insertBefore(sectionTocElt, insertBeforeTocElt); this.tocElt.insertBefore(sectionTocElt, insertBeforeTocElt);
@ -212,15 +275,22 @@ const editorSvc = Object.assign(new Vue(), editorSvcDiscussions, editorSvcUtils,
this.makeTextToPreviewDiffs(); this.makeTextToPreviewDiffs();
// Wait for images to load // Wait for images to load
const loadedPromises = loadingImages.map(imgElt => new Promise((resolve) => { const loadedPromises = loadingImages.map(it => new Promise((resolve, reject) => {
if (!imgElt.src) { 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(); resolve();
return; return;
} }
const img = new window.Image(); const img = new window.Image();
img.onload = resolve; img.onload = resolve;
img.onerror = resolve; img.onerror = resolve;
img.src = imgElt.src; img.src = it.imgElt.src;
})); }));
await Promise.all(loadedPromises); await Promise.all(loadedPromises);
@ -383,7 +453,17 @@ const editorSvc = Object.assign(new Vue(), editorSvcDiscussions, editorSvcUtils,
}); });
return true; 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.editorElt.parentNode.addEventListener('scroll', () => this.saveContentState(true));
this.previewElt.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 = []; let imgEltsToCache = [];
if (store.getters['data/computedSettings'].editor.inlineImages) { if (store.getters['data/computedSettings'].editor.inlineImages) {
this.clEditor.highlighter.on('sectionHighlighted', (section) => { this.clEditor.highlighter.on('sectionHighlighted', (section) => {
const loadImgs = [];
section.elt.getElementsByClassName('token img').cl_each((imgTokenElt) => { section.elt.getElementsByClassName('token img').cl_each((imgTokenElt) => {
const srcElt = imgTokenElt.querySelector('.token.cl-src'); const srcElt = imgTokenElt.querySelector('.token.cl-src');
if (srcElt) { if (srcElt) {
@ -493,6 +574,10 @@ const editorSvc = Object.assign(new Vue(), editorSvcDiscussions, editorSvcUtils,
} }
} }
imgEltsToCache.push(imgElt); imgEltsToCache.push(imgElt);
if (imgElt.src.indexOf(origin) >= 0) {
imgElt.removeAttribute('src');
loadImgs.push({ imgElt, uri: decodeURIComponent(uri) });
}
} }
const imgTokenWrapper = document.createElement('span'); const imgTokenWrapper = document.createElement('span');
imgTokenWrapper.className = 'token img-wrapper'; imgTokenWrapper.className = 'token img-wrapper';
@ -501,9 +586,18 @@ const editorSvc = Object.assign(new Vue(), editorSvcDiscussions, editorSvcUtils,
imgTokenWrapper.appendChild(imgTokenElt); 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', () => { this.clEditor.highlighter.on('highlighted', () => {
imgEltsToCache.forEach((imgElt) => { imgEltsToCache.forEach((imgElt) => {
const cachedImgElt = getFromImgCache(imgElt); const cachedImgElt = getFromImgCache(imgElt);

View File

@ -1,3 +1,4 @@
import md5 from 'js-md5';
import FileSaver from 'file-saver'; import FileSaver from 'file-saver';
import TemplateWorker from 'worker-loader!./templateWorker.js'; // eslint-disable-line import TemplateWorker from 'worker-loader!./templateWorker.js'; // eslint-disable-line
import localDbSvc from './localDbSvc'; import localDbSvc from './localDbSvc';
@ -34,6 +35,24 @@ function groupHeadings(headings, level = 1) {
return result; 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'); const containerElt = document.createElement('div');
containerElt.className = 'hidden-rendering-container'; containerElt.className = 'hidden-rendering-container';
document.body.appendChild(containerElt); document.body.appendChild(containerElt);
@ -54,6 +73,13 @@ export default {
const parsingCtx = markdownConversionSvc.parseSections(converter, content.text); const parsingCtx = markdownConversionSvc.parseSections(converter, content.text);
const conversionCtx = markdownConversionSvc.convert(parsingCtx); const conversionCtx = markdownConversionSvc.convert(parsingCtx);
const html = conversionCtx.htmlSectionList.map(htmlSanitizer.sanitizeHtml).join(''); 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; containerElt.innerHTML = html;
extensionSvc.sectionPreview(containerElt, options); extensionSvc.sectionPreview(containerElt, options);
@ -65,8 +91,48 @@ export default {
wrapperElt.parentNode.removeChild(wrapperElt); 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 // 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, title: headingElt.textContent,
anchor: headingElt.id, anchor: headingElt.id,
level: parseInt(headingElt.tagName.slice(1), 10), level: parseInt(headingElt.tagName.slice(1), 10),
@ -83,6 +149,9 @@ export default {
yamlProperties: content.properties, yamlProperties: content.properties,
html: containerElt.innerHTML, html: containerElt.innerHTML,
toc, toc,
colorThemeClass,
themeClass,
themeStyleContent,
}, },
}], }],
}; };

94
src/services/imageSvc.js Normal file
View 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 };
},
};

Some files were not shown because too many files have changed in this diff Show More