mirror of
https://github.com/marktext/marktext.git
synced 2025-05-02 10:11:56 +08:00

* feat: image setting * opti: inline image * add imageSelectAction * remove axios from muya * update image selector * finish image selector ui * add load success style * delete image by click delete icon * opti structure of image html * handle arrow key * enter to edit * image preview by press space * handle backspace when the previous element is image wrapper * update codes for change another PC * emable select all in input * handle arrow and backspace key * create a new paragraph after the last paragraph if its not empty * handle backspace when the previous element is image wrapper * handle enter event in image selector * rewrite auto show image selector * modify image folder * copy file to folder * select image * handle paste image * picgo * guess image path from clipboard * drag and drop image to Mark Text * add github uploader * remove unused codes * remove unused codes * rewrite image path auto complete * support `path` imageInsertAction * doc: add image uploader doc * remove debug codes * set init value in image uploader page * fix typo * remove unused codes * drag web image to Mark Text * add save notification * opti uploading process * fix did not close image selector bug * check image content type when drag web link image * fix: unable to preview relative path image. * emit change event after paste/drop image * add url map in image selector * feat: screenshot and auto insert the screenshot image * update error handler * feat: use the native screencapture command line on macOs system * opti: drop image * fix: handle enter error when cursor is after a image * fix: hasOwnProperty error * remove debug codes * fix: backspace when the previous ele is image * fix: CI error and optimize some codes * use hash of file path to generate the copied filename * change default imageInsertAction to `path` * fix: typo * remove some unused codes and opti get image file name * fix some bugs and opti codes * update image edit icon * romove screen capture on Linux and Windows * fix: conflict * fix error that can not insert image after the existed image or before existed image
335 lines
10 KiB
JavaScript
335 lines
10 KiB
JavaScript
import path from 'path'
|
|
import fse from 'fs-extra'
|
|
import log from 'electron-log'
|
|
import { exec } from 'child_process'
|
|
import { app, ipcMain, systemPreferences, clipboard } from 'electron'
|
|
import dayjs from 'dayjs'
|
|
import { isLinux, isOsx } from '../config'
|
|
import { isDirectory, isMarkdownFileOrLink, normalizeAndResolvePath } from '../filesystem'
|
|
import { getMenuItemById } from '../menu'
|
|
import { selectTheme } from '../menu/actions/theme'
|
|
import { dockMenu } from '../menu/templates'
|
|
import { watchers } from '../utils/imagePathAutoComplement'
|
|
import EditorWindow from '../windows/editor'
|
|
import SettingWindow from '../windows/setting'
|
|
import { WindowType } from './windowManager'
|
|
// import ShortcutCapture from 'shortcut-capture'
|
|
|
|
class App {
|
|
|
|
/**
|
|
* @param {Accessor} accessor The application accessor for application instances.
|
|
* @param {arg.Result} args Parsed application arguments.
|
|
*/
|
|
constructor (accessor, args) {
|
|
this._accessor = accessor
|
|
this._args = args || {_: []}
|
|
this._openFilesCache = []
|
|
this._openFilesTimer = null
|
|
this._windowManager = this._accessor.windowManager
|
|
// this.launchScreenshotWin = null // The window which call the screenshot.
|
|
// this.shortcutCapture = null
|
|
|
|
this._listenForIpcMain()
|
|
}
|
|
|
|
/**
|
|
* The entry point into the application.
|
|
*/
|
|
init () {
|
|
// Enable these features to use `backdrop-filter` css rules!
|
|
if (isOsx) {
|
|
app.commandLine.appendSwitch('enable-experimental-web-platform-features', 'true')
|
|
}
|
|
|
|
app.on('open-file', this.openFile) // macOS only
|
|
|
|
app.on('ready', this.ready)
|
|
|
|
app.on('window-all-closed', () => {
|
|
// Close all the image path watcher
|
|
for (const watcher of watchers.values()) {
|
|
watcher.close()
|
|
}
|
|
this._windowManager.closeWatcher()
|
|
if (!isOsx) {
|
|
app.quit()
|
|
}
|
|
})
|
|
|
|
app.on('activate', () => { // macOS only
|
|
// On OS X it's common to re-create a window in the app when the
|
|
// dock icon is clicked and there are no other windows open.
|
|
if (this._windowManager.windowCount === 0) {
|
|
this.ready()
|
|
}
|
|
})
|
|
|
|
// Prevent to load webview and opening links or new windows via HTML/JS.
|
|
app.on('web-contents-created', (event, contents) => {
|
|
contents.on('will-attach-webview', (event, webPreferences, params) => {
|
|
console.warn('Prevented webview creation.')
|
|
event.preventDefault()
|
|
})
|
|
contents.on('will-navigate', event => {
|
|
console.warn('Prevented opening a link.')
|
|
event.preventDefault()
|
|
})
|
|
contents.on('new-window', (event, url) => {
|
|
console.warn('Prevented opening a new window.')
|
|
event.preventDefault()
|
|
})
|
|
})
|
|
}
|
|
|
|
get screenshotFileName () {
|
|
const screenshotFolderPath = this._accessor.dataCenter.getItem('screenshotFolderPath')
|
|
const fileName = `${dayjs().format('YYYY-MM-DD-HH-mm-ss')}-screenshot.png`
|
|
return path.join(screenshotFolderPath, fileName)
|
|
}
|
|
|
|
ready = () => {
|
|
const { _args: args } = this
|
|
if (!isOsx && args._.length) {
|
|
for (const pathname of args._) {
|
|
// Ignore all unknown flags
|
|
if (pathname.startsWith('--')) {
|
|
continue
|
|
}
|
|
|
|
const info = this.normalizePath(pathname)
|
|
if (info) {
|
|
this._openFilesCache.push(info)
|
|
}
|
|
}
|
|
}
|
|
|
|
if (process.platform === 'darwin') {
|
|
app.dock.setMenu(dockMenu)
|
|
|
|
// Listen for system theme change and change Mark Text own `dark` and `light`.
|
|
// In macOS 10.14 Mojave, Apple introduced a new system-wide dark mode for
|
|
// all macOS computers.
|
|
systemPreferences.subscribeNotification(
|
|
'AppleInterfaceThemeChangedNotification',
|
|
() => {
|
|
const preferences = this._accessor.preferences
|
|
const { theme } = preferences.getAll()
|
|
let setedTheme = null
|
|
if (systemPreferences.isDarkMode() && theme !== 'dark') {
|
|
selectTheme('dark')
|
|
setedTheme = 'dark'
|
|
}
|
|
if (!systemPreferences.isDarkMode() && theme === 'dark') {
|
|
selectTheme('light')
|
|
setedTheme = 'light'
|
|
}
|
|
if (setedTheme) {
|
|
const themeMenu = getMenuItemById('themeMenu')
|
|
const menuItem = themeMenu.submenu.items.filter(item => (item.id === setedTheme))[0]
|
|
if (menuItem) {
|
|
menuItem.checked = true
|
|
}
|
|
}
|
|
}
|
|
)
|
|
}
|
|
|
|
if (this._openFilesCache.length) {
|
|
this.openFileCache()
|
|
} else {
|
|
this.createEditorWindow()
|
|
}
|
|
// this.shortcutCapture = new ShortcutCapture()
|
|
// if (process.env.NODE_ENV === 'development') {
|
|
// this.shortcutCapture.dirname = path.resolve(path.join(__dirname, '../../../node_modules/shortcut-capture'))
|
|
// }
|
|
// this.shortcutCapture.on('capture', async ({ dataURL }) => {
|
|
// const { screenshotFileName } = this
|
|
// const image = nativeImage.createFromDataURL(dataURL)
|
|
// const bufferImage = image.toPNG()
|
|
|
|
// if (this.launchScreenshotWin) {
|
|
// this.launchScreenshotWin.webContents.send('mt::screenshot-captured')
|
|
// this.launchScreenshotWin = null
|
|
// }
|
|
|
|
// try {
|
|
// // write screenshot image into screenshot folder.
|
|
// await fse.writeFile(screenshotFileName, bufferImage)
|
|
// } catch (err) {
|
|
// log.error(err)
|
|
// }
|
|
// })
|
|
}
|
|
|
|
openFile = (event, pathname) => {
|
|
event.preventDefault()
|
|
const info = this.normalizePath(pathname)
|
|
if (info) {
|
|
this._openFilesCache.push(info)
|
|
|
|
if (app.isReady()) {
|
|
// It might come more files
|
|
if (this._openFilesTimer) {
|
|
clearTimeout(this._openFilesTimer)
|
|
}
|
|
this._openFilesTimer = setTimeout(() => {
|
|
this._openFilesTimer = null
|
|
this.openFileCache()
|
|
}, 100)
|
|
}
|
|
}
|
|
}
|
|
|
|
openFileCache = () => {
|
|
// TODO: Allow to open multiple files in the same window.
|
|
this._openFilesCache.forEach(fileInfo => this.createEditorWindow(fileInfo.path))
|
|
this._openFilesCache.length = 0 // empty the open file path cache
|
|
}
|
|
|
|
normalizePath = pathname => {
|
|
const isDir = isDirectory(pathname)
|
|
if (isDir || isMarkdownFileOrLink(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
|
|
}
|
|
|
|
// --- private --------------------------------
|
|
|
|
/**
|
|
* Creates a new editor window.
|
|
*
|
|
* @param {string} [pathname] Path to a file, directory or link.
|
|
* @param {string} [markdown] Markdown content.
|
|
* @param {*} [options] BrowserWindow options.
|
|
*/
|
|
createEditorWindow (pathname = null, markdown = '', options = {}) {
|
|
const editor = new EditorWindow(this._accessor)
|
|
editor.createWindow(pathname, markdown, options)
|
|
this._windowManager.add(editor)
|
|
if (this._windowManager.windowCount === 1) {
|
|
this._accessor.menu.setActiveWindow(editor.id)
|
|
}
|
|
}
|
|
/**
|
|
* Create a new setting window.
|
|
*/
|
|
createSettingWindow () {
|
|
const setting = new SettingWindow(this._accessor)
|
|
setting.createWindow()
|
|
this._windowManager.add(setting)
|
|
if (this._windowManager.windowCount === 1) {
|
|
this._accessor.menu.setActiveWindow(setting.id)
|
|
}
|
|
}
|
|
|
|
// TODO(sessions): ...
|
|
// // Make Mark Text a single instance application.
|
|
// _makeSingleInstance() {
|
|
// if (process.mas) return
|
|
//
|
|
// app.requestSingleInstanceLock()
|
|
//
|
|
// app.on('second-instance', (event, argv, workingDirectory) => {
|
|
// // // TODO: Get active/last active window and open process arvg etc
|
|
// // if (currentWindow) {
|
|
// // if (currentWindow.isMinimized()) currentWindow.restore()
|
|
// // currentWindow.focus()
|
|
// // }
|
|
// })
|
|
// }
|
|
|
|
_listenForIpcMain () {
|
|
ipcMain.on('app-create-editor-window', () => {
|
|
this.createEditorWindow()
|
|
})
|
|
|
|
ipcMain.on('screen-capture', win => {
|
|
if (isOsx) {
|
|
// Use macOs `screencapture` command line when in macOs system.
|
|
const { screenshotFileName } = this
|
|
exec(`screencapture -i -c`, async (err) => {
|
|
if (err) {
|
|
log.error(err)
|
|
return
|
|
}
|
|
try {
|
|
// Write screenshot image into screenshot folder.
|
|
const image = clipboard.readImage()
|
|
const bufferImage = image.toPNG()
|
|
await fse.writeFile(screenshotFileName, bufferImage)
|
|
} catch (err) {
|
|
log.error(err)
|
|
}
|
|
win.webContents.send('mt::screenshot-captured')
|
|
})
|
|
} else {
|
|
// Do nothing, maybe we'll add screenCapture later on Linux and Windows.
|
|
// if (this.shortcutCapture) {
|
|
// this.launchScreenshotWin = win
|
|
// this.shortcutCapture.shortcutCapture()
|
|
// }
|
|
}
|
|
})
|
|
|
|
ipcMain.on('app-create-settings-window', () => {
|
|
const settingWins = this._windowManager.windowsOfType(WindowType.SETTING)
|
|
if (settingWins.length >= 1) {
|
|
// A setting window is already created
|
|
const browserSettingWindow = settingWins[0].win.browserWindow
|
|
if (isLinux) {
|
|
browserSettingWindow.focus()
|
|
} else {
|
|
browserSettingWindow.moveTop()
|
|
}
|
|
return
|
|
}
|
|
this.createSettingWindow()
|
|
})
|
|
|
|
// ipcMain.on('app-open-file', filePath => {
|
|
// const windowId = this._windowManager.getActiveWindow()
|
|
// ipcMain.emit('app-open-file-by-id', windowId, filePath)
|
|
// })
|
|
|
|
ipcMain.on('app-open-file-by-id', (windowId, filePath) => {
|
|
const { openFilesInNewWindow } = this._accessor.preferences.getAll()
|
|
if (openFilesInNewWindow) {
|
|
this.createEditorWindow(filePath)
|
|
} else {
|
|
const editor = this._windowManager.get(windowId)
|
|
if (editor && !editor.quitting) {
|
|
editor.openTab(filePath, true)
|
|
}
|
|
}
|
|
})
|
|
|
|
ipcMain.on('app-open-markdown-by-id', (windowId, data) => {
|
|
const { openFilesInNewWindow } = this._accessor.preferences.getAll()
|
|
if (openFilesInNewWindow) {
|
|
this.createEditorWindow(undefined, data)
|
|
} else {
|
|
const editor = this._windowManager.get(windowId)
|
|
if (editor && !editor.quitting) {
|
|
editor.openUntitledTab(true, data)
|
|
}
|
|
}
|
|
})
|
|
|
|
ipcMain.on('app-open-directory-by-id', (windowId, pathname) => {
|
|
// TODO: Open the directory in an existing window if prefered.
|
|
this.createEditorWindow(pathname)
|
|
})
|
|
}
|
|
}
|
|
|
|
export default App
|