ivnote-web-clipper

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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);
}

})();