2

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

 2 years ago
source link: https://www.zhangxinxu.com/wordpress/2022/08/gitee-feishu-okr-at-mention/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=10513 鑫空间-鑫生活
本文可以全文转载,无需授权,欢迎分享,欢迎点赞。

飞书

一、效果预览

先看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号小朋友开学了,本应开心才对,可惜每天要各种码,各种上报,还要天天核酸,口罩长带,想想就脑壳疼。

1f62a.svg

(本篇完)1f44d.svg 是不是学到了很多?可以分享到微信
1f44a.svg 有话要说?点击这里


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK