mirror of
https://github.com/marktext/marktext.git
synced 2025-05-04 06:00:38 +08:00
Add symbolic link support (#802)
* Allow symbolic links * Update changelog
This commit is contained in:
parent
94066706f5
commit
f115b7ea41
1
.github/CHANGELOG.md
vendored
1
.github/CHANGELOG.md
vendored
@ -79,6 +79,7 @@ This update **fixes a XSS security vulnerability** when exporting a document.
|
||||
- Fixed multiple parser issues (update marked.js to v0.6.1)
|
||||
- Fixed [...] is displayed in gray and orange (#432)
|
||||
- Fixed an issue that relative images are not loaded after closing a tab
|
||||
- Add symbolic link support
|
||||
|
||||
### 0.13.65
|
||||
|
||||
|
@ -7,7 +7,7 @@ import appWindow from '../window'
|
||||
import { EXTENSION_HASN, EXTENSIONS, PANDOC_EXTENSIONS, URL_REG } from '../config'
|
||||
import { loadMarkdownFile, writeFile, writeMarkdownFile } from '../utils/filesystem'
|
||||
import appMenu from '../menu'
|
||||
import { getPath, isMarkdownFile, log, isFile, isDirectory, getRecommendTitle } from '../utils'
|
||||
import { getPath, isMarkdownFile, isMarkdownFileOrLink, normalizeAndResolvePath, log, isFile, isDirectory, getRecommendTitle } from '../utils'
|
||||
import userPreference from '../preference'
|
||||
import pandoc from '../utils/pandoc'
|
||||
|
||||
@ -213,7 +213,7 @@ ipcMain.on('AGANI::close-window', e => {
|
||||
ipcMain.on('AGANI::window::drop', async (e, fileList) => {
|
||||
const win = BrowserWindow.fromWebContents(e.sender)
|
||||
for (const file of fileList) {
|
||||
if (isMarkdownFile(file)) {
|
||||
if (isMarkdownFileOrLink(file)) {
|
||||
openFileOrFolder(win, file)
|
||||
break
|
||||
}
|
||||
@ -327,12 +327,13 @@ export const print = win => {
|
||||
}
|
||||
|
||||
export const openFileOrFolder = (win, pathname) => {
|
||||
if (isFile(pathname)) {
|
||||
const resolvedPath = normalizeAndResolvePath(pathname)
|
||||
if (isFile(resolvedPath)) {
|
||||
const { openFilesInNewWindow } = userPreference.getAll()
|
||||
if (openFilesInNewWindow) {
|
||||
appWindow.createWindow(pathname)
|
||||
appWindow.createWindow(resolvedPath)
|
||||
} else {
|
||||
loadMarkdownFile(pathname).then(rawDocument => {
|
||||
loadMarkdownFile(resolvedPath).then(rawDocument => {
|
||||
newTab(win, rawDocument)
|
||||
}).catch(err => {
|
||||
// TODO: Handle error --> create a end-user error handler.
|
||||
@ -340,10 +341,10 @@ export const openFileOrFolder = (win, pathname) => {
|
||||
log(err)
|
||||
})
|
||||
}
|
||||
} else if (isDirectory(pathname)) {
|
||||
appWindow.createWindow(pathname)
|
||||
} else if (isDirectory(resolvedPath)) {
|
||||
appWindow.createWindow(resolvedPath)
|
||||
} else {
|
||||
console.error(`[ERROR] Cannot open unknown file: "${pathname}"`)
|
||||
console.error(`[ERROR] Cannot open unknown file: "${resolvedPath}"`)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,8 @@
|
||||
import path from 'path'
|
||||
import { app, systemPreferences } from 'electron'
|
||||
import appWindow from './window'
|
||||
import { isOsx } from './config'
|
||||
import { dockMenu } from './menus'
|
||||
import { isDirectory, isMarkdownFile, getMenuItemById } from './utils'
|
||||
import { isDirectory, isMarkdownFileOrLink, getMenuItemById, normalizeAndResolvePath } from './utils'
|
||||
import { watchers } from './utils/imagePathAutoComplement'
|
||||
import { selectTheme } from './actions/theme'
|
||||
import preference from './preference'
|
||||
@ -70,10 +69,16 @@ class App {
|
||||
for (const arg of process.argv) {
|
||||
if (arg.startsWith('--')) {
|
||||
continue
|
||||
} else if (isDirectory(arg) || isMarkdownFile(arg)) {
|
||||
// Normalize path into an absolute path.
|
||||
this.openFilesCache = [ path.resolve(arg) ]
|
||||
} else if (isDirectory(arg) || isMarkdownFileOrLink(arg)) {
|
||||
// Normalize and resolve the path or link target.
|
||||
const resolved = normalizeAndResolvePath(arg)
|
||||
if (resolved) {
|
||||
// TODO: Allow to open multiple files.
|
||||
this.openFilesCache = [ resolved ]
|
||||
break
|
||||
} else {
|
||||
console.error(`[ERROR] Cannot resolve "${arg}".`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -64,32 +64,93 @@ export const hasSameKeys = (a, b) => {
|
||||
return JSON.stringify(aKeys) === JSON.stringify(bKeys)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the path is a directory with read access.
|
||||
*
|
||||
* @param {string} dirPath The directory path.
|
||||
*/
|
||||
export const isDirectory = dirPath => {
|
||||
try {
|
||||
return fs.existsSync(dirPath) && fs.lstatSync(dirPath).isDirectory()
|
||||
} catch (e) {
|
||||
// No permissions
|
||||
log(e)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// returns true if the path is a file with read access.
|
||||
/**
|
||||
* Returns true if the path is a file with read access.
|
||||
*
|
||||
* @param {string} filepath The file path.
|
||||
*/
|
||||
export const isFile = filepath => {
|
||||
try {
|
||||
return fs.existsSync(filepath) && fs.lstatSync(filepath).isFile()
|
||||
} catch (e) {
|
||||
// No permissions
|
||||
log(e)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// returns true if the file is a supported markdown file.
|
||||
/**
|
||||
* Returns true if the path is a symbolic link with read access.
|
||||
*
|
||||
* @param {string} filepath The link path.
|
||||
*/
|
||||
export const isSymbolicLink = filepath => {
|
||||
try {
|
||||
return fs.existsSync(filepath) && fs.lstatSync(filepath).isSymbolicLink()
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the path is a markdown file.
|
||||
*
|
||||
* @param {string} filepath The path or link path.
|
||||
*/
|
||||
export const isMarkdownFile = filepath => {
|
||||
return isFile(filepath) && hasMarkdownExtension(filepath)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the path is a markdown file or symbolic link to a markdown file.
|
||||
*
|
||||
* @param {string} filepath The path or link path.
|
||||
*/
|
||||
export const isMarkdownFileOrLink = filepath => {
|
||||
if (!isFile(filepath)) return false
|
||||
if (hasMarkdownExtension(filepath)) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Symbolic link to a markdown file
|
||||
if (isSymbolicLink(filepath)) {
|
||||
const targetPath = fs.readlinkSync(filepath)
|
||||
return isFile(targetPath) && hasMarkdownExtension(targetPath)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize the path into an absolute path and resolves the link target if needed.
|
||||
*
|
||||
* @param {string} pathname The path or link path.
|
||||
* @returns {string} Returns the absolute path and resolved link. If the link target
|
||||
* cannot be resolved, an empty string is returned.
|
||||
*/
|
||||
export const normalizeAndResolvePath = pathname => {
|
||||
if (isSymbolicLink(pathname)) {
|
||||
const absPath = path.dirname(pathname)
|
||||
const targetPath = path.resolve(absPath, fs.readlinkSync(pathname))
|
||||
if (isFile(targetPath) || isDirectory(targetPath)) {
|
||||
return path.resolve(targetPath)
|
||||
}
|
||||
console.error(`Cannot resolve link target "${pathname}" (${targetPath}).`)
|
||||
return ''
|
||||
}
|
||||
return path.resolve(pathname)
|
||||
}
|
||||
|
||||
export const readJson = (filePath, printError) => {
|
||||
try {
|
||||
const content = fs.readFileSync(filePath, 'utf-8')
|
||||
|
@ -1,10 +1,9 @@
|
||||
import path from 'path'
|
||||
import { app, BrowserWindow, screen } from 'electron'
|
||||
import windowStateKeeper from 'electron-window-state'
|
||||
import { getOsLineEndingName, loadMarkdownFile, getDefaultTextDirection } from './utils/filesystem'
|
||||
import appMenu from './menu'
|
||||
import Watcher from './watcher'
|
||||
import { isMarkdownFile, isDirectory, log } from './utils'
|
||||
import { isMarkdownFile, isDirectory, normalizeAndResolvePath, log } from './utils'
|
||||
import { TITLE_BAR_HEIGHT, defaultWinOptions, isLinux } from './config'
|
||||
import userPreference from './preference'
|
||||
|
||||
@ -48,10 +47,17 @@ class AppWindow {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new editor window.
|
||||
*
|
||||
* @param {string} [pathname] Path to a file, directory or link.
|
||||
* @param {string} [markdown] Markdown content.
|
||||
* @param {*} [options] BrowserWindow options.
|
||||
*/
|
||||
createWindow (pathname = null, markdown = '', options = {}) {
|
||||
// Ensure path is normalized
|
||||
if (pathname) {
|
||||
pathname = path.resolve(pathname)
|
||||
pathname = normalizeAndResolvePath(pathname)
|
||||
}
|
||||
|
||||
const { windows } = this
|
||||
@ -88,7 +94,7 @@ class AppWindow {
|
||||
mainWindowState.manage(win)
|
||||
win.show()
|
||||
|
||||
// open single mrkdown file
|
||||
// open single markdown file
|
||||
if (pathname && isMarkdownFile(pathname)) {
|
||||
appMenu.addRecentlyUsedDocument(pathname)
|
||||
loadMarkdownFile(pathname)
|
||||
|
Loading…
Reference in New Issue
Block a user