一个表格复制引发的血案😇

avatar
数据可视化 @蚂蚁集团

背景

卑微的Antv/S2客服:“喂,你好,这里是Antv/S2金牌服务专线,请问有什么可以帮你?”

小李: “救命啊!!电子表格想要默认好用快速上手就选Antv/S2,试问谁不知道?🤔

卑微的Antv/S2 客服:“雀食我们也知道,那你遇到什么问题了呢”

小李: “这次我们就选了Antv/S2,做我们的数据预览表格呢, 啪的一下 ⚡,很快啊,一首歌的时间就完成需求下班了”

卑微的Antv/S2 客服:“听起来针不戳,那么问题究竟是什么呢 ̄□ ̄||?”

小李: “这需求做完当天就上线了,但老板用了直接暴跳如雷,说要我结清工资走人(略带哭腔),我一问你知道是啥,从Antv/S2里面复制出来的部分根本就没办法粘贴到语雀文档呐,全变成字符串了”

卑微的Antv/S2 客服:“让我试试...咦...这个还真不行...可是为什么别人竞品都支持呢...”

小李:(卒)

场景回放

当我们在谈论「复制」时,我们在谈论什么?

Antv/S2为我们贴心的内置了复制功能,只需在s2Options加入如下代码:

const options: S2Options = {
  interaction: {
    enableCopy: true,
  },
};

即可随时使用ctrl+c触发复制选中单元格中的代码。

image.png

而在Antv/S2代码内部,会触发getSelectedData方法,并会根据表格所属种类(透视表、明细表)将相应数据转换成二维矩阵(string[][]),随后processCopyData将其序列化成text/plain型文本,并调用``navigator.clipboard.writeText`浏览器API将其写入到剪贴板。

可是为什么竞品复制出来的内容不一样呢?明明大家用的都是一样的navigator.clipboard.writeText

等等!这里不是还有一个.write()API吗?看起来似乎能解决我们的问题。

逻辑对比😑

Antv/S2的逻辑

先看看S2复制出来的内容

嗯,一个标准的ClipboardItem,里面

好,很有精神, 使用了标准的制表符\t来做同列数据的分割、\r\n标准的换行符来做数据分行。值得一提的是,这也是业界标准的表格逻辑。正因如此,复制到excel,语雀数据表,google sheets等等电子表格产品都是完全没有问题的

再看看我们的神秘竞品:

神秘竞品

好家伙,终于发现了华点, 竞品里clipboardItem居然有两个MIMEType

  • text/html
  • text/plain

上文中使用到的代码:

const clipboardItems = await navigator.clipboard.read()
const textItem = await clipboardItems[0].getType('text/plain')
const htmlItem = await clipboardItems[0].getType('text/html')
const text = await textItem.text()
const html = await htmlItem.text()

疑问解答👊

疑惑都得到了解答。在复制的时候,仅用一次操作,我们可以在剪贴板内写入各种不同MIMEType的数据(比如text/html, img/png等等。而读取剪贴板的一方,可以自由选择最适合其使用场景的格式。与之相对的,我们也可以使用无格式复制来强制使用纯文本来复制内容。

下面来看看从vscode中复制代码到google docs的例子:

对于Antv/S2来说,答案也呼之欲出了,我们只需要在复制的时候为剪贴板提供一份正确的text/html数据,那在word、语雀文档等产品中就能享受到一键生成表格的感觉🌶

亡羊补牢💪

幸亏Antv/S2架构设计扩展性好,我们仅需实现从数据矩阵到 html 格式 string 的 formatter 即可完成 html 模式复制的支持:

// 把 string[][] 矩阵转换成 CopyableItem
const matrixPlainTextTransformer: MatrixTransformer = (dataMatrix) => {
  return {
    type: CopyMIMEType.PLAIN,
    content: map(dataMatrix, (line) => line.join(newTab)).join(newLine),
  };
};

// 把 string[][] 矩阵转换成 CopyableItem
const matrixHtmlTransformer: MatrixTransformer = (dataMatrix) => {
  function createTableData(data: string[], tagName: string) {
    return data
      .map((cell) => `<${tagName}>${escape(cell)}</${tagName}>`)
      .join('');
  }

  function createBody(data: string[][], tagName: string) {
    return data
      .map((row) => `<${tagName}>${createTableData(row, 'td')}</${tagName}>`)
      .join('');
  }

  return {
    type: CopyMIMEType.HTML,
    content: `<meta charset="utf-8"><table><tbody>${createBody(
      dataMatrix,
      'tr',
    )}</tbody></table>`,
  };
};

支持前:

支持后:

结语

AntV/S2 是多维交叉分析领域的表格解决方案。如果看完这篇文章你有所收获,欢迎给我们的仓库 Star⭐️ 鼓励。

S2 的相关链接:

往期文章: