Skip to content

Commit

Permalink
User-defined open files (#735)
Browse files Browse the repository at this point in the history
  • Loading branch information
fxha authored and Jocs committed Mar 7, 2019
1 parent 44c537a commit cbd90bf
Show file tree
Hide file tree
Showing 13 changed files with 270 additions and 35 deletions.
3 changes: 3 additions & 0 deletions doc/wip/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Internal Documentation

WIP documentation of Mark Text.
109 changes: 109 additions & 0 deletions doc/wip/renderer/editor.md
Original file line number Diff line number Diff line change
@@ -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
43 changes: 32 additions & 11 deletions src/main/actions/file.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { promisify } from 'util'
import { BrowserWindow, dialog, ipcMain } from 'electron'
import appWindow from '../window'
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 { getPath, isMarkdownFile, log, isFile, isDirectory, getRecommendTitle } from '../utils'
import userPreference from '../preference'
Expand Down Expand Up @@ -271,7 +271,7 @@ ipcMain.on('AGANI::ask-for-open-project-in-sidebar', e => {
properties: ['openDirectory', 'createDirectory']
})
if (pathname && pathname[0]) {
appWindow.openProject(win, pathname[0])
appWindow.openFolder(win, pathname[0])
}
})

Expand Down Expand Up @@ -302,40 +302,61 @@ export const print = win => {
win.webContents.send('AGANI::print')
}

export const openFileOrProject = pathname => {
export const openFileOrFolder = pathname => {
if (isFile(pathname) || isDirectory(pathname)) {
appWindow.createWindow(pathname)
}
}

export const openProject = win => {
export const openFolder = win => {
const pathname = dialog.showOpenDialog(win, {
properties: ['openDirectory', 'createDirectory']
})
if (pathname && pathname[0]) {
openFileOrProject(pathname[0])
openFileOrFolder(pathname[0])
}
}

export const open = win => {
const filename = dialog.showOpenDialog(win, {
export const openFile = win => {
const fileList = dialog.showOpenDialog(win, {
properties: ['openFile'],
filters: [{
name: 'text',
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)
})
}
}

export const newFile = () => {
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 => {
Expand Down
2 changes: 1 addition & 1 deletion src/main/menus/dock.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as actions from '../actions/file'
const dockMenu = Menu.buildFromTemplate([{
label: 'Open...',
click (menuItem, browserWindow) {
actions.open(browserWindow)
actions.openFile(browserWindow)
}
}, {
label: 'Clear Recent',
Expand Down
6 changes: 3 additions & 3 deletions src/main/menus/file.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ export default function (recentlyUsedFiles) {
label: 'Open File',
accelerator: keybindings.getAccelerator('fileOpenFile'),
click (menuItem, browserWindow) {
actions.open(browserWindow)
actions.openFile(browserWindow)
}
}, {
label: 'Open Folder',
accelerator: keybindings.getAccelerator('fileOpenFolder'),
click (menuItem, browserWindow) {
actions.openProject(browserWindow)
actions.openFolder(browserWindow)
}
}]
}
Expand All @@ -50,7 +50,7 @@ export default function (recentlyUsedFiles) {
recentlyUsedMenu.submenu.push({
label: item,
click (menuItem, browserWindow) {
actions.openFileOrProject(menuItem.label)
actions.openFileOrFolder(menuItem.label)
}
})
}
Expand Down
23 changes: 18 additions & 5 deletions src/main/utils/filesystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ const convertLineEndings = (text, lineEnding) => {

export const writeFile = (pathname, content, extension) => {
if (!pathname) {
const errMsg = '[ERROR] Cannot save file without path.'
return Promise.reject(errMsg)
return Promise.reject('[ERROR] Cannot save file without path.')
}
pathname = !extension || pathname.endsWith(extension) ? pathname : `${pathname}${extension}`

Expand All @@ -54,6 +53,12 @@ export const writeMarkdownFile = (pathname, content, options, win) => {
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 => {
let markdown = await fse.readFile(path.resolve(pathname), 'utf-8')
// Check UTF-8 BOM (EF BB BF) encoding
Expand All @@ -65,7 +70,7 @@ export const loadMarkdownFile = async pathname => {
// Detect line ending
const isLf = LF_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
let lineEnding = getOsLineEndingName()
if (isLf && !isCrlf) {
Expand All @@ -75,24 +80,32 @@ export const loadMarkdownFile = async pathname => {
}

let adjustLineEndingOnSave = false
if (isMixed || isUnknownEnding || lineEnding !== 'lf') {
if (isMixedLineEndings || isUnknownEnding || lineEnding !== 'lf') {
adjustLineEndingOnSave = lineEnding !== 'lf'
// Convert to LF for internal use.
markdown = convertLineEndings(markdown, 'lf')
}

const filename = path.basename(pathname)

// TODO(refactor:renderer/editor): Remove this entry! This should be loaded separately if needed.
const textDirection = getDefaultTextDirection()

return {
// document information
markdown,
filename,
pathname,

// options
isUtf8BomEncoded,
lineEnding,
adjustLineEndingOnSave,
isMixed,

// raw file information
isMixedLineEndings,

// TODO(refactor:renderer/editor): see above
textDirection
}
}
16 changes: 10 additions & 6 deletions src/main/window.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 mainWindowState = windowStateKeeper({
defaultWidth: 1200,
Expand Down Expand Up @@ -95,7 +100,7 @@ class AppWindow {
isUtf8BomEncoded,
lineEnding,
adjustLineEndingOnSave,
isMixed,
isMixedLineEndings,
textDirection
} = data

Expand All @@ -113,7 +118,7 @@ class AppWindow {
})

// Notify user about mixed endings
if (isMixed) {
if (isMixedLineEndings) {
win.webContents.send('AGANI::show-notification', {
title: 'Mixed Line Endings',
type: 'error',
Expand All @@ -125,7 +130,7 @@ class AppWindow {
.catch(log)
// open directory / folder
} else if (pathname && isDirectory(pathname)) {
this.openProject(win, pathname)
this.openFolder(win, pathname)
// open a window but do not open a file or directory
} else {
const lineEnding = getOsLineEndingName()
Expand Down Expand Up @@ -180,11 +185,10 @@ class AppWindow {
return win
}

openProject (win, pathname) {
openFolder (win, pathname) {
const unwatcher = this.watcher.watch(win, pathname)
this.windows.get(win.id).watchers.push(unwatcher)
try {
// const tree = await loadProject(pathname)
win.webContents.send('AGANI::open-project', {
name: path.basename(pathname),
pathname
Expand Down
4 changes: 2 additions & 2 deletions src/renderer/components/sideBar/tree.vue
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
</div>
</div>
<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">
<use xlink:href="#icon-create-project"></use>
</svg>
Expand Down Expand Up @@ -174,7 +174,7 @@
titleIconClick (active) {
//
},
openProject () {
openFolder () {
this.$store.dispatch('ASK_FOR_OPEN_PROJECT')
},
saveAll (isClose) {
Expand Down
4 changes: 2 additions & 2 deletions src/renderer/mixins/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ export const fileMixins = {
handleFileClick () {
const { data, isMarkdown, pathname } = this.file
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 fileState = isOpened || getFileStateFromData(data)
this.$store.dispatch('UPDATE_CURRENT_FILE', fileState)

if (isMixed && !isOpened) {
if (isMixedLineEndings && !isOpened) {
this.$notify({
title: 'Line Ending',
message: `${filename} has mixed line endings which are automatically normalized to ${lineEnding.toUpperCase()}.`,
Expand Down
Loading

0 comments on commit cbd90bf

Please sign in to comment.