forked from marktext/marktext
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmarkdown.js
More file actions
148 lines (130 loc) · 4.6 KB
/
markdown.js
File metadata and controls
148 lines (130 loc) · 4.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import fsPromises from 'fs/promises'
import path from 'path'
import log from 'electron-log'
import iconv from 'iconv-lite'
import { LINE_ENDING_REG, LF_LINE_ENDING_REG, CRLF_LINE_ENDING_REG } from '../config'
import { isDirectory2 } from 'common/filesystem'
import { isMarkdownFile } from 'common/filesystem/paths'
import { normalizeAndResolvePath, writeFile } from '../filesystem'
import { guessEncoding } from './encoding'
const getLineEnding = lineEnding => {
if (lineEnding === 'lf') {
return '\n'
} else if (lineEnding === 'crlf') {
return '\r\n'
}
// This should not happend but use fallback value.
log.error(`Invalid end of line character: expected "lf" or "crlf" but got "${lineEnding}".`)
return '\n'
}
const convertLineEndings = (text, lineEnding) => {
return text.replace(LINE_ENDING_REG, getLineEnding(lineEnding))
}
/**
* Special function to normalize directory and markdown file paths.
*
* @param {string} pathname The path to the file or directory.
* @returns {{isDir: boolean, path: string}?} Returns the normalize path and a
* directory hint or null if it's not a directory or markdown file.
*/
export const normalizeMarkdownPath = pathname => {
const isDir = isDirectory2(pathname)
if (isDir || isMarkdownFile(pathname)) {
// Normalize and resolve the path or link target.
const resolved = normalizeAndResolvePath(pathname)
if (resolved) {
return { isDir, path: resolved }
} else {
console.error(`[ERROR] Cannot resolve "${pathname}".`)
}
}
return null
}
/**
* Write the content into a file.
*
* @param {string} pathname The path to the file.
* @param {string} content The buffer to save.
* @param {IMarkdownDocumentOptions} options The markdown document options
*/
export const writeMarkdownFile = (pathname, content, options) => {
const { adjustLineEndingOnSave, lineEnding } = options
const { encoding, isBom } = options.encoding
const extension = path.extname(pathname) || '.md'
if (adjustLineEndingOnSave) {
content = convertLineEndings(content, lineEnding)
}
const buffer = iconv.encode(content, encoding, { addBOM: isBom })
// TODO(@fxha): "safeSaveDocuments" using temporary file and rename syscall.
return writeFile(pathname, buffer, extension, undefined)
}
/**
* Reads the contents of a markdown file.
*
* @param {string} pathname The path to the markdown file.
* @param {string} preferredEol The preferred EOL.
* @param {boolean} autoGuessEncoding Whether we should try to auto guess encoding.
* @param {*} trimTrailingNewline The trim trailing newline option.
* @returns {IMarkdownDocumentRaw} Returns a raw markdown document.
*/
export const loadMarkdownFile = async (pathname, preferredEol, autoGuessEncoding = true, trimTrailingNewline = 2) => {
// TODO: Use streams to not buffer the file multiple times and only guess
// encoding on the first 256/512 bytes.
let buffer = await fsPromises.readFile(path.resolve(pathname))
const encoding = guessEncoding(buffer, autoGuessEncoding)
const supported = iconv.encodingExists(encoding.encoding)
if (!supported) {
throw new Error(`"${encoding.encoding}" encoding is not supported.`)
}
let markdown = iconv.decode(buffer, encoding.encoding)
// Detect line ending
const isLf = LF_LINE_ENDING_REG.test(markdown)
const isCrlf = CRLF_LINE_ENDING_REG.test(markdown)
const isMixedLineEndings = isLf && isCrlf
const isUnknownEnding = !isLf && !isCrlf
let lineEnding = preferredEol
if (isLf && !isCrlf) {
lineEnding = 'lf'
} else if (isCrlf && !isLf) {
lineEnding = 'crlf'
}
let adjustLineEndingOnSave = false
if (isMixedLineEndings || isUnknownEnding || lineEnding !== 'lf') {
adjustLineEndingOnSave = lineEnding !== 'lf'
// Convert to LF for internal use.
markdown = convertLineEndings(markdown, 'lf')
}
// Detect final newline
if (trimTrailingNewline === 2) {
if (!markdown) {
// Use default value
trimTrailingNewline = 3
} else {
const lastIndex = markdown.length - 1
if (lastIndex >= 1 && markdown[lastIndex] === '\n' && markdown[lastIndex - 1] === '\n') {
// Disabled
trimTrailingNewline = 2
} else if (markdown[lastIndex] === '\n') {
// Ensure single trailing newline
trimTrailingNewline = 1
} else {
// Trim trailing newlines
trimTrailingNewline = 0
}
}
}
const filename = path.basename(pathname)
return {
// document information
markdown,
filename,
pathname,
// options
encoding,
lineEnding,
adjustLineEndingOnSave,
trimTrailingNewline,
// raw file information
isMixedLineEndings
}
}