Valine实现评论“撤回”重新编辑

2022-06-27 701阅读

温馨提示:这篇文章已超过667天没有更新,请注意相关的内容是否还可用!

说到消息“撤回”,应该能想到 qq 或者微信提供的消息撤回功能,应该算是互联网上的一剂后悔药,这样做不仅能减少评论错误,还能极大程度提升用户使用体验,这两全其美的事情何乐而不为,可我们修改的对象 Valine 一如既往的没有提供这些人性化的功能,所以我们还得和以前一样看看文档写写 bug 才能把功能捣鼓出来..(想法来自 @Zsedczy 的评论重新编辑和撤销删除)

非撤回再编辑

具体实现

既然又是 leancloud 那就先聊下,因为 leancloud 官方文档里有写到一个 revert() 方法可以撤销尚未保存的修改,不过我捣鼓半天没看懂这玩意咋能套进 Valine 实现保存数据前修改再保存,所以放弃了这个思路,转而像更简单粗暴的“数据更新”方法。

实现思路

同样的,利用 leancloud 数据储存的数据更新方法,对已经成功储存的指定数据进行修改再储存,简单来说就是更新数据(和之前更新点赞数据相似)当 Valine 提交评论后我们提供按钮对指定 id 的数据进行查询并修改的过程,而撤回评论则是使用 leancloud 提供的 destory() 方法进行云端数据的删除操作。

实践

了解完思路我们仍然从 Valine.js 下手,首先新增/修改按钮,定位到

<button type="button" title="Ctrl+Enter" class="vsubmit vbtn">' + e.locale.ctrl.reply + '</button>

修改为

<button type="button" title="Ctrl+Enter" id="pushBtn" class="vsubmit vbtn">' + e.locale.ctrl.reply + '</button><button type="button" id="repushBtn" class="vsubmit vbtn" style="display:none"> 重新提交 </button>

我们需要找到 valine 提交评论数据后的位置,定位到 n.setACL(I()),n.save().then(function(e){ 后添加

let input = _.comment.value,  //输入框评论(已提交)
objId = e.id,  //评论id(已提交)
redo = AV.Object.createWithoutData('Comment', objId),  //更新当前评论数据方法
el = document.createElement("div"),
div = '<p>评论<ins style="color:#fff"> '+input+' </ins>已成功提交!你还可以 <button id="redo"> 重新编辑 </button> 或 <button id="del"> 撤销 </button> 该评论!</p>',
push = document.getElementById("pushBtn"),  //旧按钮
repush = document.getElementById("repushBtn"), //新按钮
pushBack = (e)=>{
push.style.display="block";
repush.style.display="none";
document.getElementById(e).remove();
};
el.setAttribute("id","reEdit");
document.body.appendChild(el);
el.innerHTML = div;
//重新编辑点击事件
document.getElementById("redo").onclick=function(){
_.comment.focus();  //聚焦输入框
_.comment.value = input;  //将已提交的评论内容重新写入到输入框
push.style.display="none";  //隐藏旧按钮
repush.style.display="block";  //显示新按钮
//重新编辑按钮点击事件
repush.onclick=function(){
redo.set("comment", "<p>"+_.comment.value+"</p>");  //更新数据为:当前输入框内容
//redo.set("isEdited", true);  //可选(设定重复编辑判断规则,和之前的置顶实现一样在控制台新增指定列,然后更新数据做判断后写入元素,这里就不重复列了)
//保存更新后的数据
redo.save().then(function(e){
pushBack("reEdit");  //隐藏新按钮 显示旧按钮 移除提示框
document.getElementById(objId).querySelector(".vcontent p").innerHTML = _.comment.value;  //更新本地显示内容(伪即时更新)
B()  //执行预设函数 B()(数据清空)
}).catch(function(e) {
console.log("reEdit Err.")
})
}
};
//撤销点击事件
document.getElementById("del").onclick=function(){
//定义删除函数
let del = () =>{
let v = u.find(t.el, ".vnum");
//递减当前评论数量并写入本地显示
if(v!=null){
let n = Number(v.innerText);
n--;
v.innerText=n
}
redo.destroy();  //删除云端数据
pushBack("reEdit");  //隐藏新按钮 显示旧按钮 移除提示框
document.getElementById(objId).remove();  //更新本地显示内容(伪即时更新)
B()  //执行预设函数 B()(数据清空)
};
let cf = confirm(" 即将删除评论(不可逆): "+input+",确定?");  //弹窗确认删除
cf==true ? del() : false;  //删除逻辑
};

问题修复

以上代码是已经可以实现重复编辑和撤销评论的逻辑了,不过这时候发现更新数据提交后没有响应,打开控制台才发现报了 400 错误,一看是有关于 ACL 权限的就懂了,看了下 save 之前的 setACL,是一个定义好的函数 I() ,是这么写的(其实这里已经很明了了0!就是开权限了)

I = function() {
var e = new AV.ACL;
return e.setPublicReadAccess(!0),
e.setPublicWriteAccess(!1),
e
}

两个读写权限设置,这一看报错就是因为没有权限写入更新数据,去 leancloud 控制台一看,果不其然

安全考虑都是可读不可写

然后我在 官方 ACL 文档 里找到了一些设置权限的方法,

I = function() {
var e = new AV.ACL,
admin = new AV.Role('admin');
return e.setPublicReadAccess(!0),
e.setPublicWriteAccess(!1),  //实际这里改为 !0 就可以了(不考虑安全因素
//e.setRoleWriteAccess(admin, true); //指定用户编辑
//e.setWriteAccess(AV.User.current(), true),  //当前 id 可编辑(Owner 有 bug 用户首次评论点击重复编辑按钮会获取到上一个id,弃用了)
e
}

以上的当前用户可修改评论和指定用户可修改最终效果都不太理想,所以还是将 e.setPublicWriteAccess(!1) 改为 e.setPublicWriteAccess(!0) 全读写了。

拓展

要实现评论是否被重复编辑,一个是记录并判断评论提交时间,不过太麻烦,还有一个就和置顶 topset 的实现是一样的,在 leancloud 控制台新建一个 isEdited 列默认值 flase ,然后在重复编辑评论提交时 set 一个 isEdited 的参数就行了,剩下的就和上次笔记写的一样了(写个判断,在写入元素前新增个元素并将值作为判断 isEdited 的结果即可)

重复编辑显示是可选项

问题优化

ACL 权限全读写还没完全搞懂,虽然可以用不过还是不推荐这么搞,记得经常备份才行。

F12 400 报错:Error: 此电子邮箱已经被占用问题

参考链接 leancloud 官方文档

“因果关系”

因为长期对 valine 的修改,现在发现了一个很早就出现的 bug.. 页面没有评论时第一次在页面评论不会刷出评论数量(刷新后又好了)不知道是哪里出问题了,因为我有很大量备份索性回滚,无果。

后期有时间好好排查下什么情况引起的,改的东西太多就容易出错,还不容易察觉,就是这么蛋疼..

已知 bug

  • 取消回复后,没有清除已@到的人

  • 重新编辑提交后,没有@到人

以上,有问题评论区留言。