Compare commits

...

43 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
73 changed files with 1535 additions and 755 deletions

View File

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

View File

@ -5,7 +5,7 @@ StackEdit中文版
</h1> </h1>
<p align="center"> <p align="center">
<strong>笔记利器在线Markdown编辑器。</strong><br> <strong>笔记利器在线Markdown编辑器。</strong><br>
如果你喜欢该项目请点一下Star您的肯定是作者最大的动力 项目clone自<a href="https://gitee.com/mafgwo/stackedit" target="_blank" title="豆萁">豆萁/stackedit</a>如果你喜欢该项目,请过去点一下Star您的肯定是作者最大的动力
</p> </p>
<p align="center"> <p align="center">
<a href="https://stackedit.cn/">https://stackedit.cn</a> <a href="https://stackedit.cn/">https://stackedit.cn</a>
@ -22,18 +22,19 @@ StackEdit中文版
</a> </a>
</p> </p>
<br/> <br/>
<p align="center">
<a target="_blank" href="https://jq.qq.com/?_wv=1027&k=wUSCNqmN">
<img src="https://img.shields.io/badge/QQ交流群-703187410-orange"/></a>
</p>
<hr /> <hr />
1 笔记支持Gitee、GitHub、Gitea等Git仓库存储。<br> 1 笔记支持Gitee、GitHub、Gitea等Git仓库存储。<br>
2 支持直接上传图片也支持多种外部图床GitHub、Gitea、SM.MS、自定义图床粘贴或拖拽上传。<br> 2 支持直接上传图片也支持多种外部图床GitHub、Gitea、SM.MS、自定义图床粘贴或拖拽上传。<br>
3 编辑区域支持选择主题或自定义,总有你喜欢的主题。<br> 3 编辑区域支持选择主题或自定义,总有你喜欢的主题。<br>
4 支持历史版本管理,不用担心编辑覆盖后无法回滚。<br> 4 支持历史版本管理,不用担心编辑覆盖后无法回滚。<br>
5 支持KaTeX数学表达式、Mermaid UML图、乐谱等扩展。 5 支持ChatGPT辅助写作。<br>
6 支持KaTeX数学表达式、Mermaid UML图、乐谱等扩展。
<hr /> <hr />
## 说明
本项目为本人clone修改自用如果你也喜欢请至原作者处获取及交流。
## 截图 ## 截图
**亮暗主题切换、编辑主题切换** **亮暗主题切换、编辑主题切换**
@ -48,7 +49,11 @@ StackEdit中文版
**支持文档搜索** **支持文档搜索**
![](./images/search.gif) ![](./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
@ -73,14 +78,17 @@ StackEdit中文版
- 导出HTML、PDF支持带预览主题导出2023-02-26 - 导出HTML、PDF支持带预览主题导出2023-02-26
- 支持分享文档2023-03-30 - 支持分享文档2023-03-30
- 支持ChatGPT生成内容2023-04-10 - 支持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官方仓库下载太慢可以使用阿里云的镜像仓库镜像仓库地址registry.cn-hangzhou.aliyuncs.com/mafgwo/stackedit:【版本号】
@ -109,6 +117,7 @@ services:
- GITEA_CLIENT_SECRET=【不需要支持则删掉】 - GITEA_CLIENT_SECRET=【不需要支持则删掉】
- GITEA_URL=【不需要支持则删掉】 - GITEA_URL=【不需要支持则删掉】
- GITLAB_CLIENT_ID=【不需要支持则删掉】 - GITLAB_CLIENT_ID=【不需要支持则删掉】
- GITLAB_CLIENT_SECRET=【不需要支持则删掉】
- GITLAB_URL=【不需要支持则删掉】 - GITLAB_URL=【不需要支持则删掉】
ports: ports:
- 8080:8080/tcp - 8080:8080/tcp
@ -117,6 +126,7 @@ services:
``` ```
docker-compose方式的启动或停止命令 docker-compose方式的启动或停止命令
```bash ```bash
# 在 docker-compose.yml 文件目录下 启动命令 # 在 docker-compose.yml 文件目录下 启动命令
docker-compose up -d docker-compose up -d
@ -145,25 +155,25 @@ docker run -itd --name stackedit \
-e GITEA_CLIENT_SECRET=【不需要支持则删掉】 \ -e GITEA_CLIENT_SECRET=【不需要支持则删掉】 \
-e GITEA_URL=【不需要支持则删掉】 \ -e GITEA_URL=【不需要支持则删掉】 \
-e GITLAB_CLIENT_ID=【不需要支持则删掉】 \ -e GITLAB_CLIENT_ID=【不需要支持则删掉】 \
-e GITLAB_CLIENT_SECRET=【不需要支持则删掉】 \
-e GITLAB_URL=【不需要支持则删掉】 \ -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)** - Gitea可选择性配置环境变量未配置则在关联时前端指定有配置则仅允许配置的应用信息GITEA_CLIENT_ID、GITEA_CLIENT_SECRET、GITEA_URL**[如何创建Gitea应用](./docs/部署之Gitea应用创建.md)**
- Gitlab可选择性配置环境变量未配置则在关联时前端指定有配置则仅允许配置的应用信息GITLAB_CLIENT_ID、GITLAB_CLIENT_SECRET、GITLAB_URL **如何创建Gitlab应用(待补充文档)**
- Gitlab可选择性配置环境变量未配置则在关联时前端指定有配置则仅允许配置的应用信息GITLAB_CLIENT_ID、GITLAB_URL **如何创建Gitlab应用(待补充文档)**
特别说明自建的Gitea、Gitlab要能接入stackedit必须支持跨域 特别说明自建的Gitea、Gitlab要能接入stackedit必须支持跨域
## 编译与运行 ## 编译与运行
> 编译运行的nodejs版本选择11.15.0版本 > 编译运行的nodejs版本选择11.15.0版本
```bash ```bash
@ -179,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 "操作完成"

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,28 +1,28 @@
{ {
"name": "StackEdit中文版", "name": "StackEdit中文版",
"description": "支持Gitee仓库/粘贴图片自动上传的浏览器内 Markdown 编辑器", "description": "支持Gitee仓库/粘贴图片自动上传的浏览器内 Markdown 编辑器",
"version": "5.15.17", "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",
"64": "icon-64.png", "64": "icon-64.png",
"128": "icon-128.png", "128": "icon-128.png",
"256": "icon-256.png", "256": "icon-256.png",
"512": "icon-512.png" "512": "icon-512.png"
}, },
"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,
"permissions": [ "permissions": [
"unlimitedStorage" "unlimitedStorage"
] ]
} }

View File

@ -12,6 +12,7 @@ module.exports = merge(prodEnv, {
// GITEA_CLIENT_ID: '"fe30f8f9-b1e8-4531-8f72-c1a5d3912805"', // GITEA_CLIENT_ID: '"fe30f8f9-b1e8-4531-8f72-c1a5d3912805"',
// GITEA_CLIENT_SECRET: '"lus7oMnb3H6M1hsChndphArE20Txr7erwJLf7SDBQWTw"', // GITEA_CLIENT_SECRET: '"lus7oMnb3H6M1hsChndphArE20Txr7erwJLf7SDBQWTw"',
// GITEA_URL: '"https://gitea.test.com"', // GITEA_URL: '"https://gitea.test.com"',
// GITLAB_CLIENT_ID: '"33e01128c27fe75df3e5b35218d710c7df280e6ee9c90b6ca27ac9d9fdfb92f7"', GITLAB_CLIENT_ID: '"074cd5103c62dea0f479dac861039656ac80935e304c8113a02cc64c629496ae"',
// GITLAB_URL: '"http://gitlab.qicoder.com"', GITLAB_CLIENT_SECRET: '"6f406f24216b686d55d28313dec1913c2a8e599afdb08380d5e8ce838e16e41e"',
GITLAB_URL: '"http://gitlab.qicoder.com"',
}) })

BIN
images/chatgpt.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 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>
(function() { var _hmt = _hmt || [];
var hm = document.createElement("script"); (function() {
hm.src = "https://hm.baidu.com/hm.js?20a1e7a201b42702c49074c87a1f1035"; var hm = document.createElement("script");
var s = document.getElementsByTagName("script")[0]; hm.src = "https://hm.baidu.com/hm.js?dad4b4383b13eedea1ab45ee323df1c3";
s.parentNode.insertBefore(hm, s); var s = document.getElementsByTagName("script")[0];
})(); s.parentNode.insertBefore(hm, s);
})();
</script> </script>
</body> <!-- baidu统计结束 -->
</body>
</html> </html>

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "stackedit", "name": "stackedit",
"version": "5.15.20", "version": "5.15.21",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@ -1,6 +1,6 @@
{ {
"name": "stackedit", "name": "stackedit",
"version": "5.15.20", "version": "5.15.21",
"description": "免费, 开源, 功能齐全的 Markdown 编辑器", "description": "免费, 开源, 功能齐全的 Markdown 编辑器",
"author": "Benoit Schweblin, 豆萁", "author": "Benoit Schweblin, 豆萁",
"license": "Apache-2.0", "license": "Apache-2.0",

View File

@ -15,6 +15,7 @@ const giteaClientId = process.env.GITEA_CLIENT_ID;
const giteaClientSecret = process.env.GITEA_CLIENT_SECRET; const giteaClientSecret = process.env.GITEA_CLIENT_SECRET;
const giteaUrl = process.env.GITEA_URL; const giteaUrl = process.env.GITEA_URL;
const gitlabClientId = process.env.GITLAB_CLIENT_ID; const gitlabClientId = process.env.GITLAB_CLIENT_ID;
const gitlabClientSecret = process.env.GITLAB_CLIENT_SECRET;
const gitlabUrl = process.env.GITLAB_URL; const gitlabUrl = process.env.GITLAB_URL;
exports.values = { exports.values = {
@ -33,6 +34,9 @@ exports.values = {
giteaClientId, giteaClientId,
giteaClientSecret, giteaClientSecret,
giteaUrl, giteaUrl,
gitlabClientId,
gitlabClientSecret,
gitlabUrl,
}; };
exports.publicValues = { exports.publicValues = {

View File

@ -1,4 +1,3 @@
const qs = require('qs');
const request = require('request'); const request = require('request');
const conf = require('./conf'); const conf = require('./conf');

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

@ -5,6 +5,7 @@ const path = require('path');
const github = require('./github'); const github = require('./github');
const gitee = require('./gitee'); const gitee = require('./gitee');
const gitea = require('./gitea'); 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');
@ -28,6 +29,7 @@ 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/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.post('/pdfExport', pdf.generate); app.post('/pdfExport', pdf.generate);
app.post('/pandocExport', pandoc.generate); app.post('/pandocExport', pandoc.generate);
@ -70,6 +72,7 @@ module.exports = (app) => {
})); }));
// Serve share.html // Serve share.html
app.get('/share.html', (req, res) => res.sendFile(resolvePath('static/landing/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 // Serve static resources
if (process.env.NODE_ENV === 'production') { if (process.env.NODE_ENV === 'production') {

View File

@ -59,7 +59,7 @@ exports.generate = (req, res) => {
const metadata = readJson(req.query.metadata); const metadata = readJson(req.query.metadata);
const params = []; const params = [];
params.push('--latex-engine=xelatex'); params.push('--pdf-engine=xelatex');
params.push('--webtex=http://chart.apis.google.com/chart?cht=tx&chf=bg,s,FFFFFF00&chco=000000&chl='); params.push('--webtex=http://chart.apis.google.com/chart?cht=tx&chf=bg,s,FFFFFF00&chco=000000&chl=');
if (options.toc) { if (options.toc) {
params.push('--toc'); params.push('--toc');

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 28 KiB

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

@ -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>
@ -42,7 +43,6 @@ import SponsorModal from './modals/SponsorModal';
import CommitMessageModal from './modals/CommitMessageModal'; import CommitMessageModal from './modals/CommitMessageModal';
import WorkspaceImgPathModal from './modals/WorkspaceImgPathModal'; import WorkspaceImgPathModal from './modals/WorkspaceImgPathModal';
import ChatGptModal from './modals/ChatGptModal'; import ChatGptModal from './modals/ChatGptModal';
import ChatGptConfigModal from './modals/ChatGptConfigModal';
// Providers // Providers
import GooglePhotoModal from './modals/providers/GooglePhotoModal'; import GooglePhotoModal from './modals/providers/GooglePhotoModal';
@ -114,7 +114,6 @@ export default {
CommitMessageModal, CommitMessageModal,
WorkspaceImgPathModal, WorkspaceImgPathModal,
ChatGptModal, ChatGptModal,
ChatGptConfigModal,
// Providers // Providers
GooglePhotoModal, GooglePhotoModal,
GoogleDriveAccountModal, GoogleDriveAccountModal,
@ -189,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

@ -26,6 +26,7 @@ import store from '../store';
import DropdownMenu from './common/DropdownMenu'; import DropdownMenu from './common/DropdownMenu';
import publishSvc from '../services/publishSvc'; import publishSvc from '../services/publishSvc';
import giteeGistProvider from '../services/providers/giteeGistProvider'; import giteeGistProvider from '../services/providers/giteeGistProvider';
import gistProvider from '../services/providers/gistProvider';
export default { export default {
components: { components: {
@ -107,12 +108,15 @@ export default {
store.dispatch('notification/info', '登录主文档空间之后才可使用分享功能!'); store.dispatch('notification/info', '登录主文档空间之后才可使用分享功能!');
return; return;
} }
let giteeGistId = null; let tempGistId = null;
const filterLocations = this.publishLocations.filter(it => it.providerId === 'giteegist' && it.url && it.gistId); 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) { if (filterLocations.length > 0) {
giteeGistId = filterLocations[0].gistId; tempGistId = filterLocations[0].gistId;
} }
const location = giteeGistProvider.makeLocation( const location = (isGithub ? gistProvider : giteeGistProvider).makeLocation(
mainToken, mainToken,
`分享-${currentFile.name}`, `分享-${currentFile.name}`,
true, true,
@ -120,9 +124,10 @@ export default {
); );
location.templateId = 'styledHtmlWithTheme'; location.templateId = 'styledHtmlWithTheme';
location.fileId = currentFile.id; location.fileId = currentFile.id;
location.gistId = giteeGistId; location.gistId = tempGistId;
const { gistId } = await publishSvc.publishLocationAndStore(location); const { gistId } = await publishSvc.publishLocationAndStore(location);
const url = `${window.location.protocol}//${window.location.host}/share.html?id=${gistId}`; 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 }); await store.dispatch('modal/open', { type: 'shareHtml', name: currentFile.name, url });
} catch (err) { } catch (err) {
if (err) { if (err) {

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>
@ -55,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';
@ -168,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

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>
@ -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>
@ -142,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';
@ -194,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

@ -263,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() {

View File

@ -239,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() {

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

@ -1,52 +0,0 @@
<template>
<modal-inner aria-label="ChatGPT配置">
<div class="modal__content">
<div class="modal__image">
<icon-chat-gpt></icon-chat-gpt>
</div>
<p> <b>ChatGPT</b> 配置.</p>
<form-entry label="代理地址" error="proxyHost">
<input slot="field" class="textfield" type="text" v-model.trim="proxyHost" @keydown.enter="resolve()">
<div class="form-entry__info">
<b>非必填默认是官方接口地址(https://api.openai.com):</b> https://openai.geekr.cool
</div>
</form-entry>
<form-entry label="apiKey" error="apiKey">
<input slot="field" class="textfield" type="text" v-model.trim="apiKey" @keydown.enter="resolve()">
<div class="form-entry__info">
<b>apiKey</b> 请到<a href="https://platform.openai.com/account/api-keys" target="_blank">https://platform.openai.com/account/api-keys</a> <br>
</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 modalTemplate from './common/modalTemplate';
export default modalTemplate({
data: () => ({
apiKey: this.config.apiKey,
proxyHost: this.config.proxyHost,
}),
computedLocalSettings: {
apiKey: 'chatgptApiKey',
proxyHost: 'chatgptProxyHost',
},
methods: {
resolve() {
if (!this.apiKey) {
this.setError('apiKey');
}
if (this.proxyHost && this.proxyHost.endsWith('/')) {
this.proxyHost = this.proxyHost.substring(0, this.proxyHost.length - 1);
}
this.config.resolve({ apiKey: this.apiKey, proxyHost: this.proxyHost });
},
},
});
</script>

View File

@ -6,22 +6,14 @@
</div> </div>
<p><b>ChatGPT内容生成</b><br>生成时长受ChatGPT服务响应与网络响应时长影响时间可能较长</p> <p><b>ChatGPT内容生成</b><br>生成时长受ChatGPT服务响应与网络响应时长影响时间可能较长</p>
<form-entry label="生成内容要求详细描述" error="content"> <form-entry label="生成内容要求详细描述" error="content">
<textarea slot="field" class="text-input" type="text" placeholder="输入内容" v-model.trim="content" :disabled="generating || !chatGptConfig.apiKey"></textarea> <textarea slot="field" class="text-input" type="text" placeholder="输入内容(支持换行)" v-model.trim="content" :disabled="generating"></textarea>
<div class="form-entry__info"> <div class="form-entry__info">
<span v-if="!chatGptConfig.apiKey" class="config-warning"> 使用 <a href="https://api35.pxj123.cn/" target="_blank">api35.pxj123.cn</a> 的免费接口生成内容AI模型是GPT-3.5 Turbo
未配置apiKey请点击 <a href="javascript:void(0)" @click="openConfig">配置</a> apiKey
</span>
<span v-else>
<span v-if="chatGptConfig.proxyHost">
<b>当前使用的接口代理</b>{{ chatGptConfig.proxyHost }}
</span>
<a href="javascript:void(0)" @click="openConfig">修改apiKey配置</a>
</span>
</div> </div>
</form-entry> </form-entry>
<div class="modal__result"> <div class="modal__result">
<span v-if="generating && !result">(等待生成中...)</span> <pre class="result_pre" v-if="generating && !result">(等待生成中...)</pre>
<pre class="result_pre" v-html="result"></pre> <pre class="result_pre" v-else v-text="result"></pre>
</div> </div>
</div> </div>
<div class="modal__button-bar"> <div class="modal__button-bar">
@ -33,7 +25,6 @@
</template> </template>
<script> <script>
import { mapGetters } from 'vuex';
import modalTemplate from './common/modalTemplate'; import modalTemplate from './common/modalTemplate';
import chatGptSvc from '../../services/chatGptSvc'; import chatGptSvc from '../../services/chatGptSvc';
import store from '../../store'; import store from '../../store';
@ -45,14 +36,9 @@ export default modalTemplate({
result: '', result: '',
xhr: null, xhr: null,
}), }),
computed: {
...mapGetters('chatgpt', [
'chatGptConfig',
]),
},
methods: { methods: {
resolve(evt) { resolve(evt) {
evt.preventDefault(); // Fixes https://github.com/mafgwo/stackedit/issues/1503 evt.preventDefault();
const { callback } = this.config; const { callback } = this.config;
this.config.resolve(); this.config.resolve();
callback(this.result); callback(this.result);
@ -73,18 +59,14 @@ export default modalTemplate({
this.generating = true; this.generating = true;
this.result = ''; this.result = '';
try { try {
this.xhr = chatGptSvc.chat(this.chatGptConfig.proxyHost, this.chatGptConfig.apiKey, `${this.content}\n(使用Markdown方式输出结果)`, this.process); this.xhr = chatGptSvc.chat({
content: `${this.content}\n(使用Markdown方式输出结果)`,
}, this.process);
} catch (err) { } catch (err) {
this.generating = false; this.generating = false;
store.dispatch('notification/error', err); store.dispatch('notification/error', err);
} }
}, },
async openConfig() {
try {
const config = await store.dispatch('modal/open', { type: 'chatGptConfig', apiKey: this.chatGptConfig.apiKey, proxyHost: this.chatGptConfig.proxyHost });
store.dispatch('chatgpt/setCurrConfig', config);
} catch (e) { /* Cancel */ }
},
reject() { reject() {
if (this.generating) { if (this.generating) {
if (this.xhr) { if (this.xhr) {
@ -98,10 +80,14 @@ export default modalTemplate({
callback(null); callback(null);
}, },
}, },
async created() { mounted() {
// store chatgpt const script = document.createElement('script');
const config = localStorage.getItem('chatgpt/config'); script.src = `https://api35.pxj123.cn/js/chat.js?t=${new Date().getTime()}`;
store.dispatch('chatgpt/setCurrConfig', JSON.parse(config || '{}')); script.onload = () => {
/* eslint-disable */
console.log('加载外部chatgpt的js成功!');
};
this.$el.appendChild(script);
}, },
}); });
</script> </script>
@ -139,7 +125,7 @@ export default modalTemplate({
} }
.text-input { .text-input {
min-height: 50px; min-height: 60px;
} }
} }
</style> </style>

View File

@ -2,9 +2,12 @@
<modal-inner aria-label="提交信息"> <modal-inner aria-label="提交信息">
<p>自定义 <b>{{ config.name }}</b> 提交信息</p> <p>自定义 <b>{{ config.name }}</b> 提交信息</p>
<div class="modal__content"> <div class="modal__content">
<form-entry label="提交信息"> <div class="form-entry">
<input slot="field" class="textfield" placeholder="提交信息非必填" type="text" v-model.trim="commitMessage" @keydown.enter="resolve()"> <label class="form-entry__label">提交信息</label>
</form-entry> <div class="form-entry__field">
<input slot="field" class="textfield" placeholder="提交信息非必填" type="text" v-model.trim="commitMessage" @keydown.enter="resolve()">
</div>
</div>
</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>

View File

@ -89,14 +89,14 @@ export default {
badgeSvc.addBadge('removePublishLocation'); badgeSvc.addBadge('removePublishLocation');
}, },
shareUrl(location) { shareUrl(location) {
if (location.providerId !== 'giteegist') { if (location.providerId !== 'giteegist' && location.providerId !== 'gist') {
return null; return null;
} }
if (!location.url) { if (!location.url || !location.gistId) {
return null; return null;
} }
const splitIndex = location.url.lastIndexOf('/'); const sharePage = location.providerId === 'gist' ? 'gistshare.html' : 'share.html';
return `${window.location.protocol}//${window.location.host}/share.html?id=${location.url.substr(splitIndex + 1)}`; return `${window.location.protocol}//${window.location.host}/${sharePage}?id=${location.gistId}`;
}, },
}, },
}; };

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,7 +18,7 @@
<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 === 'githubWorkspace' <template v-if="workspace.providerId === 'giteeAppData' || workspace.providerId === 'githubAppData' || workspace.providerId === 'githubWorkspace'
|| workspace.providerId === 'giteeWorkspace' || workspace.providerId === 'gitlabWorkspace' || workspace.providerId === 'giteaWorkspace'"> || 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="'关闭自动同步'"> <button class="workspace-entry__button button" @click="stopAutoSync(id)" v-if="workspace.autoSync == undefined || workspace.autoSync" v-title="'关闭自动同步'">
<icon-sync-auto></icon-sync-auto> <icon-sync-auto></icon-sync-auto>

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

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

@ -18,6 +18,9 @@
</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>
@ -47,6 +50,7 @@ export default modalTemplate({
computedLocalSettings: { computedLocalSettings: {
serverUrl: 'gitlabServerUrl', serverUrl: 'gitlabServerUrl',
applicationId: 'gitlabApplicationId', applicationId: 'gitlabApplicationId',
applicationSecret: 'gitlabApplicationSecret',
}, },
computed: { computed: {
httpAppUrl() { httpAppUrl() {
@ -78,7 +82,10 @@ export default modalTemplate({
if (!this.applicationId) { if (!this.applicationId) {
this.setError('applicationId'); this.setError('applicationId');
} }
if (serverUrl && this.applicationId) { if (!this.applicationSecret) {
this.setError('applicationSecret');
}
if (serverUrl && this.applicationId && this.applicationSecret) {
const parsedUrl = serverUrl.match(/^(http[s]?:\/\/[^/]+)/); const parsedUrl = serverUrl.match(/^(http[s]?:\/\/[^/]+)/);
if (!parsedUrl) { if (!parsedUrl) {
this.setError('serverUrl'); this.setError('serverUrl');
@ -86,6 +93,7 @@ export default modalTemplate({
this.config.resolve({ this.config.resolve({
serverUrl: parsedUrl[1], serverUrl: parsedUrl[1],
applicationId: this.applicationId, applicationId: this.applicationId,
applicationSecret: this.applicationSecret,
}); });
} }
} }

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',
@ -40,6 +41,4 @@ export default () => ({
zendescPublishSectionId: '', zendescPublishSectionId: '',
zendescPublishLocale: '', zendescPublishLocale: '',
zendeskPublishTemplate: 'plainHtml', zendeskPublishTemplate: 'plainHtml',
chatgptApiKey: '',
chatgptProxyHost: '',
}); });

View File

@ -68,6 +68,8 @@ shortcuts:
mod+shift+t: table mod+shift+t: table
mod+shift+u: ulist mod+shift+u: ulist
mod+shift+f: inlineformula mod+shift+f: inlineformula
# 切换编辑与预览模式
mod+shift+e: toggleeditor
'= = > space': '= = > space':
method: expand method: expand
params: params:
@ -111,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仓库数据同步。',
), ),
], ],
), ),

View File

@ -1,6 +1,7 @@
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,
}); });
@ -65,21 +66,35 @@ export default {
'关闭窗口', '关闭窗口',
), ),
shareHtmlPre: simpleModal( shareHtmlPre: simpleModal(
config => `<p>将给文档 "${config.name}" 创建分享链接,创建后将会把文档公开发布到GiteeGist中。您确定吗</p>`, 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>',

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

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(tempEndMarkerPos - 1) === 0x5C /* \ */) {
if (state.src.length - 1 > tempEndMarkerPos) {
return getIndex(tempEndMarkerPos + 1);
}
return -1;
}
return tempEndMarkerPos;
} }
if (state.src.charCodeAt(endMarkerPos - 1) === 0x5C /* \ */) { const endMarkerPos = getIndex(startMathPos);
if (endMarkerPos === -1) {
return false; return false;
} }
const nextPos = endMarkerPos + endMarker.length; const nextPos = endMarkerPos + endMarker.length;

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':
@ -31,6 +32,8 @@ export default {
case 'giteeWorkspace': case 'giteeWorkspace':
case 'giteegist': case 'giteegist':
return 'gitee'; return 'gitee';
case 'stackedit':
return 'stackedit';
default: default:
return this.providerId; return this.providerId;
} }

View File

@ -1,16 +1,20 @@
import store from '../store'; import store from '../store';
export default { export default {
chat(proxyHost, apiKey, content, callback) { chat({ content }, callback) {
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
const url = `${proxyHost || 'https://api.openai.com'}/v1/chat/completions`; const url = 'https://api.openai-proxy.com/v1/chat/completions';
xhr.open('POST', url); xhr.open('POST', url);
xhr.setRequestHeader('Authorization', `Bearer ${apiKey}`);
xhr.setRequestHeader('Content-Type', 'application/json'); xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('Authorization', `Bearer ${window.my_api_key}`);
xhr.send(JSON.stringify({ xhr.send(JSON.stringify({
model: 'gpt-3.5-turbo', model: 'gpt-3.5-turbo',
max_tokens: 3000,
top_p: 0,
temperature: 0.9,
frequency_penalty: 0,
presence_penalty: 0,
messages: [{ role: 'user', content }], messages: [{ role: 'user', content }],
temperature: 1,
stream: true, stream: true,
})); }));
let lastRespLen = 0; let lastRespLen = 0;

View File

@ -217,8 +217,8 @@ const editorSvc = Object.assign(new Vue(), editorSvcDiscussions, editorSvcUtils,
]; ];
Array.prototype.slice.call(sectionPreviewElt.getElementsByTagName('a')).forEach((aElt) => { Array.prototype.slice.call(sectionPreviewElt.getElementsByTagName('a')).forEach((aElt) => {
const url = aElt.attributes.href.nodeValue; const url = aElt.attributes && aElt.attributes.href && aElt.attributes.href.nodeValue;
if (url.indexOf('http://') >= 0 || url.indexOf('https://') >= 0 || url.indexOf('#') >= 0) { if (!url || url.indexOf('http://') >= 0 || url.indexOf('https://') >= 0 || url.indexOf('#') >= 0) {
return; return;
} }
aElt.href = 'javascript:void(0);'; // eslint-disable-line no-script-url aElt.href = 'javascript:void(0);'; // eslint-disable-line no-script-url
@ -233,7 +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);
headingElt.innerHTML = `<span class="prefix"></span><span class="content">${headingElt.innerHTML}</span><span class="suffix"></span>`; // 创建一个新的 <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);

View File

@ -93,8 +93,8 @@ export default {
// 替换相对路径图片为blob图片 // 替换相对路径图片为blob图片
const imgs = Array.prototype.slice.call(containerElt.getElementsByTagName('img')).map((imgElt) => { const imgs = Array.prototype.slice.call(containerElt.getElementsByTagName('img')).map((imgElt) => {
let uri = imgElt.attributes.src.nodeValue; let uri = imgElt.attributes && imgElt.attributes.src && imgElt.attributes.src.nodeValue;
if (uri.indexOf('http://') !== 0 && uri.indexOf('https://') !== 0) { if (uri && uri.indexOf('http://') !== 0 && uri.indexOf('https://') !== 0) {
uri = decodeURIComponent(uri); uri = decodeURIComponent(uri);
imgElt.removeAttribute('src'); imgElt.removeAttribute('src');
return { imgElt, uri }; return { imgElt, uri };
@ -116,7 +116,21 @@ export default {
// Make TOC // Make TOC
const allHeaders = containerElt.querySelectorAll('h1,h2,h3,h4,h5,h6'); const allHeaders = containerElt.querySelectorAll('h1,h2,h3,h4,h5,h6');
Array.prototype.slice.call(allHeaders).forEach((headingElt) => { Array.prototype.slice.call(allHeaders).forEach((headingElt) => {
headingElt.innerHTML = `<span class="prefix"></span><span class="content">${headingElt.innerHTML}</span><span class="suffix"></span>`; // 创建一个新的 <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 => ({ const headings = allHeaders.cl_map(headingElt => ({
title: headingElt.textContent, title: headingElt.textContent,

View File

@ -3,8 +3,8 @@ import store from '../../store';
import editorSvc from '../../services/editorSvc'; import editorSvc from '../../services/editorSvc';
import syncSvc from '../../services/syncSvc'; import syncSvc from '../../services/syncSvc';
// Skip shortcuts if modal is open or editor is hidden // Skip shortcuts if modal is open
Mousetrap.prototype.stopCallback = () => store.getters['modal/config'] || !store.getters['content/isCurrentEditable']; Mousetrap.prototype.stopCallback = () => store.getters['modal/config'];
const pagedownHandler = name => () => { const pagedownHandler = name => () => {
editorSvc.pagedownEditor.uiManager.doClick(name); editorSvc.pagedownEditor.uiManager.doClick(name);
@ -20,6 +20,14 @@ const findReplaceOpener = type => () => {
return true; return true;
}; };
const toggleEditor = () => () => {
store.dispatch('data/toggleEditor', !store.getters['data/layoutSettings'].showEditor);
return true;
};
// 非编辑模式下支持的快捷键
const noEditableShortcutMethods = ['toggleeditor'];
const methods = { const methods = {
bold: pagedownHandler('bold'), bold: pagedownHandler('bold'),
italic: pagedownHandler('italic'), italic: pagedownHandler('italic'),
@ -36,6 +44,7 @@ const methods = {
inline: pagedownHandler('heading'), inline: pagedownHandler('heading'),
hr: pagedownHandler('hr'), hr: pagedownHandler('hr'),
inlineformula: pagedownHandler('inlineformula'), inlineformula: pagedownHandler('inlineformula'),
toggleeditor: toggleEditor(),
sync() { sync() {
if (syncSvc.isSyncPossible()) { if (syncSvc.isSyncPossible()) {
syncSvc.requestSync(); syncSvc.requestSync();
@ -67,8 +76,11 @@ const methods = {
}; };
store.watch( store.watch(
() => store.getters['data/computedSettings'], () => ({
(computedSettings) => { computedSettings: store.getters['data/computedSettings'],
isCurrentEditable: store.getters['content/isCurrentEditable'],
}),
({ computedSettings, isCurrentEditable }) => {
Mousetrap.reset(); Mousetrap.reset();
Object.entries(computedSettings.shortcuts).forEach(([key, shortcut]) => { Object.entries(computedSettings.shortcuts).forEach(([key, shortcut]) => {
@ -80,14 +92,18 @@ store.watch(
} }
if (Object.prototype.hasOwnProperty.call(methods, method)) { if (Object.prototype.hasOwnProperty.call(methods, method)) {
try { try {
Mousetrap.bind(`${key}`, () => !methods[method].apply(null, params)); // editor is editable or 一些非编辑模式下支持的快捷键
if (isCurrentEditable || noEditableShortcutMethods.indexOf(method) !== -1) {
Mousetrap.bind(`${key}`, () => !methods[method].apply(null, params));
}
} catch (e) { } catch (e) {
// Ignore // Ignore
} }
} }
} }
}); });
}, { },
{
immediate: true, immediate: true,
}, },
); );

View File

@ -36,7 +36,7 @@ export default new Provider({
async uploadContent(token, content, syncLocation) { async uploadContent(token, content, syncLocation) {
const updatedSyncLocation = { const updatedSyncLocation = {
...syncLocation, ...syncLocation,
projectId: await giteaHelper.getProjectId(syncLocation), projectId: await giteaHelper.getProjectId(token, syncLocation),
}; };
if (!savedSha[updatedSyncLocation.id]) { if (!savedSha[updatedSyncLocation.id]) {
try { try {
@ -59,7 +59,7 @@ export default new Provider({
async publish(token, html, metadata, publishLocation, commitMessage) { async publish(token, html, metadata, publishLocation, commitMessage) {
const updatedPublishLocation = { const updatedPublishLocation = {
...publishLocation, ...publishLocation,
projectId: await giteaHelper.getProjectId(publishLocation), projectId: await giteaHelper.getProjectId(token, publishLocation),
}; };
try { try {
// Get the last sha // Get the last sha
@ -81,7 +81,7 @@ export default new Provider({
async openFile(token, syncLocation) { async openFile(token, syncLocation) {
const updatedSyncLocation = { const updatedSyncLocation = {
...syncLocation, ...syncLocation,
projectId: await giteaHelper.getProjectId(syncLocation), projectId: await giteaHelper.getProjectId(token, syncLocation),
}; };
// Check if the file exists and open it // Check if the file exists and open it

View File

@ -5,91 +5,91 @@ import utils from '../utils';
import userSvc from '../userSvc'; import userSvc from '../userSvc';
export default new Provider({ export default new Provider({
id: 'giteegist', id: 'giteegist',
name: 'GiteeGist', name: 'GiteeGist',
getToken({ sub }) { getToken({ sub }) {
return store.getters['data/giteeTokensBySub'][sub]; return store.getters['data/giteeTokensBySub'][sub];
}, },
getLocationUrl({ gistId }) { getLocationUrl({ gistId }) {
return `https://gitee.com/mafgwo/codes/${gistId}`; return `https://gitee.com/caojiezi2003/codes/${gistId}`;
}, },
getLocationDescription({ filename }) { getLocationDescription({ filename }) {
return filename; return filename;
}, },
async downloadContent(token, syncLocation) { async downloadContent(token, syncLocation) {
const content = await giteeHelper.downloadGist({ const content = await giteeHelper.downloadGist({
...syncLocation, ...syncLocation,
token, token,
}); });
return Provider.parseContent(content, `${syncLocation.fileId}/content`); return Provider.parseContent(content, `${syncLocation.fileId}/content`);
}, },
async uploadContent(token, content, syncLocation) { async uploadContent(token, content, syncLocation) {
const file = store.state.file.itemsById[syncLocation.fileId]; const file = store.state.file.itemsById[syncLocation.fileId];
const description = utils.sanitizeName(file && file.name); const description = utils.sanitizeName(file && file.name);
const gist = await giteeHelper.uploadGist({ const gist = await giteeHelper.uploadGist({
...syncLocation, ...syncLocation,
token, token,
description, description,
content: Provider.serializeContent(content), content: Provider.serializeContent(content),
}); });
return { return {
...syncLocation, ...syncLocation,
gistId: gist.id, gistId: gist.id,
}; };
}, },
async publish(token, html, metadata, publishLocation) { async publish(token, html, metadata, publishLocation) {
const gist = await giteeHelper.uploadGist({ const gist = await giteeHelper.uploadGist({
...publishLocation, ...publishLocation,
token, token,
description: metadata.title, description: metadata.title,
content: html, content: html,
}); });
return { return {
...publishLocation, ...publishLocation,
gistId: gist.id, gistId: gist.id,
}; };
}, },
makeLocation(token, filename, isPublic, gistId) { makeLocation(token, filename, isPublic, gistId) {
return { return {
providerId: this.id, providerId: this.id,
sub: token.sub, sub: token.sub,
filename, filename,
isPublic, isPublic,
gistId, gistId,
}; };
}, },
async listFileRevisions({ token, syncLocation }) { async listFileRevisions({ token, syncLocation }) {
const entries = await giteeHelper.getGistCommits({ const entries = await giteeHelper.getGistCommits({
...syncLocation, ...syncLocation,
token, token,
}); });
return entries.map((entry) => { return entries.map((entry) => {
const sub = `${giteeHelper.subPrefix}:${entry.user.id}`; const sub = `${giteeHelper.subPrefix}:${entry.user.id}`;
userSvc.addUserInfo({ id: sub, name: entry.user.login, imageUrl: entry.user.avatar_url }); userSvc.addUserInfo({ id: sub, name: entry.user.login, imageUrl: entry.user.avatar_url });
return { return {
sub, sub,
id: entry.version, id: entry.version,
message: entry.commit && entry.commit.message, message: entry.commit && entry.commit.message,
created: new Date(entry.committed_at).getTime(), created: new Date(entry.committed_at).getTime(),
}; };
}); });
}, },
async loadFileRevision() { async loadFileRevision() {
// Revision are already loaded // Revision are already loaded
return false; return false;
}, },
// async getFileRevisionContent({ // async getFileRevisionContent({
// token, // token,
// contentId, // contentId,
// syncLocation, // syncLocation,
// revisionId, // revisionId,
// }) { // }) {
// const data = await giteeHelper.downloadGistRevision({ // const data = await giteeHelper.downloadGistRevision({
// ...syncLocation, // ...syncLocation,
// token, // token,
// sha: revisionId, // sha: revisionId,
// }); // });
// return Provider.parseContent(data, contentId); // return Provider.parseContent(data, contentId);
// }, // },
}); });

View File

@ -0,0 +1,292 @@
import store from '../../store';
import githubHelper from './helpers/githubHelper';
import Provider from './common/Provider';
import gitWorkspaceSvc from '../gitWorkspaceSvc';
import userSvc from '../userSvc';
const appDataRepo = 'stackedit-app-data';
const appDataBranch = 'master';
export default new Provider({
id: 'githubAppData',
name: 'Gitee应用数据',
getToken() {
return store.getters['workspace/syncToken'];
},
getWorkspaceParams() {
// No param as it's the main workspace
return {};
},
getWorkspaceLocationUrl() {
// No direct link to app data
return null;
},
getSyncDataUrl() {
// No direct link to app data
return null;
},
getSyncDataDescription({ id }) {
return id;
},
async initWorkspace() {
// Nothing much to do since the main workspace isn't necessarily synchronized
// Return the main workspace
return store.getters['workspace/workspacesById'].main;
},
getChanges() {
const token = this.getToken();
return githubHelper.getTree({
owner: token.name,
repo: appDataRepo,
branch: appDataBranch,
token,
});
},
prepareChanges(tree) {
return gitWorkspaceSvc.makeChanges(tree);
},
async saveWorkspaceItem({ item }) {
const syncData = {
id: store.getters.gitPathsByItemId[item.id],
type: item.type,
hash: item.hash,
};
// Files and folders are not in git, only contents
if (item.type === 'file' || item.type === 'folder') {
return { syncData };
}
// locations are stored as paths, so we upload an empty file
const syncToken = store.getters['workspace/syncToken'];
await githubHelper.uploadFile({
owner: syncToken.name,
repo: appDataRepo,
branch: appDataBranch,
token: syncToken,
path: syncData.id,
content: '',
sha: gitWorkspaceSvc.shaByPath[syncData.id],
commitMessage: item.commitMessage,
});
// Return sync data to save
return { syncData };
},
async removeWorkspaceItem({ syncData }) {
if (gitWorkspaceSvc.shaByPath[syncData.id]) {
const syncToken = store.getters['workspace/syncToken'];
await githubHelper.removeFile({
owner: syncToken.name,
repo: appDataRepo,
branch: appDataBranch,
token: syncToken,
path: syncData.id,
sha: gitWorkspaceSvc.shaByPath[syncData.id],
});
}
},
async downloadWorkspaceContent({
token,
contentId,
contentSyncData,
fileSyncData,
}) {
const { sha, data } = await githubHelper.downloadFile({
owner: token.name,
repo: appDataRepo,
branch: appDataBranch,
token,
path: fileSyncData.id,
});
gitWorkspaceSvc.shaByPath[fileSyncData.id] = sha;
const content = Provider.parseContent(data, contentId);
return {
content,
contentSyncData: {
...contentSyncData,
hash: content.hash,
sha,
},
};
},
async downloadFile({ token, path }) {
const { sha, data } = await githubHelper.downloadFile({
owner: token.name,
repo: appDataRepo,
branch: appDataBranch,
token,
path,
isImg: true,
});
return {
content: data,
sha,
};
},
async downloadWorkspaceData({ token, syncData }) {
if (!syncData) {
return {};
}
const path = `.stackedit-data/${syncData.id}.json`;
// const path = store.getters.gitPathsByItemId[syncData.id];
// const path = syncData.id;
const { sha, data } = await githubHelper.downloadFile({
owner: token.name,
repo: appDataRepo,
branch: appDataBranch,
token,
path,
});
if (!sha) {
return {};
}
gitWorkspaceSvc.shaByPath[path] = sha;
const item = JSON.parse(data);
return {
item,
syncData: {
...syncData,
hash: item.hash,
sha,
type: 'data',
},
};
},
async uploadWorkspaceContent({
token,
content,
file,
commitMessage,
}) {
const isImg = file.type === 'img';
const path = !isImg ? store.getters.gitPathsByItemId[file.id] : file.path;
const res = await githubHelper.uploadFile({
owner: token.name,
repo: appDataRepo,
branch: appDataBranch,
token,
path,
content: !isImg ? Provider.serializeContent(content) : file.content,
sha: gitWorkspaceSvc.shaByPath[!isImg ? path : file.path],
isImg,
commitMessage,
});
if (isImg) {
return {
sha: res.content.sha,
};
}
// Return new sync data
return {
contentSyncData: {
id: store.getters.gitPathsByItemId[content.id],
type: content.type,
hash: content.hash,
sha: res.content.sha,
},
fileSyncData: {
id: path,
type: 'file',
hash: file.hash,
},
};
},
async uploadWorkspaceData({
token,
item,
syncData,
}) {
const path = `.stackedit-data/${item.id}.json`;
// const path = store.getters.gitPathsByItemId[item.id];
// const path = syncData.id;
const res = await githubHelper.uploadFile({
token,
owner: token.name,
repo: appDataRepo,
branch: appDataBranch,
path,
content: JSON.stringify(item),
sha: gitWorkspaceSvc.shaByPath[path],
});
return {
syncData: {
...syncData,
type: item.type,
hash: item.hash,
data: item.data,
sha: res.content.sha,
},
};
},
async listFileRevisions({ token, fileSyncDataId }) {
const { owner, repo, branch } = {
owner: token.name,
repo: appDataRepo,
branch: appDataBranch,
};
const entries = await githubHelper.getCommits({
token,
owner,
repo,
sha: branch,
path: fileSyncDataId,
});
return entries.map(({
author,
committer,
commit,
sha,
}) => {
let user;
if (author && author.login) {
user = author;
} else if (committer && committer.login) {
user = committer;
}
const sub = `${githubHelper.subPrefix}:${user.login}`;
if (user.avatar_url && user.avatar_url.endsWith('.png') && !user.avatar_url.endsWith('no_portrait.png')) {
user.avatar_url = `${user.avatar_url}!avatar60`;
}
userSvc.addUserInfo({ id: sub, name: user.login, imageUrl: user.avatar_url });
const date = (commit.author && commit.author.date)
|| (commit.committer && commit.committer.date)
|| 1;
return {
id: sha,
sub,
message: commit.message,
created: new Date(date).getTime(),
};
});
},
async loadFileRevision() {
// Revisions are already loaded
return false;
},
async getFileRevisionContent({
token,
contentId,
fileSyncDataId,
revisionId,
}) {
const { data } = await githubHelper.downloadFile({
owner: token.name,
repo: appDataRepo,
branch: revisionId,
token,
path: fileSyncDataId,
});
return Provider.parseContent(data, contentId);
},
getFilePathUrl(path) {
const token = this.getToken();
if (!token) {
return null;
}
return `https://github.com/${token.name}/${appDataRepo}/blob/${appDataBranch}${path}`;
},
});

View File

@ -75,11 +75,11 @@ export default new Provider({
const sub = workspace ? workspace.sub : utils.queryParams.sub; const sub = workspace ? workspace.sub : utils.queryParams.sub;
let token = store.getters['data/gitlabTokensBySub'][sub]; let token = store.getters['data/gitlabTokensBySub'][sub];
if (!token) { if (!token) {
const { applicationId } = await store.dispatch('modal/open', { const { applicationId, applicationSecret } = await store.dispatch('modal/open', {
type: 'gitlabAccount', type: 'gitlabAccount',
forceServerUrl: serverUrl, forceServerUrl: serverUrl,
}); });
token = await gitlabHelper.addAccount(serverUrl, applicationId, sub); token = await gitlabHelper.addAccount(serverUrl, applicationId, applicationSecret, sub);
} }
if (!workspace) { if (!workspace) {

View File

@ -317,6 +317,12 @@ export default {
isImg, isImg,
commitMessage, commitMessage,
}) { }) {
// 非法的文件名 不让提交
if (!path || path.endsWith('undefined')) {
return new Promise((resolve) => {
resolve({ res: { content: { sha: null } } });
});
}
let uploadContent = content; let uploadContent = content;
if (isImg && typeof content !== 'string') { if (isImg && typeof content !== 'string') {
uploadContent = await utils.encodeFiletoBase64(content); uploadContent = await utils.encodeFiletoBase64(content);

View File

@ -147,6 +147,7 @@ export default {
sub: `${user.login}`, sub: `${user.login}`,
}; };
if (isMain) { if (isMain) {
token.providerId = 'giteeAppData';
// 检查 stackedit-app-data 仓库是否已经存在 如果不存在则创建该仓库 // 检查 stackedit-app-data 仓库是否已经存在 如果不存在则创建该仓库
await this.checkAndCreateRepo(token); await this.checkAndCreateRepo(token);
} }
@ -279,6 +280,12 @@ export default {
isImg, isImg,
commitMessage, commitMessage,
}) { }) {
// 非法的文件名 不让提交
if (!path || path.endsWith('undefined')) {
return new Promise((resolve) => {
resolve({ res: { content: { sha: null } } });
});
}
let uploadContent = content; let uploadContent = content;
if (isImg && typeof content !== 'string') { if (isImg && typeof content !== 'string') {
uploadContent = await utils.encodeFiletoBase64(content); uploadContent = await utils.encodeFiletoBase64(content);

View File

@ -6,6 +6,8 @@ import badgeSvc from '../../badgeSvc';
const getScopes = token => [token.repoFullAccess ? 'repo' : 'public_repo', 'gist']; const getScopes = token => [token.repoFullAccess ? 'repo' : 'public_repo', 'gist'];
const appDataRepo = 'stackedit-app-data';
const request = (token, options) => networkSvc.request({ const request = (token, options) => networkSvc.request({
...options, ...options,
headers: { headers: {
@ -62,7 +64,7 @@ export default {
/** /**
* https://developer.github.com/apps/building-oauth-apps/authorization-options-for-oauth-apps/ * https://developer.github.com/apps/building-oauth-apps/authorization-options-for-oauth-apps/
*/ */
async startOauth2(scopes, sub = null, silent = false) { async startOauth2(scopes, sub = null, silent = false, isMain) {
await networkSvc.getServerConf(); await networkSvc.getServerConf();
const clientId = store.getters['data/serverConf'].githubClientId; const clientId = store.getters['data/serverConf'].githubClientId;
@ -110,16 +112,26 @@ export default {
const token = { const token = {
scopes, scopes,
accessToken, accessToken,
// 主文档空间的登录 标识登录
isLogin: !!isMain || (oldToken && !!oldToken.isLogin),
name: user.login, name: user.login,
sub: `${user.id}`, sub: `${user.id}`,
imgStorages: oldToken && oldToken.imgStorages, imgStorages: oldToken && oldToken.imgStorages,
repoFullAccess: scopes.includes('repo'), repoFullAccess: scopes.includes('repo'),
}; };
if (isMain) {
token.providerId = 'githubAppData';
// check stackedit-app-data repo exist?
await this.checkAndCreateRepo(token);
}
// Add token to github tokens // Add token to github tokens
store.dispatch('data/addGithubToken', token); store.dispatch('data/addGithubToken', token);
return token; return token;
}, },
signin() {
return this.startOauth2(['repo', 'gist'], null, false, true);
},
async addAccount(repoFullAccess = false) { async addAccount(repoFullAccess = false) {
const token = await this.startOauth2(getScopes({ repoFullAccess })); const token = await this.startOauth2(getScopes({ repoFullAccess }));
badgeSvc.addBadge('addGitHubAccount'); badgeSvc.addBadge('addGitHubAccount');
@ -148,6 +160,30 @@ export default {
return tree; return tree;
}, },
async checkAndCreateRepo(token) {
const url = `https://api.github.com/repos/${encodeURIComponent(token.name)}/${encodeURIComponent(appDataRepo)}`;
try {
await request(token, { url });
} catch (err) {
// create
if (err.status === 404) {
await request(token, {
method: 'POST',
url: 'https://api.github.com/repos/mafgwo/stackedit-appdata-template/generate',
body: {
owner: token.name,
name: appDataRepo,
description: 'StackEdit中文版默认空间.',
include_all_branches: false,
private: true,
},
});
} else {
throw err;
}
}
},
/** /**
* https://developer.github.com/v3/repos/commits/#list-commits-on-a-repository * https://developer.github.com/v3/repos/commits/#list-commits-on-a-repository
*/ */
@ -157,11 +193,29 @@ export default {
repo, repo,
sha, sha,
path, path,
tryTimes,
}) { }) {
return repoRequest(token, owner, repo, { let tryCount = tryTimes || 1;
url: 'commits', try {
params: { sha, path }, return repoRequest(token, owner, repo, {
}); url: 'commits',
params: { sha, path },
});
} catch (err) {
// 主文档 并且 409 则重试3次
if (tryCount <= 3 && err.status === 409 && repo === appDataRepo) {
tryCount += 1;
return this.getCommits({
token,
owner,
repo,
sha,
path,
tryTimes: tryCount,
});
}
throw err;
}
}, },
/** /**
@ -179,6 +233,12 @@ export default {
isImg, isImg,
commitMessage, commitMessage,
}) { }) {
// 非法的文件名 不让提交
if (!path || path.endsWith('undefined')) {
return new Promise((resolve) => {
resolve({ res: { content: { sha: null } } });
});
}
let uploadContent = content; let uploadContent = content;
if (isImg && typeof content !== 'string') { if (isImg && typeof content !== 'string') {
uploadContent = await utils.encodeFiletoBase64(content); uploadContent = await utils.encodeFiletoBase64(content);
@ -228,14 +288,30 @@ export default {
path, path,
isImg, isImg,
}) { }) {
const { sha, content } = await repoRequest(token, owner, repo, { try {
url: `contents/${encodeURIComponent(path)}`, const { sha, content, encoding } = await repoRequest(token, owner, repo, {
params: { ref: branch }, url: `contents/${encodeURIComponent(path)}`,
}); params: { ref: branch },
return { });
sha, let tempContent = content;
data: !isImg ? utils.decodeBase64(content) : content, // 如果是图片且 encoding 为 none 则 需要获取 blob
}; if (isImg && encoding === 'none') {
const blobInfo = await repoRequest(token, owner, repo, {
url: `git/blobs/${sha}`,
});
tempContent = blobInfo.content;
}
return {
sha,
data: !isImg ? utils.decodeBase64(tempContent) : tempContent,
};
} catch (err) {
// not .stackedit-data throw err
if (err.status === 404 && path.indexOf('.stackedit-data') >= 0) {
return {};
}
throw err;
}
}, },
/** /**
* 获取仓库信息 * 获取仓库信息

View File

@ -3,6 +3,9 @@ import networkSvc from '../../networkSvc';
import store from '../../../store'; import store from '../../../store';
import userSvc from '../../userSvc'; import userSvc from '../../userSvc';
import badgeSvc from '../../badgeSvc'; import badgeSvc from '../../badgeSvc';
import constants from '../../../data/constants';
const tokenExpirationMargin = 5 * 60 * 1000;
const request = ({ accessToken, serverUrl }, options) => networkSvc.request({ const request = ({ accessToken, serverUrl }, options) => networkSvc.request({
...options, ...options,
@ -50,34 +53,90 @@ export default {
/** /**
* https://docs.gitlab.com/ee/api/oauth2.html * https://docs.gitlab.com/ee/api/oauth2.html
*/ */
async startOauth2(serverUrl, applicationId, sub = null, silent = false) { async startOauth2(
serverUrl, applicationId, applicationSecret,
sub = null, silent = false, refreshToken,
) {
let apiUrl = serverUrl; let apiUrl = serverUrl;
let clientId = applicationId; let clientId = applicationId;
// 获取gitea配置的参数 let useServerConf = false;
// 获取gitlab配置的参数
await networkSvc.getServerConf(); await networkSvc.getServerConf();
const confClientId = store.getters['data/serverConf'].gitlabClientId; const confClientId = store.getters['data/serverConf'].gitlabClientId;
const confServerUrl = store.getters['data/serverConf'].gitlabUrl; const confServerUrl = store.getters['data/serverConf'].gitlabUrl;
// 存在gitea配置则使用后端配置 // 存在gitlab配置则使用后端配置
if (confClientId && confServerUrl) { if (confClientId && confServerUrl) {
apiUrl = confServerUrl; apiUrl = confServerUrl;
clientId = confClientId; clientId = confClientId;
useServerConf = true;
}
let tokenBody;
if (!silent) {
// Get an OAuth2 code
const { code } = await networkSvc.startOauth2(
`${apiUrl}/oauth/authorize`,
{
client_id: clientId,
response_type: 'code',
redirect_uri: constants.oauth2RedirectUri,
},
silent,
);
if (useServerConf) {
tokenBody = (await networkSvc.request({
method: 'GET',
url: 'oauth2/gitlabToken',
params: {
code,
grant_type: 'authorization_code',
redirect_uri: constants.oauth2RedirectUri,
},
})).body;
} else {
// Exchange code with token
tokenBody = (await networkSvc.request({
method: 'POST',
url: `${apiUrl}/oauth/token`,
params: {
client_id: clientId,
client_secret: applicationSecret,
code,
grant_type: 'authorization_code',
redirect_uri: constants.oauth2RedirectUri,
},
})).body;
}
} else if (useServerConf) {
tokenBody = (await networkSvc.request({
method: 'GET',
url: 'oauth2/gitlabToken',
params: {
refresh_token: refreshToken,
grant_type: 'refresh_token',
redirect_uri: constants.oauth2RedirectUri,
},
})).body;
} else {
// Exchange refreshToken with token
tokenBody = (await networkSvc.request({
method: 'POST',
url: `${apiUrl}/oauth/token`,
body: {
client_id: clientId,
client_secret: applicationSecret,
refresh_token: refreshToken,
grant_type: 'refresh_token',
redirect_uri: constants.oauth2RedirectUri,
},
})).body;
} }
// Get an OAuth2 code
const { accessToken } = await networkSvc.startOauth2(
`${apiUrl}/oauth/authorize`,
{
client_id: clientId,
response_type: 'token',
scope: 'api',
},
silent,
);
const accessToken = tokenBody.access_token;
// Call the user info endpoint // Call the user info endpoint
const user = await request({ accessToken, serverUrl }, { const user = await request({ accessToken, serverUrl: apiUrl }, {
url: 'user', url: 'user',
}); });
const uniqueSub = `${serverUrl}/${user.id}`; const uniqueSub = `${apiUrl}/${user.id}`;
userSvc.addUserInfo({ userSvc.addUserInfo({
id: `${subPrefix}:${uniqueSub}`, id: `${subPrefix}:${uniqueSub}`,
name: user.username, name: user.username,
@ -89,11 +148,17 @@ export default {
throw new Error('GitLab account ID not expected.'); throw new Error('GitLab account ID not expected.');
} }
const oldToken = store.getters['data/gitlabTokensBySub'][uniqueSub];
// Build token object including scopes and sub // Build token object including scopes and sub
const token = { const token = {
accessToken, accessToken,
name: user.username, name: user.username,
serverUrl, applicationId: clientId,
applicationSecret,
imgStorages: oldToken && oldToken.imgStorages,
refreshToken: tokenBody.refresh_token,
expiresOn: Date.now() + ((tokenBody.expires_in || 7200) * 1000),
serverUrl: apiUrl,
sub: uniqueSub, sub: uniqueSub,
}; };
@ -101,12 +166,58 @@ export default {
store.dispatch('data/addGitlabToken', token); store.dispatch('data/addGitlabToken', token);
return token; return token;
}, },
async addAccount(serverUrl, applicationId, sub = null) { async addAccount(serverUrl, applicationId, applicationSecret, sub = null) {
const token = await this.startOauth2(serverUrl, applicationId, sub); const token = await this.startOauth2(serverUrl, applicationId, applicationSecret, sub);
badgeSvc.addBadge('addGitLabAccount'); badgeSvc.addBadge('addGitLabAccount');
return token; return token;
}, },
// 刷新token
async refreshToken(token) {
const {
serverUrl,
applicationId,
applicationSecret,
sub,
} = token;
const lastToken = store.getters['data/gitlabTokensBySub'][sub];
// 兼容旧的没有过期时间
if (!lastToken.expiresOn || !lastToken.refreshToken) {
await store.dispatch('modal/open', {
type: 'providerRedirection',
name: 'Gitlab',
});
return this.startOauth2(serverUrl, applicationId, applicationSecret, sub);
}
// lastToken is not expired
if (lastToken.expiresOn > Date.now() + tokenExpirationMargin) {
return lastToken;
}
// existing token is about to expire.
// Try to get a new token in background
try {
return await this.startOauth2(
serverUrl, applicationId, applicationSecret,
sub, true, lastToken.refreshToken,
);
} catch (err) {
// If it fails try to popup a window
if (store.state.offline) {
throw err;
}
await store.dispatch('modal/open', {
type: 'providerRedirection',
name: 'Gitlab',
});
return this.startOauth2(serverUrl, applicationId, applicationSecret, sub);
}
},
// 带刷新token
async requestWithRefreshToken(token, options) {
const refreshedToken = await this.refreshToken(token);
const result = await request(refreshedToken, options);
return result;
},
/** /**
* https://docs.gitlab.com/ee/api/projects.html#get-single-project * https://docs.gitlab.com/ee/api/projects.html#get-single-project
*/ */
@ -114,8 +225,7 @@ export default {
if (projectId) { if (projectId) {
return projectId; return projectId;
} }
const project = await this.requestWithRefreshToken(token, {
const project = await request(token, {
url: `projects/${encodeURIComponent(projectPath)}`, url: `projects/${encodeURIComponent(projectPath)}`,
}); });
return project.id; return project.id;
@ -129,7 +239,7 @@ export default {
projectId, projectId,
branch, branch,
}) { }) {
return request(token, { return this.requestWithRefreshToken(token, {
url: `projects/${encodeURIComponent(projectId)}/repository/tree`, url: `projects/${encodeURIComponent(projectId)}/repository/tree`,
params: { params: {
ref: branch, ref: branch,
@ -148,7 +258,7 @@ export default {
branch, branch,
path, path,
}) { }) {
return request(token, { return this.requestWithRefreshToken(token, {
url: `projects/${encodeURIComponent(projectId)}/repository/commits`, url: `projects/${encodeURIComponent(projectId)}/repository/commits`,
params: { params: {
ref_name: branch, ref_name: branch,
@ -171,11 +281,17 @@ export default {
isImg, isImg,
commitMessage, commitMessage,
}) { }) {
// 非法的文件名 不让提交
if (!path || path.endsWith('undefined')) {
return new Promise((resolve) => {
resolve({ res: { content: { sha: null } } });
});
}
let uploadContent = content; let uploadContent = content;
if (isImg && typeof content !== 'string') { if (isImg && typeof content !== 'string') {
uploadContent = await utils.encodeFiletoBase64(content); uploadContent = await utils.encodeFiletoBase64(content);
} }
return request(token, { return this.requestWithRefreshToken(token, {
method: sha ? 'PUT' : 'POST', method: sha ? 'PUT' : 'POST',
url: `projects/${encodeURIComponent(projectId)}/repository/files/${encodeURIComponent(path)}`, url: `projects/${encodeURIComponent(projectId)}/repository/files/${encodeURIComponent(path)}`,
body: { body: {
@ -198,7 +314,7 @@ export default {
path, path,
sha, sha,
}) { }) {
return request(token, { return this.requestWithRefreshToken(token, {
method: 'DELETE', method: 'DELETE',
url: `projects/${encodeURIComponent(projectId)}/repository/files/${encodeURIComponent(path)}`, url: `projects/${encodeURIComponent(projectId)}/repository/files/${encodeURIComponent(path)}`,
body: { body: {
@ -219,7 +335,7 @@ export default {
path, path,
isImg, isImg,
}) { }) {
const res = await request(token, { const res = await this.requestWithRefreshToken(token, {
url: `projects/${encodeURIComponent(projectId)}/repository/files/${encodeURIComponent(path)}`, url: `projects/${encodeURIComponent(projectId)}/repository/files/${encodeURIComponent(path)}`,
params: { ref: branch }, params: { ref: branch },
}); });

View File

@ -10,8 +10,7 @@ const tokenExpirationMargin = 5 * 60 * 1000; // 5 min (tokens expire after 1h)
const driveAppDataScopes = ['https://www.googleapis.com/auth/drive.appdata']; const driveAppDataScopes = ['https://www.googleapis.com/auth/drive.appdata'];
const getDriveScopes = token => [token.driveFullAccess const getDriveScopes = token => [token.driveFullAccess
? 'https://www.googleapis.com/auth/drive' ? 'https://www.googleapis.com/auth/drive'
: 'https://www.googleapis.com/auth/drive.file', : 'https://www.googleapis.com/auth/drive.file'];
'https://www.googleapis.com/auth/drive.install'];
const bloggerScopes = ['https://www.googleapis.com/auth/blogger']; const bloggerScopes = ['https://www.googleapis.com/auth/blogger'];
const photosScopes = ['https://www.googleapis.com/auth/photos']; const photosScopes = ['https://www.googleapis.com/auth/photos'];

View File

@ -6,6 +6,7 @@ import diffUtils from './diffUtils';
import networkSvc from './networkSvc'; import networkSvc from './networkSvc';
import providerRegistry from './providers/common/providerRegistry'; import providerRegistry from './providers/common/providerRegistry';
import giteeAppDataProvider from './providers/giteeAppDataProvider'; import giteeAppDataProvider from './providers/giteeAppDataProvider';
import githubAppDataProvider from './providers/githubAppDataProvider';
import './providers/couchdbWorkspaceProvider'; import './providers/couchdbWorkspaceProvider';
import './providers/githubWorkspaceProvider'; import './providers/githubWorkspaceProvider';
import './providers/giteeWorkspaceProvider'; import './providers/giteeWorkspaceProvider';
@ -830,7 +831,7 @@ const syncWorkspace = async (skipContents = false) => {
} }
if (workspace.id === 'main') { if (workspace.id === 'main') {
badgeSvc.addBadge('syncMainWorkspace'); badgeSvc.addBadge(workspace.providerId === 'giteeAppData' ? 'syncMainWorkspace' : 'githubSyncMainWorkspace');
} }
} catch (err) { } catch (err) {
if (err && err.message === 'TOO_LATE') { if (err && err.message === 'TOO_LATE') {
@ -969,6 +970,15 @@ const requestSync = (addTriggerSyncBadge = false) => {
}); });
}; };
const afterSignIn = async () => {
if (store.getters['workspace/currentWorkspace'].id === 'main' && workspaceProvider) {
const mainToken = store.getters['workspace/mainWorkspaceToken'];
// Try to find a suitable workspace sync provider
workspaceProvider = mainToken.providerId === 'githubAppData' ? githubAppDataProvider : giteeAppDataProvider;
await workspaceProvider.initWorkspace();
}
};
export default { export default {
async init() { async init() {
// Load workspaces and tokens from localStorage // Load workspaces and tokens from localStorage
@ -980,10 +990,11 @@ export default {
await actionProvider.initAction(); await actionProvider.initAction();
} }
const mainToken = store.getters['workspace/mainWorkspaceToken'];
// Try to find a suitable workspace sync provider // Try to find a suitable workspace sync provider
workspaceProvider = providerRegistry.providersById[utils.queryParams.providerId]; workspaceProvider = providerRegistry.providersById[utils.queryParams.providerId];
if (!workspaceProvider || !workspaceProvider.initWorkspace) { if (!workspaceProvider || !workspaceProvider.initWorkspace) {
workspaceProvider = giteeAppDataProvider; workspaceProvider = mainToken && mainToken.providerId === 'githubAppData' ? githubAppDataProvider : giteeAppDataProvider;
} }
const workspace = await workspaceProvider.initWorkspace(); const workspace = await workspaceProvider.initWorkspace();
// Fix the URL hash // Fix the URL hash
@ -1041,6 +1052,7 @@ export default {
}, 5000); }, 5000);
} }
}, },
afterSignIn,
syncImg, syncImg,
isSyncPossible, isSyncPossible,
requestSync, requestSync,

View File

@ -400,7 +400,8 @@ export default {
return path || ''; return path || '';
}, },
// 根据当前绝对路径 与 文件路径计算出文件绝对路径 // 根据当前绝对路径 与 文件路径计算出文件绝对路径
getAbsoluteFilePath(currDirNode, filePath) { getAbsoluteFilePath(currDirNode, originFilePath) {
const filePath = originFilePath && originFilePath.replaceAll('\\', '/');
const currAbsolutePath = this.getAbsoluteDir(currDirNode); const currAbsolutePath = this.getAbsoluteDir(currDirNode);
// "/"开头说明已经是绝对路径 // "/"开头说明已经是绝对路径
if (filePath.indexOf('/') === 0) { if (filePath.indexOf('/') === 0) {

View File

@ -1,25 +0,0 @@
const chatgptConfigKey = 'chatgpt/config';
export default {
namespaced: true,
state: {
config: {
apiKey: null,
proxyHost: null,
},
},
mutations: {
setCurrConfig: (state, value) => {
state.config = value;
},
},
getters: {
chatGptConfig: state => state.config,
},
actions: {
setCurrConfig({ commit }, value) {
commit('setCurrConfig', value);
localStorage.setItem(chatgptConfigKey, JSON.stringify(value));
},
},
};

View File

@ -1,5 +1,6 @@
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';
const idShifter = offset => (state, getters) => { const idShifter = offset => (state, getters) => {
@ -136,8 +137,13 @@ export default {
const loginToken = rootGetters['workspace/loginToken']; const loginToken = rootGetters['workspace/loginToken'];
if (!loginToken) { if (!loginToken) {
try { try {
await dispatch('modal/open', 'signInForComment', { root: true }); const signInWhere = await dispatch('modal/open', 'signInForComment', { root: true });
await giteeHelper.signin(); if (signInWhere === 'github') {
await githubHelper.signin();
} else {
await giteeHelper.signin();
}
await syncSvc.afterSignIn();
syncSvc.requestSync(); syncSvc.requestSync();
await dispatch('createNewDiscussion', selection); await dispatch('createNewDiscussion', selection);
} catch (e) { /* cancel */ } } catch (e) { /* cancel */ }

View File

@ -20,7 +20,6 @@ import userInfo from './userInfo';
import workspace from './workspace'; import workspace from './workspace';
import img from './img'; import img from './img';
import theme from './theme'; import theme from './theme';
import chatgpt from './chatgpt';
import locationTemplate from './locationTemplate'; import locationTemplate from './locationTemplate';
import emptyPublishLocation from '../data/empties/emptyPublishLocation'; import emptyPublishLocation from '../data/empties/emptyPublishLocation';
import emptySyncLocation from '../data/empties/emptySyncLocation'; import emptySyncLocation from '../data/empties/emptySyncLocation';
@ -52,7 +51,6 @@ const store = new Vuex.Store({
workspace, workspace,
img, img,
theme, theme,
chatgpt,
}, },
state: { state: {
light: false, light: false,

View File

@ -22,7 +22,7 @@ export default {
Object.entries(rootGetters['data/workspaces']).forEach(([id, workspace]) => { Object.entries(rootGetters['data/workspaces']).forEach(([id, workspace]) => {
const sanitizedWorkspace = { const sanitizedWorkspace = {
id, id,
providerId: 'giteeAppData', providerId: (mainWorkspaceToken && mainWorkspaceToken.providerId) || 'giteeAppData',
sub: mainWorkspaceToken && mainWorkspaceToken.sub, sub: mainWorkspaceToken && mainWorkspaceToken.sub,
...workspace, ...workspace,
}; };
@ -47,17 +47,19 @@ export default {
|| currentWorkspace.providerId === 'giteeWorkspace' || currentWorkspace.providerId === 'giteeWorkspace'
|| currentWorkspace.providerId === 'gitlabWorkspace' || currentWorkspace.providerId === 'gitlabWorkspace'
|| currentWorkspace.providerId === 'giteaWorkspace' || currentWorkspace.providerId === 'giteaWorkspace'
|| currentWorkspace.providerId === 'giteeAppData', || currentWorkspace.providerId === 'giteeAppData'
|| currentWorkspace.providerId === 'githubAppData',
currentWorkspaceHasUniquePaths: (state, { currentWorkspace }) => currentWorkspaceHasUniquePaths: (state, { currentWorkspace }) =>
currentWorkspace.providerId === 'githubWorkspace' currentWorkspace.providerId === 'githubWorkspace'
|| currentWorkspace.providerId === 'giteeWorkspace' || currentWorkspace.providerId === 'giteeWorkspace'
|| currentWorkspace.providerId === 'gitlabWorkspace' || currentWorkspace.providerId === 'gitlabWorkspace'
|| currentWorkspace.providerId === 'giteaWorkspace' || currentWorkspace.providerId === 'giteaWorkspace'
|| currentWorkspace.providerId === 'giteeAppData', || currentWorkspace.providerId === 'giteeAppData'
|| currentWorkspace.providerId === 'githubAppData',
lastSyncActivityKey: (state, { currentWorkspace }) => `${currentWorkspace.id}/lastSyncActivity`, lastSyncActivityKey: (state, { currentWorkspace }) => `${currentWorkspace.id}/lastSyncActivity`,
lastFocusKey: (state, { currentWorkspace }) => `${currentWorkspace.id}/lastWindowFocus`, lastFocusKey: (state, { currentWorkspace }) => `${currentWorkspace.id}/lastWindowFocus`,
mainWorkspaceToken: (state, getters, rootState, rootGetters) => mainWorkspaceToken: (state, getters, rootState, rootGetters) =>
utils.someResult(Object.values(rootGetters['data/giteeTokensBySub']), (token) => { utils.someResult([...Object.values(rootGetters['data/giteeTokensBySub']), ...Object.values(rootGetters['data/githubTokensBySub'])], (token) => {
if (token.isLogin) { if (token.isLogin) {
return token; return token;
} }
@ -85,8 +87,10 @@ export default {
switch (currentWorkspace.providerId) { switch (currentWorkspace.providerId) {
case 'googleDriveWorkspace': case 'googleDriveWorkspace':
return 'google'; return 'google';
case 'githubAppData':
case 'githubWorkspace': case 'githubWorkspace':
return 'github'; return 'github';
case 'giteeAppData':
case 'giteeWorkspace': case 'giteeWorkspace':
default: default:
return 'gitee'; return 'gitee';

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 264 KiB

View File

@ -0,0 +1,169 @@
<!DOCTYPE html>
<html>
<head>
<title>文章分享 - Markdown编辑器</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="canonical" href="https://md.jonylee.top/">
<link rel="icon" href="static/landing/favicon.ico" type="image/x-icon">
<link rel="shortcut icon" href="static/landing/favicon.ico" type="image/x-icon">
<meta charset="UTF-8">
<meta name="keywords" content="Markdown编辑器,StackEdit中文版,StackEdit汉化版,StackEdit,在线Markdown,笔记利器,Markdown笔记">
<meta name="description" content="支持直接将码云Gitee、GitHub、Gitea等仓库作为笔记存储仓库且支持拖拽/粘贴上传图片并且可以直接在页面编辑同步和管理的Markdown编辑器。">
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1">
<meta name="baidu-site-verification" content="code-tGpn2BT069" />
<meta name="msvalidate.01" content="90A9558158543277BD284CFA054E7F5B" />
<link rel="stylesheet" href="style.css">
<style>
.share-header {
position: fixed;
top: 0;
left: 0;
width: 100%;
background-color: #383c4a;
color: #fff;
padding: 10px;
box-sizing: border-box;
z-index: 99999;
}
.share-header .logo {
margin: 0 0 -8px 0;
}
.share-header nav {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.share-header nav ul {
list-style-type: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: row;
}
.share-header nav li {
margin: 0 10px;
}
.share-header nav a {
color: #fff;
text-decoration: none;
}
.share-header nav a:hover {
text-decoration: underline;
}
.share-content {
transform: translateY(50px);
height: 100vh;
}
</style>
<script type="text/javascript">
function getQueryString(name) {
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
var r = window.location.search.substr(1).match(reg);
if (r != null) {
return unescape(r[2]);
}
return null;
}
function appendTagHtml(newdoc, tagName, targetParentEle) {
const tags = newdoc.getElementsByTagName(tagName);
if (!tags) {
return;
}
for (let i = 0; i < tags.length; i++) {
targetParentEle.append(tags[i]);
}
}
window.onload = function() {
const xhr = new XMLHttpRequest();
const gistId = getQueryString('id');
let accessToken = null;
const tokens = window.localStorage.getItem('data/tokens');
if (tokens) {
const tokensObj = JSON.parse(tokens);
if (tokensObj.data && tokensObj.data.github) {
const tokenArr = Object.keys(tokensObj.data.github).map(it => tokensObj.data.github[it]).filter(it => it && it.isLogin);
if (tokenArr.length > 0) {
accessToken = tokenArr[0].accessToken;
}
}
}
const url = `https://api.github.com/gists/${gistId}`;
xhr.open('GET', url);
if (accessToken) {
xhr.setRequestHeader('Authorization', `Bearer ${accessToken}`);
}
xhr.onload = function() {
if (xhr.status === 200) {
const newdoc = document.implementation.createHTMLDocument("");
const body = JSON.parse(xhr.responseText);
for (let key in body.files) {
newdoc.documentElement.innerHTML = body.files[key].content;
}
const currHead = document.head;
// head
appendTagHtml(newdoc, 'style', currHead);
// title
document.title = newdoc.title + ' - StackEdit中文版';
// 内容
const shareContent = document.getElementsByClassName('share-content')[0];
shareContent.innerHTML = newdoc.body.innerHTML;
document.body.className = newdoc.body.className;
} else if (xhr.status === 403) {
const rateLimit = xhr.responseText && xhr.responseText.indexOf('Rate Limit') >= 0;
const appUri = `${window.location.protocol}//${window.location.host}/app`;
document.getElementById('div_info').innerHTML = `${rateLimit ? "请求太过频繁" : "无权限访问"}请使用GitHub登录 <a href="${appUri}" target="_brank">主文档空间</a> 后再刷新此页面!`;
} else {
console.error('An error occurred: ' + xhr.status);
document.getElementById('div_info').innerHTML = `分享内容获取失败或已失效请使用GitHub登录 <a href="${appUri}" target="_brank">主文档空间</a> 后再刷新此页面!`;
}
};
xhr.send();
}
</script>
</head>
<body>
<div class="share-header">
<nav>
<a class="logo" href="https://md.jonylee.top" target="_blank">
<img src="static/landing/logo.svg" height="30px" />
</a>
<ul>
<li><a href="https://md.jonylee.top" target="_blank">首页</a></li>
<li><a href="https://md.jonylee.top/app" target="_blank">写笔记</a></li>
</ul>
</nav>
</div>
<div class="share-content stackedit">
<div id="div_info" style="text-align: center; height: 600px;">文章加载中......</div>
</div>
<script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
<!-- built files will be auto injected -->
<!-- baidu统计-->
<script>
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?dad4b4383b13eedea1ab45ee323df1c3";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
<!-- baidu统计结束-->
</body>
</html>

View File

@ -2,14 +2,13 @@
<html manifest="cache.manifest"> <html manifest="cache.manifest">
<head> <head>
<title>StackEdit中文版 浏览器内 Markdown 编辑器 & 笔记利器</title> <title>Markdown编辑器-JonyLee的设计导航</title>
<link rel="canonical" href="https://stackedit.cn/"> <link rel="canonical" href="https://md.jonylee.top/">
<link rel="icon" href="static/landing/favicon.ico" type="image/x-icon"> <link rel="icon" href="static/landing/favicon.ico" type="image/x-icon">
<link rel="shortcut icon" href="static/landing/favicon.ico" type="image/x-icon"> <link rel="shortcut icon" href="static/landing/favicon.ico" type="image/x-icon">
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="keywords" content="Markdown编辑器,StackEdit中文版,StackEdit汉化版,StackEdit,在线Markdown,笔记利器,Markdown笔记"> <meta name="keywords" content="Markdown编辑器,StackEdit中文版,StackEdit汉化版,StackEdit,在线Markdown,笔记利器,Markdown笔记">
<meta name="description" <meta name="description" content="支持直接将码云Gitee、GitHub、Gitea等仓库作为笔记存储仓库且支持拖拽/粘贴上传图片并且可以直接在页面编辑同步和管理的Markdown编辑器。">
content="支持直接将码云Gitee、GitHub、Gitea等仓库作为笔记存储仓库且支持拖拽/粘贴上传图片并且可以直接在页面编辑同步和管理的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="baidu-site-verification" content="code-tGpn2BT069" /> <meta name="baidu-site-verification" content="code-tGpn2BT069" />
<meta name="msvalidate.01" content="90A9558158543277BD284CFA054E7F5B" /> <meta name="msvalidate.01" content="90A9558158543277BD284CFA054E7F5B" />
@ -134,16 +133,9 @@
background-color: #2c2c2c; background-color: #2c2c2c;
position: fixed; position: fixed;
padding: 5px; padding: 5px;
text-align: center;
width: 100%; width: 100%;
z-index: 1; z-index: 1;
display: flex;
justify-content: space-between;
align-items: center;
}
.navigation-bar__write {
text-align: center;
flex-grow: 1;
} }
.navigation-bar__button { .navigation-bar__button {
@ -157,49 +149,6 @@
background-color: rgba(255, 255, 255, 0.1); background-color: rgba(255, 255, 255, 0.1);
} }
/* 语言切换样式 */
.navigation-bar__lang-switcher {
display: flex;
align-items: center;
margin-left: auto;
font-size: 14px;
}
/* 隐藏下拉内容区域 */
.navigation-bar__dropdown-content {
display: none;
position: absolute;
z-index: 1;
}
/* 当鼠标悬停在按钮上时,显示下拉内容区域 */
.navigation-bar__dropdown:hover .navigation-bar__dropdown-content {
display: block;
}
/* 样式化下拉按钮 */
.navigation-bar__dropbtn {
background-color: #4CAF50;
color: white;
padding: 10px 16px;
font-size: 16px;
border: none;
cursor: pointer;
}
/* 样式化下拉选项 */
.navigation-bar__dropdown-content a {
color: black;
background-color: #f1f1f1;
padding: 12px 16px;
text-decoration: none;
display: block;
}
/* 当鼠标悬停在下拉选项上时,更改其背景颜色 */
.navigation-bar__dropdown-content a:hover {
background-color: #b2b2b2;
}
.splash-screen { .splash-screen {
position: relative; position: relative;
width: 100%; width: 100%;
@ -282,7 +231,6 @@
color: #fff; color: #fff;
} }
.social .icon { .social .icon {
height: 30px; height: 30px;
width: 30px; width: 30px;
@ -311,7 +259,9 @@
</style> </style>
<script> <script>
function scrollTo(selector) { function scrollTo(selector) {
$('html,body').animate({scrollTop: $(selector).offset().top}, 500); $('html,body').animate({
scrollTop: $(selector).offset().top
}, 500);
} }
</script> </script>
</head> </head>
@ -319,46 +269,19 @@
<body> <body>
<div class="landing"> <div class="landing">
<div class="navigation-bar"> <div class="navigation-bar">
<div class="navigation-bar__write"> <a class="navigation-bar__button button" href="app" title="The app">
<a class="navigation-bar__button button" href="app" title="The app"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="icon"><path d="M 16.8363,2.73375C 16.45,2.73375 16.0688,2.88125 15.7712,3.17375L 13.6525,5.2925L 18.955,10.5962L 21.0737,8.47625C 21.665,7.89 21.665,6.94375 21.0737,6.3575L 17.895,3.17375C 17.6025,2.88125 17.2163,2.73375 16.8363,2.73375 Z M 12.9437,6.00125L 4.84375,14.1062L 7.4025,14.39L 7.57875,16.675L 9.85875,16.85L 10.1462,19.4088L 18.2475,11.3038M 4.2475,15.0437L 2.515,21.7337L 9.19875,19.9412L 8.955,17.7838L 6.645,17.6075L 6.465,15.2925"></path></svg> 开始写作
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="icon"><path d="M 16.8363,2.73375C 16.45,2.73375 16.0688,2.88125 15.7712,3.17375L 13.6525,5.2925L 18.955,10.5962L 21.0737,8.47625C 21.665,7.89 21.665,6.94375 21.0737,6.3575L 17.895,3.17375C 17.6025,2.88125 17.2163,2.73375 16.8363,2.73375 Z M 12.9437,6.00125L 4.84375,14.1062L 7.4025,14.39L 7.57875,16.675L 9.85875,16.85L 10.1462,19.4088L 18.2475,11.3038M 4.2475,15.0437L 2.515,21.7337L 9.19875,19.9412L 8.955,17.7838L 6.645,17.6075L 6.465,15.2925"></path></svg> </a>
开始写作
</a>
</div>
<div class="navigation-bar__lang-switcher">
<div class="navigation-bar__dropdown">
<button class="navigation-bar__dropbtn">Language</button>
<div class="navigation-bar__dropdown-content">
<a href="/">简体中文</a>
<a href="https://stackedit.net" target="_blank">English</a>
</div>
</div>
</div>
</div> </div>
<div class="splash-screen"> <div class="splash-screen">
<div class="splash-screen__logo"> <div class="splash-screen__logo">
<div class="splash-screen__subtitle"> <div class="splash-screen__subtitle">
浏览器内 Markdown 笔记利器 浏览器内 Markdown 笔记利器
<div class="social">
<a href="https://jq.qq.com/?_wv=1027&k=wUSCNqmN" target="_blank" title="QQ交流群">
<svg t="1665396466500" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3543" width="32" height="32">
<path d="M512 0C229.12 0 0 229.12 0 512c0 282.88 229.12 512 512 512s512-229.12 512-512C1024 229.12 794.88 0 512 0zM782.08 670.72c-11.52 6.4-30.72-7.68-48.64-34.56-6.4 28.16-24.32 53.76-48.64 74.24 25.6 8.96 42.24 25.6 42.24 42.24 0 29.44-46.08 52.48-102.4 52.48-51.2 0-93.44-19.2-101.12-43.52-2.56 0-10.24 0-12.8 0-7.68 24.32-49.92 43.52-101.12 43.52-56.32 0-102.4-23.04-102.4-52.48 0-17.92 16.64-33.28 42.24-42.24-24.32-20.48-42.24-46.08-48.64-74.24-17.92 25.6-37.12 39.68-48.64 34.56-17.92-8.96-14.08-57.6 7.68-107.52 16.64-39.68 39.68-69.12 57.6-75.52 0-2.56 0-5.12 0-7.68 0-15.36 3.84-29.44 11.52-40.96 0-1.28 0-1.28 0-2.56 0-7.68 1.28-14.08 5.12-19.2C340.48 312.32 408.32 230.4 518.4 230.4c110.08 0 177.92 81.92 183.04 185.6 2.56 5.12 5.12 12.8 5.12 19.2 0 1.28 0 1.28 0 2.56 7.68 11.52 11.52 25.6 11.52 40.96 0 2.56 0 5.12 0 7.68 17.92 6.4 40.96 35.84 57.6 75.52C796.16 613.12 800 661.76 782.08 670.72z" p-id="3544"></path>
</svg>
</a>
<a href="https://gitee.com/mafgwo/stackedit" target="_blank" title="码云开源仓库">
<svg t="1652950823759" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2991" width="32" height="32" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs><style type="text/css"></style></defs>
<path d="M512 1024C229.222 1024 0 794.778 0 512S229.222 0 512 0s512 229.222 512 512-229.222 512-512 512z m259.149-568.883h-290.74a25.293 25.293 0 0 0-25.292 25.293l-0.026 63.206c0 13.952 11.315 25.293 25.267 25.293h177.024c13.978 0 25.293 11.315 25.293 25.267v12.646a75.853 75.853 0 0 1-75.853 75.853h-240.23a25.293 25.293 0 0 1-25.267-25.293V417.203a75.853 75.853 0 0 1 75.827-75.853h353.946a25.293 25.293 0 0 0 25.267-25.292l0.077-63.207a25.293 25.293 0 0 0-25.268-25.293H417.152a189.62 189.62 0 0 0-189.62 189.645V771.15c0 13.977 11.316 25.293 25.294 25.293h372.94a170.65 170.65 0 0 0 170.65-170.65V480.384a25.293 25.293 0 0 0-25.293-25.267z" fill="#C71D23" p-id="2992"></path>
</svg>
</a>
</div>
</div> </div>
</div> </div>
<div class="splash-screen__footer"> <div class="splash-screen__footer">
<a class="button" href="javascript:scrollTo($('.anchor'))"> <a class="button" href="javascript:scrollTo($('.anchor'))">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="icon"><path d="M 11,4L 13,4L 13,16.0104L 18.5052,10.5052L 19.9194,11.9194L 12,19.8388L 4.08058,11.9194L 5.49479,10.5052L 11,16.0104L 11,4 Z "/></path></svg> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="icon"><path d="M 11,4L 13,4L 13,16.0104L 18.5052,10.5052L 19.9194,11.9194L 12,19.8388L 4.08058,11.9194L 5.49479,10.5052L 11,16.0104L 11,4 Z "/></path></svg> 阅读更多
阅读更多
</a> </a>
</div> </div>
</div> </div>
@ -493,31 +416,24 @@
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="column"> <div class="column">
<div class="image" style="width: 250px"> <div class="image" style="width: 250px">
<img width="230" src="static/landing/twemoji.png"> <img width="230" src="static/landing/twemoji.png">
</div>
</div> </div>
</div> <div class="column">
<div class="column"> <div class="feature">
<div class="feature"> <h3>Emojis表情</h3>
<h3>Emojis表情</h3> <p>StackEdit中文版 支持使用 Markdown 表情符号标记在文件中插入表情符号。</p>
<p>StackEdit中文版 支持使用 Markdown 表情符号标记在文件中插入表情符号。</p> </div>
</div> </div>
</div>
</div> </div>
</div> </div>
<div class="landing__footer"> <div class="landing__footer">
<div class="social"> <a href="app" title="Markdown编辑器">Markdown编辑器</a> <a href="https://gitee.com/mafgwo" target="_blank" title="豆萁">代码作者</a><br> Copyright&nbsp;&copy;
<a href="https://gitee.com/mafgwo/stackedit" target="_blank"> <script>
<svg t="1652950823759" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2991" width="32" height="32" xmlns:xlink="http://www.w3.org/1999/xlink"> document.write((new Date()).getFullYear())
<defs><style type="text/css"></style></defs> </script>&nbsp;<a href="https://jonylee.top">JonyLee</a>&nbsp;&amp;&nbsp;<a href="https://beian.miit.gov.cn/" target="_blank">沪ICP备2023021298号-1</a><br> 开源许可
<path d="M512 1024C229.222 1024 0 794.778 0 512S229.222 0 512 0s512 229.222 512 512-229.222 512-512 512z m259.149-568.883h-290.74a25.293 25.293 0 0 0-25.292 25.293l-0.026 63.206c0 13.952 11.315 25.293 25.267 25.293h177.024c13.978 0 25.293 11.315 25.293 25.267v12.646a75.853 75.853 0 0 1-75.853 75.853h-240.23a25.293 25.293 0 0 1-25.267-25.293V417.203a75.853 75.853 0 0 1 75.827-75.853h353.946a25.293 25.293 0 0 0 25.267-25.292l0.077-63.207a25.293 25.293 0 0 0-25.268-25.293H417.152a189.62 189.62 0 0 0-189.62 189.645V771.15c0 13.977 11.316 25.293 25.294 25.293h372.94a170.65 170.65 0 0 0 170.65-170.65V480.384a25.293 25.293 0 0 0-25.293-25.267z" fill="#C71D23" p-id="2992"></path>
</svg>
</a>
</div>
<a href="app" title="The app">The app</a> <a href="https://gitee.com/mafgwo/stackedit/issues" target="_blank" title="The app">Community</a><br>
© 2022 <a href="https://gitee.com/mafgwo" target="_blank">豆萁</a> <a href="https://beian.miit.gov.cn/" target="_blank">粤ICP备18096694号</a><br>
Licensed under an
<a target="_blank" href="http://www.apache.org/licenses/LICENSE-2.0">Apache License</a> <a target="_blank" href="http://www.apache.org/licenses/LICENSE-2.0">Apache License</a>
<a href="privacy_policy.html" target="_blank">隐私策略</a> <a href="privacy_policy.html" target="_blank">隐私策略</a>
</div> </div>
@ -525,15 +441,17 @@
<script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script> <script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
<!-- built files will be auto injected --> <!-- built files will be auto injected -->
<!-- baidu统计-->
<script> <script>
var _hmt = _hmt || []; 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>
<!-- baidu统计结束-->
</body> </body>
</html> </html>

View File

@ -1,90 +1,90 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head>
<meta charset="utf-8" />
<meta name="format-detection" content="telephone=no" />
<meta name="msapplication-tap-highlight" content="no" />
<meta http-equiv="content-type" content="text/html;charset=utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1" />
<meta name="renderer" content="webkit" />
<meta name="viewport" id="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width" />
<title> 隐私权政策</title>
<style>
html {
font-size: 14px;
color: #666;
}
* { <head>
margin: 0; <meta charset="utf-8" />
padding: 0; <meta name="format-detection" content="telephone=no" />
} <meta name="msapplication-tap-highlight" content="no" />
<meta http-equiv="content-type" content="text/html;charset=utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1" />
<meta name="renderer" content="webkit" />
<meta name="viewport" id="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width" />
<title> 隐私权政策</title>
<style>
html {
font-size: 14px;
color: #666;
}
.title { * {
padding: 10px; margin: 0;
font-size: 16px; padding: 0;
color: #333; }
text-align: center;
}
.date-text { .title {
text-align: right; padding: 10px;
padding-right: 20px; font-size: 16px;
color: #333; color: #333;
padding-bottom: 6px; text-align: center;
} }
body { .date-text {
padding: 10px 10px 20px; text-align: right;
} padding-right: 20px;
color: #333;
padding-bottom: 6px;
}
.main-text { body {
padding: 10px 0; padding: 10px 10px 20px;
text-indent: 20px; }
color: #333;
font-size: 16px; .main-text {
} padding: 10px 0;
text-indent: 20px;
color: #333;
font-size: 16px;
}
.content {
text-indent: 20px;
line-height: 22px;
}
</style>
</head>
<body>
<h3 class="title"> 隐私权政策 </h3>
<div class="content">
【Markdown编辑器】以下简称“本站”深知个人信息对您的重要性 故不会特意收集个人信息。
</div>
<div class="content">
请在使用本产品(或服务)前,仔细阅读并了解本《隐私权政策》。
</div>
<div class="main-text">
一、关于您的文件信息
</div>
<div class="content">
个人文档都是存储在第三方,本站对所有第三方的文件都是在您授权之后,通过您的浏览器直接访问,并不会在本站后端获取和保存您的任何个人文件信息。
</div>
<div class="main-text">
二、关于您的用户信息
</div>
<div class="content">
本站不存在注册行为,待您授权后,您在第三方平台上个人信息的获取仅仅是在您的浏览器中直接获取,并不会在本站后端获取和保存您的个人信息。
</div>
<div class="main-text">
三、其他
</div>
<div class="content">
本站可能会更新本隐私政策,以反映本站的业务需求和适用法律的变化。在更新隐私政策时,我会通过此网站或其他合适的方式通知您。如果您继续使用本站的服务,则意味着您同意更新后的隐私政策。
</div>
<div class="main-text">
四、如何联系我
</div>
<div class="content">
如果您对本隐私政策有任何疑问、意见或建议,可通过<a href="https://jonylee.top" target="_blank" title="个人主页-JonyLee的设计导航">个人主页</a>联系方式与我联系:
</div>
</body>
.content {
text-indent: 20px;
line-height: 22px;
}
</style>
</head>
<body>
<h3 class="title"> 隐私权政策 </h3>
<div class="content">
【StackEdit中文版】以下简称“此站”深知个人信息对您的重要性 故不会特意收集个人信息。
</div>
<div class="content">
请在使用我们的产品(或服务)前,仔细阅读并了解本《隐私权政策》。
</div>
<div class="main-text">
一、我们不会收集个人文档信息
</div>
<div class="content">
个人文档都是存储在第三方此站仅仅是通过前端缓存的token来获取或保存文档到第三方并不会在本站后端保存用户的任何文档信息。
</div>
<div class="main-text">
二、我们不会收集个人第三方的Token或用户信息
</div>
<div class="content">
本站不存在注册行为不会收集个人信息但是为了正常从第三方把Markdown文档读取或写入有时需要借助本站后端获取第三方网站Token信息再返回给前端但是本站承诺绝不会收集个人第三方网站的Token信息只会在后端获取到之后返回给本站前端供前端使用。
</div>
<div class="main-text">
三、如何联系我们
</div>
<div class="content">
如果您对本隐私政策有任何疑问、意见或建议,通过以下方式与我们联系:
</div>
<div class="content">
邮箱【mafgwo@163.com】
</div>
<div class="content">
微信【qicoding】
</div>
<div class="content">
一般情况下,我们将在一周内回复。
</div>
</body>
</html> </html>

View File

@ -1,164 +1,181 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>文章分享 - StackEdit中文版</title> <title>文章分享 - Markdown编辑器</title>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="canonical" href="https://stackedit.cn/"> <link rel="canonical" href="https://md.jonylee.top/">
<link rel="icon" href="static/landing/favicon.ico" type="image/x-icon"> <link rel="icon" href="static/landing/favicon.ico" type="image/x-icon">
<link rel="shortcut icon" href="static/landing/favicon.ico" type="image/x-icon"> <link rel="shortcut icon" href="static/landing/favicon.ico" type="image/x-icon">
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="keywords" content="Markdown编辑器,StackEdit中文版,StackEdit汉化版,StackEdit,在线Markdown,笔记利器,Markdown笔记"> <meta name="keywords" content="Markdown编辑器,StackEdit中文版,StackEdit汉化版,StackEdit,在线Markdown,笔记利器,Markdown笔记">
<meta name="description" <meta name="description" content="支持直接将码云Gitee、GitHub、Gitea等仓库作为笔记存储仓库且支持拖拽/粘贴上传图片并且可以直接在页面编辑同步和管理的Markdown编辑器。">
content="支持直接将码云Gitee、GitHub、Gitea等仓库作为笔记存储仓库且支持拖拽/粘贴上传图片并且可以直接在页面编辑同步和管理的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="baidu-site-verification" content="code-tGpn2BT069" /> <meta name="baidu-site-verification" content="code-tGpn2BT069" />
<meta name="msvalidate.01" content="90A9558158543277BD284CFA054E7F5B" /> <meta name="msvalidate.01" content="90A9558158543277BD284CFA054E7F5B" />
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<style> <style>
.share-header { .share-header {
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
width: 100%; width: 100%;
background-color: #383c4a; background-color: #383c4a;
color: #fff; color: #fff;
padding: 10px; padding: 10px;
box-sizing: border-box; box-sizing: border-box;
z-index: 99999; z-index: 99999;
} }
.share-header .logo { .share-header .logo {
margin: 0 0 -8px 0; margin: 0 0 -8px 0;
} }
.share-header nav { .share-header nav {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
} }
.share-header nav ul { .share-header nav ul {
list-style-type: none; list-style-type: none;
margin: 0; margin: 0;
padding: 0; padding: 0;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
} }
.share-header nav li { .share-header nav li {
margin: 0 10px; margin: 0 10px;
} }
.share-header nav a { .share-header nav a {
color: #fff; color: #fff;
text-decoration: none; text-decoration: none;
} }
.share-header nav a:hover { .share-header nav a:hover {
text-decoration: underline; text-decoration: underline;
} }
.share-content { .share-content {
padding-top: 50px; transform: translateY(50px);
} height: 100vh;
</style> }
</style>
<script type="text/javascript"> <script type="text/javascript">
function getQueryString(name) { function getQueryString(name) {
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i'); var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
var r = window.location.search.substr(1).match(reg); var r = window.location.search.substr(1).match(reg);
if (r != null) { if (r != null) {
return unescape(r[2]); return unescape(r[2]);
}
return null;
} }
return null;
}
function appendTagHtml(newdoc, tagName, targetParentEle) { function appendTagHtml(newdoc, tagName, targetParentEle) {
const tags = newdoc.getElementsByTagName(tagName); const tags = newdoc.getElementsByTagName(tagName);
if (!tags) { if (!tags) {
return; return;
} }
for (let i = 0; i < tags.length; i++) { for (let i = 0; i < tags.length; i++) {
targetParentEle.append(tags[i]); targetParentEle.append(tags[i]);
}
}
window.onload = function() {
const xhr = new XMLHttpRequest();
const gistId = getQueryString('id');
let accessToken = null;
const tokens = window.localStorage.getItem('data/tokens');
if (tokens) {
const tokensObj = JSON.parse(tokens);
if (tokensObj.data && tokensObj.data.gitee) {
const tokenArr = Object.keys(tokensObj.data.gitee).map(it => tokensObj.data.gitee[it]).filter(it => it && it.isLogin);
if (tokenArr.length > 0) {
accessToken = tokenArr[0].accessToken;
}
} }
} }
let url = `https://gitee.com/api/v5/gists/${gistId}`;
if (accessToken) { window.onload = function() {
url = `${url}?access_token=${accessToken}`; const xhr = new XMLHttpRequest();
} const gistId = getQueryString('id');
xhr.open('GET', url); let accessToken = null;
xhr.onload = function() { const tokens = window.localStorage.getItem('data/tokens');
if (xhr.status === 200) { if (tokens) {
const newdoc = document.implementation.createHTMLDocument(""); const tokensObj = JSON.parse(tokens);
const body = JSON.parse(xhr.responseText); if (tokensObj.data && tokensObj.data.gitee) {
for (let key in body.files) { const tokenArr = Object.keys(tokensObj.data.gitee).map(it => tokensObj.data.gitee[it]).filter(it => it && it.isLogin);
newdoc.documentElement.innerHTML = body.files[key].content; if (tokenArr.length > 0) {
accessToken = tokenArr[0].accessToken;
}
} }
const currHead = document.head;
// 头部
appendTagHtml(newdoc, 'style', currHead);
// title
document.title = newdoc.title + ' - StackEdit中文版';
// 内容
const shareContent = document.getElementsByClassName('share-content')[0];
shareContent.innerHTML = newdoc.body.innerHTML;
document.body.className = newdoc.body.className;
} else if (xhr.status === 403) {
const rateLimit = xhr.responseText && xhr.responseText.indexOf('Rate Limit') >= 0;
const appUri = `${window.location.protocol}//${window.location.host}/app`;
document.getElementById('div_info').innerHTML = `${rateLimit ? "请求太过频繁" : "无权限访问"},请登录 <a href="${appUri}" target="_brank">主文档空间</a> 后再刷新此页面!`;
} else {
console.error('An error occurred: ' + xhr.status);
document.getElementById('div_info').innerHTML = '分享内容获取失败或已失效!';
} }
}; const url = `https://gitee.com/api/v5/gists/${gistId}`;
xhr.send(); let urlWithToken = url;
} let withToken = false;
</script> if (accessToken) {
urlWithToken = `${url}?access_token=${accessToken}`;
withToken = true;
}
xhr.open('GET', urlWithToken);
xhr.onload = function() {
if (xhr.status === 200) {
const newdoc = document.implementation.createHTMLDocument("");
const body = JSON.parse(xhr.responseText);
for (let key in body.files) {
newdoc.documentElement.innerHTML = body.files[key].content;
}
const currHead = document.head;
// 头部
appendTagHtml(newdoc, 'style', currHead);
// title
document.title = newdoc.title + ' - StackEdit中文版';
// 内容
const shareContent = document.getElementsByClassName('share-content')[0];
shareContent.innerHTML = newdoc.body.innerHTML;
document.body.className = newdoc.body.className;
} else if (xhr.status === 403) {
const rateLimit = xhr.responseText && xhr.responseText.indexOf('Rate Limit') >= 0;
const appUri = `${window.location.protocol}//${window.location.host}/app`;
document.getElementById('div_info').innerHTML = `${rateLimit ? "请求太过频繁" : "无权限访问"},请登录 <a href="${appUri}" target="_brank">主文档空间</a> 后再刷新此页面!`;
} else if (xhr.status === 401) {
if (withToken) {
withToken = false;
xhr.open('GET', url);
xhr.send();
} else {
console.error('An error occurred: ' + xhr.status);
document.getElementById('div_info').innerHTML = `分享内容获取失败或已失效!请登录 <a href="${appUri}" target="_brank">主文档空间</a> 后再刷新此页面!`;
}
} else {
console.error('An error occurred: ' + xhr.status);
document.getElementById('div_info').innerHTML = `分享内容获取失败或已失效!请登录 <a href="${appUri}" target="_brank">主文档空间</a> 后再刷新此页面!`;
}
};
xhr.send();
}
</script>
</head> </head>
<body> <body>
<div class="share-header"> <div class="share-header">
<nav> <nav>
<a class="logo" href="https://stackedit.cn" target="_blank"> <a class="logo" href="https://md.jonylee.top" target="_blank">
<img src="static/landing/logo.svg" height="30px"/> <img src="static/landing/logo.svg" height="30px" />
</a> </a>
<ul> <ul>
<li><a href="https://stackedit.cn" target="_blank">首页</a></li> <li><a href="https://md.jonylee.top" target="_blank">首页</a></li>
<li><a href="https://stackedit.cn/app" target="_blank">写笔记</a></li> <li><a href="https://md.jonylee.top/app" target="_blank">写笔记</a></li>
</ul> </ul>
</nav> </nav>
</div> </div>
<div class="share-content stackedit"> <div class="share-content stackedit">
<div id="div_info" style="text-align: center; height: 600px;">文章加载中......</div> <div id="div_info" style="text-align: center; height: 600px;">文章加载中......</div>
</div> </div>
<script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script> <script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
<!-- built files will be auto injected --> <!-- built files will be auto injected -->
<!-- baidu统计-->
<script> <script>
var _hmt = _hmt || []; 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>
<!-- baidu统计结束-->
</body> </body>
</html> </html>

View File

@ -1,12 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url> <url>
<loc>https://stackedit.cn/</loc> <loc>https://md.jonylee.top/</loc>
<changefreq>weekly</changefreq> <changefreq>weekly</changefreq>
<priority>1.0</priority> <priority>1.0</priority>
</url> </url>
<url> <url>
<loc>https://stackedit.cn/app</loc> <loc>https://md.jonylee.top/app</loc>
<changefreq>weekly</changefreq> <changefreq>weekly</changefreq>
<priority>1.0</priority> <priority>1.0</priority>
</url> </url>
@ -16,7 +16,7 @@
<priority>0.8</priority> <priority>0.8</priority>
</url> </url>
<url> <url>
<loc>https://stackedit.cn/privacy_policy.html</loc> <loc>https://md.jonylee.top/privacy_policy.html</loc>
<changefreq>monthly</changefreq> <changefreq>monthly</changefreq>
<priority>0.6</priority> <priority>0.6</priority>
</url> </url>

View File

@ -36,7 +36,7 @@ style.innerHTML = "/** activeblue 灵动蓝\n \
top: 0;\n \ top: 0;\n \
width: 60px;\n \ width: 60px;\n \
height: 60px;\n \ height: 60px;\n \
background: url(https://my-wechat.mdnice.com/ape_blue.svg);\n \ background: url(https://imgs.qicoder.com/stackedit/ape_blue.svg);\n \
background-size: 100% 100%;\n \ background-size: 100% 100%;\n \
opacity: .12;\n \ opacity: .12;\n \
}\n \ }\n \

View File

@ -100,7 +100,7 @@ style.innerHTML = "/* 草原绿 caoyuangreen\n \
width:30px;\n \ width:30px;\n \
height:30px;\n \ height:30px;\n \
display:block;\n \ display:block;\n \
background-image:url(https://files.mdnice.com/grass-green.png);\n \ background-image:url(https://imgs.qicoder.com/stackedit/grass-green.png);\n \
background-position:center;\n \ background-position:center;\n \
background-size:30px;\n \ background-size:30px;\n \
margin:auto;\n \ margin:auto;\n \