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 multiple parser issues (update marked.js to v0.6.1)
|
||||||
- Fixed [...] is displayed in gray and orange (#432)
|
- Fixed [...] is displayed in gray and orange (#432)
|
||||||
- Fixed an issue that relative images are not loaded after closing a tab
|
- Fixed an issue that relative images are not loaded after closing a tab
|
||||||
|
- Add symbolic link support
|
||||||
|
|
||||||
### 0.13.65
|
### 0.13.65
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import appWindow from '../window'
|
|||||||
import { EXTENSION_HASN, EXTENSIONS, PANDOC_EXTENSIONS, URL_REG } from '../config'
|
import { EXTENSION_HASN, EXTENSIONS, PANDOC_EXTENSIONS, URL_REG } from '../config'
|
||||||
import { loadMarkdownFile, writeFile, writeMarkdownFile } from '../utils/filesystem'
|
import { loadMarkdownFile, writeFile, writeMarkdownFile } from '../utils/filesystem'
|
||||||
import appMenu from '../menu'
|
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 userPreference from '../preference'
|
||||||
import pandoc from '../utils/pandoc'
|
import pandoc from '../utils/pandoc'
|
||||||
|
|
||||||
@ -213,7 +213,7 @@ ipcMain.on('AGANI::close-window', e => {
|
|||||||
ipcMain.on('AGANI::window::drop', async (e, fileList) => {
|
ipcMain.on('AGANI::window::drop', async (e, fileList) => {
|
||||||
const win = BrowserWindow.fromWebContents(e.sender)
|
const win = BrowserWindow.fromWebContents(e.sender)
|
||||||
for (const file of fileList) {
|
for (const file of fileList) {
|
||||||
if (isMarkdownFile(file)) {
|
if (isMarkdownFileOrLink(file)) {
|
||||||
openFileOrFolder(win, file)
|
openFileOrFolder(win, file)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -327,12 +327,13 @@ export const print = win => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const openFileOrFolder = (win, pathname) => {
|
export const openFileOrFolder = (win, pathname) => {
|
||||||
if (isFile(pathname)) {
|
const resolvedPath = normalizeAndResolvePath(pathname)
|
||||||
|
if (isFile(resolvedPath)) {
|
||||||
const { openFilesInNewWindow } = userPreference.getAll()
|
const { openFilesInNewWindow } = userPreference.getAll()
|
||||||
if (openFilesInNewWindow) {
|
if (openFilesInNewWindow) {
|
||||||
appWindow.createWindow(pathname)
|
appWindow.createWindow(resolvedPath)
|
||||||
} else {
|
} else {
|
||||||
loadMarkdownFile(pathname).then(rawDocument => {
|
loadMarkdownFile(resolvedPath).then(rawDocument => {
|
||||||
newTab(win, rawDocument)
|
newTab(win, rawDocument)
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
// TODO: Handle error --> create a end-user error handler.
|
// TODO: Handle error --> create a end-user error handler.
|
||||||
@ -340,10 +341,10 @@ export const openFileOrFolder = (win, pathname) => {
|
|||||||
log(err)
|
log(err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else if (isDirectory(pathname)) {
|
} else if (isDirectory(resolvedPath)) {
|
||||||
appWindow.createWindow(pathname)
|
appWindow.createWindow(resolvedPath)
|
||||||
} else {
|
} 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 { app, systemPreferences } from 'electron'
|
||||||
import appWindow from './window'
|
import appWindow from './window'
|
||||||
import { isOsx } from './config'
|
import { isOsx } from './config'
|
||||||
import { dockMenu } from './menus'
|
import { dockMenu } from './menus'
|
||||||
import { isDirectory, isMarkdownFile, getMenuItemById } from './utils'
|
import { isDirectory, isMarkdownFileOrLink, getMenuItemById, normalizeAndResolvePath } from './utils'
|
||||||
import { watchers } from './utils/imagePathAutoComplement'
|
import { watchers } from './utils/imagePathAutoComplement'
|
||||||
import { selectTheme } from './actions/theme'
|
import { selectTheme } from './actions/theme'
|
||||||
import preference from './preference'
|
import preference from './preference'
|
||||||
@ -70,10 +69,16 @@ class App {
|
|||||||
for (const arg of process.argv) {
|
for (const arg of process.argv) {
|
||||||
if (arg.startsWith('--')) {
|
if (arg.startsWith('--')) {
|
||||||
continue
|
continue
|
||||||
} else if (isDirectory(arg) || isMarkdownFile(arg)) {
|
} else if (isDirectory(arg) || isMarkdownFileOrLink(arg)) {
|
||||||
// Normalize path into an absolute path.
|
// Normalize and resolve the path or link target.
|
||||||
this.openFilesCache = [ path.resolve(arg) ]
|
const resolved = normalizeAndResolvePath(arg)
|
||||||
break
|
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)
|
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 => {
|
export const isDirectory = dirPath => {
|
||||||
try {
|
try {
|
||||||
return fs.existsSync(dirPath) && fs.lstatSync(dirPath).isDirectory()
|
return fs.existsSync(dirPath) && fs.lstatSync(dirPath).isDirectory()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// No permissions
|
|
||||||
log(e)
|
|
||||||
return false
|
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 => {
|
export const isFile = filepath => {
|
||||||
try {
|
try {
|
||||||
return fs.existsSync(filepath) && fs.lstatSync(filepath).isFile()
|
return fs.existsSync(filepath) && fs.lstatSync(filepath).isFile()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// No permissions
|
|
||||||
log(e)
|
|
||||||
return false
|
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 => {
|
export const isMarkdownFile = filepath => {
|
||||||
return isFile(filepath) && hasMarkdownExtension(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) => {
|
export const readJson = (filePath, printError) => {
|
||||||
try {
|
try {
|
||||||
const content = fs.readFileSync(filePath, 'utf-8')
|
const content = fs.readFileSync(filePath, 'utf-8')
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import path from 'path'
|
|
||||||
import { app, BrowserWindow, screen } from 'electron'
|
import { app, BrowserWindow, screen } from 'electron'
|
||||||
import windowStateKeeper from 'electron-window-state'
|
import windowStateKeeper from 'electron-window-state'
|
||||||
import { getOsLineEndingName, loadMarkdownFile, getDefaultTextDirection } from './utils/filesystem'
|
import { getOsLineEndingName, loadMarkdownFile, getDefaultTextDirection } from './utils/filesystem'
|
||||||
import appMenu from './menu'
|
import appMenu from './menu'
|
||||||
import Watcher from './watcher'
|
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 { TITLE_BAR_HEIGHT, defaultWinOptions, isLinux } from './config'
|
||||||
import userPreference from './preference'
|
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 = {}) {
|
createWindow (pathname = null, markdown = '', options = {}) {
|
||||||
// Ensure path is normalized
|
// Ensure path is normalized
|
||||||
if (pathname) {
|
if (pathname) {
|
||||||
pathname = path.resolve(pathname)
|
pathname = normalizeAndResolvePath(pathname)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { windows } = this
|
const { windows } = this
|
||||||
@ -88,7 +94,7 @@ class AppWindow {
|
|||||||
mainWindowState.manage(win)
|
mainWindowState.manage(win)
|
||||||
win.show()
|
win.show()
|
||||||
|
|
||||||
// open single mrkdown file
|
// open single markdown file
|
||||||
if (pathname && isMarkdownFile(pathname)) {
|
if (pathname && isMarkdownFile(pathname)) {
|
||||||
appMenu.addRecentlyUsedDocument(pathname)
|
appMenu.addRecentlyUsedDocument(pathname)
|
||||||
loadMarkdownFile(pathname)
|
loadMarkdownFile(pathname)
|
||||||
|
Loading…
Reference in New Issue
Block a user