Typecho插件:增强渲染MarkdownPlus

简单介绍

MarkdownPlus是让Codex通过本站主题HansJack文章的渲染功能来剥离而出的Typecho插件,不确保你的主题能不能完美适配,不过添加了主题文章内容选中器。

主要功能

  • 提示块(alert)和引用块增强

[!NOTE]
111

[!TIP]
111

[!IMPORTANT]
111

[!WARNING]
111

[!CAUTION]
111

并且支持折叠。

  • 行内语法增强

剧透插入高亮注音

  • 嵌入短代码

{% youtube %}、{% bilibili %}、{% video %}、{% iframe/embed %}、{% comment %}

  • Onebox 卡片

段落里单独 URL 自动卡片化;GitHub repo/issue/PR 会调用 GitHub API 补全信息

# Vintage Story 中文社区(Vintage Story Forum)

访问地址:https://vintagestory.top

复古物语非官方中文社区是一个由玩家自发建立的交流平台,围绕游戏 Vintage Story 展开。本社区与官方团队无任何隶属关系,所有内容均由玩家贡献与维护。
社区主要提供游戏攻略、模组与资源分享、版本更新讨论、服务器交流以及技术问题解答等内容,致力于为中文玩家打造一个开放、自由且长期活跃的交流环境。

## 关于社区在做什么

这个论坛目前主要承担几个功能。

- 第一是内容整理。包括游戏攻略、玩法经验、问题解答,这些内容会逐步积累,形成可以反复查阅的资料。
- 第二是资源分享。比如模组推荐、整合内容、外部资源链接等,尽量让信息集中,而不是零散分布。
- 第三是信息同步。官方更新、补丁说明、活动信息会进行整理,避免玩家只依赖零散渠道获取消息。
- 第四是交流讨论。提供一个相对结构化的讨论环境,适合深入讨论而不是碎片化聊天。

## 当前板块说明

- 社区活动这个板块主要用来发布活动和福利信息,例如抽奖、节日活动、社区互动等,属于周期性内容。
- 社区创作用来承载质量更高的内容,比如攻略、教程、模组相关内容或者整理类帖子,这一块后续会是重点发展方向。
- 常规话题是一个相对自由的区域,用于日常讨论、提问以及不适合归类的内容。
- 网站反馈用于收集对论坛本身的意见,包括功能建议、使用体验问题以及改进方向。
- 官方资讯集中整理游戏更新、补丁说明以及官方相关动态,方便快速查阅。
- 管理人员板块是内部使用,不对外开放,用于日常管理讨论。

## 当前阶段

- 社区目前还在早期阶段,内容数量不多,但结构已经基本确定。接下来会优先补充内容,同时逐步优化分类和信息组织方式。
- 搜索引擎收录正在进行中,随着内容增加和访问量提升,会逐渐稳定下来。
- 想法,也可以在反馈板块提出。

## 关于这个仓库

这个仓库主要用于对外说明社区的定位和结构,同时作为搜索引擎的一个入口。后续如果有需要,也可能会放一些与社区相关的工具或扩展内容。
  • 图片增强

独立图片段落转 figure + figcaption,并自动 loading="lazy"

图片

  • 代码块增强

复制按钮、超过 10 行可折叠

class Plugin implements PluginInterface
{
    private const VERSION = '1.0.2';
    private static $frontRendered = false;

    public static function activate()
    {
        \Typecho\Plugin::factory('Widget_Archive')->footer = [__CLASS__, 'renderFooter'];
        return _t('MarkdownPlus 已启用');
    }

    public static function deactivate()
    {
    }

    public static function config(Form $form)
    {
        $selector = new Text(
            'contentSelector',
            null,
            '.post-content, .post_content',
            _t('文章内容容器选择器'),
            _t('支持多个 CSS 选择器,使用英文逗号分隔。留空时默认匹配 default / OneBlog 的正文容器。')
        );
        $form->addInput($selector);
    }

    public static function personalConfig(Form $form)
    {
    }

    private static function cut($text, $len)
    {
        if (function_exists('mb_substr')) {
            return mb_substr((string) $text, 0, (int) $len);
        }
        return substr((string) $text, 0, (int) $len);
    }

    private static function getConfig()
    {
        $defaults = [
            'contentSelector' => '.post-content, .post_content',
        ];

        try {
            $raw = \Helper::options()->plugin('MarkdownPlus')->toArray();
            if (!is_array($raw)) {
                $raw = [];
            }
        } catch (\Throwable $e) {
            $raw = [];
        }

        $cfg = array_merge($defaults, $raw);
        $selector = trim((string) ($cfg['contentSelector'] ?? ''));
        $selector = str_replace(["\r\n", "\r", "\n"], ' ', $selector);
        $selector = preg_replace('/\s{2,}/u', ' ', $selector);
        $selector = trim((string) $selector);
        if ($selector === '') {
            $selector = $defaults['contentSelector'];
        }
        $cfg['contentSelector'] = self::cut($selector, 600);

        return $cfg;
    }

    public static function renderFooter($archive)
    {
        if (defined('__TYPECHO_ADMIN__') && __TYPECHO_ADMIN__) {
            return;
        }

        if (self::$frontRendered) {
            return;
        }

        if (!is_object($archive) || !method_exists($archive, 'is')) {
            return;
        }

        $isSingle = false;
        try {
            $isSingle = $archive->is('single') || $archive->is('post') || $archive->is('page');
        } catch (\Throwable $e) {
            $isSingle = false;
        }
        if (!$isSingle) {
            return;
        }

        $cfg = self::getConfig();

        $pluginUrl = '';
        try {
            $pluginUrl = rtrim((string) (\Helper::options()->pluginUrl ?? ''), '/') . '/MarkdownPlus';
        } catch (\Throwable $e) {
            $pluginUrl = '';
        }
        if ($pluginUrl === '') {
            return;
        }

        $bootstrap = [
            'version' => self::VERSION,
            'contentSelector' => (string) ($cfg['contentSelector'] ?? ''),
            'assetBase' => $pluginUrl . '/assets',
        ];

        $json = json_encode(
            $bootstrap,
            JSON_UNESCAPED_UNICODE
            | JSON_UNESCAPED_SLASHES
            | JSON_HEX_TAG
            | JSON_HEX_AMP
            | JSON_HEX_APOS
            | JSON_HEX_QUOT
        );
        if ($json === false) {
            return;
        }

        self::$frontRendered = true;

        echo '<link rel="stylesheet" href="'
            . htmlspecialchars($pluginUrl . '/assets/markdown-plus.css?v=' . self::VERSION, ENT_QUOTES, 'UTF-8')
            . '">';
        echo '<script type="application/json" id="markdownplus-config">' . $json . '</script>';
        echo '<script src="'
            . htmlspecialchars($pluginUrl . '/assets/markdown-plus.js?v=' . self::VERSION, ENT_QUOTES, 'UTF-8')
            . '" defer></script>';
    }
}
  • 图表/白板渲染

支持 vega-lite 和 excalidraw 代码块

  • 移动端交互优化

tooltip/spoiler 点击展开逻辑

本文采用 CC BY-NC-SA 4.0 进行许可。
评论 1
  1. 懋和道人的头像
    懋和道人

    ❤️宝贝辛苦了。