From 145f4ffadc028d85f03328a51362a160c3c12a78 Mon Sep 17 00:00:00 2001 From: Zachary Vance Date: Mon, 29 Jul 2024 19:17:17 -0400 Subject: [PATCH 1/2] Add support for 'preserve' rules. 'preserve' is a little like 'keep', but it converts descendents to markdown. An element which looks like this:
some bolded text
And with this rule added: turndownService.preserve('div') Will convert into this markdown:
**some bolded text**
Note that the results are not standard markdown. They require the use of the extended `markdown="1"` syntax. --- src/rules.js | 10 ++++++++++ src/turndown.js | 24 +++++++++++++++++++++++- src/utilities.js | 17 +++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/rules.js b/src/rules.js index 5414fcd6..3c6b28ba 100644 --- a/src/rules.js +++ b/src/rules.js @@ -6,12 +6,14 @@ export default function Rules (options) { this.options = options this._keep = [] this._remove = [] + this._preserve = [] this.blankRule = { replacement: options.blankReplacement } this.keepReplacement = options.keepReplacement + this.preserveReplacement = options.preserveReplacement this.defaultRule = { replacement: options.defaultReplacement @@ -42,12 +44,20 @@ Rules.prototype = { }) }, + preserve: function (filter) { + this._preserve.unshift({ + filter: filter, + replacement: this.preserveReplacement + }) + }, + 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._preserve, node, this.options))) return rule if ((rule = findRule(this._remove, node, this.options))) return rule return this.defaultRule diff --git a/src/turndown.js b/src/turndown.js index ff4a2cb3..3fd7a7e9 100644 --- a/src/turndown.js +++ b/src/turndown.js @@ -1,6 +1,6 @@ import COMMONMARK_RULES from './commonmark-rules' import Rules from './rules' -import { extend, trimLeadingNewlines, trimTrailingNewlines } from './utilities' +import { extend, trimLeadingNewlines, trimTrailingNewlines, getOpenTag, getCloseTag } from './utilities' import RootNode from './root-node' import Node from './node' var reduce = Array.prototype.reduce @@ -44,6 +44,15 @@ export default function TurndownService (options) { }, defaultReplacement: function (content, node) { return node.isBlock ? '\n\n' + content + '\n\n' : content + }, + preserveReplacement: function (innerContent, node) { + const html = node.cloneNode() + html.setAttribute("markdown", "1") + const openTag = getOpenTag(html) + const closeTag = getCloseTag(html) + + const outerContent = openTag + innerContent + closeTag + return node.isBlock ? '\n\n' + outerContent + '\n\n' : outerContent } } this.options = extend({}, defaults, options) @@ -131,6 +140,19 @@ TurndownService.prototype = { return this }, + /** + * Preserves (using markdown="1") 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 + */ + + preserve: function (filter) { + this.rules.preserve(filter) + return this + }, + /** * Escapes Markdown syntax * @public diff --git a/src/utilities.js b/src/utilities.js index 36f0acce..a7bcedcf 100644 --- a/src/utilities.js +++ b/src/utilities.js @@ -74,3 +74,20 @@ function has (node, tagNames) { }) ) } + +export function getOpenTag(element) { + // Credit: https://stackoverflow.com/questions/9604235 + const outerHtml = element.outerHTML; + const len = outerHtml.length; + + if (outerHtml[len - 2] === '/') { // Is self-closing tag? + return outerHtml.slice(0, len-2) + ">" + } else { + const openTagLength = len - element.innerHTML.length - (element.tagName.length + 3) + return outerHtml.slice(0, openTagLength) + } +} +export function getCloseTag(element) { + return `` +} + From c9a00c4e480ae1b5db0051a7f339db8e03f68e30 Mon Sep 17 00:00:00 2001 From: Zachary Vance Date: Mon, 29 Jul 2024 21:41:37 -0400 Subject: [PATCH 2/2] Code style --- src/turndown.js | 2 +- src/utilities.js | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/turndown.js b/src/turndown.js index 3fd7a7e9..b8773b19 100644 --- a/src/turndown.js +++ b/src/turndown.js @@ -47,7 +47,7 @@ export default function TurndownService (options) { }, preserveReplacement: function (innerContent, node) { const html = node.cloneNode() - html.setAttribute("markdown", "1") + html.setAttribute('markdown', '1') const openTag = getOpenTag(html) const closeTag = getCloseTag(html) diff --git a/src/utilities.js b/src/utilities.js index a7bcedcf..61afe42f 100644 --- a/src/utilities.js +++ b/src/utilities.js @@ -75,19 +75,19 @@ function has (node, tagNames) { ) } -export function getOpenTag(element) { - // Credit: https://stackoverflow.com/questions/9604235 - const outerHtml = element.outerHTML; - const len = outerHtml.length; - - if (outerHtml[len - 2] === '/') { // Is self-closing tag? - return outerHtml.slice(0, len-2) + ">" - } else { - const openTagLength = len - element.innerHTML.length - (element.tagName.length + 3) - return outerHtml.slice(0, openTagLength) - } -} -export function getCloseTag(element) { - return `` +export function getOpenTag (element) { + // Credit: https://stackoverflow.com/questions/9604235 + const outerHtml = element.outerHTML + const len = outerHtml.length + + if (outerHtml[len - 2] === '/') { // Is self-closing tag? + return outerHtml.slice(0, len - 2) + '>' + } else { + const openTagLength = len - element.innerHTML.length - (element.tagName.length + 3) + return outerHtml.slice(0, openTagLength) + } } +export function getCloseTag (element) { + return `` +}