Files
mama-box/js/cm-pics.js

149 lines
11 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 11-05 评论 / 回复带图(用户:「评论的时候,或者回复别人评论时候,可以加上上传图片功能」)
孕妈广场帖子详情(guangchang/index.html)和专题页评论区(topic/_shared/tools.js)共用。
· 每条最多 3 张;选图后先在本机压到长边 1600、JPEG 0.85,再传 /api/upload(同投稿),拿回本站地址随评论提交。
· 后端 /plaza/comment 带 images[](只认本站 /api/upload 地址),图片和文字一起进后台「评论审核」,通过后大家才看得到。
· 显示:评论文字下面一排小方图(最多 3 张),点开全屏看大图,点任意处关闭。
用法:
var P = MBCmPics.picker({ strip: 放缩略图的元素, onChange: fn })
P.pick() 打开相册 · P.urls() 已传好的地址 · P.busy() 还有没传完的 · P.count() 已选张数 · P.clear() 清空
MBCmPics.html(images) → 评论里那排图的 HTML(没有图返回 '')
11-05 兜爸心选宝妈表情包(「妈妈盒子 / 表情包」会话 ee1ae406 做的 10 张,assets/img/<md5 前 12>.webp,240px):
· 输入框里加笑脸按钮,点开 5×2 面板选一张(每条一张,再点别的就换),和文字 / 图片一起发;
· 存法:文字末尾带 [兜表情:收到~] 这样的记号(后端不用改,后台审核里也看得懂是哪张);
· 显示:MBCmPics.txt(文字) 代替 esc(文字) —— 记号换成表情图,不认识的记号原样当文字。
P.emoji() 当前选的表情记号(没选 = '');P.toggleEmo() 开关面板。 */
(function () {
if (window.MBCmPics) return;
var MAX = 3;
var ME_SRC = document.currentScript ? document.currentScript.src : location.href;
var EMO = [["收到~", "e2f8d1f15db3"], ["嘘,娃刚睡", "52c77092e7ce"], ["求链接!", "865e4ae61d08"], ["抱抱你", "7f19e4c30dd6"], ["带娃好累…", "e89ae2a04626"], ["我也是!", "57a4273dd024"], ["已下单 ✓", "e8808f01ec7b"], ["谢谢姐妹", "a2c15d7bf5a8"], ["哈哈哈哈", "26a8bdcafbb7"], ["宝妈加油!", "23459742b18f"]];
var EMO_DIR = new URL('../assets/img/', ME_SRC).href, EMO_BY = {};
EMO.forEach(function (e) { EMO_BY[e[0]] = e[1]; });
var emoSrc = function (h) { return EMO_DIR + h + '.webp'; };
var st = document.createElement('style');
st.textContent =
'.cmp-strip{display:flex;gap:8px;padding:8px 2px 2px;overflow-x:auto}.cmp-strip:empty{display:none}' +
'.cmp-t{position:relative;flex:none;width:58px;height:58px;border-radius:10px;overflow:hidden;background:#F1E9E5}' +
'.cmp-t img{width:100%;height:100%;object-fit:cover;display:block}' +
'.cmp-t.up::after{content:"上传中";position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.38);color:#fff;font-size:11px}' +
'.cmp-t.bad::after{content:"失败";position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(200,40,40,.5);color:#fff;font-size:11px}' +
'.cmp-t button{position:absolute;right:2px;top:2px;z-index:1;width:18px;height:18px;border-radius:50%;background:rgba(0,0,0,.55);color:#fff;font-size:13px;line-height:18px;text-align:center;padding:0;border:0}' +
'.cmp-g{display:flex;gap:6px;margin-top:6px}' +
'.cmp-g img{width:76px;height:76px;border-radius:8px;object-fit:cover;background:#F1E9E5;cursor:zoom-in}' +
'.cmp-btn{flex:none;display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;padding:0;border:0;border-radius:50%;background:none;color:#9C8781}' +
'.cmp-btn svg{width:21px;height:21px}' +
'.cmp-btn.on{color:#F4593C}' +
'.cmp-lb{position:fixed;inset:0;z-index:10000;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.92)}' +
'.cmp-lb img{max-width:100%;max-height:100%;object-fit:contain}' +
'.cmp-emo{display:grid;grid-template-columns:repeat(5,1fr);gap:6px;padding:10px 12px 8px;background:#fff;border-top:1px solid #EEE6E2}' +
'.cmp-emo[hidden]{display:none}' +
'.cmp-emo button{display:block;padding:2px;border:0;border-radius:10px;background:#FBF6F3;aspect-ratio:1}' +
'.cmp-emo button.on{box-shadow:inset 0 0 0 2px #F4593C}' +
'.cmp-emo img{display:block;width:100%;height:100%;object-fit:contain}' +
'.cmp-t.emo{background:#FBF6F3}.cmp-t.emo img{object-fit:contain}' +
'.cmp-sk{display:block;width:96px;height:96px;margin-top:6px;object-fit:contain}';
(document.head || document.documentElement).appendChild(st);
var EMO_ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><circle cx="12" cy="12" r="9"/><path d="M8.5 14.5c.9 1.3 2.1 2 3.5 2s2.6-.7 3.5-2"/><circle cx="9" cy="10" r=".6" fill="currentColor"/><circle cx="15" cy="10" r=".6" fill="currentColor"/></svg>';
var ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="15" rx="3"/><circle cx="9" cy="10.5" r="1.8"/><path d="M21 16l-5-5-8 8"/></svg>';
var esc = function (s) { return String(s).replace(/[&<>"']/g, function (c) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]; }); };
function shrink(f) {
return new Promise(function (res) {
var u = URL.createObjectURL(f), im = new Image();
im.onload = function () {
var k = Math.min(1, 1600 / Math.max(im.width, im.height)), c = document.createElement('canvas');
c.width = Math.round(im.width * k); c.height = Math.round(im.height * k);
c.getContext('2d').drawImage(im, 0, 0, c.width, c.height); URL.revokeObjectURL(u);
c.toBlob(function (b) { res(b || f); }, 'image/jpeg', .85);
};
im.onerror = function () { URL.revokeObjectURL(u); res(f); };
im.src = u;
});
}
function picker(o) {
var pics = [], ups = 0, strip = o.strip, emo = '', emoEl = null, panel = null;
function setEmo(n) {
emo = n || '';
if (emoEl) { emoEl.remove(); emoEl = null; }
if (emo) {
emoEl = document.createElement('span'); emoEl.className = 'cmp-t emo';
emoEl.innerHTML = '<img alt="" src="' + emoSrc(EMO_BY[emo]) + '"><button type="button" aria-label="去掉表情">×</button>';
emoEl.querySelector('button').addEventListener('mousedown', function (e) { e.preventDefault(); });
emoEl.querySelector('button').onclick = function () { setEmo(''); };
strip.insertBefore(emoEl, strip.firstChild);
}
if (panel) [].forEach.call(panel.children, function (b) { b.classList.toggle('on', b.dataset.n === emo); });
ch();
}
if (o.emoPanel) {
panel = o.emoPanel; panel.className = 'cmp-emo'; panel.hidden = true;
panel.innerHTML = EMO.map(function (e) { return '<button type="button" data-n="' + esc(e[0]) + '" aria-label="' + esc(e[0]) + '"><img alt="" src="' + emoSrc(e[1]) + '" loading="lazy"></button>'; }).join('');
panel.addEventListener('mousedown', function (e) { e.preventDefault(); });
panel.addEventListener('click', function (e) { var b = e.target.closest('[data-n]'); if (!b) return; setEmo(b.dataset.n === emo ? '' : b.dataset.n); panel.hidden = true; ch(); });
}
var fi = document.createElement('input'); fi.type = 'file'; fi.accept = 'image/*'; fi.multiple = true; fi.hidden = true;
document.body.appendChild(fi);
var ch = function () { if (o.onChange) o.onChange(); };
fi.addEventListener('change', function () {
var fs = [].slice.call(fi.files || []); fi.value = '';
if (pics.length + fs.length > MAX && o.toast) o.toast('每条最多带 ' + MAX + ' 张图');
fs.slice(0, MAX - pics.length).forEach(function (f) {
var it = { url: null, el: document.createElement('span') };
it.el.className = 'cmp-t up';
it.el.innerHTML = '<img alt=""><button type="button" aria-label="删除这张">×</button>';
it.el.querySelector('img').src = URL.createObjectURL(f);
it.el.querySelector('button').addEventListener('mousedown', function (e) { e.preventDefault(); });
it.el.querySelector('button').onclick = function () { pics = pics.filter(function (x) { return x !== it; }); it.el.remove(); ch(); };
strip.appendChild(it.el); pics.push(it); ups++;
shrink(f).then(function (b) { var fd = new FormData(); fd.append('file', b, 'comment.jpg'); return fetch('/api/upload', { method: 'POST', body: fd }); })
.then(function (r) { return r.json(); }).then(function (j) { it.url = j && j.result && j.result.url || null; if (!it.url) throw 0; it.el.classList.remove('up'); })
.catch(function () { if (o.toast) o.toast('图片上传失败,可以删掉重新选'); it.el.classList.remove('up'); it.el.classList.add('bad'); })
.then(function () { ups--; ch(); });
});
ch();
});
return {
pick: function () { if (pics.length >= MAX) { if (o.toast) o.toast('每条最多带 ' + MAX + ' 张图'); return; } fi.click(); },
urls: function () { return pics.map(function (x) { return x.url; }).filter(Boolean); },
busy: function () { return ups > 0; },
count: function () { return pics.length + (emo ? 1 : 0); },
clear: function () { pics = []; emo = ''; emoEl = null; strip.innerHTML = ''; if (panel) panel.hidden = true; ch(); },
emoji: function () { return emo ? '[兜表情:' + emo + ']' : ''; },
hasEmo: function () { return !!emo; },
toggleEmo: function () { if (panel) { panel.hidden = !panel.hidden; ch(); } },
emoOpen: function () { return !!(panel && !panel.hidden); }
};
}
function html(imgs) {
imgs = (imgs || []).filter(function (u) { return typeof u === 'string' && /^(https?:)?\/\//.test(u) || /^\//.test(u); }).slice(0, MAX);
return imgs.length ? '<div class="cmp-g">' + imgs.map(function (u) { return '<img src="' + esc(u) + '" alt="" loading="lazy" data-cmp="' + esc(u) + '">'; }).join('') + '</div>' : '';
}
/* 点评论里的图 → 全屏大图;点任意处关 */
document.addEventListener('click', function (e) {
var im = e.target.closest && e.target.closest('[data-cmp]');
if (!im) return;
e.preventDefault(); e.stopPropagation();
var lb = document.createElement('div'); lb.className = 'cmp-lb';
lb.innerHTML = '<img alt="" src="' + esc(im.dataset.cmp) + '">';
lb.addEventListener('click', function () { lb.remove(); });
document.body.appendChild(lb);
}, true);
/* 评论文字 → HTML:转义 + 把 [兜表情:xx] 换成表情图(每条只认第一个,其余去掉) */
function txt(t) {
var sk = '', out = esc(t || '').replace(/\[兜表情:([^\]]{1,12})\]/g, function (m, n) {
var h = EMO_BY[n] || EMO_BY[n.replace(/\s+/g, ' ')];
if (!h) return m;
if (!sk) sk = '<img class="cmp-sk" alt="' + esc(n) + '" src="' + emoSrc(h) + '">';
return '';
}).trim();
return out + sk;
}
window.MBCmPics = { picker: picker, html: html, txt: txt, ICON: ICON, EMO_ICON: EMO_ICON, MAX: MAX };
})();