有偿请高手解决一个问题:添加 @user 回复的功能 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
zhiyongyici
V2EX    问与答

有偿请高手解决一个问题:添加 @user 回复的功能

  •  
  •   zhiyongyici 2014-08-07 20:10:48 +08:00 6841 次点击
    这是一个创建于 4134 天前的主题,其中的信息可能已经有所发展或是发生改变。
    我使用wordpress+bbpress 搭建了一个小型社区网站(风格仿了v2ex),并且使用了 simditor 编辑器。现在想用js实现 @user 功能。请高手给予帮助。

    作为回报,我可以付费,或者赠送本站半年的广告(自然志全站)。

    实现功能:增加 @user 功能。
    使用的程序:wordpress+bbpress插件。
    地址: http://ziranzhi.com/bbs
    测试账户:ceshi
    密码:cc5228600

    方便的话可以加QQ详谈。
    11061-3846
    第 1 条附言    2014-08-07 21:55:30 +08:00
    我现在的问题可能更加简单,如果不用可视化编辑 ,点击 [回复] 按钮回复框中会出现 <a href="xxx">@user</a> 的代码,如果加载了可视化编辑器(simditor)之后就无法显示,可以用测试账户登录查看实际的效果。
    59 条回复    2015-07-01 19:24:18 +08:00
    spance
        1
    spance  
       2014-08-07 20:19:06 +08:00
    这个通常都是服务端来做的。

    用类似这样的正则来提取 @(\w+)\b
    提取到以后,拼到sql where里面检查是不是有这个人,查询输出的是存在的用户;
    然后对这些用户的消息表里面写新消息,具体写什么就你自己定了,或者发到客户端。

    这是一个精简的逻辑结构,你可以补充你的业务上去。
    zhiyongyici
        2
    zhiyongyici  
    OP
       2014-08-07 20:21:08 +08:00
    @spance 我不是很懂的,你可以帮忙实现吗?
    spance
        3
    spance  
       2014-08-07 20:26:19 +08:00
    @zhiyongyici 不好意思,我不搞php的东西。
    zts1993
        4
    zts1993  
       2014-08-07 20:38:55 +08:00
    text上js监听输入。检测到@ 之后ajax 加载用户,每输入一个字或者字母都查询一次,然后显示出来。。。
    zhiyongyici
        5
    zhiyongyici  
    OP
       2014-08-07 20:48:12 +08:00
    @zts1993 能帮忙实现一下吗?
    zhiyongyici
        6
    zhiyongyici  
    OP
       2014-08-07 20:50:02 +08:00
    @zts1993 其实就类似 v2ex 这样,点击回复 编辑窗口出现 @user 即可。你们说的我感觉有点复杂。
    qq5775548
        7
    qq5775548  
       2014-08-07 21:07:26 +08:00   1
    这个说起来简单 实际操作起来 要注意的问题挺多的:
    方法同上面说的基本一样,就像微博@TA, 弹出输入框。
    1: 控制textarea本身网上大把,记得要兼容ie 所以找个好点得小型库吧,insert,insertAfterStart, insertAfterSelect, del(删除x-y位置的字), getSelectText, getpos, select,selectAll,selectString,getCursorOffset 一些常用的函数还是要写得 juquery 貌似没有这些功能,至少我找不到。
    2: 确定光标位置(getCursorOffset函数),因为具体pageX/Y 无法通过直接形式获得,常用方法建一个大小完全跟textarea一样的div 里面 的html跟 textarea 一样,记着要br来实现换行,然后在结尾添加一个自定义 span或其他标签 然后获取这个标签的位置就可以获取其相对于页面的位置鸟
    3: 获取@ 的对象,这个形式是 "@mygirl" 当后面还有一大段的时候 就如: "@mygril 草泥马" 你应该确定我@的是mygril 而不是 mygril 草泥马。此外还需确定是当前光标对应前面的@ 而非后面或开始的@。这里还处理到点会之前的@, 修改@的人,要将@后面的人名字删除然后替换成新的人。
    4还有几点应该都是小问题,努力尝试下做吧,主要注意就这个点
    mahone3297
        8
    mahone3297  
       2014-08-07 21:13:45 +08:00
    好奇这个功能多少钱。。。
    不过这个要看效果做成如何。如果只是@还好一点,如果要输入人名的时候,自动提示,那又要麻烦一点。
    Sunyanzi
        9
    Sunyanzi  
       2014-08-07 21:18:59 +08:00   1
    提前占位吧 ... 如果到这周六还没人接的话这活我做了 ...

    V2 做 php 的人不少 ... 有其他人想接的话只管接 ... 我只是提供个兜底而已 ...
    zhiyongyici
        10
    zhiyongyici  
    OP
       2014-08-07 21:22:00 +08:00
    @qq5775548 谢谢,虽然我不是很懂,但是可以给后面的人提供帮助。
    skyshy
        11
    skyshy  
       2014-08-07 21:22:40 +08:00   1
    zhiyongyici
        12
    zhiyongyici  
    OP
       2014-08-07 21:24:30 +08:00
    @mahone3297 不用自动提示,和 V2 一样,点击回复按钮,输入框出现 @user 即可。另外,我现在的问题可能更加简单,如果不用可视化编辑 ,点击回复按钮会出现 <a href="">@user</a> 的代码,如果使用了可视化编辑器就没有反应。
    zhiyongyici
        13
    zhiyongyici  
    OP
       2014-08-07 21:25:15 +08:00
    @skyshy 这个我看了,不是很喜欢这种方式。
    greatdk
        14
    greatdk  
       2014-08-07 21:33:18 +08:00
    我以为我屏幕右下角钻了一直虫子、、、、
    zhiyongyici
        15
    zhiyongyici  
    OP
       2014-08-07 21:33:42 +08:00
    @greatdk 哈哈,都这么认为,还有人专门拿抹布去擦呢~~
    qq5775548
        16
    qq5775548  
       2014-08-07 21:35:21 +08:00
    我贴份2年前的代码吧 这里没有md 格式化样子不太好 可能要分开来看 回复两段代码吧,认为我倒米的可以吐槽。但烂代码没什么意义。希望对你有用,一直都没怎么理,找找代码,还是在一个旧项目中找到:一个jquery插件包括 emot @ 的实现 可以扩展更多的其他功能。
    qq5775548
        17
    qq5775548  
       2014-08-07 21:35:38 +08:00
    ;(function() {
    var ShareBox = function() {};
    ShareBox.TextEdit = function() {};
    ShareBox.Emotion = function() {};
    ShareBox.SuggestBox = function() {};
    ShareBox.SuggestBox = function() {};
    ShareBox.SuggestBox.Box = function() {};

    var KEY_CODE = {
    32: [' '],

    48: ['0', ')'],
    49: ['1', '!'],
    50: ['2', '@'],
    51: ['3', '#'],
    52: ['4', '$'],
    53: ['5', '%'],
    54: ['6', '^'],
    55: ['7', '&'],
    56: ['8', '*'],
    57: ['9', '('],

    65: ['a', 'A'],
    66: ['b', 'B'],
    67: ['c', 'C'],
    68: ['d', 'D'],
    69: ['e', 'E'],
    70: ['f', 'F'],
    71: ['g', 'G'],
    72: ['h', 'H'],
    73: ['i', 'I'],
    74: ['j', 'J'],
    75: ['k', 'K'],
    76: ['l', 'L'],
    77: ['m', 'M'],
    78: ['n', 'N'],
    79: ['o', 'O'],
    80: ['p', 'P'],
    81: ['q', 'Q'],
    82: ['r', 'R'],
    83: ['s', 'S'],
    84: ['t', 'T'],
    85: ['u', 'U'],
    86: ['v', 'V'],
    87: ['w', 'W'],
    88: ['x', 'X'],
    89: ['y', 'Y'],
    90: ['z', 'Z'],

    96: ['0'],
    97: ['1'],
    98: ['2'],
    99: ['3'],
    100: ['4'],
    101: ['5'],
    102: ['6'],
    103: ['7'],
    104: ['8'],
    105: ['9'],

    106: ['*'],
    107: ['+'],
    109: ['-'],
    110: ['.'],
    111: ['/'],

    186: [';', ': '],
    187: ['=', '+'],
    188: [',', '<'],
    189: ['-', '_'],
    190: ['.', '>'],
    191: ['/', '?'],
    192: ['`', '~'],

    219: ['[', '{'],
    220: ['\'', '"'],
    221: [']', '}'],
    222: ['', '"']
    };

    var OPTIOnS= {
    ShareBox: {
    label: "<li data-link='{datas}'><a>{view}</a></li>"
    },
    Emotion: {
    tagPrefix: '[',
    tagSuffix: ']',
    perPage: 60,
    autoHide: true,
    autoReset: true,
    defaultNav: '默认',
    path: ('object' === typeof $.EmotionOptions ? $.EmotionOptions.path : false) || {'默认':{}},
    emot: ('object' === typeof $.EmotionOptions ? $.EmotionOptions.emot : false) || {'默认':{}}
    }
    };

    var trace = function(msg, type) {
    if ('object' === typeof console) {
    type = type || 'log';
    console[type](msg);

    } else if ('object' === typeof opera) {
    opera.postError(msg);

    } else if ('object' === typeof java && 'object' === typeof java.lang) {
    java.lang.System.out.println(msg);
    }
    };

    var toJSON = function(obj) {
    switch (typeof(obj)) {
    case 'object':
    var ret = [];
    if (obj instanceof Array) {
    for (var i = 0, len = obj.length; i < len; i++) {
    ret.push(toJSON(obj[i]));
    }

    return '[' + ret.join(',') + ']';

    } else if (obj instanceof RegExp) {
    return obj.toString();

    } else {
    for (var a in obj) {
    ret.push("\"" + a + "\"" + ':' + toJSON(obj[a]));
    }

    return '{' + ret.join(',') + '}';
    }
    case 'function':
    return 'function() {}';

    case 'number':
    return obj.toString();

    case 'string':
    return "\"" + obj.replace(/(\\|\")/g, "\\$1").replace(/\n|\r|\t/g, function(a) {
    return("\n" == a) ? "\\n" : ("\r" == a) ? "\\r" : ("\t" == a) ? "\\t" : "";
    }) + "\"";

    case 'boolean':
    return obj.toString();

    default:
    return obj.toString();
    }
    };

    var getIndex = function(obj, index) {
    var k = 0;
    for (var i in obj) {
    if (index === k ++) {
    return i;
    }
    }
    };
    qq5775548
        18
    qq5775548  
       2014-08-07 21:36:11 +08:00
    ShareBox.TextEdit.prototype = {
    _constructor: function(field) {
    this.field = $(field).get(0);
    return this;
    },
    getLenInCh: function() {
    var str = this.field.value;
    var ch = str.match(/[\u4E00-\uFA29]/ig);
    var en = str.match(/[^\u4E00-\uFA29]/ig);
    var cl = ch ? ch.length * 2 : 0;
    var el = en ? en.length : 0;

    return Math.ceil((cl + el) / 2);
    },
    insert: function(value, type) {
    var field = this.getField();
    value = value.toString();

    if (document.selection) { //IE
    field.focus();
    var seltext = this.getSelectText(field),
    startPos = 'string' === typeof seltext ? field.value.length - seltext.length : field.value.length,
    sel = document.selection.createRange();
    sel.text = value;
    sel.select();

    if (type) {
    var rng = field.createTextRange();

    if (type == 'select') {
    rng.moveStart('character', startPos);

    } else if (type == 'start') {
    rng.moveEnd('character', - value.length);
    rng.moveStart('character', startPos);
    }

    rng.select();
    }

    } else if (field.selectionStart || field.selectiOnStart== '0') { //^IE
    var startPos = field.selectionStart, endPos = field.selectionEnd,
    restoreTop = field.scrollTop;

    field.value = field.value.substring(0, startPos) + value + field.value.substring(endPos, field.value.length);
    if (restoreTop > 0) field.scrollTop = restoreTop;

    field.focus();
    if (type == 'select') {
    field.selectiOnStart= startPos;
    field.selectiOnEnd= startPos + value.length;

    } else if (type == 'start') {
    field.selectiOnStart= field.selectiOnEnd= startPos;

    } else {
    field.selectiOnStart= field.selectiOnEnd= startPos + value.length;
    }

    } else{ //others
    field.value += value;
    field.focus();
    }

    return this;
    },
    insertAfterStart: function(value) {
    this.insert(value, 'start');
    return this;
    },
    insertAfterSelect: function(value) {
    this.insert(value, 'select');
    return this;
    },
    del: function(del_num) {
    var field = this.getField();
    var pos = this.getPos(field);
    if (pos.startPos == 0) { //如果位置为0, 则会 自动在加上匹配内容;
    return false;
    }

    var ft = field.scrollTop;
    var val = field.value;
    field.value = del_num > 0 ? val.slice(0, pos - del_num) + val.slice(pos): val.slice(0, pos) + val.slice(pos - num);
    setPos(field, pos - (del_num < 0 ? 0 : del_num));

    setTimeout(function() {
    if (field.scrollTop != ft) field.scrollTop = ft;
    }, 10);

    return this;
    },
    getSelectText: function() {
    var field = this.getField();
    field.focus();

    if (typeof document.selection != 'undefined') {
    return document.selection.createRange().text;
    }

    if (field.selectionStart || field.selectiOnStart== '0') {
    return field.value.substr(field.selectionStart, field.selectionEnd - field.selectionStart);
    }
    },
    getPos: function() {
    var field = this.getField();
    if (document.selection) {
    field.focus();

    var rng = document.selection.createRange();
    var tx_rng = document.body.createTextRange();
    tx_rng.moveToElementText(field);

    for (var startPos = 0; tx_rng.compareEndPoints('StartToStart' , rng) < 0; startPos ++) {
    tx_rng.moveStart('character', 1);
    }

    for (var endPos = 0; tx_rng.compareEndPoints('StartToEnd' , rng) < 0; endPos ++) {
    tx_rng.moveStart('character', 1);
    }

    return {
    startPos: startPos,
    endPos: endPos
    };

    } else if (field.selectionStart || field.selectiOnStart== '0') {
    return {
    startPos: field.selectionStart,
    endPos: field.selectionEnd
    };
    }
    },
    select: function(start_pos, end_pos) {
    var field = this.getField();
    if (start_pos == undefined || end_pos == undefined || start_pos < 0 || end_pos > field.value.length) {
    return false;
    }

    if (document.selection) { //IE
    var rng = field.createTextRange();
    rng.moveEnd('character', - field.value.length);
    rng.moveEnd('character', end_pos);
    rng.moveStart('character', start_pos);
    rng.select();

    } else { //^IE;
    field.setSelectionRange(start_pos, end_pos);
    field.focus();
    }

    return this;
    },
    setPos: function(pos) {
    this.select(pos , pos);
    return this;
    },
    selectAll:function() {
    var field = this.getField();
    this.select(0, field.value.length);
    return this;
    },
    selectString: function(str) {
    var field = this.getField();
    var index = field.value.indexOf(str);
    return index != -1
    ? this.select(field, index, index + str.length)
    : false;
    },
    getCursorOffset: function(cursor_pos) {
    var field = this.getField();
    if (document.selection) {
    var range = document.selection.createRange();
    var $win = $(window);

    return {
    left: range.boundingLeft + $win.scrollLeft(),
    top: range.boundingTop + $win.scrollTop() + range.boundingHeight
    };
    }

    var $field = $(field),
    $editor = $field.shareBox(),
    w = $field.width(),
    h = $field.height(),

    pos = $field.offset(),
    x = pos.left,
    y = pos.top,

    end_pos = $editor.getPos().endPos,
    str = $field.val(),
    start_str = str.substr(0, end_pos).replace(/[(^*\n*)|(^*\r*)]/g, '<br />'),
    end_str = str.substr(end_pos, str.length).replace(/[(^*\n*)|(^*\r*)]/g, '<br />'),

    fOntSize= $field.css('fontSize'),
    padding = $field.css('padding'),
    lineHeight = $field.css('lineHeight'),
    overflow = $field.css('overflow'),
    scrollTop = $field.scrollTop();

    if (! this.fake) this.fake = $('<div>').appendTo('body');

    this.fake.html(start_str + '<span>x</span>' + end_str)
    .css({
    padding: padding,
    width: w, height: h,
    opacity: 0,
    overflow: overflow,
    position: 'absolute',
    left: x, top: y, zIndex: -9999,
    lineHeight: lineHeight,
    wordWrap: 'break-word',
    fontSize: fontSize
    })
    .scrollTop(scrollTop);

    var marker = this.fake.children('span'),
    height = marker.outerHeight(),
    ofs = marker.offset(),
    left = ofs.left,
    top = ofs.top,
    st = marker.scrollTop();

    return {
    left: left,
    top: top + height - st
    };
    },
    getField: function() {
    return this.field;
    }
    };
    qq5775548
        19
    qq5775548  
       2014-08-07 21:36:51 +08:00
    ShareBox.Emotion.prototype = {
    _constructor: function(options) {
    this.nav_list = [];
    this.page_list = [];
    this.curNav = '';
    this.emot_list = {};
    this.active = false;
    this.cOnfig= $.extend({}, OPTIONS.Emotion, options);

    var $emot = $('<div class="shareEdit-emot">').bind('click', function(e) {
    e.stopPropagation();
    });

    this.oEmot = $emot.get(0);
    this.Onav= $('<ul class="emot-nav">').appendTo(this.oEmot).get(0);
    this.oList = $('<ul class="emot-list">').appendTo(this.oEmot).get(0);
    this.oPage = $('<ul class="emot-page">').appendTo(this.oEmot).get(0);

    this.oEmot = $emot = $('<div class="shareEdit-emot-pure">')
    .append(this.oEmot)
    .appendTo('body')
    .bind('click', function(e) {
    e.stopPropagation();
    });

    //注册导航栏
    var self = this;
    for (var i in this.config.emot) {
    var $emot = $('<li class="emotNav" data-nav="' + i + '"><a title="' + i + '">' + i + '</a></li>')
    .appendTo(this.oNav)
    .bind('click', function(e) {
    e.stopPropagation();

    self.nav($(this).attr('data-nav'));
    });

    this.nav_list.push($emot[0]);
    }

    //注册分页
    for (var i = 1; i < 10; i ++) {
    var $page = $('<li class="emotPage" data-page="' + i + '"><a title="' + i + '">' + i + '</a></li>')
    .appendTo(this.oPage)
    .bind('click', function(e) {
    e.stopPropagation();

    self.page($(this).attr('data-page'));
    })

    this.page_list.push($page[0]);
    }

    if (this.config.autoHide) {
    $(document).bind('click', function() {
    self.hide();
    });
    }

    return this;
    },
    emot: function(nav) {
    if (! this.config.emot[nav]) return;

    var self = this, num = page = 0;
    this.emot_list[nav] = [];
    for (var i in this.config.emot[nav]) {
    if (0 === num % this.config.perPage) {
    var $list = $('<ul class="emotList"></ul>')
    .appendTo(this.oList);

    this.emot_list[nav][page ++] = $list[0];
    }

    $('<li class="ShareBoxEmotIcons" data-emot="' + i + '"><a title="' + i + '"><img src="' + self.config.path[nav] + self.config.emot[nav][i] + '" alt="' + i + '"/></a></li>')
    .appendTo($list)
    .bind('click', function(e) {
    e.stopPropagation();

    var emot = self.config.tagPrefix + $(this).attr('data-emot') + self.config.tagSuffix;
    var $editor = $(self.field)
    $editor.shareBox().insert(emot)
    $editor.keyup();
    /**
    * 上面 autoHide 已在window 绑定 hide事件
    * 同时更好处理 在window click warpper hide 出现问题
    * 即 <div>...<emots dialog><div>
    * $(document).bind('click', function(){$(div).hide()})
    * $(div).live('click', function(){return false;})
    * 此时 点击表情 div 不会消失 因此将 document 设置成 全局方法载体
    */
    $(document).click();
    });

    num ++;
    }

    //补空
    while (! (0 === num ++ % this.config.perPage)) {
    $('<li class="ShareBoxEmotIcons"><a><img src="' + this.config.path[nav] + 'blank.gif" /></a></li>')
    .appendTo($list)
    .bind('click', function(e) {
    e.stopPropagation();
    });
    }

    return this;
    },
    showPage: function(nav) {
    $(this.page_list).hide()
    .filter(':lt(' + this.emot_list[nav].length + ')')
    .show();

    return this;
    },
    page: function(page) {
    $(this.page_list)
    .removeClass('active')
    .filter('[data-page="' + page + '"]')
    .addClass('active');

    for (var i in this.emot_list) {
    $(this.emot_list[i]).hide();
    }

    $(this.emot_list[this.curNav][page - 1]).show();

    return this;
    },
    nav: function(nav) {
    if (this.curNav === nav || 'string' !== typeof nav) return;

    if (! this.config.emot[nav]) nav = getIndex(this.config.path, 0);

    $(this.nav_list).removeClass('active')
    .filter('[data-nav="' + nav + '"]')
    .addClass('active');

    if ('undefined' === typeof this.emot_list[nav]) this.emot(nav);

    this.curNav = nav;
    this.showPage(nav);
    this.page(1);

    return this;
    },
    show: function(field, reset, place) {
    var self = this;
    this.field = $(field)[0];

    //重置或设置 导航
    if (reset || this.config.autoReset) {
    this.nav(getIndex(this.config.path, 0));
    this.page(1);

    } else {
    this.nav(this.curNav);
    }

    //居中显示
    $(this.oEmot).show();

    if (place) this.to(place);
    else this.toCenter();

    this.active = true;
    return this;
    },
    hide: function() {
    $(this.oEmot).hide();

    this.active = false;
    return this;
    },
    to: function(place) {
    var $win = $(window), $ct = $(this.oEmot), $place = $(place), ofs = $place.offset();
    var ww = $win.width(), wh = $win.height(), wl = $win.scrollLeft(), wt = $win.scrollTop();
    var cw = $ct.outerWidth(), ch = $ct.outerHeight(), cl = ct = 0;
    var pw = $place.outerWidth(), ph = $place.outerHeight(), pl = ofs.left, pt = ofs.top;

    if (pl + cw/2 >= wl + ww/1.5) cl = pl + pw - cw;
    else cl = pl;

    if (pt - ch/2 <= wt + wh/3) ct = pt + ph + 4;
    else ct = pt - ch - 4;

    $ct.css({left:cl, top:ct});
    return this;
    },
    toCenter: function() {
    var $win = $(window), $emot = $(this.oEmot),
    wh = $win.height(), ww = $win.width(),
    wl = $win.scrollLeft(), wt = $win.scrollTop(),
    mh = $emot.outerHeight(), mw = $emot.outerWidth();

    $emot.css({
    left: (ww - mw)/2 + wl,
    top: (wh - mh)/2 + wt
    });

    return this;
    }
    };
    qq5775548
        20
    qq5775548  
       2014-08-07 21:36:57 +08:00
    ShareBox.SuggestBox.prototype = {
    label: '<li data-link="{datas}"><a>{view}</a></li>',
    uid: 0,
    queue: [],
    // '@' suggest box
    at: (function() {
    var FORBID_CODE = [9, 13, 16 ,17 ,18, 27, 38, 40, 229];

    //格式化数据
    var format = function(datas) {
    var s = [];
    if ($.isArray(datas)) {
    for (var i = 0; i < datas.length; i ++) {
    s.push({view: datas[i], datas: {value: datas[i]}})
    }

    return s;
    }

    if ($.isPlainObject(datas)) {
    for (var i in datas) {
    s.push({view: datas[i], datas: {value: datas[i]}})
    }

    return s;
    }

    return [];
    };

    //get string of '@'
    //获取 当前光标 对应的 '@xxx' 字符串 并返回其match string, start & end pos
    var getAtObject = function(str, cursor_pos) {
    var p = l = 0, m = '';
    while(! (p <= cursor_pos - 1 && p + l > cursor_pos - 1)) {
    p = str.search(/@[^\s\@\^\$\.\*\+\-\?\=\!\:\|\\\/\(\)\[\]\{\}]*/g);
    if (p == -1) {
    return;
    }

    m = str.match(/@[^\s\@\^\$\.\*\+\-\?\=\!\:\|\\\/\(\)\[\]\{\}]*/g)[0];
    l = m.length;
    str = str.replace('@', ' ');
    }

    return {
    match: m,
    startPos: p,
    endPos: p + l
    };
    };

    return function(options) {
    var self = this;
    var box = new ShareBox.SuggestBox.Box();
    box._constructor();

    var field = options.field, defaultList = options.defaultList,
    url = options.url, name = options.name,
    handle = options.ajaxGet;

    //弹出自动完成窗口
    var popHandle = function() {
    var $this = $(this);
    var $editor = $this.shareBox();
    var v = $this.val(), pos = $editor.getPos();

    //不能 match 到 @xxx 形式并定位则隐藏
    var at = getAtObject(v, pos.startPos);
    if (! ('object' === typeof at)) {
    box.hide();
    return;
    };

    var matchStr = at['match']
    .replace('@', '')
    .replace(/\s/g, '');

    //获取 选中的字符串 若有选中字符串 则必须把 match到的字符串进行过滤
    //这里 形式为 '@xxx_' 或 '@xxx' 则可以将 '_' 和 select 的字符串进行 /xx$/的替换
    var sel = $editor.getSelectText();
    if (sel.length > 0) {
    var reg = sel.replace(/\s/g, '') + '$';
    matchStr = matchStr.replace(new RegExp(reg), '');
    }

    if (matchStr != '' && 'string' === typeof url) {
    //通过ajax 拿数据 source 获取数据后 再进行 生成列表
    $.getJSON(url, [{
    name: name,
    value: matchStr

    }], function(r) {
    if ($.isFunction(handle)) options.source = handle.call(self, r, format);
    else options.source = format(r);

    box.at(options, {
    startPos: at.startPos,
    endPos: at.endPos
    });
    });

    } else {
    //刚打 '@' 时读取默认列表
    setTimeout(function() {
    //若没有 url 则 自己过滤默认数据
    if (matchStr != '') {
    for (var i = 0, l = defaultList, reg = new RegExp('^' + matchStr, 'im'), s = []; i < l.length; i ++ ) {
    if (l[i].match(reg)) {
    s.push(l[i]);
    }
    }
    }

    options.source = format(s || defaultList);
    box.at(options, {
    startPos: at.startPos,
    endPos: at.endPos
    });
    }, 1);
    }
    };

    //绑定 textarea 事件
    $(field).bind('keydown', function(e) {
    if (-1 != $.inArray(e.keyCode, [9])) return false;
    })
    .bind('keyup', function(e) {
    //禁止组合键与功能键
    if ('undefined' != typeof e.keyCode && -1 == $.inArray(e.keyCode, FORBID_CODE)) popHandle.apply(this, arguments);
    if (KEY_CODE[e.keyCode]) box.hide();
    })
    .bind('click', function() {
    popHandle.apply(this, arguments);
    });

    return box;
    };
    })(),
    suggest: function(options) {
    if (options.type) return this[options.type](options);
    }
    };
    qq5775548
        21
    qq5775548  
       2014-08-07 21:37:09 +08:00
    ShareBox.SuggestBox.Box.prototype = {
    _constructor: function(options) {
    this.handler = [];
    this.cOntainer= $('<div class="suggest-box"></div>').hide().appendTo('body').get(0);
    this.warp = $('<ul class="warpper"></ul>').appendTo(this.container).get(0);
    },
    _transform: function(html, source) {
    var fixes = html.match(/\{[^\{]*\}/ig);
    if (fixes == null) return;

    fixes = fixes.toString()
    .replace(/[\{\}]+/g, '')
    .split(',');

    //写入 data-link 属性
    for (var i = 0, f = fixes, s = source; i < f.length; i ++) {
    if (s.hasOwnProperty(f[i])) {
    if ('object' === typeof s[f[i]]) {
    var datas = toJSON(s[f[i]]);
    html = html.replace('{' + f[i] + '}', datas);
    continue;
    }

    html = html.replace('{' + f[i] + '}', s[f[i]]);
    }
    }

    return html;
    },
    //将source转化成html
    transform: function(source, code) {
    var list = [];
    if (source && source.length > 0) {
    for (var i = 0, s = source; i < s.length; i ++) {
    list.push(this._transform(code || OPTIONS.ShareBox.label, s[i]));
    }
    }

    return list.join('');
    },
    empty: function() {
    $(this.warp).empty();
    return this;
    },
    put: function(list) {
    $(this.warp).append(list);
    return this;
    },
    getDatas: function(item) {
    var datas = (item ? $(item) : $(this.warp).children().filter('.focus'))
    .attr('data-link');

    return $.parseJSON(datas);
    },
    hide: function() {
    $(this.container).hide();
    this.logout();

    return this;
    },
    at: function(options, cursor_pos) {
    var self = this;
    var type = options.type, source = options.source, field = options.field, $field = $(field);
    this.field = field;

    this.empty();
    this.put(this.transform(source));
    this.logout();

    var insert = function() {
    setTimeout(function() {
    var $eidtor = $field.shareBox();
    $eidtor.select(cursor_pos.startPos + 1, cursor_pos.endPos);

    var datas = self.getDatas();
    $eidtor.insert(datas.value + ' ');
    }, 1);
    };

    this.boxKeyDownHandle = function(e) {
    var $l = $(self.warp).children();

    switch(e.keyCode) {
    //focus prev
    case 38: //key ↑
    var k = $l.index($l.filter('.focus'));
    k = k <= 0 ? $l.length : k;

    $l.removeClass('focus')
    .eq(k-1)
    .mouseover();

    return false; //prohibit browers scroll event
    //focus next
    case 40: //key ↓
    var k = $l.index($l.filter('.focus'));
    k = k >= $l.length - 1 ? -1 : k;

    $l.removeClass('focus')
    .eq(k+1)
    .mouseover();

    return false;
    //insert
    case 9: //key Tab
    case 13: //key Enter
    insert();
    self.hide();
    return false;
    //cancle
    case 8: //key Backspace
    case 27: //key Esc
    $field.shareBox().insert('');
    self.hide();
    return;
    }
    };

    //focus style
    this.listMouseoverHandle = function(e) {
    $(self.warp).children().removeClass('focus');
    $(this).addClass('focus');
    };

    //select auto insert
    this.listClickHandle = function(e) {
    insert();
    self.hide();
    };

    this.docClickHandle = function() {
    self.hide();
    };

    $field.bind('keydown', this.boxKeyDownHandle);
    $(this.warp).children().bind('mouseover', this.listMouseoverHandle)
    .bind('click', this.listClickHandle)
    .eq(0).mouseover();

    $(document).bind('click', this.docClickHandle);

    //position
    var pos = $field.shareBox().getCursorOffset();
    $(this.container).css({left: pos.left,top: pos.top}).show();

    suggestBox.active = this;
    return this;
    },
    logout: function() {
    if ($.isFunction(this.boxKeyDownHandle)) $(this.field).unbind('keydown', this.boxKeyDownHandle);
    if ($.isFunction(this.boxKeyDownHandle)) $(this.field).unbind('keydown', this.boxKeyDownHandle);

    var $c = $(this.warp).children();
    if ($.isFunction(this.listMouseoverHandle)) $c.unbind('mouseover', this.listMouseoverHandle);
    if ($.isFunction(this.listMouseoverHandle)) $c.unbind('click', this.listMouseoverHandle);
    if ($.isFunction(this.docClickHandle)) $(document).unbind('click', this.docClickHandle);

    this.boxKeyDownHandle = this.listMouseoverHandle = this.listClickHandle = this.docClickHandle = undefined;
    }
    };
    qq5775548
        22
    qq5775548  
       2014-08-07 21:37:16 +08:00
    $.extend(ShareBox.prototype, ShareBox.TextEdit.prototype, {
    _emotions: {},
    emot: function(options) {
    var emot = this._emotions[options.id || 'box'];
    if (! (emot instanceof ShareBox.Emotion)) {
    emot = this._emotions[options.id || 'box'] = new ShareBox.Emotion(options);
    emot._constructor(options);
    }

    var field = this.field;
    if (emot.field == field) {
    emot.active == false
    ? emot.show(field, false, options.to)
    : emot;

    return emot;
    }

    emot.show(field, true, options.to);
    return emot;
    },
    parseEmot: function(text) {
    var path = OPTIONS.Emotion.path;
    var emot = OPTIONS.Emotion.emot;
    for (var i in emot) {
    for (var j in emot[i]) {
    var reg = new RegExp('\\[' + j + '\\]', 'g');
    text = text.replace(reg, function($1) {
    return '<a title="' + j + '"><img src="' + path[i] + emot[i][j] + '" alt="' + j + '" /></a>';
    });
    }
    }

    return text;
    },
    suggest: function(options) {
    if (! this._suggest) {
    optiOns= options || {};
    options.field = this.field;
    this._suggest = suggestBox.suggest(options);
    return this._suggest;
    }
    }
    });

    var suggestBox = new ShareBox.SuggestBox();

    $.fn.extend({
    shareBox: function(options) {
    var edits = [];
    this.filter('input[type="text"], textarea').each(function() {
    var edit = new ShareBox();
    edit._constructor(this);

    if ($.isPlainObject(options)) {
    if ('undefined' === typeof this.suggest && options.suggest) this.suggest = edit.suggest(options.suggest);
    if (options.emot) edit.emot(options.emot);
    if ('undefined' === typeof this._autogrow && options.autogrow) this._autogrow = $(this).autogrow();
    }

    edits.push(edit);
    });

    return edits.length > 1 ? edits : edits[0];
    },
    /**
    * Auto-growing textareas; technique ripped from Facebook
    * (Textarea need set style "overflow:hidden" under IE)
    * https://github.com/jaz303/jquery-grab-bag/blob/master/Javascripts/jquery.autogrow-textarea.js
    */
    autogrow: (function() {
    function times(string, number) {
    for (var i = 0, r = ''; i < number; i ++) r += string;
    return r;
    };

    return function() {
    this.filter('textarea').each(function() {
    this.timeoutId = null;

    var $this = $(this).css('overflow', 'hidden'),
    minH = $this.height();

    var shadow = $('<div></div>')
    .css({
    position: 'absolute',
    wordWrap: 'break-word',
    top: 0,
    left: -9999,
    display: 'none',
    width: $this.width(),
    fontSize: $this.css('fontSize'),
    fontFamily: $this.css('fontFamily'),
    lineHeight: $this.css('lineHeight')
    })
    .appendTo(document.body);

    var update = function() {
    var val = this.value
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/&/g, '&amp;')
    .replace(/\n$/, '<br/>&nbsp;')
    .replace(/\n/g, '<br/>')
    .replace(/ {2,}/g, function(space) {
    return times('&nbsp;', space.length -1) + ' ';
    });

    shadow.html(val);
    $(this).css('height', Math.max(shadow.height(), minH));
    }

    var updateTimeout = function() {
    clearTimeout(this.timeoutId);

    var that = this;
    this.timeoutId = setTimeout(function(){
    update.apply(that);
    }, 100);
    };

    $(this).bind('change', update).bind('keyup keydown', updateTimeout);

    update.apply(this);
    });

    return this;
    };
    })()
    });

    })(jQuery);
    qq5775548
        23
    qq5775548  
       2014-08-07 21:38:53 +08:00   1
    对不起各位 我发第一条已经后悔了 可惜不能删除 你妹 竟然回复字数这么少,删不到 还是直接发吧 没有CSS的 研究写这份代码应该都会明白的~~~~~~
    哎 不再在V2X 上贴代码了 欢迎拍砖 ~~~woyun~~
    hahastudio
        24
    hahastudio  
       2014-08-07 21:44:12 +08:00
    你知道么,v2ex 支持 gist 的= =
    zhiyongyici
        25
    zhiyongyici  
    OP
       2014-08-07 21:45:13 +08:00
    @Sunyanzi 我现在的问题可能更加简单,如果不用可视化编辑 ,点击 [回复] 按钮回复框中会出现 <a href="">@user</a> 的代码,如果加载了可视化编辑器(simditor)就没有反应,我感觉应该不是很麻烦。
    zhiyongyici
        26
    zhiyongyici  
    OP
       2014-08-07 21:49:44 +08:00
    @qq5775548 是的,v2ex 不能删帖挺不方便的。你的代码我真的看不懂(小白用户,莫鄙视 ^_^)。你能帮忙解决这个问题吗?可以加我Q详谈。
    mahone3297
        27
    mahone3297  
       2014-08-07 21:51:21 +08:00
    lz直接上个价格吧,大家哄抢。。。
    功能应该不难,我觉得,难在读懂wordpress+bbpress 代码
    zhiyongyici
        28
    zhiyongyici  
    OP
       2014-08-07 21:56:15 +08:00
    @mahone3297 额,,100可以吗(胆怯)?
    Mihuwa
        29
    Mihuwa  
       2014-08-07 21:59:32 +08:00
    建议去猪八戒上发个任务。
    zhiyongyici
        30
    zhiyongyici  
    OP
       2014-08-07 22:03:51 +08:00
    因为不懂,所以不知道工作量有多少,报价没有谱,会弄的朋友可以直接加我Q聊。如果没有我就去猪八戒试试看。
    qq5775548
        31
    qq5775548  
       2014-08-07 22:07:21 +08:00
    @zhiyongyici 还是算了, 还在忙着找工作呢...
    sampeng
        32
    sampeng  
       2014-08-07 22:22:58 +08:00
    这个题目是我每次面试必问的一个。。。。。
    kmvan
        33
    kmvan  
       2014-08-07 22:37:27 +08:00 via Android
    楼主的需求跟php完全无关的只是js而已。
    zhiyongyici
        34
    zhiyongyici  
    OP
       2014-08-07 22:45:46 +08:00
    @kmvan 是的,我也感觉是 Js 的事,而且是编辑器的事,但是我一头雾水,一点不懂~
    kmvan
        35
    kmvan  
       2014-08-07 22:53:05 +08:00 via Android
    @zhiyongyici 你不是会js吗?编辑器有api的,绑定点击事件,调用api写入文本就可以了啊。
    kmvan
        36
    kmvan  
       2014-08-07 22:54:13 +08:00 via Android
    这帖子好长,手机差点卡死
    shajiquan
        37
    shajiquan  
       2014-08-07 23:02:51 +08:00
    @zhiyongyici 似乎已经解决了。
    yinheli
        38
    yinheli  
       2014-08-07 23:35:14 +08:00   2
    @给你写了个插件. 用的 coffeescript, 把编译后的文件放到你的博客试试
    https://gist.github.com/yinheli/2ea1c51710c452e9b81d#file-simditor-replay-js
    zhiyongyici
        39
    zhiyongyici  
    OP
       2014-08-07 23:37:25 +08:00
    @shajiquan 已经解决了~ 一个朋友用了一段JS就完事了。。。免费的,好感动~!
    yinheli
        40
    yinheli  
       2014-08-07 23:37:28 +08:00
    @Livid sorry, 破坏了页面的样式.... 或者你可以修理下.
    zhiyongyici
        41
    zhiyongyici  
    OP
       2014-08-07 23:40:29 +08:00
    @yinheli 兄弟,感动死我了,你这么操心!!现在问题已经解决了,你的代码可以让更多人看到,也能帮更多人解决类似问题。无比感激~!不过V2贴出来的代码把屏幕都撑破了!好。^_^
    yinheli
        42
    yinheli  
       2014-08-07 23:46:30 +08:00
    @zhiyongyici 恩. 看到了. 不过不如写插件的方式好. 比如写了一段文字. 才想起来要去点一下回复的话. 插件的机制会自动判断你的光标的位置的.
    zhiyongyici
        43
    zhiyongyici  
    OP
       2014-08-07 23:49:34 +08:00
    @yinheli 对,我来试试你的代码~ ^_^
    zhiyongyici/td>
        44
    zhiyongyici  
    OP
       2014-08-07 23:54:44 +08:00
    @yinheli 确实非常好用,而且我还省了一个PHP插件!有一个小问题,点击回复之后,链接后面加了一个# 整个页面就跑开头了
    yinheli
        45
    yinheli  
       2014-08-08 00:00:58 +08:00
    @zhiyongyici 那是因为你把以前的代码搞坏了. 页面有个 bbpress_direct_quotes_quotePost 的方法, 应该是以前的那个哥们改的吧.
    zhiyongyici
        46
    zhiyongyici  
    OP
       2014-08-08 00:04:17 +08:00
    @yinheli 是的,已经改回来了,问题完美解决!!太感谢你了,我送你半年广告吧,在自然志上面,虽然没多少流量,但是能帮一点是一点,这样我的心里好受一些。^_^
    ianva
        47
    ianva  
       2014-08-08 00:49:11 +08:00
    我是来看戈达尔的
    yuankui
        48
    yuankui  
       2014-08-08 09:38:55 +08:00
    在浏览器做就可以了

    检查用户的文本中是否有@xxx的文本,如果有,就直接认为xxx是个人,可以生成对应的链接,或者js事件(点击)。
    这样做的好处是,服务器没有压力,实现简单
    坏处是,可能@xxx不是一个人~(不过概率很小,不影响使用啦),权衡吧。
    yinheli
        49
    yinheli  
       2014-08-08 09:48:36 +08:00
    @zhiyongyici 没有这个需求. 如果你心情好, 可以开源社区捐赠, 比如 beego. http://beego.me/donate
    xiaop
        50
    xiaop  
       2014-08-08 13:32:15 +08:00
    非常喜欢你们的网站!
    zhiyongyici
        51
    zhiyongyici  
    OP
       2014-08-18 20:54:55 +08:00
    @yinheli 有捐助人民币的吗。我找了半天发现只能是美元。
    yinheli
        52
    yinheli  
       2014-08-18 21:20:45 +08:00
    @zhiyongyici 有个支付宝的二维码.
    zhiyongyici
        53
    zhiyongyici  
    OP
       2014-08-18 21:25:52 +08:00
    @yinheli OK 我试试
    zhiyongyici
        54
    zhiyongyici  
    OP
       2014-08-18 21:28:23 +08:00
    @yinheli 成功了,捐了50元,不多,但是心是热的,感谢这样的开源项目为大家提供方便。敬意!
    lyqds
        55
    lyqds  
       2015-06-28 11:41:50 +08:00
    @zhiyongyici
    @yinheli
    大神啊,我现在做bbpress也遇到了这个问题,但是这段代码网址失效了,请指教啊
    yinheli
        56
    yinheli  
       2015-06-29 13:53:14 +08:00
    @lyqds 额 怎么失效了, 在 github 上
    lyqds
        57
    lyqds  
       2015-06-29 17:32:06 +08:00
    yinheli
        58
    yinheli  
       2015-06-29 21:36:58 +08:00
    @lyqds 看上去是你的网络问题. 给个邮箱, 我给你发吧
    lyqds
        59
    lyqds  
       2015-07-01 19:24:18 +08:00
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5067 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 29ms UTC 09:20 PVG 17:20 LAX 01:20 JFK 04:20
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86