Compare commits

...

245 Commits
dev ... master

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
Benoit Schweblin
b4d34a9390 Tag v5.14.2 2019-07-08 10:21:56 +01:00
Benoit Schweblin
0001e8c178 Fixed ok button in link and image modal 2019-07-08 10:20:04 +01:00
Benoit Schweblin
90520abf93 Removed kubectl in deploy script 2019-07-07 02:38:00 +01:00
Benoit Schweblin
f611060d05 Fixed typos 2019-07-02 23:52:58 +01:00
Benoit Schweblin
d2867ea1c0 Deploy script upgrades release with kubectl 2019-07-02 21:22:14 +01:00
Benoit Schweblin
b2158fc1c3 Added helm chart. 2019-07-02 10:31:14 +01:00
Benoit Schweblin
5a4fb796e0 Tag v5.14.1 2019-06-30 14:07:34 +01:00
Benoit Schweblin
043a3cd709 Added deploy script 2019-06-30 14:07:10 +01:00
Benoit Schweblin
8a6df3ef10 Fixed layout settings badge creation.
Hide sponsorship if no paypal email is configured.
2019-06-30 13:28:37 +01:00
Benoit Schweblin
e9e3cb809d Tag v5.14.0 2019-06-29 17:39:06 +01:00
Benoit Schweblin
07d824faca Added server conf endpoint.
New localDbSvc.getWorkspaceItems method used to export workspaces.
Added offline availability in the workspace management modal.
New accordion in the badge management modal.
Add badge creation checks in unit tests.
2019-06-29 17:33:21 +01:00
Benoit Schweblin
3f0597601e Added per_page parameter to GitLab tree API. Fixes #1505, #1462. 2019-06-24 23:27:59 +01:00
Benoit Schweblin
274a800742 Upgraded to Katex v0.10.2 2019-06-23 02:29:11 +01:00
Benoit Schweblin
72a15b5039 Fixed unit tests. 2019-06-23 02:21:50 +01:00
Benoit Schweblin
dc08ee4da4
Merge pull request #1469 from rkuhlf/master
Enabled momentum scrolling
2019-06-23 01:56:22 +01:00
Benoit Schweblin
a98e9a382e
Merge pull request #1415 from efemes/1-can-t-rotate-progressive-web-app
Allow rotation of Progressive Web App
2019-06-23 01:51:02 +01:00
Benoit Schweblin
a8ee8cdccd
Merge pull request #1501 from eralpsahin/fix/checkbox-check
Fix checkbox not checked on HTML export
2019-06-23 01:47:12 +01:00
Benoit Schweblin
f05b089485
Merge pull request #1497 from Flamenco/fix_explorer_natural_sort
Use natural sorting for explorer file nodes.
2019-06-22 22:56:48 +01:00
Benoit Schweblin
d6fb85a5ad Fixed GitHub security warning. 2019-06-22 22:54:28 +01:00
Benoit Schweblin
78802448c3 Fixed #1503. 2019-06-22 22:53:56 +01:00
Benoit Schweblin
1b2d48ff22 Removed monetizejs sponsorship support.
Reduced time counter increment interval.
Added badge service.
Refactored user service.
Replaced Google+ with People API.
2019-06-22 22:19:01 +01:00
Benoit Schweblin
2a865ddb44 Reorganized side bar menu 2019-06-18 13:49:48 +01:00
Eralp Sahin
c462a13ab5 Fix checkbox not checked on HTML export 2019-06-17 17:48:01 -04:00
Benoit Schweblin
8cf0b87f5f Added account management modal 2019-06-16 14:49:50 +01:00
Steven Spungin
835ef7f5bc Use natural sorting for explore nodes 2019-06-12 07:14:08 -04:00
Riley Kuhlman
8156038c53 Enabled momentum scrolling 2019-04-25 20:06:04 -05:00
Benoit Schweblin
91f8cf3c10
Merge pull request #1402 from GreenGremlin/google-profile-scope
Ensuring profile data is accesible for all Google users
2019-01-26 14:35:21 +00:00
Benoit Schweblin
1ac1e057cf Tag v5.13.3 2018-11-15 12:27:57 +01:00
Benoit Schweblin
da0ccf8d82 Fixed payment success modal 2018-11-15 12:26:28 +01:00
Benoit Schweblin
cf18184e26 Fixed light mode auto-close 2018-11-15 12:26:04 +01:00
Stefan Meller
deba3c12be Set orientation to 'any' in manifest.json generator 2018-11-05 10:55:02 +00:00
Jonathan Felchlin
547f44043c Ensuring profile data is accessible for all Google users 2018-10-01 22:38:20 -07:00
Benoit Schweblin
daadae1720 Tag v5.13.2 2018-09-21 10:54:10 +01:00
Benoit Schweblin
0e8aa0a58a Added A2HS prompt 2018-09-21 10:12:05 +01:00
Benoit Schweblin
f7542965b6 Tag v5.13.1 2018-09-20 12:12:58 +01:00
Benoit Schweblin
6fd4513c34 Fixed git workspace sync/publish locations 2018-09-20 12:12:27 +01:00
Benoit Schweblin
fa3d9b30c2 Tag v5.13.0 2018-09-20 11:56:58 +01:00
Benoit Schweblin
4be3200961 Fixed duplicate Main workspace 2018-09-20 11:52:57 +01:00
328 changed files with 19502 additions and 4906 deletions

View File

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

View File

@ -16,9 +16,7 @@ module.exports = {
],
globals: {
"NODE_ENV": false,
"VERSION": false,
"GOOGLE_CLIENT_ID": false,
"GITHUB_CLIENT_ID": false
"VERSION": false
},
// check if imports actually resolve
'settings': {

1
.gitignore vendored
View File

@ -2,6 +2,7 @@
node_modules/
dist/
.history
.idea
npm-debug.log*
.vscode
stackedit_v4

View File

@ -1,16 +1,22 @@
language: node_js
node_js:
- "8"
- "12"
services:
- docker
before_deploy:
# Run docker build
- docker build -t benweet/stackedit .
# Install Helm
- 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
deploy:
provider: script
script: bash build/docker-push-tag.sh
script: bash build/deploy.sh
on:
tags: true

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

209
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 Setup
**拖拽粘贴上传图片**
![](./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,27 +189,3 @@ npm run build
# build for production and view the bundle analyzer report
npm run build --report
```
### StackEdit can:
- Manage multiple Markdown files online or offline
- Export your files in Markdown, HTML, PDF, Word, EPUB...
- Synchronize your Markdown files in the Cloud
- Edit existing Markdown files from Google Drive, Dropbox and your local hard drive
- Post your Markdown file on Blogger/Blogspot, WordPress, Zendesk
- Publish your Markdown file on GitHub, Gist, Google Drive, Dropbox
- Share a workspace over Google Drive, CouchDB
### Features:
- Real-time HTML preview with Scroll Sync feature to bind editor and preview scrollbars
- Markdown Extra/GitHub Flavored Markdown support and Prism.js syntax highlighting
- LaTeX mathematical expressions using KaTeX
- Diagrams and flowcharts using Mermaid
- WYSIWYG control buttons
- Smart layout
- Offline editing
- Online synchronization using Google Drive, Dropbox and GitHub
- One click publish to Blogger, Dropbox, Gist, GitHub, Google Drive, WordPress, Zendesk
> **NOTE:** This page has been written and published with [StackEdit](https://stackedit.io/ "StackEdit").

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 "操作完成"

24
build/deploy.sh Normal file
View File

@ -0,0 +1,24 @@
#!/bin/bash
set -e
# Tag and push docker image
docker login -u benweet -p "$DOCKER_PASSWORD"
docker tag benweet/stackedit "benweet/stackedit:$TRAVIS_TAG"
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 --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"
git add .
git commit -m "Added $TRAVIS_TAG"
git push origin master

View File

@ -1,7 +0,0 @@
#!/bin/bash
set -e
docker login -u benweet -p "$DOCKER_PASSWORD"
docker tag benweet/stackedit "benweet/stackedit:$TRAVIS_TAG"
docker push benweet/stackedit:$TRAVIS_TAG
docker tag benweet/stackedit:$TRAVIS_TAG benweet/stackedit:latest
docker push benweet/stackedit:latest

View File

@ -5,7 +5,6 @@ var config = require('../config')
var VueLoaderPlugin = require('vue-loader/lib/plugin')
var vueLoaderConfig = require('./vue-loader.conf')
var StylelintPlugin = require('stylelint-webpack-plugin')
var FaviconsWebpackPlugin = require('favicons-webpack-plugin')
function resolve (dir) {
return path.join(__dirname, '..', dir)
@ -48,16 +47,33 @@ module.exports = {
loader: 'vue-loader',
options: vueLoaderConfig
},
// We can't pass graphlibrary to babel
{
test: /\.js$/,
loader: 'string-replace-loader',
include: [
resolve('node_modules/graphlibrary')
],
options: {
search: '^\\s*(?:let|const) ',
replace: 'var ',
flags: 'gm'
}
},
{
test: /\.js$/,
loader: 'babel-loader',
include: [resolve('src'), resolve('test'), resolve('node_modules/mermaid/src')],
include: [
resolve('src'),
resolve('test'),
resolve('node_modules/mermaid')
],
exclude: [
resolve('node_modules/mermaid/src/diagrams/class/parser'),
resolve('node_modules/mermaid/src/diagrams/flowchart/parser'),
resolve('node_modules/mermaid/src/diagrams/gantt/parser'),
resolve('node_modules/mermaid/src/diagrams/git/parser'),
resolve('node_modules/mermaid/src/diagrams/sequence/parser'),
resolve('node_modules/mermaid/src/diagrams/sequence/parser')
],
},
{
@ -86,10 +102,6 @@ module.exports = {
new StylelintPlugin({
files: ['**/*.vue', '**/*.scss']
}),
new FaviconsWebpackPlugin({
logo: resolve('src/assets/favicon.png'),
title: 'StackEdit',
}),
new webpack.DefinePlugin({
VERSION: JSON.stringify(require('../package.json').version)
})

View File

@ -19,9 +19,7 @@ module.exports = merge(baseWebpackConfig, {
devtool: 'source-map',
plugins: [
new webpack.DefinePlugin({
NODE_ENV: config.dev.env.NODE_ENV,
GOOGLE_CLIENT_ID: config.dev.env.GOOGLE_CLIENT_ID,
GITHUB_CLIENT_ID: config.dev.env.GITHUB_CLIENT_ID
NODE_ENV: config.dev.env.NODE_ENV
}),
// https://github.com/glenjamin/webpack-hot-middleware#installation--usage
new webpack.HotModuleReplacementPlugin(),

View File

@ -9,6 +9,12 @@ var HtmlWebpackPlugin = require('html-webpack-plugin')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
var OfflinePlugin = require('offline-plugin');
var WebpackPwaManifest = require('webpack-pwa-manifest')
var FaviconsWebpackPlugin = require('favicons-webpack-plugin')
function resolve (dir) {
return path.join(__dirname, '..', dir)
}
var env = config.build.env
@ -94,6 +100,23 @@ var webpackConfig = merge(baseWebpackConfig, {
ignore: ['.*']
}
]),
new FaviconsWebpackPlugin({
logo: resolve('src/assets/favicon.png'),
title: 'StackEdit',
}),
new WebpackPwaManifest({
name: 'StackEdit',
description: 'Full-featured, open-source Markdown editor',
display: 'standalone',
orientation: 'any',
start_url: 'app',
background_color: '#ffffff',
crossorigin: 'use-credentials',
icons: [{
src: resolve('src/assets/favicon.png'),
sizes: [96, 128, 192, 256, 384, 512]
}]
}),
new OfflinePlugin({
ServiceWorker: {
events: true
@ -101,7 +124,7 @@ var webpackConfig = merge(baseWebpackConfig, {
AppCache: true,
excludes: ['**/.*', '**/*.map', '**/index.html', '**/static/oauth2/callback.html', '**/icons-*/*.png', '**/static/fonts/KaTeX_*'],
externals: ['/', '/app', '/oauth2/callback']
})
}),
]
})

22
chart/.helmignore Normal file
View File

@ -0,0 +1,22 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

5
chart/Chart.yaml Normal file
View File

@ -0,0 +1,5 @@
apiVersion: v1
appVersion: vSTACKEDIT_VERSION
description: In-browser Markdown editor
name: stackedit
version: STACKEDIT_VERSION

21
chart/templates/NOTES.txt Normal file
View File

@ -0,0 +1,21 @@
1. Get the application URL by running these commands:
{{- if .Values.ingress.enabled }}
{{- range $host := .Values.ingress.hosts }}
{{- range .paths }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }}
{{- end }}
{{- end }}
{{- else if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "stackedit.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "stackedit.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "stackedit.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "stackedit.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl port-forward $POD_NAME 8080:80
{{- end }}

View File

@ -0,0 +1,45 @@
{{/* vim: set filetype=mustache: */}}
{{/*
Expand the name of the chart.
*/}}
{{- define "stackedit.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "stackedit.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "stackedit.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Common labels
*/}}
{{- define "stackedit.labels" -}}
app.kubernetes.io/name: {{ include "stackedit.name" . }}
helm.sh/chart: {{ include "stackedit.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end -}}

View File

@ -0,0 +1,87 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "stackedit.fullname" . }}
labels:
{{ include "stackedit.labels" . | indent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app.kubernetes.io/name: {{ include "stackedit.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "stackedit.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
volumeMounts:
- mountPath: /run
name: run-volume
- mountPath: /tmp
name: tmp-volume
env:
- name: PORT
value: "80"
- name: PAYPAL_RECEIVER_EMAIL
value: {{ .Values.paypalReceiverEmail }}
- name: AWS_ACCESS_KEY_ID
value: {{ .Values.awsAccessKeyId }}
- name: AWS_SECRET_ACCESS_KEY
value: {{ .Values.awsSecretAccessKey }}
- name: DROPBOX_APP_KEY
value: {{ .Values.dropboxAppKey }}
- name: DROPBOX_APP_KEY_FULL
value: {{ .Values.dropboxAppKeyFull }}
- name: GOOGLE_CLIENT_ID
value: {{ .Values.googleClientId }}
- name: GOOGLE_API_KEY
value: {{ .Values.googleApiKey }}
- name: GITHUB_CLIENT_ID
value: {{ .Values.githubClientId }}
- name: GITHUB_CLIENT_SECRET
value: {{ .Values.githubClientSecret }}
- name: WORDPRESS_CLIENT_ID
value: {{ .Values.wordpressClientId }}
- name: WORDPRESS_SECRET
value: {{ .Values.wordpressSecret }}
ports:
- name: http
containerPort: 80
protocol: TCP
livenessProbe:
httpGet:
path: /
port: http
readinessProbe:
httpGet:
path: /
port: http
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumes:
- name: run-volume
emptyDir: {}
- name: tmp-volume
emptyDir: {}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@ -0,0 +1,39 @@
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "stackedit.fullname" . -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ $fullName }}
labels:
{{ include "stackedit.labels" . | indent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ . }}
pathType: Prefix
backend:
service:
name: {{ $fullName }}
port:
name: http
{{- end }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,16 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "stackedit.fullname" . }}
labels:
{{ include "stackedit.labels" . | indent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
app.kubernetes.io/name: {{ include "stackedit.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}

View File

@ -0,0 +1,15 @@
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "stackedit.fullname" . }}-test-connection"
labels:
{{ include "stackedit.labels" . | indent 4 }}
annotations:
"helm.sh/hook": test-success
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['{{ include "stackedit.fullname" . }}:{{ .Values.service.port }}']
restartPolicy: Never

71
chart/values.yaml Normal file
View File

@ -0,0 +1,71 @@
# Default values for stackedit.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
dropboxAppKey: ""
dropboxAppKeyFull: ""
googleClientId: ""
googleApiKey: ""
githubClientId: ""
githubClientSecret: ""
giteeClientId: ""
giteeClientSecret: ""
wordpressClientId: ""
wordpressSecret: ""
paypalReceiverEmail: ""
awsAccessKeyId: ""
awsSecretAccessKey: ""
giteaClientId: ""
giteaClientSecret: ""
giteaUrl: ""
gitlabClientId: ""
gitlabUrl: ""
replicaCount: 1
image:
repository: benweet/stackedit
tag: vSTACKEDIT_VERSION
pullPolicy: IfNotPresent
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
service:
type: ClusterIP
port: 80
ingress:
enabled: false
annotations:
# kubernetes.io/ingress.class: nginx
# certmanager.k8s.io/issuer: letsencrypt-prod
# certmanager.k8s.io/acme-challenge-type: http01
hosts: []
# - host: stackedit.example.com
# paths:
# - /
tls: []
# - secretName: stackedit-tls
# hosts:
# - stackedit.example.com
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
nodeSelector: {}
tolerations: []
affinity: {}

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

View File

@ -3,6 +3,16 @@ var prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
NODE_ENV: '"development"',
GOOGLE_CLIENT_ID: '"241271498917-c3loeet001r90q6u79q484bsh5clg4fr.apps.googleusercontent.com"',
GITHUB_CLIENT_ID: '"cbf0cf25cfd026be23e1"'
// 以下配置是开发临时用的配置 随时可能失效 请替换为自己的
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,8 +23,8 @@ module.exports = {
},
dev: {
env: require('./dev.env'),
port: 8080,
autoOpenBrowser: true,
port: 80,
autoOpenBrowser: false,
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {},

View File

@ -1,5 +1,3 @@
module.exports = {
NODE_ENV: '"production"',
GOOGLE_CLIENT_ID: '"241271498917-t4t7d07qis7oc0ahaskbif3ft6tk63cd.apps.googleusercontent.com"',
GITHUB_CLIENT_ID: '"30c1491057c9ad4dbd56"'
NODE_ENV: '"production"'
}

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>
<!-- 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);

4947
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,11 @@
{
"name": "stackedit",
"version": "5.12.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": {
@ -21,12 +21,12 @@
"postversion": "git push origin master --tags && npm publish",
"patch": "npm version patch -m \"Tag v%s\"",
"minor": "npm version minor -m \"Tag v%s\"",
"major": "npm version major -m \"Tag v%s\""
"major": "npm version major -m \"Tag v%s\"",
"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",
@ -34,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.0-rc.1",
"katex": "^0.16.2",
"markdown-it": "^8.4.1",
"markdown-it-abbr": "^1.0.4",
"markdown-it-deflist": "^2.0.2",
@ -49,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"
},
@ -90,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",
@ -99,17 +98,20 @@
"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",
"optimize-css-assets-webpack-plugin": "^1.3.2",
"ora": "^1.2.0",
"raw-loader": "^0.5.1",
"replace-in-file": "^4.1.0",
"rimraf": "^2.6.0",
"sass-loader": "^7.0.1",
"semver": "^5.5.0",
"shelljs": "^0.8.1",
"string-replace-loader": "^2.1.1",
"stylelint": "^9.2.0",
"stylelint-config-standard": "^16.0.0",
"stylelint-processor-html": "^1.0.0",
@ -120,10 +122,11 @@
"vue-style-loader": "^4.1.0",
"vue-template-compiler": "^2.5.16",
"webpack": "^2.6.1",
"webpack-bundle-analyzer": "^2.2.1",
"webpack-bundle-analyzer": "^3.3.2",
"webpack-dev-middleware": "^1.10.0",
"webpack-hot-middleware": "^2.18.0",
"webpack-merge": "^4.1.2",
"webpack-pwa-manifest": "^3.7.1",
"worker-loader": "^1.1.1"
},
"engines": {

54
server/conf.js Normal file
View File

@ -0,0 +1,54 @@
const pandocPath = process.env.PANDOC_PATH || 'pandoc';
const wkhtmltopdfPath = process.env.WKHTMLTOPDF_PATH || 'wkhtmltopdf';
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,
paypalReceiverEmail,
dropboxAppKey,
dropboxAppKeyFull,
githubClientId,
githubClientSecret,
giteeClientId,
giteeClientSecret,
googleClientId,
googleApiKey,
wordpressClientId,
giteaClientId,
giteaClientSecret,
giteaUrl,
gitlabClientId,
gitlabClientSecret,
gitlabUrl,
};
exports.publicValues = {
dropboxAppKey,
dropboxAppKeyFull,
githubClientId,
googleClientId,
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'),
);
};

View File

@ -1,5 +1,6 @@
const qs = require('qs'); // eslint-disable-line import/no-extraneous-dependencies
const request = require('request');
const conf = require('./conf');
function githubToken(clientId, code) {
return new Promise((resolve, reject) => {
@ -8,7 +9,7 @@ function githubToken(clientId, code) {
url: 'https://github.com/login/oauth/access_token',
qs: {
client_id: clientId,
client_secret: process.env.GITHUB_SECRET,
client_secret: conf.values.githubClientSecret,
code,
},
}, (err, res, body) => {

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,14 +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) => {
@ -24,23 +27,33 @@ module.exports = (app, serveV4) => {
}
app.get('/oauth2/githubToken', github.githubToken);
app.get('/userInfo', user.userInfo);
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.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
@ -48,23 +61,23 @@ module.exports = (app, serveV4) => {
// Google Drive action receiver
app.get('/googleDriveAction', (req, res) =>
res.redirect(`./app#providerId=googleDrive&state=${encodeURIComponent(req.query.state)}`));
// 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 the static folder with 30 day max-age
app.use('/themes', serveStatic(resolvePath('static/themes'), {
maxAge: '5d',
}));
// Serve style.css with 1 day max-age
app.get('/style.css', (req, res) => res.sendFile(resolvePath('dist/style.css'), {
maxAge: '1d',
}));
// Serve share.html
app.get('/share.html', (req, res) => res.sendFile(resolvePath('static/landing/share.html')));
app.get('/gistshare.html', (req, res) => res.sendFile(resolvePath('static/landing/gistshare.html')));
// Serve static resources
if (process.env.NODE_ENV === 'production') {
// Serve index.html in /app
app.get('/app', (req, res) => res.sendFile(resolvePath('dist/index.html')));
// Serve the static folder with 1 year max-age
app.use('/static', serveStatic(resolvePath('dist/static'), {
@ -72,12 +85,5 @@ module.exports = (app, serveV4) => {
}));
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,7 @@
const { spawn } = require('child_process');
const fs = require('fs');
const tmp = require('tmp');
const user = require('./user');
const conf = require('./conf');
const outputFormats = {
asciidoc: 'text/plain',
@ -41,16 +41,7 @@ exports.generate = (req, res) => {
const outputFormat = Object.prototype.hasOwnProperty.call(outputFormats, req.query.format)
? req.query.format
: 'pdf';
Promise.all([
user.checkSponsor(req.query.idToken),
user.checkMonetize(req.query.token),
])
.then(([isSponsor, isMonetize]) => {
if (!isSponsor && !isMonetize) {
throw new Error('unauthorized');
}
return new Promise((resolve, reject) => {
new Promise((resolve, reject) => {
tmp.file({
postfix: `.${outputFormat}`,
}, (err, filePath, fd, cleanupCallback) => {
@ -63,14 +54,12 @@ exports.generate = (req, res) => {
});
}
});
});
})
.then(({ filePath, cleanupCallback }) => new Promise((resolve, reject) => {
}).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('--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');
@ -79,7 +68,7 @@ exports.generate = (req, res) => {
if (!Number.isNaN(options.tocDepth)) {
params.push('--toc-depth', options.tocDepth);
}
options.highlightStyle = highlightStyles.indexOf(options.highlightStyle) !== -1 ? options.highlightStyle : 'kate';
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]}`);
@ -93,10 +82,9 @@ exports.generate = (req, res) => {
reject(error);
}
const binPath = process.env.PANDOC_PATH || 'pandoc';
const format = outputFormat === 'pdf' ? 'latex' : outputFormat;
params.push('-f', 'json', '-t', format, '-o', filePath);
const pandoc = spawn(binPath, params, {
const pandoc = spawn(conf.values.pandocPath, params, {
stdio: [
'pipe',
'ignore',
@ -137,6 +125,7 @@ exports.generate = (req, res) => {
req.pipe(pandoc.stdin);
}))
.catch((err) => {
console.error(err);
const message = err && err.message;
if (message === 'unauthorized') {
res.statusCode = 401;

View File

@ -2,7 +2,7 @@
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 */
function waitForJavaScript() {
@ -50,15 +50,7 @@ const readJson = (str) => {
exports.generate = (req, res) => {
let wkhtmltopdfError = '';
Promise.all([
user.checkSponsor(req.query.idToken),
user.checkMonetize(req.query.token),
])
.then(([isSponsor, isMonetize]) => {
if (!isSponsor && !isMonetize) {
throw new Error('unauthorized');
}
return new Promise((resolve, reject) => {
new Promise((resolve, reject) => {
tmp.file((err, filePath, fd, cleanupCallback) => {
if (err) {
reject(err);
@ -69,9 +61,7 @@ exports.generate = (req, res) => {
});
}
});
});
})
.then(({ filePath, cleanupCallback }) => new Promise((resolve, reject) => {
}).then(({ filePath, cleanupCallback }) => new Promise((resolve, reject) => {
let finished = false;
function onError(err) {
@ -127,13 +117,12 @@ exports.generate = (req, res) => {
}
// Page size
params.push('--page-size', authorizedPageSizes.indexOf(options.pageSize) === -1 ? 'A4' : options.pageSize);
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
const binPath = process.env.WKHTMLTOPDF_PATH || 'wkhtmltopdf';
params.push('--run-script', `${waitForJavaScript.toString()}waitForJavaScript()`);
params.push('--window-status', 'done');
const wkhtmltopdf = spawn(binPath, params.concat('-', filePath), {
const wkhtmltopdf = spawn(conf.values.wkhtmltopdfPath, params.concat('-', filePath), {
stdio: [
'pipe',
'ignore',
@ -173,6 +162,7 @@ exports.generate = (req, res) => {
req.pipe(wkhtmltopdf.stdin);
}))
.catch((err) => {
console.error(err);
const message = err && err.message;
if (message === 'unauthorized') {
res.statusCode = 401;

View File

@ -1,136 +0,0 @@
const request = require('request');
const AWS = require('aws-sdk');
const verifier = require('google-id-token-verifier');
const {
USER_BUCKET_NAME = 'stackedit-users',
PAYPAL_URI = 'https://www.paypal.com/cgi-bin/webscr',
PAYPAL_RECEIVER_EMAIL = 'stackedit.project@gmail.com',
GOOGLE_CLIENT_ID,
} = process.env;
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: USER_BUCKET_NAME,
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: USER_BUCKET_NAME,
Key: id,
Body: JSON.stringify(user),
}, cb(resolve, reject));
});
exports.removeUser = id => new Promise((resolve, reject) => {
s3Client.deleteObject({
Bucket: USER_BUCKET_NAME,
Key: id,
}, cb(resolve, reject));
});
exports.getUserFromToken = idToken => new Promise((resolve, reject) => verifier
.verify(idToken, GOOGLE_CLIENT_ID, 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 !== PAYPAL_RECEIVER_EMAIL ||
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: PAYPAL_URI,
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 (!idToken) {
return Promise.resolve(false);
}
return exports.getUserFromToken(idToken)
.then(userInfo => userInfo && userInfo.sponsorUntil > Date.now(), () => false);
};
exports.checkMonetize = (token) => {
if (!token) {
return Promise.resolve(false);
}
return new Promise(resolve => request({
uri: 'https://monetizejs.com/api/payments',
qs: {
access_token: token,
},
json: true,
}, (err, paymentsRes, payments) => {
const authorized = payments && payments.app === 'ESTHdCYOi18iLhhO' && (
(payments.chargeOption && payments.chargeOption.alias === 'once') ||
(payments.subscriptionOption && payments.subscriptionOption.alias === 'yearly'));
resolve(!err && paymentsRes.statusCode === 200 && authorized);
}));
};

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

13
src/assets/iconGoogle.svg Normal file
View File

@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 48 48">
<defs>
<path id="a" d="M44.5 20H24v8.5h11.8C34.7 33.9 30.1 37 24 37c-7.2 0-13-5.8-13-13s5.8-13 13-13c3.1 0 5.9 1.1 8.1 2.9l6.4-6.4C34.6 4.1 29.6 2 24 2 11.8 2 2 11.8 2 24s9.8 22 22 22c11 0 21-8 21-22 0-1.3-.2-2.7-.5-4z"/>
</defs>
<clipPath id="b">
<use xlink:href="#a" overflow="visible"/>
</clipPath>
<path clip-path="url(#b)" fill="#FBBC05" d="M0 37V11l17 13z"/>
<path clip-path="url(#b)" fill="#EA4335" d="M0 11l17 13 7-6.1L48 14V0H0z"/>
<path clip-path="url(#b)" fill="#34A853" d="M0 37l30-23 7.9 1L48 0v48H0z"/>
<path clip-path="url(#b)" fill="#4285F4" d="M48 48L17 24l-4-3 35-10z"/>
</svg>

After

Width:  |  Height:  |  Size: 729 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

@ -1,5 +1,5 @@
<template>
<div class="app" :class="classes">
<div class="app" :class="classes" @keydown.esc="close">
<splash-screen v-if="!ready"></splash-screen>
<layout v-else></layout>
<modal></modal>
@ -19,10 +19,11 @@ import ContextMenu from './ContextMenu';
import SplashScreen from './SplashScreen';
import syncSvc from '../services/syncSvc';
import networkSvc from '../services/networkSvc';
import sponsorSvc from '../services/sponsorSvc';
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'],
@ -46,11 +47,53 @@ export default {
return Array.isArray(result) ? result : themeClasses.light;
},
},
methods: {
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();
await sponsorSvc.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

@ -1,7 +1,7 @@
<template>
<div class="explorer-node" :class="{'explorer-node--selected': isSelected, 'explorer-node--folder': node.isFolder, 'explorer-node--open': isOpen, 'explorer-node--trash': node.isTrash, 'explorer-node--temp': node.isTemp, 'explorer-node--drag-target': isDragTargetFolder}" @dragover.prevent @dragenter.stop="node.noDrop || setDragTarget(node)" @dragleave.stop="isDragTarget && setDragTarget()" @drop.prevent.stop="onDrop" @contextmenu="onContextMenu">
<div class="explorer-node__item-editor" v-if="isEditing" :style="{paddingLeft: leftPadding}" draggable="true" @dragstart.stop.prevent>
<input type="text" class="text-input" v-focus @blur="submitEdit()" @keydown.stop @keydown.enter="submitEdit()" @keydown.esc="submitEdit(true)" v-model="editingNodeName">
<input type="text" class="text-input" v-focus @blur="submitEdit()" @keydown.stop @keydown.enter="submitEdit()" @keydown.esc.stop="submitEdit(true)" v-model="editingNodeName">
</div>
<div class="explorer-node__item" v-else :style="{paddingLeft: leftPadding}" @click="select()" draggable="true" @dragstart.stop="setDragSourceId" @dragend.stop="setDragTarget()">
{{node.item.name}}
@ -10,10 +10,11 @@
<div class="explorer-node__children" v-if="node.isFolder && isOpen">
<explorer-node v-for="node in node.folders" :key="node.item.id" :node="node" :depth="depth + 1"></explorer-node>
<div v-if="newChild" class="explorer-node__new-child" :class="{'explorer-node__new-child--folder': newChild.isFolder}" :style="{paddingLeft: childLeftPadding}">
<input type="text" class="text-input" v-focus @blur="submitNewChild()" @keydown.stop @keydown.enter="submitNewChild()" @keydown.esc="submitNewChild(true)" v-model.trim="newChildName">
<input type="text" class="text-input" v-focus @blur="submitNewChild()" @keydown.stop @keydown.enter="submitNewChild()" @keydown.esc.stop="submitNewChild(true)" v-model.trim="newChildName">
</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>
@ -22,6 +23,8 @@ import { mapMutations, mapActions } from 'vuex';
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
@ -79,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) {
@ -90,8 +96,9 @@ export default {
setTimeout(() => {
if (node.isFolder) {
store.commit('explorer/toggleOpenNode', id);
} else {
} else if (store.state.file.currentId !== id) {
store.commit('file/setCurrentId', id);
badgeSvc.addBadge('switchFile');
}
}, 10);
}
@ -104,9 +111,11 @@ export default {
if (newChildNode.isFolder) {
const item = await workspaceSvc.storeItem(newChildNode.item);
this.select(item.id);
badgeSvc.addBadge('createFolder');
} else {
const item = await workspaceSvc.createFile(newChildNode.item);
this.select(item.id);
badgeSvc.addBadge('createFile');
}
} catch (e) {
// Cancel
@ -115,15 +124,16 @@ export default {
store.commit('explorer/setNewItem', null);
},
async submitEdit(cancel) {
const { item } = store.getters['explorer/editingNode'];
const { item, isFolder } = store.getters['explorer/editingNode'];
const value = this.editingValue;
this.setEditingId(null);
if (!cancel && item.id && value) {
if (!cancel && item.id && value && item.name !== value) {
try {
await workspaceSvc.storeItem({
...item,
name: value,
});
badgeSvc.addBadge(isFolder ? 'renameFolder' : 'renameFile');
} catch (e) {
// Cancel
}
@ -139,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'];
@ -151,6 +166,7 @@ export default {
...sourceNode.item,
parentId: targetNode.item.id,
});
badgeSvc.addBadge(sourceNode.isFolder ? 'moveFolder' : 'moveFile');
}
},
async onContextMenu(evt) {
@ -163,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) {
@ -209,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;
@ -230,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="onEscape">
<button class="find-replace__close-button button not-tabbable" @click="close()" v-title="'Close'">
<div class="find-replace" @keydown.esc.stop="onEscape">
<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', [
@ -177,17 +192,15 @@ export default {
.sticky-comment,
.current-discussion {
background-color: mix(#000, $editor-background-light, 6.7%);
border-color: $editor-background-light;
.app--dark & {
background-color: mix(#fff, $editor-background-dark, 6.7%);
border-color: $editor-background-dark;
}
}
}
$preview-background-light: #f3f3f3;
$preview-background-dark: #252525;
$preview-background-dark: #444;
.layout__panel--preview,
.layout__panel--button-bar {
@ -204,13 +217,16 @@ $preview-background-dark: #252525;
.sticky-comment,
.current-discussion {
background-color: mix(#000, $preview-background-light, 6.7%);
border-color: $preview-background-light;
}
}
.layout__panel--explorer,
.layout__panel--side-bar {
background-color: #ddd;
.app--dark & {
background-color: #383c4a;
}
}
.layout__panel--find-replace {
@ -221,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="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" 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/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';
@ -36,7 +37,12 @@ import ImageModal from './modals/ImageModal';
import SyncManagementModal from './modals/SyncManagementModal';
import PublishManagementModal from './modals/PublishManagementModal';
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';
@ -52,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';
@ -66,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
@ -86,7 +108,12 @@ export default {
SyncManagementModal,
PublishManagementModal,
WorkspaceManagementModal,
AccountManagementModal,
BadgeManagementModal,
SponsorModal,
CommitMessageModal,
WorkspaceImgPathModal,
ChatGptModal,
// Providers
GooglePhotoModal,
GoogleDriveAccountModal,
@ -101,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,
@ -115,6 +156,8 @@ export default {
ZendeskPublishModal,
CouchdbWorkspaceModal,
CouchdbCredentialsModal,
SmmsAccountModal,
CustomAccountModal,
},
computed: {
...mapGetters([
@ -144,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) {
@ -241,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 button" v-if="light" @click="close()" v-title="'Close StackEdit'"><icon-close-circle></icon-close-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 -->
@ -19,23 +20,23 @@
<!-- Title -->
<div class="navigation-bar__title navigation-bar__title--fake text-input"></div>
<div class="navigation-bar__title navigation-bar__title--text text-input" :style="{width: titleWidth + 'px'}">{{title}}</div>
<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="submitTitle(true)" @mouseenter="titleHover = true" @mouseleave="titleHover = false" v-model="title">
<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>
@ -57,6 +58,7 @@ import utils from '../services/utils';
import pagedownButtons from '../data/pagedownButtons';
import store from '../store';
import workspaceSvc from '../services/workspaceSvc';
import badgeSvc from '../services/badgeSvc';
// According to mousetrap
const mod = /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? 'Meta' : 'Ctrl';
@ -112,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}`,
@ -178,7 +181,7 @@ export default {
},
requestSync() {
if (this.isSyncPossible && !this.isSyncRequested) {
syncSvc.requestSync();
syncSvc.requestSync(true);
}
},
requestPublish() {
@ -186,9 +189,16 @@ export default {
publishSvc.requestPublish();
}
},
switchTheme() {
store.dispatch('data/switchThemeSetting');
},
pagedownClick(name) {
if (store.getters['content/isCurrentEditable']) {
const text = editorSvc.clEditor.getContent();
editorSvc.pagedownEditor.uiManager.doClick(name);
if (text !== editorSvc.clEditor.getContent()) {
badgeSvc.addBadge('formatButtons');
}
}
},
async editTitle(toggle) {
@ -198,12 +208,13 @@ export default {
} else {
const title = this.title.trim();
this.title = store.getters['file/current'].name;
if (title) {
if (title && this.title !== title) {
try {
await workspaceSvc.storeItem({
...store.getters['file/current'],
name: title,
});
badgeSvc.addBadge('editCurrentFileName');
} catch (e) {
// Cancel
}
@ -303,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;
@ -433,6 +456,16 @@ export default {
display: inline-block;
}
.navigation-bar__button--close {
color: lighten($link-color, 15%);
&:active,
&:focus,
&:hover {
color: lighten($link-color, 25%);
}
}
.navigation-bar__title--input {
cursor: pointer;

View File

@ -2,12 +2,19 @@
<div class="notification">
<div class="notification__item flex flex--row flex--align-center" v-for="(item, idx) in items" :key="idx">
<div class="notification__icon flex flex--column flex--center">
<icon-information v-if="item.type === 'info'"></icon-information>
<icon-alert v-else-if="item.type === 'error'"></icon-alert>
<icon-alert v-if="item.type === 'error'"></icon-alert>
<icon-check-circle v-else-if="item.type === 'badge'"></icon-check-circle>
<icon-information v-else></icon-information>
</div>
<div class="notification__content">
{{item.content}}
</div>
<button class="notification__button button" v-if="item.type === 'confirm'" @click="item.reject">
</button>
<button class="notification__button button" v-if="item.type === 'confirm'" @click="item.resolve">
</button>
</div>
</div>
</template>
@ -49,4 +56,17 @@ export default {
margin-right: 12px;
flex: none;
}
.notification__button {
color: $navbar-color;
padding: 8px;
flex: none;
&:active,
&:focus,
&:hover {
color: $navbar-hover-color;
background-color: $navbar-hover-background;
}
}
</style>

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'">
<icon-arrow-left></icon-arrow-left>
<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>
@ -18,11 +18,13 @@
<publish-menu v-else-if="panel === 'publish'"></publish-menu>
<history-menu v-else-if="panel === 'history'"></history-menu>
<export-menu v-else-if="panel === 'export'"></export-menu>
<import-menu v-else-if="panel === 'import'"></import-menu>
<more-menu v-else-if="panel === 'more'"></more-menu>
<import-export-menu v-else-if="panel === 'importExport'"></import-export-menu>
<workspace-backup-menu v-else-if="panel === 'workspaceBackups'"></workspace-backup-menu>
<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>
@ -39,24 +41,26 @@ import WorkspacesMenu from './menus/WorkspacesMenu';
import SyncMenu from './menus/SyncMenu';
import PublishMenu from './menus/PublishMenu';
import HistoryMenu from './menus/HistoryMenu';
import ExportMenu from './menus/ExportMenu';
import ImportMenu from './menus/ImportMenu';
import MoreMenu from './menus/MoreMenu';
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',
export: 'Export to disk',
import: 'Import from disk',
more: 'More',
menu: '菜单',
workspaces: '文档空间',
help: 'Markdown 帮助',
toc: '目录',
sync: '同步',
publish: '发布',
history: '文件历史',
importExport: '导入/导出',
workspaceBackups: '文档空间备份',
editTheme: '编辑区主题',
previewTheme: '预览区主题',
};
export default {
@ -67,9 +71,10 @@ export default {
SyncMenu,
PublishMenu,
HistoryMenu,
ExportMenu,
ImportMenu,
MoreMenu,
ImportExportMenu,
WorkspaceBackupMenu,
EditThemeMenu,
PreviewThemeMenu,
},
data: () => ({
markdownSample: markdownConversionSvc.highlight(markdownSample),
@ -177,8 +182,10 @@ export default {
font-size: 0.95em;
p {
margin: 10px;
line-height: 1.5;
margin: 10px 15px;
font-size: 0.9rem;
opacity: 0.67;
line-height: 1.3;
}
}
</style>

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

@ -1,48 +1,56 @@
<template>
<div class="tour" @keydown.esc="skip">
<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;
}
@ -139,7 +149,8 @@ export default {
position: absolute;
}
$tour-step-background: mix(#f3f3f3, $selection-highlighting-color, 75%);
$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;
}
}
}
}

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