适配日记簿

This commit is contained in:
anian
2026-03-10 17:21:05 +08:00
parent af82232813
commit 6810791637
46 changed files with 28829 additions and 25659 deletions
+64
View File
@@ -0,0 +1,64 @@
// 接管手机端底部音乐按钮
function modify_m_bottom_music_button_link() {
const music = document.querySelector('a[href*="#m_bottom_music_button"]');
if (!music) return;
music.addEventListener('click', function(event) {
event.preventDefault();
mu_box_show();
});
}
// 视频默认正方形,播放恢复比例
function modify_moment_video_size() {
const videos = document.querySelectorAll('.pix_video');
videos.forEach(video => {
if (!video.hasAttribute('data-video-events-bound')) {
video.addEventListener('play', () => {
video.classList.add('is-playing');
});
video.addEventListener('ended', () => {
video.classList.remove('is-playing');
});
video.setAttribute('data-video-events-bound', 'true');
}
});
}
// = = = = = = = = = = = = = = = = 触发器 = = = = = = = = = = = = = = = =
// 集合:PJAX(初始化执行+PJAX执行)
function init_anian_pjax() {
modify_moment_video_size();
}
// 集合:初始化(仅初始化执行一次)
function init_anian_inport() {
init_anian_pjax();
modify_m_bottom_music_button_link();
}
// PJAX监听
document.addEventListener('pjax:complete', function () {
init_anian_pjax();
})
// 入口
init_anian_inport();
+2794 -1594
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+216 -216
View File
@@ -1,217 +1,217 @@
var pix = {
/**
* 页面運行時間
*/
addRuntime: function () {
const $runtimeCount = document.getElementById('runtimeshow');
if ($runtimeCount) {
var s1 = $runtimeCount.innerText;//建站时间
if (s1) {
s1 = new Date(s1.replace(/-/g, "/"));
s2 = new Date();
var days = s2.getTime() - s1.getTime();
var number_of_days = parseInt(days / (1000 * 60 * 60 * 24));
$runtimeCount.innerText = number_of_days + '天';
}
}
},
loadLightbox: function () {
/**
* fancybox
*/
const addFancybox = function (ele) {
const runFancybox = (ele) => {
ele.each(function (i, o) {
const $this = $(o)
const lazyloadSrc = $this.attr('data-src') || $this.attr('src')
const dataCaption = $this.attr('alt') || ''
$this.wrap(`<a href="${lazyloadSrc}" data-fancybox="images" class="fancybox" data-srcset="${lazyloadSrc}"></a>`)
})
}
runFancybox($(ele))
}
const $fancyboxEle = document.querySelectorAll('.single-content :not(a) > img,.single-content > img')
if ($fancyboxEle.length > 0) {
addFancybox($fancyboxEle)
}
},
initTopBar: function () {
let topBar = $('#masthead .top_bar');
var placeholder = document.querySelector('#masthead .uk-sticky-placeholder');
var width = $('#masthead').width();
var height = '71.9979px;'
if (window.innerWidth < 960) {
if (window.innerWidth <= 540) {
height = '63.9957px'
}
topBar.addClass('uk-sticky-fixed').css({ "width": width, "top": '0px', "position": "fixed" });
$('#masthead .uk-sticky-placeholder').css({ "width": width, "height": height, "margin": "0px" });
placeholder.hidden = false;
} else {
topBar.removeAttr("style");
$('#masthead .uk-sticky-placeholder').css({ "width": width, "height": '71.9979px;', "margin": "0px" });
placeholder.hidden = true;
}
},
getMsg: function () {
var com_msg_btn = $('.com_msg_btn')
var msg_modal_inner = $('.msg_modal_inner')
let username = Theme.username
var f_unread_num = 0;
if (com_msg_btn.length > 0) {
$.ajax({
type: "get",
url: `/apis/api.notification.halo.run/v1alpha1/userspaces/${username}/notifications?fieldSelector=spec.unread=true`,
success: function (res) {
if (res.total == 0) {
$('.f_unread_num').remove();
}else {
f_unread_num = res.total
com_msg_btn.append(`<small class="f_unread_num">${res.total}</small>`);
}
if (msg_modal_inner.length > 0) {
$.ajax({
type: "get",
url: `/apis/api.notification.halo.run/v1alpha1/userspaces/${username}/notifications?page=1&size=10`,
success: function (res) {
var data = res.items
if (data.length > 0) {
var unread_box = `
<div class="unread_box">
<div class="unread_tip"># 您有${f_unread_num}条未读消息 #
<a href="/uc/notifications" target="_blank">查看全部</a>
</div>
`
var read_box = `
<div class="read_box">
`
var unreadSum = 0
data.forEach((e => {
if (e.spec.unread) {
unreadSum = unreadSum + 1;
unread_box += `
<div class="vi_reply_item">
<div class="box" msg-name="${e.metadata.name}">
<span class="mark_read">标记为已读</span>
<div class="avatar_top"><img alt=""
src="https://cravatar.cn/avatar/?d=mp"
class="avatar avatar-50 photo" height="50" width="50" decoding="async"></div>
${e.spec.htmlContent}
</div>
</div>
`
} else {
read_box += `
<div class="vi_reply_item">
<div class="box">
<div class="avatar_top"><img alt=""
src="https://cravatar.cn/avatar/?d=mp"
class="avatar avatar-50 photo" height="50" width="50" decoding="async"></div>
${e.spec.htmlContent}
</div>
</div>
`
}
}
))
unread_box += `</div>`
read_box += `</div><p class="msg_limit">只显示最新10条未读和已读信息</p> `
msg_modal_inner.html(unread_box + read_box)
} else {
msg_modal_inner.html(`
<p class="no_posts"><small># 暂无消息 #</small><img class="s_nodata"
src="/themes/theme-pix/assets/img/nodata.png"></p>
<p class="msg_limit">只显示最新10条未读和已读信息</p>
`)
}
},
error: function () {
msg_modal_inner.html(`
<p class="no_posts"><small># 暂无消息 #</small><img class="s_nodata"
src="/themes/theme-pix/assets/img/nodata.png"></p>
<p class="msg_limit">只显示最新10条未读和已读信息</p>
`)
},
});
}
}
});
}
},
initGalleryPhotos: function () {
var galleryPhotos = document.querySelectorAll('.gallery-photos .gallery-photo');
if (galleryPhotos.length > 0) {
// 瀑布流排版
imgStatus.watch('.photo-img', () => {
waterfall('.gallery-photos');
});
}
},
topCategoriesBarScroll: function () {
if (document.getElementById("cat_nav_items")) {
let xscroll = document.getElementById("cat_nav_items");
xscroll.addEventListener("mousewheel", function (e) {
//计算鼠标滚轮滚动的距离
let v = -e.wheelDelta / 2;
xscroll.scrollLeft += v;
//阻止浏览器默认方法
e.preventDefault();
}, false);
}
},
topTableBarScroll: function () {
var tables = document.querySelectorAll('.single-content table')
if (tables.length>0) {
tables.forEach(function (xscroll) {
xscroll.addEventListener("mousewheel", function (e) {
//计算鼠标滚轮滚动的距离
let v = -e.wheelDelta / 2;
xscroll.scrollLeft += v;
//阻止浏览器默认方法
e.preventDefault();
}, false);
})
}
},
//处理瞬间权限
roleMoments: async function () {
const response = await fetch(`/apis/api.console.halo.run/v1alpha1/users/-`);
const data = await response.json();
var roles = data.roles;
if(roles.length>0) {
var name = roles[0].metadata.name
if(name != 'super-role') {
let dependencies = roles[0].metadata.annotations["rbac.authorization.halo.run/dependencies"]
if(dependencies != undefined) {
var targetRoles = ["role-template-uc-moments-approved","role-template-uc-moments-publish"]
const result = targetRoles.some(r => dependencies.includes(r));
if(!result) {
$('.normal_edit').remove();
}
}else {
$('.normal_edit').remove();
}
}
}
},
var pix = {
/**
* 页面運行時間
*/
addRuntime: function () {
const $runtimeCount = document.getElementById('runtimeshow');
if ($runtimeCount) {
var s1 = $runtimeCount.innerText;//建站时间
if (s1) {
s1 = new Date(s1.replace(/-/g, "/"));
s2 = new Date();
var days = s2.getTime() - s1.getTime();
var number_of_days = parseInt(days / (1000 * 60 * 60 * 24));
$runtimeCount.innerText = number_of_days + '天';
}
}
},
loadLightbox: function () {
/**
* fancybox
*/
const addFancybox = function (ele) {
const runFancybox = (ele) => {
ele.each(function (i, o) {
const $this = $(o)
const lazyloadSrc = $this.attr('data-src') || $this.attr('src')
const dataCaption = $this.attr('alt') || ''
$this.wrap(`<a href="${lazyloadSrc}" data-fancybox="images" class="fancybox" data-srcset="${lazyloadSrc}"></a>`)
})
}
runFancybox($(ele))
}
const $fancyboxEle = document.querySelectorAll('.single-content :not(a) > img,.single-content > img')
if ($fancyboxEle.length > 0) {
addFancybox($fancyboxEle)
}
},
initTopBar: function () {
let topBar = $('#masthead .top_bar');
var placeholder = document.querySelector('#masthead .uk-sticky-placeholder');
var width = $('#masthead').width();
var height = '71.9979px;'
if (window.innerWidth < 960) {
if (window.innerWidth <= 540) {
height = '63.9957px'
}
topBar.addClass('uk-sticky-fixed').css({ "width": width, "top": '0px', "position": "fixed" });
$('#masthead .uk-sticky-placeholder').css({ "width": width, "height": height, "margin": "0px" });
placeholder.hidden = false;
} else {
topBar.removeAttr("style");
$('#masthead .uk-sticky-placeholder').css({ "width": width, "height": '71.9979px;', "margin": "0px" });
placeholder.hidden = true;
}
},
getMsg: function () {
var com_msg_btn = $('.com_msg_btn')
var msg_modal_inner = $('.msg_modal_inner')
let username = Theme.username
var f_unread_num = 0;
if (com_msg_btn.length > 0) {
$.ajax({
type: "get",
url: `/apis/api.notification.halo.run/v1alpha1/userspaces/${username}/notifications?fieldSelector=spec.unread=true`,
success: function (res) {
if (res.total == 0) {
$('.f_unread_num').remove();
}else {
f_unread_num = res.total
com_msg_btn.append(`<small class="f_unread_num">${res.total}</small>`);
}
if (msg_modal_inner.length > 0) {
$.ajax({
type: "get",
url: `/apis/api.notification.halo.run/v1alpha1/userspaces/${username}/notifications?page=1&size=10`,
success: function (res) {
var data = res.items
if (data.length > 0) {
var unread_box = `
<div class="unread_box">
<div class="unread_tip"># 您有${f_unread_num}条未读消息 #
<a href="/uc/notifications" target="_blank">查看全部</a>
</div>
`
var read_box = `
<div class="read_box">
`
var unreadSum = 0
data.forEach((e => {
if (e.spec.unread) {
unreadSum = unreadSum + 1;
unread_box += `
<div class="vi_reply_item">
<div class="box" msg-name="${e.metadata.name}">
<span class="mark_read">标记为已读</span>
<div class="avatar_top"><img alt=""
src="https://cravatar.cn/avatar/?d=mp"
class="avatar avatar-50 photo" height="50" width="50" decoding="async"></div>
${e.spec.htmlContent}
</div>
</div>
`
} else {
read_box += `
<div class="vi_reply_item">
<div class="box">
<div class="avatar_top"><img alt=""
src="https://cravatar.cn/avatar/?d=mp"
class="avatar avatar-50 photo" height="50" width="50" decoding="async"></div>
${e.spec.htmlContent}
</div>
</div>
`
}
}
))
unread_box += `</div>`
read_box += `</div><p class="msg_limit">只显示最新10条未读和已读信息</p> `
msg_modal_inner.html(unread_box + read_box)
} else {
msg_modal_inner.html(`
<p class="no_posts"><small># 暂无消息 #</small><img class="s_nodata"
src="/upload/nodata.png"></p>
<p class="msg_limit">只显示最新10条未读和已读信息</p>
`)
}
},
error: function () {
msg_modal_inner.html(`
<p class="no_posts"><small># 暂无消息 #</small><img class="s_nodata"
src="/upload/nodata.png"></p>
<p class="msg_limit">只显示最新10条未读和已读信息</p>
`)
},
});
}
}
});
}
},
initGalleryPhotos: function () {
var galleryPhotos = document.querySelectorAll('.gallery-photos .gallery-photo');
if (galleryPhotos.length > 0) {
// 瀑布流排版
imgStatus.watch('.photo-img', () => {
waterfall('.gallery-photos');
});
}
},
topCategoriesBarScroll: function () {
if (document.getElementById("cat_nav_items")) {
let xscroll = document.getElementById("cat_nav_items");
xscroll.addEventListener("mousewheel", function (e) {
//计算鼠标滚轮滚动的距离
let v = -e.wheelDelta / 2;
xscroll.scrollLeft += v;
//阻止浏览器默认方法
e.preventDefault();
}, false);
}
},
topTableBarScroll: function () {
var tables = document.querySelectorAll('.single-content table')
if (tables.length>0) {
tables.forEach(function (xscroll) {
xscroll.addEventListener("mousewheel", function (e) {
//计算鼠标滚轮滚动的距离
let v = -e.wheelDelta / 2;
xscroll.scrollLeft += v;
//阻止浏览器默认方法
e.preventDefault();
}, false);
})
}
},
//处理瞬间权限
roleMoments: async function () {
const response = await fetch(`/apis/api.console.halo.run/v1alpha1/users/-`);
const data = await response.json();
var roles = data.roles;
if(roles.length>0) {
var name = roles[0].metadata.name
if(name != 'super-role') {
let dependencies = roles[0].metadata.annotations["rbac.authorization.halo.run/dependencies"]
if(dependencies != undefined) {
var targetRoles = ["role-template-uc-moments-approved","role-template-uc-moments-publish"]
const result = targetRoles.some(r => dependencies.includes(r));
if(!result) {
$('.normal_edit').remove();
}
}else {
$('.normal_edit').remove();
}
}
}
},
}
File diff suppressed because it is too large Load Diff
+520 -418
View File
@@ -1,418 +1,520 @@
// 部分代码来自ZIBILL主题作者
// canvas生成海报
const poster = (function() {
const DEBUG = false
const WIDTH = 700
const HEIGHT = 1160
function init(config) {
const $container = document.querySelector(config.selector)
$container.style.border = '1px solid #f0f0f0'
const $wrapper = createDom('div', 'id', 'wrapper')
const $canvas = createDom('canvas', 'id', 'canvas', 'block')
const $day = createDom('canvas', 'id', 'day')
const $date = createDom('canvas', 'id', 'date')
const $title = createDom('canvas', 'id', 'title')
const $content = createDom('canvas', 'id', 'content')
const $logo = createDom('canvas', 'id', 'logo')
const $description = createDom('canvas', 'id', 'description')
appendChilds($wrapper, $canvas, $day, $date, $title, $content, $logo, $description)
$container.appendChild($wrapper)
const date = new Date()
// day
const dayStyle = {
font: 'bold 80px Helvetica',
color: 'rgba(255, 255, 255, 1)',
position: 'right'
}
drawOneline($day, dayStyle, date.getDate());
// date
const dateStyle = {
font: '23px Helvetica',
color: 'rgba(255, 255, 255, 1)',
position: 'right'
}
drawOneline($date, dateStyle, date.getFullYear() + '/' + (date.getMonth() + 1))
// title canvas
const titleStyle = {
font: '30px Helvetica',
lineHeight: 1.5,
color: 'rgba(255, 255, 255, 1)',
length: 2,
position: 'left'
}
titleStyle.font = (config.titleStyle && config.titleStyle.font) || titleStyle.font
titleStyle.color = (config.titleStyle && config.titleStyle.color) || titleStyle.color
titleStyle.position = (config.titleStyle && config.titleStyle.position) || titleStyle.position
drawMoreLines($title, titleStyle, config.title)
// content canvas
const contentStyle = {
font: '22px Helvetica',
lineHeight: 1.5,
position: 'left',
color: 'rgba(236, 241, 255, 1)'
}
contentStyle.font = (config.contentStyle && config.contentStyle.font) || contentStyle.font
contentStyle.color = (config.contentStyle && config.contentStyle.color) || contentStyle.color
contentStyle.lineHeight = (config.contentStyle && config.contentStyle.lineHeight) || contentStyle.lineHeight
contentStyle.position = (config.contentStyle && config.contentStyle.position) || contentStyle.position
drawMoreLines($content, contentStyle, config.content);
// description
const descriptionStyle = {
font: '24px Helvetica',
color: 'rgba(180, 180, 180, 1)',
lineHeight: 1.2,
position: 'left'
}
drawMoreLines($description, descriptionStyle, config.description)
// background image
const image = new Image();
image.crossOrigin = "Anonymous";
//logo
const logo = new Image();
logo.crossOrigin = "Anonymous";
logo.src = config.logo;
//qrcode
const qrcode = new Image();
qrcode.src = config.qrcode;
const onload = function() {
$canvas.width = WIDTH;
$canvas.height = HEIGHT;
image.src = config.banner;
image.onload = function() {
const ctx = $canvas.getContext('2d')
ctx.fillStyle = 'rgba(255, 255, 255, 1)';
ctx.fillRect(0, 0, $canvas.width, $canvas.height);
// banner
imgRect = coverImg($canvas.width - 40, $canvas.height / 1.2 - 40, image.width, image.height);
ctx.drawImage(image, imgRect.sx, imgRect.sy, imgRect.sWidth, imgRect.sHeight, 20, 20, $canvas.width - 40, $canvas.height / 1.2 - 40);
//覆盖层
ctx.fillStyle="rgba(0, 0, 0, 0.3)";
ctx.fillRect(20,20,$canvas.width - 40,$canvas.height / 1.2 - 40);
// 时间
ctx.drawImage($day, -20, 50)
ctx.drawImage($date, -21, 125)
//logo
var logowidth = logo.width;
var logoheight = logo.height;
var scale = logowidth / logoheight;
var logoh = 60;
var cwidth = logoh * scale;
ctx.drawImage(logo, 60, $canvas.height / 1.2 + 30, cwidth, logoh);
ctx.drawImage(qrcode, $canvas.width - 160, $canvas.height / 1.2 + 20, 120, 120);
//标题文字
ctx.drawImage($title, 20, $canvas.height / 2 + 20)
ctx.drawImage($content, 20, $canvas.height / 2 + 120)
ctx.drawImage($description, 20, $canvas.height / 1.2 + 110)
ctx.strokeStyle = 'rgba(122, 122, 122, 0.5)';
const img = new Image();
img.crossOrigin = "Anonymous";
img.src = $canvas.toDataURL('image/png')
const radio = config.radio || 0.7
img.width = WIDTH * radio
img.height = HEIGHT * radio
img.className = 'poster_load';
ctx.clearRect(0, 0, $canvas.width, $canvas.height)
$canvas.style.display = 'none'
if ($container.querySelector('.poster_load')) {
$container.querySelector('.poster_load').src = img.src;
} else {
$container.appendChild(img);
}
$container.appendChild(img);
$container.removeChild($wrapper)
if (config.callback) {
config.callback($container)
}
}
}
onload()
}
//裁切
function containImg(sx, sy, box_w, box_h, source_w, source_h) {
var dx = sx,
dy = sy,
dWidth = box_w,
dHeight = box_h;
if (source_w > source_h || (source_w == source_h && box_w < box_h)) {
dHeight = source_h * dWidth / source_w;
dy = sy + (box_h - dHeight) / 2;
} else if (source_w < source_h || (source_w == source_h && box_w > box_h)) {
dWidth = source_w * dHeight / source_h;
dx = sx + (box_w - dWidth) / 2;
}
return {
dx,
dy,
dWidth,
dHeight
}
}
function coverImg(box_w, box_h, source_w, source_h) {
var sx = 0,
sy = 0,
sWidth = source_w,
sHeight = source_h;
if (source_w > source_h || (source_w == source_h && box_w < box_h)) {
sWidth = box_w * sHeight / box_h;
sx = (source_w - sWidth) / 2;
} else if (source_w < source_h || (source_w == source_h && box_w > box_h)) {
sHeight = box_h * sWidth / box_w;
sy = (source_h - sHeight) / 2;
}
return {
sx,
sy,
sWidth,
sHeight
}
}
function createDom(name, key, value, display = 'none') {
const $dom = document.createElement(name)
$dom.setAttribute(key, value)
$dom.style.display = display
$dom.width = WIDTH
return $dom
}
function appendChilds(parent, ...doms) {
doms.forEach(dom => {
parent.appendChild(dom)
})
}
function drawOneline(canvas, style, content) {
const ctx = canvas.getContext('2d')
canvas.height = parseInt(style.font.match(/\d+/), 10) + 20
ctx.font = style.font
ctx.fillStyle = style.color
ctx.textBaseline = 'top'
let lineWidth = 0
let idx = 0
let truncated = false
for (let i = 0; i < content.length; i++) {
lineWidth += ctx.measureText(content[i]).width;
if (lineWidth > canvas.width - 60) {
truncated = true
idx = i
break
}
}
let padding = 30
if (truncated) {
content = content.substring(0, idx)
padding = canvas.width / 2 - lineWidth / 2
}
if (DEBUG) {
ctx.strokeStyle = "#6fda92";
ctx.strokeRect(0, 0, canvas.width, canvas.height);
}
if (style.position === 'center') {
ctx.textAlign = 'center';
ctx.fillText(content, canvas.width / 2, 0)
} else if (style.position === 'left') {
ctx.fillText(content, padding, 0)
} else {
ctx.textAlign = 'right'
ctx.fillText(content, canvas.width - padding, 0)
}
}
function drawMoreLines(canvas, style, content) {
const ctx = canvas.getContext('2d')
const fontHeight = parseInt(style.font.match(/\d+/), 10)
if (DEBUG) {
ctx.strokeStyle = "#6fda92";
ctx.strokeRect(0, 0, canvas.width, canvas.height);
}
ctx.font = style.font
ctx.fillStyle = style.color
ctx.textBaseline = 'top'
ctx.textAlign = 'center'
let alignX = 0
if (style.position === 'center') {
alignX = canvas.width / 2;
} else if (style.position === 'left') {
ctx.textAlign = 'left'
alignX = 40
} else {
ctx.textAlign = 'right'
alignX = canvas.width - 40
}
let lineWidth = 0
let lastSubStrIndex = 0
let offsetY = 0
for (let i = 0; i < content.length; i++) {
lineWidth += ctx.measureText(content[i]).width;
if (lineWidth > canvas.width - 120) {
ctx.fillText(content.substring(lastSubStrIndex, i), alignX, offsetY);
offsetY += fontHeight * style.lineHeight
lineWidth = 0
lastSubStrIndex = i
}
if (i === content.length - 1) {
ctx.fillText(content.substring(lastSubStrIndex, i + 1), alignX, offsetY);
}
}
}
return {
init
}
})()
//ajax生成文章海报
$('body').on('click', '.cr_poster', function () {
var post_id = $(this).attr('poster-data');
var banner = $(this).attr('banner');
var single_content = $('.single-content')
var t_content = $('#post-'+post_id+' .t_content')
var content = single_content.length>0 ? single_content[0].innerText : t_content[0].innerText
var content_length = content.length
var permalink = t_content.length>0 ? window.location.origin + '/moments/'+post_id : window.location.origin + window.location.pathname;
var title = t_content.length>0 ? Theme.site_title+'-瞬间' : document.title;
new QRious({ element: document.getElementById("twoCode"), value: permalink, size: 260, });
$('.poster_box').remove();
$('#share_modal_' + post_id + ' .poster_box_ap').append('<div class="poster_box"></div>');
if (content_length > 120) {
content = content.substring(0, 80) + '...'
}
function Posterdown(e) {
if (e == null) {
return;
}
var modal = '#share_modal_' + post_id;
var url = $('' + modal + ' .poster_box img').attr('src');
$('' + modal + ' .post_share_box').removeClass('hide');
$('' + modal + ' .poster_download').attr('href', url).attr('download', 'poster_' + post_id + '.png');
}
$('#share_modal_' + post_id + ' .poster_box').append('<div class="loading_box"><div uk-spinner></div></div>');
$('.loading_box').remove();
poster.init({
banner: banner,
selector: '.poster_box',
title: title,
content: content,
logo: Theme.site_logo,
qrcode: $('#twoCode').attr('src'),
description: Theme.admin_des,
callback: Posterdown
});
});
function convertImgToBase64(url, callback) {
var canvas = document.createElement('canvas');
var img = new Image();
img.crossOrigin = 'Anonymous';
img.onload = function () {
canvas.width = img.width;
canvas.height = img.height;
var ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
var ext = img.src.substring(img.src.lastIndexOf('.') + 1).toLowerCase();
var dataURL = canvas.toDataURL('image/' + ext);
callback(dataURL);
canvas = null;
};
img.src = url;
}
function handleShare(e, pic, title,pathname) {
var permalink = window.location.origin + pathname;
switch (e) {
case 'wb':
var url = `https://service.weibo.com/share/share.php?url=${permalink}&type=button&language=zh_cn&pic=${pic}&title=${title}`
window.open(url);
break
case 'qzone':
var url = `https://sns.qzone.qq.com/cgi-bin/qzshare/cgi_qzshare_onekey?url=${permalink}&title=${title}&pics=${pic}`
window.open(url);
break
case "qq":
var url = `http://connect.qq.com/widget/shareqq/index.html?url=${permalink}&title=${title}&pics=${pic}`
window.open(url);
break;
case "copy":
if (navigator.clipboard) {
navigator.clipboard.writeText(permalink).then(function() {
cocoMessage.success("链接已复制到剪贴板!");
}, function(err) {
cocoMessage.error("复制失败,请手动复制链接");
});
} else {
// 兼容旧浏览器
var tempInput = document.createElement('input');
tempInput.value = permalink;
document.body.appendChild(tempInput);
tempInput.select();
try {
document.execCommand('copy');
cocoMessage.success("链接已复制到剪贴板!");
} catch (err) {
cocoMessage.error("复制失败,请手动复制链接");
}
document.body.removeChild(tempInput);
}
break;
default:
break
}
}
// 部分代码来自ZIBILL主题作者
// canvas生成海报
const poster = (function() {
const DEBUG = false
const WIDTH = 700
const HEIGHT = 1160
/**
* 将图片URL加载为Image对象并返回Promise
* @param {string} url - 图片URL
* @returns {Promise<Image>} - 加载完成的Image对象
*/
function loadImage(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.crossOrigin = "Anonymous";
img.onload = () => resolve(img);
img.onerror = () => reject(new Error('Failed to load image: ' + url));
img.src = url;
});
}
/**
* 核心初始化函数
* @param {object} config - 配置对象
*/
function init(config) {
const $container = document.querySelector(config.selector)
// ------------------ 【修复点 2:清理容器,避免PJAX残余】 ------------------
// 在重新绘制前,确保清空容器,移除旧的 img 和 wrapper
$container.innerHTML = '';
// -----------------------------------------------------------------------
$container.style.border = '1px solid #f0f0f0'
const $wrapper = createDom('div', 'id', 'wrapper')
const $canvas = createDom('canvas', 'id', 'canvas', 'block')
const $day = createDom('canvas', 'id', 'day')
const $date = createDom('canvas', 'id', 'date')
const $title = createDom('canvas', 'id', 'title')
const $content = createDom('canvas', 'id', 'content')
const $logo = createDom('canvas', 'id', 'logo')
const $description = createDom('canvas', 'id', 'description')
appendChilds($wrapper, $canvas, $day, $date, $title, $content, $logo, $description)
$container.appendChild($wrapper)
const date = new Date()
// day canvas
const dayStyle = {
font: 'bold 80px Helvetica',
color: 'rgba(255, 255, 255, 1)',
position: 'right'
}
drawOneline($day, dayStyle, date.getDate());
// date canvas
const dateStyle = {
font: '23px Helvetica',
color: 'rgba(255, 255, 255, 1)',
position: 'right'
}
drawOneline($date, dateStyle, date.getFullYear() + '/' + (date.getMonth() + 1))
// title canvas
const titleStyle = {
font: '30px Helvetica',
lineHeight: 1.5,
color: 'rgba(255, 255, 255, 1)',
length: 2,
position: 'left'
}
titleStyle.font = (config.titleStyle && config.titleStyle.font) || titleStyle.font
titleStyle.color = (config.titleStyle && config.titleStyle.color) || titleStyle.color
titleStyle.position = (config.titleStyle && config.titleStyle.position) || titleStyle.position
drawMoreLines($title, titleStyle, config.title)
// content canvas
const contentStyle = {
font: '22px Helvetica',
lineHeight: 1.5,
position: 'left',
color: 'rgba(236, 241, 255, 1)'
}
contentStyle.font = (config.contentStyle && config.contentStyle.font) || contentStyle.font
contentStyle.color = (config.contentStyle && config.contentStyle.color) || contentStyle.color
contentStyle.lineHeight = (config.contentStyle && config.contentStyle.lineHeight) || contentStyle.lineHeight
contentStyle.position = (config.contentStyle && config.contentStyle.position) || contentStyle.position
drawMoreLines($content, contentStyle, config.content);
// description canvas
const descriptionStyle = {
font: '24px Helvetica',
color: 'rgba(180, 180, 180, 1)',
lineHeight: 1.2,
position: 'left'
}
drawMoreLines($description, descriptionStyle, config.description)
// ------------------ 【修复点 1:使用 Promise.all 等待所有图片加载】 ------------------
Promise.all([
loadImage(config.banner),
loadImage(config.logo),
loadImage(config.qrcode)
]).then(([image, logo, qrcode]) => {
$canvas.width = WIDTH;
$canvas.height = HEIGHT;
const ctx = $canvas.getContext('2d')
// 1. 绘制背景
ctx.fillStyle = 'rgba(255, 255, 255, 1)';
ctx.fillRect(0, 0, $canvas.width, $canvas.height);
// 2. 绘制 Banner
const imgRect = coverImg($canvas.width - 40, $canvas.height / 1.2 - 40, image.width, image.height);
ctx.drawImage(image, imgRect.sx, imgRect.sy, imgRect.sWidth, imgRect.sHeight, 20, 20, $canvas.width - 40, $canvas.height / 1.2 - 40);
// 3. 绘制覆盖层
ctx.fillStyle="rgba(0, 0, 0, 0.3)";
ctx.fillRect(20, 20, $canvas.width - 40, $canvas.height / 1.2 - 40);        
// 4. 绘制时间 (使用预先绘制好的 Canvas)
ctx.drawImage($day, -20, 50)
ctx.drawImage($date, -21, 125)
// 5. 绘制 Logo
var logoh = 60;
var cwidth = logoh * (logo.width / logo.height);
ctx.drawImage(logo, 60, $canvas.height / 1.2 + 30, cwidth, logoh);
// 6. 绘制二维码
ctx.drawImage(qrcode, $canvas.width - 160, $canvas.height / 1.2 + 20, 120, 120);
// 7. 绘制文字 (使用预先绘制好的 Canvas)
ctx.drawImage($title, 20, $canvas.height / 2 + 20)
ctx.drawImage($content, 20, $canvas.height / 2 + 120)
ctx.drawImage($description, 20, $canvas.height / 1.2 + 110)
ctx.strokeStyle = 'rgba(122, 122, 122, 0.5)';
// 8. 转换并显示最终图片
const img = new Image();
img.crossOrigin = "Anonymous";
img.src = $canvas.toDataURL('image/png')
const radio = config.radio || 0.7
img.width = WIDTH * radio
img.height = HEIGHT * radio
img.className = 'poster_load';
// 清理 Canvas 避免在屏幕上停留
ctx.clearRect(0, 0, $canvas.width, $canvas.height)
$canvas.style.display = 'none'
// 移除旧的 wrapper 并添加最终海报图
$container.appendChild(img);
// 必须先移除再调用 callback,以确保 DOM 结构干净
$container.removeChild($wrapper)
if (config.callback) {
config.callback($container)
}
}).catch(error => {
console.error('海报图片加载失败:', error);
// 可在此处添加错误处理,例如显示一个错误信息
$container.removeChild($wrapper);
if (config.callback) {
// 即使失败也调用回调,传入 null 或错误信息,以便上层逻辑处理
config.callback(null);
}
});
// -----------------------------------------------------------------------
}
// 裁剪函数(不变)
function containImg(sx, sy, box_w, box_h, source_w, source_h) {
var dx = sx,
dy = sy,
dWidth = box_w,
dHeight = box_h;
if (source_w > source_h || (source_w == source_h && box_w < box_h)) {
dHeight = source_h * dWidth / source_w;
dy = sy + (box_h - dHeight) / 2;
} else if (source_w < source_h || (source_w == source_h && box_w > box_h)) {
dWidth = source_w * dHeight / source_h;
dx = sx + (box_w - dWidth) / 2;
}
return {
dx,
dy,
dWidth,
dHeight
}
}
// 覆盖函数(不变)
function coverImg(box_w, box_h, source_w, source_h) {
var sx = 0,
sy = 0,
sWidth = source_w,
sHeight = source_h;
if (source_w > source_h || (source_w == source_h && box_w < box_h)) {
sWidth = box_w * sHeight / box_h;
sx = (source_w - sWidth) / 2;
} else if (source_w < source_h || (source_w == source_h && box_w > box_h)) {
sHeight = box_h * sWidth / box_w;
sY = (source_h - sHeight) / 2;
}
return {
sx,
sy,
sWidth,
sHeight
}
}
// 创建 DOM 元素(不变)
function createDom(name, key, value, display = 'none') {
const $dom = document.createElement(name)
$dom.setAttribute(key, value)
$dom.style.display = display
$dom.width = WIDTH
$dom.height = HEIGHT // 新增:确保 canvas 都有默认高度,尽管后续会重设
return $dom
}
// 追加子元素(不变)
function appendChilds(parent, ...doms) {
doms.forEach(dom => {
parent.appendChild(dom)
})
}
// 绘制单行文本(不变)
function drawOneline(canvas, style, content) {
const ctx = canvas.getContext('2d')
canvas.height = parseInt(style.font.match(/\d+/), 10) + 20
ctx.font = style.font
ctx.fillStyle = style.color
ctx.textBaseline = 'top'
let lineWidth = 0
let idx = 0
let truncated = false
for (let i = 0; i < content.length; i++) {
lineWidth += ctx.measureText(content[i]).width;
if (lineWidth > canvas.width - 60) {
truncated = true
idx = i
break
}
}
let padding = 30
if (truncated) {
content = content.substring(0, idx)
// 修正:如果内容被截断,应该重新计算截断后的文本宽度,再计算 padding
lineWidth = ctx.measureText(content).width;
if (style.position === 'center') {
padding = canvas.width / 2 - lineWidth / 2;
} else if (style.position === 'left') {
padding = 30; // 保持左侧间距
} else {
padding = canvas.width - 30; // 保持右侧间距
}
}
if (DEBUG) {
ctx.strokeStyle = "#6fda92";
ctx.strokeRect(0, 0, canvas.width, canvas.height);
}
if (style.position === 'center') {
ctx.textAlign = 'center';
ctx.fillText(content, canvas.width / 2, 0)
} else if (style.position === 'left') {
ctx.textAlign = 'left'
ctx.fillText(content, padding, 0)
} else {
ctx.textAlign = 'right'
ctx.fillText(content, canvas.width - padding, 0)
}
}
// 绘制多行文本(不变)
function drawMoreLines(canvas, style, content) {
const ctx = canvas.getContext('2d')
const fontHeight = parseInt(style.font.match(/\d+/), 10)
let totalLines = 0;
// 预计算行高并设置 Canvas 高度
let lineWidth = 0
let currentLine = 0
for (let i = 0; i < content.length; i++) {
lineWidth += ctx.measureText(content[i]).width;
if (lineWidth > canvas.width - 120) {
currentLine++;
lineWidth = 0
}
if (i === content.length - 1) {
currentLine++;
}
}
totalLines = currentLine;
canvas.height = (fontHeight * style.lineHeight) * totalLines + 20; // 留出一些底部边距
if (DEBUG) {
ctx.strokeStyle = "#6fda92";
ctx.strokeRect(0, 0, canvas.width, canvas.height);
}
ctx.font = style.font
ctx.fillStyle = style.color
ctx.textBaseline = 'top'
ctx.textAlign = 'center'
let alignX = 0
if (style.position === 'center') {
alignX = canvas.width / 2;
} else if (style.position === 'left') {
ctx.textAlign = 'left'
alignX = 40
} else {
ctx.textAlign = 'right'
alignX = canvas.width - 40
}
lineWidth = 0
let lastSubStrIndex = 0
let offsetY = 0
for (let i = 0; i < content.length; i++) {
lineWidth += ctx.measureText(content[i]).width;
if (lineWidth > canvas.width - 120) {
ctx.fillText(content.substring(lastSubStrIndex, i), alignX, offsetY);
offsetY += fontHeight * style.lineHeight
lineWidth = 0
lastSubStrIndex = i
}
if (i === content.length - 1) {
ctx.fillText(content.substring(lastSubStrIndex, i + 1), alignX, offsetY);
}
}
}
return {
init
}
})()
// ajax生成文章海报
// 此处调用部分需要确保在 PJAX 环境下,该事件监听器能够重新绑定(如果使用传统的 document.ready,在 PJAX 中需要改为监听 pjax:success 等事件)
$('body').on('click', '.cr_poster', function () {
var post_id = $(this).attr('poster-data');
var banner = $(this).attr('banner');
var single_content = $('.single-content');
var t_content = $('#post-'+post_id+' .t_content');
// 确保 content 区域存在
var content_element = single_content.length > 0 ? single_content[0] : (t_content.length > 0 ? t_content[0] : null);
if (!content_element) {
// 如果没有内容元素,则不执行后续操作或给出提示
console.error('无法获取文章内容元素。');
return;
}
var content = content_element.innerText;
var content_length = content.length;
var permalink = t_content.length > 0 ? window.location.origin + '/moments/' + post_id : window.location.origin + window.location.pathname;
var title = t_content.length > 0 ? Theme.site_title + '-瞬间' : document.title;
// 确保 QRious 库可用
if (typeof QRious === 'undefined') {
console.error('QRious 库未加载。');
return;
}
// 检查 #twoCode 是否存在,如果不存在需要动态创建,否则 QRious 会失败
let twoCodeCanvas = document.getElementById("twoCode");
if (!twoCodeCanvas) {
twoCodeCanvas = document.createElement('canvas');
twoCodeCanvas.id = 'twoCode';
twoCodeCanvas.style.display = 'none';
document.body.appendChild(twoCodeCanvas); // 插入到 DOM 中
}
new QRious({ element: twoCodeCanvas, value: permalink, size: 260, });
var poster_box_selector = '#share_modal_' + post_id + ' .poster_box';
// ------------------ 【修复点 2:清理容器】 ------------------
// 先移除旧的 poster_box
$('.poster_box').remove();
// 重新创建并插入新的 poster_box
$('#share_modal_' + post_id + ' .poster_box_ap').append('<div class="poster_box"></div>');
// -----------------------------------------------------------------------
if (content_length > 120) {
content = content.substring(0, 80) + '...'
}
// Posterdown 函数中不再需要移除 loading_box,因为 init 会清空容器
function Posterdown(e) {
var modal = '#share_modal_' + post_id;
// 检查海报是否成功生成
if (e && $(modal + ' .poster_box img').length > 0) {
var url = $(modal + ' .poster_box img').attr('src');
$(modal + ' .post_share_box').removeClass('hide');
$(modal + ' .poster_download').attr('href', url).attr('download', 'poster_' + post_id + '.png');
} else {
// 错误处理,如果海报生成失败
cocoMessage.error("海报生成失败,请检查图片链接!");
}
}
// 移除 loading box 的时机提前,或者在 init 开始前完成
// $('.loading_box').remove(); // 原始代码中这行在创建 loading_box 之后,但其作用是移除**所有** .loading_box,可能存在问题
// 重新添加 loading 状态,并在 init 中等待
$(poster_box_selector).append('<div class="loading_box"><div uk-spinner></div></div>');
poster.init({
banner: banner,
selector: poster_box_selector, // 传入完整的选择器
title: title,
content: content,
logo: Theme.site_logo,
qrcode: $('#twoCode').attr('src'),
description: Theme.admin_des,
callback: Posterdown
});
// 移除 loading 动画
$(poster_box_selector + ' .loading_box').remove();
});
function convertImgToBase64(url, callback) {
var canvas = document.createElement('canvas');
var img = new Image();
img.crossOrigin = 'Anonymous';
img.onload = function () {
canvas.width = img.width;
canvas.height = img.height;
var ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
var ext = img.src.substring(img.src.lastIndexOf('.') + 1).toLowerCase();
// 修正:确保 toDataURL 使用正确的 MIME type,如果是 jpg/jpeg 应使用 image/jpeg
var mimeType = (ext === 'jpg' || ext === 'jpeg') ? 'image/jpeg' : 'image/png';
var dataURL = canvas.toDataURL(mimeType);
callback(dataURL);
canvas = null;
};
img.onerror = function() {
console.error('convertImgToBase64: 无法加载图片 ' + url);
callback(null);
}
img.src = url;
}
function handleShare(e, pic, title, pathname) {
var permalink = window.location.origin + pathname;
// 确保 cocoMessage 库可用
if (typeof cocoMessage === 'undefined') {
console.error('cocoMessage 库未加载。');
// 如果 cocoMessage 不可用,可以替换为原生的 alert
if (e === 'copy') {
alert("链接已复制到剪贴板!");
}
}
switch (e) {
case 'wb':
var url = `https://service.weibo.com/share/share.php?url=${encodeURIComponent(permalink)}&type=button&language=zh_cn&pic=${encodeURIComponent(pic)}&title=${encodeURIComponent(title)}`
window.open(url);
break
case 'qzone':
var url = `https://sns.qzone.qq.com/cgi-bin/qzshare/cgi_qzshare_onekey?url=${encodeURIComponent(permalink)}&title=${encodeURIComponent(title)}&pics=${encodeURIComponent(pic)}`
window.open(url);
break
case "qq":
var url = `http://connect.qq.com/widget/shareqq/index.html?url=${encodeURIComponent(permalink)}&title=${encodeURIComponent(title)}&pics=${encodeURIComponent(pic)}`
window.open(url);
break;
case "copy":
if (navigator.clipboard) {
navigator.clipboard.writeText(permalink).then(function() {
cocoMessage.success("链接已复制到剪贴板!");
}, function(err) {
cocoMessage.error("复制失败,请手动复制链接");
});
} else {
// 兼容旧浏览器
var tempInput = document.createElement('input');
tempInput.value = permalink;
document.body.appendChild(tempInput);
tempInput.select();
try {
document.execCommand('copy');
cocoMessage.success("链接已复制到剪贴板!");
} catch (err) {
cocoMessage.error("复制失败,请手动复制链接");
}
document.body.removeChild(tempInput);
}
break;
default:
break
}
}