mirror of
https://github.com/zadam/trilium.git
synced 2025-11-02 03:16:11 +01:00
chore(monorepo/server): integrate turndown-plugin-gfm
This commit is contained in:
5
packages/turndown-plugin-gfm/.gitignore
vendored
Normal file
5
packages/turndown-plugin-gfm/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
dist
|
||||
lib
|
||||
node_modules
|
||||
npm-debug.log
|
||||
test/*browser.js
|
||||
4
packages/turndown-plugin-gfm/.travis.yml
Normal file
4
packages/turndown-plugin-gfm/.travis.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "node"
|
||||
- "6"
|
||||
21
packages/turndown-plugin-gfm/LICENSE
Normal file
21
packages/turndown-plugin-gfm/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Dom Christie
|
||||
|
||||
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.
|
||||
64
packages/turndown-plugin-gfm/README.md
Normal file
64
packages/turndown-plugin-gfm/README.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# turndown-plugin-gfm
|
||||
|
||||
A [Turndown](https://github.com/domchristie/turndown) plugin which adds GitHub Flavored Markdown extensions.
|
||||
|
||||
This is a fork of the original [turndown-plugin-gfm](https://github.com/domchristie/turndown-plugin-gfm) for use with [Joplin](https://github.com/laurent22/joplin). The changes are:
|
||||
|
||||
- New: Always render tables even if they don't have a header.
|
||||
- New: Don't render the border of tables that contain other tables (frequent for websites that do the layout using tables). Only render the inner tables, if any, and if they also don't contain other tables.
|
||||
- New: Replace newlines (`\n`) with `<br>` inside table cells so that multi-line content is displayed correctly as Markdown.
|
||||
- New: Table cells are at least three characters long (padded with spaces) so that they render correctly in GFM-compliant renderers.
|
||||
- New: Handle colspan in TD tags
|
||||
- Fixed: Ensure there are no blank lines inside tables (due for example to an empty `<tr>` tag)
|
||||
- Fixed: Fixed importing tables that contain pipes.
|
||||
|
||||
## Installation
|
||||
|
||||
npm:
|
||||
|
||||
```
|
||||
npm install @joplin/turndown-plugin-gfm
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
// For Node.js
|
||||
var TurndownService = require('@joplin/turndown')
|
||||
var turndownPluginGfm = require('@joplin/turndown-plugin-gfm')
|
||||
|
||||
var gfm = turndownPluginGfm.gfm
|
||||
var turndownService = new TurndownService()
|
||||
turndownService.use(gfm)
|
||||
var markdown = turndownService.turndown('<strike>Hello world!</strike>')
|
||||
```
|
||||
|
||||
turndown-plugin-gfm is a suite of plugins which can be applied individually. The available plugins are as follows:
|
||||
|
||||
- `strikethrough` (for converting `<strike>`, `<s>`, and `<del>` elements)
|
||||
- `tables`
|
||||
- `taskListItems`
|
||||
- `gfm` (which applies all of the above)
|
||||
|
||||
So for example, if you only wish to convert tables:
|
||||
|
||||
```js
|
||||
var tables = require('@joplin/turndown-plugin-gfm').tables
|
||||
var turndownService = new TurndownService()
|
||||
turndownService.use(tables)
|
||||
```
|
||||
|
||||
### Typescript
|
||||
|
||||
To use this in a typescript project, add this to a `declarations.d.ts` file, as described in https://www.npmjs.com/package/@joplin/turndown, and then add:
|
||||
|
||||
```ts
|
||||
declare module "@joplin/turndown-plugin-gfm" {
|
||||
export const gfm: any;
|
||||
// Add other named exports if necessary
|
||||
}
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
turndown-plugin-gfm is copyright © 2017+ Dom Christie and released under the MIT license.
|
||||
8
packages/turndown-plugin-gfm/build_for_test.sh
Normal file
8
packages/turndown-plugin-gfm/build_for_test.sh
Normal file
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
ROOT_DIR="$SCRIPT_DIR/../.."
|
||||
|
||||
npm run build
|
||||
cd $ROOT_DIR/packages/app-cli && npm run test -- HtmlToMd
|
||||
@@ -0,0 +1,8 @@
|
||||
import config from './rollup.config.js';
|
||||
|
||||
export default config({
|
||||
output: {
|
||||
format: 'cjs',
|
||||
file: 'lib/turndown-plugin-gfm.browser.cjs.js',
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import config from './rollup.config.js';
|
||||
|
||||
export default config({
|
||||
output: {
|
||||
format: 'es',
|
||||
file: 'lib/turndown-plugin-gfm.browser.es.js',
|
||||
},
|
||||
});
|
||||
8
packages/turndown-plugin-gfm/config/rollup.config.cjs.js
Normal file
8
packages/turndown-plugin-gfm/config/rollup.config.cjs.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import config from './rollup.config.js';
|
||||
|
||||
export default config({
|
||||
output: {
|
||||
format: 'cjs',
|
||||
file: 'lib/turndown-plugin-gfm.cjs.js',
|
||||
},
|
||||
});
|
||||
8
packages/turndown-plugin-gfm/config/rollup.config.es.js
Normal file
8
packages/turndown-plugin-gfm/config/rollup.config.es.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import config from './rollup.config.js';
|
||||
|
||||
export default config({
|
||||
output: {
|
||||
format: 'es',
|
||||
file: 'lib/turndown-plugin-gfm.es.js',
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
import config from './rollup.config.js';
|
||||
|
||||
export default config({
|
||||
output: {
|
||||
format: 'iife',
|
||||
file: 'dist/turndown-plugin-gfm.js',
|
||||
name: 'turndownPluginGfm'
|
||||
},
|
||||
});
|
||||
6
packages/turndown-plugin-gfm/config/rollup.config.js
Normal file
6
packages/turndown-plugin-gfm/config/rollup.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export default function(config) {
|
||||
return {
|
||||
input: 'src/gfm.js',
|
||||
output: config.output,
|
||||
};
|
||||
}
|
||||
6568
packages/turndown-plugin-gfm/package-lock.json
generated
Normal file
6568
packages/turndown-plugin-gfm/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
46
packages/turndown-plugin-gfm/package.json
Normal file
46
packages/turndown-plugin-gfm/package.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "@triliumnext/turndown-plugin-gfm",
|
||||
"description": "Turndown plugin to add GitHub Flavored Markdown extensions.",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"version": "1.0.61",
|
||||
"author": "Dom Christie",
|
||||
"main": "lib/turndown-plugin-gfm.cjs.js",
|
||||
"devDependencies": {
|
||||
"browserify": "^17.0.1",
|
||||
"rollup": "^4.36.0",
|
||||
"standard": "^17.1.2",
|
||||
"turndown": "7.2.0",
|
||||
"turndown-attendant": "0.0.3"
|
||||
},
|
||||
"files": [
|
||||
"lib",
|
||||
"dist"
|
||||
],
|
||||
"keywords": [
|
||||
"turndown",
|
||||
"turndown-plugin",
|
||||
"html-to-markdown",
|
||||
"html",
|
||||
"markdown",
|
||||
"github-flavored-markdown",
|
||||
"gfm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laurent22/joplin-turndown-plugin-gfm.git"
|
||||
},
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build-all": "npm run build-cjs && npm run build-es && npm run build-iife",
|
||||
"build": "rollup -c config/rollup.config.cjs.js",
|
||||
"build-cjs": "rollup -c config/rollup.config.cjs.js && rollup -c config/rollup.config.browser.cjs.js",
|
||||
"build-es": "rollup -c config/rollup.config.es.js && rollup -c config/rollup.config.browser.es.js",
|
||||
"build-iife": "rollup -c config/rollup.config.iife.js",
|
||||
"build-test": "browserify test/turndown-plugin-gfm-test.js --outfile test/turndown-plugin-gfm-test.browser.js",
|
||||
"prepare": "npm run build"
|
||||
},
|
||||
"gitHead": "05a29b450962bf05a8642bbd39446a1f679a96ba"
|
||||
}
|
||||
3
packages/turndown-plugin-gfm/publish.sh
Normal file
3
packages/turndown-plugin-gfm/publish.sh
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
npm version patch
|
||||
npm publish
|
||||
15
packages/turndown-plugin-gfm/src/gfm.js
Normal file
15
packages/turndown-plugin-gfm/src/gfm.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import highlightedCodeBlock from './highlighted-code-block.js'
|
||||
import strikethrough from './strikethrough.js'
|
||||
import tables from './tables.js'
|
||||
import taskListItems from './task-list-items.js'
|
||||
|
||||
function gfm (turndownService) {
|
||||
turndownService.use([
|
||||
highlightedCodeBlock,
|
||||
strikethrough,
|
||||
tables,
|
||||
taskListItems
|
||||
])
|
||||
}
|
||||
|
||||
export { gfm, highlightedCodeBlock, strikethrough, tables, taskListItems }
|
||||
25
packages/turndown-plugin-gfm/src/highlighted-code-block.js
Normal file
25
packages/turndown-plugin-gfm/src/highlighted-code-block.js
Normal file
@@ -0,0 +1,25 @@
|
||||
var highlightRegExp = /highlight-(?:text|source)-([a-z0-9]+)/
|
||||
|
||||
export default function highlightedCodeBlock (turndownService) {
|
||||
turndownService.addRule('highlightedCodeBlock', {
|
||||
filter: function (node) {
|
||||
var firstChild = node.firstChild
|
||||
return (
|
||||
node.nodeName === 'DIV' &&
|
||||
highlightRegExp.test(node.className) &&
|
||||
firstChild &&
|
||||
firstChild.nodeName === 'PRE'
|
||||
)
|
||||
},
|
||||
replacement: function (content, node, options) {
|
||||
var className = node.className || ''
|
||||
var language = (className.match(highlightRegExp) || [null, ''])[1]
|
||||
|
||||
return (
|
||||
'\n\n' + options.fence + language + '\n' +
|
||||
node.firstChild.textContent +
|
||||
'\n' + options.fence + '\n\n'
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
8
packages/turndown-plugin-gfm/src/strikethrough.js
Normal file
8
packages/turndown-plugin-gfm/src/strikethrough.js
Normal file
@@ -0,0 +1,8 @@
|
||||
export default function strikethrough (turndownService) {
|
||||
turndownService.addRule('strikethrough', {
|
||||
filter: ['del', 's', 'strike'],
|
||||
replacement: function (content) {
|
||||
return '~~' + content + '~~'
|
||||
}
|
||||
})
|
||||
}
|
||||
286
packages/turndown-plugin-gfm/src/tables.js
Normal file
286
packages/turndown-plugin-gfm/src/tables.js
Normal file
@@ -0,0 +1,286 @@
|
||||
var indexOf = Array.prototype.indexOf
|
||||
var every = Array.prototype.every
|
||||
var rules = {}
|
||||
var alignMap = { left: ':---', right: '---:', center: ':---:' };
|
||||
|
||||
let isCodeBlock_ = null;
|
||||
let options_ = null;
|
||||
|
||||
// We need to cache the result of tableShouldBeSkipped() as it is expensive.
|
||||
// Caching it means we went from about 9000 ms for rendering down to 90 ms.
|
||||
// Fixes https://github.com/laurent22/joplin/issues/6736
|
||||
const tableShouldBeSkippedCache_ = new WeakMap();
|
||||
|
||||
function getAlignment(node) {
|
||||
return node ? (node.getAttribute('align') || node.style.textAlign || '').toLowerCase() : '';
|
||||
}
|
||||
|
||||
function getBorder(alignment) {
|
||||
return alignment ? alignMap[alignment] : '---';
|
||||
}
|
||||
|
||||
function getColumnAlignment(table, columnIndex) {
|
||||
var votes = {
|
||||
left: 0,
|
||||
right: 0,
|
||||
center: 0,
|
||||
'': 0,
|
||||
};
|
||||
|
||||
var align = '';
|
||||
|
||||
for (var i = 0; i < table.rows.length; ++i) {
|
||||
var row = table.rows[i];
|
||||
if (columnIndex < row.childNodes.length) {
|
||||
var cellAlignment = getAlignment(row.childNodes[columnIndex]);
|
||||
++votes[cellAlignment];
|
||||
|
||||
if (votes[cellAlignment] > votes[align]) {
|
||||
align = cellAlignment;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return align;
|
||||
}
|
||||
|
||||
rules.tableCell = {
|
||||
filter: ['th', 'td'],
|
||||
replacement: function (content, node) {
|
||||
if (tableShouldBeSkipped(nodeParentTable(node))) return content;
|
||||
return cell(content, node)
|
||||
}
|
||||
}
|
||||
|
||||
rules.tableRow = {
|
||||
filter: 'tr',
|
||||
replacement: function (content, node) {
|
||||
const parentTable = nodeParentTable(node);
|
||||
if (tableShouldBeSkipped(parentTable)) return content;
|
||||
|
||||
var borderCells = ''
|
||||
|
||||
if (isHeadingRow(node)) {
|
||||
const colCount = tableColCount(parentTable);
|
||||
for (var i = 0; i < colCount; i++) {
|
||||
const childNode = i < node.childNodes.length ? node.childNodes[i] : null;
|
||||
var border = getBorder(getColumnAlignment(parentTable, i));
|
||||
borderCells += cell(border, childNode, i);
|
||||
}
|
||||
}
|
||||
return '\n' + content + (borderCells ? '\n' + borderCells : '')
|
||||
}
|
||||
}
|
||||
|
||||
rules.table = {
|
||||
filter: function (node, options) {
|
||||
return node.nodeName === 'TABLE';
|
||||
},
|
||||
|
||||
replacement: function (content, node) {
|
||||
// Only convert tables that can result in valid Markdown
|
||||
// Other tables are kept as HTML using `keep` (see below).
|
||||
if (tableShouldBeHtml(node, options_)) {
|
||||
return node.outerHTML;
|
||||
} else {
|
||||
if (tableShouldBeSkipped(node)) return content;
|
||||
|
||||
// Ensure there are no blank lines
|
||||
content = content.replace(/\n+/g, '\n')
|
||||
|
||||
// If table has no heading, add an empty one so as to get a valid Markdown table
|
||||
var secondLine = content.trim().split('\n');
|
||||
if (secondLine.length >= 2) secondLine = secondLine[1]
|
||||
var secondLineIsDivider = /\| :?---/.test(secondLine);
|
||||
|
||||
var columnCount = tableColCount(node);
|
||||
var emptyHeader = ''
|
||||
if (columnCount && !secondLineIsDivider) {
|
||||
emptyHeader = '|' + ' |'.repeat(columnCount) + '\n' + '|'
|
||||
for (var columnIndex = 0; columnIndex < columnCount; ++columnIndex) {
|
||||
emptyHeader += ' ' + getBorder(getColumnAlignment(node, columnIndex)) + ' |';
|
||||
}
|
||||
}
|
||||
|
||||
const captionContent = node.caption ? node.caption.textContent || '' : '';
|
||||
const caption = captionContent ? `${captionContent}\n\n` : '';
|
||||
const tableContent = `${emptyHeader}${content}`.trimStart();
|
||||
return `\n\n${caption}${tableContent}\n\n`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rules.tableCaption = {
|
||||
filter: ['caption'],
|
||||
replacement: () => '',
|
||||
};
|
||||
|
||||
rules.tableColgroup = {
|
||||
filter: ['colgroup', 'col'],
|
||||
replacement: () => '',
|
||||
};
|
||||
|
||||
rules.tableSection = {
|
||||
filter: ['thead', 'tbody', 'tfoot'],
|
||||
replacement: function (content) {
|
||||
return content
|
||||
}
|
||||
}
|
||||
|
||||
// A tr is a heading row if:
|
||||
// - the parent is a THEAD
|
||||
// - or if its the first child of the TABLE or the first TBODY (possibly
|
||||
// following a blank THEAD)
|
||||
// - and every cell is a TH
|
||||
function isHeadingRow (tr) {
|
||||
var parentNode = tr.parentNode
|
||||
return (
|
||||
parentNode.nodeName === 'THEAD' ||
|
||||
(
|
||||
parentNode.firstChild === tr &&
|
||||
(parentNode.nodeName === 'TABLE' || isFirstTbody(parentNode)) &&
|
||||
every.call(tr.childNodes, function (n) { return n.nodeName === 'TH' })
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
function isFirstTbody (element) {
|
||||
var previousSibling = element.previousSibling
|
||||
return (
|
||||
element.nodeName === 'TBODY' && (
|
||||
!previousSibling ||
|
||||
(
|
||||
previousSibling.nodeName === 'THEAD' &&
|
||||
/^\s*$/i.test(previousSibling.textContent)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
function cell (content, node = null, index = null) {
|
||||
if (index === null) index = indexOf.call(node.parentNode.childNodes, node)
|
||||
var prefix = ' '
|
||||
if (index === 0) prefix = '| '
|
||||
let filteredContent = content.trim().replace(/\n\r/g, '<br>').replace(/\n/g, "<br>");
|
||||
filteredContent = filteredContent.replace(/\|+/g, '\\|')
|
||||
while (filteredContent.length < 3) filteredContent += ' ';
|
||||
if (node) filteredContent = handleColSpan(filteredContent, node, ' ');
|
||||
return prefix + filteredContent + ' |'
|
||||
}
|
||||
|
||||
function nodeContainsTable(node) {
|
||||
if (!node.childNodes) return false;
|
||||
|
||||
for (let i = 0; i < node.childNodes.length; i++) {
|
||||
const child = node.childNodes[i];
|
||||
if (child.nodeName === 'TABLE') return true;
|
||||
if (nodeContainsTable(child)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const nodeContains = (node, types) => {
|
||||
if (!node.childNodes) return false;
|
||||
|
||||
for (let i = 0; i < node.childNodes.length; i++) {
|
||||
const child = node.childNodes[i];
|
||||
if (types === 'code' && isCodeBlock_ && isCodeBlock_(child)) return true;
|
||||
if (types.includes(child.nodeName)) return true;
|
||||
if (nodeContains(child, types)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const tableShouldBeHtml = (tableNode, options) => {
|
||||
const possibleTags = [
|
||||
'UL',
|
||||
'OL',
|
||||
'H1',
|
||||
'H2',
|
||||
'H3',
|
||||
'H4',
|
||||
'H5',
|
||||
'H6',
|
||||
'HR',
|
||||
'BLOCKQUOTE',
|
||||
'PRE'
|
||||
];
|
||||
|
||||
// In general we should leave as HTML tables that include other tables. The
|
||||
// exception is with the Web Clipper when we import a web page with a layout
|
||||
// that's made of HTML tables. In that case we have this logic of removing the
|
||||
// outer table and keeping only the inner ones. For the Rich Text editor
|
||||
// however we always want to keep nested tables.
|
||||
if (options.preserveNestedTables) possibleTags.push('TABLE');
|
||||
|
||||
return nodeContains(tableNode, 'code') ||
|
||||
nodeContains(tableNode, possibleTags);
|
||||
}
|
||||
|
||||
// Various conditions under which a table should be skipped - i.e. each cell
|
||||
// will be rendered one after the other as if they were paragraphs.
|
||||
function tableShouldBeSkipped(tableNode) {
|
||||
const cached = tableShouldBeSkippedCache_.get(tableNode);
|
||||
if (cached !== undefined) return cached;
|
||||
|
||||
const result = tableShouldBeSkipped_(tableNode);
|
||||
|
||||
tableShouldBeSkippedCache_.set(tableNode, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
function tableShouldBeSkipped_(tableNode) {
|
||||
if (!tableNode) return true;
|
||||
if (!tableNode.rows) return true;
|
||||
if (tableNode.rows.length === 1 && tableNode.rows[0].childNodes.length <= 1) return true; // Table with only one cell
|
||||
if (nodeContainsTable(tableNode)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function nodeParentDiv(node) {
|
||||
let parent = node.parentNode;
|
||||
while (parent.nodeName !== 'DIV') {
|
||||
parent = parent.parentNode;
|
||||
if (!parent) return null;
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
|
||||
function nodeParentTable(node) {
|
||||
let parent = node.parentNode;
|
||||
while (parent.nodeName !== 'TABLE') {
|
||||
parent = parent.parentNode;
|
||||
if (!parent) return null;
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
|
||||
function handleColSpan(content, node, emptyChar) {
|
||||
const colspan = node.getAttribute('colspan') || 1;
|
||||
for (let i = 1; i < colspan; i++) {
|
||||
content += ' | ' + emptyChar.repeat(3);
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
function tableColCount(node) {
|
||||
let maxColCount = 0;
|
||||
for (let i = 0; i < node.rows.length; i++) {
|
||||
const row = node.rows[i]
|
||||
const colCount = row.childNodes.length
|
||||
if (colCount > maxColCount) maxColCount = colCount
|
||||
}
|
||||
return maxColCount
|
||||
}
|
||||
|
||||
export default function tables (turndownService) {
|
||||
isCodeBlock_ = turndownService.isCodeBlock;
|
||||
options_ = turndownService.options;
|
||||
|
||||
turndownService.keep(function (node) {
|
||||
if (node.nodeName === 'TABLE' && tableShouldBeHtml(node, turndownService.options)) return true;
|
||||
return false;
|
||||
});
|
||||
for (var key in rules) turndownService.addRule(key, rules[key])
|
||||
}
|
||||
10
packages/turndown-plugin-gfm/src/task-list-items.js
Normal file
10
packages/turndown-plugin-gfm/src/task-list-items.js
Normal file
@@ -0,0 +1,10 @@
|
||||
export default function taskListItems (turndownService) {
|
||||
turndownService.addRule('taskListItems', {
|
||||
filter: function (node) {
|
||||
return node.type === 'checkbox' && node.parentNode.nodeName === 'LI'
|
||||
},
|
||||
replacement: function (content, node) {
|
||||
return (node.checked ? '[x]' : '[ ]') + ' '
|
||||
}
|
||||
})
|
||||
}
|
||||
323
packages/turndown-plugin-gfm/test/index.html
Normal file
323
packages/turndown-plugin-gfm/test/index.html
Normal file
@@ -0,0 +1,323 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>turndown test runner</title>
|
||||
<link rel="stylesheet" href="../node_modules/turndown-attendant/dist/styles.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- TEST CASES -->
|
||||
|
||||
<div class="case" data-name="strike">
|
||||
<div class="input"><strike>Lorem ipsum</strike></div>
|
||||
<pre class="expected">~Lorem ipsum~</pre>
|
||||
</div>
|
||||
|
||||
<div class="case" data-name="s">
|
||||
<div class="input"><s>Lorem ipsum</s></div>
|
||||
<pre class="expected">~Lorem ipsum~</pre>
|
||||
</div>
|
||||
|
||||
<div class="case" data-name="del">
|
||||
<div class="input"><del>Lorem ipsum</del></div>
|
||||
<pre class="expected">~Lorem ipsum~</pre>
|
||||
</div>
|
||||
|
||||
<div class="case" data-name="unchecked inputs">
|
||||
<div class="input"><ul><li><input type=checkbox>Check Me!</li></ul></div>
|
||||
<pre class="expected">* [ ] Check Me!</pre>
|
||||
</div>
|
||||
|
||||
<div class="case" data-name="checked inputs">
|
||||
<div class="input"><ul><li><input type=checkbox checked>Checked!</li></ul></div>
|
||||
<pre class="expected">* [x] Checked!</pre>
|
||||
</div>
|
||||
|
||||
<div class="case" data-name="basic table">
|
||||
<div class="input">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Column 1</th>
|
||||
<th>Column 2</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Row 1, Column 1</td>
|
||||
<td>Row 1, Column 2</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Row 2, Column 1</td>
|
||||
<td>Row 2, Column 2</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<pre class="expected">| Column 1 | Column 2 |
|
||||
| --- | --- |
|
||||
| Row 1, Column 1 | Row 1, Column 2 |
|
||||
| Row 2, Column 1 | Row 2, Column 2 |</pre>
|
||||
</div>
|
||||
|
||||
<div class="case" data-name="cell alignment">
|
||||
<div class="input">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th align="left">Column 1</th>
|
||||
<th align="center">Column 2</th>
|
||||
<th align="right">Column 3</th>
|
||||
<th align="foo">Column 4</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Row 1, Column 1</td>
|
||||
<td>Row 1, Column 2</td>
|
||||
<td>Row 1, Column 3</td>
|
||||
<td>Row 1, Column 4</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Row 2, Column 1</td>
|
||||
<td>Row 2, Column 2</td>
|
||||
<td>Row 2, Column 3</td>
|
||||
<td>Row 2, Column 4</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<pre class="expected">| Column 1 | Column 2 | Column 3 | Column 4 |
|
||||
| :-- | :-: | --: | --- |
|
||||
| Row 1, Column 1 | Row 1, Column 2 | Row 1, Column 3 | Row 1, Column 4 |
|
||||
| Row 2, Column 1 | Row 2, Column 2 | Row 2, Column 3 | Row 2, Column 4 |</pre>
|
||||
</div>
|
||||
|
||||
<div class="case" data-name="empty cells">
|
||||
<div class="input">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th align="left">Column 1</th>
|
||||
<th align="center">Column 2</th>
|
||||
<th align="right">Column 3</th>
|
||||
<th align="foo">Column 4</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>Row 1, Column 2</td>
|
||||
<td>Row 1, Column 3</td>
|
||||
<td>Row 1, Column 4</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Row 2, Column 1</td>
|
||||
<td></td>
|
||||
<td>Row 2, Column 3</td>
|
||||
<td>Row 2, Column 4</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Row 3, Column 1</td>
|
||||
<td>Row 3, Column 2</td>
|
||||
<td></td>
|
||||
<td>Row 3, Column 4</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Row 4, Column 1</td>
|
||||
<td>Row 4, Column 2</td>
|
||||
<td>Row 4, Column 3</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>Row 5, Column 4</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<pre class="expected">| Column 1 | Column 2 | Column 3 | Column 4 |
|
||||
| :-- | :-: | --: | --- |
|
||||
| | Row 1, Column 2 | Row 1, Column 3 | Row 1, Column 4 |
|
||||
| Row 2, Column 1 | | Row 2, Column 3 | Row 2, Column 4 |
|
||||
| Row 3, Column 1 | Row 3, Column 2 | | Row 3, Column 4 |
|
||||
| Row 4, Column 1 | Row 4, Column 2 | Row 4, Column 3 | |
|
||||
| | | | Row 5, Column 4 |</pre>
|
||||
</div>
|
||||
|
||||
<div class="case" data-name="empty rows">
|
||||
<div class="input">
|
||||
<table>
|
||||
<thead>
|
||||
<td>Heading 1</td>
|
||||
<td>Heading 2</td>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Row 1</td>
|
||||
<td>Row 1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Row 3</td>
|
||||
<td>Row 3</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<pre class="expected">| Heading 1 | Heading 2 |
|
||||
| --- | --- |
|
||||
| Row 1 | Row 1 |
|
||||
| Row 3 | Row 3 |</pre>
|
||||
</div>
|
||||
|
||||
<div class="case" data-name="th in first row">
|
||||
<div class="input">
|
||||
<table>
|
||||
<tr>
|
||||
<th>Heading</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Content</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<pre class="expected">| Heading |
|
||||
| --- |
|
||||
| Content |</pre>
|
||||
</div>
|
||||
|
||||
<div class="case" data-name="th first row in tbody">
|
||||
<div class="input">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Heading</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Content</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<pre class="expected">| Heading |
|
||||
| --- |
|
||||
| Content |</pre>
|
||||
</div>
|
||||
|
||||
<div class="case" data-name="table with two tbodies">
|
||||
<div class="input">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Heading</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Content</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Heading</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Content</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<pre class="expected">| Heading |
|
||||
| --- |
|
||||
| Content |
|
||||
| Heading |
|
||||
| Content |</pre>
|
||||
</div>
|
||||
|
||||
<div class="case" data-name="heading cells in both thead and tbody">
|
||||
<div class="input">
|
||||
<table>
|
||||
<thead><tr><th>Heading</th></tr></thead>
|
||||
<tbody><tr><th>Cell</th></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<pre class="expected">| Heading |
|
||||
| --- |
|
||||
| Cell |</pre>
|
||||
</div>
|
||||
|
||||
<div class="case" data-name="empty head">
|
||||
<div class="input">
|
||||
<table>
|
||||
<thead><tr><th></th></tr></thead>
|
||||
<tbody><tr><th>Heading</th></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<pre class="expected">| Heading |
|
||||
| --- |</pre>
|
||||
</div>
|
||||
|
||||
<div class="case" data-name="non-definitive heading row (converted but with empty header)">
|
||||
<div class="input">
|
||||
<table>
|
||||
<tr><td>Row 1 Cell 1</td><td>Row 1 Cell 2</td></tr>
|
||||
<tr><td>Row 2 Cell 1</td><td>Row 2 Cell 2</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
<pre class="expected">| | |
|
||||
| --- | --- |
|
||||
| Row 1 Cell 1 | Row 1 Cell 2 |
|
||||
| Row 2 Cell 1 | Row 2 Cell 2 |</pre>
|
||||
</div>
|
||||
|
||||
<div class="case" data-name="non-definitive heading row with th (converted but with empty header)">
|
||||
<div class="input">
|
||||
<table>
|
||||
<tr>
|
||||
<th>Heading</th>
|
||||
<td>Not a heading</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Heading</td>
|
||||
<td>Not a heading</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<pre class="expected">| | |
|
||||
| --- | --- |
|
||||
| Heading | Not a heading |
|
||||
| Heading | Not a heading |</pre>
|
||||
</div>
|
||||
|
||||
<div class="case" data-name="highlighted code block with html">
|
||||
<div class="input">
|
||||
<div class="highlight highlight-text-html-basic">
|
||||
<pre><<span class="pl-ent">p</span>>Hello world</<span class="pl-ent">p</span>></pre>
|
||||
</div>
|
||||
</div>
|
||||
<pre class="expected">```html
|
||||
<p>Hello world</p>
|
||||
```</pre>
|
||||
</div>
|
||||
|
||||
<div class="case" data-name="highlighted code block with js">
|
||||
<div class="input">
|
||||
<div class="highlight highlight-source-js">
|
||||
<pre>;(<span class="pl-k">function</span> () {})()</pre>
|
||||
</div>
|
||||
</div>
|
||||
<pre class="expected">```js
|
||||
;(function () {})()
|
||||
```</pre>
|
||||
</div>
|
||||
|
||||
<!-- /TEST CASES -->
|
||||
|
||||
<script src="turndown-plugin-gfm-test.browser.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,13 @@
|
||||
const Attendant = require('turndown-attendant');
|
||||
const TurndownService = require('turndown');
|
||||
const gfm = require('../lib/turndown-plugin-gfm.cjs').gfm;
|
||||
|
||||
const attendant = new Attendant({
|
||||
file: `${__dirname}/index.html`,
|
||||
TurndownService: TurndownService,
|
||||
beforeEach: function(turndownService) {
|
||||
turndownService.use(gfm);
|
||||
},
|
||||
});
|
||||
|
||||
attendant.run();
|
||||
Reference in New Issue
Block a user