notionNext集成memos说说插件


这里使用notionNext版本是4.7.11

Node版本:20.7.0

image.png

📝 导航

改动文件:添加和修改的文件

image.png

  • 1.增加新的页面路由

    1. 在项目根目录 /pages文件夹中新建一个名为memos的文件夹,并在文件夹中创建一个名为Index.js的文件夹

      image.png

    2. 在pages目录下memos/index.js添加以下代码

      在4.7.11版本中,使用的是DynamicLayout动态布局

      // import { useRouter } from 'next/router'
      // import { getLayoutByTheme } from '@/themes/theme'
      import { DynamicLayout } from '@/themes/theme'
      import { siteConfig } from '@/lib/config'
      import { getGlobalData } from '@/lib/db/getSiteData'
      import React from 'react'
      import BLOG from '@/blog.config'
      
      const MemosIndex = props => {
        const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG)
        return <DynamicLayout theme={theme} layoutName='LayoutMemos' {...props} />
        //下面的方法适用于4.4.2及以下版本
        // const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
        // return <Layout {...props} />
      }
      
      export async function getStaticProps() {
        const from = 'tag-index-props'
        const props = await getGlobalData({ from })
        delete props.allPages
        return {
          props,
          revalidate: parseInt(BLOG.NEXT_REVALIDATE_SECOND)
        }
      }
      
      export default MemosIndex
      
  • 2.主题配置增加新页面的主题索引

    在 blog.config.js 文件的 LAYOUT_MAPPINGS中增加路径 /memos 的组件映射

    NotionNext 4.3版本才支持LAYOUT_MAPPINGS配置,如果不是这个版本,修改更复杂些,需要自己到themes/theme.js中修改对应的映射,建议升级到4.7.11版本。

    image.png

    即添加以下代码到指定位置:

    '/memos' : 'LayoutMemos',

    然后在blog.config.js指定位置,添加以下环境变量配置:

    image.png

    在blog.config.js文件中增加:

    //**************** 自定义配置官方没有提供,需要自己手动引入 start  ****************
    //url匹配
    URL_HEADER_PATHS: process.env.NEXT_PUBLIC_URL_HEADER_PATHS || ['/archive', '/category', '/tag','/memos'], // 随记url匹配
    // Memos 随记 @see https://github.com/usememos/usememos
    MEMOS_ENABLE: process.env.NEXT_PUBLIC_MEMOS || false, // 开关
    MEMOS_HOST: process.env.NEXT_PUBLIC_MEMOS_HOST || 'https://memos.chenge.ink', // 随记地址
    MEMOS_LIMIT: process.env.NEXT_PUBLIC_MEMOS_LIMIT || '10',// 随记数量
    MEMOS_DOM_ID: process.env.NEXT_PUBLIC_MEMOS_DOM_ID || '#memos',// 随记dom id
    MEMOS_CREATOR_ID: process.env.NEXT_PUBLIC_MEMOS_CREATOR_ID || '1',// 随记创建者id
    MEMOS_USERNAME: process.env.NEXT_PUBLIC_MEMOS_USERNAME || 'keney', // 随记用户名
    MEMOS_NAME: process.env.NEXT_PUBLIC_MEMOS_NAME || 'keney', // 随记名称
    
    //**************** 自定义配置官方没有提供,需要自己手动引入 start  ****************
  • 3.主题的代码修改,增加Memos模块

    hexo-change

    这是hexo主题,我这里只是复制了一份进行修改的

    建议将你选定的主题复制一份出来,重命名为自己的名字(比如我复制了hexo主题文件夹并命名为chenge),然后将主题切换为自己的主题,后续的主题修改就都在自己创建的主题中,不会和NotionNext官方的更新冲突。

    image.png

    创建:CatHeader.js

    import Link from 'next/link'
    import TagItemMini from './TagItemMini'
    import { useGlobal } from '@/lib/global'
    import NotionIcon from '@/components/NotionIcon'
    import LazyImage from '@/components/LazyImage'
    import { formatDateFmt } from '@/lib/utils/formatDate'
    import { siteConfig } from '@/lib/config'
    import { useRouter } from 'next/router';
    
    /**
     * 归档/近期/标签页的头部
     * @param {*} props
     * @returns
     */
    export default function CatHeader({ post, siteInfo }) {
      const { locale, fullWidth } = useGlobal()
      const router = useRouter()
    
      const getCategoryName = () => {
        switch(router.route) {
          case '/archive':
            return (
              <div>
                <i className="fas fa-clock-rotate-left"> &nbsp; </i>
                {locale.NAV.ARCHIVE}
              </div>  )
          case '/category':
            return (
              <div>
                <i className="fas fa-th"> &nbsp; </i>
                {locale.COMMON.CATEGORY}
              </div>  ); 
          case '/tag':
            return (
              <div>
                <i className="fas fa-tag"> &nbsp; </i>
                {locale.COMMON.TAGS}
              </div>  ); 
          case '/memos':
            return (
              <div>
                <i className="fa-solid fa-wand-magic-sparkles"> &nbsp; </i>
                {locale.COMMON.MEMOS}
              </div>  ); 
          default:
            return 'UnKnown'; 
        }
      }
    
      const headerImage = post?.pageCover ? post.pageCover : siteInfo?.pageCover
    
      return (
        <div id="header" className="w-full h-[50vh] relative md:flex-shrink-0 z-10 min-h-[25rem] min-w-[25rem] flex flex-col justify-center items-center" >
          <LazyImage priority={true} src={headerImage} className='w-full h-full object-cover object-center absolute top-0'/>
          <div id = 'article-header-cover' className='bg-black bg-opacity-70 absolute top-0 w-full h-full py-10' />
          <header className="flex flex-col justify-center items-center z-10">
            <div className="leading-snug font-bold xs:text-4xl sm:text-4xl md:text-5xl md:leading-snug text-4xl shadow-text-md flex justify-center text-center text-white">
              {getCategoryName()}
            </div>
          </header>
          {/* 波浪效果 */}
          <div id="waves" className="absolute bottom-0 w-full">
              <svg className="waves" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" viewBox="0 24 150 28" preserveAspectRatio="none" shapeRendering="auto">
                <defs>
                  <path id="gentle-wave" d="M-160 44c30 0 58-18 88-18s 58 18 88 18 58-18 88-18 58 18 88 18 v44h-352z" />
                </defs>
                <g className="parallax">
                  <use xlinkHref="#gentle-wave" x="48" y="0" />
                  <use xlinkHref="#gentle-wave" x="48" y="3" />
                  <use xlinkHref="#gentle-wave" x="48" y="5" />
                  <use xlinkHref="#gentle-wave" x="48" y="7" />
                </g>
              </svg>
            </div>
        </div>
      )
    }
    

    创建 MemosBlog.js 文件

    新增一个MemosBlog组件,专门用于呈现Memos内容。我的路径是 themes/chenge/components/MemosBlog.js,你按照自己的主题找对应位置即可。

    import React, { useEffect, useState } from 'react';
    import { loadExternalResource } from '@/lib/utils';
    import { siteConfig } from '@/lib/config'
    
    const BlogMemos = () => {
        const [isResourcesLoaded, setResourcesLoaded] = useState(false);
        const [error, setError] = useState(null);
    
        // 加载资源
        useEffect(() => {
            const loadResources = async () => {
                try {
                    await Promise.all([
                        loadExternalResource('/css/memos.css', 'css'),
                        loadExternalResource('/css/highlight.github.min.css', 'css'),
                        loadExternalResource('/js/lazyload.min.js?v=17.8.3', 'js'),
                        loadExternalResource('/js/marked.min.js?v=11.1.1', 'js'),
                        loadExternalResource('/js/view-image.min.js?v=2.0.2', 'js'),
                        loadExternalResource('/js/highlight.min.js?v=11.9.0', 'js'),
                        loadExternalResource('/js/moment.min.js?v=2.30.1', 'js'),
                    ]);
                    
                    await loadExternalResource('/js/moment.twitter.js', 'js');
                    setResourcesLoaded(true);
                } catch (err) {
                    console.error('Failed to load resources:', err);
                    setError(err);
                }
            };
    
            loadResources();
        }, []);
    
        // 从配置文件读取 Memos 配置
    // var memoConfig = {
    //     host: 'https://memos.chenge.ink',
    //     limit: '10',
    //     creatorId: '1',
    //     domId: '#memos',
    //     username: '陈源泉',
    //     name: '尘歌'
    // }
    
        // 初始化 Memos
        useEffect(() => {
            if (isResourcesLoaded) {
                try {
                    if(!siteConfig('MEMOS_ENABLE')){
                        console.log("memos is not enabled")
                        return
                    }
                    // 设置全局配置
                     // 将配置注入到window对象中供memos.js使用
                    //注意避免 污染全局变量 todo:后续可以优化,使用createContext状态管理
                    window.memoConfig = {
                        host: siteConfig('MEMOS_HOST'),
                        limit: siteConfig('MEMOS_LIMIT'),
                        creatorId: siteConfig('MEMOS_CREATOR_ID'),
                        domId: siteConfig("MEMOS_DOM_ID"),
                        username: siteConfig('MEMOS_USERNAME'),
                        name: siteConfig('MEMOS_NAME')
                    };
                    
    
                    const script = document.createElement('script');
                    script.src = '/js/memos.js';
                    script.async = true;
                    document.body.appendChild(script);
    
                    return () => {
                        document.body.removeChild(script);
                    };
                } catch (err) {
                    console.error('Failed to initialize memos:', err);
                    setError(err);
                }
            }
        }, [isResourcesLoaded]);
    
        if (error) {
            return <div>Error loading memos: {error.message}</div>;
        }
        
        return (
            <section id="main" className="container">
                <h2>{siteConfig('MEMOS_ENABLE')?siteConfig('MEMOS_NAME'):''}</h2>
                <blockquote id="tag-filter" className="filter">
                    <div id="tags"></div>
                </blockquote>
                <div id="memos" className="memos">
                    {!isResourcesLoaded && <div>Loading...</div>}
                </div>
            </section>
        );
    };
    
    export default BlogMemos;
    

    然后在hexo-change主题目录下index文件中,引入对应的组件

    image.png

    然后在themes\hexo-change\index.js文件中的文章详情或者其他位置增加代码:

    /**
     * Memos 说说
     * @param {*} props
     * @returns
     */
    const LayoutMemos = (props) => {
      const memoPageInfo = {
        id: "9e6c78642def47bcbabe35f526307639", // 固定ID,确保唯一性
        type: "Memos",
        title: "我的说说",
      };
      return  (
      <div className="w-full lg:hover:shadow rounded-md lg:rounded-md lg:px-2 lg:py-4 article">
        <div id="article-wrapper" className="overflow-x-auto flex-grow mx-auto md:w-full px-3 font-serif">
          <article itemScope itemType="https://schema.org/Movie" className="subpixel-antialiased overflow-y-hidden overflow-x-hidden" >
            {/* Notion文章主体 */}
            <section className='justify-center mx-auto max-w-2xl lg:max-w-full'>
             {/* 加载 Memos */}
                <BlogMemos {...props}/>
            </section>
          </article>
          <div className='pt-4 border-dashed'></div>
          {/* 评论互动 */}
          <div className="duration-200 overflow-x-auto px-3">
            <Comment frontMatter={memoPageInfo} />
          </div>
        </div>
      </div>)
    }

    在themes\hexo-change\index.js文件底部暴露LayoutMemos

    image.png

  • 3.1引入静态资源

    由于MemosBlog组件中引入了外部资源,如一些外部的css和js样式来实现说说页的样式和动态加载,需要将对应的这些文件分别放入public文件夹下。

    1. 在项目根目录 /pages中新建一个名为Memos的文件夹,并在文件夹中创建一个名为Index.js的文件夹

      memos.js

      这里需要注意:我在MemosGallery中的图片爬取就是在memos服务中发布的,这里需要增加过滤条件,来排除大量图片,在memos中大量图片我使用的标签是#images,看个人需求了。

      (function () {
          function logElementEvent(eventName, element) {
              console.log(Date.now(), eventName, element.getAttribute("data-src"));
          }
      
          var callback_enter = function (element) {
              logElementEvent("🔑 ENTERED", element);
          };
          var callback_exit = function (element) {
              logElementEvent("🚪 EXITED", element);
          };
          var callback_loading = function (element) {
              logElementEvent("⌚ LOADING", element);
          };
          var callback_loaded = function (element) {
              logElementEvent("👍 LOADED", element);
          };
          var callback_error = function (element) {
              logElementEvent("💀 ERROR", element);
          };
          var callback_finish = function () {
              logElementEvent("✔️ FINISHED", document.documentElement);
          };
          var callback_cancel = function (element) {
              logElementEvent("🔥 CANCEL", element);
          };
      
          var ll = new LazyLoad({
              class_applied: "lz-applied",
              class_loading: "lz-loading",
              class_loaded: "lz-loaded",
              class_error: "lz-error",
              class_entered: "lz-entered",
              class_exited: "lz-exited",
              // Assign the callbacks defined above
              callback_enter: callback_enter,
              callback_exit: callback_exit,
              callback_cancel: callback_cancel,
              callback_loading: callback_loading,
              callback_loaded: callback_loaded,
              callback_error: callback_error,
              callback_finish: callback_finish
          });
      })();
      // Lazyload End
      
      // Memos Start
      
      const memoObj = window.memoConfig;
      // if (typeof (memos) !== "undefined") {
      //     for (var key in memos) {
      //         if (memos[key]) {
      //             memoObj[key] = memos[key];
      //         }
      //     }
      // }
      //memoss获取的是一个div盒子
      if (typeof (memos) !== "undefined") {
          //配置对象通常需要保留所有值,包括 falsy 值
          //一次性合并所有属性,性能通常更好
          Object.assign(memoObj, memos);
      }
      
      var limit = memoObj.limit
      var memosHost = memoObj.host.replace(/\/$/, '')
      var memoUrl = memosHost + "/api/v1/memos?pageSize=" + memoObj.limit + "&filter=visibilities==['PUBLIC']&&creator=='users/" + memoObj.creatorId + "'&rowStatus==ACTIVE"
      var page = 1,
          offset = 0,
          nextLength = 0,
          nextDom = '';
      var tag='';
      var btnRemove = 0;
      var pageToken = null;
      var memoDom = document.querySelector(memoObj.domId);
      var load = '<button class="load-btn button-load">努力加载中……</button>'
      if (memoDom) {
          memoDom.insertAdjacentHTML('afterend', load);
          getFirstList() // 首次加载数据
          // 添加 button 事件监听器
          btnRemove = 0;
          var btn = document.querySelector("button.button-load");
          btn.addEventListener("click", function () {
              btn.textContent = '努力加载中……';
              updateHTMl(nextDom)
              if (nextLength < limit) { // 返回数据条数小于限制条数,隐藏
                  document.querySelector("button.button-load").remove()
                  btnRemove = 1
                  return
              }
              getNextList()
          });
      }
      
      function getFirstList() {
          var memoUrl_first = memoUrl;
          fetch(memoUrl_first).then(res => res.json()).then(resdata => {
              updateHTMl(resdata.memos)
              var nowLength = resdata.memos.length
              pageToken = resdata.nextPageToken
      
              if (nowLength < limit) { // 返回数据条数小于 limit 则直接移除“加载更多”按钮,中断预加载
                  document.querySelector("button.button-load").remove()
                  btnRemove = 1
                  return
              }
              page++
              offset = limit * (page - 1)
              getNextList()
          });
      }
      // 预加载下一页数据
      function getNextList() {
          if (!pageToken) {
              document.querySelector("button.button-load").remove();
              btnRemove = 1;
              return;
          }
      
          var memoUrl_next = memoUrl + "&pageToken=" + pageToken;
      
          fetch(memoUrl_next)
              .then(res => res.json())
              .then(resdata => {
                  nextDom = resdata.memos;
                  nextLength = nextDom.length;
                  pageToken = resdata.nextPageToken;
      
                  if (nextLength < 1) { // 返回数据条数为 0 ,隐藏
                      document.querySelector("button.button-load").remove();
                      btnRemove = 1;
                      return;
                  }
              });
      }
      
      // 标签选择
      // ⚠ 0605备注,目前Tag的API改动后消失,暂时无法支持点击tag筛选过滤memos,无法正确显示。
      document.addEventListener('click', function (event) {
          var target = event.target;
          if (target.tagName.toLowerCase() === 'a' && target.getAttribute('href').startsWith('#')) {    
              event.preventDefault();
              tag = target.getAttribute('href').substring(1); // 获取标签名
              if (btnRemove) {    // 如果 botton 被 remove
                  btnRemove = 0;
                  memoDom.insertAdjacentHTML('afterend', load);
                  // 添加 button 事件监听器
                  var btn = document.querySelector("button.button-load");
                  btn.addEventListener("click", function () {
                      btn.textContent = '努力加载中……';
                      updateHTMl(nextDom)
                      if (nextLength < limit) { // 返回数据条数小于限制条数,隐藏
                          document.querySelector("button.button-load").remove()
                          btnRemove = 1
                          return
                      }
                      getNextList()
                  });
                  
              }        
              getTagFirstList();
              var filterElem = document.getElementById('tag-filter');
              filterElem.style.display = 'block';    // 显示过滤器
              var tags = document.getElementById('tags');
              var tagresult = `Filter: <span class='tag-span'><a rel='noopener noreferrer' href=''>#${tag}</a></span>`
              tags.innerHTML = tagresult;
              scrollTo(0,0);    // 回到顶部
          }
      });
      
      function getTagFirstList() {
          page = 1;
          offset = 0;
          nextLength = 0;
          nextDom = '';
          memoDom.innerHTML = "";
          var memoUrl_tag = memoUrl + "&tag=" + tag;
          fetch(memoUrl_tag).then(res => res.json()).then(resdata => {
              updateHTMl(resdata);
              var nowLength = resdata.length
              if (nowLength < limit) { // 返回数据条数小于 limit 则直接移除“加载更多”按钮,中断预加载
                  document.querySelector("button.button-load").remove()
                  btnRemove = 1
                  return
              }
              page++
              offset = limit * (page - 1)
              getNextList()
          });
      }
      
      // 标签选择 end
      
      // 插入 html
      function updateHTMl(data) {
          var memoResult = "", resultAll = "";
      
          // 解析 TAG 标签,添加样式
          const TAG_REG = /#([^\s#]+?)(?=\s|$)/g;
      
          // 解析 BiliBili
          const BILIBILI_REG = /<a\shref="https:\/\/www\.bilibili\.com\/video\/((av[\d]{1,10})|(BV([\w]{10})))\/?">.*<\/a>/g;
          // 解析网易云音乐
          const NETEASE_MUSIC_REG = /<a\shref="https:\/\/music\.163\.com\/.*id=([0-9]+)".*?>.*<\/a>/g;
          // 解析 QQ 音乐
          const QQMUSIC_REG = /<a\shref="https\:\/\/y\.qq\.com\/.*(\/[0-9a-zA-Z]+)(\.html)?".*?>.*?<\/a>/g;
          // 解析腾讯视频
          const QQVIDEO_REG = /<a\shref="https:\/\/v\.qq\.com\/.*\/([a-z|A-Z|0-9]+)\.html".*?>.*<\/a>/g;
          // 解析 Spotify
          const SPOTIFY_REG = /<a\shref="https:\/\/open\.spotify\.com\/(track|album)\/([\s\S]+)".*?>.*<\/a>/g;
          // 解析优酷视频
          const YOUKU_REG = /<a\shref="https:\/\/v\.youku\.com\/.*\/id_([a-z|A-Z|0-9|==]+)\.html".*?>.*<\/a>/g;
          //解析 Youtube
          const YOUTUBE_REG = /<a\shref="https:\/\/www\.youtube\.com\/watch\?v\=([a-z|A-Z|0-9]{11})\".*?>.*<\/a>/g;
      
          // Marked Options
          marked.setOptions({
              breaks: true,
              smartypants: true,
              langPrefix: 'language-',
              highlight: function (code, lang) {
                  const language = hljs.getLanguage(lang) ? lang : 'plaintext';
                  return hljs.highlight(code, { language }).value;
              },
          });
      
          // Memos Content
          for (var i = 0; i < data.length; i++) {
              var memoContREG = data[i].content
                  .replace(TAG_REG, "<span class='tag-span'><a rel='noopener noreferrer' href='#$1'>#$1</a></span>")
      
              // For CJK language users
              // 用 PanguJS 自动处理中英文混合排版
              // 在 index.html 引入 JS:<script type="text/javascript" src="assets/js/pangu.min.js?v=4.0.7"></script>
              // 把下面的 memoContREG = marked.parse(memoContREG) 改为:memoContREG = marked.parse(pangu.spacing(memoContREG))
      
              memoContREG = marked.parse(memoContREG)
                  .replace(BILIBILI_REG, "<div class='video-wrapper'><iframe src='//www.bilibili.com/blackboard/html5mobileplayer.html?bvid=$1&as_wide=1&high_quality=1&danmaku=0' scrolling='no' border='0' frameborder='no' framespacing='0' allowfullscreen='true' style='position:absolute;height:100%;width:100%;'></iframe></div>")
                  .replace(YOUTUBE_REG, "<div class='video-wrapper'><iframe src='https://www.youtube.com/embed/$1' title='YouTube video player' frameborder='0' allow='accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture' allowfullscreen title='YouTube Video'></iframe></div>")
                  .replace(NETEASE_MUSIC_REG, "<meting-js auto='https://music.163.com/#/song?id=$1'></meting-js>")
                  .replace(QQMUSIC_REG, "<meting-js auto='https://y.qq.com/n/yqq/song$1.html'></meting-js>")
                  .replace(QQVIDEO_REG, "<div class='video-wrapper'><iframe src='//v.qq.com/iframe/player.html?vid=$1' allowFullScreen='true' frameborder='no'></iframe></div>")
                  .replace(SPOTIFY_REG, "<div class='spotify-wrapper'><iframe style='border-radius:12px' src='https://open.spotify.com/embed/$1/$2?utm_source=generator&theme=0' width='100%' frameBorder='0' allowfullscreen='' allow='autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture' loading='lazy'></iframe></div>")
                  .replace(YOUKU_REG, "<div class='video-wrapper'><iframe src='https://player.youku.com/embed/$1' frameborder=0 'allowfullscreen'></iframe></div>")
                  .replace(YOUTUBE_REG, "<div class='video-wrapper'><iframe src='https://www.youtube.com/embed/$1' title='YouTube video player' frameborder='0' allow='accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture' allowfullscreen title='YouTube Video'></iframe></div>")
      
              // 解析内置资源文件
              if (data[i].resources && data[i].resources.length > 0) {
                  var resourceList = data[i].resources;
                  var imgUrl = '', resUrl = '', resImgLength = 0;
      
                  for (var j = 0; j < resourceList.length; j++) {
                      var resType = resourceList[j].type;
                      var resFilename = resourceList[j].filename;
                      var resName = resourceList[j].name;
                      var resexlink = resourceList[j].externalLink;
                      var resLink = '';
      
                      if (resexlink) {
                          resLink = resexlink
                      } else {
                          var fileId = resourceList[j].publicId || resName
                          resLink = memosHost+'/file/'+fileId+ '/' + resFilename
                      }
                      if (resType.startsWith('image')) {
                          imgUrl += '<div class="resimg"><img loading="lazy" src="' + resLink + '"/></div>'
                          resImgLength = resImgLength + 1
                      } else {
                          resUrl += '<a target="_blank" rel="noreferrer" href="' + resLink + '">' + resFilename + '</a>'
                      }
                  }
                  if (imgUrl) {
                      var resImgGrid = ""
                      if (resImgLength !== 1) { var resImgGrid = "grid grid-" + resImgLength }
                      memoContREG += '<div class="resource-wrapper "><div class="images-wrapper">' + imgUrl + '</div></div>'
                  }
                  if (resUrl) {
                      memoContREG += '<div class="resource-wrapper "><p class="datasource">' + resUrl + '</p></div>'
                  }
              }
              //console.log(memoContREG)
              memoResult += '<li class="timeline"><div class="memos__content"><div class="memos__text"><div class="memos__userinfo"><div>' + memoObj.name + '</div><div><svg viewBox="0 0 24 24" aria-label="认证账号" class="memos__verify"><g><path d="M22.5 12.5c0-1.58-.875-2.95-2.148-3.6.154-.435.238-.905.238-1.4 0-2.21-1.71-3.998-3.818-3.998-.47 0-.92.084-1.336.25C14.818 2.415 13.51 1.5 12 1.5s-2.816.917-3.437 2.25c-.415-.165-.866-.25-1.336-.25-2.11 0-3.818 1.79-3.818 4 0 .494.083.964.237 1.4-1.272.65-2.147 2.018-2.147 3.6 0 1.495.782 2.798 1.942 3.486-.02.17-.032.34-.032.514 0 2.21 1.708 4 3.818 4 .47 0 .92-.086 1.335-.25.62 1.334 1.926 2.25 3.437 2.25 1.512 0 2.818-.916 3.437-2.25.415.163.865.248 1.336.248 2.11 0 3.818-1.79 3.818-4 0-.174-.012-.344-.033-.513 1.158-.687 1.943-1.99 1.943-3.484zm-6.616-3.334l-4.334 6.5c-.145.217-.382.334-.625.334-.143 0-.288-.04-.416-.126l-.115-.094-2.415-2.415c-.293-.293-.293-.768 0-1.06s.768-.294 1.06 0l1.77 1.767 3.825-5.74c.23-.345.696-.436 1.04-.207.346.23.44.696.21 1.04z"></path></g></svg></div><div class="memos__id">@' + memoObj.username + '</div></div><p>' + memoContREG + '</p></div><div class="memos__meta"><small class="memos__date">' + moment(data[i].createTime).twitter() + ' • 来自「<a href="' + memoObj.host + '/m/' + data[i].uid + '" target="_blank">Memos</a>」</small></div></div></li>'
          }
          var memoBefore = '<ul class="">'
          var memoAfter = '</ul>'
          resultAll = memoBefore + memoResult + memoAfter
          memoDom.insertAdjacentHTML('beforeend', resultAll);
          //取消这行注释解析豆瓣电影和豆瓣阅读
          // fetchDB()
          document.querySelector('button.button-load').textContent = '加载更多';
          // 更新DOM后调用highlightAll进行代码高亮
          if (window.hljs) {
              window.hljs.highlightAll();
          }
      }
      // Memos End
      
      // 解析豆瓣 Start
      // 文章内显示豆瓣条目 https://immmmm.com/post-show-douban-item/
      // 解析豆瓣必须要API,请找朋友要权限,或自己按 https://github.com/eallion/douban-api-rs 这个架设 API,非常简单,资源消耗很少
      // 已内置样式,修改 API 即可使用
      function fetchDB() {
          var dbAPI = "https://api.example.com/";  // 修改为自己的 API
          var dbA = document.querySelectorAll(".timeline a[href*='douban.com/subject/']:not([rel='noreferrer'])") || '';
          if (dbA) {
              for (var i = 0; i < dbA.length; i++) {
                  _this = dbA[i]
                  var dbHref = _this.href
                  var db_reg = /^https\:\/\/(movie|book)\.douban\.com\/subject\/([0-9]+)\/?/;
                  var db_type = dbHref.replace(db_reg, "$1");
                  var db_id = dbHref.replace(db_reg, "$2").toString();
                  if (db_type == 'movie') {
                      var this_item = 'movie' + db_id;
                      var url = dbAPI + "movies/" + db_id;
                      if (localStorage.getItem(this_item) == null || localStorage.getItem(this_item) == 'undefined') {
                          fetch(url).then(res => res.json()).then(data => {
                              let fetch_item = 'movies' + data.sid;
                              let fetch_href = "https://movie.douban.com/subject/" + data.sid + "/"
                              localStorage.setItem(fetch_item, JSON.stringify(data));
                              movieShow(fetch_href, fetch_item)
                          });
                      } else {
                          movieShow(dbHref, this_item)
                      }
                  } else if (db_type == 'book') {
                      var this_item = 'book' + db_id;
                      var url = dbAPI + "v2/book/id/" + db_id;
                      if (localStorage.getItem(this_item) == null || localStorage.getItem(this_item) == 'undefined') {
                          fetch(url).then(res => res.json()).then(data => {
                              let fetch_item = 'book' + data.id;
                              let fetch_href = "https://book.douban.com/subject/" + data.id + "/"
                              localStorage.setItem(fetch_item, JSON.stringify(data));
                              bookShow(fetch_href, fetch_item)
                          });
                      } else {
                          bookShow(dbHref, this_item)
                      }
                  }
              }// for end
          }
      }
      
      function movieShow(fetch_href, fetch_item) {
          var storage = localStorage.getItem(fetch_item);
          var data = JSON.parse(storage);
          var db_star = Math.ceil(data.rating);
          var db_html = "<div class='post-preview'><div class='post-preview--meta'><div class='post-preview--middle'><h4 class='post-preview--title'><a target='_blank' rel='noreferrer' href='" + fetch_href + "'>《" + data.name + "》</a></h4><div class='rating'><div class='rating-star allstar" + db_star + "'></div><div class='rating-average'>" + data.rating + "</div></div><time class='post-preview--date'>导演:" + data.director + " / 类型:" + data.genre + " / " + data.year + "</time><section class='post-preview--excerpt'>" + data.intro.replace(/\s*/g, "") + "</section></div></div><img referrer-policy='no-referrer' loading='lazy' class='post-preview--image' src=" + data.img + "></div>"
          var db_div = document.createElement("div");
          var qs_href = ".timeline a[href='" + fetch_href + "']"
          var qs_dom = document.querySelector(qs_href)
          qs_dom.parentNode.replaceChild(db_div, qs_dom);
          db_div.innerHTML = db_html
      }
      
      function bookShow(fetch_href, fetch_item) {
          var storage = localStorage.getItem(fetch_item);
          var data = JSON.parse(storage);
          var db_star = Math.ceil(data.rating.average);
          var db_html = "<div class='post-preview'><div class='post-preview--meta'><div class='post-preview--middle'><h4 class='post-preview--title'><a target='_blank' rel='noreferrer' href='" + fetch_href + "'>《" + data.title + "》</a></h4><div class='rating'><div class='rating-star allstar" + db_star + "'></div><div class='rating-average'>" + data.rating.average + "</div></div><time class='post-preview--date'>作者:" + data.author + " </time><section class='post-preview--excerpt'>" + data.summary.replace(/\s*/g, "") + "</section></div></div><img referrer-policy='no-referrer' loading='lazy' class='post-preview--image' src=" + data.images.medium + "></div>"
          var db_div = document.createElement("div");
          var qs_href = ".timeline a[href='" + fetch_href + "']"
          var qs_dom = document.querySelector(qs_href)
          qs_dom.parentNode.replaceChild(db_div, qs_dom);
          db_div.innerHTML = db_html
      }
      // 解析豆瓣 End
      
      // Images lightbox
      window.ViewImage && ViewImage.init('.container img');
      
      // Memos Total Start
      // Get Memos total count
      // function getTotal() {
      //     var totalUrl = memos + "/api/v1/memos/stats?name=users/" + memo.creatorId + "&filter=visibilities==['PUBLIC']";
      //     var accessToken = memo.accessToken;
      //     fetch(totalUrl, {
      //         method: 'GET',
      //         headers: {
      //             'Authorization': 'Bearer ' + accessToken
      //         }
      //     }).then(res => res.json()).then(resdata => {
      //         if (resdata && resdata.stats) {
      //             var stats = resdata.stats;
      //             var allnums = Object.values(stats).reduce((acc, count) => acc + count, 0);
      //             var memosCount = document.getElementById('total');
      //             memosCount.innerHTML = allnums;
      //         }
      //     }).catch(err => {
      //         // Handle the error here
      //         console.error(err);
      //     });
      // }
      
      // window.onload = getTotal;
      // Memos Total End
      
      // Toggle Darkmode
      // const localTheme = window.localStorage && window.localStorage.getItem("theme");
      // const themeToggle = document.querySelector(".theme-toggle");
      
      // if (localTheme) {
      //     document.body.classList.remove("light-theme", "dark-theme");
      //     document.body.classList.add(localTheme);
      // }
      
      // themeToggle.addEventListener("click", () => {
      //     const themeUndefined = !new RegExp("(dark|light)-theme").test(document.body.className);
      //     const isOSDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
      
      //     if (themeUndefined) {
      //         if (isOSDark) {
      //             document.body.classList.add("light-theme");
      //         } else {
      //             document.body.classList.add("dark-theme");
      //         }
      //     } else {
      //         document.body.classList.toggle("light-theme");
      //         document.body.classList.toggle("dark-theme");
      //     }
      
      //     window.localStorage &&
      //         window.localStorage.setItem(
      //             "theme",
      //             document.body.classList.contains("dark-theme") ? "dark-theme" : "light-theme",
      //         );
      // });
      // Darkmode End
      
  • 4.在Notion Blog Database新建Memos菜单

    在notion模板中修改:

    增加memos路径和对应参数

    image.png

    iconfont icon-magic  

效果:

image.png

📎 参考文章

本地调试 env配置:

.env、.env.local、.env.prod,优先级不同,本地调试可以自行选择,这些文件可以不提交到仓库

image.png

导航页是否需要背景图:

修改:themes\hexo-change\index.js

之前是注释的那部分:

// 文章页显示文章头,与下面注释的代码二选一
// const headerSlot = post ? (
//   <PostHero {...props} />
// ) : router.route === '/' &&
//   siteConfig('HEXO_HOME_BANNER_ENABLE', null, CONFIG) ? (
//   <Hero {...props} />
// ) : null

// 归档/分类/标签/说说页面的头部组件,
const headerPathsStr = siteConfig('URL_HEADER_PATHS', null, CONFIG);
// 将字符串转换为数组
// const headerPaths = JSON.parse(headerPathsStr);
//将字符串转换为数组,"['/archive', '/category', '/tag','/memos']"转换为['/archive', '/category', '/tag','/memos']
// console.log("headerPaths",headerPaths)
// 根据不同路由显示不同的头部组件
const headerSlot = post
? <PostHero {...props} /> // 文章页显示文章头
: (router.route === '/' && siteConfig('HEXO_HOME_BANNER_ENABLE', null, CONFIG)
    ? <Hero {...props} /> // 首页且开启banner时显示Hero组件
    : (headerPathsStr.includes(router.route)
    ? <CatHeader {...props} /> // 归档/分类/标签页显示分类头
    : null))

文章作者: keney
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 keney !
评论
来发评论吧~
Powered By Valine
v1.5.2
  目录