V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
zhiyongyici
V2EX  ›  问与答

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

  •  
  •   zhiyongyici · 2014-08-07 20:10:48 +08:00 · 6108 次点击
    这是一个创建于 3558 天前的主题,其中的信息可能已经有所发展或是发生改变。
    我使用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
        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   ·   我们的愿景   ·   实用小工具   ·   2342 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 05:31 · PVG 13:31 · LAX 22:31 · JFK 01:31
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.