mirror of
https://github.com/Cronocide/WebMessage.git
synced 2025-01-22 11:18:25 +00:00
Subject line supprt
Mute chats Custom notification sounds Use system sounds option Slidable chats for options
This commit is contained in:
parent
062204a0de
commit
6ad3095400
51
src/App.vue
51
src/App.vue
@ -199,6 +199,7 @@ export default {
|
||||
this.chats = []
|
||||
this.$store.commit('resetMessages')
|
||||
this.$router.push('/').catch(()=>{})
|
||||
this.notifSound = new Audio(this.$store.state.notifSound)
|
||||
|
||||
const baseURI = this.$store.getters.baseURI
|
||||
this.$connect(baseURI, {
|
||||
@ -300,7 +301,7 @@ export default {
|
||||
this.win.on('move', this.onMove)
|
||||
})
|
||||
|
||||
this.notifSound = new Audio('wm-audio://receivedText.mp3')
|
||||
this.notifSound = new Audio(this.$store.state.notifSound)
|
||||
|
||||
if (!(this.$store.state.macstyle || process.platform === 'darwin')) {
|
||||
document.body.style.border = 'none'
|
||||
@ -351,17 +352,19 @@ export default {
|
||||
}
|
||||
|
||||
if (messageData.sender != 1 && remote.Notification.isSupported()) {
|
||||
if (this.$store.state.mutedChats.includes(messageData.personId)) return
|
||||
|
||||
const notification = {
|
||||
title: messageData.name,
|
||||
body: messageData.text,
|
||||
silent: this.$store.state.playsound
|
||||
silent: !this.$store.state.systemSound
|
||||
}
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
notification.icon = __static + '/icon.png'
|
||||
}
|
||||
|
||||
if (this.$store.state.playsound) this.notifSound.play()
|
||||
if (!this.$store.state.systemSound) this.notifSound.play()
|
||||
let notif = new remote.Notification(notification)
|
||||
notif.on('click', (event, arg) => {
|
||||
if (chatData && chatData.id) {
|
||||
@ -535,7 +538,7 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@200;300;500;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css?family=Roboto:light,regular,medium,thin,italic,mediumitalic,bold');
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
@ -637,6 +640,26 @@ body {
|
||||
color: #42b983;
|
||||
}
|
||||
}
|
||||
|
||||
input:not([type="range"]):not([type="color"]):not(.message-input) {
|
||||
height: auto;
|
||||
height: inherit;
|
||||
font-size: 13px;
|
||||
height: 6px;
|
||||
padding: 10px 6px 10px 6px;
|
||||
outline: none;
|
||||
border: 1px solid rgb(213, 213, 213);
|
||||
margin: 5px;
|
||||
cursor: text;
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
input:not([type="range"]):not([type="color"]):not(.message-input):focus {
|
||||
border-radius: 1px;
|
||||
box-shadow: 0px 0px 0px 3.5px rgba(23, 101, 144, 1);
|
||||
animation: showFocus .3s;
|
||||
border-color: rgb(122, 167, 221) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.titlebar {
|
||||
@ -736,26 +759,6 @@ body {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
input:not([type="range"]):not([type="color"]):not(.message-input) {
|
||||
height: auto;
|
||||
height: inherit;
|
||||
font-size: 13px;
|
||||
height: 6px;
|
||||
padding: 10px 6px 10px 6px;
|
||||
outline: none;
|
||||
border: 1px solid rgb(213, 213, 213);
|
||||
margin: 5px;
|
||||
cursor: text;
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
input:not([type="range"]):not([type="color"]):not(.message-input):focus {
|
||||
border-radius: 1px;
|
||||
box-shadow: 0px 0px 0px 3.5px rgba(23, 101, 144, 1);
|
||||
animation: showFocus .3s;
|
||||
border-color: rgb(122, 167, 221) !important;
|
||||
}
|
||||
|
||||
@keyframes showFocus {
|
||||
0% {
|
||||
border-color: #dbdbdb;
|
||||
|
@ -295,6 +295,19 @@ function registerLocalAudioProtocol () {
|
||||
)
|
||||
}
|
||||
})
|
||||
protocol.registerFileProtocol('local-file', (request, callback) => {
|
||||
const url = request.url.replace(/^local-file:\/\//, '')
|
||||
// Decode URL to prevent errors when loading filenames with UTF-8 chars or chars like "#"
|
||||
const decodedUrl = decodeURI(url) // Needed in case URL contains spaces
|
||||
try {
|
||||
return callback(decodedUrl)
|
||||
} catch (error) {
|
||||
console.error(
|
||||
'ERROR: registerLocalAudioProtocol: Could not get file path:',
|
||||
error
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function registerShortcuts () {
|
||||
|
@ -1,26 +1,29 @@
|
||||
<template>
|
||||
<div class="chatContainer" :class="this.$route.path == '/message/'+this.chatid ? 'active' : ''" :id="'id'+chatid" @click="navigate"
|
||||
@mouseover="showDelete = true" @mouseout="showDelete = false">
|
||||
<div class="unread" :style="read ? 'background-color: transparent;' : ''"></div>
|
||||
<div class="avatarContainer">
|
||||
<img v-if='(docid && docid != 0)' class="avatar" :src="`${$store.getters.httpURI}/contactimg?docid=${encodeURIComponent(docid)}&auth=${encodeURIComponent($store.state.password)}`" />
|
||||
<img v-else class="avatar" src="../assets/profile.jpg" />
|
||||
</div>
|
||||
<div class="chatContent">
|
||||
<div class="title">
|
||||
<span class="author">
|
||||
<span class="name" v-html="$options.filters.twemoji(author)"></span>
|
||||
<span v-if="showNum" class="number"> ({{ this.chatid }})</span>
|
||||
</span>
|
||||
<span class="date">{{ date/1000 | moment }}</span>
|
||||
<div class="chatContainer" :class="this.$route.path == '/message/'+this.chatid ? 'active' : ''" :id="'id'+chatid" @click="navigate">
|
||||
<div class="chatWrapper" @mousedown="dragMouseDown" :style="{ left: '-'+slideX+'px' }">
|
||||
<div class="unread" :style="read ? 'background-color: transparent;' : ''"></div>
|
||||
<div class="avatarContainer">
|
||||
<img v-if='(docid && docid != 0)' class="avatar" :src="`${$store.getters.httpURI}/contactimg?docid=${encodeURIComponent(docid)}&auth=${encodeURIComponent($store.state.password)}`" />
|
||||
<img v-else class="avatar" src="../assets/profile.jpg" />
|
||||
</div>
|
||||
<div class="text">
|
||||
<span v-html="trimmedText"></span>
|
||||
</div>
|
||||
<div class="delete" v-show="showDelete" @click="deleteChat">
|
||||
<feather type="x-circle" stroke="rgba(255,0,0,0.7)" :fill="this.$route.path == '/message/'+this.chatid ? '#2284FF' : 'rgba(45,45,45,0.8)'" size="15"></feather>
|
||||
<div class="chatContent">
|
||||
<div class="title">
|
||||
<span class="author">
|
||||
<span class="name" v-html="$options.filters.twemoji(author)"></span>
|
||||
<span v-if="showNum" class="number"> ({{ this.chatid }})</span>
|
||||
<feather type="bell-off" stroke="rgb(85,85,85)" size="13" v-if="$store.state.mutedChats.includes(chatid)"></feather>
|
||||
</span>
|
||||
<span class="date">{{ date/1000 | moment }}</span>
|
||||
</div>
|
||||
<div class="text">
|
||||
<span v-html="trimmedText"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="slidableDiv" :style="{ left: 'calc(100% - '+slideX+'px)' }">
|
||||
<div class="hideAlerts" @click="hideAlerts">{{ $store.state.mutedChats.includes(chatid) ? 'Unhide Alerts' : 'Hide Alerts' }}</div>
|
||||
<div class="deleteChat" @click="deleteChat">Delete</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -40,7 +43,10 @@ export default {
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
showDelete: false
|
||||
initialX: 0,
|
||||
slideX: 0,
|
||||
closingSlider: false,
|
||||
sliderWidth: 120,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@ -80,7 +86,62 @@ export default {
|
||||
this.$router.push('/message/'+this.chatid)
|
||||
}
|
||||
},
|
||||
dragMouseDown (e) {
|
||||
e.preventDefault()
|
||||
if (this.closingSlider) return
|
||||
this.initialX = e.clientX
|
||||
if (this.slideX > 0) {
|
||||
this.closeDragElement()
|
||||
}
|
||||
|
||||
document.onmousemove = this.elementDrag
|
||||
document.onmouseup = this.finishDragElement
|
||||
},
|
||||
elementDrag (e) {
|
||||
e.preventDefault()
|
||||
if (this.closingSlider) return
|
||||
if (this.initialX - e.clientX < 0 || this.initialX - e.clientX > this.sliderWidth) return
|
||||
this.slideX = this.initialX - e.clientX
|
||||
},
|
||||
closeDragElement () {
|
||||
this.closingSlider = true
|
||||
let interval = setInterval(() => {
|
||||
if (this.slideX <= 0) {
|
||||
this.closingSlider = false
|
||||
this.slideX = 0
|
||||
this.initialX = 0
|
||||
clearInterval(interval)
|
||||
return
|
||||
}
|
||||
|
||||
this.slideX -= 3
|
||||
}, 6)
|
||||
|
||||
document.onmouseup = null
|
||||
document.onmousemove = null
|
||||
},
|
||||
finishDragElement () {
|
||||
if (this.closingSlider) return
|
||||
if (this.slideX >= (this.sliderWidth/2.2)) {
|
||||
let interval = setInterval(() => {
|
||||
if (this.closingSlider) return
|
||||
if (this.slideX >= this.sliderWidth) {
|
||||
this.slideX = this.sliderWidth
|
||||
clearInterval(interval)
|
||||
return
|
||||
}
|
||||
|
||||
this.slideX += 3
|
||||
}, 6)
|
||||
} else {
|
||||
this.closeDragElement()
|
||||
}
|
||||
|
||||
document.onmouseup = null
|
||||
document.onmousemove = null
|
||||
},
|
||||
deleteChat () {
|
||||
this.closeDragElement()
|
||||
this.$confirm({
|
||||
title: 'Warning',
|
||||
message: `Are you sure you want to delete your messages from ${this.author}? This cannot be undone.`,
|
||||
@ -95,6 +156,10 @@ export default {
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
hideAlerts () {
|
||||
this.closeDragElement()
|
||||
this.$store.commit(this.$store.state.mutedChats.includes(this.chatid) ? 'unmuteChat' : 'muteChat', this.chatid)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -112,23 +177,6 @@ export default {
|
||||
margin-top: 26px;
|
||||
}
|
||||
|
||||
.delete {
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
right: 4px;
|
||||
padding: 0px 3px;
|
||||
cursor: pointer;
|
||||
|
||||
display: inherit !important; /* override v-show display: none */
|
||||
transition: opacity 0.2s;
|
||||
|
||||
&[style*="display: none;"] {
|
||||
opacity: 0;
|
||||
pointer-events: none; /* disable user interaction */
|
||||
user-select: none; /* disable user selection */
|
||||
}
|
||||
}
|
||||
|
||||
.chatContent {
|
||||
float: right;
|
||||
// padding: 0px;
|
||||
@ -168,12 +216,17 @@ export default {
|
||||
|
||||
.number {
|
||||
font-weight: 200;
|
||||
font-size: 12px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
i {
|
||||
padding-left: 4px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.date {
|
||||
font-weight: 200;
|
||||
font-weight: 300;
|
||||
padding-right: 10px;
|
||||
float: right;
|
||||
color: rgb(157,157,157);
|
||||
@ -197,6 +250,8 @@ export default {
|
||||
width: 300px;
|
||||
height: 64px;
|
||||
border-radius: 5px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&.active {
|
||||
background-color: #2284FF;
|
||||
@ -213,5 +268,41 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chatWrapper {
|
||||
height: inherit;
|
||||
position: absolute;
|
||||
width: inherit;
|
||||
}
|
||||
|
||||
.slidableDiv {
|
||||
position: absolute;
|
||||
left: 100%;
|
||||
display: flex;
|
||||
width: 120px;
|
||||
height: 100%;
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
flex: 1 1 0px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
filter: brightness(85%);
|
||||
}
|
||||
}
|
||||
|
||||
.deleteChat {
|
||||
background: #FF3B2F;
|
||||
}
|
||||
|
||||
.hideAlerts {
|
||||
background: #5959D1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -53,7 +53,9 @@
|
||||
:key="'msg'+msg.id+'-text'+ii"
|
||||
:class="(msg.texts.length-1 == ii ? 'last ' : '') + (isEmojis(text.text) ? 'jumbo' : '')"
|
||||
:style="msg.sender == 1 && text.showStamp && (text.read > 0 || text.delivered > 0) ? 'margin-bottom: 0px;' : ''">
|
||||
<span style="white-space: pre-wrap;" v-html="$options.filters.twemoji(text.text)" v-if="!text.undisplayable" v-linkified></span>
|
||||
<div class="subject" v-if="text.subject && text.subject != ''" v-html="$options.filters.twemoji(text.subject)"></div>
|
||||
|
||||
<span style="white-space: pre-wrap;" v-if="!text.undisplayable" v-html="$options.filters.twemoji(text.text)" v-linkified></span>
|
||||
<div style="white-space: pre-wrap;text-align:center;" v-else>
|
||||
<div style="font-weight:500;font-size:14px;">Unsupported Type</div>
|
||||
<div style="font-size:11px;margin-top:4px;">
|
||||
@ -82,18 +84,24 @@
|
||||
{{ attachment.name }}
|
||||
</div>
|
||||
</div>
|
||||
<twemoji-textarea @contentChanged="autoResize" placeholder="Send a message..."
|
||||
:emojiData="emojiDataAll"
|
||||
:emojiGroups="emojiGroups"
|
||||
:initialContent="messageText[$route.params.id]"
|
||||
:class="hasAttachments ? 'withAttachments' : ''"
|
||||
@enterKey="sendText">
|
||||
<template v-slot:twemoji-picker-button>
|
||||
<feather type="smile" fill="rgb(152,152,152)" stroke="rgb(29,29,29)" size="26"></feather>
|
||||
</template>
|
||||
</twemoji-textarea>
|
||||
<img src="@/assets/loading.webp" style="height:22px;" v-if="!canSend" />
|
||||
<div class="msgTextboxWrapper">
|
||||
<input type="text" v-if="$store.state.subjectLine" class="subjectLine" ref="subjectLine" placeholder="Subject" @keyup.enter.exact="sendText" :class="{ noTopBorder: hasAttachments }">
|
||||
<twemoji-textarea @contentChanged="autoResize" :placeholder="this.messages[0].type.replace('SMS', 'Text Message')"
|
||||
:emojiData="emojiDataAll"
|
||||
:emojiGroups="emojiGroups"
|
||||
:initialContent="messageText[$route.params.id]"
|
||||
:class="(hasAttachments || $store.state.subjectLine) ? 'noTopBorder' : ''"
|
||||
@enterKey="sendText">
|
||||
<template v-slot:twemoji-picker-button>
|
||||
<feather type="smile" fill="rgb(152,152,152)" stroke="rgb(29,29,29)" size="26"></feather>
|
||||
</template>
|
||||
</twemoji-textarea>
|
||||
</div>
|
||||
<img src="@/assets/loading.webp" style="height:26px;float:right;" v-if="!canSend" />
|
||||
<upload-button v-show="canSend" ref="uploadButton" :enableiMessageAttachments="this.messages[0] && this.messages[0].type == 'iMessage'" @filesChanged="previewFiles" />
|
||||
<div class="sendBtn" :class="{ cantSend: !canSend }" @click="sendText">
|
||||
<feather type="arrow-up" size="20"></feather>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
@ -173,7 +181,7 @@ export default {
|
||||
const groupDates = (date1, date2) => (date2 - date1 < 3600000)
|
||||
const groupAuthor = (author1, author2) => (author1 == author2)
|
||||
|
||||
const groupedMessages = messages.reduce((r, { text, dateRead, dateDelivered, guid, reactions, payload, ...rest }, i, arr) => {
|
||||
const groupedMessages = messages.reduce((r, { text, subject, dateRead, dateDelivered, guid, reactions, payload, ...rest }, i, arr) => {
|
||||
const prev = arr[i +-1]
|
||||
let extras = { }
|
||||
|
||||
@ -200,9 +208,9 @@ export default {
|
||||
}
|
||||
|
||||
if (prev && groupAuthor(rest.author, prev.author) && groupAuthor(rest.sender, prev.sender) && groupDates(rest.date, prev.date))
|
||||
r[r.length - 1].texts.unshift({ text: text, date: rest.date, attachments: rest.attachments, read: dateRead, delivered: dateDelivered, guid: guid, reactions: reactions, showStamp: rest.sender == 1 && !lastSentMessageFound, ...extras })
|
||||
r[r.length - 1].texts.unshift({ text: text, subject: subject, date: rest.date, attachments: rest.attachments, read: dateRead, delivered: dateDelivered, guid: guid, reactions: reactions, showStamp: rest.sender == 1 && !lastSentMessageFound, ...extras })
|
||||
else
|
||||
r.push({ ...rest, texts: [{ text: text, date: rest.date, attachments: rest.attachments, read: dateRead, delivered: dateDelivered, guid: guid, reactions: reactions, showStamp: rest.sender == 1 && !lastSentMessageFound, ...extras }] })
|
||||
r.push({ ...rest, texts: [{ text: text, subject: subject, date: rest.date, attachments: rest.attachments, read: dateRead, delivered: dateDelivered, guid: guid, reactions: reactions, showStamp: rest.sender == 1 && !lastSentMessageFound, ...extras }] })
|
||||
|
||||
if (rest.sender == 1 && !lastSentMessageFound) lastSentMessageFound = true
|
||||
|
||||
@ -228,6 +236,9 @@ export default {
|
||||
if (this.$refs.uploadButton) {
|
||||
this.$refs.uploadButton.clear()
|
||||
}
|
||||
if (this.$refs.subjectLine) {
|
||||
this.$refs.subjectLine.value = ''
|
||||
}
|
||||
|
||||
this.autoCompleteHooks()
|
||||
this.fetchMessages()
|
||||
@ -312,10 +323,14 @@ export default {
|
||||
this.$nextTick(this.autoCompleteHooks)
|
||||
},
|
||||
isEmojis(msgText) {
|
||||
const regex = /<% RGI_Emoji %>|\p{Emoji_Presentation}|\p{Emoji}\uFE0F|\p{Emoji_Modifier_Base}|[\u180B-\u180D\uFE00-\uFE0F]/gu
|
||||
const regex = /<% RGI_Emoji %>|\p{Emoji_Presentation}|\p{Emoji}\uFE0F|\p{Emoji_Modifier_Base}/gu
|
||||
const variationSelector = /[\u180B-\u180D\uFE00-\uFE0F]|\uDB40[\uDD00-\uDDEF]/gu
|
||||
|
||||
msgText = msgText.replace(/\u{fffc}/gu, "")
|
||||
|
||||
return msgText.replace(' ', '').replace(regex, '').length == 0 && msgText.replace(' ', '').length <= 8 && msgText.replace(' ', '').length != 0
|
||||
return msgText.replace(' ', '').replace(regex, '').replace(variationSelector, '').length == 0
|
||||
&& msgText.replace(' ', '').replace(variationSelector, '').length <= 8
|
||||
&& msgText.replace(' ', '').length != 0
|
||||
},
|
||||
humanReadableDay (date) {
|
||||
let ts = date
|
||||
@ -415,20 +430,29 @@ export default {
|
||||
}})
|
||||
},
|
||||
sendText () {
|
||||
let messageText = this.messageText[this.$route.params.id]
|
||||
if (!messageText) messageText = ''
|
||||
if (messageText == '' && (!this.$refs.uploadButton.attachments || this.$refs.uploadButton.attachments.length == 0)) return
|
||||
if (!this.canSend) return
|
||||
|
||||
let messageText = this.messageText[this.$route.params.id]
|
||||
let subjectText = this.$refs.subjectLine ? this.$refs.subjectLine.value : ''
|
||||
|
||||
if (messageText == '' && subjectText != '') {
|
||||
messageText = subjectText
|
||||
subjectText = ''
|
||||
}
|
||||
|
||||
if (messageText == '' && (!this.$refs.uploadButton.attachments || this.$refs.uploadButton.attachments.length == 0)) return
|
||||
this.canSend = false
|
||||
|
||||
let textObj = {
|
||||
text: messageText,
|
||||
attachments: this.$refs.uploadButton.attachments,
|
||||
address: this.messages[0] ? this.messages[0].chatId : this.receiver
|
||||
address: this.messages[0] ? this.messages[0].chatId : this.receiver,
|
||||
subject: subjectText
|
||||
}
|
||||
|
||||
document.getElementById("twemoji-textarea").innerHTML = ""
|
||||
this.messageText[this.$route.params.id] = ""
|
||||
if (this.$refs.subjectLine) this.$refs.subjectLine.value = ""
|
||||
|
||||
axios.post(this.$store.getters.httpURI+'/sendText', textObj)
|
||||
.then(response => {
|
||||
@ -669,7 +693,7 @@ export default {
|
||||
.attachmentPreview {
|
||||
border: 1px solid #545454;
|
||||
margin-left: 32px;
|
||||
margin-right: 32px;
|
||||
margin-right: 64px;
|
||||
margin-bottom: -1px;
|
||||
border-top-left-radius: 14px;
|
||||
border-top-right-radius: 14px;
|
||||
@ -742,9 +766,41 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.msgTextboxWrapper {
|
||||
.subjectLine {
|
||||
border:none;
|
||||
background-image: none;
|
||||
background-color: transparent;
|
||||
-webkit-box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
|
||||
font-family: 'Roboto', -apple-system, BlinkMacSystemFont, Avenir, Helvetica, Arial, sans-serif;
|
||||
margin-left: 32px;
|
||||
width: calc(100% - 118px);
|
||||
background: #1d1d1d !important;
|
||||
border: 1px solid #545454 !important;
|
||||
border-top-left-radius: 14px;
|
||||
border-top-right-radius: 14px;
|
||||
margin-bottom: -1px;
|
||||
color: white;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
line-height: 20px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.25px;
|
||||
|
||||
&.noTopBorder {
|
||||
border-top-left-radius: 0px;
|
||||
border-top-right-radius: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#twemoji-textarea-outer {
|
||||
background-color: transparent !important;
|
||||
width: calc(100% - 26px);
|
||||
width: calc(100% - 58px);
|
||||
float: left;
|
||||
|
||||
#twemoji-textarea {
|
||||
@ -765,6 +821,7 @@ export default {
|
||||
flex-grow: 90;
|
||||
border-radius: 14px !important;
|
||||
padding-left: 10px !important;
|
||||
padding-right: 10px !important;
|
||||
background: rgba(29,29,29, 1) !important;
|
||||
border: 1px solid #545454 !important;
|
||||
line-height: 21px !important;
|
||||
@ -780,7 +837,7 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
&.withAttachments {
|
||||
&.noTopBorder {
|
||||
#twemoji-textarea {
|
||||
border-top-right-radius: 0px !important;
|
||||
border-top-left-radius: 0px !important;
|
||||
@ -920,8 +977,40 @@ export default {
|
||||
|
||||
.feather {
|
||||
&:hover {
|
||||
fill: lighten(rgb(152,152,152), 20%);
|
||||
cursor: pointer;
|
||||
filter: brightness(115%);
|
||||
}
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sendBtn {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
background: #2284FF;
|
||||
float: right;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
margin-right: 6px;
|
||||
|
||||
svg {
|
||||
stroke: rgb(255,255,255);
|
||||
cursor: initial;
|
||||
}
|
||||
|
||||
&.cantSend {
|
||||
filter: brightness(40%);
|
||||
cursor: initial;
|
||||
}
|
||||
|
||||
&:not(.cantSend) {
|
||||
.feather {
|
||||
cursor: pointer;
|
||||
}
|
||||
&:hover {
|
||||
filter: brightness(115%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1071,6 +1160,12 @@ export default {
|
||||
display: inline-block;
|
||||
text-align: start;
|
||||
unicode-bidi: plaintext;
|
||||
|
||||
.subject {
|
||||
white-space: pre-wrap;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.25px;
|
||||
}
|
||||
}
|
||||
|
||||
.bubbleWrapper {
|
||||
|
@ -6,48 +6,69 @@
|
||||
|
||||
<div class="modal__dialog">
|
||||
<h3>Settings</h3>
|
||||
<input type="password" placeholder="Password" class="textinput" v-model="password" />
|
||||
<input type="text" placeholder="IP Address" class="textinput" v-model="ipAddress" :disabled="this.enableTunnel"/>
|
||||
<div class="tunnelToggle">
|
||||
<feather type="circle" size="20" @click="toggleTunnel" :fill="relayColor" v-popover:tunnel.bottom></feather>
|
||||
<div class="settingsWrapper">
|
||||
<div class="settingsColumn">
|
||||
<h4>Tweak</h4>
|
||||
<input type="password" placeholder="Password" class="textinput" v-model="password" />
|
||||
<input type="text" placeholder="IP Address" class="textinput" v-model="ipAddress" :disabled="this.enableTunnel"/>
|
||||
<div class="tunnelToggle">
|
||||
<feather type="circle" size="20" @click="toggleTunnel" :fill="relayColor" v-popover:tunnel.bottom></feather>
|
||||
</div>
|
||||
<input ref="portField" type="number" placeholder="Port" class="textinput" min="1" max="65535" @keyup="enforceConstraints" v-model="port" />
|
||||
<label class="switch">
|
||||
<input type="checkbox" v-model="ssl">
|
||||
<i></i>
|
||||
<div>Enable SSL</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="settingsColumn">
|
||||
<h4>Client</h4>
|
||||
<label class="switch">
|
||||
<input type="checkbox" v-model="subjectLine">
|
||||
<i></i>
|
||||
<div>Enable subject line</div>
|
||||
</label>
|
||||
<label class="switch">
|
||||
<input type="checkbox" v-model="systemSound">
|
||||
<i></i>
|
||||
<div>Use system notification sound</div>
|
||||
</label>
|
||||
<label class="switch">
|
||||
<input type="checkbox" v-model="cacheMessages">
|
||||
<i></i>
|
||||
<div>Precache messages <span style="color: rgba(255,0,0,0.8);font-size: 12px;">More battery drain</span></div>
|
||||
</label>
|
||||
<label class="switch">
|
||||
<input type="checkbox" v-model="startup">
|
||||
<i></i>
|
||||
<div>Launch on startup</div>
|
||||
</label>
|
||||
<label class="switch">
|
||||
<input type="checkbox" v-model="minimize">
|
||||
<i></i>
|
||||
<div>Keep in tray</div>
|
||||
</label>
|
||||
<label class="switch" v-if="process.platform !== 'darwin'">
|
||||
<input type="checkbox" v-model="macstyle">
|
||||
<i></i>
|
||||
<div>Use macOS style</div>
|
||||
</label>
|
||||
<label class="switch">
|
||||
<input type="checkbox" v-model="acceleration">
|
||||
<i></i>
|
||||
<div>Enable hardware acceleration</div>
|
||||
</label>
|
||||
<label class="file">
|
||||
<div>Select custom notification file:</div>
|
||||
<input type="file" name="soundFile" ref="soundFile" style="display: none;" @change="notifSoundChanged" accept="audio/*">
|
||||
<div class="fileBtn" @click.prevent="$refs.soundFile.click">
|
||||
Browse ({{ this.notifSound.includes('wm-audio') ? 'Default' : this.notifSound.split('/').pop() }})
|
||||
</div>
|
||||
<div class="fileBtn" @click.prevent="notifSound = 'wm-audio://receivedText.mp3'" style="margin-left: 8px;">Reset</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<input ref="portField" type="number" placeholder="Port" class="textinput" min="1" max="65535" @keyup="enforceConstraints" v-model="port" />
|
||||
<label class="switch">
|
||||
<input type="checkbox" v-model="ssl">
|
||||
<i></i>
|
||||
<div>Enable SSL</div>
|
||||
</label>
|
||||
<hr>
|
||||
<label class="switch">
|
||||
<input type="checkbox" v-model="playsound">
|
||||
<i></i>
|
||||
<div>Custom notification sound</div>
|
||||
</label>
|
||||
<label class="switch">
|
||||
<input type="checkbox" v-model="cacheMessages">
|
||||
<i></i>
|
||||
<div>Precache messages <span style="color: rgba(255,0,0,0.8);font-size: 12px;">More battery drain</span></div>
|
||||
</label>
|
||||
<label class="switch">
|
||||
<input type="checkbox" v-model="startup">
|
||||
<i></i>
|
||||
<div>Launch on startup</div>
|
||||
</label>
|
||||
<label class="switch">
|
||||
<input type="checkbox" v-model="minimize">
|
||||
<i></i>
|
||||
<div>Keep in tray</div>
|
||||
</label>
|
||||
<label class="switch" v-if="process.platform !== 'darwin'">
|
||||
<input type="checkbox" v-model="macstyle">
|
||||
<i></i>
|
||||
<div>Use macOS style</div>
|
||||
</label>
|
||||
<label class="switch">
|
||||
<input type="checkbox" v-model="acceleration">
|
||||
<i></i>
|
||||
<div>Enable hardware acceleration</div>
|
||||
</label>
|
||||
|
||||
<a class="btn" v-on:click="saveModal">Save</a>
|
||||
<a v-on:click="closeModal" class="btn destructive">Cancel</a>
|
||||
|
||||
@ -71,7 +92,8 @@ export default {
|
||||
ipAddress: '',
|
||||
port: 8180,
|
||||
ssl: false,
|
||||
playsound: false,
|
||||
subjectLine: false,
|
||||
systemSound: false,
|
||||
launchOnStartup: false,
|
||||
minimize: true,
|
||||
macstyle: true,
|
||||
@ -83,7 +105,8 @@ export default {
|
||||
relayMessage: "Tunneling is currently disabled. Click the circle and ensure your device is attached to enable it.",
|
||||
relayColor: 'rgba(152,152,152,0.5)',
|
||||
enableTunnel: false,
|
||||
cacheMessages: false
|
||||
cacheMessages: false,
|
||||
notifSound: 'wm-audio://receivedText.mp3'
|
||||
}
|
||||
},
|
||||
beforeDestroy () {
|
||||
@ -93,6 +116,14 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
notifSoundChanged(e) {
|
||||
if (e.target.files && e.target.files[0]) {
|
||||
const file = e.target.files[0]
|
||||
const path = 'local-file://'+file.path
|
||||
|
||||
this.notifSound = path
|
||||
}
|
||||
},
|
||||
toggleTunnel () {
|
||||
this.enableTunnel = !this.enableTunnel
|
||||
this.$store.commit('setTunnel', this.enableTunnel)
|
||||
@ -154,12 +185,14 @@ export default {
|
||||
this.$store.commit('setFallbackIPAddress', this.ipAddress)
|
||||
this.$store.commit('setPort', this.port)
|
||||
this.$store.commit('setSSL', this.ssl)
|
||||
this.$store.commit('setPlaySound', this.playsound)
|
||||
this.$store.commit('setSubjectLine', this.subjectLine)
|
||||
this.$store.commit('setSystemSound', this.systemSound)
|
||||
this.$store.commit('setStartup', this.startup)
|
||||
this.$store.commit('setMinimize', this.minimize)
|
||||
this.$store.commit('setMacStyle', this.macstyle)
|
||||
this.$store.commit('setAcceleration', this.acceleration)
|
||||
this.$store.commit('setCacheMessages', this.cacheMessages)
|
||||
this.$store.commit('setNotifSound', this.notifSound)
|
||||
this.show = false
|
||||
if (this.enableTunnel) {
|
||||
this.initTunnel()
|
||||
@ -181,13 +214,15 @@ export default {
|
||||
this.ipAddress = this.$store.state.fallbackIpAddress != '' ? this.$store.state.fallbackIpAddress : this.ipAddress
|
||||
this.port = this.$store.state.port
|
||||
this.ssl = this.$store.state.ssl
|
||||
this.playsound = this.$store.state.playsound
|
||||
this.subjectLine = this.$store.state.subjectLine
|
||||
this.systemSound = this.$store.state.systemSound
|
||||
this.startup = this.$store.state.startup
|
||||
this.minimize = this.$store.state.minimize
|
||||
this.macstyle = this.$store.state.macstyle
|
||||
this.acceleration = this.$store.state.acceleration
|
||||
this.enableTunnel = this.$store.state.enableTunnel
|
||||
this.cacheMessages = this.$store.state.cacheMessages
|
||||
this.notifSound = this.$store.state.notifSound
|
||||
if (this.enableTunnel) {
|
||||
this.initTunnel()
|
||||
}
|
||||
@ -285,13 +320,31 @@ export default {
|
||||
position: relative;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
max-width: 300px;
|
||||
max-width: 650px;
|
||||
margin: auto auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 10px;
|
||||
z-index: 2;
|
||||
|
||||
.settingsWrapper {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
|
||||
.settingsColumn {
|
||||
display: flex;
|
||||
flex: 1 1 0px;
|
||||
flex-direction: column;
|
||||
max-width: 300px;
|
||||
|
||||
h4 {
|
||||
padding-left: 8px;
|
||||
margin: 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.textinput {
|
||||
width: auto;
|
||||
margin: 0px 5px !important;
|
||||
@ -363,6 +416,26 @@ export default {
|
||||
@media screen and (max-width: 992px) {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
input:not([type="range"]):not([type="file"]):not([type="color"]):not(.message-input) {
|
||||
height: auto;
|
||||
height: inherit;
|
||||
font-size: 13px;
|
||||
height: 6px;
|
||||
padding: 10px 6px 10px 6px;
|
||||
outline: none;
|
||||
border: 1px solid rgb(213, 213, 213);
|
||||
margin: 5px;
|
||||
cursor: text;
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
input:not([type="range"]):not([type="file"]):not([type="color"]):not(.message-input):focus {
|
||||
border-radius: 1px;
|
||||
box-shadow: 0px 0px 0px 3.5px rgba(23, 101, 144, 1);
|
||||
animation: showFocus .3s;
|
||||
border-color: rgb(122, 167, 221) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__close {
|
||||
@ -390,6 +463,31 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
label.file {
|
||||
text-align: left;
|
||||
font-size: 15px;
|
||||
margin-bottom: 10px;
|
||||
margin-left: 8px;
|
||||
|
||||
.fileBtn {
|
||||
background: #646462;
|
||||
padding: 4px 8px;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
width: fit-content;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
border: 2px solid rgba(0,0,0,0.6);
|
||||
border-top: 0px;
|
||||
border-left: 0px;
|
||||
max-width: 215px;
|
||||
|
||||
&:hover {
|
||||
filter: brightness(85%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.switch {
|
||||
padding-left: 8px;
|
||||
text-align: left;
|
||||
|
@ -15,14 +15,17 @@ export default new Vuex.Store({
|
||||
fallbackIpAddress: persistentStore.get('fallbackIpAddress', ''),
|
||||
port: persistentStore.get('port', 8180),
|
||||
ssl: persistentStore.get('ssl', true),
|
||||
playsound: persistentStore.get('playsound', true),
|
||||
subjectLine: persistentStore.get('subjectLine', false),
|
||||
systemSound: persistentStore.get('systemSound', false),
|
||||
startup: persistentStore.get('startup', false),
|
||||
minimize: persistentStore.get('minimize', true),
|
||||
macstyle: persistentStore.get('macstyle', true),
|
||||
acceleration: persistentStore.get('acceleration', true),
|
||||
messagesCache: [],
|
||||
enableTunnel: persistentStore.get('enableTunnel', false),
|
||||
cacheMessages: persistentStore.get('cacheMessages', false)
|
||||
cacheMessages: persistentStore.get('cacheMessages', false),
|
||||
mutedChats: persistentStore.get('mutedChats', []),
|
||||
notifSound: persistentStore.get('notifSound', 'wm-audio://receivedText.mp3')
|
||||
},
|
||||
mutations: {
|
||||
setPassword(state, password) {
|
||||
@ -46,9 +49,13 @@ export default new Vuex.Store({
|
||||
state['ssl'] = ssl
|
||||
persistentStore.set('ssl', ssl)
|
||||
},
|
||||
setPlaySound(state, playsound) {
|
||||
state['playsound'] = playsound
|
||||
persistentStore.set('playsound', playsound)
|
||||
setSubjectLine(state, subjectLine) {
|
||||
state['subjectLine'] = subjectLine
|
||||
persistentStore.set('subjectLine', subjectLine)
|
||||
},
|
||||
setSystemSound(state, systemSound) {
|
||||
state['systemSound'] = systemSound
|
||||
persistentStore.set('systemSound', systemSound)
|
||||
},
|
||||
setStartup(state, startup) {
|
||||
state['startup'] = startup
|
||||
@ -71,6 +78,10 @@ export default new Vuex.Store({
|
||||
state['enableTunnel'] = enableTunnel
|
||||
persistentStore.set('enableTunnel', enableTunnel)
|
||||
},
|
||||
setNotifSound(state, notifSound) {
|
||||
state['notifSound'] = notifSound
|
||||
persistentStore.set('notifSound', notifSound)
|
||||
},
|
||||
setCacheMessages(state, cacheMessages) {
|
||||
state['cacheMessages'] = cacheMessages
|
||||
persistentStore.set('cacheMessages', cacheMessages)
|
||||
@ -79,6 +90,19 @@ export default new Vuex.Store({
|
||||
if (!state['messagesCache'][messages.id]) state['messagesCache'][messages.id] = []
|
||||
state['messagesCache'][messages.id] = messages.data
|
||||
},
|
||||
muteChat(state, chatId) {
|
||||
if (state.mutedChats.includes(chatId)) return
|
||||
state.mutedChats.push(chatId)
|
||||
persistentStore.set('mutedChats', state.mutedChats)
|
||||
},
|
||||
unmuteChat(state, chatId) {
|
||||
if (!state.mutedChats.includes(chatId)) return
|
||||
const index = state.mutedChats.indexOf(chatId)
|
||||
if (index > -1) {
|
||||
state.mutedChats.splice(index, 1)
|
||||
}
|
||||
persistentStore.set('mutedChats', state.mutedChats)
|
||||
},
|
||||
resetMessages(state) {
|
||||
state['messagesCache'] = []
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user