ivnote-web-clipper

Choose your favorite DOM node and its content will be automatically copied to the clipboard in markdown format

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         ivnote-web-clipper
// @version      0.8.1
// @description  Choose your favorite DOM node and its content will be automatically copied to the clipboard in markdown format
// @author       Ivan Jiang
// @match        http://*/*
// @match        https://*/*
// @grant        GM_addStyle
// @require      https://cdn.bootcss.com/to-markdown/3.0.3/to-markdown.min.js
// @namespace http://ivnote.xyz
// ==/UserScript==

/* globals GM_addStyle, toMarkdown */
GM_addStyle(` .ivnote-web-clipper-hover { background: rgba(124, 201, 191, .7); }
.ivnote-web-clipper-icon {
  position: fixed; bottom: 15px; right: 15px;
  height: 50px; width: 50px; border-radius: 50%;
  background: RGBA(124, 201, 191, .6) url('https://github.com/iplus26/ivnote/raw/master/web-clipper/toggle-icon.png') no-repeat center center / 80%;
  opacity: .7; cursor: pointer;
  transition: all .3s ease-in-out;
  z-index: 9999;
}
.ivnote-web-clipper-icon.ivnote-web-clipper-enable {
  opacity: 1; transform: rotate(180deg);
  right: 50px;
}
.ivnote-web-clipper-icon:hover {
  opacity: 1;
  transform: rotate(180deg)
}`);

(function() {
'use strict';

// Utils =========================================================================
var classCache = {};

var _ = {
    hasClass: hasClass,
    addClass: addClass,
    removeClass: removeClass,
};

function hasClass(node, name) {
    if (!name) {
        return false;
    }
    return classRE(name).test(className(node));
}

function addClass(node, name) {
    if (!name) {
        return node;
    }
    if (!('className' in node)) {
        return;
    }
    var classList = [];
    var cls = className(node); // get node's className
    name.split(/\s+/g).forEach(function(klass) {
        if (!hasClass(node, klass)) {
            classList.push(klass);
        }
    });
    return classList.length && className(node, cls + (cls ? ' ' : '') + classList.join(' '));
}

function removeClass(node, name) {
    if (!('className' in node)) {
        return;
    }
    if (name === undefined) {
        return className(node, '');
    }
    let cls = className(node);
    name.split(/\s+/g)
        .forEach((klass) => cls = cls.replace(classRE(klass), ' '));
    className(node, cls.trim());
}

function classRE(name) {
    return name in classCache ?
        classCache[name] : (classCache[name] = new RegExp('(^|\\s)' + name + '(\\s|$)'));
}
// access className property while respecting SVGAnimatedString
function className(el, value) {
    var klass = el.className || '',
        svg = klass && klass.baseVal !== undefined;

    // get className
    if (value === undefined) {
        return svg ? klass.baseVal : klass;
    }

    // set className
    return svg ? (klass.baseVal = value) : (el.className = value);
}

// Toggle icon =====================================================================
var toggle = document.createElement('div');
toggle.className = "ivnote-web-clipper-icon";
document.body.appendChild(toggle);

var enable = false;

toggle.addEventListener('click', function(e) {
  if (_.hasClass(toggle, 'ivnote-web-clipper-enable')) {
    // turn it off
    _.removeClass(toggle, 'ivnote-web-clipper-enable');
    enable = false;
  } else {
    // turn it on
    _.addClass(toggle, 'ivnote-web-clipper-enable');
    enable = true;
  }
  e.stopPropagation();
});

// Manipulate the doms ==============================================================
document.addEventListener('mouseover', function(e) {
    if (enable) _.addClass(e.target, 'ivnote-web-clipper-hover');
});

document.addEventListener('mouseout', function(e) {
    if (enable) {
        _.removeClass(e.target, 'ivnote-web-clipper-hover');

        // To be safe
        var select = document.querySelectorAll('.ivnote-web-clipper-hover');
        Array.prototype.slice.call(select).forEach(function(el) { _.removeClass(el, 'ivnote-web-clipper-hover'); });
    }
});

document.addEventListener('click', function(e) {
    if (enable) {
        var el = e.target;

        var fRemoveContent   = function() { return '' }
        var fRemoveInlineTag = function(content) { return content }
        var fRemoveBlockTag  = function(content) { return '\n\n' + content + '\n\n' }

        var generalConverters = [
            // Originally, toMarkdown keeps the tag name of block level elements. 
            {
                filter: ['article', 'div'],
                replacement: fRemoveBlockTag
            },
            {
                filter: ['span'],
                replacement: fRemoveInlineTag
            },
            {
                filter: ['style', 'script'],
                replacement: fRemoveContent
            },
            {
                filter: function(node) {
                    return node.nodeName === 'A' && !node.getAttribute('href');
                },
                replacement: fRemoveInlineTag
            },
            // More cases to create the TR spec.
            {
                filter: 'tr',
                replacement: function(content, node) {
                    var borderCells = '';
                    var alignMap = {
                        left: ':--',
                        right: '--:',
                        center: ':-:'
                    };

                    var headRow = node.parentNode.nodeName === 'THEAD';

                    // Assume ths in first tr in tbody are header cells. (Issue #89)
                    headRow = headRow || node.parentNode.nodeName === 'TBODY' && !node.previousSibling && !node.parentNode.previousSibling;

                    if (headRow) {
                        for (var i = 0; i < node.childNodes.length; i++) {
                            var align = node.childNodes[i].attributes.align;
                            var border = '---';

                            if (align) {
                                border = alignMap[align.value] || border;
                            }

                            borderCells += cell(border, node.childNodes[i]);
                        }
                    }
                    return '\n' + content + (borderCells ? '\n' + borderCells : '');
                }
            }
        ];

        var particularWebsitesConverters = [
            {
                filter: function(node) {
                    return node.nodeName === 'BR' && /^(.+\.)?zhihu.com$/gi.test(location.host)
                },
                replacement: function() {
                    return '\n\n'
                }
            }
        ];

        var markdown = '*The note is clipped from [here](' + location.href + ').*\n\n' +
            (document.title && !el.querySelector('h1') ? '#' + document.title + '\n\n' : '') +
            toMarkdown(el.outerHTML, {
                gfm: true,
                converters: particularWebsitesConverters.concat(generalConverters)
            });
        copyTextToClipboard(markdown);
    }
});

// Internal function of to-markdown
function cell (content, node) {
  var index = Array.prototype.indexOf.call(node.parentNode.childNodes, node);
  var prefix = ' ';
  if (index === 0) prefix = '| ';
  return prefix + content + ' |';
}

// Copy to the clipboard ============================================================
// Copyright (c) Dean Taylor. Original post: http://stackoverflow.com/a/30810322/4158282
function copyTextToClipboard(text) {
  var textArea = document.createElement("textarea");
  textArea.style.position = 'fixed';
  textArea.style.top = 0;
  textArea.style.left = 0;
  textArea.style.width = '2em';
  textArea.style.height = '2em';
  textArea.style.padding = 0;
  textArea.style.border = 'none';
  textArea.style.outline = 'none';
  textArea.style.boxShadow = 'none';
  textArea.style.background = 'transparent';
  textArea.value = text;
  document.body.appendChild(textArea);
  textArea.select();
  try {
    var successful = document.execCommand('copy');
    var msg = successful ? 'successful' : 'unsuccessful';
    console.log('Copying text command was ' + msg);
  } catch (err) {
    console.log('Oops, unable to copy');
  }
  document.body.removeChild(textArea);
}

})();