Anzhiyu主题基于Memos实现说说

前言

本文基于Leonus基于Memos实现说说和清单功能。适用于2024-2-16日前Leonus未更新的教程。

本文仅适用于Anzhiyu主题,其他主题可以修改部分css以及源码以达到样式上的适配

我一开始使用的Anzhiyu主题自带的即刻说说界面,但是由于每次写说说都需要重新上传,导致异常的麻烦,以前就从Leonus那里看到了基于Memos的说说界面,碍于以前能力有限,没有搭建Memos,最近在跟着教程走的时候,发现Leonus的教程有一些些的问题,再加上和自己的主题有一些css样式上的差异,就打算重新写一篇魔改教程。

部署Memos

Docker部署

我这里使用的1panel面板,使用bt面板的可以先去看看Leonus的这篇教程:基于Memos实现说说和清单功能
我们先在容器-镜像里选择拉取镜像,仓库选择Docker Hub,镜像名输入neosmemo/memos:latest,点击拉取。

容器-容器中选择创建容器,输入名称,选择你拉取的镜像,在端口中选择添加服务器端口容器端口都输入5230
挂载中点击添加,选择本机目录,在本机目录中填写你要放置的位置,我这里是/root/memos容器目录中输入/var/opt/memos
重启规则可以选择一直重启

设置反代

网站-创建网站-反向代理中输入你的域名,在代理地址中输入127.0.0.1:5230,点击确认即可

使用指南

这里盗取Leonus的图片用于介绍

  • 假如我要发送一篇带B站视频的说说,你可以这样写
    #说说 {洛天依} 洛天依天下第一可爱!
    {bilibili BV1KZ4y157xQ}
  • 音乐,这里使用的是MetingJs,直接使用音乐的链接就行,具体链接要求看MetingJs的要求
    #说说 {洛天依} 虽然我没有病,但是还是好喜欢听这首歌
    {music https://music.163.com/song?id=1494001389}
  • 图片
    #说说 {恋爱记} 把关于本人界面的生涯给魔改成了恋爱记,感觉还不错 ![](https://bu.dusays.com/2023/09/30/65181e3cb366c.png)
  • 链接
    链接似乎会在说说页面有些问题,没法跳转(使用cdn的情况下),本地测试没问题
    #说说 {魔改} 重新把友链自动获取写了一下,这下应该没有什么大问题了,感兴趣的可以看我的这篇文章 [Butterfly 友链魔改之自动获取友链](https://byer.top/posts/c16c2ef2.html)

到了这里,你的memos就正式配置完成了,接下来就是修改你的博客界面了

博客界面生成

创建Memos Pug样式文件

[themes/anzhiyu/layout/includes/page]中创建memos.pug文件,内容如下:
你可以修改相关内容来自定义上方的内容

- let top_background = 'https://bu.dusays.com/2023/09/28/65157df2c2c17.jpg'
- let title = '即刻短文'
- let subTitle = '咸鱼的日常生活。'
- let tips = '随时随地,分享生活'
- let buttonLink = '/about/'
- let buttonText = '关于我'
#memos_page
    .author-content.author-content-item.essayPage.single(style = top_background ? `background: url(${top_background}) left 28% / cover no-repeat;` : "")
        .card-content
          .author-content-item-tips=title
          span.author-content-item-title=subTitle
          .content-bottom
            .tips=tips
          .banner-button-group
            a.banner-button(href=buttonLink)
              i.anzhiyufont.anzhiyu-icon-arrow-circle-right(style='font-size: 1.3rem')
              span.banner-button-text=buttonText
    #talk
    #bber-tips(style='color: var(--anzhiyu-secondtext);')
        ="- 只展示最近30条短文 -"
script(defer data-pjax src=url_for('https://unpkg.com/masonry-layout@4/dist/masonry.pkgd.min.js'))
script(defer data-pjax src=url_for('/js/custom/memos.js'))

masonry.pkgd.min.js文件可以下载下来放置在你的本地目录下,unpkg的访问速度可能有大问题

创建Memos Css样式文件

[themes/anzhiyu/source/css]中创建custom/memos.css,内容如下:

body[data-type="memos"] #page {
    border: 0;
    box-shadow: none !important;
    padding: 0 !important;
    background: transparent !important;
  }
body[data-type="memos"] #page .page-title {
    display: none;
  }
body[data-type="memos"] #web_bg {
    background: var(--anzhiyu-background);
  }

.talk_item{
    background: var(--card-bg);
    border: 1px solid #e0e3ed;
    box-shadow: 0 5px 10px rgb(189 189 189 / 10%);
    transition: all .3s ease-in-out;
    border-radius: 12px;
}

#talk{
    margin-top: 1rem;
}

.talk_item {
    padding: 20px;
    margin-bottom: 15px;
    width: 33%;
}

.avatar {
    margin: 0 !important;
    width: 60px;
    height: 60px;
    border-radius: 10px;
}


.talk_bottom,
.talk_meta {
    display: flex;
    align-items: center;
    width: 100%;
    line-height: 1.5;
}
.talk_bottom{
    justify-content: space-between;
}
.talk_meta .info {
    display: flex;
    flex-direction: column;
    margin-left: 10px;
}
span.talk_nick {
    color: #6dbdc3;
    font-size: 1.2rem;
}

span.talk_date {
    opacity: .6;
}

.talk_content {
    line-height: 1.5;
    margin-top: 10px;
}
.zone_imgbox {
    display: flex;
    flex-wrap: wrap;
    --w: calc(50% - 8px);
    gap: 10px;
    margin-top: 5px;
}
.zone_imgbox a {
    display: block;
    border-radius: 12px;
    width: var(--w);
    aspect-ratio: 1/1;
    position: relative;
}

.zone_imgbox img {
    width: 100%;
    height: 100%;
    margin: 0 !important;
    object-fit: cover;
}
/* 底部 */

.talk_bottom {
    opacity: .9;
}
.talk_bottom .icon {
    color: var(--font-color);
    float: right;
    transition: all .3s;
}

.talk_bottom .icon:hover {
    color: #49b1f5;
}

span.talk_tag{
    font-size: 14px;
}
.talk_content>a {
    margin: 0 3px;
    color: #ff7d73 !important;
}
.talk_content>a:hover{
    text-decoration: none !important;
    color: #ff5143 !important
}

.talk_content iframe {
    margin: 0;
    border-radius: 12px;
    border: var(--leonus-border);
}

@media screen and (max-width: 768px) {
    .talk_item {
        width: 100%;
    }
}

创建Memos Js文件

打开[themes/anzhiyu/source/js],创建custom/memos.js文件,内容如下:

if(1) {
    fetch('https://cnsf.byer.top/data').then(res => res.json()).then(data => { //修改为你的memos地址
        let items = [],
            html = '',
            icon = '<svg viewBox="0 0 512 512"xmlns="http://www.w3.org/2000/svg"class="is-badge icon"><path d="m512 268c0 17.9-4.3 34.5-12.9 49.7s-20.1 27.1-34.6 35.4c.4 2.7.6 6.9.6 12.6 0 27.1-9.1 50.1-27.1 69.1-18.1 19.1-39.9 28.6-65.4 28.6-11.4 0-22.3-2.1-32.6-6.3-8 16.4-19.5 29.6-34.6 39.7-15 10.2-31.5 15.2-49.4 15.2-18.3 0-34.9-4.9-49.7-14.9-14.9-9.9-26.3-23.2-34.3-40-10.3 4.2-21.1 6.3-32.6 6.3-25.5 0-47.4-9.5-65.7-28.6-18.3-19-27.4-42.1-27.4-69.1 0-3 .4-7.2 1.1-12.6-14.5-8.4-26-20.2-34.6-35.4-8.5-15.2-12.8-31.8-12.8-49.7 0-19 4.8-36.5 14.3-52.3s22.3-27.5 38.3-35.1c-4.2-11.4-6.3-22.9-6.3-34.3 0-27 9.1-50.1 27.4-69.1s40.2-28.6 65.7-28.6c11.4 0 22.3 2.1 32.6 6.3 8-16.4 19.5-29.6 34.6-39.7 15-10.1 31.5-15.2 49.4-15.2s34.4 5.1 49.4 15.1c15 10.1 26.6 23.3 34.6 39.7 10.3-4.2 21.1-6.3 32.6-6.3 25.5 0 47.3 9.5 65.4 28.6s27.1 42.1 27.1 69.1c0 12.6-1.9 24-5.7 34.3 16 7.6 28.8 19.3 38.3 35.1 9.5 15.9 14.3 33.4 14.3 52.4zm-266.9 77.1 105.7-158.3c2.7-4.2 3.5-8.8 2.6-13.7-1-4.9-3.5-8.8-7.7-11.4-4.2-2.7-8.8-3.6-13.7-2.9-5 .8-9 3.2-12 7.4l-93.1 140-42.9-42.8c-3.8-3.8-8.2-5.6-13.1-5.4-5 .2-9.3 2-13.1 5.4-3.4 3.4-5.1 7.7-5.1 12.9 0 5.1 1.7 9.4 5.1 12.9l58.9 58.9 2.9 2.3c3.4 2.3 6.9 3.4 10.3 3.4 6.7-.1 11.8-2.9 15.2-8.7z"fill="#1da1f2"></path></svg>';
        data.forEach(item => { items.push(Format(item)) });
        if (items.length == 30) document.querySelector('.limit').style.display = 'block';
        items.forEach(item => {
            html += `
            <div class="talk_item">
                <div class="talk_meta">
                    <img class="nolazyload avatar" src="https://byer.top/img/ava.jpg"> //修改为你自己的头像
                    <div class="info">
                        <span class="talk_nick">${item.avatar}${icon}</span>
                        <span class="talk_date">${item.date}</span>
                    </div>
                </div>
                <div class="talk_content">${item.content}</div>
                <div class="talk_bottom">
                    <div>
                        <span class="talk_tag"># ${item.tag}</span>
                    </div>
                    <a href="javascript:;"onclick="goComment('${item.text}')">
                        <span class="icon">
                            <i class="anzhiyufont anzhiyu-icon-message"></i>
                        </span>
                    </a>
                </div>
            </div>`
        })
        document.getElementById('talk').innerHTML = html
        var elem = document.querySelector('#talk');
        var containerTalkWidth = document.querySelector('.card-content').offsetWidth;
        var containerelemWidth = document.querySelector('.talk_item').offsetWidth;
        var isMobile = window.matchMedia("(max-width: 768px)").matches;
        if (!isMobile) {
            var msnry = new Masonry( elem, {
                // options
                itemSelector: '.talk_item',
                columnWidth: '.talk_item',
                gutter: (containerTalkWidth - containerelemWidth*3)/2,
                percentPosition: true,
                fitWidth: true
            });
        }
    })
    // 页面评论
    function goComment(e) {
        var n = document.querySelector(".el-textarea__inner")
        n.value = `> ${e}\n\n`;
        n.focus();
        var element = document.querySelector('#post-comment .comment-tips').style.display = 'block';
    }
    // 页面内容格式化
    function Format(item) {
        let date = getTime(new Date(item.createdTs * 1000).toString()),
        content = item.content,
        userId = item.creatorUsername,
        avatar = item.creatorName,
        tag = item.content.match(/\{((?!bilibili|music|video).)*?\}/g),
        imgs = content.match(/!\[.*\]\(.*?\)/g), 
        bilibili = content.match(/\{\s*bilibili (.*?)\}/g);
        video = content.match(/\{\s*video (.*?)\}/g);
        music = content.match(/\{\s*music (.*?)\}/g);
        text = ''
        if (imgs) imgs = imgs.map(item => { return item.replace(/!\[.*\]\((.*?)\)/, '$1') })
        if (bilibili) bilibili = bilibili.map(item => { return item.replace(/\{\s*bilibili (.*?)\}/, '$1') })
        if (video) video = video.map(item => { return item.replace(/\{\s*video (.*?)\}/, '$1') })
        if (music) music = music.map(item => { return item.replace(/\{\s*music (.*?)\}/, '$1') })
        if (item.resourceList.length) {
            if (!imgs) imgs = []
            item.resourceList.forEach(t => {
                if (t.externalLink) imgs.push(t.externalLink)
                else imgs.push(`${url}/o/r/${t.id}/${t.publicId}/${t.filename}`)
            })
        }
        text = content.replace(/#(.*?)\s/g, '').replace(/\!\[(.*?)\]\((.*?)\)/g,'').replace(/\{(.*?)\}/g, '').trimEnd()
        content = text.replace(/\[(.*?)\]\((.*?)\)/g, `<a href="$2" target="_blank">@$1</a>`);
        if (imgs) {
            content += `<div class="zone_imgbox">`
            imgs.forEach(e => {
                    content += `<a href="${e}" data-fancybox="gallery" class="fancybox" data-thumb="${e}"><img class="nolazyload" src="${e}"></a>` 
                    text += ` [图片]`
            })
            content += '</div>'
        }
        if (bilibili) {
            content += `<div style='margin-top: 10px; margin-bottom: 10px'>`
            bilibili.forEach(e => {
                content += `<div style='position: relative; padding: 30% 45%; margin-bottom: 10px'><iframe style='position: absolute; width: 100%; height: 100%; left: 0; top: 0;' src="https://player.bilibili.com/player.html?autoplay=0&bvid=${e}" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"></iframe></div>`
                text += ` [B站视频]`
            })
            content += '</div>'
        }
        if (video) {
            content += `<div style='margin-top: 10px; margin-bottom: 10px'>`
            video.forEach(e => {
                content += `<div style='position: relative; padding: 30% 45%; margin-bottom: 10px'><video style='position: absolute; width: 100%; height: 100%; left: 0; top: 0;' src="${e}" controls="controls" preload="auto" width="100%" height="100%"></video></div>`
                text += ` [视频]`
            })
            content += '</div>'
        }
        if (music) {
            content += `<div style='margin-top: 10px; margin-bottom: 10px;height:90px'>`
            music.forEach(e => {
                content += `<meting-js auto="${e}"></meting-js>`
                text += ` [音乐]`
            })
            content += '</div>'
        }
        return {
            content: content,
            avatar: avatar,
            userId: userId,
            tag: tag ? tag[0].replace(/\{(.*?)\}/,'$1') : '无标签',
            date: date,
            text: text.replace(/\[(.*?)\]\((.*?)\)/g, '[链接]')
        }
    }
    // 页面时间格式化
    function getTime(time) {
        let d = new Date(time),
            ls = [d.getFullYear(), d.getMonth() + 1, d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds()];
        for (let i = 0; i < ls.length; i++) {
            ls[i] = ls[i] <= 9 ? '0' + ls[i] : ls[i] + ''
        }
        if (new Date().getFullYear() == ls[0]) return ls[1] + '月' + ls[2] + '日 ' + ls[3] +':'+ ls[4]
        else return ls[0] + '年' + ls[1] + '月' + ls[2] + '日 ' + ls[3] +':'+ ls[4]
    }
}

记得修改一部分的内容,大约位于第二行和第十二行
你的Memosapi地址为https://memos地址/api/v1/memo?creatorId=1&tag=说说&limit=30

创建memos页面

输入hexo new page memos创建memos页面,打开index.md

---
title: 即刻短文
date: 2024-02-12 14:08:50
type: "memos"
comments: true
aside: false
top_img: false
---

完成了这一步之后你就可以访问你的memos界面去查看样式是否正确了,如果不正确,你可以对其进行微调(从头改css,爱莫能助)

首页说说轮播

修改[themes/anzhiyu/layout/includes/bbTimeList.pug]文件,将其全部替换如下:

#bbTimeList.bbTimeList.container
    i.anzhiyufont.anzhiyu-icon-jike.bber-logo.fontbold(onclick=`pjax.loadUrl("/memos/")`,title="即刻短文", aria-hidden="true")
    #bbtalk.swiper-container.swiper-no-swiping.essay_bar_swiper_container(tabindex="-1")
      #bber-talk.swiper-wrapper(onclick=`pjax.loadUrl("/memos/")`)
        .li-style.ul.talk-list 说说加载中。。。
    i.bber-gotobb.anzhiyufont.anzhiyu-icon-circle-arrow-right( onclick=`pjax.loadUrl("/essay/")`,title="查看全文")

script(src=url_for(theme.home_top.swiper.swiper_js))
script(defer data-pjax).
  // 存数据
  function saveData(name, data) { localStorage.setItem(name, JSON.stringify({ 'time': Date.now(), 'data': data })) };
  // 取数据
  function loadData(name, time) {
      var d = JSON.parse(localStorage.getItem(name));
      if (d) {
          var t = Date.now() - d.time
          if (-1 < t && t < (time * 60000)) return d.data;
      }
      return 0;
  };

  var talkTimer = null;
  function indexTalk() {
      if (talkTimer) {
          clearInterval(talkTimer)
          talkTimer = null;
      }
      if (!document.getElementById('bber-talk')) return

      function toText(ls) {
          var text = []
          ls.forEach(item => {
              text.push(item.content
              .replace(/#(.*?)\s/g, '')
              .replace(/\{((?!bilibili|music).)*?\}/g, '')
              .replace(/(\!\[(.*?)\]\((.*?)\)\s*)+/g, '<i class="anzhiyufont anzhiyu-icon-images"></i>')
              .replace(/(\[(.*?)\]\((.*?)\)\s*)+/g, '<i class="anzhiyufont anzhiyu-icon-link"></i>')
              .replace(/(\{\s*bilibili (.*?)\}\s*)+/g, '<i class="anzhiyufont anzhiyu-icon-bilibili"></i>')
              .replace(/(\{\s*video (.*?)\}\s*)+/g, '<i class="anzhiyufont anzhiyu-icon-video"></i>')
              .replace(/(\{\s*music (.*?)\}\s*)+/g, '<i class="anzhiyufont anzhiyu-icon-music"></i>')
              .trimEnd())
          });
          return text
      }

      function talk(ls) {
          var html = ''
          ls.forEach((item, i) => { html += `<li class="item item-${i + 1}">${item}</li>` });
          var box = document.querySelector("#bber-talk .talk-list")
          box.innerHTML = html;
          talkTimer = setInterval(() => {
              box.appendChild(box.children[0]);
          }, 5000);
      }

      var d = loadData('talk', 10);
      if (d) talk(d);
      else {
          fetch('https://cnsf.byer.top/data').then(res => res.json()).then(data => {
              data = toText(data)
              talk(data);
              saveData('talk', data);
          })
      }
  }
  indexTalk();

不要忘记将59行的链接替换为自己的链接
修改[themes/anzhiyu/source/js/utils.js]文件,关闭首页说说的swiper动画(单纯因为是我不会改动画)
大约位于459行,改为
if (document.getElementById("bbTimeList")) return;