diff --git a/.babelrc b/.babelrc index 9c2de33f..e8c16d89 100644 --- a/.babelrc +++ b/.babelrc @@ -42,8 +42,8 @@ } }, "plugins": [["component", { - "libraryName": "element-ui", - "styleLibraryName": "theme-chalk" + "style": false, + "libraryName": "element-ui" } ], "transform-runtime"] } diff --git a/.electron-vue/webpack.renderer.config.js b/.electron-vue/webpack.renderer.config.js index ae3fc444..d4613346 100644 --- a/.electron-vue/webpack.renderer.config.js +++ b/.electron-vue/webpack.renderer.config.js @@ -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' + ] } ] }, diff --git a/doc/Block addition properties and its value.md b/doc/BLOCK_ADDITION_PROPERTY.md similarity index 100% rename from doc/Block addition properties and its value.md rename to doc/BLOCK_ADDITION_PROPERTY.md diff --git a/doc/PREFERENCE.md b/doc/PREFERENCE.md new file mode 100644 index 00000000..e7a7207c --- /dev/null +++ b/doc/PREFERENCE.md @@ -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` | diff --git a/doc/SETTINGS.md b/doc/SETTINGS.md deleted file mode 100644 index e0f68191..00000000 --- a/doc/SETTINGS.md +++ /dev/null @@ -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** diff --git a/package.json b/package.json index 8dc882bc..f631f1b9 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/main/app/index.js b/src/main/app/index.js index 3c29f7db..8c168ed4 100644 --- a/src/main/app/index.js +++ b/src/main/app/index.js @@ -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 => { diff --git a/src/main/app/windowManager.js b/src/main/app/windowManager.js index db320496..c4478894 100644 --- a/src/main/app/windowManager.js +++ b/src/main/app/windowManager.js @@ -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) + } } }) } diff --git a/src/main/config.js b/src/main/config.js index f04e841a..560315c0 100644 --- a/src/main/config.js +++ b/src/main/config.js @@ -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/ diff --git a/src/main/menu/actions/view.js b/src/main/menu/actions/view.js index 1261efbf..9494d04a 100644 --- a/src/main/menu/actions/view.js +++ b/src/main/menu/actions/view.js @@ -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 }) } diff --git a/src/main/menu/index.js b/src/main/menu/index.js index d3659ec1..b2352fa9 100644 --- a/src/main/menu/index.js +++ b/src/main/menu/index.js @@ -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) + } + }) } } diff --git a/src/main/menu/templates/edit.js b/src/main/menu/templates/edit.js index a74d3402..fb60fa30 100755 --- a/src/main/menu/templates/edit.js +++ b/src/main/menu/templates/edit.js @@ -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') diff --git a/src/main/menu/templates/file.js b/src/main/menu/templates/file.js index a45a1d44..b39f50af 100755 --- a/src/main/menu/templates/file.js +++ b/src/main/menu/templates/file.js @@ -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) } diff --git a/src/main/menu/templates/index.js b/src/main/menu/templates/index.js index 370ff884..d9fa4962 100644 --- a/src/main/menu/templates/index.js +++ b/src/main/menu/templates/index.js @@ -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. * diff --git a/src/main/menu/templates/view.js b/src/main/menu/templates/view.js index 02ca1ed0..96c6520f 100755 --- a/src/main/menu/templates/view.js +++ b/src/main/menu/templates/view.js @@ -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', diff --git a/src/main/preferences/index.js b/src/main/preferences/index.js index 71a16235..1416120b 100644 --- a/src/main/preferences/index.js +++ b/src/main/preferences/index.js @@ -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) }) } } diff --git a/src/main/preferences/schema.json b/src/main/preferences/schema.json new file mode 100644 index 00000000..9f53ac96 --- /dev/null +++ b/src/main/preferences/schema.json @@ -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" + } +} diff --git a/src/main/windows/base.js b/src/main/windows/base.js new file mode 100644 index 00000000..f4e51b7e --- /dev/null +++ b/src/main/windows/base.js @@ -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 diff --git a/src/main/windows/editor.js b/src/main/windows/editor.js index 5545c039..c7cc42ad 100644 --- a/src/main/windows/editor.js +++ b/src/main/windows/editor.js @@ -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 diff --git a/src/main/windows/setting.js b/src/main/windows/setting.js new file mode 100644 index 00000000..6b91125f --- /dev/null +++ b/src/main/windows/setting.js @@ -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 diff --git a/src/muya/lib/config/index.js b/src/muya/lib/config/index.js index 402823c9..25872d7f 100644 --- a/src/muya/lib/config/index.js +++ b/src/muya/lib/config/index.js @@ -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, diff --git a/src/muya/lib/contentState/inputCtrl.js b/src/muya/lib/contentState/inputCtrl.js index 6308b1fa..1dcf09a3 100644 --- a/src/muya/lib/contentState/inputCtrl.js +++ b/src/muya/lib/contentState/inputCtrl.js @@ -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) diff --git a/src/muya/lib/contentState/paragraphCtrl.js b/src/muya/lib/contentState/paragraphCtrl.js index f250d833..a9ac1af2 100644 --- a/src/muya/lib/contentState/paragraphCtrl.js +++ b/src/muya/lib/contentState/paragraphCtrl.js @@ -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') || diff --git a/src/muya/lib/contentState/updateCtrl.js b/src/muya/lib/contentState/updateCtrl.js index a5b21a45..6bda3e30 100644 --- a/src/muya/lib/contentState/updateCtrl.js +++ b/src/muya/lib/contentState/updateCtrl.js @@ -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 diff --git a/src/muya/lib/index.js b/src/muya/lib/index.js index 71d6ed32..c772bb21 100644 --- a/src/muya/lib/index.js +++ b/src/muya/lib/index.js @@ -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 () { diff --git a/src/muya/themes/default.css b/src/muya/themes/default.css index dbb05e6e..f2670119 100644 --- a/src/muya/themes/default.css +++ b/src/muya/themes/default.css @@ -150,7 +150,7 @@ kbd { } .v-modal { - background: var(--maskColor); + background: var(--maskColor) !important; } body>*:first-child { diff --git a/src/renderer/assets/icons/pref_editor.svg b/src/renderer/assets/icons/pref_editor.svg new file mode 100644 index 00000000..dd612acd --- /dev/null +++ b/src/renderer/assets/icons/pref_editor.svg @@ -0,0 +1 @@ + diff --git a/src/renderer/assets/icons/pref_email.svg b/src/renderer/assets/icons/pref_email.svg new file mode 100644 index 00000000..d12e9705 --- /dev/null +++ b/src/renderer/assets/icons/pref_email.svg @@ -0,0 +1 @@ + diff --git a/src/renderer/assets/icons/pref_fontsize.svg b/src/renderer/assets/icons/pref_fontsize.svg new file mode 100644 index 00000000..dc882d23 --- /dev/null +++ b/src/renderer/assets/icons/pref_fontsize.svg @@ -0,0 +1 @@ + diff --git a/src/renderer/assets/icons/pref_general.svg b/src/renderer/assets/icons/pref_general.svg new file mode 100644 index 00000000..845ce188 --- /dev/null +++ b/src/renderer/assets/icons/pref_general.svg @@ -0,0 +1 @@ + diff --git a/src/renderer/assets/icons/pref_lineheight.svg b/src/renderer/assets/icons/pref_lineheight.svg new file mode 100644 index 00000000..1fcd1128 --- /dev/null +++ b/src/renderer/assets/icons/pref_lineheight.svg @@ -0,0 +1 @@ + diff --git a/src/renderer/assets/icons/pref_markdown.svg b/src/renderer/assets/icons/pref_markdown.svg new file mode 100644 index 00000000..fee3d72d --- /dev/null +++ b/src/renderer/assets/icons/pref_markdown.svg @@ -0,0 +1 @@ + diff --git a/src/renderer/assets/icons/pref_theme.svg b/src/renderer/assets/icons/pref_theme.svg new file mode 100644 index 00000000..69776db2 --- /dev/null +++ b/src/renderer/assets/icons/pref_theme.svg @@ -0,0 +1 @@ + diff --git a/src/renderer/assets/styles/index.css b/src/renderer/assets/styles/index.css index f7f328b8..be64dfd0 100644 --- a/src/renderer/assets/styles/index.css +++ b/src/renderer/assets/styles/index.css @@ -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; +} diff --git a/src/renderer/assets/themes/dark.theme.css b/src/renderer/assets/themes/dark.theme.css index e09130a2..a553ad4f 100644 --- a/src/renderer/assets/themes/dark.theme.css +++ b/src/renderer/assets/themes/dark.theme.css @@ -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); diff --git a/src/renderer/assets/themes/graphite.theme.css b/src/renderer/assets/themes/graphite.theme.css index 45eeb5bb..8b78cedb 100644 --- a/src/renderer/assets/themes/graphite.theme.css +++ b/src/renderer/assets/themes/graphite.theme.css @@ -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); diff --git a/src/renderer/assets/themes/material-dark.theme.css b/src/renderer/assets/themes/material-dark.theme.css index 7ff4e8ad..2851c99b 100644 --- a/src/renderer/assets/themes/material-dark.theme.css +++ b/src/renderer/assets/themes/material-dark.theme.css @@ -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); diff --git a/src/renderer/assets/themes/one-dark.theme.css b/src/renderer/assets/themes/one-dark.theme.css index bf55576e..73dc42dd 100644 --- a/src/renderer/assets/themes/one-dark.theme.css +++ b/src/renderer/assets/themes/one-dark.theme.css @@ -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; diff --git a/src/renderer/assets/themes/ulysses.theme.css b/src/renderer/assets/themes/ulysses.theme.css index 6a5988ec..424428ab 100644 --- a/src/renderer/assets/themes/ulysses.theme.css +++ b/src/renderer/assets/themes/ulysses.theme.css @@ -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); diff --git a/src/renderer/bootstrap.js b/src/renderer/bootstrap.js index ccfbf40e..d635b17d 100644 --- a/src/renderer/bootstrap.js +++ b/src/renderer/bootstrap.js @@ -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 diff --git a/src/renderer/components/editorWithTabs/editor.vue b/src/renderer/components/editorWithTabs/editor.vue index 30684398..daca8fc2 100644 --- a/src/renderer/components/editorWithTabs/editor.vue +++ b/src/renderer/components/editorWithTabs/editor.vue @@ -2,7 +2,7 @@
@@ -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 { diff --git a/src/renderer/components/font.vue b/src/renderer/components/font.vue deleted file mode 100644 index 7ed05764..00000000 --- a/src/renderer/components/font.vue +++ /dev/null @@ -1,136 +0,0 @@ - - - - - diff --git a/src/renderer/components/titleBar.vue b/src/renderer/components/titleBar.vue index a8a2c721..f4590999 100644 --- a/src/renderer/components/titleBar.vue +++ b/src/renderer/components/titleBar.vue @@ -6,7 +6,7 @@ >
Mark Text @@ -30,9 +30,9 @@
-
+
@@ -66,7 +66,7 @@
@@ -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); } } diff --git a/src/renderer/components/uploadImage/index.vue b/src/renderer/components/uploadImage/index.vue index 340293d2..7c523906 100644 --- a/src/renderer/components/uploadImage/index.vue +++ b/src/renderer/components/uploadImage/index.vue @@ -92,7 +92,7 @@ color: #E6A23C; } .el-upload-dragger { - background: var(--itemBgColor); + background: var(--itemBgColor) !important; & .el-upload__text { color: var(--sideBarColor); & em { diff --git a/src/renderer/main.js b/src/renderer/main.js index 0bd06715..03507820 100644 --- a/src/renderer/main.js +++ b/src/renderer/main.js @@ -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: '' + router, + template: '' }).$mount('#app') diff --git a/src/renderer/app.vue b/src/renderer/pages/app.vue similarity index 98% rename from src/renderer/app.vue rename to src/renderer/pages/app.vue index a78cde87..ef6dd6f4 100644 --- a/src/renderer/app.vue +++ b/src/renderer/pages/app.vue @@ -29,7 +29,6 @@ - @@ -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 diff --git a/src/renderer/pages/preference.vue b/src/renderer/pages/preference.vue new file mode 100644 index 00000000..884e9280 --- /dev/null +++ b/src/renderer/pages/preference.vue @@ -0,0 +1,95 @@ + + + + + diff --git a/src/renderer/prefComponents/common/bool/index.vue b/src/renderer/prefComponents/common/bool/index.vue new file mode 100644 index 00000000..3e3d5049 --- /dev/null +++ b/src/renderer/prefComponents/common/bool/index.vue @@ -0,0 +1,93 @@ + + + + + + diff --git a/src/renderer/prefComponents/common/range/index.vue b/src/renderer/prefComponents/common/range/index.vue new file mode 100644 index 00000000..702cd4b8 --- /dev/null +++ b/src/renderer/prefComponents/common/range/index.vue @@ -0,0 +1,100 @@ + + + + + + diff --git a/src/renderer/prefComponents/common/select/index.vue b/src/renderer/prefComponents/common/select/index.vue new file mode 100644 index 00000000..36e78b41 --- /dev/null +++ b/src/renderer/prefComponents/common/select/index.vue @@ -0,0 +1,107 @@ + + + + + + diff --git a/src/renderer/prefComponents/common/separator/index.vue b/src/renderer/prefComponents/common/separator/index.vue new file mode 100644 index 00000000..3a3c89f7 --- /dev/null +++ b/src/renderer/prefComponents/common/separator/index.vue @@ -0,0 +1,12 @@ + + + + diff --git a/src/renderer/prefComponents/common/titlebar.vue b/src/renderer/prefComponents/common/titlebar.vue new file mode 100644 index 00000000..d2f72042 --- /dev/null +++ b/src/renderer/prefComponents/common/titlebar.vue @@ -0,0 +1,86 @@ + + + + + diff --git a/src/renderer/prefComponents/editor/config.js b/src/renderer/prefComponents/editor/config.js new file mode 100644 index 00000000..07e9657e --- /dev/null +++ b/src/renderer/prefComponents/editor/config.js @@ -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' +}] diff --git a/src/renderer/prefComponents/editor/index.vue b/src/renderer/prefComponents/editor/index.vue new file mode 100644 index 00000000..d754b5cc --- /dev/null +++ b/src/renderer/prefComponents/editor/index.vue @@ -0,0 +1,157 @@ + + + + + diff --git a/src/renderer/prefComponents/general/config.js b/src/renderer/prefComponents/general/config.js new file mode 100644 index 00000000..53a9a939 --- /dev/null +++ b/src/renderer/prefComponents/general/config.js @@ -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' +}] diff --git a/src/renderer/prefComponents/general/index.vue b/src/renderer/prefComponents/general/index.vue new file mode 100644 index 00000000..dd55e092 --- /dev/null +++ b/src/renderer/prefComponents/general/index.vue @@ -0,0 +1,131 @@ + + + + + diff --git a/src/renderer/prefComponents/markdown/config.js b/src/renderer/prefComponents/markdown/config.js new file mode 100644 index 00000000..58a119b2 --- /dev/null +++ b/src/renderer/prefComponents/markdown/config.js @@ -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 +}] diff --git a/src/renderer/prefComponents/markdown/index.vue b/src/renderer/prefComponents/markdown/index.vue new file mode 100644 index 00000000..ab070256 --- /dev/null +++ b/src/renderer/prefComponents/markdown/index.vue @@ -0,0 +1,97 @@ + + + + + diff --git a/src/renderer/prefComponents/sideBar/config.js b/src/renderer/prefComponents/sideBar/config.js new file mode 100644 index 00000000..b6b7f08a --- /dev/null +++ b/src/renderer/prefComponents/sideBar/config.js @@ -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 + } +}) diff --git a/src/renderer/prefComponents/sideBar/index.vue b/src/renderer/prefComponents/sideBar/index.vue new file mode 100644 index 00000000..f3273d00 --- /dev/null +++ b/src/renderer/prefComponents/sideBar/index.vue @@ -0,0 +1,190 @@ + + + + diff --git a/src/renderer/prefComponents/theme/config.js b/src/renderer/prefComponents/theme/config.js new file mode 100644 index 00000000..f124330a --- /dev/null +++ b/src/renderer/prefComponents/theme/config.js @@ -0,0 +1,20 @@ +export const themes = [ + { + name: 'light' + }, + { + name: 'dark' + }, + { + name: 'graphite' + }, + { + name: 'material-dark' + }, + { + name: 'ulysses' + }, + { + name: 'one-dark' + } +] diff --git a/src/renderer/prefComponents/theme/index.vue b/src/renderer/prefComponents/theme/index.vue new file mode 100644 index 00000000..78036a5d --- /dev/null +++ b/src/renderer/prefComponents/theme/index.vue @@ -0,0 +1,178 @@ + + + + + diff --git a/src/renderer/prefComponents/theme/theme.md b/src/renderer/prefComponents/theme/theme.md new file mode 100644 index 00000000..9dbe1083 --- /dev/null +++ b/src/renderer/prefComponents/theme/theme.md @@ -0,0 +1,3 @@ +### {theme} + +**Lorem Ipsum** is simply [dummy](http://marktext.app) text of the printing and typesetting industry. diff --git a/src/renderer/router/index.js b/src/renderer/router/index.js new file mode 100644 index 00000000..5bde3b05 --- /dev/null +++ b/src/renderer/router/index.js @@ -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 diff --git a/src/renderer/store/listenForMain.js b/src/renderer/store/listenForMain.js index dff2b593..364fdccb 100644 --- a/src/renderer/store/listenForMain.js +++ b/src/renderer/store/listenForMain.js @@ -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 }) { diff --git a/src/renderer/store/preferences.js b/src/renderer/store/preferences.js index 145eed62..92ca0ac8 100644 --- a/src/renderer/store/preferences.js +++ b/src/renderer/store/preferences.js @@ -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 }) } } diff --git a/src/renderer/util/index.js b/src/renderer/util/index.js index 24fdcbbd..a881be0a 100644 --- a/src/renderer/util/index.js +++ b/src/renderer/util/index.js @@ -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' diff --git a/src/renderer/util/markdownToHtml.js b/src/renderer/util/markdownToHtml.js new file mode 100644 index 00000000..02faee52 --- /dev/null +++ b/src/renderer/util/markdownToHtml.js @@ -0,0 +1,8 @@ +import ExportHtml from 'muya/lib/utils/exportHtml' + +const markdownToHtml = async markdown => { + const html = await new ExportHtml(markdown).renderHtml() + return `
${html}
` +} + +export default markdownToHtml diff --git a/src/renderer/util/theme.js b/src/renderer/util/theme.js index a713e593..4eda81c3 100644 --- a/src/renderer/util/theme.js +++ b/src/renderer/util/theme.js @@ -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 diff --git a/static/preference.json b/static/preference.json new file mode 100644 index 00000000..ab637bfc --- /dev/null +++ b/static/preference.json @@ -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" +} diff --git a/static/preference.md b/static/preference.md deleted file mode 100755 index 44ffd0d9..00000000 --- a/static/preference.md +++ /dev/null @@ -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. diff --git a/yarn.lock b/yarn.lock index ced3bd2c..9c0abee0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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==