@@ -166,7 +167,7 @@
-webkit-app-region: drag;
user-select: none;
width: 100%;
- height: 22px;
+ height: 25px;
box-sizing: border-box;
color: #F2F6FC;
position: fixed;
@@ -177,9 +178,6 @@
transition: color .4s ease-in-out;
cursor: default;
}
- .title-bar.frameless {
- height: 25px;
- }
.active {
color: #909399;
}
@@ -191,10 +189,17 @@
.title {
padding: 0 100px;
height: 100%;
- line-height: 22px;
- font-size: 12px;
+ line-height: 25px;
+ font-size: 14px;
text-align: center;
transition: all .25s ease-in-out;
+ & .filename {
+ transition: all .25s ease-in-out;
+ }
+ }
+
+ .title-bar:not(.frameless) .title .filename:hover {
+ color: var(--primary);
}
.active .save-dot {
@@ -237,10 +242,10 @@
}
.word-count {
cursor: pointer;
- font-size: 12px;
+ font-size: 14px;
color: #F2F6FC;
- height: 15px;
- line-height: 15px;
+ height: 17px;
+ line-height: 17px;
margin-top: 4px;
padding: 1px 5px;
border-radius: 1px;
diff --git a/src/renderer/components/tweet/index.vue b/src/renderer/components/tweet/index.vue
index 4dbfb3e7..d1625491 100644
--- a/src/renderer/components/tweet/index.vue
+++ b/src/renderer/components/tweet/index.vue
@@ -189,11 +189,15 @@
box-sizing: border-box;
display: inline-block;
background: #eee;
+ cursor: not-allowed;
}
- .button a.active, .button a.twitter:hover {
- background: var(--activeColor);
+ .button a.active {
+ background: var(--primary);
color: #fff;
}
+ .button a.active {
+ cursor: pointer;
+ }
.button a.github {
color: var(--secondaryColor);
text-decoration: none;
@@ -211,4 +215,8 @@
border-color: transparent;
color: var(--darkInputColor);
}
+ .tweet-dialog.light .el-dialog__header {
+ background: var(--primary);
+ color: #fff;
+ }
diff --git a/src/renderer/index.css b/src/renderer/index.css
index 076bdbcc..222cd9d4 100644
--- a/src/renderer/index.css
+++ b/src/renderer/index.css
@@ -1,4 +1,8 @@
: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);
diff --git a/src/renderer/main.js b/src/renderer/main.js
index 52b24dd4..2c26bf3f 100644
--- a/src/renderer/main.js
+++ b/src/renderer/main.js
@@ -8,6 +8,9 @@ import store from './store'
import './assets/symbolIcon'
import './index.css'
import { Dialog, Form, FormItem, InputNumber, Button, Tooltip, Upload, Slider, ColorPicker, Col, Row } from 'element-ui'
+import services from './services'
+
+// import notice from './services/notification'
// In the renderer process:
// var webFrame = require('electron').webFrame
// var SpellCheckProvider = require('electron-spell-check-provider')
@@ -55,6 +58,10 @@ if (!process.env.IS_WEB) Vue.use(require('vue-electron'))
Vue.http = Vue.prototype.$http = axios
Vue.config.productionTip = false
+services.forEach(s => {
+ Vue.prototype['$' + s.name] = s[s.name]
+})
+
/* eslint-disable no-new */
new Vue({
components: { App },
diff --git a/src/renderer/mixins/index.js b/src/renderer/mixins/index.js
index 2d9f0796..5aeb36d7 100644
--- a/src/renderer/mixins/index.js
+++ b/src/renderer/mixins/index.js
@@ -1,5 +1,4 @@
import { getFileStateFromData } from '../store/help.js'
-import { message } from '../notice'
export const tabsMixins = {
methods: {
@@ -31,7 +30,13 @@ export const fileMixins = {
this.$store.dispatch('UPDATE_CURRENT_FILE', fileState)
if (isMixed && !isOpened) {
- message(`${filename} has mixed line endings which are automatically normalized to ${lineEnding.toUpperCase()}.`, 20000)
+ this.$notify({
+ title: 'Line Ending',
+ message: `${filename} has mixed line endings which are automatically normalized to ${lineEnding.toUpperCase()}.`,
+ type: 'primary',
+ time: 20000,
+ showConfirm: false
+ })
}
}
}
diff --git a/src/renderer/notice/index.js b/src/renderer/notice/index.js
deleted file mode 100644
index 4d4d2091..00000000
--- a/src/renderer/notice/index.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import bus from '../bus'
-import { getUniqueId } from '../../editor/utils'
-
-export const error = msg => {
- bus.$emit('status-error', msg)
-}
-
-export const message = (msg, timeout) => {
- bus.$emit('status-message', msg, timeout)
-}
-
-export const promote = msg => {
- const eventId = getUniqueId()
- bus.$emit('status-promote', msg, eventId)
-
- return new Promise((resolve, reject) => {
- bus.$on(eventId, bool => {
- bool ? resolve() : reject(bool) // reject bool just for fix the esint error: `prefer-promise-reject-errors`
- })
- })
-}
diff --git a/src/renderer/services/index.js b/src/renderer/services/index.js
new file mode 100644
index 00000000..1b9b9d9d
--- /dev/null
+++ b/src/renderer/services/index.js
@@ -0,0 +1,5 @@
+import notification from './notification'
+
+export default [
+ notification
+]
diff --git a/src/renderer/services/notification/index.css b/src/renderer/services/notification/index.css
new file mode 100644
index 00000000..1e4fee60
--- /dev/null
+++ b/src/renderer/services/notification/index.css
@@ -0,0 +1,116 @@
+.mt-notification {
+ position: fixed;
+ z-index: 10000;
+ overflow: hidden;
+ border-radius: 5px;
+ max-width: 350px;
+ min-width: 280px;
+ transition: all .2s ease;
+ box-shadow: 0px 12px 0px 0px rgba(0, 0, 0, 0.0);
+ backface-visibility: hidden;
+ right: 15px;
+ bottom: 15px;
+
+ & > .notice-bg {
+ width: 0;
+ height: 0;
+ position: absolute;
+ transition: all .8s ease,left .4s ease,top .4s ease;
+ border-radius: 50%;
+ z-index: 10;
+ transform: translate(-50%, -50%);
+ top: 50%;
+ left: 50%;
+ }
+
+ & .content {
+ z-index: 100;
+ color: #fff;
+ position: relative;
+ & .icon-wrapper {
+ width: 28px;
+ height: 28px;
+ border-radius: 50%;
+ display: flex;
+ justify-content: space-around;
+ align-items: center;
+ background: rgba(255, 255, 255, .2);
+ }
+ & .title {
+ display: flex;
+ align-items: center;
+ padding: 5px 5px;
+ & svg.icon {
+ width: .85em;
+ height: .85em;
+ }
+ & span {
+ flex: 1;
+ margin-left: 10px;
+ }
+ & .close {
+ margin-right: 10px;
+ opacity: 0;
+ transition: all .2s ease;
+ transform: scale(0);
+ cursor: pointer;
+ transform-origin: center;
+ }
+ }
+ & .body {
+ display: flex;
+ align-items: center;
+ font-size: 14px;
+ padding-bottom: 5px;
+ & .left-text {
+ overflow: hidden;
+ word-wrap: break-word;
+ padding: 5px;
+ box-sizing: border-box;
+ width: 100%;
+ }
+ & .confirm {
+ display: none;
+ margin-left: 7px;
+ width: 30px;
+ height: 30px;
+ cursor: pointer;
+
+ }
+ }
+ }
+ & .fluent-container {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ z-index: 40;
+ display: block;
+ top: 0px;
+ left: 0px;
+ }
+ & .fluent {
+ position: absolute;
+ z-index: 50;
+ display: block;
+ backface-visibility: hidden;
+ transform: translate(-50%, -50%);
+ border-radius: 50%;
+ transition: opacity 1s ease,width .4s ease,height .4s ease;
+ background: rgba(255, 255, 255, .2);
+ opacity: 0;
+ filter: blur(22px);
+ }
+
+ &:hover .content .title .close {
+ opacity: 1;
+ transform: scale(1);
+ }
+}
+.mt-notification.mt-confirm .content .body {
+ & .left-text {
+ width: calc(100% - 45px);
+ }
+ & .confirm {
+ display: flex;
+ }
+}
\ No newline at end of file
diff --git a/src/renderer/services/notification/index.html b/src/renderer/services/notification/index.html
new file mode 100644
index 00000000..8f45eaa2
--- /dev/null
+++ b/src/renderer/services/notification/index.html
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
{{title}}
+
+
+
+
{{message}}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/renderer/services/notification/index.js b/src/renderer/services/notification/index.js
new file mode 100644
index 00000000..d805b1e0
--- /dev/null
+++ b/src/renderer/services/notification/index.js
@@ -0,0 +1,145 @@
+import template from './index.html'
+import './index.css'
+
+const INON_HASH = {
+ primary: 'icon-message',
+ error: 'icon-error',
+ warning: 'icon-warn',
+ info: 'icon-info'
+}
+
+const notification = {
+ name: 'notify',
+ notify ({
+ time = 10000,
+ title = '',
+ message = '',
+ type = 'primary',
+ showConfirm = false
+ }) {
+ let rs
+ let rj
+ let timer = null
+
+ const fragment = document.createElement('div')
+ fragment.innerHTML = template
+ .replace(/\{\{icon\}\}/, INON_HASH[type])
+ .replace(/\{\{title\}\}/, title)
+ .replace(/\{\{message\}\}/, message)
+
+ const noticeContainer = fragment.querySelector('.mt-notification')
+ const bgNotice = noticeContainer.querySelector('.notice-bg')
+ const fluent = noticeContainer.querySelector('.fluent')
+ const close = noticeContainer.querySelector('.close')
+ const { offsetHeight } = noticeContainer
+ let target = noticeContainer
+ noticeContainer.classList.add(`mt-${type}`)
+
+ if (showConfirm) {
+ noticeContainer.classList.add(`mt-confirm`)
+ target = noticeContainer.querySelector('.confirm')
+ }
+
+ bgNotice.style.backgroundColor = `var(--${type})`
+
+ fluent.style.height = offsetHeight * 2 + 'px'
+ fluent.style.width = offsetHeight * 2 + 'px'
+
+ const setCloseTimer = () => {
+ if (typeof time === 'number' && time > 0) {
+ timer = setTimeout(() => {
+ remove()
+ }, time)
+ }
+ }
+
+ const mousemoveHandler = event => {
+ const { left, top } = noticeContainer.getBoundingClientRect()
+ const x = event.pageX
+ const y = event.pageY
+ fluent.style.left = x - left + 'px'
+ fluent.style.top = y - top + 'px'
+ fluent.style.opacity = '1'
+ fluent.style.height = noticeContainer.offsetHeight * 2 + 'px'
+ fluent.style.width = noticeContainer.offsetHeight * 2 + 'px'
+
+ if (timer) clearTimeout(timer)
+ }
+
+ const mouseleaveHandler = event => {
+ fluent.style.opacity = '0'
+ fluent.style.height = noticeContainer.offsetHeight * 4 + 'px'
+ fluent.style.width = noticeContainer.offsetHeight * 4 + 'px'
+
+ if (timer) clearTimeout(timer)
+ setCloseTimer()
+ }
+
+ const clickHandler = event => {
+ event.preventDefault()
+ event.stopPropagation()
+ remove()
+ rs && rs()
+ }
+
+ const closeHandler = event => {
+ event.preventDefault()
+ event.stopPropagation()
+ remove()
+ rj && rj()
+ }
+
+ const rePositionNotices = () => {
+ const notices = document.querySelectorAll('.mt-notification')
+ let i
+ let hx = 0
+ let len = notices.length
+ for (i = 0; i < len; i++) {
+ notices[i].style.transform = `translate(0, -${hx}px)`
+ notices[i].style.zIndex = 10000 - i
+ hx += notices[i].offsetHeight + 10
+ }
+ }
+
+ const remove = () => {
+ fluent.style.filter = 'blur(10px)'
+ fluent.style.opacity = '0'
+ fluent.style.height = noticeContainer.offsetHeight * 5 + 'px'
+ fluent.style.width = noticeContainer.offsetHeight * 5 + 'px'
+
+ noticeContainer.style.opacity = '0'
+ noticeContainer.style.right = '-400px'
+
+ setTimeout(() => {
+ noticeContainer.removeEventListener('mousemove', mousemoveHandler)
+ noticeContainer.removeEventListener('mouseleave', mouseleaveHandler)
+ target.removeEventListener('click', clickHandler)
+ close.removeEventListener('click', closeHandler)
+ noticeContainer.remove()
+ rePositionNotices()
+ }, 100)
+ }
+
+ noticeContainer.addEventListener('mousemove', mousemoveHandler)
+ noticeContainer.addEventListener('mouseleave', mouseleaveHandler)
+ target.addEventListener('click', clickHandler)
+ close.addEventListener('click', closeHandler)
+
+ setTimeout(() => {
+ bgNotice.style.width = noticeContainer.offsetWidth * 3.5 + 'px'
+ bgNotice.style.height = noticeContainer.offsetWidth * 3.5 + 'px'
+ rePositionNotices()
+ }, 50)
+
+ setCloseTimer()
+
+ document.body.prepend(noticeContainer, document.body.firstChild)
+
+ return new Promise((resolve, reject) => {
+ rs = resolve
+ rj = reject
+ })
+ }
+}
+
+export default notification
diff --git a/src/renderer/store/autoUpdates.js b/src/renderer/store/autoUpdates.js
index 34a22b5c..05e0a1cc 100644
--- a/src/renderer/store/autoUpdates.js
+++ b/src/renderer/store/autoUpdates.js
@@ -1,5 +1,5 @@
import { ipcRenderer } from 'electron'
-import { error, message, promote } from '../notice'
+import notice from '../services/notification'
const state = {}
@@ -10,17 +10,35 @@ const mutations = {}
// AGANI::UPDATE_DOWNLOADED
const actions = {
LISTEN_FOR_UPDATE ({ commit }) {
- ipcRenderer.on('AGANI::UPDATE_ERROR', (e, msg) => {
- error(msg)
+ ipcRenderer.on('AGANI::UPDATE_ERROR', (e, message) => {
+ notice.notify({
+ title: 'Update',
+ type: 'error',
+ time: 10000,
+ message
+ })
})
- ipcRenderer.on('AGANI::UPDATE_NOT_AVAILABLE', (e, msg) => {
- message(msg)
+ ipcRenderer.on('AGANI::UPDATE_NOT_AVAILABLE', (e, message) => {
+ notice.notify({
+ title: 'Update not Available',
+ type: 'warning',
+ message
+ })
})
- ipcRenderer.on('AGANI::UPDATE_DOWNLOADED', (e, msg) => {
- message(msg)
+ ipcRenderer.on('AGANI::UPDATE_DOWNLOADED', (e, message) => {
+ notice.notify({
+ title: 'Update Downloaded',
+ type: 'info',
+ message
+ })
})
- ipcRenderer.on('AGANI::UPDATE_AVAILABLE', (e, msg) => {
- promote(msg)
+ ipcRenderer.on('AGANI::UPDATE_AVAILABLE', (e, message) => {
+ notice.notify({
+ title: 'Update Available',
+ type: 'primary',
+ message,
+ showConfirm: true
+ })
.then(() => {
const needUpdate = true
ipcRenderer.send('AGANI::NEED_UPDATE', { needUpdate })
diff --git a/src/renderer/store/editor.js b/src/renderer/store/editor.js
index ade32ae3..a9855fd8 100644
--- a/src/renderer/store/editor.js
+++ b/src/renderer/store/editor.js
@@ -1,8 +1,9 @@
-import { ipcRenderer } from 'electron'
+import { ipcRenderer, shell } from 'electron'
import path from 'path'
import bus from '../bus'
import { hasKeys } from '../util'
import { getOptionsFromState, getSingleFileState, getBlankFileState } from './help'
+import notice from '../services/notification'
const toc = require('markdown-toc')
@@ -434,6 +435,19 @@ const actions = {
ipcRenderer.send('AGANI::response-export', { type, content, filename, pathname })
},
+ LINTEN_FOR_EXPORT_SUCCESS ({ commit }) {
+ ipcRenderer.on('AGANI::export-success', (e, { type, filePath }) => {
+ notice.notify({
+ title: 'Export',
+ message: `Export ${path.basename(filePath)} successfully`,
+ showConfirm: true
+ })
+ .then(() => {
+ shell.showItemInFolder(filePath)
+ })
+ })
+ },
+
LISTEN_FOR_INSERT_IMAGE ({ commit, state }) {
ipcRenderer.on('AGANI::INSERT_IMAGE', (e, { filename: imagePath, type }) => {
if (!hasKeys(state.currentFile)) return
diff --git a/src/renderer/store/notification.js b/src/renderer/store/notification.js
index 14de0314..55b344e8 100644
--- a/src/renderer/store/notification.js
+++ b/src/renderer/store/notification.js
@@ -1,5 +1,5 @@
import { ipcRenderer } from 'electron'
-import { error, message } from '../notice'
+import notice from '../services/notification'
const state = {}
@@ -10,11 +10,20 @@ const mutations = {
const actions = {
LISTEN_FOR_NOTIFICATION ({ commit }) {
- ipcRenderer.on('AGANI::show-error-notification', (e, msg) => {
- error(msg)
+ ipcRenderer.on('AGANI::show-error-notification', (e, message) => {
+ notice.notify({
+ title: 'Error',
+ type: 'error',
+ message
+ })
})
- ipcRenderer.on('AGANI::show-info-notification', (e, { msg, timeout }) => {
- message(msg, timeout)
+ ipcRenderer.on('AGANI::show-info-notification', (e, { message, timeout }) => {
+ notice.notify({
+ title: 'Infomation',
+ type: 'info',
+ time: timeout,
+ message
+ })
})
}
}
diff --git a/src/renderer/store/project.js b/src/renderer/store/project.js
index ea11dbfd..ed09803c 100644
--- a/src/renderer/store/project.js
+++ b/src/renderer/store/project.js
@@ -3,7 +3,7 @@ import { ipcRenderer, shell } from 'electron'
import { addFile, unlinkFile, changeFile, addDirectory, unlinkDirectory } from './treeCtrl'
import bus from '../bus'
import { create, paste, rename } from '../util/fileSystem'
-import { error } from '../notice'
+import notice from '../services/notification'
const width = localStorage.getItem('side-bar-width')
const sideBarWidth = typeof +width === 'number' ? Math.max(+width, 180) : 280
@@ -164,7 +164,11 @@ const actions = {
commit('SET_CLIPBOARD', null)
})
.catch(err => {
- error(err.message)
+ notice.notify({
+ title: 'Paste Error',
+ type: 'error',
+ message: err.message
+ })
})
}
})
@@ -182,7 +186,11 @@ const actions = {
commit('CREATE_PATH', {})
})
.catch(err => {
- error(err.message)
+ notice.notify({
+ title: 'Error in Side Bar',
+ type: 'error',
+ message: err.message
+ })
})
},