feat: user preference and fix issues #45 #16

This commit is contained in:
Jocs 2018-03-21 17:11:18 +08:00
parent 3e28b7e328
commit 39f5400544
12 changed files with 186 additions and 31 deletions

13
.github/CHANGELOG.md vendored
View File

@ -1,6 +1,15 @@
### 0.7.18 ### 0.8.4
**Bug fix** **:cactus:Feature**
- Add user preferences in `Mark Text menu`, the shoutcut is `CmdorCtrl + ,`, you can set the default `theme` and `autoSave`.
- Add `autoSave` to `file menu`, the default value is in `preferences.md` which you can open in `Mark Text menu`. #45
**:butterfly:Optimization**
- Theme can be saved in user preferences now #16
**:beetle:Bug fix**
- fix: prevent open image or file directly when drag and drop over Mark Text #42 - fix: prevent open image or file directly when drag and drop over Mark Text #42
- fix: set theme to all the open window not just the active one. - fix: set theme to all the open window not just the active one.

View File

@ -1,6 +1,6 @@
{ {
"name": "marktext", "name": "marktext",
"version": "0.7.17", "version": "0.8.4",
"author": "Jocs <luoran1988@126.com>", "author": "Jocs <luoran1988@126.com>",
"description": "Next generation markdown editor", "description": "Next generation markdown editor",
"license": "MIT", "license": "MIT",

View File

@ -6,6 +6,7 @@ import path from 'path'
import { app, dialog, ipcMain, BrowserWindow } from 'electron' import { app, dialog, ipcMain, BrowserWindow } from 'electron'
import createWindow, { windows } from '../createWindow' import createWindow, { windows } from '../createWindow'
import { EXTENSIONS, EXTENSION_HASN } from '../config' import { EXTENSIONS, EXTENSION_HASN } from '../config'
import { getUserPreference, setUserPreference } from '../utils'
const watchAndReload = (pathname, win) => { // when i build, and failed. const watchAndReload = (pathname, win) => { // when i build, and failed.
// const watcher = chokidar.watch(pathname, { // const watcher = chokidar.watch(pathname, {
@ -114,6 +115,13 @@ ipcMain.on('AGANI::response-close-confirm', (e, { filename, pathname, markdown }
}) })
ipcMain.on('AGANI::response-file-save', handleResponseForSave) ipcMain.on('AGANI::response-file-save', handleResponseForSave)
ipcMain.on('AGANI::ask-for-auto-save', e => {
const win = BrowserWindow.fromWebContents(e.sender)
const { autoSave } = getUserPreference()
win.webContents.send('AGANI::auto-save', autoSave)
})
ipcMain.on('AGANI::response-export', handleResponseForExport) ipcMain.on('AGANI::response-export', handleResponseForExport)
ipcMain.on('AGANI::close-window', e => { ipcMain.on('AGANI::close-window', e => {
@ -155,6 +163,15 @@ export const saveAs = win => {
win.webContents.send('AGANI::ask-file-save-as') win.webContents.send('AGANI::ask-file-save-as')
} }
export const autoSave = (menuItem, win) => { export const autoSave = (menuItem, browserWindow) => {
// TODO const { checked } = menuItem
setUserPreference('autoSave', checked)
.then(() => {
for (const win of windows.values()) {
win.webContents.send('AGANI::auto-save', checked)
}
})
.catch(err => {
console.log(err)
})
} }

View File

@ -1,7 +1,7 @@
import fs from 'fs' import fs from 'fs'
import path from 'path' import path from 'path'
import { ipcMain } from 'electron' import { ipcMain } from 'electron'
import { getMenuItem } from '../utils' import { getMenuItem, setUserPreference } from '../utils'
import { windows } from '../createWindow' import { windows } from '../createWindow'
/** /**
* Set `__static` path to static files in production * Set `__static` path to static files in production
@ -17,9 +17,15 @@ const THEME_PATH = path.join(__static, '/themes')
const themeCSS = {} const themeCSS = {}
export const selectTheme = (theme, themeCSS) => { export const selectTheme = (theme, themeCSS) => {
for (const win of windows.values()) { setUserPreference('theme', theme)
win.webContents.send('AGANI::theme', { theme, themeCSS }) .then(() => {
} for (const win of windows.values()) {
win.webContents.send('AGANI::theme', { theme, themeCSS })
}
})
.catch(err => {
console.log(err)
})
} }
const getSelectTheme = () => { const getSelectTheme = () => {

View File

@ -26,7 +26,7 @@ export const EXTENSION_HASN = {
pdf: '.pdf' pdf: '.pdf'
} }
export const DEFAULT_THEME = 'dark' // export const DEFAULT_THEME = 'dark'
export const VIEW_MENU_ITEM = { export const VIEW_MENU_ITEM = {
'Source Code Mode': false, 'Source Code Mode': false,

View File

@ -1,4 +1,7 @@
import * as actions from '../actions/file' import * as actions from '../actions/file'
import { getUserPreference } from '../utils'
const { autoSave } = getUserPreference()
export default { export default {
label: 'File', label: 'File',
@ -37,9 +40,10 @@ export default {
} }
}, { }, {
label: 'Auto Save', label: 'Auto Save',
type: 'radio', type: 'checkbox',
checked: autoSave,
click (menuItem, browserWindow) { click (menuItem, browserWindow) {
actions.autoSave(browserWindow) actions.autoSave(menuItem, browserWindow)
} }
}, { }, {
type: 'separator' type: 'separator'

View File

@ -1,18 +1,21 @@
import * as actions from '../actions/theme' import * as actions from '../actions/theme'
import { getUserPreference } from '../utils'
const { theme } = getUserPreference()
export default { export default {
label: 'Theme', label: 'Theme',
submenu: [{ submenu: [{
label: 'Dark', label: 'Dark',
type: 'radio', type: 'radio',
checked: false, checked: theme === 'dark',
click (menuItem, browserWindow) { click (menuItem, browserWindow) {
actions.selectTheme('dark') actions.selectTheme('dark')
} }
}, { }, {
label: 'Light', label: 'Light',
type: 'radio', type: 'radio',
checked: true, checked: theme === 'light',
click (menuItem, browserWindow) { click (menuItem, browserWindow) {
actions.selectTheme('light') actions.selectTheme('light')
} }

View File

@ -1,6 +1,47 @@
import fs from 'fs'
import path from 'path'
import { Menu } from 'electron' import { Menu } from 'electron'
const JSON_REG = /```json(.+)```/g
const preferencePath = path.join(__static, 'preference.md')
let PREFERENCE_CACHE = null
export const getMenuItem = menuName => { export const getMenuItem = menuName => {
const menus = Menu.getApplicationMenu() const menus = Menu.getApplicationMenu()
return menus.items.find(menu => menu.label === menuName) return menus.items.find(menu => menu.label === menuName)
} }
export const getUserPreference = () => {
if (PREFERENCE_CACHE) {
return PREFERENCE_CACHE
} else {
const content = fs.readFileSync(preferencePath, 'utf-8')
try {
const userSetting = JSON_REG.exec(content.replace(/\n/g, ''))[1]
PREFERENCE_CACHE = JSON.parse(userSetting)
return PREFERENCE_CACHE
} catch (err) {
// todo notice the user
console.log(err)
}
}
}
export const setUserPreference = (key, value) => {
const preUserSetting = getUserPreference()
const newUserSetting = PREFERENCE_CACHE = Object.assign({}, preUserSetting, { [key]: value })
return new Promise((resolve, reject) => {
const content = fs.readFileSync(preferencePath, 'utf-8')
const tokens = content.split('```')
const newContent = tokens[0] +
'```json\n' +
JSON.stringify(newUserSetting, null, 2) +
'\n```' +
tokens[2]
fs.writeFile(preferencePath, newContent, 'utf-8', err => {
if (err) reject(err)
else resolve(newUserSetting)
})
})
}

View File

@ -7,6 +7,7 @@
:word-count="wordCount" :word-count="wordCount"
:theme="theme" :theme="theme"
:platform="platform" :platform="platform"
:is-saved="isSaved"
></title-bar> ></title-bar>
<editor <editor
:typewriter="typewriter" :typewriter="typewriter"
@ -57,7 +58,7 @@
}, },
computed: { computed: {
...mapState([ ...mapState([
'pathname', 'filename', 'windowActive', 'wordCount', 'pathname', 'filename', 'isSaved', 'windowActive', 'wordCount',
'typewriter', 'focus', 'sourceCode', 'markdown', 'typewriter', 'focus', 'sourceCode', 'markdown',
'cursor', 'theme', 'themeCSS', 'platform' 'cursor', 'theme', 'themeCSS', 'platform'
]) ])
@ -66,6 +67,7 @@
const { dispatch } = this.$store const { dispatch } = this.$store
dispatch('ASK_FOR_THEME') dispatch('ASK_FOR_THEME')
dispatch('ASK_FOR_AUTO_SAVE')
dispatch('ASK_FOR_MODE') dispatch('ASK_FOR_MODE')
dispatch('LISTEN_FOR_CLOSE') dispatch('LISTEN_FOR_CLOSE')
dispatch('LISTEN_FOR_SAVE_AS') dispatch('LISTEN_FOR_SAVE_AS')

View File

@ -10,6 +10,7 @@
</svg> </svg>
</span> </span>
<span :class="[{ 'title-no-drag': platform === 'win32' }]">{{ filename }}</span> <span :class="[{ 'title-no-drag': platform === 'win32' }]">{{ filename }}</span>
<span class="save-dot" :class="{'show': !isSaved}"></span>
</div> </div>
<div :class="platform === 'win32' ? 'left-toolbar' : 'right-toolbar'"> <div :class="platform === 'win32' ? 'left-toolbar' : 'right-toolbar'">
<div v-if="platform === 'win32'" class="windows-titlebar-menu title-no-drag" @click.stop="handleMenuClick">&#9776;</div> <div v-if="platform === 'win32'" class="windows-titlebar-menu title-no-drag" @click.stop="handleMenuClick">&#9776;</div>
@ -48,7 +49,8 @@
active: Boolean, active: Boolean,
wordCount: Object, wordCount: Object,
theme: String, theme: String,
platform: String platform: String,
isSaved: Boolean
}, },
computed: { computed: {
paths () { paths () {
@ -81,7 +83,10 @@
}, },
handleMenuClick () { handleMenuClick () {
const win = remote.getCurrentWindow() const win = remote.getCurrentWindow()
remote.Menu.getApplicationMenu().popup({window: win, x: 23, y: 20}) remote
.Menu
.getApplicationMenu()
.popup({ window: win, x: 23, y: 20 })
} }
} }
} }
@ -121,9 +126,25 @@
text-align: center; text-align: center;
transition: all .25s ease-in-out; transition: all .25s ease-in-out;
} }
.active .save-dot {
margin-left: 3px;
width: 7px;
height: 7px;
display: inline-block;
border-radius: 50%;
background: rgba(242, 134, 94, .7);
visibility: hidden;
}
.active .save-dot.show {
visibility: visible;
}
.title:hover { .title:hover {
color: #303133; color: #303133;
} }
.title:hover .save-dot {
background: rgb(242, 134, 94);
}
.right-toolbar { .right-toolbar {
padding: 0 10px; padding: 0 10px;
height: 100%; height: 100%;

View File

@ -16,6 +16,7 @@ const state = {
sourceCode: false, // source code mode sourceCode: false, // source code mode
pathname: '', pathname: '',
isSaved: true, isSaved: true,
autoSave: false,
markdown: '', markdown: '',
cursor: null, cursor: null,
windowActive: true, windowActive: true,
@ -51,7 +52,7 @@ const mutations = {
window.__dirname = path.dirname(pathname) window.__dirname = path.dirname(pathname)
state.pathname = pathname state.pathname = pathname
}, },
SET_STATUS (state, status) { SET_SAVE_STATUS (state, status) {
state.isSaved = status state.isSaved = status
}, },
SET_MARKDOWN (state, markdown) { SET_MARKDOWN (state, markdown) {
@ -62,6 +63,9 @@ const mutations = {
}, },
SET_CURSOR (state, cursor) { SET_CURSOR (state, cursor) {
state.cursor = cursor state.cursor = cursor
},
SET_AUTO_SAVE (state, autoSave) {
state.autoSave = autoSave
} }
} }
@ -72,9 +76,24 @@ const actions = {
commit('SET_THEME', themes) commit('SET_THEME', themes)
}) })
}, },
ASK_FOR_AUTO_SAVE ({ commit, state }) {
ipcRenderer.send('AGANI::ask-for-auto-save')
ipcRenderer.on('AGANI::auto-save', (e, autoSave) => {
const { pathname, markdown } = state
commit('SET_AUTO_SAVE', autoSave)
if (autoSave && pathname) {
commit('SET_SAVE_STATUS', true)
ipcRenderer.send('AGANI::response-file-save', { pathname, markdown })
}
})
},
SEARCH ({ commit }, value) { SEARCH ({ commit }, value) {
commit('SET_SEARCH', value) commit('SET_SEARCH', value)
}, },
ASK_FOR_MODE ({ commit }) { ASK_FOR_MODE ({ commit }) {
ipcRenderer.send('AGANI::ask-for-mode') ipcRenderer.send('AGANI::ask-for-mode')
ipcRenderer.on('AGANI::res-for-mode', (e, modes) => { ipcRenderer.on('AGANI::res-for-mode', (e, modes) => {
@ -86,70 +105,86 @@ const actions = {
}) })
}) })
}, },
LINTEN_WIN_STATUS ({ commit }) { LINTEN_WIN_STATUS ({ commit }) {
ipcRenderer.on('AGANI::window-active-status', (e, { status }) => { ipcRenderer.on('AGANI::window-active-status', (e, { status }) => {
commit('SET_WIN_STATUS', status) commit('SET_WIN_STATUS', status)
}) })
}, },
LISTEN_FOR_SAVE ({ commit, state }) { LISTEN_FOR_SAVE ({ commit, state }) {
ipcRenderer.on('AGANI::ask-file-save', () => { ipcRenderer.on('AGANI::ask-file-save', () => {
const { pathname, markdown } = state const { pathname, markdown } = state
ipcRenderer.send('AGANI::response-file-save', { pathname, markdown }) ipcRenderer.send('AGANI::response-file-save', { pathname, markdown })
commit('SET_SAVE_STATUS', true)
}) })
}, },
LISTEN_FOR_SAVE_AS ({ commit, state }) { LISTEN_FOR_SAVE_AS ({ commit, state }) {
ipcRenderer.on('AGANI::ask-file-save-as', () => { ipcRenderer.on('AGANI::ask-file-save-as', () => {
const { pathname, markdown } = state const { pathname, markdown } = state
ipcRenderer.send('AGANI::response-file-save-as', { pathname, markdown }) ipcRenderer.send('AGANI::response-file-save-as', { pathname, markdown })
}) })
}, },
GET_FILENAME ({ commit, state }) { GET_FILENAME ({ commit, state }) {
ipcRenderer.on('AGANI::set-pathname', (e, { pathname, filename }) => { ipcRenderer.on('AGANI::set-pathname', (e, { pathname, filename }) => {
commit('SET_FILENAME', filename) commit('SET_FILENAME', filename)
commit('SET_PATHNAME', pathname) commit('SET_PATHNAME', pathname)
commit('SET_STATUS', true) commit('SET_SAVE_STATUS', true)
}) })
}, },
LISTEN_FOR_FILE_LOAD ({ commit, state }) { LISTEN_FOR_FILE_LOAD ({ commit, state }) {
ipcRenderer.on('AGANI::file-loaded', (e, { file, filename, pathname }) => { ipcRenderer.on('AGANI::file-loaded', (e, { file, filename, pathname }) => {
commit('SET_FILENAME', filename) commit('SET_FILENAME', filename)
commit('SET_PATHNAME', pathname) commit('SET_PATHNAME', pathname)
commit('SET_MARKDOWN', file) commit('SET_MARKDOWN', file)
commit('SET_STATUS', true) commit('SET_SAVE_STATUS', true)
bus.$emit('file-loaded', file) bus.$emit('file-loaded', file)
}) })
}, },
LISTEN_FOR_FILE_CHANGE ({ commit, state }) { LISTEN_FOR_FILE_CHANGE ({ commit, state }) {
ipcRenderer.on('AGANI::file-change', (e, { file, filename, pathname }) => { ipcRenderer.on('AGANI::file-change', (e, { file, filename, pathname }) => {
const { windowActive } = state const { windowActive } = state
commit('SET_FILENAME', filename) commit('SET_FILENAME', filename)
commit('SET_PATHNAME', pathname) commit('SET_PATHNAME', pathname)
commit('SET_MARKDOWN', file) commit('SET_MARKDOWN', file)
commit('SET_STATUS', true) commit('SET_SAVE_STATUS', true)
if (!windowActive) { if (!windowActive) {
bus.$emit('file-loaded', file) bus.$emit('file-loaded', file)
} }
}) })
}, },
EDITE_FILE ({ commit }) { EDITE_FILE ({ commit }) {
commit('SET_STATUS', false) commit('SET_SAVE_STATUS', false)
}, },
EXPORT ({ commit, state }, { type, content }) { EXPORT ({ commit, state }, { type, content }) {
const { filename, pathname } = state const { filename, pathname } = state
ipcRenderer.send('AGANI::response-export', { type, content, filename, pathname }) ipcRenderer.send('AGANI::response-export', { type, content, filename, pathname })
}, },
SAVE_FILE ({ commit, state }, { markdown, wordCount, cursor }) { SAVE_FILE ({ commit, state }, { markdown, wordCount, cursor }) {
const { pathname, autoSave, markdown: oldMarkdown } = state
commit('SET_MARKDOWN', markdown) commit('SET_MARKDOWN', markdown)
// set word count
if (wordCount) commit('SET_WORD_COUNT', wordCount) if (wordCount) commit('SET_WORD_COUNT', wordCount)
// set cursor
if (cursor) commit('SET_CURSOR', cursor) if (cursor) commit('SET_CURSOR', cursor)
const { pathname } = state // save to file only when the markdown changed!
if (pathname) { if (markdown !== oldMarkdown) {
commit('SET_STATUS', true) if (pathname && autoSave) {
ipcRenderer.send('AGANI::response-file-save', { pathname, markdown }) commit('SET_SAVE_STATUS', true)
} else { ipcRenderer.send('AGANI::response-file-save', { pathname, markdown })
commit('SET_STATUS', false) } else {
commit('SET_SAVE_STATUS', false)
}
} }
}, },
SELECTION_CHANGE ({ commit }, changes) { SELECTION_CHANGE ({ commit }, changes) {
const { start, end } = changes const { start, end } = changes
if (start.key === end.key && start.block.text) { if (start.key === end.key && start.block.text) {
@ -163,15 +198,18 @@ const actions = {
ipcRenderer.send('AGANI::selection-change', changes) ipcRenderer.send('AGANI::selection-change', changes)
}, },
SELECTION_FORMATS ({ commit }, formats) { SELECTION_FORMATS ({ commit }, formats) {
ipcRenderer.send('AGANI::selection-formats', formats) ipcRenderer.send('AGANI::selection-formats', formats)
}, },
// listen for export from main process // listen for export from main process
LISTEN_FOR_EXPORT ({ commit, state }) { LISTEN_FOR_EXPORT ({ commit, state }) {
ipcRenderer.on('AGANI::export', (e, { type }) => { ipcRenderer.on('AGANI::export', (e, { type }) => {
bus.$emit('export', type) bus.$emit('export', type)
}) })
}, },
LISTEN_FOR_INSERT_IMAGE ({ commit, state }) { LISTEN_FOR_INSERT_IMAGE ({ commit, state }) {
ipcRenderer.on('AGANI::INSERT_IMAGE', (e, { filename: imagePath, type }) => { ipcRenderer.on('AGANI::INSERT_IMAGE', (e, { filename: imagePath, type }) => {
if (type === 'absolute' || type === 'relative') { if (type === 'absolute' || type === 'relative') {
@ -186,16 +224,19 @@ const actions = {
} }
}) })
}, },
LISTEN_FOR_EDIT ({ commit }) { LISTEN_FOR_EDIT ({ commit }) {
ipcRenderer.on('AGANI::edit', (e, { type }) => { ipcRenderer.on('AGANI::edit', (e, { type }) => {
bus.$emit(type) bus.$emit(type)
}) })
}, },
LISTEN_FOR_VIEW ({ commit }) { LISTEN_FOR_VIEW ({ commit }) {
ipcRenderer.on('AGANI::view', (e, data) => { ipcRenderer.on('AGANI::view', (e, data) => {
commit('SET_MODE', data) commit('SET_MODE', data)
}) })
}, },
LISTEN_FOR_PARAGRAPH_INLINE_STYLE ({ commit }) { LISTEN_FOR_PARAGRAPH_INLINE_STYLE ({ commit }) {
ipcRenderer.on('AGANI::paragraph', (e, { type }) => { ipcRenderer.on('AGANI::paragraph', (e, { type }) => {
bus.$emit('paragraph', type) bus.$emit('paragraph', type)
@ -204,6 +245,7 @@ const actions = {
bus.$emit('format', type) bus.$emit('format', type)
}) })
}, },
LISTEN_FOR_CLOSE ({ commit, state }) { LISTEN_FOR_CLOSE ({ commit, state }) {
ipcRenderer.on('AGANI::ask-for-close', e => { ipcRenderer.on('AGANI::ask-for-close', e => {
const { isSaved, markdown, pathname, filename } = state const { isSaved, markdown, pathname, filename } = state

View File

@ -1,6 +1,10 @@
#### User Preferences ### :bust_in_silhouette:User Preferences
Edit and save to update preferences: Edit and save to update preferences, You can only change the json bellow!
- **theme**: *String* `dark` or `light`
- **autoSave**: *Boolean* `true` or `false`
```json ```json
{ {
@ -9,4 +13,10 @@ Edit and save to update preferences:
} }
``` ```
More user preferences comming. More user preferences comming soon.
**Please use `CmdOrCtrl + S` to save your preferences and reload Mark Text to use your setting!**
> Your friends at Mark Text