├── screenshot.png ├── assets ├── img │ ├── icp.png │ ├── avatar.png │ ├── loading.gif │ ├── favicon.svg │ ├── thumbnail.svg │ └── logo.svg ├── css │ ├── tabs.css │ ├── comment.css │ └── dark.css └── js │ ├── animation.js │ ├── back-to-top.js │ ├── comment.js │ └── main.js ├── page ├── tags.php ├── categories.php └── archives.php ├── single.php ├── library ├── Plugin │ ├── Progressbar.php │ ├── Moment.php │ ├── Clipboard.php │ ├── Animejs.php │ ├── BackToTop.php │ ├── Gallery.php │ ├── OutdatedBrowser.php │ ├── Highlight.php │ ├── Copyleft.php │ ├── Tabs.php │ ├── Mathjax.php │ └── SweetAlert.php ├── Ajax.php ├── Module │ ├── Archive.php │ ├── Paginator.php │ ├── Category.php │ ├── Tag.php │ ├── Link.php │ ├── RecentPost.php │ ├── DarkMode.php │ ├── Donate.php │ ├── Search.php │ ├── Navbar.php │ ├── Toc.php │ ├── Profile.php │ └── Comments.php ├── FormHelper.php ├── I18n.php ├── Backup.php ├── Module.php ├── Plugin.php ├── Page.php ├── Util.php ├── Aside.php ├── Assets.php ├── Config.php └── Content.php ├── index.php ├── .gitignore ├── 404.php ├── LICENSE ├── component ├── header.php └── footer.php ├── README.md ├── functions.php └── archive.php /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HsukqiLee/Typecho-Theme-Kylin/HEAD/screenshot.png -------------------------------------------------------------------------------- /assets/img/icp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HsukqiLee/Typecho-Theme-Kylin/HEAD/assets/img/icp.png -------------------------------------------------------------------------------- /assets/img/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HsukqiLee/Typecho-Theme-Kylin/HEAD/assets/img/avatar.png -------------------------------------------------------------------------------- /assets/img/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HsukqiLee/Typecho-Theme-Kylin/HEAD/assets/img/loading.gif -------------------------------------------------------------------------------- /page/tags.php: -------------------------------------------------------------------------------- 1 | need('component/header.php'); 6 | 7 | Icarus_Module::show('Tag', TRUE); 8 | 9 | $this->need('component/footer.php'); 10 | -------------------------------------------------------------------------------- /single.php: -------------------------------------------------------------------------------- 1 | need('component/header.php'); 6 | 7 | Icarus_Module::show('Single', $this); 8 | 9 | $this->need('component/footer.php'); 10 | -------------------------------------------------------------------------------- /page/categories.php: -------------------------------------------------------------------------------- 1 | need('component/header.php'); 6 | 7 | Icarus_Module::show('Category', TRUE); 8 | 9 | $this->need('component/footer.php'); 10 | -------------------------------------------------------------------------------- /library/Plugin/Progressbar.php: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /library/Plugin/Animejs.php: -------------------------------------------------------------------------------- 1 | 14 | 15 | 14 | 15 | 16 | 17 | need('component/header.php'); 15 | 16 | Icarus_Module::load('Single'); 17 | $post = new Icarus_Module_Single($this); 18 | while ($this->next()) 19 | { 20 | $post->doOutput(); 21 | } 22 | 23 | Icarus_Module::show('Paginator', $this); 24 | 25 | $this->need('component/footer.php'); 26 | -------------------------------------------------------------------------------- /assets/img/thumbnail.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Copilot instructions (internal development configuration) 3 | .github/copilot-instructions.md 4 | 5 | # IDE and Editor files 6 | .vscode/ 7 | .idea/ 8 | *.swp 9 | *.swo 10 | *~ 11 | 12 | # OS generated files 13 | .DS_Store 14 | .DS_Store? 15 | ._* 16 | .Spotlight-V100 17 | .Trashes 18 | ehthumbs.db 19 | Thumbs.db 20 | 21 | # Temporary files 22 | *.tmp 23 | *.temp 24 | *.log 25 | 26 | # Backup files 27 | *.bak 28 | *.backup 29 | 30 | # Node modules (if any) 31 | node_modules/ 32 | npm-debug.log* 33 | yarn-debug.log* 34 | yarn-error.log* 35 | 36 | # PHP specific 37 | composer.phar 38 | vendor/ 39 | *.log 40 | 41 | # Cache files 42 | cache/ 43 | *.cache 44 | 45 | # Environment files 46 | .env 47 | .env.local 48 | .env.development.local 49 | .env.test.local 50 | .env.production.local 51 | 52 | # Build outputs 53 | dist/ 54 | build/ 55 | 56 | # Sass cache 57 | .sass-cache/ 58 | -------------------------------------------------------------------------------- /library/Plugin/OutdatedBrowser.php: -------------------------------------------------------------------------------- 1 | 20 |
21 | 30 | isPost() && isset($request->icarus_action)) 14 | { 15 | ob_clean(); 16 | $security->protect(); 17 | 18 | $notice->set(_IcT($msgId), $result ? 'success' : 'error'); 19 | Icarus_Util::jsonResponse(array( 20 | 'action' => 'refresh' 21 | )); 22 | exit; 23 | 24 | $notice->set(_IcT($msgId . '.' . $result), $result == 0 ? 'success' : 'error'); 25 | Icarus_Util::jsonResponse(array( 26 | 'action' => 'refresh' 27 | )); 28 | exit; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /library/Plugin/Highlight.php: -------------------------------------------------------------------------------- 1 | packInput('Highlight/theme', 'atom-one-light', 'w-40'); 12 | $form->packInput('Highlight/theme_dark', 'atom-one-dark', 'w-40'); 13 | } 14 | 15 | public static function header() 16 | { 17 | Icarus_Assets::cdn('css', 'highlight.js', self::VERSION, 'styles/' . Icarus_Config::get('highlight_theme', 'atom-one-light') . '.min.css'); 18 | echo '' . PHP_EOL; 19 | } 20 | 21 | public static function footer() 22 | { 23 | Icarus_Assets::cdn('js+defer', 'highlight.js', self::VERSION, 'highlight.min.js'); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /404.php: -------------------------------------------------------------------------------- 1 | clear(); 6 | Icarus_Aside::$asideRight->clear(); 7 | 8 | $this->need('component/header.php'); 9 | 10 | ?> 11 | 12 |
13 |
14 |

15 | 16 | 17 |
18 | 28 |
29 | 30 | need('component/footer.php'); 32 | -------------------------------------------------------------------------------- /library/Plugin/Copyleft.php: -------------------------------------------------------------------------------- 1 | packInput('Copyleft/text', 'CC BY', 'w-40'); 10 | } 11 | 12 | public static function footer() 13 | { 14 | ?> 15 | 16 | 30 | 19 | 42 | div{display:none;width:100%;height:auto;text-align:center;background:#fff;border:1px solid #ddd;margin-top:-1px;padding:20px;border-top-right-radius:3px;border-bottom-left-radius:3px;border-bottom-right-radius:3px;font-size:14px}.my-tabs>div>table{margin-bottom:0!important}.my-tabs>div>pre{margin:0!important}.my-tabs div.selected{display:block}.tab-li{margin:0!important} -------------------------------------------------------------------------------- /library/Module/Archive.php: -------------------------------------------------------------------------------- 1 | 14 |
15 |
16 | 43 |
44 |
45 | packInput('Mathjax/extensions', '', 'w-100'); 10 | $form->packRadio('Mathjax/messages', array('0', '1'), '1'); 11 | //$form->packRadio('Mathjax/menu', array('0', '1'), '1'); 12 | } 13 | 14 | public static function footer() 15 | { 16 | Icarus_Config::tryGet('mathjax_extensions', $Extensions); 17 | $Extension = '"'.implode('","', explode(',', $Extensions)).'"'; 18 | ?> 19 | 38 | pageNav('«', '»', 1, '...', array( 10 | 'wrapTag' => 'ul', 11 | 'wrapClass' => 'pagination-list', 12 | 'currentClass' => 'is-current', 13 | 'prevClass' => '', 14 | 'nextClass' => '' 15 | )); 16 | $content = ob_get_contents(); 17 | ob_end_clean(); 18 | $content = str_replace( 19 | array( 20 | '
  • 36 |
    37 | 40 |
    41 | -------------------------------------------------------------------------------- /library/FormHelper.php: -------------------------------------------------------------------------------- 1 | 3 | 4 | > 5 | 6 | 7 | 8 | 9 | 10 | 11 | <?php 12 | Icarus_Page::printPageTitle(); 13 | if($this->_currentPage>1) echo ' - ',_IcT('title.page.before'),$this->_currentPage,_IcT('title.page.after'); 14 | ?> 15 | 16 | 17 | header('generator=&template=&pingback=&xmlrpc=&wlw=&atom=&rss1=&rss2=&commentReply='); 20 | 21 | 22 | Icarus_Assets::cdn('css', 'icon'); 23 | Icarus_Assets::cdn('css', 'font', 'Ubuntu:400,600|Source+Code+Pro'); 24 | Icarus_Assets::cdn('css', 'bulma', '0.7.2', 'css/bulma.min.css'); 25 | 26 | 27 | Icarus_Assets::printThemeCss('style.css', FALSE); 28 | 29 | Icarus_Plugin::headerAll(); 30 | Icarus_Module::headerAll(); 31 | 32 | 33 | Icarus_Page::printHeader(); 34 | 35 | ?> 36 | 37 | 38 | 39 |
    40 |
    41 |
    42 |
    43 | 44 | -------------------------------------------------------------------------------- /library/Module/Category.php: -------------------------------------------------------------------------------- 1 | 14 |
    15 |
    16 | 30 |
    31 |
    32 | 39 |
  • 40 | 41 | 42 | name, ENT_QUOTES, 'UTF-8'); ?> 43 | 44 | 45 | count); ?> 46 | 47 | 48 | children) { 50 | $widget->treeViewCategories(); 51 | } 52 | ?> 53 |
  • 54 | packInput('Tag/limit', '20', 'w-20'); 11 | } 12 | 13 | private static function getLimit() 14 | { 15 | $limit = intval(Icarus_Config::get('tag_limit', 20)); 16 | if ($limit < 0) 17 | $limit = 20; 18 | return $limit; 19 | } 20 | 21 | public static function output($showAll = FALSE) 22 | { 23 | $tags = Typecho_Widget::widget( 24 | 'Widget_Metas_Tag_Cloud', 25 | $showAll ? NULL : ('limit=' . self::getLimit()) 26 | ); 27 | ?> 28 |
    29 |
    30 | 51 |
    52 |
    53 | 54 | .navbar, body > .section, body > .footer').forEach(function (element) { 7 | element.style.transition = '0s'; 8 | element.style.opacity = '0'; 9 | }); 10 | document.querySelector('body > .navbar').style.transform = 'translateY(-100px)'; 11 | ['.column-main > .card', 12 | '.column-left > .card, .column-right-shadow > .card', 13 | '.column-right > .card'].map(function (selector) { 14 | $(selector).forEach(function (element) { 15 | element.style.transition = '0s'; 16 | element.style.opacity = '0'; 17 | element.style.transform = 'scale(0.8)'; 18 | element.style.transformOrigin = 'center top'; 19 | }); 20 | }); 21 | setTimeout(function () { 22 | $('body > .navbar, body > .section, body > .footer').forEach(function (element) { 23 | element.style.opacity = '1'; 24 | element.style.transition = 'opacity 0.3s ease-out, transform 0.3s ease-out'; 25 | }); 26 | document.querySelector('body > .navbar').style.transform = 'translateY(0)'; 27 | ['.column-main > .card', 28 | '.column-left > .card, .column-right-shadow > .card', 29 | '.column-right > .card'].map(function (selector) { 30 | var i = 1; 31 | $(selector).forEach(function (element) { 32 | setTimeout(function () { 33 | element.style.opacity = '1'; 34 | element.style.transform = ''; 35 | element.style.transition = 'opacity 0.3s ease-out, transform 0.3s ease-out'; 36 | }, i * 100); 37 | i++; 38 | }); 39 | }); 40 | }); 41 | })(); 42 | -------------------------------------------------------------------------------- /assets/js/back-to-top.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | function $() { 3 | return Array.prototype.slice.call(document.querySelectorAll.apply(document, arguments)); 4 | } 5 | 6 | $('body > .navbar, body > .section, body > .footer').forEach(function (element) { 7 | element.style.transition = '0s'; 8 | element.style.opacity = '0'; 9 | }); 10 | document.querySelector('body > .navbar').style.transform = 'translateY(-100px)'; 11 | ['.column-main > .card', 12 | '.column-left > .card, .column-right-shadow > .card', 13 | '.column-right > .card'].map(function (selector) { 14 | $(selector).forEach(function (element) { 15 | element.style.transition = '0s'; 16 | element.style.opacity = '0'; 17 | element.style.transform = 'scale(0.8)'; 18 | element.style.transformOrigin = 'center top'; 19 | }); 20 | }); 21 | setTimeout(function () { 22 | $('body > .navbar, body > .section, body > .footer').forEach(function (element) { 23 | element.style.opacity = '1'; 24 | element.style.transition = 'opacity 0.3s ease-out, transform 0.3s ease-out'; 25 | }); 26 | document.querySelector('body > .navbar').style.transform = 'translateY(0)'; 27 | ['.column-main > .card', 28 | '.column-left > .card, .column-right-shadow > .card', 29 | '.column-right > .card'].map(function (selector) { 30 | var i = 1; 31 | $(selector).forEach(function (element) { 32 | setTimeout(function () { 33 | element.style.opacity = '1'; 34 | element.style.transform = ''; 35 | element.style.transition = 'opacity 0.3s ease-out, transform 0.3s ease-out'; 36 | }, i * 100); 37 | i++; 38 | }); 39 | }); 40 | }); 41 | })(); 42 | -------------------------------------------------------------------------------- /library/Module/Link.php: -------------------------------------------------------------------------------- 1 | packTextarea('Link/links', "Typecho,http://typecho.org/\nGitHub,https://github.com/"); 11 | } 12 | 13 | private static function getUrlDomain($url) 14 | { 15 | return parse_url($url, PHP_URL_HOST); 16 | } 17 | 18 | private static function getLinks() 19 | { 20 | return Icarus_Util::parseMultilineData(Icarus_Config::get('link_links'), 2); 21 | } 22 | 23 | public static function output() 24 | { 25 | $links = self::getLinks(); 26 | if (Icarus_Util::isEmpty($links) === true) 27 | return; 28 | 29 | ?> 30 |
    31 |
    32 | 53 |
    54 |
    55 | _lang = $lang; 14 | $this->_data = array(); 15 | if (is_array($data)) { 16 | self::processTranslation($data, $this->_data); 17 | } 18 | } 19 | 20 | private static function processTranslation($data, &$result, $baseKey = '') 21 | { 22 | foreach ($data as $k => $v) { 23 | if (is_array($v)) { 24 | self::processTranslation($v, $result, $baseKey . $k . '.'); 25 | } else { 26 | $result[$baseKey . $k] = $v; 27 | } 28 | } 29 | } 30 | 31 | public static function getInstance() 32 | { 33 | if (is_null(self::$_instance)) { 34 | $curLang = Icarus_Util::$options->lang; 35 | if (!preg_match('/^[a-zA-Z0-9-_]+$/', $curLang)) 36 | $curLang = 'zh_CN'; 37 | 38 | $langFile = __ICARUS_ROOT__ . 'lang/' . $curLang . '.php'; 39 | $langData = file_exists($langFile) ? require $langFile : array(); 40 | 41 | self::$_instance = new self($curLang, $langData); 42 | } 43 | return self::$_instance; 44 | } 45 | 46 | public static function init() 47 | { 48 | self::getInstance(); 49 | } 50 | 51 | public function hasTranslation($key) 52 | { 53 | return array_key_exists($key, $this->_data); 54 | } 55 | 56 | public function getTranslation($key) 57 | { 58 | if ($this->hasTranslation($key)) { 59 | $translation = $this->_data[$key]; 60 | if (strlen($translation) > 0 && $translation[0] == '@') 61 | return $this->getTranslation(substr($translation, 1)); 62 | else 63 | return $translation; 64 | } else { 65 | return NULL; 66 | } 67 | } 68 | 69 | public static function get($key) 70 | { 71 | $lang = self::getInstance(); 72 | $result = $lang->getTranslation($key); 73 | return is_null($result) ? $key : $result; 74 | } 75 | 76 | public static function has($key) 77 | { 78 | $lang = self::getInstance(); 79 | return $lang->hasTranslation($key); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /library/Backup.php: -------------------------------------------------------------------------------- 1 | __get(self::THEME_BACKUP_FIELD)); 12 | } 13 | 14 | protected static function getCurrentSerializedConfig() 15 | { 16 | return Icarus_Util::$options->__get('theme:' . Icarus_Util::$options->theme); 17 | } 18 | 19 | public static function save() 20 | { 21 | $db = Typecho_Db::get(); 22 | 23 | if (self::exist()) { 24 | $sql = $db->update('table.options') 25 | ->where('name = ?', self::THEME_BACKUP_FIELD) 26 | ->rows(array( 27 | 'value' => self::getCurrentSerializedConfig() 28 | )); 29 | } else { 30 | $sql = $db->insert('table.options') 31 | ->rows(array( 32 | 'name' => self::THEME_BACKUP_FIELD, 33 | 'value' => self::getCurrentSerializedConfig(), 34 | 'user' => 0 35 | )); 36 | } 37 | 38 | try { 39 | $db->query($sql); 40 | } catch (Typecho_Db_Query_Exception $exception) { 41 | return 1; 42 | } 43 | return 0; 44 | } 45 | 46 | public static function delete() 47 | { 48 | $db = Typecho_Db::get(); 49 | 50 | if (self::exist()) { 51 | $sql = $db->delete('table.options') 52 | ->where('name = ?', self::THEME_BACKUP_FIELD); 53 | } else { 54 | return 1; 55 | } 56 | 57 | try { 58 | if ($db->query($sql) > 0) { 59 | return 0; 60 | } else { 61 | return 1; 62 | } 63 | } catch (Typecho_Db_Query_Exception $exception) { 64 | return 2; 65 | } 66 | } 67 | 68 | public static function restore() 69 | { 70 | $db = Typecho_Db::get(); 71 | 72 | if (self::exist()) { 73 | $sql = $db->update('table.options') 74 | ->where('name = ?', 'theme:' . Icarus_Util::$options->theme) 75 | ->rows(array( 76 | 'value' => Icarus_Util::$options->__get(self::THEME_BACKUP_FIELD) 77 | )); 78 | } else { 79 | return 1; 80 | } 81 | 82 | try { 83 | $db->query($sql); 84 | } catch (Typecho_Db_Query_Exception $exception) { 85 | return 2; 86 | } 87 | return 0; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /library/Module/RecentPost.php: -------------------------------------------------------------------------------- 1 | packInput('RecentPost/limit', '5', 'w-20'); 11 | $form->packRadio('RecentPost/thumbnail', array('0', '1'), '1'); 12 | } 13 | 14 | private static function getLimit() 15 | { 16 | $limit = intval(Icarus_Config::get('recent_post_limit', 5)); 17 | if ($limit <= 0) 18 | $limit = 5; 19 | return $limit; 20 | } 21 | 22 | public static function output() 23 | { 24 | $posts = Typecho_Widget::widget('Widget_Contents_Post_Recent', 'pageSize=' . self::getLimit()); 25 | if ($posts->length == 0) 26 | return; 27 | $thumbnailEnabled = !!Icarus_Config::get('recent_post_thumbnail', true); 28 | 29 | ?> 30 |
    31 |
    32 | 35 | next()): ?> 36 |
    37 | 38 | 39 |

    40 | <?php $posts->title(); ?> 41 |

    42 |
    43 | 44 |
    45 |
    46 |
    47 | title(); ?> 48 | categories): ?> 49 |

    50 | categories[0]; 52 | $directory = Typecho_Widget::widget('Widget_Metas_Category_List')->getAllParents($category['mid']); 53 | $directory[] = $category; 54 | 55 | if (Icarus_Util::isEmpty($directory) === false) { 56 | $result = array(); 57 | 58 | foreach ($directory as $category) { 59 | $result[] = '' 60 | . $category['name'] . ''; 61 | } 62 | 63 | echo implode(' / ', $result); 64 | } 65 | ?> 66 |

    67 | 68 |
    69 |
    70 |
    71 | 72 |
    73 |
    74 | packTitle('DarkMode'); 9 | $form->packRadio('DarkMode/enable', array('0', '1'), '1'); 10 | } 11 | 12 | public static function header() 13 | { 14 | 15 | if (Icarus_Config::tryGet('logo_img', $logo_img)) { 16 | $logoUrl = htmlspecialchars(Icarus_Assets::getUrlForAssets($logo_img), ENT_QUOTES); 17 | echo '' . PHP_EOL; 18 | } 19 | 20 | if (Icarus_Config::tryGet('logo_img_dark', $logo_img_dark)) { 21 | $logoDarkUrl = htmlspecialchars(Icarus_Assets::getUrlForAssets($logo_img_dark), ENT_QUOTES); 22 | echo '' . PHP_EOL; 23 | } 24 | //echo ''; 25 | } 26 | 27 | public static function footer() 28 | { 29 | ?> 30 | 44 | 59 | 62 | 69 | 2 | 3 |
    4 | A simple, delicate and modern theme | 一个简洁、精致、现代的 Typecho 主题 5 |
    6 | 本主题为 Ruipeng Zhang 的 Hexo 主题 Icarus 的 Typecho 移植版本。 7 |

    8 | 9 | ## 预览 Preview 10 | 11 | ![Icarus Preview](https://user-images.githubusercontent.com/32296555/55554465-6ca20d00-5715-11e9-852d-3072f1571854.png) 12 | 13 | - Hexo Demo (Original Theme): [hexo-theme-icarus](https://blog.zhangruipeng.me/hexo-theme-icarus/) 14 | - [图片预览](https://github.com/KeNorizon/typecho-theme-icarus/wiki/%E5%9B%BE%E7%89%87%E9%A2%84%E8%A7%88) 15 | 16 | ## 安装 Installation 17 | 18 | 1. 从 GitHub 上获取本主题。 19 | 20 | 获取途径如下: 21 | 22 | - 下载[最新发布版本](https://github.com/KeNorizon/typecho-theme-icarus/releases)(较稳定) 23 | - 下载[最新的 master 分支](https://github.com/KeNorizon/typecho-theme-icarus/archive/master.zip)(包含新的特性,不保证稳定性) 24 | 25 | 2. 将本主题压缩包解压到 Typecho 博客的 `usr/themes/icarus/` 目录下。 26 | 3. 前往 Typecho 控制台 - 网站外观 - 可以使用的外观 页面,启用 Icarus 主题。 27 | 28 | ## 更新 Upgrade 29 | 30 | 1. 重复 `安装` 步骤第1、2步。 31 | 2. 前往 Typecho 控制台 - 网站外观 - 设置外观 页面,(若有设置项更新提示,则)根据提示点击 `保存设置` 按钮以应用新版本的设置项。 32 | 33 | ## 特性 Feature 34 | 35 | ### 基于 Bulma 的外观 36 | 本主题使用基于 Flexbox 的 [Bulma CSS 框架](https://bulma.io/) 提供其外观。 37 | 38 | ### 常用 Widget 支持 39 | 本主题中的 Widget 指侧边栏中的各个小部件。支持 Widget 包括: 40 | - 简介:显示包括头像、昵称、社交网络链接、博客信息等。 41 | - 归档:按月份列出归档页面链接。 42 | - 链接:用于放置一些链接。 43 | - 分类/最新文章/标签:列出站点的最新文章/分类/标签。 44 | - TOC:显示文章目录(只在文章页面显示)。 45 | 46 | ### Widget 自由布局 47 | 侧边栏 Widget 显示哪些、显示的顺序和左右位置均可自由设定,可以据此调整站点的总体外观。各个 Widget 通用的三个设置项说明如下: 48 | 49 | - **开关**:通过设定开关以决定 Widget 是否显示。 50 | - **顺序**:通过顺序数值的大小决定 Widget 排列的顺序。 51 | - **位置**:允许指定 Widget 显示在左边栏还是右边栏。 52 | 53 | #### 单栏 / 双栏 / 三栏切换 54 | 主题默认为三栏布局。若要切换为双栏,则需要在设置中将一侧的 Widget 关闭或移动到另一侧。若要切换为单栏,则需要关闭全部 Widget。 55 | 56 | #### 首页 / 文章页侧边栏独立设置 57 | 通过设定在首页、文章页分别**隐藏**何种 Widget,可以为首页、文章页设定不同的侧边栏布局。 58 | 59 | ### 多国语言支持 60 | 暂时只有中文支持。将会追加英文翻译支持。 61 | 62 | ### 响应式布局 63 | 在手机、平板、桌面端均有良好的显示效果。 64 | 65 | ### 外部功能支持 66 | 以下功能通过开源组件提供支持。 67 | - 代码高亮:[highlight.js](https://highlightjs.org/) 提供支持。可在设置中指定代码高亮使用的样式主题。 68 | - 人性化时间转换:[Moment.js](https://momentjs.com/) 提供支持。将文章发布时间、评论发表时间转换为更易读的表达形式。 69 | - 图片展示优化:[lightgallery](https://sachinchoolur.github.io/lightGallery/) 提供图片灯箱展示支持。[Justified Gallery](https://miromannino.github.io/Justified-Gallery/) 提供图集展示支持。 70 | - 数学公式渲染:[Mathjax](https://www.mathjax.org/) 提供支持。 71 | 72 | ### 完善的主题设置页面 73 | 本主题设置项较多,设置页面按功能进行了划分,并提供了相应的描述。悬浮在右侧的目录可点击跳转到指定设置项。 74 | 75 | 提供了主题设置的备份功能,避免切换主题导致的设置项丢失。 76 | 77 | ### 可选的主题资源 CDN 78 | 主题提供以下可选公共 CDN 79 | #### 前端公共库 80 | - [JsDelivr](https://www.jsdelivr.com/) 81 | - [cdnjs](https://cdnjs.com/) 82 | - [cdnjs.loli.net](https://css.loli.net/) 83 | #### 字体 84 | - [Google Fonts](https://fonts.google.com/) 85 | - [fonts.loli.net](https://css.loli.net/) 86 | #### Gravatar 87 | - [Gravatar](https://en.gravatar.com/) 88 | - [V2EX Gravatar CDN](https://cdn.v2ex.com/) 89 | - [gravatar.loli.net](https://css.loli.net/) 90 | 91 | ### 页头 / 页脚 / 导航栏 / 评论区自定义 92 | - 自定义第三方评论系统支持(需要自行填入第三方评论系统的调用代码)。 93 | - 页头、页脚、head标签自定义内容追加支持。 94 | - 导航栏、Social Icons、页脚 Icons 可自定义。 95 | -------------------------------------------------------------------------------- /functions.php: -------------------------------------------------------------------------------- 1 | toc(); 67 | } 68 | 69 | /** 70 | * fix duplicated themeFields() calls made by 71 | * admin/custom-fields.php 72 | */ 73 | function themeFieldsInit() 74 | { 75 | static $inited = FALSE; 76 | if (!$inited) 77 | { 78 | require __ICARUS_ROOT__ . 'library/Util.php'; 79 | require __ICARUS_ROOT__ . 'library/I18n.php'; 80 | require __ICARUS_ROOT__ . 'library/Content.php'; 81 | 82 | Icarus_Util::init(NULL); 83 | Icarus_I18n::init(); 84 | 85 | $inited = TRUE; 86 | } 87 | } 88 | 89 | function themeFields($form) 90 | { 91 | themeFieldsInit(); 92 | Icarus_Content::fieldsConfig($form); 93 | } 94 | 95 | // Icarus Translation 96 | function _IcT($key) 97 | { 98 | return Icarus_I18n::get($key); 99 | } 100 | 101 | // Icarus Translation + Print 102 | function _IcTp($key) 103 | { 104 | echo htmlspecialchars(Icarus_I18n::get($key), ENT_QUOTES, 'UTF-8'); 105 | } 106 | -------------------------------------------------------------------------------- /page/archives.php: -------------------------------------------------------------------------------- 1 | need('component/header.php'); 5 | 6 | Typecho_Widget::widget('Widget_Contents_Post_Recent', 'pageSize=100000')->to($archives); 7 | $year = -1; 8 | $yearCount = 0; 9 | 10 | while ($archives->next()): 11 | $year_tmp = date('Y',$archives->created); 12 | if ($year_tmp !== $year): 13 | if ($year !== -1): 14 | ?> 15 |
    16 |
    17 |
    18 | 22 |
    23 |
    24 | 27 |
    28 | 29 |
    30 | 31 | 32 |

    33 | <?php $archives->title(); ?> 34 |

    35 |
    36 | 37 |
    38 |
    39 | 40 | title(); ?> 41 | 65 |
    66 |
    67 |
    68 | 72 |
    73 |
    74 |
    75 | need('component/footer.php'); 78 | -------------------------------------------------------------------------------- /assets/js/comment.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){function serializeFormData(form){return $(form).serialize().replace(/%20/g,'+');}function postComment(url,data,successCallback,errorCallback){$.ajax({type:'POST',url:url,data:data,beforeSend:function(xhr){xhr.setRequestHeader('X-Requested-With','XMLHttpRequest');},success:function(response){successCallback(response);},error:function(xhr,status,error){errorCallback(xhr,status,error);}});}function showToast(icon,title,text,timer){Toast.fire({icon:icon,title:title,text:text||'',timer:timer||3000,});}$('#comment-form').submit(function(e){e.preventDefault();var $form=$(this);var url=$form.attr('action');var formData=serializeFormData($form);postComment(url,formData,function(response){handleCommentInsertion(response);resetTurnstile();},function(xhr,status,error){showToast('error',commentErrorTitle,commentFailedToSubmitMessage);resetTurnstile();});});function getCommentParent(){var $input=$('#comment-parent[name="parent"]');if($input.length>0){var value=$input.val();var number=value.match(/\d+/)[0];return number;}return false;}function getNewestCommentHTML(response){var regex=/
    [\s\S]*?id="comment-(\d+)"[\s\S]*?<\/div>\s*<\/div>\s*<\/div>/g;var matches;var maxId=-1;var newestCommentHTML='';while((matches=regex.exec(response))!==null){var commentId=parseInt(matches[1],10);if(commentId>maxId){maxId=commentId;newestCommentHTML=matches[0];}}return newestCommentHTML;}function getCommentNum(response){var $parsedHTML=$(response);var commentText=null;$parsedHTML.find('.tk-comments-count span').each(function(){commentText=$(this).text();return false;});return commentText;}function getCommentChildren(responseHTML){var response=new DOMParser().parseFromString(responseHTML,'text/html');var comments=Array.prototype.filter.call(response.querySelectorAll('[id^="comment-"]'),function(comment){return /^comment-\d+$/.test(comment.id);});var maxId=Math.max.apply(Math,comments.map(function(comment){return parseInt(comment.id.split('-')[1],10);}));var maxCommentElement=response.querySelector('#comment-'+maxId);if(!maxCommentElement)return false;return maxCommentElement.closest('.media.comment');}function handleCommentInsertion(responseHTML){var commentParent=getCommentParent();var newCommentNum=getCommentNum(responseHTML);if(newCommentNum){$('.tk-comments-count > span').text(newCommentNum);}if(commentParent){var commentChildren=getCommentChildren(responseHTML);if(commentChildren){$('div#comment-'+commentParent+' > div.content').after('
    '+commentChildren.outerHTML+'
    ');TypechoComment.cancelReply();resetCommentForm();showToast('success',commentSuccessTitle,commentSuccessMessage);}else{showToast('error',commentErrorTitle,commentFailedToFetchMessage);}}else{var newCommentHTML=getNewestCommentHTML(responseHTML);if(newCommentHTML){$('.comment-list').prepend(newCommentHTML);resetCommentForm();showToast('success',commentSuccessTitle,commentSuccessMessage);}else{showToast('error',commentErrorTitle,commentFailedToFetchMessage);}}}function resetCommentForm(){$('#comment-form').find('textarea[name="text"]').val('');$('div.comment img.lazyload').lazyload();if(typeof moment==='function'){$('.comment time').each(function(){var datetime=$(this).attr('datetime');$(this).text(moment(datetime).fromNow());});}}function resetTurnstile(){if(typeof window.onloadTurnstileCallback==='function'){window.onloadTurnstileCallback();}}}); 2 | -------------------------------------------------------------------------------- /library/Module/Donate.php: -------------------------------------------------------------------------------- 1 | packTitle('Donate'); 9 | 10 | $form->packTextarea('Donate/buttons', "type=wechat,image=https://example.com/weixin.jpg\ntype=afdian,image=https://example.com/afdian.jpg,url=https://afdian.com\ntype=paypal,business=paypal@paypal.com,currency=USD"); 11 | } 12 | 13 | private static function getButtons() 14 | { 15 | return Icarus_Config::get('donate_buttons'); 16 | } 17 | 18 | public static function output() 19 | { 20 | $buttons=explode(PHP_EOL,self::getButtons()); 21 | if(empty(self::getButtons())) return; 22 | $font_awesome=array( 23 | 'qq' => 'fab fa-qq', 24 | 'wechat' => 'fab fa-weixin', 25 | 'alipay' => 'fab fa-alipay', 26 | 'afdian' => 'fas fa-charging-station', 27 | 'paypal' => 'fab fa-paypal', 28 | 'buymeacoffee' => 'fas fa-coffee', 29 | ); 30 | foreach ($buttons as &$line) 31 | { 32 | $param=explode(',',$line); 33 | $line=array(); 34 | foreach ($param as $obj) 35 | { 36 | $obj_list=explode('=',$obj); 37 | $line[$obj_list[0]]=$obj_list[1]; 38 | } 39 | } 40 | 41 | ?> 42 |
    43 |
    44 | 45 | 68 |
    69 |
    70 | packTitle($pluginName); 119 | 120 | $form->makeRadio( 121 | Icarus_Util::parseName($pluginName) . '_enable', 122 | _IcT('setting.plugin_common.enable.title'), 123 | NULL, 124 | array( 125 | '0' => _IcT('setting.plugin_common.enable.options.0'), 126 | '1' => _IcT('setting.plugin_common.enable.options.1'), 127 | ), 128 | $defaultEnable 129 | ); 130 | } 131 | } -------------------------------------------------------------------------------- /library/Module/Search.php: -------------------------------------------------------------------------------- 1 | packTitle('Search'); 9 | 10 | $form->packRadio('Search/enable', array('0', '1'), '1'); 11 | $form->packRadio('Search/type', array('internal', 'exsearch'), 'internal'); 12 | } 13 | 14 | public static function output() 15 | { 16 | switch (Icarus_Config::get('search_type', 'internal')) 17 | { 18 | case 'internal': 19 | default: 20 | self::outputInternal(); 21 | break; 22 | } 23 | } 24 | 25 | 26 | private static function outputInternal() 27 | { 28 | $post_num=0;$tag_num=0; 29 | ?> 30 | 31 | 109 | packTitle('Navbar'); 9 | 10 | $form->packTextarea('Navbar/menu', 11 | sprintf( 12 | _IcT('setting.navbar.default_value'), 13 | Icarus_Util::$options->index, 14 | Icarus_Util::urlFor('page', array('slug' => 'archives')), 15 | Icarus_Util::urlFor('page', array('slug' => 'categories')) 16 | ) 17 | ); 18 | $form->packTextarea('Navbar/icons', "Download on GitHub,fab fa-github,http://github.com/HsukqiLee/Typecho-Theme-Kylin"); 19 | 20 | $form->packRadio('Navbar/top', array('0', '1'), '0'); 21 | } 22 | 23 | private static function getMenu() 24 | { 25 | return Icarus_Util::parseMultilineData(Icarus_Config::get('navbar_menu'), 2); 26 | } 27 | 28 | private static function getIcons() 29 | { 30 | return Icarus_Util::parseMultilineData(Icarus_Config::get('navbar_icons'), 3); 31 | } 32 | 33 | private static function isCurLink($uri) 34 | { 35 | return Typecho_Request::getInstance()->getRequestUri() == $uri; 36 | } 37 | 38 | public static function output() 39 | { 40 | ?> 41 | 92 | 3 | 4 | output(); 6 | Icarus_Aside::$asideRight->output(); 7 | ?> 8 |
    9 | 10 | 11 | 64 | 70 | 71 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /library/Module/Toc.php: -------------------------------------------------------------------------------- 1 | 20 |
    21 |
    22 | 28 |
    29 |
    30 | 41 | 63 | array( 70 | 'parent' => null, 71 | 'children' => array(), 72 | ), 73 | ); 74 | 75 | $tocNumStack = array(); 76 | 77 | $content = preg_replace_callback( 78 | '/]*)>(.*?)<\/h\1>/i', 79 | function ($matches) use(&$tocNumStack) { 80 | $tocIndex = count(self::$_toc) - 1; 81 | $depthCounter = count($tocNumStack); 82 | 83 | $depth = intval($matches[1]) - 1; 84 | $title = trim(strip_tags($matches[3])); 85 | 86 | if ($depthCounter < $depth) 87 | { 88 | for (; $depthCounter < $depth - 1; $depthCounter++) 89 | { 90 | $newIndex = count(self::$_toc); 91 | self::$_toc[] = array( 92 | 'parent' => $tocIndex, 93 | 'children' => array(), 94 | ); 95 | self::$_toc[$tocIndex]['children'][] = $newIndex; 96 | $tocIndex = $newIndex; 97 | $tocNumStack[] = 1; 98 | } 99 | if (count($tocNumStack) < $depth) 100 | { 101 | $tocNumStack[] = 0; 102 | } 103 | } 104 | else 105 | { 106 | array_splice($tocNumStack, $depth); 107 | for (; $depthCounter >= $depth; $depthCounter--) 108 | { 109 | $tocIndex = self::$_toc[$tocIndex]['parent']; 110 | } 111 | } 112 | 113 | $newIndex = count(self::$_toc); 114 | $tocNumStack[$depth - 1]++; 115 | $id = preg_replace( 116 | '/\s+/', 117 | '-', 118 | implode('-', $tocNumStack) . '-' . $title 119 | ); 120 | self::$_toc[] = array( 121 | 'parent' => $tocIndex, 122 | 'children' => array(), 123 | 'id' => $id, 124 | 'title' => $title, 125 | 'index' => $tocNumStack[$depth - 1], 126 | ); 127 | self::$_toc[$tocIndex]['children'][] = $newIndex; 128 | $tocIndex = $newIndex; 129 | 130 | return "{$matches[3]}"; 131 | }, 132 | $content 133 | ); 134 | return $content; 135 | } 136 | } -------------------------------------------------------------------------------- /assets/css/comment.css: -------------------------------------------------------------------------------- 1 | .tk-avatar{overflow:hidden;width:2.5rem;border-radius:5px;text-align:center;flex-shrink:0}.tk-avatar,.tk-avatar .tk-avatar-img{height:2.5rem}.tk-avatar .tk-avatar-img svg{fill:#c0c4cc}.tk-meta-input{display:flex}.tk-meta-input .el-input{width:auto;width:calc((100% - 1rem)/ 3);flex:1}.tk-meta-input .el-input+.el-input{margin-left:.5rem}.tk-meta-input .el-input .el-input-group__prepend{padding:0 1rem}.tk-meta-input .el-input input:invalid{border:1px solid #f56c6c;box-shadow:none}@media screen and (max-width:767px){.tk-meta-input{flex-direction:column}.tk-meta-input .el-input{width:auto}.tk-meta-input .el-input+.el-input{margin-top:.5rem;margin-left:0}}.el-input__inner,.el-textarea__inner{-webkit-box-sizing:border-box;background-image:none;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1)}.el-textarea{position:relative;display:inline-block;width:100%;vertical-align:bottom;font-size:14px}.el-textarea__inner{display:block;box-sizing:border-box;padding:5px 15px;width:100%;border:1px solid #dcdfe6;border-radius:4px;background-color:#fff;color:#606266;font-size:inherit;line-height:1.5;resize:vertical;transition:border-color .2s cubic-bezier(.645,.045,.355,1)}.el-textarea__inner::-webkit-input-placeholder{color:#c0c4cc}.el-textarea__inner:-ms-input-placeholder{color:#c0c4cc}.el-textarea__inner::-ms-input-placeholder{color:#c0c4cc}.el-textarea__inner:hover{border-color:#c0c4cc}.el-textarea__inner:focus{outline:0;border-color:#409eff}.el-input{position:relative;display:inline-block;width:100%;font-size:14px}.el-input::-webkit-scrollbar{z-index:11;width:6px}.el-input::-webkit-scrollbar:horizontal{height:6px}.el-input::-webkit-scrollbar-thumb{width:6px;border-radius:5px;background:#b4bccc}.el-input::-webkit-scrollbar-corner,.el-input::-webkit-scrollbar-track{background:#fff}.el-input::-webkit-scrollbar-track-piece{width:6px;background:#fff}.el-input__inner{font-size:inherit;display:inline-block;box-sizing:border-box;padding:0 15px;width:100%;height:40px;outline:0;border:1px solid #dcdfe6;border-radius:4px;background-color:#fff;color:#606266;line-height:40px;transition:border-color .2s cubic-bezier(.645,.045,.355,1);-webkit-appearance:none}.el-input__inner::-ms-reveal{display:none}.el-input__inner::-webkit-input-placeholder{color:#c0c4cc}.el-input__inner:-ms-input-placeholder{color:#c0c4cc}.el-input__inner::-ms-input-placeholder{color:#c0c4cc}.el-input__inner:hover{border-color:#c0c4cc}.el-input__inner:focus{outline:0;border-color:#409eff}.el-input--small{font-size:13px}.el-input--small .el-input__inner{height:32px;line-height:32px}.el-input-group{display:inline-table;width:100%;border-collapse:separate;line-height:normal;border-spacing:0}.el-input-group>.el-input__inner,.el-input-group__prepend{display:table-cell;vertical-align:middle}.el-input-group__prepend{position:relative;padding:0 20px;width:1px;border:1px solid #dcdfe6;border-radius:4px;background-color:#f5f7fa;color:#909399;white-space:nowrap}.el-input-group--prepend .el-input__inner{border-bottom-left-radius:0;border-top-left-radius:0}.el-input-group__prepend{border-top-right-radius:0;border-bottom-right-radius:0}.el-input-group__prepend:focus{outline:0}.el-input-group__prepend{border-right:0}.el-input__inner::-ms-clear{display:none;width:0;height:0}.el-button{display:inline-block;-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:12px 20px;outline:0;border:1px solid #dcdfe6;border-radius:4px;background:#fff;color:#606266;text-align:center;white-space:nowrap;font-weight:500;font-size:14px;line-height:1;cursor:pointer;-webkit-transition:.1s;transition:.1s;-webkit-appearance:none;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none}.el-button+.el-button{margin-left:10px}.el-button:focus,.el-button:hover{border-color:#c6e2ff;background-color:#ecf5ff;color:#409eff}.el-button:active{outline:0;border-color:#3a8ee6;color:#3a8ee6}.el-button::-moz-focus-inner{border:0}.el-button--primary{border-color:#409eff;background-color:#409eff;color:#fff}.el-button--primary:focus,.el-button--primary:hover{border-color:#66b1ff;background:#66b1ff;color:#fff}.el-button--primary:active{outline:0;border-color:#3a8ee6;background:#3a8ee6;color:#fff}.el-button--small{border-radius:3px;font-size:12px;padding:9px 15px}.tk-submit{display:flex;flex-direction:column}.tk-row{display:flex;flex-direction:row}.tk-col{display:flex;flex:1;flex-direction:column}.tk-meta-input{margin-bottom:.5rem}.tk-row.actions{position:relative;margin-top:1rem;margin-bottom:1rem;margin-left:3.5rem;align-items:center;justify-content:flex-end}.tk-row-actions-start{display:flex;flex:1;align-items:center}.tk-action-icon{display:inline-block;margin-right:10px;width:1.25em;line-height:0;cursor:pointer;align-self:center;flex-shrink:0}.tk-action-icon svg:hover{opacity:.8}.tk-action-icon.__markdown svg{fill:#909399}.tk-avatar{margin-right:1rem}.tk-input{flex:1}.tk-input .el-textarea__inner{background-position:100% 100%;background-repeat:no-repeat}.tk-comments-title{display:flex;margin-bottom:1rem;font-weight:700;font-size:1.25rem;align-items:baseline;justify-content:space-between}.tk-icon{display:inline-flex;margin-left:.5em;width:.75em;height:.75em;vertical-align:sub;line-height:0;cursor:pointer;align-items:center;justify-content:center}.tk-icon svg{width:100%;height:100%;fill:#409eff} -------------------------------------------------------------------------------- /library/Page.php: -------------------------------------------------------------------------------- 1 | is('single')) { 15 | if (Icarus_Util::isEmpty($widget->fields->custom_excerpt) === false) { 16 | $excerpt = strip_tags($widget->markdown($widget->fields->custom_excerpt)); 17 | $widget->setDescription($excerpt); 18 | } 19 | } 20 | } 21 | 22 | public static function printPageTitle() 23 | { 24 | Icarus_Util::$widget->archiveTitle(array( 25 | 'category' => _IcT('title.category'), 26 | 'search' => _IcT('title.search'), 27 | 'tag' => _IcT('title.tag'), 28 | 'author' => _IcT('title.author'), 29 | 'date' => _IcT('title.date') 30 | ), '', ' - '); 31 | Icarus_Util::$options->title(); 32 | } 33 | 34 | public static function printHtmlLang() 35 | { 36 | $lang = Icarus_Util::$options->lang; 37 | if (Icarus_Util::isEmpty($lang) === true) 38 | $lang = 'zh-CN'; 39 | else 40 | $lang = str_replace('_', '-', $lang); 41 | 42 | echo 'lang="', $lang, '"'; 43 | } 44 | 45 | public static function printHeader() 46 | { 47 | // favicon 48 | Icarus_Config::callback('head_favicon', function ($faviconUrl) { 49 | echo '', PHP_EOL; 50 | }); 51 | 52 | // search 53 | Icarus_Module::load('Search'); 54 | //Icarus_Module_Search::header(); 55 | 56 | // custom head content 57 | echo Icarus_Config::get('head_extend'); 58 | 59 | // style settings 60 | $background = Icarus_Config::get('head_background', ''); 61 | $transparency = Icarus_Config::get('head_transparency', 100); 62 | ?> 63 | 75 | is($archiveType, $archiveSlug); 103 | } 104 | 105 | public static function config($form) 106 | { 107 | $form->packTitle('Head'); 108 | 109 | $form->packInput('Head/favicon', 'img/favicon.svg'); 110 | $form->packTextarea('Head/extend', ''); 111 | 112 | $form->packInput('Head/background'); 113 | 114 | $form->packInput('Head/transparency', 100, 'w-20'); 115 | 116 | $form->packRadio('Head/mourning', array('0', '1'), '0'); 117 | 118 | $form->packTitle('Logo'); 119 | 120 | $form->packInput('Logo/text', ''); 121 | $form->packInput('Logo/img', 'img/logo.svg'); 122 | $form->packInput('Logo/img_dark', 'img/logo.svg'); 123 | 124 | $form->packTitle('Footer'); 125 | 126 | $form->packTextarea('Footer/links', 'Attribution-NonCommercial 4.0 International (CC BY-NC 4.0),fab fa-creative-commons|fab fa-creative-commons-by|fab fa-creative-commons-nc,https://creativecommons.org/licenses/by-nc/4.0/'); 127 | $form->packTextarea('Footer/content_left', ''); 128 | $form->packInput('Footer/icp', ''); 129 | $form->packInput('Footer/beian', ''); 130 | $form->packInput('Footer/beian_code', 'http://www.beian.gov.cn/portal/registerSystemInfo?recordcode='); 131 | $form->packTextarea('Footer/scripts', ''); 132 | } 133 | 134 | public static function getFooterLinks() 135 | { 136 | $result = Icarus_Util::parseMultilineData(Icarus_Config::get('footer_links'), 3); 137 | if (Icarus_Util::isEmpty($result) === false) { 138 | foreach ($result as $k => $link) { 139 | $result[$k][1] = Icarus_Util::isEmpty($link[1]) === true ? null : explode('|', $link[1]); 140 | } 141 | } 142 | return $result; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /library/Module/Profile.php: -------------------------------------------------------------------------------- 1 | packInput('Profile/author', 'Your name', 'w-40'); 8 | $form->packInput('Profile/author_title', 'Your title', 'w-40'); 9 | $form->packInput('Profile/location', 'Your location', 'w-40'); 10 | $form->packInput('Profile/avatar', 'img/avatar.png'); 11 | $form->packInput('Profile/gravatar', ''); 12 | $form->packInput('Profile/follow_link', 'https://github.com/'); 13 | $form->packTextarea('Profile/social_links', "GitHub,fab fa-github,https://github.com/\nTwitter,fab fa-twitter,https://twitter.com/\nFacebook,fab fa-facebook,https://facebook.com/"); 14 | } 15 | 16 | private static function printAvatarUrl() 17 | { 18 | $avatar = Icarus_Config::get('profile_avatar'); 19 | if (Icarus_Config::tryGet('profile_gravatar', $gravatarEmail)) 20 | { 21 | echo Icarus_Util::getAvatar($gravatarEmail, 128); 22 | } 23 | else 24 | { 25 | echo Icarus_Assets::getUrlForAssets( 26 | Icarus_Config::get('profile_avatar', 'img/avatar.png')); 27 | } 28 | } 29 | 30 | private static function getSocialLinks() 31 | { 32 | return Icarus_Util::parseMultilineData(Icarus_Config::get('profile_social_links'), 3); 33 | } 34 | 35 | public static function output() 36 | { 37 | ?> 38 |
    39 |
    40 | 65 | 97 | 98 |
    99 | > 100 | 101 |
    102 | 103 | 104 | 105 | 117 | 118 |
    119 |
    120 | need('component/header.php'); 6 | ?> 7 |
    8 |
    9 | 98 |
    99 |
    100 | have()) { 104 | while ($this->next()) 105 | { 106 | $post->doOutput(); 107 | } 108 | } else { 109 | switch ($this->getArchiveType()) 110 | { 111 | case 'category': 112 | case 'date': 113 | case 'search': 114 | case 'tag': 115 | $title = _IcT('empty.' . $this->getArchiveType() . '.title'); 116 | $desc = _IcT('empty.' . $this->getArchiveType() . '.desc'); 117 | $jump = _IcT('empty.' . $this->getArchiveType() . '.jump'); 118 | break; 119 | default: 120 | $title = _IcT('404.title'); 121 | $desc = _IcT('404.desc'); 122 | $jump = NULL; 123 | break; 124 | } 125 | switch ($this->getArchiveType()) 126 | { 127 | case 'category': 128 | $jumpTarget = Icarus_Util::urlFor('page', array('slug' => 'categories')); 129 | break; 130 | case 'date': 131 | $jumpTarget = Icarus_Util::urlFor('page', array('slug' => 'archives')); 132 | break; 133 | case 'search': 134 | $jumpTarget = '#'; 135 | ?> 136 | 145 | 'tags')); 149 | break; 150 | } 151 | ?> 152 |
    153 |
    154 |

    155 |

    156 |
    157 | 167 |
    168 | need('component/footer.php'); 174 | 175 | -------------------------------------------------------------------------------- /library/Util.php: -------------------------------------------------------------------------------- 1 | to(self::$options); 13 | self::$widget = $typechoWidget; 14 | } 15 | 16 | public static function stat() 17 | { 18 | return Typecho_Widget::widget('Widget_Stat'); 19 | } 20 | 21 | public static function strStartsWith($str, $startsWith) 22 | { 23 | return substr($str, 0, strlen($startsWith)) === $startsWith; 24 | } 25 | 26 | public static function isUrl($path) 27 | { 28 | return self::strStartsWith($path, "https://") || self::strStartsWith($path, "http://") || self::strStartsWith($path, "//"); 29 | } 30 | 31 | public static function parseMultilineData($str, $columnCount) 32 | { 33 | $result = array(); 34 | if (Icarus_Util::isEmpty($str) === false) { 35 | $data = explode("\n", $str); 36 | foreach ($data as $item) { 37 | $item = trim($item); 38 | if (Icarus_Util::isEmpty($item) === false) { 39 | $itemData = explode(',', $item, $columnCount); 40 | if (count($itemData) === $columnCount) { 41 | foreach ($itemData as $k => $v) { 42 | $itemData[$k] = trim($v); 43 | } 44 | $result[] = $itemData; 45 | } 46 | } 47 | } 48 | } 49 | return $result; 50 | } 51 | 52 | public static function getAvatar($email, $size) 53 | { 54 | return self::getGravatar($email, Icarus_Assets::getGravatarUrl(), $size, Icarus_Config::get('comments_default_avatar')); 55 | } 56 | 57 | public static function getGravatar($email, $host, $size, $default = null) 58 | { 59 | $rating = Icarus_Util::$options->commentsAvatarRating; 60 | $hash = md5(strtolower($email)); 61 | $avatar = $host . '/' . $hash . '?s=' . $size . '&r=' . $rating; 62 | if (Icarus_Util::isEmpty($default) === false) { 63 | $avatar .= '&d=' . urlencode($default); 64 | } 65 | return $avatar; 66 | } 67 | 68 | public static function getSiteInstallTime() 69 | { 70 | $time = FALSE; 71 | $typechoCfgFile = __TYPECHO_ROOT_DIR__ . '/config.inc.php'; 72 | if (file_exists($typechoCfgFile) === true) 73 | { 74 | $time = @filemtime($typechoCfgFile); 75 | } 76 | return $time === FALSE ? time() : $time; 77 | } 78 | 79 | public static function getSiteRunDays() 80 | { 81 | if (Icarus_Config::tryGet('general_install_time', $installTime) === true) 82 | { 83 | $date = DateTime::createFromFormat('Y-m-d', $installTime); 84 | } 85 | else 86 | { 87 | $date = new DateTime(); 88 | } 89 | $curDate = new DateTime(); 90 | $interval = $date->diff($curDate); 91 | return $interval->format('%a'); 92 | } 93 | 94 | public static function getSiteInstallYear() 95 | { 96 | if (Icarus_Config::tryGet('general_install_time', $installTime) === true) 97 | { 98 | $date = DateTime::createFromFormat('Y-m-d', $installTime); 99 | } 100 | else 101 | { 102 | $date = new DateTime(); 103 | } 104 | return $date->format('Y'); 105 | } 106 | 107 | /** 108 | * 字符串命名风格转换 109 | * @param string $name 字符串 110 | * @return string 111 | * 112 | * @link https://github.com/top-think/thinkphp/blob/109bf30254a38651c21837633d9293a4065c300b/ThinkPHP/Common/functions.php#L497 113 | */ 114 | public static function parseName($name) 115 | { 116 | return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_")); 117 | } 118 | 119 | /** 120 | * 自闭合html修复函数 121 | * 使用方法: 122 | * 123 | * $input = '这是一段被截断的html文本 127 | * 128 | * Typecho 17.10.30 版 fixHtml 函数不能正确处理
    等自闭合标签 129 | * Commit c056f6c895f440b1d72fc1415878fb7541fc249f 修复了此问题 130 | * 131 | * @access public 132 | * @param string $string 需要修复处理的字符串 133 | * @return string 134 | * 135 | * @link https://github.com/typecho/typecho/blob/c056f6c895f440b1d72fc1415878fb7541fc249f/var/Typecho/Common.php#L519 136 | */ 137 | public static function fixHtml($string) 138 | { 139 | //关闭自闭合标签 140 | $startPos = strrpos($string, "<"); 141 | if (false == $startPos) { 142 | return $string; 143 | } 144 | $trimString = substr($string, $startPos); 145 | if (false === strpos($trimString, ">")) { 146 | $string = substr($string, 0, $startPos); 147 | } 148 | //非自闭合html标签列表 149 | preg_match_all("/<([_0-9a-zA-Z-\:]+)\s*([^>]*)>/is", $string, $startTags); 150 | preg_match_all("/<\/([_0-9a-zA-Z-\:]+)>/is", $string, $closeTags); 151 | if (Icarus_Util::isEmpty($startTags[1]) === false && is_array($startTags[1]) === true) { 152 | krsort($startTags[1]); 153 | $closeTagsIsArray = is_array($closeTags[1]); 154 | foreach ($startTags[1] as $key => $tag) { 155 | $attrLength = strlen($startTags[2][$key]); 156 | if ($attrLength > 0 && "/" == trim($startTags[2][$key][$attrLength - 1])) { 157 | continue; 158 | } 159 | // 白名单 160 | if (preg_match("/^(area|base|br|col|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/i", $tag) === 1) { 161 | continue; 162 | } 163 | if (Icarus_Util::isEmpty($closeTags[1]) === false && $closeTagsIsArray === true) { 164 | if (false !== ($index = array_search($tag, $closeTags[1]))) { 165 | unset($closeTags[1][$index]); 166 | continue; 167 | } 168 | } 169 | $string .= ""; 170 | } 171 | } 172 | return preg_replace("/\\s*\<\/p\>/is", '

    ', $string); 173 | } 174 | 175 | public static function isEmpty($value) 176 | { 177 | $exist = !is_null($value); 178 | if ($exist === true) 179 | { 180 | if (is_array($value) === true) 181 | $exist = count($value) > 0; 182 | else if (is_string($value) === true) 183 | $exist = strlen(trim($value)) > 0; 184 | } 185 | return !$exist; 186 | } 187 | 188 | public static function urlFor($type, $param) 189 | { 190 | return Typecho_Common::url(Typecho_Router::url($type, $param), self::$options->index); 191 | } 192 | 193 | public static function jsonResponse($data) 194 | { 195 | header('Content-Type: application/json'); 196 | echo json_encode($data); 197 | } 198 | } -------------------------------------------------------------------------------- /library/Aside.php: -------------------------------------------------------------------------------- 1 | packTitle('Aside'); 37 | 38 | $form->packRadio('Aside/left_sticky', array('0', '1'), '0'); 39 | $form->packRadio('Aside/right_sticky', array('0', '1'), '0'); 40 | 41 | $form->packRadio('Aside/left_post_hide', array('0', '1'), '0'); 42 | $form->packRadio('Aside/right_post_hide', array('0', '1'), '0'); 43 | 44 | $form->packCheckbox('Aside/non_post_hide_widget', self::$_asideWidgets); 45 | $form->packCheckbox('Aside/post_hide_widget', self::$_asideWidgets); 46 | } 47 | 48 | public function __construct($position) 49 | { 50 | $this->_position = $position; 51 | if (Icarus_Util::$widget->is('post')) 52 | { 53 | switch ($position) 54 | { 55 | case self::LEFT: 56 | if (Icarus_Config::get('aside_left_post_hide', false)) { 57 | return; 58 | } 59 | break; 60 | case self::RIGHT: 61 | if (Icarus_Config::get('aside_right_post_hide', false)) { 62 | return; 63 | } 64 | break; 65 | } 66 | } 67 | $seqMap = array(); 68 | $hiddenWidgets = Icarus_Util::$widget->is('post') ? Icarus_Config::get('aside_post_hide_widget') : Icarus_Config::get('aside_non_post_hide_widget'); 69 | 70 | if (!is_array($hiddenWidgets)) 71 | $hiddenWidgets = array(); 72 | 73 | // categories / tags page patch 74 | if (Icarus_Util::$widget->is('page', 'categories') && !in_array('Category', $hiddenWidgets)) 75 | { 76 | $hiddenWidgets[] = 'Category'; 77 | } 78 | if (Icarus_Util::$widget->is('page', 'tags') && !in_array('Tag', $hiddenWidgets)) 79 | { 80 | $hiddenWidgets[] = 'Tag'; 81 | } 82 | 83 | foreach (self::$_asideWidgets as $widgetName) 84 | { 85 | if (Icarus_Module::enabled($widgetName) && self::getWidgetPosition($widgetName) == $position && !in_array($widgetName, $hiddenWidgets)) 86 | { 87 | $this->_widgets[] = $widgetName; 88 | $seqMap[$widgetName] = self::getWidgetSeq($widgetName); 89 | } 90 | } 91 | 92 | usort($this->_widgets, function($a, $b) use($seqMap){ 93 | $seqA = $seqMap[$a]; 94 | $seqB = $seqMap[$b]; 95 | if ($seqA == $seqB) 96 | { 97 | return strcmp($a, $b); 98 | } 99 | return $seqA < $seqB ? -1 : 1; 100 | }); 101 | } 102 | 103 | public static function getColumnCount() 104 | { 105 | return 1 + (self::$asideLeft->count() > 0) 106 | + (self::$asideRight->count() > 0); 107 | } 108 | 109 | public function clear() 110 | { 111 | $this->_widgets = array(); 112 | } 113 | 114 | public static function getWidgetPosition($name) 115 | { 116 | return Icarus_Config::get(Icarus_Util::parseName($name) . '_position', 'left') == 'right' ? self::RIGHT : self::LEFT; 117 | } 118 | 119 | public static function getWidgetSeq($name) 120 | { 121 | return intval(Icarus_Config::get(Icarus_Util::parseName($name) . '_seq', 0)); 122 | } 123 | 124 | public static function printSideColumnClass() 125 | { 126 | switch (self::getColumnCount()) { 127 | case 2: 128 | echo ' is-4-tablet is-4-desktop is-4-widescreen'; 129 | break; 130 | case 3: 131 | echo ' is-4-tablet is-4-desktop is-3-widescreen'; 132 | break; 133 | } 134 | } 135 | public function printVisibilityClass() 136 | { 137 | //if (self::getColumnCount() === 3 && $this->_position == self::RIGHT) { 138 | // echo ' is-hidden-touch is-hidden-desktop-only'; 139 | //} 140 | } 141 | 142 | public function printOrderClass() 143 | { 144 | echo ($this->_position == self::RIGHT) ? ' has-order-3' : ' has-order-1'; 145 | } 146 | 147 | public function printStickyClass() 148 | { 149 | self::printStickyClassByPos($this->_position); 150 | } 151 | 152 | public function printPosition() 153 | { 154 | echo ($this->_position == self::RIGHT) ? ' column-right' : ' column-left'; 155 | } 156 | 157 | public static function printStickyClassByPos($position) 158 | { 159 | if (Icarus_Config::get(($position == self::RIGHT) ? 'aside_right_sticky' : 'aside_left_sticky', false)) 160 | { 161 | echo ' is-sticky'; 162 | } 163 | } 164 | 165 | public function output() 166 | { 167 | if ($this->count() == 0) 168 | return; 169 | ?> 170 | 177 | _widgets); 183 | } 184 | 185 | public static function basicConfig($form, $widgetName, $defaultEnable, $defaultPosition, $defaultIndex) 186 | { 187 | $widgetCfgName = Icarus_Util::parseName($widgetName); 188 | 189 | $form->packTitle($widgetName); 190 | 191 | $form->makeRadio( 192 | $widgetCfgName . '_enable', 193 | _IcT('setting.aside_common.enable.title'), 194 | _IcT('setting.aside_common.enable.desc'), 195 | array( 196 | '0' => _IcT('setting.aside_common.enable.options.0'), 197 | '1' => _IcT('setting.aside_common.enable.options.1'), 198 | ), 199 | $defaultEnable 200 | ); 201 | 202 | $form->makeRadio( 203 | $widgetCfgName . '_position', 204 | _IcT('setting.aside_common.position.title'), 205 | _IcT('setting.aside_common.position.desc'), 206 | array( 207 | 'left' => _IcT('setting.aside_common.position.options.left'), 208 | 'right' => _IcT('setting.aside_common.position.options.right'), 209 | ), 210 | $defaultPosition 211 | ); 212 | 213 | $form->makeIntInput( 214 | $widgetCfgName . '_seq', 215 | _IcT('setting.aside_common.seq.title'), 216 | _IcT('setting.aside_common.seq.desc'), 217 | $defaultIndex, 218 | 'w-20' 219 | ); 220 | } 221 | } -------------------------------------------------------------------------------- /assets/img/logo.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /assets/js/main.js: -------------------------------------------------------------------------------- 1 | (function($){ 2 | $('.article img:not(".not-gallery-item")').each(function () { 3 | if ($(this).parent('a').length === 0) { 4 | $(this).wrap('
    '); 5 | if (this.alt) { 6 | $(this).after('
    ' + this.alt + '
    '); 7 | } 8 | } 9 | }); 10 | if (typeof $.fn.lightGallery === 'function') { 11 | $('.article').lightGallery({ selector: '.gallery-item' }); 12 | } 13 | $('.article > .content > table').each(function() { 14 | $(this).wrap('
    '); 15 | }); 16 | function adjustNavbar() { 17 | const navbarWidth = $('.navbar-main .navbar-start').outerWidth() + $('.navbar-main .navbar-end').outerWidth(); 18 | if ($(document).outerWidth() < navbarWidth) { 19 | $('.navbar-main .navbar-menu').addClass('justify-content-start'); 20 | } else { 21 | $('.navbar-main .navbar-menu').removeClass('justify-content-start'); 22 | } 23 | } 24 | adjustNavbar(); 25 | $(window).resize(adjustNavbar); 26 | if ($('aside.column').length === 2) { 27 | var movedToLeft = false; 28 | $('.column-right>.card').addClass('card-on-right'); 29 | function adjustColumn() { 30 | if (($(window).outerWidth() < 1280)&&(!movedToLeft)) { 31 | movedToLeft = true; 32 | $('.column-right>.card').appendTo($('.column-left')); 33 | } else if (movedToLeft) { 34 | movedToLeft = false; 35 | $('.column-left>.card.card-on-right').appendTo($('.column-right')); 36 | } 37 | } 38 | adjustColumn(); 39 | $(window).resize(adjustColumn); 40 | } 41 | $('td.code>pre,pre>code').each(function(i, block) {try{hljs.highlightElement(block);}catch(e){}}); 42 | $('figure.highlight .code .line span').each(function() { 43 | const classes = $(this).attr('class').split(/\s+/); 44 | if (classes.length === 1) { 45 | $(this).addClass('hljs-' + classes[0]); 46 | $(this).removeClass(classes[0]); 47 | } 48 | }); 49 | setTimeout(function() { 50 | if (typeof moment === 'function') { 51 | moment.locale($('html').attr('lang')); 52 | $('.article-meta time, .comment-meta time').each(function () { 53 | $(this).attr('title', $(this).text()); 54 | $(this).text(moment($(this).attr('datetime')).fromNow()); 55 | }); 56 | } 57 | if (typeof(ClipboardJS) !== 'undefined') { 58 | $('figure.hljs').each(function () { 59 | var id = 'code-' + Date.now() + (Math.random() * 1000 | 0); 60 | $(this).attr('id', id); 61 | $(this).find('figcaption .level-right').append($(``)); 62 | }); 63 | new ClipboardJS('figcaption>div.level-right>a.copy'); 64 | } 65 | if (typeof(MathJax) !== 'undefined') { 66 | MathJax.Hub.Config({ 67 | 'HTML-CSS': {matchFontHeight: false}, 68 | SVG: {matchFontHeight: false}, 69 | CommonHTML: {matchFontHeight: false} 70 | }); 71 | MathJax.Hub.Queue(["Typeset",MathJax.Hub]); 72 | } 73 | },100); 74 | const $toc = $('#toc'); 75 | if ($toc.length > 0) { 76 | const $mask = $('
    '); 77 | $mask.attr('id', 'toc-mask'); 78 | $('body').append($mask); 79 | function toggleToc() { 80 | $toc.toggleClass('is-active'); 81 | $mask.toggleClass('is-active'); 82 | } 83 | $toc.on('click', toggleToc); 84 | $mask.on('click', toggleToc); 85 | $('.navbar-main .catalogue').on('click', toggleToc); 86 | }else { 87 | $('.navbar-main .catalogue').hide(); 88 | } 89 | const MD5 = (function () { 90 | function M(d){for(var _,m="0123456789ABCDEF",f="",r=0;r>>4&15)+m.charAt(15&_);return f}function X(d){for(var _=Array(d.length>>2),m=0;m<_.length;m++)_[m]=0;for(m=0;m<8*d.length;m+=8)_[m>>5]|=(255&d.charCodeAt(m/8))<>5]>>>m%32&255);return _}function Y(d,_){d[_>>5]|=128<<_%32,d[14+(_+64>>>9<<4)]=_;for(var m=1732584193,f=-271733879,r=-1732584194,i=271733878,n=0;n>16)+(_>>16)+(m>>16)<<16|65535&m}function bit_rol(d,_){return d<<_|d>>>32-_} 91 | return function (d){result = M(V(Y(X(d),8*d.length)));return result.toLowerCase()}; 92 | })(); 93 | const $svg = $('.tk-avatar-img>svg'); 94 | const $img = $('.tk-avatar-img>img'); 95 | if ($img.attr('data-original') == undefined) $img.hide(); else $svg.hide(); 96 | $('#email').blur(function() { 97 | var email = $(this).val(); 98 | if (email == "") { 99 | $img.hide();$svg.show();return; 100 | } 101 | var avatarUrl=$(this).attr('avatar-url-tpl'); 102 | avatarUrl=avatarUrl.replace('{md5}',MD5(email)).replace('{size}','128'); 103 | $img.attr('src', avatarUrl); 104 | setTimeout(function() { $img.show();$svg.hide(); }, 200); 105 | }); 106 | const $scrollHandle = $("html, body"); 107 | $('#toc .menu-list a').each(function (){ 108 | $(this).click(function (event){ 109 | const href = $(this).attr('href'); 110 | $scrollHandle.animate( 111 | { scrollTop: $(href)?.offset()?.top }, 112 | 500, 113 | function () { 114 | window.location.hash = href; 115 | } 116 | ); 117 | event.preventDefault(); 118 | }); 119 | }); 120 | $("img.lazyload").lazyload({effect: "fadeIn", threshold: 100}); 121 | $("img.avatar").lazyload({effect: "fadeIn", threshold: 100}); 122 | $('.navbar-main .search').click(function () { 123 | $('.searchbox').toggleClass('show'); 124 | }); 125 | $('.searchbox .searchbox-mask').click(function () { 126 | $('.searchbox').removeClass('show'); 127 | }); 128 | $('.searchbox-close').click(function () { 129 | $('.searchbox').removeClass('show'); 130 | }); 131 | }(jQuery)); 132 | -------------------------------------------------------------------------------- /library/Plugin/SweetAlert.php: -------------------------------------------------------------------------------- 1 | 15 | 41 | 48 | 64 | 68 | 223 | 226 | 256 | array( 9 | 'jsdelivr' => array( 10 | '_tpl' => 'https://cdn.jsdelivr.net/npm/{package}@{version}/{file}', 11 | '_alias' => array( 12 | 'pace' => 'pace-js', 13 | 'clipboard.js' => 'clipboard', 14 | 'moment.js' => 'moment', 15 | 'outdated-browser' => 'outdatedbrowser' 16 | ), 17 | 'highlight.js' => 'https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@{version}/build/{file}', 18 | 'lightgallery' => 'https://cdn.jsdelivr.net/npm/{package}@{version}/dist/{file}', 19 | 'justifiedGallery' => 'https://cdn.jsdelivr.net/npm/{package}@{version}/dist/{file}', 20 | 'clipboard' => 'https://cdn.jsdelivr.net/npm/{package}@{version}/dist/{file}', 21 | 'jquery' => 'https://cdn.jsdelivr.net/npm/{package}@{version}/dist/{file}', 22 | 'mathjax' => 'https://cdn.jsdelivr.net/npm/{package}@{version}/unpacked/{file}', 23 | 'outdatedbrowser' => 'https://cdn.jsdelivr.net/npm/{package}@{version}/outdatedbrowser/{file}', 24 | 'moment' => 'https://cdn.jsdelivr.net/npm/{package}@{version}/min/{file}', 25 | ), 26 | 'unpkg' => array( 27 | '_tpl' => 'https://unpkg.com/{package}@{version}/{file}', 28 | '_alias' => array( 29 | 'pace' => 'pace-js', 30 | 'clipboard.js' => 'clipboard', 31 | 'moment.js' => 'moment', 32 | 'outdated-browser' => 'outdatedbrowser' 33 | ), 34 | 'highlight.js' => 'https://unpkg.com/highlightjs@{version}/{file}', 35 | 'lightgallery' => 'https://unpkg.com/{package}@{version}/{file}', 36 | 'justifiedGallery' => 'https://unpkg.com/{package}@{version}/{file}', 37 | 'clipboard' => 'https://unpkg.com/{package}@{version}/{file}', 38 | 'jquery' => 'https://unpkg.com/{package}@{version}/{file}', 39 | 'mathjax' => 'https://unpkg.com/{package}@{version}/unpacked/{file}', 40 | 'outdatedbrowser' => 'https://unpkg.com/{package}@{version}/outdatedbrowser/{file}', 41 | 'moment' => 'https://unpkg.com/{package}@{version}/min/{file}', 42 | ), 43 | 'cdnjs' => array( 44 | '_tpl' => 'https://cdnjs.cloudflare.com/ajax/libs/{package}/{version}/{file}', 45 | ), 46 | 'staticfile' => array( 47 | '_tpl' => 'https://cdn.staticfile.org/{package}/{version}/{file}', 48 | ), 49 | 'bootcdn' => array( 50 | '_tpl' => 'https://cdn.bootcdn.net/ajax/libs/{package}/{version}/{file}', 51 | ), 52 | 'loli' => array( 53 | '_tpl' => 'https://cdnjs.cloudflare.com/ajax/libs/{package}/{version}/{file}', 54 | ), 55 | 'sevencdn' => array( 56 | '_tpl' => 'https://use.sevencdn.com/ajax/libs/{package}/{version}/{file}', 57 | ), 58 | ), 59 | 'icon' => array( 60 | 'fontawesome' => 'https://use.fontawesome.com/releases/v5.4.1/css/all.css', 61 | 'jsdelivr' => 'https://blog-1305208807.file.myqcloud.com/assets/css/all.min.css', 62 | 'cdnjs' => 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.4.1/css/all.min.css', 63 | 'bootcdn' => 'https://cdn.bootcdn.net/ajax/libs/font-awesome/5.4.1/css/all.min.css', 64 | 'loli' => 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.4.1/css/all.min.css', 65 | 'staticfile' => 'https://cdn.staticfile.org/font-awesome/5.4.1/css/all.min.css', 66 | 'sevencdn' => 'https://use.sevencdn.com/ajax/libs/font-awesome/5.4.1/css/all.min.css', 67 | ), 68 | 'font' => array( 69 | 'google' => 'https://fonts.googleapis.com/{type}?family={fontname}&display=swap', 70 | 'loli' => 'https://fonts.googleapis.com/{type}?family={fontname}&display=swap', 71 | 'sevencdn' => 'https://use.sevencdn.com/{type}?family={fontname}&display=swap', 72 | ), 73 | 'gravatar' => array( 74 | 'gravatar' => 'https://secure.gravatar.com/avatar', 75 | 'v2ex' => 'https://cdn.v2ex.com/gravatar', 76 | 'loli' => 'https://gravatar.loli.net/avatar', 77 | 'cravatar' => 'https://cn.cravatar.com/avatar', 78 | 'sevencdn' => 'https://use.sevencdn.com/avatar', 79 | ), 80 | ); 81 | private static $_assetsCdnUrl = array(); 82 | 83 | const DEFAULT_ASSETS_CDN = 'jsdelivr'; 84 | const DEFAULT_ICON_CDN = 'fontawesome'; 85 | const DEFAULT_FONT_CDN = 'google'; 86 | const DEFAULT_GRAVATAR_CDN = 'cravatar'; 87 | 88 | public static function config($form) 89 | { 90 | $form->packTitle('Assets'); 91 | $form->packInput('Assets/theme_assets_base', ''); 92 | $form->packRadio('Assets/public_assets', array_keys(self::$_cdnProviders['assets']), self::DEFAULT_ASSETS_CDN); 93 | $form->packRadio('Assets/public_icon', array_keys(self::$_cdnProviders['icon']), self::DEFAULT_ICON_CDN); 94 | $form->packRadio('Assets/public_font', array_keys(self::$_cdnProviders['font']), self::DEFAULT_FONT_CDN); 95 | $form->packRadio('Assets/public_gravatar', array_keys(self::$_cdnProviders['gravatar']), self::DEFAULT_GRAVATAR_CDN); 96 | } 97 | 98 | public static function init() 99 | { 100 | self::$_assetsBaseUrl = Icarus_Config::get( 101 | 'assets_theme_assets_base', 102 | Typecho_Common::url('assets', Icarus_Util::$options->themeUrl) 103 | ); 104 | 105 | self::loadAssetsCDNConfig( 106 | 'assets', 107 | Icarus_Config::get('assets_public_assets'), 108 | self::DEFAULT_ASSETS_CDN 109 | ); 110 | self::loadAssetsCDNConfig( 111 | 'icon', 112 | Icarus_Config::get('assets_public_icon'), 113 | self::DEFAULT_ICON_CDN 114 | ); 115 | self::loadAssetsCDNConfig( 116 | 'font', 117 | Icarus_Config::get('assets_public_font'), 118 | self::DEFAULT_FONT_CDN 119 | ); 120 | self::loadAssetsCDNConfig( 121 | 'gravatar', 122 | Icarus_Config::get('assets_public_gravatar'), 123 | self::DEFAULT_GRAVATAR_CDN 124 | ); 125 | } 126 | 127 | private static function loadAssetsCDNConfig($type, $cdnName, $defaultCDNName) 128 | { 129 | if (array_key_exists($type, self::$_cdnProviders) === false) 130 | return; 131 | 132 | $cdn = self::$_cdnProviders[$type]; 133 | 134 | if (is_null($cdnName) === true || array_key_exists($cdnName, $cdn) === false) 135 | $cdnName = $defaultCDNName; 136 | 137 | self::$_assetsCdnUrl = array_merge( 138 | self::$_assetsCdnUrl, 139 | is_array($cdn[$cdnName]) ? $cdn[$cdnName] : array($type => $cdn[$cdnName])); 140 | } 141 | 142 | public static function getUrlForAssets($path) 143 | { 144 | if (Icarus_Util::isUrl($path) === true) 145 | return $path; 146 | return Typecho_Common::url($path, self::$_assetsBaseUrl); 147 | } 148 | 149 | public static function printCssTag($cssUrl, $preload = FALSE) 150 | { 151 | if ($preload === false) echo '', PHP_EOL; 152 | else echo '', PHP_EOL; 153 | } 154 | 155 | public static function printJsTag($jsUrl, $defer = FALSE, $async = FALSE, $instant = TRUE) 156 | { 157 | echo '', PHP_EOL; 169 | } 170 | 171 | public static function printThemeCss($name, $preload = FALSE) 172 | { 173 | self::printCssTag(self::getUrlForAssets("css/" . $name), $preload); 174 | } 175 | 176 | public static function printThemeJs($name, $defer = FALSE, $async = FALSE, $instant=TRUE) 177 | { 178 | self::printJsTag(self::getUrlForAssets("js/" . $name), $defer, $async, $instant); 179 | } 180 | 181 | public static function getCdnUrl($name, $version, $file) 182 | { 183 | if (array_key_exists('_alias', self::$_assetsCdnUrl) === true) { 184 | $alias = self::$_assetsCdnUrl['_alias']; 185 | if (array_key_exists($name, $alias) === true) { 186 | $name = $alias[$name]; 187 | } 188 | } 189 | if (array_key_exists($name, self::$_assetsCdnUrl) === true) { 190 | $cdnUrl = self::$_assetsCdnUrl[$name]; 191 | } else if (array_key_exists('_tpl', self::$_assetsCdnUrl) === true) { 192 | $cdnUrl = self::$_assetsCdnUrl['_tpl']; 193 | } else { 194 | return; 195 | } 196 | return str_replace(array('{package}', '{version}', '{file}'), array($name, $version, $file), $cdnUrl); 197 | } 198 | 199 | public static function getFontCdnUrl($fontname, $type = 'css') 200 | { 201 | if (array_key_exists('font', self::$_assetsCdnUrl) === false) 202 | return; 203 | $cdnUrl = self::$_assetsCdnUrl['font']; 204 | return str_replace(array('{fontname}', '{type}'), array($fontname, $type), $cdnUrl); 205 | } 206 | 207 | public static function getIconCdnUrl() 208 | { 209 | if (array_key_exists('icon', self::$_assetsCdnUrl) === false) 210 | return; 211 | return self::$_assetsCdnUrl['icon']; 212 | } 213 | 214 | public static function getGravatarUrl() 215 | { 216 | if (array_key_exists('gravatar', self::$_assetsCdnUrl) === false) 217 | return; 218 | return self::$_assetsCdnUrl['gravatar']; 219 | } 220 | 221 | public static function cdn($cssJs, $type) 222 | { 223 | $args = func_get_args(); 224 | 225 | $config = explode('+', $cssJs); 226 | $defer = in_array('defer', $config); 227 | $async = in_array('async', $config); 228 | $preload = in_array('nopreload', $config); 229 | //$instant = in_array('instant', $config); 230 | 231 | $cssJs = $config[0]; 232 | 233 | $funcName = ''; 234 | switch ($type) 235 | { 236 | case 'font': 237 | array_splice($args, 0, 2); 238 | $funcName = 'getFontCdnUrl'; 239 | if (count($args) === 1) 240 | { 241 | array_push($args, $cssJs); 242 | } 243 | break; 244 | case 'icon': 245 | array_splice($args, 0, 2); 246 | $funcName = 'getIconCdnUrl'; 247 | break; 248 | default: 249 | array_splice($args, 0, 1); 250 | $funcName = 'getCdnUrl'; 251 | break; 252 | } 253 | $url = call_user_func_array(array('Icarus_Assets', $funcName), $args); 254 | if (Icarus_Util::isEmpty($url) === true) 255 | { 256 | return; 257 | } 258 | if ($cssJs === 'js') 259 | { 260 | self::printJsTag($url, $defer, $async, FALSE); 261 | } 262 | else 263 | { 264 | self::printCssTag($url, !$preload); 265 | } 266 | } 267 | } -------------------------------------------------------------------------------- /library/Config.php: -------------------------------------------------------------------------------- 1 | set(sprintf(_IcT('setting.cfg_version_notice'), __ICARUS_VERSION__), 'notice'); 18 | } 19 | 20 | $verInfo = new Icarus_Form_VersionField(); 21 | $form->_form->addInput($verInfo); 22 | 23 | $form->html(self::CONFIG_STYLESHEET); 24 | 25 | $form->showTitle(_IcT('setting.general.title'), 'General'); 26 | $form->html(sprintf( 27 | _IcT('setting.general.desc'), 28 | __TYPECHO_THEME_DIR__ . '/' . Icarus_Util::$options->theme, // theme dir 29 | Typecho_Common::url('write-page.php#icarus', Icarus_Util::$options->adminUrl) // create page link 30 | )); 31 | 32 | $form->packInput('General/install_time', date('Y-m-d', Icarus_Util::getSiteInstallTime()), 'w-20'); 33 | 34 | //$form->_form->addInput(new Icarus_Form_ConfigBackup()); 35 | } 36 | 37 | public function __construct($form) 38 | { 39 | $this->_form = $form; 40 | } 41 | 42 | public static function prefixKey($key) 43 | { 44 | return self::PREFIX . $key; 45 | } 46 | 47 | private static function optionalDesc($key) 48 | { 49 | $key .= '.desc'; 50 | if (Icarus_I18n::has($key)) 51 | { 52 | return _IcT($key); 53 | } 54 | else 55 | { 56 | return NULL; 57 | } 58 | } 59 | 60 | private static function cfgNameToLangKey($cfgName) 61 | { 62 | $split = explode('/', $cfgName, 2); 63 | switch (count($split)) 64 | { 65 | case 2: 66 | return 'setting.' . Icarus_Util::parseName($split[0]) . '.' . $split[1]; 67 | case 1: 68 | return 'setting.' . Icarus_Util::parseName($cfgName); 69 | } 70 | } 71 | 72 | private static function cfgNameToCfgKey($cfgName) 73 | { 74 | $split = explode('/', $cfgName, 2); 75 | return Icarus_Util::parseName($split[0]) . '_' . $split[1]; 76 | } 77 | 78 | public function html($html) 79 | { 80 | $layout = new Typecho_Widget_Helper_Layout(); 81 | $layout->html($html); 82 | $this->_form->addItem($layout); 83 | } 84 | 85 | public function showDesc($content) 86 | { 87 | $layout = new Typecho_Widget_Helper_Layout('div'); 88 | $layout->html($content); 89 | $layout->class = 'icarus-description'; 90 | $this->_form->addItem($layout); 91 | } 92 | 93 | public function showTitle($title, $id = NULL) 94 | { 95 | static $titleCount = 0; 96 | $titleCount++; 97 | 98 | if (is_null($id)) { 99 | $id = 'Section-' . $titleCount; 100 | } 101 | 102 | $this->_titleList[] = array($title, $id); 103 | 104 | $layout = new Typecho_Widget_Helper_Layout('h2'); 105 | $layout->id = $id; 106 | $layout->class = 'icarus-config-title'; 107 | $layout->html($title); 108 | 109 | $this->_form->addItem($layout); 110 | } 111 | 112 | public function packTitle($name) 113 | { 114 | $langStr = self::cfgNameToLangKey($name); 115 | 116 | $this->showTitle(_IcT($langStr . '.title'), $name); 117 | if (Icarus_I18n::has($langStr . '.desc')) 118 | { 119 | $this->showDesc(_IcT($langStr . '.desc')); 120 | } 121 | } 122 | 123 | public function makeInput($name, $title, $desc, $default = NULL, $classAppend = NULL) 124 | { 125 | $input = new Icarus_Form_Element_Text( 126 | self::prefixKey($name), NULL, $default, 127 | $title, 128 | $desc 129 | ); 130 | if (!empty($classAppend)) 131 | { 132 | $input->input->setAttribute('class', $classAppend); 133 | } 134 | $this->_form->addInput($input); 135 | return $input; 136 | } 137 | 138 | public function makeIntInput($name, $title, $desc, $default = NULL, $classAppend = NULL) 139 | { 140 | $input = $this->makeInput($name, $title, $desc, $default, $classAppend); 141 | $input->addRule('isInteger', _t('请填入一个数字')); 142 | return $input; 143 | } 144 | 145 | public function packInput($name, $default = NULL, $classAppend = NULL) 146 | { 147 | $langStr = self::cfgNameToLangKey($name); 148 | $this->makeInput(self::cfgNameToCfgKey($name), _IcT($langStr . '.title'), self::optionalDesc($langStr), $default, $classAppend); 149 | } 150 | 151 | public function makeTextarea($name, $title, $desc, $default = NULL) 152 | { 153 | $this->_form->addInput(new Icarus_Form_Element_Textarea( 154 | self::prefixKey($name), NULL, $default, 155 | $title, 156 | $desc 157 | )); 158 | } 159 | 160 | public function packTextarea($name, $default = NULL) 161 | { 162 | $langStr = self::cfgNameToLangKey($name); 163 | $this->makeTextarea(self::cfgNameToCfgKey($name), _IcT($langStr . '.title'), self::optionalDesc($langStr), $default); 164 | } 165 | 166 | public function makeRadio($name, $title, $desc, array $options, $default = NULL) 167 | { 168 | $this->_form->addInput(new Icarus_Form_Element_Radio( 169 | self::prefixKey($name), $options, $default, 170 | $title, 171 | $desc 172 | )); 173 | } 174 | 175 | public function packRadio($name, array $options, $default = NULL) 176 | { 177 | $langStr = self::cfgNameToLangKey($name); 178 | $optionsReal = array(); 179 | foreach ($options as $option) 180 | { 181 | $optionsReal[$option] = _IcT($langStr . '.options.' . $option); 182 | } 183 | $this->makeRadio(self::cfgNameToCfgKey($name), _IcT($langStr . '.title'), self::optionalDesc($langStr), $optionsReal, $default); 184 | } 185 | 186 | public function makeCheckbox($name, $title, $desc, array $options) 187 | { 188 | $this->_form->addInput(new Icarus_Form_Element_Checkbox( 189 | self::prefixKey($name), $options, NULL, 190 | $title, 191 | $desc 192 | )); 193 | } 194 | 195 | public function packCheckbox($name, array $options) 196 | { 197 | $langStr = self::cfgNameToLangKey($name); 198 | $optionsReal = array(); 199 | foreach ($options as $option) 200 | { 201 | $optionsReal[$option] = _IcT($langStr . '.options.' . $option); 202 | } 203 | $this->makeCheckbox(self::cfgNameToCfgKey($name), _IcT($langStr . '.title'), self::optionalDesc($langStr), $optionsReal); 204 | } 205 | 206 | public function toc() 207 | { 208 | $container = new Typecho_Widget_Helper_Layout('div'); 209 | $container->id = 'icarus-config-toc'; 210 | $container->class = 'col-mb-12 col-tb-2 hide'; 211 | 212 | $title = new Typecho_Widget_Helper_Layout('h2'); 213 | $title->html(_IcT('general.catalog')); 214 | $container->addItem($title); 215 | 216 | $button = new Typecho_Widget_Helper_Layout('button'); 217 | $button->type = 'submit'; 218 | $button->class = 'btn primary btn-xs'; 219 | $button->id = 'icarus-save-btn'; 220 | $button->html(_t('保存设置')); 221 | $container->addItem($button); 222 | 223 | $ul = new Typecho_Widget_Helper_Layout('ul'); 224 | $container->addItem($ul); 225 | 226 | foreach ($this->_titleList as $title) 227 | { 228 | $li = new Typecho_Widget_Helper_Layout('li'); 229 | $a = new Typecho_Widget_Helper_Layout('a'); 230 | $a->href = '#' . $title[1]; 231 | $a->html($title[0]); 232 | $li->addItem($a); 233 | $ul->addItem($li); 234 | } 235 | 236 | $script = new Typecho_Widget_Helper_Layout('script'); 237 | $script->html(self::CONFIG_SCRIPT); 238 | 239 | $this->_form->addItem($container); 240 | $this->_form->addItem($script); 241 | } 242 | 243 | public static function get($key, $default = NULL) 244 | { 245 | return self::tryGet($key, $result) ? $result: $default; 246 | } 247 | 248 | public static function has($key) 249 | { 250 | return self::tryGet($key, $result); 251 | } 252 | 253 | public static function callback($key, $callback) 254 | { 255 | if (self::tryGet($key, $result)) 256 | { 257 | $callback($result); 258 | } 259 | } 260 | 261 | public static function tryGet($key, &$result) 262 | { 263 | $key = self::prefixKey($key); 264 | $value = Icarus_Util::$options->$key; 265 | $exist = !Icarus_Util::isEmpty($value); 266 | if ($exist) 267 | $result = $value; 268 | return $exist; 269 | } 270 | 271 | public static function cfgVersionMatch() 272 | { 273 | return Icarus_Config::get('config_version') === __ICARUS_CFG_VERSION__; 274 | } 275 | 276 | const CONFIG_STYLESHEET = << 278 | form code 279 | { 280 | color: #e50833; 281 | background: #fff; 282 | padding: 2px 4px; 283 | border-radius: 3px; 284 | } 285 | .icarus-config-title 286 | { 287 | margin-top: 2em; 288 | margin-bottom: 0.2em; 289 | border-bottom: 1px solid #D9D9D6; 290 | padding-bottom: 0.2em; 291 | } 292 | .icarus-description 293 | { 294 | color: #999; 295 | font-size: .92857em; 296 | } 297 | .icaurs-general-desc-list 298 | { 299 | line-height: 1.8em; 300 | } 301 | #icarus-config-toc 302 | { 303 | position: relative; 304 | top: 0; 305 | padding: 15px 0 10px 20px; 306 | border: 2px solid #ccc; 307 | background: #eee; 308 | } 309 | #icarus-config-toc.hide 310 | { 311 | display: none; 312 | } 313 | #icarus-config-toc > ul 314 | { 315 | padding-left: 0; 316 | list-style: none; 317 | max-height: 400px; 318 | max-height: 75vh; 319 | overflow-y: scroll; 320 | } 321 | #icarus-config-toc > ul > li 322 | { 323 | margin: 3px 0; 324 | } 325 | #icarus-config-toc > ul > li > a 326 | { 327 | display: block; 328 | text-decoration: none; 329 | color: #444; 330 | padding: 1px 0 1px 8px; 331 | border-left: 2px solid #aaa; 332 | } 333 | #icarus-config-toc > ul > li > a:hover 334 | { 335 | border-left-color: #666; 336 | } 337 | #icarus-config-toc > h2 338 | { 339 | margin: 0 0 .5em 0; 340 | } 341 | #icarus-save-btn 342 | { 343 | position: absolute; 344 | right: 10px; 345 | top: 20px; 346 | } 347 | @media (max-width: 1200px) 348 | { 349 | #icarus-config-toc 350 | { 351 | width: 20%; 352 | } 353 | } 354 | @media (max-width: 768px) 355 | { 356 | #icarus-config-toc 357 | { 358 | display: none; 359 | } 360 | } 361 | 362 | #icarus-config-toc > ul::-webkit-scrollbar { 363 | width: 10px; 364 | height: 10px; 365 | } 366 | 367 | #icarus-config-toc > ul::-webkit-scrollbar-thumb { 368 | background-color: rgba(50, 50, 50, .25); 369 | border: 2px solid transparent; 370 | background-clip: padding-box 371 | } 372 | 373 | #icarus-config-toc > ul::-webkit-scrollbar-thumb:hover { 374 | background-color: rgba(50, 50, 50, .5) 375 | } 376 | 377 | #icarus-config-toc > ul::-webkit-scrollbar-track { 378 | background-color: rgba(50, 50, 50, 0) 379 | } 380 | 381 | STYLESHEET; 382 | 383 | const CONFIG_SCRIPT = << 73 | commentsShowUrl : $autoLink; 88 | $noFollow = (NULL === $noFollow) ? Icarus_Util::$options->commentsUrlNofollow : $noFollow; 89 | 90 | if ($comment->url && $autoLink) { 91 | echo '' , htmlspecialchars($comment->author, ENT_QUOTES, 'UTF-8') , ''; 92 | } else { 93 | echo htmlspecialchars($comment->author, ENT_QUOTES, 'UTF-8'); 94 | } 95 | } 96 | 97 | private static function outputInternal($widget) 98 | { 99 | $widget->comments()->to($comments); 100 | $options = Icarus_Util::$options; 101 | $user = Typecho_Widget::widget('Widget_User'); 102 | $mail = empty($user->mail)?$widget->remember('mail',true):$user->mail; 103 | //$security = Typecho_Widget::widget('Widget_Security'); 104 | 105 | if ($comments->have() || $widget->allow('comment')): 106 | ?> 107 |
    108 |
    109 |
    110 |
    111 |

    112 |
    113 |
    114 | 115 | allow('comment')): ?> 116 |
    117 |
    118 |
    119 |
    120 |
    121 |
    >
    122 |
    123 |
    124 |
    125 |
    hasLogin())?'disabled="disabled" ':''; ?>type="text" name="author" placeholder="" value="hasLogin())?$user->screenName():$widget->remember('author'); ?>" class="el-input__inner" title="">
    126 | hasLogin()): ?> 127 |
    128 |
    129 | 130 | 131 |
    132 |
    133 | 134 |
    135 |
    136 |
    137 |
    138 |
    139 | 140 |
    141 | hasLogin()): ?> 142 | 143 | 144 | allowRegister): ?> 145 | 146 | 147 | 148 | 149 | 150 | 151 |
    152 |
    153 | hasLogin()) { 155 | if(array_key_exists('CaptchaPlus', Typecho_Plugin::export()['activated'])) CaptchaPlus_Plugin::output(); 156 | elseif(array_key_exists('GrCv3Protect', Typecho_Plugin::export()['activated'])) GrCv3Protect_Plugin::OutputCode(); 157 | elseif(array_key_exists('reCAPTCHA', Typecho_Plugin::export()['activated'])) reCAPTCHA_Plugin::output(); 158 | elseif(array_key_exists('reCAPTCHAv3', Typecho_Plugin::export()['activated'])) reCAPTCHAv3_Plugin::output(); 159 | elseif(array_key_exists('SimpleCommentCaptcha', Typecho_Plugin::export()['activated'])) SimpleCommentCaptcha_Plugin::outputSimpleCommentCaptchaField(); 160 | } 161 | ?> 162 |
    163 |
    164 |
    165 |
    166 |
    167 | commentsNum(_IcT('comments.num.0'), _IcT('comments.num.1'), _IcT('comments.num.more')); ?>
    168 |
    169 | listComments(array('before' => '', 'after' => '')); ?> 170 |
    171 | 172 | 173 | 174 | 175 |
    176 |

    177 |
    178 | 179 | 180 |
    181 |
    182 | 183 | 184 | getArchiveType() . '-' . $widget->getArchiveSlug(); 191 | ?> 192 |
    193 |
    194 | 195 |
    196 |
    197 | 204 |
    205 |
    206 |

    207 | <?php echo $comments->author; ?> 211 |

    212 |
    213 |
    214 |
    215 |
    216 | 217 | authorId && $comments->authorId == $comments->ownerId): ?> 218 | 219 | 220 | status): ?> 221 | 222 | 223 |
    224 |
    content(); ?>
    225 |
      reply(_IcT('comments.reply')); ?>
    226 |
    227 | children): ?> 228 |
    229 | threadedComments(); ?> 230 |
    231 | 232 |
    233 |
    234 | 'processGalleryCallback', 10 | 'ruby' => 'processRubyCallback', 11 | 'tip' => 'processTipCallback', 12 | 'tabs' => 'processTabsCallback', 13 | 'bili' => 'processBilibiliCallback', 14 | 'block' => 'processBlockCallback', 15 | ); 16 | 17 | public static function config($form) 18 | { 19 | $form->packTitle('Post'); 20 | $form->packRadio('Post/excerpt_preserve_tags', array('0', '1'), '0'); 21 | $form->packInput('Post/excerpt_length', '100', 'w-20'); 22 | $form->packRadio('Post/tiny_item', array('0', '1'), '0'); 23 | $form->packRadio('Post/license', array('0', '1'), '1'); 24 | $form->packTextarea('Post/license_extend', 'CC BY-NC-ND 4.0'); 25 | $form->packTextarea('Post/content_extend', ''); 26 | } 27 | 28 | /** 29 | * Retrieve the shortcode regular expression for searching. 30 | * 31 | * The regular expression combines the shortcode tags in the regular expression 32 | * in a regex class. 33 | * 34 | * 1 - An extra [ to allow for escaping shortcodes with double [[]] 35 | * 2 - The shortcode name 36 | * 3 - The shortcode argument list 37 | * 4 - The self closing / 38 | * 5 - The content of a shortcode when it wraps some content. 39 | * 6 - An extra ] to allow for escaping shortcodes with double [[]] 40 | * 41 | * 42 | * @param array $tagnames Optional. List of shortcodes to find. Defaults to all registered shortcodes. 43 | * @return string The shortcode search regular expression 44 | * 45 | * @link https://github.com/WordPress/WordPress/blob/70bc51e46f18a15b2abf9161aa82635f059beb82/wp-includes/shortcodes.php#L230 46 | */ 47 | private static function getShortcodeRegex( $tagnames ) { 48 | $tagregexp = join( '|', array_map( 'preg_quote', $tagnames ) ); 49 | return 50 | '\\[' // Opening bracket 51 | . '(\\[?)' // 1: Optional second opening bracket for escaping shortcodes: [[tag]] 52 | . "($tagregexp)" // 2: Shortcode name 53 | . '(?![\\w-])' // Not followed by word character or hyphen 54 | . '(' // 3: Unroll the loop: Inside the opening shortcode tag 55 | . '[^\\]\\/]*' // Not a closing bracket or forward slash 56 | . '(?:' 57 | . '\\/(?!\\])' // A forward slash not followed by a closing bracket 58 | . '[^\\]\\/]*' // Not a closing bracket or forward slash 59 | . ')*?' 60 | . ')' 61 | . '(?:' 62 | . '(\\/)' // 4: Self closing tag ... 63 | . '\\]' // ... and closing bracket 64 | . '|' 65 | . '\\]' // Closing bracket 66 | . '(?:' 67 | . '(' // 5: Unroll the loop: Optionally, anything between the opening and closing shortcode tags 68 | . '[^\\[]*+' // Not an opening bracket 69 | . '(?:' 70 | . '\\[(?!\\/\\2\\])' // An opening bracket not followed by the closing shortcode tag 71 | . '[^\\[]*+' // Not an opening bracket 72 | . ')*+' 73 | . ')' 74 | . '\\[\\/\\2\\]' // Closing shortcode tag 75 | . ')?' 76 | . ')' 77 | . '(\\]?)'; // 6: Optional second closing brocket for escaping shortcodes: [[tag]] 78 | } 79 | 80 | private static function processGalleryCallback($matches) 81 | { 82 | return ''; 83 | } 84 | 85 | private static function processBilibiliCallback($matches) 86 | { 87 | return ''; 88 | } 89 | 90 | private static function processRubyCallback($matches) 91 | { 92 | $attr = self::parseShortcodeAttrs($matches[3], 'title'); 93 | if (isset($attr['title']) === true) 94 | return '' . $matches[5] . ' (' . $attr['title'] . ') '; 95 | else 96 | return $matches[5]; 97 | } 98 | 99 | private static function processTabsCallback($matches) 100 | { 101 | if (Icarus_Module::enabled('Tabs') === false) return; 102 | $list=$matches[0]; 103 | $regeTab="/\[tab.*?name=(.*?)\](.+?)\[\/tab\]/is"; 104 | preg_match_all($regeTab , $list , $results); 105 | $name=json_decode(json_encode($results[1])); 106 | $content=json_decode(json_encode($results[2])); 107 | $html="
      "; 108 | foreach($name as $key=>$value){ 109 | if($key==0){ 110 | $html.='
    • '.str_replace('"',"",$name[$key]).'
    • '; 111 | }else{ 112 | $html.='
    • '.str_replace('"',"",$name[$key]).'
    • '; 113 | } 114 | } 115 | $html.='
    '; 116 | foreach($content as $key=>$value){ 117 | if($key==0){ 118 | $html.='
    '.$content[$key].'
    '; 119 | }else{ 120 | $html.='
    '.$content[$key].'
    '; 121 | } 122 | } 123 | $html.="
    "; 124 | return $html ; 125 | } 126 | 127 | private static function processTipCallback($matches) 128 | { 129 | preg_match_all("/\[tip.*?type=(.*?)\](.+?)\[\/tip\]/is", $matches[0], $results); 130 | return '
    131 |
    '.$results[2][0].'
    '; 132 | } 133 | 134 | private static function processButtonCallback($matches) 135 | { 136 | preg_match_all("/\[button.*?onclick=(.*?)\](.+?)\[\/button\]/is", $matches[0], $results); 137 | return '
    '; 151 | } 152 | 153 | private static function parseShortcodeAttrs($attrContent, $attrName) 154 | { 155 | if (is_array($attrName) === true) 156 | { 157 | $attrRegexp = join('|', array_map('preg_quote', $attrName));; 158 | } 159 | else 160 | { 161 | $attrRegexp = preg_quote($attrName); 162 | $attrName = array($attrName); 163 | } 164 | preg_match_all('/(' . $attrRegexp . ')="([^"]*?)"/i', $attrContent, $matches, PREG_SET_ORDER); 165 | $result = array(); 166 | foreach ($matches as $match) 167 | { 168 | $result[$match[1]] = $match[2]; 169 | } 170 | return $result; 171 | } 172 | 173 | private static function processAllShortcodes($content) 174 | { 175 | if (strpos($content, '[') === FALSE) { 176 | return $content; 177 | } 178 | $pattern = self::getShortcodeRegex(array_keys(self::$shortcodeHandler)); 179 | return preg_replace_callback( 180 | "/$pattern/", array(__CLASS__, 'processShortcodeDispatch'), 181 | $content 182 | ); 183 | } 184 | 185 | private static function removeAllShortcodes($content) 186 | { 187 | if (strpos($content, '[') === FALSE) { 188 | return $content; 189 | } 190 | $pattern = self::getShortcodeRegex(array_keys(self::$shortcodeHandler)); 191 | return preg_replace("/$pattern/", '\5', $content); 192 | } 193 | 194 | private static function processShortcodeDispatch($matches) 195 | { 196 | // escaping [[tagName]] 197 | if ( $matches[1] == '[' && $matches[6] == ']' ) { 198 | return substr($matches[0], 1, -1); 199 | } else { 200 | $tagName = $matches[2]; 201 | return call_user_func(array(__CLASS__, self::$shortcodeHandler[$tagName]), $matches); 202 | } 203 | } 204 | 205 | private static function processSpecialSyntax($content) 206 | { 207 | return str_replace(['[ ]','
  • [x]'],[' ','
  • '],$content); 208 | } 209 | 210 | public static function getContent($post) 211 | { 212 | $content = $post->content; 213 | $content = self::processAllShortcodes($content); 214 | $content = self::processSpecialSyntax($content); 215 | if (Icarus_Module::enabled('Toc') === true) 216 | { 217 | Icarus_Module::load('Toc'); 218 | $content = Icarus_Module_Toc::generate($content); 219 | } 220 | if (Icarus_Config::tryGet('post_content_extend', $extendTpl) === true && $post->is('post') === true) 221 | { 222 | $content .= str_replace( 223 | array( 224 | '{title}', 225 | '{author}', 226 | '{url}', 227 | '{date}', 228 | ), 229 | array( 230 | $post->title, 231 | $post->author->screenName, 232 | $post->permalink, 233 | $post->date->format(Icarus_Util::$options->postDateFormat), 234 | ), 235 | $extendTpl 236 | ); 237 | } 238 | return $content; 239 | } 240 | 241 | public static function getExcerpt($post) 242 | { 243 | // hidden post overrided, show the password form 244 | if ($post->hidden === true) { 245 | //return $post->text; 246 | return ''; 247 | } 248 | 249 | // user config 250 | $truncateLength = intval(Icarus_Config::get('post_excerpt_length', 100)); 251 | 252 | $preserveTags = !!Icarus_Config::get('post_excerpt_preserve_tags', FALSE); 253 | 254 | if (Icarus_Util::isEmpty($post->fields->custom_excerpt) === false) { 255 | // custom excerpt support 256 | 257 | $excerpt = $post->markdown($post->fields->custom_excerpt); 258 | $truncateRequired = FALSE; 259 | } else { 260 | // original excerpt process 261 | 262 | $content = $post->pluginHandle('Widget_Abstract_Contents') 263 | ->trigger($plugged) 264 | ->excerpt($post->text, $post); 265 | 266 | if ($plugged === false) { 267 | $content = $post->isMarkdown ? $post->markdown($content) 268 | : $post->autoP($content); 269 | } 270 | 271 | if (FALSE !== ($excerptPos = strpos($content, ''))) { 272 | $excerpt = substr($content, 0, $excerptPos); 273 | } else { 274 | $excerpt = $content; 275 | } 276 | 277 | // fixHtml func patched 278 | $excerpt = Icarus_Util::fixHtml( 279 | $post->pluginHandle('Widget_Abstract_Contents') 280 | ->excerptEx($excerpt, $post) 281 | ); 282 | 283 | // handle shortcode 284 | $excerpt = self::removeAllShortcodes($excerpt); 285 | 286 | // condition flags 287 | $truncateRequired = $truncateLength > 0 && ($excerptPos === FALSE); 288 | } 289 | 290 | if ($preserveTags === false || $truncateRequired === true) { 291 | $excerpt = strip_tags($excerpt); 292 | if ($truncateRequired === true) { 293 | $excerpt = Typecho_Common::subStr($excerpt, 0, $truncateLength, '...'); 294 | } 295 | } 296 | 297 | return str_replace(['[hide]','[/hide]'],['',''],$excerpt); 298 | } 299 | 300 | public static function hasThumbnail($post) 301 | { 302 | return !Icarus_Util::isEmpty($post->fields->thumbnail); 303 | //如果需要显示随机图片,可以改为 return true; 304 | } 305 | 306 | public static function getThumbnail($post) 307 | { 308 | return self::hasThumbnail($post) 309 | ? $post->fields->thumbnail 310 | : Icarus_Assets::getUrlForAssets('img/thumbnail.svg'); 311 | 312 | } 313 | 314 | public static function fieldsConfig($form) 315 | { 316 | $thumbnail = new Typecho_Widget_Helper_Form_Element_Text( 317 | 'thumbnail', NULL, NULL, 318 | _IcT('fields.thumbnail.title'), 319 | _IcT('fields.thumbnail.desc') 320 | ); 321 | $thumbnail->input->class = 'w-100'; 322 | $form->addItem($thumbnail); 323 | 324 | $language = new Typecho_Widget_Helper_Form_Element_Text( 325 | 'language', NULL, NULL, 326 | _IcT('fields.language.title'), 327 | _IcT('fields.language.desc') 328 | ); 329 | $language->input->class = 'w-100'; 330 | $form->addItem($language); 331 | 332 | $translate = new Typecho_Widget_Helper_Form_Element_Text( 333 | 'translate', NULL, NULL, 334 | _IcT('fields.translate.title'), 335 | _IcT('fields.translate.desc') 336 | ); 337 | $translate->input->class = 'w-100'; 338 | $form->addItem($translate); 339 | 340 | $blocked = new Typecho_Widget_Helper_Form_Element_Text( 341 | 'blocked_countries', NULL, NULL, 342 | _IcT('fields.blocked_countries.title'), 343 | _IcT('fields.blocked_countries.desc') 344 | ); 345 | $blocked->input->class = 'w-100'; 346 | $form->addItem($blocked); 347 | 348 | $excerpt = new Typecho_Widget_Helper_Form_Element_Textarea( 349 | 'custom_excerpt', NULL, NULL, 350 | _IcT('fields.excerpt.title'), 351 | _IcT('fields.excerpt.desc') 352 | ); 353 | $excerpt->input->class = 'w-100'; 354 | $form->addItem($excerpt); 355 | 356 | if (defined('__ICARUS_WIDGET_CLASS__') === true && __ICARUS_WIDGET_CLASS__ === 'Widget_Contents_Page_Edit') 357 | { 358 | self::fieldsInfoForPage(); 359 | } 360 | } 361 | 362 | private static function fieldsInfoForPage() 363 | { 364 | if (self::$fieldsInfoOutputed === false) 365 | { 366 | self::$fieldsInfoOutputed = TRUE; 367 | ?> 368 | 389 |
    390 | 393 |
    394 | 395 | 396 | 397 |
    398 |
    399 | 432 |