markdown-magic 3.0.3 → 3.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +295 -101
- package/cli.js +4 -1
- package/lib/block-parser.js +32 -28
- package/lib/block-parser.test.js +2 -0
- package/lib/cli.js +101 -19
- package/lib/cli.test.js +12 -12
- package/lib/index.js +418 -119
- package/lib/process-contents.js +61 -23
- package/lib/process-file.js +40 -4
- package/lib/transforms/code.js +33 -10
- package/lib/transforms/file.js +4 -2
- package/lib/transforms/index.js +114 -0
- package/lib/transforms/sectionToc.js +2 -2
- package/lib/transforms/toc.js +22 -4
- package/lib/transforms/wordCount.js +2 -2
- package/lib/utils/fs.js +8 -172
- package/lib/utils/fs.test.js +4 -162
- package/lib/utils/hash-file.js +28 -0
- package/lib/utils/logs.js +16 -2
- package/lib/utils/syntax.js +1 -0
- package/lib/utils/text.js +1 -1
- package/lib/utils/toposort.js +131 -0
- package/package.json +4 -3
- package/lib/utils/md/filters.js +0 -20
- package/lib/utils/md/find-code-blocks.js +0 -88
- package/lib/utils/md/find-date.js +0 -32
- package/lib/utils/md/find-frontmatter.js +0 -92
- package/lib/utils/md/find-frontmatter.test.js +0 -17
- package/lib/utils/md/find-html-tags.js +0 -105
- package/lib/utils/md/find-images-md.js +0 -27
- package/lib/utils/md/find-images.js +0 -107
- package/lib/utils/md/find-links.js +0 -220
- package/lib/utils/md/find-unmatched-html-tags.js +0 -32
- package/lib/utils/md/fixtures/2022-01-22-date-in-filename.md +0 -14
- package/lib/utils/md/fixtures/file-with-frontmatter.md +0 -32
- package/lib/utils/md/fixtures/file-with-links.md +0 -153
- package/lib/utils/md/md.test.js +0 -105
- package/lib/utils/md/parse.js +0 -146
- package/lib/utils/md/utils.js +0 -19
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
const { getLineCount, getLineNumberFromMatch } = require('./utils')
|
|
2
|
-
|
|
3
|
-
// https://regex101.com/r/nIlW1U/6
|
|
4
|
-
const CODE_BLOCK_REGEX = /^([A-Za-z \t]*)```([A-Za-z]*)?\n([\s\S]*?)```([A-Za-z \t]*)*$/gm
|
|
5
|
-
// https://regex101.com/r/oPKKoC/1
|
|
6
|
-
const REMOVE_CODE_BLOCK_REGEX = /^(?:[A-Za-z \t]*)?(```(?:[A-Za-z]*)?\n(?:[\s\S]*?)```)([A-Za-z \t]*)*$/gm
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Parse code blocks out of markdown
|
|
10
|
-
* @param {string} block
|
|
11
|
-
* @param {Object} opts
|
|
12
|
-
* @returns {Object}
|
|
13
|
-
* @example
|
|
14
|
-
* const { blocks, errors } = findCodeBlocks(content)
|
|
15
|
-
* console.log('blocks', blocks)
|
|
16
|
-
* console.log('errors', errors)
|
|
17
|
-
*/
|
|
18
|
-
function findCodeBlocks(block, opts = {}) {
|
|
19
|
-
const { filePath = '', includePositions } = opts
|
|
20
|
-
let matches
|
|
21
|
-
let errors = []
|
|
22
|
-
let blocks = []
|
|
23
|
-
const msg = (filePath) ? ` in ${filePath}` : ''
|
|
24
|
-
while ((matches = CODE_BLOCK_REGEX.exec(block)) !== null) {
|
|
25
|
-
if (matches.index === CODE_BLOCK_REGEX.lastIndex) {
|
|
26
|
-
CODE_BLOCK_REGEX.lastIndex++ // avoid infinite loops with zero-width matches
|
|
27
|
-
}
|
|
28
|
-
const [ match, prefix, syntax, content, postFix ] = matches
|
|
29
|
-
const lineNumber = getLineNumberFromMatch(block, matches)
|
|
30
|
-
let hasError = false
|
|
31
|
-
/* // debug
|
|
32
|
-
console.log(`prefix: "${prefix}"`)
|
|
33
|
-
console.log(`postFix: "${postFix}"`)
|
|
34
|
-
console.log('syntax:', lang)
|
|
35
|
-
console.log('Content:')
|
|
36
|
-
console.log(content.trim())
|
|
37
|
-
console.log('───────────────────────')
|
|
38
|
-
/** */
|
|
39
|
-
const codeBlock = {}
|
|
40
|
-
if (includePositions) {
|
|
41
|
-
codeBlock.line = lineNumber
|
|
42
|
-
codeBlock.index = matches.index
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (syntax) {
|
|
46
|
-
codeBlock.syntax = syntax
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
codeBlock.block = match
|
|
50
|
-
|
|
51
|
-
/* Validate code blocks */
|
|
52
|
-
if (prefix && prefix.match(/\S/)) {
|
|
53
|
-
hasError = true
|
|
54
|
-
errors.push({
|
|
55
|
-
line: lineNumber,
|
|
56
|
-
index: matches.index,
|
|
57
|
-
message: `Prefix "${prefix}" not allowed on line ${lineNumber}. Fix the code block${msg}.`,
|
|
58
|
-
block: match
|
|
59
|
-
})
|
|
60
|
-
}
|
|
61
|
-
if (postFix && postFix.match(/\S/)) {
|
|
62
|
-
hasError = true
|
|
63
|
-
const line = lineNumber + (getLineCount(match) - 1)
|
|
64
|
-
errors.push({
|
|
65
|
-
line,
|
|
66
|
-
index: matches.index + match.length,
|
|
67
|
-
message: `Postfix "${postFix}" not allowed on line ${line}. Fix the code block${msg}.`,
|
|
68
|
-
block: match
|
|
69
|
-
})
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (!hasError) {
|
|
73
|
-
codeBlock.code = content.trim()
|
|
74
|
-
blocks.push(codeBlock)
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return {
|
|
79
|
-
errors,
|
|
80
|
-
blocks
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
module.exports = {
|
|
85
|
-
findCodeBlocks,
|
|
86
|
-
CODE_BLOCK_REGEX,
|
|
87
|
-
REMOVE_CODE_BLOCK_REGEX
|
|
88
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
const DATE_FORMAT_REGEX = /(([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1]))-?/g
|
|
2
|
-
|
|
3
|
-
function findDate({
|
|
4
|
-
frontmatter = {},
|
|
5
|
-
dateKey = 'date',
|
|
6
|
-
filePath
|
|
7
|
-
}) {
|
|
8
|
-
let date = frontmatter[dateKey]
|
|
9
|
-
if (!date && filePath) {
|
|
10
|
-
const dateFromFile = filePath.match(DATE_FORMAT_REGEX)
|
|
11
|
-
if (dateFromFile) {
|
|
12
|
-
date = dateFromFile[0].replace(/-$/, '')
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
return convertDateToString(date)
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function convertDateToString(dateValue) {
|
|
19
|
-
let date = dateValue
|
|
20
|
-
if (typeof dateValue === 'string') {
|
|
21
|
-
date = dateValue
|
|
22
|
-
} else if (dateValue instanceof Date) {
|
|
23
|
-
var newDate = new Date(dateValue.toString())
|
|
24
|
-
date = newDate.toISOString().substring(0, 10)
|
|
25
|
-
}
|
|
26
|
-
return date
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
module.exports = {
|
|
30
|
-
findDate,
|
|
31
|
-
convertDateToString
|
|
32
|
-
}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
const matter = require('gray-matter')
|
|
2
|
-
/* Match <!-- frontmatter --> https://regex101.com/r/Q9bBxC/1 */
|
|
3
|
-
const HIDDEN_FRONTMATTER_REGEX = /^<!--+(?:\r\n|\r|\n)([\w\W]*?)--+>/g
|
|
4
|
-
// const HIDDEN_FRONTMATTER_REGEX = /^<!--.*((.|\r?\n)*?.*-->)/g
|
|
5
|
-
|
|
6
|
-
/* Match --- frontmatter --- https://regex101.com/r/d7eAw4/1 */
|
|
7
|
-
const FRONTMATTER_REGEX = /(^--+(?:\r\n|\r|\n)([\w\W]*?)--+)/
|
|
8
|
-
// const FRONTMATTER_REGEX = /^---.*((.|\r?\n)*?.*---)/gm
|
|
9
|
-
|
|
10
|
-
function removeConflictingContent(str) {
|
|
11
|
-
return str
|
|
12
|
-
.replace(/[\t ]{1}---/g, '__LINE_BREAK__')
|
|
13
|
-
.replace(/[\t ]--+>/g, '__CLOSE_COMMENT__')
|
|
14
|
-
// TODO also handle nested <!-- comments -->
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function replaceConflictingContent(str) {
|
|
18
|
-
return str
|
|
19
|
-
.replace(/__LINE_BREAK__/g, ' ---')
|
|
20
|
-
.replace(/__CLOSE_COMMENT__/g, ' -->')
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function findFrontmatter(content = '') {
|
|
24
|
-
const text = removeConflictingContent(content.trim())
|
|
25
|
-
const hasFrontMatter = text.match(FRONTMATTER_REGEX)
|
|
26
|
-
const hasHiddenFrontMatter = text.match(HIDDEN_FRONTMATTER_REGEX)
|
|
27
|
-
let raw = ''
|
|
28
|
-
let match = ''
|
|
29
|
-
let isHidden = false
|
|
30
|
-
// console.log('hasFrontMatter', hasFrontMatter)
|
|
31
|
-
// console.log('hasHiddenFrontMatter', hasHiddenFrontMatter)
|
|
32
|
-
if (hasFrontMatter) {
|
|
33
|
-
raw = hasFrontMatter[1]
|
|
34
|
-
match = raw.trim()
|
|
35
|
-
// Fix Leading frontmatter brackets
|
|
36
|
-
.replace(/^---+/, '---')
|
|
37
|
-
// Trailing frontmatter brackets
|
|
38
|
-
.replace(/--+$/, `---`)
|
|
39
|
-
} else if (hasHiddenFrontMatter) {
|
|
40
|
-
isHidden = true
|
|
41
|
-
raw = hasHiddenFrontMatter[1]
|
|
42
|
-
match = raw.trim()
|
|
43
|
-
// Leading frontmatter brackets
|
|
44
|
-
.replace(/<!--+/, '---')
|
|
45
|
-
// Trailing frontmatter brackets
|
|
46
|
-
.replace(/--+>/, `---`)
|
|
47
|
-
}
|
|
48
|
-
return {
|
|
49
|
-
frontMatterRaw: replaceConflictingContent(raw),
|
|
50
|
-
frontMatter: replaceConflictingContent(match),
|
|
51
|
-
isHidden
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function parseFrontmatter(text) {
|
|
56
|
-
const { frontMatter, frontMatterRaw } = findFrontmatter(text)
|
|
57
|
-
// console.log('frontMatter', frontMatter)
|
|
58
|
-
let frontmatter = { data: {}, content: '' }
|
|
59
|
-
/* Missing all frontmatter */
|
|
60
|
-
if (!frontMatter) {
|
|
61
|
-
// throw new Error(`Missing or broken frontmatter in ${filePath}. Double check file for --- frontmatter tags in files`)
|
|
62
|
-
return frontmatter
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
let mdContent = text
|
|
66
|
-
if (frontMatterRaw) {
|
|
67
|
-
mdContent = text
|
|
68
|
-
// Replace frontmatter brackets
|
|
69
|
-
.replace(frontMatterRaw, frontMatter)
|
|
70
|
-
// Replace leading lines
|
|
71
|
-
// .replace(/---+\s+\n/g, '---\n')
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
frontmatter = matter(mdContent)
|
|
76
|
-
} catch (err) {
|
|
77
|
-
/* Add line numbers to output */
|
|
78
|
-
const formattedError = frontMatterRaw.split('\n').map((line, i) => {
|
|
79
|
-
return `${i + 1}. ${line}`
|
|
80
|
-
})
|
|
81
|
-
throw new Error(`Frontmatter error:\n${err.message}\n${formattedError.join('\n')}`)
|
|
82
|
-
}
|
|
83
|
-
// console.log('frontMatter', frontmatter)
|
|
84
|
-
return Object.assign(frontmatter, { frontMatterRaw })
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
module.exports = {
|
|
88
|
-
parseFrontmatter,
|
|
89
|
-
findFrontmatter,
|
|
90
|
-
HIDDEN_FRONTMATTER_REGEX,
|
|
91
|
-
FRONTMATTER_REGEX
|
|
92
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
const path = require('path')
|
|
2
|
-
const fs = require('fs')
|
|
3
|
-
const { test } = require('uvu')
|
|
4
|
-
const assert = require('uvu/assert')
|
|
5
|
-
const { findLinks } = require('./find-links')
|
|
6
|
-
const { parseFrontmatter } = require('./find-frontmatter')
|
|
7
|
-
|
|
8
|
-
const FILE_PATH = path.join(__dirname, 'fixtures/file-with-frontmatter.md')
|
|
9
|
-
const fileWithLinks = fs.readFileSync(FILE_PATH, 'utf-8')
|
|
10
|
-
|
|
11
|
-
test('Find frontmatter', async () => {
|
|
12
|
-
const frontmatter = parseFrontmatter(fileWithLinks)
|
|
13
|
-
console.log('frontmatter', frontmatter)
|
|
14
|
-
assert.is(typeof frontmatter.data, 'object')
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
test.run()
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
// Might need ([\s\S]*?) instead of '*' in between tags
|
|
3
|
-
const HTML_TAG = /<([a-zA-Z1-6]+)([^<]+)*(?:>(.*)<\/\1>|\s+\/>)/gim
|
|
4
|
-
|
|
5
|
-
const MATCH_HTML_TAGS_REGEX = /<([a-zA-Z1-6]+)\b([^>]*)>*(?:>([\s\S]*?)<\/\1>|\s?\/?>)/gm
|
|
6
|
-
// old forces closes / - /<([a-zA-Z1-6]+)\b([^>]*)>*(?:>([\s\S]*?)<\/\1>|\s?\/>)/gm
|
|
7
|
-
|
|
8
|
-
function findHtmlTags(mdContents) {
|
|
9
|
-
const parents = mdContents
|
|
10
|
-
/* Fix non terminating <tags> */
|
|
11
|
-
.replace(/(['"`]<(.*)>['"`])/gm, '_$2_')
|
|
12
|
-
.match(MATCH_HTML_TAGS_REGEX)
|
|
13
|
-
// console.log('parents', parents)
|
|
14
|
-
|
|
15
|
-
if (parents) {
|
|
16
|
-
// const children = parents.filter(Boolean).map((p) => {
|
|
17
|
-
// return p.match(HTML_TAG)
|
|
18
|
-
// })
|
|
19
|
-
// console.log('children', children)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const htmlTags = mdContents
|
|
23
|
-
/* Fix non terminating <tags> */
|
|
24
|
-
.replace(/(['"`]<(.*)>['"`])/gm, '_$2_')
|
|
25
|
-
.match(MATCH_HTML_TAGS_REGEX)
|
|
26
|
-
// console.log('htmlTags', htmlTags)
|
|
27
|
-
|
|
28
|
-
let tags = []
|
|
29
|
-
if (htmlTags) {
|
|
30
|
-
let propsValues = {}
|
|
31
|
-
// var regexSingleTag = /<([a-zA-Z1-6]+)([^<]+)*(?:>(.*)<\/\1>|\s+\/>)/
|
|
32
|
-
// var regexSingleTag = /<([a-zA-Z1-6]+)([^<]+)*(?:>([\s\S]*?)<\/\1>|\s*\/>)/
|
|
33
|
-
var regexSingleTag = /<([a-zA-Z1-6]+)\b([^>]*)>*(?:>([\s\S]*?)<\/\1>|\s?\/?>)/
|
|
34
|
-
for (var i = 0; i < htmlTags.length; i++) {
|
|
35
|
-
// console.log('htmlTags[i]', htmlTags[i])
|
|
36
|
-
var tagMatches = regexSingleTag.exec(htmlTags[i]) || []
|
|
37
|
-
// console.log('tagMatches', tagMatches)
|
|
38
|
-
var [ match, tag, props ] = tagMatches
|
|
39
|
-
// console.log(`Tag #${i} ${tag}`)
|
|
40
|
-
if (props) {
|
|
41
|
-
const cleanProps = props
|
|
42
|
-
// Remove new lines and tabs
|
|
43
|
-
.replace(/\n\t/g, '')
|
|
44
|
-
// Remove extra spaces
|
|
45
|
-
.replace(/\s\s+/g, ' ')
|
|
46
|
-
.trim()
|
|
47
|
-
|
|
48
|
-
propsValues = cleanProps.split(" ").reduce((acc, curr) => {
|
|
49
|
-
const hasQuotes = curr.match(/=['"]/)
|
|
50
|
-
// Check key="value" | key='value' | key={value}
|
|
51
|
-
const propWithValue = /([A-Za-z-_$]+)=['{"](.*)['}"]/g.exec(curr)
|
|
52
|
-
if (propWithValue && propWithValue[1]) {
|
|
53
|
-
return {
|
|
54
|
-
...acc,
|
|
55
|
-
[`${propWithValue[1]}`]: (hasQuotes) ? propWithValue[2] : convert(propWithValue[2])
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
// Check isLoading boolean props
|
|
59
|
-
const booleanProp = curr.match(/([A-Za-z]*)/)
|
|
60
|
-
if (booleanProp && booleanProp[1]) {
|
|
61
|
-
return {
|
|
62
|
-
...acc,
|
|
63
|
-
[`${booleanProp[1]}`]: true
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return acc
|
|
67
|
-
}, {})
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
tags.push({
|
|
71
|
-
tag: tag,
|
|
72
|
-
props: propsValues,
|
|
73
|
-
raw: match
|
|
74
|
-
})
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return tags
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function convert(value) {
|
|
81
|
-
if (value === 'false') {
|
|
82
|
-
return false
|
|
83
|
-
}
|
|
84
|
-
if (value === 'true') {
|
|
85
|
-
return true
|
|
86
|
-
}
|
|
87
|
-
const isNumber = Number(value)
|
|
88
|
-
if (typeof isNumber === 'number' && !isNaN(isNumber)) {
|
|
89
|
-
return isNumber
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
try {
|
|
93
|
-
const val = JSON.parse(value)
|
|
94
|
-
return val
|
|
95
|
-
} catch (err) {
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return value
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
module.exports = {
|
|
103
|
-
findHtmlTags,
|
|
104
|
-
MATCH_HTML_TAGS_REGEX
|
|
105
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
const { onlyUnique } = require('./filters')
|
|
2
|
-
|
|
3
|
-
// https://regex101.com/r/u2DwY2/2/
|
|
4
|
-
const MARKDOWN_IMAGE_REGEX = /!\[[^\]]*\]\((.*?)\s*("(?:.*[^"])")?\s*\)/g
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Get markdown style images from text
|
|
8
|
-
* @param {string} text
|
|
9
|
-
* @returns {string[]}
|
|
10
|
-
*/
|
|
11
|
-
function findMarkdownImages(text) {
|
|
12
|
-
let matches
|
|
13
|
-
let imageLinks = []
|
|
14
|
-
while ((matches = MARKDOWN_IMAGE_REGEX.exec(text)) !== null) {
|
|
15
|
-
if (matches.index === MARKDOWN_IMAGE_REGEX.lastIndex) {
|
|
16
|
-
MARKDOWN_IMAGE_REGEX.lastIndex++ // avoid infinite loops with zero-width matches
|
|
17
|
-
}
|
|
18
|
-
const [ match, image, altText ] = matches
|
|
19
|
-
imageLinks.push(image)
|
|
20
|
-
}
|
|
21
|
-
return imageLinks.filter(onlyUnique)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
module.exports = {
|
|
25
|
-
findMarkdownImages,
|
|
26
|
-
MARKDOWN_IMAGE_REGEX
|
|
27
|
-
}
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
const { onlyUnique, isImage, isRelative } = require('./filters')
|
|
2
|
-
const { findLinks, findAbsoluteLinks } = require('./find-links')
|
|
3
|
-
const { findMarkdownImages, MARKDOWN_IMAGE_REGEX } = require('./find-images-md')
|
|
4
|
-
|
|
5
|
-
// https://regex101.com/r/Uxgu3P/1
|
|
6
|
-
const RELATIVE_IMAGES_REGEX = /(<img.*?src=['"])(?!(?:(?:https?|ftp):\/\/|data:))(\.?\/)?(.*?)(['"].*?\/?>)/gim
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @typedef {object} findImagesOpts
|
|
10
|
-
* @property {string[]} [links] - optional links to use to avoid re-parse
|
|
11
|
-
* @property {boolean} [unique=true] - ensure links unique
|
|
12
|
-
* @property {Record<string,any>} [frontmatter] - Frontmatter data
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* @typedef {object} findImagesResult
|
|
17
|
-
* @property {string[]} all - All links
|
|
18
|
-
* @property {string[]} absolute - All absolute links
|
|
19
|
-
* @property {string[]} relative - All relative links
|
|
20
|
-
* @property {string[]} md - All md style links
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Get image links from content
|
|
25
|
-
* @param {string} content
|
|
26
|
-
* @param {findImagesOpts} [opts] - optional links to use to avoid re-parse
|
|
27
|
-
* @returns {findImagesResult}
|
|
28
|
-
*/
|
|
29
|
-
function findImages(content = '', opts = {}) {
|
|
30
|
-
const { links, unique = true, frontmatter } = opts
|
|
31
|
-
let foundLinks = []
|
|
32
|
-
if (links && Array.isArray(links)) {
|
|
33
|
-
foundLinks = links
|
|
34
|
-
} else {
|
|
35
|
-
const results = findLinks(content, { frontmatter })
|
|
36
|
-
foundLinks = results.links.concat(results.images)
|
|
37
|
-
}
|
|
38
|
-
const imageLinks = foundLinks.filter(isImage)
|
|
39
|
-
const markdownLinks = findMarkdownImages(content)
|
|
40
|
-
const allImageLinks = imageLinks.concat(markdownLinks)
|
|
41
|
-
const all = (!unique) ? allImageLinks : allImageLinks.filter(onlyUnique)
|
|
42
|
-
return {
|
|
43
|
-
all,
|
|
44
|
-
absolute: all.filter((link) => !isRelative(link)),
|
|
45
|
-
relative: all.filter((link) => isRelative(link)),
|
|
46
|
-
md: markdownLinks
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Get absolute url image links
|
|
52
|
-
* @param {string} content
|
|
53
|
-
* @param {array} [links] - optional links to use to avoid re-parse
|
|
54
|
-
* @returns {array}
|
|
55
|
-
*/
|
|
56
|
-
function findAbsoluteImages(content = '', links) {
|
|
57
|
-
const foundLinks = (links && Array.isArray(links)) ? links : findAbsoluteLinks(content)
|
|
58
|
-
const imageLinks = foundLinks.filter(isImage)
|
|
59
|
-
const markdownLinks = findMarkdownImages(content)
|
|
60
|
-
const allImageLinks = imageLinks.concat(markdownLinks)
|
|
61
|
-
|
|
62
|
-
return allImageLinks
|
|
63
|
-
.filter(onlyUnique)
|
|
64
|
-
.filter((link) => !isRelative(link))
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Get relative image links from content
|
|
69
|
-
* @param {string} text
|
|
70
|
-
* @returns {array}
|
|
71
|
-
*/
|
|
72
|
-
function findRelativeImages(text) {
|
|
73
|
-
const imgTags = findRelativeImgTags(text) || []
|
|
74
|
-
const mdTags = findMarkdownImages(text).filter(isRelative)
|
|
75
|
-
return imgTags.concat(mdTags)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/*
|
|
79
|
-
// https://regex101.com/r/SvMfme/1
|
|
80
|
-
<img src="img/deploy/button.svg" />
|
|
81
|
-
<img src="/img/deploy/button.svg" />
|
|
82
|
-
<img src='/img/deploy/button.svg' />
|
|
83
|
-
<img src='./img/deploy/button.svg' />
|
|
84
|
-
<img src='../img/deploy/button.svg' />
|
|
85
|
-
<img src='../../img/deploy/button.svg' />
|
|
86
|
-
*/
|
|
87
|
-
function findRelativeImgTags(block) {
|
|
88
|
-
let matches
|
|
89
|
-
let relLinks = []
|
|
90
|
-
while ((matches = RELATIVE_IMAGES_REGEX.exec(block)) !== null) {
|
|
91
|
-
if (matches.index === RELATIVE_IMAGES_REGEX.lastIndex) {
|
|
92
|
-
RELATIVE_IMAGES_REGEX.lastIndex++ // avoid infinite loops with zero-width matches
|
|
93
|
-
}
|
|
94
|
-
const [ match, _, start, link ] = matches
|
|
95
|
-
relLinks.push(`${start || ''}${link}`)
|
|
96
|
-
}
|
|
97
|
-
return relLinks.filter(onlyUnique)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
module.exports = {
|
|
101
|
-
findImages,
|
|
102
|
-
findAbsoluteImages,
|
|
103
|
-
findRelativeImages,
|
|
104
|
-
findMarkdownImages,
|
|
105
|
-
MARKDOWN_IMAGE_REGEX,
|
|
106
|
-
RELATIVE_IMAGES_REGEX
|
|
107
|
-
}
|
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
const { onlyUnique, isImage, isRelative } = require('./filters')
|
|
2
|
-
const { findMarkdownImages } = require('./find-images-md')
|
|
3
|
-
// Alt https://github.com/MikeKovarik/link-extract
|
|
4
|
-
|
|
5
|
-
// https://regex101.com/r/In5HtG/3
|
|
6
|
-
// const LIVE_LINKS_REGEX = /(?:['"(])((?:https?:\/\/)[\w\d\-_,./?=#%:+&]{3,})/gmi
|
|
7
|
-
// https://regex101.com/r/In5HtG/4
|
|
8
|
-
const LIVE_LINKS_REGEX = /['"(]((?:https?:\/\/)[\w\d\-_,./?=#%:+&]{3,})|<(\S*:\/\/\S*)>/gmi
|
|
9
|
-
// https://regex101.com/r/Nywerx/3
|
|
10
|
-
const RELATIVE_LINKS_REGEX = /(src|href|\()=?(['"/])(?!(?:(?:https?|ftp):\/\/|data:))(\.?\/)?([\w\d-_./,?=#%:+&]+)(?:['")])?/gim
|
|
11
|
-
// https://regex101.com/r/u2DwY2/2/
|
|
12
|
-
const MARKDOWN_IMAGE_REGEX = /!\[[^\]]*\]\((.*?)\s*("(?:.*[^"])")?\s*\)/g
|
|
13
|
-
// https://regex101.com/r/UeQ049/2 <https://www.markdownguide.org>
|
|
14
|
-
const ANGLE_LINKS = /(<)(\S*[@:]\S*)(>)/
|
|
15
|
-
|
|
16
|
-
const LINK_PATTERN = /^https?:\/\//
|
|
17
|
-
|
|
18
|
-
function isLocal(link) {
|
|
19
|
-
return isRelative(link)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function isRemote(link) {
|
|
23
|
-
return !isRelative(link)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* @typedef {object} findLinksOpts
|
|
28
|
-
* @property {Record<string,any>} [frontmatter] - Frontmatter data
|
|
29
|
-
* @property {boolean} [unique=true] - ensure links unique
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* @typedef {object} findLinksResult
|
|
34
|
-
* @property {string[]} links - All images
|
|
35
|
-
* @property {string[]} images - All live image links
|
|
36
|
-
*/
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Finds all links in text relative or otherwise
|
|
40
|
-
* @param {string} text
|
|
41
|
-
* @param {findLinksOpts} opts
|
|
42
|
-
* @returns {findLinksResult}
|
|
43
|
-
*/
|
|
44
|
-
function findLinks(text, opts = {}) {
|
|
45
|
-
const { unique = true, frontmatter } = opts
|
|
46
|
-
|
|
47
|
-
const absoluteLinks = findAbsoluteLinks(text)
|
|
48
|
-
// console.log('absoluteLinks', absoluteLinks)
|
|
49
|
-
const relativeLinks = findRelativeLinks(text)
|
|
50
|
-
// console.log('relativeLinks', relativeLinks)
|
|
51
|
-
const frontmatterLinks = (frontmatter) ? findLinksInFrontMatter(frontmatter) : []
|
|
52
|
-
// console.log('frontmatterLinks', frontmatterLinks)
|
|
53
|
-
const markdownImages = findMarkdownImages(text)
|
|
54
|
-
// console.log('markdownImages', markdownImages)
|
|
55
|
-
const foundLinks = frontmatterLinks
|
|
56
|
-
.concat(absoluteLinks)
|
|
57
|
-
.concat(relativeLinks)
|
|
58
|
-
.concat(markdownImages)
|
|
59
|
-
|
|
60
|
-
const allLinks = (!unique) ? foundLinks : foundLinks.filter(onlyUnique)
|
|
61
|
-
|
|
62
|
-
let _images = markdownImages
|
|
63
|
-
let _links = []
|
|
64
|
-
for (let i = 0; i < allLinks.length; i++) {
|
|
65
|
-
if (isImage(allLinks[i])) {
|
|
66
|
-
_images.push(allLinks[i])
|
|
67
|
-
} else {
|
|
68
|
-
_links.push(allLinks[i])
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
// const allImages = normalLinks.filter((link) => isImage(link)).concat(mdLinks)
|
|
72
|
-
// const links = normalLinks.filter((link) => !isImage(link))
|
|
73
|
-
const images = _images.filter(onlyUnique)
|
|
74
|
-
const links = _links.filter(onlyUnique).filter(function(el) {
|
|
75
|
-
return images.indexOf(el) < 0
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
return {
|
|
79
|
-
links,
|
|
80
|
-
images
|
|
81
|
-
}
|
|
82
|
-
/*
|
|
83
|
-
// Old return https://github.com/DavidWells/markdown-magic/blob/b148b4ad3876c4ea07371451d230a5e9cec57ce5/lib/utils/md/find-links.js#L32-L44
|
|
84
|
-
return {
|
|
85
|
-
// all,
|
|
86
|
-
// live,
|
|
87
|
-
// relative,
|
|
88
|
-
// frontmatter: frontmatterLinks,
|
|
89
|
-
links: {
|
|
90
|
-
all: allLink,
|
|
91
|
-
relative: allLink.filter(isLocal),
|
|
92
|
-
live: allLink.filter(isRemote)
|
|
93
|
-
},
|
|
94
|
-
images: {
|
|
95
|
-
all: allImg,
|
|
96
|
-
relative: allImg.filter(isLocal),
|
|
97
|
-
live: allImg.filter(isRemote)
|
|
98
|
-
},
|
|
99
|
-
}
|
|
100
|
-
*/
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function findMarkdownImageLinks(text) {
|
|
104
|
-
let matches
|
|
105
|
-
let imageLinks = []
|
|
106
|
-
while ((matches = MARKDOWN_IMAGE_REGEX.exec(text)) !== null) {
|
|
107
|
-
if (matches.index === MARKDOWN_IMAGE_REGEX.lastIndex) {
|
|
108
|
-
MARKDOWN_IMAGE_REGEX.lastIndex++ // avoid infinite loops with zero-width matches
|
|
109
|
-
}
|
|
110
|
-
const [ match, image, altText ] = matches
|
|
111
|
-
imageLinks.push(image)
|
|
112
|
-
}
|
|
113
|
-
return imageLinks.filter(onlyUnique)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Finds all links in markdown <a>, <img> and md link format []()
|
|
118
|
-
* @param {string} text
|
|
119
|
-
* @returns
|
|
120
|
-
*/
|
|
121
|
-
function findAbsoluteLinks(text) {
|
|
122
|
-
let matches
|
|
123
|
-
let links = []
|
|
124
|
-
while ((matches = LIVE_LINKS_REGEX.exec(text)) !== null) {
|
|
125
|
-
if (matches.index === LIVE_LINKS_REGEX.lastIndex) {
|
|
126
|
-
LIVE_LINKS_REGEX.lastIndex++ // avoid infinite loops with zero-width matches
|
|
127
|
-
}
|
|
128
|
-
const [ match, url, bracketLink ] = matches
|
|
129
|
-
links.push(url || bracketLink)
|
|
130
|
-
}
|
|
131
|
-
return links.filter(onlyUnique)
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/*
|
|
135
|
-
Match relative links
|
|
136
|
-
<h1 jdjdjjdjd=lksjskljfsdlk="jdjdj">Netlify + FaunaDB
|
|
137
|
-
<a href="https://app.netlify.com/start/deploy?repository=https://github.com/netlify/netlify-faunadb-example&stack=fauna">
|
|
138
|
-
<img src="../../../../img/deploy/lol.svg">
|
|
139
|
-
</a>
|
|
140
|
-
</h1>
|
|
141
|
-
[link](/my-great-page)
|
|
142
|
-
<a href="img/deploy/one.svg">cool</a>
|
|
143
|
-
<img src="img/deploy/duplicate.svg" />
|
|
144
|
-
<img src="img/deploy/duplicate.svg" >
|
|
145
|
-
<img src="/img/deploy/three.svg" />
|
|
146
|
-
<img src='/img/deploy/four.svg' />
|
|
147
|
-
<img src='./img/deploy/five.svg' />
|
|
148
|
-
<img src='../img/deploy/button.svg' />
|
|
149
|
-
<img src='../../img/deploy/button.svg' />
|
|
150
|
-
<img src="https://www.netlify.com/img/deploy/button.svg" />
|
|
151
|
-
<img src="https://www.netlify.com/img/deploy/button.svg" />
|
|
152
|
-

|
|
153
|
-
*/
|
|
154
|
-
|
|
155
|
-
function findRelativeLinks(text) {
|
|
156
|
-
let matches
|
|
157
|
-
let relLinks = []
|
|
158
|
-
while ((matches = RELATIVE_LINKS_REGEX.exec(text)) !== null) {
|
|
159
|
-
if (matches.index === RELATIVE_LINKS_REGEX.lastIndex) {
|
|
160
|
-
RELATIVE_LINKS_REGEX.lastIndex++ // avoid infinite loops with zero-width matches
|
|
161
|
-
}
|
|
162
|
-
// console.log(matches)
|
|
163
|
-
const [ match, _, start, link, x ] = matches
|
|
164
|
-
const one = (start === '/') ? start : ''
|
|
165
|
-
const two = (link === '/') ? link : ''
|
|
166
|
-
relLinks.push(`${one}${two}${x}`)
|
|
167
|
-
}
|
|
168
|
-
return relLinks.filter(onlyUnique)
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function findLinksInFrontMatter(data) {
|
|
172
|
-
const yamlStrings = traverse(data)
|
|
173
|
-
// console.log('yamlStrings', yamlStrings)
|
|
174
|
-
const linksInYml = yamlStrings.map((string) => {
|
|
175
|
-
if (LINK_PATTERN.test(string)) {
|
|
176
|
-
return [string]
|
|
177
|
-
}
|
|
178
|
-
// console.log('string', string)
|
|
179
|
-
const results = findLinks(string)
|
|
180
|
-
return results.links.concat(results.images)
|
|
181
|
-
})
|
|
182
|
-
.filter((x) => {
|
|
183
|
-
if (x && x.length) {
|
|
184
|
-
return x.length
|
|
185
|
-
}
|
|
186
|
-
return false
|
|
187
|
-
})
|
|
188
|
-
.flat()
|
|
189
|
-
// console.log('linksInYml', linksInYml)
|
|
190
|
-
return linksInYml
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function traverse(x, arr = []) {
|
|
194
|
-
if (typeof x === 'string') {
|
|
195
|
-
arr.push(x)
|
|
196
|
-
} else if (Array.isArray(x)) {
|
|
197
|
-
traverseArray(x, arr)
|
|
198
|
-
} else if ((typeof x === 'object') && (x !== null)) {
|
|
199
|
-
traverseObject(x, arr)
|
|
200
|
-
}
|
|
201
|
-
return arr
|
|
202
|
-
}
|
|
203
|
-
function traverseArray(arr, acc) {
|
|
204
|
-
for (let i = 0; i < arr.length; i++) {
|
|
205
|
-
traverse(arr[i], acc)
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
function traverseObject(obj, acc) {
|
|
209
|
-
for (var key in obj) {
|
|
210
|
-
if (obj.hasOwnProperty(key)) {
|
|
211
|
-
traverse(obj[key], acc)
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
module.exports = {
|
|
217
|
-
findLinks,
|
|
218
|
-
findAbsoluteLinks,
|
|
219
|
-
findRelativeLinks
|
|
220
|
-
}
|