Home Manual Reference Source Repository

src/dnd-details.js

import isUrl from 'is-url';

export const imageSrcRegExp = /<img\s.*?src=(?:'|")([^'">]+)(?:'|")/gim;
export const linkHrefRegExp = /<a\s.*?href=(?:'|")([^'">]+)(?:'|")/gim;

/**
 * Retrieves details about dropped content in a drag-drop operation.
 *
 * Expects the `DragEvent` object of a drop event.
 * Returns an object with info, depending of the kind of content that was dragged.
 *
 * - When dragging files from the local filesystem, `"files"` will be the only set property, the other properties will have empty or default values
 * - When dragging content from another browser window, `"files"` will be always empty, but some or all of the other values might be set
 *
 * @param {DragEvent} event - The `ondrop` event object
 * @return {object} details
 * @property {FileList} files - A list of files dragged in from the local filesystem
 * @property {String} html - The HTML code of an element dragged in from another browser window
 * @property {String} text - A text, e.g. the text of a dragged link, or simply dragged text content
 * @property {Array} links - An array of URLs found in links of dropped HTML content.
 * @property {Array} images - An array of image URLs found in `src` attributes of images in dropped HTML content.
 */
export default function getDetails(event) {
    const html = (event.dataTransfer && event.dataTransfer.getData('text/html')) || '';
    const images = getRegExpMatches(html, imageSrcRegExp) || [];
    let links = getRegExpMatches(html, linkHrefRegExp) || [];
    let text = event.dataTransfer.getData('Text') || '';

    // text is sometimes same as the src for dropped images
    // (with some encoding/escaping differences, normalized via getHtmlText)
    // reset text to null in these cases
    if (text === getHtmlText(images[0]) && html.substr(0, 4) === '<img') {
        text = '';
    }

    // text is sometimes same as the href for dropped links
    // most times, we can get the real text from the html
    if (text === links[0] && html.substr(0, 2) === '<a') {
        text = getHtmlText(html);
    }

    // many times the text is a URL, even if sometimes no were detected (e.g. drop address bar)
    if (isUrl(text)) {
        if (links.length === 0) {
            links = [text];
        } else if (links.indexOf(text) === -1) {
            links.push(text);
        }
    }

    // always return empty arrays for links and images instead of e.g. null,
    // this way, callsites can safely check for e.g. images.length or images[0]
    return {
        files: (event.dataTransfer && event.dataTransfer.files) || null,
        html: html || '',
        text: text || '',
        links: links,
        images: images
    };
}

function getHtmlText(str) {
    if (!str) return '';
    const el = document.createElement('div');
    el.innerHTML = str;
    return el.innerText.trim();
}

function getRegExpMatches(str, reg, group = 1) {
    const matches = [];
    if (str) {
        let match = reg.exec(str);
        while (match != null) {
            matches.push(match[group]);
            match = reg.exec(str);
        }
    }
    return matches;
}