Compare commits

...

207 Commits

Author SHA1 Message Date
7a82ba2250 更新:README文件 2024-09-09 15:14:26 +08:00
445cfe69fc 更新:README文档 2024-09-09 14:50:22 +08:00
40e77fef15 更新gitee分享文件位置链接 2024-08-05 10:23:12 +08:00
348b5668fd 更新git后缀链接 2024-08-05 10:03:03 +08:00
60c751f340 更新站点icon图标 2024-08-04 17:36:02 +08:00
024706f20f 更新隐私政策 2024-08-04 17:35:16 +08:00
408618eceb 更新站点链接 2024-08-04 17:34:56 +08:00
9a7570b02f 更新首页信息 2024-08-04 17:34:01 +08:00
182c863eef 更新自述 2024-08-04 17:32:02 +08:00
xiaoqi.cxq
d422df5e42 去掉英文版入口 2024-03-22 16:14:05 +08:00
xiaoqi.cxq
91215e6db9 截断字符长度从25万改成1000万 2024-01-04 08:24:26 +08:00
xiaoqi.cxq
1286c42d4c 支持复制路径 2023-12-25 11:26:15 +08:00
xiaoqi.cxq
3467a6ee09 github作为主空间时的bug修复 2023-12-20 11:21:01 +08:00
xiaoqi.cxq
b4f4c71f85 快捷键会失效的问题修复 2023-10-20 13:50:33 +08:00
xiaoqi.cxq
39167fb193 主文档空间支持GitHub登录 2023-10-19 18:20:34 +08:00
xiaoqi.cxq
97b8d3c288 优化导出中的header生成 2023-10-18 10:09:46 +08:00
xiaoqi.cxq
b4c9407b06 优化预览header生成 2023-10-18 09:40:43 +08:00
xiaoqi.cxq
96ea8cd0db 避免undefined文件提交 2023-10-18 09:09:27 +08:00
xiaoqi.cxq
550bb2fd91 分享页面请求逻辑优化 2023-09-19 09:29:28 +08:00
xiaoqi.cxq
2092045b7f gitea publish bugfix 2023-09-08 14:40:22 +08:00
xiaoqi.cxq
12e4befa96 GitLab授权调整 2023-08-26 01:15:54 +08:00
xiaoqi.cxq
80e0e3bc99 修改chatgpt的api代理地址 2023-08-26 01:11:16 +08:00
xiaoqi.cxq
4747f91749 更改chatgpt接口地址 2023-08-08 13:25:43 +08:00
xiaoqi.cxq
e04fd5a911 chatgpt优化 2023-07-01 19:40:16 +08:00
xiaoqi.cxq
9cd27e274e 导出标题标签优化 2023-06-30 11:29:41 +08:00
xiaoqi.cxq
81cad7ee84 路径支持反斜杠 2023-06-30 11:28:26 +08:00
xiaoqi.cxq
81612deab7 标题锚点可以是数字开头 2023-06-30 11:28:00 +08:00
xiaoqi.cxq
282b546edc 修复h标签渲染包含图片锚点错误并图片渲染不了的问题 2023-06-30 09:40:52 +08:00
xiaoqi.cxq
1727be1eaf 增加Docker构建的脚本 2023-06-29 22:25:01 +08:00
xiaoqi.cxq
57931b9db2 渲染问题bugfix 2023-06-29 22:24:07 +08:00
xiaoqi.cxq
d175557ab9 图片加载bugfix 2023-06-29 21:49:16 +08:00
xiaoqi.cxq
8e12eaebd2 替换ChatGPT接口为其他免费接口 2023-06-29 21:49:02 +08:00
xiaoqi.cxq
90d887519d chatgpt调整 2023-06-13 18:32:49 +08:00
xiaoqi.cxq
c1232b59db chatgpt优化 2023-05-23 10:44:26 +08:00
xiaoqi.cxq
a40af9c545 更新隐私策略 2023-05-12 15:19:36 +08:00
xiaoqi.cxq
f3d827fef1 update google api scope 2023-05-09 11:37:12 +08:00
xiaoqi.cxq
92f2c4dee6 latex \$ bugfix 2023-04-11 11:28:09 +08:00
xiaoqi.cxq
74f25af839 删除非必要代码 2023-04-11 09:58:25 +08:00
xiaoqi.cxq
20d7a9d2db ChatGPT辅助写作窗口控制台报错bugfix 2023-04-11 09:30:15 +08:00
xiaoqi.cxq
64d493d692 优化文案 2023-04-10 19:57:33 +08:00
xiaoqi.cxq
eda517cd61 bugfix 2023-04-10 19:57:11 +08:00
xiaoqi.cxq
24635c54ed update readme 2023-04-10 14:53:39 +08:00
xiaoqi.cxq
87c37401ed update readme 2023-04-10 12:59:26 +08:00
xiaoqi.cxq
599d71b597 支持ChatGPT生成内容 2023-04-10 10:24:22 +08:00
xiaoqi.cxq
0e02822add 优化share 2023-04-09 21:35:55 +08:00
xiaoqi.cxq
1daa5afe39 update styles 2023-04-06 14:00:15 +08:00
xiaoqi.cxq
2e9e4b73f6 homepage update 2023-03-31 09:39:24 +08:00
xiaoqi.cxq
4243a41e31 update share.html 2023-03-30 18:21:22 +08:00
xiaoqi.cxq
ae828cfb56 支持分享功能 2023-03-30 15:56:24 +08:00
xiaoqi.cxq
58c9144612 更新文案 2023-02-27 08:29:31 +08:00
xiaoqi.cxq
1b8124f2a2 导出HTML、PDF支持带预览主题导出 2023-02-26 11:29:25 +08:00
xiaoqi.cxq
8713688b57 update readme 2023-02-24 09:37:28 +08:00
xiaoqi.cxq
e65c433f13 update readme 2023-02-24 09:22:50 +08:00
xiaoqi.cxq
b1691e0d4f Gitlab支持优化 2023-02-23 15:33:55 +08:00
xiaoqi.cxq
4d8ff0ea0c 代码块不拼写检查 2022-12-08 23:08:55 +08:00
xiaoqi.cxq
d757b48d99 update readme 2022-12-05 09:10:24 +08:00
xiaoqi.cxq
d927099b28 支持预览区主题 2022-12-04 21:40:54 +08:00
xiaoqi.cxq
9ebde2eb75 调整a标签跳转 2022-11-25 20:29:01 +08:00
xiaoqi.cxq
bda261a767 优化pdf导出 2022-11-25 20:12:51 +08:00
xiaoqi.cxq
9419865d76 导出pdf文案调整 2022-11-24 16:49:41 +08:00
xiaoqi.cxq
be9323c408 编辑器头部按钮显示与否支持配置 2022-11-24 09:06:07 +08:00
xiaoqi.cxq
a756acf27c update readme 2022-11-20 18:02:31 +08:00
xiaoqi.cxq
e731016e04 update version 2022-11-20 15:18:28 +08:00
xiaoqi.cxq
6cca063f8c update readme 2022-11-20 15:14:51 +08:00
xiaoqi.cxq
13b9528840 支持笔记之间双链 2022-11-20 15:11:31 +08:00
xiaoqi.cxq
31bec53520 校验在线的js改成外部的 2022-11-17 20:19:00 +08:00
xiaoqi.cxq
4e9acad585 当前文档空间上传图片优化 2022-11-13 15:16:46 +08:00
xiaoqi.cxq
5eb2b2e67a 更新判断在线与否的外部js 2022-11-12 17:17:07 +08:00
xiaoqi.cxq
2b45a94879 update readme 2022-11-12 11:51:50 +08:00
xiaoqi.cxq
5a30338e83 路径替换bugfix 2022-11-12 10:02:48 +08:00
xiaoqi.cxq
808891e47c 当前文档空间路径上传图片绝对路径计算bugfix,支持数学表达式输入快捷键 2022-11-11 17:01:41 +08:00
xiaoqi.cxq
d3193e1739 文案调整 2022-11-05 14:15:06 +08:00
xiaoqi.cxq
df91db5882 当前文档空间图片路径优化 2022-11-05 12:37:03 +08:00
xiaoqi.cxq
26e8979245 update readme 2022-10-31 12:57:12 +08:00
xiaoqi.cxq
dd78ec7b3a 存储图片路径为相对路径时显示的bugfix 2022-10-29 16:37:26 +08:00
xiaoqi.cxq
401c2787af update readme 2022-10-29 15:48:34 +08:00
xiaoqi.cxq
a4ab4b2da1 图片支持相对本地空间的路径存储 2022-10-29 15:46:57 +08:00
xiaoqi.cxq
e7450df251 粘贴拖拽图片体验优化 2022-10-22 15:43:07 +08:00
xiaoqi.cxq
058fcaa147 update katex version 2022-10-21 11:23:36 +08:00
xiaoqi.cxq
ed79c8cd49 解决文档空间链接复制授权登录无法进入的bug 2022-10-13 13:53:06 +08:00
xiaoqi.cxq
867315a19d 增加冲突自动合并提醒 2022-10-13 13:22:42 +08:00
xiaoqi.cxq
480875a5ec 调整首页 2022-10-10 18:11:25 +08:00
xiaoqi.cxq
440c5e93b8 update readme 2022-10-10 00:34:49 +08:00
xiaoqi.cxq
405e082651 update readme 2022-10-10 00:33:17 +08:00
xiaoqi.cxq
7335455185 update readme 2022-10-10 00:27:55 +08:00
xiaoqi.cxq
7da611b398 update readme 2022-10-10 00:14:33 +08:00
xiaoqi.cxq
554547af5a 编辑区域右上角图标支持隐藏 2022-10-09 18:41:51 +08:00
xiaoqi.cxq
380980d66f update gitea应用创建说明 2022-10-09 10:13:44 +08:00
xiaoqi.cxq
68f281c6e7 bugfix 2022-10-07 15:25:56 +08:00
xiaoqi.cxq
545f8da3cb 支持编辑区自定义主题/添加编辑区主题 2022-10-07 15:17:06 +08:00
xiaoqi.cxq
ee9bd1ab5a 编辑主题缓存时间调整为一天 2022-10-06 04:23:34 +08:00
xiaoqi.cxq
e7fa160383 主文档空间文档历史revision显示bugfix 2022-10-06 04:10:25 +08:00
xiaoqi.cxq
f71cef4d9f update readme 2022-10-06 03:49:25 +08:00
xiaoqi.cxq
347358f6bc 支持编辑区域主题选择 2022-10-06 03:45:51 +08:00
xiaoqi.cxq
8aff518e34 update readme 2022-10-02 00:24:01 +08:00
xiaoqi.cxq
21a3e59b5d gitea支持启动时指定clientId和接口地址等 2022-10-02 00:20:01 +08:00
xiaoqi.cxq
95d27a4a0a 文档版本显示提交信息 2022-09-23 23:18:15 +08:00
xiaoqi.cxq
b1ad58a121 update readme 2022-09-23 19:39:09 +08:00
xiaoqi.cxq
d51c19d6fd update version 2022-09-23 19:36:12 +08:00
xiaoqi.cxq
398784efc4 仓库支持关闭自动同步 2022-09-23 19:33:25 +08:00
xiaoqi.cxq
f020cb887b update readme 2022-09-10 20:54:39 +08:00
xiaoqi.cxq
6fa7992685 发布支持自定义提交信息 2022-09-10 19:48:28 +08:00
xiaoqi.cxq
a6493a41da 修复暗色主题下find高亮时光标看不清的问题 2022-09-08 00:12:47 +08:00
xiaoqi.cxq
f5b4627083 toc支持小写占位符 2022-09-04 18:28:25 +08:00
xiaoqi.cxq
fc74346b4b 支持目录TOC 2022-09-04 17:28:24 +08:00
xiaoqi.cxq
a957432749 update css 2022-08-24 21:22:32 +08:00
xiaoqi.cxq
d3abfe2fe6 update readme 2022-08-22 14:06:11 +08:00
xiaoqi.cxq
f073f00019 数学公式改为等宽字体 2022-08-22 12:38:04 +08:00
xiaoqi.cxq
06827fe3eb update doc 2022-08-22 10:26:37 +08:00
xiaoqi.cxq
e69474db36 update doc 2022-08-22 10:25:20 +08:00
xiaoqi.cxq
ed1a07ccac update doc 2022-08-22 10:23:17 +08:00
xiaoqi.cxq
3b66956763 update welcomeFile 2022-08-19 07:59:13 +08:00
xiaoqi.cxq
4158737843 update image 2022-08-18 19:39:21 +08:00
xiaoqi.cxq
fa1f207ca1 update image 2022-08-18 19:27:41 +08:00
xiaoqi.cxq
5bd3a259b7 update file search icon 2022-08-18 17:05:22 +08:00
xiaoqi.cxq
8fa5084a25 update readme 2022-08-18 13:45:48 +08:00
xiaoqi.cxq
cf9089b087 Github暗色主题图标调整 2022-08-18 13:27:43 +08:00
xiaoqi.cxq
04ee93237d 修复publish随MD文件移动循环请求的bug 2022-08-18 12:46:48 +08:00
xiaoqi.cxq
c6d5ddfe3d 搜索文件相关样式调整 2022-08-17 14:12:42 +08:00
xiaoqi.cxq
a85fe36105 searchfile徽章 2022-08-17 13:51:36 +08:00
xiaoqi.cxq
be3cb3c65f update input tag 2022-08-17 13:25:03 +08:00
xiaoqi.cxq
1d57f0365f update version 2022-08-17 13:23:54 +08:00
xiaoqi.cxq
3d61b71350 update version 2022-08-17 13:22:06 +08:00
xiaoqi.cxq
5f116222fd support search file 2022-08-17 13:15:03 +08:00
xiaoqi.cxq
1da8fbd02a update logo 2022-08-15 08:28:49 +08:00
xiaoqi.cxq
36a4832eff update readme 2022-08-12 17:28:55 +08:00
xiaoqi.cxq
80a49a3da5 update css 2022-08-12 00:22:22 +08:00
xiaoqi.cxq
b033b7372a css微调 2022-08-11 11:40:04 +08:00
xiaoqi.cxq
c99e46cb6a update image 2022-08-11 10:06:57 +08:00
xiaoqi.cxq
beeb026abe update readme 2022-08-11 10:04:00 +08:00
xiaoqi.cxq
13a584e400 update css 2022-08-11 07:12:21 +08:00
xiaoqi.cxq
51367f1ae5 更新样式 2022-08-09 09:21:30 +08:00
xiaoqi.cxq
08e81f39b0 更新样式 2022-08-09 09:02:08 +08:00
xiaoqi.cxq
9239541df8 update readme 2022-08-07 18:27:14 +08:00
xiaoqi.cxq
19d3a16034 切换主题bugfix 2022-08-07 11:23:57 +08:00
xiaoqi.cxq
73ead46873 update readme file 2022-08-07 11:09:13 +08:00
xiaoqi.cxq
73b23e1659 update version 2022-08-07 11:06:03 +08:00
xiaoqi.cxq
246f5b394e dark主题样式调整 2022-08-07 10:48:53 +08:00
xiaoqi.cxq
dc8b4677be get giteeClientId random时排除第一个 避免被随机到 2022-08-05 09:16:56 +08:00
xiaoqi.cxq
7cd0cee836 gitee clientId默认第一个出现401则随机一个 2022-08-04 17:12:38 +08:00
xiaoqi.cxq
bfb7a6447e month error bugfix 2022-08-04 16:34:04 +08:00
xiaoqi.cxq
59a867b8b9 update readme 2022-08-03 15:32:32 +08:00
xiaoqi.cxq
3c53903239 update readme 2022-07-31 02:52:26 +08:00
xiaoqi.cxq
1352ea0a9a 支持Github图床 2022-07-31 02:23:59 +08:00
xiaoqi.cxq
09416c75a4 添加gitea账号填入Gitea URL时提示文案调整 2022-07-27 17:32:20 +08:00
xiaoqi.cxq
492958702a gitea支持http 2022-07-26 17:14:49 +08:00
xiaoqi.cxq
ce91307a9c 恢复部分修改 2022-07-14 23:24:32 +08:00
xiaoqi.cxq
72f9fea42e gitee 接口限制兼容 2022-07-14 22:48:46 +08:00
xiaoqi.cxq
e1eb7ec350 gitee接口限制问题解决 2022-07-14 20:48:42 +08:00
xiaoqi.cxq
9f800ecece update readme 2022-07-10 13:03:04 +08:00
xiaoqi.cxq
8ec5bf9ec6 自定义图床上传bugfix 2022-07-10 11:48:28 +08:00
xiaoqi.cxq
1580f1c658 update readme 2022-07-10 11:25:57 +08:00
xiaoqi.cxq
cb1354516f 支持自定义图床 2022-07-10 11:24:29 +08:00
xiaoqi.cxq
cc6c8ff5ab 发布后publish文件写gitee空内容报错bugfix 2022-07-04 20:13:41 +08:00
xiaoqi.cxq
8a045e3ca9 update 文案 2022-07-02 17:24:52 +08:00
xiaoqi.cxq
31ee3d15e7 update readme 2022-07-02 13:15:52 +08:00
xiaoqi.cxq
070206cb5a 图床支持gitea图床 2022-07-02 13:09:24 +08:00
xiaoqi.cxq
698ddb9abd 支持SM.MS图床 2022-07-01 13:02:06 +08:00
xiaoqi.cxq
7a5a4e65c2 update readme 2022-06-27 09:25:36 +08:00
xiaoqi.cxq
a15309be14 update readme 2022-06-27 09:20:16 +08:00
豆萁
273b4e3500
!4 支持workspaces、badgeCreations保存到app-data
Merge pull request !4 from 豆萁/zh
2022-06-25 00:41:09 +00:00
豆萁
0bee41647b
!3 合并到master
Merge pull request !3 from 豆萁/zh
2022-06-25 00:38:14 +00:00
xiaoqi.cxq
613ff29076 save workspaces and badgeCreations 2022-06-25 07:47:44 +08:00
xiaoqi.cxq
1cb3810af8 update readme 2022-06-11 14:36:38 +08:00
xiaoqi.cxq
eaa37bab02 meta update 2022-06-10 14:00:07 +08:00
豆萁
f489a611b0
!2 合并到master
Merge pull request !2 from 豆萁/zh
2022-06-07 06:48:55 +00:00
xiaoqi.cxq
9651197c5c update default website 2022-06-07 07:49:58 +08:00
xiaoqi.cxq
8b55b32ec8 main workspaces bugfix 2022-06-07 06:35:35 +08:00
xiaoqi.cxq
f0612d1120 主空间修改为gitee 2022-06-04 03:07:10 +08:00
xiaoqi.cxq
b94898960a gitee头像显示问题bugfix 2022-06-02 08:54:51 +08:00
xiaoqi.cxq
5789b2993f 持续汉化 2022-06-02 07:45:13 +08:00
豆萁
45bee6bbf6
汉化
Merge pull request !1 from 豆萁/zh
2022-06-01 09:57:56 +00:00
xiaoqi.cxq
e53ee489b4 update readme 2022-06-01 17:47:19 +08:00
xiaoqi.cxq
74b0fc4635 update readme 2022-06-01 17:45:31 +08:00
xiaoqi.cxq
198c8cb647 gitee gitea 支持bugfix 2022-06-01 17:37:31 +08:00
xiaoqi.cxq
cb58d05147 trans zh 2022-05-27 08:54:38 +08:00
xiaoqi.cxq
97b436534f trans zh 2022-05-27 07:55:37 +08:00
xiaoqi.cxq
a33cef478b update readme 2022-05-27 04:17:49 +08:00
xiaoqi.cxq
dc0a0521ec update readme 2022-05-27 04:17:13 +08:00
xiaoqi.cxq
c1976545ef readme update 2022-05-26 13:15:27 +08:00
xiaoqi.cxq
0656045f67 gitee token 过期后自动刷新处理 2022-05-26 13:13:18 +08:00
xiaoqi.cxq
3ee3cf6470 文案调整 2022-05-25 19:20:39 +08:00
xiaoqi.cxq
dd8191efe7 update readme 2022-05-25 13:56:49 +08:00
xiaoqi.cxq
a8afbc69c2 update readme 2022-05-25 13:49:03 +08:00
xiaoqi.cxq
666db76f3c support gitea 2022-05-25 13:43:45 +08:00
xiaoqi.cxq
329d00c707 update README 2022-05-24 10:51:37 +08:00
xiaoqi.cxq
9e5d865f6a update README 2022-05-24 10:44:59 +08:00
xiaoqi.cxq
4ef712b988 update docker image version 2022-05-24 03:50:25 +08:00
xiaoqi.cxq
d52c677acd 修复Open On Gitee 和 Save On Gitee 的bug 2022-05-24 03:13:23 +08:00
Jacky Chen
e7e335d958 支持Gitee 修复Github Oauth2授权bug 2022-05-23 11:29:54 +08:00
Benoit Schweblin
46383b5b6a Tag v5.14.10 2021-03-29 20:21:35 +01:00
Benoit Schweblin
bad9abd1c5 Fixed ingress in helm chart. 2021-03-29 20:20:52 +01:00
Benoit Schweblin
97c97c5137 Tag v5.14.9 2021-03-29 19:32:54 +01:00
Benoit Schweblin
f335c386e5 Fixed ingress in helm chart. 2021-03-29 19:32:29 +01:00
Benoit Schweblin
260f085585 Tag v5.14.8 2021-03-29 18:13:59 +01:00
Benoit Schweblin
e7bb627385 Update .travis.yml to use node v12. 2021-03-29 17:47:25 +01:00
Benoit Schweblin
bc688eab71 Tag v5.14.7 2021-03-29 17:34:13 +01:00
Benoit Schweblin
c8987a8e84 Upgraded Katex to v0.13.
Upgraded Mermaid to v8.9.
Removed StackEdit v4 support.
2021-03-29 17:33:29 +01:00
Benoit Schweblin
0f0c9bed6c Tag v5.14.6 2021-03-29 13:47:36 +01:00
Benoit Schweblin
b75deaf8a4 Upgraded gulp to fix compatibility with node v12 2021-03-29 13:46:44 +01:00
Benoit Schweblin
a61a35be75 Fixed K8s/Helm deployment. 2021-03-29 13:28:44 +01:00
Benoit Schweblin
2fdbb67c67 Tag v5.14.5 2019-07-08 13:57:05 +01:00
Benoit Schweblin
52cd051198 Fixed travis build. 2019-07-08 13:56:38 +01:00
Benoit Schweblin
3dd66ae280 Tag v5.14.4 2019-07-08 13:16:45 +01:00
Benoit Schweblin
ede9551356 Attempt to fix the travis build 2019-07-08 13:16:13 +01:00
Benoit Schweblin
c6de74220a Tag v5.14.3 2019-07-08 10:37:21 +01:00
Benoit Schweblin
8d70a4f438 Fixed travis build. 2019-07-08 10:36:53 +01:00
281 changed files with 16468 additions and 4253 deletions

View File

@ -2,3 +2,8 @@ node_modules
.git
dist
.history
images
docs
Dockerfile
README.md
build.sh

View File

@ -1,7 +1,7 @@
language: node_js
node_js:
- "8"
- "12"
services:
- docker
@ -10,12 +10,10 @@ before_deploy:
# Run docker build
- docker build -t benweet/stackedit .
# Install Helm
- curl -SLO https://git.io/get_helm.sh
- chmod 700 get_helm.sh
- ./get_helm.sh
- curl -SL -o /tmp/get_helm.sh https://git.io/get_helm.sh
- chmod 700 /tmp/get_helm.sh
- /tmp/get_helm.sh
- helm init --client-only
# Make the chart
- npm run chart
deploy:
provider: script

View File

@ -1,21 +1,10 @@
FROM benweet/stackedit-base
RUN mkdir -p /opt/stackedit/stackedit_v4
WORKDIR /opt/stackedit/stackedit_v4
ENV SERVE_V4 true
ENV V4_VERSION 4.3.22
RUN npm pack stackedit@$V4_VERSION \
&& tar xzf stackedit-*.tgz --strip 1 \
&& yarn \
&& yarn cache clean \
&& rm -rf ~/.cache/bower \
&& rm -rf ~/.local/share/bower
FROM mafgwo/wkhtmltopdf-nodejs:11.15.0
WORKDIR /opt/stackedit
COPY package*json /opt/stackedit/
COPY gulpfile.js /opt/stackedit/
RUN npm install --unsafe-perm \
&& npm cache clean --force
COPY . /opt/stackedit

236
README.md
View File

@ -1,22 +1,183 @@
# StackEdit
<h1 align="center" style="text-align:center;">
<img src="chrome-app/icon-512.png" width="128" />
<br />
StackEdit中文版
</h1>
<p align="center">
<strong>笔记利器在线Markdown编辑器。</strong><br>
项目clone自<a href="https://gitee.com/mafgwo/stackedit" target="_blank" title="豆萁">豆萁/stackedit</a>如果你喜欢该项目请过去点一下Star您的肯定是作者最大的动力
</p>
<p align="center">
<a href="https://stackedit.cn/">https://stackedit.cn</a>
</p>
<p align="center">
<a target="_blank" href="https://www.apache.org/licenses/LICENSE-2.0.txt">
<img src="https://img.shields.io/:license-Apache2-blue.svg" alt="Apache 2" />
</a>
<a target="_blank" href="https://hub.docker.com/r/mafgwo/stackedit">
<img src="https://img.shields.io/docker/pulls/mafgwo/stackedit.svg" alt="Docker Pulls" />
</a>
<a target="_blank" href='https://gitee.com/mafgwo/stackedit/stargazers'>
<img src='https://gitee.com/mafgwo/stackedit/badge/star.svg' alt='gitee star'/>
</a>
</p>
<br/>
<hr />
1 笔记支持Gitee、GitHub、Gitea等Git仓库存储。<br>
2 支持直接上传图片也支持多种外部图床GitHub、Gitea、SM.MS、自定义图床粘贴或拖拽上传。<br>
3 编辑区域支持选择主题或自定义,总有你喜欢的主题。<br>
4 支持历史版本管理,不用担心编辑覆盖后无法回滚。<br>
5 支持ChatGPT辅助写作。<br>
6 支持KaTeX数学表达式、Mermaid UML图、乐谱等扩展。
<hr />
[![Build Status](https://img.shields.io/travis/benweet/stackedit.svg?style=flat)](https://travis-ci.org/benweet/stackedit) [![NPM version](https://img.shields.io/npm/v/stackedit.svg?style=flat)](https://www.npmjs.org/package/stackedit)
## 说明
> Full-featured, open-source Markdown editor based on PageDown, the Markdown library used by Stack Overflow and the other Stack Exchange sites.
本项目为本人clone修改自用如果你也喜欢请至原作者处获取及交流。
https://stackedit.io/
## 截图
### Ecosystem
**亮暗主题切换、编辑主题切换**
![](./images/theme.gif)
- [Chrome app](https://chrome.google.com/webstore/detail/iiooodelglhkcpgbajoejffhijaclcdg)
- NEW! Embed StackEdit in any website with [stackedit.js](https://github.com/benweet/stackedit.js)
- NEW! [Chrome extension](https://chrome.google.com/webstore/detail/ajehldoplanpchfokmeempkekhnhmoha) that uses stackedit.js
- [Community](https://community.stackedit.io/)
**支持的文档空间**
![](./images/workspace.png)
### Build
**拖拽粘贴上传图片**
![](./images/uploadimg.gif)
``` bash
# install dependencies
**支持文档搜索**
![](./images/search.gif)
**ChatGPT集成协助写作**
![](./images/chatgpt.gif)
## 相比国外开源版本的区别:
- 修复了Github授权登录问题
- 支持了Gitee仓库2022-05-25
- 支持了Gitea仓库2022-05-25
- 汉化2022-06-01
- 主文档空间从GoogleDrive切换为Gitee2022-06-04
- 支持SM.MS图床粘贴/拖拽图片自动上传2022-07-01
- 支持Gitea图床粘贴/拖拽图片自动上传2022-07-02
- 支持自定义图床粘贴/拖拽图片自动上传2022-07-04
- 支持GitHub图床粘贴/拖拽图片自动上传2022-07-31
- 支持了右上角一键切换主题补全了深色主题的样式2022-08-07
- 编辑与预览区域样式优化2022-08-10
- 左边栏文件资源管理支持搜索文件2022-08-17
- 支持[TOC]目录2022-09-04
- 发布支持填写提交信息[针对Gitee、GitHub、Gitea、Gitlab]2022-09-10
- 支持文档空间关闭自动同步[针对Gitee、GitHub、Gitea、Gitlab]关闭后可自定义提交信息2022-09-23
- Gitea支持后端配置指定应用ID和Secret2022-10-03
- 支持编辑区域选择主题样式2022-10-06
- 支持图片直接存储到当前文档空间2022-10-29
- 支持MD文档之间链接跳转2022-11-20
- 支持预览区域选择主题样式2022-12-04
- Gitlab的支持优化2023-02-23
- 导出HTML、PDF支持带预览主题导出2023-02-26
- 支持分享文档2023-03-30
- 支持ChatGPT生成内容2023-04-10
- GitLab授权接口调整2023-08-26
- 主文档空间支持GitHub登录2023-10-19
## 国外开源版本弊端:
- 作者已经不维护了或很少维护了
- 不支持国内常用Gitee
- 强依赖GoogleDrive而Google Drive在国内不能正常访问
## 部署说明
> 建议docker-compose方式部署其他部署方式如遇到问题欢迎提issue。
docker官方仓库下载太慢可以使用阿里云的镜像仓库镜像仓库地址registry.cn-hangzhou.aliyuncs.com/mafgwo/stackedit:【版本号】
`docker-compose.yml`如下:
```yaml
version: "3.7"
services:
stackedit:
image: mafgwo/stackedit:【docker中央仓库找到最新版本】
container_name: stackedit
environment:
- LISTENING_PORT=8080
- ROOT_URL=/
- USER_BUCKET_NAME=root
- DROPBOX_APP_KEY=【不需要支持则删掉】
- DROPBOX_APP_KEY_FULL=【不需要支持则删掉】
- GITHUB_CLIENT_ID=【不需要支持则删掉】
- GITHUB_CLIENT_SECRET=【不需要支持则删掉】
- GITEE_CLIENT_ID=【不需要支持则删掉】
- GITEE_CLIENT_SECRET=【不需要支持则删掉】
- GOOGLE_CLIENT_ID=【不需要支持则删掉】
- GOOGLE_API_KEY=【不需要支持则删掉】
- GITEA_CLIENT_ID=【不需要支持则删掉】
- GITEA_CLIENT_SECRET=【不需要支持则删掉】
- GITEA_URL=【不需要支持则删掉】
- GITLAB_CLIENT_ID=【不需要支持则删掉】
- GITLAB_CLIENT_SECRET=【不需要支持则删掉】
- GITLAB_URL=【不需要支持则删掉】
ports:
- 8080:8080/tcp
network_mode: bridge
restart: always
```
docker-compose方式的启动或停止命令
```bash
# 在 docker-compose.yml 文件目录下 启动命令
docker-compose up -d
# 在 docker-compose.yml 文件目录下 停止命令
docker-compose down
# 更新镜像只需要修改docker-compose.yml中镜像版本执行再停止、启动命令即可
```
或者可以直接通过Docker命名直接启动命令如下
```bash
docker run -itd --name stackedit \
-p 8080:8080 \
-e LISTENING_PORT=8080 \
-e ROOT_URL=/ \
-e USER_BUCKET_NAME=root \
-e DROPBOX_APP_KEY=【不需要支持则删掉】 \
-e DROPBOX_APP_KEY_FULL=【不需要支持则删掉】 \
-e GITHUB_CLIENT_ID=【不需要支持则删掉】 \
-e GITHUB_CLIENT_SECRET=【不需要支持则删掉】 \
-e GITEE_CLIENT_ID=【不需要支持则删掉】 \
-e GITEE_CLIENT_SECRET=【不需要支持则删掉】 \
-e GOOGLE_CLIENT_ID=【不需要支持则删掉】 \
-e GOOGLE_API_KEY=【不需要支持则删掉】 \
-e GITEA_CLIENT_ID=【不需要支持则删掉】 \
-e GITEA_CLIENT_SECRET=【不需要支持则删掉】 \
-e GITEA_URL=【不需要支持则删掉】 \
-e GITLAB_CLIENT_ID=【不需要支持则删掉】 \
-e GITLAB_CLIENT_SECRET=【不需要支持则删掉】 \
-e GITLAB_URL=【不需要支持则删掉】 \
mafgwo/stackedit:【docker中央仓库找到最新版本】
```
## 如何创建三方平台应用
> 部署时如果需要支持Gitee或GitHub则需要自行到对应三方平台创建应用获取到应用ID和秘钥替换到以上的环境变量中再启动应用。
- Gitee的环境变量GITEE_CLIENT_ID、GITEE_CLIENT_SECRET**[如何创建Gitee应用](./docs/部署之Gitee应用创建.md)**
- GitHub的环境变量GITHUB_CLIENT_ID、GITEE_CLIENT_SECRET**[如何创建GitHub应用](./docs/部署之GitHub应用创建.md)**
- Gitea可选择性配置环境变量未配置则在关联时前端指定有配置则仅允许配置的应用信息GITEA_CLIENT_ID、GITEA_CLIENT_SECRET、GITEA_URL**[如何创建Gitea应用](./docs/部署之Gitea应用创建.md)**
- Gitlab可选择性配置环境变量未配置则在关联时前端指定有配置则仅允许配置的应用信息GITLAB_CLIENT_ID、GITLAB_CLIENT_SECRET、GITLAB_URL **如何创建Gitlab应用(待补充文档)**
特别说明自建的Gitea、Gitlab要能接入stackedit必须支持跨域
## 编译与运行
> 编译运行的nodejs版本选择11.15.0版本
```bash
# 安装依赖
npm install
# serve with hot reload at localhost:8080
@ -28,54 +189,3 @@ npm run build
# build for production and view the bundle analyzer report
npm run build --report
```
### Deploy with Helm
StackEdit Helm chart allows easy StackEdit deployment to any Kubernetes cluster.
You can use it to configure deployment with your existing ingress controller and cert-manager.
```bash
# Add the StackEdit Helm repository
helm repo add stackedit https://benweet.github.io/stackedit-charts/
# Update your local Helm chart repository cache
helm repo update
# Deploy StackEdit chart to your cluster
helm install --name stackedit stackedit/stackedit \
--set dropboxAppKey=$DROPBOX_API_KEY \
--set dropboxAppKeyFull=$DROPBOX_FULL_ACCESS_API_KEY \
--set googleClientId=$GOOGLE_CLIENT_ID \
--set googleApiKey=$GOOGLE_API_KEY \
--set githubClientId=$GITHUB_CLIENT_ID \
--set githubClientSecret=$GITHUB_CLIENT_SECRET \
--set wordpressClientId=\"$WORDPRESS_CLIENT_ID\" \
--set wordpressSecret=$WORDPRESS_CLIENT_SECRET
# Upgrade to the latest version
helm repo update
helm upgrade stackedit stackedit/stackedit
# Uninstall StackEdit
helm delete --purge stackedit
# Deploy using your existing ingress controller and cert-manager
# See https://docs.cert-manager.io/en/latest/tutorials/acme/quick-start/index.html
helm install --name stackedit stackedit/stackedit \
--set dropboxAppKey=$DROPBOX_API_KEY \
--set dropboxAppKeyFull=$DROPBOX_FULL_ACCESS_API_KEY \
--set googleClientId=$GOOGLE_CLIENT_ID \
--set googleApiKey=$GOOGLE_API_KEY \
--set githubClientId=$GITHUB_CLIENT_ID \
--set githubClientSecret=$GITHUB_CLIENT_SECRET \
--set wordpressClientId=\"$WORDPRESS_CLIENT_ID\" \
--set wordpressSecret=$WORDPRESS_CLIENT_SECRET \
--set ingress.enabled=true \
--set ingress.annotations."kubernetes\.io/ingress\.class"=nginx \
--set ingress.annotations."certmanager\.k8s\.io/issuer"=letsencrypt-prod \
--set ingress.annotations."certmanager\.k8s\.io/acme-challenge-type"=http01 \
--set ingress.hosts[0].host=stackedit.example.com \
--set ingress.hosts[0].paths[0]=/ \
--set ingress.tls[0].secretName=stackedit-tls \
--set ingress.tls[0].hosts[0]=stackedit.example.com
```

37
build.sh Normal file
View File

@ -0,0 +1,37 @@
#!/bin/bash
# 检查参数是否提供版本号
if [ -z "$1" ]; then
echo "请提供版本号作为参数"
exit 1
fi
# 定义版本号变量
VERSION="$1"
IMAGE_NAME="mafgwo/stackedit"
# 构建 Docker 镜像
build_image() {
docker build -t "$IMAGE_NAME" .
}
# 标记 Docker 镜像
tag_image() {
docker tag "$IMAGE_NAME" "$IMAGE_NAME:$VERSION"
docker tag "$IMAGE_NAME" "registry.cn-hangzhou.aliyuncs.com/$IMAGE_NAME:$VERSION"
}
# 推送 Docker 镜像
push_image() {
docker push "$IMAGE_NAME"
docker push "registry.cn-hangzhou.aliyuncs.com/$IMAGE_NAME"
docker push "$IMAGE_NAME:$VERSION"
docker push "registry.cn-hangzhou.aliyuncs.com/$IMAGE_NAME:$VERSION"
}
# 执行构建、标记和推送
build_image
tag_image
push_image
echo "操作完成"

View File

@ -8,10 +8,14 @@ docker push benweet/stackedit:$TRAVIS_TAG
docker tag benweet/stackedit:$TRAVIS_TAG benweet/stackedit:latest
docker push benweet/stackedit:latest
# Build the chart
cd "$TRAVIS_BUILD_DIR"
npm run chart
# Add chart to helm repository
git clone "https://benweet:$GITHUB_TOKEN@github.com/benweet/stackedit-charts.git" charts
cd charts
helm package ../dist/stackedit
git clone --branch master "https://benweet:$GITHUB_TOKEN@github.com/benweet/stackedit-charts.git" /tmp/charts
cd /tmp/charts
helm package "$TRAVIS_BUILD_DIR/dist/stackedit"
helm repo index --url https://benweet.github.io/stackedit-charts/ .
git config user.name "Benoit Schweblin"
git config user.email "benoit.schweblin@gmail.com"

View File

@ -1,6 +1,6 @@
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "stackedit.fullname" . -}}
apiVersion: extensions/v1beta1
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ $fullName }}
@ -28,9 +28,12 @@ spec:
paths:
{{- range .paths }}
- path: {{ . }}
pathType: Prefix
backend:
serviceName: {{ $fullName }}
servicePort: http
service:
name: {{ $fullName }}
port:
name: http
{{- end }}
{{- end }}
{{- end }}

View File

@ -8,11 +8,18 @@ googleClientId: ""
googleApiKey: ""
githubClientId: ""
githubClientSecret: ""
giteeClientId: ""
giteeClientSecret: ""
wordpressClientId: ""
wordpressSecret: ""
paypalReceiverEmail: ""
awsAccessKeyId: ""
awsSecretAccessKey: ""
giteaClientId: ""
giteaClientSecret: ""
giteaUrl: ""
gitlabClientId: ""
gitlabUrl: ""
replicaCount: 1

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -1,28 +1,28 @@
{
"name": "StackEdit",
"description": "In-browser Markdown editor",
"version": "1.0.13",
"manifest_version": 2,
"container" : "GOOGLE_DRIVE",
"api_console_project_id" : "241271498917",
"icons": {
"16": "icon-16.png",
"32": "icon-32.png",
"64": "icon-64.png",
"128": "icon-128.png",
"256": "icon-256.png",
"512": "icon-512.png"
},
"app": {
"urls": [
"https://stackedit.io/"
],
"launch": {
"web_url": "https://stackedit.io/app"
}
},
"offline_enabled": true,
"permissions": [
"unlimitedStorage"
]
}
"name": "StackEdit中文版",
"description": "支持Gitee仓库/粘贴图片自动上传的浏览器内 Markdown 编辑器",
"version": "5.15.17",
"manifest_version": 2,
"container": "GITEE",
"api_console_project_id": "241271498917",
"icons": {
"16": "icon-16.png",
"32": "icon-32.png",
"64": "icon-64.png",
"128": "icon-128.png",
"256": "icon-256.png",
"512": "icon-512.png"
},
"app": {
"urls": [
"https://md.jonylee.top/"
],
"launch": {
"web_url": "https://md.jonylee.top/app"
}
},
"offline_enabled": true,
"permissions": [
"unlimitedStorage"
]
}

View File

@ -2,5 +2,17 @@ var merge = require('webpack-merge')
var prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
NODE_ENV: '"development"'
})
NODE_ENV: '"development"',
// 以下配置是开发临时用的配置 随时可能失效 请替换为自己的
GITHUB_CLIENT_ID: '"845b8f75df48f2ee0563"',
GITHUB_CLIENT_SECRET: '"80df676597abded1450926861965cc3f9bead6a0"',
GITEE_CLIENT_ID: '"925ba7c78b85dec984f7877e4aca5cab10ae333c6d68e761bdb0b9dfb8f55672"',
GITEE_CLIENT_SECRET: '"f05731066e42d307339dc8ebbb037a103881dafc7207a359a393b87749f1c562"',
CLIENT_ID: '"thF3qCGLN39OtafjGnqHyj6n02WwE6xD"',
// GITEA_CLIENT_ID: '"fe30f8f9-b1e8-4531-8f72-c1a5d3912805"',
// GITEA_CLIENT_SECRET: '"lus7oMnb3H6M1hsChndphArE20Txr7erwJLf7SDBQWTw"',
// GITEA_URL: '"https://gitea.test.com"',
GITLAB_CLIENT_ID: '"074cd5103c62dea0f479dac861039656ac80935e304c8113a02cc64c629496ae"',
GITLAB_CLIENT_SECRET: '"6f406f24216b686d55d28313dec1913c2a8e599afdb08380d5e8ce838e16e41e"',
GITLAB_URL: '"http://gitlab.qicoder.com"',
})

View File

@ -23,7 +23,7 @@ module.exports = {
},
dev: {
env: require('./dev.env'),
port: 8080,
port: 80,
autoOpenBrowser: false,
assetsSubDirectory: 'static',
assetsPublicPath: '/',

View File

@ -0,0 +1,10 @@
# 大文档导出PDF方式说明
> 由于大文档导出PDF需要消费非常多的服务器资源而且很容易导致导出超时故导出PDF的MD文档过大时可以使用 **[wkhtmltopdf](https://wkhtmltopdf.org/downloads.html)** 工具导出。
# 操作步骤
- 先在 **[StackEdit中文版](https://stackedit.cn/app)** 中使用 `导出为HTML` 功能导出MD文档导出后可以得到一个HTML文档。
- 到 **[wkhtmltopdf](https://wkhtmltopdf.org/downloads.html)** 官网下载安装程序。
- 使用 wkhtmltopdf 的导出PDF的命令 `wkhtmltopdf [GLOBAL OPTION]... [OBJECT]... <output file>` 把HTML导出为PDF如简单的导出命令`wkhtmltopdf test.html test.pdf`,具体的 `GLOBAL OPTION` 参数说明可以通过 `wkhtmltopdf -H` 查看帮助文档。

View File

@ -0,0 +1,20 @@
# GitHub应用配置说明
> StackEdit中文版部署如果需要支持GitHub则需要到GitHub创建一个应用并复制其中的clientId和clientSecret填充到环境变量 GITHUB_CLIENT_ID 和 GITHUB_CLIENT_SECRET 中。
# 如何创建GitHub应用
按下面图的指示创建
![](../images/github/github01.png)
![](../images/github/github02.png)
![](../images/github/github03.png)
![](../images/github/github04.png)
![](../images/github/github05.png)

View File

@ -0,0 +1,35 @@
# Gitea应用配置说明
> StackEdit中文版支持Gitea则需要到Gitea创建一个应用在StackEdit中文版绑定Gitea账号的时候填入。
# 如何创建Gitea应用
按下面图的指示创建
![](../images/gitea/gitea01.png)
![](../images/gitea/gitea02.png)
![](../images/gitea/gitea03.png)
![](../images/gitea/gitea04.png)
创建成功后即可看到应用ID 和 应用秘钥。
# Gitea跨域问题
由于StackEdit中文版是从浏览器直接访问Gitea接口故个人部署的Gitea需要支持跨域至于如何支持跨域请参考官方文档https://docs.gitea.io/en-us/config-cheat-sheet/#cors-cors (官方跨域的支持好像存在问题我个人包括很多网友通过这个配置支持跨域都失败了如果你也失败了可以试试用nginx代理实现跨域)
nginx配置实现跨域的配置如下
```
add_header Access-Control-Allow-Headers *;
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Methods GET,POST,PUT,DELETE,OPTIONS;
if ($request_method = 'OPTIONS') {
return 204;
}
```

View File

@ -0,0 +1,19 @@
# Gitee应用配置说明
> StackEdit中文版部署如果需要支持Gitee则需要到Gitee创建一个应用并复制其中的clientId和clientSecret填充到环境变量 GITEE_CLIENT_ID 和 GITEE_CLIENT_SECRET 中。
# 如何创建Gitee应用
按下面图的指示创建
![](../images/gitee/gitee01.png)
![](../images/gitee/gitee02.png)
![](../images/gitee/gitee03.png)
![](../images/gitee/gitee04.png)
创建成功后即可看到client id 和 client secret。

BIN
images/chatgpt.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

BIN
images/dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 793 KiB

BIN
images/fileSearch.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

BIN
images/gitea/gitea01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

BIN
images/gitea/gitea02.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
images/gitea/gitea03.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
images/gitea/gitea04.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
images/gitee/gitee01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
images/gitee/gitee02.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
images/gitee/gitee03.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
images/gitee/gitee04.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

BIN
images/github/github01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

BIN
images/github/github02.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

BIN
images/github/github03.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
images/github/github04.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

BIN
images/github/github05.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

BIN
images/imageBed.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 KiB

BIN
images/light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 726 KiB

BIN
images/qq.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

BIN
images/search.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 KiB

BIN
images/theme.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 937 KiB

BIN
images/uploadimg.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 761 KiB

BIN
images/workspace.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

View File

@ -1,17 +1,31 @@
<!DOCTYPE html>
<html>
<head>
<head>
<meta charset="utf-8">
<title>StackEdit</title>
<link rel="canonical" href="https://stackedit.io/app">
<meta name="description" content="Free, open-source, full-featured Markdown editor.">
<title>Markdown编辑器-JonyLee的设计导航</title>
<link rel="canonical" href="https://md.jonylee.top">
<meta name="description" content="StackEdit中文版免费开源功能全面的Markdown编辑器。">
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
</head>
<body>
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
<!-- 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

@ -11,7 +11,7 @@ const express = require('express');
const app = express();
require('./server')(app, process.env.SERVE_V4);
require('./server')(app);
const port = parseInt(process.env.PORT || 8080, 10);
const httpServer = http.createServer(app);

4019
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,11 @@
{
"name": "stackedit",
"version": "5.14.2",
"description": "Free, open-source, full-featured Markdown editor",
"author": "Benoit Schweblin",
"version": "5.15.21",
"description": "免费, 开源, 功能齐全的 Markdown 编辑器",
"author": "Benoit Schweblin, 豆萁",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/benweet/stackedit/issues"
"url": "https://github.com/mafgwo/stackedit/issues"
},
"main": "index.js",
"scripts": {
@ -22,12 +22,11 @@
"patch": "npm version patch -m \"Tag v%s\"",
"minor": "npm version minor -m \"Tag v%s\"",
"major": "npm version major -m \"Tag v%s\"",
"chart": "rm -rf dist/stackedit && cp -r chart dist/stackedit && sed -i.bak -e s/STACKEDIT_VERSION/$npm_package_version/g dist/stackedit/*.yaml && rm dist/stackedit/*.yaml.bak"
"chart": "mkdir -p dist && rm -rf dist/stackedit && cp -r chart dist/stackedit && sed -i.bak -e s/STACKEDIT_VERSION/$npm_package_version/g dist/stackedit/*.yaml && rm dist/stackedit/*.yaml.bak"
},
"dependencies": {
"@vue/test-utils": "^1.0.0-beta.16",
"abcjs": "^5.2.0",
"aws-sdk": "^2.317.0",
"babel-runtime": "^6.26.0",
"bezier-easing": "^1.1.0",
"body-parser": "^1.18.2",
@ -35,11 +34,10 @@
"compression": "^1.7.0",
"diff-match-patch": "^1.0.0",
"file-saver": "^1.3.8",
"google-id-token-verifier": "^0.2.3",
"handlebars": "^4.0.10",
"indexeddbshim": "^3.6.2",
"js-yaml": "^3.11.0",
"katex": "^v0.10.2",
"katex": "^0.16.2",
"markdown-it": "^8.4.1",
"markdown-it-abbr": "^1.0.4",
"markdown-it-deflist": "^2.0.2",
@ -50,14 +48,14 @@
"markdown-it-pandoc-renderer": "1.1.3",
"markdown-it-sub": "^1.0.0",
"markdown-it-sup": "^1.0.0",
"mermaid": "^8.0.0-rc.8",
"mermaid": "^8.9.2",
"mousetrap": "^1.6.1",
"normalize-scss": "^7.0.1",
"prismjs": "^1.6.0",
"request": "^2.85.0",
"serve-static": "^1.13.2",
"tmp": "^0.0.33",
"turndown": "^4.0.2",
"turndown": "^7.1.1",
"vue": "^2.5.16",
"vuex": "^3.0.1"
},
@ -91,7 +89,7 @@
"favicons-webpack-plugin": "^0.0.9",
"file-loader": "^1.1.11",
"friendly-errors-webpack-plugin": "^1.7.0",
"gulp": "^3.9.1",
"gulp": "^4.0.2",
"gulp-concat": "^2.6.1",
"html-webpack-plugin": "^3.2.0",
"http-proxy-middleware": "^0.18.0",
@ -100,7 +98,8 @@
"jest": "^23.0.0",
"jest-raw-loader": "^1.0.1",
"jest-serializer-vue": "^0.3.0",
"node-sass": "^4.9.0",
"js-md5": "^0.7.3",
"node-sass": "^4.0.0",
"npm-bump": "^0.0.23",
"offline-plugin": "^5.0.3",
"opn": "^4.0.2",

View File

@ -1,30 +1,42 @@
const pandocPath = process.env.PANDOC_PATH || 'pandoc';
const wkhtmltopdfPath = process.env.WKHTMLTOPDF_PATH || 'wkhtmltopdf';
const userBucketName = process.env.USER_BUCKET_NAME || 'stackedit-users';
const paypalUri = process.env.PAYPAL_URI || 'https://www.paypal.com/cgi-bin/webscr';
const paypalReceiverEmail = process.env.PAYPAL_RECEIVER_EMAIL;
const dropboxAppKey = process.env.DROPBOX_APP_KEY;
const dropboxAppKeyFull = process.env.DROPBOX_APP_KEY_FULL;
const githubClientId = process.env.GITHUB_CLIENT_ID;
const githubClientSecret = process.env.GITHUB_CLIENT_SECRET;
const giteeClientId = process.env.GITEE_CLIENT_ID;
const giteeClientSecret = process.env.GITEE_CLIENT_SECRET;
const googleClientId = process.env.GOOGLE_CLIENT_ID;
const googleApiKey = process.env.GOOGLE_API_KEY;
const wordpressClientId = process.env.WORDPRESS_CLIENT_ID;
const giteaClientId = process.env.GITEA_CLIENT_ID;
const giteaClientSecret = process.env.GITEA_CLIENT_SECRET;
const giteaUrl = process.env.GITEA_URL;
const gitlabClientId = process.env.GITLAB_CLIENT_ID;
const gitlabClientSecret = process.env.GITLAB_CLIENT_SECRET;
const gitlabUrl = process.env.GITLAB_URL;
exports.values = {
pandocPath,
wkhtmltopdfPath,
userBucketName,
paypalUri,
paypalReceiverEmail,
dropboxAppKey,
dropboxAppKeyFull,
githubClientId,
githubClientSecret,
giteeClientId,
giteeClientSecret,
googleClientId,
googleApiKey,
wordpressClientId,
giteaClientId,
giteaClientSecret,
giteaUrl,
gitlabClientId,
gitlabClientSecret,
gitlabUrl,
};
exports.publicValues = {
@ -35,4 +47,8 @@ exports.publicValues = {
googleApiKey,
wordpressClientId,
allowSponsorship: !!paypalReceiverEmail,
giteaClientId,
giteaUrl,
gitlabClientId,
gitlabUrl,
};

40
server/gitea.js Normal file
View File

@ -0,0 +1,40 @@
const request = require('request');
const conf = require('./conf');
function giteaToken(queryParam) {
return new Promise((resolve, reject) => {
request({
method: 'POST',
url: `${conf.values.giteaUrl}/login/oauth/access_token`,
headers: {
'content-type': 'application/json',
},
json: true,
body: {
...queryParam,
client_id: conf.values.giteaClientId,
client_secret: conf.values.giteaClientSecret,
},
}, (err, res, body) => {
if (err) {
reject(err);
}
const token = body.access_token;
if (token) {
resolve(body);
} else {
reject(res.statusCode + ',body:' + JSON.stringify(body));
}
});
});
}
exports.giteaToken = (req, res) => {
giteaToken(req.query)
.then(
tokenBody => res.send(tokenBody),
err => res
.status(400)
.send(err ? err.message || err.toString() : 'bad_code'),
);
};

42
server/gitee.js Normal file
View File

@ -0,0 +1,42 @@
const qs = require('qs'); // eslint-disable-line import/no-extraneous-dependencies
const request = require('request');
const conf = require('./conf');
function giteeToken(clientId, code, oauth2RedirectUri) {
const clientIndex = conf.values.giteeClientId.split(',').indexOf(clientId);
const clientSecret = conf.values.giteeClientSecret.split(',')[clientIndex];
return new Promise((resolve, reject) => {
request({
method: 'POST',
url: 'https://gitee.com/oauth/token',
form: {
client_id: clientId,
client_secret: clientSecret,
code,
grant_type: 'authorization_code',
redirect_uri: oauth2RedirectUri,
},
json: true
}, (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.giteeToken = (req, res) => {
giteeToken(req.query.clientId, req.query.code, req.query.oauth2RedirectUri)
.then(
tokenBody => res.send(tokenBody),
err => res
.status(400)
.send(err ? err.message || err.toString() : 'bad_code'),
);
};

40
server/gitlab.js Normal file
View File

@ -0,0 +1,40 @@
const request = require('request');
const conf = require('./conf');
function gitlabToken(queryParam) {
return new Promise((resolve, reject) => {
request({
method: 'POST',
url: `${conf.values.gitlabUrl}/oauth/token`,
headers: {
'content-type': 'application/json',
},
json: true,
qs: {
...queryParam,
client_id: conf.values.gitlabClientId,
client_secret: conf.values.gitlabClientSecret,
},
}, (err, res, body) => {
if (err) {
reject(err);
}
const token = body.access_token;
if (token) {
resolve(body);
} else {
reject(res.statusCode + ',body:' + JSON.stringify(body));
}
});
});
}
exports.gitlabToken = (req, res) => {
gitlabToken(req.query)
.then(
tokenBody => res.send(tokenBody),
err => res
.status(400)
.send(err ? err.message || err.toString() : 'bad_code'),
);
};

View File

@ -2,15 +2,17 @@ const compression = require('compression');
const serveStatic = require('serve-static');
const bodyParser = require('body-parser');
const path = require('path');
const user = require('./user');
const github = require('./github');
const gitee = require('./gitee');
const gitea = require('./gitea');
const gitlab = require('./gitlab');
const pdf = require('./pdf');
const pandoc = require('./pandoc');
const conf = require('./conf');
const resolvePath = pathToResolve => path.join(__dirname, '..', pathToResolve);
module.exports = (app, serveV4) => {
module.exports = (app) => {
if (process.env.NODE_ENV === 'production') {
// Enable CORS for fonts
app.all('*', (req, res, next) => {
@ -25,24 +27,33 @@ module.exports = (app, serveV4) => {
}
app.get('/oauth2/githubToken', github.githubToken);
app.get('/oauth2/giteeToken', gitee.giteeToken);
app.get('/oauth2/giteaToken', gitea.giteaToken);
app.get('/oauth2/gitlabToken', gitlab.gitlabToken);
app.get('/conf', (req, res) => res.send(conf.publicValues));
app.get('/userInfo', user.userInfo);
app.post('/pdfExport', pdf.generate);
app.post('/pandocExport', pandoc.generate);
app.post('/paypalIpn', bodyParser.urlencoded({
extended: false,
}), user.paypalIpn);
if (serveV4) {
/* eslint-disable global-require, import/no-unresolved */
app.post('/sshPublish', require('../stackedit_v4/app/ssh').publish);
app.post('/picasaImportImg', require('../stackedit_v4/app/picasa').importImg);
app.get('/downloadImport', require('../stackedit_v4/app/download').importPublic);
/* eslint-enable global-require, import/no-unresolved */
}
app.get('/giteeClientId', (req, res) => {
const giteeClientIds = conf.values.giteeClientId.split(',');
// 仅一个 则直接返回
if (giteeClientIds.length === 1) {
res.send(giteeClientIds[0]);
return;
}
// 是否随机一个clientId 默认第一个 如果random 为1 则使用随机 避免单个应用接口次数用满无法自动切换其他应用
const random = req.query.random;
if (!random) {
res.send(giteeClientIds[0]);
return;
}
// 随机一个 排除第一个 因为第一个应用接口次数用完了
const clientId = giteeClientIds[Math.floor(((giteeClientIds.length - 1) * Math.random())) + 1];
res.send(clientId);
});
// Serve landing.html
app.get('/', (req, res) => res.sendFile(resolvePath('static/landing/index.html')));
// Serve privacy_policy.html
app.get('/privacy_policy.html', (req, res) => res.sendFile(resolvePath('static/landing/privacy_policy.html')));
// Serve sitemap.xml
app.get('/sitemap.xml', (req, res) => res.sendFile(resolvePath('static/sitemap.xml')));
// Serve callback.html
@ -50,36 +61,29 @@ module.exports = (app, serveV4) => {
// Google Drive action receiver
app.get('/googleDriveAction', (req, res) =>
res.redirect(`./app#providerId=googleDrive&state=${encodeURIComponent(req.query.state)}`));
// Serve the static folder with 30 day max-age
app.use('/themes', serveStatic(resolvePath('static/themes'), {
maxAge: '5d',
}));
// Serve style.css with 1 day max-age
app.get('/style.css', (req, res) => res.sendFile(resolvePath('dist/style.css'), {
maxAge: '1d',
}));
// Serve share.html
app.get('/share.html', (req, res) => res.sendFile(resolvePath('static/landing/share.html')));
app.get('/gistshare.html', (req, res) => res.sendFile(resolvePath('static/landing/gistshare.html')));
// Serve static resources
if (process.env.NODE_ENV === 'production') {
if (serveV4) {
// Serve editor.html in /viewer
app.get('/editor', (req, res) => res.sendFile(resolvePath('stackedit_v4/views/editor.html')));
// Serve viewer.html in /viewer
app.get('/viewer', (req, res) => res.sendFile(resolvePath('stackedit_v4/views/viewer.html')));
}
// Serve index.html in /app
app.get('/app', (req, res) => res.sendFile(resolvePath('dist/index.html')));
// Serve style.css with 1 day max-age
app.get('/style.css', (req, res) => res.sendFile(resolvePath('dist/style.css'), {
maxAge: '1d',
}));
// Serve the static folder with 1 year max-age
app.use('/static', serveStatic(resolvePath('dist/static'), {
maxAge: '1y',
}));
app.use(serveStatic(resolvePath('dist')));
if (serveV4) {
app.use(serveStatic(path.dirname(resolvePath('stackedit_v4/public/cache.manifest'))));
// Error 404
app.use((req, res) => res.status(404).sendFile(resolvePath('stackedit_v4/views/error_404.html')));
}
}
};

View File

@ -2,7 +2,6 @@
const { spawn } = require('child_process');
const fs = require('fs');
const tmp = require('tmp');
const user = require('./user');
const conf = require('./conf');
const outputFormats = {
@ -42,108 +41,101 @@ exports.generate = (req, res) => {
const outputFormat = Object.prototype.hasOwnProperty.call(outputFormats, req.query.format)
? req.query.format
: 'pdf';
user.checkSponsor(req.query.idToken)
.then((isSponsor) => {
if (!isSponsor) {
throw new Error('unauthorized');
}
return new Promise((resolve, reject) => {
tmp.file({
postfix: `.${outputFormat}`,
}, (err, filePath, fd, cleanupCallback) => {
if (err) {
reject(err);
} else {
resolve({
filePath,
cleanupCallback,
});
}
});
});
})
.then(({ filePath, cleanupCallback }) => new Promise((resolve, reject) => {
const options = readJson(req.query.options);
const metadata = readJson(req.query.metadata);
const params = [];
params.push('--latex-engine=xelatex');
params.push('--webtex=http://chart.apis.google.com/chart?cht=tx&chf=bg,s,FFFFFF00&chco=000000&chl=');
if (options.toc) {
params.push('--toc');
}
options.tocDepth = parseInt(options.tocDepth, 10);
if (!Number.isNaN(options.tocDepth)) {
params.push('--toc-depth', options.tocDepth);
}
options.highlightStyle = highlightStyles.includes(options.highlightStyle) ? options.highlightStyle : 'kate';
params.push('--highlight-style', options.highlightStyle);
Object.keys(metadata).forEach((key) => {
params.push('-M', `${key}=${metadata[key]}`);
});
let finished = false;
function onError(error) {
finished = true;
cleanupCallback();
reject(error);
}
const format = outputFormat === 'pdf' ? 'latex' : outputFormat;
params.push('-f', 'json', '-t', format, '-o', filePath);
const pandoc = spawn(conf.values.pandocPath, params, {
stdio: [
'pipe',
'ignore',
'pipe',
],
});
let timeoutId = setTimeout(() => {
timeoutId = null;
pandoc.kill();
}, 50000);
pandoc.on('error', onError);
pandoc.stdin.on('error', onError);
pandoc.stderr.on('data', (data) => {
pandocError += `${data}`;
});
pandoc.on('close', (code) => {
if (!finished) {
clearTimeout(timeoutId);
if (!timeoutId) {
res.statusCode = 408;
cleanupCallback();
reject(new Error('timeout'));
} else if (code) {
cleanupCallback();
reject();
} else {
res.set('Content-Type', outputFormats[outputFormat]);
const readStream = fs.createReadStream(filePath);
readStream.on('open', () => readStream.pipe(res));
readStream.on('close', () => cleanupCallback());
readStream.on('error', () => {
cleanupCallback();
reject();
});
}
}
});
req.pipe(pandoc.stdin);
}))
.catch((err) => {
const message = err && err.message;
if (message === 'unauthorized') {
res.statusCode = 401;
res.end('Unauthorized.');
} else if (message === 'timeout') {
res.statusCode = 408;
res.end('Request timeout.');
new Promise((resolve, reject) => {
tmp.file({
postfix: `.${outputFormat}`,
}, (err, filePath, fd, cleanupCallback) => {
if (err) {
reject(err);
} else {
res.statusCode = 400;
res.end(pandocError || 'Unknown error.');
resolve({
filePath,
cleanupCallback,
});
}
});
}).then(({ filePath, cleanupCallback }) => new Promise((resolve, reject) => {
const options = readJson(req.query.options);
const metadata = readJson(req.query.metadata);
const params = [];
params.push('--pdf-engine=xelatex');
params.push('--webtex=http://chart.apis.google.com/chart?cht=tx&chf=bg,s,FFFFFF00&chco=000000&chl=');
if (options.toc) {
params.push('--toc');
}
options.tocDepth = parseInt(options.tocDepth, 10);
if (!Number.isNaN(options.tocDepth)) {
params.push('--toc-depth', options.tocDepth);
}
options.highlightStyle = highlightStyles.includes(options.highlightStyle) ? options.highlightStyle : 'kate';
params.push('--highlight-style', options.highlightStyle);
Object.keys(metadata).forEach((key) => {
params.push('-M', `${key}=${metadata[key]}`);
});
let finished = false;
function onError(error) {
finished = true;
cleanupCallback();
reject(error);
}
const format = outputFormat === 'pdf' ? 'latex' : outputFormat;
params.push('-f', 'json', '-t', format, '-o', filePath);
const pandoc = spawn(conf.values.pandocPath, params, {
stdio: [
'pipe',
'ignore',
'pipe',
],
});
let timeoutId = setTimeout(() => {
timeoutId = null;
pandoc.kill();
}, 50000);
pandoc.on('error', onError);
pandoc.stdin.on('error', onError);
pandoc.stderr.on('data', (data) => {
pandocError += `${data}`;
});
pandoc.on('close', (code) => {
if (!finished) {
clearTimeout(timeoutId);
if (!timeoutId) {
res.statusCode = 408;
cleanupCallback();
reject(new Error('timeout'));
} else if (code) {
cleanupCallback();
reject();
} else {
res.set('Content-Type', outputFormats[outputFormat]);
const readStream = fs.createReadStream(filePath);
readStream.on('open', () => readStream.pipe(res));
readStream.on('close', () => cleanupCallback());
readStream.on('error', () => {
cleanupCallback();
reject();
});
}
}
});
req.pipe(pandoc.stdin);
}))
.catch((err) => {
console.error(err);
const message = err && err.message;
if (message === 'unauthorized') {
res.statusCode = 401;
res.end('Unauthorized.');
} else if (message === 'timeout') {
res.statusCode = 408;
res.end('Request timeout.');
} else {
res.statusCode = 400;
res.end(pandocError || 'Unknown error.');
}
});
};

View File

@ -2,7 +2,6 @@
const { spawn } = require('child_process');
const fs = require('fs');
const tmp = require('tmp');
const user = require('./user');
const conf = require('./conf');
/* eslint-disable no-var, prefer-arrow-callback, func-names */
@ -51,135 +50,129 @@ const readJson = (str) => {
exports.generate = (req, res) => {
let wkhtmltopdfError = '';
user.checkSponsor(req.query.idToken)
.then((isSponsor) => {
if (!isSponsor) {
throw new Error('unauthorized');
}
return new Promise((resolve, reject) => {
tmp.file((err, filePath, fd, cleanupCallback) => {
if (err) {
reject(err);
} else {
resolve({
filePath,
cleanupCallback,
});
}
});
});
})
.then(({ filePath, cleanupCallback }) => new Promise((resolve, reject) => {
let finished = false;
function onError(err) {
finished = true;
cleanupCallback();
new Promise((resolve, reject) => {
tmp.file((err, filePath, fd, cleanupCallback) => {
if (err) {
reject(err);
}
const options = readJson(req.query.options);
const params = [];
// Margins
const marginTop = parseInt(`${options.marginTop}`, 10);
params.push('-T', Number.isNaN(marginTop) ? 25 : marginTop);
const marginRight = parseInt(`${options.marginRight}`, 10);
params.push('-R', Number.isNaN(marginRight) ? 25 : marginRight);
const marginBottom = parseInt(`${options.marginBottom}`, 10);
params.push('-B', Number.isNaN(marginBottom) ? 25 : marginBottom);
const marginLeft = parseInt(`${options.marginLeft}`, 10);
params.push('-L', Number.isNaN(marginLeft) ? 25 : marginLeft);
// Header
if (options.headerCenter) {
params.push('--header-center', `${options.headerCenter}`);
}
if (options.headerLeft) {
params.push('--header-left', `${options.headerLeft}`);
}
if (options.headerRight) {
params.push('--header-right', `${options.headerRight}`);
}
if (options.headerFontName) {
params.push('--header-font-name', `${options.headerFontName}`);
}
if (options.headerFontSize) {
params.push('--header-font-size', `${options.headerFontSize}`);
}
// Footer
if (options.footerCenter) {
params.push('--footer-center', `${options.footerCenter}`);
}
if (options.footerLeft) {
params.push('--footer-left', `${options.footerLeft}`);
}
if (options.footerRight) {
params.push('--footer-right', `${options.footerRight}`);
}
if (options.footerFontName) {
params.push('--footer-font-name', `${options.footerFontName}`);
}
if (options.footerFontSize) {
params.push('--footer-font-size', `${options.footerFontSize}`);
}
// Page size
params.push('--page-size', !authorizedPageSizes.includes(options.pageSize) ? 'A4' : options.pageSize);
// Use a temp file as wkhtmltopdf can't access /dev/stdout on Amazon EC2 for some reason
params.push('--run-script', `${waitForJavaScript.toString()}waitForJavaScript()`);
params.push('--window-status', 'done');
const wkhtmltopdf = spawn(conf.values.wkhtmltopdfPath, params.concat('-', filePath), {
stdio: [
'pipe',
'ignore',
'pipe',
],
});
let timeoutId = setTimeout(function () {
timeoutId = null;
wkhtmltopdf.kill();
}, 50000);
wkhtmltopdf.on('error', onError);
wkhtmltopdf.stdin.on('error', onError);
wkhtmltopdf.stderr.on('data', (data) => {
wkhtmltopdfError += `${data}`;
});
wkhtmltopdf.on('close', (code) => {
if (!finished) {
clearTimeout(timeoutId);
if (!timeoutId) {
cleanupCallback();
reject(new Error('timeout'));
} else if (code) {
cleanupCallback();
reject();
} else {
res.set('Content-Type', 'application/pdf');
const readStream = fs.createReadStream(filePath);
readStream.on('open', () => readStream.pipe(res));
readStream.on('close', () => cleanupCallback());
readStream.on('error', () => {
cleanupCallback();
reject();
});
}
}
});
req.pipe(wkhtmltopdf.stdin);
}))
.catch((err) => {
const message = err && err.message;
if (message === 'unauthorized') {
res.statusCode = 401;
res.end('Unauthorized.');
} else if (message === 'timeout') {
res.statusCode = 408;
res.end('Request timeout.');
} else {
res.statusCode = 400;
res.end(wkhtmltopdfError || 'Unknown error.');
resolve({
filePath,
cleanupCallback,
});
}
});
}).then(({ filePath, cleanupCallback }) => new Promise((resolve, reject) => {
let finished = false;
function onError(err) {
finished = true;
cleanupCallback();
reject(err);
}
const options = readJson(req.query.options);
const params = [];
// Margins
const marginTop = parseInt(`${options.marginTop}`, 10);
params.push('-T', Number.isNaN(marginTop) ? 25 : marginTop);
const marginRight = parseInt(`${options.marginRight}`, 10);
params.push('-R', Number.isNaN(marginRight) ? 25 : marginRight);
const marginBottom = parseInt(`${options.marginBottom}`, 10);
params.push('-B', Number.isNaN(marginBottom) ? 25 : marginBottom);
const marginLeft = parseInt(`${options.marginLeft}`, 10);
params.push('-L', Number.isNaN(marginLeft) ? 25 : marginLeft);
// Header
if (options.headerCenter) {
params.push('--header-center', `${options.headerCenter}`);
}
if (options.headerLeft) {
params.push('--header-left', `${options.headerLeft}`);
}
if (options.headerRight) {
params.push('--header-right', `${options.headerRight}`);
}
if (options.headerFontName) {
params.push('--header-font-name', `${options.headerFontName}`);
}
if (options.headerFontSize) {
params.push('--header-font-size', `${options.headerFontSize}`);
}
// Footer
if (options.footerCenter) {
params.push('--footer-center', `${options.footerCenter}`);
}
if (options.footerLeft) {
params.push('--footer-left', `${options.footerLeft}`);
}
if (options.footerRight) {
params.push('--footer-right', `${options.footerRight}`);
}
if (options.footerFontName) {
params.push('--footer-font-name', `${options.footerFontName}`);
}
if (options.footerFontSize) {
params.push('--footer-font-size', `${options.footerFontSize}`);
}
// Page size
params.push('--page-size', !authorizedPageSizes.includes(options.pageSize) ? 'A4' : options.pageSize);
// Use a temp file as wkhtmltopdf can't access /dev/stdout on Amazon EC2 for some reason
params.push('--run-script', `${waitForJavaScript.toString()}waitForJavaScript()`);
params.push('--window-status', 'done');
const wkhtmltopdf = spawn(conf.values.wkhtmltopdfPath, params.concat('-', filePath), {
stdio: [
'pipe',
'ignore',
'pipe',
],
});
let timeoutId = setTimeout(function () {
timeoutId = null;
wkhtmltopdf.kill();
}, 50000);
wkhtmltopdf.on('error', onError);
wkhtmltopdf.stdin.on('error', onError);
wkhtmltopdf.stderr.on('data', (data) => {
wkhtmltopdfError += `${data}`;
});
wkhtmltopdf.on('close', (code) => {
if (!finished) {
clearTimeout(timeoutId);
if (!timeoutId) {
cleanupCallback();
reject(new Error('timeout'));
} else if (code) {
cleanupCallback();
reject();
} else {
res.set('Content-Type', 'application/pdf');
const readStream = fs.createReadStream(filePath);
readStream.on('open', () => readStream.pipe(res));
readStream.on('close', () => cleanupCallback());
readStream.on('error', () => {
cleanupCallback();
reject();
});
}
}
});
req.pipe(wkhtmltopdf.stdin);
}))
.catch((err) => {
console.error(err);
const message = err && err.message;
if (message === 'unauthorized') {
res.statusCode = 401;
res.end('Unauthorized.');
} else if (message === 'timeout') {
res.statusCode = 408;
res.end('Request timeout.');
} else {
res.statusCode = 400;
res.end(wkhtmltopdfError || 'Unknown error.');
}
});
};

View File

@ -1,116 +0,0 @@
const request = require('request');
const AWS = require('aws-sdk');
const verifier = require('google-id-token-verifier');
const conf = require('./conf');
const s3Client = new AWS.S3();
const cb = (resolve, reject) => (err, res) => {
if (err) {
reject(err);
} else {
resolve(res);
}
};
exports.getUser = id => new Promise((resolve, reject) => {
s3Client.getObject({
Bucket: conf.values.userBucketName,
Key: id,
}, cb(resolve, reject));
})
.then(
res => JSON.parse(`${res.Body}`),
(err) => {
if (err.code !== 'NoSuchKey') {
throw err;
}
},
);
exports.putUser = (id, user) => new Promise((resolve, reject) => {
s3Client.putObject({
Bucket: conf.values.userBucketName,
Key: id,
Body: JSON.stringify(user),
}, cb(resolve, reject));
});
exports.removeUser = id => new Promise((resolve, reject) => {
s3Client.deleteObject({
Bucket: conf.values.userBucketName,
Key: id,
}, cb(resolve, reject));
});
exports.getUserFromToken = idToken => new Promise((resolve, reject) => verifier
.verify(idToken, conf.values.googleClientId, cb(resolve, reject)))
.then(tokenInfo => exports.getUser(tokenInfo.sub));
exports.userInfo = (req, res) => exports.getUserFromToken(req.query.idToken)
.then(
user => res.send(Object.assign({
sponsorUntil: 0,
}, user)),
err => res
.status(400)
.send(err ? err.message || err.toString() : 'invalid_token'),
);
exports.paypalIpn = (req, res, next) => Promise.resolve()
.then(() => {
const userId = req.body.custom;
const paypalEmail = req.body.payer_email;
const gross = parseFloat(req.body.mc_gross);
let sponsorUntil;
if (gross === 5) {
sponsorUntil = Date.now() + (3 * 31 * 24 * 60 * 60 * 1000); // 3 months
} else if (gross === 15) {
sponsorUntil = Date.now() + (366 * 24 * 60 * 60 * 1000); // 1 year
} else if (gross === 25) {
sponsorUntil = Date.now() + (2 * 366 * 24 * 60 * 60 * 1000); // 2 years
} else if (gross === 50) {
sponsorUntil = Date.now() + (5 * 366 * 24 * 60 * 60 * 1000); // 5 years
}
if (
req.body.receiver_email !== conf.values.paypalReceiverEmail ||
req.body.payment_status !== 'Completed' ||
req.body.mc_currency !== 'USD' ||
(req.body.txn_type !== 'web_accept' && req.body.txn_type !== 'subscr_payment') ||
!userId || !sponsorUntil
) {
// Ignoring PayPal IPN
return res.end();
}
// Processing PayPal IPN
req.body.cmd = '_notify-validate';
return new Promise((resolve, reject) => request.post({
uri: conf.values.paypalUri,
form: req.body,
}, (err, response, body) => {
if (err) {
reject(err);
} else if (body !== 'VERIFIED') {
reject(new Error('PayPal IPN unverified'));
} else {
resolve();
}
}))
.then(() => exports.putUser(userId, {
paypalEmail,
sponsorUntil,
}))
.then(() => res.end());
})
.catch(next);
exports.checkSponsor = (idToken) => {
if (!conf.publicValues.allowSponsorship) {
return Promise.resolve(true);
}
if (!idToken) {
return Promise.resolve(false);
}
return exports.getUserFromToken(idToken)
.then(userInfo => userInfo && userInfo.sponsorUntil > Date.now(), () => false);
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -1,10 +1 @@
<svg
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 180">
<g>
<path d="M20.512,178.499c-3.359,-0.884 -6.258,-2.184 -8.931,-4.006c-2.257,-1.538 -5.556,-4.717 -6.811,-6.563c-1.532,-2.255 -3.293,-6.117 -4.011,-8.795c-0.732,-2.732 -0.743,-3.82 -0.757,-69.395c-0.013,-65.245 0.002,-66.679 0.72,-69.483c2.537,-9.916 10.395,-17.46 20.529,-19.711c2.914,-0.647 133.08,-0.76 136.223,-0.118c8.509,1.738 15.198,6.846 19.068,14.564c3.078,6.135 2.803,-0.617 2.943,72.231c0.09,46.349 0.007,65.808 -0.288,68.232c-1.386,11.345 -9.211,20.143 -20.471,23.019c-2.88,0.735 -3.882,0.746 -69.275,0.726c-63.227,-0.019 -66.474,-0.052 -68.939,-0.701l0,0Z" style="fill:#f57d00;fill-rule:nonzero;"/>
<path d="M115.162,144.835c8.064,-1.1 14.384,-4.333 20.313,-10.39c4.289,-4.382 6.974,-9.125 8.728,-15.419c0.729,-2.615 0.79,-3.888 0.924,-19.242c0.101,-11.588 0.017,-17.015 -0.285,-18.385c-0.437,-1.986 -1.677,-3.83 -3.092,-4.599c-0.435,-0.237 -3.224,-0.538 -6.198,-0.67c-4.982,-0.221 -5.54,-0.318 -7.113,-1.24c-2.494,-1.462 -3.181,-3.041 -3.188,-7.327c-0.013,-8.189 -3.421,-15.792 -10.155,-22.654c-4.797,-4.889 -10.149,-8.198 -16.257,-10.052c-1.462,-0.444 -4.736,-0.595 -15.702,-0.725c-17.207,-0.203 -21.026,0.15 -26.884,2.483c-10.8,4.302 -18.56,13.368 -21.39,24.99c-0.532,2.183 -0.635,5.682 -0.761,25.779c-0.157,25.177 0.016,28.874 1.59,33.864c1.299,4.122 2.611,6.648 5.313,10.234c5.146,6.83 12.86,11.763 20.572,13.156c3.67,0.663 48.948,0.829 53.585,0.197Z" style="fill:#fff;fill-rule:nonzero;"/>
<path d="M67.575,75.717c-4.123,-1.136 -5.663,-7.051 -2.633,-10.111c1.937,-1.955 2.472,-2.029 14.595,-2.029c10.883,0 11.249,0.023 12.848,0.831c2.31,1.167 3.314,2.812 3.314,5.432c0,2.367 -0.943,4.025 -3.046,5.357c-1.129,0.716 -1.804,0.76 -12.467,0.823c-6.584,0.039 -11.83,-0.087 -12.611,-0.303l0,0Z" style="fill:#f57d00;fill-rule:nonzero;"/>
<path d="M67.058,115.526c-1.769,-0.771 -3.417,-2.913 -3.702,-4.813c-0.272,-1.809 0.638,-4.296 2.032,-5.558c1.757,-1.59 2.528,-1.643 24.134,-1.66c22.227,-0.017 22.111,-0.027 24.219,1.941c2.976,2.78 2.349,7.728 -1.239,9.76l-3.686,0.6l-19.213,0.224c-16.883,0.198 -21.666,-0.111 -22.545,-0.494l0,0Z" style="fill:#f57d00;fill-rule:nonzero;"/>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 180"><path d="M20.512 178.499c-3.359-.884-6.258-2.184-8.931-4.006-2.257-1.538-5.556-4.717-6.811-6.563-1.532-2.255-3.293-6.117-4.011-8.795-.732-2.732-.743-3.82-.757-69.395-.013-65.245.002-66.679.72-69.483C3.259 10.341 11.117 2.797 21.251.546c2.914-.647 133.08-.76 136.223-.118 8.509 1.738 15.198 6.846 19.068 14.564 3.078 6.135 2.803-.617 2.943 72.231.09 46.349.007 65.808-.288 68.232-1.386 11.345-9.211 20.143-20.471 23.019-2.88.735-3.882.746-69.275.726-63.227-.019-66.474-.052-68.939-.701z" fill="#f57d00"/><path d="M115.162 144.835c8.064-1.1 14.384-4.333 20.313-10.39 4.289-4.382 6.974-9.125 8.728-15.419.729-2.615.79-3.888.924-19.242.101-11.588.017-17.015-.285-18.385-.437-1.986-1.677-3.83-3.092-4.599-.435-.237-3.224-.538-6.198-.67-4.982-.221-5.54-.318-7.113-1.24-2.494-1.462-3.181-3.041-3.188-7.327-.013-8.189-3.421-15.792-10.155-22.654-4.797-4.889-10.149-8.198-16.257-10.052-1.462-.444-4.736-.595-15.702-.725-17.207-.203-21.026.15-26.884 2.483-10.8 4.302-18.56 13.368-21.39 24.99-.532 2.183-.635 5.682-.761 25.779-.157 25.177.016 28.874 1.59 33.864 1.299 4.122 2.611 6.648 5.313 10.234 5.146 6.83 12.86 11.763 20.572 13.156 3.67.663 48.948.829 53.585.197z" fill="#fff"/><path d="M67.575 75.717c-4.123-1.136-5.663-7.051-2.633-10.111 1.937-1.955 2.472-2.029 14.595-2.029 10.883 0 11.249.023 12.848.831 2.31 1.167 3.314 2.812 3.314 5.432 0 2.367-.943 4.025-3.046 5.357-1.129.716-1.804.76-12.467.823-6.584.039-11.83-.087-12.611-.303zM67.058 115.526c-1.769-.771-3.417-2.913-3.702-4.813-.272-1.809.638-4.296 2.032-5.558 1.757-1.59 2.528-1.643 24.134-1.66 22.227-.017 22.111-.027 24.219 1.941 2.976 2.78 2.349 7.728-1.239 9.76l-3.686.6-19.213.224c-16.883.198-21.666-.111-22.545-.494z" fill="#f57d00"/></svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,3 @@
<svg t="1657361174041" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4922" width="32" height="32">
<path d="M259.072 303.104q30.72 0 52.736 22.016t22.016 53.76q0 30.72-22.016 52.736t-52.736 22.016q-31.744 0-53.248-22.016t-21.504-52.736q0-31.744 21.504-53.76t53.248-22.016zM864.256 57.344q43.008 0 69.12 28.672t26.112 65.536l0 550.912q0 23.552-16.896 39.936t-40.448 16.384l-70.656 0 0-123.904 44.032 0q11.264 0 19.456-8.192t8.192-20.48q0-11.264-8.192-19.456t-19.456-8.192l-44.032 0 0-79.872 44.032 0q11.264 0 19.456-8.192t8.192-19.456-8.192-19.968-19.456-8.704l-44.032 0 0-72.704 44.032 0q11.264 0 19.456-8.192t8.192-20.48q0-11.264-8.192-19.456t-19.456-8.192l-44.032 0 0-86.016q0-57.344-26.624-80.896t-90.112-23.552l-394.24 0 0-9.216q0-23.552 16.896-39.936t40.448-16.384l486.4 0zM692.224 184.32q39.936 0 57.856 23.04t17.92 59.904l0 565.248q0 23.552-19.456 43.52t-48.128 19.968l-572.416 0q-24.576 0-44.032-20.48t-19.456-48.128l0-575.488q0-29.696 16.384-48.64t43.008-18.944l568.32 0zM703.488 291.84q0-17.408-10.752-30.208t-34.304-12.8l-488.448 0q-4.096 0-11.264 1.536t-14.336 5.12-12.288 9.728-5.12 15.36l0 274.432q8.192 9.216 23.04 22.016t34.816 23.552 44.544 18.432 53.248 7.68q43.008 0 75.264-13.824t59.904-34.816 54.272-45.056 58.88-45.568 73.728-36.352 98.816-16.896l0-142.336z" p-id="4923" fill="#1296db"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

1
src/assets/iconGitea.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640" width="32" height="32"><path d="M395.9 484.2l-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5 21.2-17.9 33.8-11.8 17.2 8.3 27.1 13 27.1 13l-.1-109.2 16.7-.1.1 117.1s57.4 24.2 83.1 40.1c3.7 2.3 10.2 6.8 12.9 14.4 2.1 6.1 2 13.1-1 19.3l-61 126.9c-6.2 12.7-21.4 18.1-33.9 12z" fill="#fff"/><path d="M622.7 149.8c-4.1-4.1-9.6-4-9.6-4s-117.2 6.6-177.9 8c-13.3.3-26.5.6-39.6.7v117.2c-5.5-2.6-11.1-5.3-16.6-7.9 0-36.4-.1-109.2-.1-109.2-29 .4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5c-9.8-.6-22.5-2.1-39 1.5-8.7 1.8-33.5 7.4-53.8 26.9C-4.9 212.4 6.6 276.2 8 285.8c1.7 11.7 6.9 44.2 31.7 72.5 45.8 56.1 144.4 54.8 144.4 54.8s12.1 28.9 30.6 55.5c25 33.1 50.7 58.9 75.7 62 63 0 188.9-.1 188.9-.1s12 .1 28.3-10.3c14-8.5 26.5-23.4 26.5-23.4S547 483 565 451.5c5.5-9.7 10.1-19.1 14.1-28 0 0 55.2-117.1 55.2-231.1-1.1-34.5-9.6-40.6-11.6-42.6zM125.6 353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6 321.8 60 295.4c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5 38.5-30c13.8-3.7 31-3.1 31-3.1s7.1 59.4 15.7 94.2c7.2 29.2 24.8 77.7 24.8 77.7s-26.1-3.1-43-9.1zm300.3 107.6s-6.1 14.5-19.6 15.4c-5.8.4-10.3-1.2-10.3-1.2s-.3-.1-5.3-2.1l-112.9-55s-10.9-5.7-12.8-15.6c-2.2-8.1 2.7-18.1 2.7-18.1L322 273s4.8-9.7 12.2-13c.6-.3 2.3-1 4.5-1.5 8.1-2.1 18 2.8 18 2.8L467.4 315s12.6 5.7 15.3 16.2c1.9 7.4-.5 14-1.8 17.2-6.3 15.4-55 113.1-55 113.1z" fill="#609926"/><path d="M326.8 380.1c-8.2.1-15.4 5.8-17.3 13.8-1.9 8 2 16.3 9.1 20 7.7 4 17.5 1.8 22.7-5.4 5.1-7.1 4.3-16.9-1.8-23.1l24-49.1c1.5.1 3.7.2 6.2-.5 4.1-.9 7.1-3.6 7.1-3.6 4.2 1.8 8.6 3.8 13.2 6.1 4.8 2.4 9.3 4.9 13.4 7.3.9.5 1.8 1.1 2.8 1.9 1.6 1.3 3.4 3.1 4.7 5.5 1.9 5.5-1.9 14.9-1.9 14.9-2.3 7.6-18.4 40.6-18.4 40.6-8.1-.2-15.3 5-17.7 12.5-2.6 8.1 1.1 17.3 8.9 21.3 7.8 4 17.4 1.7 22.5-5.3 5-6.8 4.6-16.3-1.1-22.6 1.9-3.7 3.7-7.4 5.6-11.3 5-10.4 13.5-30.4 13.5-30.4.9-1.7 5.7-10.3 2.7-21.3-2.5-11.4-12.6-16.7-12.6-16.7-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3 4.7-9.7 9.4-19.3 14.1-29-4.1-2-8.1-4-12.2-6.1-4.8 9.8-9.7 19.7-14.5 29.5-6.7-.1-12.9 3.5-16.1 9.4-3.4 6.3-2.7 14.1 1.9 19.8l-24.6 50.4z" fill="#609926"/></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

5
src/assets/iconGitee.svg Normal file
View File

@ -0,0 +1,5 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<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>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -1,4 +1,3 @@
<svg
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 58">
<g fill="none" fill-rule="evenodd">

Before

Width:  |  Height:  |  Size: 1011 B

After

Width:  |  Height:  |  Size: 1010 B

View File

@ -0,0 +1,6 @@
<svg
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 58">
<g fill="none" fill-rule="evenodd">
<path d="m1324.62 140c-16.355 0-29.616 13.219-29.616 29.527 0 13.04 8.485 24.11 20.256 28.01 1.482.27 2.02-.642 2.02-1.425 0-.7-.025-2.557-.04-5.02-8.238 1.784-9.976-3.958-9.976-3.958-1.347-3.411-3.289-4.317-3.289-4.317-2.689-1.832.204-1.796.204-1.796 2.973.21 4.536 3.043 4.536 3.043 2.642 4.511 6.931 3.208 8.62 2.454.269-1.909 1.033-3.21 1.88-3.948-6.576-.745-13.491-3.279-13.491-14.592 0-3.223 1.155-5.858 3.049-7.922-.305-.747-1.322-3.748.289-7.814 0 0 2.487-.794 8.145 3.03 2.362-.656 4.896-.982 7.415-.995 2.515.013 5.05.339 7.415.995 5.655-3.821 8.136-3.03 8.136-3.03 1.616 4.065.6 7.07.295 7.814 1.898 2.064 3.045 4.7 3.045 7.922 0 11.343-6.925 13.838-13.524 14.569 1.064.912 2.01 2.713 2.01 5.468 0 3.946-.036 7.13-.036 8.098 0 .79.533 1.709 2.036 1.421 11.758-3.913 20.238-14.971 20.238-28.01 0-16.309-13.262-29.527-29.62-29.527" transform="translate(-1295-140)" fill="#fff"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1007 B

25
src/assets/iconSmms.svg Normal file
View File

@ -0,0 +1,25 @@
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve"> <image id="image0" width="32" height="32" x="0" y="0"
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAABGdBTUEAALGPC/xhBQAAACBjSFJN
AAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAB7FBMVEUAAAB3AP+IAP+IAP+H
AP+IAP+IAP+GAP+HAP+AAP+GAP+IAP+IAP+HAP+KAP+OAP+IAP+IAP+IAP9xAP+IAP+IAP+KAP+F
AP+HAP+IAP+IAP+DAP+AAP+HAP+HAP+AAP+IAP+IAP+JAP+HAP+IAP+HAP+IAP+HAP+HAP+IAP+J
AP+GAP+IAP+IAP+IAP+HAP+HAP+HAP+HAP+IAP+IAP+IAP+HAP+IAP+HAP+IAP+IAP+HAP+HAP+H
AP+DAP+HAP+DAP+HAP+IAP+IAP+JAv+mQv+8cP+9c/+sTv+MCf+iOP/s1//////05/+mQf+hNv/6
9v/37/+QE//kx//Yrf+pR/+nRP/YrP+1Yf+SF//+/v+TGP/Df/+gNP/Egf+lPv+5af+aKP/8+P+j
O//asf/27f+RFf+IAv/x4//37v/NlP/x4v+jOv+4Z//Rn/+LB/+IAf/Xqv+/eP+LBv/Wqf+7b/+3
Zv/euP+vVf/Nlv/8+v+bKf/Dgf/Ghv/Ol/+eMf/9/P+vU/+QEf/48f/Sof+UG//ctP/48P/v3v+M
CP/z5f/16/+aJ/+SFv/Zr//ozv/euf+cKv+WHv+tUP/r1P+mQP/Egv/Lkf+PEP/v3P/Jjf/duf/+
/f/fu/+kPf+2Y//Cff/Bev+xWP+TGf+sor+CAAAAQ3RSTlMAD1qczOv7nFkOJp72nSUJjfqLCdTS
IzLqL+kjCtXRCI+JJ/v5oJoR9/QNXVafmNDI8Oj++PzszJsP9SSO0iWKJ5/LDpe2AgAAAAFiS0dE
TPdvEPMAAAAHdElNRQfmBhwAJyh2NlUnAAAByUlEQVQ4y2NggANGJmYWVjY2VhZ2Dk4GTMDFzeMM
Bzy8fGjS/AKCzihAUEgYWZ5PxBkDiIoh5MVZnbEACUm4fiR5F1c3dw9PCFtKGiIvI4uQ9/L2AQJf
PwhPTh6sQAEh7x/gAwaBQRC+IkheSRmhINjHJyQ0LNzHJwLCVwFZooqQj4zyiYp0do728YmBiqgx
MKhrIBTEAk2Pc3aO9/FJgIpoajFoI3ksEaggKdk5xSc1DSakw6CLpCA9A6giM8snOwcupMegjxw2
uWA/5OUjRAwYDJEVFBSCFEQVIUSMGIxRgre4BKyiFC5gwmCKkC0rr3AuqASpqKqGiZkymMHla3x8
amG21MEEzRjMYcz6Bh+fRiDd1AxU4A0TZWFghjFbgOKtIEYbkNEOE7VgYIIxO4DinTAFXTBRDgZL
WFLsBor3AOlekBV9UEErYPLlhrL7geITJk6aPAXkz6lQQWZQcoYm12k+CDAdKi9oDUoQNhDOjJkw
6ahZLlAFQpAkB03SrbOjQNJz5s6DudAWmvTtYIm2Z/6ChYsWw0NOSozYZI8949hKI2ctGQFlVGlB
e5SsBwQOjkiZ14rZDkv+tmRiNjdjU2Z1skDO/gDtseT0Fzic2AAAACV0RVh0ZGF0ZTpjcmVhdGUA
MjAyMi0wNi0yOFQwMDozOTo0MCswMDowMPmC6NgAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjItMDYt
MjhUMDA6Mzk6NDArMDA6MDCI31BkAAAAAElFTkSuQmCC" />
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -1 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><path d="M23.997,6.002c0,-3.311 -2.688,-5.999 -5.999,-5.999l-11.999,0c-3.311,0 -5.999,2.688 -5.999,5.999l0,11.999c0,3.311 2.688,5.999 5.999,5.999l11.999,0c3.311,0 5.999,-2.688 5.999,-5.999l0,-11.999Z" style="fill:none;"/><clipPath id="_clip1"><path d="M23.997,6.002c0,-3.311 -2.688,-5.999 -5.999,-5.999l-11.999,0c-3.311,0 -5.999,2.688 -5.999,5.999l0,11.999c0,3.311 2.688,5.999 5.999,5.999l11.999,0c3.311,0 5.999,-2.688 5.999,-5.999l0,-11.999Z"/></clipPath><g clip-path="url(#_clip1)"><path d="M23.997,0.003l-24,0l12,12l12,-12Z" style="fill:#ffd700;"/><path d="M-0.003,0.003l0,24l12,-12l-12,-12Z" style="fill:#a5c700;"/><path d="M-0.003,24.003l24,0l-12,-12l-12,12Z" style="fill:#ff8a00;"/><path d="M23.997,24.003l0,-24l-12,12l12,12Z" style="fill:#66aefd;"/><path d="M22.497,-1.497l-10.5,10.497l3,3.003l10.5,-10.5l-3,-3Z" style="fill:url(#_Linear2);"/><path d="M25.499,22.503l-10.498,-10.5l-3.002,3l10.5,10.5l3,-3Z" style="fill:url(#_Linear3);"/><path d="M1.497,25.501l10.5,-10.497l-3,-3.003l-10.5,10.5l3,3Z" style="fill:url(#_Linear4);"/><path d="M-1.503,1.503l10.498,10.5l3.002,-3l-10.5,-10.5l-3,3Z" style="fill:url(#_Linear5);"/></g><path d="M21.75,5.852c0,-2.195 -1.782,-3.977 -3.977,-3.977l-11.546,0c-2.195,0 -3.977,1.782 -3.977,3.977l0,11.546c0,2.195 1.782,3.977 3.977,3.977l11.546,0c2.195,0 3.977,-1.782 3.977,-3.977l0,-11.546Z" style="fill:#fff;"/><path d="M4.633,6.013l1.37,0l0,-1.828l1.399,0l0,1.828l1.696,0l0,-1.828l1.399,0l0,1.828l1.37,0l0,1.691l-1.37,0l0,1.902l1.37,0l0,1.69l-1.37,0l0,1.829l-1.399,0l0,-1.829l-1.696,0l0,1.829l-1.399,0l0,-1.829l-1.37,0l0,-1.69l1.37,0l0,-1.902l-1.37,0l0,-1.691Zm2.769,1.691l0,1.902l1.696,0l0,-1.902l-1.696,0Z" style="fill:#737373;"/><defs><linearGradient id="_Linear2" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-2.99995,-3,3,-2.99995,23.9974,3.00265)"><stop offset="0" style="stop-color:#66aefd;stop-opacity:1"/><stop offset="1" style="stop-color:#ffd700;stop-opacity:1"/></linearGradient><linearGradient id="_Linear3" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(3,-2.99995,2.99995,3,20.9987,24.0027)"><stop offset="0" style="stop-color:#ff8a00;stop-opacity:1"/><stop offset="1" style="stop-color:#66aefd;stop-opacity:1"/></linearGradient><linearGradient id="_Linear4" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(2.99995,3,-3,2.99995,-0.00255928,21.0013)"><stop offset="0" style="stop-color:#a5c700;stop-opacity:1"/><stop offset="1" style="stop-color:#ff8a00;stop-opacity:1"/></linearGradient><linearGradient id="_Linear5" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-3,2.99995,-2.99995,-3,2.99744,0.00265252)"><stop offset="0" style="stop-color:#ffd700;stop-opacity:1"/><stop offset="1" style="stop-color:#a5c700;stop-opacity:1"/></linearGradient></defs></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill-rule="evenodd" stroke-linejoin="round" clip-rule="evenodd" stroke-miterlimit="1.414"><path d="M 24 6 c 0 -3.3 -2.7 -6 -6 -6 H 6 C 2.7 0 0 2.7 0 6 v 12 c 0 3.3 2.7 6 6 6 h 12 c 3.3 0 6 -2.7 6 -6 V 6 z" fill="none"/><clipPath id="prefix__a"><path d="M 24 6 c 0 -3.3 -2.7 -6 -6 -6 H 6 C 2.7 0 0 2.7 0 6 v 12 c 0 3.3 2.7 6 6 6 h 12 c 3.3 0 6 -2.7 6 -6 V 6 z"/></clipPath><g clip-path="url(#prefix__a)"><path d="M 24 0 H 0 l 12 12 L 24 0 z" fill="gold"/><path d="M 0 0 v 24 l 12 -12 L 0 0 z" fill="#a5c700"/><path d="M 0 24 h 24 L 12 12 L 0 24 z" fill="#ff8a00"/><path d="M 24 24 V 0 L 12 12 l 12 12 z" fill="#66aefd"/><path d="M 22.5 -1.5 L 12 9 l 3 3 L 25.5 1.5 l -3 -3 z" fill="url(#prefix___Linear2)"/><path d="M 25.5 22.5 L 15 12 l -3 3 l 10.5 10.5 l 3 -3 z" fill="url(#prefix___Linear3)"/><path d="M 1.5 25.5 L 12 15 l -3 -3 l -10.5 10.5 l 3 3 z" fill="url(#prefix___Linear4)"/><path d="M -1.5 1.5 L 9 12 l 3 -3 L 1.5 -1.5 l -3 3 z" fill="url(#prefix___Linear5)"/></g><path d="M 21.8 5.9 c 0 -2.2 -1.8 -4 -4 -4 H 6.3 c -2.2 0 -4 1.8 -4 4 v 11.5 c 0 2.2 1.8 4 4 4 h 11.5 c 2.2 0 4 -1.8 4 -4 V 5.9 z" fill="#ffffff"/><path d="M 4.6 6 H 6 V 4.2 h 1.4 V 6 h 1.7 V 4.2 h 1.4 V 6 h 1.4 v 1.7 h -1.4 v 1.9 h 1.4 v 1.7 h -1.4 v 1.8 H 9.1 v -1.8 H 7.4 v 1.8 H 6 v -1.8 H 4.6 V 9.6 H 6 V 7.7 H 4.6 V 6 z m 2.8 1.7 v 1.9 h 1.7 V 7.7 H 7.4 z M 10 14 v 6 h 4 v -2 h -2 v -2 h 2 v -2 H 10 z m 5 0 v 6 h 2 v -3 l 1 3 h 2 v -6 h -2 v 3 l -1 -3 h -2 z M 7 18 l 0 2 l 2 0 l 0 -2 l -2 0 Z" fill="#737373"/><defs><linearGradient id="prefix___Linear2" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-2.99995 -3 3 -2.99995 23.997 3.003)"><stop offset="0" stop-color="#66aefd"/><stop offset="1" stop-color="gold"/></linearGradient><linearGradient id="prefix___Linear3" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(3 -2.99995 2.99995 3 20.999 24.003)"><stop offset="0" stop-color="#ff8a00"/><stop offset="1" stop-color="#66aefd"/></linearGradient><linearGradient id="prefix___Linear4" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(2.99995 3 -3 2.99995 -.003 21.001)"><stop offset="0" stop-color="#a5c700"/><stop offset="1" stop-color="#ff8a00"/></linearGradient><linearGradient id="prefix___Linear5" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-3 2.99995 -2.99995 -3 2.997 .003)"><stop offset="0" stop-color="gold"/><stop offset="1" stop-color="#a5c700"/></linearGradient></defs></svg>

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@ -22,6 +22,8 @@ import networkSvc from '../services/networkSvc';
import tempFileSvc from '../services/tempFileSvc';
import store from '../store';
import './common/vueGlobals';
import utils from '../services/utils';
import providerRegistry from '../services/providers/common/providerRegistry';
const themeClasses = {
light: ['app--light'],
@ -49,11 +51,49 @@ export default {
close() {
tempFileSvc.close();
},
//
viewFileByPath(path) {
// md
if (!path) {
return;
}
const currDirNode = store.getters['explorer/selectedNodeFolder'];
if (path.slice(-3) === '.md') {
const rootNode = store.getters['explorer/rootNode'];
const node = utils.findNodeByPath(rootNode, currDirNode, path);
if (!node) {
return;
}
store.commit('explorer/setSelectedId', node.item.id);
// Prevent from freezing the UI while loading the file
setTimeout(() => {
store.commit('file/setCurrentId', node.item.id);
}, 10);
} else {
const workspace = store.getters['workspace/currentWorkspace'];
const provider = providerRegistry.providersById[workspace.providerId];
if (provider == null) {
return;
}
const absolutePath = utils.getAbsoluteFilePath(currDirNode, path);
const url = provider.getFilePathUrl(absolutePath);
if (url) {
window.open(url, '_blank');
}
}
},
},
async created() {
window.viewFileByPath = this.viewFileByPath;
try {
await syncSvc.init();
await networkSvc.init();
// store
const editTheme = localStorage.getItem('theme/currEditTheme');
store.dispatch('theme/setEditTheme', editTheme || 'default');
// store
const previewTheme = localStorage.getItem('theme/currPreviewTheme');
store.dispatch('theme/setPreviewTheme', previewTheme || 'default');
this.ready = true;
tempFileSvc.setReady();
} catch (err) {

View File

@ -1,24 +1,24 @@
<template>
<div class="button-bar">
<div class="button-bar__inner button-bar__inner--top">
<button class="button-bar__button button-bar__button--navigation-bar-toggler button" :class="{ 'button-bar__button--on': layoutSettings.showNavigationBar }" v-if="!light" @click="toggleNavigationBar()" v-title="'Toggle navigation bar'">
<button class="button-bar__button button-bar__button--navigation-bar-toggler button" :class="{ 'button-bar__button--on': layoutSettings.showNavigationBar }" v-if="!light" @click="toggleNavigationBar()" v-title="'切换导航栏'">
<icon-navigation-bar></icon-navigation-bar>
</button>
<button class="button-bar__button button-bar__button--side-preview-toggler button" :class="{ 'button-bar__button--on': layoutSettings.showSidePreview }" tour-step-anchor="editor" @click="toggleSidePreview()" v-title="'Toggle side preview'">
<button class="button-bar__button button-bar__button--side-preview-toggler button" :class="{ 'button-bar__button--on': layoutSettings.showSidePreview }" tour-step-anchor="editor" @click="toggleSidePreview()" v-title="'切换侧边预览'">
<icon-side-preview></icon-side-preview>
</button>
<button class="button-bar__button button-bar__button--editor-toggler button" @click="toggleEditor(false)" v-title="'Reader mode'">
<button class="button-bar__button button-bar__button--editor-toggler button" @click="toggleEditor(false)" v-title="'阅读模式'">
<icon-eye></icon-eye>
</button>
</div>
<div class="button-bar__inner button-bar__inner--bottom">
<button class="button-bar__button button-bar__button--focus-mode-toggler button" :class="{ 'button-bar__button--on': layoutSettings.focusMode }" @click="toggleFocusMode()" v-title="'Toggle focus mode'">
<button class="button-bar__button button-bar__button--focus-mode-toggler button" :class="{ 'button-bar__button--on': layoutSettings.focusMode }" @click="toggleFocusMode()" v-title="'切换对焦模式'">
<icon-target></icon-target>
</button>
<button class="button-bar__button button-bar__button--scroll-sync-toggler button" :class="{ 'button-bar__button--on': layoutSettings.scrollSync }" @click="toggleScrollSync()" v-title="'Toggle scroll sync'">
<button class="button-bar__button button-bar__button--scroll-sync-toggler button" :class="{ 'button-bar__button--on': layoutSettings.scrollSync }" @click="toggleScrollSync()" v-title="'切换滚动同步'">
<icon-scroll-sync></icon-scroll-sync>
</button>
<button class="button-bar__button button-bar__button--status-bar-toggler button" :class="{ 'button-bar__button--on': layoutSettings.showStatusBar }" @click="toggleStatusBar()" v-title="'Toggle status bar'">
<button class="button-bar__button button-bar__button--status-bar-toggler button" :class="{ 'button-bar__button--on': layoutSettings.showStatusBar }" @click="toggleStatusBar()" v-title="'切换状态栏'">
<icon-status-bar></icon-status-bar>
</button>
</div>

View File

@ -7,11 +7,12 @@ import Prism from 'prismjs';
import cledit from '../services/editor/cledit';
export default {
props: ['value', 'lang', 'disabled'],
props: ['value', 'lang', 'disabled', 'scrollClass'],
mounted() {
const preElt = this.$el;
let scrollElt = preElt;
while (scrollElt && !scrollElt.classList.contains('modal')) {
const scrollCls = this.scrollClass || 'modal';
while (scrollElt && !scrollElt.classList.contains(scrollCls)) {
scrollElt = scrollElt.parentNode;
}
if (scrollElt) {
@ -43,6 +44,10 @@ export default {
overflow: auto;
padding: 0.2em 0.4em;
.app--dark & {
caret-color: $editor-color-dark-low;
}
* {
line-height: $line-height-base;
font-size: inherit !important;

View File

@ -1,5 +1,5 @@
<template>
<div class="editor">
<div class="editor" ondrop="return false;">
<pre class="editor__inner markdown-highlighting" :style="{padding: styles.editorPadding}" :class="{monospaced: computedSettings.editor.monospacedFontOnly}"></pre>
<div class="gutter" :style="{left: styles.editorGutterLeft + 'px'}">
<comment-list v-if="styles.editorGutterWidth"></comment-list>
@ -13,6 +13,9 @@ import { mapGetters } from 'vuex';
import CommentList from './gutters/CommentList';
import EditorNewDiscussionButton from './gutters/EditorNewDiscussionButton';
import store from '../store';
import editorSvc from '../services/editorSvc';
import imageSvc from '../services/imageSvc';
import utils from '../services/utils';
export default {
components: {
@ -30,7 +33,51 @@ export default {
'computedSettings',
]),
},
methods: {
async processUpload(items) {
let file = null;
if (!items || items.length === 0) {
return;
}
for (let i = 0; i < items.length; i += 1) {
if (items[i].type.indexOf('image') !== -1) {
file = items[i].getAsFile();
break;
}
}
if (!file) {
return;
}
const imgId = utils.uid();
store.dispatch('img/setCurrImgId', imgId);
editorSvc.pagedownEditor.uiManager.doClick('imageUploading');
try {
const { url, error } = await imageSvc.updateImg(file);
//
if (error) {
editorSvc.clEditor.replaceAll(`[图片上传中...(image-${imgId})]`, `[图片上传失败...(image-${imgId})]`);
store.dispatch('notification/error', error);
return;
}
editorSvc.clEditor.replaceAll(`[图片上传中...(image-${imgId})]`, `![输入图片说明](${url})`);
} catch (err) {
console.error(err); // eslint-disable-line no-console
editorSvc.clEditor.replaceAll(`[图片上传中...(image-${imgId})]`, `[图片上传失败...(image-${imgId})]`);
store.dispatch('notification/error', err);
}
},
},
mounted() {
//
const currImgStorageStr = localStorage.getItem('img/checkedStorage');
if (currImgStorageStr) {
store.commit('img/changeCheckedStorage', JSON.parse(currImgStorageStr));
}
//
const workspaceImgPath = localStorage.getItem('img/workspaceImgPath');
if (workspaceImgPath) {
store.commit('img/setWorkspaceImgPath', JSON.parse(workspaceImgPath));
}
const editorElt = this.$el.querySelector('.editor__inner');
const onDiscussionEvt = cb => (evt) => {
let elt = evt.target;
@ -56,6 +103,15 @@ export default {
store.commit('discussion/setCurrentDiscussionId', discussionId);
}));
editorElt.addEventListener('drop', (event) => {
const transItems = event.dataTransfer.items;
this.processUpload(transItems);
});
editorElt.addEventListener('paste', (event) => {
const pasteItems = (event.clipboardData || window.clipboardData).items;
this.processUpload(pasteItems);
});
this.$watch(
() => store.state.discussion.currentDiscussionId,
(discussionId, oldDiscussionId) => {

View File

@ -0,0 +1,197 @@
<template>
<div class="editor-in-page-buttons">
<ul>
<li :title="`查找 ${mod}+F`">
<a @click="showFind"><icon-search></icon-search></a>
</li>
<li :title="`替换 ${mod}+Alt+F`">
<a @click="showFindReplace"><icon-find-replace></icon-find-replace></a>
</li>
<li title="切换编辑主题">
<dropdown-menu :selected="selectedTheme" :options="allThemes" :closeOnItemClick="false" @change="changeTheme">
<icon-select-theme></icon-select-theme>
</dropdown-menu>
</li>
<li class="after">
<icon-ellipsis></icon-ellipsis>
</li>
</ul>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import store from '../store';
import editorSvc from '../services/editorSvc';
import DropdownMenu from './common/DropdownMenu';
const mod = /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? 'Meta' : 'Ctrl';
export default {
components: {
DropdownMenu,
},
data: () => ({
mod,
allThemes: [{
name: '默认主题',
value: 'default',
}, {
name: '天蓝黑',
value: 'azure',
}, {
name: '冰山黑',
value: 'iceberg_contrast',
}, {
name: '黎明白',
value: 'dawn',
}, {
name: '孔雀黑',
value: 'peacock',
}, {
name: '薄荷黑',
value: 'mintchoc',
}, {
name: '薄荷绿',
value: 'spearmint',
}, {
name: '暗蓝黑',
value: 'slate',
}, {
name: '文墨黑',
value: 'carbonight',
}, {
name: '日光白',
value: 'solarized_light',
}, {
name: '咖啡黑',
value: 'espresso_libre',
}, {
name: '薰衣草黑',
value: 'lavender',
}, {
name: '耀斑黑',
value: 'solarflare',
}, {
name: 'Clouds白',
value: 'clouds',
}, {
name: 'Clouds黑',
value: 'clouds_midnight',
}, {
name: 'GitHub白',
value: 'github',
}, {
name: '自定义',
value: 'custom',
}],
}),
computed: {
...mapGetters('theme', [
'currEditTheme',
'customEditThemeStyle',
]),
selectedTheme() {
return {
value: this.currEditTheme || 'default',
};
},
},
methods: {
...mapActions('data', [
'toggleSideBar',
]),
showFind() {
store.dispatch('findReplace/open', {
type: 'find',
findText: editorSvc.clEditor.selectionMgr.hasFocus() &&
editorSvc.clEditor.selectionMgr.getSelectedText(),
});
},
showFindReplace() {
store.dispatch('findReplace/open', {
type: 'replace',
findText: editorSvc.clEditor.selectionMgr.hasFocus() &&
editorSvc.clEditor.selectionMgr.getSelectedText(),
});
},
async changeTheme(item) {
await store.dispatch('theme/setEditTheme', item.value);
//
if (item.value === 'custom' && !this.customEditThemeStyle) {
this.toggleSideBar(true);
store.dispatch('data/setSideBarPanel', 'editTheme');
}
},
},
};
</script>
<style lang="scss">
@import '../styles/variables.scss';
.editor-in-page-buttons {
position: absolute;
top: 0;
left: -108px;
height: 34px;
padding: 5px;
background-color: rgba(84, 96, 114, 0.4);
border-radius: $border-radius-base;
transition: 0.5s;
display: flex;
.dropdown-menu {
display: none;
.dropdown-menu-items {
right: unset;
left: 0;
}
}
&:active,
&:focus,
&:hover {
left: 0;
transition: 0.5s;
background-color: #546072;
.dropdown-menu {
display: block;
}
}
ul {
padding: 0;
margin-left: 10px;
line-height: 20px;
li {
width: 16px;
display: inline-block;
vertical-align: middle;
list-style: none;
cursor: pointer;
font-size: 14px;
margin-right: 10px;
}
}
.icon {
color: #dea731;
opacity: 0.7;
&:active,
&:focus,
&:hover {
opacity: 1;
}
}
.after {
margin-left: 0;
margin-right: -6px;
}
}
</style>

View File

@ -1,27 +1,49 @@
<template>
<div class="explorer flex flex--column">
<div class="side-title flex flex--row flex--space-between">
<div class="flex flex--row">
<button class="side-title__button side-title__button--new-file button" @click="newItem()" v-title="'New file'">
<div class="flex flex--row" v-if="!showSearch">
<button class="side-title__button side-title__button--new-file button" @click="newItem()" v-title="'创建文件'">
<icon-file-plus></icon-file-plus>
</button>
<button class="side-title__button side-title__button--new-folder button" @click="newItem(true)" v-title="'New folder'">
<button class="side-title__button side-title__button--new-folder button" @click="newItem(true)" v-title="'创建文件夹'">
<icon-folder-plus></icon-folder-plus>
</button>
<button class="side-title__button side-title__button--delete button" @click="deleteItem()" v-title="'Delete'">
<button class="side-title__button side-title__button--delete button" @click="deleteItem()" v-title="'删除'">
<icon-delete></icon-delete>
</button>
<button class="side-title__button side-title__button--rename button" @click="editItem()" v-title="'Rename'">
<button class="side-title__button side-title__button--rename button" @click="editItem()" v-title="'重命名'">
<icon-pen></icon-pen>
</button>
<button class="side-title__button side-title__button--search button" @click="toSearch()" v-title="'搜索文件'">
<icon-file-search></icon-file-search>
</button>
</div>
<button class="side-title__button side-title__button--close button" @click="toggleExplorer(false)" v-title="'Close explorer'">
<div class="flex flex--row" v-else>
<button class="side-title__button button" @click="back()" v-title="'返回资源管理器'">
<icon-dots-horizontal></icon-dots-horizontal>
</button>
<div class="side-title__title">
搜索文件
</div>
</div>
<button class="side-title__button side-title__button--close button" @click="toggleExplorer(false)" v-title="'关闭资源管理器'">
<icon-close></icon-close>
</button>
</div>
<div class="explorer__tree" :class="{'explorer__tree--new-item': !newChildNode.isNil}" v-if="!light" tabindex="0" @keydown.delete="deleteItem()">
<div class="explorer__tree" :class="{'explorer__tree--new-item': !newChildNode.isNil}" v-if="!light" v-show="!showSearch" tabindex="0" @keydown.delete="deleteItem()">
<explorer-node :node="rootNode" :depth="0"></explorer-node>
</div>
<div class="explorer__search" tabindex="0" v-if="!light && showSearch">
<input type="text" v-model="searchText" class="text-input" placeholder="请输入关键字回车" @keyup.enter="search" />
<div class="explorer__search-list">
<div class="search-tips" v-if="searching">正在查询中...</div>
<a class="menu-entry button flex flex--row flex--align-center" :class="{'search-node--selected': currentFileId === fileItem.id}"
v-for="fileItem in searchItems" :key="fileItem.id" @click="clickSearch(fileItem)" href="javascript:void(0)">
{{ fileItem.name }}
</a>
<div class="search-tips">最多返回匹配的50个文档</div>
</div>
</div>
</div>
</template>
@ -30,11 +52,22 @@ import { mapState, mapGetters, mapActions } from 'vuex';
import ExplorerNode from './ExplorerNode';
import explorerSvc from '../services/explorerSvc';
import store from '../store';
import MenuEntry from './menus/common/MenuEntry';
import localDbSvc from '../services/localDbSvc';
import badgeSvc from '../services/badgeSvc';
export default {
components: {
ExplorerNode,
MenuEntry,
},
data: () => ({
currentFileId: '',
showSearch: false,
searching: false,
searchText: '',
searchItems: [],
}),
computed: {
...mapState([
'light',
@ -46,6 +79,7 @@ export default {
'rootNode',
'selectedNode',
]),
workspaceId: () => store.getters['workspace/currentWorkspace'].id,
},
methods: {
...mapActions('data', [
@ -59,11 +93,69 @@ export default {
store.commit('explorer/setEditingId', node.item.id);
}
},
back() {
this.showSearch = false;
},
toSearch() {
this.showSearch = true;
},
search() {
this.searchItems = [];
if (!this.searchText) {
return;
}
this.searching = true;
const allFileById = {};
const filterIds = [];
localDbSvc.getWorkspaceItems(this.workspaceId, (item) => {
if (item.type !== 'file' && item.type !== 'content') {
return;
}
if (item.type === 'file') {
allFileById[item.id] = item;
}
if (filterIds.length >= 50) {
return;
}
const fileId = item.id.split('/')[0];
//
if (filterIds.indexOf(fileId) > -1) {
return;
}
if (item.name && item.name.indexOf(this.searchText) > -1) {
filterIds.push(fileId);
}
if (item.text && item.text.indexOf(this.searchText) > -1) {
filterIds.push(fileId);
}
}, () => {
filterIds.forEach((it) => {
const file = allFileById[it];
if (file) {
this.searchItems.push(file);
}
});
this.searching = false;
badgeSvc.addBadge('searchFile');
});
},
clickSearch(item) {
const node = store.getters['explorer/nodeMap'][item.id];
if (!node) {
return;
}
store.commit('explorer/setSelectedId', item.id);
// Prevent from freezing the UI while loading the file
setTimeout(() => {
store.commit('file/setCurrentId', item.id);
}, 10);
},
},
created() {
this.$watch(
() => store.getters['file/current'].id,
(currentFileId) => {
this.currentFileId = currentFileId;
store.commit('explorer/setSelectedId', currentFileId);
store.dispatch('explorer/openNode', currentFileId);
}, {
@ -75,6 +167,8 @@ export default {
</script>
<style lang="scss">
@import '../styles/variables.scss';
.explorer,
.explorer__tree {
height: 100%;
@ -89,4 +183,43 @@ export default {
cursor: auto;
}
}
.explorer__search {
overflow: auto;
.explorer__search-list {
margin-top: 10px;
}
.menu-entry {
font-size: 14px;
padding: 5px;
}
.menu-entry__icon {
width: 0;
margin-left: 0;
border-bottom: 1px solid $hr-color;
}
.search-tips {
font-size: 10px;
background-color: rgba(255, 173, 51, 0.14902);
padding: 5px;
text-align: center;
}
.search-node--selected {
background-color: rgba(0, 0, 0, 0.2);
.app--dark & {
background-color: rgba(0, 0, 0, 0.4);
}
&:focus {
background-color: #39f;
color: #fff;
}
}
}
</style>

View File

@ -14,6 +14,7 @@
</div>
<explorer-node v-for="node in node.files" :key="node.item.id" :node="node" :depth="depth + 1"></explorer-node>
</div>
<button ref="copyId" v-clipboard="copyPath()" @click="info('路径已复制到剪切板!')" style="display: none;"></button>
</div>
</template>
@ -23,6 +24,7 @@ import workspaceSvc from '../services/workspaceSvc';
import explorerSvc from '../services/explorerSvc';
import store from '../store';
import badgeSvc from '../services/badgeSvc';
import utils from '../services/utils';
export default {
name: 'explorer-node', // Required for recursivity
@ -80,6 +82,9 @@ export default {
...mapActions('explorer', [
'setDragTarget',
]),
...mapActions('notification', [
'info',
]),
select(id = this.node.item.id, doOpen = true) {
const node = store.getters['explorer/nodeMap'][id];
if (!node) {
@ -144,6 +149,11 @@ export default {
// See https://stackoverflow.com/a/3977637/1333165
evt.dataTransfer.setData('Text', '');
},
copyPath() {
let path = utils.getAbsoluteDir(this.node).replaceAll(' ', '%20');
path = path.indexOf('/') === 0 ? path : `/${path}`;
return this.node.isFolder ? path : `${path}.md`;
},
onDrop() {
const sourceNode = store.getters['explorer/dragSourceNode'];
const targetNode = store.getters['explorer/dragTargetNodeFolder'];
@ -169,22 +179,26 @@ export default {
top: evt.clientY,
},
items: [{
name: 'New file',
name: '新建文件',
disabled: !this.node.isFolder || this.node.isTrash,
perform: () => explorerSvc.newItem(false),
}, {
name: 'New folder',
name: '新建文件夹',
disabled: !this.node.isFolder || this.node.isTrash || this.node.isTemp,
perform: () => explorerSvc.newItem(true),
}, {
type: 'separator',
}, {
name: 'Rename',
name: '重命名',
disabled: this.node.isTrash || this.node.isTemp,
perform: () => this.setEditingId(this.node.item.id),
}, {
name: 'Delete',
name: '删除',
perform: () => explorerSvc.deleteItem(),
}, {
name: '复制路径',
disabled: this.node.isTrash || this.node.isTemp,
perform: () => this.$refs.copyId.click(),
}],
});
if (item) {
@ -215,6 +229,10 @@ $item-font-size: 14px;
.explorer-node--selected > & {
background-color: rgba(0, 0, 0, 0.2);
.app--dark & {
background-color: rgba(0, 0, 0, 0.4);
}
.explorer__tree:focus & {
background-color: #39f;
color: #fff;
@ -236,6 +254,10 @@ $item-font-size: 14px;
.explorer-node--trash,
.explorer-node--temp {
color: rgba(0, 0, 0, 0.5);
.app--dark & {
color: rgba(255, 255, 255, 0.5);
}
}
.explorer-node--folder > .explorer-node__item,

View File

@ -1,6 +1,6 @@
<template>
<div class="find-replace" @keydown.esc.stop="onEscape">
<button class="find-replace__close-button button not-tabbable" @click="close()" v-title="'Close'">
<button class="find-replace__close-button button not-tabbable" @click="close()" v-title="'关闭'">
<icon-close></icon-close>
</button>
<div class="find-replace__row">
@ -14,8 +14,8 @@
<button class="find-replace__button find-replace__button--find-option button" :class="{'find-replace__button--on': findUseRegexp}" @click="findUseRegexp = !findUseRegexp" title="Regular expression">.<sup></sup></button>
</div>
<div class="flex flex--row">
<button class="find-replace__button button" @click="find('backward')">Previous</button>
<button class="find-replace__button button" @click="find('forward')">Next</button>
<button class="find-replace__button button" @click="find('backward')">上一个</button>
<button class="find-replace__button button" @click="find('forward')">下一个</button>
</div>
</div>
</div>
@ -24,8 +24,8 @@
<input type="text" class="find-replace__text-input find-replace__text-input--replace text-input" @keydown.enter="replace" v-model="replaceText">
</div>
<div class="find-replace__row flex flex--row flex--end">
<button class="find-replace__button button" @click="replace">Replace</button>
<button class="find-replace__button button" @click="replaceAll">All</button>
<button class="find-replace__button button" @click="replace">替换</button>
<button class="find-replace__button button" @click="replaceAll">全部替换</button>
</div>
</div>
</div>
@ -307,20 +307,36 @@ export default {
color: rgba(0, 0, 0, 0.25);
text-transform: none;
.app--dark & {
color: rgba(255, 255, 255, 0.25);
}
&:active,
&:focus,
&:hover {
color: rgba(0, 0, 0, 0.25);
.app--dark & {
color: rgba(255, 255, 255, 0.25);
}
}
}
.find-replace__button--on {
color: rgba(0, 0, 0, 0.67);
.app--dark & {
color: rgba(255, 255, 255, 0.67);
}
&:active,
&:focus,
&:hover {
color: rgba(0, 0, 0, 0.67);
.app--dark & {
color: rgba(255, 255, 255, 0.67);
}
}
}
@ -343,10 +359,18 @@ export default {
padding: 2px;
color: rgba(0, 0, 0, 0.5);
.app--dark & {
color: rgba(255, 255, 255, 0.5);
}
&:active,
&:focus,
&:hover {
color: rgba(0, 0, 0, 0.75);
.app--dark & {
color: rgba(255, 255, 255, 0.75);
}
}
}
@ -359,6 +383,10 @@ export default {
.find-replace-highlighting {
background-color: $highlighting-color;
color: $editor-color-light !important;
.app--dark & {
background-color: $dark-highlighting-color;
}
}
.find-replace-selection {

View File

@ -9,11 +9,12 @@
<navigation-bar></navigation-bar>
</div>
<div class="layout__panel flex flex--row" :style="{height: styles.innerHeight + 'px'}">
<div class="layout__panel layout__panel--editor" v-show="styles.showEditor" :style="{width: (styles.editorWidth + styles.editorGutterWidth) + 'px', fontSize: styles.fontSize + 'px'}">
<div class="layout__panel layout__panel--editor" :class="editTheme" v-show="styles.showEditor" :style="{width: (styles.editorWidth + styles.editorGutterWidth) + 'px', fontSize: styles.fontSize + 'px'}">
<div class="gutter" :style="{left: styles.editorGutterLeft + 'px'}">
<div class="gutter__background" v-if="styles.editorGutterWidth" :style="{width: styles.editorGutterWidth + 'px'}"></div>
</div>
<editor></editor>
<editor-in-page-buttons v-if="editorShowInPageButtons"></editor-in-page-buttons>
<div class="gutter" :style="{left: styles.editorGutterLeft + 'px'}">
<sticky-comment v-if="styles.editorGutterWidth && stickyComment === 'top'"></sticky-comment>
<current-discussion v-if="styles.editorGutterWidth"></current-discussion>
@ -27,6 +28,7 @@
<div class="gutter__background" v-if="styles.previewGutterWidth" :style="{width: styles.previewGutterWidth + 'px'}"></div>
</div>
<preview></preview>
<preview-in-page-buttons></preview-in-page-buttons>
<div class="gutter" :style="{left: styles.previewGutterLeft + 'px'}">
<sticky-comment v-if="styles.previewGutterWidth && stickyComment === 'top'"></sticky-comment>
<current-discussion v-if="styles.previewGutterWidth"></current-discussion>
@ -58,6 +60,8 @@ import SideBar from './SideBar';
import Editor from './Editor';
import Preview from './Preview';
import Tour from './Tour';
import EditorInPageButtons from './EditorInPageButtons';
import PreviewInPageButtons from './PreviewInPageButtons';
import StickyComment from './gutters/StickyComment';
import CurrentDiscussion from './gutters/CurrentDiscussion';
import FindReplace from './FindReplace';
@ -75,6 +79,8 @@ export default {
Editor,
Preview,
Tour,
EditorInPageButtons,
PreviewInPageButtons,
StickyComment,
CurrentDiscussion,
FindReplace,
@ -96,9 +102,18 @@ export default {
...mapGetters('data', [
'layoutSettings',
]),
...mapGetters('theme', [
'currEditTheme',
]),
editTheme() {
return `edit-theme--${this.currEditTheme || 'default'}`;
},
showFindReplace() {
return !!store.state.findReplace.type;
},
editorShowInPageButtons() {
return store.getters['data/computedSettings'].editor.showInPageButtons;
},
},
methods: {
...mapActions('layout', [
@ -185,7 +200,7 @@ export default {
}
$preview-background-light: #f3f3f3;
$preview-background-dark: #252525;
$preview-background-dark: #444;
.layout__panel--preview,
.layout__panel--button-bar {
@ -208,6 +223,10 @@ $preview-background-dark: #252525;
.layout__panel--explorer,
.layout__panel--side-bar {
background-color: #ddd;
.app--dark & {
background-color: #383c4a;
}
}
.layout__panel--find-replace {
@ -218,5 +237,9 @@ $preview-background-dark: #252525;
width: 300px;
height: auto;
border-top-right-radius: $border-radius-base;
.app--dark & {
background-color: #4d5160;
}
}
</style>

View File

@ -1,15 +1,16 @@
<template>
<div class="modal" v-if="config" @keydown.esc.stop="onEscape" @keydown.tab="onTab" @focusin="onFocusInOut" @focusout="onFocusInOut">
<div class="modal__sponsor-banner" v-if="!isSponsor">
StackEdit is <a class="not-tabbable" target="_blank" href="https://github.com/benweet/stackedit/">open source</a>, please consider
<!-- <div class="modal__sponsor-banner" v-if="!isSponsor">
StackEdit is <a class="not-tabbable" target="_blank" href="https://github.com/mafgwo/stackedit/">open source</a>, please consider
<a class="not-tabbable" href="javascript:void(0)" @click="sponsor">sponsoring</a> for just $5.
</div>
</div> -->
<component v-if="currentModalComponent" :is="currentModalComponent"></component>
<modal-inner v-else aria-label="Dialog">
<div class="modal__content" v-html="simpleModal.contentHtml(config)"></div>
<div class="modal__button-bar">
<button class="button" v-if="simpleModal.rejectText" @click="config.reject()">{{simpleModal.rejectText}}</button>
<button class="button button--resolve" v-if="simpleModal.resolveText" @click="config.resolve()">{{simpleModal.resolveText}}</button>
<button v-for="(item, idx) in (simpleModal.resolveArray || [])" class="button button--resolve" @click="config.resolve(item.value)">{{item.text}}</button>
</div>
</modal-inner>
</div>
@ -20,7 +21,7 @@ import { mapGetters } from 'vuex';
import simpleModals from '../data/simpleModals';
import editorSvc from '../services/editorSvc';
import syncSvc from '../services/syncSvc';
import googleHelper from '../services/providers/helpers/googleHelper';
import giteeHelper from '../services/providers/helpers/giteeHelper';
import store from '../store';
import ModalInner from './modals/common/ModalInner';
@ -39,6 +40,9 @@ import WorkspaceManagementModal from './modals/WorkspaceManagementModal';
import AccountManagementModal from './modals/AccountManagementModal';
import BadgeManagementModal from './modals/BadgeManagementModal';
import SponsorModal from './modals/SponsorModal';
import CommitMessageModal from './modals/CommitMessageModal';
import WorkspaceImgPathModal from './modals/WorkspaceImgPathModal';
import ChatGptModal from './modals/ChatGptModal';
// Providers
import GooglePhotoModal from './modals/providers/GooglePhotoModal';
@ -54,13 +58,27 @@ import GithubOpenModal from './modals/providers/GithubOpenModal';
import GithubSaveModal from './modals/providers/GithubSaveModal';
import GithubWorkspaceModal from './modals/providers/GithubWorkspaceModal';
import GithubPublishModal from './modals/providers/GithubPublishModal';
import GithubImgStorageModal from './modals/providers/GithubImgStorageModal';
import GistSyncModal from './modals/providers/GistSyncModal';
import GistPublishModal from './modals/providers/GistPublishModal';
import GiteeAccountModal from './modals/providers/GiteeAccountModal';
import GiteeOpenModal from './modals/providers/GiteeOpenModal';
import GiteeSaveModal from './modals/providers/GiteeSaveModal';
import GiteeWorkspaceModal from './modals/providers/GiteeWorkspaceModal';
import GiteePublishModal from './modals/providers/GiteePublishModal';
import GiteeGistSyncModal from './modals/providers/GiteeGistSyncModal';
import GiteeGistPublishModal from './modals/providers/GiteeGistPublishModal';
import GitlabAccountModal from './modals/providers/GitlabAccountModal';
import GitlabOpenModal from './modals/providers/GitlabOpenModal';
import GitlabPublishModal from './modals/providers/GitlabPublishModal';
import GitlabSaveModal from './modals/providers/GitlabSaveModal';
import GitlabWorkspaceModal from './modals/providers/GitlabWorkspaceModal';
import GiteaAccountModal from './modals/providers/GiteaAccountModal';
import GiteaOpenModal from './modals/providers/GiteaOpenModal';
import GiteaPublishModal from './modals/providers/GiteaPublishModal';
import GiteaSaveModal from './modals/providers/GiteaSaveModal';
import GiteaWorkspaceModal from './modals/providers/GiteaWorkspaceModal';
import GiteaImgStorageModal from './modals/providers/GiteaImgStorageModal';
import WordpressPublishModal from './modals/providers/WordpressPublishModal';
import BloggerPublishModal from './modals/providers/BloggerPublishModal';
import BloggerPagePublishModal from './modals/providers/BloggerPagePublishModal';
@ -68,6 +86,8 @@ import ZendeskAccountModal from './modals/providers/ZendeskAccountModal';
import ZendeskPublishModal from './modals/providers/ZendeskPublishModal';
import CouchdbWorkspaceModal from './modals/providers/CouchdbWorkspaceModal';
import CouchdbCredentialsModal from './modals/providers/CouchdbCredentialsModal';
import SmmsAccountModal from './modals/providers/SmmsAccountModal';
import CustomAccountModal from './modals/providers/CustomAccountModal';
const getTabbables = container => container.querySelectorAll('a[href], button, .textfield, input[type=checkbox]')
// Filter enabled and visible element
@ -91,6 +111,9 @@ export default {
AccountManagementModal,
BadgeManagementModal,
SponsorModal,
CommitMessageModal,
WorkspaceImgPathModal,
ChatGptModal,
// Providers
GooglePhotoModal,
GoogleDriveAccountModal,
@ -105,13 +128,27 @@ export default {
GithubSaveModal,
GithubWorkspaceModal,
GithubPublishModal,
GithubImgStorageModal,
GistSyncModal,
GistPublishModal,
GiteeAccountModal,
GiteeOpenModal,
GiteeSaveModal,
GiteeWorkspaceModal,
GiteePublishModal,
GiteeGistSyncModal,
GiteeGistPublishModal,
GitlabAccountModal,
GitlabOpenModal,
GitlabPublishModal,
GitlabSaveModal,
GitlabWorkspaceModal,
GiteaAccountModal,
GiteaOpenModal,
GiteaPublishModal,
GiteaSaveModal,
GiteaWorkspaceModal,
GiteaImgStorageModal,
WordpressPublishModal,
BloggerPublishModal,
BloggerPagePublishModal,
@ -119,6 +156,8 @@ export default {
ZendeskPublishModal,
CouchdbWorkspaceModal,
CouchdbCredentialsModal,
SmmsAccountModal,
CustomAccountModal,
},
computed: {
...mapGetters([
@ -148,7 +187,8 @@ export default {
if (!store.getters['workspace/sponsorToken']) {
// User has to sign in
await store.dispatch('modal/open', 'signInForSponsorship');
await googleHelper.signin();
await giteeHelper.signin();
await syncSvc.afterSignIn();
syncSvc.requestSync();
}
if (!store.getters.isSponsor) {
@ -245,6 +285,10 @@ export default {
position: relative;
overflow: hidden;
.app--dark & {
background-color: #383c4a;
}
&::before {
content: '';
position: absolute;

View File

@ -2,13 +2,14 @@
<nav class="navigation-bar" :class="{'navigation-bar--editor': styles.showEditor && !revisionContent, 'navigation-bar--light': light}">
<!-- Explorer -->
<div class="navigation-bar__inner navigation-bar__inner--left navigation-bar__inner--button">
<button class="navigation-bar__button navigation-bar__button--close button" v-if="light" @click="close()" v-title="'Close StackEdit'"><icon-check-circle></icon-check-circle></button>
<button class="navigation-bar__button navigation-bar__button--explorer-toggler button" v-else tour-step-anchor="explorer" @click="toggleExplorer()" v-title="'Toggle explorer'"><icon-folder></icon-folder></button>
<button class="navigation-bar__button navigation-bar__button--close button" v-if="light" @click="close()" v-title="'关闭StackEdit'"><icon-check-circle></icon-check-circle></button>
<button class="navigation-bar__button navigation-bar__button--explorer-toggler button" v-else tour-step-anchor="explorer" @click="toggleExplorer()" v-title="'切换资源管理器'"><icon-folder></icon-folder></button>
</div>
<!-- Side bar -->
<div class="navigation-bar__inner navigation-bar__inner--right navigation-bar__inner--button">
<a class="navigation-bar__button navigation-bar__button--stackedit button" v-if="light" href="app" target="_blank" v-title="'Open StackEdit'"><icon-provider provider-id="stackedit"></icon-provider></a>
<button class="navigation-bar__button navigation-bar__button--stackedit button" v-else tour-step-anchor="menu" @click="toggleSideBar()" v-title="'Toggle side bar'"><icon-provider provider-id="stackedit"></icon-provider></button>
<button class="navigation-bar__button navigation-bar__button--theme button" v-title="'切换主题'" tour-step-anchor="theme" @click="switchTheme"><icon-switch-theme></icon-switch-theme></button>
<a class="navigation-bar__button navigation-bar__button--stackedit button" v-if="light" href="app" target="_blank" v-title="'打开StackEdit'"><icon-provider provider-id="stackedit"></icon-provider></a>
<button class="navigation-bar__button navigation-bar__button--stackedit button" v-else tour-step-anchor="menu" @click="toggleSideBar()" v-title="'切换侧边栏'"><icon-provider provider-id="stackedit"></icon-provider></button>
</div>
<div class="navigation-bar__inner navigation-bar__inner--right navigation-bar__inner--title flex flex--row">
<!-- Spinner -->
@ -22,20 +23,20 @@
<input class="navigation-bar__title navigation-bar__title--input text-input" :class="{'navigation-bar__title--focus': titleFocus, 'navigation-bar__title--scrolling': titleScrolling}" :style="{width: titleWidth + 'px'}" @focus="editTitle(true)" @blur="editTitle(false)" @keydown.enter="submitTitle(false)" @keydown.esc.stop="submitTitle(true)" @mouseenter="titleHover = true" @mouseleave="titleHover = false" v-model="title">
<!-- Sync/Publish -->
<div class="flex flex--row" :class="{'navigation-bar__hidden': styles.hideLocations}">
<a class="navigation-bar__button navigation-bar__button--location button" :class="{'navigation-bar__button--blink': location.id === currentLocation.id}" v-for="location in syncLocations" :key="location.id" :href="location.url" target="_blank" v-title="'Synchronized location'"><icon-provider :provider-id="location.providerId"></icon-provider></a>
<button class="navigation-bar__button navigation-bar__button--sync button" :disabled="!isSyncPossible || isSyncRequested || offline" @click="requestSync" v-title="'Synchronize now'"><icon-sync></icon-sync></button>
<a class="navigation-bar__button navigation-bar__button--location button" :class="{'navigation-bar__button--blink': location.id === currentLocation.id}" v-for="location in publishLocations" :key="location.id" :href="location.url" target="_blank" v-title="'Publish location'"><icon-provider :provider-id="location.providerId"></icon-provider></a>
<button class="navigation-bar__button navigation-bar__button--publish button" :disabled="!publishLocations.length || isPublishRequested || offline" @click="requestPublish" v-title="'Publish now'"><icon-upload></icon-upload></button>
<a class="navigation-bar__button navigation-bar__button--location button" :class="{'navigation-bar__button--blink': location.id === currentLocation.id}" v-for="location in syncLocations" :key="location.id" :href="location.url" target="_blank" v-title="'同步位置'"><icon-provider :provider-id="location.providerId"></icon-provider></a>
<button class="navigation-bar__button navigation-bar__button--sync button" :disabled="!isSyncPossible || isSyncRequested || offline" @click="requestSync" v-title="'立即同步'"><icon-sync></icon-sync></button>
<a class="navigation-bar__button navigation-bar__button--location button" :class="{'navigation-bar__button--blink': location.id === currentLocation.id}" v-for="location in publishLocations" :key="location.id" :href="location.url" target="_blank" v-title="'发布位置'"><icon-provider :provider-id="location.providerId"></icon-provider></a>
<button class="navigation-bar__button navigation-bar__button--publish button" :disabled="!publishLocations.length || isPublishRequested || offline" @click="requestPublish" v-title="'立即发布'"><icon-upload></icon-upload></button>
</div>
<!-- Revision -->
<div class="flex flex--row" v-if="revisionContent">
<button class="navigation-bar__button navigation-bar__button--revision navigation-bar__button--restore button" @click="restoreRevision">Restore</button>
<button class="navigation-bar__button navigation-bar__button--revision button" @click="setRevisionContent()" v-title="'Close revision'"><icon-close></icon-close></button>
<button class="navigation-bar__button navigation-bar__button--revision navigation-bar__button--restore button" @click="restoreRevision">恢复</button>
<button class="navigation-bar__button navigation-bar__button--revision button" @click="setRevisionContent()" v-title="'关闭修订'"><icon-close></icon-close></button>
</div>
</div>
<div class="navigation-bar__inner navigation-bar__inner--edit-pagedownButtons">
<button class="navigation-bar__button button" @click="undo" v-title="'Undo'" :disabled="!canUndo"><icon-undo></icon-undo></button>
<button class="navigation-bar__button button" @click="redo" v-title="'Redo'" :disabled="!canRedo"><icon-redo></icon-redo></button>
<button class="navigation-bar__button button" @click="undo" v-title="'回退'" :disabled="!canUndo"><icon-undo></icon-undo></button>
<button class="navigation-bar__button button" @click="redo" v-title="'重做'" :disabled="!canRedo"><icon-redo></icon-redo></button>
<div v-for="button in pagedownButtons" :key="button.method">
<button class="navigation-bar__button button" v-if="button.method" @click="pagedownClick(button.method)" v-title="button.titleWithShortcut">
<component :is="button.iconClass"></component>
@ -113,7 +114,8 @@ export default {
publishLocations: 'current',
}),
pagedownButtons() {
return pagedownButtons.map(button => ({
const buttonShowObj = store.getters['data/computedSettings'].editor.headButtons;
return pagedownButtons.filter(it => buttonShowObj[it.method]).map(button => ({
...button,
titleWithShortcut: `${button.title}${getShortcut(button.method)}`,
iconClass: `icon-${button.icon}`,
@ -187,6 +189,9 @@ export default {
publishSvc.requestPublish();
}
},
switchTheme() {
store.dispatch('data/switchThemeSetting');
},
pagedownClick(name) {
if (store.getters['content/isCurrentEditable']) {
const text = editorSvc.clEditor.getContent();
@ -309,6 +314,18 @@ export default {
padding: 0 4px;
width: 38px;
&.navigation-bar__button--theme {
width: 34px;
padding: 0 7px;
opacity: 0.85;
&:active,
&:focus,
&:hover {
opacity: 1;
}
}
&.navigation-bar__button--stackedit {
opacity: 0.85;

View File

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

View File

@ -1,7 +1,7 @@
<template>
<div class="preview">
<div class="preview__inner-1" @click="onClick" @scroll="onScroll">
<div class="preview__inner-2" :style="{padding: styles.previewPadding}">
<div class="preview__inner-2" :class="previewTheme" :style="{padding: styles.previewPadding}">
</div>
<div class="gutter" :style="{left: styles.previewGutterLeft + 'px'}">
<comment-list v-if="styles.previewGutterWidth"></comment-list>
@ -9,7 +9,7 @@
</div>
</div>
<div v-if="!styles.showEditor" class="preview__corner">
<button class="preview__button button" @click="toggleEditor(true)" v-title="'Edit file'">
<button class="preview__button button" @click="toggleEditor(true)" v-title="'编辑文件'">
<icon-pen></icon-pen>
</button>
</div>
@ -37,9 +37,15 @@ export default {
...mapGetters('file', [
'isCurrentTemp',
]),
...mapGetters('theme', [
'currPreviewTheme',
]),
...mapGetters('layout', [
'styles',
]),
previewTheme() {
return `preview-theme--${this.currPreviewTheme || 'default'}`;
},
},
methods: {
...mapActions('data', [

View File

@ -0,0 +1,212 @@
<template>
<div class="preview-in-page-buttons">
<ul>
<li class="before">
<icon-ellipsis></icon-ellipsis>
</li>
<li title="分享">
<a href="javascript:void(0)" @click="share"><icon-share></icon-share></a>
</li>
<li title="切换预览主题">
<dropdown-menu :selected="selectedTheme" :options="allThemes" :closeOnItemClick="false" @change="changeTheme">
<icon-select-theme></icon-select-theme>
</dropdown-menu>
</li>
<li title="Markdown语法帮助">
<a href="javascript:void(0)" @click="showHelp"><icon-help-circle></icon-help-circle></a>
</li>
</ul>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
// import juice from 'juice';
import store from '../store';
import DropdownMenu from './common/DropdownMenu';
import publishSvc from '../services/publishSvc';
import giteeGistProvider from '../services/providers/giteeGistProvider';
import gistProvider from '../services/providers/gistProvider';
export default {
components: {
DropdownMenu,
},
data: () => ({
allThemes: [{
name: '默认主题',
value: 'default',
}, {
name: '凝夜紫',
value: 'ningyezi',
}, {
name: '草原绿',
value: 'caoyuangreen',
}, {
name: '雁栖湖',
value: 'yanqihu',
}, {
name: '灵动蓝',
value: 'activeblue',
}, {
name: '极客黑',
value: 'jikebrack',
}, {
name: '极简黑',
value: 'simplebrack',
}, {
name: '全栈蓝',
value: 'allblue',
}, {
name: '自定义',
value: 'custom',
}],
baseCss: '',
sharing: false,
}),
computed: {
...mapGetters('theme', [
'currPreviewTheme',
'customPreviewThemeStyle',
]),
...mapGetters('publishLocation', {
publishLocations: 'current',
}),
selectedTheme() {
return {
value: this.currPreviewTheme || 'default',
};
},
},
methods: {
...mapActions('data', [
'toggleSideBar',
]),
async changeTheme(item) {
await store.dispatch('theme/setPreviewTheme', item.value);
//
if (item.value === 'custom' && !this.customPreviewThemeStyle) {
this.toggleSideBar(true);
store.dispatch('data/setSideBarPanel', 'previewTheme');
}
},
showHelp() {
this.toggleSideBar(true);
store.dispatch('data/setSideBarPanel', 'help');
},
async share() {
if (this.sharing) {
store.dispatch('notification/info', '分享链接创建中...请稍后再试');
return;
}
try {
const currentFile = store.getters['file/current'];
await store.dispatch('modal/open', { type: 'shareHtmlPre', name: currentFile.name });
this.sharing = true;
const mainToken = store.getters['workspace/mainWorkspaceToken'];
if (!mainToken) {
store.dispatch('notification/info', '登录主文档空间之后才可使用分享功能!');
return;
}
let tempGistId = null;
const isGithub = mainToken.providerId === 'githubAppData';
const gistProviderId = isGithub ? 'gist' : 'giteegist';
const filterLocations = this.publishLocations.filter(it => it.providerId === gistProviderId
&& it.url && it.gistId);
if (filterLocations.length > 0) {
tempGistId = filterLocations[0].gistId;
}
const location = (isGithub ? gistProvider : giteeGistProvider).makeLocation(
mainToken,
`分享-${currentFile.name}`,
true,
null,
);
location.templateId = 'styledHtmlWithTheme';
location.fileId = currentFile.id;
location.gistId = tempGistId;
const { gistId } = await publishSvc.publishLocationAndStore(location);
const sharePage = mainToken.providerId === 'githubAppData' ? 'gistshare.html' : 'share.html';
const url = `${window.location.protocol}//${window.location.host}/${sharePage}?id=${gistId}`;
await store.dispatch('modal/open', { type: 'shareHtml', name: currentFile.name, url });
} catch (err) {
if (err) {
store.dispatch('notification/error', err);
}
} finally {
this.sharing = false;
}
},
},
};
</script>
<style lang="scss">
@import '../styles/variables.scss';
.preview-in-page-buttons {
position: absolute;
bottom: 10px;
right: -98px;
height: 34px;
padding: 5px;
background-color: rgba(84, 96, 114, 0.4);
border-radius: $border-radius-base;
transition: 0.5s;
display: flex;
.dropdown-menu {
display: none;
}
&:active,
&:focus,
&:hover {
right: 0;
transition: 0.5s;
background-color: #546072;
.dropdown-menu {
display: block;
}
}
.dropdown-menu-items {
bottom: 100%;
top: unset;
}
ul {
padding: 0;
margin-left: 10px;
line-height: 20px;
li {
line-height: 16px;
width: 16px;
display: inline-block;
vertical-align: middle;
list-style: none;
cursor: pointer;
font-size: 14px;
margin-right: 10px;
.icon {
color: #fff;
opacity: 0.7;
&:active,
&:focus,
&:hover {
opacity: 1;
}
}
}
.before {
margin-left: -16px;
margin-right: 0;
}
}
}
</style>

View File

@ -1,13 +1,13 @@
<template>
<div class="side-bar flex flex--column">
<div class="side-title flex flex--row">
<button v-if="panel !== 'menu'" class="side-title__button button" @click="setPanel('menu')" v-title="'Main menu'">
<button v-if="panel !== 'menu'" class="side-title__button button" @click="setPanel('menu')" v-title="'主菜单'">
<icon-dots-horizontal></icon-dots-horizontal>
</button>
<div class="side-title__title">
{{panelName}}
</div>
<button class="side-title__button button" @click="toggleSideBar(false)" v-title="'Close side bar'">
<button class="side-title__button button" @click="toggleSideBar(false)" v-title="'关闭侧边栏'">
<icon-close></icon-close>
</button>
</div>
@ -23,6 +23,8 @@
<div v-else-if="panel === 'help'" class="side-bar__panel side-bar__panel--help">
<pre class="markdown-highlighting" v-html="markdownSample"></pre>
</div>
<edit-theme-menu v-else-if="panel === 'editTheme'"></edit-theme-menu>
<preview-theme-menu v-else-if="panel === 'previewTheme'"></preview-theme-menu>
<div class="side-bar__panel side-bar__panel--toc" :class="{'side-bar__panel--hidden': panel !== 'toc'}">
<toc>
</toc>
@ -41,20 +43,24 @@ import PublishMenu from './menus/PublishMenu';
import HistoryMenu from './menus/HistoryMenu';
import ImportExportMenu from './menus/ImportExportMenu';
import WorkspaceBackupMenu from './menus/WorkspaceBackupMenu';
import EditThemeMenu from './menus/EditThemeMenu';
import PreviewThemeMenu from './menus/PreviewThemeMenu';
import markdownSample from '../data/markdownSample.md';
import markdownConversionSvc from '../services/markdownConversionSvc';
import store from '../store';
const panelNames = {
menu: 'Menu',
workspaces: 'Workspaces',
help: 'Markdown cheat sheet',
toc: 'Table of contents',
sync: 'Synchronize',
publish: 'Publish',
history: 'File history',
importExport: 'Import/export',
workspaceBackups: 'Workspace backups',
menu: '菜单',
workspaces: '文档空间',
help: 'Markdown 帮助',
toc: '目录',
sync: '同步',
publish: '发布',
history: '文件历史',
importExport: '导入/导出',
workspaceBackups: '文档空间备份',
editTheme: '编辑区主题',
previewTheme: '预览区主题',
};
export default {
@ -67,6 +73,8 @@ export default {
HistoryMenu,
ImportExportMenu,
WorkspaceBackupMenu,
EditThemeMenu,
PreviewThemeMenu,
},
data: () => ({
markdownSample: markdownConversionSvc.highlight(markdownSample),

View File

@ -8,7 +8,7 @@
<span v-for="stat in textStats" :key="stat.id">
<span class="stat-panel__value">{{stat.value}}</span> {{stat.name}}
</span>
<span class="stat-panel__value">Ln {{line}}, Col {{column}}</span>
<span class="stat-panel__value">{{line}} , {{column}} </span>
</div>
<div class="stat-panel__block stat-panel__block--right">
<span class="stat-panel__block-name">
@ -43,14 +43,13 @@ export default {
line: 0,
column: 0,
textStats: [
new Stat('bytes', '[\\s\\S]'),
new Stat('words', '\\S+'),
new Stat('lines', '\n'),
new Stat('字符', '[\\s\\S]'),
new Stat('字数', '\\S'),
new Stat('行数', '\n'),
],
htmlStats: [
new Stat('characters', '\\S'),
new Stat('words', '\\S+'),
new Stat('paragraphs', '\\S.*'),
new Stat('字数', '\\S'),
new Stat('段落', '\\S.*'),
],
}),
computed: mapGetters('layout', [

View File

@ -99,6 +99,10 @@ export default {
-ms-user-select: none;
user-select: none;
.app--dark & {
color: rgba(255, 255, 255, 0.67);
}
* {
font-weight: inherit;
pointer-events: none;
@ -150,5 +154,9 @@ export default {
height: 35px;
background-color: rgba(255, 255, 255, 0.2);
pointer-events: none;
.app--dark & {
color: rgba(0, 0, 0, 0.2);
}
}
</style>

View File

@ -2,47 +2,55 @@
<div class="tour" @keydown.esc.stop="skip">
<div class="tour-step" :class="'tour-step--' + step" :style="stepStyle">
<div class="tour-step__inner" v-if="step === 'welcome'">
<h2>Welcome back!</h2>
<p>The new <b>StackEdit 5</b> is here!</p>
<p>Please click <b>Next</b> to take a quick tour.</p>
<h2>欢迎回来</h2>
<p>新的<b>StackEdit中文版</b>在这里</p>
<p>请单击<b>下一步</b>快速浏览</p>
<div class="tour-step__button-bar">
<button class="button" @click="finish">Skip</button>
<button class="button button--resolve" @click="next">Next</button>
<button class="button" @click="finish">跳过</button>
<button class="button button--resolve" @click="next">下一步</button>
</div>
</div>
<div class="tour-step__inner" v-else-if="step === 'editor'">
<h2>Your Markdown editor</h2>
<p>StackEdit converts your Markdown to HTML in real-time.</p>
<p>Click <icon-side-preview></icon-side-preview> to toggle the side preview.</p>
<h2>您的Markdown编辑器</h2>
<p>StackEdit中文版实时将Markdown转换为HTML</p>
<p>点击 <icon-side-preview></icon-side-preview> </p>
<div class="tour-step__button-bar">
<button class="button" @click="finish">Skip</button>
<button class="button button--resolve" @click="next">Next</button>
<button class="button" @click="finish">跳过</button>
<button class="button button--resolve" @click="next">下一步</button>
</div>
</div>
<div class="tour-step__inner" v-else-if="step === 'explorer'">
<h2>File explorer</h2>
<p>StackEdit can manage multiple files and folders in a workspace.</p>
<p>Click <icon-folder></icon-folder> to open the file explorer.</p>
<h2>文件资源管理器</h2>
<p>StackEdit中文版可以管理文档空间中的多个文件和文件夹</p>
<p>点击 <icon-folder></icon-folder> </p>
<div class="tour-step__button-bar">
<button class="button" @click="finish">Skip</button>
<button class="button button--resolve" @click="next">Next</button>
<button class="button" @click="finish">跳过</button>
<button class="button button--resolve" @click="next">下一步</button>
</div>
</div>
<div class="tour-step__inner" v-else-if="step === 'menu'">
<h2>Do a lot more!</h2>
<p>StackEdit can also synchronize and publish your files, manage collaborative workspaces...</p>
<p>Click <icon-provider provider-id="stackedit"></icon-provider> to explore the menu.</p>
<h2>切换侧边栏</h2>
<p>StackEdit中文版还可以同步和发布文件管理协作文档空间...</p>
<p>点击 <icon-provider provider-id="stackedit"></icon-provider> </p>
<div class="tour-step__button-bar">
<button class="button" @click="finish">Skip</button>
<button class="button button--resolve" @click="next">Next</button>
<button class="button" @click="finish">跳过</button>
<button class="button button--resolve" @click="next">下一步</button>
</div>
</div>
<div class="tour-step__inner" v-else-if="step === 'theme'">
<h2>切换主题</h2>
<p>StackEdit中文版可以切换亮/暗主题</p>
<p>点击 <icon-switch-theme></icon-switch-theme> </p>
<div class="tour-step__button-bar">
<button class="button" @click="finish">跳过</button>
<button class="button button--resolve" @click="next">下一步</button>
</div>
</div>
<div class="tour-step__inner" v-else-if="step === 'end'">
<h2>Enjoy!</h2>
<p>If you like StackEdit, please rate 5 stars on the <a target="_blank" href="https://chrome.google.com/webstore/detail/iiooodelglhkcpgbajoejffhijaclcdg/reviews">Chrome Web Store</a>.</p>
<p>You can also star the project on <a target="_blank" href="https://github.com/benweet/stackedit">GitHub</a> and join the <a target="_blank" href="https://community.stackedit.io/">community</a>.</p>
<p>如果您喜欢StackEdit中文版请在<a href="https://gitee.com/mafgwo/stackedit">Gitee仓库</a>上点一下Star谢谢</p>
<div class="tour-step__button-bar">
<button class="button button--resolve" @click="finish">Ok</button>
<button class="button button--resolve" @click="finish">确认</button>
</div>
</div>
</div>
@ -58,6 +66,7 @@ const steps = [
'editor',
'explorer',
'menu',
'theme',
'end',
];
@ -91,7 +100,8 @@ export default {
break;
}
case 'editor':
case 'menu': {
case 'menu':
case 'theme': {
style.left = `${anchorRect.left}px`;
break;
}
@ -140,6 +150,7 @@ export default {
}
$tour-step-background: transparentize(mix(#f3f3f3, $selection-highlighting-color, 75%), 0.025);
$tour-step-darkbackground: transparentize(mix(#4d4d4d, $selection-highlighting-color, 75%), 0.025);
$tour-step-width: 240px;
.tour-step__inner {
@ -152,6 +163,10 @@ $tour-step-width: 240px;
text-align: center;
border-radius: $border-radius-base;
.app--dark & {
background-color: $tour-step-darkbackground;
}
h2 {
margin: 0;
@ -184,11 +199,16 @@ $tour-step-width: 240px;
right: 0;
border-top: 10px solid $tour-step-background;
border-left: 10px solid transparent;
.app--dark & {
border-top: 10px solid $tour-step-darkbackground;
}
}
}
.tour-step--editor &,
.tour-step--menu & {
.tour-step--menu &,
.tour-step--theme & {
right: 15px;
border-top-right-radius: 0;
@ -197,6 +217,10 @@ $tour-step-width: 240px;
right: -10px;
border-top: 10px solid $tour-step-background;
border-right: 10px solid transparent;
.app--dark & {
border-top: 10px solid $tour-step-darkbackground;
}
}
}
@ -209,6 +233,10 @@ $tour-step-width: 240px;
left: -10px;
border-top: 10px solid $tour-step-background;
border-left: 10px solid transparent;
.app--dark & {
border-top: 10px solid $tour-step-darkbackground;
}
}
}
}

View File

@ -0,0 +1,137 @@
<template>
<span class="dropdown-menu">
<span ref="slotInfo" @click="toggleMenu()" class="dropdown-toggle">
<slot></slot>
</span>
<ul class="dropdown-menu-items" :style="dropdownStyle" v-if="showMenu">
<li v-for="(option, idx) in options" :key="idx">
<a href="javascript:void(0)" :class="{selected: option.value === selectedOption.value}" @click="updateOption(option)">
{{ option.name }}
</a>
</li>
</ul>
</span>
</template>
<script>
import store from '../../store';
export default {
data: () => ({
selectedOption: {
value: '',
name: '',
},
showMenu: false,
}),
props: {
options: {
type: [Array, Object],
},
selected: {},
closeOnOutsideClick: {
type: [Boolean],
default: true,
},
closeOnItemClick: {
type: [Boolean],
default: true,
},
},
mounted() {
this.selectedOption = this.selected;
if (this.closeOnOutsideClick) {
document.addEventListener('click', this.clickHandler);
}
},
beforeDestroy() {
document.removeEventListener('click', this.clickHandler);
},
computed: {
dropdownStyle() {
const height = store.state.layout.bodyHeight;
return `max-height: ${height * 0.7}px;`;
},
},
methods: {
updateOption(option) {
this.selectedOption = option;
if (this.closeOnItemClick) {
this.showMenu = false;
}
this.$emit('change', option);
},
toggleMenu() {
this.showMenu = !this.showMenu;
},
clickHandler(event) {
const { target } = event;
const { $el } = this;
if (!$el.contains(target)) {
this.showMenu = false;
}
},
},
watch: {
selected(val) {
this.selectedOption = val;
},
},
};
</script>
<style lang="scss">
.dropdown-menu {
.dropdown-menu-items {
position: absolute;
top: 100%;
right: 0;
float: left;
min-width: 160px;
max-height: 450px;
overflow-y: scroll;
padding: 5px 0;
margin: 0;
list-style: none;
font-size: 15px;
background-color: #666;
border: 1px solid #666;
border-radius: 4px;
-webkit-box-shadow: 0 6px 12px rgb(0, 0, 0 / 18%);
box-shadow: 0 6px 12px rgb(0, 0, 0 / 18%);
background-clip: padding-box;
li {
width: 100%;
display: list-item;
margin: 0;
text-align: -webkit-match-parent;
a {
display: block;
clear: both;
font-weight: normal;
line-height: 1.45;
white-space: nowrap;
color: #eee;
padding: 5px 20px;
border-top: 1px solid transparent;
border-bottom: 1px solid transparent;
text-decoration: none;
&:active,
&:focus,
&:hover {
background-color: rgb(82, 82, 82);
}
}
a.selected {
background: #74b936 !important;
color: #fff !important;
}
}
}
}
</style>

View File

@ -5,7 +5,7 @@
<div class="comment__user-image">
<user-image :user-id="comment.sub"></user-image>
</div>
<button class="comment__remove-button button" v-title="'Remove comment'" @click="removeComment">
<button class="comment__remove-button button" v-title="'删除评论'" @click="removeComment">
<icon-delete></icon-delete>
</button>
<user-name :user-id="comment.sub"></user-name>
@ -16,7 +16,7 @@
<div class="comment__text-inner" v-html="text"></div>
</div>
<div class="comment__buttons flex flex--row flex--end" v-if="showReply">
<button class="comment__button button" @click="setIsCommenting(true)">Reply</button>
<button class="comment__button button" @click="setIsCommenting(true)">评论</button>
</div>
</div>
</template>

View File

@ -4,18 +4,18 @@
<div class="current-discussion__inner">
<div class="flex flex--row flex--space-between">
<div class="current-discussion__buttons flex flex--row flex--end">
<button class="current-discussion__button button" v-if="showNext" @click="goToDiscussion(previousDiscussionId)" v-title="'Previous discussion'">
<button class="current-discussion__button button" v-if="showNext" @click="goToDiscussion(previousDiscussionId)" v-title="'上一个批注'">
<icon-arrow-left></icon-arrow-left>
</button>
<button class="current-discussion__button current-discussion__button--rotate button" v-if="showNext" @click="goToDiscussion(nextDiscussionId)" v-title="'Next discussion'">
<button class="current-discussion__button current-discussion__button--rotate button" v-if="showNext" @click="goToDiscussion(nextDiscussionId)" v-title="'下一个批注'">
<icon-arrow-left></icon-arrow-left>
</button>
</div>
<div class="current-discussion__buttons flex flex--row flex--end">
<button class="current-discussion__button current-discussion__button--remove button" v-if="showRemove" @click="removeDiscussion" v-title="'Remove discussion'">
<button class="current-discussion__button current-discussion__button--remove button" v-if="showRemove" @click="removeDiscussion" v-title="'删除批注'">
<icon-delete></icon-delete>
</button>
<button class="current-discussion__button button" @click="setCurrentDiscussionId()" v-title="'Close discussion'">
<button class="current-discussion__button button" @click="setCurrentDiscussionId()" v-title="'关闭批注'">
<icon-close></icon-close>
</button>
</div>

View File

@ -1,5 +1,5 @@
<template>
<a class="new-discussion-button" href="javascript:void(0)" v-if="coordinates" :style="{top: coordinates.top + 'px'}" v-title="'Start a discussion'" @mousedown.stop.prevent @click="createNewDiscussion(selection)">
<a class="new-discussion-button" href="javascript:void(0)" v-if="coordinates" :style="{top: coordinates.top + 'px'}" v-title="'开始批注'" @mousedown.stop.prevent @click="createNewDiscussion(selection)">
<icon-message></icon-message>
</a>
</template>

View File

@ -14,8 +14,8 @@
</div>
</div>
<div class="comment__buttons flex flex--row flex--end">
<button class="comment__button button" @click="cancelNewComment">Cancel</button>
<button class="comment__button button" @click="addComment">Ok</button>
<button class="comment__button button" @click="cancelNewComment">取消</button>
<button class="comment__button button" @click="addComment">确认</button>
</div>
</div>
</template>

View File

@ -1,5 +1,5 @@
<template>
<a class="new-discussion-button" href="javascript:void(0)" v-if="coordinates" :style="{top: coordinates.top + 'px'}" v-title="'Start a discussion'" @mousedown.stop.prevent @click="createNewDiscussion(selection)">
<a class="new-discussion-button" href="javascript:void(0)" v-if="coordinates" :style="{top: coordinates.top + 'px'}" v-title="'添加批注'" @mousedown.stop.prevent @click="createNewDiscussion(selection)">
<icon-message></icon-message>
</a>
</template>

View File

@ -0,0 +1,116 @@
<template>
<div class="edit-theme side-bar__panel side-bar__panel--menu">
<div class="side-bar__info">
<div class="menu-entry menu-entry--info flex flex--row flex--align-center">
<span v-if="currEditTheme==='custom'">
下面的自定义主题样式可编辑可参考其他主题样式填入自己喜欢的编辑样式<br>
主题class为edit-theme--custom
</span>
<span v-else>
下面的主题样式不可编辑
</span>
</div>
</div>
<div class="side-bar__content">
<template v-if="currEditTheme === 'default'">
默认主题无额外样式请选择其他主题
</template>
<template v-else>
<code-editor v-for="(value, index) in styleEles" :key="index"
v-if="value.id === `edit-theme-${currEditTheme}`" lang="css" :value="value.innerHTML"
:disabled="value.id!=='edit-theme-custom'" @changed="changeText" scrollClass="side-bar__inner"></code-editor>
</template>
</div>
<div class="flex flex--row flex--end" v-if="currEditTheme==='custom'">
<button class="edit-theme__button button" @click="saveStyleText">保存</button>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import MenuEntry from './common/MenuEntry';
import CodeEditor from '../CodeEditor';
import store from '../../store';
export default {
components: {
MenuEntry,
CodeEditor,
},
data: () => ({
themeStyleText: '',
styleEles: [],
}),
computed: {
...mapGetters('theme', [
'currEditTheme',
]),
},
methods: {
saveStyleText() {
const typeEle = this.findByTheme(this.currEditTheme);
if (!typeEle || !this.themeStyleText) {
return;
}
typeEle.innerHTML = this.themeStyleText;
store.dispatch('theme/setCustomEditThemeStyle', this.themeStyleText);
store.dispatch('notification/info', '保存自定义主题样式成功!');
},
findByTheme(theme) {
const findEles = this.styleEles.filter(it => it.id === `edit-theme-${theme}`);
return findEles.length ? findEles[0] : null;
},
changeText(text) {
this.themeStyleText = text;
},
close() {
store.dispatch('data/setSideBarPanel', 'menu');
},
initStyle(theme) {
if (theme === 'default') {
return;
}
const value = theme || this.currEditTheme;
if (this.findByTheme(value)) {
return;
}
const styleId = `edit-theme-${value}`;
const styleEle = document.getElementById(styleId);
if (!styleEle) {
setTimeout(() => this.initStyle(value), 1000);
return;
}
this.styleEles.push(styleEle);
},
},
watch: {
currEditTheme: {
immediate: true,
handler(val) {
this.initStyle(val);
},
},
},
created() {
this.initStyle();
},
};
</script>
<style lang="scss">
.side-bar__panel--menu {
.side-bar__content {
.code-editor {
min-height: 400px !important;
max-height: 100%;
}
}
.edit-theme__button {
font-size: 14px;
margin-top: 0.5em;
}
}
</style>

View File

@ -8,18 +8,18 @@
</option>
</select>
</p>
<p v-if="!historyContext">Synchronize <b>{{currentFileName}}</b> to enable revision history or <a href="javascript:void(0)" @click="signin">sign in with Google</a> to synchronize your main workspace.</p>
<p v-else-if="loading">Loading history</p>
<p v-else-if="!revisionsWithSpacer.length"><b>{{currentFileName}}</b> has no history.</p>
<p v-if="!historyContext">同步 <b>{{currentFileName}}</b> 以启用修订历史 或者 <a href="javascript:void(0)" @click="signin">登录 Gitee</a> <a href="javascript:void(0)" @click="signinWithGithub">登录 GitHub</a> 以同步您的主文档空间</p>
<p v-else-if="loading">历史版本加载中</p>
<p v-else-if="!revisionsWithSpacer.length"><b>{{currentFileName}}</b> 没有历史版本.</p>
<div class="menu-entry menu-entry--info flex flex--row flex--align-center" v-else>
<div class="menu-entry__icon menu-entry__icon--image">
<icon-provider :provider-id="syncLocation.providerId"></icon-provider>
</div>
<span v-if="syncLocation.url">
The following revisions are stored in <a :href="syncLocation.url" target="_blank">{{ syncLocationProviderName }}</a>.
下面的历史版本存储在 <a :href="syncLocation.url" target="_blank">{{ syncLocationProviderName }}</a>.
</span>
<span v-else>
The following revisions are stored in {{ syncLocationProviderName }}.
下面的历史版本存储在 {{ syncLocationProviderName }}.
</span>
</div>
</div>
@ -33,13 +33,14 @@
<div class="revision__header flex flex--column">
<user-name :user-id="revision.sub"></user-name>
<div class="revision__created">{{revision.created | formatTime}}</div>
<div class="revision__msg">{{revision.message}}</div>
</div>
</a>
</div>
</div>
<div class="history__spacer history__spacer--last" v-if="revisions.length"></div>
<div class="flex flex--row flex--end" v-if="showMoreButton">
<button class="history__button button" @click="showMore">More</button>
<button class="history__button button" @click="showMore">更多</button>
</div>
</div>
</template>
@ -53,7 +54,8 @@ import UserName from '../UserName';
import EditorClassApplier from '../common/EditorClassApplier';
import PreviewClassApplier from '../common/PreviewClassApplier';
import utils from '../../services/utils';
import googleHelper from '../../services/providers/helpers/googleHelper';
import giteeHelper from '../../services/providers/helpers/giteeHelper';
import githubHelper from '../../services/providers/helpers/githubHelper';
import syncSvc from '../../services/syncSvc';
import store from '../../store';
import badgeSvc from '../../services/badgeSvc';
@ -166,7 +168,17 @@ export default {
]),
async signin() {
try {
await googleHelper.signin();
await giteeHelper.signin();
await syncSvc.afterSignIn();
syncSvc.requestSync();
} catch (e) {
// Cancel
}
},
async signinWithGithub() {
try {
await githubHelper.signin();
await syncSvc.afterSignIn();
syncSvc.requestSync();
} catch (e) {
// Cancel
@ -412,6 +424,14 @@ export default {
opacity: 0.6;
}
.revision__msg {
font-size: 0.75em;
opacity: 0.6;
white-space: pre-wrap;
word-break: break-word;
word-wrap: break-word;
}
.layout--revision {
.cledit-section *,
.cl-preview-section * {

View File

@ -6,8 +6,8 @@
<icon-upload></icon-upload>
</div>
<div class="flex flex--column">
<div>Import Markdown</div>
<span>Import a plain text file.</span>
<div>导入 Markdown</div>
<span>导入纯文本文件</span>
</div>
</label>
<input class="hidden-file" id="import-html-file-input" type="file" @change="onImportHtml">
@ -16,31 +16,31 @@
<icon-upload></icon-upload>
</div>
<div class="flex flex--column">
<div>Import HTML</div>
<span>Convert an HTML file to Markdown.</span>
<div>导入 HTML</div>
<span>将HTML文件转换为Markdown</span>
</div>
</label>
<hr>
<menu-entry @click.native="exportMarkdown">
<icon-download slot="icon"></icon-download>
<div>Export as Markdown</div>
<span>Save plain text file.</span>
<div>导出为 Markdown</div>
<span>保存纯文本文件</span>
</menu-entry>
<menu-entry @click.native="exportHtml">
<icon-download slot="icon"></icon-download>
<div>Export as HTML</div>
<span>Generate an HTML page from a template.</span>
<div>导出为 HTML</div>
<span>从模板生成HTML页面</span>
</menu-entry>
<menu-entry @click.native="exportPdf">
<icon-download slot="icon"></icon-download>
<div><div class="menu-entry__label" :class="{'menu-entry__label--warning': !isSponsor}">sponsor</div> Export as PDF</div>
<span>Produce a PDF from an HTML template.</span>
<div>导出为 HTML PDF</div>
<span>从HTML模板生成PDF</span>
</menu-entry>
<menu-entry @click.native="exportPandoc">
<!-- <menu-entry @click.native="exportPandoc">
<icon-download slot="icon"></icon-download>
<div><div class="menu-entry__label" :class="{'menu-entry__label--warning': !isSponsor}">sponsor</div> Export with Pandoc</div>
<span>Convert to PDF, Word, EPUB...</span>
</menu-entry>
<div>导出为 HTML Pandoc</div>
<span>转换为PDFWordEPUB...</span>
</menu-entry> -->
</div>
</template>

View File

@ -5,117 +5,141 @@
<div class="menu-entry__icon menu-entry__icon--image">
<user-image :user-id="userId"></user-image>
</div>
<span>Signed in as <b>{{loginToken.name}}</b>.</span>
<span>登录名为<b>{{loginToken.name}}</b></span>
</div>
<div class="menu-entry menu-entry--info flex flex--row flex--align-center" v-if="syncToken">
<div class="menu-entry__icon menu-entry__icon--image">
<icon-provider :provider-id="currentWorkspace.providerId"></icon-provider>
</div>
<span v-if="currentWorkspace.providerId === 'googleDriveAppData'">
<b>{{currentWorkspace.name}}</b> synced with your Google Drive app data folder.
<span v-if="currentWorkspace.providerId === 'giteeAppData'">
<b>{{currentWorkspace.name}}</b> 与您的 Gitee 默认文档空间仓库同步
</span>
<span v-else-if="currentWorkspace.providerId === 'githubAppData'">
<b>{{currentWorkspace.name}}</b> 与您的 GitHub 默认文档空间仓库同步
</span>
<span v-else-if="currentWorkspace.providerId === 'googleDriveWorkspace'">
<b>{{currentWorkspace.name}}</b> synced with a <a :href="workspaceLocationUrl" target="_blank">Google Drive folder</a>.
<b>{{currentWorkspace.name}}</b> <a :href="workspaceLocationUrl" target="_blank">Google Drive 文件夹</a>同步
</span>
<span v-else-if="currentWorkspace.providerId === 'couchdbWorkspace'">
<b>{{currentWorkspace.name}}</b> synced with a <a :href="workspaceLocationUrl" target="_blank">CouchDB database</a>.
<b>{{currentWorkspace.name}}</b> <a :href="workspaceLocationUrl" target="_blank">CouchDB 数据库</a>同步
</span>
<span v-else-if="currentWorkspace.providerId === 'githubWorkspace'">
<b>{{currentWorkspace.name}}</b> synced with a <a :href="workspaceLocationUrl" target="_blank">GitHub repo</a>.
<b>{{currentWorkspace.name}}</b> <a :href="workspaceLocationUrl" target="_blank">GitHub 仓库</a> 同步
</span>
<span v-else-if="currentWorkspace.providerId === 'giteeWorkspace'">
<b>{{currentWorkspace.name}}</b> <a :href="workspaceLocationUrl" target="_blank">Gitee 仓库</a> 同步
</span>
<span v-else-if="currentWorkspace.providerId === 'gitlabWorkspace'">
<b>{{currentWorkspace.name}}</b> synced with a <a :href="workspaceLocationUrl" target="_blank">GitLab project</a>.
<b>{{currentWorkspace.name}}</b> <a :href="workspaceLocationUrl" target="_blank">GitLab 项目</a>同步
</span>
<span v-else-if="currentWorkspace.providerId === 'giteaWorkspace'">
<b>{{currentWorkspace.name}}</b> <a :href="workspaceLocationUrl" target="_blank">Gitea 项目</a>同步
</span>
</div>
<div class="menu-entry menu-entry--info flex flex--row flex--align-center" v-else>
<div class="menu-entry__icon menu-entry__icon--disabled">
<icon-sync-off></icon-sync-off>
</div>
<span><b>{{currentWorkspace.name}}</b> not synced.</span>
<span><b>{{currentWorkspace.name}}</b> 未同步</span>
</div>
</div>
<menu-entry v-if="!loginToken" @click.native="signin">
<icon-login slot="icon"></icon-login>
<div>Sign in with Google</div>
<span>Sync your main workspace and unlock functionalities.</span>
<div>使用 Gitee 登录</div>
<span>同步您的主文档空间并解锁功能</span>
</menu-entry>
<menu-entry v-if="!loginToken" @click.native="signinWithGithub">
<icon-login slot="icon"></icon-login>
<div>使用 GitHub 登录</div>
<span>同步您的主文档空间并解锁功能</span>
</menu-entry>
<menu-entry @click.native="setPanel('workspaces')">
<icon-database slot="icon"></icon-database>
<div><div class="menu-entry__label menu-entry__label--count" v-if="workspaceCount">{{workspaceCount}}</div> Workspaces</div>
<span>Switch to another workspace.</span>
<div><div class="menu-entry__label menu-entry__label--count" v-if="workspaceCount">{{workspaceCount}}</div> 文档空间</div>
<span>切换到另一个文档空间</span>
</menu-entry>
<hr>
<menu-entry @click.native="setPanel('sync')">
<icon-sync slot="icon"></icon-sync>
<div><div class="menu-entry__label menu-entry__label--count" v-if="syncLocationCount">{{syncLocationCount}}</div> Synchronize</div>
<span>Sync your files in the Cloud.</span>
<div><div class="menu-entry__label menu-entry__label--count" v-if="syncLocationCount">{{syncLocationCount}}</div> 同步</div>
<span>在云端同步您的文件</span>
</menu-entry>
<menu-entry @click.native="setPanel('publish')">
<icon-upload slot="icon"></icon-upload>
<div><div class="menu-entry__label menu-entry__label--count" v-if="publishLocationCount">{{publishLocationCount}}</div>Publish</div>
<span>Export your files to the web.</span>
<div><div class="menu-entry__label menu-entry__label--count" v-if="publishLocationCount">{{publishLocationCount}}</div>发布</div>
<span>将您的文件导出到 Web</span>
</menu-entry>
<menu-entry @click.native="setPanel('history')">
<icon-history slot="icon"></icon-history>
<div>History</div>
<span>Track and restore file revisions.</span>
<div>历史</div>
<span>跟踪和恢复文件修订</span>
</menu-entry>
<menu-entry @click.native="fileProperties">
<icon-view-list slot="icon"></icon-view-list>
<div>File properties</div>
<span>Add metadata and configure extensions.</span>
<div>文件属性</div>
<span>添加元数据并配置扩展</span>
</menu-entry>
<hr>
<menu-entry @click.native="setPanel('toc')">
<icon-toc slot="icon"></icon-toc>
Table of contents
目录
</menu-entry>
<menu-entry @click.native="setPanel('help')">
<icon-help-circle slot="icon"></icon-help-circle>
Markdown cheat sheet
Markdown 帮助
</menu-entry>
<hr>
<menu-entry @click.native="setPanel('importExport')">
<icon-content-save slot="icon"></icon-content-save>
Import/export
导入/导出
</menu-entry>
<menu-entry @click.native="print">
<icon-printer slot="icon"></icon-printer>
Print
打印
</menu-entry>
<hr>
<menu-entry @click.native="badges">
<icon-seal slot="icon"></icon-seal>
<div><div class="menu-entry__label menu-entry__label--count">{{badgeCount}}/{{featureCount}}</div> Badges</div>
<span>List application features and earned badges.</span>
<div><div class="menu-entry__label menu-entry__label--count">{{badgeCount}}/{{featureCount}}</div> 徽章</div>
<span>列出应用程序功能和获得的徽章</span>
</menu-entry>
<menu-entry @click.native="accounts">
<icon-key slot="icon"></icon-key>
<div><div class="menu-entry__label menu-entry__label--count">{{accountCount}}</div> Accounts</div>
<span>Manage access to your external accounts.</span>
<div><div class="menu-entry__label menu-entry__label--count">{{accountCount}}</div> 账号</div>
<span>管理对您的外部账号的访问</span>
</menu-entry>
<menu-entry @click.native="templates">
<icon-code-braces slot="icon"></icon-code-braces>
<div><div class="menu-entry__label menu-entry__label--count">{{templateCount}}</div> Templates</div>
<span>Configure Handlebars templates for your exports.</span>
<div><div class="menu-entry__label menu-entry__label--count">{{templateCount}}</div> 模板</div>
<span>为您的导出配置 Handlebars 模板</span>
</menu-entry>
<menu-entry @click.native="setPanel('editTheme')">
<icon-select-theme slot="icon"></icon-select-theme>
编辑区主题
<span>编辑区主题样式(自定义主题可编辑)</span>
</menu-entry>
<menu-entry @click.native="setPanel('previewTheme')">
<icon-select-theme slot="icon"></icon-select-theme>
预览区主题
<span>预览区主题样式(自定义主题可编辑)</span>
</menu-entry>
<menu-entry @click.native="settings">
<icon-settings slot="icon"></icon-settings>
<div>Settings</div>
<span>Tweak application and keyboard shortcuts.</span>
<div>配置</div>
<span>调整应用程序和键盘快捷键</span>
</menu-entry>
<hr>
<menu-entry @click.native="setPanel('workspaceBackups')">
<icon-content-save slot="icon"></icon-content-save>
Workspace backups
文档空间备份
</menu-entry>
<menu-entry @click.native="reset">
<icon-logout slot="icon"></icon-logout>
Reset application
重置应用程序
</menu-entry>
<menu-entry @click.native="about">
<icon-help-circle slot="icon"></icon-help-circle>
About StackEdit
关于 StackEdit
</menu-entry>
</div>
</template>
@ -125,7 +149,8 @@ import { mapGetters, mapActions } from 'vuex';
import MenuEntry from './common/MenuEntry';
import providerRegistry from '../../services/providers/common/providerRegistry';
import UserImage from '../UserImage';
import googleHelper from '../../services/providers/helpers/googleHelper';
import giteeHelper from '../../services/providers/helpers/giteeHelper';
import githubHelper from '../../services/providers/helpers/githubHelper';
import syncSvc from '../../services/syncSvc';
import userSvc from '../../services/userSvc';
import store from '../../store';
@ -177,7 +202,17 @@ export default {
}),
async signin() {
try {
await googleHelper.signin();
await giteeHelper.signin();
await syncSvc.afterSignIn();
syncSvc.requestSync();
} catch (e) {
// Cancel
}
},
async signinWithGithub() {
try {
await githubHelper.signin();
await syncSvc.afterSignIn();
syncSvc.requestSync();
} catch (e) {
// Cancel

View File

@ -0,0 +1,116 @@
<template>
<div class="preview-theme side-bar__panel side-bar__panel--menu">
<div class="side-bar__info">
<div class="menu-entry menu-entry--info flex flex--row flex--align-center">
<span v-if="currPreviewTheme==='custom'">
下面的自定义主题样式可编辑可参考其他主题样式填入自己喜欢的预览样式<br>
主题class为preview-theme--custom
</span>
<span v-else>
下面的主题样式不可编辑
</span>
</div>
</div>
<div class="side-bar__content">
<template v-if="currPreviewTheme === 'default'">
默认主题无额外样式请选择其他主题
</template>
<template v-else>
<code-editor v-for="(value, index) in styleEles" :key="index"
v-if="value.id === `preview-theme-${currPreviewTheme}`" lang="css" :value="value.innerHTML"
:disabled="value.id!=='preview-theme-custom'" @changed="changeText" scrollClass="side-bar__inner"></code-editor>
</template>
</div>
<div class="flex flex--row flex--end" v-if="currPreviewTheme==='custom'">
<button class="preview-theme__button button" @click="saveStyleText">保存</button>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import MenuEntry from './common/MenuEntry';
import CodeEditor from '../CodeEditor';
import store from '../../store';
export default {
components: {
MenuEntry,
CodeEditor,
},
data: () => ({
themeStyleText: '',
styleEles: [],
}),
computed: {
...mapGetters('theme', [
'currPreviewTheme',
]),
},
methods: {
saveStyleText() {
const typeEle = this.findByTheme(this.currPreviewTheme);
if (!typeEle || !this.themeStyleText) {
return;
}
typeEle.innerHTML = this.themeStyleText;
store.dispatch('theme/setCustomPreviewThemeStyle', this.themeStyleText);
store.dispatch('notification/info', '保存自定义主题样式成功!');
},
findByTheme(theme) {
const findEles = this.styleEles.filter(it => it.id === `preview-theme-${theme}`);
return findEles.length ? findEles[0] : null;
},
changeText(text) {
this.themeStyleText = text;
},
close() {
store.dispatch('data/setSideBarPanel', 'menu');
},
initStyle(theme) {
if (theme === 'default') {
return;
}
const value = theme || this.currPreviewTheme;
if (this.findByTheme(value)) {
return;
}
const styleId = `preview-theme-${value}`;
const styleEle = document.getElementById(styleId);
if (!styleEle) {
setTimeout(() => this.initStyle(value), 1000);
return;
}
this.styleEles.push(styleEle);
},
},
watch: {
currPreviewTheme: {
immediate: true,
handler(val) {
this.initStyle(val);
},
},
},
created() {
this.initStyle();
},
};
</script>
<style lang="scss">
.side-bar__panel--menu {
.side-bar__content {
.code-editor {
min-height: 400px !important;
max-height: 100%;
}
}
.preview-theme__button {
font-size: 14px;
margin-top: 0.5em;
}
}
</style>

View File

@ -1,113 +1,140 @@
<template>
<div class="side-bar__panel side-bar__panel--menu">
<div class="side-bar__info" v-if="isCurrentTemp">
<p>{{currentFileName}} can't be published as it's a temporary file.</p>
<p>{{currentFileName}} 无法发布因为它是一个临时文件</p>
</div>
<div v-else>
<div class="side-bar__info" v-if="publishLocations.length">
<p>{{currentFileName}} is already published.</p>
<p>{{currentFileName}} 已经发布</p>
<menu-entry @click.native="requestPublish">
<icon-upload slot="icon"></icon-upload>
<div>Publish now</div>
<span>Update publications for {{currentFileName}}.</span>
<div>立即发布</div>
<span>发布 {{currentFileName}} 的更新</span>
</menu-entry>
<menu-entry @click.native="managePublish">
<icon-view-list slot="icon"></icon-view-list>
<div><div class="menu-entry__label menu-entry__label--count">{{locationCount}}</div> File publication</div>
<span>Manage publication locations for {{currentFileName}}.</span>
<div><div class="menu-entry__label menu-entry__label--count">{{locationCount}}</div> 文件发布</div>
<span>管理 {{currentFileName}} 的发布位置</span>
</menu-entry>
</div>
<div class="side-bar__info" v-else-if="noToken">
<p>You have to link an account to start publishing files.</p>
<p>您必须链接一个账号才能开始发布文件</p>
</div>
<hr>
<div v-for="token in bloggerTokens" :key="'blogger-' + token.sub">
<menu-entry @click.native="publishBlogger(token)">
<icon-provider slot="icon" provider-id="blogger"></icon-provider>
<div>Publish to Blogger</div>
<div>发布到 Blogger</div>
<span>{{token.name}}</span>
</menu-entry>
<menu-entry @click.native="publishBloggerPage(token)">
<icon-provider slot="icon" provider-id="bloggerPage"></icon-provider>
<div>Publish to Blogger Page</div>
<div>发布到 Blogger Page</div>
<span>{{token.name}}</span>
</menu-entry>
</div>
<div v-for="token in dropboxTokens" :key="token.sub">
<menu-entry @click.native="publishDropbox(token)">
<icon-provider slot="icon" provider-id="dropbox"></icon-provider>
<div>Publish to Dropbox</div>
<div>发布到 Dropbox</div>
<span>{{token.name}}</span>
</menu-entry>
</div>
<div v-for="token in githubTokens" :key="token.sub">
<menu-entry @click.native="publishGist(token)">
<icon-provider slot="icon" provider-id="gist"></icon-provider>
<div>Publish to Gist</div>
<div>发布到 GitHubGist</div>
<span>{{token.name}}</span>
</menu-entry>
<menu-entry @click.native="publishGithub(token)">
<icon-provider slot="icon" provider-id="github"></icon-provider>
<div>Publish to GitHub</div>
<div>发布到 GitHub</div>
<span>{{token.name}}</span>
</menu-entry>
</div>
<div v-for="token in giteeTokens" :key="token.sub">
<menu-entry @click.native="publishGiteeGist(token)">
<icon-provider slot="icon" provider-id="giteegist"></icon-provider>
<div>发布到 GiteeGist</div>
<span>{{token.name}}</span>
</menu-entry>
<menu-entry @click.native="publishGitee(token)">
<icon-provider slot="icon" provider-id="gitee"></icon-provider>
<div>发布到 Gitee</div>
<span>{{token.name}}</span>
</menu-entry>
</div>
<div v-for="token in gitlabTokens" :key="token.sub">
<menu-entry @click.native="publishGitlab(token)">
<icon-provider slot="icon" provider-id="gitlab"></icon-provider>
<div>Publish to GitLab</div>
<div>发布到 GitLab</div>
<span>{{token.name}}</span>
</menu-entry>
</div>
<div v-for="token in giteaTokens" :key="token.sub">
<menu-entry @click.native="publishGitea(token)">
<icon-provider slot="icon" provider-id="gitea"></icon-provider>
<div>发布到 Gitea</div>
<span>{{token.name}}</span>
</menu-entry>
</div>
<div v-for="token in googleDriveTokens" :key="token.sub">
<menu-entry @click.native="publishGoogleDrive(token)">
<icon-provider slot="icon" provider-id="googleDrive"></icon-provider>
<div>Publish to Google Drive</div>
<div>发布到 Google Drive</div>
<span>{{token.name}}</span>
</menu-entry>
</div>
<div v-for="token in wordpressTokens" :key="token.sub">
<menu-entry @click.native="publishWordpress(token)">
<icon-provider slot="icon" provider-id="wordpress"></icon-provider>
<div>Publish to WordPress</div>
<div>发布到 WordPress</div>
<span>{{token.name}}</span>
</menu-entry>
</div>
<div v-for="token in zendeskTokens" :key="token.sub">
<menu-entry @click.native="publishZendesk(token)">
<icon-provider slot="icon" provider-id="zendesk"></icon-provider>
<div>Publish to Zendesk Help Center</div>
<div>发布到 Zendesk Help Center</div>
<span>{{token.name}} {{token.subdomain}}</span>
</menu-entry>
</div>
<hr>
<menu-entry @click.native="addBloggerAccount">
<icon-provider slot="icon" provider-id="blogger"></icon-provider>
<span>Add Blogger account</span>
<span>添加 Blogger 账号</span>
</menu-entry>
<menu-entry @click.native="addDropboxAccount">
<icon-provider slot="icon" provider-id="dropbox"></icon-provider>
<span>Add Dropbox account</span>
<span>添加 Dropbox 账号</span>
</menu-entry>
<menu-entry @click.native="addGithubAccount">
<icon-provider slot="icon" provider-id="github"></icon-provider>
<span>Add GitHub account</span>
<span>添加 GitHub 账号</span>
</menu-entry>
<menu-entry @click.native="addGiteeAccount">
<icon-provider slot="icon" provider-id="gitee"></icon-provider>
<span>添加 Gitee 账号</span>
</menu-entry>
<menu-entry @click.native="addGitlabAccount">
<icon-provider slot="icon" provider-id="gitlab"></icon-provider>
<span>Add GitLab account</span>
<span>添加 GitLab 账号</span>
</menu-entry>
<menu-entry @click.native="addGiteaAccount">
<icon-provider slot="icon" provider-id="gitea"></icon-provider>
<span>添加 Gitea 账号</span>
</menu-entry>
<menu-entry @click.native="addGoogleDriveAccount">
<icon-provider slot="icon" provider-id="googleDrive"></icon-provider>
<span>Add Google Drive account</span>
<span>添加 Google Drive 账号</span>
</menu-entry>
<menu-entry @click.native="addWordpressAccount">
<icon-provider slot="icon" provider-id="wordpress"></icon-provider>
<span>Add WordPress account</span>
<span>添加 WordPress 账号</span>
</menu-entry>
<menu-entry @click.native="addZendeskAccount">
<icon-provider slot="icon" provider-id="zendesk"></icon-provider>
<span>Add Zendesk account</span>
<span>添加 Zendesk 账号</span>
</menu-entry>
</div>
</div>
@ -119,7 +146,9 @@ import MenuEntry from './common/MenuEntry';
import googleHelper from '../../services/providers/helpers/googleHelper';
import dropboxHelper from '../../services/providers/helpers/dropboxHelper';
import githubHelper from '../../services/providers/helpers/githubHelper';
import giteeHelper from '../../services/providers/helpers/giteeHelper';
import gitlabHelper from '../../services/providers/helpers/gitlabHelper';
import giteaHelper from '../../services/providers/helpers/giteaHelper';
import wordpressHelper from '../../services/providers/helpers/wordpressHelper';
import zendeskHelper from '../../services/providers/helpers/zendeskHelper';
import publishSvc from '../../services/publishSvc';
@ -168,9 +197,15 @@ export default {
githubTokens() {
return tokensToArray(store.getters['data/githubTokensBySub']);
},
giteeTokens() {
return tokensToArray(store.getters['data/giteeTokensBySub']);
},
gitlabTokens() {
return tokensToArray(store.getters['data/gitlabTokensBySub']);
},
giteaTokens() {
return tokensToArray(store.getters['data/giteaTokensBySub']);
},
googleDriveTokens() {
return tokensToArray(store.getters['data/googleTokensBySub'], token => token.isDrive);
},
@ -184,7 +219,9 @@ export default {
return !this.bloggerTokens.length
&& !this.dropboxTokens.length
&& !this.githubTokens.length
&& !this.giteeTokens.length
&& !this.gitlabTokens.length
&& !this.giteaTokens.length
&& !this.googleDriveTokens.length
&& !this.wordpressTokens.length
&& !this.zendeskTokens.length;
@ -218,10 +255,22 @@ export default {
await githubHelper.addAccount(store.getters['data/localSettings'].githubRepoFullAccess);
} catch (e) { /* cancel */ }
},
async addGiteeAccount() {
try {
await store.dispatch('modal/open', { type: 'giteeAccount' });
await giteeHelper.addAccount();
} catch (e) { /* cancel */ }
},
async addGitlabAccount() {
try {
const { serverUrl, applicationId } = await store.dispatch('modal/open', { type: 'gitlabAccount' });
await gitlabHelper.addAccount(serverUrl, applicationId);
const { serverUrl, applicationId, applicationSecret } = await store.dispatch('modal/open', { type: 'gitlabAccount' });
await gitlabHelper.addAccount(serverUrl, applicationId, applicationSecret);
} catch (e) { /* cancel */ }
},
async addGiteaAccount() {
try {
const applicationInfo = await store.dispatch('modal/open', { type: 'giteaAccount' });
await giteaHelper.addAccount(applicationInfo);
} catch (e) { /* cancel */ }
},
async addGoogleDriveAccount() {
@ -246,7 +295,10 @@ export default {
publishDropbox: publishModalOpener('dropboxPublish', 'publishToDropbox'),
publishGithub: publishModalOpener('githubPublish', 'publishToGithub'),
publishGist: publishModalOpener('gistPublish', 'publishToGist'),
publishGitee: publishModalOpener('giteePublish', 'publishToGitee'),
publishGiteeGist: publishModalOpener('giteeGistPublish', 'publishGiteeGist'),
publishGitlab: publishModalOpener('gitlabPublish', 'publishToGitlab'),
publishGitea: publishModalOpener('giteaPublish', 'publishToGitea'),
publishGoogleDrive: publishModalOpener('googleDrivePublish', 'publishToGoogleDrive'),
publishWordpress: publishModalOpener('wordpressPublish', 'publishToWordPress'),
publishZendesk: publishModalOpener('zendeskPublish', 'publishToZendesk'),

View File

@ -1,95 +1,132 @@
<template>
<div class="side-bar__panel side-bar__panel--menu">
<div class="side-bar__info" v-if="isCurrentTemp">
<p>{{currentFileName}} can't be synced as it's a temporary file.</p>
<p>{{currentFileName}} 无法同步因为它是临时文件</p>
</div>
<div v-else>
<div class="side-bar__info" v-if="syncLocations.length">
<p>{{currentFileName}} is already synchronized.</p>
<p>{{currentFileName}} 已同步</p>
<menu-entry @click.native="requestSync">
<icon-sync slot="icon"></icon-sync>
<div>Synchronize now</div>
<span>Download / upload file changes.</span>
<div>立即同步</div>
<span>下载/上载文件更改</span>
</menu-entry>
<menu-entry @click.native="manageSync">
<icon-view-list slot="icon"></icon-view-list>
<div><div class="menu-entry__label menu-entry__label--count">{{locationCount}}</div> File synchronization</div>
<span>Manage synchronized locations for {{currentFileName}}.</span>
<div><div class="menu-entry__label menu-entry__label--count">{{locationCount}}</div> 文件同步</div>
<span>管理 {{currentFileName}} 的同步位置</span>
</menu-entry>
</div>
<div class="side-bar__info" v-else-if="noToken">
<p>You have to link an account to start syncing files.</p>
<p>您必须链接一个账号才能开始同步文件</p>
</div>
<hr>
<div v-for="token in dropboxTokens" :key="token.sub">
<menu-entry @click.native="openDropbox(token)">
<icon-provider slot="icon" provider-id="dropbox"></icon-provider>
<div>Open from Dropbox</div>
<div>Dropbox 打开</div>
<span>{{token.name}}</span>
</menu-entry>
<menu-entry @click.native="saveDropbox(token)">
<icon-provider slot="icon" provider-id="dropbox"></icon-provider>
<div>Save on Dropbox</div>
<div>在Dropbox上保存</div>
<span>{{token.name}}</span>
</menu-entry>
</div>
<div v-for="token in githubTokens" :key="token.sub">
<menu-entry @click.native="openGithub(token)">
<icon-provider slot="icon" provider-id="github"></icon-provider>
<div>Open from GitHub</div>
<div>GitHub 打开</div>
<span>{{token.name}}</span>
</menu-entry>
<menu-entry @click.native="saveGithub(token)">
<icon-provider slot="icon" provider-id="github"></icon-provider>
<div>Save on GitHub</div>
<div>在GitHub上保存</div>
<span>{{token.name}}</span>
</menu-entry>
<menu-entry @click.native="saveGist(token)">
<icon-provider slot="icon" provider-id="gist"></icon-provider>
<div>Save on Gist</div>
<div>在GitHubGist上保存</div>
<span>{{token.name}}</span>
</menu-entry>
</div>
<div v-for="token in giteeTokens" :key="token.sub">
<menu-entry @click.native="openGitee(token)">
<icon-provider slot="icon" provider-id="gitee"></icon-provider>
<div> Gitee 打开</div>
<span>{{token.name}}</span>
</menu-entry>
<menu-entry @click.native="saveGitee(token)">
<icon-provider slot="icon" provider-id="gitee"></icon-provider>
<div>在Gitee上保存</div>
<span>{{token.name}}</span>
</menu-entry>
<menu-entry @click.native="saveGiteeGist(token)">
<icon-provider slot="icon" provider-id="giteegist"></icon-provider>
<div>在GiteeGist上保存</div>
<span>{{token.name}}</span>
</menu-entry>
</div>
<div v-for="token in gitlabTokens" :key="token.sub">
<menu-entry @click.native="openGitlab(token)">
<icon-provider slot="icon" provider-id="gitlab"></icon-provider>
<div>Open from GitLab</div>
<div>GitLab 打开</div>
<span>{{token.name}}</span>
</menu-entry>
<menu-entry @click.native="saveGitlab(token)">
<icon-provider slot="icon" provider-id="gitlab"></icon-provider>
<div>Save on GitLab</div>
<div>在GitLab上保存</div>
<span>{{token.name}}</span>
</menu-entry>
</div>
<div v-for="token in giteaTokens" :key="token.sub">
<menu-entry @click.native="openGitea(token)">
<icon-provider slot="icon" provider-id="gitea"></icon-provider>
<div> Gitea 打开</div>
<span>{{token.name}}</span>
</menu-entry>
<menu-entry @click.native="saveGitea(token)">
<icon-provider slot="icon" provider-id="gitea"></icon-provider>
<div>在Gitea上保存</div>
<span>{{token.name}}</span>
</menu-entry>
</div>
<div v-for="token in googleDriveTokens" :key="token.sub">
<menu-entry @click.native="openGoogleDrive(token)">
<icon-provider slot="icon" provider-id="googleDrive"></icon-provider>
<div>Open from Google Drive</div>
<div>Google Drive 打开</div>
<span>{{token.name}}</span>
</menu-entry>
<menu-entry @click.native="saveGoogleDrive(token)">
<icon-provider slot="icon" provider-id="googleDrive"></icon-provider>
<div>Save on Google Drive</div>
<div>在Google Drive上保存</div>
<span>{{token.name}}</span>
</menu-entry>
</div>
<hr>
<menu-entry @click.native="addDropboxAccount">
<icon-provider slot="icon" provider-id="dropbox"></icon-provider>
<span>Add Dropbox account</span>
<span>添加 Dropbox 账号</span>
</menu-entry>
<menu-entry @click.native="addGithubAccount">
<icon-provider slot="icon" provider-id="github"></icon-provider>
<span>Add GitHub account</span>
<span>添加 GitHub 账号</span>
</menu-entry>
<menu-entry @click.native="addGiteeAccount">
<icon-provider slot="icon" provider-id="gitee"></icon-provider>
<span>添加 Gitee 账号</span>
</menu-entry>
<menu-entry @click.native="addGitlabAccount">
<icon-provider slot="icon" provider-id="gitlab"></icon-provider>
<span>Add GitLab account</span>
<span>添加 GitLab 账号</span>
</menu-entry>
<menu-entry @click.native="addGiteaAccount">
<icon-provider slot="icon" provider-id="gitea"></icon-provider>
<span>添加 Gitea 账号</span>
</menu-entry>
<menu-entry @click.native="addGoogleDriveAccount">
<icon-provider slot="icon" provider-id="googleDrive"></icon-provider>
<span>Add Google Drive account</span>
<span>添加 Google Drive 账号</span>
</menu-entry>
</div>
</div>
@ -101,11 +138,15 @@ import MenuEntry from './common/MenuEntry';
import googleHelper from '../../services/providers/helpers/googleHelper';
import dropboxHelper from '../../services/providers/helpers/dropboxHelper';
import githubHelper from '../../services/providers/helpers/githubHelper';
import giteeHelper from '../../services/providers/helpers/giteeHelper';
import gitlabHelper from '../../services/providers/helpers/gitlabHelper';
import giteaHelper from '../../services/providers/helpers/giteaHelper';
import googleDriveProvider from '../../services/providers/googleDriveProvider';
import dropboxProvider from '../../services/providers/dropboxProvider';
import githubProvider from '../../services/providers/githubProvider';
import giteeProvider from '../../services/providers/giteeProvider';
import gitlabProvider from '../../services/providers/gitlabProvider';
import giteaProvider from '../../services/providers/giteaProvider';
import syncSvc from '../../services/syncSvc';
import store from '../../store';
import badgeSvc from '../../services/badgeSvc';
@ -148,16 +189,23 @@ export default {
githubTokens() {
return tokensToArray(store.getters['data/githubTokensBySub']);
},
giteeTokens() {
return tokensToArray(store.getters['data/giteeTokensBySub']);
},
gitlabTokens() {
return tokensToArray(store.getters['data/gitlabTokensBySub']);
},
giteaTokens() {
return tokensToArray(store.getters['data/giteaTokensBySub']);
},
googleDriveTokens() {
return tokensToArray(store.getters['data/googleTokensBySub'], token => token.isDrive);
},
noToken() {
return !this.googleDriveTokens.length
&& !this.dropboxTokens.length
&& !this.githubTokens.length;
&& !this.githubTokens.length
&& !this.giteeTokens.length;
},
},
methods: {
@ -183,10 +231,22 @@ export default {
await githubHelper.addAccount(store.getters['data/localSettings'].githubRepoFullAccess);
} catch (e) { /* cancel */ }
},
async addGiteeAccount() {
try {
await store.dispatch('modal/open', { type: 'giteeAccount' });
await giteeHelper.addAccount();
} catch (e) { /* cancel */ }
},
async addGitlabAccount() {
try {
const { serverUrl, applicationId } = await store.dispatch('modal/open', { type: 'gitlabAccount' });
await gitlabHelper.addAccount(serverUrl, applicationId);
const { serverUrl, applicationId, applicationSecret } = await store.dispatch('modal/open', { type: 'gitlabAccount' });
await gitlabHelper.addAccount(serverUrl, applicationId, applicationSecret);
} catch (e) { /* cancel */ }
},
async addGiteaAccount() {
try {
const applicationInfo = await store.dispatch('modal/open', { type: 'giteaAccount' });
await giteaHelper.addAccount(applicationInfo);
} catch (e) { /* cancel */ }
},
async addGoogleDriveAccount() {
@ -248,12 +308,39 @@ export default {
badgeSvc.addBadge('saveOnGithub');
} catch (e) { /* cancel */ }
},
async openGitee(token) {
try {
const syncLocation = await store.dispatch('modal/open', {
type: 'giteeOpen',
token,
});
store.dispatch(
'queue/enqueue',
async () => {
await giteeProvider.openFile(token, syncLocation);
badgeSvc.addBadge('openFromGitee');
},
);
} catch (e) { /* cancel */ }
},
async saveGitee(token) {
try {
await openSyncModal(token, 'giteeSave');
badgeSvc.addBadge('saveOnGitee');
} catch (e) { /* cancel */ }
},
async saveGist(token) {
try {
await openSyncModal(token, 'gistSync');
badgeSvc.addBadge('saveOnGist');
} catch (e) { /* cancel */ }
},
async saveGiteeGist(token) {
try {
await openSyncModal(token, 'giteeGistSync');
badgeSvc.addBadge('saveOnGiteeGist');
} catch (e) { /* cancel */ }
},
async openGitlab(token) {
try {
const syncLocation = await store.dispatch('modal/open', {
@ -269,12 +356,33 @@ export default {
);
} catch (e) { /* cancel */ }
},
async openGitea(token) {
try {
const syncLocation = await store.dispatch('modal/open', {
type: 'giteaOpen',
token,
});
store.dispatch(
'queue/enqueue',
async () => {
await giteaProvider.openFile(token, syncLocation);
badgeSvc.addBadge('openFromGitea');
},
);
} catch (e) { /* cancel */ }
},
async saveGitlab(token) {
try {
await openSyncModal(token, 'gitlabSave');
badgeSvc.addBadge('saveOnGitlab');
} catch (e) { /* cancel */ }
},
async saveGitea(token) {
try {
await openSyncModal(token, 'giteaSave');
badgeSvc.addBadge('saveOnGitea');
} catch (e) { /* cancel */ }
},
},
};
</script>

View File

@ -6,12 +6,12 @@
<icon-content-save></icon-content-save>
</div>
<div class="flex flex--column">
Import workspace backup
导入文档空间备份
</div>
</label>
<menu-entry @click.native="exportWorkspace">
<icon-content-save slot="icon"></icon-content-save>
Export workspace backup
导出文档空间备份
</menu-entry>
</div>
</template>

View File

@ -2,32 +2,41 @@
<div class="side-bar__panel side-bar__panel--menu">
<menu-entry @click.native="manageWorkspaces">
<icon-database slot="icon"></icon-database>
<div><div class="menu-entry__label menu-entry__label--count">{{workspaceCount}}</div> Manage workspaces</div>
<span>List, rename, remove workspaces</span>
<div><div class="menu-entry__label menu-entry__label--count">{{workspaceCount}}</div> 管理文档空间</div>
<span>列出重命名删除文档空间</span>
</menu-entry>
<hr>
<div class="workspace" v-for="(workspace, id) in workspacesById" :key="id">
<menu-entry :href="workspace.url" target="_blank">
<icon-provider slot="icon" :provider-id="workspace.providerId"></icon-provider>
<div class="workspace__name"><div class="menu-entry__label" v-if="currentWorkspace === workspace">current</div>{{workspace.name}}</div>
<icon-provider v-if="id === 'main' && !workspace.sub" slot="icon" :provider-id="'stackedit'"></icon-provider>
<icon-provider v-else slot="icon" :provider-id="workspace.providerId"></icon-provider>
<div class="workspace__name"><div class="menu-entry__label" v-if="currentWorkspace === workspace">当前</div>{{workspace.name}}</div>
</menu-entry>
</div>
<hr>
<menu-entry @click.native="addCouchdbWorkspace">
<icon-provider slot="icon" provider-id="couchdbWorkspace"></icon-provider>
<span>Add a <b>CouchDB</b> workspace</span>
</menu-entry>
<menu-entry @click.native="addGithubWorkspace">
<icon-provider slot="icon" provider-id="githubWorkspace"></icon-provider>
<span>Add a <b>GitHub</b> workspace</span>
<span>新增 <b>GitHub</b> 文档空间</span>
</menu-entry>
<menu-entry @click.native="addGiteeWorkspace">
<icon-provider slot="icon" provider-id="giteeWorkspace"></icon-provider>
<span>新增 <b>Gitee</b> 文档空间</span>
</menu-entry>
<menu-entry @click.native="addGitlabWorkspace">
<icon-provider slot="icon" provider-id="gitlabWorkspace"></icon-provider>
<span>Add a <b>GitLab</b> workspace</span>
<span>新增 <b>GitLab</b> 文档空间</span>
</menu-entry>
<menu-entry @click.native="addGiteaWorkspace">
<icon-provider slot="icon" provider-id="giteaWorkspace"></icon-provider>
<span>新增 <b>Gitea</b> 文档空间</span>
</menu-entry>
<menu-entry @click.native="addGoogleDriveWorkspace">
<icon-provider slot="icon" provider-id="googleDriveWorkspace"></icon-provider>
<span>Add a <b>Google Drive</b> workspace</span>
<span>新增 <b>Google Drive</b> 文档空间</span>
</menu-entry>
<menu-entry @click.native="addCouchdbWorkspace">
<icon-provider slot="icon" provider-id="couchdbWorkspace"></icon-provider>
<span>新增 <b>CouchDB</b> 文档空间</span>
</menu-entry>
</div>
</template>
@ -37,6 +46,7 @@ import { mapGetters } from 'vuex';
import MenuEntry from './common/MenuEntry';
import googleHelper from '../../services/providers/helpers/googleHelper';
import gitlabHelper from '../../services/providers/helpers/gitlabHelper';
import giteaHelper from '../../services/providers/helpers/giteaHelper';
import store from '../../store';
export default {
@ -67,16 +77,33 @@ export default {
});
} catch (e) { /* Cancel */ }
},
async addGiteeWorkspace() {
try {
store.dispatch('modal/open', {
type: 'giteeWorkspace',
});
} catch (e) { /* Cancel */ }
},
async addGitlabWorkspace() {
try {
const { serverUrl, applicationId } = await store.dispatch('modal/open', { type: 'gitlabAccount' });
const token = await gitlabHelper.addAccount(serverUrl, applicationId);
const { serverUrl, applicationId, applicationSecret } = await store.dispatch('modal/open', { type: 'gitlabAccount' });
const token = await gitlabHelper.addAccount(serverUrl, applicationId, applicationSecret);
store.dispatch('modal/open', {
type: 'gitlabWorkspace',
token,
});
} catch (e) { /* Cancel */ }
},
async addGiteaWorkspace() {
try {
const applicationInfo = await store.dispatch('modal/open', { type: 'giteaAccount' });
const token = await giteaHelper.addAccount(applicationInfo);
store.dispatch('modal/open', {
type: 'giteaWorkspace',
token,
});
} catch (e) { /* Cancel */ }
},
async addGoogleDriveWorkspace() {
try {
const token = await googleHelper.addDriveAccount(true);

View File

@ -73,6 +73,10 @@
background-color: #fff;
border-radius: 3px;
opacity: 0.6;
.app--dark & {
background-color: #000;
}
}
.menu-entry__label--warning {

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