mirror of
https://github.com/marktext/marktext.git
synced 2025-05-03 03:12:18 +08:00
feat: add dark theme and light theme
This commit is contained in:
parent
590f49cf24
commit
12c22ce588
@ -6,6 +6,8 @@
|
||||
|
||||
- Add Focus Mode, the current paragraph's will be focused.
|
||||
|
||||
- Add Dark theme, Light theme.
|
||||
|
||||
**Optimization**
|
||||
|
||||
- Optimize the display of path name and file name in title bar.
|
||||
|
@ -47,6 +47,7 @@ export const LOWERCASE_TAGS = generateKeyHash([
|
||||
|
||||
export const CLASS_OR_ID = genUpper2LowerKeyHash([
|
||||
'mousetrap',
|
||||
'AG_THEME_ID',
|
||||
'AG_GRAY',
|
||||
'AG_HIDE',
|
||||
'AG_WARN',
|
||||
|
@ -1,7 +1,7 @@
|
||||
import ContentState from './contentState'
|
||||
import selection from './selection'
|
||||
import EventCenter from './event'
|
||||
import { LOWERCASE_TAGS, EVENT_KEYS, CLASS_OR_ID } from './config'
|
||||
import { LOWERCASE_TAGS, EVENT_KEYS, CLASS_OR_ID, codeMirrorConfig } from './config'
|
||||
import { throttle, debounce } from './utils'
|
||||
import { search } from './codeMirror'
|
||||
import { checkEditLanguage } from './codeMirror/language'
|
||||
@ -429,6 +429,23 @@ class Aganippe {
|
||||
this.focusMode = bool
|
||||
}
|
||||
|
||||
setTheme (name, css) {
|
||||
if (name === 'dark') {
|
||||
codeMirrorConfig.theme = 'railscasts'
|
||||
} else {
|
||||
delete codeMirrorConfig.theme
|
||||
}
|
||||
const themeStyleId = CLASS_OR_ID['AG_THEME_ID']
|
||||
let styleEle = document.querySelector(`#${themeStyleId}`)
|
||||
if (!styleEle) {
|
||||
styleEle = document.createElement('style')
|
||||
styleEle.id = themeStyleId
|
||||
document.querySelector('head').appendChild(styleEle)
|
||||
}
|
||||
styleEle.innerHTML = css
|
||||
this.contentState.render()
|
||||
}
|
||||
|
||||
updateParagraph (type) {
|
||||
this.contentState.updateParagraph(type)
|
||||
}
|
||||
|
@ -84,7 +84,6 @@
|
||||
.ag-table-picker .footer button {
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
vertical-align: text-bottom;
|
||||
border-radius: 2px;
|
||||
line-height: 12px;
|
||||
border: none;
|
||||
|
@ -5,37 +5,8 @@
|
||||
|
||||
@include-when-export url(http://fonts.googleapis.com/css?family=Open+Sans:400italic,700italic,700,400&subset=latin,latin-ext);
|
||||
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
src: local('Open Sans Regular'), url('./github/400.woff') format('woff')
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: normal;
|
||||
src: local('Open Sans Italic'), url('./github/400i.woff') format('woff')
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
src: local('Open Sans Bold'), url('./github/700.woff') format('woff')
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: bold;
|
||||
src: local('Open Sans Bold Italic'), url('./github/700i.woff') format('woff')
|
||||
}
|
||||
|
||||
html, body {
|
||||
font-size: 16px;
|
||||
background: rgb(43, 43, 43);
|
||||
}
|
||||
|
||||
body {
|
||||
@ -62,11 +33,44 @@ body {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.ag-float-box {
|
||||
background: #303133;
|
||||
border: 1px solid #303133;
|
||||
}
|
||||
.ag-float-item {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.ag-float-item-active {
|
||||
background: #606266;
|
||||
color: #C0C4CC;
|
||||
}
|
||||
|
||||
.ag-table-picker {
|
||||
background: #606266;
|
||||
border: 1px solid #606266;
|
||||
}
|
||||
|
||||
.ag-table-picker::before {
|
||||
background: #606266;
|
||||
border: 1px solid #606266;
|
||||
border-right: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.ag-gray {
|
||||
color: #909399;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.el-dialog {
|
||||
background: rgb(43, 43, 43);
|
||||
}
|
||||
|
||||
.v-modal {
|
||||
background: rgba(0, 0, 0, .9);
|
||||
}
|
||||
|
||||
body>*:first-child {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
@ -5,34 +5,6 @@
|
||||
|
||||
@include-when-export url(http://fonts.googleapis.com/css?family=Open+Sans:400italic,700italic,700,400&subset=latin,latin-ext);
|
||||
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
src: local('Open Sans Regular'), url('./github/400.woff') format('woff')
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: normal;
|
||||
src: local('Open Sans Italic'), url('./github/400i.woff') format('woff')
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
src: local('Open Sans Bold'), url('./github/700.woff') format('woff')
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: bold;
|
||||
src: local('Open Sans Bold Italic'), url('./github/700i.woff') format('woff')
|
||||
}
|
||||
|
||||
html, body {
|
||||
font-size: 16px;
|
||||
background: rgb(252, 252, 252);
|
||||
@ -40,7 +12,7 @@ html, body {
|
||||
|
||||
body {
|
||||
font-family: "Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
color: #606266;
|
||||
color: #303133;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
@ -67,6 +39,11 @@ body {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.v-modal {
|
||||
background: #fff;
|
||||
opacity: .8;
|
||||
}
|
||||
|
||||
body>*:first-child {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
45
src/main/actions/theme.js
Normal file
45
src/main/actions/theme.js
Normal file
@ -0,0 +1,45 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { ipcMain, BrowserWindow } from 'electron'
|
||||
import { getMenuItem } from '../utils'
|
||||
|
||||
const THEME_PATH = path.resolve(__dirname, '../../editor/themes')
|
||||
const themeCSS = {}
|
||||
|
||||
export const selectTheme = (win, theme, themeCSS) => {
|
||||
win.webContents.send('AGANI::theme', { theme, themeCSS })
|
||||
}
|
||||
|
||||
const getSelectTheme = () => {
|
||||
const themeMenu = getMenuItem('Theme')
|
||||
return themeMenu.submenu.items.find(item => item.checked)
|
||||
}
|
||||
|
||||
ipcMain.on('AGANI::ask-for-theme', e => {
|
||||
const win = BrowserWindow.fromWebContents(e.sender)
|
||||
if (!Object.keys(themeCSS).length) {
|
||||
const promises = ['dark', 'light'].map(theme => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readFile(`${THEME_PATH}/${theme}.css`, 'utf-8', (err, data) => {
|
||||
if (err) reject(err)
|
||||
resolve({ theme, data })
|
||||
})
|
||||
})
|
||||
})
|
||||
Promise.all(promises)
|
||||
.then(themes => {
|
||||
themes.forEach(t => {
|
||||
console.log(t)
|
||||
const { theme, data } = t
|
||||
themeCSS[theme] = data
|
||||
})
|
||||
const selectedTheme = getSelectTheme().label.toLowerCase()
|
||||
console.log(selectedTheme)
|
||||
console.log(themeCSS)
|
||||
selectTheme(win, selectedTheme, themeCSS)
|
||||
})
|
||||
} else {
|
||||
const selectedTheme = getSelectTheme().label.toLowerCase()
|
||||
selectTheme(win, selectedTheme, themeCSS)
|
||||
}
|
||||
})
|
@ -17,6 +17,8 @@ export const EXTENSION_HASN = {
|
||||
pdf: '.pdf'
|
||||
}
|
||||
|
||||
export const DEFAULT_THEME = 'dark'
|
||||
|
||||
export const VIEW_MENU_ITEM = {
|
||||
'Source Code Mode': false,
|
||||
'Typewriter Mode': false,
|
||||
|
@ -6,6 +6,7 @@ import view from './view'
|
||||
import windowMenu from './windowMenu'
|
||||
import paragraph from './paragraph'
|
||||
import format from './format'
|
||||
import theme from './theme'
|
||||
|
||||
export dockMenu from './dock'
|
||||
|
||||
@ -19,6 +20,7 @@ export default function configureMenu ({ app }) {
|
||||
paragraph,
|
||||
format,
|
||||
windowMenu,
|
||||
theme,
|
||||
view,
|
||||
help
|
||||
]
|
||||
|
20
src/main/menus/theme.js
Normal file
20
src/main/menus/theme.js
Normal file
@ -0,0 +1,20 @@
|
||||
import * as actions from '../actions/theme'
|
||||
|
||||
export default {
|
||||
label: 'Theme',
|
||||
submenu: [{
|
||||
label: 'Dark',
|
||||
type: 'radio',
|
||||
checked: true,
|
||||
click (menuItem, browserWindow) {
|
||||
actions.selectTheme(browserWindow, 'dark')
|
||||
}
|
||||
}, {
|
||||
label: 'Light',
|
||||
type: 'radio',
|
||||
checked: false,
|
||||
click (menuItem, browserWindow) {
|
||||
actions.selectTheme(browserWindow, 'light')
|
||||
}
|
||||
}]
|
||||
}
|
@ -2,5 +2,6 @@ import { Menu } from 'electron'
|
||||
|
||||
export const getMenuItem = menuName => {
|
||||
const menus = Menu.getApplicationMenu()
|
||||
console.log(typeof menus.append)
|
||||
return menus.items.find(menu => menu.label === menuName)
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
:filename="filename"
|
||||
:active="windowActive"
|
||||
:word-count="wordCount"
|
||||
:theme="theme"
|
||||
></title-bar>
|
||||
<editor
|
||||
:typewriter="typewriter"
|
||||
@ -13,14 +14,18 @@
|
||||
:markdown="markdown"
|
||||
:cursor="cursor"
|
||||
v-if="!sourceCode"
|
||||
:theme="theme"
|
||||
:theme-css="themeCSS"
|
||||
></editor>
|
||||
<source-code
|
||||
v-else
|
||||
:markdown="markdown"
|
||||
:cursor="cursor"
|
||||
:theme="theme"
|
||||
></source-code>
|
||||
<search
|
||||
v-if="!sourceCode"
|
||||
:theme="theme"
|
||||
></search>
|
||||
</div>
|
||||
</template>
|
||||
@ -46,12 +51,14 @@
|
||||
computed: {
|
||||
...mapState([
|
||||
'pathname', 'filename', 'windowActive', 'wordCount',
|
||||
'typewriter', 'focus', 'sourceCode', 'markdown', 'cursor'
|
||||
'typewriter', 'focus', 'sourceCode', 'markdown', 'cursor',
|
||||
'theme', 'themeCSS'
|
||||
])
|
||||
},
|
||||
created () {
|
||||
const { dispatch } = this.$store
|
||||
|
||||
dispatch('ASK_FOR_THEME')
|
||||
dispatch('ASK_FOR_MODE')
|
||||
dispatch('LISTEN_FOR_CLOSE')
|
||||
dispatch('LISTEN_FOR_SAVE_AS')
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
class="editor-wrapper"
|
||||
:class="{ 'typewriter': typewriter, 'focus': focus, 'source': sourceCode }"
|
||||
:class="[{ 'typewriter': typewriter, 'focus': focus, 'source': sourceCode }, theme]"
|
||||
>
|
||||
<div
|
||||
ref="editor"
|
||||
@ -83,7 +83,9 @@
|
||||
required: true
|
||||
},
|
||||
markdown: String,
|
||||
cursor: Object
|
||||
cursor: Object,
|
||||
theme: String,
|
||||
themeCss: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
@ -106,6 +108,12 @@
|
||||
},
|
||||
focus: function (value) {
|
||||
this.editor.setFocusMode(value)
|
||||
},
|
||||
theme: function (value, oldValue) {
|
||||
const { editor, themeCss } = this
|
||||
if (value !== oldValue && editor) {
|
||||
editor.setTheme(value, themeCss[value])
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
@ -113,7 +121,7 @@
|
||||
const ele = this.$refs.editor
|
||||
this.editor = new Aganippe(ele)
|
||||
const { container } = this.editor
|
||||
const { markdown } = this
|
||||
const { markdown, theme, themeCss } = this
|
||||
// init set markdown and edit mode(typewriter mode and focus mode)
|
||||
if (markdown.trim()) {
|
||||
this.setMarkdownToEditor(markdown)
|
||||
@ -123,6 +131,10 @@
|
||||
this.editor.setFocusMode(this.focus)
|
||||
}
|
||||
|
||||
if (theme) {
|
||||
this.editor.setTheme(theme, themeCss[theme])
|
||||
}
|
||||
|
||||
bus.$on('file-loaded', this.setMarkdownToEditor)
|
||||
bus.$on('undo', () => this.editor.undo())
|
||||
bus.$on('redo', () => this.editor.redo())
|
||||
@ -247,7 +259,7 @@
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import '../../editor/themes/light.css';
|
||||
/* @import '../../editor/themes/dark.css';*/
|
||||
@import '../../editor/index.css';
|
||||
.editor-wrapper {
|
||||
height: calc(100vh - 22px);
|
||||
@ -267,15 +279,17 @@
|
||||
padding-top: calc(50vh - 136px);
|
||||
padding-bottom: calc(50vh - 54px);
|
||||
}
|
||||
.v-modal {
|
||||
background: #fff;
|
||||
opacity: .8;
|
||||
}
|
||||
|
||||
.ag-dialog-table {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.ag-dialog-table .dialog-title svg {
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
}
|
||||
/* for dark theme */
|
||||
.dark.editor-wrapper {
|
||||
background: rgb(43, 43, 43);
|
||||
}
|
||||
</style>
|
||||
|
@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div class="search-bar"
|
||||
:class="theme"
|
||||
@click.stop="noop"
|
||||
v-show="showSearch"
|
||||
>
|
||||
@ -100,6 +101,9 @@
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
theme: String
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
showSearch: false,
|
||||
@ -258,6 +262,7 @@
|
||||
display: flex;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.input-wrapper .search-result {
|
||||
position: absolute;
|
||||
@ -276,4 +281,21 @@
|
||||
color: #606266;
|
||||
padding: 0 8px;
|
||||
}
|
||||
</style>
|
||||
/* css for dark theme*/
|
||||
.dark {
|
||||
caret-color: #efefef;
|
||||
background: rgb(43, 43, 43);
|
||||
color: #606266;
|
||||
}
|
||||
.dark input {
|
||||
background: rgb(54, 55, 49);
|
||||
color: #C0C4CC;
|
||||
}
|
||||
.dark .button:hover {
|
||||
background: rgb(71, 72, 66);
|
||||
color: #C0C4CC;
|
||||
}
|
||||
.dark .button:active {
|
||||
background: #303133;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="source-code" ref="sourceCode">
|
||||
<div class="source-code" ref="sourceCode" :class="[theme]">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -11,7 +11,8 @@
|
||||
export default {
|
||||
props: {
|
||||
markdown: String,
|
||||
cursor: Object
|
||||
cursor: Object,
|
||||
theme: String
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
@ -19,14 +20,28 @@
|
||||
editor: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
theme: function (value, oldValue) {
|
||||
const cm = this.$refs.sourceCode.querySelector('.CodeMirror')
|
||||
if (value !== oldValue) {
|
||||
if (value === 'dark') {
|
||||
cm.classList.remove('cm-s-default')
|
||||
cm.classList.add('cm-s-railscasts')
|
||||
} else {
|
||||
cm.classList.add('cm-s-default')
|
||||
cm.classList.remove('cm-s-railscasts')
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.$nextTick(() => {
|
||||
const { markdown = '' } = this
|
||||
const { markdown = '', theme } = this
|
||||
this.contentState = new ContentState()
|
||||
const container = this.$refs.sourceCode
|
||||
const editor = this.editor = codeMirror(container, {
|
||||
const codeMirrorConfig = {
|
||||
// theme: 'railscasts',
|
||||
value: markdown,
|
||||
value: '',
|
||||
lineNumbers: true,
|
||||
autofocus: true,
|
||||
lineWrapping: true,
|
||||
@ -38,9 +53,22 @@
|
||||
return ''
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
if (theme === 'dark') codeMirrorConfig.theme = 'railscasts'
|
||||
this.editor = codeMirror(container, codeMirrorConfig)
|
||||
bus.$on('file-loaded', this.setMarkdown)
|
||||
|
||||
this.listenChange()
|
||||
|
||||
this.setMarkdown(markdown)
|
||||
})
|
||||
},
|
||||
beforeDestory () {
|
||||
bus.$off('file-loaded', this.setMarkdown)
|
||||
},
|
||||
methods: {
|
||||
listenChange () {
|
||||
const { editor } = this
|
||||
editor.on('cursorActivity', (cm, event) => {
|
||||
const cursor = cm.getCursor()
|
||||
const markdown = cm.getValue()
|
||||
@ -51,13 +79,7 @@
|
||||
})
|
||||
|
||||
setMode(editor, 'markdown')
|
||||
this.setMarkdown(markdown)
|
||||
})
|
||||
},
|
||||
beforeDestory () {
|
||||
bus.$off('file-loaded', this.setMarkdown)
|
||||
},
|
||||
methods: {
|
||||
},
|
||||
setMarkdown (markdown) {
|
||||
const { editor, cursor } = this
|
||||
this.editor.setValue(markdown)
|
||||
@ -89,4 +111,11 @@
|
||||
.source-code .CodeMirror-activeline-gutter {
|
||||
background: #F2F6FC;
|
||||
}
|
||||
.dark {
|
||||
background: rgb(43, 43, 43);
|
||||
}
|
||||
.dark.source-code .CodeMirror-activeline-background,
|
||||
.dark.source-code .CodeMirror-activeline-gutter {
|
||||
background: #333;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="title-bar"
|
||||
:class="{ 'active': active }"
|
||||
:class="[{ 'active': active }, theme]"
|
||||
>
|
||||
<div class="title">
|
||||
<span v-for="(path, index) of paths" :key="index">
|
||||
@ -37,7 +37,8 @@
|
||||
filename: String,
|
||||
pathname: String,
|
||||
active: Boolean,
|
||||
wordCount: Object
|
||||
wordCount: Object,
|
||||
theme: String
|
||||
},
|
||||
computed: {
|
||||
paths () {
|
||||
@ -122,4 +123,16 @@
|
||||
background: #F2F6FC;
|
||||
color: #606266;
|
||||
}
|
||||
/* css for dark theme */
|
||||
.dark {
|
||||
background: rgb(43, 43, 43);
|
||||
color: #909399;
|
||||
}
|
||||
.dark .title:hover {
|
||||
color: #F2F6FC;
|
||||
}
|
||||
.dark .word-count:hover {
|
||||
background: rgb(71, 72, 66);
|
||||
color: #C0C4CC;
|
||||
}
|
||||
</style>
|
||||
|
@ -9,6 +9,8 @@ const state = {
|
||||
matches: [],
|
||||
value: ''
|
||||
},
|
||||
theme: '',
|
||||
themeCSS: null,
|
||||
typewriter: false, // typewriter mode
|
||||
focus: false, // focus mode
|
||||
sourceCode: false, // source code mode
|
||||
@ -26,6 +28,12 @@ const state = {
|
||||
}
|
||||
|
||||
const mutations = {
|
||||
SET_THEME (state, { theme, themeCSS }) {
|
||||
state.theme = theme
|
||||
if (themeCSS) {
|
||||
state.themeCSS = themeCSS
|
||||
}
|
||||
},
|
||||
SET_MODE (state, { type, checked }) {
|
||||
state[type] = checked
|
||||
},
|
||||
@ -57,6 +65,13 @@ const mutations = {
|
||||
}
|
||||
|
||||
const actions = {
|
||||
ASK_FOR_THEME ({ commit }) {
|
||||
ipcRenderer.send('AGANI::ask-for-theme')
|
||||
ipcRenderer.on('AGANI::theme', (e, themes) => {
|
||||
console.log(themes)
|
||||
commit('SET_THEME', themes)
|
||||
})
|
||||
},
|
||||
SEARCH ({ commit }, value) {
|
||||
commit('SET_SEARCH', value)
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user