mirror of
https://github.com/marktext/marktext.git
synced 2025-05-02 04:50:09 +08:00
User-defined open files (#735)
This commit is contained in:
parent
44c537a62c
commit
cbd90bfeb4
3
doc/wip/README.md
Normal file
3
doc/wip/README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Internal Documentation
|
||||||
|
|
||||||
|
WIP documentation of Mark Text.
|
109
doc/wip/renderer/editor.md
Normal file
109
doc/wip/renderer/editor.md
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
# Editor
|
||||||
|
|
||||||
|
TBD
|
||||||
|
|
||||||
|
## Internal
|
||||||
|
|
||||||
|
### Raw markdown document
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface IMarkdownDocumentRaw
|
||||||
|
{
|
||||||
|
// Markdown content
|
||||||
|
markdown: string,
|
||||||
|
// Filename
|
||||||
|
filename: string,
|
||||||
|
// Full path (may be empty?)
|
||||||
|
pathname: string,
|
||||||
|
|
||||||
|
// Indicates whether the document is UTF8 or UTF8-DOM encoded.
|
||||||
|
isUtf8BomEncoded: boolean,
|
||||||
|
// "lf" or "crlf"
|
||||||
|
lineEnding: string,
|
||||||
|
// Convert document ("lf") to `lineEnding` when saving
|
||||||
|
adjustLineEndingOnSave: boolean
|
||||||
|
|
||||||
|
// Whether the document has mixed line endings (lf and crlf) and was converted to lf.
|
||||||
|
isMixedLineEndings: boolean
|
||||||
|
|
||||||
|
// TODO(refactor:renderer/editor): Remove this entry! This should be loaded separately if needed.
|
||||||
|
textDirection: boolean
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Markdowm document
|
||||||
|
|
||||||
|
A markdown document (`IMarkdownDocument`) represent a file.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface IMarkdownDocument
|
||||||
|
{
|
||||||
|
// Markdown content
|
||||||
|
markdown: string,
|
||||||
|
// Filename
|
||||||
|
filename: string,
|
||||||
|
// Full path (may be empty?)
|
||||||
|
pathname: string,
|
||||||
|
|
||||||
|
// Indicates whether the document is UTF8 or UTF8-DOM encoded.
|
||||||
|
isUtf8BomEncoded: boolean,
|
||||||
|
// "lf" or "crlf"
|
||||||
|
lineEnding: string,
|
||||||
|
// Convert document ("lf") to `lineEnding` when saving
|
||||||
|
adjustLineEndingOnSave: boolean
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### File State
|
||||||
|
|
||||||
|
Internal state of a markdown document. `IMarkdownDocument` is used to create a `IFileState`.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface IDocumentState
|
||||||
|
{
|
||||||
|
isSaved: boolean,
|
||||||
|
pathname: string,
|
||||||
|
filename: string,
|
||||||
|
markdown: string,
|
||||||
|
isUtf8BomEncoded: boolean,
|
||||||
|
lineEnding: string,
|
||||||
|
adjustLineEndingOnSave: boolean,
|
||||||
|
textDirection: string,
|
||||||
|
history: {
|
||||||
|
stack: Array<any>,
|
||||||
|
index: number
|
||||||
|
},
|
||||||
|
cursor: any,
|
||||||
|
wordCount: {
|
||||||
|
paragraph: number,
|
||||||
|
word: number,
|
||||||
|
character: number,
|
||||||
|
all: number
|
||||||
|
},
|
||||||
|
searchMatches: {
|
||||||
|
index: number,
|
||||||
|
matches: Array<any>,
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ...
|
||||||
|
|
||||||
|
TBD
|
||||||
|
|
||||||
|
## View
|
||||||
|
|
||||||
|
TBD
|
||||||
|
|
||||||
|
### Side Bar
|
||||||
|
|
||||||
|
TBD
|
||||||
|
|
||||||
|
### Tabs
|
||||||
|
|
||||||
|
TBD
|
||||||
|
|
||||||
|
### Document
|
||||||
|
|
||||||
|
TBD
|
@ -5,7 +5,7 @@ import { promisify } from 'util'
|
|||||||
import { BrowserWindow, dialog, ipcMain } from 'electron'
|
import { BrowserWindow, dialog, ipcMain } from 'electron'
|
||||||
import appWindow from '../window'
|
import appWindow from '../window'
|
||||||
import { EXTENSION_HASN, EXTENSIONS, PANDOC_EXTENSIONS } from '../config'
|
import { EXTENSION_HASN, EXTENSIONS, PANDOC_EXTENSIONS } from '../config'
|
||||||
import { 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, log, isFile, isDirectory, getRecommendTitle } from '../utils'
|
||||||
import userPreference from '../preference'
|
import userPreference from '../preference'
|
||||||
@ -271,7 +271,7 @@ ipcMain.on('AGANI::ask-for-open-project-in-sidebar', e => {
|
|||||||
properties: ['openDirectory', 'createDirectory']
|
properties: ['openDirectory', 'createDirectory']
|
||||||
})
|
})
|
||||||
if (pathname && pathname[0]) {
|
if (pathname && pathname[0]) {
|
||||||
appWindow.openProject(win, pathname[0])
|
appWindow.openFolder(win, pathname[0])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -302,31 +302,46 @@ export const print = win => {
|
|||||||
win.webContents.send('AGANI::print')
|
win.webContents.send('AGANI::print')
|
||||||
}
|
}
|
||||||
|
|
||||||
export const openFileOrProject = pathname => {
|
export const openFileOrFolder = pathname => {
|
||||||
if (isFile(pathname) || isDirectory(pathname)) {
|
if (isFile(pathname) || isDirectory(pathname)) {
|
||||||
appWindow.createWindow(pathname)
|
appWindow.createWindow(pathname)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const openProject = win => {
|
export const openFolder = win => {
|
||||||
const pathname = dialog.showOpenDialog(win, {
|
const pathname = dialog.showOpenDialog(win, {
|
||||||
properties: ['openDirectory', 'createDirectory']
|
properties: ['openDirectory', 'createDirectory']
|
||||||
})
|
})
|
||||||
if (pathname && pathname[0]) {
|
if (pathname && pathname[0]) {
|
||||||
openFileOrProject(pathname[0])
|
openFileOrFolder(pathname[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const open = win => {
|
export const openFile = win => {
|
||||||
const filename = dialog.showOpenDialog(win, {
|
const fileList = dialog.showOpenDialog(win, {
|
||||||
properties: ['openFile'],
|
properties: ['openFile'],
|
||||||
filters: [{
|
filters: [{
|
||||||
name: 'text',
|
name: 'text',
|
||||||
extensions: EXTENSIONS
|
extensions: EXTENSIONS
|
||||||
}]
|
}]
|
||||||
})
|
})
|
||||||
if (filename && filename[0]) {
|
|
||||||
openFileOrProject(filename[0])
|
if (!fileList || !fileList[0]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const filename = fileList[0]
|
||||||
|
const { openFilesInNewWindow } = userPreference.getAll()
|
||||||
|
if (openFilesInNewWindow) {
|
||||||
|
openFileOrFolder(filename)
|
||||||
|
} else {
|
||||||
|
loadMarkdownFile(filename).then(rawDocument => {
|
||||||
|
newTab(win, rawDocument)
|
||||||
|
}).catch(err => {
|
||||||
|
// TODO: Handle error --> create a end-user error handler.
|
||||||
|
console.error('[ERROR] Cannot open file.')
|
||||||
|
log(err)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -334,8 +349,14 @@ export const newFile = () => {
|
|||||||
appWindow.createWindow()
|
appWindow.createWindow()
|
||||||
}
|
}
|
||||||
|
|
||||||
export const newTab = win => {
|
/**
|
||||||
win.webContents.send('AGANI::new-tab')
|
* Creates a new tab.
|
||||||
|
*
|
||||||
|
* @param {BrowserWindow} win Browser window
|
||||||
|
* @param {IMarkdownDocumentRaw} [rawDocument] Optional markdown document. If null a blank tab is created.
|
||||||
|
*/
|
||||||
|
export const newTab = (win, rawDocument = null) => {
|
||||||
|
win.webContents.send('AGANI::new-tab', rawDocument)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const closeTab = win => {
|
export const closeTab = win => {
|
||||||
|
@ -4,7 +4,7 @@ import * as actions from '../actions/file'
|
|||||||
const dockMenu = Menu.buildFromTemplate([{
|
const dockMenu = Menu.buildFromTemplate([{
|
||||||
label: 'Open...',
|
label: 'Open...',
|
||||||
click (menuItem, browserWindow) {
|
click (menuItem, browserWindow) {
|
||||||
actions.open(browserWindow)
|
actions.openFile(browserWindow)
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
label: 'Clear Recent',
|
label: 'Clear Recent',
|
||||||
|
@ -29,13 +29,13 @@ export default function (recentlyUsedFiles) {
|
|||||||
label: 'Open File',
|
label: 'Open File',
|
||||||
accelerator: keybindings.getAccelerator('fileOpenFile'),
|
accelerator: keybindings.getAccelerator('fileOpenFile'),
|
||||||
click (menuItem, browserWindow) {
|
click (menuItem, browserWindow) {
|
||||||
actions.open(browserWindow)
|
actions.openFile(browserWindow)
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
label: 'Open Folder',
|
label: 'Open Folder',
|
||||||
accelerator: keybindings.getAccelerator('fileOpenFolder'),
|
accelerator: keybindings.getAccelerator('fileOpenFolder'),
|
||||||
click (menuItem, browserWindow) {
|
click (menuItem, browserWindow) {
|
||||||
actions.openProject(browserWindow)
|
actions.openFolder(browserWindow)
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
@ -50,7 +50,7 @@ export default function (recentlyUsedFiles) {
|
|||||||
recentlyUsedMenu.submenu.push({
|
recentlyUsedMenu.submenu.push({
|
||||||
label: item,
|
label: item,
|
||||||
click (menuItem, browserWindow) {
|
click (menuItem, browserWindow) {
|
||||||
actions.openFileOrProject(menuItem.label)
|
actions.openFileOrFolder(menuItem.label)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -31,8 +31,7 @@ const convertLineEndings = (text, lineEnding) => {
|
|||||||
|
|
||||||
export const writeFile = (pathname, content, extension) => {
|
export const writeFile = (pathname, content, extension) => {
|
||||||
if (!pathname) {
|
if (!pathname) {
|
||||||
const errMsg = '[ERROR] Cannot save file without path.'
|
return Promise.reject('[ERROR] Cannot save file without path.')
|
||||||
return Promise.reject(errMsg)
|
|
||||||
}
|
}
|
||||||
pathname = !extension || pathname.endsWith(extension) ? pathname : `${pathname}${extension}`
|
pathname = !extension || pathname.endsWith(extension) ? pathname : `${pathname}${extension}`
|
||||||
|
|
||||||
@ -54,6 +53,12 @@ export const writeMarkdownFile = (pathname, content, options, win) => {
|
|||||||
return writeFile(pathname, content, extension)
|
return writeFile(pathname, content, extension)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the contents of a markdown file.
|
||||||
|
*
|
||||||
|
* @param {String} The path to the markdown file.
|
||||||
|
* @returns {IMarkdownDocumentRaw} Returns a raw markdown document.
|
||||||
|
*/
|
||||||
export const loadMarkdownFile = async pathname => {
|
export const loadMarkdownFile = async pathname => {
|
||||||
let markdown = await fse.readFile(path.resolve(pathname), 'utf-8')
|
let markdown = await fse.readFile(path.resolve(pathname), 'utf-8')
|
||||||
// Check UTF-8 BOM (EF BB BF) encoding
|
// Check UTF-8 BOM (EF BB BF) encoding
|
||||||
@ -65,7 +70,7 @@ export const loadMarkdownFile = async pathname => {
|
|||||||
// Detect line ending
|
// Detect line ending
|
||||||
const isLf = LF_LINE_ENDING_REG.test(markdown)
|
const isLf = LF_LINE_ENDING_REG.test(markdown)
|
||||||
const isCrlf = CRLF_LINE_ENDING_REG.test(markdown)
|
const isCrlf = CRLF_LINE_ENDING_REG.test(markdown)
|
||||||
const isMixed = isLf && isCrlf
|
const isMixedLineEndings = isLf && isCrlf
|
||||||
const isUnknownEnding = !isLf && !isCrlf
|
const isUnknownEnding = !isLf && !isCrlf
|
||||||
let lineEnding = getOsLineEndingName()
|
let lineEnding = getOsLineEndingName()
|
||||||
if (isLf && !isCrlf) {
|
if (isLf && !isCrlf) {
|
||||||
@ -75,7 +80,7 @@ export const loadMarkdownFile = async pathname => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let adjustLineEndingOnSave = false
|
let adjustLineEndingOnSave = false
|
||||||
if (isMixed || isUnknownEnding || lineEnding !== 'lf') {
|
if (isMixedLineEndings || isUnknownEnding || lineEnding !== 'lf') {
|
||||||
adjustLineEndingOnSave = lineEnding !== 'lf'
|
adjustLineEndingOnSave = lineEnding !== 'lf'
|
||||||
// Convert to LF for internal use.
|
// Convert to LF for internal use.
|
||||||
markdown = convertLineEndings(markdown, 'lf')
|
markdown = convertLineEndings(markdown, 'lf')
|
||||||
@ -83,16 +88,24 @@ export const loadMarkdownFile = async pathname => {
|
|||||||
|
|
||||||
const filename = path.basename(pathname)
|
const filename = path.basename(pathname)
|
||||||
|
|
||||||
|
// TODO(refactor:renderer/editor): Remove this entry! This should be loaded separately if needed.
|
||||||
const textDirection = getDefaultTextDirection()
|
const textDirection = getDefaultTextDirection()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
// document information
|
||||||
markdown,
|
markdown,
|
||||||
filename,
|
filename,
|
||||||
pathname,
|
pathname,
|
||||||
|
|
||||||
|
// options
|
||||||
isUtf8BomEncoded,
|
isUtf8BomEncoded,
|
||||||
lineEnding,
|
lineEnding,
|
||||||
adjustLineEndingOnSave,
|
adjustLineEndingOnSave,
|
||||||
isMixed,
|
|
||||||
|
// raw file information
|
||||||
|
isMixedLineEndings,
|
||||||
|
|
||||||
|
// TODO(refactor:renderer/editor): see above
|
||||||
textDirection
|
textDirection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,12 @@ class AppWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createWindow (pathname, markdown = '', options = {}) {
|
createWindow (pathname = null, markdown = '', options = {}) {
|
||||||
|
// Ensure path is normalized
|
||||||
|
if (pathname) {
|
||||||
|
pathname = path.resolve(pathname)
|
||||||
|
}
|
||||||
|
|
||||||
const { windows } = this
|
const { windows } = this
|
||||||
const mainWindowState = windowStateKeeper({
|
const mainWindowState = windowStateKeeper({
|
||||||
defaultWidth: 1200,
|
defaultWidth: 1200,
|
||||||
@ -95,7 +100,7 @@ class AppWindow {
|
|||||||
isUtf8BomEncoded,
|
isUtf8BomEncoded,
|
||||||
lineEnding,
|
lineEnding,
|
||||||
adjustLineEndingOnSave,
|
adjustLineEndingOnSave,
|
||||||
isMixed,
|
isMixedLineEndings,
|
||||||
textDirection
|
textDirection
|
||||||
} = data
|
} = data
|
||||||
|
|
||||||
@ -113,7 +118,7 @@ class AppWindow {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Notify user about mixed endings
|
// Notify user about mixed endings
|
||||||
if (isMixed) {
|
if (isMixedLineEndings) {
|
||||||
win.webContents.send('AGANI::show-notification', {
|
win.webContents.send('AGANI::show-notification', {
|
||||||
title: 'Mixed Line Endings',
|
title: 'Mixed Line Endings',
|
||||||
type: 'error',
|
type: 'error',
|
||||||
@ -125,7 +130,7 @@ class AppWindow {
|
|||||||
.catch(log)
|
.catch(log)
|
||||||
// open directory / folder
|
// open directory / folder
|
||||||
} else if (pathname && isDirectory(pathname)) {
|
} else if (pathname && isDirectory(pathname)) {
|
||||||
this.openProject(win, pathname)
|
this.openFolder(win, pathname)
|
||||||
// open a window but do not open a file or directory
|
// open a window but do not open a file or directory
|
||||||
} else {
|
} else {
|
||||||
const lineEnding = getOsLineEndingName()
|
const lineEnding = getOsLineEndingName()
|
||||||
@ -180,11 +185,10 @@ class AppWindow {
|
|||||||
return win
|
return win
|
||||||
}
|
}
|
||||||
|
|
||||||
openProject (win, pathname) {
|
openFolder (win, pathname) {
|
||||||
const unwatcher = this.watcher.watch(win, pathname)
|
const unwatcher = this.watcher.watch(win, pathname)
|
||||||
this.windows.get(win.id).watchers.push(unwatcher)
|
this.windows.get(win.id).watchers.push(unwatcher)
|
||||||
try {
|
try {
|
||||||
// const tree = await loadProject(pathname)
|
|
||||||
win.webContents.send('AGANI::open-project', {
|
win.webContents.send('AGANI::open-project', {
|
||||||
name: path.basename(pathname),
|
name: path.basename(pathname),
|
||||||
pathname
|
pathname
|
||||||
|
@ -91,7 +91,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="open-project">
|
<div v-else class="open-project">
|
||||||
<a href="javascript:;" @click="openProject" title="Open Folder">
|
<a href="javascript:;" @click="openFolder" title="Open Folder">
|
||||||
<svg class="icon" aria-hidden="true">
|
<svg class="icon" aria-hidden="true">
|
||||||
<use xlink:href="#icon-create-project"></use>
|
<use xlink:href="#icon-create-project"></use>
|
||||||
</svg>
|
</svg>
|
||||||
@ -174,7 +174,7 @@
|
|||||||
titleIconClick (active) {
|
titleIconClick (active) {
|
||||||
//
|
//
|
||||||
},
|
},
|
||||||
openProject () {
|
openFolder () {
|
||||||
this.$store.dispatch('ASK_FOR_OPEN_PROJECT')
|
this.$store.dispatch('ASK_FOR_OPEN_PROJECT')
|
||||||
},
|
},
|
||||||
saveAll (isClose) {
|
saveAll (isClose) {
|
||||||
|
@ -23,13 +23,13 @@ export const fileMixins = {
|
|||||||
handleFileClick () {
|
handleFileClick () {
|
||||||
const { data, isMarkdown, pathname } = this.file
|
const { data, isMarkdown, pathname } = this.file
|
||||||
if (!isMarkdown || this.currentFile.pathname === pathname) return
|
if (!isMarkdown || this.currentFile.pathname === pathname) return
|
||||||
const { isMixed, filename, lineEnding } = data
|
const { isMixedLineEndings, filename, lineEnding } = data
|
||||||
const isOpened = this.tabs.filter(file => file.pathname === pathname)[0]
|
const isOpened = this.tabs.filter(file => file.pathname === pathname)[0]
|
||||||
|
|
||||||
const fileState = isOpened || getFileStateFromData(data)
|
const fileState = isOpened || getFileStateFromData(data)
|
||||||
this.$store.dispatch('UPDATE_CURRENT_FILE', fileState)
|
this.$store.dispatch('UPDATE_CURRENT_FILE', fileState)
|
||||||
|
|
||||||
if (isMixed && !isOpened) {
|
if (isMixedLineEndings && !isOpened) {
|
||||||
this.$notify({
|
this.$notify({
|
||||||
title: 'Line Ending',
|
title: 'Line Ending',
|
||||||
message: `${filename} has mixed line endings which are automatically normalized to ${lineEnding.toUpperCase()}.`,
|
message: `${filename} has mixed line endings which are automatically normalized to ${lineEnding.toUpperCase()}.`,
|
||||||
|
@ -2,7 +2,7 @@ import { clipboard, ipcRenderer, shell } from 'electron'
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import bus from '../bus'
|
import bus from '../bus'
|
||||||
import { hasKeys } from '../util'
|
import { hasKeys } from '../util'
|
||||||
import { getOptionsFromState, getSingleFileState, getBlankFileState } from './help'
|
import { createDocumentState, getOptionsFromState, getSingleFileState, getBlankFileState } from './help'
|
||||||
import notice from '../services/notification'
|
import notice from '../services/notification'
|
||||||
|
|
||||||
// HACK: When rewriting muya, create and update muya's TOC during heading parsing and pass it to the renderer process.
|
// HACK: When rewriting muya, create and update muya's TOC during heading parsing and pass it to the renderer process.
|
||||||
@ -357,8 +357,14 @@ const actions = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
LISTEN_FOR_NEW_TAB ({ dispatch }) {
|
LISTEN_FOR_NEW_TAB ({ dispatch }) {
|
||||||
ipcRenderer.on('AGANI::new-tab', e => {
|
ipcRenderer.on('AGANI::new-tab', (e, markdownDocument) => {
|
||||||
dispatch('NEW_BLANK_FILE')
|
if (markdownDocument) {
|
||||||
|
// Create tab with content.
|
||||||
|
dispatch('NEW_TAB_WITH_CONTENT', markdownDocument)
|
||||||
|
} else {
|
||||||
|
// Create an empty tab
|
||||||
|
dispatch('NEW_BLANK_FILE')
|
||||||
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -383,6 +389,36 @@ const actions = {
|
|||||||
bus.$emit('file-loaded', markdown)
|
bus.$emit('file-loaded', markdown)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new tab from the given markdown document
|
||||||
|
*
|
||||||
|
* @param {*} context Store context
|
||||||
|
* @param {IMarkdownDocumentRaw} markdownDocument Class that represent a markdown document
|
||||||
|
*/
|
||||||
|
NEW_TAB_WITH_CONTENT ({ commit, state, dispatch }, markdownDocument) {
|
||||||
|
if (!markdownDocument) {
|
||||||
|
console.warn('Cannot create a file tab without a markdown document!')
|
||||||
|
dispatch('NEW_BLANK_FILE')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { markdown, isMixedLineEndings } = markdownDocument
|
||||||
|
const docState = createDocumentState(markdownDocument)
|
||||||
|
dispatch('UPDATE_CURRENT_FILE', docState)
|
||||||
|
bus.$emit('file-loaded', markdown)
|
||||||
|
|
||||||
|
if (isMixedLineEndings) {
|
||||||
|
const { filename, lineEnding } = markdownDocument
|
||||||
|
notice({
|
||||||
|
title: 'Line Ending',
|
||||||
|
message: `${filename} has mixed line endings which are automatically normalized to ${lineEnding.toUpperCase()}.`,
|
||||||
|
type: 'primary',
|
||||||
|
time: 20000,
|
||||||
|
showConfirm: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
LISTEN_FOR_OPEN_BLANK_WINDOW ({ commit, state, dispatch }) {
|
LISTEN_FOR_OPEN_BLANK_WINDOW ({ commit, state, dispatch }) {
|
||||||
ipcRenderer.on('AGANI::open-blank-window', (e, { lineEnding, markdown: source }) => {
|
ipcRenderer.on('AGANI::open-blank-window', (e, { lineEnding, markdown: source }) => {
|
||||||
const { tabs } = state
|
const { tabs } = state
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
import { getUniqueId } from '../util'
|
import { getUniqueId, cloneObj } from '../util'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default internel markdown document with editor options.
|
||||||
|
*
|
||||||
|
* @type {IDocumentState} Internel markdown document
|
||||||
|
*/
|
||||||
export const defaultFileState = {
|
export const defaultFileState = {
|
||||||
isSaved: true,
|
isSaved: true,
|
||||||
pathname: '',
|
pathname: '',
|
||||||
@ -81,6 +86,8 @@ export const getBlankFileState = (tabs, lineEnding = 'lf', markdown = '') => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const getSingleFileState = ({ id = getUniqueId(), markdown, filename, pathname, options }) => {
|
export const getSingleFileState = ({ id = getUniqueId(), markdown, filename, pathname, options }) => {
|
||||||
|
// TODO(refactor:renderer/editor): Replace this function with `createDocumentState`.
|
||||||
|
|
||||||
const fileState = JSON.parse(JSON.stringify(defaultFileState))
|
const fileState = JSON.parse(JSON.stringify(defaultFileState))
|
||||||
const { isUtf8BomEncoded, lineEnding, adjustLineEndingOnSave } = options
|
const { isUtf8BomEncoded, lineEnding, adjustLineEndingOnSave } = options
|
||||||
|
|
||||||
@ -97,6 +104,37 @@ export const getSingleFileState = ({ id = getUniqueId(), markdown, filename, pat
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a internal document from the given document.
|
||||||
|
*
|
||||||
|
* @param {IMarkdownDocument} markdownDocument Markdown document
|
||||||
|
* @param {String} [id] Random identifier
|
||||||
|
* @returns {IDocumentState} Returns a document state
|
||||||
|
*/
|
||||||
|
export const createDocumentState = (markdownDocument, id = getUniqueId()) => {
|
||||||
|
const docState = cloneObj(defaultFileState, true)
|
||||||
|
const {
|
||||||
|
markdown,
|
||||||
|
filename,
|
||||||
|
pathname,
|
||||||
|
isUtf8BomEncoded,
|
||||||
|
lineEnding,
|
||||||
|
adjustLineEndingOnSave,
|
||||||
|
} = markdownDocument
|
||||||
|
|
||||||
|
assertLineEnding(adjustLineEndingOnSave, lineEnding)
|
||||||
|
|
||||||
|
return Object.assign(docState, {
|
||||||
|
id,
|
||||||
|
markdown,
|
||||||
|
filename,
|
||||||
|
pathname,
|
||||||
|
isUtf8BomEncoded,
|
||||||
|
lineEnding,
|
||||||
|
adjustLineEndingOnSave
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const assertLineEnding = (adjustLineEndingOnSave, lineEnding) => {
|
const assertLineEnding = (adjustLineEndingOnSave, lineEnding) => {
|
||||||
lineEnding = lineEnding.toLowerCase()
|
lineEnding = lineEnding.toLowerCase()
|
||||||
if ((adjustLineEndingOnSave && lineEnding !== 'crlf') ||
|
if ((adjustLineEndingOnSave && lineEnding !== 'crlf') ||
|
||||||
|
@ -176,3 +176,13 @@ export const getUniqueId = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const hasKeys = obj => Object.keys(obj).length > 0
|
export const hasKeys = obj => Object.keys(obj).length > 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clone an object as a shallow or deep copy.
|
||||||
|
*
|
||||||
|
* @param {*} obj Object to clone
|
||||||
|
* @param {Boolean} deepCopy Create a shallow (false) or deep copy (true)
|
||||||
|
*/
|
||||||
|
export const cloneObj = (obj, deepCopy=true) => {
|
||||||
|
return deepCopy ? JSON.parse(JSON.stringify(obj)) : Object.assign({}, obj)
|
||||||
|
}
|
||||||
|
@ -35,7 +35,8 @@ Edit and save to update preferences. You can only change the JSON below!
|
|||||||
"endOfLine": "default",
|
"endOfLine": "default",
|
||||||
"tabSize": 4,
|
"tabSize": 4,
|
||||||
"textDirection": "ltr",
|
"textDirection": "ltr",
|
||||||
"titleBarStyle": "csd"
|
"titleBarStyle": "csd",
|
||||||
|
"openFilesInNewWindow": true
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user