markdown-magic 2.6.1 → 3.0.0

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.
Files changed (61) hide show
  1. package/README.md +6 -10
  2. package/cli.js +5 -82
  3. package/lib/block-parser-js.test.js +179 -0
  4. package/lib/block-parser.js +389 -0
  5. package/lib/{utils/new-parser.test.js → block-parser.test.js} +168 -50
  6. package/lib/cli.js +234 -0
  7. package/lib/cli.test.js +409 -0
  8. package/lib/defaults.js +12 -0
  9. package/lib/index.js +319 -184
  10. package/lib/index.test.js +11 -0
  11. package/lib/options-parser.js +498 -0
  12. package/lib/options-parser.test.js +1237 -0
  13. package/lib/process-contents.js +330 -0
  14. package/lib/process-file.js +34 -0
  15. package/lib/transforms/code.js +67 -22
  16. package/lib/transforms/file.js +13 -10
  17. package/lib/transforms/remote.js +9 -6
  18. package/lib/transforms/toc.js +136 -64
  19. package/lib/transforms/wordCount.js +5 -0
  20. package/lib/utils/fs.js +340 -0
  21. package/lib/utils/fs.test.js +268 -0
  22. package/lib/utils/html-to-json/compat.js +42 -0
  23. package/lib/utils/html-to-json/format.js +64 -0
  24. package/lib/utils/html-to-json/index.js +37 -0
  25. package/lib/utils/html-to-json/lexer.js +345 -0
  26. package/lib/utils/html-to-json/parser.js +146 -0
  27. package/lib/utils/html-to-json/stringify.js +37 -0
  28. package/lib/utils/html-to-json/tags.js +171 -0
  29. package/lib/utils/index.js +19 -0
  30. package/{cli-utils.js → lib/utils/load-config.js} +2 -6
  31. package/lib/utils/logs.js +89 -0
  32. package/lib/utils/md/filters.js +20 -0
  33. package/lib/utils/md/find-code-blocks.js +80 -0
  34. package/lib/utils/md/find-date.js +32 -0
  35. package/lib/utils/md/find-frontmatter.js +94 -0
  36. package/lib/utils/md/find-frontmatter.test.js +17 -0
  37. package/lib/utils/md/find-html-tags.js +105 -0
  38. package/lib/utils/md/find-images.js +102 -0
  39. package/lib/utils/md/find-links.js +202 -0
  40. package/lib/utils/md/find-unmatched-html-tags.js +33 -0
  41. package/lib/utils/md/fixtures/2022-01-22-date-in-filename.md +14 -0
  42. package/lib/utils/md/fixtures/file-with-frontmatter.md +32 -0
  43. package/lib/utils/md/fixtures/file-with-links.md +143 -0
  44. package/lib/utils/md/md.test.js +37 -0
  45. package/lib/utils/md/parse.js +122 -0
  46. package/lib/utils/md/utils.js +19 -0
  47. package/lib/utils/regex-timeout.js +83 -0
  48. package/lib/utils/regex.js +38 -5
  49. package/lib/utils/remoteRequest.js +54 -0
  50. package/lib/utils/syntax.js +79 -0
  51. package/lib/utils/text.js +260 -0
  52. package/lib/utils/text.test.js +311 -0
  53. package/package.json +25 -25
  54. package/index.js +0 -46
  55. package/lib/processFile.js +0 -154
  56. package/lib/transforms/index.js +0 -114
  57. package/lib/updateContents.js +0 -125
  58. package/lib/utils/_md.test.js +0 -63
  59. package/lib/utils/new-parser.js +0 -412
  60. package/lib/utils/weird-parse.js +0 -230
  61. package/lib/utils/weird-parse.test.js +0 -217
@@ -1,11 +1,7 @@
1
- const resolve = require('path').resolve
1
+ const path = require('path')
2
2
 
3
3
  const loadJSConfig = getAttemptModuleRequireFn(function onFail(configPath, requirePath) {
4
4
  console.log(`Unable to find JS config at "${configPath}". Attempted to require as "${requirePath}"`)
5
- // log.error({
6
- // message: colors.red(`Unable to find JS config at "${configPath}". Attempted to require as "${requirePath}"`),
7
- // ref: 'unable-to-find-config',
8
- // })
9
5
  return undefined
10
6
  })
11
7
 
@@ -15,7 +11,7 @@ const loadJSConfig = getAttemptModuleRequireFn(function onFail(configPath, requi
15
11
  * @return {String} the module path to require
16
12
  */
17
13
  function getModuleRequirePath(moduleName) {
18
- return moduleName[0] === '.' ? resolve(process.cwd(), moduleName) : moduleName
14
+ return moduleName[0] === '.' ? path.resolve(process.cwd(), moduleName) : moduleName
19
15
  }
20
16
 
21
17
  function getAttemptModuleRequireFn(onFail) {
@@ -0,0 +1,89 @@
1
+ const util = require('util')
2
+ const ansi = require('ansi-styles') // https://github.com/chalk/ansi-styles/blob/main/index.js
3
+ const process = require('process')
4
+
5
+ // via https://github.com/sindresorhus/is-unicode-supported/blob/main/index.js
6
+ function isUnicodeSupported() {
7
+ if (process.platform !== 'win32') return process.env.TERM !== 'linux' // Linux console (kernel)
8
+ return Boolean(process.env.CI)
9
+ || Boolean(process.env.WT_SESSION) // Windows Terminal
10
+ || process.env.ConEmuTask === '{cmd::Cmder}' // ConEmu and cmder
11
+ || process.env.TERM_PROGRAM === 'vscode' || process.env.TERM === 'xterm-256color' || process.env.TERM === 'alacritty'
12
+ }
13
+
14
+ const isObject = (obj) => obj !== null && typeof obj === 'object'
15
+ function neverNull(obj) {
16
+ const match = (some, none = () => {}) => (obj !== null) ? some(obj) : none()
17
+ return new Proxy((some, none) => {
18
+ if (some) return some === 'string' ? '' : some
19
+ if (!some && !none) return obj
20
+ return match(some, none)
21
+ },
22
+ {
23
+ get: (target, key) => {
24
+ const obj = target()
25
+ if (isObject(obj)) return neverNull(obj[key])
26
+ return neverNull()
27
+ },
28
+ set: (target, key, val) => {
29
+ const obj = target()
30
+ if (isObject(obj)) obj[key] = val
31
+ return true
32
+ },
33
+ })
34
+ }
35
+
36
+ function safeColors(disableColors) {
37
+ return (disableColors) ? neverNull(ansi) : ansi
38
+ }
39
+
40
+ const allowed = isUnicodeSupported()
41
+ const styles = safeColors(process.env.DISABLE_COLOR)
42
+ const SPACES = ' '
43
+ const SUCCESS = allowed ? '✔' : '√'
44
+ const INFO = allowed ? 'ℹ' : 'i'
45
+ const WARNING = allowed ? '⚠' : '‼'
46
+ const ERROR = allowed ? '✖' : '×'
47
+
48
+ const colors = {
49
+ default: ['white', ''],
50
+ success: ['greenBright', `${SUCCESS}${SPACES}`],
51
+ info: ['cyanBright', `${INFO}${SPACES}`],
52
+ warning: ['yellowBright', `${WARNING}${SPACES}`],
53
+ error: ['redBright', `${ERROR}${SPACES}`]
54
+ }
55
+
56
+ function log(type, msg, customPrefix, noLog) {
57
+ const [color, prefix] = colors[type] || colors.default
58
+ const finalPrefix = typeof customPrefix !== 'undefined' ? customPrefix : prefix
59
+ const logMsg = `${styles[color].open}${finalPrefix}${msg}${styles[color].close}`
60
+ if (noLog) return logMsg
61
+ console.log(logMsg)
62
+ }
63
+
64
+ function deepLog(myObject) {
65
+ console.log(util.inspect(myObject, false, null, true /* enable colors */))
66
+ }
67
+
68
+ const success = log.bind(null, 'success')
69
+ const info = log.bind(null, 'info')
70
+ const warning = log.bind(null, 'warning')
71
+ const error = log.bind(null, 'error')
72
+
73
+ /*
74
+ // Usage:
75
+ console.log('Nice logs')
76
+ deepLog({ deep: {object }})
77
+ success('Success! Yay it worked')
78
+ info('Info: Additional details here')
79
+ warning('Warning: Watch out')
80
+ error('Error: Oh no!')
81
+ /**/
82
+
83
+ module.exports = {
84
+ deepLog,
85
+ success,
86
+ info,
87
+ warning,
88
+ error,
89
+ }
@@ -0,0 +1,20 @@
1
+
2
+ const RELATIVE_LINK_REGEX = /^(?!(?:(?:https?|ftp):\/\/|data:))((?:\.\.?\/)*)*([\w\d\-_./?=#%:+&]+)/
3
+
4
+ function onlyUnique(value, index, self) {
5
+ return self.indexOf(value) === index;
6
+ }
7
+
8
+ function isImage(link) {
9
+ return link.match(/(png|jpe?g|gif|webp|svg)$/)
10
+ }
11
+
12
+ function isRelative(filepath) {
13
+ return RELATIVE_LINK_REGEX.test(filepath)
14
+ }
15
+
16
+ module.exports = {
17
+ onlyUnique,
18
+ isImage,
19
+ isRelative
20
+ }
@@ -0,0 +1,80 @@
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
+ * @returns {Object}
12
+ * @example
13
+ * const { blocks, errors } = findCodeBlocks(content)
14
+ * console.log('blocks', blocks)
15
+ * console.log('errors', errors)
16
+ */
17
+ function findCodeBlocks(block, filePath = '') {
18
+ let matches
19
+ let errors = []
20
+ let blocks = []
21
+ const msg = (filePath) ? ` in ${filePath}` : ''
22
+ while ((matches = CODE_BLOCK_REGEX.exec(block)) !== null) {
23
+ if (matches.index === CODE_BLOCK_REGEX.lastIndex) {
24
+ CODE_BLOCK_REGEX.lastIndex++ // avoid infinite loops with zero-width matches
25
+ }
26
+ const [ match, prefix, syntax, content, postFix ] = matches
27
+ const lineNumber = getLineNumberFromMatch(block, matches)
28
+ let hasError = false
29
+ /* // debug
30
+ console.log(`prefix: "${prefix}"`)
31
+ console.log(`postFix: "${postFix}"`)
32
+ console.log('syntax:', lang)
33
+ console.log('Content:')
34
+ console.log(content.trim())
35
+ console.log('───────────────────────')
36
+ /** */
37
+
38
+ /* Validate code blocks */
39
+ if (prefix && prefix.match(/\S/)) {
40
+ hasError = true
41
+ errors.push({
42
+ line: lineNumber,
43
+ index: matches.index,
44
+ message: `Prefix "${prefix}" not allowed on line ${lineNumber}. Fix the code block${msg}.`,
45
+ block: match
46
+ })
47
+ }
48
+ if (postFix && postFix.match(/\S/)) {
49
+ hasError = true
50
+ const line = lineNumber + (getLineCount(match) - 1)
51
+ errors.push({
52
+ line,
53
+ index: matches.index + match.length,
54
+ message: `Postfix "${postFix}" not allowed on line ${line}. Fix the code block${msg}.`,
55
+ block: match
56
+ })
57
+ }
58
+
59
+ if (!hasError) {
60
+ blocks.push({
61
+ line: lineNumber,
62
+ index: matches.index,
63
+ syntax: syntax || '',
64
+ block: match,
65
+ code: content.trim()
66
+ })
67
+ }
68
+ }
69
+
70
+ return {
71
+ errors,
72
+ blocks
73
+ }
74
+ }
75
+
76
+ module.exports = {
77
+ findCodeBlocks,
78
+ CODE_BLOCK_REGEX,
79
+ REMOVE_CODE_BLOCK_REGEX
80
+ }
@@ -0,0 +1,32 @@
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
+ }
@@ -0,0 +1,94 @@
1
+ const matter = require('gray-matter')
2
+ /* Match <!-- frontmatter --> */
3
+ // https://regex101.com/r/Q9bBxC/1
4
+ const HIDDEN_FRONTMATTER_REGEX = /^<!--+(?:\r\n|\r|\n)([\w\W]*?)--+>/g
5
+ // const HIDDEN_FRONTMATTER_REGEX = /^<!--.*((.|\r?\n)*?.*-->)/g
6
+
7
+ /* Match --- frontmatter --- */
8
+ // https://regex101.com/r/d7eAw4/1
9
+ const FRONTMATTER_REGEX = /(^--+(?:\r\n|\r|\n)([\w\W]*?)--+)/
10
+ // const FRONTMATTER_REGEX = /^---.*((.|\r?\n)*?.*---)/gm
11
+
12
+ function removeConflictingContent(str) {
13
+ return str
14
+ .replace(/[\t ]{1}---/g, '__LINE_BREAK__')
15
+ .replace(/[\t ]--+>/g, '__CLOSE_COMMENT__')
16
+ // TODO also handle nested <!-- comments -->
17
+ }
18
+
19
+ function replaceConflictingContent(str) {
20
+ return str
21
+ .replace(/__LINE_BREAK__/g, ' ---')
22
+ .replace(/__CLOSE_COMMENT__/g, ' -->')
23
+ }
24
+
25
+ function findFrontmatterRaw(content = '') {
26
+ const text = removeConflictingContent(content.trim())
27
+ const hasFrontMatter = text.match(FRONTMATTER_REGEX)
28
+ const hasHiddenFrontMatter = text.match(HIDDEN_FRONTMATTER_REGEX)
29
+ let raw = ''
30
+ let match = ''
31
+ let isHidden = false
32
+ // console.log('hasFrontMatter', hasFrontMatter)
33
+ // console.log('hasHiddenFrontMatter', hasHiddenFrontMatter)
34
+ if (hasFrontMatter) {
35
+ raw = hasFrontMatter[1]
36
+ match = raw.trim()
37
+ // Fix Leading frontmatter brackets
38
+ .replace(/^---+/, '---')
39
+ // Trailing frontmatter brackets
40
+ .replace(/--+$/, `---`)
41
+ } else if (hasHiddenFrontMatter) {
42
+ isHidden = true
43
+ raw = hasHiddenFrontMatter[1]
44
+ match = raw.trim()
45
+ // Leading frontmatter brackets
46
+ .replace(/<!--+/, '---')
47
+ // Trailing frontmatter brackets
48
+ .replace(/--+>/, `---`)
49
+ }
50
+ return {
51
+ rawFrontMatter: replaceConflictingContent(raw),
52
+ frontMatter: replaceConflictingContent(match),
53
+ isHidden
54
+ }
55
+ }
56
+
57
+ function findFrontmatter(text) {
58
+ const { frontMatter, rawFrontMatter } = findFrontmatterRaw(text)
59
+ // console.log('frontMatter', frontMatter)
60
+ let frontmatter = { data: {}, content: '' }
61
+ /* Missing all frontmatter */
62
+ if (!frontMatter) {
63
+ // throw new Error(`Missing or broken frontmatter in ${filePath}. Double check file for --- frontmatter tags in files`)
64
+ return frontmatter
65
+ }
66
+
67
+ let mdContent = text
68
+ if (rawFrontMatter) {
69
+ mdContent = text
70
+ // Replace frontmatter brackets
71
+ .replace(rawFrontMatter, frontMatter)
72
+ // Replace leading lines
73
+ // .replace(/---+\s+\n/g, '---\n')
74
+ }
75
+
76
+ try {
77
+ frontmatter = matter(mdContent)
78
+ } catch (err) {
79
+ /* Add line numbers to output */
80
+ const formattedError = rawFrontMatter.split('\n').map((line, i) => {
81
+ return `${i + 1}. ${line}`
82
+ })
83
+ throw new Error(`Frontmatter error:\n${err.message}\n${formattedError.join('\n')}`)
84
+ }
85
+ // console.log('frontMatter', frontmatter)
86
+ return Object.assign(frontmatter, { rawFrontMatter })
87
+ }
88
+
89
+ module.exports = {
90
+ findFrontmatter,
91
+ findFrontmatterRaw,
92
+ HIDDEN_FRONTMATTER_REGEX,
93
+ FRONTMATTER_REGEX
94
+ }
@@ -0,0 +1,17 @@
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 { findFrontmatter } = 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 = findFrontmatter(fileWithLinks, FILE_PATH)
13
+ console.log('frontmatter', frontmatter)
14
+ assert.is(typeof frontmatter.data, 'object')
15
+ })
16
+
17
+ test.run()
@@ -0,0 +1,105 @@
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
+ }
@@ -0,0 +1,102 @@
1
+ const { onlyUnique, isImage, isRelative } = require('./filters')
2
+ const { findLinks, findLiveLinks } = require('./find-links')
3
+
4
+ // https://regex101.com/r/u2DwY2/2/
5
+ const MARKDOWN_IMAGE_REGEX = /!\[[^\]]*\]\((.*?)\s*("(?:.*[^"])")?\s*\)/g
6
+ // https://regex101.com/r/Uxgu3P/1
7
+ const RELATIVE_IMAGES_REGEX = /(<img.*?src=['"])(?!(?:(?:https?|ftp):\/\/|data:))(\.?\/)?(.*?)(['"].*?\/?>)/gim
8
+
9
+ /**
10
+ * Get image links from content
11
+ * @param {string} content
12
+ * @param {array} [links] - optional links to use to avoid re-parse
13
+ * @returns {array}
14
+ */
15
+ function findImages(content = '', opts = {}) {
16
+ const { links, unique = true } = opts
17
+ const foundLinks = (links && Array.isArray(links)) ? links : findLinks(content).all
18
+ const imageLinks = foundLinks.filter(isImage)
19
+ const mdLinks = findMarkdownImages(content)
20
+ const allImageLinks = imageLinks.concat(mdLinks)
21
+ const all = (!unique) ? allImageLinks : allImageLinks.filter(onlyUnique)
22
+ return {
23
+ all,
24
+ live: all.filter((link) => !isRelative(link)),
25
+ relative: all.filter((link) => isRelative(link)),
26
+ md: mdLinks
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Get live url image links
32
+ * @param {string} content
33
+ * @param {array} [links] - optional links to use to avoid re-parse
34
+ * @returns {array}
35
+ */
36
+ function findLiveImages(content = '', links) {
37
+ const foundLinks = (links && Array.isArray(links)) ? links : findLiveLinks(content)
38
+ const imageLinks = foundLinks.filter((link) => {
39
+ return link.match(/(png|jpe?g|gif|webp|svg)$/)
40
+ })
41
+ const mdLinks = findMarkdownImages(content)
42
+ const allImageLinks = imageLinks.concat(mdLinks)
43
+
44
+ return allImageLinks
45
+ .filter(onlyUnique)
46
+ .filter((link) => !isRelative(link))
47
+ }
48
+
49
+ function findMarkdownImages(text) {
50
+ let matches
51
+ let imageLinks = []
52
+ while ((matches = MARKDOWN_IMAGE_REGEX.exec(text)) !== null) {
53
+ if (matches.index === MARKDOWN_IMAGE_REGEX.lastIndex) {
54
+ MARKDOWN_IMAGE_REGEX.lastIndex++ // avoid infinite loops with zero-width matches
55
+ }
56
+ const [ match, image, altText ] = matches
57
+ imageLinks.push(image)
58
+ }
59
+ return imageLinks.filter(onlyUnique)
60
+ }
61
+
62
+ /**
63
+ * Get relative image links from content
64
+ * @param {string} content
65
+ * @returns {array}
66
+ */
67
+ function findRelativeImages(text) {
68
+ const imgTags = findRelativeImgTags(text) || []
69
+ const mdTags = findMarkdownImages(text).filter(isRelative)
70
+ return imgTags.concat(mdTags)
71
+ }
72
+
73
+ /*
74
+ // https://regex101.com/r/SvMfme/1
75
+ <img src="img/deploy/button.svg" />
76
+ <img src="/img/deploy/button.svg" />
77
+ <img src='/img/deploy/button.svg' />
78
+ <img src='./img/deploy/button.svg' />
79
+ <img src='../img/deploy/button.svg' />
80
+ <img src='../../img/deploy/button.svg' />
81
+ */
82
+ function findRelativeImgTags(block) {
83
+ let matches
84
+ let relLinks = []
85
+ while ((matches = RELATIVE_IMAGES_REGEX.exec(block)) !== null) {
86
+ if (matches.index === RELATIVE_IMAGES_REGEX.lastIndex) {
87
+ RELATIVE_IMAGES_REGEX.lastIndex++ // avoid infinite loops with zero-width matches
88
+ }
89
+ const [ match, _, start, link ] = matches
90
+ relLinks.push(`${start || ''}${link}`)
91
+ }
92
+ return relLinks.filter(onlyUnique)
93
+ }
94
+
95
+ module.exports = {
96
+ findImages,
97
+ findLiveImages,
98
+ findRelativeImages,
99
+ findMarkdownImages,
100
+ MARKDOWN_IMAGE_REGEX,
101
+ RELATIVE_IMAGES_REGEX
102
+ }