剪贴板自定义类型跨浏览器支持

引子

在编辑器开发中, 遇到了需要设置内容到剪贴板和获取并剪贴板内容的情况. 有关以下问题

  • 跨浏览器获取Clipboard
  • 从剪贴板中获取不同类型数据

可以参考 THE DEFINITIVE GUIDE TO COPYING AND PASTING IN JAVASCRIPT

下面分享以下在 THE DEFINITIVE GUIDE TO COPYING AND PASTING IN JAVASCRIPT 基础上如何实现跨浏览器支持自定义类型.

自定类型浏览器支持情况

从 THE DEFINITIVE GUIDE TO COPYING AND PASTING IN JAVASCRIPT 一文我们可以了解
clipboardData.setData(type, value) API 中 type 在不同浏览器中的支持情况:

  • Chrome and Safari: They support any content type on the clipboardData, including custom types. So, we can call clipboardData.setData('application/lucidObjects', serializedObjects) for pasting, and then call var serialized = clipboardData.getData('application/lucidObjects')
  • Firefox: It currently only allows access to the data types described above. You can set custom types on copy, but when pasting, only the white-listed types are passed through.
  • Internet Explorer: In true IE fashion, it supports just two data types: Text and URL. Oh, and if you set one, you can’t set the other (it gets nulled out). There is a hack, however, that also allows us to indirectly get and set HTML.

Windows Edge 浏览器的情况与 FireFox 类似 ( Windows Edge 浏览器2016年才发布, THE DEFINITIVE GUIDE TO COPYING AND PASTING IN JAVASCRIPT 一文写于 2014 年, 故没有提到 Windows Edge ).

在编辑器(仅支持 Webkit 内核浏览器和 Windows Edge )中, 除了设置text/plain, text/html 到剪贴板外, 还会用到挺多自定义类型的数据 (如 text/yne-table-json, text/yne-json). 自定义类型数据的主要使用场景是:

  • 同一个端不同笔记间(同一个编辑器, 不同实例)相互拷贝粘贴
  • 不同端的编辑器(不同编辑器, 不同实例)相互拷贝粘贴, 如从PC端中的编辑器拷贝, 粘贴到Web浏览器的编辑器中

但 Windows Edge 浏览器的剪贴板又不支持自定义类型数据, 于是琢磨着如何应对 Windows Edge 浏览器.

  1. 在 Windows Edge 下 try ... catch ... 设置内容到剪贴板, 如果不能设置到剪贴板, 则不设置.
  2. 无论在 Webkit 内核浏览器还是 Windows Edge 中, 都能统一处理设置到剪贴板.

第一种解决方案肯定不太好, 而第二种方案又该如何实现呢?

思路

在 Clipboard API and events - Mandatory data types 可以发现, 剪贴板支持读取的数据类型:

  • text/plain
  • text/uri-list
  • text/csv
  • text/css
  • text/html
  • application/xhtml+xml
  • image/png
  • image/jpg, image/jpeg
  • image/gif
  • image/svg+xml
  • application/xml, text/xml
  • application/javascript
  • application/json
  • application/octet-stream

剪贴板支持写入的数据类型

  • text/plain
  • text/uri-list
  • text/csv
  • text/html
  • image/svg+xml
  • application/xml, text/xml
  • application/json

无论读取还是写入剪贴板, 都支持写入类型.

能否将自定义类型数据 JSON 序列化后写入标准写入类型中的一种呢? 要读取时,也可以
从剪贴板中读取该标准写入类型, JSON 处理后再提取出自定义类型.

参考代码

后来实验成功, 以下是参考代码:

DataTransfer.js


/**
 *
 * @see https://www.lucidchart.com/techblog/2014/12/02/definitive-guide-copying-pasting-javascript
 * @see https://w3c.github.io/clipboard-apis/#mandatory-data-types
 *
 * @author fudesign2008
 * @date  2016-12-29
 */
define(function (require) {
    var _ = require('underscore'),
        Class = require('jtk/core/Class'),
        L = require('jtk/core/L'),
        USER_AGENT = require('../util/userAgent'),
        ONLY_SUPPORT_STANDARD_TYPES = USER_AGENT.isEdge(),
        REGISTERED_TYPES = {
            /**
             * @type {Boolean} is standard type or not
             * @see https://w3c.github.io/clipboard-apis/#mandatory-data-types
             */
            'text/html': true,
            'text/plain': true,
            'text/uri-list': true,
            'text/yne-image-json': false,
            'text/yne-json': false,
            'text/yne-note-id': false,
            'text/yne-table-json': false,
        },
        ALT_STANDARD_TYPE = 'application/json',
        DataTransfer;

    DataTransfer = Class.extend({

        /**
         * @param {Event} options.event
         */
        initialize: function (options) {
            var that = this,
                event = options.event,
                rawEvent = event.originalEvent || event;

            that._dataTransfer = rawEvent.dataTransfer;

        },

        _getCustomData: function (type) {
            var that = this,
                dataStr,
                dataObj;

            if (!that._altData) {
                dataStr = that._dataTransfer.getData(ALT_STANDARD_TYPE);
                try {
                    dataObj = JSON.parse(dataStr);
                    that._altData = dataObj;
                } catch (ex) {
                    L.error(ex);
                    that._altData = {};
                }
            }

            return that._altData[type];
        },

        /**
         * @param {String} type
         * @return {Any}
         */
        getData: function (type) {
            var that = this,
                dataTransfer = that._dataTransfer,
                isStardard,
                value;

            if (ONLY_SUPPORT_STANDARD_TYPES) {
                isStardard = REGISTERED_TYPES[type];
                if (isStardard === true) {
                    value = dataTransfer.getData(type);
                    L.log('get standard type', type, value);
                } else if (isStardard === false) {
                    value = that._getCustomData(type);
                    L.log('get custom type', type, value);
                } else {
                    L.error('type should be registered!', type);
                }
                return value;
            } else {
                return dataTransfer.getData(type);
            }
        },

        /**
         * @param {Object} data
         */
        setDataMap: function (dataMap) {
            var dataTransfer = this._dataTransfer,
                setData = function (value, type) {
                    L.log('set data to data transfer', type, value);
                    try {
                        dataTransfer.setData(type, value);
                    } catch (ex) {
                        L.error(ex);
                    }
                },
                customData = {},
                customCounter = 0,
                str;

            if (ONLY_SUPPORT_STANDARD_TYPES) {
                _.each(dataMap, function (value, type) {
                    var isStardard = REGISTERED_TYPES[type];

                    if (isStardard === true) {
                        setData(value, type);
                    } else if (isStardard === false){
                        L.log('set custom type', type);
                        customData[type] = value;
                        customCounter++;
                    } else {
                        L.error('type should be registered!', type);
                    }
                });
                if (customCounter > 0) {
                    try {
                        str = JSON.stringify(customData);
                        setData(str, ALT_STANDARD_TYPE);
                    } catch (ex) {
                        L.error(ex);
                    }
                }
            } else {
                _.each(dataMap, setData);
            }
        }

    });

    return DataTransfer;
});


ClipboardData.js


/**
 *
 * @see https://www.lucidchart.com/techblog/2014/12/02/definitive-guide-copying-pasting-javascript
 * @see https://w3c.github.io/clipboard-apis/#mandatory-data-types
 *
 * @author fudesign2008
 * @date  2016-12-29
 */
define(function (require) {

    var DataTransfer = require('./DataTransfer'),
        ClipboardData = DataTransfer.extend({
            /**
             * @param {Event} options.event
             * @override
             */
            initialize: function (options) {
                var that = this,
                    event = options.event,
                    rawEvent = event.originalEvent || event;

                that._dataTransfer = rawEvent.clipboardData;
            }
        });

    return ClipboardData;
});

无论在 Webkit 内核浏览器和 Windows Edge 浏览器都能如此使用:


var ClipboardData = require('./ClipboardData');

$(el).on('copy', function (event) {
    var clipboardData = new ClipboardData({
            event: event
        });

    clipboardData.setDataMap({
        'text/plain': 'plain xxxx',
        'text/html': 'html xxxx',
        'yne-json': 'xxxx',
        'yne-table-json': 'yyyy'
    });
}).on('paste', function (event) {
    var clipboardData = new ClipboardData({
            event: event
        }),
        html = clipboardData.get('text/html'),
        yneTableJSON = clipboardData.get('yne-table-json');

    console.log('clipboard data text/html', html);
    console.log('clipboard data yne-table-json', yneTableJSON);

});

以上方案能够解决以下场景

  • 同一个端不同笔记间(同一个编辑器, 不同实例)相互拷贝粘贴

无法解决以下场景

  • 不同端的编辑器(不同编辑器, 不同实例)相互拷贝粘贴, 如从PC端中的编辑器拷贝, 粘贴到Web浏览器的编辑器中

参考

  • THE DEFINITIVE GUIDE TO COPYING AND PASTING IN JAVASCRIPT
  • Clipboard API and events - Mandatory data types

你可能感兴趣的:(javascript,javascript,浏览器,编辑器,剪贴板,clipboard)