diff --git a/package.json b/package.json
index 0e755c1..f73aef1 100644
--- a/package.json
+++ b/package.json
@@ -23,7 +23,6 @@
"@fortawesome/free-solid-svg-icons": "^5.15.2",
"@fortawesome/vue-fontawesome": "^3.0.0-3",
"@techassi/vue-lazy-image": "^1.0.0",
- "@trevoreyre/autocomplete-vue": "^2.2.0",
"auto-launch": "^5.0.5",
"axios": "^0.21.1",
"bplist-parser": "^0.3.0",
@@ -40,18 +39,15 @@
"jquery": "^3.6.0",
"moment": "^2.29.1",
"node-notifier": "^9.0.1",
+ "powertoast": "^1.2.3",
"rangy": "^1.3.0",
- "simplebar-vue": "^1.6.0",
"usbmux": "^0.1.0",
"vue": "^3.0.0",
- "vue-avatar": "^2.3.3",
"vue-class-component": "^8.0.0-0",
- "vue-confirm-dialog": "^1.0.2",
"vue-feather": "^2.0.0-beta",
"vue-js-popover": "^1.2.1",
"vue-linkify": "^1.0.1",
"vue-long-click": "^0.0.4",
- "vue-native-websocket": "^2.0.14",
"vue-native-websocket-vue3": "^3.1.0",
"vue-router": "^4.0.0-0",
"vuex": "^4.0.0-0"
@@ -61,6 +57,7 @@
"@types/electron-devtools-installer": "^2.2.0",
"@types/electron-localshortcut": "^3.1.0",
"@types/jquery": "^3.5.5",
+ "@types/node-notifier": "^8.0.0",
"@types/rangy": "^0.0.33",
"@typescript-eslint/eslint-plugin": "^2.33.0",
"@typescript-eslint/parser": "^2.33.0",
diff --git a/src/background.ts b/src/background.ts
index 62be015..fa99c3f 100644
--- a/src/background.ts
+++ b/src/background.ts
@@ -182,6 +182,8 @@ function showWin() {
else {
win.setSkipTaskbar(false)
win.show()
+ win.setAlwaysOnTop(true)
+ win.setAlwaysOnTop(false) // weird work around but it works
}
if (app.dock) app.dock.show()
@@ -267,12 +269,6 @@ ipcMain.on('loaded', () => {
registerShortcuts()
})
-// ipcMain.on('notification', (event, opts) => {
-// Notifier.notify("hello??")
-// Notifier.notify(opts)
-// console.log("notifying with", opts)
-// })
-
ipcMain.on('rightClickMessage', (event, args) => {
rightClickedMessage = args
})
@@ -292,7 +288,7 @@ ipcMain.on('startup_check', () => {
autoLauncher
.isEnabled()
.then(function(isEnabled) {
- const optionEnabled = persistentStore.get('startup', false)
+ const optionEnabled = persistentStore.get('launchOnStartup', false)
if (optionEnabled && !isEnabled) {
autoLauncher.enable()
@@ -317,11 +313,15 @@ ipcMain.on('quit_app', () => {
app.quit()
})
-ipcMain.on('minimizeToTray', () => {
+function minimizeToTray () {
if (!win) return
win.setSkipTaskbar(true)
win.hide()
if (app.dock) app.dock.hide()
+}
+
+ipcMain.on('minimizeToTray', () => {
+ minimizeToTray()
})
ipcMain.on('show_win', () => {
@@ -351,7 +351,16 @@ function registerLocalFileProtocols() {
}
})
- // protocol.registerHttpProtocol('WebMessage', (request, callback) => {})
+ app.removeAsDefaultProtocolClient('webmessage');
+
+ // If we are running a non-packaged version of the app && on windows
+ if(isDevelopment && process.platform === 'win32') {
+ // Set the path of electron.exe and your app.
+ // These two additional parameters are only available on windows.
+ app.setAsDefaultProtocolClient('webmessage', process.execPath, [path.resolve(process.argv[1])])
+ } else {
+ app.setAsDefaultProtocolClient('webmessage');
+ }
}
const gotTheLock = app.requestSingleInstanceLock()
@@ -359,8 +368,12 @@ const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
app.quit()
} else {
- app.on('second-instance', () => {
+ app.on('second-instance', (event, args) => {
showWin()
+ let uriLocation = args.pop() as string
+ let personId = uriLocation.split(':').pop()
+ if (!personId || personId.trim() == '') return
+ if (win) win.webContents.send('navigateChat', personId)
})
app.whenReady().then(() => {
diff --git a/src/components/AutoComplete/AutocompleteCore.js b/src/components/AutoComplete/AutocompleteCore.js
new file mode 100644
index 0000000..e32ff0c
--- /dev/null
+++ b/src/components/AutoComplete/AutocompleteCore.js
@@ -0,0 +1,186 @@
+import closest from './util/closest.js'
+import isPromise from './util/isPromise.js'
+
+class AutocompleteCore {
+ value = ''
+ searchCounter = 0
+ results = []
+ selectedIndex = -1
+
+ constructor({
+ search,
+ autoSelect = false,
+ setValue = () => {},
+ setAttribute = () => {},
+ onUpdate = () => {},
+ onSubmit = () => {},
+ onShow = () => {},
+ onHide = () => {},
+ onLoading = () => {},
+ onLoaded = () => {},
+ } = {}) {
+ this.search = isPromise(search) ? search : value => Promise.resolve(search(value))
+ this.autoSelect = autoSelect
+ this.setValue = setValue
+ this.setAttribute = setAttribute
+ this.onUpdate = onUpdate
+ this.onSubmit = onSubmit
+ this.onShow = onShow
+ this.onHide = onHide
+ this.onLoading = onLoading
+ this.onLoaded = onLoaded
+ }
+
+ destroy = () => {
+ this.search = null
+ this.setValue = null
+ this.setAttribute = null
+ this.onUpdate = null
+ this.onSubmit = null
+ this.onShow = null
+ this.onHide = null
+ this.onLoading = null
+ this.onLoaded = null
+ }
+
+ handleInput = event => {
+ const { value } = event.target
+ this.updateResults(value)
+ this.value = value
+ }
+
+ handleKeyDown = event => {
+ const { key } = event
+
+ switch (key) {
+ case 'Up': // IE/Edge
+ case 'Down': // IE/Edge
+ case 'ArrowUp':
+ case 'ArrowDown': {
+ const selectedIndex = key === 'ArrowUp' || key === 'Up' ? this.selectedIndex - 1 : this.selectedIndex + 1
+ event.preventDefault()
+ this.handleArrows(selectedIndex)
+ break
+ }
+ case 'Tab': {
+ this.selectResult()
+ break
+ }
+ case 'Enter': {
+ const selectedResult = this.results[this.selectedIndex]
+ this.selectResult()
+ this.onSubmit(selectedResult)
+ break
+ }
+ case 'Esc': // IE/Edge
+ case 'Escape': {
+ this.hideResults()
+ this.setValue()
+ break
+ }
+ default:
+ return
+ }
+ }
+
+ handleFocus = event => {
+ const { value } = event.target
+ this.updateResults(value)
+ this.value = value
+ }
+
+ handleBlur = () => {
+ this.hideResults()
+ }
+
+ // The mousedown event fires before the blur event. Calling preventDefault() when
+ // the results list is clicked will prevent it from taking focus, firing the
+ // blur event on the input element, and closing the results list before click fires.
+ handleResultMouseDown = event => {
+ event.preventDefault()
+ }
+
+ handleResultClick = event => {
+ const { target } = event
+ const result = closest(target, '[data-result-index]')
+ if (result) {
+ this.selectedIndex = parseInt(result.dataset.resultIndex, 10)
+ const selectedResult = this.results[this.selectedIndex]
+ this.selectResult()
+ this.onSubmit(selectedResult)
+ }
+ }
+
+ handleArrows = selectedIndex => {
+ // Loop selectedIndex back to first or last result if out of bounds
+ const resultsCount = this.results.length
+ this.selectedIndex = ((selectedIndex % resultsCount) + resultsCount) % resultsCount
+
+ // Update results and aria attributes
+ this.onUpdate(this.results, this.selectedIndex)
+ }
+
+ selectResult = () => {
+ const selectedResult = this.results[this.selectedIndex]
+ if (selectedResult) {
+ this.setValue(selectedResult)
+ }
+ this.hideResults()
+ }
+
+ updateResults = value => {
+ const currentSearch = ++this.searchCounter
+ this.onLoading()
+ this.search(value).then(results => {
+ if (currentSearch !== this.searchCounter) {
+ return
+ }
+ this.results = results
+ this.onLoaded()
+
+ if (this.results.length === 0) {
+ this.hideResults()
+ return
+ }
+
+ this.selectedIndex = this.autoSelect ? 0 : -1
+ this.onUpdate(this.results, this.selectedIndex)
+ this.showResults()
+ })
+ }
+
+ showResults = () => {
+ this.setAttribute('aria-expanded', true)
+ this.onShow()
+ }
+
+ hideResults = () => {
+ this.selectedIndex = -1
+ this.results = []
+ this.setAttribute('aria-expanded', false)
+ this.setAttribute('aria-activedescendant', '')
+ this.onUpdate(this.results, this.selectedIndex)
+ this.onHide()
+ }
+
+ // Make sure selected result isn't scrolled out of view
+ checkSelectedResultVisible = resultsElement => {
+ const selectedResultElement = resultsElement.querySelector(`[data-result-index="${this.selectedIndex}"]`)
+ if (!selectedResultElement) {
+ return
+ }
+
+ const resultsPosition = resultsElement.getBoundingClientRect()
+ const selectedPosition = selectedResultElement.getBoundingClientRect()
+
+ if (selectedPosition.top < resultsPosition.top) {
+ // Element is above viewable area
+ resultsElement.scrollTop -= resultsPosition.top - selectedPosition.top
+ } else if (selectedPosition.bottom > resultsPosition.bottom) {
+ // Element is below viewable area
+ resultsElement.scrollTop += selectedPosition.bottom - resultsPosition.bottom
+ }
+ }
+}
+
+export default AutocompleteCore
diff --git a/src/components/AutoComplete/index.vue b/src/components/AutoComplete/index.vue
new file mode 100644
index 0000000..61db2c4
--- /dev/null
+++ b/src/components/AutoComplete/index.vue
@@ -0,0 +1,337 @@
+// Forked from trevoreyre/autocomplete
+
+
+
+
+
+
+
+
+
+ -
+ {{ getResultValue(result) }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/AutoComplete/util/closest.js b/src/components/AutoComplete/util/closest.js
new file mode 100644
index 0000000..5f2a038
--- /dev/null
+++ b/src/components/AutoComplete/util/closest.js
@@ -0,0 +1,22 @@
+// Polyfill for element.closest, to support Internet Explorer. It's a relatively
+// simple polyfill, so we'll just include it rather than require the user to
+// include the polyfill themselves. Adapted from
+// https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill
+import matches from './matches.js'
+
+const closestPolyfill = (el, selector) => {
+ let element = el
+ while (element && element.nodeType === 1) {
+ if (matches(element, selector)) {
+ return element
+ }
+ element = element.parentNode
+ }
+ return null
+}
+
+const closest = (element, selector) => {
+ return element.closest ? element.closest(selector) : closestPolyfill(element, selector)
+}
+
+export default closest
diff --git a/src/components/AutoComplete/util/debounce.js b/src/components/AutoComplete/util/debounce.js
new file mode 100644
index 0000000..7769a36
--- /dev/null
+++ b/src/components/AutoComplete/util/debounce.js
@@ -0,0 +1,29 @@
+// Credit David Walsh (https://davidwalsh.name/javascript-debounce-function)
+
+// Returns a function, that, as long as it continues to be invoked, will not
+// be triggered. The function will be called after it stops being called for
+// N milliseconds. If `immediate` is passed, trigger the function on the
+// leading edge, instead of the trailing.
+const debounce = (func, wait, immediate) => {
+ let timeout
+
+ return function executedFunction() {
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
+ const context = this
+ // eslint-disable-next-line prefer-rest-params
+ const args = arguments
+
+ const later = function() {
+ timeout = null
+ if (!immediate) func.apply(context, args)
+ }
+
+ const callNow = immediate && !timeout
+ clearTimeout(timeout)
+ timeout = setTimeout(later, wait)
+
+ if (callNow) func.apply(context, args)
+ }
+}
+
+export default debounce
diff --git a/src/components/AutoComplete/util/getRelativePosition.js b/src/components/AutoComplete/util/getRelativePosition.js
new file mode 100644
index 0000000..6b00d74
--- /dev/null
+++ b/src/components/AutoComplete/util/getRelativePosition.js
@@ -0,0 +1,18 @@
+// Calculates whether element2 should be above or below element1. Always
+// places element2 below unless all of the following:
+// 1. There isn't enough visible viewport below to fit element2
+// 2. There is more room above element1 than there is below
+// 3. Placing elemen2 above 1 won't overflow window
+const getRelativePosition = (element1, element2) => {
+ const position1 = element1.getBoundingClientRect()
+ const position2 = element2.getBoundingClientRect()
+
+ const positionAbove =
+ /* 1 */ position1.bottom + position2.height > window.innerHeight &&
+ /* 2 */ window.innerHeight - position1.bottom < position1.top &&
+ /* 3 */ window.pageYOffset + position1.top - position2.height > 0
+
+ return positionAbove ? 'above' : 'below'
+}
+
+export default getRelativePosition
diff --git a/src/components/AutoComplete/util/isPromise.js b/src/components/AutoComplete/util/isPromise.js
new file mode 100644
index 0000000..b5a21e1
--- /dev/null
+++ b/src/components/AutoComplete/util/isPromise.js
@@ -0,0 +1,5 @@
+// Returns true if the value has a "then" function. Adapted from
+// https://github.com/graphql/graphql-js/blob/499a75939f70c4863d44149371d6a99d57ff7c35/src/jsutils/isPromise.js
+const isPromise = value => Boolean(value && typeof value.then === 'function')
+
+export default isPromise
diff --git a/src/components/AutoComplete/util/matches.js b/src/components/AutoComplete/util/matches.js
new file mode 100644
index 0000000..ddf187b
--- /dev/null
+++ b/src/components/AutoComplete/util/matches.js
@@ -0,0 +1,15 @@
+// Polyfill for element.matches, to support Internet Explorer. It's a relatively
+// simple polyfill, so we'll just include it rather than require the user to
+// include the polyfill themselves. Adapted from
+// https://developer.mozilla.org/en-US/docs/Web/API/Element/matches#Polyfill
+const matches = (element, selector) => {
+ return element.matches
+ ? element.matches(selector)
+ : element.msMatchesSelector
+ ? element.msMatchesSelector(selector)
+ : element.webkitMatchesSelector
+ ? element.webkitMatchesSelector(selector)
+ : null
+}
+
+export default matches
diff --git a/src/components/AutoComplete/util/uniqueId.js b/src/components/AutoComplete/util/uniqueId.js
new file mode 100644
index 0000000..f6f183d
--- /dev/null
+++ b/src/components/AutoComplete/util/uniqueId.js
@@ -0,0 +1,6 @@
+// Generates a unique ID, with optional prefix. Adapted from
+// https://github.com/lodash/lodash/blob/61acdd0c295e4447c9c10da04e287b1ebffe452c/uniqueId.js
+let idCounter = 0
+const uniqueId = (prefix = '') => `${prefix}${++idCounter}`
+
+export default uniqueId
diff --git a/src/components/PayloadAttachment.vue b/src/components/PayloadAttachment.vue
index e91a96f..72c7277 100644
--- a/src/components/PayloadAttachment.vue
+++ b/src/components/PayloadAttachment.vue
@@ -188,7 +188,7 @@ export default {
align-items: center;
justify-content: center;
- >>> .feather {
+ .feather:deep() {
margin-left: 1px;
}
}
diff --git a/src/components/Settings.vue b/src/components/Settings.vue
index 7e83732..2ae53c2 100644
--- a/src/components/Settings.vue
+++ b/src/components/Settings.vue
@@ -364,6 +364,7 @@ export default {
mounted() {
this.loadValues()
+ ipcRenderer.send('startup_check')
ipcRenderer.send('app_version')
ipcRenderer.on('app_version', (event, arg) => {
ipcRenderer.removeAllListeners('app_version')
diff --git a/src/store/index.ts b/src/store/index.ts
index b47ff14..c67e5b9 100644
--- a/src/store/index.ts
+++ b/src/store/index.ts
@@ -123,7 +123,6 @@ export default createStore({
state['isTypingTimer'][data.chatId] = setTimeout(() => {
state['isTyping'][data.chatId] = false
- console.log('closing')
}, 60000)
},
},
diff --git a/src/types/modules.d.ts b/src/types/modules.d.ts
index dba5133..939851e 100644
--- a/src/types/modules.d.ts
+++ b/src/types/modules.d.ts
@@ -6,17 +6,12 @@ declare module 'vue-linkify'
declare module 'vue-confirm-dialog'
declare module 'vue-long-click'
declare module 'vue-js-popover'
+declare module 'powertoast'
declare module 'bplist-parser'
declare module '@techassi/vue-lazy-image'
declare module './mixin'
-declare module '@trevoreyre/autocomplete-vue'
-declare module '@/components/VideoPlayer'
-declare module '@/components/ExpandableImage'
-declare module '@/components/DownloadAttachment'
-declare module '@/components/UploadButton'
-declare module '@/components/ReactionMenu'
-declare module '@/components/ReactionBubbles'
+declare module '@trevoreyre/autocomplete-vue/index.js'
declare module '@/views/Home'
declare module '@/views/Chat'
declare module '@/views/Blank'
diff --git a/src/views/Chat/index.vue b/src/views/Chat/index.vue
index 68e945f..368312e 100644
--- a/src/views/Chat/index.vue
+++ b/src/views/Chat/index.vue
@@ -281,21 +281,19 @@