organize the post folder
This commit is contained in:
910
source/_posts/2025.08/set-shuoshuo-page.md
Normal file
910
source/_posts/2025.08/set-shuoshuo-page.md
Normal file
@@ -0,0 +1,910 @@
|
||||
---
|
||||
title: 配置说说页面
|
||||
tags: 网站
|
||||
categories: 建站手札
|
||||
series: webcustom
|
||||
abbrlink: ad244066
|
||||
summary: >-
|
||||
这段文字看起来是一个部署教程,主要是关于如何在服务器上部署一个名为“Moments”的应用,并将其与网站集成。以下是对这段文字的概括: 1.
|
||||
介绍了硬件和软件的要求,包括服务器、域名、Docker环境和反向代理工具(如Nginx)。 2.
|
||||
提供了Moments应用的部署方法,包括使用docker-compose进行部署和配置Nginx反向代理。 3.
|
||||
介绍了如何在前端集成Moments应用,包括引入必要的CSS和JS文件,以及创建一个新的页面来显示Moments的内容。
|
||||
需要注意的是,这段文字中的一些链接和代码可能已经过时或者不适用于所有场景,建议在实际操作时参考最新的官方
|
||||
date: 2025-08-10 08:25:39
|
||||
---
|
||||
又开始折腾啦,这次把说说页面加上,改用moments项目的api
|
||||
{% link Moments 极简朋友圈,githun@kingwrcy,https://github.com/kingwrcy/moments %}
|
||||
|
||||
# 前期要求
|
||||
|
||||
## 硬件要求
|
||||
|
||||
1. 一台服务器
|
||||
2. 一个可自主解析的域名
|
||||
|
||||
## 软件要求
|
||||
|
||||
1. docker环境
|
||||
2. 反向代理工具(本文以Nginx为例)
|
||||
|
||||
# 介绍与展示
|
||||
|
||||
这里先给大家展示一下最终的效果,注意该教程可能仅适合部分主题,如果出现主题不适配的情况请自行适配,这里以本站主题 `Hexo-theme-butterfly`为基础进行修改:
|
||||
|
||||
1. 说说页面:
|
||||
{% link 我的说说,胡言乱语ing。。。,https://blog.biss.click %}
|
||||
2. 轻量朋友圈
|
||||
{% link 朋友圈,依旧胡言乱语,https://mm.biss.blog %}
|
||||
3. 功能说明
|
||||
|
||||
Moments作为一个轻量朋友圈,其功能都是分享上的部分,如下所示:
|
||||
|
||||
* 分享:链接,图片,音乐,视频,书籍,电影
|
||||
* 信息:自定义位置,自定义标签,是否公开
|
||||
* 页面:Markdown渲染,编辑说说,删除说说,暗夜模式,自定义图标,信息,CSS及JS代码
|
||||
* 功能:S3存储,文件查询,多用户注册,点赞,评论,API
|
||||
|
||||
简单介绍完毕,下面我就来教大家如何进行部署!
|
||||
|
||||
# 部署教程
|
||||
|
||||
## Moments部署
|
||||
|
||||
### Compose部署
|
||||
|
||||
官方给予了很完善的教程,这里我仅仅简单介绍一下 `docker-compose`部署的方式,如果你想以源码等其他方式进行部署,请查看文章开头部分的 `github`地址进行查阅。
|
||||
|
||||
首先,在服务器任意位置创建文件:`docker-compose.yaml`,填入以下内容:
|
||||
|
||||
```bash
|
||||
version: '3'
|
||||
services:
|
||||
moments:
|
||||
image: kingwrcy/moments:latest
|
||||
container_name: moments
|
||||
restart: always
|
||||
environment:
|
||||
port: 3000
|
||||
JWT_KEY: "自己随便生成点字符串"
|
||||
ENABLE_SWAGGER: "true"
|
||||
CORS_ORIGIN: # 填写跨域域名
|
||||
ports:
|
||||
- "3000:3000" # 自行换端口,换前面的,后面的3000不要动
|
||||
volumes:
|
||||
- ./opt/data:/app/data
|
||||
# - ./data/localtime:/etc/localtime:ro
|
||||
# - ./data/timezone:/etc/timezone:ro
|
||||
```
|
||||
|
||||
注意文件,我将 `/opt/`文件夹(当然可以改成其他的)下的 `/data`文件夹挂载了进去,数据都会在里面,迁移时仅需整体打包到新服务器即可。然后执行以下两条命令,后续需要升级也仅需要执行这两个命令:
|
||||
|
||||
```bash
|
||||
docker-compose pull
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
如果网络环境不佳,可尝试替换 `docker`源,可以自行查找
|
||||
|
||||
通过反向代理将其添加到某个域名中,这里就不再多说了,各大面板都有极其完备的反代文档。
|
||||
|
||||
### Nginx修改
|
||||
|
||||
`Moments`在跨域 `docker-compose`文件中可以配置,所以不必进行此步
|
||||
|
||||
网站目录
|
||||
|
||||
返回到上一级目录,也就是域名名称的文件夹下,找到 `Proxy`文件夹,编辑里面的 `root.conf`文件为如下内容:
|
||||
|
||||
```bash
|
||||
#代理配置
|
||||
|
||||
location / {
|
||||
# 跨域设置
|
||||
add_header Access-Control-Allow-Origin *; # 允许所有域名访问,你也可以指定具体域名
|
||||
add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS'; # 允许的 HTTP 方法
|
||||
add_header Access-Control-Allow-Headers 'Origin, X-Requested-With, Content-Type, Accept, Authorization'; # 允许的请求头
|
||||
|
||||
# 处理 OPTIONS 请求,预检请求
|
||||
if ($request_method = 'OPTIONS') {
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS';
|
||||
add_header Access-Control-Allow-Headers 'Origin, X-Requested-With, Content-Type, Accept, Authorization';
|
||||
add_header Access-Control-Max-Age 1728000;
|
||||
add_header Content-Type 'text/plain charset=UTF-8';
|
||||
add_header Content-Length 0;
|
||||
return 204;
|
||||
}
|
||||
|
||||
# 原代理设置
|
||||
proxy_pass http://127.0.0.1:3003;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header REMOTE-HOST $remote_addr;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $http_connection;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_http_version 1.1;
|
||||
add_header X-Cache $upstream_cache_status;
|
||||
add_header Cache-Control no-cache;
|
||||
proxy_ssl_server_name off;
|
||||
proxy_ssl_name $proxy_host;
|
||||
add_header Strict-Transport-Security "max-age=31536000";
|
||||
}
|
||||
```
|
||||
|
||||
下面是原代理设置,可以仅仅复制上面部分内容,下面保持不变,注意端口不要出问题。
|
||||
|
||||
## 前端实现
|
||||
|
||||
由于该项目利用了MetingJS和APlayer,所以请提前引入这两个包,Hexo-theme-butterfly中虽然有内置的两个包,仅需修改配置文件即可开启,但是版本比较老,这里我建议自行引入最新版本,在配置中引入以下文件,注意css和js应该是分开引入的:
|
||||
|
||||
```bash
|
||||
<link rel="stylesheet" href="https://fastly.jsdelivr.net/npm/aplayer/dist/APlayer.min.css" media="all" onload="this.media="all"">
|
||||
<script src="https://fastly.jsdelivr.net/npm/aplayer/dist/APlayer.min.js"></script>
|
||||
<script src="https://fastly.jsdelivr.net/npm/meting/dist/Meting.min.js"></script>
|
||||
```
|
||||
|
||||
新建页面shuoshuo,在文件内写入一下内容:
|
||||
|
||||
```bash
|
||||
---
|
||||
title: 日常哔哔,键盘侠的日常吐槽
|
||||
aside: false
|
||||
---
|
||||
<div id="talk"></div>
|
||||
<div class="limit">- 只展示最近30条说说 -</div>
|
||||
<script src="/js/shuoshuo.js" no-pjax></script>
|
||||
```
|
||||
|
||||
其中的JS文件地址清自行修改,放在主题目录 `/script/`目录,自行创建,并写入以下内容:
|
||||
|
||||
```js
|
||||
function renderTalks() {
|
||||
const talkContainer = document.querySelector('#talk');
|
||||
const domain = 'https://mm.biss.click';
|
||||
if (!talkContainer) return;
|
||||
talkContainer.innerHTML = '';
|
||||
const generateIconSVG = () => {
|
||||
return `<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>`;
|
||||
}
|
||||
const waterfall = (a) => {
|
||||
function b(a, b) {
|
||||
var c = window.getComputedStyle(b);
|
||||
return parseFloat(c["margin" + a]) || 0
|
||||
}
|
||||
|
||||
function c(a) {
|
||||
return a + "px"
|
||||
}
|
||||
|
||||
function d(a) {
|
||||
return parseFloat(a.style.top)
|
||||
}
|
||||
|
||||
function e(a) {
|
||||
return parseFloat(a.style.left)
|
||||
}
|
||||
|
||||
function f(a) {
|
||||
return a.clientWidth
|
||||
}
|
||||
|
||||
function g(a) {
|
||||
return a.clientHeight
|
||||
}
|
||||
|
||||
function h(a) {
|
||||
return d(a) + g(a) + b("Bottom", a)
|
||||
}
|
||||
|
||||
function i(a) {
|
||||
return e(a) + f(a) + b("Right", a)
|
||||
}
|
||||
|
||||
function j(a) {
|
||||
a = a.sort(function (a, b) {
|
||||
return h(a) === h(b) ? e(b) - e(a) : h(b) - h(a)
|
||||
})
|
||||
}
|
||||
|
||||
function k(b) {
|
||||
f(a) != t && (b.target.removeEventListener(b.type, arguments.callee), waterfall(a))
|
||||
}
|
||||
"string" == typeof a && (a = document.querySelector(a));
|
||||
var l = [].map.call(a.children, function (a) {
|
||||
return a.style.position = "absolute", a
|
||||
});
|
||||
a.style.position = "relative";
|
||||
var m = [];
|
||||
l.length && (l[0].style.top = "0px", l[0].style.left = c(b("Left", l[0])), m.push(l[0]));
|
||||
for (var n = 1; n < l.length; n++) {
|
||||
var o = l[n - 1],
|
||||
p = l[n],
|
||||
q = i(o) + f(p) <= f(a);
|
||||
if (!q) break;
|
||||
p.style.top = o.style.top, p.style.left = c(i(o) + b("Left", p)), m.push(p)
|
||||
}
|
||||
for (; n < l.length; n++) {
|
||||
j(m);
|
||||
var p = l[n],
|
||||
r = m.pop();
|
||||
p.style.top = c(h(r) + b("Top", p)), p.style.left = c(e(r)), m.push(p)
|
||||
}
|
||||
j(m);
|
||||
var s = m[0];
|
||||
a.style.height = c(h(s) + b("Bottom", s));
|
||||
var t = f(a);
|
||||
window.addEventListener ? window.addEventListener("resize", k) : document.body.onresize = k
|
||||
};
|
||||
|
||||
const fetchAndRenderTalks = () => {
|
||||
const url = 'https://mm.biss.click/api/memo/list';
|
||||
const cacheKey = 'talksCache';
|
||||
const cacheTimeKey = 'talksCacheTime';
|
||||
const cacheDuration = 30 * 60 * 1000; // 半个小时 (30 分钟)
|
||||
|
||||
const cachedData = localStorage.getItem(cacheKey);
|
||||
const cachedTime = localStorage.getItem(cacheTimeKey);
|
||||
const currentTime = new Date().getTime();
|
||||
|
||||
// 判断缓存是否有效
|
||||
if (cachedData && cachedTime && (currentTime - cachedTime < cacheDuration)) {
|
||||
const data = JSON.parse(cachedData);
|
||||
renderTalks(data); // 使用缓存渲染数据
|
||||
} else {
|
||||
if (talkContainer) {
|
||||
talkContainer.innerHTML = '';
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
size: 30
|
||||
})
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (data.code === 0 && data.data && Array.isArray(data.data.list)) {
|
||||
// 缓存数据
|
||||
localStorage.setItem(cacheKey, JSON.stringify(data.data.list));
|
||||
localStorage.setItem(cacheTimeKey, currentTime.toString());
|
||||
renderTalks(data.data.list); // 渲染数据
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching data:', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染函数
|
||||
function renderTalks(list) {
|
||||
// 确保 data 是一个数组
|
||||
if (Array.isArray(list)) {
|
||||
let items = list.map(item => formatTalk(item, url));
|
||||
items.forEach(item => talkContainer.appendChild(generateTalkElement(item)));
|
||||
waterfall('#talk');
|
||||
} else {
|
||||
console.error('Data is not an array:', list);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const formatTalk = (item, url) => {
|
||||
let date = formatTime(new Date(item.createdAt).toString());
|
||||
let content = item.content;
|
||||
let imgs = item.imgs ? item.imgs.split(',') : [];
|
||||
let text = content;
|
||||
content = text.replace(/\[(.*?)\]\((.*?)\)/g, `<a href="$2">@$1</a>`)
|
||||
.replace(/- \[ \]/g, '⚪')
|
||||
.replace(/- \[x\]/g, '⚫');
|
||||
// 保留换行符,转换 \n 为 <br>
|
||||
content = content.replace(/\n/g, '<br>');
|
||||
// 将content用一个类包裹,便于后续处理
|
||||
content = `<div class="talk_content_text">${content}</div>`;
|
||||
if (imgs.length > 0) {
|
||||
const imgDiv = document.createElement('div');
|
||||
imgDiv.className = 'zone_imgbox';
|
||||
imgs.forEach(e => {
|
||||
const imgLink = document.createElement('a');
|
||||
const imgUrl = domain + e;
|
||||
imgLink.href = imgUrl;
|
||||
imgLink.setAttribute('data-fancybox', 'gallery');
|
||||
imgLink.className = 'fancybox';
|
||||
imgLink.setAttribute('data-thumb', e);
|
||||
const imgTag = document.createElement('img');
|
||||
imgTag.src = domain + e;
|
||||
imgLink.appendChild(imgTag);
|
||||
imgDiv.appendChild(imgLink);
|
||||
});
|
||||
content += imgDiv.outerHTML;
|
||||
}
|
||||
|
||||
// 外链分享功能
|
||||
if (item.externalUrl) {
|
||||
const externalUrl = item.externalUrl;
|
||||
const externalTitle = item.externalTitle;
|
||||
const externalFavicon = item.externalFavicon;
|
||||
|
||||
const externalContainer = `
|
||||
<div class="shuoshuo-external-link">
|
||||
<a class="external-link" href="${externalUrl}" target="_blank" rel="external nofollow noopener noreferrer">
|
||||
<div class="external-link-left" style="background-image: url(${externalFavicon})"></div>
|
||||
<div class="external-link-right">
|
||||
<div class="external-link-title">${externalTitle}</div>
|
||||
<div>点击跳转<i class="fa-solid fa-angle-right"></i></div>
|
||||
</div>
|
||||
</a>
|
||||
</div>`;
|
||||
|
||||
content += externalContainer;
|
||||
}
|
||||
|
||||
const ext = JSON.parse(item.ext || '{}');
|
||||
|
||||
if (ext.music && ext.music.id) {
|
||||
const music = ext.music;
|
||||
const musicUrl = music.api.replace(':server', music.server)
|
||||
.replace(':type', music.type)
|
||||
.replace(':id', music.id);
|
||||
content += `
|
||||
<meting-js server="${music.server}" type="${music.type}" id="${music.id}" api="${music.api}"></meting-js>
|
||||
`;
|
||||
}
|
||||
|
||||
if (ext.doubanMovie && ext.doubanMovie.id) {
|
||||
const doubanMovie = ext.doubanMovie;
|
||||
const doubanMovieUrl = doubanMovie.url;
|
||||
const doubanTitle = doubanMovie.title;
|
||||
// const doubanDesc = doubanMovie.desc || '暂无描述';
|
||||
const doubanImage = doubanMovie.image;
|
||||
const doubanDirector = doubanMovie.director || '未知导演';
|
||||
const doubanRating = doubanMovie.rating || '暂无评分';
|
||||
// const doubanReleaseDate = doubanMovie.releaseDate || '未知上映时间';
|
||||
// const doubanActors = doubanMovie.actors || '未知演员';
|
||||
const doubanRuntime = doubanMovie.runtime || '未知时长';
|
||||
|
||||
content += `
|
||||
<a class="douban-card" href="${doubanMovieUrl}" target="_blank">
|
||||
<div class="douban-card-bgimg" style="background-image: url('${doubanImage}');"></div>
|
||||
<div class="douban-card-left">
|
||||
<div class="douban-card-img" style="background-image: url('${doubanImage}');"></div>
|
||||
</div>
|
||||
<div class="douban-card-right">
|
||||
<div class="douban-card-item"><span>电影名: </span><strong>${doubanTitle}</strong></div>
|
||||
<div class="douban-card-item"><span>导演: </span><span>${doubanDirector}</span></div>
|
||||
<div class="douban-card-item"><span>评分: </span><span>${doubanRating}</span></div>
|
||||
<div class="douban-card-item"><span>时长: </span><span>${doubanRuntime}</span></div>
|
||||
</div>
|
||||
</a>
|
||||
`;
|
||||
}
|
||||
|
||||
if (ext.doubanBook && ext.doubanBook.id) {
|
||||
const doubanBook = ext.doubanBook;
|
||||
const bookUrl = doubanBook.url;
|
||||
const bookTitle = doubanBook.title;
|
||||
// const bookDesc = doubanBook.desc;
|
||||
const bookImage = doubanBook.image;
|
||||
const bookAuthor = doubanBook.author;
|
||||
const bookRating = doubanBook.rating;
|
||||
const bookPubDate = doubanBook.pubDate;
|
||||
|
||||
const bookTemplate = `
|
||||
<a class="douban-card" href="${bookUrl}" target="_blank">
|
||||
<div class="douban-card-bgimg" style="background-image: url('${bookImage}');"></div>
|
||||
<div class="douban-card-left">
|
||||
<div class="douban-card-img" style="background-image: url('${bookImage}');"></div>
|
||||
</div>
|
||||
<div class="douban-card-right">
|
||||
<div class="douban-card-item">
|
||||
<span>书名: </span><strong>${bookTitle}</strong>
|
||||
</div>
|
||||
<div class="douban-card-item">
|
||||
<span>作者: </span><span>${bookAuthor}</span>
|
||||
</div>
|
||||
<div class="douban-card-item">
|
||||
<span>出版年份: </span><span>${bookPubDate}</span>
|
||||
</div>
|
||||
<div class="douban-card-item">
|
||||
<span>评分: </span><span>${bookRating}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
`;
|
||||
|
||||
content += bookTemplate;
|
||||
}
|
||||
|
||||
if (ext.video && ext.video.type) {
|
||||
const videoType = ext.video.type;
|
||||
const videoUrl = ext.video.value;
|
||||
if (videoType === 'bilibili') {
|
||||
// Bilibili 视频模板
|
||||
// 从形如https://www.bilibili.com/video/BV1VGAPeAEMQ/?vd_source=91b3158d27d98ff41f842508c3794a13 的链接中提取视频 BV1VGAPeAEMQ
|
||||
const biliTemplate = `
|
||||
<div style="position: relative; padding: 30% 45%; margin-top: 10px;">
|
||||
<iframe
|
||||
style="position: absolute; width: 100%; height: 100%; left: 0; top: 0; border-radius: 12px;"
|
||||
src="${videoUrl}&autoplay=0"
|
||||
scrolling="no"
|
||||
frameborder="no"
|
||||
allowfullscreen>
|
||||
</iframe>
|
||||
</div>
|
||||
`;
|
||||
// 将模板插入到 DOM 中
|
||||
content += biliTemplate;
|
||||
|
||||
} else if (videoType === 'youtube') {
|
||||
// YouTube 视频模板
|
||||
// 从形如https://youtu.be/2V6lvCUPT8I?si=DVhUas6l6qlAr6Ru的链接中提取视频 ID2V6lvCUPT8I
|
||||
const youtubeTemplate = `
|
||||
<div style="position: relative; padding: 30% 45%; margin-top: 10px;">
|
||||
<iframe width="100%"
|
||||
style="position: absolute; width: 100%; height: 100%; left: 0; top: 0; border-radius: 12px;"
|
||||
src="${videoUrl}"
|
||||
title="YouTube video player"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
referrerpolicy="strict-origin-when-cross-origin"
|
||||
allowfullscreen>
|
||||
</iframe>
|
||||
</div>
|
||||
`;
|
||||
// 将模板插入到 DOM 中
|
||||
content += youtubeTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
content: content,
|
||||
user: item.user.nickname || '匿名',
|
||||
avatar: item.user.avatarUrl || 'https://p.liiiu.cn/i/2024/03/29/66061417537af.png',
|
||||
date: date,
|
||||
location: item.location || '山西',
|
||||
tags: item.tags ? item.tags.split(',').filter(tag => tag.trim() !== '') : ['无标签'],
|
||||
text: content.replace(/\[(.*?)\]\((.*?)\)/g, '[链接]' + `${imgs.length ? '[图片]' : ''}`)
|
||||
};
|
||||
};
|
||||
|
||||
const generateTalkElement = (item) => {
|
||||
const talkItem = document.createElement('div');
|
||||
talkItem.className = 'talk_item';
|
||||
|
||||
const talkMeta = document.createElement('div');
|
||||
talkMeta.className = 'talk_meta';
|
||||
|
||||
const avatar = document.createElement('img');
|
||||
avatar.className = 'no-lightbox avatar';
|
||||
avatar.src = item.avatar;
|
||||
|
||||
const info = document.createElement('div');
|
||||
info.className = 'info';
|
||||
|
||||
const talkNick = document.createElement('span');
|
||||
talkNick.className = 'talk_nick';
|
||||
talkNick.innerHTML = `${item.user} ${generateIconSVG()}`;
|
||||
|
||||
const talkDate = document.createElement('span');
|
||||
talkDate.className = 'talk_date';
|
||||
talkDate.textContent = item.date;
|
||||
|
||||
const talkContent = document.createElement('div');
|
||||
talkContent.className = 'talk_content';
|
||||
talkContent.innerHTML = item.content;
|
||||
|
||||
const talkBottom = document.createElement('div');
|
||||
talkBottom.className = 'talk_bottom';
|
||||
|
||||
const TagContainer = document.createElement('div');
|
||||
|
||||
const talkTag = document.createElement('span');
|
||||
talkTag.className = 'talk_tag';
|
||||
talkTag.textContent = `🏷️${item.tags}`;
|
||||
|
||||
const locationTag = document.createElement('span');
|
||||
locationTag.className = 'location_tag';
|
||||
locationTag.textContent = `🌍${item.location}`;
|
||||
|
||||
TagContainer.appendChild(talkTag);
|
||||
TagContainer.appendChild(locationTag);
|
||||
|
||||
const commentLink = document.createElement('a');
|
||||
commentLink.href = 'javascript:;';
|
||||
commentLink.onclick = () => goComment(item.text);
|
||||
const commentIcon = document.createElement('span');
|
||||
commentIcon.className = 'icon';
|
||||
const commentIconInner = document.createElement('i');
|
||||
commentIconInner.className = 'fa-solid fa-message fa-fw';
|
||||
commentIcon.appendChild(commentIconInner);
|
||||
commentLink.appendChild(commentIcon);
|
||||
|
||||
talkMeta.appendChild(avatar);
|
||||
info.appendChild(talkNick);
|
||||
info.appendChild(talkDate);
|
||||
talkMeta.appendChild(info);
|
||||
talkItem.appendChild(talkMeta);
|
||||
talkItem.appendChild(talkContent);
|
||||
talkBottom.appendChild(TagContainer);
|
||||
talkBottom.appendChild(commentLink);
|
||||
talkItem.appendChild(talkBottom);
|
||||
|
||||
return talkItem;
|
||||
};
|
||||
|
||||
const goComment = (e) => {
|
||||
const match = e.match(/<div class="talk_content_text">([\s\S]*?)<\/div>/);
|
||||
const textContent = match ? match[1] : "";
|
||||
const n = document.querySelector(".atk-textarea");
|
||||
n.value = `> ${textContent}\n\n`;
|
||||
n.focus();
|
||||
btf.snackbarShow("已为您引用该说说,不删除空格效果更佳");
|
||||
// const n = document.querySelector(".atk-textarea");
|
||||
// n.value = `> ${e}\n\n`;
|
||||
// n.focus();
|
||||
// btf.snackbarShow("已为您引用该说说,不删除空格效果更佳");
|
||||
};
|
||||
|
||||
const formatTime = (time) => {
|
||||
const d = new Date(time);
|
||||
const ls = [
|
||||
d.getFullYear(),
|
||||
d.getMonth() + 1,
|
||||
d.getDate(),
|
||||
d.getHours(),
|
||||
d.getMinutes(),
|
||||
d.getSeconds(),
|
||||
];
|
||||
const r = ls.map((a) => (a.toString().length === 1 ? '0' + a : a));
|
||||
return `${r[0]}-${r[1]}-${r[2]} ${r[3]}:${r[4]}`;
|
||||
};
|
||||
|
||||
fetchAndRenderTalks();
|
||||
}
|
||||
|
||||
renderTalks();
|
||||
|
||||
// function whenDOMReady() {
|
||||
// const talkContainer = document.querySelector('#talk');
|
||||
// talkContainer.innerHTML = '';
|
||||
// fetchAndRenderTalks();
|
||||
// }
|
||||
// whenDOMReady();
|
||||
// document.addEventListener("pjax:complete", whenDOMReady);
|
||||
```
|
||||
|
||||
自行修改js文件中的Moments地址为你的地址,在文件中,有一个gocomment函数,实现的是获取卡片中的文本内容,如果如果出现不匹配的情况,请自行修改一下类名,这里我匹配的是artalk的输入框。在这里我把代码做了修改,因为我不想使用s3,结果导致说说图片无法加载。。。
|
||||
修改历程:日后再写
|
||||
|
||||
然后引入样式文件,这个文件可以在配置文件中引用,也可以在页面文件中类似于shuoshuo.js一样引用,样式内容如下:
|
||||
|
||||
```css
|
||||
:root {
|
||||
--liushen-card-bg: #fff;
|
||||
--liushen-card-border: 1px solid #e3e8f7;
|
||||
--card-box-shadow: 0 3px 8px 6px rgba(7,17,27,0.09);
|
||||
--card-hover-box-shadow: 0 3px 8px 6px rgba(7,17,27,0.2);
|
||||
--liushen-card-secondbg: #f1f3f8;
|
||||
--liushen-button-hover-bg: #2679cc;
|
||||
--liushen-text: #4c4948;
|
||||
--liushen-button-bg: #f1f3f8;
|
||||
--liushen-fancybox-bg: rgba(255,255,255,0.5);
|
||||
}
|
||||
|
||||
:root, [data-theme=dark] {
|
||||
--liushen-card-bg: #181818;
|
||||
--liushen-card-secondbg: #30343f;
|
||||
--liushen-card-border: 1px solid #42444a;
|
||||
--card-box-shadow: 0 3px 8px 6px rgba(7,17,27,0.09);
|
||||
--card-hover-box-shadow: 0 3px 8px 6px rgba(7,17,27,0.2);
|
||||
--liushen-button-bg: #30343f;
|
||||
--liushen-button-hover-bg: #2679cc;
|
||||
--liushen-text: rgba(255,255,255,0.702);
|
||||
--liushen-fancybox-bg: rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
/* 卡片初始化 */
|
||||
#talk .talk_item {
|
||||
width: calc(33.333% - 6px);
|
||||
background: var(--liushen-card-bg);
|
||||
border: var(--liushen-card-border);
|
||||
box-shadow: var(--card-box-shadow);
|
||||
transition: box-shadow .3s ease-in-out;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
margin-bottom: 9px;
|
||||
margin-right: 9px;
|
||||
}
|
||||
#talk .talk_item:hover {
|
||||
box-shadow: var(--card-hover-box-shadow);
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
#talk .talk_item {
|
||||
width: calc(50% - 5px);
|
||||
}
|
||||
}
|
||||
@media (max-width: 450px) {
|
||||
#talk .talk_item {
|
||||
width: calc(100%);
|
||||
}
|
||||
}
|
||||
|
||||
#talk{
|
||||
position: relative;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#talk .talk_meta .avatar {
|
||||
margin: 0 !important;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
#talk .talk_bottom,
|
||||
#talk .talk_meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
#talk .talk_meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px dashed grey; /* 添加灰色虚线边框 */
|
||||
}
|
||||
#talk .talk_bottom {
|
||||
margin-top: 15px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px dashed grey; /* 添加灰色虚线边框 */
|
||||
justify-content: space-between;
|
||||
}
|
||||
#talk .talk_meta .info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 10px;
|
||||
}
|
||||
#talk .talk_meta .info .talk_nick {
|
||||
color: #6dbdc3;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
#talk .talk_meta .info svg.is-badge.icon {
|
||||
width: 15px;
|
||||
padding-top: 3px;
|
||||
}
|
||||
#talk .talk_meta .info span.talk_date {
|
||||
opacity: .6;
|
||||
}
|
||||
#talk .talk_item .talk_content {
|
||||
margin-top: 10px;
|
||||
}
|
||||
#talk .talk_item .talk_content .zone_imgbox {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
--w: calc(25% - 8px);
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
#talk .talk_item .talk_content .zone_imgbox a {
|
||||
display: block;
|
||||
border-radius: 12px;
|
||||
width: var(--w);
|
||||
aspect-ratio: 1/1;
|
||||
position: relative;
|
||||
}
|
||||
#talk .talk_item .talk_content .zone_imgbox a:first-child {
|
||||
width: 100%;
|
||||
aspect-ratio: 1.8;
|
||||
}
|
||||
#talk .talk_item .talk_content .zone_imgbox img {
|
||||
border-radius: 10px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0 !important;
|
||||
object-fit: cover;
|
||||
}
|
||||
/* 底部 */
|
||||
#talk .talk_item .talk_bottom {
|
||||
opacity: .9;
|
||||
}
|
||||
#talk .talk_item .talk_bottom .icon {
|
||||
float: right;
|
||||
transition: all .3s;
|
||||
}
|
||||
#talk .talk_item .talk_bottom .icon:hover {
|
||||
color: #49b1f5;
|
||||
}
|
||||
#talk .talk_item .talk_bottom span.talk_tag,
|
||||
#talk .talk_item .talk_bottom span.location_tag {
|
||||
font-size: 14px;
|
||||
background-color: var(--liushen-card-secondbg);
|
||||
border-radius: 12px;
|
||||
padding: 3px 15px 3px 10px;
|
||||
transition: box-shadow 0.3s ease;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
#talk .talk_item .talk_bottom span.location_tag {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
#talk .talk_item .talk_bottom span.talk_tag:hover,
|
||||
#talk .talk_item .talk_bottom span.location_tag:hover {
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
#talk .talk_item .talk_content>a {
|
||||
margin: 0 3px;
|
||||
color: #ff7d73 !important;
|
||||
}
|
||||
#talk .talk_item .talk_content>a:hover{
|
||||
text-decoration: none !important;
|
||||
color: #ff5143 !important
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
#talk .talk_item .talk_content .zone_imgbox {
|
||||
--w: calc(33% - 5px);
|
||||
}
|
||||
#talk .talk_item #post-comment{
|
||||
margin: 0 3px
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 768px) {
|
||||
.zone_imgbox {
|
||||
gap: 6px;
|
||||
}
|
||||
.zone_imgbox {
|
||||
--w: calc(50% - 3px);
|
||||
}
|
||||
span.talk_date {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
#talk .talk_item .talk_content .douban-card {
|
||||
margin-top: 10px !important;
|
||||
text-decoration: none;
|
||||
align-items: center;
|
||||
border-radius: 12px;
|
||||
color: #faebd7;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 10px;
|
||||
max-width: 400px;
|
||||
overflow: hidden;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.douban-card .douban-card-bgimg {
|
||||
background-position: 50%;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100%;
|
||||
filter: blur(15px) brightness(.6);
|
||||
height: 115%;
|
||||
position: absolute;
|
||||
width: 115%;
|
||||
}
|
||||
|
||||
.douban-card .douban-card-left {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.douban-card .douban-card-left .douban-card-img {
|
||||
transition: all .5s ease;
|
||||
height: 130px;
|
||||
position: relative;
|
||||
width: 80px;
|
||||
background-position: 50%;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100%;
|
||||
}
|
||||
|
||||
.douban-card .douban-card-left:hover .douban-card-img {
|
||||
filter: blur(5px) brightness(.6);
|
||||
transform: perspective(800px) rotateX(180deg);
|
||||
}
|
||||
|
||||
.douban-card .douban-card-right {
|
||||
color: #faebd7;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
margin-left: 12px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.douban-card .douban-card-right .douban-card-item {
|
||||
margin-top: 4px;
|
||||
max-width: 95%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 外链卡片 */
|
||||
#talk .talk_item .talk_content .shuoshuo-external-link {
|
||||
/* 无下划线 */
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
margin-top: 10px;
|
||||
border-radius: 12px;
|
||||
background-color: var(--liushen-card-secondbg);
|
||||
color: var(--liushen-card-text);
|
||||
border: var(--liushen-card-border);
|
||||
transition: background-color .3s ease-in-out;
|
||||
}
|
||||
|
||||
.shuoshuo-external-link:hover {
|
||||
background-color: var(--liushen-button-hover-bg);
|
||||
}
|
||||
|
||||
.shuoshuo-external-link .external-link {
|
||||
display: flex;
|
||||
color: var(--liushen-text) !important;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.shuoshuo-external-link .external-link:hover {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.shuoshuo-external-link .external-link:hover {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.shuoshuo-external-link .external-link-left {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
margin: 10px;
|
||||
border-radius: 12px;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.shuoshuo-external-link .external-link-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
width: calc(100% - 80px);
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.shuoshuo-external-link .external-link-right .external-link-title {
|
||||
font-size: 1.0rem;
|
||||
font-weight: 800;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.shuoshuo-external-link .external-link-right i {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.limit {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin-top: 30px;
|
||||
}
|
||||
```
|
||||
|
||||
如果一切正常,应该就可以显示了,但是我在使用时发现卡片是黑色的,把前面的root内容删除之后就好了。
|
||||
|
||||
# 参考内容
|
||||
|
||||
{% link liushen的博客,liushen,https://blog.liushen.fun/posts/8338183a/ %}
|
||||
|
||||
当然还有chatgpt的支持
|
||||
Reference in New Issue
Block a user