javascript:(function(){
/* turndown */
function extend(destination) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (source.hasOwnProperty(key)) destination[key] = source[key]; } } return destination; }
function repeat(character, count) { return Array(count + 1).join(character); }
var blockElements = [ 'address', 'article', 'aside', 'audio', 'blockquote', 'body', 'canvas', 'center', 'dd', 'dir', 'div', 'dl', 'dt', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hgroup', 'hr', 'html', 'isindex', 'li', 'main', 'menu', 'nav', 'noframes', 'noscript', 'ol', 'output', 'p', 'pre', 'section', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'ul'];
function isBlock(node) { return blockElements.indexOf(node.nodeName.toLowerCase()) !== -1; }
var voidElements = [ 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr'];
function isVoid(node) { return voidElements.indexOf(node.nodeName.toLowerCase()) !== -1; }
var voidSelector = voidElements.join(); function hasVoid(node) { return node.querySelector && node.querySelector(voidSelector); }
var rules = {};
rules.paragraph = { filter: 'p',
replacement: function (content) { return '\n\n' + content + '\n\n'; } };
rules.lineBreak = { filter: 'br',
replacement: function (content, node, options) { return options.br + '\n'; } };
rules.heading = { filter: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
replacement: function (content, node, options) { var hLevel = Number(node.nodeName.charAt(1));
if (options.headingStyle === 'setext' && hLevel < 3) { var underline = repeat(hLevel === 1 ? '=' : '-', content.length); return ( '\n\n' + content + '\n' + underline + '\n\n');
} else { return '\n\n' + repeat('#', hLevel) + ' ' + content + '\n\n'; } } };
rules.blockquote = { filter: 'blockquote',
replacement: function (content) { content = content.replace(/^\n+|\n+$/g, ); content = content.replace(/^/gm, '> '); return '\n\n' + content + '\n\n'; } };
rules.list = { filter: ['ul', 'ol'],
replacement: function (content, node) { var parent = node.parentNode; if (parent.nodeName === 'LI' && parent.lastElementChild === node) { return '\n' + content; } else { return '\n\n' + content + '\n\n'; } } };
rules.listItem = { filter: 'li',
replacement: function (content, node, options) { content = content. replace(/^\n+/, ) .replace(/\n+$/, '\n') .replace(/\n/gm, '\n '); var prefix = options.bulletListMarker + ' '; var parent = node.parentNode; if (parent.nodeName === 'OL') { var start = parent.getAttribute('start'); var index = Array.prototype.indexOf.call(parent.children, node); prefix = (start ? Number(start) + index : index + 1) + '. '; } return ( prefix + content + (node.nextSibling && !/\n$/.test(content) ? '\n' : ));
} };
rules.indentedCodeBlock = { filter: function (node, options) { return ( options.codeBlockStyle === 'indented' && node.nodeName === 'PRE' && node.firstChild && node.firstChild.nodeName === 'CODE');
},
replacement: function (content, node, options) { return ( '\n\n ' + node.firstChild.textContent.replace(/\n/g, '\n ') + '\n\n');
} };
rules.fencedCodeBlock = { filter: function (node, options) { return ( options.codeBlockStyle === 'fenced' && node.nodeName === 'PRE' && node.firstChild && node.firstChild.nodeName === 'CODE');
},
replacement: function (content, node, options) { var className = node.firstChild.className || ; var language = (className.match(/language-(\S+)/) || [null, ])[1]; var code = node.firstChild.textContent;
var fenceChar = options.fence.charAt(0); var fenceSize = 3; var fenceInCodeRegex = new RegExp('^' + fenceChar + '{3,}', 'gm');
var match; while (match = fenceInCodeRegex.exec(code)) { if (match[0].length >= fenceSize) { fenceSize = match[0].length + 1; } }
var fence = repeat(fenceChar, fenceSize);
return ( '\n\n' + fence + language + '\n' + code.replace(/\n$/, ) + '\n' + fence + '\n\n');
} };
rules.horizontalRule = { filter: 'hr',
replacement: function (content, node, options) { return '\n\n' + options.hr + '\n\n'; } };
rules.inlineLink = { filter: function (node, options) { return ( options.linkStyle === 'inlined' && node.nodeName === 'A' && node.getAttribute('href'));
},
replacement: function (content, node) { var href = node.getAttribute('href'); var title = node.title ? ' "' + node.title + '"' : ; return '[' + content + '](' + href + title + ')'; } };
rules.referenceLink = { filter: function (node, options) { return ( options.linkStyle === 'referenced' && node.nodeName === 'A' && node.getAttribute('href'));
},
replacement: function (content, node, options) { var href = node.getAttribute('href'); var title = node.title ? ' "' + node.title + '"' : ; var replacement; var reference;
switch (options.linkReferenceStyle) { case 'collapsed': replacement = '[' + content + '][]'; reference = '[' + content + ']: ' + href + title; break; case 'shortcut': replacement = '[' + content + ']'; reference = '[' + content + ']: ' + href + title; break; default: var id = this.references.length + 1; replacement = '[' + content + '][' + id + ']'; reference = '[' + id + ']: ' + href + title;}
this.references.push(reference); return replacement; },
references: [],
append: function (options) { var references = ; if (this.references.length) { references = '\n\n' + this.references.join('\n') + '\n\n'; this.references = []; } return references; } };
rules.emphasis = { filter: ['em', 'i'],
replacement: function (content, node, options) { if (!content.trim()) return ; return options.emDelimiter + content + options.emDelimiter; } };
rules.strong = { filter: ['strong', 'b'],
replacement: function (content, node, options) { if (!content.trim()) return ; return options.strongDelimiter + content + options.strongDelimiter; } };
rules.code = { filter: function (node) { var hasSiblings = node.previousSibling || node.nextSibling; var isCodeBlock = node.parentNode.nodeName === 'PRE' && !hasSiblings;
return node.nodeName === 'CODE' && !isCodeBlock; },
replacement: function (content) { if (!content.trim()) return ;
var delimiter = '`'; var leadingSpace = ; var trailingSpace = ; var matches = content.match(/`+/gm); if (matches) { if (/^`/.test(content)) leadingSpace = ' '; if (/`$/.test(content)) trailingSpace = ' '; while (matches.indexOf(delimiter) !== -1) delimiter = delimiter + '`'; }
return delimiter + leadingSpace + content + trailingSpace + delimiter; } };
rules.image = { filter: 'img',
replacement: function (content, node) { var alt = node.alt || ; var src = node.getAttribute('src') || ; var title = node.title || ; var titlePart = title ? ' "' + title + '"' : ; return src ? '![' + alt + ']' + '(' + src + titlePart + ')' : ; } };
/** * Manages a collection of rules used to convert HTML to Markdown */
function Rules(options) { this.options = options; this._keep = []; this._remove = [];
this.blankRule = { replacement: options.blankReplacement };
this.keepReplacement = options.keepReplacement;
this.defaultRule = { replacement: options.defaultReplacement };
this.array = []; for (var key in options.rules) this.array.push(options.rules[key]); }
Rules.prototype = { add: function (key, rule) { this.array.unshift(rule); },
keep: function (filter) { this._keep.unshift({ filter: filter, replacement: this.keepReplacement });
},
remove: function (filter) { this._remove.unshift({ filter: filter, replacement: function () { return ; } });
},
forNode: function (node) { if (node.isBlank) return this.blankRule; var rule;
if (rule = findRule(this.array, node, this.options)) return rule; if (rule = findRule(this._keep, node, this.options)) return rule; if (rule = findRule(this._remove, node, this.options)) return rule;
return this.defaultRule; },
forEach: function (fn) { for (var i = 0; i < this.array.length; i++) fn(this.array[i], i); } };
function findRule(rules, node, options) { for (var i = 0; i < rules.length; i++) { var rule = rules[i]; if (filterValue(rule, node, options)) return rule; } return void 0; }
function filterValue(rule, node, options) { var filter = rule.filter; if (typeof filter === 'string') { if (filter === node.nodeName.toLowerCase()) return true; } else if (Array.isArray(filter)) { if (filter.indexOf(node.nodeName.toLowerCase()) > -1) return true; } else if (typeof filter === 'function') { if (filter.call(rule, node, options)) return true; } else { throw new TypeError('`filter` needs to be a string, array, or function'); } }
/** * The collapseWhitespace function is adapted from collapse-whitespace * by Luc Thevenard. * * The MIT License (MIT) * * Copyright (c) 2014 Luc Thevenard <lucthevenard@gmail.com> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */
/** * collapseWhitespace(options) removes extraneous whitespace from an the given element. * * @param {Object} options */ function collapseWhitespace(options) { var element = options.element; var isBlock = options.isBlock; var isVoid = options.isVoid; var isPre = options.isPre || function (node) { return node.nodeName === 'PRE'; };
if (!element.firstChild || isPre(element)) return;
var prevText = null; var prevVoid = false;
var prev = null; var node = next(prev, element, isPre);
while (node !== element) { if (node.nodeType === 3 || node.nodeType === 4) { var text = node.data.replace(/[ \r\n\t]+/g, ' ');
if ((!prevText || / $/.test(prevText.data)) && !prevVoid && text[0] === ' ') { text = text.substr(1); }
if (!text) { node = remove(node); continue; }
node.data = text;
prevText = node; } else if (node.nodeType === 1) { if (isBlock(node) || node.nodeName === 'BR') { if (prevText) { prevText.data = prevText.data.replace(/ $/, ); }
prevText = null; prevVoid = false; } else if (isVoid(node)) {
prevText = null; prevVoid = true; } } else { node = remove(node); continue; }
var nextNode = next(prev, node, isPre); prev = node; node = nextNode; }
if (prevText) { prevText.data = prevText.data.replace(/ $/, ); if (!prevText.data) { remove(prevText); } } }
/** * remove(node) removes the given node from the DOM and returns the * next node in the sequence. * * @param {Node} node * @return {Node} node */ function remove(node) { var next = node.nextSibling || node.parentNode;
node.parentNode.removeChild(node);
return next; }
/** * next(prev, current, isPre) returns the next node in the sequence, given the * current and previous nodes. * * @param {Node} prev * @param {Node} current * @param {Function} isPre * @return {Node} */ function next(prev, current, isPre) { if (prev && prev.parentNode === current || isPre(current)) { return current.nextSibling || current.parentNode; }
return current.firstChild || current.nextSibling || current.parentNode; }
/* * Set up window for Node.js */
var root = typeof window !== 'undefined' ? window : {};
/* * Parsing HTML strings */
function canParseHTMLNatively() { var Parser = root.DOMParser; var canParse = false;
try {
if (new Parser().parseFromString(, 'text/html')) { canParse = true; } } catch (e) {}
return canParse; }
function createHTMLParser() { var Parser = function () {};
{ var JSDOM = require('jsdom').JSDOM; Parser.prototype.parseFromString = function (string) { return new JSDOM(string).window.document; }; } return Parser; }
var HTMLParser = canParseHTMLNatively() ? root.DOMParser : createHTMLParser();
function RootNode(input) { var root; if (typeof input === 'string') { var doc = htmlParser().parseFromString(
'<x-turndown id="turndown-root">' + input + '</x-turndown>', 'text/html');
root = doc.getElementById('turndown-root'); } else { root = input.cloneNode(true); } collapseWhitespace({ element: root, isBlock: isBlock, isVoid: isVoid });
return root; }
var _htmlParser; function htmlParser() { _htmlParser = _htmlParser || new HTMLParser(); return _htmlParser; }
function Node(node) { node.isBlock = isBlock(node); node.isCode = node.nodeName.toLowerCase() === 'code' || node.parentNode.isCode; node.isBlank = isBlank(node); node.flankingWhitespace = flankingWhitespace(node); return node; }
function isBlank(node) { return ( ['A', 'TH', 'TD', 'IFRAME', 'SCRIPT', 'AUDIO', 'VIDEO'].indexOf(node.nodeName) === -1 && /^\s*$/i.test(node.textContent) && !isVoid(node) && !hasVoid(node)); }
function flankingWhitespace(node) { var leading = ; var trailing = ;
if (!node.isBlock) { var hasLeading = /^\s/.test(node.textContent); var hasTrailing = /\s$/.test(node.textContent); var blankWithSpaces = node.isBlank && hasLeading && hasTrailing;
if (hasLeading && !isFlankedByWhitespace('left', node)) { leading = ' '; }
if (!blankWithSpaces && hasTrailing && !isFlankedByWhitespace('right', node)) { trailing = ' '; } }
return { leading: leading, trailing: trailing }; }
function isFlankedByWhitespace(side, node) { var sibling; var regExp; var isFlanked;
if (side === 'left') { sibling = node.previousSibling; regExp = / $/; } else { sibling = node.nextSibling; regExp = /^ /; }
if (sibling) { if (sibling.nodeType === 3) { isFlanked = regExp.test(sibling.nodeValue); } else if (sibling.nodeType === 1 && !isBlock(sibling)) { isFlanked = regExp.test(sibling.textContent); } } return isFlanked; }
var reduce = Array.prototype.reduce; var leadingNewLinesRegExp = /^\n*/; var trailingNewLinesRegExp = /\n*$/; var escapes = [ [/\\/g, '\\\\'], [/\*/g, '\\*'], [/^-/g, '\\-'], [/^\+ /g, '\\+ '], [/^(=+)/g, '\\$1'], [/^(#{1,6}) /g, '\\$1 '], [/`/g, '\\`'], [/^~~~/g, '\\~~~'], [/\[/g, '\\['], [/\]/g, '\\]'], [/^>/g, '\\>'], [/_/g, '\\_'], [/^(\d+)\. /g, '$1\\. ']];
function TurndownService(options) { if (!(this instanceof TurndownService)) return new TurndownService(options);
var defaults = { rules: rules, headingStyle: 'setext', hr: '* * *', bulletListMarker: '*', codeBlockStyle: 'indented', fence: '```', emDelimiter: '_', strongDelimiter: '**', linkStyle: 'inlined', linkReferenceStyle: 'full', br: ' ', blankReplacement: function (content, node) { return node.isBlock ? '\n\n' : ; }, keepReplacement: function (content, node) { return node.isBlock ? '\n\n' + node.outerHTML + '\n\n' : node.outerHTML; }, defaultReplacement: function (content, node) { return node.isBlock ? '\n\n' + content + '\n\n' : content; } };
this.options = extend({}, defaults, options); this.rules = new Rules(this.options); }
TurndownService.prototype = { /** * The entry point for converting a string or DOM node to Markdown * @public * @param {String|HTMLElement} input The string or DOM node to convert * @returns A Markdown representation of the input * @type String */
turndown: function (input) { if (!canConvert(input)) { throw new TypeError( input + ' is not a string, or an element/document/fragment node.');
}
if (input === ) return ;
var output = process.call(this, new RootNode(input)); return postProcess.call(this, output); },
/** * Add one or more plugins * @public * @param {Function|Array} plugin The plugin or array of plugins to add * @returns The Turndown instance for chaining * @type Object */
use: function (plugin) { if (Array.isArray(plugin)) { for (var i = 0; i < plugin.length; i++) this.use(plugin[i]); } else if (typeof plugin === 'function') { plugin(this); } else { throw new TypeError('plugin must be a Function or an Array of Functions'); } return this; },
/** * Adds a rule * @public * @param {String} key The unique key of the rule * @param {Object} rule The rule * @returns The Turndown instance for chaining * @type Object */
addRule: function (key, rule) { this.rules.add(key, rule); return this; },
/** * Keep a node (as HTML) that matches the filter * @public * @param {String|Array|Function} filter The unique key of the rule * @returns The Turndown instance for chaining * @type Object */
keep: function (filter) { this.rules.keep(filter); return this; },
/** * Remove a node that matches the filter * @public * @param {String|Array|Function} filter The unique key of the rule * @returns The Turndown instance for chaining * @type Object */
remove: function (filter) { this.rules.remove(filter); return this; },
/** * Escapes Markdown syntax * @public * @param {String} string The string to escape * @returns A string with Markdown syntax escaped * @type String */
escape: function (string) { return escapes.reduce(function (accumulator, escape) { return accumulator.replace(escape[0], escape[1]); }, string); } };
/** * Reduces a DOM node down to its Markdown string equivalent * @private * @param {HTMLElement} parentNode The node to convert * @returns A Markdown representation of the node * @type String */
function process(parentNode) { var self = this; return reduce.call(parentNode.childNodes, function (output, node) { node = new Node(node);
var replacement = ; if (node.nodeType === 3) { replacement = node.isCode ? node.nodeValue : self.escape(node.nodeValue); } else if (node.nodeType === 1) { replacement = replacementForNode.call(self, node); }
return join(output, replacement); }, ); }
/** * Appends strings as each rule requires and trims the output * @private * @param {String} output The conversion output * @returns A trimmed version of the ouput * @type String */
function postProcess(output) { var self = this; this.rules.forEach(function (rule) { if (typeof rule.append === 'function') { output = join(output, rule.append(self.options)); } });
return output.replace(/^[\t\r\n]+/, ).replace(/[\t\r\n\s]+$/, ); }
/** * Converts an element node to its Markdown equivalent * @private * @param {HTMLElement} node The node to convert * @returns A Markdown representation of the node * @type String */
function replacementForNode(node) { var rule = this.rules.forNode(node); var content = process.call(this, node); var whitespace = node.flankingWhitespace; if (whitespace.leading || whitespace.trailing) content = content.trim(); return ( whitespace.leading + rule.replacement(content, node, this.options) + whitespace.trailing);
}
/** * Determines the new lines between the current output and the replacement * @private * @param {String} output The current conversion output * @param {String} replacement The string to append to the output * @returns The whitespace to separate the current output and the replacement * @type String */
function separatingNewlines(output, replacement) { var newlines = [ output.match(trailingNewLinesRegExp)[0], replacement.match(leadingNewLinesRegExp)[0]]. sort(); var maxNewlines = newlines[newlines.length - 1]; return maxNewlines.length < 2 ? maxNewlines : '\n\n'; }
function join(string1, string2) { var separator = separatingNewlines(string1, string2);
string1 = string1.replace(trailingNewLinesRegExp, ); string2 = string2.replace(leadingNewLinesRegExp, );
return string1 + separator + string2; }
/** * Determines whether an input can be converted * @private * @param {String|HTMLElement} input Describe this parameter * @returns Describe what it returns * @type String|Object|Array|Boolean|Number */
function canConvert(input) { return ( input != null && ( typeof input === 'string' || input.nodeType && ( input.nodeType === 1 || input.nodeType === 9 || input.nodeType === 11)));
}
/* export default TurndownService; */
/* Readability */
/* eslint-env es6:false*/ /* * Copyright (c) 2010 Arc90 Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http: * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */
/* * This code is heavily based on Arc90's readability.js (1.7.1) script * available at: http: */
/** * Public constructor. * @param {HTMLDocument} doc The document to parse. * @param {Object} options The options object. */ function Readability(doc, options) {
if (options && options.documentElement) { doc = options; options = arguments[2]; } else if (!doc || !doc.documentElement) { throw new Error("First argument to Readability constructor should be a document object."); } options = options || {};
this._doc = doc; this._docJSDOMParser = this._doc.firstChild.__JSDOMParser__; this._articleTitle = null; this._articleByline = null; this._articleDir = null; this._articleSiteName = null; this._attempts = [];
this._debug = !!options.debug; this._maxElemsToParse = options.maxElemsToParse || this.DEFAULT_MAX_ELEMS_TO_PARSE; this._nbTopCandidates = options.nbTopCandidates || this.DEFAULT_N_TOP_CANDIDATES; this._charThreshold = options.charThreshold || this.DEFAULT_CHAR_THRESHOLD; this._classesToPreserve = this.CLASSES_TO_PRESERVE.concat(options.classesToPreserve || []); this._keepClasses = !!options.keepClasses;
this._flags = this.FLAG_STRIP_UNLIKELYS | this.FLAG_WEIGHT_CLASSES | this.FLAG_CLEAN_CONDITIONALLY; let logEl;
if (this._debug) { logEl = function(e) { const rv = e.nodeName + " "; if (e.nodeType == e.TEXT_NODE) { return rv + '("' + e.textContent + '")'; } const classDesc = e.className && ("." + e.className.replace(/ /g, ".")); let elDesc = ""; if (e.id) { elDesc = "(#" + e.id + classDesc + ")"; } else if (classDesc) { elDesc = "(" + classDesc + ")"; } return rv + elDesc; }; this.log = function() { if (typeof dump !== "undefined") { const msg = Array.prototype.map.call(arguments, x => (x && x.nodeName) ? logEl(x) : x).join(" "); dump("Reader: (Readability) " + msg + "\n"); } else if (typeof console !== "undefined") { const args = [ "Reader: (Readability) " ].concat(arguments); console.log.apply(console, args); } }; } else { this.log = function() {}; } }
Readability.prototype = { FLAG_STRIP_UNLIKELYS: 0x1, FLAG_WEIGHT_CLASSES: 0x2, FLAG_CLEAN_CONDITIONALLY: 0x4,
ELEMENT_NODE: 1, TEXT_NODE: 3,
DEFAULT_MAX_ELEMS_TO_PARSE: 0,
DEFAULT_N_TOP_CANDIDATES: 5,
DEFAULT_TAGS_TO_SCORE: "section,h2,h3,h4,h5,h6,p,td,pre".toUpperCase().split(","),
DEFAULT_CHAR_THRESHOLD: 500,
REGEXPS: {
unlikelyCandidates: /-ad-|ai2html|banner|breadcrumbs|combx|comment|community|cover-wrap|disqus|extra|footer|gdpr|header|legends|menu|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager|popup|yom-remote/i, okMaybeItsACandidate: /and|article|body|column|content|main|shadow/i,
positive: /article|body|content|entry|hentry|h-entry|main|page|pagination|post|text|blog|story/i, negative: /hidden|^hid$| hid$| hid |^hid |banner|combx|comment|com-|contact|foot|footer|footnote|gdpr|masthead|media|meta|outbrain|promo|related|scroll|share|shoutbox|sidebar|skyscraper|sponsor|shopping|tags|tool|widget/i, extraneous: /print|archive|comment|discuss|e[\-]?mail|share|reply|all|login|sign|single|utility/i, byline: /byline|author|dateline|writtenby|p-author/i, replaceFonts: /<(\/?)font[^>]*>/gi, normalize: /\s{2,}/g, videos: /\/\/(www\.)?((dailymotion|youtube|youtube-nocookie|player\.vimeo|v\.qq)\.com|(archive|upload\.wikimedia)\.org|player\.twitch\.tv)/i, shareElements: /(\b|_)(share|sharedaddy)(\b|_)/i, nextLink: /(next|weiter|continue|>([^\|]|$)|»([^\|]|$))/i, prevLink: /(prev|earl|old|new|<|«)/i, whitespace: /^\s*$/, hasContent: /\S$/, srcsetUrl: /(\S+)(\s+[\d.]+[xw])?(\s*(?:,|$))/g, b64DataUrl: /^data:\s*([^\s;,]+)\s*;\s*base64\s*,/i, },
DIV_TO_P_ELEMS: [ "A", "BLOCKQUOTE", "DL", "DIV", "IMG", "OL", "P", "PRE", "TABLE", "UL", "SELECT" ],
ALTER_TO_DIV_EXCEPTIONS: [ "DIV", "ARTICLE", "SECTION", "P" ],
PRESENTATIONAL_ATTRIBUTES: [ "align", "background", "bgcolor", "border", "cellpadding", "cellspacing", "frame", "hspace", "rules", "style", "valign", "vspace" ],
DEPRECATED_SIZE_ATTRIBUTE_ELEMS: [ "TABLE", "TH", "TD", "HR", "PRE" ],
PHRASING_ELEMS: [
"ABBR", "AUDIO", "B", "BDO", "BR", "BUTTON", "CITE", "CODE", "DATA", "DATALIST", "DFN", "EM", "EMBED", "I", "IMG", "INPUT", "KBD", "LABEL", "MARK", "MATH", "METER", "NOSCRIPT", "OBJECT", "OUTPUT", "PROGRESS", "Q", "RUBY", "SAMP", "SCRIPT", "SELECT", "SMALL", "SPAN", "STRONG", "SUB", "SUP", "TEXTAREA", "TIME", "VAR", "WBR", ],
CLASSES_TO_PRESERVE: [ "page" ],
HTML_ESCAPE_MAP: { lt: "<", gt: ">", amp: "&", quot: '"', apos: "'", },
/** * Run any post-process modifications to article content as necessary. * * @param Element * @return void **/ _postProcessContent(articleContent) {
this._fixRelativeUris(articleContent);
if (!this._keepClasses) {
this._cleanClasses(articleContent); } },
/** * Iterates over a NodeList, calls `filterFn` for each node and removes node * if function returned `true`. * * If function is not passed, removes all the nodes in node list. * * @param NodeList nodeList The nodes to operate on * @param Function filterFn the function to use as a filter * @return void */ _removeNodes(nodeList, filterFn) {
if (this._docJSDOMParser && nodeList._isLiveNodeList) { throw new Error("Do not pass live node lists to _removeNodes"); } for (let i = nodeList.length - 1; i >= 0; i--) { const node = nodeList[i]; const parentNode = node.parentNode; if (parentNode) { if (!filterFn || filterFn.call(this, node, i, nodeList)) { parentNode.removeChild(node); } } } },
/** * Iterates over a NodeList, and calls _setNodeTag for each node. * * @param NodeList nodeList The nodes to operate on * @param String newTagName the new tag name to use * @return void */ _replaceNodeTags(nodeList, newTagName) {
if (this._docJSDOMParser && nodeList._isLiveNodeList) { throw new Error("Do not pass live node lists to _replaceNodeTags"); } for (let i = nodeList.length - 1; i >= 0; i--) { const node = nodeList[i]; this._setNodeTag(node, newTagName); } },
/** * Iterate over a NodeList, which doesn't natively fully implement the Array * interface. * * For convenience, the current object context is applied to the provided * iterate function. * * @param NodeList nodeList The NodeList. * @param Function fn The iterate function. * @return void */ _forEachNode(nodeList, fn) { Array.prototype.forEach.call(nodeList, fn, this); },
/** * Iterate over a NodeList, return true if any of the provided iterate * function calls returns true, false otherwise. * * For convenience, the current object context is applied to the * provided iterate function. * * @param NodeList nodeList The NodeList. * @param Function fn The iterate function. * @return Boolean */ _someNode(nodeList, fn) { return Array.prototype.some.call(nodeList, fn, this); },
/** * Iterate over a NodeList, return true if all of the provided iterate * function calls return true, false otherwise. * * For convenience, the current object context is applied to the * provided iterate function. * * @param NodeList nodeList The NodeList. * @param Function fn The iterate function. * @return Boolean */ _everyNode(nodeList, fn) { return Array.prototype.every.call(nodeList, fn, this); },
/** * Concat all nodelists passed as arguments. * * @return ...NodeList * @return Array */ _concatNodeLists() { const slice = Array.prototype.slice; const args = slice.call(arguments); const nodeLists = args.map(list => slice.call(list)); return Array.prototype.concat.apply([], nodeLists); },
_getAllNodesWithTag(node, tagNames) { if (node.querySelectorAll) { return node.querySelectorAll(tagNames.join(",")); } return [].concat.apply([], tagNames.map(tag => { const collection = node.getElementsByTagName(tag); return Array.isArray(collection) ? collection : Array.from(collection); })); },
/** * Removes the class="" attribute from every element in the given * subtree, except those that match CLASSES_TO_PRESERVE and * the classesToPreserve array from the options object. * * @param Element * @return void */ _cleanClasses(node) { const classesToPreserve = this._classesToPreserve; const className = (node.getAttribute("class") || "") .split(/\s+/) .filter(cls => classesToPreserve.indexOf(cls) != -1) .join(" ");
if (className) { node.setAttribute("class", className); } else { node.removeAttribute("class"); }
for (node = node.firstElementChild; node; node = node.nextElementSibling) { this._cleanClasses(node); } },
/** * Converts each <a> and <img> uri in the given element to an absolute URI, * ignoring #ref URIs. * * @param Element * @return void */ _fixRelativeUris(articleContent) { const baseURI = this._doc.baseURI; const documentURI = this._doc.documentURI; function toAbsoluteURI(uri) {
if (baseURI == documentURI && uri.charAt(0) == "#") { return uri; }
try { return new URL(uri, baseURI).href; } catch (ex) {
} return uri; }
const links = this._getAllNodesWithTag(articleContent, [ "a" ]); this._forEachNode(links, function(link) { const href = link.getAttribute("href"); if (href) {
if (href.indexOf("javascript:") === 0) {
if (link.childNodes.length === 1 && link.childNodes[0].nodeType === this.TEXT_NODE) { const text = this._doc.createTextNode(link.textContent); link.parentNode.replaceChild(text, link); } else {
const container = this._doc.createElement("span"); while (link.childNodes.length > 0) { container.appendChild(link.childNodes[0]); } link.parentNode.replaceChild(container, link); } } else { link.setAttribute("href", toAbsoluteURI(href)); } } });
const medias = this._getAllNodesWithTag(articleContent, [ "img", "picture", "figure", "video", "audio", "source", ]);
this._forEachNode(medias, function(media) { const src = media.getAttribute("src"); const poster = media.getAttribute("poster"); const srcset = media.getAttribute("srcset");
if (src) { media.setAttribute("src", toAbsoluteURI(src)); }
if (poster) { media.setAttribute("poster", toAbsoluteURI(poster)); }
if (srcset) { const newSrcset = srcset.replace(this.REGEXPS.srcsetUrl, (_, p1, p2, p3) => toAbsoluteURI(p1) + (p2 || "") + p3);
media.setAttribute("srcset", newSrcset); } }); },
/** * Get the article title as an H1. * * @return void **/ _getArticleTitle() { const doc = this._doc; let curTitle = ""; let origTitle = "";
try { curTitle = origTitle = doc.title.trim();
if (typeof curTitle !== "string") { curTitle = origTitle = this._getInnerText(doc.getElementsByTagName("title")[0]); } } catch (e) { /* ignore exceptions setting the title. */ }
let titleHadHierarchicalSeparators = false; function wordCount(str) { return str.split(/\s+/).length; }
if ((/ [\|\-\\\/>»] /).test(curTitle)) { titleHadHierarchicalSeparators = / [\\\/>»] /.test(curTitle); curTitle = origTitle.replace(/(.*)[\|\-\\\/>»] .*/gi, "$1");
if (wordCount(curTitle) < 3) { curTitle = origTitle.replace(/[^\|\-\\\/>»]*[\|\-\\\/>»](.*)/gi, "$1"); } } else if (curTitle.indexOf(": ") !== -1) {
const headings = this._concatNodeLists( doc.getElementsByTagName("h1"), doc.getElementsByTagName("h2"), ); const trimmedTitle = curTitle.trim(); const match = this._someNode(headings, heading => heading.textContent.trim() === trimmedTitle);
if (!match) { curTitle = origTitle.substring(origTitle.lastIndexOf(":") + 1);
if (wordCount(curTitle) < 3) { curTitle = origTitle.substring(origTitle.indexOf(":") + 1);
} else if (wordCount(origTitle.substr(0, origTitle.indexOf(":"))) > 5) { curTitle = origTitle; } } } else if (curTitle.length > 150 || curTitle.length < 15) { const hOnes = doc.getElementsByTagName("h1");
if (hOnes.length === 1) { curTitle = this._getInnerText(hOnes[0]); } }
curTitle = curTitle.trim().replace(this.REGEXPS.normalize, " ");
const curTitleWordCount = wordCount(curTitle); if (curTitleWordCount <= 4 && (!titleHadHierarchicalSeparators || curTitleWordCount != wordCount(origTitle.replace(/[\|\-\\\/>»]+/g, "")) - 1)) { curTitle = origTitle; }
return curTitle; },
/** * Prepare the HTML document for readability to scrape it. * This includes things like stripping javascript, CSS, and handling terrible markup. * * @return void **/ _prepDocument() { const doc = this._doc;
this._removeNodes(this._getAllNodesWithTag(doc, [ "style" ]));
if (doc.body) { this._replaceBrs(doc.body); }
this._replaceNodeTags(this._getAllNodesWithTag(doc, [ "font" ]), "SPAN"); },
/** * Finds the next element, starting from the given node, and ignoring * whitespace in between. If the given node is an element, the same node is * returned. */ _nextElement(node) { let next = node; while (next && (next.nodeType != this.ELEMENT_NODE) && this.REGEXPS.whitespace.test(next.textContent)) { next = next.nextSibling; } return next; },
/**
* Replaces 2 or more successive
elements with a single
. * Whitespace between
elements are ignored. For example: *
bar
abc* will become: *
abc
*/ _replaceBrs(elem) { this._forEachNode(this._getAllNodesWithTag(elem, [ "br" ]), function(br) { let next = br.nextSibling;
let replaced = false;
while ((next = this._nextElement(next)) && (next.tagName == "BR")) { replaced = true; const brSibling = next.nextSibling; next.parentNode.removeChild(next); next = brSibling; }
if (replaced) { const p = this._doc.createElement("p"); br.parentNode.replaceChild(p, br);
next = p.nextSibling; while (next) {
if (next.tagName == "BR") { const nextElem = this._nextElement(next.nextSibling); if (nextElem && nextElem.tagName == "BR") { break; } }
if (!this._isPhrasingContent(next)) { break; }
const sibling = next.nextSibling; p.appendChild(next); next = sibling; }
while (p.lastChild && this._isWhitespace(p.lastChild)) { p.removeChild(p.lastChild); }
if (p.parentNode.tagName === "P") { this._setNodeTag(p.parentNode, "DIV"); } } }); },
_setNodeTag(node, tag) { this.log("_setNodeTag", node, tag); if (this._docJSDOMParser) { node.localName = tag.toLowerCase(); node.tagName = tag.toUpperCase(); return node; }
const replacement = node.ownerDocument.createElement(tag); while (node.firstChild) { replacement.appendChild(node.firstChild); } node.parentNode.replaceChild(replacement, node); if (node.readability) { replacement.readability = node.readability; }
for (let i = 0; i < node.attributes.length; i++) { try { replacement.setAttribute(node.attributes[i].name, node.attributes[i].value); } catch (ex) { /* it's possible for setAttribute() to throw if the attribute name * isn't a valid XML Name. Such attributes can however be parsed from * source in HTML docs, see https: * so we can hit them here and then throw. We don't care about such * attributes so we ignore them. */ } } return replacement; },
/** * Prepare the article node for display. Clean out any inline styles,
* iframes, forms, strip extraneous
tags, etc. * * @param Element * @return void **/ _prepArticle(articleContent) { this._cleanStyles(articleContent); this._markDataTables(articleContent); this._fixLazyImages(articleContent); this._cleanConditionally(articleContent, "form"); this._cleanConditionally(articleContent, "fieldset"); this._clean(articleContent, "object"); this._clean(articleContent, "embed"); this._clean(articleContent, "h1"); this._clean(articleContent, "footer"); this._clean(articleContent, "link"); this._clean(articleContent, "aside"); const shareElementThreshold = this.DEFAULT_CHAR_THRESHOLD; this._forEachNode(articleContent.children, function(topCandidate) { this._cleanMatchedNodes(topCandidate, function(node, matchString) { return this.REGEXPS.shareElements.test(matchString) && node.textContent.length < shareElementThreshold; }); }); const h2 = articleContent.getElementsByTagName("h2"); if (h2.length === 1) { const lengthSimilarRate = (h2[0].textContent.length - this._articleTitle.length) / this._articleTitle.length; if (Math.abs(lengthSimilarRate) < 0.5) { let titlesMatch = false; if (lengthSimilarRate > 0) { titlesMatch = h2[0].textContent.includes(this._articleTitle); } else { titlesMatch = this._articleTitle.includes(h2[0].textContent); } if (titlesMatch) { this._clean(articleContent, "h2"); } } } this._clean(articleContent, "iframe"); this._clean(articleContent, "input"); this._clean(articleContent, "textarea"); this._clean(articleContent, "select"); this._clean(articleContent, "button"); this._cleanHeaders(articleContent); this._cleanConditionally(articleContent, "table"); this._cleanConditionally(articleContent, "ul"); this._cleanConditionally(articleContent, "div"); this._removeNodes(this._getAllNodesWithTag(articleContent, [ "p" ]), function(paragraph) { const imgCount = paragraph.getElementsByTagName("img").length; const embedCount = paragraph.getElementsByTagName("embed").length; const objectCount = paragraph.getElementsByTagName("object").length; const iframeCount = paragraph.getElementsByTagName("iframe").length; const totalCount = imgCount + embedCount + objectCount + iframeCount; return totalCount === 0 && !this._getInnerText(paragraph, false); }); this._forEachNode(this._getAllNodesWithTag(articleContent, [ "br" ]), function(br) { const next = this._nextElement(br.nextSibling); if (next && next.tagName == "P") { br.parentNode.removeChild(br); } }); this._forEachNode(this._getAllNodesWithTag(articleContent, [ "table" ]), function(table) { const tbody = this._hasSingleTagInsideElement(table, "TBODY") ? table.firstElementChild : table; if (this._hasSingleTagInsideElement(tbody, "TR")) { const row = tbody.firstElementChild; if (this._hasSingleTagInsideElement(row, "TD")) { let cell = row.firstElementChild; cell = this._setNodeTag(cell, this._everyNode(cell.childNodes, this._isPhrasingContent) ? "P" : "DIV"); table.parentNode.replaceChild(cell, table); } } }); }, /** * Initialize a node with the readability object. Also checks the * className/id for special names to add to its score. * * @param Element * @return void **/ _initializeNode(node) { node.readability = { contentScore: 0 }; switch (node.tagName) { case "DIV": node.readability.contentScore += 5; break; case "PRE": case "TD": case "BLOCKQUOTE": node.readability.contentScore += 3; break; case "ADDRESS": case "OL": case "UL": case "DL": case "DD": case "DT": case "LI": case "FORM": node.readability.contentScore -= 3; break; case "H1": case "H2": case "H3": case "H4": case "H5": case "H6": case "TH": node.readability.contentScore -= 5; break; } node.readability.contentScore += this._getClassWeight(node); }, _removeAndGetNext(node) { const nextNode = this._getNextNode(node, true); node.parentNode.removeChild(node); return nextNode; }, /** * Traverse the DOM from node to node, starting at the node passed in. * Pass true for the second parameter to indicate this node itself * (and its kids) are going away, and we want the next node over. * * Calling this in a loop will traverse the DOM depth-first. */ _getNextNode(node, ignoreSelfAndKids) { if (!ignoreSelfAndKids && node.firstElementChild) { return node.firstElementChild; } if (node.nextElementSibling) { return node.nextElementSibling; } do { node = node.parentNode; } while (node && !node.nextElementSibling); return node && node.nextElementSibling; }, _checkByline(node, matchString) { if (this._articleByline) { return false; } if (node.getAttribute !== undefined) { var rel = node.getAttribute("rel"); var itemprop = node.getAttribute("itemprop"); } if ((rel === "author" || (itemprop && itemprop.indexOf("author") !== -1) || this.REGEXPS.byline.test(matchString)) && this._isValidByline(node.textContent)) { this._articleByline = node.textContent.trim(); return true; } return false; }, _getNodeAncestors(node, maxDepth) { maxDepth = maxDepth || 0; let i = 0, ancestors = []; while (node.parentNode) { ancestors.push(node.parentNode); if (maxDepth && ++i === maxDepth) { break; } node = node.parentNode; } return ancestors; }, /** * * grabArticle - Using a variety of metrics (content score, classname, element types), find the content that is * most likely to be the stuff a user wants to read. Then return it wrapped up in a div. * * @param page a document to run upon. Needs to be a full document, complete with body. * @return Element **/ _grabArticle(page) { this.log("**** grabArticle ****"); const doc = this._doc; const isPaging = (page !== null); page = page ? page : this._doc.body; if (!page) { this.log("No body found in document. Abort."); return null; } const pageCacheHtml = page.innerHTML; while (true) { const stripUnlikelyCandidates = this._flagIsActive(this.FLAG_STRIP_UNLIKELYS); const elementsToScore = []; let node = this._doc.documentElement; while (node) { const matchString = node.className + " " + node.id; if (!this._isProbablyVisible(node)) { this.log("Removing hidden node - " + matchString); node = this._removeAndGetNext(node); continue; } if (this._checkByline(node, matchString)) { node = this._removeAndGetNext(node); continue; } if (stripUnlikelyCandidates) { if (this.REGEXPS.unlikelyCandidates.test(matchString) && !this.REGEXPS.okMaybeItsACandidate.test(matchString) && !this._hasAncestorTag(node, "table") && node.tagName !== "BODY" && node.tagName !== "A") { this.log("Removing unlikely candidate - " + matchString); node = this._removeAndGetNext(node); continue; } if (node.getAttribute("role") == "complementary") { this.log("Removing complementary content - " + matchString); node = this._removeAndGetNext(node); continue; } } if ((node.tagName === "DIV" || node.tagName === "SECTION" || node.tagName === "HEADER" || node.tagName === "H1" || node.tagName === "H2" || node.tagName === "H3" || node.tagName === "H4" || node.tagName === "H5" || node.tagName === "H6") && this._isElementWithoutContent(node)) { node = this._removeAndGetNext(node); continue; } if (this.DEFAULT_TAGS_TO_SCORE.indexOf(node.tagName) !== -1) { elementsToScore.push(node); } if (node.tagName === "DIV") { let p = null; let childNode = node.firstChild; while (childNode) { const nextSibling = childNode.nextSibling; if (this._isPhrasingContent(childNode)) { if (p !== null) { p.appendChild(childNode); } else if (!this._isWhitespace(childNode)) { p = doc.createElement("p"); node.replaceChild(p, childNode); p.appendChild(childNode); } } else if (p !== null) { while (p.lastChild && this._isWhitespace(p.lastChild)) { p.removeChild(p.lastChild); } p = null; } childNode = nextSibling; } if (this._hasSingleTagInsideElement(node, "P") && this._getLinkDensity(node) < 0.25) { const newNode = node.children[0]; node.parentNode.replaceChild(newNode, node); node = newNode; elementsToScore.push(node); } else if (!this._hasChildBlockElement(node)) { node = this._setNodeTag(node, "P"); elementsToScore.push(node); } } node = this._getNextNode(node); } /** * Loop through all paragraphs, and assign a score to them based on how content-y they look. * Then add their score to their parent node. * * A score is determined by things like number of commas, class names, etc. Maybe eventually link density. **/ var candidates = []; this._forEachNode(elementsToScore, function(elementToScore) { if (!elementToScore.parentNode || typeof (elementToScore.parentNode.tagName) === "undefined") { return; } const innerText = this._getInnerText(elementToScore); if (innerText.length < 25) { return; } const ancestors = this._getNodeAncestors(elementToScore, 3); if (ancestors.length === 0) { return; } let contentScore = 0; contentScore += 1; contentScore += innerText.split(",").length; contentScore += Math.min(Math.floor(innerText.length / 100), 3); this._forEachNode(ancestors, function(ancestor, level) { if (!ancestor.tagName || !ancestor.parentNode || typeof (ancestor.parentNode.tagName) === "undefined") { return; } if (typeof (ancestor.readability) === "undefined") { this._initializeNode(ancestor); candidates.push(ancestor); } if (level === 0) { var scoreDivider = 1; } else if (level === 1) { scoreDivider = 2; } else { scoreDivider = level * 3; } ancestor.readability.contentScore += contentScore / scoreDivider; }); }); const topCandidates = []; for (let c = 0, cl = candidates.length; c < cl; c += 1) { const candidate = candidates[c]; const candidateScore = candidate.readability.contentScore * (1 - this._getLinkDensity(candidate)); candidate.readability.contentScore = candidateScore; this.log("Candidate:", candidate, "with score " + candidateScore); for (let t = 0; t < this._nbTopCandidates; t++) { const aTopCandidate = topCandidates[t]; if (!aTopCandidate || candidateScore > aTopCandidate.readability.contentScore) { topCandidates.splice(t, 0, candidate); if (topCandidates.length > this._nbTopCandidates) { topCandidates.pop(); } break; } } } let topCandidate = topCandidates[0] || null; let neededToCreateTopCandidate = false; var parentOfTopCandidate; if (topCandidate === null || topCandidate.tagName === "BODY") { topCandidate = doc.createElement("DIV"); neededToCreateTopCandidate = true; const kids = page.childNodes; while (kids.length) { this.log("Moving child out:", kids[0]); topCandidate.appendChild(kids[0]); } page.appendChild(topCandidate); this._initializeNode(topCandidate); } else if (topCandidate) { const alternativeCandidateAncestors = []; for (let i = 1; i < topCandidates.length; i++) { if (topCandidates[i].readability.contentScore / topCandidate.readability.contentScore >= 0.75) { alternativeCandidateAncestors.push(this._getNodeAncestors(topCandidates[i])); } } const MINIMUM_TOPCANDIDATES = 3; if (alternativeCandidateAncestors.length >= MINIMUM_TOPCANDIDATES) { parentOfTopCandidate = topCandidate.parentNode; while (parentOfTopCandidate.tagName !== "BODY") { let listsContainingThisAncestor = 0; for (let ancestorIndex = 0; ancestorIndex < alternativeCandidateAncestors.length && listsContainingThisAncestor < MINIMUM_TOPCANDIDATES; ancestorIndex++) { listsContainingThisAncestor += Number(alternativeCandidateAncestors[ancestorIndex].includes(parentOfTopCandidate)); } if (listsContainingThisAncestor >= MINIMUM_TOPCANDIDATES) { topCandidate = parentOfTopCandidate; break; } parentOfTopCandidate = parentOfTopCandidate.parentNode; } } if (!topCandidate.readability) { this._initializeNode(topCandidate); } parentOfTopCandidate = topCandidate.parentNode; let lastScore = topCandidate.readability.contentScore; const scoreThreshold = lastScore / 3; while (parentOfTopCandidate.tagName !== "BODY") { if (!parentOfTopCandidate.readability) { parentOfTopCandidate = parentOfTopCandidate.parentNode; continue; } const parentScore = parentOfTopCandidate.readability.contentScore; if (parentScore < scoreThreshold) { break; } if (parentScore > lastScore) { topCandidate = parentOfTopCandidate; break; } lastScore = parentOfTopCandidate.readability.contentScore; parentOfTopCandidate = parentOfTopCandidate.parentNode; } parentOfTopCandidate = topCandidate.parentNode; while (parentOfTopCandidate.tagName != "BODY" && parentOfTopCandidate.children.length == 1) { topCandidate = parentOfTopCandidate; parentOfTopCandidate = topCandidate.parentNode; } if (!topCandidate.readability) { this._initializeNode(topCandidate); } } let articleContent = doc.createElement("DIV"); if (isPaging) { articleContent.id = "readability-content"; } const siblingScoreThreshold = Math.max(10, topCandidate.readability.contentScore * 0.2); parentOfTopCandidate = topCandidate.parentNode; const siblings = parentOfTopCandidate.children; for (let s = 0, sl = siblings.length; s < sl; s++) { let sibling = siblings[s]; let append = false; this.log("Looking at sibling node:", sibling, sibling.readability ? ("with score " + sibling.readability.contentScore) : ""); this.log("Sibling has score", sibling.readability ? sibling.readability.contentScore : "Unknown"); if (sibling === topCandidate) { append = true; } else { let contentBonus = 0; if (sibling.className === topCandidate.className && topCandidate.className !== "") { contentBonus += topCandidate.readability.contentScore * 0.2; } if (sibling.readability && ((sibling.readability.contentScore + contentBonus) >= siblingScoreThreshold)) { append = true; } else if (sibling.nodeName === "P") { const linkDensity = this._getLinkDensity(sibling); const nodeContent = this._getInnerText(sibling); const nodeLength = nodeContent.length; if (nodeLength > 80 && linkDensity < 0.25) { append = true; } else if (nodeLength < 80 && nodeLength > 0 && linkDensity === 0 && nodeContent.search(/\.( |$)/) !== -1) { append = true; } } } if (append) { this.log("Appending node:", sibling); if (this.ALTER_TO_DIV_EXCEPTIONS.indexOf(sibling.nodeName) === -1) { this.log("Altering sibling:", sibling, "to div."); sibling = this._setNodeTag(sibling, "DIV"); } articleContent.appendChild(sibling); s -= 1; sl -= 1; } } if (this._debug) { this.log("Article content pre-prep: " + articleContent.innerHTML); } this._prepArticle(articleContent); if (this._debug) { this.log("Article content post-prep: " + articleContent.innerHTML); } if (neededToCreateTopCandidate) { topCandidate.id = "readability-page-1"; topCandidate.className = "page"; } else { const div = doc.createElement("DIV"); div.id = "readability-page-1"; div.className = "page"; const children = articleContent.childNodes; while (children.length) { div.appendChild(children[0]); } articleContent.appendChild(div); } if (this._debug) { this.log("Article content after paging: " + articleContent.innerHTML); } let parseSuccessful = true; const textLength = this._getInnerText(articleContent, true).length; if (textLength < this._charThreshold) { parseSuccessful = false; page.innerHTML = pageCacheHtml; if (this._flagIsActive(this.FLAG_STRIP_UNLIKELYS)) { this._removeFlag(this.FLAG_STRIP_UNLIKELYS); this._attempts.push({ articleContent, textLength }); } else if (this._flagIsActive(this.FLAG_WEIGHT_CLASSES)) { this._removeFlag(this.FLAG_WEIGHT_CLASSES); this._attempts.push({ articleContent, textLength }); } else if (this._flagIsActive(this.FLAG_CLEAN_CONDITIONALLY)) { this._removeFlag(this.FLAG_CLEAN_CONDITIONALLY); this._attempts.push({ articleContent, textLength }); } else { this._attempts.push({ articleContent, textLength }); this._attempts.sort((a, b) => b.textLength - a.textLength); if (!this._attempts[0].textLength) { return null; } articleContent = this._attempts[0].articleContent; parseSuccessful = true; } } if (parseSuccessful) { const ancestors = [ parentOfTopCandidate, topCandidate ].concat(this._getNodeAncestors(parentOfTopCandidate)); this._someNode(ancestors, function(ancestor) { if (!ancestor.tagName) { return false; } const articleDir = ancestor.getAttribute("dir"); if (articleDir) { this._articleDir = articleDir; return true; } return false; }); return articleContent; } } }, /** * Check whether the input string could be a byline. * This verifies that the input is a string, and that the length * is less than 100 chars. * * @param possibleByline {string} - a string to check whether its a byline. * @return Boolean - whether the input string is a byline. */ _isValidByline(byline) { if (typeof byline == "string" || byline instanceof String) { byline = byline.trim(); return (byline.length > 0) && (byline.length < 100); } return false; }, /** * Converts some of the common HTML entities in string to their corresponding characters. * * @param str {string} - a string to unescape. * @return string without HTML entity. */ _unescapeHtmlEntities(str) { if (!str) { return str; } const htmlEscapeMap = this.HTML_ESCAPE_MAP; return str.replace(/&(quot|amp|apos|lt|gt);/g, (_, tag) => htmlEscapeMap[tag]).replace(/&#(?:x([0-9a-z]{1,4})|([0-9]{1,4}));/gi, (_, hex, numStr) => { const num = parseInt(hex || numStr, hex ? 16 : 10); return String.fromCharCode(num); }); }, /** * Attempts to get excerpt and byline metadata for the article. * * @return Object with optional "excerpt" and "byline" properties */ _getArticleMetadata() { const metadata = {}; const values = {}; const metaElements = this._doc.getElementsByTagName("meta"); const propertyPattern = /\s*(dc|dcterm|og|twitter)\s*:\s*(author|creator|description|title|site_name)\s*/gi; const namePattern = /^\s*(?:(dc|dcterm|og|twitter|weibo:(article|webpage))\s*[\.:]\s*)?(author|creator|description|title|site_name)\s*$/i; this._forEachNode(metaElements, element => { const elementName = element.getAttribute("name"); const elementProperty = element.getAttribute("property"); const content = element.getAttribute("content"); if (!content) { return; } let matches = null; let name = null; if (elementProperty) { matches = elementProperty.match(propertyPattern); if (matches) { for (let i = matches.length - 1; i >= 0; i--) { name = matches[i].toLowerCase().replace(/\s/g, ""); values[name] = content.trim(); } } } if (!matches && elementName && namePattern.test(elementName)) { name = elementName; if (content) { name = name.toLowerCase().replace(/\s/g, "").replace(/\./g, ":"); values[name] = content.trim(); } } }); metadata.title = values["dc:title"] || values["dcterm:title"] || values["og:title"] || values["weibo:article:title"] || values["weibo:webpage:title"] || values.title || values["twitter:title"]; if (!metadata.title) { metadata.title = this._getArticleTitle(); } metadata.byline = values["dc:creator"] || values["dcterm:creator"] || values.author; metadata.excerpt = values["dc:description"] || values["dcterm:description"] || values["og:description"] || values["weibo:article:description"] || values["weibo:webpage:description"] || values.description || values["twitter:description"]; metadata.siteName = values["og:site_name"]; metadata.title = this._unescapeHtmlEntities(metadata.title); metadata.byline = this._unescapeHtmlEntities(metadata.byline); metadata.excerpt = this._unescapeHtmlEntities(metadata.excerpt); metadata.siteName = this._unescapeHtmlEntities(metadata.siteName); return metadata; }, /** * Check if node is image, or if node contains exactly only one image * whether as a direct child or as its descendants. * * @param Element **/ _isSingleImage(node) { if (node.tagName === "IMG") { return true; } if (node.children.length !== 1 || node.textContent.trim() !== "") { return false; } return this._isSingleImage(node.children[0]); }, /** * Find all <noscript> that are located after <img> nodes, and which contain only one * <img> element. Replace the first image with the image from inside the <noscript> tag, * and remove the <noscript> tag. This improves the quality of the images we use on * some sites (e.g. Medium). * * @param Element **/ _unwrapNoscriptImages(doc) { const imgs = Array.from(doc.getElementsByTagName("img")); this._forEachNode(imgs, img => { for (let i = 0; i < img.attributes.length; i++) { const attr = img.attributes[i]; switch (attr.name) { case "src": case "srcset": case "data-src": case "data-srcset": return; } if (/\.(jpg|jpeg|png|webp)/i.test(attr.value)) { return; } } img.parentNode.removeChild(img); }); const noscripts = Array.from(doc.getElementsByTagName("noscript")); this._forEachNode(noscripts, function(noscript) { const tmp = doc.createElement("div"); tmp.innerHTML = noscript.innerHTML; if (!this._isSingleImage(tmp)) { return; } const prevElement = noscript.previousElementSibling; if (prevElement && this._isSingleImage(prevElement)) { let prevImg = prevElement; if (prevImg.tagName !== "IMG") { prevImg = prevElement.getElementsByTagName("img")[0]; } const newImg = tmp.getElementsByTagName("img")[0]; for (let i = 0; i < prevImg.attributes.length; i++) { const attr = prevImg.attributes[i]; if (attr.value === "") { continue; } if (attr.name === "src" || attr.name === "srcset" || /\.(jpg|jpeg|png|webp)/i.test(attr.value)) { if (newImg.getAttribute(attr.name) === attr.value) { continue; } let attrName = attr.name; if (newImg.hasAttribute(attrName)) { attrName = "data-old-" + attrName; } newImg.setAttribute(attrName, attr.value); } } noscript.parentNode.replaceChild(tmp.firstElementChild, prevElement); } }); }, /** * Removes script tags from the document. * * @param Element **/ _removeScripts(doc) { this._removeNodes(this._getAllNodesWithTag(doc, [ "script" ]), scriptNode => { scriptNode.nodeValue = ""; scriptNode.removeAttribute("src"); return true; }); this._removeNodes(this._getAllNodesWithTag(doc, [ "noscript" ])); }, /** * Check if this node has only whitespace and a single element with given tag * Returns false if the DIV node contains non-empty text nodes * or if it contains no element with given tag or more than 1 element. * * @param Element * @param string tag of child element **/ _hasSingleTagInsideElement(element, tag) { if (element.children.length != 1 || element.children[0].tagName !== tag) { return false; } return !this._someNode(element.childNodes, function(node) { return node.nodeType === this.TEXT_NODE && this.REGEXPS.hasContent.test(node.textContent); }); }, _isElementWithoutContent(node) { return node.nodeType === this.ELEMENT_NODE && node.textContent.trim().length == 0 && (node.children.length == 0 || node.children.length == node.getElementsByTagName("br").length + node.getElementsByTagName("hr").length); }, /** * Determine whether element has any children block level elements. * * @param Element */ _hasChildBlockElement(element) { return this._someNode(element.childNodes, function(node) { return this.DIV_TO_P_ELEMS.indexOf(node.tagName) !== -1 || this._hasChildBlockElement(node); }); }, /** * * Determine if a node qualifies as phrasing content. * https: **/ _isPhrasingContent(node) { return node.nodeType === this.TEXT_NODE || this.PHRASING_ELEMS.indexOf(node.tagName) !== -1 || ((node.tagName === "A" || node.tagName === "DEL" || node.tagName === "INS") && this._everyNode(node.childNodes, this._isPhrasingContent)); }, _isWhitespace(node) { return (node.nodeType === this.TEXT_NODE && node.textContent.trim().length === 0) || (node.nodeType === this.ELEMENT_NODE && node.tagName === "BR"); }, /** * Get the inner text of a node - cross browser compatibly. * This also strips out any excess whitespace to be found. * * @param Element * @param Boolean normalizeSpaces (default: true) * @return string **/ _getInnerText(e, normalizeSpaces) { normalizeSpaces = (typeof normalizeSpaces === "undefined") ? true : normalizeSpaces; const textContent = e.textContent.trim(); if (normalizeSpaces) { return textContent.replace(this.REGEXPS.normalize, " "); } return textContent; }, /** * Get the number of times a string s appears in the node e. * * @param Element * @param string - what to split on. Default is "," * @return number (integer) **/ _getCharCount(e, s) { s = s || ","; return this._getInnerText(e).split(s).length - 1; }, /** * Remove the style attribute on every e and under. * TODO: Test if getElementsByTagName(*) is faster. * * @param Element * @return void **/ _cleanStyles(e) { if (!e || e.tagName.toLowerCase() === "svg") { return; } for (let i = 0; i < this.PRESENTATIONAL_ATTRIBUTES.length; i++) { e.removeAttribute(this.PRESENTATIONAL_ATTRIBUTES[i]); } if (this.DEPRECATED_SIZE_ATTRIBUTE_ELEMS.indexOf(e.tagName) !== -1) { e.removeAttribute("width"); e.removeAttribute("height"); } let cur = e.firstElementChild; while (cur !== null) { this._cleanStyles(cur); cur = cur.nextElementSibling; } }, /** * Get the density of links as a percentage of the content * This is the amount of text that is inside a link divided by the total text in the node. * * @param Element * @return number (float) **/ _getLinkDensity(element) { const textLength = this._getInnerText(element).length; if (textLength === 0) { return 0; } let linkLength = 0; this._forEachNode(element.getElementsByTagName("a"), function(linkNode) { linkLength += this._getInnerText(linkNode).length; }); return linkLength / textLength; }, /** * Get an elements class/id weight. Uses regular expressions to tell if this * element looks good or bad. * * @param Element * @return number (Integer) **/ _getClassWeight(e) { if (!this._flagIsActive(this.FLAG_WEIGHT_CLASSES)) { return 0; } let weight = 0; if (typeof (e.className) === "string" && e.className !== "") { if (this.REGEXPS.negative.test(e.className)) { weight -= 25; } if (this.REGEXPS.positive.test(e.className)) { weight += 25; } } if (typeof (e.id) === "string" && e.id !== "") { if (this.REGEXPS.negative.test(e.id)) { weight -= 25; } if (this.REGEXPS.positive.test(e.id)) { weight += 25; } } return weight; }, /** * Clean a node of all elements of type "tag". * (Unless it's a youtube/vimeo video. People love movies.) * * @param Element * @param string tag to clean * @return void **/ _clean(e, tag) { const isEmbed = [ "object", "embed", "iframe" ].indexOf(tag) !== -1; this._removeNodes(this._getAllNodesWithTag(e, [ tag ]), function(element) { if (isEmbed) { for (let i = 0; i < element.attributes.length; i++) { if (this.REGEXPS.videos.test(element.attributes[i].value)) { return false; } } if (element.tagName === "object" && this.REGEXPS.videos.test(element.innerHTML)) { return false; } } return true; }); }, /** * Check if a given node has one of its ancestor tag name matching the * provided one. * @param HTMLElement node * @param String tagName * @param Number maxDepth * @param Function filterFn a filter to invoke to determine whether this node 'counts' * @return Boolean */ _hasAncestorTag(node, tagName, maxDepth, filterFn) { maxDepth = maxDepth || 3; tagName = tagName.toUpperCase(); let depth = 0; while (node.parentNode) { if (maxDepth > 0 && depth > maxDepth) { return false; } if (node.parentNode.tagName === tagName && (!filterFn || filterFn(node.parentNode))) { return true; } node = node.parentNode; depth++; } return false; }, /** * Return an object indicating how many rows and columns this table has. */ _getRowAndColumnCount(table) { let rows = 0; let columns = 0; const trs = table.getElementsByTagName("tr"); for (let i = 0; i < trs.length; i++) { let rowspan = trs[i].getAttribute("rowspan") || 0; if (rowspan) { rowspan = parseInt(rowspan, 10); } rows += (rowspan || 1); let columnsInThisRow = 0; const cells = trs[i].getElementsByTagName("td"); for (let j = 0; j < cells.length; j++) { let colspan = cells[j].getAttribute("colspan") || 0; if (colspan) { colspan = parseInt(colspan, 10); } columnsInThisRow += (colspan || 1); } columns = Math.max(columns, columnsInThisRow); } return { rows, columns }; }, /** * Look for 'data' (as opposed to 'layout') tables, for which we use * similar checks as * https: */ _markDataTables(root) { const tables = root.getElementsByTagName("table"); for (let i = 0; i < tables.length; i++) { var table = tables[i]; const role = table.getAttribute("role"); if (role == "presentation") { table._readabilityDataTable = false; continue; } const datatable = table.getAttribute("datatable"); if (datatable == "0") { table._readabilityDataTable = false; continue; } const summary = table.getAttribute("summary"); if (summary) { table._readabilityDataTable = true; continue; } const caption = table.getElementsByTagName("caption")[0]; if (caption && caption.childNodes.length > 0) { table._readabilityDataTable = true; continue; } const dataTableDescendants = [ "col", "colgroup", "tfoot", "thead", "th" ]; const descendantExists = function(tag) { return !!table.getElementsByTagName(tag)[0]; }; if (dataTableDescendants.some(descendantExists)) { this.log("Data table because found data-y descendant"); table._readabilityDataTable = true; continue; } if (table.getElementsByTagName("table")[0]) { table._readabilityDataTable = false; continue; } const sizeInfo = this._getRowAndColumnCount(table); if (sizeInfo.rows >= 10 || sizeInfo.columns > 4) { table._readabilityDataTable = true; continue; } table._readabilityDataTable = sizeInfo.rows * sizeInfo.columns > 10; } }, /* convert images and figures that have properties like data-src into images that can be loaded without JS */ _fixLazyImages(root) { this._forEachNode(this._getAllNodesWithTag(root, [ "img", "picture", "figure" ]), function(elem) { if (elem.src && this.REGEXPS.b64DataUrl.test(elem.src)) { const parts = this.REGEXPS.b64DataUrl.exec(elem.src); if (parts[1] === "image/svg+xml") { return; } let srcCouldBeRemoved = false; for (let i = 0; i < elem.attributes.length; i++) { var attr = elem.attributes[i]; if (attr.name === "src") { continue; } if (/\.(jpg|jpeg|png|webp)/i.test(attr.value)) { srcCouldBeRemoved = true; break; } } if (srcCouldBeRemoved) { const b64starts = elem.src.search(/base64\s*/i) + 7; const b64length = elem.src.length - b64starts; if (b64length < 133) { elem.removeAttribute("src"); } } } if ((elem.src || (elem.srcset && elem.srcset != "null")) && elem.className.toLowerCase().indexOf("lazy") === -1) { return; } for (let j = 0; j < elem.attributes.length; j++) { attr = elem.attributes[j]; if (attr.name === "src" || attr.name === "srcset") { continue; } let copyTo = null; if (/\.(jpg|jpeg|png|webp)\s+\d/.test(attr.value)) { copyTo = "srcset"; } else if (/^\s*\S+\.(jpg|jpeg|png|webp)\S*\s*$/.test(attr.value)) { copyTo = "src"; } if (copyTo) { if (elem.tagName === "IMG" || elem.tagName === "PICTURE") { elem.setAttribute(copyTo, attr.value); } else if (elem.tagName === "FIGURE" && !this._getAllNodesWithTag(elem, [ "img", "picture" ]).length) { const img = this._doc.createElement("img"); img.setAttribute(copyTo, attr.value); elem.appendChild(img); } } } }); }, /** * Clean an element of all tags of type "tag" if they look fishy. * "Fishy" is an algorithm based on content length, classnames, link density, number of images & embeds, etc. * * @return void **/ _cleanConditionally(e, tag) { if (!this._flagIsActive(this.FLAG_CLEAN_CONDITIONALLY)) { return; } const isList = tag === "ul" || tag === "ol"; this._removeNodes(this._getAllNodesWithTag(e, [ tag ]), function(node) { const isDataTable = function(t) { return t._readabilityDataTable; }; if (tag === "table" && isDataTable(node)) { return false; } if (this._hasAncestorTag(node, "table", -1, isDataTable)) { return false; } const weight = this._getClassWeight(node); const contentScore = 0; this.log("Cleaning Conditionally", node); if (weight + contentScore < 0) { return true; } if (this._getCharCount(node, ",") < 10) { const p = node.getElementsByTagName("p").length; const img = node.getElementsByTagName("img").length; const li = node.getElementsByTagName("li").length - 100; const input = node.getElementsByTagName("input").length; let embedCount = 0; const embeds = this._getAllNodesWithTag(node, [ "object", "embed", "iframe" ]); for (let i = 0; i < embeds.length; i++) { for (let j = 0; j < embeds[i].attributes.length; j++) { if (this.REGEXPS.videos.test(embeds[i].attributes[j].value)) { return false; } } if (embeds[i].tagName === "object" && this.REGEXPS.videos.test(embeds[i].innerHTML)) { return false; } embedCount++; } const linkDensity = this._getLinkDensity(node); const contentLength = this._getInnerText(node).length; const haveToRemove = (img > 1 && p / img < 0.5 && !this._hasAncestorTag(node, "figure")) || (!isList && li > p) || (input > Math.floor(p / 3)) || (!isList && contentLength < 25 && (img === 0 || img > 2) && !this._hasAncestorTag(node, "figure")) || (!isList && weight < 25 && linkDensity > 0.2) || (weight >= 25 && linkDensity > 0.5) || ((embedCount === 1 && contentLength < 75) || embedCount > 1); return haveToRemove; } return false; }); }, /** * Clean out elements that match the specified conditions * * @param Element * @param Function determines whether a node should be removed * @return void **/ _cleanMatchedNodes(e, filter) { const endOfSearchMarkerNode = this._getNextNode(e, true); let next = this._getNextNode(e); while (next && next != endOfSearchMarkerNode) { if (filter.call(this, next, next.className + " " + next.id)) { next = this._removeAndGetNext(next); } else { next = this._getNextNode(next); } } }, /** * Clean out spurious headers from an Element. Checks things like classnames and link density. * * @param Element * @return void **/ _cleanHeaders(e) { this._removeNodes(this._getAllNodesWithTag(e, [ "h1", "h2" ]), function(header) { return this._getClassWeight(header) < 0; }); }, _flagIsActive(flag) { return (this._flags & flag) > 0; }, _removeFlag(flag) { this._flags = this._flags & ~flag; }, _isProbablyVisible(node) { return (!node.style || node.style.display != "none") && !node.hasAttribute("hidden") && (!node.hasAttribute("aria-hidden") || node.getAttribute("aria-hidden") != "true" || (node.className && node.className.indexOf && node.className.indexOf("fallback-image") !== -1)); }, /** * Runs readability. * * Workflow: * 1. Prep the document by removing script tags, css, etc. * 2. Build readability's DOM tree. * 3. Grab the article content from the current dom tree. * 4. Replace the current DOM tree with the new one. * 5. Read peacefully. * * @return void **/ parse() { if (this._maxElemsToParse > 0) { const numTags = this._doc.getElementsByTagName("*").length; if (numTags > this._maxElemsToParse) { throw new Error("Aborting parsing document; " + numTags + " elements found"); } } this._unwrapNoscriptImages(this._doc); this._removeScripts(this._doc); this._prepDocument(); const metadata = this._getArticleMetadata(); this._articleTitle = metadata.title; const articleContent = this._grabArticle(); if (!articleContent) { return null; } this.log("Grabbed: " + articleContent.innerHTML); this._postProcessContent(articleContent); if (!metadata.excerpt) { const paragraphs = articleContent.getElementsByTagName("p"); if (paragraphs.length > 0) { metadata.excerpt = paragraphs[0].textContent.trim(); } } const textContent = articleContent.textContent; return { title: this._articleTitle, byline: metadata.byline || this._articleByline, dir: this._articleDir, content: articleContent.innerHTML, textContent, length: textContent.length, excerpt: metadata.excerpt, siteName: metadata.siteName || this._articleSiteName, }; }, }; /* export default Readability; */ /* Optional vault name */ const vault = "diamond"; /* Optional folder name such as "Clippings/" */ folder = "0. ZETTELKASTEN/0.0 INBOX/"; /* Optional tags */ /* const tags = "#clippings"; */ function getSelectionHtml() { var html = ""; if (typeof window.getSelection != "undefined") { var sel = window.getSelection(); if (sel.rangeCount) { var container = document.createElement("div"); for (var i = 0, len = sel.rangeCount; i < len; ++i) { container.appendChild(sel.getRangeAt(i).cloneContents()); html = container.innerHTML; } } } else if (typeof document.selection != "undefined") { if (document.selection.type == "Text") { html = document.selection.createRange().htmlText; } } return html; } const selection = getSelectionHtml(); const { title, byline, content } = new Readability(document.cloneNode(true)).parse(); function getFileName(fileName) { var userAgent = window.navigator.userAgent, platform = window.navigator.platform, windowsPlatforms = ['Win32', 'Win64', 'Windows', 'WinCE']; if (windowsPlatforms.indexOf(platform) !== -1) { fileName = fileName.replace(':', ).replace(/[/\\?%*|"<>]/g, '-'); } else { fileName = fileName.replace(':', ).replace(/\//g, '-').replace(/\\/g, '-'); } return fileName; } const fileName = getFileName(title); if (selection) { var markdownify = selection; } else { var markdownify = content; } if (vault) { var vaultName = 'vault=' + encodeURIComponent(`${vault}`); } else { var vaultName = ; } const markdownBody = new TurndownService({ headingStyle: 'atx', hr: '---', bulletListMarker: '-', codeBlockStyle: 'fenced', emDelimiter: '*', }).turndown(markdownify); var date = new Date(); function convertDate(date) { var yyyy = date.getFullYear().toString(); var mm = (date.getMonth()+1).toString(); var dd = date.getDate().toString(); var mmChars = mm.split(); var ddChars = dd.split(); return yyyy + '-' + (mmChars[1]?mm:"0"+mmChars[0]) + '-' + (ddChars[1]?dd:"0"+ddChars[0]); } const today = convertDate(date); dafault_tags = "foo" tags = prompt("Enter tags: ", dafault_tags) /* if (tags == null) { alert ("no tag") } else { alert ("tags :" + tags) } */ const fileContent = "---" +"\n" + "title: " + title + "\n" + "source: " + document.URL + ")\n" + "author: " + byline + "\n" + "clipped: " + today + "\n" + "published: " + "\n" + "tags : " + tags + "\n\n" + "---" +"\n" + markdownBody ; const YAMLheader = "---" +"\n" + "title: " + title + "\n" + "source: " + document.URL + ")\n" + "author: " + byline + "\n" + "clipped: [(" + today + ")]\n" + "published: " + "\n" + "tags : " + tags + "\n" + "---" +"\n" ; /* alert (fileContent) */ function copyToClipboard(text) { if (window.clipboardData && window.clipboardData.setData) { /*IE specific code path to prevent textarea being shown while dialog is visible.*/ console.warn("IE") ; return clipboardData.setData("Text", text); } else if (document.queryCommandSupported && document.queryCommandSupported("copy")) { var textarea = document.createElement("textarea"); textarea.textContent = text; textarea.style.position = "fixed"; /* Prevent scrolling to bottom of page in MS Edge.*/ document.body.appendChild(textarea); textarea.select(); try { console.warn("TextArea") ; navigator.clipboard.writeText(text).then( () => { console.warn("clipboard successfully set") return (true) }, () => { console.warn("clipboard write failed ") return (false) } ); /* return document.execCommand("copy"); */ /* Security exception may be thrown by some browsers.*/ } catch (ex) { console.warn("Copy to clipboard failed.", ex); return false; } finally { console.warn("There finally") ; document.body.removeChild(textarea); } } } copyToClipboard(fileContent); linkToObsidian = "obsidian://advanced-uri?" + vaultName + "&" + "filepath=" + encodeURIComponent(folder + fileName + ".md") + "&" + "clipboard=true" + "&" + "mode=new" ; console.warn(linkToObsidian) ; document.location.href = linkToObsidian ; })();