marktext/src/main/app/index.js
Ran Luo c239e99f1b
Refactor inline image to support paste/drop image (#1028)
* 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
2019-05-26 23:55:13 +08:00

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