opti: rewrite TOC and remove hard code theme (#778)

* opti: prevent the duplicated header text error

* scroll error in toc

* delay the change event when init muya, so that the change event listener can handle change event.

* opti: style

* almost finished need last check

* style in table

* clear up some unused codes

* clear up theme hard code

* add no search data picture and new Folder new File

* move search and replace to top of editor

* add: no data image when there is no TOC

* update structure

* little bug fix

* update styles

* update un_draw icons

* update style of editor

* add animation when make open folder collapse.

* remove theme props in editor component, deprecated lightColor and darkColor

* add Ulysses Light

* theme: graphite

* update Ulysses blockquote

* update emoji style

* update titleBar height

* change theme color

* Support macOs dark mode

* update style of tooltip in editor

* update styles

* fix: TOC auto expand all

* patch added
This commit is contained in:
Ran Luo 2019-03-24 05:09:00 +08:00 committed by GitHub
parent 07d0bbb249
commit e1cfec6e3e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
60 changed files with 1368 additions and 1578 deletions

View File

@ -88,7 +88,8 @@
]
},
"mac": {
"icon": "resources/icons/icon.icns"
"icon": "resources/icons/icon.icns",
"darkModeSupport": true
},
"win": {
"icon": "resources/icons/icon.ico",
@ -143,7 +144,7 @@
"chokidar": "^2.1.2",
"codemirror": "^5.44.0",
"command-exists": "^1.2.8",
"diacritics-map": "^0.1.0",
"dayjs": "^1.8.10",
"dompurify": "^1.0.10",
"electron-is-accelerator": "^0.1.2",
"element-resize-detector": "^1.2.0",

View File

@ -1,10 +1,12 @@
import path from 'path'
import { app } from 'electron'
import { app, systemPreferences } from 'electron'
import appWindow from './window'
import { isOsx } from './config'
import { dockMenu } from './menus'
import { isDirectory, isMarkdownFile } from './utils'
import { isDirectory, isMarkdownFile, getMenuItemById } from './utils'
import { watchers } from './utils/imagePathAutoComplement'
import { selectTheme } from './actions/theme'
import preference from './preference'
class App {
constructor () {
@ -22,12 +24,12 @@ class App {
app.commandLine.appendSwitch('remote-debugging-port', '8315')
}
app.on('open-file', this.openFile.bind(this))
app.on('open-file', this.openFile)
app.on('ready', this.ready.bind(this))
app.on('ready', this.ready)
app.on('window-all-closed', () => {
app.removeListener('open-file', this.openFile.bind(this))
app.removeListener('open-file', this.openFile)
// close all the image path watcher
for (const watcher of watchers.values()) {
watcher.close()
@ -63,7 +65,7 @@ class App {
})
}
ready () {
ready = () => {
if (!isOsx && process.argv.length >= 2) {
for (const arg of process.argv) {
if (arg.startsWith('--')) {
@ -79,6 +81,32 @@ class App {
// Set dock on macOS
if (process.platform === 'darwin') {
app.dock.setMenu(dockMenu)
// listen for system theme change and change Mark Text own `dark` and `light`,
// In macOS 10.14 Mojave,
// Apple introduced a new system-wide dark mode for all macOS computers.
systemPreferences.subscribeNotification(
'AppleInterfaceThemeChangedNotification',
() => {
const { theme } = preference.getAll()
let setedTheme = null
if (systemPreferences.isDarkMode() && theme !== 'dark') {
selectTheme('dark')
setedTheme = 'dark'
}
if (!systemPreferences.isDarkMode() && theme === 'dark') {
selectTheme('light')
setedTheme = 'light'
}
if (setedTheme) {
const themeMenu = getMenuItemById('themeMenu')
const menuItem = themeMenu.submenu.items.filter(item => (item.id === setedTheme))[0]
if (menuItem) {
menuItem.checked = true
}
}
}
)
}
if (this.openFilesCache.length) {
@ -89,7 +117,7 @@ class App {
}
}
openFile (event, path) {
openFile = (event, path) => {
const { openFilesCache } = this
event.preventDefault()
if (app.isReady()) {

View File

@ -15,7 +15,7 @@ export const defaultWinOptions = {
useContentSize: true,
show: false,
frame: false,
titleBarStyle: 'hidden'
titleBarStyle: 'hiddenInset'
}
export const EXTENSIONS = [

View File

@ -5,19 +5,38 @@ const { theme } = userPreference.getAll()
export default {
label: 'Theme',
id: 'themeMenu',
submenu: [{
label: 'Dark',
label: 'Mateial Dark',
type: 'radio',
id: 'dark',
checked: theme === 'dark',
click (menuItem, browserWindow) {
actions.selectTheme('dark')
}
}, {
label: 'Light',
label: 'Cadmium Light',
type: 'radio',
id: 'light',
checked: theme === 'light',
click (menuItem, browserWindow) {
actions.selectTheme('light')
}
}, {
label: 'Ulysses Light',
type: 'radio',
id: 'ulysses',
checked: theme === 'ulysses',
click (menuItem, browserWindow) {
actions.selectTheme('ulysses')
}
}, {
label: 'Graphite Light',
type: 'radio',
id: 'graphite',
checked: theme === 'graphite',
click (menuItem, browserWindow) {
actions.selectTheme('graphite')
}
}]
}

View File

@ -1,22 +1,3 @@
/* Common CSS use by both light and dark themes */
:root {
--brandColor: #5b3cc4;
--successColor: rgb(23, 201, 100);
--warningColor: rgb(255, 130, 0);
--dangerColor: rgb(242, 19, 93);
--activeColor: #409eff;
--infoColor: #909399;
--primaryColor: #303133;
--regularColor: #606266;
--secondaryColor: #909399;
--placeholerColor: #C0C4CC;
--baseBorder: #DCDFE6;
--lightBorder: #E4E7ED;
--lighterBorder: #EBEEF5;
--extraLightBorder: #F2F6FC;
--editorAreaWidth: 700px;
}
html {
-webkit-font-smoothing: antialiased;
}
@ -54,14 +35,14 @@ pre {
top: 0;
left: -25px;
font-size: 14px;
color: var(--secondaryColor);
color: var(--iconColor);
font-weight: 400;
font-style: italic;
}
div.ag-show-quick-insert-hint p.ag-paragraph.ag-active > span.ag-line:first-of-type:empty::after {
content: 'Type @ to insert';
color: var(--placeholerColor);
color: var(--editorColor10);
}
.ag-paragraph:empty::after,
@ -81,7 +62,7 @@ div.ag-show-quick-insert-hint p.ag-paragraph.ag-active > span.ag-line:first-of-t
.ag-reference-marker {
font-size: .9em;
color: var(--secondaryColor);
color: var(--editorColor10);
}
.ag-reference-title {
@ -94,19 +75,14 @@ div.ag-show-quick-insert-hint p.ag-paragraph.ag-active > span.ag-line:first-of-t
margin: 0 5px;
}
/* .ag-soft-line-break::after {
content: '↓';
opacity: .5;
} */
.ag-hard-line-break::after {
content: '↩';
opacity: .5;
}
*:not(.ag-hide)::selection, .ag-selection {
background: var(--baseBorder);
color: var(--primaryColor);
background: var(--selectionColor);
color: var(--editorColor);
}
.ag-hide::selection {
@ -149,19 +125,18 @@ div.ag-function-html.ag-active .ag-html-preview {
animation-name: highlight;
animation-duration: .25s;
display: inline-block;
background: rgb(249, 226, 153);
color: var(--primaryColor);
background: var(--highlightColor);
}
span.ag-html-tag {
color: var(--secondaryColor);
color: var(--editorColor50);
font-weight: 200;
font-family: monospace;
}
span.ag-math {
position: relative;
color: rebeccapurple;
color: var(--editorColor);
font-family: monospace;
display: inline-block;
vertical-align: bottom;
@ -179,7 +154,7 @@ span.ag-math {
div.ag-empty {
text-align: center;
color: var(--regularColor);
color: var(--editorColor50);
font-size: 14px;
font-style: italic;
font-family: monospace;
@ -187,7 +162,7 @@ div.ag-empty {
div.ag-math-error,
span.ag-math > .ag-math-render.ag-math-error {
color: var(--warningColor);
color: var(--deleteColor);
font-size: 14px;
font-style: italic;
font-family: monospace;
@ -268,11 +243,10 @@ figure {
display: flex;
width: 20px;
height: 20px;
padding: 2px;
margin-right: 3px;
cursor: pointer;
border-radius: 3px;
color: var(--regularColor);
color: var(--iconColor);
}
.ag-tool-bar ul li[data-label=delete] {
@ -281,23 +255,18 @@ figure {
}
.ag-tool-bar ul li[data-label=delete] {
color: var(--dangerColor);
color: var(--deleteColor);
right: 0;
}
.ag-tool-bar ul li.active svg {
fill: #409eff;
fill: var(--themeColor);
}
.ag-tool-bar ul li svg {
width: 100%;
height: 100%;
will-change: transform;
transition: transform .2s ease-in-out;
}
.ag-tool-bar ul li:hover svg {
transform: scale(1.1);
}
figure.ag-active .ag-tool-bar {
@ -313,7 +282,7 @@ figure.ag-active[data-role=HTML]::before {
top: 0;
left: -45px;
font-size: 12px;
color: var(--secondaryColor);
color: var(--editorColor50);
font-weight: 400;
font-style: italic;
}
@ -345,66 +314,56 @@ li.ag-task-list-item {
li.ag-task-list-item > input[type=checkbox] {
position: absolute;
cursor: pointer;
width: 16px;
height: 16px;
margin: 4px 0px 0px;
top: 0;
width: 10px;
height: 10px;
top: 5px;
left: -22px;
transform-origin: center;
transform: rotate(-90deg);
transition: all .2s ease;
}
li.ag-task-list-item > input.ag-checkbox-checked {
transform: rotate(0);
}
li.ag-task-list-item > input.ag-checkbox-checked ~ * {
color: var(--regularColor);
color: var(--editorColor50);
}
li.ag-task-list-item > input.ag-checkbox-checked ~ p {
text-decoration: line-through;
color: var(--secondaryColor);
color: var(--editorColor50);
}
li.ag-task-list-item > input[type=checkbox]::before {
content: '';
width: 16px;
height: 16px;
width: 14px;
height: 14px;
box-sizing: border-box;
display: inline-block;
border: 2px solid var(--infoColor);
border-radius: 2px;
background-color: #fff;
border: 1px solid var(--iconColor);
border-radius: 50%;
background-color: var(--editorBgColor);
position: absolute;
top: 0;
left: 0;
top: -2px;
left: -2px;
transition: all .2s ease;
}
li.ag-task-list-item > input.ag-checkbox-checked::before {
border: transparent;
background-color: var(--activeColor);
}
li.ag-task-list-item > input::after {
content: '';
transform: rotate(-45deg) scale(0);
width: 9px;
height: 5px;
border: 2px solid #fff;
transform: rotate(-28deg) skew(0, -25deg) scale(0);
width: 8px;
height: 4px;
border: 1px solid var(--iconColor);
border-top: none;
border-right: none;
position: absolute;
display: inline-block;
top: 3px;
left: 3px;
top: 0px;
left: 2px;
transform-origin: bottom;
transition: all .2s ease;
}
li.ag-task-list-item > input.ag-checkbox-checked::after {
transform: rotate(-45deg) scale(1);
transform: rotate(-28deg) skew(0, -25deg) scale(1);
}
/* li p .ag-hide:first-child {
@ -419,8 +378,8 @@ p:not(.ag-active)[data-role="hr"] {
p:not(.ag-active)[data-role="hr"]::before {
content: '';
width: 100%;
height: 5px;
background: gainsboro;
height: 2px;
background: var(--editorColor10);
position: absolute;
top: 50%;
transform: translateY(-50%);
@ -433,7 +392,7 @@ p:not(.ag-active)[data-role="hr"] * {
pre.ag-multiple-math,
pre.ag-front-matter {
position: relative;
background: #f6f8fa;
background: var(--selectionColor);
padding: .5rem;
border: 5px;
font-size: 14px;
@ -445,17 +404,17 @@ pre.ag-front-matter {
pre.ag-front-matter span.ag-code-line:first-of-type:empty::after {
content: 'Input YAML Front Matter...';
color: var(--placeholerColor);
color: var(--editorColor10);
}
pre[data-role$='code'] span.ag-language-input:empty::after {
content: 'Input Language...';
color: var(--placeholerColor);
color: var(--editorColor10);
}
pre.ag-multiple-math span.ag-code-line:first-of-type:empty::after {
content: 'Input Mathematical Formula...';
color: var(--placeholerColor);
color: var(--editorColor10);
}
figure,
@ -545,7 +504,7 @@ pre.ag-active.ag-indent-code::before,
pre.ag-active.ag-indent-code::after,
pre.ag-active.ag-multiple-math::before,
pre.ag-active.ag-multiple-math::after {
color: var(--regularColor);
color: var(--editorColor30);
font-family: monospace;
position: absolute;
font-weight: 600;
@ -574,11 +533,6 @@ pre.ag-active.ag-indent-code::after {
bottom: -23px;
}
span.ag-multiple-math-line {
color: purple;
font-family: monospace;
}
figure.ag-container-block div.ag-container-preview {
width: 100%;
text-align: center;
@ -593,8 +547,8 @@ figure.ag-active.ag-container-block > div.ag-container-preview {
z-index: 10000;
transform: translateX(-50%);
padding: .5rem;
background: #fff;
border: 1px solid var(--lightBorder);
background: var(--floatBgColor);
border: 1px solid var(--floatBorderColor);
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);
}
@ -614,7 +568,7 @@ hr {
span.ag-emoji-marked-text {
position: relative;
color: var(--regularColor);
color: var(--themeColor);
text-decoration: none;
}
@ -632,14 +586,14 @@ span.ag-emoji-marked-text {
.ag-emoji-marked-text::before {
position: absolute;
content: attr(data-emoji);
color: #000;
color: var(--editorColor);
top: -6px;
left: -1.1em;
font-size: 1.5em;
font-size: 1em;
}
.ag-hide.ag-emoji-marked-text::before {
top: -19px;
top: -15px;
}
.ag-html-escape {
@ -653,7 +607,7 @@ span.ag-emoji-marked-text {
top: -2px;
left: -1rem;
width: 1rem;
color: var(--secondaryColor);
color: var(--editorColor30);
height: 100%;
display: flex;
justify-content: space-around;
@ -670,14 +624,14 @@ span.ag-emoji-marked-text {
font-size: 14px;
font-family: monospace;
font-weight: 600;
color: var(--activeColor);
color: var(--themeColor);
background: transparent;
border: none;
z-index: 10;
}
.ag-language-input::placeholder {
color: var(--placeholerColor);
color: var(--editorColor10);
}
pre.ag-active .ag-language-input {
@ -685,14 +639,14 @@ pre.ag-active .ag-language-input {
}
.ag-language {
color: var(--activeColor);
color: var(--themeColor);
font-weight: 600;
text-decoration: none;
font-family: monospace;
}
span.ag-image-marked-text, span.ag-link-in-bracket, span.ag-link-in-bracket .ag-backlash {
color: var(--regularColor);
color: var(--editorColor50);
font-size: 16px;
text-decoration: none;
font-family: monospace;
@ -703,7 +657,7 @@ span.ag-image-marked-text, span.ag-link-in-bracket, span.ag-link-in-bracket .ag-
color: rgb(51, 51, 51);
}
span.ag-warn.ag-emoji-marked-text {
color: var(--warningColor);
color: var(--deleteColor);
text-decoration: none;
}
@ -735,12 +689,12 @@ span.ag-warn.ag-emoji-marked-text {
}
span[data-role="link"], a[data-role="link"], span[data-role="link"] .ag-backlash {
color: #0366d6;
color: var(--themeColor);
text-decoration: none;
}
span.ag-reference-link {
color: var(--warningColor);
color: var(--deleteColor);
}
.ag-focus-mode p.ag-paragraph,

View File

@ -218,7 +218,6 @@ export const EXPORT_DOMPURIFY_CONFIG = {
export const MUYA_DEFAULT_OPTION = {
focusMode: false,
theme: 'light',
markdown: '',
preferLooseListItem: true,
autoPairBracket: true,

View File

@ -22,6 +22,7 @@ import imagePathCtrl from './imagePathCtrl'
import htmlBlockCtrl from './htmlBlock'
import clickCtrl from './clickCtrl'
import inputCtrl from './inputCtrl'
import tocCtrl from './tocCtrl'
import importMarkdown from '../utils/importMarkdown'
const prototypes = [
@ -44,6 +45,7 @@ const prototypes = [
htmlBlockCtrl,
clickCtrl,
inputCtrl,
tocCtrl,
importMarkdown
]

View File

@ -0,0 +1,24 @@
const tocCtrl = ContentState => {
ContentState.prototype.getTOC = function () {
const { blocks } = this
const toc = []
for (const block of blocks) {
if (/^h\d$/.test(block.type)) {
const { headingStyle, text, key, type } = block
const content = headingStyle === 'setext' ? text.trim() : text.replace(/^ *#{1,6} {1,}/, '').trim()
const lvl = +type.substring(1)
const slug = key
toc.push({
content,
lvl,
slug
})
}
}
return toc
}
}
export default tocCtrl

View File

@ -18,9 +18,8 @@ class Muya {
}
constructor (container, options) {
this.options = Object.assign({}, MUYA_DEFAULT_OPTION, options)
const { focusMode, theme, markdown } = this.options
const { focusMode, markdown } = this.options
this.focusMode = focusMode
this.theme = theme
this.markdown = markdown
this.container = getContainer(container, this.options)
this.eventCenter = new EventCenter()
@ -42,7 +41,7 @@ class Muya {
init () {
const { container, contentState, eventCenter } = this
contentState.stateRender.setContainer(container.children[0])
eventCenter.subscribe('stateChange', this.dispatchChange.bind(this))
eventCenter.subscribe('stateChange', this.dispatchChange)
eventCenter.attachDOMEvent(container, 'contextmenu', event => {
event.preventDefault()
event.stopPropagation()
@ -61,19 +60,19 @@ class Muya {
eventCenter.dispatch('contextmenu', event, sectionChanges)
})
contentState.listenForPathChange()
const { theme, focusMode, markdown } = this
this.setTheme(theme)
const { focusMode, markdown } = this
this.setMarkdown(markdown)
this.setFocusMode(focusMode)
}
dispatchChange () {
dispatchChange = () => {
const { eventCenter } = this
const markdown = this.markdown = this.getMarkdown()
const wordCount = this.getWordCount(markdown)
const cursor = this.getCursor()
const history = this.getHistory()
eventCenter.dispatch('change', { markdown, wordCount, cursor, history })
const toc = this.getTOC()
eventCenter.dispatch('change', { markdown, wordCount, cursor, history, toc })
}
getMarkdown () {
@ -85,6 +84,10 @@ class Muya {
return this.contentState.getHistory()
}
getTOC () {
return this.contentState.getTOC()
}
setHistory (history) {
return this.contentState.setHistory(history)
}
@ -119,7 +122,9 @@ class Muya {
this.contentState.importMarkdown(newMarkdown)
this.contentState.importCursor(cursor)
this.contentState.render(isRenderCursor)
this.dispatchChange()
setTimeout(() => {
this.dispatchChange()
}, 0)
}
createTable (tableChecker) {
@ -140,16 +145,6 @@ class Muya {
this.focusMode = bool
}
setTheme (name) {
if (!name) return
const { eventCenter } = this
this.theme = name
// Render cursor and refresh code block
this.contentState.render(true)
// notice the ui components to change theme
eventCenter.dispatch('theme-change', name)
}
setFont ({ fontSize, lineHeight }) {
if (fontSize) this.contentState.fontSize = parseInt(fontSize, 10)
if (lineHeight) this.contentState.lineHeight = lineHeight

View File

@ -1,8 +1,7 @@
import katex from 'katex'
import mermaid from 'mermaid'
import prism, { loadedCache } from '../../../prism/'
import { slugify } from '../../../utils/dirtyToc'
import { CLASS_OR_ID, DEVICE_MEMORY, isInElectron, PREVIEW_DOMPURIFY_CONFIG, HAS_TEXT_BLOCK_REG } from '../../../config'
import { CLASS_OR_ID, DEVICE_MEMORY, PREVIEW_DOMPURIFY_CONFIG, HAS_TEXT_BLOCK_REG } from '../../../config'
import { tokenizer } from '../../parse'
import { snakeToCamel, sanitize, escapeHtml, getLongUniqueId, getImageInfo } from '../../../utils'
import { h, htmlToVNode } from '../snabbdom'
@ -199,8 +198,7 @@ export default function renderLeafBlock (block, cursor, activeBlocks, matches, u
// TODO: This should be the best place to create and update the TOC.
// Cache `block.key` and title and update only if necessary.
Object.assign(data.dataset, {
head: type,
id: isInElectron ? slugify(text.replace(/^#+\s(.*)/, (_, p1) => p1)) : ''
head: type
})
selector += `.${headingStyle}`
}

View File

@ -7,9 +7,9 @@
top: -1000px;
right: -1000px;
border-radius: 8px;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.1);
border: 1px solid rgba(0, 0, 0, 0.1);
background-color: #ffffff;
box-shadow: 0 4px 8px 0 var(--floatBorderColor);
border: 1px solid var(--floatBorderColor);
background-color: var(--floatBgColor);
transition: opacity .25s ease-in-out;
transform-origin: top;
box-sizing: border-box;
@ -17,11 +17,6 @@
overflow: hidden;
}
.ag-float-wrapper.dark {
background: var(--primaryColor);
border-color: var(--primaryColor);
}
.ag-float-container::-webkit-scrollbar:vertical {
width: 0px;
}
@ -40,10 +35,6 @@
transform: rotate(45deg);
}
.dark[x-placement] .ag-popper-arrow {
border-color: var(--primaryColor);
}
[x-placement="bottom-start"] > .ag-popper-arrow {
border-right: none;
border-bottom: none;

View File

@ -88,16 +88,6 @@ class BaseFloat {
}
}
const themeChange = theme => {
const { container, floatBox } = this
;[container, floatBox].forEach(ele => {
if (!ele.classList.contains(theme)) {
ele.classList.remove(theme === 'dark' ? 'light' : 'dark')
ele.classList.add(theme)
}
})
}
eventCenter.attachDOMEvent(document, 'click', this.hide.bind(this))
eventCenter.attachDOMEvent(floatBox, 'click', event => {
event.stopPropagation()
@ -105,7 +95,6 @@ class BaseFloat {
})
eventCenter.attachDOMEvent(container, 'keydown', keydownHandler)
eventCenter.attachDOMEvent(container, 'scroll', scrollHandler)
eventCenter.subscribe('theme-change', themeChange)
}
hide () {

View File

@ -20,11 +20,14 @@
.ag-list-picker:hover .active {
background: transparent;
color: #606266;
}
.ag-list-picker .item .language {
color: var(--editorColor);
}
.ag-list-picker .active, .ag-list-picker .item:hover {
background-color: #ecf5ff;
color: var(--activeColor);
background-color: var(--floatHoverColor);
}
.ag-list-picker .item .icon-wrapper {
@ -48,13 +51,4 @@
font-size: 14px;
line-height: 35px;
margin-left: 10px;
}
.ag-list-picker.dark:hover .active {
background: transparent;
color: currentColor;
}
.ag-list-picker.dark .active, .ag-list-picker.dark .item:hover {
background-color: rgb(71, 72, 66);
color: var(--activeColor);
}

View File

@ -6,7 +6,7 @@
}
.ag-emoji-picker .title {
color: rgba(55, 53, 47, 0.6);
color: var(--editorColor);
line-height: 120%;
font-size: 11px;
padding: 10px 14px 12px 14px;
@ -16,7 +16,7 @@
font-weight: 600;
position: sticky;
top: 0;
background: #fff;
background: var(--floatBgColor);
z-index: 1001;
}
@ -44,8 +44,7 @@
}
.ag-emoji-picker .active, .ag-emoji-picker .item:hover {
background-color: #ecf5ff;
border: 1px solid var(--activeColor);
background-color: var(--floatHoverColor);
}
.ag-emoji-picker section .emoji-wrapper .item span {
@ -58,23 +57,4 @@
transition: transform .2s ease-in;
transform-origin: center;
}
.ag-emoji-picker .active span, .ag-emoji-picker .item:hover span {
transform: scale(1.1);
}
.ag-emoji-picker.dark .title {
background: var(--primaryColor);
color: var(--placeholerColor);
}
.ag-emoji-picker.dark:hover .active {
background: transparent;
border-color: transparent;
}
.ag-emoji-picker.dark .active, .ag-emoji-picker.dark .item:hover {
background-color: rgb(72, 71, 66);
border: 1px solid var(--primaryColor);
}

View File

@ -26,7 +26,7 @@
}
.ag-format-picker li.item:hover {
background: rgb(243, 243, 243);
background: var(--floatHoverColor);
}
.ag-format-picker li.item:last-of-type {
@ -38,29 +38,20 @@
width: 2px;
position: absolute;
height: 18px;
background: #eee;
background: var(--editorColor10);
left: -4px;
top: 6px;
}
.ag-format-picker li.item svg {
fill: #666;
fill: var(--iconColor);
}
.ag-format-picker li.item.active svg {
fill: var(--activeColor);
fill: var(--themeColor);
}
.ag-format-picker li.item .icon-wrapper {
width: 16px;
height: 16px;
}
/* dark theme */
.ag-format-picker.dark li.item:hover {
background: rgb(60, 60, 60);
}
.ag-format-picker.dark li.item:last-of-type:before {
background: rgb(71, 71, 71);
}

View File

@ -6,7 +6,7 @@
}
.ag-quick-insert .title {
color: rgba(0, 0, 0, .6);
color: var(--editorColor);
line-height: 120%;
font-size: 14px;
padding: 10px 14px 10px 14px;
@ -15,7 +15,7 @@
letter-spacing: 1px;
position: sticky;
top: 0;
background: #fff;
background: var(--floatBgColor);
z-index: 1001;
}
@ -32,20 +32,21 @@
}
.ag-quick-insert .active,
.ag-quick-insert div.item:hover {
background-color: rgba(0, 0, 0, .04);
background-color: var(--floatHoverColor);
}
.ag-quick-insert .no-result {
margin-top: 20px;
padding: 0 20px;
font-size: 14px;
color: #999;
text-align: center;
color: var(--themeColor);
}
.ag-quick-insert .icon-container {
width: 40px;
height: 40px;
border: 1px solid rgb(0, 0, 0, .1);
border: 1px solid var(--floatBorderColor);
display: flex;
justify-content: space-around;
align-items: center;
@ -57,7 +58,7 @@
width: 20px;
height: 20px;
opacity: .6;
fill: rgba(0, 0, 0, .6);
fill: var(--iconColor);
}
.ag-quick-insert .description {
@ -70,49 +71,21 @@
margin-right: 20px;
flex-shrink: 1;
text-align: right;
color: rgba(0, 0, 0, .3);
color: var(--editorColor50);
font-size: 14px;
letter-spacing: 1px;
}
.ag-quick-insert .big-title {
font-size: 14px;
color: rgba(0, 0, 0, .6);
color: var(--editorColor);
}
.ag-quick-insert .sub-title {
font-size: 12px;
color: rgba(0, 0, 0, .4);
color: var(--editorColor50);
}
.ag-quick-insert .hide {
display: none;
}
.ag-quick-insert.dark .title {
background: var(--primaryColor);
color: var(--placeholerColor);
}
.ag-quick-insert.dark .big-title {
color: var(--lightBorder);
}
.ag-quick-insert.dark .sub-title {
color: rgba(255, 255, 255, .6);
}
.ag-quick-insert.dark .active,
.ag-quick-insert.dark div.item:hover {
background-color: rgb(71, 72, 66);
}
.ag-quick-insert.dark .icon-container > svg {
fill: rgba(255, 255, 255, .5);
}
.ag-quick-insert.dark .short-cut {
color: rgba(255, 255, 255, .5);
}

View File

@ -19,46 +19,54 @@
box-sizing: border-box;
margin-right: 1px;
margin-bottom: 1px;
border: 1px solid #ccc;
border: 1px solid var(--editorColor10);
cursor: pointer;
}
.ag-table-picker-header span {
background: #ddd;
background: var(--editorColor10);
}
.ag-table-picker-cell.current {
background: darkgray;
background: var(--editorColor30);
}
.ag-table-picker-header .current {
background: #666;
background: var(--editorColor50);
}
.ag-table-picker-cell.selected {
background: #ecf5ff;
background: var(--selectionColor);
}
.ag-table-picker-header .selected {
background: #ecf5ff;
background: var(--selectionColor);
}
.ag-table-picker-cell:last-of-type {
margin-right: 0;
}
.ag-table-picker .footer {
padding-top: 5px;
border-top: 1px solid #ccc;
padding: 10px 0 0 0;
border-top: 1px solid var(--floatBorderColor);
text-align: center;
display: flex;
justify-content: space-around;
}
.ag-table-picker .footer input {
color: var(--editorColor);
background: var(--floatBorderColor);
outline: none;
border-radius: 3px;
text-align: center;
border: none;
box-sizing: border-box;
padding: 0;
height: 16px;
width: 16px;
height: 20px;
width: 30px;
border: none;
}
.ag-table-picker .footer button {
outline: none;
cursor: pointer;
border-radius: 2px;
line-height: 12px;
border-radius: 3px;
line-height: 20px;
border: none;
height: 16px;
background: #409eff;
height: 20px;
background: var(--themeColor);
color: #fff;
}

View File

@ -4,8 +4,11 @@
font-size: 12px;
padding: 5px 7px;
border-radius: 3px;
background: rgb(50, 50, 50);
color: rgb(255, 255, 255);
background: var(--floatBgColor);
color: var(--editorColor);
box-shadow: 0 4px 8px 0 var(--floatBorderColor);
border: 1px solid var(--floatBorderColor);
box-sizing: border-box;
z-index: 1000;
opacity: 0;
}
@ -16,10 +19,13 @@
}
.ag-tooltip:after {
top: 0;
top: -2px;
left: 50%;
content: '';
background: inherit;
border: 1px solid var(--floatBorderColor);
border-right: none;
border-bottom: none;
width: 8px;
height: 8px;
position: absolute;

View File

@ -6,7 +6,7 @@ const position = (source, ele) => {
Object.assign(ele.style, {
top: `${top + height + 10}px`,
left: `${right - ele.offsetWidth / 2 - 3}px`
left: `${right - ele.offsetWidth / 2 - 5}px`
})
}

View File

@ -1,39 +0,0 @@
import { Lexer } from '../parser/marked'
import diacritics from 'diacritics-map'
export const getTocFromMarkdown = markdown => {
const tokens = new Lexer({ disableInline: true }).lex(markdown)
const toc = []
let token = null
while ((token = tokens.shift())) {
switch (token.type) {
case 'heading': {
const { depth, text } = token
toc.push({
content: text,
lvl: depth,
slug: slugify(text)
})
break
}
}
}
return toc
}
export const slugify = str => {
str = str.replace(/^\s+|\s+$/g, '').toLowerCase()
// replace accents
str = str.replace(/[À-ž]/g, c => {
return diacritics[c] || c
})
return str
.replace(/[^a-z0-9 -]/g, '') // remove invalid chars
.replace(/\t/g, '--') // collapse tabs and replace by --
.replace(/\s+/g, '-') // collapse whitespace and replace by -
.replace(/^-+/, '') // trim - from start of text
.replace(/-+$/, '') // trim - from end of text
.replace(/-+/g, '-') // collapse dashes
}

View File

@ -1,633 +0,0 @@
/*
* Open Sans
* https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,600,600i,700,700i&subset=latin-ext
*/
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
src: local('Open Sans Light'), local('OpenSans-Light'),
url('./fonts/open-sans-v15-latin_latin-ext-300.woff') format('woff');
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
src: local('Open Sans Light Italic'), local('OpenSans-LightItalic'),
url('./fonts/open-sans-v15-latin_latin-ext-300italic.woff') format('woff');
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: local('Open Sans Regular'), local('OpenSans-Regular'),
url('./fonts/open-sans-v15-latin_latin-ext-regular.woff') format('woff');
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
src: local('Open Sans Italic'), local('OpenSans-Italic'),
url('./fonts/open-sans-v15-latin_latin-ext-italic.woff') format('woff');
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'),
url('./fonts/open-sans-v15-latin_latin-ext-600.woff') format('woff');
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
src: local('Open Sans SemiBold Italic'), local('OpenSans-SemiBoldItalic'),
url('./fonts/open-sans-v15-latin_latin-ext-600italic.woff') format('woff');
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
src: local('Open Sans Bold'), local('OpenSans-Bold'),
url('./fonts/open-sans-v15-latin_latin-ext-700.woff') format('woff');
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 700;
src: local('Open Sans Bold Italic'), local('OpenSans-BoldItalic'),
url('./fonts/open-sans-v15-latin_latin-ext-700italic.woff') format('woff');
}
/*
* DejaVu Sans Mono
*/
@font-face {
font-family: "DejaVu Sans Mono";
src: local('DejaVu Sans Mono'), url('./fonts/DejaVuSansMono.ttf');
}
@font-face {
font-family: "DejaVu Sans Mono";
font-weight: bold;
src: url('./fonts/DejaVuSansMono-Bold.ttf');
}
@font-face {
font-family: "DejaVu Sans Mono";
font-style: oblique;
font-weight: bold;
src: url('./fonts/DejaVuSansMono-BoldOblique.ttf');
}
@font-face {
font-family: "DejaVu Sans Mono";
font-style: italic;
font-weight: bold;
src: url('./fonts/DejaVuSansMono-BoldOblique.ttf');
}
@font-face {
font-family: "DejaVu Sans Mono";
font-style: italic;
src: url('./fonts/DejaVuSansMono-Oblique.ttf');
}
@font-face {
font-family: "DejaVu Sans Mono";
font-style: oblique;
src: url('./fonts/DejaVuSansMono-Oblique.ttf');
}
html, body {
font-family: "Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 16px;
color: rgb(217, 217, 217);
line-height: 1.6;
background: rgb(45, 45, 45);
}
span code,
td code,
th code,
code,
code[class*="language-"],
.CodeMirror,
pre.ag-paragraph {
font-family: "DejaVu Sans Mono", "Source Code Pro", "Droid Sans Mono", Consolas, monospace;
font-size: 14px;
}
::-webkit-scrollbar {
background: rgb(43, 43, 43);
}
::-webkit-scrollbar:vertical {
width: 12px;
}
::-webkit-scrollbar-thumb {
background: rgb(55, 55, 55);
}
::-webkit-scrollbar-thumb:hover {
background: #3F3F3F;
}
#ag-editor-id {
max-width: var(--editorAreaWidth);
margin: 0 auto;
padding: 20px 50px 40px 50px;
padding-top: 20px;
padding-bottom: 100px;
}
#ag-editor-id, [contenteditable] {
outline: none;
caret-color: #efefef;
}
.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;
}
[x-placement] > .ag-popper-arrow {
border: 1px solid #606266;
border-right: none;
border-bottom: none;
}
figure.ag-active.ag-container-block > div.ag-container-preview {
background: rgb(50, 50, 50);
border: 1px solid rgb(43, 43, 43);
}
.ag-gray {
color: #909399;
text-decoration: none;
}
.el-dialog {
background: rgb(43, 43, 43);
}
.v-modal {
backdrop-filter: blur(5px);
background: rgba(0, 0, 0, .9);
}
body > *:first-child {
margin-top: 0 !important;
}
body>*:last-child {
margin-bottom: 0 !important;
}
a {
color: #4183C4;
}
h1,
h2,
h3,
h4,
h5,
h6 {
position: relative;
margin-top: 1rem;
margin-bottom: 1rem;
font-weight: bold;
line-height: 1.4;
cursor: text;
}
h1:hover a.anchor,
h2:hover a.anchor,
h3:hover a.anchor,
h4:hover a.anchor,
h5:hover a.anchor,
h6:hover a.anchor {
/*background: url("../../images/modules/styleguide/para.png") no-repeat 10px center;*/
text-decoration: none;
}
h1 tt,
h1 code {
font-size: inherit;
}
h2 tt,
h2 code {
font-size: inherit;
}
h3 tt,
h3 code {
font-size: inherit;
}
h4 tt,
h4 code {
font-size: inherit;
}
h5 tt,
h5 code {
font-size: inherit;
}
h6 tt,
h6 code {
font-size: inherit;
}
h1 {
padding-bottom: .3em;
font-size: 2.25em;
line-height: 1.2;
border-bottom: 1px solid #eee;
}
h2 {
padding-bottom: .3em;
font-size: 1.75em;
line-height: 1.225;
border-bottom: 1px solid #eee;
}
h3 {
font-size: 1.5em;
line-height: 1.43;
}
h4 {
font-size: 1.25em;
}
h5 {
font-size: 1em;
}
h6 {
font-size: 1em;
color: #777;
}
p,
blockquote,
ul,
ol,
dl,
table {
margin: 0.5em 0;
}
li>ol,
li>ul {
margin: 0 0;
}
hr {
height: 4px;
padding: 0;
margin: 16px 0;
background-color: #545454;
border: 0 none;
overflow: hidden;
box-sizing: content-box;
}
p:not(.ag-active)[data-role="hr"]::before {
background-color: #545454;
}
body>h2:first-child {
margin-top: 0;
padding-top: 0;
}
body>h1:first-child {
margin-top: 0;
padding-top: 0;
}
body>h1:first-child+h2 {
margin-top: 0;
padding-top: 0;
}
body>h3:first-child,
body>h4:first-child,
body>h5:first-child,
body>h6:first-child {
margin-top: 0;
padding-top: 0;
}
a:first-child h1,
a:first-child h2,
a:first-child h3,
a:first-child h4,
a:first-child h5,
a:first-child h6 {
margin-top: 0;
padding-top: 0;
}
h1 p,
h2 p,
h3 p,
h4 p,
h5 p,
h6 p {
margin-top: 0;
}
li p.first {
display: inline-block;
}
li.ag-tight-list-item > p {
padding: 0;
margin: 0;
}
ul,
ol {
padding-left: 30px;
}
ul:first-child,
ol:first-child {
margin-top: 0;
}
ul:last-child,
ol:last-child {
margin-bottom: 0;
}
blockquote {
border-left: 4px solid #606266;
padding: 0 15px;
color: #999999;
}
blockquote blockquote {
padding-right: 0;
}
table {
padding: 0;
word-break: initial;
}
table tr {
margin: 0;
padding: 0;
}
table thead tr,
table tr:nth-child(2n) {
background-color: #303133;
}
table tr th {
font-weight: bold;
border: 2px solid #606266;
text-align: left;
margin: 0;
padding: 6px 13px;
}
table tr td {
border: 1px solid #606266;
text-align: left;
margin: 0;
padding: 6px 13px;
}
table tr th:first-child,
table tr td:first-child {
margin-top: 0;
}
table tr th:last-child,
table tr td:last-child {
margin-bottom: 0;
}
/* custom add */
span.ag-line code,
th code,
td code {
border: none;
padding: 2px 4px;
border-radius: 3px;
font-size: 90%;
color: #efefef;
background-color: #606266;
border-radius: 4px;
/*font-family: Menlo, Monaco, Consolas, "Courier New", monospace;*/
}
@media print {
html {
font-size: 13px;
}
table,
pre {
page-break-inside: avoid;
}
pre {
word-wrap: break-word;
}
}
.ag-hide.ag-math > .ag-math-render {
color: #efefef;
}
blockquote .ag-hide.ag-math > .ag-math-render {
color: #999999;
}
.ag-gray.ag-math > .ag-math-render {
color: #333333;
background: #f6f8fa;
box-shadow: 0 2px 12px 0 rgba(255, 255, 255, 0.3);
}
.ag-gray.ag-math > .ag-math-render::before {
border-bottom-color: #f6f8fa;
}
#ag-editor-id pre.ag-html-block {
padding: 0 .5rem;
margin-top: 0;
}
#ag-editor-id pre.ag-front-matter {
background: transparent;
border-bottom: 1px dashed #efefef;
}
#ag-editor-id pre.ag-multiple-math {
background: transparent;
border: 1px solid #909399;
}
#ag-editor-id span.ag-math-text,
#ag-editor-id pre.ag-multiple-math span.ag-multiple-math-line {
color: lightsalmon;
}
#ag-editor-id div.ag-math-preview {
background: #303133;
border-color: #333;
}
#ag-editor-id pre.ag-html-block {
font-size: 90%;
line-height: 1.6;
background: var(--primaryColor);
color: #777777;
}
.ag-color-dark {
color: #c6c6c6;
}
/**
* okaidia theme for JavaScript, CSS and HTML
* Loosely based on Monokai textmate theme by http://www.monokai.nl/
* @author ocodia
*/
code[class*="language-"],
pre.ag-paragraph {
color: #f8f8f2;
/*font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;*/
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
overflow: visible;
}
/* Code blocks */
pre.ag-paragraph {
padding: 1em;
margin: 1em 0;
border-radius: 0.3em;
}
:not(pre) > code[class*="language-"],
pre.ag-paragraph {
background: #272822;
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #f8f8f2;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.constant,
.token.symbol {
color: #f92672;
}
.token.boolean,
.token.number {
color: #ae81ff;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin {
color: #a6e22e;
}
.token.inserted {
color: #22863a;
background: #f0fff4;
}
.token.deleted {
color: #b31d28;
background: #ffeef0;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string,
.token.variable {
color: #f8f8f2;
}
.token.atrule,
.token.attr-value,
.token.function,
.token.class-name {
color: #e6db74;
}
.token.keyword {
color: #66d9ef;
}
.token.regex,
.token.important {
color: #fd971f;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}

View File

@ -99,7 +99,6 @@ html, body {
font-size: 16px;
color: #303133;
line-height: 1.6;
background: rgb(252, 252, 252);
}
span code,
@ -113,19 +112,6 @@ pre.ag-paragraph {
font-size: 14px;
}
::-webkit-scrollbar {
background: rgb(252, 252, 252);
}
::-webkit-scrollbar:vertical {
width: 12px;
}
::-webkit-scrollbar-thumb {
background: #EBEEF5;
}
::-webkit-scrollbar-thumb:hover {
background: #E1E4EA;
}
#ag-editor-id {
max-width: var(--editorAreaWidth);
margin: 0 auto;
@ -136,17 +122,16 @@ pre.ag-paragraph {
#ag-editor-id, [contenteditable] {
outline: none;
caret-color: #000000;
}
.ag-gray {
color: #C0C4CC;
color: var(--editorColor30);
text-decoration: none;
}
.v-modal {
backdrop-filter: blur(5px);
background: rgba(230, 230, 230, .9);
background: var(--floatBorderColor);
}
body>*:first-child {
@ -158,7 +143,7 @@ body>*:last-child {
}
a {
color: #4183C4;
color: var(--themeColor);
}
h1,
@ -216,35 +201,27 @@ h6 code {
}
h1 {
padding-bottom: .3em;
font-size: 2.25em;
line-height: 1.2;
border-bottom: 1px solid #eee;
font-size: 40px;
}
h2 {
padding-bottom: .3em;
font-size: 1.75em;
line-height: 1.225;
border-bottom: 1px solid #eee;
font-size: 32px;
}
h3 {
font-size: 1.5em;
line-height: 1.43;
font-size: 28px;
}
h4 {
font-size: 1.25em;
font-size: 24px;
}
h5 {
font-size: 1em;
font-size: 20px;
}
h6 {
font-size: 1em;
color: #777;
font-size: 16px;
}
p,
@ -271,10 +248,6 @@ hr {
box-sizing: content-box;
}
p:not(.ag-active)[data-role="hr"]::before {
background: gainsboro;
}
body>h2:first-child {
margin-top: 0;
padding-top: 0;
@ -317,6 +290,14 @@ h6 p {
margin-top: 0;
}
li.ag-paragraph {
color: var(--themeColor);
}
li > * {
color: var(--editorColor);
}
li p.first {
display: inline-block;
}
@ -342,9 +323,20 @@ ol:last-child {
}
blockquote {
border-left: 4px solid #dddddd;
padding: 0 15px;
color: #777777;
position: relative;
padding: 0 30px;
color: var(--editorColor50);
}
blockquote::before {
content: '';
display: block;
height: 100%;
width: 2px;
position: absolute;
left: 13px;
top: 0;
background: var(--themeColor);
}
blockquote blockquote {
@ -363,19 +355,19 @@ table tr {
table thead tr,
table tr:nth-child(2n) {
background-color: #fafafa;
background-color: var(--codeBlockBgColor);
}
table tr th {
font-weight: bold;
border: 1px solid #ebeef5;
border: 1px solid var(--editorColor10);
text-align: left;
margin: 0;
padding: 6px 13px;
}
table tr td {
border: 1px solid #ebeef5;
border: 1px solid var(--editorColor10);
text-align: left;
margin: 0;
padding: 6px 13px;
@ -394,7 +386,7 @@ table tr td:last-child {
span code,
td code,
th code {
background-color: rgba(27, 31, 35, 0.05);
background-color: var(--codeBlockBgColor);
border-radius: 3px;
padding: 0;
/*font-family: Menlo, Monaco, Consolas, "Courier New", monospace;*/
@ -402,7 +394,7 @@ th code {
font-size: 85%;
margin: 0;
padding: 0.2em 0.4em;
color: #24292e;
color: var(--editorColor);
}
:not(pre) > code[class*="language-"],
@ -411,10 +403,10 @@ pre[class*="language-"] {
overflow: visible;
font-size: 90%;
line-height: 1.6;
background: #f6f8fa;
background: var(--codeBlockBgColor);
border: 0;
border-radius: 3px;
color: #777777;
color: var(--editorColor50);
}
@media print {
@ -431,20 +423,20 @@ pre[class*="language-"] {
}
.ag-hide.ag-math > .ag-math-render {
color: #333333;
color: var(--editorColor);
}
blockquote .ag-hide.ag-math > .ag-math-render {
color: #777777;
color: var(--editorColor50);
}
.ag-gray.ag-math > .ag-math-render {
color: #f6f8fa;
background: #333333;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.3);
color: var(--editorColor);
background: var(--floatBgColor);
box-shadow: 0 4px 8px 0 var(--floatBorderColor);
}
.ag-gray.ag-math > .ag-math-render::before {
border-bottom-color: #333333;
border-bottom-color: var(--floatBgColor);
}
#ag-editor-id pre.ag-html-block {
@ -454,15 +446,7 @@ blockquote .ag-hide.ag-math > .ag-math-render {
}
#ag-editor-id pre.ag-active.ag-html-block {
background: #f6f8fa;
}
p:not(.ag-active)[data-role="hr"]::before {
background: gainsboro;
}
.fg-color-dark {
color: #303133;
background: var(--codeBlockBgColor);
}
/* prismjs default theme */

View File

@ -2,18 +2,17 @@
<div
class="editor-container"
>
<title-bar
:project="projectTree"
:pathname="pathname"
:filename="filename"
:active="windowActive"
:word-count="wordCount"
:theme="theme"
:platform="platform"
:is-saved="isSaved"
></title-bar>
<side-bar></side-bar>
<div class="editor-middle">
<side-bar></side-bar>
<title-bar
:project="projectTree"
:pathname="pathname"
:filename="filename"
:active="windowActive"
:word-count="wordCount"
:platform="platform"
:is-saved="isSaved"
></title-bar>
<recent
v-if="!hasCurrentFile && init"
></recent>
@ -22,34 +21,28 @@
:markdown="markdown"
:filename="filename"
:cursor="cursor"
:theme="theme"
:source-code="sourceCode"
:show-tab-bar="showTabBar"
:text-direction="textDirection"
></editor-with-tabs>
<aidou></aidou>
<upload-image></upload-image>
<about-dialog></about-dialog>
<font></font>
<rename></rename>
<tweet></tweet>
<import-modal></import-modal>
</div>
<bottom-bar
:source-code="sourceCode"
:theme="theme"
></bottom-bar>
<aidou></aidou>
<upload-image></upload-image>
<about-dialog></about-dialog>
<font></font>
<rename></rename>
<tweet></tweet>
<import-modal></import-modal>
</div>
</template>
<script>
import { remote } from 'electron'
import { addStyles } from '@/util/theme'
import { addStyles, addThemeStyle } from '@/util/theme'
import Recent from '@/components/recent'
import EditorWithTabs from '@/components/editorWithTabs'
import TitleBar from '@/components/titleBar'
import SideBar from '@/components/sideBar'
import BottomBar from '@/components/bottomBar'
import Aidou from '@/components/aidou/aidou'
import UploadImage from '@/components/uploadImage'
import AboutDialog from '@/components/about'
@ -69,7 +62,6 @@
EditorWithTabs,
TitleBar,
SideBar,
BottomBar,
UploadImage,
AboutDialog,
Font,
@ -104,6 +96,13 @@
return this.markdown !== undefined
}
},
watch: {
theme: function (value, oldValue) {
if (value !== oldValue) {
addThemeStyle(value)
}
}
},
created () {
const { dispatch } = this.$store
// store/index.js
@ -182,7 +181,15 @@
<style scoped>
.editor-container {
padding-top: var(--titleBarHeight);
display: flex;
flex-direction: row;
position: absolute;
width: 100vw;
height: 100vh;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.editor-container .hide {
z-index: -1;
@ -192,7 +199,9 @@
}
.editor-middle {
display: flex;
min-height: calc(100vh - var(--titleBarHeight));
flex: 1;
min-height: 100vh;
position: relative;
& > .editor {
flex: 1;
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 48 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 34 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 19 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -1,19 +1,62 @@
/* Common CSS use by both light and dark themes */
:root {
--primary: #409eff;
--info: #909399;
--warning: rgb(255, 130, 0);
--error: rgb(242, 19, 93);
--lightBarColor: rgb(245, 245, 245);
--lightTabColor: rgb(243, 243, 243);
--darkBgColor: rgb(45, 45, 45);
--darkInputBgColor: rgb(54, 55, 49);
--darkInputColor: rgb(255, 255, 255);
--darkHoverColor: rgb(70, 70, 70);
--titleBarHeight: 25px;
--titleBarHeight: 32px;
--editorAreaWidth: 700px;
--lightBgHighlightColor: rgb(246, 248, 249);
--darkBgHighlightColor: rgb(40, 40, 40);
/*editor*/
--themeColor: rgba(33, 181, 111, 1);
--highlightColor: rgba(33, 181, 111, .7);
--selectionColor: rgba(33, 181, 111, .4);
--editorColor: rgba(0, 0, 0, .8);
--editorColor50: rgba(0, 0, 0, .5);
--editorColor30: rgba(0, 0, 0, .3);
--editorColor10: rgba(0, 0, 0, .1);
--editorBgColor: rgba(255, 255, 255, 1);
--deleteColor: #ff6969;
--iconColor: #333;
--codeBgColor: #d8d8d869;
--codeBlockBgColor: rgba(33, 181, 111, 0.08);
/*marktext*/
--sideBarColor: rgba(0, 0, 0, .6);
--sideBarTitleColor: rgba(0, 0, 0, 1);
--sideBarTextColor: rgba(0, 0, 0, .4);
--sideBarBgColor: rgba(242, 242, 242, 0.9);
--sideBarItemHoverBgColor: rgba(0, 0, 0, .03);
--itemBgColor: rgba(255, 255, 255, 0.6);
--floatBgColor: #fff;
--floatHoverColor: rgba(0, 0, 0, .04);
--floatBorderColor: rgba(0, 0, 0, .1);
}
::-webkit-scrollbar {
background: var(--floatHoverColor);
}
::-webkit-scrollbar:vertical {
width: 12px;
}
::-webkit-scrollbar-thumb {
background: var(--editorColor10);
}
::-webkit-scrollbar-thumb:hover {
background: var(--editorColor30);
}
html, body {
margin: 0;
padding: 0;
background: transparent !important;
caret-color: var(--editorColor);
}
body {
position: fixed;
width: 100vw;
height: 100vh;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.icon {
width: 1em; height: 1em;
vertical-align: -0.15em;
@ -26,3 +69,35 @@
overflow: hidden;
white-space: nowrap;
}
/* cover Element style*/
.el-tooltip__popper.is-dark {
background: var(--floatBgColor);
color: var(--sideBarColor);
box-shadow: 0 4px 8px 0 var(--floatBorderColor);
border: solid 1px var(--floatBorderColor);
}
.el-tooltip__popper.is-dark .popper__arrow {
display: none;
}
.el-slider__button {
border-color: var(--themeColor);
}
.el-slider__bar {
background-color: var(--themeColor);
}
.ag-dialog-table {
border-radius: 8px;
box-shadow: 0 4px 8px 0 var(--floatBorderColor);
border: solid 1px var(--floatBorderColor);
background-color: var(--floatBgColor);
}
.ag-dialog-table .dialog-title svg {
width: 1.5em;
height: 1.5em;
}

View File

@ -1,5 +1,5 @@
<template>
<div class="about-dialog" :class="theme">
<div class="about-dialog">
<el-dialog
:visible.sync="showAboutDialog"
:show-close="false"
@ -10,7 +10,7 @@
<img class="logo" src="../../assets/images/logo.png" />
<el-row>
<el-col :span="24">
<h3 class="text fg-color-dark">{{ name }}</h3>
<h3 class="title">{{ name }}</h3>
</el-col>
<el-col :span="24">
<div class="text">{{ appVersion }}</div>
@ -37,8 +37,7 @@
},
computed: {
...mapState({
'appVersion': state => state.appVersion,
'theme': state => state.preferences.theme
'appVersion': state => state.appVersion
})
},
created () {
@ -62,15 +61,24 @@
display: block;
}
.about-dialog .logo {
width: 100px;
height: 100px;
.about-dialog img.logo {
width: 80px;
height: 80px;
display: inherit;
margin: 0 auto;
}
.about-dialog .title,
.about-dialog .text {
text-align: center;
min-height: 32px;
text-align: center;
}
.about-dialog .title {
color: var(--sideBarTitleColor);
}
.about-dialog .text {
color: var(--sideBarTextColor);
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<div class="aidou" :class="theme">
<div class="aidou">
<el-dialog
:visible.sync="showAiDou"
:show-close="false"
@ -105,9 +105,6 @@
'aiList': state => state.aidou.aiList,
'aiLoading': state => state.aidou.aiLoading
}),
...mapState({
'theme': state => state.preferences.theme
}),
emojis () {
return this.aiList.map(e => {
e.collected = this.collection.findIndex(c => c.link === e.link) > -1
@ -253,14 +250,17 @@
align-items: center;
height: auto;
padding: 5px;
background: #fff;
box-shadow: 0 3px 8px rgba(0, 0, 0, .1);
border: 1px solid #eeeeee;
color: var(--editorColor);
background: var(--floatBgColor);
box-shadow: 0 3px 8px 3px var(--floatHoverColor);
border: 1px solid var(--floatBorderColor);
border-radius: 3px;
}
.input-wrapper {
display: flex;
width: 100%;
border: 1px solid var(--sideBarTextColor);
border-radius: 14px;
}
.search {
width: 100%;
@ -270,18 +270,19 @@
font-size: 14px;
padding: 0 8px;
margin: 0 10px;
color: #606266;
color: var(--editorColor);
background: transparent;
}
.search-wrapper svg {
cursor: pointer;
margin: 0 5px;
width: 30px;
height: 30px;
color: #606266;
color: var(--iconColor);
transition: all .3s ease-in-out;
}
.search-wrapper svg:hover {
color: var(--activeColor);
color: var(--themeColor);
}
ul.history {
display: flex;
@ -312,12 +313,12 @@
}
ul.history .clear-history span {
font-size: 12px;
color: #C0C4CC;
color: var(--themeColor);
text-align: center;
cursor: pointer;
}
ul.history li.active {
background: #EBEEF5;
background: var(--floatHoverColor);
}
ul.history:hover li {
background: transparent;
@ -326,7 +327,7 @@
display: block;
}
ul.history li:hover {
background: #EBEEF5;
background: var(--floatHoverColor);
}
.image-container {
height: 410px;
@ -358,7 +359,7 @@
display: none;
}
.image-container .img-wrapper > svg.active {
color: var(--activeColor);
color: var(--themeColor);
}
.image-container .img-wrapper:hover > svg {
display: block;
@ -378,20 +379,4 @@
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
/* style for dark theme */
.dark .search-wrapper {
background: var(--darkInputBgColor);
border-color: transparent;
& input {
background: transparent;
color: var(--darkInputColor);
}
}
.dark ul.history li.active {
background: rgb(39, 39, 39);
}
.dark ul.history li:hover {
background: rgb(39, 39, 39);
}
</style>

View File

@ -50,15 +50,15 @@ export default {
animation: 3s infinite linear;
}
.loader span:nth-child(1) {
background: #F2F6FC;
background: var(--themeColor);
animation: kiri 1.2s infinite linear;
}
.loader span:nth-child(2) {
z-index: 100;
background: #EBEEF5;
background: var(--highlightColor);
}
.loader span:nth-child(3) {
background: #C0C4CC;
background: var(--selectionColor);
animation: kanan 1.2s infinite linear;
}

View File

@ -1,36 +0,0 @@
<template>
<div class="bottom-bar" :class="theme">
<search
v-if="!sourceCode"
:theme="theme"
></search>
</div>
</template>
<script>
import Search from './search'
export default {
components: {
Search
},
props: {
sourceCode: Boolean,
theme: String
}
}
</script>
<style scoped>
.bottom-bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
background: rgb(245, 245, 245);
}
/* dark theme */
.dark {
background: rgb(43, 43, 43);
}
</style>

View File

@ -1,8 +1,8 @@
<template>
<div
class="editor-wrapper"
:class="[{ 'typewriter': typewriter, 'focus': focus, 'source': sourceCode }, theme]"
:style="{ 'color': theme === 'dark' ? darkColor : lightColor, 'lineHeight': lineHeight, 'fontSize': fontSize,
:class="[{ 'typewriter': typewriter, 'focus': focus, 'source': sourceCode }]"
:style="{ 'lineHeight': lineHeight, 'fontSize': fontSize,
'font-family': editorFontFamily ? `${editorFontFamily}, ${defaultFontFamily}` : `${defaultFontFamily}` }"
:dir="textDirection"
>
@ -20,9 +20,7 @@
dir='ltr'
>
<div slot="title" class="dialog-title">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-table-3d"></use>
</svg>
Insert Table
</div>
<el-form :model="tableChecker" :inline="true">
<el-form-item label="Rows">
@ -47,17 +45,16 @@
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogTableVisible = false" size="mini">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-close"></use>
</svg>
Cancel
</el-button>
<el-button type="primary" @click="handleDialogTableConfirm" size="mini">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-gou"></use>
</svg>
Ok
</el-button>
</div>
</el-dialog>
<search
v-if="!sourceCode"
></search>
</div>
</template>
@ -71,23 +68,24 @@
import ImagePathPicker from 'muya/lib/ui/imagePicker'
import FormatPicker from 'muya/lib/ui/formatPicker'
import bus from '../../bus'
import Search from '../search.vue'
import { animatedScrollTo } from '../../util'
import { showContextMenu } from '../../contextMenu/editor'
import Printer from '@/services/printService'
import { DEFAULT_EDITOR_FONT_FAMILY } from '@/config'
import { addThemeStyle } from '@/util/theme'
import 'muya/themes/light.css'
const STANDAR_Y = 320
export default {
components: {
Search
},
props: {
filename: {
type: String
},
theme: {
type: String,
required: true
},
markdown: String,
cursor: Object,
textDirection: {
@ -138,13 +136,6 @@
focus: function (value) {
this.editor.setFocusMode(value)
},
theme: function (value, oldValue) {
const { editor } = this
if (value !== oldValue && editor) {
editor.setTheme(value)
addThemeStyle(value)
}
},
fontSize: function (value, oldValue) {
const { editor } = this
if (value !== oldValue && editor) {
@ -175,7 +166,6 @@
this.printer = new Printer()
const ele = this.$refs.editor
const {
theme,
focus: focusMode,
markdown,
preferLooseListItem,
@ -196,7 +186,6 @@
Muya.use(ImagePathPicker)
Muya.use(FormatPicker)
const { container } = this.editor = new Muya(ele, {
theme,
focusMode,
markdown,
preferLooseListItem,
@ -212,9 +201,6 @@
this.scrollToCursor()
}
// the default theme is light write in the store
addThemeStyle(theme)
// listen for bus events.
bus.$on('file-loaded', this.setMarkdownToEditor)
bus.$on('undo', this.handleUndo)
@ -336,7 +322,7 @@
},
scrollToHeader (slug) {
return this.scrollToElement(`[data-id="${slug}"]`)
return this.scrollToElement(`#${slug}`)
},
scrollToElement (selector) {
@ -445,7 +431,6 @@
this.editor.copy(name)
}
},
beforeDestroy () {
bus.$off('file-loaded', this.setMarkdownToEditor)
bus.$off('undo', this.handleUndo)
@ -479,6 +464,31 @@
height: 100%;
position: relative;
flex: 1;
color: var(--editorColor);
& .ag-dialog-table {
& .el-button {
width: 70px;
}
& .el-button:focus,
& .el-button:hover {
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 {
position: absolute;
@ -496,26 +506,4 @@
padding-top: calc(50vh - 136px);
padding-bottom: calc(50vh - 54px);
}
/* for dark theme */
.dark.editor-wrapper,
.dark.editor-wrapper #ag-editor-id {
background: var(--darkBgColor);
}
</style>
<style>
.ag-dialog-table {
border-radius: 5px;
box-shadow: 0 1px 3px rgba(230, 230, 230, .3);
}
.dark .ag-dialog-table {
box-shadow: 0 1px 3px rgba(0, 0, 0, .3);
}
.ag-dialog-table .dialog-title svg {
width: 1.5em;
height: 1.5em;
}
</style>

View File

@ -5,7 +5,6 @@
<tabs v-show="showTabBar"></tabs>
<div class="container">
<editor
:theme="theme"
:fileanme="filename"
:markdown="markdown"
:cursor="cursor"
@ -13,7 +12,6 @@
></editor>
<source-code
v-if="sourceCode"
:theme="theme"
:markdown="markdown"
:cursor="cursor"
:text-direction="textDirection"
@ -29,10 +27,6 @@
export default {
props: {
theme: {
type: String,
required: true
},
filename: {
type: String
},
@ -69,11 +63,15 @@
<style scoped>
.editor-with-tabs {
width: 100%;
height: 100%;
padding-top: var(--titleBarHeight);
flex: 1;
display: flex;
flex-direction: column;
height: calc(100vh - var(--titleBarHeight));
overflow: hidden;
background: var(--editorBgColor);
& > .container {
flex: 1;
overflow: hidden;

View File

@ -1,7 +1,6 @@
<template>
<div
class="source-code"
:class="[theme]"
ref="sourceCode"
>
</div>
@ -10,15 +9,13 @@
<script>
import codeMirror, { setMode, setCursorAtLastLine, setTextDirection } from '../../codeMirror'
import { wordCount as getWordCount } from 'muya/lib/utils'
import { mapState } from 'vuex'
import { adjustCursor } from '../../util'
import bus from '../../bus'
import { railscastsThemes } from '@/config'
export default {
props: {
theme: {
type: String,
required: true
},
markdown: String,
cursor: Object,
textDirection: {
@ -27,6 +24,12 @@
}
},
computed: {
...mapState({
'theme': state => state.preferences.theme
})
},
data () {
return {
contentState: null,
@ -35,18 +38,6 @@
},
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')
}
}
},
textDirection: function (value, oldValue) {
const { editor } = this
if (value !== oldValue && editor) {
@ -74,7 +65,9 @@
}
}
}
if (theme === 'dark') codeMirrorConfig.theme = 'railscasts'
if (railscastsThemes.includes(theme)) {
codeMirrorConfig.theme = 'railscasts'
}
const editor = this.editor = codeMirror(container, codeMirrorConfig)
bus.$on('file-loaded', this.setMarkdown)
bus.$on('file-changed', this.handleMarkdownChange)
@ -147,6 +140,7 @@
.source-code .CodeMirror {
margin: 50px auto;
max-width: var(--editorAreaWidth);
background: transparent;
}
.source-code .CodeMirror-gutters {
border-right: none;
@ -154,14 +148,6 @@
}
.source-code .CodeMirror-activeline-background,
.source-code .CodeMirror-activeline-gutter {
background: #F2F6FC;
}
.source-code.dark,
.source-code.dark .CodeMirror {
background: var(--darkBgColor);
}
.dark.source-code .CodeMirror-activeline-background,
.dark.source-code .CodeMirror-activeline-gutter {
background: #333;
background: var(--floatHoverColor);
}
</style>

View File

@ -1,7 +1,6 @@
<template>
<div
class="editor-tabs"
:class="theme"
>
<ul class="tabs-container">
<li
@ -37,7 +36,6 @@
mixins: [tabsMixins],
computed: {
...mapState({
theme: state => state.preferences.theme,
currentFile: state => state.editor.currentFile,
tabs: state => state.editor.tabs
})
@ -54,7 +52,6 @@
.editor-tabs {
width: 100%;
height: 35px;
background: var(--lightBarColor);
user-select: none;
}
.tabs-container {
@ -70,21 +67,13 @@
& > li {
position: relative;
padding: 0 8px;
color: var(--secondaryColor);
color: var(--editorColor50);
font-size: 12px;
line-height: 35px;
height: 35px;
background: var(--lightTabColor);
background: var(--floatBgColor);
display: flex;
align-items: center;
&:not(:last-child):before {
content: '';
position: absolute;
top: 20%;
right: 0;
border-right: 1px solid #fff;
height: 60%;
}
& > svg {
opacity: 0;
}
@ -99,7 +88,7 @@
}
}
& > li.active {
background: #fff;
background: var(--itemBgColor);
&:not(:last-child):after {
content: '';
position: absolute;
@ -107,7 +96,7 @@
bottom: 0;
right: 0;
height: 2px;
background: var(--primary);
background: var(--themeColor);
}
& > svg {
opacity: 1;
@ -125,16 +114,4 @@
cursor: pointer;
}
}
.editor-tabs.dark {
background: var(--darkBgColor);
}
.editor-tabs.dark ul li {
background: var(--darkBgColor);
&:not(:last-child):before {
border-right-color: var(--darkHoverColor);
}
&.active {
color: var(--lightBorder);
}
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<div class="font" :class="theme">
<div class="font">
<el-dialog
:visible.sync="showFontSetting"
:show-close="false"
@ -56,7 +56,6 @@
},
computed: {
...mapState({
'theme': state => state.preferences.theme,
'fontSize': state => state.preferences.fontSize,
'lightColor': state => state.preferences.lightColor,
'darkColor': state => state.preferences.darkColor,

View File

@ -81,19 +81,20 @@ export default {
<style scoped>
.drop-container {
border-radius: 5px;
border: 2px dashed #9c9c9c;
color: var(--sideBarColor);
border: 1px dashed var(--sideBarTextColor);
& div,
& p {
text-align: center;
}
&.active {
border: 2px dashed #409eff;
background-color: rgba(32, 159, 255, 0.06);
border: 1px dashed var(--themeColor);
background-color: var(--itemBgColor);
}
}
.img-wrapper {
width: 70px;
height: 100px;
width: 50px;
height: 70px;
margin: 40px auto 0 auto;
& img {
width: 100%;
@ -107,12 +108,12 @@ export default {
& div {
width: 70px;
height: 70px;
border: 1px solid #eee;
border: 1px solid var(--sideBarTextColor);
border-radius: 3px;
text-align: center;
font-size: 18px;
line-height: 70px;
color: #999;
color: var(--sideBarTitleColor);
}
}
</style>

View File

@ -1,20 +1,25 @@
<template>
<div
class="recent-files-projects"
:class="theme"
@click="newFile"
>
<div class="button-group">
<svg :viewBox="ContentIcon.viewBox" aria-hidden="true">
<use :xlink:href="ContentIcon.url" />
</svg>
<a href="javascript:;" @click="newFile">
New File
</a>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
import ContentIcon from '@/assets/icons/undraw_content.svg'
export default {
computed: {
...mapState({
'theme': state => state.preferences.theme
})
data () {
this.ContentIcon = ContentIcon
return {}
},
methods: {
newFile () {
@ -26,12 +31,32 @@
<style scoped>
.recent-files-projects {
background: var(--editorBgColor);
flex: 1;
}
.dark.recent-files-projects {
background: var(--darkBgColor);
& > div {
color: var(--baseBorder);
display: flex;
align-items: center;
justify-content: space-around;
& .button-group {
display: flex;
flex-direction: column;
align-items: center;
& svg {
width: 200px;
fill: var(--themeColor);
}
& a {
text-decoration: none;
background: var(--themeColor);
box-shadow: 0 0 8px 0 var(--selectionColor);
display: block;
padding: 4px 10px;
border-radius: 5px;
margin-top: 20px;
color: #fff;
&:active {
opacity: .5;
}
}
}
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<div class="rename" :class="theme">
<div class="rename">
<el-dialog
:visible.sync="showRename"
:show-close="false"
@ -44,8 +44,7 @@
},
computed: {
...mapState({
filename: state => state.editor.currentFile.filename,
theme: state => state.preferences.theme
filename: state => state.editor.currentFile.filename
})
},
methods: {
@ -63,6 +62,10 @@
</script>
<style>
.rename .el-dialog__header {
height: 42px;
box-sizing: border-box;
}
.rename .el-dialog__body {
display: none;
}
@ -84,13 +87,16 @@
align-items: center;
height: auto;
padding: 5px;
background: #fff;
box-shadow: 0 3px 8px rgba(0, 0, 0, .1);
border: 1px solid #eeeeee;
background: var(--floatBorderColor);
box-shadow: 0 3px 8px var(--floatBorderColor);
border: 1px solid var(--floatBorderColor);
border-radius: 3px;
& .input-wrapper {
display: flex;
width: 100%;
& input {
background: transparent;
}
}
}
.search {
@ -101,28 +107,17 @@
font-size: 14px;
padding: 0 8px;
margin: 0 10px;
color: #606266;
color: var(--sideBarColor);
}
.search-wrapper svg {
cursor: pointer;
margin: 0 5px;
width: 30px;
height: 30px;
color: #606266;
color: var(--iconColor);
transition: all .3s ease-in-out;
}
.search-wrapper svg:hover {
color: var(--activeColor);
color: var(--themeColor);
}
/* style for dark theme */
.dark .search-wrapper {
background: var(--darkInputBgColor);
border-color: transparent;
& input {
background: transparent;
color: var(--darkInputColor);
}
}
</style>

View File

@ -1,6 +1,5 @@
<template>
<div class="search-bar"
:class="theme"
@click.stop="noop"
v-show="showSearch"
>
@ -29,7 +28,7 @@
:visible-arrow="false"
:open-delay="1000"
>
<button class="button" @click="caseClick" :class="{ 'active': caseSensitive }">
<button class="button left" @click="caseClick" :class="{ 'active': caseSensitive }">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-case"></use>
</svg>
@ -46,19 +45,19 @@
>
<span class="search-result">{{`${highlightIndex + 1} / ${highlightCount}`}}</span>
</div>
<button class="button" @click="find('prev')">
<button class="button right" @click="find('prev')">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-arrow-up"></use>
</svg>
</button>
<button class="button" @click="find('next')">
<button class="button right" @click="find('next')">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-arrowdown"></use>
</svg>
</button>
</section>
<section class="replace" v-if="type === 'replace'">
<button class="button active" @click="typeClick">
<button class="button active left" @click="typeClick">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-findreplace"></use>
</svg>
@ -73,7 +72,7 @@
:visible-arrow="false"
:open-delay="1000"
>
<button class="button" @click="replace(false)">
<button class="button right" @click="replace(false)">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-all-inclusive"></use>
</svg>
@ -86,7 +85,7 @@
:visible-arrow="false"
:open-delay="1000"
>
<button class="button" @click="replace(true)">
<button class="button right" @click="replace(true)">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-replace"></use>
</svg>
@ -101,9 +100,6 @@
import { mapState } from 'vuex'
export default {
props: {
theme: String
},
data () {
return {
showSearch: false,
@ -219,8 +215,15 @@
<style scoped>
.search-bar {
width: 100%;
position: absolute;
width: 400px;
padding: 5px;
top: 0;
right: 20px;
box-shadow: 0 4px 8px 0 var(--floatBorderColor);
border: 1px solid var(--floatBorderColor);
border-radius: 5px;
background: var(--floatBgColor);
}
.search {
margin-bottom: 5px;
@ -228,6 +231,7 @@
.search, .replace {
height: 30px;
display: flex;
padding: 0 10px;
}
.search-bar .button {
outline: none;
@ -236,65 +240,55 @@
background: transparent;
box-sizing: border-box;
height: 30px;
width: 50px;
width: 30px;
text-align: center;
padding: 3px 5px;
padding: 5px;
display: inline-block;
margin-right: 5px;
font-weight: 500;
color: #606266;
color: var(--iconColor);
&.left {
margin-right: 10px;
}
&.right {
margin-left: 10px;
}
}
.button.active {
color: rgb(242, 134, 94);
color: var(--themeColor);
}
.search-bar .button > svg {
width: 1.6em;
height: 1.6em;
}
.search-bar .button:hover {
background: #EBEEF5;
width: 20px;
height: 20px;
}
.search-bar .button:active {
background: #DCDFE6;
opacity: .5;
}
.input-wrapper {
display: flex;
flex: 1;
position: relative;
margin-right: 5px;
background: var(--floatHoverColor);
border-radius: 4px;
overflow: hidden;
}
.input-wrapper .search-result {
position: absolute;
top: 6px;
right: 5px;
right: 10px;
font-size: 12px;
color: #C0C4CC;
color: var(--sideBarTitleColor);
}
.input-wrapper input {
flex: 1;
padding: 0 8px;
height: 30px;
outline: none;
border: none;
box-sizing: border-box;
font-size: 14px;
color: #606266;
color: var(--editorColor);
padding: 0 8px;
background: rgb(252, 252, 252);
}
/* css for dark theme*/
.dark {
caret-color: #efefef;
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;
background: transparent;
}
</style>

View File

@ -4,7 +4,7 @@
class="side-bar-file"
:style="{'padding-left': `${depth * 5 + 15}px`, 'opacity': file.isMarkdown ? 1 : 0.75 }"
@click="handleFileClick()"
:class="[{'current': currentFile.pathname === file.pathname, 'active': file.id === activeItem.id }, theme]"
:class="[{'current': currentFile.pathname === file.pathname, 'active': file.id === activeItem.id }]"
ref="file"
>
<file-icon
@ -53,7 +53,6 @@
},
computed: {
...mapState({
'theme': state => state.preferences.theme,
'renameCache': state => state.project.renameCache,
'activeItem': state => state.project.activeItem,
'clipboard': state => state.project.clipboard,
@ -103,7 +102,7 @@
box-sizing: border-box;
padding-right: 15px;
&:hover {
background: var(--extraLightBorder);
background: var(--sideBarItemHoverBgColor);
}
& > span {
overflow: hidden;
@ -115,7 +114,7 @@
position: absolute;
display: block;
left: 0;
background: var(--activeColor);
background: var(--themeColor);
width: 2px;
height: 0;
top: 50%;
@ -126,19 +125,21 @@
.side-bar-file.current::before {
height: 100%;
}
.side-bar-file.active {
background: var(--lightBorder);
.side-bar-file.current > span {
color: var(--themeColor);
}
.side-bar-file.active > span {
color: var(--sideBarTitleColor);
}
input.rename {
height: 22px;
outline: none;
margin: 5px 0;
border: 1px solid var(--lightBorder);
padding: 0 8px;
color: var(--sideBarColor);
border: 1px solid var(--floatBorderColor);
background: var(--floatBorderColor);
width: 100%;
border-radius: 3px;
}
.dark.side-bar-file:hover {
background: var(--darkHoverColor);
color: var(--lightTabColor);
}
</style>

View File

@ -1,7 +1,6 @@
<template>
<div
class="side-bar-folder"
:class="theme"
>
<div
class="folder-name" @click="folderNameClick"
@ -80,7 +79,6 @@
},
computed: {
...mapState({
'theme': state => state.preferences.theme,
'renameCache': state => state.project.renameCache,
'createCache': state => state.project.createCache,
'activeItem': state => state.project.activeItem,
@ -132,11 +130,11 @@
padding-right: 15px;
& > svg {
flex-shrink: 0;
color: darkgray;
color: var(--iconColor);
margin-right: 5px;
}
&:hover {
background: var(--extraLightBorder);
background: var(--sideBarItemHoverBgColor);
}
}
}
@ -144,12 +142,10 @@
outline: none;
height: 22px;
margin: 5px 0;
border: 1px solid var(--lightBorder);
width: 100%;
padding: 0 8px;
border: 1px solid var(--floatBorderColor);
background: var(--floatBorderColor);
width: 70%;
border-radius: 3px;
}
.dark.side-bar-folder > .folder-name:hover {
background-color: var(--darkHoverColor);
color: var(--lightTabColor);
}
</style>

View File

@ -2,11 +2,10 @@
<div
v-show="showSideBar"
class="side-bar"
:class="[theme]"
ref="sideBar"
:style="{ 'width': `${finalSideBarWidth}px` }"
>
<div class="title-bar-bg" :class="[theme]"></div>
<div class="title-bar-bg"></div>
<div class="left-column">
<ul>
<li
@ -75,7 +74,6 @@
},
computed: {
...mapState({
'theme': state => state.preferences.theme,
'rightColumn': state => state.layout.rightColumn,
'showSideBar': state => state.layout.showSideBar,
'projectTree': state => state.project.projectTree,
@ -143,26 +141,13 @@
<style scoped>
.side-bar {
display: flex;
height: calc(100vh - var(--titleBarHeight));
height: 100vh;
position: relative;
color: var(--secondaryColor);
color: var(--sideBarColor);
}
.side-bar.light,
.title-bar-bg.light {
background: var(--lightBgHighlightColor);
border-right: 1px solid rgb(242, 242, 242);
}
.side-bar.dark,
.title-bar-bg.dark {
background: var(--darkBgHighlightColor);
}
.title-bar-bg {
position: absolute;
top: calc(-1 * var(--titleBarHeight));
left: 0;
height: var(--titleBarHeight);
width: 100%;
.side-bar {
background: var(--sideBarBgColor);
border-right: 1px solid var(--itemBgColor);
}
.left-column {
@ -171,6 +156,8 @@
display: flex;
flex-direction: column;
justify-content: space-between;
padding-top: 40px;
box-sizing: border-box;
& > ul {
opacity: 1;
}
@ -195,15 +182,14 @@
width: 18px;
height: 18px;
opacity: 1;
color: var(--secondaryColor);
color: var(--iconColor);
transition: transform .25s ease-in-out;
}
&:hover > svg {
color: var(--primary);
transform: scale(1.2);
color: var(--themeColor);
}
&.active > svg {
color: var(--primary);
color: var(--themeColor);
}
}
}
@ -222,10 +208,10 @@
right: 0;
bottom: 0;
height: 100%;
width: 2px;
width: 8px;
cursor: col-resize;
&:hover {
background: var(--secondaryColor);
border-right: 2px solid var(--sideBarTextColor);
}
}
</style>

View File

@ -2,15 +2,14 @@
<div
class="side-bar-list-file"
@click="handleFileClick"
:class="[{ 'active': file.pathname === currentFile.pathname }, theme]"
:class="[{ 'active': file.pathname === currentFile.pathname }]"
>
<div class="title">
<span class="filename">{{ filename }}</span>
<span>{{ extension }}</span>
<span class="filename">{{ filename + extension }}</span>
<span class="birth-time">{{ relativeTime }}</span>
</div>
<div class="folder-date">
<span class="folder">{{parent}}</span>
<span class="birth-time">{{ new Date(file.birthTime).toLocaleString().split(/\s/)[0] }}</span>
</div>
<div class="content">
{{ file.data.markdown.substring(0, 50) }}
@ -23,6 +22,7 @@
import { mapState } from 'vuex'
import { fileMixins } from '../../mixins'
import { PATH_SEPARATOR } from '../../config'
import dayjs from '@/util/day'
export default {
mixins: [fileMixins],
@ -34,7 +34,6 @@
},
computed: {
...mapState({
theme: state => state.preferences.theme,
tabs: state => state.editor.tabs,
currentFile: state => state.editor.currentFile
}),
@ -44,6 +43,10 @@
return path.basename(this.file.name, path.extname(this.file.name))
},
relativeTime () {
return dayjs(+new Date(this.file.birthTime)).fromNow()
},
// Return the filename extension or null.
extension () {
return path.extname(this.file.name)
@ -63,20 +66,20 @@
position: relative;
user-select: none;
padding: 10px 20px;
color: var(--secondaryColor);
color: var(--sideBarColor);
font-size: 14px;
& .title .filename {
font-size: 15px;
}
&:hover {
background: var(--extraLightBorder);
background: var(--sideBarItemHoverBgColor);
}
&::before {
content: '';
position: absolute;
display: block;
left: 0;
background: var(--activeColor);
background: var(--themeColor);
width: 2px;
height: 0;
top: 50%;
@ -86,28 +89,33 @@
}
.side-bar-list-file.active {
font-weight: 600;
color: var(--regularColor);
.title {
color: var(--themeColor);
}
}
.side-bar-list-file.active::before {
height: 100%;
}
.title {
display: flex;
color: var(--sideBarTitleColor);
& .filename {
flex: 1;
}
& .birth-time {
color: var(--sideBarTextColor);
}
}
.folder-date {
margin-top: 5px;
display: flex;
justify-content: space-between;
}
.folder-date .folder,
.content {
width: 100%;
margin-top: 5px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.dark.side-bar-list-file.active {
color: var(--lightTabColor);
}
.dark.side-bar-list-file:hover {
background: var(--darkHoverColor);
color: var(--lightTabColor);
color: var(--sideBarTextColor);
}
</style>

View File

@ -3,7 +3,7 @@
class="opened-file"
:title="file.pathname"
@click="selectFile(file)"
:class="[{'active': currentFile.id === file.id, 'unsaved': !file.isSaved }, theme]"
:class="[{'active': currentFile.id === file.id, 'unsaved': !file.isSaved }]"
>
<svg class="icon" aria-hidden="true"
@click.stop="removeFileInTab(file)"
@ -28,7 +28,6 @@
},
computed: {
...mapState({
'theme': state => state.preferences.theme,
'currentFile': state => state.editor.currentFile
})
}
@ -43,7 +42,7 @@
line-height: 28px;
padding-left: 30px;
position: relative;
color: var(--regularColor);
color: var(--sideBarColor);
& > svg {
display: none;
width: 10px;
@ -56,7 +55,7 @@
display: inline-block;
}
&:hover {
background: var(--extraLightBorder);
background: var(--sideBarItemHoverBgColor);
}
& > span {
overflow: hidden;
@ -65,14 +64,14 @@
}
}
.opened-file.active {
color: var(--primary);
color: var(--themeColor);
}
.unsaved.opened-file::before {
content: '';
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--warning);
background: var(--themeColor);
position: absolute;
top: 11px;
left: 12px;
@ -80,8 +79,4 @@
.unsaved.opened-file:hover::before {
content: none;
}
.dark.opened-file:hover {
background: var(--darkHoverColor);
color: var(--lightTabColor);
}
</style>

View File

@ -1,11 +1,11 @@
<template>
<div class="side-bar-search"
:class="theme"
<div
class="side-bar-search"
>
<div class="search-wrapper">
<input
type="text" v-model="keyword"
placeholder="Search in project..."
placeholder="Search in folder..."
@keyup="search"
>
<svg class="icon" aria-hidden="true">
@ -20,17 +20,23 @@
></list-file>
</div>
<div class="empty" v-else>
<span>{{ !keyword ? 'Input search keyword' : 'No matched files' }}</span>
<div class="no-data">
<svg :viewBox="EmptyIcon.viewBox" aria-hidden="true">
<use :xlink:href="EmptyIcon.url" />
</svg>
</div>
</div>
</div>
</template>
<script>
import { mapGetters, mapState } from 'vuex'
import { mapGetters } from 'vuex'
import ListFile from './listFile.vue'
import EmptyIcon from '@/assets/icons/undraw_empty.svg'
export default {
data () {
this.EmptyIcon = EmptyIcon
return {
keyword: '',
searchResult: []
@ -40,9 +46,6 @@
ListFile
},
computed: {
...mapState({
'theme': state => state.preferences.theme
}),
...mapGetters(['fileList'])
},
methods: {
@ -70,10 +73,14 @@
margin: 35px 20px;
border-radius: 3px;
height: 30px;
border: 1px solid var(--lightBorder);
border: 1px solid var(--floatBorderColor);
background: var(--floatBorderColor);
border-radius: 15px;
box-sizing: border-box;
align-items: center;
& > input {
color: var(--sideBarColor);
background: transparent;
height: 100%;
flex: 1;
border: none;
@ -87,16 +94,17 @@
flex-shrink: 0;
width: 20px;
height: 20px;
margin-right: 5px;
margin-right: 10px;
&:hover {
color: var(--brandColor);
color: var(--iconColor);
}
}
}
.empty,
.search-result {
flex: 1;
overflow: auto;
overflow-y: auto;
overflow-x: hidden;
&::-webkit-scrollbar:vertical {
width: 5px;
}
@ -104,14 +112,13 @@
.empty {
font-size: 14px;
text-align: center;
}
.dark.side-bar-search .search-wrapper {
background: rgb(54, 55, 49);
border-color: transparent;
& > input,
& > svg {
background: transparent;
color: #C0C4CC;
display: flex;
flex-direction: column;
justify-content: space-around;
padding-bottom: 100px;
& .no-data svg {
fill: var(--themeColor);
width: 120px;
}
}
</style>

View File

@ -1,56 +1,96 @@
<template>
<ul class="side-bar-toc">
<li v-for="(item, index) of toc"
:key="index"
:style="{'padding-left': `${(item.lvl - startLvl) * 20}px`}"
@click="handleClick(item)"
:class="{ 'active': item.i === activeIndex }"
>
{{ item.content }}
</li>
</ul>
<div class="side-bar-toc">
<div class="title">Table Of Content</div>
<el-tree
v-if="toc.length"
:data="toc"
:props="defaultProps"
@node-click="handleClick"
:expand-on-click-node="false"
:indent="10"
></el-tree>
<div class="no-data" v-else>
<svg aria-hidden="true" :viewBox="EmptyIcon.viewBox">
<use :xlink:href="EmptyIcon.url"></use>
</svg>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import { mapState } from 'vuex'
import bus from '../../bus'
import EmptyIcon from '@/assets/icons/undraw_toc_empty.svg'
export default {
data () {
this.EmptyIcon = EmptyIcon
return {
activeIndex: -1
defaultProps: {
children: 'children',
label: 'label'
}
}
},
computed: {
...mapGetters(['toc']),
startLvl () {
return Math.min(...this.toc.map(h => h.lvl))
}
...mapState({
'toc': state => state.editor.toc
})
},
methods: {
handleClick (item) {
this.activeIndex = item.i
bus.$emit('scroll-to-header', item.slug)
handleClick ({ slug }) {
bus.$emit('scroll-to-header', slug)
}
}
}
</script>
<style scoped>
<style>
.side-bar-toc {
height: calc(100% - 35px);
margin: 0;
margin-top: 35px;
padding: 0;
list-style: none;
overflow: scroll;
overflow: auto;
display: flex;
flex-direction: column;
& .title {
padding: 5px 0;
color: var(--sideBarTitleColor);
font-weight: 600px;
font-size: 16px;
margin: 20px 0;
text-align: center;
}
& .el-tree-node {
margin-top: 8px;
}
& .el-tree {
background: transparent;
color: var(--sideBarColor);
}
& .el-tree-node:focus > .el-tree-node__content {
background-color: var(--sideBarItemHoverBgColor);
}
& .el-tree-node__content:hover {
background: var(--sideBarItemHoverBgColor);
}
& > li {
font-size: 14px;
margin-bottom: 15px;
cursor: pointer;
}
& > li.active {
color: var(--primary);
& .no-data {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
padding-bottom: 50px;
& svg {
width: 120px;
fill: var(--themeColor);
}
}
}
</style>

View File

@ -23,7 +23,7 @@
<!-- opened files -->
<div class="opened-files">
<div class="title">
<svg class="icon" aria-hidden="true" @click.stop="toggleOpenedFiles()">
<svg class="icon icon-arrow" :class="{'fold': !showOpenedFiles}" aria-hidden="true" @click.stop="toggleOpenedFiles()">
<use xlink:href="#icon-arrow"></use>
</svg>
<span class="default-cursor text-overflow" @click.stop="toggleOpenedFiles()">Opened files</span>
@ -54,7 +54,7 @@
class="project-tree" v-if="projectTree"
>
<div class="title">
<svg class="icon" aria-hidden="true" @click.stop="toggleDirectories()">
<svg class="icon icon-arrow" :class="{'fold': !showDirectories}" aria-hidden="true" @click.stop="toggleDirectories()">
<use xlink:href="#icon-arrow"></use>
</svg>
<span class="default-cursor text-overflow" @click.stop="toggleDirectories()">{{ projectTree.name }}</span>
@ -91,11 +91,14 @@
</div>
</div>
<div v-else class="open-project">
<a href="javascript:;" @click="openFolder" title="Open Folder">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-create-project"></use>
<div class="button-group">
<svg aria-hidden="true" :viewBox="FolderIcon.viewBox">
<use :xlink:href="FolderIcon.url"></use>
</svg>
</a>
<a href="javascript:;" @click="openFolder">
Open Folder
</a>
</div>
</div>
</div>
</template>
@ -108,11 +111,13 @@
import { mapState } from 'vuex'
import bus from '../../bus'
import { createFileOrDirectoryMixins } from '../../mixins'
import FolderIcon from '@/assets/icons/undraw_folder.svg'
export default {
mixins: [createFileOrDirectoryMixins],
data () {
this.depth = 0
this.FolderIcon = FolderIcon
return {
active: 'tree', // tree or list
showDirectories: true,
@ -199,6 +204,7 @@
display: inline-block;
margin-right: 10px;
}
.list-enter-active, .list-leave-active {
transition: all .2s;
}
@ -209,7 +215,7 @@
}
.tree-view {
font-size: 14px;
color: var(--regularColor);
color: var(--sideBarColor);
display: flex;
flex-direction: column;
height: 100%;
@ -228,20 +234,31 @@
pointer-events: auto;
cursor: pointer;
margin-left: 8px;
color: var(--secondaryColor);
color: var(--iconColor);
opacity: 0;
}
& > a:hover {
color: var(--primary);
color: var(--themeColor);
}
& > a.active {
color: var(--primary);
color: var(--themeColor);
}
}
.tree-view:hover .title a {
opacity: 1;
}
.icon-arrow {
margin-right: 5px;
transition: all .25s ease-out;
transform: rotate(90deg);
fill: var(--sideBarTextColor);
}
.icon-arrow.fold {
transform: rotate(0);
}
.opened-files,
.project-tree {
& > .title {
@ -261,7 +278,7 @@
& > a {
display: none;
text-decoration: none;
color: var(--secondaryColor);
color: var(--sideBarColor);
margin-left: 8px;
}
}
@ -269,7 +286,7 @@
.opened-files div.title > a:hover {
display: block;
&:hover {
color: var(--primary);
color: var(--sideBarTitleColor);
}
}
.opened-files {
@ -277,7 +294,7 @@
flex-direction: column;
}
.default-cursor {
cursor: default;
cursor: pointer;
}
.opened-files .opened-files-list {
max-height: 200px;
@ -308,26 +325,30 @@
.open-project {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
margin-top: -100px;
& > a {
width: 35px;
height: 35px;
border-radius: 50%;
text-decoration: none;
background: rgba(31, 116, 255, .5);
transition: all .3s ease;
padding-bottom: 100px;
& .button-group {
display: flex;
justify-content: space-around;
flex-direction: column;
align-items: center;
& > svg {
width: 18px;
height: 18px;
color: #fff;
}
&:hover {
background: var(--primary);
}
& svg {
width: 120px;
fill: var(--themeColor);
}
& a {
text-decoration: none;
background: var(--themeColor);
box-shadow: 0 0 8px 0 var(--selectionColor);
display: block;
padding: 4px 7px;
border-radius: 5px;
margin-top: 20px;
color: #fff;
&:active {
opacity: .5;
}
}
}
@ -335,7 +356,7 @@
outline: none;
height: 22px;
margin: 5px 0;
border: 1px solid var(--lightBorder);
border: 1px solid var(--themeColor);
width: calc(100% - 45px);
border-radius: 3px;
}
@ -352,7 +373,7 @@
padding-top: 40px;
align-items: center;
& > a {
color: var(--primary);
color: var(--themeColor);
text-align: center;
margin-top: 15px;
text-decoration: none;

View File

@ -1,7 +1,7 @@
<template>
<div
class="title-bar"
:class="[{ 'active': active }, theme, { 'frameless': titleBarStyle === 'custom' }, { 'isOsx': platform === 'darwin' }]"
:class="[{ 'active': active }, { 'frameless': titleBarStyle === 'custom' }, { 'isOsx': platform === 'darwin' }]"
>
<div class="title">
<span v-if="!filename">Mark Text</span>
@ -34,11 +34,20 @@
<el-tooltip
v-if="wordCount"
class="item"
effect="dark"
:content="`${wordCount[show]} ${HASH[show].full + (wordCount[show] > 1 ? 's' : '')}`"
:open-delay="500"
placement="bottom-end"
>
<div slot="content">
<div class="title-item">
<span class="front">Words:</span><span class="text">{{wordCount['word']}}</span>
</div>
<div class="title-item">
<span class="front">Characters:</span><span class="text">{{wordCount['character']}}</span>
</div>
<div class="title-item">
<span class="front">Paragraph:</span><span class="text">{{wordCount['paragraph']}}</span>
</div>
</div>
<div
v-if="wordCount"
class="word-count"
@ -125,7 +134,6 @@
pathname: String,
active: Boolean,
wordCount: Object,
theme: String,
platform: String,
isSaved: Boolean
},
@ -206,20 +214,20 @@
.title-bar {
-webkit-app-region: drag;
user-select: none;
background: var(--editorBgColor);
width: 100%;
height: var(--titleBarHeight);
box-sizing: border-box;
color: #F2F6FC;
position: fixed;
color: var(--editorColor50);
position: absolute;
top: 0;
left: 0;
right: 0;
z-index: 1;
z-index: 2;
transition: color .4s ease-in-out;
cursor: default;
}
.active {
color: #909399;
color: var(--editorColor);
}
img {
height: 90%;
@ -248,7 +256,7 @@
}
.title-bar .title .filename.isOsx:hover {
color: var(--primary);
color: var(--themeColor);
}
.active .save-dot {
@ -257,7 +265,7 @@
height: 7px;
display: inline-block;
border-radius: 50%;
background: var(--warning);
background: var(--themeColor);
opacity: .7;
visibility: hidden;
}
@ -265,11 +273,9 @@
visibility: visible;
}
.title:hover {
color: #303133;
}
.title:hover .save-dot {
background: var(--warning);
color: var(sideBarTitleColor);
}
.right-toolbar {
padding: 0 10px;
height: 100%;
@ -278,6 +284,7 @@
right: 0;
width: 100px;
display: flex;
align-items: center;
flex-direction: row-reverse;
}
.left-toolbar {
@ -293,20 +300,19 @@
.word-count {
cursor: pointer;
font-size: 14px;
color: #F2F6FC;
height: 17px;
line-height: 17px;
margin-top: 4px;
padding: 1px 5px;
border-radius: 1px;
color: var(--editorColor30);
height: 20px;
text-align: center;
line-height: 24px;
padding: 0px 5px;
box-sizing: border-box;
border-radius: 4px;
transition: all .25s ease-in-out;
}
.active .word-count {
color: #DCDFE6;
}
.word-count:hover {
background: #F2F6FC;
color: #606266;
background: var(--sideBarBgColor);
color: var(--sideBarTitleColor);
}
.title-no-drag {
-webkit-app-region: no-drag;
@ -326,7 +332,7 @@
transform: translateX(-50%) translateY(-50%);
}
.frameless-titlebar-menu {
color: #606266;
color: var(--sideBarColor);
}
.frameless-titlebar-close:hover {
background-color: rgb(228, 79, 79);
@ -341,26 +347,19 @@
.frameless-titlebar-close:hover svg {
fill: #ffffff
}
/* css for dark theme */
.dark {
background: var(--darkBgColor);
color: #909399;
}
.dark .title:hover {
color: #F2F6FC;
}
.dark .word-count:hover {
background: rgb(71, 72, 66);
color: #C0C4CC;
}
.dark .frameless-titlebar-button svg {
fill: #909399
}
.dark .frameless-titlebar-close:hover svg {
fill: #ffffff
}
/* exclude titlebar so we can apply a custom sidebar background color */
.title-bar.dark {
background: transparent;
}
</style>
<style>
.title-item {
height: 28px;
line-height: 28px;
& .front {
color: var(--editorColor50);
}
& .text {
margin-left: 10px;
color: var(--editorColor30);
}
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<div class="tweet-dialog" :class="theme">
<div class="tweet-dialog">
<el-dialog
:visible.sync="showTweetDialog"
:show-close="false"
@ -68,7 +68,6 @@
<script>
import { shell } from 'electron'
import { mapState } from 'vuex'
import bus from '../../bus'
export default {
@ -79,11 +78,6 @@
selectedFace: 'smile'
}
},
computed: {
...mapState({
'theme': state => state.preferences.theme
})
},
created () {
bus.$on('tweetDialog', this.showDialog)
},
@ -127,8 +121,7 @@
<style>
.tweet-dialog {
width: 450px;
color: var(--regularColor);
color: var(--sideBarColor);
& .title {
font-size: 24px;
}
@ -136,11 +129,14 @@
border-top-left-radius: 5px;
border-top-right-radius: 5px;
}
& .el-dialog__body {
color: var(--sideBarColor);
}
}
.feeling, .feedback {
.tweet-dialog .feeling, .tweet-dialog .feedback {
font-size: 16px;
}
.feeling {
.tweet-dialog .feeling {
& ul {
display: flex;
list-style: none;
@ -163,7 +159,7 @@
color: rgb(255, 204, 0);
}
}
.feedback {
.tweet-dialog .feedback {
& > textarea {
width: 100%;
box-sizing: border-box;
@ -171,19 +167,21 @@
padding: .5rem;
resize: none;
outline: none;
border-color: var(--lightBorder);
border: 1px solid var(--floatBorderColor);
background: var(--floatBorderColor);
color: var(--editorColor);
border-radius: 5px;
font-size: 14px;
height: 80px;
}
}
.button {
.tweet-dialog .button {
display: flex;
justify-content: space-between;
align-items: center;
}
.button a.twitter {
color: var(--secondaryColor);
.tweet-dialog .button a.twitter {
color: var(--themeColor);
text-decoration: none;
width: auto;
height: 30px;
@ -195,18 +193,18 @@
background: #eee;
cursor: not-allowed;
}
.button a.active {
background: var(--primary);
.tweet-dialog .button a.active {
background: var(--themeColor);
color: #fff;
}
.button a.active {
.tweet-dialog .button a.active {
cursor: pointer;
}
.button a.github {
color: var(--secondaryColor);
.tweet-dialog .button a.github {
color: var(--iconColor);
text-decoration: none;
&:hover {
color: #1da1f2;
color: var(--themeColor);
}
& > svg {
width: 1.4rem;
@ -214,13 +212,8 @@
vertical-align: bottom;
}
}
.tweet-dialog.dark textarea {
background: var(--darkInputBgColor);
border-color: transparent;
color: var(--darkInputColor);
}
.tweet-dialog.light .el-dialog__header {
background: var(--primary);
.tweet-dialog .el-dialog__header {
background: var(--themeColor);
color: #fff;
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<div class="aidou" :class="theme">
<div class="aidou">
<el-dialog
:visible.sync="showUpload"
:show-close="false"
@ -27,7 +27,6 @@
</template>
<script>
import { mapState } from 'vuex'
import bus from '../../bus'
const msg = 'jpg | png | gif | jpeg only, max size 5M'
@ -40,11 +39,6 @@
error: false
}
},
computed: {
...mapState({
'theme': state => state.preferences.theme
})
},
created () {
this.$nextTick(() => {
bus.$on('upload-image', this.handleUpload)
@ -92,11 +86,21 @@
<style>
.el-upload__tip {
text-align: center;
color: var(--sideBarColor);
}
.el-upload__tip.error {
color: #E6A23C;
}
.dark .el-upload-dragger {
background: rgb(39, 39, 39);
.el-upload-dragger {
background: var(--itemBgColor);
& .el-upload__text {
color: var(--sideBarColor);
& em {
color: var(--themeColor);
}
}
}
.el-upload-dragger:hover {
border-color: var(--themeColor);
}
</style>

View File

@ -2,7 +2,7 @@ import path from 'path'
export const PATH_SEPARATOR = path.sep
export const THEME_LINK_ID = 'ag-theme'
export const THEME_STYLE_ID = 'ag-theme'
export const COMMON_STYLE_ID = 'ag-common-style'
export const DEFAULT_EDITOR_FONT_FAMILY = '"Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif'
@ -12,3 +12,5 @@ export const DEFAULT_STYLE = {
codeFontSize: '14px',
theme: 'light'
}
export const railscastsThemes = ['dark']

View File

@ -6,7 +6,20 @@ import locale from 'element-ui/lib/locale'
import App from './app'
import store from './store'
import './assets/symbolIcon'
import { Dialog, Form, FormItem, InputNumber, Button, Tooltip, Upload, Slider, ColorPicker, Col, Row } from 'element-ui'
import {
Dialog,
Form,
FormItem,
InputNumber,
Button,
Tooltip,
Upload,
Slider,
ColorPicker,
Col,
Row,
Tree
} from 'element-ui'
import services from './services'
import './assets/styles/index.css'
@ -54,6 +67,7 @@ Vue.use(Slider)
Vue.use(ColorPicker)
Vue.use(Col)
Vue.use(Row)
Vue.use(Tree)
if (!process.env.IS_WEB) Vue.use(require('vue-electron'))
Vue.http = Vue.prototype.$http = axios

View File

@ -2,24 +2,16 @@ import { clipboard, ipcRenderer, shell } from 'electron'
import path from 'path'
import bus from '../bus'
import { hasKeys } from '../util'
import listToTree from '../util/listToTree'
import { createDocumentState, getOptionsFromState, getSingleFileState, getBlankFileState } from './help'
import notice from '../services/notification'
// HACK: When rewriting muya, create and update muya's TOC during heading parsing and pass it to the renderer process.
import { getTocFromMarkdown } from 'muya/lib/utils/dirtyToc'
const state = {
lineEnding: 'lf',
currentFile: {},
tabs: [],
textDirection: 'ltr'
}
const getters = {
toc: state => {
const { markdown } = state.currentFile
return getTocFromMarkdown(markdown)
}
textDirection: 'ltr',
toc: []
}
const mutations = {
@ -27,6 +19,9 @@ const mutations = {
SET_SEARCH (state, value) {
state.currentFile.searchMatches = value
},
SET_TOC (state, toc) {
state.toc = listToTree(toc)
},
SET_CURRENT_FILE (state, currentFile) {
const oldCurrentFile = state.currentFile
if (!oldCurrentFile.id || oldCurrentFile.id !== currentFile.id) {
@ -463,7 +458,7 @@ const actions = {
// },
// Content change from realtime preview editor and source code editor
LISTEN_FOR_CONTENT_CHANGE ({ commit, state, rootState }, { markdown, wordCount, cursor, history }) {
LISTEN_FOR_CONTENT_CHANGE ({ commit, state, rootState }, { markdown, wordCount, cursor, history, toc }) {
const { autoSave } = rootState.preferences
const { projectTree } = rootState.project
const { pathname, markdown: oldMarkdown, id } = state.currentFile
@ -481,6 +476,8 @@ const actions = {
if (cursor) commit('SET_CURSOR', cursor)
// set history
if (history) commit('SET_HISTORY', history)
// set toc
if (toc) commit('SET_TOC', toc)
// change save status/save to file only when the markdown changed!
if (markdown !== oldMarkdown) {
@ -591,4 +588,4 @@ const actions = {
}
}
export default { state, getters, mutations, actions }
export default { state, mutations, actions }

9
src/renderer/util/day.js Normal file
View File

@ -0,0 +1,9 @@
import dayjs from 'dayjs/esm'
import relativeTime from 'dayjs/esm/plugin/relativeTime'
import 'dayjs/esm/locale/en' // load on demand
dayjs.locale('en') // use Spanish locale globally
dayjs.extend(relativeTime)
export default dayjs

View File

@ -0,0 +1,30 @@
const listToTree = list => {
const result = []
let parent = null
let child = null
let tempLvl = 7 // any number great than 6
for (const { lvl, content, slug } of list) {
const item = {
lvl, label: content, slug, children: []
}
if (lvl < tempLvl) {
tempLvl = lvl
result.push(item)
parent = { children: result }
child = item
} else if (lvl === tempLvl) {
parent.children.push(item)
child = item
} else if (lvl > tempLvl) {
tempLvl = lvl
child.children.push(item)
parent = child
child = item
}
}
return result
}
export default listToTree

View File

@ -1,18 +1,42 @@
import { THEME_LINK_ID, COMMON_STYLE_ID, DEFAULT_CODE_FONT_FAMILY } from '../config'
import { THEME_STYLE_ID, COMMON_STYLE_ID, DEFAULT_CODE_FONT_FAMILY, railscastsThemes } from '../config'
import { dark, ulysses, graphite } from './themeColor'
export const addThemeStyle = theme => {
const href = process.env.NODE_ENV !== 'production'
? `./src/muya/themes/${theme}.css`
: `./static/themes/${theme}.css`
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 = dark
break
case 'ulysses':
themeStyleEle.innerHTML = ulysses
break
case 'graphite':
themeStyleEle.innerHTML = graphite
break
default:
console.log('unknown theme')
break
}
let link = document.querySelector(`#${THEME_LINK_ID}`)
if (!link) {
link = document.createElement('link')
link.setAttribute('rel', 'stylesheet')
link.id = THEME_LINK_ID
document.head.appendChild(link)
// change codeMirror theme
const cm = document.querySelector('.CodeMirror')
if (cm) {
if (railscastsThemes.includes(theme)) {
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')
}
link.href = href
}
}
export const addCommonStyle = style => {

View File

@ -0,0 +1,392 @@
// we can load custom theme from userData folder, we also can write this theme in userData folder.
export const dark = `
:root {
--themeColor: #f48237;
--highlightColor: rgba(244, 130, 55, .9);
--selectionColor: rgba(244, 130, 55, .4);
--editorColor: rgba(255, 255, 255, .8);
--editorColor50: rgba(255, 255, 255, .5);
--editorColor30: rgba(255, 255, 255, .3);
--editorColor10: rgba(255, 255, 255, .1);
--editorBgColor: #34393f;
--deleteColor: #ff6969;
--iconColor: rgba(255, 255, 255, .8);
--codeBgColor: #d8d8d869;
--codeBlockBgColor: rgba(244, 130, 55, .04);
--sideBarColor: rgba(255, 255, 255, .6);
--sideBarTitleColor: rgba(255, 255, 255, 1);
--sideBarTextColor: rgba(255, 255, 255, .4);
--sideBarBgColor: rgba(26, 33, 41, 0.9);
--sideBarItemHoverBgColor: rgba(255, 255, 255, .03);
--itemBgColor: rgba(71, 78, 86, 0.6);
--floatBgColor: #3c4650;
--floatHoverColor: rgba(255, 255, 255, .04);
--floatBorderColor: rgba(0, 0, 0, .03);
--editorAreaWidth: 700px;
}
div.title-bar .frameless-titlebar-button > div > svg {
fill: #ffffff;
}
/**
* okaidia theme for JavaScript, CSS and HTML
* Loosely based on Monokai textmate theme by http://www.monokai.nl/
* @author ocodia
*/
code[class*="language-"],
pre.ag-paragraph {
color: #f8f8f2;
/*font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;*/
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
overflow: visible;
}
/* Code blocks */
pre.ag-paragraph {
padding: 1em;
margin: 1em 0;
border-radius: 0.3em;
}
:not(pre) > code[class*="language-"],
pre.ag-paragraph {
/*background: #272822;*/
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #f8f8f2;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.constant,
.token.symbol {
color: #f92672;
}
.token.boolean,
.token.number {
color: #ae81ff;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin {
color: #a6e22e;
}
.token.inserted {
color: #22863a;
background: #f0fff4;
}
.token.deleted {
color: #b31d28;
background: #ffeef0;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string,
.token.variable {
color: #f8f8f2;
}
.token.atrule,
.token.attr-value,
.token.function,
.token.class-name {
color: #e6db74;
}
.token.keyword {
color: #66d9ef;
}
.token.regex,
.token.important {
color: #fd971f;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
`
export const ulysses = `
:root {
--themeColor: rgb(12, 139, 186);
--highlightColor: rgba(12, 139, 186, .9);
--selectionColor: rgba(12, 139, 186, .4);
--editorColor: rgba(101, 101, 101, .8);
--editorColor50: rgba(101, 101, 101, .5);
--editorColor30: rgba(101, 101, 101, .3);
--editorColor10: rgba(101, 101, 101, .1);
--editorBgColor: #f3f3f3;
--deleteColor: #ff6969;
--iconColor: rgba(101, 101, 101, .8);
--codeBgColor: #d8d8d869;
--codeBlockBgColor: rgba(12, 139, 186, .04);
--sideBarColor: rgba(101, 101, 101, .6);
--sideBarTitleColor: rgba(101, 101, 101, 1);
--sideBarTextColor: rgba(101, 101, 101, .4);
--sideBarBgColor: rgba(248, 248, 248, 0.9);
--sideBarItemHoverBgColor: rgba(101, 101, 101, .03);
--itemBgColor: rgba(245, 245, 245, 0.6);
--floatBgColor: #ffffff;
--floatHoverColor: rgba(101, 101, 101, .04);
--floatBorderColor: rgba(0, 0, 0, .03);
--editorAreaWidth: 700px;
}
h1, h2, h3, h4, h5, h6 {
color: var(--themeColor);
text-align: center;
}
li.ag-bullet-list-item {
position: relative;
list-style: none;
}
li.ag-bullet-list-item::before {
content: '';
display: block;
position: absolute;
width: 5px;
height: 2px;
left: -18px;
top: 15px;
background: var(--editorColor);
}
blockquote.ag-paragraph {
background: rgb(233, 233, 233);
}
blockquote.ag-paragraph::before {
content: none;
}
li.ag-paragraph {
color: var(--editorColor);
}
/*task list*/
li.ag-task-list-item {
list-style-type: none;
position: relative;
}
li.ag-task-list-item > input[type=checkbox] {
position: absolute;
cursor: pointer;
width: 16px;
height: 16px;
margin: 4px 0px 0px;
top: 2px;
left: -22px;
transform-origin: center;
transform: rotate(-90deg);
transition: all .2s ease;
}
li.ag-task-list-item > input.ag-checkbox-checked {
transform: rotate(0);
opacity: .5;
}
li.ag-task-list-item > input[type=checkbox]::before {
content: '';
width: 16px;
height: 16px;
box-sizing: border-box;
display: inline-block;
border: 2px solid var(--editorColor);
border-radius: 2px;
background-color: var(--editorBgColor);
position: absolute;
top: 0;
left: 0;
transition: all .2s ease;
}
li.ag-task-list-item > input.ag-checkbox-checked::before {
border: transparent;
background-color: var(--editorColor);
}
li.ag-task-list-item > input::after {
content: '';
transform: rotate(-45deg) scale(0);
width: 9px;
height: 5px;
border: 2px solid #fff;
border-top: none;
border-right: none;
position: absolute;
display: inline-block;
top: 1px;
left: 5px;
transition: all .2s ease;
}
li.ag-task-list-item > input.ag-checkbox-checked::after {
transform: rotate(-45deg) scale(1);
}
/*horizontal line*/
p:not(.ag-active)[data-role="hr"]::before {
content: '';
position: absolute;
width: 50%;
display: block;
left: 50%;
top: 50%;
height: 2px;
box-sizing: border-box;
border-bottom: 2px dashed var(--editorColor50);
transform: translateX(-50%) translateY(-50%);
}
`
export const graphite = `
:root {
--themeColor: rgb(104, 134, 170);
--highlightColor: rgba(104, 134, 170, .9);
--selectionColor: rgba(104, 134, 170, .4);
--editorColor: rgba(43, 48, 50, .8);
--editorColor50: rgba(43, 48, 50, .5);
--editorColor30: rgba(43, 48, 50, .3);
--editorColor10: rgba(43, 48, 50, .1);
--editorBgColor: #f7f7f7;
--deleteColor: #ff6969;
--iconColor: rgba(135, 135, 135, .8);
--codeBgColor: #d8d8d869;
--codeBlockBgColor: rgba(104, 134, 170, .04);
--sideBarColor: rgba(188, 193, 197, .8);
--sideBarTitleColor: rgba(255, 255, 255, 1);
--sideBarTextColor: rgba(188, 193, 197, .4);
--sideBarBgColor: rgba(69, 75, 80, 1);
--sideBarItemHoverBgColor: rgba(255, 255, 255, .03);
--itemBgColor: rgba(43, 48, 50, .5);
--floatBgColor: rgb(237, 237, 238);
--floatHoverColor: rgba(43, 48, 50, .04);
--floatBorderColor: rgba(0, 0, 0, .03);
--editorAreaWidth: 700px;
}
li.ag-paragraph {
color: var(--editorColor);
}
/*task list*/
li.ag-task-list-item {
list-style-type: none;
position: relative;
}
li.ag-task-list-item > input[type=checkbox] {
position: absolute;
cursor: pointer;
width: 16px;
height: 16px;
margin: 4px 0px 0px;
top: 2px;
left: -22px;
transform-origin: center;
transform: rotate(-90deg);
transition: all .2s ease;
}
li.ag-task-list-item > input.ag-checkbox-checked {
transform: rotate(0);
opacity: .5;
}
li.ag-task-list-item > input[type=checkbox]::before {
content: '';
width: 16px;
height: 16px;
box-sizing: border-box;
display: inline-block;
border: 2px solid var(--editorColor);
border-radius: 2px;
background-color: var(--editorBgColor);
position: absolute;
top: 0;
left: 0;
transition: all .2s ease;
}
li.ag-task-list-item > input.ag-checkbox-checked::before {
border: transparent;
background-color: var(--editorColor);
}
li.ag-task-list-item > input::after {
content: '';
transform: rotate(-45deg) scale(0);
width: 9px;
height: 5px;
border: 2px solid #fff;
border-top: none;
border-right: none;
position: absolute;
display: inline-block;
top: 1px;
left: 5px;
transition: all .2s ease;
}
li.ag-task-list-item > input.ag-checkbox-checked::after {
transform: rotate(-45deg) scale(1);
}
/*horizontal line*/
p:not(.ag-active)[data-role="hr"]::before {
content: '';
position: absolute;
width: 100%;
display: block;
left: 50%;
top: 50%;
height: 2px;
box-sizing: border-box;
border-bottom: 2px dashed var(--editorColor50);
transform: translateX(-50%) translateY(-50%);
}
`

View File

@ -3168,6 +3168,11 @@ dateformat@^1.0.6:
get-stdin "^4.0.1"
meow "^3.3.0"
dayjs@^1.8.10:
version "1.8.10"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.8.10.tgz#fc35a976ccac2a9c44b50485a06c4a5ecf5d1b37"
integrity sha512-U+7kBBkJzPWww0vNeMkaBeJwnkivTACoajm+bTfwparjFcPI6/5JSQN40WVnX6yCsm20oGf1SkMkIIp4m/boAw==
de-indent@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
@ -3397,11 +3402,6 @@ di@^0.0.1:
resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c"
integrity sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=
diacritics-map@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/diacritics-map/-/diacritics-map-0.1.0.tgz#6dfc0ff9d01000a2edf2865371cac316e94977af"
integrity sha1-bfwP+dAQAKLt8oZTccrDFulJd68=
diff@3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"