gitee上撸了个类似飞书OKR输入框的@提及项目

这篇文章发布于 2022年08月31日,星期三,22:53,归类于 JS实例。 阅读 10124 次, 今日 1 次 5 条评论

 

飞书

一、效果预览

先看demo:https://zhangxinxu.gitee.io/okr-at-mention/

效果如下视频所示(不动点击播放),输入 @ 字符会出现匹配的人名列表,由于是静态页面,所以请求的数据是死的,实际开发是后端根据搜索关键字动态返回的。

鼠标hover悬停在人名上,会出现 popover 提示浮层,可以查看人物相关的信息。

另外,本 JS 项目对复制或者拖拽进来的富文本进行了过滤处理,保证了了输入框里面内容的纯粹。

以及对回车行为进行了劫持,例如你希望回车不换行,而是做其他事情,也是可以的。

项目地址

项目地址:https://gitee.com/zhangxinxu/okr-at-mention

项目地址截图

欢迎 Star,欢迎 fork,也欢迎关注我的 gitee 账号,会不定期更新一些工作中遇到的小玩意,或者突然萌发的有趣的想法。

二、使用说明

使用很简单,引入对应的CSS和JS,然后按照暴露的方法进行调用就可以了。

例如:

<link rel="stylesheet" href="./src/atMention.css">

假设有容器元素(也就是输入框元素):

<div id="container"></div>

则对应的 JavaScript 代码则可以这么使用:

<script type="module">
    import atWakaka from './src/atWakaka.js';
    atWakaka('container', {
        url: './cgi/data.json'
    });
</script>

就可以实现对应的功能了。需要注意的是,本 JS 依赖 tributejs 这个知名的原生AT提及 JS,所以,使用的时候,需要保证 atWakaka.js 同目录下有 tributejs。

tributejs项目截图

当然,不同项目对交互细节的要求也不一样,因此,也提供了对于的 API 参数接口。

语法和参数

语法为:

atWakaka(container, options, optionsTribute);

之所以叫做”wakaka”,没有什么特别的原因,纯粹是当时我就有这股莫名的冲动。

其中:

container
可输入的编辑框容器元素,可以是 DOM 元素本身,也可以是元素的 id 字符串。
options
可选参数,下面有注释说明。

{
    url: '',
    // 按下回车键后,如果希望阻止默认的回车换行
    // 并做一些事情,这个参数就可以用到
    pressEnter: null,
    // 鼠标经过的提示元素,默认本组件会自己创建
    // 也可以可以自己指定具体的元素
    popOver: 'auto',
    // 鼠标经过和移出 @ 元素的处理
    // event 是事件对象
    // data 是 @元素 对应的请求数据
    // popover 是浮层元素
    onMouseOver: function (event, data, popover) {},
    onMouseOut: function (event, data, popover) {}
}
optionsTribute
可选参数。参见 https://github.com/zurb/tribute 中的参数设置,或者源码中对应参数的使用(如下所示)。

{
    // 下拉容器类名
    containerClass: 'ui-at-drop-x',
    // 前面不需要有空格
    requireLeadingSpace: false,
    // 是否高亮匹配,这里走的是自己匹配
    // 这里其实仅 skip 有效
    searchOpts: {
        pre: '<mark>',
        post: '</mark>',
        // 是否服务端搜索数据
        skip: true
    },
    // 动态获取匹配的值
    values: valuesTribute,
    // 没数据时候返回的HTML内容
    noMatchTemplate: function () {},
    // 选中返回到输入框的HTML内容
    selectTemplate: function(item) {},
    // 下拉菜单每一项的HTML内容
    menuItemTemplate: function (item) {}
};

大家如果希望改变下拉列表的元素结构,就是使用 optionsTribute 中的可选参数进行设置。

三、实现技巧

这里有三个实现技巧我觉得值得和大家分享下。

1. @描述整删整加

在可编辑的 div 元素中,要想让里面某段文字不能编辑,有个简单的方法,就是设置 contenteditable="false",例如,下面 HTML 代码中的 <span> 元素就无法编辑,里面的文字五毒不侵。

<div contenteditable="true">
   我是文字,可逐个删除,<span style="display:inline-block;" contenteditable="false">我只能整体删除</span>!
</div>

然后,上面的实现看似完美,实际上有个很头疼的问题,设置了 contenteditable="false" 的元素后面是不能光标定位的,这就导致我想定位在 @xxx 的后面,然后删除,做不到,要么 JS 实时观察并改变光标位置,要么在后面插入一个零宽空格。

上面无论哪个方法,成本都比较高。

在本 JS 的实现中,创新的采用了单标签元素 <hr> 来模拟 @xxx 效果,由于单标签元素本身内容 textContent 是空的,因此,无需设置contenteditable="false",就能实现删除只能删整体。

在所有浏览器中,<hr>元素都支持 ::before/::after 伪元素,因此,可以创建丰富的内容和图形生成,有兴趣的同学可以看看我之前的这篇文章:“666,看hr标签实现分隔线如何玩出花”。

2. hover出现浮层交互

Hover出现浮层的交互并不难实现,可如果是在可编辑的 div 内部呢?以及,要是是在 Vue 或者 React 等框架中的。

如果还是按照传统的实现,找到对应的 trigger 元素,然后使用组件包一下,那可能就会出现很多的问题,比方说包不了,又比方说事件绑定不上。

面对这样的场景,解决方法都是类似的,那就是委托。

将mouseover/mouseout的行为绑定在容器上,然后进行定位处理。

因为容器元素是固定的,而里面的元素是多变的,绑定在容器上就能以不变应万变,性能也更好。

具体实现参见 JS 源码

3. 复制粘贴或者拖拽进去的都是纯文本

富文本编辑机纯手打应该是打不了富文本的,但是粘贴和拖拽却能将富文本弄进去。

有没有什么办法过滤富文本,让用户粘贴或拖拽的内容默认就是纯文本呢?

有的哈!

浏览器其实提供了原生的能力。

包括获取剪切板里面的文本和富文本内容,获取拖拽内容中的文本和富文本,此时,我们就可以阻止默认行为,将纯文本内容插入就可以了。

来一招神不知鬼不觉的移花接木。

相关代码如下所示(拖拽和粘贴二合一了,因为 API 类似):

const doStripHtml = function (event) {
    var dataInput = event.clipboardData || event.dataTransfer;
    // 富文本
    let htmlOrigin = dataInput.getData('text/html');
    // 纯文本
    let textOrigin = dataInput.getData('text');

    // 如果包含富文本
    if (htmlOrigin) {
        // 手动插入
        // 阻止默认的行为
        event.preventDefault();

        // 只插入纯文本
        let lastRange = window.getSelection().getRangeAt(0);
        const newNode = document.createTextNode(textOrigin);
        lastRange.deleteContents();
        lastRange.insertNode(newNode);
        lastRange.setStartAfter(newNode);
        event.target.focus();
    }
};

其中,插入内容这段代码对于任意的富文本编辑器都是受用的,关于光标和选区更多知识可以参见这篇公众号文章:Web 中的“选区”和“光标”

四、结语

使用<hr> 来模拟 @xxx 效果也并非完美无瑕,也是有所牺牲的,首先就是 @xxx 这样的文字内容是无法框选复制的,因为伪元素生成的文本是无法选择的。

其次,就是数据提交的时候,直接 div.textContent 是不行的,因为会丢失 @xxx 这样的信息,需要在额外处理下。

不过相比弊,带来的利想让是更大的。

好,就说这么多的,希望对遇到类似需求的小伙伴有所帮助。

……

明天9月1号小朋友开学了,本应开心才对,可惜每天要各种码,各种上报,还要天天核酸,口罩长带,想想就脑壳疼。

(本篇完)

分享到:


发表评论(目前5 条评论)

  1. Valueof说道:

    大神,求问有没有实时高亮contenteditable里部分文字颜色的方案?

  2. psilo说道:

    关于富文本粘贴,是否需要考虑 xss 的场景

  3. 小神仙说道:

    很实用 ToB场景有机会用到

  4. meepo说道:

    已star