Begin adding server-side autocomplete to web client

This commit is contained in:
Phillip Thelen
2018-11-26 15:47:22 +01:00
parent aae2fb1cb9
commit e9c86622c5
5 changed files with 161 additions and 28 deletions

16
package-lock.json generated
View File

@@ -1701,7 +1701,7 @@
"dependencies": {
"sax": {
"version": "1.2.1",
"resolved": "http://registry.npmjs.org/sax/-/sax-1.2.1.tgz",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz",
"integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o="
},
"uuid": {
@@ -17240,7 +17240,7 @@
},
"chalk": {
"version": "1.1.3",
"resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"requires": {
"ansi-styles": "^2.2.1",
@@ -21931,7 +21931,7 @@
"dependencies": {
"css-select": {
"version": "1.2.0",
"resolved": "http://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
"integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=",
"requires": {
"boolbase": "~1.0.0",
@@ -24684,6 +24684,11 @@
"punycode": "^1.4.1"
}
},
"tributejs": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/tributejs/-/tributejs-3.4.0.tgz",
"integrity": "sha512-BWB2YvfKpa6hZgcP9hKN5/tH3P/Guspn4r+ePgwNpftnQwMb6GVWTUgBpkMtVXkR5dwLLcP/iW87i9C1mp21zQ=="
},
"trim-leading-lines": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/trim-leading-lines/-/trim-leading-lines-0.1.1.tgz",
@@ -26042,6 +26047,11 @@
"resolved": "https://registry.npmjs.org/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.6.0.tgz",
"integrity": "sha512-x3LV3wdmmERhVCYy3quqA57NJW7F3i6faas++pJQWtknWT+n7k30F4TVdHvCLn48peTJFRvCpxs3UuFPqgeELg=="
},
"vue-tribute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/vue-tribute/-/vue-tribute-1.0.1.tgz",
"integrity": "sha1-ThJfdoEjxUBd4izjQ4NqmaCVRz0="
},
"vuedraggable": {
"version": "2.16.0",
"resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-2.16.0.tgz",

View File

@@ -90,6 +90,7 @@
"svg-url-loader": "^2.3.2",
"svgo": "^1.0.5",
"svgo-loader": "^2.1.0",
"tributejs": "^3.4.0",
"universal-analytics": "^0.4.16",
"update": "^0.7.4",
"upgrade": "^1.1.0",
@@ -104,6 +105,7 @@
"vue-router": "^3.0.0",
"vue-style-loader": "^4.1.0",
"vue-template-compiler": "^2.5.16",
"vue-tribute": "^1.0.1",
"vuedraggable": "^2.15.0",
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#5d237615463a84a23dd6f3f77c6ab577d68593ec",
"webpack": "^3.12.0",

View File

@@ -16,7 +16,7 @@ div
span.mr-1(v-if="msg.username") @{{ msg.username }}
span.mr-1(v-if="msg.username")
span(v-b-tooltip="", :title="msg.timestamp | date") {{ msg.timestamp | timeAgo }}
.text(v-html='atHighlight(parseMarkdown(msg.text))')
.text(v-html='atHighlight(parseMarkdown(msg.text))', ref='markdownContainer')
hr
.d-flex(v-if='msg.id')
.action.d-flex.align-items-center(v-if='!inbox', @click='copyAsTodo(msg)')
@@ -244,6 +244,16 @@ export default {
return achievementsLib.getContribText(message.contributor, message.backer) || '';
},
},
mounted () {
const links = this.$refs.markdownContainer.getElementsByTagName('a');
for (var i = 0; i < links.length; i++) {
const link = links[i];
links[i].onclick = (event) => {
event.preventDefault();
this.$router.push({ path: link.getAttribute('href')});
};
}
},
methods: {
async like () {
let message = cloneDeep(this.msg);
@@ -299,7 +309,8 @@ export default {
});
},
parseMarkdown (text) {
return habiticaMarkdown.render(text);
const mdText = habiticaMarkdown.render(text);
return mdText;
},
},
};

View File

@@ -4,23 +4,17 @@
h3(v-once) {{ label }}
.row
textarea(:placeholder='placeholder',
v-model='newMessage',
ref='user-entry',
:class='{"user-entry": newMessage}',
@keydown='updateCarretPosition',
@keyup.ctrl.enter='sendMessageShortcut()',
@paste='disableMessageSendShortcut()',
maxlength='3000'
)
vue-tribute(:options="autocompleteOptions")
textarea(:placeholder='placeholder',
v-model='newMessage',
ref='user-entry',
:class='{"user-entry": newMessage}',
@keydown='updateCarretPosition',
@keyup.ctrl.enter='sendMessageShortcut()',
@paste='disableMessageSendShortcut()',
maxlength='3000'
)
span {{ currentLength }} / 3000
autocomplete(
:text='newMessage',
v-on:select="selectedAutocomplete",
:textbox='textbox',
:coords='coords',
:caretPosition = 'caretPosition',
:chat='group.chat')
.row.chat-actions
.col-6.chat-receive-actions
@@ -42,17 +36,29 @@
<script>
import debounce from 'lodash/debounce';
import VueTribute from 'vue-tribute';
import axios from 'axios';
import autocomplete from '../chat/autoComplete';
import communityGuidelines from './communityGuidelines';
import chatMessage from '../chat/chatMessages';
import styleHelper from 'client/mixins/styleHelper';
import tier1 from 'assets/svg/tier-1.svg';
import tier2 from 'assets/svg/tier-2.svg';
import tier3 from 'assets/svg/tier-3.svg';
import tier4 from 'assets/svg/tier-4.svg';
import tier5 from 'assets/svg/tier-5.svg';
import tier6 from 'assets/svg/tier-6.svg';
import tier7 from 'assets/svg/tier-7.svg';
import tier8 from 'assets/svg/tier-mod.svg';
import tier9 from 'assets/svg/tier-staff.svg';
import tierNPC from 'assets/svg/tier-npc.svg';
export default {
props: ['label', 'group', 'placeholder'],
components: {
autocomplete,
communityGuidelines,
chatMessage,
VueTribute,
},
data () {
return {
@@ -67,6 +73,34 @@
LEFT: 0,
},
textbox: this.$refs,
icons: Object.freeze({
tier1,
tier2,
tier3,
tier4,
tier5,
tier6,
tier7,
tier8,
tier9,
tierNPC,
}),
autocompleteOptions: {
async values (text, cb) {
let suggestions = await axios.get(`/api/v4/members/find/${text}`);
cb(suggestions.data.data);
},
selectTemplate (item) {
return `@${item.original.auth.local.username}`;
},
lookup (item) {
return item.auth.local.username;
},
menuItemTemplate (item) {
let userTierClass = styleHelper.methods.userLevelStyle(item.original);
return `<h3 class='profile-name ${userTierClass}'> ${item.original.profile.name}</h3> @${item.string}`;
},
},
};
},
computed: {
@@ -140,16 +174,19 @@
}, 500);
},
selectedAutocomplete (newText) {
this.newMessage = newText;
},
fetchRecentMessages () {
this.$emit('fetchRecentMessages');
},
reverseChat () {
this.group.chat.reverse();
},
tierIcon (user) {
const isNPC = Boolean(user.backer && user.backer.npc);
if (isNPC) {
return this.icons.tierNPC;
}
return this.icons[`tier${user.contributor.level}`];
},
},
beforeRouteUpdate (to, from, next) {
// Reset chat
@@ -230,4 +267,77 @@
}
}
.v-tribute {
width: 100%;
}
</style>
<style lang="scss">
@import '~client/assets/scss/tiers.scss';
@import '~client/assets/scss/colors.scss';
.tribute-container {
position: absolute;
top: 0;
left: 0;
height: auto;
max-height: 400px;
max-width: 600px;
overflow: auto;
display: block;
z-index: 999999;
border-radius: 4px;
box-shadow: 0 1px 4px rgba(#000, 0.13);
}
.tribute-container ul {
margin: 0;
margin-top: 2px;
padding: 0;
list-style: none;
background: #fff;
border-radius: 4px;
border: 1px solid rgba(#000, 0.13);
background-clip: padding-box;
overflow: hidden;
-moz-transition: none;
-webkit-transition: none;
transition: none;
}
.tribute-container li {
color: $gray-200;
padding: 12px 24px;
cursor: pointer;
font-size: 14px;
-moz-transition: none;
-webkit-transition: none;
transition: none;
}
.tribute-container li.highlight,
.tribute-container li:hover {
background-color: rgba(213, 200, 255, 0.32);
color: $purple-300;
}
.tribute-container li span {
font-weight: bold;
}
.tribute-container li.no-match {
cursor: default;
}
.profile-name {
display: inline-block;
font-size: 16px;
margin-bottom: 0rem;
}
.tier-svg-icon {
width: 10px;
display: inline-block;
margin-left: .5em;
}
</style>

View File

@@ -15,7 +15,7 @@ export async function highlightMentions (text) {
.exec();
members.forEach((member) => {
const username = member.auth.local.username;
text = text.replace(new RegExp(`@${username}\\b`, 'g'), `[@${username}](https://habitica.com/members/${member._id})`);
text = text.replace(new RegExp(`@${username}\\b`, 'g'), `[@${username}](/members/${member._id})`);
});
}
return text;