Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/spreed.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDaniel Calviño Sánchez <danxuliu@gmail.com>2020-01-08 04:23:05 +0300
committerJoas Schilling <coding@schilljs.com>2020-01-09 22:19:18 +0300
commitc728282147a1c376630902fbe7bf0d3f3d3b5cb6 (patch)
tree3c6f1be316b50e9104f906c113e42f398380fe81 /src
parent920deedec88bdd8ceb3fdc3c382ebb0a1630b32e (diff)
Add basic autocompletion of mentions
The vue-at component is used to provide the autocompletion UI. This component handles the edition in its wrapped contenteditable, so the "vue-contenteditable-directive" is no longer needed. As the new message form hides its overflowing children (and it can not be removed, as it breaks the layout when typing long (tall) messages) the "vue-at-reparenter" helper is used. In this initial version the candidate mentions show the id of the user or guest that matches the current "@" mention being written and, when selected, adds that id as plain text to the editable content. Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
Diffstat (limited to 'src')
-rw-r--r--src/components/NewMessageForm/AdvancedInput/AdvancedInput.vue70
-rw-r--r--src/components/NewMessageForm/NewMessageForm.vue1
-rw-r--r--src/main.js2
-rw-r--r--src/mainChatTab.js2
-rw-r--r--src/services/mentionsService.js42
5 files changed, 107 insertions, 10 deletions
diff --git a/src/components/NewMessageForm/AdvancedInput/AdvancedInput.vue b/src/components/NewMessageForm/AdvancedInput/AdvancedInput.vue
index d5304e835..4dcf557ee 100644
--- a/src/components/NewMessageForm/AdvancedInput/AdvancedInput.vue
+++ b/src/components/NewMessageForm/AdvancedInput/AdvancedInput.vue
@@ -20,19 +20,33 @@
-->
<template>
- <div ref="contentEditable"
- v-contenteditable:text.dangerousHTML="activeInput"
- :placeHolder="placeholderText"
- class="new-message-form__advancedinput"
- @keydown.enter="handleKeydown"
- @paste="onPaste" />
+ <At ref="at"
+ v-model="text"
+ :members="autoCompleteMentionCandidates"
+ @at="handleAtEvent">
+ <div ref="contentEditable"
+ :contenteditable="activeInput"
+ :placeHolder="placeholderText"
+ class="new-message-form__advancedinput"
+ @keydown.enter="handleKeydown"
+ @paste="onPaste" />
+ </At>
</template>
<script>
+import At from 'vue-at'
+import VueAtReparenter from '../../../mixins/vueAtReparenter'
import { EventBus } from '../../../services/EventBus'
+import { searchPossibleMentions } from '../../../services/mentionsService'
export default {
name: 'AdvancedInput',
+ components: {
+ At,
+ },
+ mixins: [
+ VueAtReparenter,
+ ],
props: {
/**
* The placeholder for the input field
@@ -54,10 +68,19 @@ export default {
type: String,
required: true,
},
+
+ /**
+ * The token of the conversation to get candidate mentions for.
+ */
+ token: {
+ type: String,
+ required: true,
+ },
},
data: function() {
return {
text: '',
+ autoCompleteMentionCandidates: [],
}
},
watch: {
@@ -69,6 +92,14 @@ export default {
value(value) {
this.text = value
},
+ atwho(atwho) {
+ if (!atwho) {
+ // Clear mention candidates when closing the panel. Otherwise
+ // they would be shown when the panel is opened again until the
+ // new ones are received.
+ this.autoCompleteMentionCandidates = []
+ }
+ },
},
mounted() {
this.focusInput()
@@ -107,12 +138,39 @@ export default {
* @param {object} event the event object;
*/
handleKeydown(event) {
+ // Prevent submit event when vue-at panel is open, as that should
+ // just select the mention from the panel.
+ if (this.atwho) {
+ return
+ }
+
// TODO: add support for CTRL+ENTER new line
if (!(event.shiftKey)) {
event.preventDefault()
this.$emit('submit', event)
}
},
+
+ /**
+ * Sets the autocomplete mention candidates based on the matched text
+ * after the "@".
+ *
+ * @param {String} chunk the matched text to look candidate mentions for.
+ */
+ async handleAtEvent(chunk) {
+ const response = await searchPossibleMentions(this.token, chunk)
+ const possibleMentions = response.data.ocs.data
+
+ // Wrap mention ids with spaces in quotes.
+ possibleMentions.forEach(possibleMention => {
+ if (possibleMention.id.indexOf(' ') !== -1
+ || possibleMention.id.indexOf('guest/') === 0) {
+ possibleMention.id = '"' + possibleMention.id + '"'
+ }
+ })
+
+ this.autoCompleteMentionCandidates = possibleMentions.map(possibleMention => possibleMention.id)
+ },
},
}
</script>
diff --git a/src/components/NewMessageForm/NewMessageForm.vue b/src/components/NewMessageForm/NewMessageForm.vue
index e29dc0e92..0a6dd36d4 100644
--- a/src/components/NewMessageForm/NewMessageForm.vue
+++ b/src/components/NewMessageForm/NewMessageForm.vue
@@ -40,6 +40,7 @@
v-bind="messageToBeReplied" />
<AdvancedInput
v-model="text"
+ :token="token"
@submit="handleSubmit" />
</div>
<button
diff --git a/src/main.js b/src/main.js
index 7230aebf5..f3756d7fa 100644
--- a/src/main.js
+++ b/src/main.js
@@ -38,7 +38,6 @@ import { generateFilePath } from '@nextcloud/router'
import { getRequestToken } from '@nextcloud/auth'
// Directives
-import contenteditableDirective from 'vue-contenteditable-directive'
import VueClipboard from 'vue-clipboard2'
import { translate, translatePlural } from '@nextcloud/l10n'
import VueObserveVisibility from 'vue-observe-visibility'
@@ -60,7 +59,6 @@ Vue.prototype.n = translatePlural
Vue.prototype.OC = OC
Vue.prototype.OCA = OCA
-Vue.use(contenteditableDirective)
Vue.use(Vuex)
Vue.use(VueRouter)
Vue.use(VueClipboard)
diff --git a/src/mainChatTab.js b/src/mainChatTab.js
index 955ebcd6d..4ddc04384 100644
--- a/src/mainChatTab.js
+++ b/src/mainChatTab.js
@@ -34,7 +34,6 @@ import { generateFilePath } from '@nextcloud/router'
import { getRequestToken } from '@nextcloud/auth'
// Directives
-import contenteditableDirective from 'vue-contenteditable-directive'
import { translate, translatePlural } from '@nextcloud/l10n'
// CSP config for webpack dynamic chunk loading
@@ -53,7 +52,6 @@ Vue.prototype.n = translatePlural
Vue.prototype.OC = OC
Vue.prototype.OCA = OCA
-Vue.use(contenteditableDirective)
Vue.use(Vuex)
const newTab = () => new Vue({
diff --git a/src/services/mentionsService.js b/src/services/mentionsService.js
new file mode 100644
index 000000000..0c1b8db5c
--- /dev/null
+++ b/src/services/mentionsService.js
@@ -0,0 +1,42 @@
+/**
+ *
+ * @copyright Copyright (c) 2020, Daniel Calviño Sánchez <danxuliu@gmail.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+import axios from '@nextcloud/axios'
+import { generateOcsUrl } from '@nextcloud/router'
+
+/**
+ * Fetch possible mentions
+ *
+ * @param {string} token The token of the conversation.
+ * @param {string} searchText The string that will be used in the search query.
+ */
+const searchPossibleMentions = async function(token, searchText) {
+ try {
+ const response = await axios.get(generateOcsUrl('apps/spreed/api/v1/chat', 2) + `${token}/mentions?search=${searchText}`)
+ return response
+ } catch (error) {
+ console.debug('Error while searching possible mentions: ', error)
+ }
+}
+
+export {
+ searchPossibleMentions,
+}