Mark Text Preference (#1003)

* dynamic change element-ui theme to our themeColor

* add some ui components

* add preference doc

* add json schema file

* update preference.json and schema.json

* reset to old commit

* rename preference file for rebase

* rebase develop

* add setting window

* user electron-store to store preferences

* add themes setting

* add select component

* add markdown pref

* fix: bool and select init value

* add font size setting

* editor pref

* add general preference

* search preference

* update menu after preference changed

* update muya codes

* prevent scale setting window

* fix: titlebar undefined

* update input style

* remove window from windowManager after close setting window

* remove old docs and preference.md

* if a setting window is already created, no need to create another one, just move it to top

* rename openFilesInNewWindow to openFileInNewWindow

* change aidou runtime

* change hideQuickInsertHint by setting page runtime

* change autopair runtime

* change codefont and codefontfamily dynamic

* change default value of autoSave to false

* update bulletListMarker

* fix style error

* add custom titlebar to settings window

* add window shadow for Linux and Windows

* fix Windows build

* fix some typo error

* update doc

* add default menu and setting menu

* fix update menu bug

* fix typo

* remove mac titlebarstyle

* do not need to send titlebarstyle to renderer

* fix typo

* crash Mark Text if no initial preference.json file

* update the path

* add showCustomTitleBar prop

* set empty settings menu on Linux/Windows + workaround
This commit is contained in:
Ran Luo 2019-05-09 09:26:28 +08:00 committed by GitHub
parent a74a17118a
commit 4bd22b6dc5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
72 changed files with 2486 additions and 539 deletions

View File

@ -42,8 +42,8 @@
}
},
"plugins": [["component", {
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
"style": false,
"libraryName": "element-ui"
}
], "transform-runtime"]
}

View File

@ -47,7 +47,7 @@ const rendererConfig = {
}
},
{
test: /(katex|github\-markdown|prism[\-a-z]*|\.theme)\.css$/,
test: /(theme\-chalk(?:\/|\\)index|katex|github\-markdown|prism[\-a-z]*|\.theme)\.css$/,
use: [
'to-string-loader',
'css-loader'
@ -55,7 +55,7 @@ const rendererConfig = {
},
{
test: /\.css$/,
exclude: /(katex|github\-markdown|prism[\-a-z]*|\.theme)\.css$/,
exclude: /(theme\-chalk(?:\/|\\)index|katex|github\-markdown|prism[\-a-z]*|\.theme)\.css$/,
use: [
proMode ? MiniCssExtractPlugin.loader : 'style-loader',
{ loader: 'css-loader', options: { importLoaders: 1 } },
@ -131,6 +131,12 @@ const rendererConfig = {
name: 'fonts/[name]--[folder].[ext]'
}
}
},
{
test: /\.md$/,
use: [
'raw-loader'
]
}
]
},

48
doc/PREFERENCE.md Normal file
View File

@ -0,0 +1,48 @@
## Mark Text Preference
#### General
| Key | Type | Default Value | Description |
| -------------------- | ------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| autoSave | Boolean | ture | Automatically save the content being edited. option value: true, false |
| autoSaveDelay | Number | 3000 | The delay in milliseconds after a changed file is saved automatically? 3000 ~10000 |
| titleBarStyle | String | csd | The title bar style. the native option will result in a standard gray opaque title bar. `csd` (macOS only), `custom`, `native` |
| openFilesInNewWindow | Boolean | false | true, false |
| aidou | Boolean | true | Enable aidou. Optional value: true, false |
| fileSortBy | String | modified | Sort files in opened folder by `created` time, modified time and title. |
| startUp | String | lastState | The action after Mark Text startup, open the last edited content, open the specified folder or blank page, optional value: `lasteState`, `folder`, `blank` |
| language | String | en | The language Mark Text use. |
#### Editor
| Key | Type | Defaut | Description |
| ---------------------- | ------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| fontSize | Number | 16 | Font size in pixels. 12 ~ 32 |
| editorFontFamily | String | Open Sans | Font Family |
| lineHeight | Number | 1.6 | Line Height |
| autoPairBracket | Boolean | true | Automatically brackets when editing |
| autoPairMarkdownSyntax | Boolean | true | Autocomplete markdown syntax |
| autoPairQuote | Boolean | true | Automatic completion of quotes |
| endOfLine | String | default | The newline character used at the end of each line. The default value is default, which will be selected according to your system intelligence. `lf` `crlf` `default` |
| textDirection | String | ltr | The writing text direction, optional value: `ltr` or `rtl` |
| codeFontSize | Number | 14 | Font size on code block, the range is 12 ~ 28 |
| codeFontFamily | String | `DejaVu Sans Mono` | Code font family |
| hideQuickInsertHint | Boolean | false | Hide hint for quickly creating paragraphs |
| imageDropAction | String | folder | The default behavior after paste or drag the image to Mark Text, upload it to the image cloud (if configured), move to the specified folder, insert the path |
#### Markdown
| Key | Type | Default | Description |
| ------------------- | ------- | ------- | --------------------------------------------------------------------------------------------------------------------------------- |
| preferLooseListItem | Boolean | true | The preferred list type. |
| bulletListMarker | String | `-` | The preferred marker used in bullet list, optional value: `-`, `*` `+` |
| orderListDelimiter | String | `.` | The preferred delimiter used in order list, optional value: `.` `)` |
| preferHeadingStyle | String | `atx` | The preferred heading style in Mark Text, optional value `atx` `setext`, [more info](https://spec.commonmark.org/0.29/#atx-headings) |
| tabSize | Number | 4 | The number of spaces a tab is equal to |
| listIndentation | String | 1 | The list indentation of sub list items or paragraphs, optional value `dfm`, `tab` or number 1~4 |
#### Theme
| Key | Type | Default | Description |
| ----- | ------ | ------- | -------------------------------------------------------------- |
| theme | String | light | `dark` `graphite` `material-dark` `one-dark` `light` `ulysses` |

View File

@ -1,42 +0,0 @@
# Settings
## Options
### Editor
- **fontSize**: The editor font size.
- **editorFontFamily**: The editor font family name.
- **codeFontSize**: The code block font size.
- **codeFontFamily**: The code block font family name.
- **lineHeight**: The line height of the editor.
- **tabSize**: The number of spaces a tab is equal to.
- **listIndentation**: The list indentation of sub list items or paragraphs (`"dfm"`, `"tab"` or number `1-4`)
- `dfm`: Each subsequent paragraph in a list item must be indented by either 4 spaces or one tab, we are using 4 spaces (used by Bitbucket and Daring Fireball Markdown Spec).
- `number`: Dynamic indent subsequent paragraphs by the given number (1-4) plus list marker width (default).
- **autoPairBracket**: If `true` the editor automatically closes brackets.
- **autoPairMarkdownSyntax**: If `true` the editor automatically closes inline markdown like `*` or `_`.
- **autoPairQuote**: If `true` the editor automatically closes quotes (`'` and `"`).
- **hideQuickInsertHint**: If `true` the editor hides the quick insert hint.
- **preferLooseListItem**: The preferred list style. If `true` a loose list is preferred otherwise a tight list.
- **bulletListMarker**: The preferred list item bullet (`+`,`-` or `*`).
### Files
- **autoSave**: Automatically saves the file after editing.
- **endOfLine**: The default end of line character (`lf`, `crlf` or `default`).
### Window
- **theme**: Specifies the theme (`dark`, `graphite`, `material-dark`, `one-dark`, `light` or `ulysses`).
- **textDirection**: The editor text direction (`ltr` or `rtl`).
- **openFilesInNewWindow**: If `true` files should opened in a new window.
- **titleBarStyle**: Specifies the title bar (`csd` (macOS only), `custom` or `native`).
### Misc
- **aidou**: Show aidou menu entry.
### Deprecated
- **lightColor**
- **darkColor**

View File

@ -176,6 +176,7 @@
"dragula": "^3.7.2",
"electron-is-accelerator": "^0.1.2",
"electron-log": "^3.0.5",
"electron-store": "^3.2.0",
"electron-window-state": "^5.0.3",
"element-resize-detector": "^1.2.0",
"element-ui": "^2.8.2",
@ -203,6 +204,7 @@
"view-image": "^0.0.1",
"vue": "^2.6.10",
"vue-electron": "^1.0.6",
"vue-router": "^3.0.6",
"vuex": "^3.1.0"
},
"devDependencies": {
@ -265,6 +267,7 @@
"node-loader": "^0.6.0",
"postcss-loader": "^3.0.0",
"postcss-preset-env": "^6.6.0",
"raw-loader": "^2.0.0",
"require-dir": "^1.2.0",
"spectron": "^5.0.0",
"style-loader": "^0.23.1",

View File

@ -1,11 +1,13 @@
import { app, ipcMain, systemPreferences } from 'electron'
import { isOsx } from '../config'
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'
class App {
@ -182,6 +184,17 @@ class App {
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.
@ -205,8 +218,18 @@ class App {
})
ipcMain.on('app-create-settings-window', () => {
const { paths } = this._accessor
this.createEditorWindow(paths.preferencesFilePath)
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 => {

View File

@ -11,10 +11,11 @@ import Watcher from '../filesystem/watcher'
* @property {WindowType} type The window type.
*/
// Currently it makes no sense because we have only one (editor) window but we
// will add more windows like settings and worker windows.
// Window type marktext support.
export const WindowType = {
EDITOR: 0
BASE: 'base', // You shold never create a `BASE` window.
EDITOR: 'editor',
SETTING: 'setting'
}
class WindowActivityList {
@ -86,8 +87,7 @@ class WindowManager extends EventEmitter {
this._windows.set(window.id, window)
if (!this._appMenu.has(window.id)) {
// TODO: Build a default menu for macOS.
this._appMenu.addMenu(window.id, null)
this._appMenu.addDefaultMenu(window.id)
}
if (this.windowCount === 1) {
@ -175,6 +175,28 @@ class WindowManager extends EventEmitter {
return this._windows.size
}
/**
*
* @param {type} type the WindowType one of ['base', 'editor', 'setting']
* Return the windows of the given {type}
*/
windowsOfType (type) {
if (!WindowType[type.toUpperCase()]) {
console.error(`${type} is not a valid window type.`)
}
const { windows } = this
const result = []
for (var [key, value] of windows) {
if (value.type === type) {
result.push({
id: key,
win: value
})
}
}
return result
}
// --- helper ---------------------------------
closeWatcher () {
@ -280,8 +302,14 @@ class WindowManager extends EventEmitter {
})
ipcMain.on('broadcast-preferences-changed', prefs => {
for (const { browserWindow } of this._windows.values()) {
browserWindow.webContents.send('AGANI::user-preference', prefs)
// We can not dynamic change the title bar style, so do not need to send it to renderer.
if (typeof prefs.titleBarStyle !== 'undefined') {
delete prefs.titleBarStyle
}
if (Object.keys(prefs).length > 0) {
for (const { browserWindow } of this._windows.values()) {
browserWindow.webContents.send('AGANI::user-preference', prefs)
}
}
})
}

View File

@ -15,6 +15,26 @@ export const defaultWinOptions = {
titleBarStyle: 'hiddenInset'
}
export const defaultPreferenceWinOptions = {
width: 950,
height: 650,
webPreferences: {
nodeIntegration: true,
webSecurity: false,
},
fullscreenable: false,
fullscreen: false,
resizable: false,
minimizable: false,
maximizable: false,
useContentSize: true,
show: false,
frame: false,
thickFrame: !isOsx,
titleBarStyle: 'hiddenInset',
center: true
}
export const EXTENSIONS = [
'markdown',
'mdown',
@ -68,7 +88,7 @@ export const EXTENSION_HASN = {
pdf: '.pdf'
}
export const TITLE_BAR_HEIGHT = isOsx ? 21 : 25
export const TITLE_BAR_HEIGHT = isOsx ? 21 : 32
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/

View File

@ -17,10 +17,6 @@ export const typeMode = (win, type, item) => {
}
}
export const changeFont = win => {
win.webContents.send('AGANI::font-setting')
}
export const layout = (item, win, type) => {
win.webContents.send('AGANI::listen-for-view-layout', { [type]: item.checked })
}

View File

@ -2,9 +2,10 @@ import fs from 'fs'
import path from 'path'
import { app, ipcMain, Menu } from 'electron'
import log from 'electron-log'
import { isLinux } from '../config'
import { ensureDirSync, isDirectory, isFile } from '../filesystem'
import { parseMenu } from '../keyboard/shortcutHandler'
import configureMenu from '../menu/templates'
import configureMenu, { configSettingMenu } from '../menu/templates'
class AppMenu {
@ -92,9 +93,16 @@ class AppMenu {
fs.writeFileSync(RECENTS_PATH, json, 'utf-8')
}
addMenu (windowId, menu=null) {
addDefaultMenu (windowId) {
const { windowMenus } = this
windowMenus.set(windowId, { menu })
const menu = this.buildSettingMenu() // Setting menu is also the fallback menu.
windowMenus.set(windowId, menu)
}
addSettingMenu (window) {
const { windowMenus } = this
const menu = this.buildSettingMenu()
windowMenus.set(window.id, menu)
}
addEditorMenu (window) {
@ -146,7 +154,7 @@ class AppMenu {
setActiveWindow (windowId) {
if (this.activeWindowId !== windowId) {
// Change application menu to the current window menu.
Menu.setApplicationMenu(this.getWindowMenuById(windowId))
this._setApplicationMenu(this.getWindowMenuById(windowId))
this.activeWindowId = windowId
}
}
@ -170,6 +178,15 @@ class AppMenu {
}
}
buildSettingMenu () {
if (this.isOsx) {
const menuTemplate = configSettingMenu(this._keybindings)
const menu = Menu.buildFromTemplate(menuTemplate)
return { menu }
}
return { menu: null }
}
updateAppMenu (recentUsedDocuments) {
if (!recentUsedDocuments) {
recentUsedDocuments = this.getRecentlyUsedDocuments()
@ -197,7 +214,7 @@ class AppMenu {
// update application menu if necessary
const { activeWindowId } = this
if (activeWindowId === key) {
Menu.setApplicationMenu(newMenu)
this._setApplicationMenu(newMenu)
}
})
}
@ -212,6 +229,55 @@ class AppMenu {
menu.checked = flag
}
updateThemeMenu = theme => {
this.windowMenus.forEach((value, key) => {
const { menu } = value
const themeMenus = menu.getMenuItemById('themeMenu')
if (!themeMenus) {
return
}
themeMenus.submenu.items.forEach(item => (item.checked = false))
themeMenus.submenu.items
.forEach(item => {
if (item.id && item.id === theme) {
item.checked = true
}
})
})
}
updateAutoSaveMenu = autoSave => {
this.windowMenus.forEach((value, key) => {
const { menu } = value
const autoSaveMenu = menu.getMenuItemById('autoSaveMenuItem')
if (!autoSaveMenu) {
return
}
autoSaveMenu.checked = autoSave
})
}
updateAidouMenu = bool => {
this.windowMenus.forEach((value, key) => {
const { menu } = value
const aidouMenu = menu.getMenuItemById('aidou')
if (!aidouMenu) {
return
}
aidouMenu.visible = bool
})
}
_setApplicationMenu (menu) {
if (isLinux && !menu) {
// WORKAROUND for Electron#16521: We cannot hide the (application) menu on Linux.
const dummyMenu = Menu.buildFromTemplate([])
Menu.setApplicationMenu(dummyMenu)
} else {
Menu.setApplicationMenu(menu)
}
}
_listenForIpcMain () {
ipcMain.on('mt::add-recently-used-document', (e, pathname) => {
this.addRecentlyUsedDocument(pathname)
@ -220,6 +286,18 @@ class AppMenu {
ipcMain.on('menu-clear-recently-used', () => {
this.clearRecentlyUsedDocuments()
})
ipcMain.on('broadcast-preferences-changed', prefs => {
if (prefs.theme !== undefined) {
this.updateThemeMenu(prefs.theme)
}
if (prefs.autoSave !== undefined) {
this.updateAutoSaveMenu(prefs.autoSave)
}
if (prefs.aidou !== undefined) {
this.updateAidouMenu(prefs.aidou)
}
})
}
}

View File

@ -108,6 +108,7 @@ export default function (keybindings, userPreference) {
}, {
label: 'Aidou',
visible: aidou,
id: 'aidou',
accelerator: keybindings.getAccelerator('editAidou'),
click (menuItem, browserWindow) {
actions.edit(browserWindow, 'aidou')

View File

@ -101,6 +101,7 @@ export default function (keybindings, userPreference, recentlyUsedFiles) {
label: 'Auto Save',
type: 'checkbox',
checked: autoSave,
id: 'autoSaveMenuItem',
click (menuItem, browserWindow) {
actions.autoSave(menuItem, browserWindow)
}

View File

@ -10,6 +10,18 @@ import theme from './theme'
export dockMenu from './dock'
/**
* Create the setting window menu.
*
* @param {Keybindings} keybindings The keybindings instance
*/
export const configSettingMenu = (keybindings) => {
return [
...(process.platform === 'darwin' ? [ marktext(keybindings) ] : []),
help()
]
}
/**
* Create the application menu for the editor window.
*

View File

@ -14,14 +14,6 @@ export default function (keybindings) {
}
}, {
type: 'separator'
}, {
label: 'Font...',
accelerator: keybindings.getAccelerator('viewChangeFont'),
click (item, browserWindow) {
actions.changeFont(browserWindow)
}
}, {
type: 'separator'
}, {
id: 'sourceCodeModeMenuItem',
label: 'Source Code Mode',

View File

@ -1,12 +1,14 @@
import fse from 'fs-extra'
import fs from 'fs'
import path from 'path'
import EventEmitter from 'events'
import Store from 'electron-store'
import { BrowserWindow, ipcMain, systemPreferences } from 'electron'
import log from 'electron-log'
import { isOsx, isWindows } from '../config'
import { ensureDirSync } from '../filesystem'
import { hasSameKeys } from '../utils'
import { getStringRegKey, winHKEY } from '../platform/win32/registry.js'
import schema from './schema'
const isDarkSystemMode = () => {
if (isOsx) {
@ -19,6 +21,8 @@ const isDarkSystemMode = () => {
return false
}
const PREFERENCES_FILE_NAME = 'preferences'
class Preference extends EventEmitter {
/**
@ -27,33 +31,38 @@ class Preference extends EventEmitter {
constructor (paths) {
super()
const { userDataPath, preferencesFilePath } = paths
this._userDataPath = userDataPath
const { preferencesPath } = paths
this.preferencesPath = preferencesPath
this.hasPreferencesFile = fs.existsSync(path.join(this.preferencesPath, `./${PREFERENCES_FILE_NAME}.json`))
this.store = new Store({
schema,
name: PREFERENCES_FILE_NAME
})
this.cache = null
this.staticPath = path.join(__static, 'preference.md')
this.settingsPath = preferencesFilePath
this.staticPath = path.join(__static, 'preference.json')
this.init()
}
init () {
const { settingsPath, staticPath } = this
const defaultSettings = this.loadJson(staticPath)
let userSetting = null
// Try to load settings or write default settings if file doesn't exists.
if (!fs.existsSync(settingsPath) || !this.loadJson(settingsPath)) {
ensureDirSync(this._userDataPath)
const content = fs.readFileSync(staticPath, 'utf-8')
fs.writeFileSync(settingsPath, content, 'utf-8')
userSetting = this.loadJson(settingsPath)
init = () => {
let defaultSettings = null
try {
defaultSettings = fse.readJsonSync(this.staticPath)
if (isDarkSystemMode()) {
userSetting.theme = 'dark'
defaultSettings.theme = 'dark'
}
this.validateSettings(userSetting)
} catch (err) {
log(err)
}
if (!defaultSettings) {
throw new Error('Can not load static preference.json file')
}
// I don't know why `this.store.size` is 3 when first load, so I just check file existed.
if (!this.hasPreferencesFile) {
this.store.set(defaultSettings)
} else {
userSetting = this.loadJson(settingsPath)
let userSetting = this.getAll()
// Update outdated settings
const requiresUpdate = !hasSameKeys(defaultSettings, userSetting)
@ -70,35 +79,24 @@ class Preference extends EventEmitter {
userSetting[key] = defaultSettings[key]
}
}
this.validateSettings(userSetting)
this.writeJson(userSetting, false)
.catch(log.error)
} else {
this.validateSettings(userSetting)
this.store.set(userSetting)
}
}
if (!userSetting) {
console.error('ERROR: Cannot load settings.')
userSetting = defaultSettings
this.validateSettings(userSetting)
}
this.cache = userSetting
this.emit('loaded', userSetting)
this._listenForIpcMain()
}
getAll () {
return this.cache
return this.store.store
}
setItem (key, value) {
const preUserSetting = this.getAll()
const newUserSetting = this.cache = Object.assign({}, preUserSetting, { [key]: value })
this.emit('entry-changed', key, value)
return this.writeJson(newUserSetting)
ipcMain.emit('broadcast-preferences-changed', { [key]: value })
return this.store.set(key, value)
}
getItem (key) {
return this.store.get(key)
}
/**
@ -112,123 +110,25 @@ class Preference extends EventEmitter {
return
}
const preUserSetting = this.getAll()
const newUserSetting = this.cache = Object.assign({}, preUserSetting, settings)
Object.keys(settings).map(key => {
this.emit('entry-changed', key, settings[key])
})
return this.writeJson(newUserSetting)
}
loadJson (filePath) {
const JSON_REG = /```json(.+)```/g
try {
const content = fs.readFileSync(filePath, 'utf-8')
const userSetting = JSON_REG.exec(content.replace(/(?:\r\n|\n)/g, ''))[1]
return JSON.parse(userSetting)
} catch (err) {
log.error(err)
return null
}
}
writeJson (json, async = true) {
const { settingsPath } = this
return new Promise((resolve, reject) => {
const content = fs.readFileSync(this.staticPath, 'utf-8')
const tokens = content.split('```')
const newContent = tokens[0] +
'```json\n' +
JSON.stringify(json, null, 2) +
'\n```' +
tokens[2]
if (async) {
fs.writeFile(settingsPath, newContent, 'utf-8', err => {
if (err) reject(err)
else resolve(json)
})
} else {
fs.writeFileSync(settingsPath, newContent, 'utf-8')
resolve(json)
}
this.setItem(key, settings[key])
})
}
getPreferedEOL () {
const { endOfLine } = this.getAll()
const endOfLine = this.getItem('endOfLine')
if (endOfLine === 'lf') {
return 'lf'
}
return endOfLine === 'crlf' || isWindows ? 'crlf' : 'lf'
}
/**
* workaround for issue #265
* expects: settings != null
* @param {Object} settings preferences object
*/
validateSettings (settings) {
if (!settings) {
log.warn('Broken settings detected: invalid settings object.')
return
}
exportJSON () {
// todo
}
let brokenSettings = false
if (!settings.theme || (settings.theme && !/^(?:dark|graphite|material-dark|one-dark|light|ulysses)$/.test(settings.theme))) {
brokenSettings = true
settings.theme = 'light'
}
if (!settings.codeFontFamily || typeof settings.codeFontFamily !== 'string' || settings.codeFontFamily.length > 60) {
settings.codeFontFamily = 'DejaVu Sans Mono'
}
if (!settings.codeFontSize || typeof settings.codeFontSize !== 'string' || settings.codeFontFamily.length > 10) {
settings.codeFontSize = '14px'
}
if (!settings.endOfLine || !/^(?:lf|crlf)$/.test(settings.endOfLine)) {
settings.endOfLine = isWindows ? 'crlf' : 'lf'
}
if (!settings.bulletListMarker ||
(settings.bulletListMarker && !/^(?:\+|-|\*)$/.test(settings.bulletListMarker))) {
brokenSettings = true
settings.bulletListMarker = '-'
}
if (!settings.titleBarStyle || !/^(?:native|csd|custom)$/.test(settings.titleBarStyle)) {
settings.titleBarStyle = 'csd'
}
if (!settings.tabSize || typeof settings.tabSize !== 'number') {
settings.tabSize = 4
} else if (settings.tabSize < 1) {
settings.tabSize = 1
} else if (settings.tabSize > 4) {
settings.tabSize = 4
}
if (!settings.listIndentation) {
settings.listIndentation = 1
} else if (typeof settings.listIndentation === 'number') {
if (settings.listIndentation < 1 || settings.listIndentation > 4) {
settings.listIndentation = 1
}
} else if (settings.listIndentation !== 'dfm') {
settings.listIndentation = 1
}
if (brokenSettings) {
log.warn('Broken settings detected; fallback to default value(s).')
}
// Currently no CSD is available on Linux and Windows (GH#690)
const titleBarStyle = settings.titleBarStyle.toLowerCase()
if (!isOsx && titleBarStyle === 'csd') {
settings.titleBarStyle = 'custom'
}
importJSON () {
// todo
}
_listenForIpcMain () {
@ -238,9 +138,7 @@ class Preference extends EventEmitter {
})
ipcMain.on('mt::set-user-preference', (e, settings) => {
this.setItems(settings).then(() => {
ipcMain.emit('broadcast-preferences-changed', settings)
}).catch(log.error)
this.setItems(settings)
})
}
}

View File

@ -0,0 +1,171 @@
{
"autoSave": {
"description": "General--Automatically save the content being edited.",
"type": "boolean"
},
"autoSaveDelay": {
"description": "General--How long do you want to save your document(ms)?",
"type": "number",
"minimum": 500
},
"titleBarStyle": {
"description": "General--The title bar style (Windows and Linux system only).",
"enum": [
"custom",
"native"
]
},
"openFilesInNewWindow": {
"description": "General--Open file in new window",
"type": "boolean"
},
"aidou": {
"description": "General--Enable aidou",
"type": "boolean"
},
"fileSortBy": {
"description": "General--Sort files in opened folder by created time, modified time and title.",
"enum": [
"modified",
"created",
"title"
]
},
"startUp": {
"description": "General--The action after Mark Text startup, open the last edited content, open the specified folder or blank page",
"enum": [
"folder",
"lastState",
"blank"
]
},
"language": {
"description": "General--The language Mark Text use.",
"type": "string"
},
"editorFontFamily": {
"description": "Editor--editor font family",
"enum": [
"Open Sans",
"Clear Sans",
"Helvetica Neue",
"Helvetica",
"Arial",
"sans-serif"
]
},
"fontSize": {
"description": "Editor--Font size in pixels",
"type": "number",
"maximum": 32,
"minimum": 12,
"default": 16
},
"lineHeight": {
"description": "Editor--Line Height",
"type": "number",
"maximum": 2,
"minimum": 1.2,
"default": 1.6
},
"codeFontSize": {
"description": "Editor--Font size in code Block, the range is 12 ~ 18",
"type": "number",
"maximum": 28,
"minimum": 12,
"default": 14
},
"codeFontFamily": {
"description": "Editor--Font family used in code block",
"enum": [
"DejaVu Sans Mono",
"Source Code Pro",
"Droid Sans Mono",
"monospace"
]
},
"autoPairBracket": {
"description": "Editor--Automatically brackets when editing",
"type": "boolean"
},
"autoPairMarkdownSyntax": {
"description": "Editor--Autocomplete markdown syntax",
"type": "boolean"
},
"autoPairQuote": {
"description": "Editor--Automatic completion of quotes",
"type": "boolean"
},
"endOfLine": {
"description": "Editor--The newline character used at the end of each line. The default value is default, which will be selected according to your system intelligence.",
"enum": [
"default",
"lf",
"crlf"
]
},
"textDirection": {
"description": "Editor--The writing text direction",
"enum": [
"ltr",
"rtl"
]
},
"hideQuickInsertHint": {
"description": "Editor--Hide hint for quickly creating paragraphs",
"type": "boolean"
},
"imageDropAction": {
"description": "Editor--The default behavior after paste or drag the image to Mark Text",
"enum": [
"upload",
"folder",
"path"
]
},
"preferLooseListItem": {
"description": "Markdown--The preferred list type",
"type": "boolean"
},
"bulletListMarker": {
"description": "Markdown--The marker used in bullet list",
"enum": [
"-",
"*",
"+"
]
},
"orderListDelimiter": {
"description": "Markdown--The dilimiter used in order list",
"enum": [
".",
")"
]
},
"preferHeadingStyle": {
"description": "Markdown--The preferred heading style in Mark Text",
"enum": [
"atx",
"setext"
]
},
"tabSize": {
"description": "Markdown--Replace the tab with x spaces",
"type": "number"
},
"listIndentation": {
"description": "Markdown--Select the indent of list",
"enum": [
"dfm",
"tab",
1,
2,
3,
4
]
},
"theme": {
"description": "Theme--Select the theme used in Mark Text",
"type": "string"
}
}

57
src/main/windows/base.js Normal file
View File

@ -0,0 +1,57 @@
import EventEmitter from 'events'
import { WindowType } from '../app/windowManager'
class BaseWindow extends EventEmitter {
/**
* @param {Accessor} accessor The application accessor for application instances.
*/
constructor (accessor) {
super()
this._accessor = accessor
this.type = WindowType.BASE
this.id = null
this.browserWindow = null
this.quitting = false
}
destroy () {
this.quitting = true
this.emit('bye')
this.removeAllListeners()
this.browserWindow.destroy()
this.browserWindow = null
this.id = null
}
// --- private ---------------------------------
_buildUrlWithSettings (windowId, env, userPreference) {
// NOTE: Only send absolutely necessary values. Theme and titlebar settings
// are sended because we delay load the preferences.
const { type } = this
const { debug, paths } = env
const { codeFontFamily, codeFontSize, theme, titleBarStyle } = userPreference.getAll()
const baseUrl = process.env.NODE_ENV === 'development'
? `http://localhost:9091`
: `file://${__dirname}/index.html`
const url = new URL(baseUrl)
url.searchParams.set('udp', paths.userDataPath)
url.searchParams.set('debug', debug ? '1' : '0')
url.searchParams.set('wid', windowId)
url.searchParams.set('type', type)
// Settings
url.searchParams.set('cff', codeFontFamily)
url.searchParams.set('cfs', codeFontSize)
url.searchParams.set('theme', theme)
url.searchParams.set('tbs', titleBarStyle)
return url.toString()
}
}
export default BaseWindow

View File

@ -1,28 +1,22 @@
import path from 'path'
import EventEmitter from 'events'
import BaseWindow from './base'
import { BrowserWindow, ipcMain } from 'electron'
import log from 'electron-log'
import windowStateKeeper from 'electron-window-state'
import { WindowType } from '../app/windowManager'
import { TITLE_BAR_HEIGHT, defaultWinOptions, isLinux } from '../config'
import { TITLE_BAR_HEIGHT, defaultWinOptions, isLinux, isOsx } from '../config'
import { isDirectory, isMarkdownFile, normalizeAndResolvePath } from '../filesystem'
import { loadMarkdownFile } from '../filesystem/markdown'
import { ensureWindowPosition } from './utils'
class EditorWindow extends EventEmitter {
class EditorWindow extends BaseWindow {
/**
* @param {Accessor} accessor The application accessor for application instances.
*/
constructor (accessor) {
super()
this._accessor = accessor
this.id = null
this.browserWindow = null
super(accessor)
this.type = WindowType.EDITOR
this.quitting = false
}
/**
@ -53,11 +47,11 @@ class EditorWindow extends EventEmitter {
// Enable native or custom/frameless window and titlebar
const { titleBarStyle } = preferences.getAll()
if (titleBarStyle === 'custom') {
winOptions.titleBarStyle = ''
} else if (titleBarStyle === 'native') {
winOptions.frame = true
winOptions.titleBarStyle = ''
if (!isOsx) {
winOptions.titleBarStyle = 'default'
if (titleBarStyle === 'native') {
winOptions.frame = true
}
}
let win = this.browserWindow = new BrowserWindow(winOptions)
@ -160,16 +154,6 @@ class EditorWindow extends EventEmitter {
browserWindow.webContents.send('AGANI::open-project', pathname)
}
destroy () {
this.quitting = true
this.emit('bye')
this.removeAllListeners()
this.browserWindow.destroy()
this.browserWindow = null
this.id = null
}
// --- private ---------------------------------
// Only called once during window bootstrapping.
@ -213,30 +197,6 @@ class EditorWindow extends EventEmitter {
})
}
}
_buildUrlWithSettings (windowId, env, userPreference) {
// NOTE: Only send absolutely necessary values. Theme and titlebar settings
// are sended because we delay load the preferences.
const { debug, paths } = env
const { codeFontFamily, codeFontSize, theme, titleBarStyle } = userPreference.getAll()
const baseUrl = process.env.NODE_ENV === 'development'
? `http://localhost:9091`
: `file://${__dirname}/index.html`
const url = new URL(baseUrl)
url.searchParams.set('udp', paths.userDataPath)
url.searchParams.set('debug', debug ? '1' : '0')
url.searchParams.set('wid', windowId)
// Settings
url.searchParams.set('cff', codeFontFamily)
url.searchParams.set('cfs', codeFontSize)
url.searchParams.set('theme', theme)
url.searchParams.set('tbs', titleBarStyle)
return url.toString()
}
}
export default EditorWindow

View File

@ -0,0 +1,84 @@
import path from 'path'
import BaseWindow from './base'
import { BrowserWindow, ipcMain } from 'electron'
import { WindowType } from '../app/windowManager'
import { TITLE_BAR_HEIGHT, defaultPreferenceWinOptions, isLinux, isOsx } from '../config'
class SettingWindow extends BaseWindow {
/**
* @param {Accessor} accessor The application accessor for application instances.
*/
constructor (accessor) {
super(accessor)
this.type = WindowType.SETTING
}
/**
* Creates a new setting window.
*
* @param {*} [options] BrowserWindow options.
*/
createWindow (options = {}) {
const { menu: appMenu, env, preferences } = this._accessor
const winOptions = Object.assign({}, defaultPreferenceWinOptions, options)
if (isLinux) {
winOptions.icon = path.join(__static, 'logo-96px.png')
}
// Enable native or custom/frameless window and titlebar
const { titleBarStyle } = preferences.getAll()
if (!isOsx) {
winOptions.titleBarStyle = 'default'
if (titleBarStyle === 'native') {
winOptions.frame = true
}
}
let win = this.browserWindow = new BrowserWindow(winOptions)
this.id = win.id
// Create a menu for the current window
appMenu.addSettingMenu(win)
win.once('ready-to-show', async () => {
win.show()
this.emit('window-ready-to-show')
})
win.on('focus', () => {
this.emit('window-focus')
win.webContents.send('AGANI::window-active-status', { status: true })
})
// Lost focus
win.on('blur', () => {
this.emit('window-blur')
win.webContents.send('AGANI::window-active-status', { status: false })
})
win.on('close', event => {
this.emit('window-close')
event.preventDefault()
ipcMain.emit('window-close-by-id', win.id)
})
// The window is now destroyed.
win.on('closed', () => {
this.emit('window-closed')
// Free window reference
win = null
})
win.loadURL(this._buildUrlWithSettings(this.id, env, preferences))
win.setSheetOffset(TITLE_BAR_HEIGHT)
return win
}
}
export default SettingWindow

View File

@ -157,11 +157,14 @@ export const CURSOR_DNA = getLongUniqueId()
export const DEFAULT_TURNDOWN_CONFIG = {
headingStyle: 'atx', // setext or atx
hr: '---',
bulletListMarker: '-', // -, +, or *
codeBlockStyle: 'fenced', // fenced or indented
fence: '```', // ``` or ~~~
emDelimiter: '*', // _ or *
strongDelimiter: '**', // ** or __
linkStyle: 'inlined',
linkReferenceStyle: 'full',
blankReplacement (content, node, options) {
if (node && node.classList.contains('ag-soft-line-break')) {
return LINE_BREAK
@ -230,7 +233,7 @@ export const MUYA_DEFAULT_OPTION = {
autoPairMarkdownSyntax: true,
autoPairQuote: true,
bulletListMarker: '-',
orderListMarker: '.',
orderListDelimiter: '.',
tabSize: 4,
// bullet/list marker width + listIndentation, tab or Daring Fireball Markdown (4 spaces) --> list indentation
listIndentation: 1,

View File

@ -131,7 +131,7 @@ const inputCtrl = ContentState => {
event.type === 'input'
) {
const { offset } = start
const { autoPairBracket, autoPairMarkdownSyntax, autoPairQuote } = this
const { autoPairBracket, autoPairMarkdownSyntax, autoPairQuote } = this.muya.options
const inputChar = text.charAt(+offset - 1)
const preInputChar = text.charAt(+offset - 2)
const prePreInputChar = text.charAt(+offset - 3)

View File

@ -94,7 +94,7 @@ const paragraphCtrl = ContentState => {
ContentState.prototype.handleListMenu = function (paraType, insertMode) {
const { start, end, affiliation } = this.selectionChange(this.cursor)
const { orderListMarker, bulletListMarker, preferLooseListItem } = this
const { orderListDelimiter, bulletListMarker, preferLooseListItem } = this.muya.options
const [blockType, listType] = paraType.split('-')
const isListed = affiliation.slice(0, 3).filter(b => /ul|ol/.test(b.type))
@ -127,7 +127,7 @@ const paragraphCtrl = ContentState => {
if (listType === 'order') {
listBlock.start = listBlock.start || 1
listBlock.children.forEach(b => (b.bulletMarkerOrDelimiter = orderListMarker))
listBlock.children.forEach(b => (b.bulletMarkerOrDelimiter = orderListDelimiter))
}
if (
(listType === 'bullet' && oldListType === 'order') ||

View File

@ -164,7 +164,7 @@ const updateCtrl = ContentState => {
ContentState.prototype.updateList = function (block, type, marker = '', line) {
const cleanMarker = marker ? marker.trim() : null
const { preferLooseListItem } = this
const { preferLooseListItem } = this.muya.options
const wrapperTag = type === 'order' ? 'ol' : 'ul' // `bullet` => `ul` and `order` => `ol`
const { start, end } = this.cursor
const startOffset = start.offset
@ -210,7 +210,7 @@ const updateCtrl = ContentState => {
if (type === 'order') {
bulletMarkerOrDelimiter = (cleanMarker && cleanMarker.length >= 2) ? cleanMarker.slice(-1) : '.'
} else {
const { bulletListMarker } = this
const { bulletListMarker } = this.muya.options
bulletMarkerOrDelimiter = marker ? marker.charAt(0) : bulletListMarker
}
newListItemBlock.bulletMarkerOrDelimiter = bulletMarkerOrDelimiter
@ -286,7 +286,7 @@ const updateCtrl = ContentState => {
}
ContentState.prototype.updateTaskListItem = function (block, type, marker = '') {
const { preferLooseListItem } = this
const { preferLooseListItem } = this.muya.options
const parent = this.getParent(block)
const grandpa = this.getParent(parent)
const checked = /\[x\]\s/i.test(marker) // use `i` flag to ignore upper case or lower case

View File

@ -17,8 +17,7 @@ class Muya {
}
constructor (container, options) {
this.options = Object.assign({}, MUYA_DEFAULT_OPTION, options)
const { focusMode, markdown } = this.options
this.focusMode = focusMode
const { markdown } = this.options
this.markdown = markdown
this.container = getContainer(container, this.options)
this.eventCenter = new EventCenter()
@ -42,7 +41,8 @@ class Muya {
contentState.stateRender.setContainer(container.children[0])
eventCenter.subscribe('stateChange', this.dispatchChange)
contentState.listenForPathChange()
const { focusMode, markdown } = this
const { markdown } = this
const { focusMode } = this.options
this.setMarkdown(markdown)
this.setFocusMode(focusMode)
this.mutationObserver()
@ -156,13 +156,14 @@ class Muya {
}
setFocusMode (bool) {
const { container, focusMode } = this
const { container } = this
const { focusMode } = this.options
if (bool && !focusMode) {
container.classList.add(CLASS_OR_ID['AG_FOCUS_MODE'])
} else {
container.classList.remove(CLASS_OR_ID['AG_FOCUS_MODE'])
}
this.focusMode = bool
this.options.focusMode = bool
}
setFont ({ fontSize, lineHeight }) {
@ -170,10 +171,6 @@ class Muya {
if (lineHeight) this.contentState.lineHeight = lineHeight
}
setListItemPreference (preferLooseListItem) {
this.contentState.preferLooseListItem = preferLooseListItem
}
setTabSize (tabSize) {
if (!tabSize || typeof tabSize !== 'number') {
tabSize = 4
@ -309,6 +306,19 @@ class Muya {
if (needRender) {
this.contentState.render()
}
// Set quick insert hint visibility
const hideQuickInsertHint = options['hideQuickInsertHint']
if (typeof hideQuickInsertHint !== 'undefined') {
const hasClass = this.container.classList.contains('ag-show-quick-insert-hint')
if (hideQuickInsertHint && hasClass) {
this.container.classList.remove('ag-show-quick-insert-hint')
} else if (!hideQuickInsertHint && !hasClass) {
this.container.classList.add('ag-show-quick-insert-hint')
}
}
if (options.bulletListMarker) {
this.contentState.turndownConfig.bulletListMarker = options.bulletListMarker
}
}
destroy () {

View File

@ -150,7 +150,7 @@ kbd {
}
.v-modal {
background: var(--maskColor);
background: var(--maskColor) !important;
}
body>*:first-child {

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M768.352529 965.280644 187.019441 965.280644c-53.431933 0-96.906074-43.474141-96.906074-96.907097L90.113367 222.426859c0-53.431933 43.474141-96.906074 96.906074-96.906074l452.16831 0c17.854647 0 32.29145 14.471596 32.29145 32.292474s-14.437827 32.293497-32.29145 32.293497l-452.16831 0c-17.790178 0-32.292474 14.500249-32.292474 32.287357l0 645.951805c0 17.819854 14.502295 32.287357 32.292474 32.287357l581.367881 0c17.819854 0 32.287357-14.46648 32.287357-32.287357L800.674679 351.620289c0-17.819854 14.437827-32.29145 32.292474-32.29145 17.85567 0 32.293497 14.471596 32.293497 32.29145l0 516.752234C865.259626 921.806503 821.785485 965.280644 768.352529 965.280644L768.352529 965.280644 768.352529 965.280644zM578.600861 448.52841c-8.280594 0-16.532535-3.1569-22.811542-9.476839-12.632715-12.632715-12.632715-33.029254 0-45.661969L876.761588 72.412217c12.632715-12.632715 33.034371-12.632715 45.667086 0 12.632715 12.633738 12.632715 33.031301 0 45.664016L601.451288 439.051571C595.167165 445.37151 586.886571 448.52841 578.600861 448.52841L578.600861 448.52841 578.600861 448.52841zM445.379697 448.52841 251.599272 448.52841c-17.819854 0-32.292474-14.471596-32.292474-32.293497 0-17.819854 14.471596-32.29145 32.292474-32.29145l193.779402 0c17.819854 0 32.292474 14.471596 32.292474 32.29145C477.671147 434.056813 463.199551 448.52841 445.379697 448.52841L445.379697 448.52841 445.379697 448.52841zM639.187751 642.306788 251.599272 642.306788c-17.819854 0-32.292474-14.436804-32.292474-32.292474s14.471596-32.292474 32.292474-32.292474l387.55778 0c17.850553 0 32.28838 14.436804 32.28838 32.292474C671.480224 627.869984 657.042397 642.306788 639.187751 642.306788L639.187751 642.306788 639.187751 642.306788zM639.187751 642.306788" /></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M981.333333 255.2c-0.533333-70.133333-57.6-127.2-128-127.2H170.666667c-70.4 0-127.466667 56.8-128 127.2V768c0 70.666667 57.333333 128 128 128h682.666666c70.666667 0 128-57.333333 128-128V256.533333v-1.333333zM170.666667 213.333333h682.666666c16.8 0 31.2 9.6 38.133334 23.733334L512 502.666667 132.533333 237.066667c6.933333-14.133333 21.333333-23.733333 38.133334-23.733334z m682.666666 597.333334H170.666667c-23.466667 0-42.666667-19.2-42.666667-42.666667V337.866667l359.466667 251.733333c7.466667 5.066667 16 7.733333 24.533333 7.733333s17.066667-2.666667 24.533333-7.733333L896 337.866667V768c0 23.466667-19.2 42.666667-42.666667 42.666667z" /></svg>

After

Width:  |  Height:  |  Size: 921 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M0 853.333333l42.666667 21.333334 100.48-256H488.533333l115.84 256L640 853.333333 320 106.666667z m469.333333-277.333333H161.28L320 213.333333zM984.32 874.666667L1024 853.333333l-192-490.666666-192 490.666666 39.68 21.333334 64-170.666667h176zM761.386667 661.333333L832 477.653333 903.04 661.333333h-141.653333z" /></svg>

After

Width:  |  Height:  |  Size: 590 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M415.378286 558.756571h-214.857143C140.379429 558.756571 91.428571 509.677714 91.428571 449.371429V237.385143C91.428571 177.078857 140.379429 128 200.539429 128h214.857142c60.16 0 109.092571 49.078857 109.092572 109.385143v211.986286c0 60.306286-48.950857 109.385143-109.110857 109.385142zM200.539429 203.172571a34.194286 34.194286 0 0 0-34.102858 34.212572v211.986286a34.194286 34.194286 0 0 0 34.102858 34.194285h214.857142a34.194286 34.194286 0 0 0 34.121143-34.194285V237.385143a34.194286 34.194286 0 0 0-34.121143-34.212572h-214.857142zM415.378286 896h-214.857143C140.379429 896 91.428571 846.939429 91.428571 786.651429v-71.789715c0-60.342857 48.950857-109.385143 109.110858-109.385143h214.857142c60.16 0 109.092571 49.060571 109.092572 109.385143v71.789715c0 60.288-48.950857 109.348571-109.110857 109.348571zM200.539429 680.649143a34.194286 34.194286 0 0 0-34.102858 34.212571v71.789715a34.194286 34.194286 0 0 0 34.102858 34.194285h214.857142a34.194286 34.194286 0 0 0 34.121143-34.194285v-71.789715a34.212571 34.212571 0 0 0-34.121143-34.212571h-214.857142zM823.442286 896h-143.232c-60.141714 0-109.110857-49.060571-109.110857-109.348571V237.385143C571.099429 177.078857 620.068571 128 680.210286 128h143.232C883.620571 128 932.571429 177.078857 932.571429 237.385143V786.651429c0 60.288-48.950857 109.348571-109.129143 109.348571z m-143.232-692.827429a34.194286 34.194286 0 0 0-34.102857 34.212572V786.651429a34.194286 34.194286 0 0 0 34.102857 34.194285h143.232a34.194286 34.194286 0 0 0 34.121143-34.194285V237.385143a34.194286 34.194286 0 0 0-34.121143-34.212572h-143.232z" /></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M179.42702 205.00814 102.678032 307.33876 164.076404 307.33876 164.076404 716.66124 102.678032 716.66124 179.42702 818.99186 256.173962 716.66124 194.77559 716.66124 194.77559 307.33876 256.173962 307.33876ZM102.678032 102.33062l818.644959 0 0 30.699186-818.644959 0 0-30.699186ZM102.678032 890.970194l818.644959 0 0 30.699186-818.644959 0 0-30.699186ZM683.91493 256.17345 531.800463 256.17345l-186.242751 511.653099 99.765191 0 50.167586-142.602835L716.785571 625.223714l50.16861 142.602835 103.202477 0L683.91493 256.17345zM526.202978 538.654057l67.264986-187.677427 25.296129 0 67.007113 187.677427L526.202978 538.654057z" /></svg>

After

Width:  |  Height:  |  Size: 902 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M820.906667 913.066667c0 32.426667-25.6 58.026667-58.026667 58.026666H100.693333c-32.426667 0-58.026667-25.6-58.026666-58.026666V133.12c0-32.426667 25.6-58.026667 58.026666-58.026667h662.186667c32.426667 0 58.026667 25.6 58.026667 58.026667v779.946667z m-39.253334-740.693334c0-32.426667-25.6-58.026667-58.026666-58.026666H139.946667c-32.426667 0-58.026667 25.6-58.026667 58.026666v701.44c0 32.426667 25.6 58.026667 58.026667 58.026667h583.68c32.426667 0 58.026667-25.6 58.026666-58.026667V172.373333z m-97.28 76.8c0-10.24-8.533333-17.066667-17.066666-17.066666H215.04c-10.24 0-17.066667 8.533333-17.066667 17.066666v3.413334c0 10.24 8.533333 17.066667 17.066667 17.066666h452.266667c10.24 0 17.066667-8.533333 17.066666-17.066666v-3.413334z m0 136.533334c0-10.24-8.533333-17.066667-17.066666-17.066667H215.04c-10.24 0-17.066667 8.533333-17.066667 17.066667v3.413333c0 10.24 8.533333 17.066667 17.066667 17.066667h452.266667c10.24 0 17.066667-8.533333 17.066666-17.066667v-3.413333z m0 116.053333c0-10.24-8.533333-17.066667-17.066666-17.066667H215.04c-10.24 0-17.066667 8.533333-17.066667 17.066667v3.413333c0 10.24 8.533333 17.066667 17.066667 17.066667h452.266667c10.24 0 17.066667-8.533333 17.066666-17.066667v-3.413333z m-332.8 116.053333c0-10.24-8.533333-17.066667-17.066666-17.066666h-119.466667c-10.24 0-17.066667 8.533333-17.066667 17.066666v3.413334c0 10.24 8.533333 17.066667 17.066667 17.066666h119.466667c10.24 0 17.066667-8.533333 17.066666-17.066666v-3.413334zM882.346667 892.586667c-11.946667 0-22.186667-10.24-22.186667-22.186667V110.933333c0-11.946667 10.24-20.48 20.48-20.48 11.946667 0 22.186667 10.24 22.186667 20.48V870.4c0 11.946667-8.533333 22.186667-20.48 22.186667z" /><path d="M882.346667 957.44L836.266667 878.933333h87.04zM493.226667 868.693333h-27.306667V651.946667h40.96L568.32 836.266667l59.733333-184.32h40.96v216.746666h-13.653333l-13.653333-17.066666V667.306667v17.066666l-59.733334 184.32h-27.306666L494.933333 684.373333v184.32z" /></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="196.92px" viewBox="0 0 1040 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M717.056236 383.936299l-51.226708 0c-28.2893 0-51.226708 22.936385-51.226708 51.225685l0 128.062678c0 28.2893 22.937408 51.225685 51.226708 51.225685l51.226708 0c28.2893 0 51.225685-22.936385 51.225685-51.225685L768.281921 435.161984C768.281921 406.872684 745.345536 383.936299 717.056236 383.936299zM717.056236 537.611308c0 14.158465-11.480472 25.612331-25.613354 25.612331-14.132882 0-25.612331-11.453866-25.612331-25.612331l0-76.835969c0-14.158465 11.480472-25.613354 25.612331-25.613354 14.133905 0 25.613354 11.453866 25.613354 25.613354L717.056236 537.611308zM1013.977739 426.580538 859.776751 165.30079c-8.888438-15.063067-22.294772-25.975605-37.57171-32.080649-32.708959-34.856879-79.187527-56.638975-130.762159-56.638975L332.862064 76.581166c-51.575656 0-98.0532 21.782096-130.761136 56.639998-15.276938 6.105045-28.683273 17.017582-37.572734 32.079626L10.327206 426.580538c-21.26021 36.069497-8.655124 82.217537 28.239158 103.028515l115.00836 64.967664 0 199.163015c0 99.024318 80.264045 153.678078 179.287339 153.678078l358.580818 0c99.024318 0 179.290409-80.266092 179.290409-179.290409L870.733291 594.575694l115.00836-64.966641C1022.63184 508.798075 1035.238972 462.650035 1013.977739 426.580538zM153.574724 536.518417l-67.058278-37.875632c-24.589025-13.907755-33.019021-44.647873-18.809391-68.684312l85.86767-145.555074L153.574724 536.518417zM646.620024 127.807874c0 56.5786-60.205197 102.45137-134.467551 102.45137-74.261331 0-134.466528-45.873794-134.466528-102.45137L646.620024 127.807874zM819.507606 742.515071c0 84.893482-68.810179 153.677055-153.678078 153.677055L358.475418 896.192126c-84.8679 0-153.675008-68.783573-153.675008-153.677055l0-461.030142c0-76.150354 55.402821-139.361001 128.093377-151.545508 1.332345 83.883479 81.06734 151.545508 179.258687 151.545508 98.19237 0 177.926342-67.662029 179.25971-151.545508 72.690556 12.183484 128.096447 75.394131 128.096447 151.545508L819.508629 742.515071zM937.791569 498.642784l-67.058278 37.875632 0-252.111948 85.86767 145.552004C970.807521 453.995935 962.377524 484.736053 937.791569 498.642784z" /></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -1,10 +1,21 @@
/* Common CSS use by both light and dark themes */
:root {
--titleBarHeight: 32px;
--editorAreaWidth: 700px;
--editorAreaWidth: 750px;
/*editor*/
/*Theme color cluster*/
--themeColor: rgba(33, 181, 111, 1);
--themeColor90: rgba(33, 181, 111, .9);
--themeColor80: rgba(33, 181, 111, .8);
--themeColor70: rgba(33, 181, 111, .7);
--themeColor60: rgba(33, 181, 111, .6);
--themeColor50: rgba(33, 181, 111, .5);
--themeColor40: rgba(33, 181, 111, .4);
--themeColor30: rgba(33, 181, 111, .3);
--themeColor20: rgba(33, 181, 111, .2);
--themeColor10: rgba(33, 181, 111, .1);
--highlightColor: rgba(33, 181, 111, .4);
--selectionColor: rgba(0, 0, 0, .1);
--editorColor: rgba(0, 0, 0, .7);
@ -84,10 +95,6 @@ body {
border: solid 1px var(--floatBorderColor);
}
.el-tooltip__popper.is-dark .popper__arrow {
display: none;
}
.el-slider__button {
border-color: var(--themeColor);
}
@ -96,7 +103,7 @@ body {
background-color: var(--themeColor);
}
.ag-dialog-table {
.el-dialog.ag-dialog-table {
border-radius: 8px;
box-shadow: 0 4px 8px 0 var(--floatBorderColor);
border: solid 1px var(--floatBorderColor);
@ -107,3 +114,9 @@ body {
width: 1.5em;
height: 1.5em;
}
.ag-underdevelop {
opacity: .3;
pointer-events: none;
cursor: not-allowed;
}

View File

@ -1,6 +1,16 @@
:root {
/*editor*/
--themeColor: #409eff;
--themeColor90: rgba(64, 158, 255, .9);
--themeColor80: rgba(64, 158, 255, .8);
--themeColor70: rgba(64, 158, 255, .7);
--themeColor60: rgba(64, 158, 255, .6);
--themeColor50: rgba(64, 158, 255, .5);
--themeColor40: rgba(64, 158, 255, .4);
--themeColor30: rgba(64, 158, 255, .3);
--themeColor20: rgba(64, 158, 255, .2);
--themeColor10: rgba(64, 158, 255, .1);
--highlightColor: rgba(102, 177, 255, .6);
--selectionColor: rgba(102, 177, 255, .3);
--editorColor: rgba(255, 255, 255, .7);

View File

@ -1,5 +1,15 @@
:root {
--themeColor: rgb(104, 134, 170);
--themeColor90: rgba(104, 134, 170, .9);
--themeColor80: rgba(104, 134, 170, .8);
--themeColor70: rgba(104, 134, 170, .7);
--themeColor60: rgba(104, 134, 170, .6);
--themeColor50: rgba(104, 134, 170, .5);
--themeColor40: rgba(104, 134, 170, .4);
--themeColor30: rgba(104, 134, 170, .3);
--themeColor20: rgba(104, 134, 170, .2);
--themeColor10: rgba(104, 134, 170, .1);
--highlightColor: rgba(104, 134, 170, .4);
--selectionColor: rgba(0, 0, 0, .1);
--editorColor: rgba(43, 48, 50, .7);

View File

@ -1,6 +1,16 @@
:root {
/*editor*/
--themeColor: #f48237;
--themeColor90: rgba(244, 130, 55, .9);
--themeColor80: rgba(244, 130, 55, .8);
--themeColor70: rgba(244, 130, 55, .7);
--themeColor60: rgba(244, 130, 55, .6);
--themeColor50: rgba(244, 130, 55, .5);
--themeColor40: rgba(244, 130, 55, .4);
--themeColor30: rgba(244, 130, 55, .3);
--themeColor20: rgba(244, 130, 55, .2);
--themeColor10: rgba(244, 130, 55, .1);
--highlightColor: rgba(244, 130, 55, .4);
--selectionColor: rgba(255, 255, 255, .2);
--editorColor: rgba(171, 178, 191, .8);

View File

@ -1,6 +1,16 @@
:root {
/*editor*/
--themeColor: #e2c08d;
--themeColor: rgba(226, 192, 141, 1);
--themeColor90: rgba(226, 192, 141, .9);
--themeColor80: rgba(226, 192, 141, .8);
--themeColor70: rgba(226, 192, 141, .7);
--themeColor60: rgba(226, 192, 141, .6);
--themeColor50: rgba(226, 192, 141, .5);
--themeColor40: rgba(226, 192, 141, .4);
--themeColor30: rgba(226, 192, 141, .3);
--themeColor20: rgba(226, 192, 141, .2);
--themeColor10: rgba(226, 192, 141, .1);
--highlightColor: #ffffff10;
--selectionColor: #67769660;
--editorColor: #9da5b4;

View File

@ -1,5 +1,15 @@
:root {
--themeColor: rgb(12, 139, 186);
--themeColor90: rgba(12, 139, 186, .9);
--themeColor80: rgba(12, 139, 186, .8);
--themeColor70: rgba(12, 139, 186, .7);
--themeColor60: rgba(12, 139, 186, .6);
--themeColor50: rgba(12, 139, 186, .5);
--themeColor40: rgba(12, 139, 186, .4);
--themeColor30: rgba(12, 139, 186, .3);
--themeColor20: rgba(12, 139, 186, .2);
--themeColor10: rgba(12, 139, 186, .1);
--highlightColor: rgba(12, 139, 186, .4);
--selectionColor: rgba(0, 0, 0, .1);
--editorColor: rgba(101, 101, 101, .7);

View File

@ -1,7 +1,7 @@
import path from 'path'
import { crashReporter, ipcRenderer } from 'electron'
import log from 'electron-log'
import EnvPaths from "common/envPaths";
import EnvPaths from 'common/envPaths'
let exceptionLogger = s => console.error(s)
@ -25,7 +25,9 @@ const parseUrlArgs = () => {
const titleBarStyle = params.get('tbs')
const userDataPath = params.get('udp')
const windowId = params.get('wid')
const type = params.get('type')
return {
type,
debug,
userDataPath,
windowId,
@ -62,13 +64,14 @@ const bootstrapRenderer = () => {
ipcRenderer.send('AGANI::handle-renderer-error', copy)
})
const { debug, initialState, userDataPath, windowId } = parseUrlArgs()
const { debug, initialState, userDataPath, windowId, type } = parseUrlArgs()
const marktext = {
initialState,
env: {
debug,
paths: new EnvPaths(userDataPath),
windowId
windowId,
type
}
}
global.marktext = marktext

View File

@ -2,7 +2,7 @@
<div
class="editor-wrapper"
:class="[{ 'typewriter': typewriter, 'focus': focus, 'source': sourceCode }]"
:style="{ 'lineHeight': lineHeight, 'fontSize': fontSize,
:style="{ 'lineHeight': lineHeight, 'fontSize': `${fontSize}px`,
'font-family': editorFontFamily ? `${editorFontFamily}, ${defaultFontFamily}` : `${defaultFontFamily}` }"
:dir="textDirection"
>
@ -86,6 +86,7 @@
import bus from '../../bus'
import Search from '../search.vue'
import { animatedScrollTo } from '../../util'
import { addCommonStyle } from '../../util/theme'
import { showContextMenu } from '../../contextMenu/editor'
import Printer from '@/services/printService'
import { DEFAULT_EDITOR_FONT_FAMILY } from '@/config'
@ -120,10 +121,13 @@
'autoPairMarkdownSyntax': state => state.preferences.autoPairMarkdownSyntax,
'autoPairQuote': state => state.preferences.autoPairQuote,
'bulletListMarker': state => state.preferences.bulletListMarker,
'orderListDelimiter': state => state.preferences.orderListDelimiter,
'tabSize': state => state.preferences.tabSize,
'listIndentation': state => state.preferences.listIndentation,
'lineHeight': state => state.preferences.lineHeight,
'fontSize': state => state.preferences.fontSize,
'codeFontSize': state => state.preferences.codeFontSize,
'codeFontFamily': state => state.preferences.codeFontFamily,
'lightColor': state => state.preferences.lightColor,
'darkColor': state => state.preferences.darkColor,
'editorFontFamily': state => state.preferences.editorFontFamily,
@ -175,7 +179,9 @@
preferLooseListItem: function (value, oldValue) {
const { editor } = this
if (value !== oldValue && editor) {
editor.setListItemPreference(value)
editor.setOptions({
preferLooseListItem: value
})
}
},
tabSize: function (value, oldValue) {
@ -205,6 +211,58 @@
if (value !== oldValue && editor) {
editor.setListIndentation(value)
}
},
hideQuickInsertHint: function (value, oldValue) {
const { editor } = this
if (value !== oldValue && editor) {
editor.setOptions({ hideQuickInsertHint: value })
}
},
autoPairBracket: function (value, oldValue) {
const { editor } = this
if (value !== oldValue && editor) {
editor.setOptions({ autoPairBracket: value })
}
},
autoPairMarkdownSyntax: function (value, oldValue) {
const { editor } = this
if (value !== oldValue && editor) {
editor.setOptions({ autoPairMarkdownSyntax: value })
}
},
autoPairQuote: function (value, oldValue) {
const { editor } = this
if (value !== oldValue && editor) {
editor.setOptions({ autoPairQuote: value })
}
},
bulletListMarker: function (value, oldValue) {
const { editor } = this
if (value !== oldValue && editor) {
editor.setOptions({ bulletListMarker: value })
}
},
orderListDelimiter: function (value, oldValue) {
const { editor } = this
if (value !== oldValue && editor) {
editor.setOptions({ orderListDelimiter: value })
}
},
codeFontSize: function (value, oldValue) {
if (value !== oldValue) {
addCommonStyle({
codeFontSize: value,
codeFontFamily: this.codeFontFamily
})
}
},
codeFontFamily: function (value, oldValue) {
if (value !== oldValue) {
addCommonStyle({
codeFontSize: this.codeFontSize,
codeFontFamily: value
})
}
}
},
created () {
@ -220,6 +278,7 @@
autoPairMarkdownSyntax,
autoPairQuote,
bulletListMarker,
orderListDelimiter,
tabSize,
listIndentation,
hideQuickInsertHint,
@ -243,6 +302,7 @@
autoPairMarkdownSyntax,
autoPairQuote,
bulletListMarker,
orderListDelimiter,
tabSize,
listIndentation,
hideQuickInsertHint
@ -607,25 +667,11 @@
& .el-button {
width: 70px;
}
& .el-button:focus,
& .el-button:hover {
& .el-button:focus {
color: var(--themeColor);
border-color: var(--highlightColor);
background-color: var(--selectionColor);
}
& .el-button--primary {
color: #fff;
background: var(--themeColor);
border-color: var(--highlightColor);
}
& .el-input-number.is-controls-right .el-input__inner {
background: var(--itemBgColor);
color: var(--editorColor);
}
& .el-input-number.is-controls-right .el-input__inner:focus {
border-color: var(--themeColor);
}
}
}
.editor-wrapper.source {

View File

@ -1,136 +0,0 @@
<template>
<div class="font">
<el-dialog
:visible.sync="showFontSetting"
:show-close="false"
:modal="false"
custom-class="ag-dialog-table"
width="400px"
>
<div slot="title">
<el-color-picker
v-model="tempColor"
size="small"
@active-change="colorChange"
></el-color-picker>
<svg class="icon" aria-hidden="true" :style="{ 'color': tempColor }">
<use xlink:href="#icon-font"></use>
</svg>
</div>
<div class="row">
<div class="label">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-fontsize"></use>
</svg>
</div>
<el-slider v-model="tempSize" :format-tooltip="formatSize" :min="12" :max="30" :step="1"
@change="sizeChange"
></el-slider>
</div>
<div class="row">
<div class="label">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-lineheight"></use>
</svg>
</div>
<el-slider v-model="tempHeight" :format-tooltip="formatHeight" :min="1" :max="3" :step="0.1"
@change="heightChange"
></el-slider>
</div>
</el-dialog>
</div>
</template>
<script>
import { mapState } from 'vuex'
import bus from '../bus'
export default {
data () {
return {
showFontSetting: false,
tempSize: 16,
tempColor: '',
tempHeight: 1.6
}
},
computed: {
...mapState({
'fontSize': state => state.preferences.fontSize,
'lightColor': state => state.preferences.lightColor,
'darkColor': state => state.preferences.darkColor,
'lineHeight': state => state.preferences.lineHeight
}),
defaultSize () {
return parseInt(this.fontSize, 10)
}
},
created () {
this.$nextTick(() => {
bus.$on('font-setting', this.handleFontSetting)
})
},
beforeDestroy () {
bus.$off('font-setting', this.handleFontSetting)
},
methods: {
handleFontSetting () {
this.showFontSetting = true
const { darkColor, lightColor, theme, lineHeight } = this
this.tempSize = this.defaultSize
this.tempColor = theme === 'dark' ? darkColor : lightColor
this.tempHeight = +lineHeight
},
formatSize (val) {
return `${val} px`
},
formatHeight (val) {
return `${val}`
},
colorChange (color) {
const COLOR_KEY = this.theme === 'dark' ? 'darkColor' : 'lightColor'
this.handleChange(COLOR_KEY)(color)
},
sizeChange (size) {
this.handleChange('fontSize')(size)
},
heightChange (height) {
this.handleChange('lineHeight')(height)
},
handleChange (type) {
return (value) => {
if (!value) return
if (type === 'fontSize') value = value + 'px'
this.$store.dispatch('CHANGE_FONT', { type, value })
}
}
}
}
</script>
<style>
.font .el-color-picker__trigger {
position: absolute;
top: 6px;
opacity: 0;
}
.font .el-dialog__header {
padding-top: 10px;
text-align: center;
}
.font svg.icon {
width: 1.5em;
height: 1.5em;
}
.font .row .label {
display: inline-block;
width: 50px;
height: 38px;
line-height: 38px;
vertical-align: text-bottom;
}
.font .row .el-slider {
width: 300px;
display: inline-block;
}
</style>

View File

@ -6,7 +6,7 @@
></div>
<div
class="title-bar"
:class="[{ 'active': active }, { 'tabs-visible': showTabBar }, { 'frameless': titleBarStyle === 'custom' }, { 'isOsx': platform === 'darwin' }]"
:class="[{ 'active': active }, { 'tabs-visible': showTabBar }, { 'frameless': titleBarStyle === 'custom' }, { 'isOsx': isOsx }]"
>
<div class="title">
<span v-if="!filename">Mark Text</span>
@ -30,9 +30,9 @@
<span class="save-dot" :class="{'show': !isSaved}"></span>
</span>
</div>
<div :class="titleBarStyle === 'custom' ? 'left-toolbar title-no-drag' : 'right-toolbar'">
<div :class="showCustomTitleBar ? 'left-toolbar title-no-drag' : 'right-toolbar'">
<div
v-if="titleBarStyle === 'custom'"
v-if="showCustomTitleBar"
class="frameless-titlebar-menu title-no-drag"
@click.stop="handleMenuClick"
>
@ -66,7 +66,7 @@
</el-tooltip>
</div>
<div
v-if="titleBarStyle === 'custom' && !isFullScreen"
v-if="titleBarStyle === 'custom' && !isFullScreen && !isOsx"
class="right-toolbar"
:class="[{ 'title-no-drag': titleBarStyle === 'custom' }]"
>
@ -102,9 +102,11 @@
import { mapState } from 'vuex'
import { minimizePath, restorePath, maximizePath, closePath } from '../assets/window-controls.js'
import { PATH_SEPARATOR } from '../config'
import { isOsx } from '@/util'
export default {
data () {
this.isOsx = isOsx
this.HASH = {
'word': {
short: 'W',
@ -157,6 +159,9 @@
if (!this.pathname) return []
const pathnameToken = this.pathname.split(PATH_SEPARATOR).filter(i => i)
return pathnameToken.slice(0, pathnameToken.length - 1).slice(-3)
},
showCustomTitleBar () {
return this.titleBarStyle === 'custom' && !this.isOsx
}
},
watch: {
@ -324,6 +329,9 @@
display: flex;
align-items: center;
flex-direction: row-reverse;
& .item {
margin-right: 10px;
}
}
.word-count {
@ -389,11 +397,10 @@
height: 28px;
line-height: 28px;
& .front {
color: var(--editorColor50);
opacity: .7;
}
& .text {
margin-left: 10px;
color: var(--editorColor30);
}
}
</style>

View File

@ -92,7 +92,7 @@
color: #E6A23C;
}
.el-upload-dragger {
background: var(--itemBgColor);
background: var(--itemBgColor) !important;
& .el-upload__text {
color: var(--sideBarColor);
& em {

View File

@ -2,10 +2,10 @@ import Vue from 'vue'
import VueElectron from 'vue-electron'
import axios from 'axios'
import sourceMapSupport from 'source-map-support'
import bootstrapRenderer from './bootstrap'
import VueRouter from 'vue-router'
import lang from 'element-ui/lib/locale/lang/en'
import locale from 'element-ui/lib/locale'
import bootstrapRenderer from './bootstrap'
import App from './app'
import store from './store'
import './assets/symbolIcon'
import {
@ -20,9 +20,16 @@ import {
ColorPicker,
Col,
Row,
Tree
Tree,
Autocomplete,
Switch,
Select,
Option,
Radio
} from 'element-ui'
import services from './services'
import routes from './router'
import { addElementStyle } from '@/util/theme'
import './assets/styles/index.css'
import './assets/styles/printService.css'
@ -30,6 +37,9 @@ import './assets/styles/printService.css'
// -----------------------------------------------
// Decode source map in production - must be registered first
addElementStyle()
sourceMapSupport.install({
environment: 'node',
handleUncaughtExceptions: false,
@ -57,6 +67,13 @@ Vue.use(ColorPicker)
Vue.use(Col)
Vue.use(Row)
Vue.use(Tree)
Vue.use(Autocomplete)
Vue.use(Switch)
Vue.use(Select)
Vue.use(Option)
Vue.use(Radio)
Vue.use(VueRouter)
Vue.use(VueElectron)
Vue.http = Vue.prototype.$http = axios
@ -66,9 +83,13 @@ services.forEach(s => {
Vue.prototype['$' + s.name] = s[s.name]
})
const router = new VueRouter({
routes: routes(global.marktext.env.type)
})
/* eslint-disable no-new */
new Vue({
components: { App },
store,
template: '<App/>'
router,
template: '<router-view class="view"></router-view>'
}).$mount('#app')

View File

@ -29,7 +29,6 @@
<aidou></aidou>
<upload-image></upload-image>
<about-dialog></about-dialog>
<font></font>
<rename></rename>
<tweet></tweet>
<import-modal></import-modal>
@ -46,7 +45,6 @@
import Aidou from '@/components/aidou/aidou'
import UploadImage from '@/components/uploadImage'
import AboutDialog from '@/components/about'
import Font from '@/components/font'
import Rename from '@/components/rename'
import Tweet from '@/components/tweet'
import ImportModal from '@/components/import'
@ -64,7 +62,6 @@
SideBar,
UploadImage,
AboutDialog,
Font,
Rename,
Tweet,
ImportModal

View File

@ -0,0 +1,95 @@
<template>
<div class="pref-container">
<title-bar v-if="showCustomTitleBar"></title-bar>
<side-bar></side-bar>
<div
class="pref-content"
:class="{ 'frameless': titleBarStyle === 'custom' || isOsx }"
>
<div class="title-bar" v-if="!showCustomTitleBar"></div>
<router-view class="pref-setting"></router-view>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
import TitleBar from '@/prefComponents/common/titlebar'
import SideBar from '@/prefComponents/sideBar'
import { addThemeStyle } from '@/util/theme'
import { DEFAULT_STYLE } from '@/config'
import { isOsx } from '@/util'
export default {
data () {
this.isOsx = isOsx
return {}
},
components: {
TitleBar,
SideBar
},
computed: {
...mapState({
'theme': state => state.preferences.theme,
'titleBarStyle': state => state.preferences.titleBarStyle
}),
showCustomTitleBar () {
return this.titleBarStyle === 'custom' && !this.isOsx
}
},
watch: {
theme: function (value, oldValue) {
if (value !== oldValue) {
addThemeStyle(value)
}
}
},
created () {
this.$nextTick(() => {
const state = global.marktext.initialState || DEFAULT_STYLE
addThemeStyle(state.theme)
this.$store.dispatch('ASK_FOR_USER_PREFERENCE')
})
}
}
</script>
<style scoped>
.pref-container {
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
display: flex;
background: var(--editorBgColor);
& .pref-content {
position: relative;
flex: 1;
display: flex;
flex-direction: column;
& .title-bar {
width: 100%;
height: var(--titleBarHeight);
position: fixed;
top: 0;
right: 0;
-webkit-app-region: drag;
}
& .pref-setting {
padding: 50px 20px;
padding-top: var(--titleBarHeight);
flex: 1;
height: calc(100vh - var(--titleBarHeight));
overflow: auto;
}
}
& .pref-content.frameless .pref-setting {
/* Move the scrollbar below the titlebar */
margin-top: var(--titleBarHeight);
padding-top: 0;
}
}
</style>

View File

@ -0,0 +1,93 @@
<template>
<section class="pref-switch-item" :class="{'ag-underdevelop': disable}">
<div class="description">
<span>{{description}}</span>
<i class="el-icon-info" v-if="more"
@click="handleMoreClick"
></i>
</div>
<el-switch
style="display: block"
v-model="status"
@change="handleSwitchChange"
:active-text="status ? 'On': 'Off'">
</el-switch>
</section>
</template>
<script>
import { shell } from 'electron'
export default {
data () {
return {
status: this.bool
}
},
props: {
description: String,
bool: Boolean,
onChange: Function,
more: String,
disable: {
type: Boolean,
default: false
}
},
watch: {
bool: function (value, oldValue) {
if (value !== oldValue) {
this.status = value
}
}
},
methods: {
handleMoreClick () {
if (typeof this.more === 'string') {
shell.openExternal(this.more)
}
},
handleSwitchChange (value) {
this.onChange(value)
}
}
}
</script>
<style>
.pref-switch-item {
font-size: 14px;
user-select: none;
margin: 20px 0;
color: var(--editorColor);
}
.pref-switch-item .description {
margin-bottom: 10px;
& i {
cursor: pointer;
opacity: .7;
color: var(--iconColor);
}
& i:hover {
color: var(--themeColor);
}
}
span.el-switch__core::after {
top: 3px;
left: 7px;
width: 10px;
height: 10px;
}
.el-switch .el-switch__core {
border: 2px solid var(--iconColor);
background: transparent;
box-sizing: border-box;
}
span.el-switch__label {
color: var(--editorColor50);
}
.el-switch:not(.is-checked) .el-switch__core::after {
background: var(--iconColor);
}
</style>

View File

@ -0,0 +1,100 @@
<template>
<section class="pref-range-item" :class="{'ag-underdevelop': disable}">
<div class="description">
<span>{{description}}: <span class="value">{{selectValue + (unit ? unit : '')}}</span></span>
<i class="el-icon-info" v-if="more"
@click="handleMoreClick"
></i>
</div>
<el-slider
v-model="selectValue"
@change="select"
:min="min"
:max="max"
:format-tooltip="value => value + (unit ? unit : '')"
:step="step">
</el-slider>
</section>
</template>
<script>
import { shell } from 'electron'
export default {
data () {
return {
selectValue: this.value
}
},
props: {
description: String,
value: String | Number,
min: Number,
max: Number,
onChange: Function,
unit: String,
step: Number,
more: String,
disable: {
type: Boolean,
default: false
}
},
watch: {
value: function (value, oldValue) {
if (value !== oldValue) {
this.selectValue = value
}
}
},
methods: {
handleMoreClick () {
if (typeof this.more === 'string') {
shell.openExternal(this.more)
}
},
select (value) {
this.onChange(value)
}
}
}
</script>
<style>
.pref-range-item {
margin: 20px 0;
font-size: 14px;
color: var(--editorColor);
& .el-slider {
width: 300px;
}
& .el-slider__runway,
& .el-slider__bar {
height: 4px;
}
& .el-slider__button {
width: 12px;
height: 12px;
}
& .el-slider__button-wrapper {
width: 20px;
height: 20px;
top: -9px;
}
}
.pref-select-item .description {
margin-bottom: 10px;
& .value {
color: var(--editorColor80);
}
& i {
cursor: pointer;
opacity: .7;
color: var(--iconColor);
}
& i:hover {
color: var(--themeColor);
}
}
</style>

View File

@ -0,0 +1,107 @@
<template>
<section class="pref-select-item" :class="{'ag-underdevelop': disable}">
<div class="description">
<span>{{description}}</span>
<i class="el-icon-info" v-if="more"
@click="handleMoreClick"
></i>
</div>
<el-select v-model="selectValue"
@change="select"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</section>
</template>
<script>
import { shell } from 'electron'
export default {
data () {
return {
selectValue: this.value
}
},
props: {
description: String,
value: String | Number,
options: Array,
onChange: Function,
more: String,
disable: {
type: Boolean,
default: false
}
},
watch: {
value: function (value, oldValue) {
if (value !== oldValue) {
this.selectValue = value
}
}
},
methods: {
handleMoreClick () {
if (typeof this.more === 'string') {
shell.openExternal(this.more)
}
},
select (value) {
this.onChange(value)
}
}
}
</script>
<style>
.pref-select-item {
margin: 20px 0;
font-size: 14px;
color: var(--editorColor);
& .el-select {
width: 180px;
}
& input.el-input__inner {
height: 30px;
background: transparent;
color: var(--editorColor);
border-color: var(--editorColor10);
}
& .el-input__icon,
& .el-input__inner {
line-height: 30px;
}
}
.pref-select-item .description {
margin-bottom: 10px;
& i {
cursor: pointer;
opacity: .7;
color: var(--iconColor);
}
& i:hover {
color: var(--themeColor);
}
}
li.el-select-dropdown__item {
color: var(--editorColor);
height: 30px;
}
li.el-select-dropdown__item.hover, li.el-select-dropdown__item:hover {
background: var(--floatHoverColor);
}
div.el-select-dropdown {
background: var(--floatBgColor);
border-color: var(--floatBorderColor);
& .popper__arrow {
display: none;
}
}
</style>

View File

@ -0,0 +1,12 @@
<template>
<div class="pref-separator"></div>
</template>
<style>
.pref-separator {
margin: 20px 0 20px 0;
height: 2px;
background: var(--editorColor04);
}
</style>

View File

@ -0,0 +1,86 @@
<template>
<div class="title-bar">
<div class="title">
<span>Mark Text - Settings</span>
</div>
<div class="frameless-titlebar-button frameless-titlebar-close" @click.stop="handleCloseClick">
<div>
<svg width="10" height="10">
<path :d="windowIconClose" />
</svg>
</div>
</div>
</div>
</template>
<script>
import { remote } from 'electron'
import { closePath } from '../../assets/window-controls.js'
export default {
data () {
this.windowIconClose = closePath
return {}
},
methods: {
handleCloseClick () {
remote.getCurrentWindow().close()
}
}
}
</script>
<style scoped>
.title-bar {
-webkit-app-region: drag;
user-select: none;
background: transparent;
height: var(--titleBarHeight);
box-sizing: border-box;
color: var(--editorColor50);
position: fixed;
left: 0;
top: 0;
right: 0;
z-index: 2;
transition: color .4s ease-in-out;
cursor: default;
}
.title {
padding: 0 142px;
height: 100%;
line-height: var(--titleBarHeight);
font-size: 14px;
text-align: center;
transition: all .25s ease-in-out;
}
.title:hover {
color: var(sideBarTitleColor);
}
.frameless-titlebar-button {
position: absolute;
display: block;
top: 0;
right: 0;
width: 46px;
height: var(--titleBarHeight);
-webkit-app-region: no-drag;
}
.frameless-titlebar-button > div {
position: absolute;
display: inline-flex;
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-50%);
}
.frameless-titlebar-close:hover {
background-color: rgb(228, 79, 79);
}
.frameless-titlebar-button svg {
fill: #000000
}
.frameless-titlebar-close:hover svg {
fill: #ffffff
}
</style>

View File

@ -0,0 +1,52 @@
export const editorFontFamilyOptions = [{
label: 'Open Sans',
value: 'Open Sans'
}, {
label: 'Clear Sans',
value: 'Clear Sans'
}, {
label: 'Helvetica Neue',
value: 'Helvetica Neue'
}, {
label: 'Helvetica',
value: 'Helvetica'
}, {
label: 'Arial',
value: 'Arial'
}, {
label: 'sans-serif',
value: 'sans-serif'
}]
export const endOfLineOptions = [{
label: 'default',
value: 'default'
}, {
label: 'Carriage return and Line feed(CRLF)',
value: 'crlf'
}, {
label: 'Line feed(Lf)',
value: 'lf'
}]
export const textDirectionOptions = [{
label: 'Left to Right',
value: 'ltr'
}, {
label: 'Right to Left',
value: 'rtl'
}]
export const codeFontFamilyOptions = [{
label: 'DejaVu Sans Mono',
value: 'DejaVu Sans Mono'
}, {
label: 'Source Code Pro',
value: 'Source Code Pro'
}, {
label: 'Droid Sans Mono',
value: 'Droid Sans Mono'
}, {
label: 'monospace',
value: 'monospace'
}]

View File

@ -0,0 +1,157 @@
<template>
<div class="pref-editor">
<h4>Editor</h4>
<range
description="Font size in editor"
:value="fontSize"
:min="12"
:max="32"
unit="px"
:step="1"
:onChange="value => onSelectChange('fontSize', value)"
></range>
<cur-select
description="Font used in editor"
:value="editorFontFamily"
:options="editorFontFamilyOptions"
:onChange="value => onSelectChange('editorFontFamily', value)"
></cur-select>
<range
description="Line height in editor"
:value="lineHeight"
:min="1.2"
:max="2.0"
:step="0.1"
:onChange="value => onSelectChange('lineHeight', value)"
></range>
<separator></separator>
<bool
description="Automatically brackets when editing"
:bool="autoPairBracket"
:onChange="value => onSelectChange('autoPairBracket', value)"
></bool>
<bool
description="Autocomplete markdown syntax"
:bool="autoPairMarkdownSyntax"
:onChange="value => onSelectChange('autoPairMarkdownSyntax', value)"
></bool>
<bool
description="Automatic completion of quotes"
:bool="autoPairQuote"
:onChange="value => onSelectChange('autoPairQuote', value)"
></bool>
<separator></separator>
<cur-select
description="The default end of line character, if you select default, which will be selected according to your system intelligence"
:value="endOfLine"
:options="endOfLineOptions"
:onChange="value => onSelectChange('endOfLine', value)"
></cur-select>
<cur-select
description="The writing text direction"
:value="textDirection"
:options="textDirectionOptions"
:onChange="value => onSelectChange('textDirection', value)"
></cur-select>
<separator></separator>
<range
description="Code block font size in editor"
:value="codeFontSize"
:min="12"
:max="28"
unit="px"
:step="1"
:onChange="value => onSelectChange('codeFontSize', value)"
></range>
<cur-select
description="Font used in code block"
:value="codeFontFamily"
:options="codeFontFamilyOptions"
:onChange="value => onSelectChange('codeFontFamily', value)"
></cur-select>
<separator></separator>
<bool
description="Hide hint for quickly creating paragraphs"
:bool="hideQuickInsertHint"
:onChange="value => onSelectChange('hideQuickInsertHint', value)"
></bool>
<separator></separator>
<section class="image-ctrl ag-underdevelop">
<div>The default behavior after paste or drag the image to Mark Text</div>
<el-radio v-model="imageDropAction" label="upload">Upload image to cloud</el-radio>
<el-radio v-model="imageDropAction" label="folder">Move image to sepcial folder</el-radio>
<el-radio v-model="imageDropAction" label="path">Insert absolute or relative path of image</el-radio>
</section>
</div>
</template>
<script>
import { mapState } from 'vuex'
import Range from '../common/range'
import CurSelect from '../common/select'
import Bool from '../common/bool'
import Separator from '../common/separator'
import {
editorFontFamilyOptions,
endOfLineOptions,
textDirectionOptions,
codeFontFamilyOptions
} from './config'
export default {
components: {
Range,
CurSelect,
Bool,
Separator
},
data () {
this.editorFontFamilyOptions = editorFontFamilyOptions
this.endOfLineOptions = endOfLineOptions
this.textDirectionOptions = textDirectionOptions
this.codeFontFamilyOptions = codeFontFamilyOptions
return {}
},
computed: {
...mapState({
fontSize: state => state.preferences.fontSize,
editorFontFamily: state => state.preferences.editorFontFamily,
lineHeight: state => state.preferences.lineHeight,
autoPairBracket: state => state.preferences.autoPairBracket,
autoPairMarkdownSyntax: state => state.preferences.autoPairMarkdownSyntax,
autoPairQuote: state => state.preferences.autoPairQuote,
endOfLine: state => state.preferences.endOfLine,
textDirection: state => state.preferences.textDirection,
codeFontSize: state => state.preferences.codeFontSize,
codeFontFamily: state => state.preferences.codeFontFamily,
hideQuickInsertHint: state => state.preferences.hideQuickInsertHint,
imageDropAction: state => state.preferences.imageDropAction
})
},
methods: {
onSelectChange (type, value) {
this.$store.dispatch('SET_SINGLE_PREFERENCE', { type, value })
}
}
}
</script>
<style scoped>
.pref-editor {
& h4 {
text-transform: uppercase;
margin: 0;
font-weight: 100;
}
& .image-ctrl {
font-size: 14px;
user-select: none;
margin: 20px 0;
color: var(--editorColor);
& label {
display: block;
margin: 20px 0;
}
}
}
</style>

View File

@ -0,0 +1,23 @@
export const titleBarStyleOptions = [{
label: 'Custom',
value: 'custom'
}, {
label: 'Native',
value: 'native'
}]
export const fileSortByOptions = [{
label: 'Create time',
value: 'created'
}, {
label: 'Modified time',
value: 'modified'
}, {
label: 'Title',
value: 'title'
}]
export const languageOptions = [{
label: 'English',
value: 'en'
}]

View File

@ -0,0 +1,131 @@
<template>
<div class="pref-general">
<h4>General</h4>
<bool
description="Automatically save the content being edited"
:bool="autoSave"
:onChange="value => onSelectChange('autoSave', value)"
></bool>
<range
description="How long do you want to save your document?"
:value="autoSaveDelay"
:min="3000"
:max="10000"
unit="ms"
:step="100"
:onChange="value => onSelectChange('autoSaveDelay', value)"
:disable="true"
></range>
<cur-select
v-if="!isOsx"
description="The title bar style, frameless or not. (You need to restart Mark Text to enable it)"
:value="titleBarStyle"
:options="titleBarStyleOptions"
:onChange="value => onSelectChange('titleBarStyle', value)"
></cur-select>
<separator></separator>
<bool
description="Open file in new window"
:bool="openFilesInNewWindow"
:onChange="value => onSelectChange('openFilesInNewWindow', value)"
></bool>
<bool
description="Enable Aidou"
:bool="aidou"
:onChange="value => onSelectChange('aidou', value)"
></bool>
<separator></separator>
<cur-select
description="Sort files in opened folder by created time modified time and title"
:value="fileSortBy"
:options="fileSortByOptions"
:onChange="value => onSelectChange('fileSortBy', value)"
:disable="true"
></cur-select>
<section class="startup-ctrl ag-underdevelop">
<div>The action after Mark Text startup, open the last edited content, open the specified folder or blank page</div>
<el-radio v-model="startUp" label="lastState">Open the last closed folder and files</el-radio>
<el-radio v-model="startUp" label="folder">Open the subfolder</el-radio>
<el-button size="small">Select Folder</el-button>
<el-radio v-model="startUp" label="blank">Open blank page</el-radio>
</section>
<cur-select
description="The language Mark Text use"
:value="language"
:options="languageOptions"
:onChange="value => onSelectChange('language', value)"
:disable="true"
></cur-select>
</div>
</template>
<script>
import { mapState } from 'vuex'
import Range from '../common/range'
import CurSelect from '../common/select'
import Bool from '../common/bool'
import Separator from '../common/separator'
import { isOsx } from '@/util'
import {
titleBarStyleOptions,
fileSortByOptions,
languageOptions
} from './config'
export default {
components: {
Bool,
Range,
CurSelect,
Separator
},
data () {
this.titleBarStyleOptions = titleBarStyleOptions
this.fileSortByOptions = fileSortByOptions
this.languageOptions = languageOptions
this.isOsx = isOsx
return {}
},
computed: {
...mapState({
autoSave: state => state.preferences.autoSave,
autoSaveDelay: state => state.preferences.autoSaveDelay,
titleBarStyle: state => state.preferences.titleBarStyle,
openFilesInNewWindow: state => state.preferences.openFilesInNewWindow,
aidou: state => state.preferences.aidou,
fileSortBy: state => state.preferences.fileSortBy,
startUp: state => state.preferences.startUp,
language: state => state.preferences.language
})
},
methods: {
onSelectChange (type, value) {
this.$store.dispatch('SET_SINGLE_PREFERENCE', { type, value })
}
}
}
</script>
<style scoped>
.pref-general {
& h4 {
text-transform: uppercase;
margin: 0;
font-weight: 100;
}
& .startup-ctrl {
font-size: 14px;
user-select: none;
margin: 20px 0;
color: var(--editorColor);
& .el-button--small {
margin-left: 25px;
}
& label {
display: block;
margin: 20px 0;
}
}
}
</style>

View File

@ -0,0 +1,60 @@
export const bulletListMarkerOptions = [{
label: '*',
value: '*'
}, {
label: '-',
value: '-'
}, {
label: '+',
value: '+'
}]
export const orderListDelimiterOptions = [{
label: '.',
value: '.'
}, {
label: ')',
value: ')'
}]
export const preferHeadingStyleOptions = [{
label: 'ATX heading',
value: 'atx'
}, {
label: 'Setext heading',
value: 'setext'
}]
export const tabSizeOptions =[{
label: '1',
value: 1
}, {
label: '2',
value: 2
}, {
label: '1',
value: 3
}, {
label: '4',
value: 4
}]
export const listIndentationOptions = [{
label: 'dfm',
value: 'dfm'
}, {
label: 'tab',
value: 'tab'
}, {
label: '1 space',
value: 1,
}, {
label: '2 spaces',
value: 2
}, {
label: '3 spaces',
value: 3
}, {
label: '4 spaces',
value: 4
}]

View File

@ -0,0 +1,97 @@
<template>
<div class="pref-markdown">
<h4>markdown</h4>
<bool
description="Preferred loose list item"
:bool="preferLooseListItem"
:onChange="value => onSelectChange('preferLooseListItem', value)"
more="https://spec.commonmark.org/0.29/#loose"
></bool>
<cus-select
description="The preferred marker used in bullet list"
:value="bulletListMarker"
:options="bulletListMarkerOptions"
:onChange="value => onSelectChange('bulletListMarker', value)"
more="https://spec.commonmark.org/0.29/#bullet-list-marker"
></cus-select>
<cus-select
description="The preferred dilimiter used in order list"
:value="orderListDelimiter"
:options="orderListDelimiterOptions"
:onChange="value => onSelectChange('orderListDelimiter', value)"
more="https://spec.commonmark.org/0.29/#ordered-list"
></cus-select>
<cus-select
description="The preferred heading style"
:value="preferHeadingStyle"
:options="preferHeadingStyleOptions"
:onChange="value => onSelectChange('preferHeadingStyle', value)"
:disable="true"
></cus-select>
<cus-select
description="The number of spaces a tab is equal to"
:value="tabSize"
:options="tabSizeOptions"
:onChange="value => onSelectChange('tabSize', value)"
></cus-select>
<cus-select
description="The list indentation of sub list items or paragraphs"
:value="listIndentation"
:options="listIndentationOptions"
:onChange="value => onSelectChange('listIndentation', value)"
></cus-select>
</div>
</template>
<script>
import { mapState } from 'vuex'
import Bool from '../common/bool'
import CusSelect from '../common/select'
import {
bulletListMarkerOptions,
orderListDelimiterOptions,
preferHeadingStyleOptions,
tabSizeOptions,
listIndentationOptions
} from './config'
export default {
components: {
Bool,
CusSelect
},
data () {
this.bulletListMarkerOptions = bulletListMarkerOptions
this.orderListDelimiterOptions = orderListDelimiterOptions
this.preferHeadingStyleOptions = preferHeadingStyleOptions
this.tabSizeOptions = tabSizeOptions
this.listIndentationOptions = listIndentationOptions
return {}
},
computed: {
...mapState({
preferLooseListItem: state => state.preferences.preferLooseListItem,
bulletListMarker: state => state.preferences.bulletListMarker,
orderListDelimiter: state => state.preferences.orderListDelimiter,
preferHeadingStyle: state => state.preferences.preferHeadingStyle,
tabSize: state => state.preferences.tabSize,
listIndentation: state => state.preferences.listIndentation
})
},
methods: {
onSelectChange (type, value) {
this.$store.dispatch('SET_SINGLE_PREFERENCE', { type, value })
}
}
}
</script>
<style scoped>
.pref-markdown {
& h4 {
text-transform: uppercase;
margin: 0;
font-weight: 100;
}
}
</style>

View File

@ -0,0 +1,36 @@
import GeneralIcon from '@/assets/icons/pref_general.svg'
import EditorIcon from '@/assets/icons/pref_editor.svg'
import MarkdownIcon from '@/assets/icons/pref_markdown.svg'
import ThemeIcon from '@/assets/icons/pref_theme.svg'
import preferences from '../../../main/preferences/schema'
export const category = [{
name: 'General',
icon: GeneralIcon,
path: '/preference/general'
}, {
name: 'Editor',
icon: EditorIcon,
path: '/preference/editor'
}, {
name: 'Markdown',
icon: MarkdownIcon,
path: '/preference/markdown'
}, {
name: 'Theme',
icon: ThemeIcon,
path: '/preference/theme'
}]
export const searchContent = Object.keys(preferences).map(k => {
const { description, enum: emums } = preferences[k]
let [category, preference] = description.split('--')
if (Array.isArray(emums)) {
preference += ` optional values: ${emums.join(', ')}`
}
return {
category,
preference
}
})

View File

@ -0,0 +1,190 @@
<template>
<div class="pref-sidebar">
<h3 class="title">Preference</h3>
<section class="search-wrapper">
<el-autocomplete
popper-class="pref-autocomplete"
v-model="state"
:fetch-suggestions="querySearch"
placeholder="Search preference..."
:trigger-on-focus="false"
@select="handleSelect">
<i
class="el-icon-search el-input__icon"
slot="suffix"
>
</i>
<template slot-scope="{ item }">
<div class="name">{{ item.category }}</div>
<span class="addr">{{ item.preference }}</span>
</template>
</el-autocomplete>
</section>
<section class="category">
<div v-for="c of category" :key="c.name" class="item"
@click="handleCategoryItemClick(c)"
:class="{active: c.name.toLowerCase() === currentCategory}"
>
<svg :viewBox="c.icon.viewBox">
<use :xlink:href="c.icon.url"></use>
</svg>
<span>{{c.name}}</span>
</div>
</section>
</div>
</template>
<script>
import { category, searchContent } from './config'
export default {
data() {
this.category = category
return {
currentCategory: 'general',
restaurants: [],
state: ''
}
},
watch: {
'$route' (to, from) {
if (to.name !== from.name) {
this.currentCategory = to.name
}
}
},
methods: {
querySearch(queryString, cb) {
var restaurants = this.restaurants
var results = queryString ? restaurants.filter(this.createFilter(queryString)) : restaurants
// call callback return this results
cb(results)
},
createFilter(queryString) {
return (restaurant) => {
return (restaurant.preference.toLowerCase().indexOf(queryString.toLowerCase()) >= 0) ||
(restaurant.category.toLowerCase().indexOf(queryString.toLowerCase()) >= 0)
}
},
loadAll() {
return searchContent
},
handleSelect(item) {
this.$router.push({
path: `/preference/${item.category.toLowerCase()}`
})
},
handleCategoryItemClick (item) {
this.$router.push({
path: item.path
})
}
},
mounted() {
this.restaurants = this.loadAll()
}
}
</script>
<style>
.pref-sidebar {
-webkit-app-region: drag;
background: var(--sideBarBgColor);
width: 320px;
height: 100vh;
padding-top: 40px;
box-sizing: border-box;
& h3 {
margin: 0;
font-weight: 100;
text-align: center;
color: var(--sideBarColor);
}
}
.search-wrapper {
-webkit-app-region: no-drag;
padding: 0 20px;
margin: 30px 0;
}
.el-autocomplete {
width: 280px;
& .el-input__inner {
background: transparent;
height: 35px;
line-height: 35px;
}
}
.pref-autocomplete.el-autocomplete-suggestion {
background: var(--floatBgColor);
border-color: var(--floatBorderColor);
& .el-autocomplete-suggestion__wrap li:hover {
background: var(--floatHoverColor);
}
& .popper__arrow {
display: none;
}
& li {
line-height: normal;
padding: 7px;
opacity: .8;
& .name {
text-overflow: ellipsis;
overflow: hidden;
color: var(--editorColor80);
}
& .addr {
font-size: 12px;
color: var(--editorColor);
}
& .highlighted .addr {
color: var(--editorColor);
}
}
}
.category {
-webkit-app-region: no-drag;
& .item {
width: 100%;
height: 50px;
font-size: 18px;
color: var(--sideBarColor);
padding-left: 20px;
box-sizing: border-box;
display: flex;
flex-direction: row;
align-items: center;
cursor: pointer;
position: relative;
user-select: none;
& > svg {
width: 28px;
height: 28px;
fill: var(--iconColor);
margin-right: 15px;
}
&:hover {
background: var(--sideBarItemHoverBgColor);
}
&::before {
content: '';
width: 4px;
height: 0;
background: var(--themeColor);
position: absolute;
left: 0;
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
transition: height .25s ease-in-out;
top: 50%;
transform: translateY(-50%);
}
&.active {
color: var(--sideBarTitleColor);
}
&.active::before {
height: 100%;
}
}
}
</style>

View File

@ -0,0 +1,20 @@
export const themes = [
{
name: 'light'
},
{
name: 'dark'
},
{
name: 'graphite'
},
{
name: 'material-dark'
},
{
name: 'ulysses'
},
{
name: 'one-dark'
}
]

View File

@ -0,0 +1,178 @@
<template>
<div class="pref-theme">
<h4>Theme</h4>
<section class="offcial-themes">
<div v-for="t of themes" :key="t.name" class="theme"
:class="[t.name, { 'active': t.name === theme }]"
@click="handleSelectTheme(t.name)"
>
<div v-html="t.html"></div>
</div>
</section>
<separator></separator>
<section class="import-themes ag-underdevelop">
<div>
<span>Open the themes folder</span>
<el-button size="small">Open Folder</el-button>
</div>
<div>
<span>Import custom themes</span>
<el-button size="small">Import theme</el-button>
</div>
</section>
</div>
</template>
<script>
import { mapState } from 'vuex'
import themeMd from './theme.md'
import { themes } from './config'
import markdownToHtml from '@/util/markdownToHtml'
import Separator from '../common/separator'
export default {
components: {
Separator
},
data () {
return {
themes: []
}
},
computed: {
...mapState({
'theme': state => state.preferences.theme
})
},
created () {
this.$nextTick(async () => {
const newThemes = []
for (const theme of themes) {
const html = await markdownToHtml(themeMd.replace(/{theme}/, theme.name))
newThemes.push({
name: theme.name,
html
})
}
this.themes = newThemes
})
},
methods: {
handleSelectTheme (theme) {
this.$store.dispatch('SET_SINGLE_PREFERENCE', {
type: 'theme',
value: theme
})
}
}
}
</script>
<style>
.pref-theme {
& h4 {
text-transform: uppercase;
margin: 0;
font-weight: 100;
margin-bottom: 30px;
}
}
.offcial-themes {
& .theme {
cursor: pointer;
width: 250px;
height: 100px;
margin: 0px 22px 10px 22px;
padding-left: 30px;
padding-top: 20px;
overflow: hidden;
display: inline-block;
background: var(--editorBgColor);
color: var(--editorColor);
box-sizing: border-box;
border: 1px solid rgba(240, 240, 240, .3);
border-radius: 5px;
&.dark {
color: rgba(255, 255, 255, .7);
background: #282828;
& a {
color: #409eff;
}
}
&.light {
color: rgba(0, 0, 0, .7);
background: rgba(255, 255, 255, 1);
& a {
color: rgba(33, 181, 111, 1);;
}
}
&.graphite {
color: rgba(43, 48, 50, .7);
background: #f7f7f7;
& a {
color: rgb(104, 134, 170);
}
}
&.material-dark {
color: rgba(171, 178, 191, .8);
background: #34393f;
& a {
color: #f48237;
}
}
&.one-dark {
color: #9da5b4;
background: #282c34;
& a {
color: rgba(226, 192, 141, 1);
}
}
&.ulysses {
color: rgba(101, 101, 101, .7);
background: #f3f3f3;
& a {
color: rgb(12, 139, 186);
}
}
}
& .theme.active {
box-shadow: var(--floatShadow);
}
& h3 {
margin: 0;
font-size: 16px;
color: currentColor;
cursor: pointer;
&::before {
content: 'h3';
position: absolute;
top: 4px;
left: -20px;
display: block;
width: 10px;
height: 10px;
font-size: 12px;
opacity: .5;
}
}
& p {
font-size: 12px;
}
}
.import-themes {
padding: 10px 0;
display: flex;
justify-content: space-around;
color: var(--editorColor);
& > div {
display: flex;
flex-direction: column;
& > span {
display: inline-block;
margin-bottom: 20px;
}
}
}
</style>

View File

@ -0,0 +1,3 @@
### {theme}
**Lorem Ipsum** is simply [dummy](http://marktext.app) text of the printing and typesetting industry.

View File

@ -0,0 +1,27 @@
import App from '@/pages/app'
import Preference from '@/pages/preference'
import General from '@/prefComponents/general'
import Editor from '@/prefComponents/editor'
import Markdown from '@/prefComponents/markdown'
import Theme from '@/prefComponents/theme'
const routes = type => ([{
path: '/', redirect: type === 'editor'? '/editor' : '/preference'
}, {
path: '/editor', component: App
}, {
path: '/preference', component: Preference,
children: [{
path: '', component: General
}, {
path: 'general', component: General, name: 'general'
}, {
path: 'editor', component: Editor, name: 'editor'
}, {
path: 'markdown', component: Markdown, name: 'markdown'
}, {
path: 'theme', component: Theme, name: 'theme'
}]
}])
export default routes

View File

@ -25,9 +25,6 @@ const actions = {
ipcRenderer.on('AGANI::view', (e, data) => {
commit('SET_MODE', data)
})
ipcRenderer.on('AGANI::font-setting', e => {
bus.$emit('font-setting')
})
},
LISTEN_FOR_ABOUT_DIALOG ({ commit }) {

View File

@ -3,26 +3,36 @@ import { getOptionsFromState } from './help'
// user preference
const state = {
theme: 'light',
autoSave: true,
autoSaveDelay: 3000,
titleBarStyle: 'csd',
openFilesInNewWindow: false,
aidou: true,
fileSortBy: 'created',
startUp: 'folder',
language: 'en',
editorFontFamily: 'Open Sans',
fontSize: '16px',
codeFontFamily: 'DejaVu Sans Mono',
codeFontSize: '14px',
fontSize: 16,
lineHeight: 1.6,
textDirection: 'ltr',
lightColor: '#303133', // color in light theme
darkColor: 'rgb(217, 217, 217)', // color in dark theme
autoSave: false,
preferLooseListItem: true, // prefer loose or tight list items
bulletListMarker: '-',
codeFontSize: 14,
codeFontFamily: 'DejaVu Sans Mono',
autoPairBracket: true,
autoPairMarkdownSyntax: true,
autoPairQuote: true,
tabSize: 4,
// bullet/list marker width + listIndentation, tab or Daring Fireball Markdown (4 spaces) --> list indentation
listIndentation: 1,
endOfLine: 'default',
textDirection: 'ltr',
hideQuickInsertHint: false,
titleBarStyle: 'csd',
imageDropAction: 'folder',
preferLooseListItem: true,
bulletListMarker: '-',
orderListDelimiter: '.',
preferHeadingStyle: 'atx',
tabSize: 4,
listIndentation: 1,
theme: 'light',
// edit modes (they are not in preference.md, but still put them here)
typewriter: false, // typewriter mode
focus: false, // focus mode
@ -49,6 +59,7 @@ const actions = {
ipcRenderer.send('mt::ask-for-user-preference')
ipcRenderer.on('AGANI::user-preference', (e, preference) => {
console.log(preference)
const { autoSave } = preference
commit('SET_USER_PREFERENCE', preference)
@ -76,9 +87,9 @@ const actions = {
})
},
CHANGE_FONT ({ commit }, { type, value }) {
commit('SET_USER_PREFERENCE', { [type]: value })
// save to preference.md
SET_SINGLE_PREFERENCE ({ commit }, { type, value }) {
// commit('SET_USER_PREFERENCE', { [type]: value })
// save to electron-store
ipcRenderer.send('mt::set-user-preference', { [type]: value })
}
}

View File

@ -186,3 +186,7 @@ export const hasKeys = obj => Object.keys(obj).length > 0
export const cloneObj = (obj, deepCopy=true) => {
return deepCopy ? JSON.parse(JSON.stringify(obj)) : Object.assign({}, obj)
}
export const isOsx = process.platform === 'darwin'
export const isWindows = process.platform === 'win32'
export const isLinux = process.platform === 'linux'

View File

@ -0,0 +1,8 @@
import ExportHtml from 'muya/lib/utils/exportHtml'
const markdownToHtml = async markdown => {
const html = await new ExportHtml(markdown).renderHtml()
return `<article class="markdown-body">${html}</article>`
}
export default markdownToHtml

View File

@ -1,6 +1,8 @@
import { isLinux, THEME_STYLE_ID, COMMON_STYLE_ID, DEFAULT_CODE_FONT_FAMILY, oneDarkThemes, railscastsThemes } from '../config'
import { dark, graphite, materialDark, oneDark, ulysses } from './themeColor'
import elementStyle from 'element-ui/lib/theme-chalk/index.css'
const ORIGINAL_THEME = '#409EFF'
const patchTheme = css => {
return `@media not print {\n${css}\n}`
}
@ -11,6 +13,38 @@ const getEmojiPickerPatch = () => {
}` : ''
}
const getThemeCluster = themeColor => {
const tintColor = (color, tint) => {
let red = parseInt(color.slice(1, 3), 16)
let green = parseInt(color.slice(3, 5), 16)
let blue = parseInt(color.slice(5, 7), 16)
if (tint === 0) { // when primary color is in its rgb space
return [red, green, blue].join(',')
} else {
red += Math.round(tint * (255 - red))
green += Math.round(tint * (255 - green))
blue += Math.round(tint * (255 - blue))
red = red.toString(16)
green = green.toString(16)
blue = blue.toString(16)
return `#${ red }${ green }${ blue }`
}
}
const clusters = [{
color: themeColor,
variable: 'var(--themeColor)'
}]
for (let i = 9; i >= 1; i--) {
clusters.push({
color: tintColor(themeColor, Number((i / 10).toFixed(2))),
variable: `var(--themeColor${10 - i}0)`
})
}
return clusters
}
export const addThemeStyle = theme => {
const isCmRailscasts = railscastsThemes.includes(theme)
const isCmOneDark = oneDarkThemes.includes(theme)
@ -86,13 +120,30 @@ code[class*="language-"],
.CodeMirror,
pre.ag-paragraph {
font-family: ${codeFontFamily}, ${DEFAULT_CODE_FONT_FAMILY};
font-size: ${codeFontSize};
font-size: ${codeFontSize}px;
}
${getEmojiPickerPatch()}
`
}
export const addElementStyle = () => {
const ID = 'mt-el-style'
let sheet = document.querySelector(`#${ID}`)
if (sheet) {
return
}
const themeCluster = getThemeCluster(ORIGINAL_THEME)
let newElementStyle = elementStyle
for (const { color, variable } of themeCluster) {
newElementStyle = newElementStyle.replace(new RegExp(color, 'ig'), variable)
}
sheet = document.createElement('style')
sheet.id = ID
document.head.appendChild(sheet)
sheet.innerHTML = newElementStyle
}
// Append common sheet and theme at the end of head - order is important.
export const addStyles = style => {
const { theme } = style

32
static/preference.json Normal file
View File

@ -0,0 +1,32 @@
{
"autoSave": false,
"autoSaveDelay": 5000,
"titleBarStyle": "custom",
"openFilesInNewWindow": false,
"aidou": true,
"fileSortBy": "created",
"startUp": "folder",
"language": "en",
"editorFontFamily": "Open Sans",
"fontSize": 16,
"lineHeight": 1.6,
"codeFontSize": 14,
"codeFontFamily": "DejaVu Sans Mono",
"autoPairBracket": true,
"autoPairMarkdownSyntax": true,
"autoPairQuote": true,
"endOfLine": "default",
"textDirection": "ltr",
"hideQuickInsertHint": false,
"imageDropAction": "folder",
"preferLooseListItem": true,
"bulletListMarker": "-",
"orderListDelimiter": ".",
"preferHeadingStyle": "atx",
"tabSize": 4,
"listIndentation": 1,
"theme": "light"
}

View File

@ -1,50 +0,0 @@
### :bust_in_silhouette:User Preferences
Edit and save to update preferences. You can only change the JSON below!
- **theme**: *String* `dark`, `graphite`, `material-dark`, `one-dark`, `light` or `ulysses`
- **autoSave**: *Boolean* `true` or `false`
- **endOfLine**: *String* `lf`, `crlf` or `default`
- **listIndentation**: `"dfm"`, `"tab"` or number (`1-4`)
- **bulletListMarker**: *String* `+`,`-` or `*`
- **textDirection**: *String* `ltr` or `rtl`
- **titleBarStyle**: *String* `csd` (macOS only), `custom` or `native`
```json
{
"fontSize": "16px",
"editorFontFamily": "Open Sans",
"codeFontFamily": "DejaVu Sans Mono",
"codeFontSize": "14px",
"lightColor": "#303133",
"darkColor": "rgb(217, 217, 217)",
"lineHeight": "1.6",
"theme": "light",
"autoSave": false,
"aidou": false,
"hideQuickInsertHint": false,
"preferLooseListItem": true,
"bulletListMarker": "-",
"autoPairBracket": true,
"autoPairMarkdownSyntax": true,
"autoPairQuote": true,
"endOfLine": "default",
"tabSize": 4,
"listIndentation": 1,
"textDirection": "ltr",
"titleBarStyle": "csd",
"openFilesInNewWindow": true
}
```
More user preferences coming soon.
**Please use `Cmd + S`/`Ctrl + S` to save your preferences and reload Mark Text to use your setting!**
> Your friends at Mark Text.

View File

@ -422,7 +422,7 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.0:
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.0.tgz#4b831e7b531415a7cc518cd404e73f6193c6349d"
integrity sha512-aUjdRFISbuFOl0EIZc+9e4FfZp0bDZgAdOOf30bJmw8VM9v84SHyVyxDfbWxpGYbdZD/9XoKxfHVNmxPkhwyGw==
ajv@^6.1.0, ajv@^6.5.5, ajv@^6.9.1, ajv@^6.9.2:
ajv@^6.1.0, ajv@^6.10.0, ajv@^6.5.5, ajv@^6.9.1, ajv@^6.9.2:
version "6.10.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.0.tgz#90d0d54439da587cd7e843bfb7045f50bd22bdf1"
integrity sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==
@ -2586,6 +2586,19 @@ concat-stream@1.6.2, concat-stream@^1.5.0:
readable-stream "^2.2.2"
typedarray "^0.0.6"
conf@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/conf/-/conf-4.0.2.tgz#cc25649295259fc77f4606840b7718cca5c1d5bd"
integrity sha512-SVEWGdAlA+BNfpJ5vF7S6SbkXA7Qk1ycWV6+UAWkC3vQvjxx7xxuYNriKnShjlbIPStUiTK0MZWdQYYtTYdwZw==
dependencies:
ajv "^6.10.0"
dot-prop "^5.0.0"
env-paths "^2.2.0"
json-schema-typed "^7.0.0"
make-dir "^3.0.0"
pkg-up "^3.0.1"
write-file-atomic "^2.4.2"
configstore@^3.0.0:
version "3.1.2"
resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.2.tgz#c6f25defaeef26df12dd33414b001fe81a543f8f"
@ -3790,6 +3803,13 @@ dot-prop@^4.1.0:
dependencies:
is-obj "^1.0.0"
dot-prop@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.0.0.tgz#64b7968af349c3a9f966aa12658dbd5829f6b953"
integrity sha512-RTmaF2jx3nOBO2GvtFqjnDLycjFUMqt+2pwRx7JVYa81lDauoj9aNkyrJI2ikR58FbBIchiIlRiGG+muLJ4oHQ==
dependencies:
is-obj "^1.0.0"
dotenv-expand@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-4.2.0.tgz#def1f1ca5d6059d24a766e587942c21106ce1275"
@ -3981,6 +4001,14 @@ electron-rebuild@^1.8.4:
spawn-rx "^3.0.0"
yargs "^12.0.5"
electron-store@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/electron-store/-/electron-store-3.2.0.tgz#50d2d6677beb46293c15814f2d230b65c6ca555e"
integrity sha512-+goKW06sPo8KyPd9ctozRQGjctJ+M4qDpZ0Dx02X08AMf6lSlHQRmYHMYbKXpVpoq4y250wRfEHNxtP72HbfVQ==
dependencies:
conf "^4.0.1"
type-fest "^0.3.1"
electron-to-chromium@^1.3.124, electron-to-chromium@^1.3.47:
version "1.3.127"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.127.tgz#9b34d3d63ee0f3747967205b953b25fe7feb0e10"
@ -4136,6 +4164,11 @@ env-paths@^1.0.0:
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-1.0.0.tgz#4168133b42bb05c38a35b1ae4397c8298ab369e0"
integrity sha1-QWgTO0K7BcOKNbGuQ5fIKYqzaeA=
env-paths@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43"
integrity sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==
errno@^0.1.3, errno@~0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618"
@ -6359,6 +6392,11 @@ json-schema-traverse@^0.4.1:
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
json-schema-typed@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/json-schema-typed/-/json-schema-typed-7.0.0.tgz#714f3bb539637644b8cb9c99a097c4ee8f8e8c8f"
integrity sha512-ikVqF4dlAgRvAb3MDAgDQRtB/GIC8+iq+z5bczPh9bUT7bAZCdGfGCypJHBquzZNoxebql1UgPxWbImnvkSuJg==
json-schema@0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
@ -7036,6 +7074,13 @@ make-dir@^2.0.0:
pify "^4.0.1"
semver "^5.6.0"
make-dir@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.0.0.tgz#1b5f39f6b9270ed33f9f054c5c0f84304989f801"
integrity sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw==
dependencies:
semver "^6.0.0"
mamacro@^0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/mamacro/-/mamacro-0.0.3.tgz#ad2c9576197c9f1abf308d0787865bd975a3f3e4"
@ -8272,6 +8317,13 @@ pkg-dir@^3.0.0:
dependencies:
find-up "^3.0.0"
pkg-up@^3.0.1:
version "3.1.0"
resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5"
integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==
dependencies:
find-up "^3.0.0"
plist@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/plist/-/plist-3.0.1.tgz#a9b931d17c304e8912ef0ba3bdd6182baf2e1f8c"
@ -8968,6 +9020,14 @@ raw-body@2.4.0:
iconv-lite "0.4.24"
unpipe "1.0.0"
raw-loader@^2.0.0:
version "2.0.0"
resolved "https://registry.npm.taobao.org/raw-loader/download/raw-loader-2.0.0.tgz#e2813d9e1e3f80d1bbade5ad082e809679e20c26"
integrity sha1-4oE9nh4/gNG7reWtCC6AlnniDCY=
dependencies:
loader-utils "^1.1.0"
schema-utils "^1.0.0"
rc@^1.0.1, rc@^1.1.6, rc@^1.2.1, rc@^1.2.7:
version "1.2.8"
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
@ -10723,6 +10783,11 @@ type-detect@^4.0.0, type-detect@^4.0.5:
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
type-fest@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.3.1.tgz#63d00d204e059474fe5e1b7c011112bbd1dc29e1"
integrity sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==
type-func@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/type-func/-/type-func-1.0.3.tgz#ab184234ae80d8d50057cefeff3b2d97d08ae9b0"
@ -11437,6 +11502,11 @@ vue-loader@^15.7.0:
vue-hot-reload-api "^2.3.0"
vue-style-loader "^4.1.0"
vue-router@^3.0.6:
version "3.0.6"
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.0.6.tgz#2e4f0f9cbb0b96d0205ab2690cfe588935136ac3"
integrity sha512-Ox0ciFLswtSGRTHYhGvx2L44sVbTPNS+uD2kRISuo8B39Y79rOo0Kw0hzupTmiVtftQYCZl87mwldhh2L9Aquw==
vue-style-loader@^4.1.0, vue-style-loader@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.2.tgz#dedf349806f25ceb4e64f3ad7c0a44fba735fcf8"
@ -11826,7 +11896,7 @@ wrappy@1:
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
write-file-atomic@^2.0.0:
write-file-atomic@^2.0.0, write-file-atomic@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.2.tgz#a7181706dfba17855d221140a9c06e15fcdd87b9"
integrity sha512-s0b6vB3xIVRLWywa6X9TOMA7k9zio0TMOsl9ZnDkliA/cfJlpHXAscj0gbHVJiTdIuAYpIyqS5GW91fqm6gG5g==