diff --git a/CHANGELOG.md b/CHANGELOG.md
index 44b3b1c5..5690757b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,19 @@
+### [v1.2.4](https://gitee.com/koogua/course-tencent-cloud/releases/v1.2.4)(2021-01-10)
+
+#### 增加
+
+- 后台增加上传logo和favicon图标
+- 后台增加公众号自定义菜单配置
+- 课程页增加咨询
+
+### 优化
+
+- oauth中state参数为安全base64加解码
+- findById参数类型不对时抛出异常
+- task表增加索引加快数据查找
+- markdown内容解析改由后端完成
+- 公众号应答处理逻辑
+
### [v1.2.3](https://gitee.com/koogua/course-tencent-cloud/releases/v1.2.3)(2021-01-03)
#### 增加
diff --git a/README.md b/README.md
index c082a651..3e0f415a 100644
--- a/README.md
+++ b/README.md
@@ -2,16 +2,16 @@

-#### 项目介绍
+### 项目介绍
酷瓜云课堂,依托腾讯云基础服务架构,采用C扩展框架Phalcon开发,GPL-2.0开源协议,致力开源网课系统,开源网校系统,开源在线教育系统。
-
-
-
+
+
+

-#### 系统功能
+### 系统功能
实现了点播、直播、专栏、会员、微聊等,是一个完整的产品,具体功能我也不想写一大堆,自己体验吧!
@@ -21,17 +21,19 @@
- 课程数据来源于网络(无实质内容),切莫购买
- 管理后台已禁止数据提交,私密配置已过滤
-演示帐号:**13507083515 / 123456** (前后台通用)
-
桌面端演示:
- [前台演示](https://ctc.koogua.com)
- [后台演示](https://ctc.koogua.com/admin)
+演示帐号:100015@163.com / 123456 (前后台通用)
+
移动端演示:

+演示帐号:13507083515 / 123456
+
支付流程演示:
- [MySQL提升课程全面讲解MySQL架构设计(0.01元)](https://ctc.koogua.com/order/confirm?item_id=1390&item_type=1)
@@ -40,7 +42,18 @@
Tips: 测试支付请用手机号注册一个新账户,以便接收订单通知,以及避免课程无法购买
-#### 项目组件
+即时通讯演示:
+
+请使用以下两个帐号在不同终端或者浏览器登录,打开微聊界面
+
+- 帐号A:100015@163.com / 123456
+- 帐号B:100065@163.com / 123456
+
+微信推送演示:
+
+Tips: 请用手机注册一个新账号,用户中心 -> 关注订阅,扫码关注公众号。之后的登录、购买、退款、直播、咨询等会有消息推送。
+
+### 项目组件
- 后台框架:[phalcon 3.4.5](https://phalcon.io)
- 前端框架:[layui 2.5.6](https://layui.com), [layim 3.9.5](https://www.layui.com/layim)(已授权)
@@ -48,45 +61,27 @@ Tips: 测试支付请用手机号注册一个新账户,以便接收订单通
- 即时通讯:[workerman 3.5.22](https://workerman.net)
- 基础依赖:[php7.3](https://php.net), [mysql5.7](https://mysql.com), [redis5.0](https://redis.io)
-#### 安装指南
+### 安装指南
- [运行环境搭建](https://gitee.com/koogua/course-tencent-cloud-docker)
- [系统服务配置](https://gitee.com/koogua/course-tencent-cloud/wikis)
+- [客户终端配置](https://gitee.com/koogua/course-tencent-cloud-app)
-#### 开发计划
-
-- 桌面端:进行中
-- 移动端:进行中
-- 小程序:待启动
-
-#### 意见反馈
+### 意见反馈
- [在线反馈](https://gitee.com/koogua/course-tencent-cloud/issues)(推荐)
+- [官方论坛](https://koogua.com/forum)(推荐)
- QQ交流群: 787363898
-#### 通过这个项目能学到什么?
-
-- 项目规划,phalcon,缓存,JWT,即时通讯,全文检索
-- docker,supervisor,devops
-- git,linux,php,mysql,redis,nginx
-
-#### 有阿里云版吗?
+### 有阿里云版吗?
阿里云版规划中,之前阿里云服务过期未续费,所以腾讯云版本先出。
-#### 代码有加密吗?
+### 代码有加密吗?
所有代码都公开(授权代码除外,例如layim),没有所谓的商业版和付费插件。
-#### 有商业服务吗?
-
-生存才能发展,我们目前提供的服务包括:
-
-- 系统安装
-- 系统定制
-- 企业授权
-
-#### 开源助力
+### 开源助力
毫无保留的真开源不容易,如果对你有帮助,请给我们 **STAR** !!!
diff --git a/app/Http/Admin/Services/Setting.php b/app/Http/Admin/Services/Setting.php
index 1a28edaa..3ce3f6ac 100644
--- a/app/Http/Admin/Services/Setting.php
+++ b/app/Http/Admin/Services/Setting.php
@@ -210,29 +210,29 @@ class Setting extends Service
if (!empty($settings['menu'])) {
foreach ($settings['menu'] as $i => $top) {
- $buttons[$i]['name'] = !empty($top['name']) ? $top['name'] : sprintf('菜单%s', $i + 1);
- if (!empty($top['url'])) {
- $buttons[$i]['url'] = $top['url'];
- $buttons[$i]['type'] = 'view';
- }
+ $buttons[$i]['name'] = $top['name'];
+ $buttons[$i]['url'] = $top['url'];
+ $buttons[$i]['type'] = 'view';
foreach ($top['children'] as $j => $sub) {
if (!empty($sub['name']) && !empty($sub['url'])) {
$buttons[$i]['sub_button'][$j]['name'] = $sub['name'];
$buttons[$i]['sub_button'][$j]['url'] = $sub['url'];
$buttons[$i]['sub_button'][$j]['type'] = 'view';
+ } else {
+ unset($settings['menu'][$i]['children'][$j]);
}
}
}
$settings['menu'] = kg_json_encode($settings['menu']);
}
- $this->updateSettings($section, $settings);
-
if (!empty($buttons)) {
$service = new WechatService();
$oa = $service->getOfficialAccount();
$oa->menu->create($buttons);
}
+
+ $this->updateSettings($section, $settings);
}
}
diff --git a/app/Http/Home/Controllers/HelpController.php b/app/Http/Home/Controllers/HelpController.php
index d525a80a..22d7bc29 100644
--- a/app/Http/Home/Controllers/HelpController.php
+++ b/app/Http/Home/Controllers/HelpController.php
@@ -2,6 +2,7 @@
namespace App\Http\Home\Controllers;
+use App\Http\Home\Services\Index as IndexService;
use App\Services\Logic\Help\HelpInfo as HelpInfoService;
use App\Services\Logic\Help\HelpList as HelpListService;
@@ -12,7 +13,7 @@ class HelpController extends Controller
{
/**
- * @Get("/index", name="home.help.index")
+ * @Get("/", name="home.help.index")
*/
public function indexAction()
{
@@ -34,9 +35,19 @@ class HelpController extends Controller
$help = $service->handle($id);
+ $featuredCourses = $this->getFeaturedCourses();
+
$this->seo->prependTitle(['帮助', $help['title']]);
$this->view->setVar('help', $help);
+ $this->view->setVar('featured_courses', $featuredCourses);
+ }
+
+ protected function getFeaturedCourses()
+ {
+ $service = new IndexService();
+
+ return $service->getSimpleFeaturedCourses();
}
}
diff --git a/app/Http/Home/Controllers/PageController.php b/app/Http/Home/Controllers/PageController.php
index 6796d63d..38b31f97 100644
--- a/app/Http/Home/Controllers/PageController.php
+++ b/app/Http/Home/Controllers/PageController.php
@@ -2,6 +2,7 @@
namespace App\Http\Home\Controllers;
+use App\Http\Home\Services\Index as IndexService;
use App\Services\Logic\Page\PageInfo as PageInfoService;
/**
@@ -19,9 +20,19 @@ class PageController extends Controller
$page = $service->handle($id);
- $this->seo->prependTitle(['单页', $page['title']]);
+ $featuredCourses = $this->getFeaturedCourses();
+
+ $this->seo->prependTitle($page['title']);
$this->view->setVar('page', $page);
+ $this->view->setVar('featured_courses', $featuredCourses);
+ }
+
+ protected function getFeaturedCourses()
+ {
+ $service = new IndexService();
+
+ return $service->getSimpleFeaturedCourses();
}
}
diff --git a/app/Http/Home/Services/WechatOfficialAccount.php b/app/Http/Home/Services/WechatOfficialAccount.php
index ea19fd02..6fd0e48c 100644
--- a/app/Http/Home/Services/WechatOfficialAccount.php
+++ b/app/Http/Home/Services/WechatOfficialAccount.php
@@ -3,9 +3,9 @@
namespace App\Http\Home\Services;
use App\Models\WechatSubscribe as WechatSubscribeModel;
+use App\Repos\User as UserRepo;
use App\Repos\WechatSubscribe as WechatSubscribeRepo;
use App\Services\Wechat as WechatService;
-use App\Validators\User as UserValidator;
use EasyWeChat\Kernel\Messages\Text as TextMessage;
class WechatOfficialAccount extends Service
@@ -50,7 +50,7 @@ class WechatOfficialAccount extends Service
{
$service = new WechatService();
- $service->logger->debug('Received Message ' . json_encode($message));
+ $service->logger->info('Received Message ' . json_encode($message));
switch ($message['MsgType']) {
case 'event':
@@ -74,7 +74,7 @@ class WechatOfficialAccount extends Service
return $this->handleLocationEvent($message);
break;
default:
- return $this->emptyReplyMessage();
+ return $this->noMatchReply();
break;
}
break;
@@ -100,7 +100,7 @@ class WechatOfficialAccount extends Service
return $this->handleLinkReply($message);
break;
default:
- return $this->emptyReplyMessage();
+ return $this->noMatchReply();
break;
}
}
@@ -108,16 +108,16 @@ class WechatOfficialAccount extends Service
protected function handleSubscribeEvent($message)
{
$openId = $message['FromUserName'] ?? '';
- $eventKey = $message['EventKey'] ?? '';
- if (!$eventKey) {
- return $this->emptyReplyMessage();
+ $subscribeRepo = new WechatSubscribeRepo();
+
+ $subscribe = $subscribeRepo->findByOpenId($openId);
+
+ if ($subscribe && $subscribe->deleted == 1) {
+ $subscribe->deleted = 0;
+ $subscribe->update();
}
- $userId = str_replace('qrscene_', '', $eventKey);
-
- $this->handleSubscribeRelation($userId, $openId);
-
return new TextMessage('开心呀,我们又多了一个小伙伴!');
}
@@ -129,7 +129,7 @@ class WechatOfficialAccount extends Service
$subscribe = $subscribeRepo->findByOpenId($openId);
- if ($subscribe) {
+ if ($subscribe && $subscribe->deleted == 0) {
$subscribe->deleted = 1;
$subscribe->update();
}
@@ -139,100 +139,95 @@ class WechatOfficialAccount extends Service
protected function handleScanEvent($message)
{
- /**
- * 注意:当已关注过用户扫码时,"EventKey"没有带"qrscene_"前缀
- */
$openId = $message['FromUserName'] ?? '';
$eventKey = $message['EventKey'] ?? '';
- $userId = $eventKey;
- $this->handleSubscribeRelation($userId, $openId);
- }
+ $userId = str_replace('qrscene_', '', $eventKey);
- protected function handleClickEvent($message)
- {
- $this->defaultReplyMessage();
- }
+ $userRepo = new UserRepo();
- protected function handleViewEvent($message)
- {
- $this->defaultReplyMessage();
- }
+ $user = $userRepo->findById($userId);
- protected function handleLocationEvent($message)
- {
- $this->defaultReplyMessage();
- }
-
- protected function handleTextReply($message)
- {
- return $this->defaultReplyMessage();
- }
-
- protected function handleImageReply($message)
- {
- return $this->defaultReplyMessage();
- }
-
- protected function handleVoiceReply($message)
- {
- return $this->defaultReplyMessage();
- }
-
- protected function handleVideoReply($message)
- {
- return $this->defaultReplyMessage();
- }
-
- protected function handleShortVideoReply($message)
- {
- return $this->defaultReplyMessage();
- }
-
- protected function handleLocationReply($message)
- {
- return $this->defaultReplyMessage();
- }
-
- protected function handleLinkReply($message)
- {
- return $this->defaultReplyMessage();
- }
-
- protected function emptyReplyMessage()
- {
- return new TextMessage('');
- }
-
- protected function defaultReplyMessage()
- {
- return new TextMessage('没有匹配的服务,如有需要请联系客服!');
- }
-
- protected function handleSubscribeRelation($userId, $openId)
- {
- $validator = new UserValidator();
-
- $validator->checkUser($userId);
+ if (!$user) return;
$subscribeRepo = new WechatSubscribeRepo();
$subscribe = $subscribeRepo->findByOpenId($openId);
if ($subscribe) {
+ if ($subscribe->user_id != $userId) {
+ $subscribe->user_id = $userId;
+ }
if ($subscribe->deleted == 1) {
$subscribe->deleted = 0;
- $subscribe->update();
}
+ $subscribe->update();
} else {
- $subscribe = $subscribeRepo->findSubscribe($userId, $openId);
- if (!$subscribe) {
- $subscribe = new WechatSubscribeModel();
- $subscribe->user_id = $userId;
- $subscribe->open_id = $openId;
- $subscribe->create();
- }
+ $subscribe = new WechatSubscribeModel();
+ $subscribe->user_id = $userId;
+ $subscribe->open_id = $openId;
+ $subscribe->create();
}
}
+ protected function handleClickEvent($message)
+ {
+ return $this->emptyReply();
+ }
+
+ protected function handleViewEvent($message)
+ {
+ return $this->emptyReply();
+ }
+
+ protected function handleLocationEvent($message)
+ {
+ return $this->emptyReply();
+ }
+
+ protected function handleTextReply($message)
+ {
+ return $this->emptyReply();
+ }
+
+ protected function handleImageReply($message)
+ {
+ return $this->emptyReply();
+ }
+
+ protected function handleVoiceReply($message)
+ {
+ return $this->emptyReply();
+ }
+
+ protected function handleVideoReply($message)
+ {
+ return $this->emptyReply();
+ }
+
+ protected function handleShortVideoReply($message)
+ {
+ return $this->emptyReply();
+ }
+
+ protected function handleLocationReply($message)
+ {
+ return $this->emptyReply();
+ }
+
+ protected function handleLinkReply($message)
+ {
+ return $this->emptyReply();
+ }
+
+ protected function emptyReply()
+ {
+ return null;
+ }
+
+ protected function noMatchReply()
+ {
+ return new TextMessage('没有匹配的服务哦!');
+ }
+
}
diff --git a/app/Http/Home/Views/course/show.volt b/app/Http/Home/Views/course/show.volt
index c229a53b..e10584ed 100644
--- a/app/Http/Home/Views/course/show.volt
+++ b/app/Http/Home/Views/course/show.volt
@@ -58,7 +58,7 @@
{{ partial('course/show_catalog') }}
-
{{ course.details }}
+
{{ course.details }}
{% if show_tab_packages %}
{% set packages_url = url({'for':'home.course.packages','id':course.id}) %}
@@ -112,14 +112,12 @@
{% block link_css %}
- {{ css_link('https://cdn.jsdelivr.net/npm/vditor/dist/index.css', false) }}
+ {{ css_link('home/css/markdown.css') }}
{% endblock %}
{% block include_js %}
- {{ js_include('https://cdn.jsdelivr.net/npm/vditor/dist/method.min.js', false) }}
- {{ js_include('home/js/markdown.preview.js') }}
{{ js_include('home/js/course.show.js') }}
{{ js_include('home/js/course.share.js') }}
diff --git a/app/Http/Home/Views/help/show.volt b/app/Http/Home/Views/help/show.volt
index 79acca71..b6682c6e 100644
--- a/app/Http/Home/Views/help/show.volt
+++ b/app/Http/Home/Views/help/show.volt
@@ -2,6 +2,8 @@
{% block content %}
+ {{ partial('macros/course') }}
+
-
-
{{ help.content }}
+
{% endblock %}
{% block link_css %}
- {{ css_link('https://cdn.jsdelivr.net/npm/vditor/dist/index.css', false) }}
-
-{% endblock %}
-
-{% block include_js %}
-
- {{ js_include('https://cdn.jsdelivr.net/npm/vditor/dist/method.min.js', false) }}
- {{ js_include('home/js/markdown.preview.js') }}
+ {{ css_link('home/css/markdown.css') }}
{% endblock %}
\ No newline at end of file
diff --git a/app/Http/Home/Views/page/show.volt b/app/Http/Home/Views/page/show.volt
index 231f2885..023dfa76 100644
--- a/app/Http/Home/Views/page/show.volt
+++ b/app/Http/Home/Views/page/show.volt
@@ -2,29 +2,41 @@
{% block content %}
+ {{ partial('macros/course') }}
+
-
-
{{ page.content }}
+
{% endblock %}
{% block link_css %}
- {{ css_link('https://cdn.jsdelivr.net/npm/vditor/dist/index.css', false) }}
-
-{% endblock %}
-
-{% block include_js %}
-
- {{ js_include('https://cdn.jsdelivr.net/npm/vditor/dist/method.min.js', false) }}
- {{ js_include('home/js/markdown.preview.js') }}
+ {{ css_link('home/css/markdown.css') }}
{% endblock %}
\ No newline at end of file
diff --git a/app/Http/Home/Views/partials/header.volt b/app/Http/Home/Views/partials/header.volt
index 8300003c..1222e867 100644
--- a/app/Http/Home/Views/partials/header.volt
+++ b/app/Http/Home/Views/partials/header.volt
@@ -1,4 +1,4 @@
-
+
{% if site_info.logo %}
{{ image(site_info.logo,false) }}
{% else %}
diff --git a/app/Http/Home/Views/teacher/pager.volt b/app/Http/Home/Views/teacher/pager.volt
index e3120ce6..9b30c23a 100644
--- a/app/Http/Home/Views/teacher/pager.volt
+++ b/app/Http/Home/Views/teacher/pager.volt
@@ -5,7 +5,7 @@
{% set item.title = item.title ? item.title : '小小教书匠' %}
{% set item.about = item.about ? item.about : '这个人很懒,什么都没留下' %}
{% set user_url = url({'for':'home.teacher.show','id':item.id}) %}
-
+
diff --git a/app/Library/AppInfo.php b/app/Library/AppInfo.php
index deeafa92..f130a713 100644
--- a/app/Library/AppInfo.php
+++ b/app/Library/AppInfo.php
@@ -11,7 +11,7 @@ class AppInfo
protected $link = 'https://gitee.com/koogua';
- protected $version = '1.2.3';
+ protected $version = '1.2.4';
public function __get($name)
{
diff --git a/app/Library/Helper.php b/app/Library/Helper.php
index e7af42e4..709266c8 100644
--- a/app/Library/Helper.php
+++ b/app/Library/Helper.php
@@ -277,9 +277,13 @@ function kg_cos_img_style_trim($path)
*/
function kg_parse_markdown($content)
{
- return preg_replace_callback('/\/img\/content\/(.*?)\)/', function ($matches) {
- return '/img/content/' . trim($matches[1]) . '!content_800';
+ $content = preg_replace_callback('/\/img\/content\/(.*?)\)/', function ($matches) {
+ return sprintf('/img/content/%s!content_800)', trim($matches[1]));
}, $content);
+
+ $parser = new HyperDown\Parser();
+
+ return $parser->makeHtml($content);
}
/**
diff --git a/app/Services/OAuth.php b/app/Services/OAuth.php
index 43aeefb0..9e74647f 100644
--- a/app/Services/OAuth.php
+++ b/app/Services/OAuth.php
@@ -70,24 +70,19 @@ abstract class OAuth extends Service
*/
$crypt = Di::getDefault()->get('crypt');
- return $crypt->encryptBase64(rand(1000, 9999));
+ $text = rand(1000, 9999);
+
+ return $crypt->encryptBase64($text, null, true);
}
public function checkState($state)
{
- /**
- * 注意事项:
- * callback中的state参数并未做encode处理,参数中含有"+"
- * 获取参数的时候却自动做了decode处理,"+"变成了空格
- */
- $state = str_replace(' ', '+', $state);
-
/**
* @var $crypt Crypt
*/
$crypt = Di::getDefault()->get('crypt');
- $value = $crypt->decryptBase64($state);
+ $value = $crypt->decryptBase64($state, null, true);
if ($value < 1000 || $value > 9999) {
throw new \Exception('Invalid OAuth State Value');
diff --git a/composer.json b/composer.json
index 47cf7fd2..5b23da95 100644
--- a/composer.json
+++ b/composer.json
@@ -21,7 +21,8 @@
"xiaochong0302/ip2region": "^1.0",
"robmorgan/phinx": "^0.12",
"lcobucci/jwt": "^3.3",
- "overtrue/wechat": "^4.2"
+ "overtrue/wechat": "^4.2",
+ "joyqi/hyper-down": "dev-master"
},
"require-dev": {
"odan/phinx-migrations-generator": "^5.3",
diff --git a/composer.lock b/composer.lock
index ab593b73..ba5a5787 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "09a618cffed2c4cfb593c0a791c19b3f",
+ "content-hash": "907178db979a21189806683196c6516b",
"packages": [
{
"name": "aferrandini/phpqrcode",
@@ -56,16 +56,16 @@
],
"abandoned": "endroid/qr-code",
"time": "2013-07-08T09:39:08+00:00"
- },
- {
- "name": "cakephp/core",
- "version": "4.1.3",
- "source": {
- "type": "git",
- "url": "https://github.com/cakephp/core.git",
- "reference": "c7e88f1cd6dfe17065e71f1af305d415f155e97e"
- },
- "dist": {
+ },
+ {
+ "name": "cakephp/core",
+ "version": "4.1.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/cakephp/core.git",
+ "reference": "c7e88f1cd6dfe17065e71f1af305d415f155e97e"
+ },
+ "dist": {
"type": "zip",
"url": "https://api.github.com/repos/cakephp/core/zipball/c7e88f1cd6dfe17065e71f1af305d415f155e97e",
"reference": "c7e88f1cd6dfe17065e71f1af305d415f155e97e",
@@ -329,14 +329,14 @@
"MIT"
],
"authors": [
- {
- "name": "Guilherme Blanco",
- "email": "guilhermeblanco@gmail.com"
- },
- {
- "name": "Roman Borschel",
- "email": "roman@code-factory.org"
- },
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
{
"name": "Johannes Schmitt",
"email": "schmittjoh@gmail.com"
@@ -428,16 +428,16 @@
]
},
"require": {
- "doctrine/lexer": "^1.0.1",
- "php": ">= 5.5"
- },
- "require-dev": {
- "dominicsayers/isemail": "dev-master",
- "phpunit/phpunit": "^4.8.35||^5.7||^6.0",
- "satooshi/php-coveralls": "^1.0.1",
- "symfony/phpunit-bridge": "^4.4@dev"
- },
- "suggest": {
+ "doctrine/lexer": "^1.0.1",
+ "php": ">= 5.5"
+ },
+ "require-dev": {
+ "dominicsayers/isemail": "dev-master",
+ "phpunit/phpunit": "^4.8.35||^5.7||^6.0",
+ "satooshi/php-coveralls": "^1.0.1",
+ "symfony/phpunit-bridge": "^4.4@dev"
+ },
+ "suggest": {
"ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation"
},
"type": "library",
@@ -856,26 +856,74 @@
}
],
"description": "xunsearch php sdk, include yii, yii2 supports",
- "homepage": "http://www.xunsearch.com/",
- "keywords": [
- "search engine",
- "xunsearch",
- "yii",
- "yii2"
- ],
- "time": "2020-09-03T16:46:04+00:00"
+ "homepage": "http://www.xunsearch.com/",
+ "keywords": [
+ "search engine",
+ "xunsearch",
+ "yii",
+ "yii2"
+ ],
+ "time": "2020-09-03T16:46:04+00:00"
},
+ {
+ "name": "joyqi/hyper-down",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/SegmentFault/HyperDown.git",
+ "reference": "1774a7bb8a3853503e44cfa5a2186b1943f6493f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/SegmentFault/HyperDown/zipball/1774a7bb8a3853503e44cfa5a2186b1943f6493f",
+ "reference": "1774a7bb8a3853503e44cfa5a2186b1943f6493f",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "ext-mbstring": "*",
+ "php": ">=5.4.0"
+ },
+ "default-branch": true,
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "HyperDown\\": "./"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD"
+ ],
+ "authors": [
{
- "name": "lcobucci/jwt",
- "version": "3.3.3",
- "source": {
- "type": "git",
- "url": "https://github.com/lcobucci/jwt.git",
- "reference": "c1123697f6a2ec29162b82f170dd4a491f524773"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/lcobucci/jwt/zipball/c1123697f6a2ec29162b82f170dd4a491f524773",
+ "name": "joyqi",
+ "email": "joyqi@segmentfault.com"
+ }
+ ],
+ "description": "A light weight markdown parser library",
+ "support": {
+ "issues": "https://github.com/SegmentFault/HyperDown/issues",
+ "source": "https://github.com/SegmentFault/HyperDown/tree/master"
+ },
+ "time": "2020-11-30T04:05:08+00:00"
+ },
+ {
+ "name": "lcobucci/jwt",
+ "version": "3.3.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/lcobucci/jwt.git",
+ "reference": "c1123697f6a2ec29162b82f170dd4a491f524773"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/lcobucci/jwt/zipball/c1123697f6a2ec29162b82f170dd4a491f524773",
"reference": "c1123697f6a2ec29162b82f170dd4a491f524773",
"shasum": "",
"mirrors": [
@@ -1056,16 +1104,16 @@
"require": {
"php": ">=5.3.2"
},
- "require-dev": {
- "phpunit/phpunit": "~4.0|~5.0"
- },
- "type": "library",
- "autoload": {
- "psr-4": {
- "Cron\\": "src/Cron/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
+ "require-dev": {
+ "phpunit/phpunit": "~4.0|~5.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Cron\\": "src/Cron/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
@@ -1252,16 +1300,16 @@
]
},
"require": {
- "php": "^7"
- },
- "require-dev": {
- "phpunit/phpunit": "4.*|5.*",
- "vimeo/psalm": "^1"
- },
- "suggest": {
- "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
- },
- "type": "library",
+ "php": "^7"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "4.*|5.*",
+ "vimeo/psalm": "^1"
+ },
+ "suggest": {
+ "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
+ },
+ "type": "library",
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
@@ -1498,16 +1546,16 @@
]
},
"require": {
- "php": ">=5.3.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.0.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
"Psr\\Cache\\": "src/"
}
},
@@ -1703,15 +1751,15 @@
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
- "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
- "shasum": "",
- "mirrors": [
- {
- "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
- "preferred": true
- }
- ]
+ "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
+ "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
},
"require": {
"php": ">=5.3.0"
@@ -2017,15 +2065,15 @@
"dev-master": "6.2-dev"
}
},
- "autoload": {
- "files": [
- "lib/swift_required.php"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
+ "autoload": {
+ "files": [
+ "lib/swift_required.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
"authors": [
{
"name": "Chris Corbyn"
@@ -2251,16 +2299,16 @@
]
},
"require": {
- "php": ">=7.2.5",
- "symfony/deprecation-contracts": "^2.1",
- "symfony/filesystem": "^4.4|^5.0",
- "symfony/polyfill-ctype": "~1.8",
- "symfony/polyfill-php80": "^1.15"
- },
- "conflict": {
- "symfony/finder": "<4.4"
- },
- "require-dev": {
+ "php": ">=7.2.5",
+ "symfony/deprecation-contracts": "^2.1",
+ "symfony/filesystem": "^4.4|^5.0",
+ "symfony/polyfill-ctype": "~1.8",
+ "symfony/polyfill-php80": "^1.15"
+ },
+ "conflict": {
+ "symfony/finder": "<4.4"
+ },
+ "require-dev": {
"symfony/event-dispatcher": "^4.4|^5.0",
"symfony/finder": "^4.4|^5.0",
"symfony/messenger": "^4.4|^5.0",
@@ -2398,14 +2446,14 @@
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
"funding": [
- {
- "url": "https://symfony.com/sponsor",
- "type": "custom"
- },
- {
- "url": "https://github.com/fabpot",
- "type": "github"
- },
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
@@ -2443,16 +2491,16 @@
},
"thanks": {
"name": "symfony/contracts",
- "url": "https://github.com/symfony/contracts"
- }
- },
- "autoload": {
- "files": [
- "function.php"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
+ "url": "https://github.com/symfony/contracts"
+ }
+ },
+ "autoload": {
+ "files": [
+ "function.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
"MIT"
],
"authors": [
@@ -2468,14 +2516,14 @@
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"funding": [
- {
- "url": "https://symfony.com/sponsor",
- "type": "custom"
- },
- {
- "url": "https://github.com/fabpot",
- "type": "github"
- },
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
@@ -2513,17 +2561,17 @@
"provide": {
"psr/event-dispatcher-implementation": "1.0",
"symfony/event-dispatcher-implementation": "1.1"
- },
- "require-dev": {
- "psr/log": "~1.0",
- "symfony/config": "^3.4|^4.0|^5.0",
- "symfony/dependency-injection": "^3.4|^4.0|^5.0",
- "symfony/error-handler": "~3.4|~4.4",
- "symfony/expression-language": "^3.4|^4.0|^5.0",
- "symfony/http-foundation": "^3.4|^4.0|^5.0",
- "symfony/service-contracts": "^1.1|^2",
- "symfony/stopwatch": "^3.4|^4.0|^5.0"
- },
+ },
+ "require-dev": {
+ "psr/log": "~1.0",
+ "symfony/config": "^3.4|^4.0|^5.0",
+ "symfony/dependency-injection": "^3.4|^4.0|^5.0",
+ "symfony/error-handler": "~3.4|~4.4",
+ "symfony/expression-language": "^3.4|^4.0|^5.0",
+ "symfony/http-foundation": "^3.4|^4.0|^5.0",
+ "symfony/service-contracts": "^1.1|^2",
+ "symfony/stopwatch": "^3.4|^4.0|^5.0"
+ },
"suggest": {
"symfony/dependency-injection": "",
"symfony/http-kernel": ""
@@ -2531,16 +2579,16 @@
"type": "library",
"autoload": {
"psr-4": {
- "Symfony\\Component\\EventDispatcher\\": ""
+ "Symfony\\Component\\EventDispatcher\\": ""
},
- "exclude-from-classmap": [
- "/Tests/"
- ]
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
},
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
"authors": [
{
"name": "Fabien Potencier",
@@ -2606,15 +2654,15 @@
"url": "https://github.com/symfony/contracts"
}
},
- "autoload": {
- "psr-4": {
- "Symfony\\Contracts\\EventDispatcher\\": ""
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\EventDispatcher\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
"authors": [
{
"name": "Nicolas Grekas",
@@ -2661,16 +2709,16 @@
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/filesystem/zipball/6e4320f06d5f2cce0d96530162491f4465179157",
- "reference": "6e4320f06d5f2cce0d96530162491f4465179157",
- "shasum": "",
- "mirrors": [
- {
- "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
- "preferred": true
- }
- ]
- },
+ "url": "https://api.github.com/repos/symfony/filesystem/zipball/6e4320f06d5f2cce0d96530162491f4465179157",
+ "reference": "6e4320f06d5f2cce0d96530162491f4465179157",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
"require": {
"php": ">=7.2.5",
"symfony/polyfill-ctype": "~1.8"
@@ -2706,14 +2754,14 @@
"description": "Symfony Filesystem Component",
"homepage": "https://symfony.com",
"funding": [
- {
- "url": "https://symfony.com/sponsor",
- "type": "custom"
- },
- {
- "url": "https://github.com/fabpot",
- "type": "github"
- },
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
@@ -2751,16 +2799,16 @@
"predis/predis": "~1.0",
"symfony/cache": "^4.4|^5.0",
"symfony/expression-language": "^4.4|^5.0",
- "symfony/mime": "^4.4|^5.0"
- },
- "suggest": {
- "symfony/mime": "To use the file extension guesser"
- },
- "type": "library",
- "autoload": {
- "psr-4": {
- "Symfony\\Component\\HttpFoundation\\": ""
- },
+ "symfony/mime": "^4.4|^5.0"
+ },
+ "suggest": {
+ "symfony/mime": "To use the file extension guesser"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\HttpFoundation\\": ""
+ },
"exclude-from-classmap": [
"/Tests/"
]
@@ -3107,14 +3155,14 @@
"shim"
],
"funding": [
- {
- "url": "https://symfony.com/sponsor",
- "type": "custom"
- },
- {
- "url": "https://github.com/fabpot",
- "type": "github"
- },
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
@@ -3152,16 +3200,16 @@
"extra": {
"branch-alias": {
"dev-main": "1.20-dev"
- },
- "thanks": {
- "name": "symfony/polyfill",
- "url": "https://github.com/symfony/polyfill"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Polyfill\\Mbstring\\": ""
- },
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Mbstring\\": ""
+ },
"files": [
"bootstrap.php"
]
@@ -3434,14 +3482,14 @@
"shim"
],
"funding": [
- {
- "url": "https://symfony.com/sponsor",
- "type": "custom"
- },
- {
- "url": "https://github.com/fabpot",
- "type": "github"
- },
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
@@ -3479,16 +3527,16 @@
},
"thanks": {
"name": "symfony/polyfill",
- "url": "https://github.com/symfony/polyfill"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Polyfill\\Php80\\": ""
- },
- "files": [
- "bootstrap.php"
- ],
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Php80\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ],
"classmap": [
"Resources/stubs"
]
@@ -3654,16 +3702,16 @@
"extra": {
"branch-alias": {
"dev-master": "2.2-dev"
- },
- "thanks": {
- "name": "symfony/contracts",
- "url": "https://github.com/symfony/contracts"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Contracts\\Service\\": ""
- }
+ },
+ "thanks": {
+ "name": "symfony/contracts",
+ "url": "https://github.com/symfony/contracts"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\Service\\": ""
+ }
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@@ -3803,16 +3851,16 @@
"preferred": true
}
]
- },
- "require": {
- "guzzlehttp/guzzle": "^6.3",
- "guzzlehttp/psr7": "^1.4",
- "php": ">=5.6.0"
- },
- "type": "library",
- "autoload": {
- "classmap": [
- "src/QcloudApi/QcloudApi.php"
+ },
+ "require": {
+ "guzzlehttp/guzzle": "^6.3",
+ "guzzlehttp/psr7": "^1.4",
+ "php": ">=5.6.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/QcloudApi/QcloudApi.php"
],
"psr-4": {
"TencentCloud\\": "./src/TencentCloud"
@@ -4212,35 +4260,35 @@
}
]
},
- "require": {
- "ext-json": "*",
- "ext-pdo": "*",
- "php": "^7.2",
- "riimu/kit-phpencoder": "^2.4",
- "robmorgan/phinx": "^0.12",
- "symfony/console": "^2.8 || ^3.0 || ^4.0 || ^5.0",
- "symfony/polyfill-php73": "^1.18"
- },
- "require-dev": {
- "friendsofphp/php-cs-fixer": "^2.16",
- "overtrue/phplint": "^1.1 || ^2.0",
- "phpstan/phpstan": "^0.12",
- "phpunit/phpunit": "^8 || ^9",
- "squizlabs/php_codesniffer": "^3.4"
- },
+ "require": {
+ "ext-json": "*",
+ "ext-pdo": "*",
+ "php": "^7.2",
+ "riimu/kit-phpencoder": "^2.4",
+ "robmorgan/phinx": "^0.12",
+ "symfony/console": "^2.8 || ^3.0 || ^4.0 || ^5.0",
+ "symfony/polyfill-php73": "^1.18"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^2.16",
+ "overtrue/phplint": "^1.1 || ^2.0",
+ "phpstan/phpstan": "^0.12",
+ "phpunit/phpunit": "^8 || ^9",
+ "squizlabs/php_codesniffer": "^3.4"
+ },
"bin": [
"./bin/phinx-migrations"
],
- "type": "library",
- "autoload": {
- "psr-4": {
- "Odan\\Migration\\": "src/Migration/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Odan\\Migration\\": "src/Migration/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
"description": "Migration generator for Phinx",
"homepage": "https://github.com/odan/phinx-migrations-generator",
"keywords": [
@@ -4257,60 +4305,60 @@
},
"time": "2020-12-30T23:59:57+00:00"
},
- {
- "name": "phalcon/ide-stubs",
- "version": "v3.4.3",
- "source": {
- "type": "git",
- "url": "https://github.com/phalcon/ide-stubs.git",
- "reference": "65144f2b0fad32b182ccb062b1efc1b4edea5d44"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/phalcon/ide-stubs/zipball/65144f2b0fad32b182ccb062b1efc1b4edea5d44",
- "reference": "65144f2b0fad32b182ccb062b1efc1b4edea5d44",
- "shasum": "",
- "mirrors": [
- {
- "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
- "preferred": true
- }
- ]
- },
- "require": {
- "php": ">=5.3.0"
- },
- "type": "library",
- "notification-url": "https://packagist.jp/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Phalcon Team",
- "email": "team@phalconphp.com",
- "homepage": "https://phalconphp.com/en/team"
- },
- {
- "name": "Contributors",
- "homepage": "https://github.com/phalcon/ide-stubs/graphs/contributors"
- }
- ],
- "description": "The most complete Phalcon Framework IDE stubs library which enables autocompletion in modern IDEs.",
- "homepage": "https://phalconphp.com",
- "keywords": [
- "Devtools",
- "Eclipse",
- "autocomplete",
- "ide",
- "netbeans",
- "phalcon",
- "phpstorm",
- "stub",
- "stubs"
- ],
- "time": "2018-12-09T14:11:06+00:00"
+ {
+ "name": "phalcon/ide-stubs",
+ "version": "v3.4.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phalcon/ide-stubs.git",
+ "reference": "65144f2b0fad32b182ccb062b1efc1b4edea5d44"
},
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phalcon/ide-stubs/zipball/65144f2b0fad32b182ccb062b1efc1b4edea5d44",
+ "reference": "65144f2b0fad32b182ccb062b1efc1b4edea5d44",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "notification-url": "https://packagist.jp/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Phalcon Team",
+ "email": "team@phalconphp.com",
+ "homepage": "https://phalconphp.com/en/team"
+ },
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/phalcon/ide-stubs/graphs/contributors"
+ }
+ ],
+ "description": "The most complete Phalcon Framework IDE stubs library which enables autocompletion in modern IDEs.",
+ "homepage": "https://phalconphp.com",
+ "keywords": [
+ "Devtools",
+ "Eclipse",
+ "autocomplete",
+ "ide",
+ "netbeans",
+ "phalcon",
+ "phpstorm",
+ "stub",
+ "stubs"
+ ],
+ "time": "2018-12-09T14:11:06+00:00"
+ },
{
"name": "riimu/kit-phpencoder",
"version": "v2.4.0",
@@ -4361,26 +4409,28 @@
"homepage": "http://kit.riimu.net",
"keywords": [
"code",
- "encoder",
- "export",
- "generator",
- "variable"
+ "encoder",
+ "export",
+ "generator",
+ "variable"
],
- "time": "2018-07-03T12:46:23+00:00"
+ "time": "2018-07-03T12:46:23+00:00"
}
],
- "aliases": [],
- "minimum-stability": "stable",
- "stability-flags": [],
- "prefer-stable": false,
- "prefer-lowest": false,
- "platform": {
- "ext-phalcon": "~3.4",
- "ext-redis": "~5.0",
- "ext-pdo": "*",
- "ext-json": "*",
- "ext-fileinfo": "*"
- },
- "platform-dev": [],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": {
+ "joyqi/hyper-down": 20
+ },
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": {
+ "ext-phalcon": "~3.4",
+ "ext-redis": "~5.0",
+ "ext-pdo": "*",
+ "ext-json": "*",
+ "ext-fileinfo": "*"
+ },
+ "platform-dev": [],
"plugin-api-version": "2.0.0"
}
diff --git a/public/static/admin/js/vditor.js b/public/static/admin/js/vditor.js
index 15339fcd..69071b58 100644
--- a/public/static/admin/js/vditor.js
+++ b/public/static/admin/js/vditor.js
@@ -5,6 +5,7 @@ layui.use(['jquery'], function () {
var $textarea = $('#vditor-textarea');
var vditor = new Vditor('vditor', {
+ mode: 'sv',
minHeight: 300,
outline: false,
resize: {
diff --git a/public/static/home/css/markdown.css b/public/static/home/css/markdown.css
new file mode 100644
index 00000000..21f236ed
--- /dev/null
+++ b/public/static/home/css/markdown.css
@@ -0,0 +1,972 @@
+.markdown-body .octicon {
+ display: inline-block;
+ fill: currentColor;
+ vertical-align: text-bottom;
+}
+
+.markdown-body .anchor {
+ float: left;
+ line-height: 1;
+ margin-left: -20px;
+ padding-right: 4px;
+}
+
+.markdown-body .anchor:focus {
+ outline: none;
+}
+
+.markdown-body h1 .octicon-link,
+.markdown-body h2 .octicon-link,
+.markdown-body h3 .octicon-link,
+.markdown-body h4 .octicon-link,
+.markdown-body h5 .octicon-link,
+.markdown-body h6 .octicon-link {
+ color: #1b1f23;
+ vertical-align: middle;
+ visibility: hidden;
+}
+
+.markdown-body h1:hover .anchor,
+.markdown-body h2:hover .anchor,
+.markdown-body h3:hover .anchor,
+.markdown-body h4:hover .anchor,
+.markdown-body h5:hover .anchor,
+.markdown-body h6:hover .anchor {
+ text-decoration: none;
+}
+
+.markdown-body h1:hover .anchor .octicon-link,
+.markdown-body h2:hover .anchor .octicon-link,
+.markdown-body h3:hover .anchor .octicon-link,
+.markdown-body h4:hover .anchor .octicon-link,
+.markdown-body h5:hover .anchor .octicon-link,
+.markdown-body h6:hover .anchor .octicon-link {
+ visibility: visible;
+}
+
+.markdown-body h1:hover .anchor .octicon-link:before,
+.markdown-body h2:hover .anchor .octicon-link:before,
+.markdown-body h3:hover .anchor .octicon-link:before,
+.markdown-body h4:hover .anchor .octicon-link:before,
+.markdown-body h5:hover .anchor .octicon-link:before,
+.markdown-body h6:hover .anchor .octicon-link:before {
+ width: 16px;
+ height: 16px;
+ content: ' ';
+ display: inline-block;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' version='1.1' width='16' height='16' aria-hidden='true'%3E%3Cpath fill-rule='evenodd' d='M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z'%3E%3C/path%3E%3C/svg%3E");
+}
+
+.markdown-body {
+ -ms-text-size-adjust: 100%;
+ -webkit-text-size-adjust: 100%;
+ line-height: 1.5;
+ color: #24292e;
+ font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji;
+ font-size: 14px;
+ word-wrap: break-word;
+}
+
+.markdown-body details {
+ display: block;
+}
+
+.markdown-body summary {
+ display: list-item;
+}
+
+.markdown-body a {
+ background-color: initial;
+}
+
+.markdown-body a:active,
+.markdown-body a:hover {
+ outline-width: 0;
+}
+
+.markdown-body strong {
+ font-weight: bolder;
+}
+
+.markdown-body h1 {
+ font-size: 2em;
+ margin: .67em 0;
+}
+
+.markdown-body img {
+ border-style: none;
+}
+
+.markdown-body code,
+.markdown-body kbd,
+.markdown-body pre {
+ font-family: monospace, monospace;
+ font-size: 1em;
+}
+
+.markdown-body hr {
+ box-sizing: initial;
+ height: 0;
+ overflow: visible;
+}
+
+.markdown-body input {
+ font: inherit;
+ margin: 0;
+}
+
+.markdown-body input {
+ overflow: visible;
+}
+
+.markdown-body [type=checkbox] {
+ box-sizing: border-box;
+ padding: 0;
+}
+
+.markdown-body * {
+ box-sizing: border-box;
+}
+
+.markdown-body input {
+ font-family: inherit;
+ font-size: inherit;
+ line-height: inherit;
+}
+
+.markdown-body a {
+ color: #0366d6;
+ text-decoration: none;
+}
+
+.markdown-body a:hover {
+ text-decoration: underline;
+}
+
+.markdown-body strong {
+ font-weight: 600;
+}
+
+.markdown-body hr {
+ height: 0;
+ margin: 15px 0;
+ overflow: hidden;
+ background: transparent;
+ border: 0;
+ border-bottom: 1px solid #dfe2e5;
+}
+
+.markdown-body hr:after,
+.markdown-body hr:before {
+ display: table;
+ content: "";
+}
+
+.markdown-body hr:after {
+ clear: both;
+}
+
+.markdown-body table {
+ border-spacing: 0;
+ border-collapse: collapse;
+}
+
+.markdown-body td,
+.markdown-body th {
+ padding: 0;
+}
+
+.markdown-body details summary {
+ cursor: pointer;
+}
+
+.markdown-body kbd {
+ display: inline-block;
+ padding: 3px 5px;
+ font: 11px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
+ line-height: 10px;
+ color: #444d56;
+ vertical-align: middle;
+ background-color: #fafbfc;
+ border: 1px solid #d1d5da;
+ border-radius: 3px;
+ box-shadow: inset 0 -1px 0 #d1d5da;
+}
+
+.markdown-body h1,
+.markdown-body h2,
+.markdown-body h3,
+.markdown-body h4,
+.markdown-body h5,
+.markdown-body h6 {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+.markdown-body h1 {
+ font-size: 32px;
+}
+
+.markdown-body h1,
+.markdown-body h2 {
+ font-weight: 600;
+}
+
+.markdown-body h2 {
+ font-size: 24px;
+}
+
+.markdown-body h3 {
+ font-size: 20px;
+}
+
+.markdown-body h3,
+.markdown-body h4 {
+ font-weight: 600;
+}
+
+.markdown-body h4 {
+ font-size: 16px;
+}
+
+.markdown-body h5 {
+ font-size: 14px;
+}
+
+.markdown-body h5,
+.markdown-body h6 {
+ font-weight: 600;
+}
+
+.markdown-body h6 {
+ font-size: 12px;
+}
+
+.markdown-body p {
+ margin-top: 0;
+ margin-bottom: 10px;
+}
+
+.markdown-body blockquote {
+ margin: 0;
+}
+
+.markdown-body ol,
+.markdown-body ul {
+ padding-left: 0;
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+.markdown-body ol ol,
+.markdown-body ul ol {
+ list-style-type: lower-roman;
+}
+
+.markdown-body ol ol ol,
+.markdown-body ol ul ol,
+.markdown-body ul ol ol,
+.markdown-body ul ul ol {
+ list-style-type: lower-alpha;
+}
+
+.markdown-body dd {
+ margin-left: 0;
+}
+
+.markdown-body code,
+.markdown-body pre {
+ font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
+ font-size: 12px;
+}
+
+.markdown-body pre {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+.markdown-body input::-webkit-inner-spin-button,
+.markdown-body input::-webkit-outer-spin-button {
+ margin: 0;
+ -webkit-appearance: none;
+ appearance: none;
+}
+
+.markdown-body :checked + .radio-label {
+ position: relative;
+ z-index: 1;
+ border-color: #0366d6;
+}
+
+.markdown-body .border {
+ border: 1px solid #e1e4e8 !important;
+}
+
+.markdown-body .border-0 {
+ border: 0 !important;
+}
+
+.markdown-body .border-bottom {
+ border-bottom: 1px solid #e1e4e8 !important;
+}
+
+.markdown-body .rounded-1 {
+ border-radius: 3px !important;
+}
+
+.markdown-body .bg-white {
+ background-color: #fff !important;
+}
+
+.markdown-body .bg-gray-light {
+ background-color: #fafbfc !important;
+}
+
+.markdown-body .text-gray-light {
+ color: #6a737d !important;
+}
+
+.markdown-body .mb-0 {
+ margin-bottom: 0 !important;
+}
+
+.markdown-body .my-2 {
+ margin-top: 8px !important;
+ margin-bottom: 8px !important;
+}
+
+.markdown-body .pl-0 {
+ padding-left: 0 !important;
+}
+
+.markdown-body .py-0 {
+ padding-top: 0 !important;
+ padding-bottom: 0 !important;
+}
+
+.markdown-body .pl-1 {
+ padding-left: 4px !important;
+}
+
+.markdown-body .pl-2 {
+ padding-left: 8px !important;
+}
+
+.markdown-body .py-2 {
+ padding-top: 8px !important;
+ padding-bottom: 8px !important;
+}
+
+.markdown-body .pl-3,
+.markdown-body .px-3 {
+ padding-left: 16px !important;
+}
+
+.markdown-body .px-3 {
+ padding-right: 16px !important;
+}
+
+.markdown-body .pl-4 {
+ padding-left: 24px !important;
+}
+
+.markdown-body .pl-5 {
+ padding-left: 32px !important;
+}
+
+.markdown-body .pl-6 {
+ padding-left: 40px !important;
+}
+
+.markdown-body .f6 {
+ font-size: 12px !important;
+}
+
+.markdown-body .lh-condensed {
+ line-height: 1.25 !important;
+}
+
+.markdown-body .text-bold {
+ font-weight: 600 !important;
+}
+
+.markdown-body .pl-c {
+ color: #6a737d;
+}
+
+.markdown-body .pl-c1,
+.markdown-body .pl-s .pl-v {
+ color: #005cc5;
+}
+
+.markdown-body .pl-e,
+.markdown-body .pl-en {
+ color: #6f42c1;
+}
+
+.markdown-body .pl-s .pl-s1,
+.markdown-body .pl-smi {
+ color: #24292e;
+}
+
+.markdown-body .pl-ent {
+ color: #22863a;
+}
+
+.markdown-body .pl-k {
+ color: #d73a49;
+}
+
+.markdown-body .pl-pds,
+.markdown-body .pl-s,
+.markdown-body .pl-s .pl-pse .pl-s1,
+.markdown-body .pl-sr,
+.markdown-body .pl-sr .pl-cce,
+.markdown-body .pl-sr .pl-sra,
+.markdown-body .pl-sr .pl-sre {
+ color: #032f62;
+}
+
+.markdown-body .pl-smw,
+.markdown-body .pl-v {
+ color: #e36209;
+}
+
+.markdown-body .pl-bu {
+ color: #b31d28;
+}
+
+.markdown-body .pl-ii {
+ color: #fafbfc;
+ background-color: #b31d28;
+}
+
+.markdown-body .pl-c2 {
+ color: #fafbfc;
+ background-color: #d73a49;
+}
+
+.markdown-body .pl-c2:before {
+ content: "^M";
+}
+
+.markdown-body .pl-sr .pl-cce {
+ font-weight: 700;
+ color: #22863a;
+}
+
+.markdown-body .pl-ml {
+ color: #735c0f;
+}
+
+.markdown-body .pl-mh,
+.markdown-body .pl-mh .pl-en,
+.markdown-body .pl-ms {
+ font-weight: 700;
+ color: #005cc5;
+}
+
+.markdown-body .pl-mi {
+ font-style: italic;
+ color: #24292e;
+}
+
+.markdown-body .pl-mb {
+ font-weight: 700;
+ color: #24292e;
+}
+
+.markdown-body .pl-md {
+ color: #b31d28;
+ background-color: #ffeef0;
+}
+
+.markdown-body .pl-mi1 {
+ color: #22863a;
+ background-color: #f0fff4;
+}
+
+.markdown-body .pl-mc {
+ color: #e36209;
+ background-color: #ffebda;
+}
+
+.markdown-body .pl-mi2 {
+ color: #f6f8fa;
+ background-color: #005cc5;
+}
+
+.markdown-body .pl-mdr {
+ font-weight: 700;
+ color: #6f42c1;
+}
+
+.markdown-body .pl-ba {
+ color: #586069;
+}
+
+.markdown-body .pl-sg {
+ color: #959da5;
+}
+
+.markdown-body .pl-corl {
+ text-decoration: underline;
+ color: #032f62;
+}
+
+.markdown-body .mb-0 {
+ margin-bottom: 0 !important;
+}
+
+.markdown-body .my-2 {
+ margin-bottom: 8px !important;
+}
+
+.markdown-body .my-2 {
+ margin-top: 8px !important;
+}
+
+.markdown-body .pl-0 {
+ padding-left: 0 !important;
+}
+
+.markdown-body .py-0 {
+ padding-top: 0 !important;
+ padding-bottom: 0 !important;
+}
+
+.markdown-body .pl-1 {
+ padding-left: 4px !important;
+}
+
+.markdown-body .pl-2 {
+ padding-left: 8px !important;
+}
+
+.markdown-body .py-2 {
+ padding-top: 8px !important;
+ padding-bottom: 8px !important;
+}
+
+.markdown-body .pl-3 {
+ padding-left: 16px !important;
+}
+
+.markdown-body .pl-4 {
+ padding-left: 24px !important;
+}
+
+.markdown-body .pl-5 {
+ padding-left: 32px !important;
+}
+
+.markdown-body .pl-6 {
+ padding-left: 40px !important;
+}
+
+.markdown-body .pl-7 {
+ padding-left: 48px !important;
+}
+
+.markdown-body .pl-8 {
+ padding-left: 64px !important;
+}
+
+.markdown-body .pl-9 {
+ padding-left: 80px !important;
+}
+
+.markdown-body .pl-10 {
+ padding-left: 96px !important;
+}
+
+.markdown-body .pl-11 {
+ padding-left: 112px !important;
+}
+
+.markdown-body .pl-12 {
+ padding-left: 128px !important;
+}
+
+.markdown-body hr {
+ border-bottom-color: #eee;
+}
+
+.markdown-body:after,
+.markdown-body:before {
+ display: table;
+ content: "";
+}
+
+.markdown-body:after {
+ clear: both;
+}
+
+.markdown-body > :first-child {
+ margin-top: 0 !important;
+}
+
+.markdown-body > :last-child {
+ margin-bottom: 0 !important;
+}
+
+.markdown-body a:not([href]) {
+ color: inherit;
+ text-decoration: none;
+}
+
+.markdown-body blockquote,
+.markdown-body details,
+.markdown-body dl,
+.markdown-body ol,
+.markdown-body p,
+.markdown-body pre,
+.markdown-body table,
+.markdown-body ul {
+ margin-top: 0;
+ margin-bottom: 16px;
+}
+
+.markdown-body hr {
+ height: .25em;
+ padding: 0;
+ margin: 24px 0;
+ background-color: #e1e4e8;
+ border: 0;
+}
+
+.markdown-body blockquote {
+ padding: 0 1em;
+ color: #6a737d;
+ border-left: .25em solid #dfe2e5;
+}
+
+.markdown-body blockquote > :first-child {
+ margin-top: 0;
+}
+
+.markdown-body blockquote > :last-child {
+ margin-bottom: 0;
+}
+
+.markdown-body h1,
+.markdown-body h2,
+.markdown-body h3,
+.markdown-body h4,
+.markdown-body h5,
+.markdown-body h6 {
+ margin-top: 24px;
+ margin-bottom: 16px;
+ font-weight: 600;
+ line-height: 1.25;
+}
+
+.markdown-body h1 {
+ font-size: 2em;
+}
+
+.markdown-body h1,
+.markdown-body h2 {
+ padding-bottom: .3em;
+ border-bottom: 1px solid #eaecef;
+}
+
+.markdown-body h2 {
+ font-size: 1.5em;
+}
+
+.markdown-body h3 {
+ font-size: 1.25em;
+}
+
+.markdown-body h4 {
+ font-size: 1em;
+}
+
+.markdown-body h5 {
+ font-size: .875em;
+}
+
+.markdown-body h6 {
+ font-size: .85em;
+ color: #6a737d;
+}
+
+.markdown-body ol,
+.markdown-body ul {
+ padding-left: 2em;
+}
+
+.markdown-body ol ol,
+.markdown-body ol ul,
+.markdown-body ul ol,
+.markdown-body ul ul {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+.markdown-body li {
+ list-style: initial;
+}
+
+.markdown-body li > p {
+ margin-top: 16px;
+}
+
+.markdown-body li + li {
+ margin-top: .25em;
+}
+
+.markdown-body dl {
+ padding: 0;
+}
+
+.markdown-body dl dt {
+ padding: 0;
+ margin-top: 16px;
+ font-size: 1em;
+ font-style: italic;
+ font-weight: 600;
+}
+
+.markdown-body dl dd {
+ padding: 0 16px;
+ margin-bottom: 16px;
+}
+
+.markdown-body table {
+ display: block;
+ width: 100%;
+ overflow: auto;
+}
+
+.markdown-body table th {
+ font-weight: 600;
+}
+
+.markdown-body table td,
+.markdown-body table th {
+ padding: 6px 13px;
+ border: 1px solid #dfe2e5;
+}
+
+.markdown-body table tr {
+ background-color: #fff;
+ border-top: 1px solid #c6cbd1;
+}
+
+.markdown-body table tr:nth-child(2n) {
+ background-color: #f6f8fa;
+}
+
+.markdown-body img {
+ max-width: 100%;
+ box-sizing: initial;
+ background-color: #fff;
+}
+
+.markdown-body img[align=right] {
+ padding-left: 20px;
+}
+
+.markdown-body img[align=left] {
+ padding-right: 20px;
+}
+
+.markdown-body code {
+ padding: .2em .4em;
+ margin: 0;
+ font-size: 85%;
+ background-color: rgba(27, 31, 35, .05);
+ border-radius: 3px;
+}
+
+.markdown-body pre {
+ word-wrap: normal;
+}
+
+.markdown-body pre > code {
+ padding: 0;
+ margin: 0;
+ font-size: 100%;
+ word-break: normal;
+ white-space: pre;
+ background: transparent;
+ border: 0;
+}
+
+.markdown-body .highlight {
+ margin-bottom: 16px;
+}
+
+.markdown-body .highlight pre {
+ margin-bottom: 0;
+ word-break: normal;
+}
+
+.markdown-body .highlight pre,
+.markdown-body pre {
+ padding: 16px;
+ overflow: auto;
+ font-size: 85%;
+ line-height: 1.45;
+ background-color: #f6f8fa;
+ border-radius: 3px;
+}
+
+.markdown-body pre code {
+ display: inline;
+ max-width: auto;
+ padding: 0;
+ margin: 0;
+ overflow: visible;
+ line-height: inherit;
+ word-wrap: normal;
+ background-color: initial;
+ border: 0;
+}
+
+.markdown-body .commit-tease-sha {
+ display: inline-block;
+ font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
+ font-size: 90%;
+ color: #444d56;
+}
+
+.markdown-body .full-commit .btn-outline:not(:disabled):hover {
+ color: #005cc5;
+ border-color: #005cc5;
+}
+
+.markdown-body .blob-wrapper {
+ overflow-x: auto;
+ overflow-y: hidden;
+}
+
+.markdown-body .blob-wrapper-embedded {
+ max-height: 240px;
+ overflow-y: auto;
+}
+
+.markdown-body .blob-num {
+ width: 1%;
+ min-width: 50px;
+ padding-right: 10px;
+ padding-left: 10px;
+ font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
+ font-size: 12px;
+ line-height: 20px;
+ color: rgba(27, 31, 35, .3);
+ text-align: right;
+ white-space: nowrap;
+ vertical-align: top;
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+.markdown-body .blob-num:hover {
+ color: rgba(27, 31, 35, .6);
+}
+
+.markdown-body .blob-num:before {
+ content: attr(data-line-number);
+}
+
+.markdown-body .blob-code {
+ position: relative;
+ padding-right: 10px;
+ padding-left: 10px;
+ line-height: 20px;
+ vertical-align: top;
+}
+
+.markdown-body .blob-code-inner {
+ overflow: visible;
+ font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
+ font-size: 12px;
+ color: #24292e;
+ word-wrap: normal;
+ white-space: pre;
+}
+
+.markdown-body .pl-token.active,
+.markdown-body .pl-token:hover {
+ cursor: pointer;
+ background: #ffea7f;
+}
+
+.markdown-body .tab-size[data-tab-size="1"] {
+ -moz-tab-size: 1;
+ tab-size: 1;
+}
+
+.markdown-body .tab-size[data-tab-size="2"] {
+ -moz-tab-size: 2;
+ tab-size: 2;
+}
+
+.markdown-body .tab-size[data-tab-size="3"] {
+ -moz-tab-size: 3;
+ tab-size: 3;
+}
+
+.markdown-body .tab-size[data-tab-size="4"] {
+ -moz-tab-size: 4;
+ tab-size: 4;
+}
+
+.markdown-body .tab-size[data-tab-size="5"] {
+ -moz-tab-size: 5;
+ tab-size: 5;
+}
+
+.markdown-body .tab-size[data-tab-size="6"] {
+ -moz-tab-size: 6;
+ tab-size: 6;
+}
+
+.markdown-body .tab-size[data-tab-size="7"] {
+ -moz-tab-size: 7;
+ tab-size: 7;
+}
+
+.markdown-body .tab-size[data-tab-size="8"] {
+ -moz-tab-size: 8;
+ tab-size: 8;
+}
+
+.markdown-body .tab-size[data-tab-size="9"] {
+ -moz-tab-size: 9;
+ tab-size: 9;
+}
+
+.markdown-body .tab-size[data-tab-size="10"] {
+ -moz-tab-size: 10;
+ tab-size: 10;
+}
+
+.markdown-body .tab-size[data-tab-size="11"] {
+ -moz-tab-size: 11;
+ tab-size: 11;
+}
+
+.markdown-body .tab-size[data-tab-size="12"] {
+ -moz-tab-size: 12;
+ tab-size: 12;
+}
+
+.markdown-body .task-list-item {
+ list-style-type: none;
+}
+
+.markdown-body .task-list-item + .task-list-item {
+ margin-top: 3px;
+}
+
+.markdown-body .task-list-item input {
+ margin: 0 .2em .25em -1.6em;
+ vertical-align: middle;
+}