marktext/src/renderer/util/theme.js
2022-04-25 22:54:26 -07:00

195 lines
5.6 KiB
JavaScript

import { THEME_STYLE_ID, COMMON_STYLE_ID, DEFAULT_CODE_FONT_FAMILY, oneDarkThemes, railscastsThemes } from '../config'
import { dark, graphite, materialDark, oneDark, ulysses } from './themeColor'
import { isLinux } from './index'
import elementStyle from 'element-ui/lib/theme-chalk/index.css'
const ORIGINAL_THEME = '#409EFF'
const patchTheme = css => {
return `@media not print {\n${css}\n}`
}
const getEmojiPickerPatch = () => {
return isLinux
? '.ag-emoji-picker section .emoji-wrapper .item span { font-family: sans-serif, "Noto Color Emoji"; }'
: ''
}
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)
const isDarkTheme = isCmOneDark || isCmRailscasts
let themeStyleEle = document.querySelector(`#${THEME_STYLE_ID}`)
if (!themeStyleEle) {
themeStyleEle = document.createElement('style')
themeStyleEle.id = THEME_STYLE_ID
document.head.appendChild(themeStyleEle)
}
switch (theme) {
case 'light':
themeStyleEle.innerHTML = ''
break
case 'dark':
themeStyleEle.innerHTML = patchTheme(dark())
break
case 'material-dark':
themeStyleEle.innerHTML = patchTheme(materialDark())
break
case 'ulysses':
themeStyleEle.innerHTML = patchTheme(ulysses())
break
case 'graphite':
themeStyleEle.innerHTML = patchTheme(graphite())
break
case 'one-dark':
themeStyleEle.innerHTML = patchTheme(oneDark())
break
default:
console.log('unknown theme')
break
}
// workaround: use dark icons
document.body.classList.remove('dark')
if (isDarkTheme) {
document.body.classList.add('dark')
}
// change CodeMirror theme
const cm = document.querySelector('.CodeMirror')
if (cm) {
cm.classList.remove('cm-s-default')
cm.classList.remove('cm-s-one-dark')
cm.classList.remove('cm-s-railscasts')
if (isCmOneDark) {
cm.classList.add('cm-s-one-dark')
} else if (isCmRailscasts) {
cm.classList.add('cm-s-railscasts')
} else {
cm.classList.add('cm-s-default')
}
}
}
export const setWrapCodeBlocks = value => {
const CODE_WRAP_STYLE_ID = 'ag-code-wrap'
let result = ''
if (value) {
result = '.ag-code-content { display: block; white-space: pre-wrap; word-break: break-word; overflow: hidden; }'
} else {
result = '.ag-code-content { display: block; white-space: pre; word-break: break-word; overflow: auto; }'
}
let styleEle = document.querySelector(`#${CODE_WRAP_STYLE_ID}`)
if (!styleEle) {
styleEle = document.createElement('style')
styleEle.setAttribute('id', CODE_WRAP_STYLE_ID)
document.head.appendChild(styleEle)
}
styleEle.innerHTML = result
}
export const setEditorWidth = value => {
const EDITOR_WIDTH_STYLE_ID = 'editor-width'
let result = ''
if (value && /^[0-9]+(?:ch|px|%)$/.test(value)) {
// Overwrite the theme value and add 100px for padding.
result = `:root { --editorAreaWidth: calc(100px + ${value}); }`
}
let styleEle = document.querySelector(`#${EDITOR_WIDTH_STYLE_ID}`)
if (!styleEle) {
styleEle = document.createElement('style')
styleEle.setAttribute('id', EDITOR_WIDTH_STYLE_ID)
document.head.appendChild(styleEle)
}
styleEle.innerHTML = result
}
export const addCommonStyle = options => {
const { codeFontFamily, codeFontSize, hideScrollbar } = options
let sheet = document.querySelector(`#${COMMON_STYLE_ID}`)
if (!sheet) {
sheet = document.createElement('style')
sheet.id = COMMON_STYLE_ID
document.head.appendChild(sheet)
}
let scrollbarStyle = ''
if (hideScrollbar) {
scrollbarStyle = '::-webkit-scrollbar {display: none;}'
}
sheet.innerHTML = `${scrollbarStyle}
span code,
td code,
th code,
code,
code[class*="language-"],
.CodeMirror,
pre.ag-paragraph {
font-family: ${codeFontFamily}, ${DEFAULT_CODE_FONT_FAMILY};
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
// NOTE: Prepend element UI style, otherwise we cannot overwrite the style with the default light theme.
document.head.insertBefore(sheet, document.head.firstChild)
sheet.innerHTML = newElementStyle
}
// Append common sheet and theme at the end of head - order is important.
export const addStyles = options => {
const { theme } = options
addThemeStyle(theme)
addCommonStyle(options)
}