仿QQ空间的评论编辑器实现

news/2024/11/28 6:01:12/

为了给ikuku.cn社区加上同样的功能,花了一点时间实现了QQ空间的评论框,有想学习编辑器的实现原理的朋友,可以认真阅读代码。 需要包含jQuery。


这个编辑框可以在任意位置输入@时,弹出@人名的补全内容,并且只有@正确的人名,才会给服务器端生成数据。删除时,也是一次删掉人名,不会发生@joson 退格一下变成 @joso 这种。 可以给用户不会出错的愉快体验。


var commentEditor = function(id){
this.editorContainer = $('#'+id);
this.editorPlaceholder = $('<div class="comment_editor_placeholder"></div>');
this.editor = $('<div class="comment_editor_main"></div>');
this.currentEl = null;
this.currentOffset = null;
this.offsetLeft = 0;
this.editorStatus = 0;
this.op = {
placeholder : '在这里输入评论',
padding : 5
};
this.editorPlaceholder.css({
width : this.editorContainer.width() - this.op.padding*2,
height : this.editorContainer.height() - this.op.padding*2,
position:'relative'
});
this.editor.css({
overflow : 'auto',
width : this.editorContainer.width() - this.op.padding*2,
height : this.editorContainer.height() - this.op.padding*2,
position:'relative',
marginTop : -this.editorContainer.height()
});
this.editor.attr('contenteditable',true);
this.editorContainer.append(this.editorPlaceholder);
this.editorContainer.append(this.editor);
this.editorPlaceholder.text(this.op.placeholder);
this.editorContainer.addClass('comment_editor');
if( this.getData() != '' ){
this.editorPlaceholder.text('');
}
this.list = new commentEditor.downList(this);
this._init();
this.listenAt();
};

commentEditor.prototype = {
_init : function(){
var self = this;
this.listenChange();
$(document).bind('click',function(){
self.list.hide();
if( self.editorStatus == 1 ){
self.editorStatus = 0;
self.editor.trigger('editor.close');
}
});
this.editor.bind('click',function(e){
e.stopPropagation();
});
this.editor.bind('editor.change',function(){
self.listenCursor();
});
this.editor.bind('mouseup',function(){
self.listenCursor();
});
this.editor.bind('focus',function(){
self.editorPlaceholder.text('');
if( self.editorStatus == 0 ){
self.editorStatus = 1;
self.editor.trigger('editor.open');
}
});
this.editor.bind('blur',function(){
if( self.getData() == '' ){
self.editorPlaceholder.text(self.op.placeholder);
}
});
},
listenAt : function(){
var self = this,timeout=null,currentname=null;
this.editor.bind('editor.at',function(e,data){
self.offsetLeft = String(data).length + 1 ;
clearTimeout(timeout);
if( currentname == data ){
self.list.show();
return ;
}
currentname = data;
timeout = setTimeout(function(){
  $.post('/test_comment_exe.php',{action:'at',name:data},function(data){
  if( data != '' ){
self.list.show();
self.list.setData(data,function(data){
var op = $('<div><a href="javascript:">\
<div style="float:left;width:30px;height:30px;"><img src="/profile_image/'+data.ID+'/30" width="30" height="30" /></div>\
<div style="float:left; margin-left:10px;">'+data.user_nicename+'</div></a>\
</div>');
op.bind('click',function(){
self.insertHtml('<button contenteditable="false" class="comment_editor_element">@'+data.user_nicename+'</button>',true);
self.list.hide();
});
return op;
});
  }else{
self.list.hide();
  }
  });
},100);
});
this.editor.bind('editor.at_close',function(){
clearTimeout(timeout);
self.list.hide();
});
},
listenChange : function(){
var self = this,oldText=this.editor.html();
this.editor.bind('keyup',function(e){
var newText = self.editor.html();
if( newText != oldText ){
oldText = newText;
self.editor.trigger('editor.change');
}
});
},
getSelectionObject : function(){
if (window.getSelection){  // mozilla FF
return window.getSelection();
}else if (document.getSelection){
return document.getSelection();
}else if (document.selection){  //IE
return document.selection.createRange().text;
}
},
getRangeObject : function(selectionObject) {
return selectionObject.getRangeAt(0);
},
listenCursor : function(){
var userSelection = this.getSelectionObject();
if( !userSelection.anchorNode.data || userSelection.type == 'None' || userSelection.type == 'Range' ){
this.editor.trigger('editor.at_close');
return ;
}
this.currentOffset = userSelection.anchorOffset;
this.currentEl = userSelection.anchorNode;
var prevText = userSelection.anchorNode.data.substring( 0, userSelection.anchorOffset );
var lastDot = prevText.lastIndexOf('@');
if( lastDot != -1 ){
prevText = prevText.substring(lastDot);
if( !prevText.match(/\s/) ){
this.editor.trigger('editor.at',[ prevText.substring(1)]);
return ;
}else if( prevText.match(/^[^\s]+\s$/) ){
this.editor.trigger('editor.at_apply',[prevText.substring(1,prevText.length-1)]);
}
}
this.editor.trigger('editor.at_close');
},
insertHtml : function(html,lastspace){
var offsetLeft = this.offsetLeft;
this.offsetLeft = 0;
if( this.currentEl != null && this.currentOffset != null ){
this.editor.focus();
var userSelection = this.getSelectionObject();
var range = this.getRangeObject(userSelection);
var el = $(html);
var el_dom = el.get(0);
if( !el_dom ){
el_dom = document.createTextNode(html);
}
range.setStart(this.currentEl,this.currentOffset - offsetLeft );
range.setEnd(this.currentEl,this.currentOffset );
range.deleteContents();
range.insertNode( el_dom );
range.setStartAfter( el_dom, 0 );
range.setEndAfter( el_dom , 0 );
if(lastspace){
var space = document.createTextNode('\u00A0');
range.insertNode( space );
range.setStartAfter( space, 0 );
range.setEndAfter( space , 0 );
}
if( navigator.userAgent.indexOf('Chrome') != -1 ){
if( el_dom.nextSibling == el_dom.parentNode.lastChild && el_dom.nextSibling.data==''){
range.insertNode($('<br/>').get(0));
}
}
if( userSelection.setPosition )
userSelection.setPosition(range.startContainer,range.startOffset);
else{
var nrange = this.getRangeObject(userSelection);
nrange.setStart(range.startContainer,range.startOffset);
nrange.setEnd(range.startContainer,range.startOffset);
userSelection.removeAllRanges();
userSelection.addRange(nrange);
}
this.currentOffset = range.startOffset;
this.currentEl = range.startContainer;
}else{
//console.log( $(html) );
this.editor.html( this.editor.html() + html );
this.editor.focus();
var userSelection = this.getSelectionObject();
var range = this.getRangeObject(userSelection);
range.setStartAfter( this.editor.get(0).lastChild, 0 );
range.setEndAfter( this.editor.get(0).lastChild, 0 );
if( userSelection.setPosition ){
userSelection.setPosition(range.startContainer,range.startOffset);
}else{
var nrange = this.getRangeObject(userSelection);
nrange.setStart(range.startContainer,range.startOffset);
nrange.setEnd(range.startContainer,range.startOffset);
userSelection.removeAllRanges();
userSelection.addRange(nrange);
}
this.currentOffset = range.startOffset;
this.currentEl = range.startContainer;
}
},
getData : function(){
return this.editor.html();
},
bind : function(eventName,callback){
this.editor.bind(eventName,callback);
}
};

commentEditor.downList = function(container){
this.container = container;
this.list = $('<div class="comment_editor_list"></div>');
this.list.css({
position:'absolute',
width: this.container.editor.width()+this.container.op.padding*2 - 2,
top:this.container.editor.offset().top + this.container.editor.height()+this.container.op.padding*2,
left:this.container.editor.offset().left
});
this.status = 'show';
this.list.hide();
$(document.body).append(this.list);
this.list.bind('click',function(e){
e.stopPropagation();
});
};

commentEditor.downList.prototype = {
show : function(){
this.status = 'show';
this.list.show();
},
hide : function(){
this.list.hide();
},
setData : function(data,callback){
var self = this;
this.list.find('*').remove();
var data = $.parseJSON(data);
if(data){
$.each(data,function(i){
if(callback)
self.list.append(callback(data[i]));
});
}
}
};


http://www.ppmy.cn/news/668886.html

相关文章

安卓仿QQ空间实现(含图片的动态编辑、发表、点赞、评论)

之前做项目的时候需要用到仿空间动态的功能&#xff0c;在此做一下记录&#xff0c;简单介绍一下listview自定义适配器以及各相关功能实现方法。 一、效果图 这里添加了两条初始动态&#xff0c;其他都是现编现发的。头像这里后面使用了圆形图片控件。 动态评论以及点赞功能实…

实现QQ空间评论列表样式

其实实现起来还是比较容易的&#xff0c;主要是把原理想清楚一切都好办。 1.主要使用SpannableString类&#xff0c;该类有setSpan(what, start, end, flags)方法&#xff0c;使用该方法便可以对textview设置想要的效果了&#xff0c;这里的what就是效果名&#xff0c;start和…

爬虫(二)实现qq空间的自动评论和自动点赞

会抓个网页总感觉还不够&#xff0c;平时在空间里经常会遇到秒赞或者是秒评论的&#xff0c;现在也可以自己用爬虫在qq空间得到需要的信息&#xff0c;再向特定的url发送http请求就可以做到自动评论和点赞了&#xff0c;使用的cookie登录&#xff0c;好像有点low&#xff0c;尝…

模仿qq空间或朋友圈发布动态、评论动态、回复评论、删除动态或评论的功能(中)...

在上一篇随笔中已经将如何发布动态呈现了&#xff0c;那么现在来看一下剩下的评论动态、回复评论、删除动态和评论功能&#xff0c;这几个功能会有点绕~~~ 一、思路如下&#xff1a; &#xff08;1&#xff09;你发表动态之后&#xff0c;会有人评论这一条动态&#xff0c;当评…

Android仿QQ空间二级评论列表

之前项目中产品需求带二级的评论列表&#xff0c;首先想到是QQ空间评论列表。 先上效果图 下面我们来分析一下布局结构&#xff0c;首先一级列表是listview&#xff0c;然后二级列表也可以有多条&#xff0c;为了省事我只添加了一条&#xff0c;第一反应是listview嵌套list…

android qq空间相册,QQ空间Android3.3发布 新增空间、相册权限设置

现代社会人们越来越注重对自己隐私的保护&#xff0c;即使在虚拟环境中&#xff0c;人们还是会尽可能的为自己划出一片私密区域。近日&#xff0c;最新升级推出的QQ空间Android3.3版新增空间权限以及相册权限的设置&#xff0c;让用户随时设置权限保护自己的隐私&#xff0c;在…

查看qq空间说说及评论,设置相关表结构

说说表&#xff08;saysay&#xff09;&#xff1a; id:主键、无意义 owner&#xff1a;说说的主人 sendtime&#xff1a;发布时间 content&#xff1a;说说内容 -- -- 创建说说表 -- create table saysay( -- id int unsigned auto_increment primary key, -- owner varchar…

qq空间评论的功能

qq空间的回复功能设计的很巧妙&#xff0c;比如那个“我也说一句” 实际上是一个带边框的div。 而且当鼠标到了div区域&#xff0c;会变成输入样式。 功能实现&#xff1a; 给Div添加边框颜色 例如&#xff1a;<div idLayer1 style"display:block; float:left;b…