mirror of
https://github.com/marktext/marktext.git
synced 2025-05-03 00:19:35 +08:00
Notification (#337)
* rewrite notice module * optimization: show some notification when export html or pdf * optimization: style of open project button * little bug fix * style: uniform titlebar hight to remove some style error
This commit is contained in:
parent
311d7ddf1a
commit
4be72ade97
@ -20,16 +20,23 @@ const handleResponseForExport = (e, { type, content, filename, pathname }) => {
|
||||
defaultPath
|
||||
})
|
||||
|
||||
// If export PDF, the content will be undefined.
|
||||
if (!content && type === 'pdf') {
|
||||
win.webContents.printToPDF({ printBackground: true }, (err, data) => {
|
||||
if (err) log(err)
|
||||
else {
|
||||
writeFile(filePath, data, extension)
|
||||
.then(() => {
|
||||
win.webContents.send('AGANI::export-success', { type, filePath })
|
||||
})
|
||||
.catch(log)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
writeFile(filePath, content, extension)
|
||||
.then(() => {
|
||||
win.webContents.send('AGANI::export-success', { type, filePath })
|
||||
})
|
||||
.catch(log)
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ autoUpdater.autoDownload = false
|
||||
|
||||
autoUpdater.on('error', error => {
|
||||
if (win) {
|
||||
win.webContents.send('AGANI::UPDATE_ERROR', error === null ? 'Error: unknown' : (error.stack || error).toString())
|
||||
win.webContents.send('AGANI::UPDATE_ERROR', error === null ? 'Error: unknown' : (error.message || error).toString())
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
class="editor-container"
|
||||
:class="[{ 'frameless': platform !== 'darwin' }]"
|
||||
>
|
||||
<title-bar
|
||||
:pathname="pathname"
|
||||
@ -130,6 +129,7 @@
|
||||
dispatch('LINTEN_FOR_SET_LINE_ENDING')
|
||||
dispatch('LISTEN_FOR_NEW_TAB')
|
||||
dispatch('LISTEN_FOR_CLOSE_TAB')
|
||||
dispatch('LINTEN_FOR_EXPORT_SUCCESS')
|
||||
// module: notification
|
||||
dispatch('LISTEN_FOR_NOTIFICATION')
|
||||
}
|
||||
@ -138,9 +138,6 @@
|
||||
|
||||
<style scoped>
|
||||
.editor-container {
|
||||
padding-top: 22px;
|
||||
}
|
||||
.editor-container.frameless {
|
||||
padding-top: 25px;
|
||||
}
|
||||
.editor-container .hide {
|
||||
@ -151,6 +148,7 @@
|
||||
}
|
||||
.editor-middle {
|
||||
display: flex;
|
||||
min-height: calc(100vh - 25px);
|
||||
& > .editor {
|
||||
flex: 1;
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@ -4,18 +4,15 @@
|
||||
v-if="!sourceCode"
|
||||
:theme="theme"
|
||||
></search>
|
||||
<status></status>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Search from './search'
|
||||
import Status from './status'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Search,
|
||||
Status
|
||||
Search
|
||||
},
|
||||
props: {
|
||||
sourceCode: Boolean,
|
||||
|
@ -477,7 +477,7 @@
|
||||
|
||||
<style>
|
||||
.ag-dialog-table {
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 1px 3px rgba(230, 230, 230, .3);
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
class="editor-with-tabs"
|
||||
:class="[{ 'frameless': platform !== 'darwin' }]"
|
||||
>
|
||||
<tabs v-show="showTabBar"></tabs>
|
||||
<editor
|
||||
@ -22,7 +21,6 @@
|
||||
import Tabs from './tabs.vue'
|
||||
import Editor from './editor.vue'
|
||||
import SourceCode from './sourceCode.vue'
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
@ -49,11 +47,6 @@
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
'platform'
|
||||
])
|
||||
},
|
||||
components: {
|
||||
Tabs,
|
||||
Editor,
|
||||
@ -67,10 +60,7 @@
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 22px);
|
||||
height: calc(100vh - 25px);
|
||||
overflow: hidden;
|
||||
}
|
||||
.editor-with-tabs.frameless {
|
||||
height: calc(100vh - 25px);
|
||||
}
|
||||
</style>
|
||||
|
@ -2,7 +2,6 @@
|
||||
<div
|
||||
class="source-code"
|
||||
ref="sourceCode"
|
||||
:class="[theme, { 'frameless': platform !== 'darwin' }]"
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
@ -12,7 +11,6 @@
|
||||
import { wordCount as getWordCount } from '../../../editor/utils'
|
||||
import { adjustCursor } from '../../util'
|
||||
import bus from '../../bus'
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
@ -24,12 +22,6 @@
|
||||
cursor: Object
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState([
|
||||
'platform'
|
||||
])
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
contentState: null,
|
||||
@ -129,13 +121,10 @@
|
||||
|
||||
<style>
|
||||
.source-code {
|
||||
height: calc(100vh - 22px);
|
||||
height: calc(100vh - 25px);
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
}
|
||||
.source-code.frameless {
|
||||
height: calc(100vh - 25px);
|
||||
}
|
||||
.source-code .CodeMirror {
|
||||
margin: 50px auto;
|
||||
max-width: 860px;
|
||||
|
@ -2,9 +2,8 @@
|
||||
<div
|
||||
class="recent-files-projects"
|
||||
:class="theme"
|
||||
@click="newFile"
|
||||
>
|
||||
<h1>Mark Text</h1>
|
||||
<div>Make you fall in love with writing</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -16,6 +15,11 @@
|
||||
...mapState({
|
||||
'theme': state => state.preferences.theme
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
newFile () {
|
||||
this.$store.dispatch('NEW_BLANK_FILE')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -23,29 +27,11 @@
|
||||
<style scoped>
|
||||
.recent-files-projects {
|
||||
flex: 1;
|
||||
& h1 {
|
||||
text-align: center;
|
||||
font-weight: 100;
|
||||
font-size: 4rem;
|
||||
margin-top: 200px;
|
||||
font-family: monospace;
|
||||
color: var(--primaryColor);
|
||||
border-bottom: none;
|
||||
}
|
||||
& > div {
|
||||
font-size: 2rem;
|
||||
font-family: cursive;
|
||||
text-align: center;
|
||||
color: var(--regularColor);
|
||||
}
|
||||
}
|
||||
.dark.recent-files-projects {
|
||||
background: var(--darkBgColor);
|
||||
& > h1 {
|
||||
color: var(--baseBorder);
|
||||
}
|
||||
& > div {
|
||||
color: var(--placeholerColor);
|
||||
color: var(--baseBorder);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div
|
||||
v-show="showSideBar"
|
||||
class="side-bar"
|
||||
:class="[theme, { 'frameless': platform !== 'darwin' }]"
|
||||
:class="[theme]"
|
||||
ref="sideBar"
|
||||
:style="{ 'width': `${finalSideBarWidth}px` }"
|
||||
>
|
||||
@ -81,9 +81,6 @@
|
||||
'sideBarWidth': state => state.project.sideBarWidth,
|
||||
'tabs': state => state.editor.tabs
|
||||
}),
|
||||
...mapState([
|
||||
'platform'
|
||||
]),
|
||||
...mapGetters(['fileList']),
|
||||
finalSideBarWidth () {
|
||||
const { showSideBar, rightColumn, sideBarViewWidth } = this
|
||||
@ -145,13 +142,10 @@
|
||||
<style scoped>
|
||||
.side-bar {
|
||||
display: flex;
|
||||
height: calc(100vh - 22px);
|
||||
height: calc(100vh - 25px);
|
||||
position: relative;
|
||||
color: var(--secondaryColor);
|
||||
}
|
||||
.side-bar.frameless {
|
||||
height: calc(100vh - 25px);
|
||||
}
|
||||
.side-bar.dark {
|
||||
background: var(--darkBgColor);
|
||||
}
|
||||
|
@ -90,7 +90,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="open-project">
|
||||
<a href="javascript:;" @click="openProject">Open Project</a>
|
||||
<a href="javascript:;" @click="openProject" title="Open Project">
|
||||
<svg class="icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-create-project"></use>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -295,8 +299,23 @@
|
||||
align-items: center;
|
||||
margin-top: -100px;
|
||||
& > a {
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
border-radius: 50%;
|
||||
text-decoration: none;
|
||||
color: var(--activeColor);
|
||||
background: rgba(31, 116, 255, .5);
|
||||
transition: all .3s ease;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
& > svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: #fff;
|
||||
}
|
||||
&:hover {
|
||||
background: var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
.new-input {
|
||||
|
@ -1,108 +0,0 @@
|
||||
<template>
|
||||
<div
|
||||
class="bottom-status"
|
||||
:class="{'error': error}"
|
||||
v-show="showStatus"
|
||||
>
|
||||
<div class="status-wrapper">
|
||||
<span class="message" :title="message">{{ message }}</span>
|
||||
<span class="yes" v-show="showYes" @click="handleYesClick">[ Y ]</span>
|
||||
<span class="no" @click="close(true)">[ X ]</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import bus from '../bus'
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
error: false,
|
||||
showStatus: false,
|
||||
message: '',
|
||||
showYes: false,
|
||||
eventId: ''
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.$nextTick(() => {
|
||||
bus.$on('status-error', msg => {
|
||||
this.showStatus = true
|
||||
this.error = true
|
||||
this.message = msg
|
||||
})
|
||||
bus.$on('status-message', (msg, timeout) => {
|
||||
this.showStatus = true
|
||||
this.error = false
|
||||
this.message = msg
|
||||
if (timeout) {
|
||||
setTimeout(() => {
|
||||
this.close(true)
|
||||
}, timeout)
|
||||
}
|
||||
})
|
||||
bus.$on('status-promote', (msg, eventId) => {
|
||||
this.showStatus = true
|
||||
this.error = false
|
||||
this.eventId = eventId
|
||||
this.showYes = true
|
||||
this.message = msg
|
||||
})
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
close (isEmit = false) {
|
||||
const { eventId } = this
|
||||
this.error = false
|
||||
this.showStatus = false
|
||||
this.message = ''
|
||||
this.showYes = false
|
||||
this.eventId = ''
|
||||
if (isEmit && eventId) {
|
||||
bus.$emit(eventId, false)
|
||||
}
|
||||
},
|
||||
handleYesClick () {
|
||||
const { eventId } = this
|
||||
if (eventId) {
|
||||
bus.$emit(eventId, true)
|
||||
}
|
||||
this.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.bottom-status {
|
||||
width: 100%;
|
||||
height: 25px;
|
||||
background-color: #2196F3;
|
||||
color: #fff;
|
||||
}
|
||||
.bottom-status.error {
|
||||
background-color: var(--dangerColor);
|
||||
}
|
||||
.status-wrapper {
|
||||
text-align: center;
|
||||
line-height: 25px;
|
||||
font-size: 13px;
|
||||
color: #fff;
|
||||
}
|
||||
.message {
|
||||
max-width: 70%;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap
|
||||
}
|
||||
.message, .yes {
|
||||
margin-right: 5px;
|
||||
}
|
||||
.yes, .no {
|
||||
vertical-align: top;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div class="title-bar"
|
||||
<div
|
||||
class="title-bar"
|
||||
:class="[{ 'active': active }, theme, { 'frameless': platform !== 'darwin' }]"
|
||||
>
|
||||
<div class="title">
|
||||
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
</style>
|
||||
|
@ -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);
|
||||
|
@ -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 },
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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`
|
||||
})
|
||||
})
|
||||
}
|
5
src/renderer/services/index.js
Normal file
5
src/renderer/services/index.js
Normal file
@ -0,0 +1,5 @@
|
||||
import notification from './notification'
|
||||
|
||||
export default [
|
||||
notification
|
||||
]
|
116
src/renderer/services/notification/index.css
Normal file
116
src/renderer/services/notification/index.css
Normal file
@ -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;
|
||||
}
|
||||
}
|
27
src/renderer/services/notification/index.html
Normal file
27
src/renderer/services/notification/index.html
Normal file
@ -0,0 +1,27 @@
|
||||
<div class="mt-notification">
|
||||
<div class="notice-bg"></div>
|
||||
<div class="fluent-container">
|
||||
<div class="fluent"></div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="title">
|
||||
<div class="icon-wrapper">
|
||||
<svg class="icon" aria-hidden="true">
|
||||
<use xlink:href="#{{icon}}"></use>
|
||||
</svg>
|
||||
</div>
|
||||
<span>{{title}}</span>
|
||||
<svg class="icon close" aria-hidden="true">
|
||||
<use xlink:href="#icon-close"></use>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="body">
|
||||
<div class="left-text">{{message}}</div>
|
||||
<div class="confirm icon-wrapper">
|
||||
<svg class="icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-confirm"></use>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
145
src/renderer/services/notification/index.js
Normal file
145
src/renderer/services/notification/index.js
Normal file
@ -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
|
@ -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 })
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user