mirror of
https://github.com/marktext/marktext.git
synced 2025-05-02 11:32:13 +08:00
Line ending (#234)
* Prepare line ending feature * Detect document line ending * Line ending conversion * Add "endOfLine" settings option * Add line ending menu * Notify user about mixed endings * Fixes * Change line ending menu entries to radio style
This commit is contained in:
parent
6cad091f6d
commit
dfffc73e69
3
.github/CHANGELOG.md
vendored
3
.github/CHANGELOG.md
vendored
@ -4,7 +4,8 @@
|
||||
|
||||
- feature: add editorFont setting in user preference. (#175) - Anderson
|
||||
- feature: line break, support event and import and export markdown - Jocs
|
||||
- feat: unindent list item - Jocs
|
||||
- feature: unindent list item - Jocs
|
||||
- feature: Support for CRLF and LF line endings
|
||||
|
||||
**:butterfly:Optimization**
|
||||
|
||||
|
@ -2,6 +2,7 @@ import path from 'path'
|
||||
import { dialog, ipcMain, BrowserWindow } from 'electron'
|
||||
import { IMAGE_EXTENSIONS } from '../config'
|
||||
import { searchFilesAndDir } from '../imagePathAutoComplement'
|
||||
import { updateLineEndingnMenu } from '../menu'
|
||||
import { log } from '../utils'
|
||||
|
||||
const getAndSendImagePath = (win, type) => {
|
||||
@ -37,10 +38,18 @@ ipcMain.on('AGANI::ask-for-image-auto-path', (e, { pathname, src }) => {
|
||||
.catch(log)
|
||||
})
|
||||
|
||||
ipcMain.on('AGANI::update-line-ending-menu', (e, lineEnding) => {
|
||||
updateLineEndingnMenu(lineEnding)
|
||||
})
|
||||
|
||||
export const edit = (win, type) => {
|
||||
win.webContents.send('AGANI::edit', { type })
|
||||
}
|
||||
|
||||
export const lineEnding = (win, lineEnding) => {
|
||||
win.webContents.send('AGANI::set-line-ending', { lineEnding })
|
||||
}
|
||||
|
||||
export const insertImage = (win, type) => {
|
||||
if (type === 'absolute' || type === 'relative') {
|
||||
getAndSendImagePath(win, type)
|
||||
|
@ -3,13 +3,15 @@
|
||||
import fs from 'fs'
|
||||
// import chokidar from 'chokidar'
|
||||
import path from 'path'
|
||||
import { app, BrowserWindow, dialog, ipcMain } from 'electron'
|
||||
import createWindow, { windows } from '../createWindow'
|
||||
import { BrowserWindow, dialog, ipcMain } from 'electron'
|
||||
import createWindow, { forceClose, windows } from '../createWindow'
|
||||
import { EXTENSION_HASN, EXTENSIONS } from '../config'
|
||||
import { writeFile, writeMarkdownFile } from '../filesystem'
|
||||
import { clearRecentlyUsedDocuments } from '../menu'
|
||||
import { getPath, isMarkdownFile, log, isFile } from '../utils'
|
||||
import userPreference from '../preference'
|
||||
|
||||
// TODO(fxha): Do we still need this?
|
||||
const watchAndReload = (pathname, win) => { // when i build, and failed.
|
||||
// const watcher = chokidar.watch(pathname, {
|
||||
// persistent: true
|
||||
@ -27,43 +29,6 @@ const watchAndReload = (pathname, win) => { // when i build, and failed.
|
||||
// })
|
||||
}
|
||||
|
||||
const writeFile = (pathname, content, extension, e, callback = null) => {
|
||||
if (pathname) {
|
||||
pathname = !extension || pathname.endsWith(extension) ? pathname : `${pathname}${extension}`
|
||||
fs.writeFile(pathname, content, 'utf-8', err => {
|
||||
if (err) log(err)
|
||||
if (callback) callback(err, pathname)
|
||||
})
|
||||
} else {
|
||||
log('[ERROR] Cannot save file without path.')
|
||||
}
|
||||
}
|
||||
|
||||
const writeMarkdownFile = (pathname, content, extension, isUtf8BomEncoded, win, e, quitAfterSave = false) => {
|
||||
if (isUtf8BomEncoded) {
|
||||
// js is call-by-value, so we can insert BOM
|
||||
content = '\uFEFF' + content
|
||||
}
|
||||
|
||||
writeFile(pathname, content, extension, e, (err, filePath) => {
|
||||
if (!err) e.sender.send('AGANI::file-saved-successfully')
|
||||
const filename = path.basename(filePath)
|
||||
if (e && filePath) e.sender.send('AGANI::set-pathname', { pathname: filePath, filename })
|
||||
if (!err && quitAfterSave) forceClose(win)
|
||||
})
|
||||
}
|
||||
|
||||
const forceClose = win => {
|
||||
if (!win) return
|
||||
if (windows.has(win.id)) {
|
||||
windows.delete(win.id)
|
||||
}
|
||||
win.destroy() // if use win.close(), it will cause a endless loop.
|
||||
if (windows.size === 0) {
|
||||
app.quit()
|
||||
}
|
||||
}
|
||||
|
||||
// handle the response from render process.
|
||||
const handleResponseForExport = (e, { type, content, filename, pathname }) => {
|
||||
const win = BrowserWindow.fromWebContents(e.sender)
|
||||
@ -85,27 +50,27 @@ const handleResponseForExport = (e, { type, content, filename, pathname }) => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleResponseForSave = (e, { markdown, pathname, isUtf8BomEncoded, quitAfterSave = false }) => {
|
||||
const handleResponseForSave = (e, { markdown, pathname, options, quitAfterSave = false }) => {
|
||||
const win = BrowserWindow.fromWebContents(e.sender)
|
||||
if (pathname) {
|
||||
writeMarkdownFile(pathname, markdown, '', isUtf8BomEncoded, win, e, quitAfterSave)
|
||||
writeMarkdownFile(pathname, markdown, '', options, win, e, quitAfterSave)
|
||||
} else {
|
||||
const filePath = dialog.showSaveDialog(win, {
|
||||
defaultPath: getPath('documents') + '/Untitled.md'
|
||||
})
|
||||
writeMarkdownFile(filePath, markdown, '.md', isUtf8BomEncoded, win, e, quitAfterSave)
|
||||
writeMarkdownFile(filePath, markdown, '.md', options, win, e, quitAfterSave)
|
||||
}
|
||||
}
|
||||
|
||||
ipcMain.on('AGANI::response-file-save-as', (e, { markdown, pathname, isUtf8BomEncoded }) => {
|
||||
ipcMain.on('AGANI::response-file-save-as', (e, { markdown, pathname, options }) => {
|
||||
const win = BrowserWindow.fromWebContents(e.sender)
|
||||
let filePath = dialog.showSaveDialog(win, {
|
||||
defaultPath: pathname || getPath('documents') + '/Untitled.md'
|
||||
})
|
||||
writeMarkdownFile(filePath, markdown, '.md', isUtf8BomEncoded, win, e)
|
||||
writeMarkdownFile(filePath, markdown, '.md', options, win, e)
|
||||
})
|
||||
|
||||
ipcMain.on('AGANI::response-close-confirm', (e, { filename, pathname, markdown, isUtf8BomEncoded }) => {
|
||||
ipcMain.on('AGANI::response-close-confirm', (e, { filename, pathname, markdown, options }) => {
|
||||
const win = BrowserWindow.fromWebContents(e.sender)
|
||||
dialog.showMessageBox(win, {
|
||||
type: 'warning',
|
||||
@ -122,7 +87,7 @@ ipcMain.on('AGANI::response-close-confirm', (e, { filename, pathname, markdown,
|
||||
break
|
||||
case 0:
|
||||
setTimeout(() => {
|
||||
handleResponseForSave(e, { pathname, markdown, isUtf8BomEncoded, quitAfterSave: true })
|
||||
handleResponseForSave(e, { pathname, markdown, options, quitAfterSave: true })
|
||||
})
|
||||
break
|
||||
}
|
||||
|
@ -35,3 +35,7 @@ export const VIEW_MENU_ITEM = {
|
||||
'Typewriter Mode': false,
|
||||
'Focus Mode': false
|
||||
}
|
||||
|
||||
export const LINE_ENDING_REG = /(?:\r\n|\n)/g
|
||||
export const LF_LINE_ENDING_REG = /(?:[^\r]\n)|(?:^\n$)/
|
||||
export const CRLF_LINE_ENDING_REG = /\r\n/
|
||||
|
@ -1,12 +1,13 @@
|
||||
'use strict'
|
||||
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { BrowserWindow, screen } from 'electron'
|
||||
import { app, BrowserWindow, screen } from 'electron'
|
||||
import windowStateKeeper from 'electron-window-state'
|
||||
import { addRecentlyUsedDocuments } from './menu'
|
||||
import { isMarkdownFile, log } from './utils'
|
||||
import { getOsLineEndingName, loadMarkdownFile } from './filesystem'
|
||||
import { addRecentlyUsedDocuments, updateLineEndingnMenu } from './menu'
|
||||
import { isMarkdownFile } from './utils'
|
||||
|
||||
let focusedWindowId = -1
|
||||
export const windows = new Map()
|
||||
|
||||
const ensureWindowPosition = mainWindowState => {
|
||||
@ -66,31 +67,24 @@ const createWindow = (pathname, options = {}) => {
|
||||
|
||||
if (pathname && isMarkdownFile(pathname)) {
|
||||
addRecentlyUsedDocuments(pathname)
|
||||
const filename = path.basename(pathname)
|
||||
fs.readFile(path.resolve(pathname), 'utf-8', (err, file) => {
|
||||
if (err) {
|
||||
log(err)
|
||||
return
|
||||
}
|
||||
|
||||
// check UTF-8 BOM (EF BB BF) encoding
|
||||
let isUtf8BomEncoded = file.length >= 1 && file.charCodeAt(0) === 0xFEFF
|
||||
if (isUtf8BomEncoded) {
|
||||
file = file.slice(1)
|
||||
}
|
||||
|
||||
win.webContents.send('AGANI::file-loaded', {
|
||||
file,
|
||||
filename,
|
||||
pathname,
|
||||
isUtf8BomEncoded
|
||||
})
|
||||
loadMarkdownFile(win, pathname)
|
||||
} else {
|
||||
const lineEnding = getOsLineEndingName()
|
||||
win.webContents.send('AGANI::set-line-ending', {
|
||||
lineEnding,
|
||||
ignoreSaveStatus: true
|
||||
})
|
||||
updateLineEndingnMenu(lineEnding)
|
||||
}
|
||||
})
|
||||
|
||||
win.on('focus', () => {
|
||||
win.webContents.send('AGANI::window-active-status', { status: true })
|
||||
|
||||
if (win.id !== focusedWindowId) {
|
||||
focusedWindowId = win.id
|
||||
win.webContents.send('AGANI::req-update-line-ending-menu')
|
||||
}
|
||||
})
|
||||
|
||||
win.on('blur', () => {
|
||||
@ -113,4 +107,15 @@ const createWindow = (pathname, options = {}) => {
|
||||
return win
|
||||
}
|
||||
|
||||
export const forceClose = win => {
|
||||
if (!win) return
|
||||
if (windows.has(win.id)) {
|
||||
windows.delete(win.id)
|
||||
}
|
||||
win.destroy() // if use win.close(), it will cause a endless loop.
|
||||
if (windows.size === 0) {
|
||||
app.quit()
|
||||
}
|
||||
}
|
||||
|
||||
export default createWindow
|
||||
|
113
src/main/filesystem.js
Normal file
113
src/main/filesystem.js
Normal file
@ -0,0 +1,113 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { LINE_ENDING_REG, LF_LINE_ENDING_REG, CRLF_LINE_ENDING_REG } from './config'
|
||||
import { forceClose } from './createWindow'
|
||||
import userPreference from './preference'
|
||||
import { log } from './utils'
|
||||
|
||||
const isWin = process.platform === 'win32'
|
||||
|
||||
const convertLineEndings = (text, lineEnding) => {
|
||||
return text.replace(LINE_ENDING_REG, getLineEnding(lineEnding))
|
||||
}
|
||||
|
||||
export const getOsLineEndingName = () => {
|
||||
const { endOfLine } = userPreference.getAll()
|
||||
if (endOfLine === 'lf') {
|
||||
return 'lf'
|
||||
}
|
||||
return endOfLine === 'crlf' || isWin ? 'crlf' : 'lf'
|
||||
}
|
||||
|
||||
const getLineEnding = lineEnding => {
|
||||
if (lineEnding === 'lf') {
|
||||
return '\n'
|
||||
} else if (lineEnding === 'crlf') {
|
||||
return '\r\n'
|
||||
}
|
||||
return getOsLineEndingName() === 'crlf' ? '\r\n' : '\n'
|
||||
}
|
||||
|
||||
export const writeFile = (pathname, content, extension, e, callback = null) => {
|
||||
if (pathname) {
|
||||
pathname = !extension || pathname.endsWith(extension) ? pathname : `${pathname}${extension}`
|
||||
fs.writeFile(pathname, content, 'utf-8', err => {
|
||||
if (err) log(err)
|
||||
if (callback) callback(err, pathname)
|
||||
})
|
||||
} else {
|
||||
log('[ERROR] Cannot save file without path.')
|
||||
}
|
||||
}
|
||||
|
||||
export const writeMarkdownFile = (pathname, content, extension, options, win, e, quitAfterSave = false) => {
|
||||
const { adjustLineEndingOnSave, isUtf8BomEncoded, lineEnding } = options
|
||||
if (isUtf8BomEncoded) {
|
||||
content = '\uFEFF' + content
|
||||
}
|
||||
|
||||
if (adjustLineEndingOnSave) {
|
||||
content = convertLineEndings(content, lineEnding)
|
||||
}
|
||||
|
||||
writeFile(pathname, content, extension, e, (err, filePath) => {
|
||||
if (!err) e.sender.send('AGANI::file-saved-successfully')
|
||||
const filename = path.basename(filePath)
|
||||
if (e && filePath) e.sender.send('AGANI::set-pathname', { pathname: filePath, filename })
|
||||
if (!err && quitAfterSave) forceClose(win)
|
||||
})
|
||||
}
|
||||
|
||||
export const loadMarkdownFile = (win, pathname) => {
|
||||
fs.readFile(path.resolve(pathname), 'utf-8', (err, file) => {
|
||||
if (err) {
|
||||
log(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Check UTF-8 BOM (EF BB BF) encoding
|
||||
const isUtf8BomEncoded = file.length >= 1 && file.charCodeAt(0) === 0xFEFF
|
||||
if (isUtf8BomEncoded) {
|
||||
file = file.slice(1)
|
||||
}
|
||||
|
||||
// Detect line ending
|
||||
const isLf = LF_LINE_ENDING_REG.test(file)
|
||||
const isCrlf = CRLF_LINE_ENDING_REG.test(file)
|
||||
const isMixed = isLf && isCrlf
|
||||
const isUnknownEnding = !isLf && !isCrlf
|
||||
let lineEnding = getOsLineEndingName()
|
||||
if (isLf && !isCrlf) {
|
||||
lineEnding = 'lf'
|
||||
} else if (isCrlf && !isLf) {
|
||||
lineEnding = 'crlf'
|
||||
}
|
||||
|
||||
let adjustLineEndingOnSave = false
|
||||
if (isMixed || isUnknownEnding || lineEnding !== 'lf') {
|
||||
adjustLineEndingOnSave = lineEnding !== 'lf'
|
||||
// Convert to LF for internal use.
|
||||
file = convertLineEndings(file, 'lf')
|
||||
}
|
||||
|
||||
const filename = path.basename(pathname)
|
||||
win.webContents.send('AGANI::file-loaded', {
|
||||
file,
|
||||
filename,
|
||||
pathname,
|
||||
options: {
|
||||
isUtf8BomEncoded,
|
||||
lineEnding,
|
||||
adjustLineEndingOnSave
|
||||
}
|
||||
})
|
||||
|
||||
// Notify user about mixed endings
|
||||
if (isMixed) {
|
||||
win.webContents.send('AGANI::show-info-notification', {
|
||||
msg: `The document has mixed line endings which are automatically normalized to ${lineEnding.toUpperCase()}.`,
|
||||
timeout: 20000
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
@ -84,3 +84,14 @@ export const updateApplicationMenu = (recentUsedDocuments) => {
|
||||
}
|
||||
initMacDock = true
|
||||
}
|
||||
|
||||
export const updateLineEndingnMenu = lineEnding => {
|
||||
const menus = Menu.getApplicationMenu()
|
||||
const crlfMenu = menus.getMenuItemById('crlfLineEndingMenuEntry')
|
||||
const lfMenu = menus.getMenuItemById('lfLineEndingMenuEntry')
|
||||
if (lineEnding === 'crlf') {
|
||||
crlfMenu.checked = true
|
||||
} else {
|
||||
lfMenu.checked = true
|
||||
}
|
||||
}
|
||||
|
@ -88,5 +88,24 @@ export default {
|
||||
actions.insertImage(browserWindow, 'upload')
|
||||
}
|
||||
}]
|
||||
}, {
|
||||
type: 'separator'
|
||||
}, {
|
||||
label: 'Line Ending',
|
||||
submenu: [{
|
||||
id: 'crlfLineEndingMenuEntry',
|
||||
label: 'Carriage return and line feed (CRLF)',
|
||||
type: 'radio',
|
||||
click (menuItem, browserWindow) {
|
||||
actions.lineEnding(browserWindow, 'crlf')
|
||||
}
|
||||
}, {
|
||||
id: 'lfLineEndingMenuEntry',
|
||||
label: 'Line feed (LF)',
|
||||
type: 'radio',
|
||||
click (menuItem, browserWindow) {
|
||||
actions.lineEnding(browserWindow, 'lf')
|
||||
}
|
||||
}]
|
||||
}]
|
||||
}
|
||||
|
@ -20,6 +20,8 @@ export const getPath = directory => {
|
||||
}
|
||||
|
||||
export const getMenuItem = menuName => {
|
||||
// TODO(fxha): Please use menu id attribute to find menu entries. This will
|
||||
// cause problems with internationalization later!
|
||||
const menus = Menu.getApplicationMenu()
|
||||
return menus.items.find(menu => menu.label === menuName)
|
||||
}
|
||||
|
@ -98,6 +98,8 @@
|
||||
dispatch('LISTEN_FOR_RENAME')
|
||||
dispatch('LISTEN_FOR_IMAGE_PATH')
|
||||
dispatch('LISTEN_FOR_FILE_SAVED_SUCCESSFULLY')
|
||||
dispatch('LINTEN_FOR_SET_LINE_ENDING')
|
||||
dispatch('LISTEN_FOR_NOTIFICATION')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -1,10 +1,11 @@
|
||||
<template>
|
||||
<div
|
||||
class="bottom-status"
|
||||
:class="{'error': error}"
|
||||
v-show="showStatus"
|
||||
>
|
||||
<div class="status-wrapper">
|
||||
<span class="message" :class="{'error': error}">{{ message }}</span>
|
||||
<span class="message" :title="message">{{ message }}</span>
|
||||
<span class="yes" v-show="showYes" @click="handleYesClick">[ Y ]</span>
|
||||
<span class="no" @click="close(true)">[ X ]</span>
|
||||
</div>
|
||||
@ -31,10 +32,15 @@
|
||||
this.error = true
|
||||
this.message = msg
|
||||
})
|
||||
bus.$on('status-message', msg => {
|
||||
bus.$on('status-message', (msg, timeout) => {
|
||||
this.showStatus = true
|
||||
this.error = false
|
||||
this.message = msg
|
||||
if (timeout) {
|
||||
setTimeout(() => {
|
||||
this.close(true)
|
||||
}, timeout)
|
||||
}
|
||||
})
|
||||
bus.$on('status-promote', (msg, eventId) => {
|
||||
this.showStatus = true
|
||||
@ -72,12 +78,17 @@
|
||||
.bottom-status {
|
||||
width: 100%;
|
||||
height: 25px;
|
||||
background-color: #2196F3;
|
||||
color: #fff;
|
||||
}
|
||||
.bottom-status.error {
|
||||
background-color: #F44336;
|
||||
}
|
||||
.status-wrapper {
|
||||
text-align: center;
|
||||
line-height: 25px;
|
||||
font-size: 13px;
|
||||
color: rgb(136, 170, 204);
|
||||
color: #fff;
|
||||
}
|
||||
.message {
|
||||
max-width: 70%;
|
||||
@ -89,12 +100,9 @@
|
||||
.message, .yes {
|
||||
margin-right: 5px;
|
||||
}
|
||||
.message.error {
|
||||
color: #E6A23C;
|
||||
}
|
||||
.yes, .no {
|
||||
vertical-align: top;
|
||||
color: rgb(79, 183, 221);
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
@ -5,8 +5,8 @@ export const error = msg => {
|
||||
bus.$emit('status-error', msg)
|
||||
}
|
||||
|
||||
export const message = msg => {
|
||||
bus.$emit('status-message', msg)
|
||||
export const message = (msg, timeout) => {
|
||||
bus.$emit('status-message', msg, timeout)
|
||||
}
|
||||
|
||||
export const promote = msg => {
|
||||
|
@ -30,6 +30,8 @@ const state = {
|
||||
isSaved: true,
|
||||
markdown: '',
|
||||
isUtf8BomEncoded: false,
|
||||
lineEnding: 'lf', // lf or crlf
|
||||
adjustLineEndingOnSave: false,
|
||||
cursor: null,
|
||||
windowActive: true,
|
||||
wordCount: {
|
||||
@ -41,6 +43,11 @@ const state = {
|
||||
platform: process.platform
|
||||
}
|
||||
|
||||
export const getOptionsFromState = state => {
|
||||
const { isUtf8BomEncoded, lineEnding, adjustLineEndingOnSave } = state
|
||||
return { isUtf8BomEncoded, lineEnding, adjustLineEndingOnSave }
|
||||
}
|
||||
|
||||
const mutations = {
|
||||
SET_MODE (state, { type, checked }) {
|
||||
state[type] = checked
|
||||
@ -67,6 +74,12 @@ const mutations = {
|
||||
SET_IS_UTF8_BOM_ENCODED (state, isUtf8BomEncoded) {
|
||||
state.isUtf8BomEncoded = isUtf8BomEncoded
|
||||
},
|
||||
SET_LINE_ENDING (state, lineEnding) {
|
||||
state.lineEnding = lineEnding
|
||||
},
|
||||
SET_ADJUST_LINE_ENDING_ON_SAVE (state, adjustLineEndingOnSave) {
|
||||
state.adjustLineEndingOnSave = adjustLineEndingOnSave
|
||||
},
|
||||
SET_WORD_COUNT (state, wordCount) {
|
||||
state.wordCount = wordCount
|
||||
},
|
||||
@ -115,11 +128,11 @@ const actions = {
|
||||
|
||||
// handle autoSave
|
||||
if (autoSave) {
|
||||
const { pathname, markdown, isUtf8BomEncoded } = state
|
||||
|
||||
const { pathname, markdown } = state
|
||||
const options = getOptionsFromState(state)
|
||||
if (autoSave && pathname) {
|
||||
commit('SET_SAVE_STATUS', true)
|
||||
ipcRenderer.send('AGANI::response-file-save', { pathname, markdown, isUtf8BomEncoded })
|
||||
ipcRenderer.send('AGANI::response-file-save', { pathname, markdown, options })
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -141,31 +154,38 @@ const actions = {
|
||||
})
|
||||
},
|
||||
|
||||
LINTEN_WIN_STATUS ({ commit }) {
|
||||
LINTEN_WIN_STATUS ({ commit, state }) {
|
||||
ipcRenderer.on('AGANI::window-active-status', (e, { status }) => {
|
||||
commit('SET_WIN_STATUS', status)
|
||||
})
|
||||
ipcRenderer.on('AGANI::req-update-line-ending-menu', e => {
|
||||
const { lineEnding } = state
|
||||
ipcRenderer.send('AGANI::update-line-ending-menu', lineEnding)
|
||||
})
|
||||
},
|
||||
|
||||
LISTEN_FOR_SAVE ({ commit, state }) {
|
||||
ipcRenderer.on('AGANI::ask-file-save', () => {
|
||||
const { pathname, markdown, isUtf8BomEncoded } = state
|
||||
ipcRenderer.send('AGANI::response-file-save', { pathname, markdown, isUtf8BomEncoded })
|
||||
const { pathname, markdown } = state
|
||||
const options = getOptionsFromState(state)
|
||||
ipcRenderer.send('AGANI::response-file-save', { pathname, markdown, options })
|
||||
})
|
||||
},
|
||||
|
||||
LISTEN_FOR_SAVE_AS ({ commit, state }) {
|
||||
ipcRenderer.on('AGANI::ask-file-save-as', () => {
|
||||
const { pathname, markdown, isUtf8BomEncoded } = state
|
||||
ipcRenderer.send('AGANI::response-file-save-as', { pathname, markdown, isUtf8BomEncoded })
|
||||
const { pathname, markdown } = state
|
||||
const options = getOptionsFromState(state)
|
||||
ipcRenderer.send('AGANI::response-file-save-as', { pathname, markdown, options })
|
||||
})
|
||||
},
|
||||
|
||||
LISTEN_FOR_MOVE_TO ({ commit, state }) {
|
||||
ipcRenderer.on('AGANI::ask-file-move-to', () => {
|
||||
const { pathname, markdown, isUtf8BomEncoded } = state
|
||||
const { pathname, markdown } = state
|
||||
const options = getOptionsFromState(state)
|
||||
if (!pathname) {
|
||||
ipcRenderer.send('AGANI::response-file-save', { pathname, markdown, isUtf8BomEncoded })
|
||||
ipcRenderer.send('AGANI::response-file-save', { pathname, markdown, options })
|
||||
} else {
|
||||
ipcRenderer.send('AGANI::response-file-move-to', { pathname })
|
||||
}
|
||||
@ -174,9 +194,10 @@ const actions = {
|
||||
|
||||
LISTEN_FOR_RENAME ({ commit, state }) {
|
||||
ipcRenderer.on('AGANI::ask-file-rename', () => {
|
||||
const { pathname, markdown, isUtf8BomEncoded } = state
|
||||
const { pathname, markdown } = state
|
||||
const options = getOptionsFromState(state)
|
||||
if (!pathname) {
|
||||
ipcRenderer.send('AGANI::response-file-save', { pathname, markdown, isUtf8BomEncoded })
|
||||
ipcRenderer.send('AGANI::response-file-save', { pathname, markdown, options })
|
||||
} else {
|
||||
bus.$emit('rename')
|
||||
}
|
||||
@ -200,13 +221,17 @@ const actions = {
|
||||
},
|
||||
|
||||
LISTEN_FOR_FILE_LOAD ({ commit, state }) {
|
||||
ipcRenderer.on('AGANI::file-loaded', (e, { file, filename, pathname, isUtf8BomEncoded }) => {
|
||||
ipcRenderer.on('AGANI::file-loaded', (e, { file, filename, pathname, options }) => {
|
||||
const { adjustLineEndingOnSave, isUtf8BomEncoded, lineEnding } = options
|
||||
commit('SET_FILENAME', filename)
|
||||
commit('SET_PATHNAME', pathname)
|
||||
commit('SET_MARKDOWN', file)
|
||||
commit('SET_SAVE_STATUS', true)
|
||||
commit('SET_IS_UTF8_BOM_ENCODED', isUtf8BomEncoded)
|
||||
commit('SET_LINE_ENDING', lineEnding)
|
||||
commit('SET_ADJUST_LINE_ENDING_ON_SAVE', adjustLineEndingOnSave)
|
||||
bus.$emit('file-loaded', file)
|
||||
ipcRenderer.send('AGANI::update-line-ending-menu', lineEnding)
|
||||
})
|
||||
},
|
||||
|
||||
@ -229,7 +254,8 @@ const actions = {
|
||||
},
|
||||
|
||||
LISTEN_FOR_CONTENT_CHANGE ({ commit, state }, { markdown, wordCount, cursor }) {
|
||||
const { pathname, autoSave, markdown: oldMarkdown, isUtf8BomEncoded } = state
|
||||
const { pathname, autoSave, markdown: oldMarkdown } = state
|
||||
const options = getOptionsFromState(state)
|
||||
commit('SET_MARKDOWN', markdown)
|
||||
// set word count
|
||||
if (wordCount) commit('SET_WORD_COUNT', wordCount)
|
||||
@ -238,7 +264,7 @@ const actions = {
|
||||
// change save status/save to file only when the markdown changed!
|
||||
if (markdown !== oldMarkdown) {
|
||||
if (pathname && autoSave) {
|
||||
ipcRenderer.send('AGANI::response-file-save', { pathname, markdown, isUtf8BomEncoded })
|
||||
ipcRenderer.send('AGANI::response-file-save', { pathname, markdown, options })
|
||||
} else {
|
||||
commit('SET_SAVE_STATUS', false)
|
||||
}
|
||||
@ -323,13 +349,27 @@ const actions = {
|
||||
|
||||
LISTEN_FOR_CLOSE ({ commit, state }) {
|
||||
ipcRenderer.on('AGANI::ask-for-close', e => {
|
||||
const { isSaved, markdown, pathname, filename, isUtf8BomEncoded } = state
|
||||
const { isSaved, markdown, pathname, filename } = state
|
||||
const options = getOptionsFromState(state)
|
||||
if (!isSaved && /[^\n]/.test(markdown)) {
|
||||
ipcRenderer.send('AGANI::response-close-confirm', { filename, pathname, markdown, isUtf8BomEncoded })
|
||||
ipcRenderer.send('AGANI::response-close-confirm', { filename, pathname, markdown, options })
|
||||
} else {
|
||||
ipcRenderer.send('AGANI::close-window')
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
LINTEN_FOR_SET_LINE_ENDING ({ commit, state }) {
|
||||
ipcRenderer.on('AGANI::set-line-ending', (e, { lineEnding, ignoreSaveStatus }) => {
|
||||
const { lineEnding: oldLineEnding } = state
|
||||
if (lineEnding !== oldLineEnding) {
|
||||
commit('SET_LINE_ENDING', lineEnding)
|
||||
commit('SET_ADJUST_LINE_ENDING_ON_SAVE', lineEnding !== 'lf')
|
||||
if (!ignoreSaveStatus) {
|
||||
commit('SET_SAVE_STATUS', false)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,13 +4,15 @@ import Vuex from 'vuex'
|
||||
import editorStore from './editor'
|
||||
import aidouStore from './aidou'
|
||||
import autoUpdates from './autoUpdates'
|
||||
import notification from './notification'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
const storeArray = [
|
||||
editorStore,
|
||||
aidouStore,
|
||||
autoUpdates
|
||||
autoUpdates,
|
||||
notification
|
||||
]
|
||||
|
||||
const { actions, mutations, state } = storeArray.reduce((acc, s) => {
|
||||
|
21
src/renderer/store/notification.js
Normal file
21
src/renderer/store/notification.js
Normal file
@ -0,0 +1,21 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import { error, message } from '../notice'
|
||||
|
||||
const state = {
|
||||
}
|
||||
|
||||
const mutations = {
|
||||
}
|
||||
|
||||
const actions = {
|
||||
LISTEN_FOR_NOTIFICATION ({ commit }) {
|
||||
ipcRenderer.on('AGANI::show-error-notification', (e, msg) => {
|
||||
error(msg)
|
||||
})
|
||||
ipcRenderer.on('AGANI::show-info-notification', (e, { msg, timeout }) => {
|
||||
message(msg, timeout)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default { state, mutations, actions }
|
@ -1,11 +1,13 @@
|
||||
### :bust_in_silhouette:User Preferences
|
||||
|
||||
Edit and save to update preferences. You can only change the JSON below!
|
||||
Edit and save to update preferences. You can only change the JSON below!
|
||||
|
||||
- **theme**: *String* `dark` or `light`
|
||||
|
||||
- **autoSave**: *Boolean* `true` or `false`
|
||||
|
||||
- **endOfLine**: *String* `lf`, `crlf` or `default`
|
||||
|
||||
```json
|
||||
{
|
||||
"fontSize": "16px",
|
||||
@ -19,7 +21,8 @@ Edit and save to update preferences. You can only change the JSON below!
|
||||
"preferLooseListItem": true,
|
||||
"autoPairBracket": true,
|
||||
"autoPairMarkdownSyntax": true,
|
||||
"autoPairQuote": true
|
||||
"autoPairQuote": true,
|
||||
"endOfLine": "default"
|
||||
}
|
||||
```
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user