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

416 lines
29 KiB
JavaScript
Raw 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.
/* 星空页 · 妈妈们的留言(2026-11-07)
· 二改(用户:「往下滑评论就挡住整个背景,不好;改成点『全部』那种半弹窗,最多拉到 2/3,一定留出一块星空;
首页要有个明显入口,别只放右上角『全部』」):
「说一句」按钮下面一条入口 → 半屏面板(先开到半屏,拖把手 / 往上划能拉到 2/3,再往上就不动了;往下拖关掉)。
面板里自己滚动看全部留言。右上角「全部」也打开这个面板(原来那个只列悄悄话的抽屉不用了)。
· 数据:广场置顶笔记 wh-<话题 from> 的评论 = cm.json 模拟(AGENTS.md 12c,不得挂星)+ /plaza/comments 已审核通过的真实评论
+ 我自己发的(11-07 起只显示已通过的)。打开星空页才拉 cm.json(版本号跟广场 index.html 引的一致,共用缓存)。
· 真实留言的赞走服务端(POST /plaza/comment/like,每人每条一次、不能赞自己),赞够 starLikes(后台可改,先 3)自动挂星;
模拟留言的赞只记本机 mmbox.whcm.v1。最热 / 最新 / 同孕周 三种看法。
· 三改(用户:「我不能给别人的留言回复」):点留言上的「回复」→ 底部输入框回复那一楼(先审后发);
底部输入框不点「回复」时就是发一条新留言。楼的 key 同广场:模拟 = wh-xxx#序号,真实 = r:<id>。
· 星空页「说一句」发的话也进这里(window.MBWhCm.post,app.js 调)。
· 四改(用户 11-07):去掉「看看大家说了啥」那条入口 → 抽屉平时在屏幕底部露一截(把手 + 标题),一滑就拉开,最高再往上一点(76%);
「被赞 3 次就变成星星」做重点;自己的留言能转发出去请人帮点赞(宿主转发卡,链接 ?wh=1&wc=<评论id>,对方打开直接定位到这条);
自己的话变成星星时放一段特效(每条只放一次,记在 mmbox.whstar.v1);说一句的弹窗换成星空样式(fillModal)。 */
(function () {
var page = document.getElementById('whPage'), bar = page && page.querySelector('.whp-bar');
if (!page || !bar || !window.MB_WH_THEME) return;
var TH = MB_WH_THEME(), PID = 'wh-' + TH.from.replace(/-/g, '');
var esc = function (s) { return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]; }); };
/* 11-07 修:原来只读 hash 里的 openid —— 小程序里身份常在 query 或同源缓存里(应用间跳回、转发卡片落地),
首页显示已登录、这里却提示「登录后就能留言」。改成和首页 hashParam 同一套:hash + query → wxAuth 同源缓存,退出登录算访客;每次用时现读。 */
function oid() {
if (window.mbGate && window.mbGate.out) return '';
var h = (location.hash.slice(1) || '') + '&' + (location.search.slice(1) || ''), m = h.match(/(?:^|[&#])openid=([^&#]*)/);
var v = m ? decodeURIComponent(m[1]).split('?')[0] : '';
if (!v) try { var id = window.wxAuth && wxAuth.identity ? wxAuth.identity() : null; v = id && id.openid || ''; } catch (e) {}
return v;
}
var HEART = '<svg viewBox="0 0 24 24"><path d="M12 20.5s-7.5-4.6-7.5-10.2A4.3 4.3 0 0112 7.6a4.3 4.3 0 017.5 2.7c0 5.6-7.5 10.2-7.5 10.2z"/></svg>';
var WC = (location.search.match(/[?&]wc=([A-Za-z0-9]{1,40})/) || [])[1] || ''; /* 别人转发来请我点赞的那条 */
/* 半屏面板 */
var sh = document.createElement('div');
sh.className = 'wcs'; sh.id = 'wcs'; sh.setAttribute('aria-hidden', 'true');
sh.innerHTML = '<div class="wcs-p" role="dialog" aria-label="妈妈们的留言">' +
'<div class="wcs-hd"><i class="wcs-grab" aria-hidden="true"></i><div class="wcs-tt"><b>妈妈们的留言</b><span class="wcs-avs" id="wcmAv" aria-hidden="true"></span><span id="wcmN"></span><i class="wcs-up">上滑看看</i>' +
'<button type="button" class="wcs-x" aria-label="收起"><svg viewBox="0 0 24 24" width="14" height="14"><path d="M6 6l12 12M18 6L6 18" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"/></svg></button></div>' +
'<div class="wcs-rule"><svg viewBox="0 0 20 20"><path d="M10 .5C10.9 7.4 12.6 9.1 19.5 10 12.6 10.9 10.9 12.6 10 19.5 9.1 12.6 7.4 10.9.5 10 7.4 9.1 9.1 7.4 10 .5Z"/></svg><span>留言被赞 <b id="wcmRuleN">6</b> 次,就会变成一颗星星挂上去</span></div>' +
'<div class="wcm-tb" role="tablist"><button type="button" class="on" data-wso="hot">最热</button><button type="button" data-wso="new">最新</button><button type="button" data-wso="wk">同孕周</button></div></div>' +
'<div class="wcs-ls" id="wcsLs"><div id="wcmLs"><p class="wcm-e">留言加载中…</p></div><button type="button" class="wcm-more" id="wcmMore" hidden>再看看</button></div>' +
'<div class="wcs-in"><span class="wcs-to" id="wcsTo" hidden></span><input id="wcsIn" type="text" maxlength="200" enterkeyhint="send" placeholder="说点什么…"><button type="button" id="wcsSend">发送</button></div>' +
'</div>';
page.appendChild(sh);
var P = sh.querySelector('.wcs-p'), lsBox = sh.querySelector('#wcsLs'), ls = sh.querySelector('#wcmLs'), more = sh.querySelector('#wcmMore');
var inp = sh.querySelector('#wcsIn'), toEl = sh.querySelector('#wcsTo'), sendB = sh.querySelector('#wcsSend');
var LK = 'mmbox.whcm.v1', LIKE = {};
try { LIKE = JSON.parse(localStorage.getItem(LK) || '{}') || {}; } catch (e) {}
var avP = function (a) { return typeof a === 'number' ? 'guangchang/av/' + ('000' + a).slice(-4) + '.webp' : a.indexOf('av:') === 0 ? 'guangchang/av/' + a.slice(3) + '.webp' : a.charAt(0) === '/' ? a : 'assets/img/mom/' + a + '.jpg'; };
var fmt = function (n) { return n >= 10000 ? (n / 10000).toFixed(1) + 'w' : String(n); };
var ago = function (ts) {
var d = (Date.now() - ts) / 1000;
if (d < 3600) return Math.max(1, Math.round(d / 60)) + ' 分钟前';
if (d < 86400) return Math.round(d / 3600) + ' 小时前';
var t = new Date(ts + 8 * 3600e3); return (t.getUTCMonth() + 1) + '-' + t.getUTCDate();
};
var tT;
function toast(t) { var x = document.getElementById('toast'); if (!x) return; x.textContent = t; x.classList.add('show'); clearTimeout(tT); tT = setTimeout(function () { x.classList.remove('show'); }, 1800); }
/* ---------- 数据 ---------- */
var SIM = null, REAL = null, RREP = [], ALL = [], shown = 0, loading = false, SORT = 'hot', STAR_N = 6; // 11-07 用户改门槛为 6(以服务端 starLikes 为准)
var top = function (c) { return !c.parent; };
function realOf(c) {
return { key: 'r:' + c.id, id: c.id, real: 1, n: c.nickname || '孕妈', av: c.avatar || 'assets/img/mom/k01.jpg', s: c.stage, t: c.text,
likes: c.likes || 0, liked: !!c.liked, star: !!c.star, ts: c.at, r: [] };
}
function repOf(c, me) { return { p: c.parent, n: me ? '我' : (c.nickname || '孕妈'), me: me, to: c.parentName, t: c.text, ts: c.at, st: me ? c.status : '' }; }
function load() {
if (loading || SIM) return; loading = true;
fetch('guangchang/cm.json?v=26110635').then(function (r) { return r.json(); }).then(function (j) {
var PP = j.p;
SIM = (j.c[PID] || []).map(function (c, k) {
var p = PP[c[0]];
return { key: PID + '#' + k, n: p[0], av: avP(p[1]), s: p[2], t: c[1], likes: c[2], st0: !!c[4],
r: c[3].map(function (x) { var q = x[0] < 0 ? null : PP[x[0]]; return { n: q ? q[0] : '孕妈悄悄话', t: x[1] }; }) };
});
build(); paintGo(); focusWc();
}).catch(function () { SIM = []; build(); focusWc(); });
fetchReal();
}
function fetchReal() {
return fetch('/api/mp/public/mama-box/plaza/comments?kind=post&target=' + encodeURIComponent(PID) + (oid() ? '&openid=' + encodeURIComponent(oid()) : ''))
.then(function (r) { return r.json(); }).then(function (j) {
var R = j && j.code === 200 && j.result || {};
if (R.starLikes) STAR_N = R.starLikes;
/* 11-07 用户改:别人的只出审核通过的;自己发的审核中也显示(标「审核中」,只有自己看得到),没通过的不出 */
var mine = (R.mine || []).filter(function (c) { return c.status !== 'rejected'; });
REAL = (R.list || []).filter(top).map(realOf).concat(mine.filter(top).map(function (c) { var o = realOf(c); o.n = '我'; o.me = 1; o.st = c.status; return o; }));
RREP = (R.list || []).filter(function (c) { return !top(c); }).map(function (c) { return repOf(c, 0); })
.concat(mine.filter(function (c) { return !top(c); }).map(function (c) { return repOf(c, 1); }));
build(); paintGo();
celebrate(); focusWc();
}).catch(function () { if (!REAL) { REAL = []; build(); } });
}
/* ---------- 我的话变成星星:特效(每条只放一次) ---------- */
var SK = 'mmbox.whstar.v1';
function celebrate() {
var done = {}; try { done = JSON.parse(localStorage.getItem(SK) || '{}') || {}; } catch (e) {}
var c = (REAL || []).filter(function (x) { return x.me && x.star && !done[x.id]; })[0];
if (!c || celebrate.on) return;
/* 等星空里的星星画出来再放(拉星星要几秒),放了才记下,没放成下次进来还会放 */
celebrate.on = 1; var n = 0;
(function wait() {
if (++n > 40) { celebrate.on = 0; return; }
if (!page.classList.contains('show') || !page.querySelector('.wsk-st')) { setTimeout(wait, 500); return; }
setTimeout(function () {
celebrate.on = 0;
if (!page.classList.contains('show')) return;
done[c.id] = 1; try { localStorage.setItem(SK, JSON.stringify(done)); } catch (e) {}
starFx(c.t, true);
}, 1200);
})();
}
function starFx(text, mine) {
snap('closed');
var fx = document.createElement('div');
fx.className = 'wst-fx';
fx.innerHTML = '<i class="wst-ring"></i><i class="wst-star"></i>' + Array.apply(null, Array(14)).map(function (_, k) {
return '<i class="wst-sp" style="--a:' + (k * 360 / 14) + 'deg;--d:' + (60 + Math.random() * 70).toFixed(0) + 'px;animation-delay:' + (1.25 + Math.random() * .2).toFixed(2) + 's"></i>'; }).join('') +
'<div class="wst-tx"><b>' + (mine ? '你的话变成星星啦' : '这句变成星星啦') + '</b><p>“' + esc(text.length > 40 ? text.slice(0, 39) + '…' : text) + '”</p><span>' +
(mine ? '同孕周的妈妈点开星空,就能看到你对宝宝说的话' : '谢谢你帮她点亮') + '</span></div>';
page.appendChild(fx);
if (navigator.vibrate) navigator.vibrate([10, 60, 20]);
fx.addEventListener('click', function () { fx.classList.add('out'); setTimeout(function () { fx.remove(); }, 500); });
setTimeout(function () { if (fx.isConnected) { fx.classList.add('out'); setTimeout(function () { fx.remove(); }, 500); } }, 5200);
}
/* ---------- 转发求赞 ---------- */
function shareMine(id) {
var c = (REAL || []).filter(function (x) { return x.id === id; })[0]; if (!c) return;
var href = location.origin + location.pathname + '?wh=1&wc=' + encodeURIComponent(id);
var title = '帮我点个赞吧!被赞 ' + STAR_N + ' 次,我对宝宝说的话就能变成一颗星星';
var img = new URL('assets/share/wh-like.jpg', document.baseURI).href; /* 11-07 用户:转发卡用星空图 */
/* 宿主转发好友取 web-view 当前真实地址,不看上报的 href —— 地址栏里也要带上这条留言 */
try { var q = new URLSearchParams(location.search); q.set('wh', '1'); q.set('wc', id); history.replaceState(history.state, '', location.pathname + '?' + q + location.hash); } catch (e) {}
if (window.wxAuth && wxAuth.reportLocation) wxAuth.reportLocation({ href: href, title: title, imageUrl: img });
var g = document.createElement('div');
g.className = 'wsh-g';
g.innerHTML = '<i class="wsh-arrow"></i><div class="wsh-c"><b>点右上角「···」</b><p>选「发送给朋友」或发到群里,<br>请她们帮你点个赞</p><p class="q">“' + esc(c.t.length > 30 ? c.t.slice(0, 29) + '…' : c.t) + '”</p>' +
'<p class="s">还差 ' + Math.max(0, STAR_N - c.likes) + ' 个赞,就变成星星了</p><button type="button">知道了</button></div>';
page.appendChild(g);
g.addEventListener('click', function (e) { if (e.target === g || e.target.closest('button')) g.remove(); });
}
/* 打开的是别人转发来的链接:拉开抽屉、把那条放最前并闪一下 */
function focusWc() {
if (!WC || !REAL || !SIM) return;
var w = WC; WC = '';
try { var q = new URLSearchParams(location.search); q.delete('wc'); history.replaceState(history.state, '', location.pathname + (q.toString() ? '?' + q : '') + location.hash); } catch (e) {}
var c = REAL.filter(function (x) { return x.id === w; })[0];
if (!c) { toast('这条留言还在审核,或者已经找不到了'); return; }
REAL = [c].concat(REAL.filter(function (x) { return x !== c; }));
c.pin = 1; build();
setTimeout(function () {
snap('full');
var el = ls.querySelector('.wcm-it[data-k="' + CSS.escape(c.key) + '"]');
if (el) { el.classList.add('hl'); lsBox.scrollTop = 0; }
toast(c.star ? '这句已经变成星星啦' : (c.me ? '这是你的留言' : '帮她点个赞,让这句话变成星星'));
}, 600);
}
var wkOf = function (c) { var x = /孕\s*(\d+)\s*周/.exec(c.s || ''); return x ? +x[1] : 0; };
/* 11-07 方案 A:模拟留言 = cm.json 标了已变星的 + 赞数(含我本机这一赞)够 STAR_N 的 */
function simStar(c) { c.star = c.st0 || likesOf(c) >= STAR_N; }
var likesOf = function (c) { return c.real ? c.likes : c.likes + (LIKE[c.key] ? 1 : 0); };
var likedOf = function (c) { return c.real ? c.liked : !!LIKE[c.key]; };
/* 最热:挂了星的在前,再按赞;最新:真实留言按时间,模拟的接在后面;同孕周:和我差 ±1 周的,按最热。我自己发的永远在最上面 */
function sorted() {
var pin = REAL.filter(function (c) { return c.pin; });
var mine = REAL.filter(function (c) { return c.me && !c.pin; }), rest = REAL.filter(function (c) { return !c.me && !c.pin; }).concat(SIM);
var hot = function (a, b) { return (!!b.star - !!a.star) || (likesOf(b) - likesOf(a)); };
if (SORT === 'new') rest = rest.filter(function (c) { return c.real; }).sort(function (a, b) { return b.ts - a.ts; }).concat(SIM);
else if (SORT === 'wk') {
var w = +window.MB_WH_WEEK || 0;
rest = w ? rest.filter(function (c) { var k = wkOf(c); return k && Math.abs(k - w) <= 1; }) : [];
rest.sort(hot);
} else rest.sort(hot);
return pin.concat(mine.sort(function (a, b) { return b.ts - a.ts; }), rest);
}
function total() { return (REAL ? REAL.length : 0) + (SIM ? SIM.length : 0); }
function paintGo() {
document.getElementById('wcmN').textContent = total() ? total() + ' 条' : '';
/* 11-07 用户:「妈妈们的留言」后面加小头像 —— 取最热的 3 位 */
var av = document.getElementById('wcmAv');
if (av && SIM && SIM.length && !av.firstChild) av.innerHTML = SIM.slice().sort(function (a, b) { return b.likes - a.likes; }).slice(0, 3)
.map(function (c) { return '<img src="' + esc(c.av) + '" alt="" onerror="this.remove()">'; }).join('');
document.getElementById('wcmRuleN').textContent = STAR_N;
}
function build() {
if (!SIM || !REAL) return;
SIM.forEach(simStar);
ALL = sorted(); shown = 0; ls.innerHTML = '';
paintGo();
if (!ALL.length) {
ls.innerHTML = '<p class="wcm-e">' + (SORT === 'wk' ? (window.MB_WH_WEEK ? '同孕周的妈妈还没留言,你先说一句吧' : '填好预产期,就能看同孕周妈妈说了啥') : '还没有人留言') + '</p>';
more.hidden = true; return;
}
add(20);
}
function repsOf(c) {
var x = c.r.slice();
RREP.forEach(function (r) { if (r.p === c.key) x.push(r); });
return x;
}
function item(c) {
var on = likedOf(c), rp = repsOf(c);
return '<div class="wcm-it" data-k="' + esc(c.key) + '"><img class="wcm-av" src="' + esc(c.av) + '" alt="" loading="lazy" onerror="this.style.visibility=\'hidden\'">' +
'<div class="wcm-bd"><p class="wcm-nm"><b>' + esc(c.n) + '</b>' + (c.me && c.st && c.st !== 'approved' ? '<em class="wcm-pend">审核中</em>' : '') + (c.s ? '<span>' + esc(c.s) + '</span>' : '') + (c.ts ? '<span>' + ago(c.ts) + '</span>' : '') + '</p>' +
'<p class="wcm-tx">' + esc(c.t) + '</p>' +
(c.star ? '<p class="wcm-star"><svg viewBox="0 0 20 20"><path d="M10 .5C10.9 7.4 12.6 9.1 19.5 10 12.6 10.9 10.9 12.6 10 19.5 9.1 12.6 7.4 10.9.5 10 7.4 9.1 9.1 7.4 10 .5Z"/></svg>已经变成星星了</p>' : '') +
(rp.length ? '<div class="wcm-rp">' + rp.map(function (x) {
return '<p' + (x.me ? ' class="me"' : '') + '><b>' + esc(x.n) + '</b>' + (x.to && x.to !== c.n ? ' 回复 <b>' + esc(x.to) + '</b>' : '') + ':' + esc(x.t) + (x.me && x.st && x.st !== 'approved' ? '<em class="wcm-pend">审核中</em>' : '') + '</p>';
}).join('') + '</div>' : '') +
(c.me ? mineFoot(c) : '<button type="button" class="wcm-re" data-wrp="' + esc(c.key) + '">回复</button>') + '</div>' +
(c.me ? '<span class="wcm-lk me">' + HEART + '<span>' + fmt(likesOf(c)) + '</span></span></div>' :
'<button type="button" class="wcm-lk' + (on ? ' on' : '') + '" data-wcl="' + esc(c.key) + '" aria-label="赞">' + HEART + '<span>' + fmt(likesOf(c)) + '</span></button></div>');
}
/* 自己的留言底下:还差几个赞 + 找人帮我点赞(审核通过才能转) */
function mineFoot(c) {
if (c.star) return '';
if (c.st !== 'approved') return '<p class="wcm-mf">正在审核,现在只有你自己看得到;通过后就公开,也能转给朋友帮你点赞</p>';
var n = Math.min(c.likes, STAR_N);
return '<div class="wcm-mf"><span class="wcm-pg"><i style="width:' + Math.round(n / STAR_N * 100) + '%"></i></span><span>' + n + '/' + STAR_N + ' 个赞,还差 ' + (STAR_N - n) + ' 个就变成星星</span>' +
'<button type="button" class="wcm-share" data-wsh="' + esc(c.id) + '">找人帮我点赞</button></div>';
}
function add(k) {
ls.insertAdjacentHTML('beforeend', ALL.slice(shown, shown + k).map(item).join(''));
shown = Math.min(ALL.length, shown + k); more.hidden = shown >= ALL.length;
}
function rerender(c) { var el = ls.querySelector('.wcm-it[data-k="' + CSS.escape(c.key) + '"]'); if (el) el.outerHTML = item(c); }
more.addEventListener('click', function () { add(30); });
/* ---------- 点赞 / 回复 ---------- */
function paintLk(b, c) { var on = likedOf(c); b.classList.toggle('on', on); b.querySelector('span').textContent = fmt(likesOf(c)); }
var REPLY = null;
function setReply(c) {
REPLY = c;
toEl.hidden = !c; toEl.textContent = c ? '回复 ' + c.n : '';
inp.placeholder = c ? '回复 ' + c.n + '…' : '说点什么…';
}
ls.addEventListener('click', function (e) {
var sb = e.target.closest('[data-wsh]');
if (sb) { shareMine(sb.dataset.wsh); return; }
var r = e.target.closest('[data-wrp]');
if (r) {
var rc = ALL.filter(function (x) { return x.key === r.dataset.wrp; })[0]; if (!rc) return;
setReply(rc); snap('full'); inp.focus(); return;
}
var b = e.target.closest('[data-wcl]'); if (!b) return;
var k = b.dataset.wcl, c = ALL.filter(function (x) { return x.key === k; })[0]; if (!c) return;
if (!c.real) {
/* 跟真实留言一样:赞了就不能取消;这一赞让它够了 STAR_N → 变星特效 */
if (LIKE[k]) { toast('你已经赞过啦'); return; }
LIKE[k] = 1;
try { localStorage.setItem(LK, JSON.stringify(LIKE)); } catch (e2) {}
var was0 = c.star; simStar(c);
paintLk(b, c); b.classList.add('pop'); setTimeout(function () { b.classList.remove('pop'); }, 500);
if (navigator.vibrate) navigator.vibrate(8);
if (c.star && !was0) { rerender(c); starFx(c.t, false); }
return;
}
/* 真实留言:服务端计数,赞了就不能取消(同星星的抱抱) */
if (!oid()) { toast('登录后就能点赞啦'); return; }
if (c.liked) { toast('你已经赞过啦'); return; }
c.liked = true; c.likes++; paintLk(b, c); b.classList.add('pop'); setTimeout(function () { b.classList.remove('pop'); }, 500);
if (navigator.vibrate) navigator.vibrate(8);
fetch('/api/mp/public/mama-box/plaza/comment/like?openid=' + encodeURIComponent(oid()) + '&id=' + encodeURIComponent(c.id), { method: 'POST' })
.then(function (x) { return x.json(); }).then(function (j) {
if (!j || j.code !== 200) throw j;
c.likes = j.result.likes; var was = c.star; c.star = !!j.result.star; paintLk(b, c);
if (c.star && !was) { rerender(c); starFx(c.t, false); }
}).catch(function (j) { c.liked = false; c.likes--; paintLk(b, c); toast((j && j.message) || '网络不太好,再点一次试试'); });
});
sh.querySelector('.wcm-tb').addEventListener('click', function (e) {
var b = e.target.closest('[data-wso]'); if (!b || b.dataset.wso === SORT) return;
SORT = b.dataset.wso;
[].forEach.call(this.querySelectorAll('[data-wso]'), function (x) { x.classList.toggle('on', x === b); });
build(); lsBox.scrollTop = 0;
});
function post(text, week, reply) {
if (!oid()) return Promise.resolve('登录后就能留言啦');
var body = { kind: 'post', target: PID, title: '孕妈悄悄话 · ' + TH.name, text: text, stage: week ? '孕 ' + week + ' 周' : '' };
if (reply) { body.parent = reply.key; body.parentName = reply.n; }
return fetch('/api/mp/public/mama-box/plaza/comment?openid=' + encodeURIComponent(oid()), {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body)
}).then(function (r) { return r.json(); }).then(function (j) {
if (!j || j.code !== 200 || !j.result) return (j && j.message) || '没发出去,再试一次';
if (j.result.status === 'rejected') return null; /* 审核中的也放进列表(标「审核中」,只有自己看得到) */
if (reply) { RREP.push(repOf(j.result, 1)); rerender(reply); }
else {
var o = realOf(j.result); o.n = '我'; o.me = 1; o.st = j.result.status;
REAL = [o].concat(REAL || []);
if (SIM) { build(); lsBox.scrollTop = 0; }
if (state === 'closed') setTimeout(function () { snap('half'); }, 300); /* 发完拉开留言区,让她看到自己那条(审核中) */
paintGo();
}
return null;
}).catch(function () { return '网络不太好,再试一次'; });
}
var busy = false;
function send() {
var t = inp.value.trim();
if (t.length < 2) { toast('多写一两个字吧'); return; }
if (busy) return; busy = true; sendB.disabled = true;
var REPLY0 = REPLY;
post(t, +window.MB_WH_WEEK || 0, REPLY).then(function (msg) {
busy = false; sendB.disabled = false;
if (msg) { toast(msg); return; }
inp.value = ''; inp.blur(); setReply(null);
toast(REPLY0 ? '回复已发出,审核通过后大家就能看到' : '已发出,审核通过后大家就能看到');
});
}
sendB.addEventListener('click', send);
inp.addEventListener('keydown', function (e) { if (e.key === 'Enter') { e.preventDefault(); send(); } });
/* ---------- 半屏面板:开 / 关 / 拖 ----------
按高度伸缩(底部输入框一直露着):closed = 底部露一截(把手 + 标题,PEEK px),half = 55%,full = 76%(上面留出一块星空),再往上拉不动 */
var state = 'closed', PEEK = 58;
(function () { /* 底部露一截要加上 iPhone 底部安全区 */
var pr = document.createElement('i'); pr.style.cssText = 'position:absolute;height:env(safe-area-inset-bottom);visibility:hidden';
page.appendChild(pr); PEEK += pr.offsetHeight || 0; pr.remove();
page.style.setProperty('--wpk', PEEK + 'px');
})();
function ph() { return page.clientHeight; }
function hFor(s) { return s === 'closed' ? PEEK : ph() * (s === 'full' ? .76 : .55); }
function setH(h, anim) { P.style.transition = anim ? '' : 'none'; P.style.height = Math.round(h) + 'px'; }
function snap(s) {
state = s;
sh.classList.toggle('show', s !== 'closed');
sh.setAttribute('aria-hidden', s === 'closed' ? 'true' : 'false');
setH(hFor(s), true);
if (s === 'closed') { inp.blur(); setReply(null); lsBox.scrollTop = 0; }
}
function open() { load(); snap('half'); }
sh.querySelector('.wcs-hd').addEventListener('click', function (e) { if (state === 'closed' && !e.target.closest('.wcs-x')) open(); });
sh.querySelector('.wcs-x').addEventListener('click', function () { snap('closed'); });
sh.addEventListener('click', function (e) { if (e.target === sh) snap('closed'); });
/* 右上角「全部」也开这个面板(原来那个只列悄悄话的抽屉不再用) */
var all = document.getElementById('whvAll');
if (all) all.addEventListener('click', function (e) { e.stopImmediatePropagation(); open(); }, true);
/* 拖:抓把手 / 标题拖;列表在顶上时往下拖 = 拖面板;半屏时在列表上往上划 = 先把面板拉满 */
var d0 = null;
P.addEventListener('touchstart', function (e) {
if (e.touches.length !== 1 || e.target.closest('.wcs-in')) return;
d0 = { y: e.touches[0].clientY, base: P.offsetHeight, hd: !!e.target.closest('.wcs-hd'), drag: false, t: Date.now() };
}, { passive: true });
P.addEventListener('touchmove', function (e) {
if (!d0) return;
var dy = e.touches[0].clientY - d0.y;
if (!d0.drag) {
if (Math.abs(dy) < 6) return;
var canDown = dy > 0 && (d0.hd || lsBox.scrollTop <= 0);
var canUp = dy < 0 && (d0.hd || state !== 'full');
if (!canDown && !canUp) { d0 = null; return; }
d0.drag = true;
}
if (e.cancelable) e.preventDefault();
if (state === 'closed') { load(); sh.classList.add('show'); }
setH(Math.max(PEEK, Math.min(hFor('full'), d0.base - dy)), false);
}, { passive: false });
P.addEventListener('touchend', function (e) {
if (!d0) return;
var dd = d0; d0 = null;
if (!dd.drag) return;
/* 11-07 用户:「滑了一下又缩回去,要很大力气才上得去」→ 按方向判断:往上动过 24px(或甩一下)就往上一档,
拖过半屏再往上就直接拉满;往下同理;只有几像素的小抖动才回原位 */
var dy = e.changedTouches[0].clientY - dd.y, h = dd.base - dy, v = dy / Math.max(1, Date.now() - dd.t);
if (dy < -24 || v < -.25) snap(state !== 'closed' || h > hFor('half') + 40 ? 'full' : 'half');
else if (dy > 24 || v > .25) snap(state !== 'full' || h < hFor('half') - 40 ? 'closed' : 'half');
else snap(state);
});
/* 在「说一句」按钮一带往上划,也能把抽屉拉上来 */
var by = null;
page.addEventListener('touchstart', function (e) {
by = state === 'closed' && !P.contains(e.target) && e.touches[0].clientY > page.getBoundingClientRect().bottom - 170 ? e.touches[0].clientY : null;
}, { passive: true });
page.addEventListener('touchmove', function (e) { if (by != null && by - e.touches[0].clientY > 30) { by = null; open(); } }, { passive: true });
/* app.js 先画了「今天还能说 N 句」(那时本脚本还没加载)—— 说一句现在发进留言区,不再按每天 3 句算,换成留言规则 */
function fixLeft() {
var el = document.getElementById('whpLeft');
if (el && /^今天/.test(el.textContent)) el.textContent = '先审核再公开,被赞 ' + STAR_N + ' 次就变成星星';
}
new MutationObserver(fixLeft).observe(document.getElementById('whpLeft'), { childList: true, characterData: true, subtree: true });
/* 关掉星空页时面板一起收 */
new MutationObserver(function () {
fixLeft();
if (page.classList.contains('show')) { load(); setH(PEEK, false); } else snap('closed');
}).observe(page, { attributes: true, attributeFilter: ['class'] });
if (page.classList.contains('show')) load();
fixLeft();
/* 星空页「说一句」→ 发成留言(app.js 的 #whSend 调)。返回 Promise:resolve(null) = 成功,resolve('提示') = 没发出去 */
/* 说一句的弹窗(11-07 用户:重新设计,有点设计感;「今天还能说」这类说明放到弹窗里) */
var M = document.getElementById('modalWhisper');
function fillModal() {
if (!M) return;
M.classList.add('wh-star');
var ld = document.getElementById('whMLead');
/* 11-07 用户:只留「被赞 3 次变成星星」,明显一点 */
ld.innerHTML = '<span class="wms hi"><svg viewBox="0 0 20 20"><path d="M10 .5C10.9 7.4 12.6 9.1 19.5 10 12.6 10.9 10.9 12.6 10 19.5 9.1 12.6 7.4 10.9.5 10 7.4 9.1 9.1 7.4 10 .5Z"/></svg>' +
'<span>被赞 <b>' + STAR_N + '</b> 次,<br>你的话就变成一颗星星</span></span>';
var lf = document.getElementById('whLeft'); if (lf) lf.textContent = '审核通过后,可以转给朋友帮你点赞';
}
if (M && !M.querySelector('.wms-sky')) M.insertAdjacentHTML('afterbegin', '<i class="wms-sky" aria-hidden="true"></i>');
window.MBWhCm = {
fillModal: fillModal,
starLikes: function () { return STAR_N; },
post: function (text, week) {
return post(text, week, null).then(function (msg) { if (!msg) setTimeout(function () { snap('half'); }, 400); return msg; });
},
open: open
};
})();