MD一键生成

try to take over the world!

  1. // ==UserScript==
  2. // @name MD一键生成
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.1.4
  5. // @description try to take over the world!
  6. // @author You
  7. // @match https://juejin.im/post/*
  8. // @match https://segmentfault.com/*
  9. // @match https://www.yuque.com/*
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13. (function () {
  14. 'use strict'
  15. // Your code here...
  16. const origin = location.origin
  17. setTimeout(downloadFile, 1000)
  18.  
  19. function downloadFile () {
  20. let fileName, content, parentNode, style
  21. if (origin.includes('juejin')) { // 掘金
  22. const contentNode = document.querySelector('.article-content') || document.querySelector('.article')
  23. content = contentNode.innerHTML
  24. const fileNode = document.querySelector('.article-title')
  25. fileName = fileNode ? fileNode.innerText : `掘金${new Date().getMonth() + 1}-${new Date().getDate()}`
  26. parentNode = document.querySelector('.article-suspended-panel ')
  27. style = `display: block;
  28. position: relative;
  29. margin-bottom: .75rem;
  30. width: 3rem;
  31. height: 3rem;
  32. background-color: #fff;
  33. background-position: 50%;
  34. background-repeat: no-repeat;
  35. border-radius: 50%;
  36. box-shadow: 0 2px 4px 0 rgba(0,0,0,.04);
  37. cursor: pointer;`
  38. } else if (origin.includes('segmentfault')) { // SegmentFault
  39. content = document.querySelector('.article__content').innerHTML
  40. fileName = document.querySelector('#articleTitle a').innerText
  41. parentNode = document.querySelector('.side-widget')
  42. style = `display: block;
  43. width: 38px;
  44. height: 44px;
  45. margin-bottom: 15px;
  46. border: 1px solid transparent;
  47. border-radius: 4px;
  48. background: #26a2ff;
  49. text-align: center;
  50. color: #ccc;
  51. font-size: 18px;`
  52. } else if (origin.includes('yuque')) { // yuque
  53. content = document.querySelector('.lake-engine-view').innerHTML
  54. fileName = document.querySelector('#article-title').innerText
  55. parentNode = document.querySelector('.entry___odTWc')
  56. style = `display: block;
  57. position: relative;
  58. cursor: pointer;
  59. padding: 0px;
  60. z-index: 400;
  61. width:40px;
  62. height: 40px;
  63. background: #fff;
  64. border-radius: 40px;
  65. box-shadow: 0 2px 6px rgba(0,0,0,.15)`
  66. }
  67. html2md(content, fileName, parentNode, style)
  68. }
  69.  
  70. const TurndownService = (function () {
  71. 'use strict'
  72.  
  73. function extend (destination) {
  74. for (var i = 1; i < arguments.length; i++) {
  75. var source = arguments[i]
  76. for (var key in source) {
  77. if (source.hasOwnProperty(key)) destination[key] = source[key]
  78. }
  79. }
  80. return destination
  81. }
  82.  
  83. function repeat (character, count) {
  84. return Array(count + 1).join(character)
  85. }
  86.  
  87. var blockElements = [
  88. 'address', 'article', 'aside', 'audio', 'blockquote', 'body', 'canvas',
  89. 'center', 'dd', 'dir', 'div', 'dl', 'dt', 'fieldset', 'figcaption',
  90. 'figure', 'footer', 'form', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
  91. 'header', 'hgroup', 'hr', 'html', 'isindex', 'li', 'main', 'menu', 'nav',
  92. 'noframes', 'noscript', 'ol', 'output', 'p', 'pre', 'section', 'table',
  93. 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'ul'
  94. ]
  95.  
  96. function isBlock (node) {
  97. return blockElements.indexOf(node.nodeName.toLowerCase()) !== -1
  98. }
  99.  
  100. var voidElements = [
  101. 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input',
  102. 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr'
  103. ]
  104.  
  105. function isVoid (node) {
  106. return voidElements.indexOf(node.nodeName.toLowerCase()) !== -1
  107. }
  108.  
  109. var voidSelector = voidElements.join()
  110.  
  111. function hasVoid (node) {
  112. return node.querySelector && node.querySelector(voidSelector)
  113. }
  114.  
  115. var rules = {}
  116.  
  117. rules.paragraph = {
  118. filter: 'p',
  119.  
  120. replacement: function (content) {
  121. return '\n\n' + content + '\n\n'
  122. }
  123. }
  124.  
  125. rules.lineBreak = {
  126. filter: 'br',
  127.  
  128. replacement: function (content, node, options) {
  129. return options.br + '\n'
  130. }
  131. }
  132.  
  133. rules.heading = {
  134. filter: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
  135.  
  136. replacement: function (content, node, options) {
  137. var hLevel = Number(node.nodeName.charAt(1))
  138.  
  139. if (options.headingStyle === 'setext' && hLevel < 3) {
  140. var underline = repeat((hLevel === 1 ? '=' : '-'), content.length)
  141. return (
  142. '\n\n' + content + '\n' + underline + '\n\n'
  143. )
  144. } else {
  145. return '\n\n' + repeat('#', hLevel) + ' ' + content + '\n\n'
  146. }
  147. }
  148. }
  149.  
  150. rules.blockquote = {
  151. filter: 'blockquote',
  152.  
  153. replacement: function (content) {
  154. content = content.replace(/^\n+|\n+$/g, '')
  155. content = content.replace(/^/gm, '> ')
  156. return '\n\n' + content + '\n\n'
  157. }
  158. }
  159.  
  160. rules.list = {
  161. filter: ['ul', 'ol'],
  162.  
  163. replacement: function (content, node) {
  164. var parent = node.parentNode
  165. if (parent.nodeName === 'LI' && parent.lastElementChild === node) {
  166. return '\n' + content
  167. } else {
  168. return '\n\n' + content + '\n\n'
  169. }
  170. }
  171. }
  172.  
  173. rules.listItem = {
  174. filter: 'li',
  175.  
  176. replacement: function (content, node, options) {
  177. content = content
  178. .replace(/^\n+/, '') // remove leading newlines
  179. .replace(/\n+$/, '\n') // replace trailing newlines with just a single one
  180. .replace(/\n/gm, '\n ') // indent
  181. var prefix = options.bulletListMarker + ' '
  182. var parent = node.parentNode
  183. if (parent.nodeName === 'OL') {
  184. var start = parent.getAttribute('start')
  185. var index = Array.prototype.indexOf.call(parent.children, node)
  186. prefix = (start ? Number(start) + index : index + 1) + '. '
  187. }
  188. return (
  189. prefix + content + (node.nextSibling && !/\n$/.test(content) ? '\n' : '')
  190. )
  191. }
  192. }
  193.  
  194. rules.indentedCodeBlock = {
  195. filter: function (node, options) {
  196. return (
  197. options.codeBlockStyle === 'indented' &&
  198. node.nodeName === 'PRE' &&
  199. node.firstChild &&
  200. node.firstChild.nodeName === 'CODE'
  201. )
  202. },
  203.  
  204. replacement: function (content, node, options) {
  205. return (
  206. '\n\n ' +
  207. node.firstChild.textContent.replace(/\n/g, '\n ') +
  208. '\n\n'
  209. )
  210. }
  211. }
  212.  
  213. rules.fencedCodeBlock = {
  214. filter: function (node, options) {
  215. return (
  216. options.codeBlockStyle === 'fenced' &&
  217. node.nodeName === 'PRE' &&
  218. node.firstChild &&
  219. node.firstChild.nodeName === 'CODE'
  220. )
  221. },
  222.  
  223. replacement: function (content, node, options) {
  224. var className = node.firstChild.className || ''
  225. var language = (className.match(/language-(\S+)/) || [null, ''])[1]
  226.  
  227. return (
  228. '\n\n' + options.fence + language + '\n' +
  229. node.firstChild.textContent +
  230. '\n' + options.fence + '\n\n'
  231. )
  232. }
  233. }
  234.  
  235. rules.horizontalRule = {
  236. filter: 'hr',
  237.  
  238. replacement: function (content, node, options) {
  239. return '\n\n' + options.hr + '\n\n'
  240. }
  241. }
  242.  
  243. rules.inlineLink = {
  244. filter: function (node, options) {
  245. return (
  246. options.linkStyle === 'inlined' &&
  247. node.nodeName === 'A' &&
  248. node.getAttribute('href')
  249. )
  250. },
  251.  
  252. replacement: function (content, node) {
  253. var href = node.getAttribute('href')
  254. var title = node.title ? ' "' + node.title + '"' : ''
  255. return '[' + content + '](' + href + title + ')'
  256. }
  257. }
  258.  
  259. rules.referenceLink = {
  260. filter: function (node, options) {
  261. return (
  262. options.linkStyle === 'referenced' &&
  263. node.nodeName === 'A' &&
  264. node.getAttribute('href')
  265. )
  266. },
  267.  
  268. replacement: function (content, node, options) {
  269. var href = node.getAttribute('href')
  270. var title = node.title ? ' "' + node.title + '"' : ''
  271. var replacement
  272. var reference
  273.  
  274. switch (options.linkReferenceStyle) {
  275. case 'collapsed':
  276. replacement = '[' + content + '][]'
  277. reference = '[' + content + ']: ' + href + title
  278. break
  279. case 'shortcut':
  280. replacement = '[' + content + ']'
  281. reference = '[' + content + ']: ' + href + title
  282. break
  283. default:
  284. var id = this.references.length + 1
  285. replacement = '[' + content + '][' + id + ']'
  286. reference = '[' + id + ']: ' + href + title
  287. }
  288.  
  289. this.references.push(reference)
  290. return replacement
  291. },
  292.  
  293. references: [],
  294.  
  295. append: function (options) {
  296. var references = ''
  297. if (this.references.length) {
  298. references = '\n\n' + this.references.join('\n') + '\n\n'
  299. this.references = [] // Reset references
  300. }
  301. return references
  302. }
  303. }
  304.  
  305. rules.emphasis = {
  306. filter: ['em', 'i'],
  307.  
  308. replacement: function (content, node, options) {
  309. if (!content.trim()) return ''
  310. return options.emDelimiter + content + options.emDelimiter
  311. }
  312. }
  313.  
  314. rules.strong = {
  315. filter: ['strong', 'b'],
  316.  
  317. replacement: function (content, node, options) {
  318. if (!content.trim()) return ''
  319. return options.strongDelimiter + content + options.strongDelimiter
  320. }
  321. }
  322.  
  323. rules.code = {
  324. filter: function (node) {
  325. var hasSiblings = node.previousSibling || node.nextSibling
  326. var isCodeBlock = node.parentNode.nodeName === 'PRE' && !hasSiblings
  327.  
  328. return node.nodeName === 'CODE' && !isCodeBlock
  329. },
  330.  
  331. replacement: function (content) {
  332. if (!content.trim()) return ''
  333.  
  334. var delimiter = '`'
  335. var leadingSpace = ''
  336. var trailingSpace = ''
  337. var matches = content.match(/`+/gm)
  338. if (matches) {
  339. if (/^`/.test(content)) leadingSpace = ' '
  340. if (/`$/.test(content)) trailingSpace = ' '
  341. while (matches.indexOf(delimiter) !== -1) delimiter = delimiter + '`'
  342. }
  343.  
  344. return delimiter + leadingSpace + content + trailingSpace + delimiter
  345. }
  346. }
  347.  
  348. rules.image = {
  349. filter: 'img',
  350.  
  351. replacement: function (content, node) {
  352. var alt = node.alt || ''
  353. var src = node.getAttribute('data-src') || node.getAttribute('src')
  354. src = src.includes('http') ? src : origin + src
  355. var title = node.title || ''
  356. var titlePart = title ? ' "' + title + '"' : ''
  357. return src ? `![${alt}](${src}${titlePart})` : ''
  358. }
  359. }
  360.  
  361. /**
  362. * Manages a collection of rules used to convert HTML to Markdown
  363. */
  364.  
  365. function Rules (options) {
  366. this.options = options
  367. this._keep = []
  368. this._remove = []
  369.  
  370. this.blankRule = {
  371. replacement: options.blankReplacement
  372. }
  373.  
  374. this.keepReplacement = options.keepReplacement
  375.  
  376. this.defaultRule = {
  377. replacement: options.defaultReplacement
  378. }
  379.  
  380. this.array = []
  381. for (var key in options.rules) this.array.push(options.rules[key])
  382. }
  383.  
  384. Rules.prototype = {
  385. add: function (key, rule) {
  386. this.array.unshift(rule)
  387. },
  388.  
  389. keep: function (filter) {
  390. this._keep.unshift({
  391. filter: filter,
  392. replacement: this.keepReplacement
  393. })
  394. },
  395.  
  396. remove: function (filter) {
  397. this._remove.unshift({
  398. filter: filter,
  399. replacement: function () {
  400. return ''
  401. }
  402. })
  403. },
  404.  
  405. forNode: function (node) {
  406. if (node.isBlank) return this.blankRule
  407. var rule
  408.  
  409. if ((rule = findRule(this.array, node, this.options))) return rule
  410. if ((rule = findRule(this._keep, node, this.options))) return rule
  411. if ((rule = findRule(this._remove, node, this.options))) return rule
  412.  
  413. return this.defaultRule
  414. },
  415.  
  416. forEach: function (fn) {
  417. for (var i = 0; i < this.array.length; i++) fn(this.array[i], i)
  418. }
  419. }
  420.  
  421. function findRule (rules, node, options) {
  422. for (var i = 0; i < rules.length; i++) {
  423. var rule = rules[i]
  424. if (filterValue(rule, node, options)) return rule
  425. }
  426. return void 0
  427. }
  428.  
  429. function filterValue (rule, node, options) {
  430. var filter = rule.filter
  431. if (typeof filter === 'string') {
  432. if (filter === node.nodeName.toLowerCase()) return true
  433. } else if (Array.isArray(filter)) {
  434. if (filter.indexOf(node.nodeName.toLowerCase()) > -1) return true
  435. } else if (typeof filter === 'function') {
  436. if (filter.call(rule, node, options)) return true
  437. } else {
  438. throw new TypeError('`filter` needs to be a string, array, or function')
  439. }
  440. }
  441.  
  442. /**
  443. * The collapseWhitespace function is adapted from collapse-whitespace
  444. * by Luc Thevenard.
  445. *
  446. * The MIT License (MIT)
  447. *
  448. * Copyright (c) 2014 Luc Thevenard <lucthevenard@gmail.com>
  449. *
  450. * Permission is hereby granted, free of charge, to any person obtaining a copy
  451. * of this software and associated documentation files (the "Software"), to deal
  452. * in the Software without restriction, including without limitation the rights
  453. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  454. * copies of the Software, and to permit persons to whom the Software is
  455. * furnished to do so, subject to the following conditions:
  456. *
  457. * The above copyright notice and this permission notice shall be included in
  458. * all copies or substantial portions of the Software.
  459. *
  460. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  461. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  462. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  463. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  464. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  465. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  466. * THE SOFTWARE.
  467. */
  468.  
  469. /**
  470. * collapseWhitespace(options) removes extraneous whitespace from an the given element.
  471. *
  472. * @param {Object} options
  473. */
  474. function collapseWhitespace (options) {
  475. var element = options.element
  476. var isBlock = options.isBlock
  477. var isVoid = options.isVoid
  478. var isPre = options.isPre || function (node) {
  479. return node.nodeName === 'PRE'
  480. }
  481.  
  482. if (!element.firstChild || isPre(element)) return
  483.  
  484. var prevText = null
  485. var prevVoid = false
  486.  
  487. var prev = null
  488. var node = next(prev, element, isPre)
  489.  
  490. while (node !== element) {
  491. if (node.nodeType === 3 || node.nodeType === 4) { // Node.TEXT_NODE or Node.CDATA_SECTION_NODE
  492. var text = node.data.replace(/[ \r\n\t]+/g, ' ')
  493.  
  494. if ((!prevText || / $/.test(prevText.data)) &&
  495. !prevVoid && text[0] === ' ') {
  496. text = text.substr(1)
  497. }
  498.  
  499. // `text` might be empty at this point.
  500. if (!text) {
  501. node = remove(node)
  502. continue
  503. }
  504.  
  505. node.data = text
  506.  
  507. prevText = node
  508. } else if (node.nodeType === 1) { // Node.ELEMENT_NODE
  509. if (isBlock(node) || node.nodeName === 'BR') {
  510. if (prevText) {
  511. prevText.data = prevText.data.replace(/ $/, '')
  512. }
  513.  
  514. prevText = null
  515. prevVoid = false
  516. } else if (isVoid(node)) {
  517. // Avoid trimming space around non-block, non-BR void elements.
  518. prevText = null
  519. prevVoid = true
  520. }
  521. } else {
  522. node = remove(node)
  523. continue
  524. }
  525.  
  526. var nextNode = next(prev, node, isPre)
  527. prev = node
  528. node = nextNode
  529. }
  530.  
  531. if (prevText) {
  532. prevText.data = prevText.data.replace(/ $/, '')
  533. if (!prevText.data) {
  534. remove(prevText)
  535. }
  536. }
  537. }
  538.  
  539. /**
  540. * remove(node) removes the given node from the DOM and returns the
  541. * next node in the sequence.
  542. *
  543. * @param {Node} node
  544. * @return {Node} node
  545. */
  546. function remove (node) {
  547. var next = node.nextSibling || node.parentNode
  548.  
  549. node.parentNode.removeChild(node)
  550.  
  551. return next
  552. }
  553.  
  554. /**
  555. * next(prev, current, isPre) returns the next node in the sequence, given the
  556. * current and previous nodes.
  557. *
  558. * @param {Node} prev
  559. * @param {Node} current
  560. * @param {Function} isPre
  561. * @return {Node}
  562. */
  563. function next (prev, current, isPre) {
  564. if ((prev && prev.parentNode === current) || isPre(current)) {
  565. return current.nextSibling || current.parentNode
  566. }
  567.  
  568. return current.firstChild || current.nextSibling || current.parentNode
  569. }
  570.  
  571. /*
  572. * Set up window for Node.js
  573. */
  574.  
  575. var root = (typeof window !== 'undefined' ? window : {})
  576.  
  577. /*
  578. * Parsing HTML strings
  579. */
  580.  
  581. function canParseHTMLNatively () {
  582. var Parser = root.DOMParser
  583. var canParse = false
  584.  
  585. // Adapted from https://gist.github.com/1129031
  586. // Firefox/Opera/IE throw errors on unsupported types
  587. try {
  588. // WebKit returns null on unsupported types
  589. if (new Parser().parseFromString('', 'text/html')) {
  590. canParse = true
  591. }
  592. } catch (e) {}
  593.  
  594. return canParse
  595. }
  596.  
  597. function createHTMLParser () {
  598. var Parser = function () {}
  599. if (shouldUseActiveX()) {
  600. Parser.prototype.parseFromString = function (string) {
  601. var doc = new window.ActiveXObject('htmlfile')
  602. doc.designMode = 'on' // disable on-page scripts
  603. doc.open()
  604. doc.write(string)
  605. doc.close()
  606. return doc
  607. }
  608. } else {
  609. Parser.prototype.parseFromString = function (string) {
  610. var doc = document.implementation.createHTMLDocument('')
  611. doc.open()
  612. doc.write(string)
  613. doc.close()
  614. return doc
  615. }
  616. }
  617. return Parser
  618. }
  619.  
  620. function shouldUseActiveX () {
  621. var useActiveX = false
  622. try {
  623. document.implementation.createHTMLDocument('').open()
  624. } catch (e) {
  625. if (window.ActiveXObject) useActiveX = true
  626. }
  627. return useActiveX
  628. }
  629.  
  630. var HTMLParser = canParseHTMLNatively() ? root.DOMParser : createHTMLParser()
  631.  
  632. function RootNode (input) {
  633. var root
  634. if (typeof input === 'string') {
  635. var doc = htmlParser().parseFromString(
  636. // DOM parsers arrange elements in the <head> and <body>.
  637. // Wrapping in a custom element ensures elements are reliably arranged in
  638. // a single element.
  639. '<x-turndown id="turndown-root">' + input + '</x-turndown>',
  640. 'text/html'
  641. )
  642. root = doc.getElementById('turndown-root')
  643. } else {
  644. root = input.cloneNode(true)
  645. }
  646. collapseWhitespace({
  647. element: root,
  648. isBlock: isBlock,
  649. isVoid: isVoid
  650. })
  651.  
  652. return root
  653. }
  654.  
  655. var _htmlParser
  656.  
  657. function htmlParser () {
  658. _htmlParser = _htmlParser || new HTMLParser()
  659. return _htmlParser
  660. }
  661.  
  662. function Node (node) {
  663. node.isBlock = isBlock(node)
  664. node.isCode = node.nodeName.toLowerCase() === 'code' || node.parentNode.isCode
  665. node.isBlank = isBlank(node)
  666. node.flankingWhitespace = flankingWhitespace(node)
  667. return node
  668. }
  669.  
  670. function isBlank (node) {
  671. return (
  672. ['A', 'TH', 'TD', 'IFRAME', 'SCRIPT', 'AUDIO', 'VIDEO'].indexOf(node.nodeName) === -1 &&
  673. /^\s*$/i.test(node.textContent) &&
  674. !isVoid(node) &&
  675. !hasVoid(node)
  676. )
  677. }
  678.  
  679. function flankingWhitespace (node) {
  680. var leading = ''
  681. var trailing = ''
  682.  
  683. if (!node.isBlock) {
  684. var hasLeading = /^[ \r\n\t]/.test(node.textContent)
  685. var hasTrailing = /[ \r\n\t]$/.test(node.textContent)
  686.  
  687. if (hasLeading && !isFlankedByWhitespace('left', node)) {
  688. leading = ' '
  689. }
  690. if (hasTrailing && !isFlankedByWhitespace('right', node)) {
  691. trailing = ' '
  692. }
  693. }
  694.  
  695. return {
  696. leading: leading,
  697. trailing: trailing
  698. }
  699. }
  700.  
  701. function isFlankedByWhitespace (side, node) {
  702. var sibling
  703. var regExp
  704. var isFlanked
  705.  
  706. if (side === 'left') {
  707. sibling = node.previousSibling
  708. regExp = / $/
  709. } else {
  710. sibling = node.nextSibling
  711. regExp = /^ /
  712. }
  713.  
  714. if (sibling) {
  715. if (sibling.nodeType === 3) {
  716. isFlanked = regExp.test(sibling.nodeValue)
  717. } else if (sibling.nodeType === 1 && !isBlock(sibling)) {
  718. isFlanked = regExp.test(sibling.textContent)
  719. }
  720. }
  721. return isFlanked
  722. }
  723.  
  724. var reduce = Array.prototype.reduce
  725. var leadingNewLinesRegExp = /^\n*/
  726. var trailingNewLinesRegExp = /\n*$/
  727. var escapes = [
  728. [/\\/g, '\\\\'],
  729. [/\*/g, '\\*'],
  730. [/^-/g, '\\-'],
  731. [/^\+ /g, '\\+ '],
  732. [/^(=+)/g, '\\$1'],
  733. [/^(#{1,6}) /g, '\\$1 '],
  734. [/`/g, '\\`'],
  735. [/^~~~/g, '\\~~~'],
  736. [/\[/g, '\\['],
  737. [/\]/g, '\\]'],
  738. [/^>/g, '\\>'],
  739. [/_/g, '\\_'],
  740. [/^(\d+)\. /g, '$1\\. ']
  741. ]
  742.  
  743. function TurndownService (options) {
  744. if (!(this instanceof TurndownService)) return new TurndownService(options)
  745.  
  746. var defaults = {
  747. rules: rules,
  748. headingStyle: 'setext',
  749. hr: '* * *',
  750. bulletListMarker: '*',
  751. codeBlockStyle: 'indented',
  752. fence: '```',
  753. emDelimiter: '_',
  754. strongDelimiter: '**',
  755. linkStyle: 'inlined',
  756. linkReferenceStyle: 'full',
  757. br: ' ',
  758. blankReplacement: function (content, node) {
  759. return node.isBlock ? '\n\n' : ''
  760. },
  761. keepReplacement: function (content, node) {
  762. return node.isBlock ? '\n\n' + node.outerHTML + '\n\n' : node.outerHTML
  763. },
  764. defaultReplacement: function (content, node) {
  765. return node.isBlock ? '\n\n' + content + '\n\n' : content
  766. }
  767. }
  768. this.options = extend({}, defaults, options)
  769. this.rules = new Rules(this.options)
  770. }
  771.  
  772. TurndownService.prototype = {
  773. /**
  774. * The entry point for converting a string or DOM node to Markdown
  775. * @public
  776. * @param {String|HTMLElement} input The string or DOM node to convert
  777. * @returns A Markdown representation of the input
  778. * @type String
  779. */
  780.  
  781. turndown: function (input) {
  782. if (!canConvert(input)) {
  783. throw new TypeError(
  784. input + ' is not a string, or an element/document/fragment node.'
  785. )
  786. }
  787.  
  788. if (input === '') return ''
  789.  
  790. var output = process.call(this, new RootNode(input))
  791. return postProcess.call(this, output)
  792. },
  793.  
  794. /**
  795. * Add one or more plugins
  796. * @public
  797. * @param {Function|Array} plugin The plugin or array of plugins to add
  798. * @returns The Turndown instance for chaining
  799. * @type Object
  800. */
  801.  
  802. use: function (plugin) {
  803. if (Array.isArray(plugin)) {
  804. for (var i = 0; i < plugin.length; i++) this.use(plugin[i])
  805. } else if (typeof plugin === 'function') {
  806. plugin(this)
  807. } else {
  808. throw new TypeError('plugin must be a Function or an Array of Functions')
  809. }
  810. return this
  811. },
  812.  
  813. /**
  814. * Adds a rule
  815. * @public
  816. * @param {String} key The unique key of the rule
  817. * @param {Object} rule The rule
  818. * @returns The Turndown instance for chaining
  819. * @type Object
  820. */
  821.  
  822. addRule: function (key, rule) {
  823. this.rules.add(key, rule)
  824. return this
  825. },
  826.  
  827. /**
  828. * Keep a node (as HTML) that matches the filter
  829. * @public
  830. * @param {String|Array|Function} filter The unique key of the rule
  831. * @returns The Turndown instance for chaining
  832. * @type Object
  833. */
  834.  
  835. keep: function (filter) {
  836. this.rules.keep(filter)
  837. return this
  838. },
  839.  
  840. /**
  841. * Remove a node that matches the filter
  842. * @public
  843. * @param {String|Array|Function} filter The unique key of the rule
  844. * @returns The Turndown instance for chaining
  845. * @type Object
  846. */
  847.  
  848. remove: function (filter) {
  849. this.rules.remove(filter)
  850. return this
  851. },
  852.  
  853. /**
  854. * Escapes Markdown syntax
  855. * @public
  856. * @param {String} string The string to escape
  857. * @returns A string with Markdown syntax escaped
  858. * @type String
  859. */
  860.  
  861. escape: function (string) {
  862. return escapes.reduce(function (accumulator, escape) {
  863. return accumulator.replace(escape[0], escape[1])
  864. }, string)
  865. }
  866. }
  867.  
  868. /**
  869. * Reduces a DOM node down to its Markdown string equivalent
  870. * @private
  871. * @param {HTMLElement} parentNode The node to convert
  872. * @returns A Markdown representation of the node
  873. * @type String
  874. */
  875.  
  876. function process (parentNode) {
  877. var self = this
  878. return reduce.call(parentNode.childNodes, function (output, node) {
  879. node = new Node(node)
  880.  
  881. var replacement = ''
  882. if (node.nodeType === 3) {
  883. replacement = node.isCode ? node.nodeValue : self.escape(node.nodeValue)
  884. } else if (node.nodeType === 1) {
  885. replacement = replacementForNode.call(self, node)
  886. }
  887.  
  888. return join(output, replacement)
  889. }, '')
  890. }
  891.  
  892. /**
  893. * Appends strings as each rule requires and trims the output
  894. * @private
  895. * @param {String} output The conversion output
  896. * @returns A trimmed version of the ouput
  897. * @type String
  898. */
  899.  
  900. function postProcess (output) {
  901. var self = this
  902. this.rules.forEach(function (rule) {
  903. if (typeof rule.append === 'function') {
  904. output = join(output, rule.append(self.options))
  905. }
  906. })
  907.  
  908. return output.replace(/^[\t\r\n]+/, '').replace(/[\t\r\n\s]+$/, '')
  909. }
  910.  
  911. /**
  912. * Converts an element node to its Markdown equivalent
  913. * @private
  914. * @param {HTMLElement} node The node to convert
  915. * @returns A Markdown representation of the node
  916. * @type String
  917. */
  918.  
  919. function replacementForNode (node) {
  920. var rule = this.rules.forNode(node)
  921. var content = process.call(this, node)
  922. var whitespace = node.flankingWhitespace
  923. if (whitespace.leading || whitespace.trailing) content = content.trim()
  924. return (
  925. whitespace.leading +
  926. rule.replacement(content, node, this.options) +
  927. whitespace.trailing
  928. )
  929. }
  930.  
  931. /**
  932. * Determines the new lines between the current output and the replacement
  933. * @private
  934. * @param {String} output The current conversion output
  935. * @param {String} replacement The string to append to the output
  936. * @returns The whitespace to separate the current output and the replacement
  937. * @type String
  938. */
  939.  
  940. function separatingNewlines (output, replacement) {
  941. var newlines = [
  942. output.match(trailingNewLinesRegExp)[0],
  943. replacement.match(leadingNewLinesRegExp)[0]
  944. ].sort()
  945. var maxNewlines = newlines[newlines.length - 1]
  946. return maxNewlines.length < 2 ? maxNewlines : '\n\n'
  947. }
  948.  
  949. function join (string1, string2) {
  950. var separator = separatingNewlines(string1, string2)
  951.  
  952. // Remove trailing/leading newlines and replace with separator
  953. string1 = string1.replace(trailingNewLinesRegExp, '')
  954. string2 = string2.replace(leadingNewLinesRegExp, '')
  955.  
  956. return string1 + separator + string2
  957. }
  958.  
  959. /**
  960. * Determines whether an input can be converted
  961. * @private
  962. * @param {String|HTMLElement} input Describe this parameter
  963. * @returns Describe what it returns
  964. * @type String|Object|Array|Boolean|Number
  965. */
  966.  
  967. function canConvert (input) {
  968. return (
  969. input != null && (
  970. typeof input === 'string' ||
  971. (input.nodeType && (
  972. input.nodeType === 1 || input.nodeType === 9 || input.nodeType === 11
  973. ))
  974. )
  975. )
  976. }
  977.  
  978. return TurndownService
  979.  
  980. }())
  981.  
  982. function html2md (content, fileName, parentNode, style) {
  983. const turndownService = new TurndownService()
  984. content = turndownService.turndown(content)
  985. const aLink = document.createElement('a')
  986. const blob = new Blob([content])
  987. const evt = document.createEvent('HTMLEvents')
  988. evt.initEvent('click', false, false)
  989. aLink.download = `${fileName}.md`
  990. aLink.href = URL.createObjectURL(blob)
  991. aLink.dispatchEvent(evt)
  992. aLink.style = style
  993. parentNode.insertBefore(aLink, parentNode.childNodes[0])
  994. }
  995.  
  996. })()

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址