Blog | 菜谱:用NeoDB短代码展示书影游短评

2017字

一直想给博客加上 NeoDB 短代码,但看过的版本都不太喜欢,我不需要在博客上显示书影游的简介预览(RSS 显示结果不好看),博客主题也不适配卡片样式。想了一段时间,看到白石京的 Neodb 自动化短评卡片,灵感来了,遂基于她的代码找 ChatGPT 老师商量。经过数次迭代,感觉已经差不多能端上桌,顺便解决了以下问题:

  1. Neodb API 在国内网络环境下无法正常访问,会导致 Hugo Sever 超时,影响博客预览。虽然可以通过在预览环境下不加载这段短代码来解决,但是同时也导致只能盲推盲用,无法即时看到短评内容。
  2. 同样是 Neodb API 的网络问题,如果博客读者没有良好的网络环境,书影游戏海报无法正常加载。

解决方案:

  1. 调整预览显示,当 Hugo Sever 时,不拉取 Neodb API,显示占位文字。
  2. 调整海报显示逻辑,优先加载占位海报,API 正常返回数据后,加载对应海报。
  3. 在短代码内写入短评内容时,显示短评内容。不写入内容时,自动显示 NeoDB 内评论内容。同时允许使用 HTML 代码来渲染样式。

缺点:如果自动显示 NeoDB 内短评内容,无法正常计算文章字数,这是 Hugo 计算博客字数的机制决定的。

实现效果可以参考:

Placeholder Image
2025-01-27 玩过 #Game

很短但还是打了一个月,提前开香槟说当天要打完结果还是拖到了两天后,和逆转裁判一样的催眠神作,一打就困,托巧舟的福拥有了期间限定良好睡眠:)
比起剧本和巧妙转折更喜欢这个游戏的人物动画设计哎,简约流畅又独特,尤其是卡尼巴拉警长,受伤弯身坐在椅子上那一幕,每一条弧线都很漂亮,视觉享受。
打完觉得特别适合春节!新的一年,祝您猫狗双全!

配方如下:

根据清单准备食材

(抄一下白石京老师的内容)点击 neodo.social  右上角头像 - 设置 - 更多设置 - 查看已授权的应用程序 - 点击 Create Personal Token - 记下生成的 token。

将短代码切块洗净

不同主题路径不同,在短代码文件夹(如 layouts\shortcodes)中加入 neodb.html 写入以下内容:
注:我不喜欢展示评分,所以删掉了

{{ $dbUrl := .Get 0 }}
{{ $apiUrl := "https://neodb.social/api/me/shelf/item/" }}
{{ $itemUuid := "" }}
{{ $authToken := "" }} <!-- 请替换为你的 API Personal Token -->

<!-- 判断是否为开发模式 -->
{{ if eq (hugo.Environment) "production" }}

    <!-- 解析 item_uuid -->
    {{ if (findRE `.*neodb\.social\/.*\/(.*)` $dbUrl) }}
        {{ $itemUuid = replaceRE `.*neodb\.social\/.*\/(.*)` "$1" $dbUrl }}
    {{ else }}
        <p style="text-align: center;"><small>无效的 URL 格式。</small></p>
        {{ return }}
    {{ end }}

    <!-- 构造 API 请求地址 -->
    {{ $dbApiUrl := print $apiUrl $itemUuid }}
    {{ $headers := dict "Authorization" (print "Bearer " $authToken) }}

    <!-- 获取 API 数据 -->
    {{ $dbFetch := getJSON $dbApiUrl $headers }}

    {{ if $dbFetch }}
        {{ $shelfType := $dbFetch.shelf_type }}
        {{ $category := $dbFetch.item.category }}
        {{ $action := "" }}
        {{ $prefix := "" }}
        {{ $suffix := "" }}
        {{ $displayText := "" }}

        <!-- 根据类别决定动作 -->
        {{ if eq $category "book" }}{{ $action = "读" }}
        {{ else if or (eq $category "tv") (eq $category "movie") (eq $category "performance") }}{{ $action = "看" }}
        {{ else if or (eq $category "podcast") (eq $category "album") }}{{ $action = "听" }}
        {{ else if eq $category "game" }}{{ $action = "玩" }}
        {{ end }}

        <!-- 根据状态决定前缀和后缀 -->
        {{ if eq $shelfType "wishlist" }}{{ $prefix = "想" }}
        {{ else if eq $shelfType "complete" }}{{ $suffix = "过" }}
        {{ else if eq $shelfType "progress" }}{{ $prefix = "在" }}
        {{ else if eq $shelfType "dropped" }}{{ $prefix = "不" }}{{ $suffix = "了" }}
        {{ end }}

        {{ $displayText = print $prefix $action $suffix }}

        <!-- 唯一 ID -->
        {{ $uniqueId := (print "coverImage-" $itemUuid) }}

        <div class="db-card">
            <div class="db-card-subject">
                <div class="db-card-post">
                    <img src="" <!-- 占位图片链接 -->
                         alt="Placeholder Image" 
                         style="max-width: 100%; height: auto;" 
                         id="{{ $uniqueId }}">
                </div>
                <div class="db-card-content">
                    <div class="db-card-title">
                        <a href="{{ $dbUrl }}" class="cute" target="_blank" rel="noreferrer">{{ $dbFetch.item.title }}</a>
                    </div>
                    <div class="db-card-comment">
                        {{ $dbFetch.created_time | time.Format "2006-01-02" }} {{ $displayText }}
                        <span style="color: #6b0f0f;">#{{ $category | title }}</span>
                        {{ $comment := trim .Inner " \n\r" }}
                        {{ if $comment }}
                        <p>{{ $comment | safeHTML }}</p>
                        {{ else }}
                        <p>{{ replace $dbFetch.comment_text "\n" "<br>" | safeHTML }}</p>
                        {{ end }}
                    </div>
                </div>
            </div>
        </div>

        <script>
            document.addEventListener('DOMContentLoaded', function() {
                const img = document.getElementById('{{ $uniqueId }}');
                const newImgSrc = '{{ $dbFetch.item.cover_image_url }}';

                if (newImgSrc) {
                    const tempImg = new Image();
                    tempImg.src = newImgSrc;
                    tempImg.onload = function() {
                        img.src = newImgSrc;
                    };
                }
            });
        </script>

    {{ else }}
        <p style="text-align: center;"><small>获取数据失败,请检查 API 是否有效。</small></p>
    {{ end }}

{{ else }}

    <!-- 开发模式下显示占位图,并正常显示自定义评论内容 -->
    <div class="db-card">
        <div class="db-card-subject">
            <div class="db-card-post">
                <img src="" <!-- 占位图片链接 -->
                     alt="开发模式占位图片" 
                     style="max-width: 100%; height: auto;">
            </div>
            <div class="db-card-content">
                <div class="db-card-title">
                    <a href="{{ $dbUrl }}" class="cute" target="_blank" rel="noreferrer">示例标题</a>
                </div>
                <div class="db-card-comment">
                    2025-03-04 想读
                    <span style="color: #6b0f0f;">#示例分类</span>
                    {{ $comment := trim .Inner " \n\r" }}
                    {{ if $comment }}
                    <p>{{ $comment | safeHTML }}</p>
                    {{ else }}
                    <p>这是开发模式下的占位数据。</p>
                    {{ end }}
                </div>
            </div>
        </div>
    </div>

{{ end }} <!-- 结束环境判断 -->

适当、少量地加入 CSS

/* Neodb card style */
.db-card {
    margin: 0;
    background: var(--color-codebg);
    border-radius: 7px;
    box-shadow: none;
    font-size: 14px;
    padding-top: 10px;
}

.db-card-subject {
    display: flex;
    align-items: flex-start;
    line-height: 1.6;
    position: relative;
    font-size: inherit; /* 继承全局字体大小 */
}

.dark .db-card {
    background: var(--color-codebg);
}

.db-card-content {
    flex: 1 1 auto;
    overflow: auto;
    margin-top: 8px;
}

.db-card-post {
    width: 100px;
    margin-right: 20px;
    margin-top: 20px;
    display: flex;
    flex: 0 0 auto;
}

.db-card-title {
    padding-top: 8px; /* 轻微调整标题的高度 */
}

.db-card-rating,
.db-card-comment,
.db-card-cate {
    font-size: inherit; /* 继承全局字体大小 */
}

.db-card-title {
    margin-bottom: 3px;
    color: #fff;
    font-weight: bold;
}

.db-card-title a {
    text-decoration: none !important;
}

.db-card-comment {
    margin-top: -5px;
    color: var(--card-text-color-main);
    max-height: none;
    overflow: visible;
}

.db-card-cate {
    position: absolute;
    top: 0;
    right: 0;
    padding: 1px 8px;
    font-style: italic;
    border-radius: 0 8px 0 8px;
    text-transform: capitalize;
    background: #6b0f0f;
    color: #fff;
}

.db-card-post img {
    width: 100px !important;
    height: 150px !important;
    border-radius: 4px;
    object-fit: cover;
}

@media (max-width: 600px) {
    .db-card {
        margin: 0.8rem 0.5rem;
    }
}

空气炸锅 30min 出锅

注意前后各多加一个花括号 { },评论内容支持使用 HTML 代码,如换行使用 <br>

{< neodb "NeoDB网址" >} 在这里写评论内容,留白则自动拉取标记短评{< /neodb >}

又及

其实还可以优化一下,比如根据类别不同,显示不同的占位图片。年前已经约稿在画了,但是结果不太满意,不能直接用,心情在重新找人画和再拯救拯救之间反复横跳,至今没有动力,我们下次再议……

❤️