这里使用notionNext版本是4.7.11
Node版本:20.7.0
📝 导航
改动文件:添加和修改的文件
1.增加新的页面路由
在项目根目录 /pages文件夹中新建一个名为memos的文件夹,并在文件夹中创建一个名为Index.js的文件夹
在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版本。
即添加以下代码到指定位置:
'/memos' : 'LayoutMemos',
然后在blog.config.js指定位置,添加以下环境变量配置:
在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官方的更新冲突。
创建: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"> </i> {locale.NAV.ARCHIVE} </div> ) case '/category': return ( <div> <i className="fas fa-th"> </i> {locale.COMMON.CATEGORY} </div> ); case '/tag': return ( <div> <i className="fas fa-tag"> </i> {locale.COMMON.TAGS} </div> ); case '/memos': return ( <div> <i className="fa-solid fa-wand-magic-sparkles"> </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文件中,引入对应的组件
然后在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
3.1引入静态资源
由于MemosBlog组件中引入了外部资源,如一些外部的css和js样式来实现说说页的样式和动态加载,需要将对应的这些文件分别放入public文件夹下。
CSS文件,放在public/css文件夹下:
highlight.github.min.css、img-shadow.css、memos.css
public\js
highlight.min.js、lazyload.min.js、marked.min.js、moment.min.js、moment.twitter.js、view-image.min.js
css下:https://www.jianguoyun.com/p/DbBqOtIQg7KQDRjXiO8FIAA
js文件下载:https://www.jianguoyun.com/p/DTBdmnEQk-vsBRjw1LgFIAA
备份链接2:https://www.jianguoyun.com/p/DZCmc2cQg7KQDRjTiO8FIAA
头像:
在项目根目录 /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路径和对应参数
iconfont icon-magic
效果:
📎 参考文章
- 参考memos引入教程:https://chenge.ink/article/post20240302-1
- memos.top:https://github.com/eallion/memos.top
- 主题地址:https://github.com/tangly1024/NotionNext
- 文档:https://docs.tangly1024.com/article/vercel-deploy-notion-next
本地调试 env配置:
.env、.env.local、.env.prod,优先级不同,本地调试可以自行选择,这些文件可以不提交到仓库
导航页是否需要背景图:
修改: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))