416 lines
29 KiB
JavaScript
416 lines
29 KiB
JavaScript
/* 星空页 · 妈妈们的留言(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 { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[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=26110636').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 用户:「妈妈们的留言」后面加 6~8 个小头像 —— 取最热的 8 位 */
|
||
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, 8)
|
||
.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
|
||
};
|
||
})();
|