Solitude主题的即刻短文至目前为止仅仅只有静态yml部署方式。当你需要发一条说说时,就得打开电脑,手动去添加yml的内容,再部署上线。本地化的增删,繁琐“即刻短文”。

而MeuiCat在很早之前就已经实现了动态Json以及动态Memos数据的部署方式。你只需要像发朋友圈那样,输入后点击发送即可。

功能实现

让我们一步步解析实操,体验享受代码的乐趣。

动态Json

通俗易懂的解释一下大致的处理过程。通过Fetch获取Json的数据,将遍历数据,根据结构生成各个部分功能,如图片、音乐、视频等,拼接在一起,根据数量限制去循环列表。将生成的HTML插入页面。

其中,加入了一些样式的切换,毕竟Solitude有两套即刻的样式。再就是即刻短文显示的条数逻辑了,这里的规则为:-1等于显示全部内容;当为正数即为限制显示数量,并且当你发布的即刻数量低于你所限制的数量时,Tips将会贴心的为你提示:已展开所有短文。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
script.
(()=>{
const replaceSymbol = (str) => {
return str.replace(/[\p{P}\p{S}]/gu, "-")
}
let says = ""

fetch("!{url_for(theme.says.mode_link)}")
.then(response => response.json())
.then(str => {
for(let i = 0; i < str.length; i++){
let list = ""
let listResult = ""
let saysList = str[i].essay_list
let style = !{theme.says.style}
let strip = !{theme.says.strip}
let saysTips = ""
if (strip === -1 || strip >= str[i].essay_list.length) {
list = saysList
saysTips = `<div id="bber-tips">- 已展开所有短文 -</div>`
} else {
list = saysList.slice(0, strip)
saysTips = `<div id="bber-tips">- 只展示最近 ${strip} 条短文 -</div>`
}

for(let j = 0; j < list.length; j++){
let content = ""
let onclick = ""
if (list[j].content === undefined || list[j].content === null) {
content = ""
} else if (list[j].content) {
content = `<p class="datacont">${list[j].content}</p>`
onclick = `<a class="bber-reply goComment" onclick="sco.toTalk('${list[j].content}')"><i class="scoicon sco-chat-fill" style="font-size: 1rem;"></i></a>`
}

let imageBox = ""
let imageList = ""
if (list[j].image) {
const image = list[j].image
for(let e = 0; e < image.length; e++){
imageList += `<img src="${image[e]}" title="即刻短文配图" />`
}
imageBox += `<div class="bber-content-img">${imageList}</div>`
}

let aplayer = ""
if (list[j].aplayer) {
aplayer = `<div class="bber-music"><meting-js server="${list[j].aplayer.server}" type="song" id="${list[j].aplayer.id}" mutex="true" preload="none" theme="var(--efu-main)" data-lrctype="0"></meting-js></div>`
}

let video = ""
if (list[j].video) {
if (list[j].video.player) {
video = `<div class="bber-video"><video src="${list[j].video.player}" controls="controls" style="object-fit: cover;"></video></div>`
}
if (list[j].video.bilibili) {
video = `<div class="bber-video"><iframe src="${list[j].video.bilibili}" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"></iframe></div>`
}
}

let link = ""
if (list[j].link) {
link = `<a class="bber-content-link href="${list[j].link}" title="跳转到短文指引的链接"><i class="scoicon sco-link-m-line"></i>链接</a>`
}

if (style === 1) {
listResult += `
<li class="item">
<div id="bber-content">
${content}
${imageBox}
</div>
${video}
${aplayer}
<hr>
<div class="bber-bottom">
<div class="bber-info">
<div class="bber-info-time">
<i class="scoicon sco-calendar-todo-fill"></i>
<time class="datetime" datetime="${list[j].date}"></time>
</div>
${link}
</div>
${onclick}
</div>
</li>`
} else if (style === 2) {
listResult += `
<li class="item">
<div class="meta">
<img class="no-lightbox no-lazyload avatar" src="!{theme.aside.card.author.img}">
<div class="info">
<span class="bber_nick">#{config.author}</span>
<time class="datetime bber_date" datetime="${list[j].date}"></time>
</div>
${onclick}
</div>
<div id="bber-content">
${content}
${imageBox}
</div>
${video}
${aplayer}
</li>`
} else {
console.log('请正确配置即刻样式!')
}
}
says += `<ul id="waterfall" class="list">${listResult}</ul> ${saysTips}`
}

document.querySelector(".page-1").insertAdjacentHTML("afterbegin", says)
changeTimeFormat()
sco.reflashEssayWaterFall()
sco.lazyloadImg()
})
})()

动态Memos

Memos的处理跟Json模式的处理大差不差,只是在Memos的content处理上有略微不同。将获取的数据被解析成JSON格式后,提供content的内容,将各个部分功能,如图片、音乐、视频等,以正则表达式的方式生成对应的结构,并将在原来content的内容里减去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
ul.list#waterfall
script.
(()=>{
fetch("!{url_for(theme.says.mode_link)}").then(res => res.json()).then(data => {
let items = [],
html = '',
strip = !{theme.says.strip},
saysTips = '';

if (strip === -1 || strip >= data.length) {
data.forEach(item => { items.push(saysFormat(item)) });
saysTips = `<div id="bber-tips">- 已展开所有短文 -</div>`
} else {
data.slice(0, strip).forEach(item => {
items.push(saysFormat(item));
});
saysTips = `<div id="bber-tips">- 只展示最近 ${strip} 条短文 -</div>`
}

document.getElementsByClassName('list')[0].innerHTML = items.map(item => item.content).join('');
document.querySelector(".page-1").insertAdjacentHTML("beforeend", saysTips)
changeTimeFormat()
sco.reflashEssayWaterFall()
sco.lazyloadImg()
});

function saysFormat(item) {
let style = !{theme.says.style},
time = new Date(item.createdTs * 1000).toISOString().replace('Z', '+08:00'),
content = item.content,
image = content.match(/!\[.*\]\(.*?\)/g),
aplayer = content.match(/{\s*music\s*(.*?)\s*(.*?)\s*}/g),
player = content.match(/{\s*player\s*(.*)\s*}/g),
bilibili = content.match(/{\s*bilibili\s*(.*?)\s*}/g),
text = '',
onclick = '',
link = '',
saysLink = '',
saysContent = '',
saysContents = '';

if (image) image = image.map(item => { return item.replace(/!\[.*\]\((.*?)\)/, '$1'); });
if (item.resourceList.length) {
if (!image) image = [];
item.resourceList.forEach(t => {
if (t.externalLink) image.push(t.externalLink);
else image.push(`{url}/o/r/${t.id}/${t.publicId}/${t.filename}`);
});
}

text = content.replace(/#(.*?)\s/g, '').replace(/\!\[(.*?)\]\((.*?)\)/g, '').replace(/\{(.*?)\}/g, '');
content = text.replace(/\[(.*?)\]\((.*?)\)/g, (match, p1, p2) => {
link = p2;
return ``;
});
saysContent = `<p class="datacont">${content}</p>`;

if (text) {
onclick = `<a class="bber-reply goComment" ${content}><i class="scoicon sco-chat-fill" style="font-size:1rem"></i></a>`
}

if (image) {
saysContent += `<div class="bber-content-img">`;
image.forEach(e => saysContent += `<img src="${e}" title="即刻短文配图" />`);
saysContent += '</div>';
}

if (aplayer) aplayer.forEach(item => {
const music = item.match(/{\s*music\s*(\S+)\s*(\S+)\s*}/);
saysContents += `<div class="bber-music"><meting-js server="${music[1]}" type="song" id="${music[2]}" mutex="true" preload="none" theme="var(--efu-main)" data-lrctype="0"></meting-js></div>`
})

if (player) player.forEach(item => {
saysContents += `<div class="bber-video"><video src="${item.replace(/{\s*player\s*(.*)\s*}/, '$1')}" controls="controls" style="object-fit: cover;"></video></div>`
})
if (bilibili) bilibili.forEach(item => {
let bvid = item.match(/BV(\w+)/);
saysContents += `<div class="bber-video"><iframe src="//player.bilibili.com/player.html?autoplay=0&bvid=BV${bvid[1]}" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"></iframe></div>`
})

if (link) {
saysLink = `<a class="bber-content-link href="${link}" title="跳转到短文指引的链接"><i class="scoicon sco-link-m-line"></i>链接</a>`;
}

if (style === 1) {
return {
content: `
<li class="item">
<div id="bber-content">
${saysContent}
</div>
${saysContents}
<hr>
<div class="bber-bottom">
<div class="bber-info">
<div class="bber-info-time">
<i class="scoicon sco-calendar-todo-fill"></i>
<time class="datatime" datetime="${time}"></time>
</div>
${saysLink}
</div>
${onclick}
</div>
</li>`
};
} else if (style === 2) {
return {
content: `
<li class="item">
<div class="meta">
<img class="no-lightbox no-lazyload avatar" src="!{theme.aside.card.author.img}">
<div class="info">
<span class="bber_nick">#{config.author}</span>
<time class="datetime bber_date" datetime="${time}"></time>
</div>
${onclick}
</div>
<div id="bber-content">
${saysContent}
</div>
${saysContents}
</li>`
};
} else {
console.log('请正确配置即刻样式!')
}
}
})()

即刻Mini

对于首页的即刻Mini这个模块,大致处理就是分为:获取数据,处理数据,生成HTML,并将部分数据缓存到本地存储。

通过Fetch API异步获取数据,再根据配置的显示模式(’json’或’memos’)进行不同的处理,主要是因为Memos的content需要用到正则去匹配处理,所以才分成了两套。

最后就将数据存储到本地缓存(十分钟过期),这一步仅仅只是为了减少请求量,对于一些公益服务所作出的改动。

PS:使用F12进行调试的同学应该会注意到,本地缓存里有个Solitude组,至今为止其内包含了封面主色(postcolor),以及现在的即刻Mini数据(says)。这里想说的一点就是,目前大多Hexo的主题所使用的本地缓存组都太多太多了,就如butterfly、anzhiy等都是使用的一级,在切换主题时难免会共用到相同的组,这就是我使用二级组的原因。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
script.
(()=>{
function syasLocalCache() {
const solitudeData = JSON.parse(localStorage.getItem('Solitude'));

if (solitudeData && solitudeData.says) {
const { value, expiration } = solitudeData.says;
const currentTime = new Date().getTime();

if (expiration && currentTime < expiration) {
const bberTalkContainer = document.getElementById('bber-talk');

if (bberTalkContainer) {
bberTalkContainer.innerHTML = '';

value.forEach((item) => {
const liElement = document.createElement('div');
liElement.className = 'li-style swiper-slide';
liElement.textContent = item;
bberTalkContainer.appendChild(liElement);
});
}
} else {
saysDynamic();
}
} else {
saysDynamic();
}
}
function saysDynamic() {
fetch("!{url_for(theme.says.mode_link)}")
.then(response => response.json())
.then(data => {
var saysMode = '!{theme.says.mode}';
if (saysMode === 'json') {
const saysList = data[0].essay_list;
const saysStrip = saysList.slice(0, 10);

saysStrip.forEach(says => {
let content = says.content;
if (says.image) {
content += '【图片】';
}
if (says.aplayer) {
content += '【音乐】';
}
if (says.video) {
content += '【视频】';
}

saysDeposit(content);
});
} else if (saysMode === 'memos') {
const saysStrip = data.slice(0, 10);

saysStrip.forEach(says => {
let content = says.content
content = content.replace(/#说说/g, '');

content = content.replace(/!\[\]\((.*?)\)/g, '【图片】');
content = content.replace(/\{ music (.*?) \}/g, '【音乐】');
content = content.replace(/\{ bilibili (.*?) \}/g, '【视频】');
content = content.replace(/\{ player (.*?) \}/g, '【视频】');

saysDeposit(content);
});
}
syasLocalCache();
})
.catch(error => console.error('即刻mini | ', error));
}
function saysDeposit(saysdata) {
const groupName = 'Solitude';
const storedData = localStorage.getItem(groupName);
const existingData = storedData ? JSON.parse(storedData) : {};
existingData.says = existingData.says || {};

existingData.says.value = existingData.says.value || [];
existingData.says.value.push(saysdata);
existingData.says.expiration = new Date().getTime() + 10 * 60 * 1000;
localStorage.setItem(groupName, JSON.stringify(existingData));
}
syasLocalCache();
})()

参考

贡献