mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-18 07:07:35 +01:00
Begin adding server-side autocomplete to web client
This commit is contained in:
16
package-lock.json
generated
16
package-lock.json
generated
@@ -1701,7 +1701,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"sax": {
|
"sax": {
|
||||||
"version": "1.2.1",
|
"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="
|
"integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o="
|
||||||
},
|
},
|
||||||
"uuid": {
|
"uuid": {
|
||||||
@@ -17240,7 +17240,7 @@
|
|||||||
},
|
},
|
||||||
"chalk": {
|
"chalk": {
|
||||||
"version": "1.1.3",
|
"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=",
|
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-styles": "^2.2.1",
|
"ansi-styles": "^2.2.1",
|
||||||
@@ -21931,7 +21931,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"css-select": {
|
"css-select": {
|
||||||
"version": "1.2.0",
|
"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=",
|
"integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"boolbase": "~1.0.0",
|
"boolbase": "~1.0.0",
|
||||||
@@ -24684,6 +24684,11 @@
|
|||||||
"punycode": "^1.4.1"
|
"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": {
|
"trim-leading-lines": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/trim-leading-lines/-/trim-leading-lines-0.1.1.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.6.0.tgz",
|
||||||
"integrity": "sha512-x3LV3wdmmERhVCYy3quqA57NJW7F3i6faas++pJQWtknWT+n7k30F4TVdHvCLn48peTJFRvCpxs3UuFPqgeELg=="
|
"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": {
|
"vuedraggable": {
|
||||||
"version": "2.16.0",
|
"version": "2.16.0",
|
||||||
"resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-2.16.0.tgz",
|
"resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-2.16.0.tgz",
|
||||||
|
|||||||
@@ -90,6 +90,7 @@
|
|||||||
"svg-url-loader": "^2.3.2",
|
"svg-url-loader": "^2.3.2",
|
||||||
"svgo": "^1.0.5",
|
"svgo": "^1.0.5",
|
||||||
"svgo-loader": "^2.1.0",
|
"svgo-loader": "^2.1.0",
|
||||||
|
"tributejs": "^3.4.0",
|
||||||
"universal-analytics": "^0.4.16",
|
"universal-analytics": "^0.4.16",
|
||||||
"update": "^0.7.4",
|
"update": "^0.7.4",
|
||||||
"upgrade": "^1.1.0",
|
"upgrade": "^1.1.0",
|
||||||
@@ -104,6 +105,7 @@
|
|||||||
"vue-router": "^3.0.0",
|
"vue-router": "^3.0.0",
|
||||||
"vue-style-loader": "^4.1.0",
|
"vue-style-loader": "^4.1.0",
|
||||||
"vue-template-compiler": "^2.5.16",
|
"vue-template-compiler": "^2.5.16",
|
||||||
|
"vue-tribute": "^1.0.1",
|
||||||
"vuedraggable": "^2.15.0",
|
"vuedraggable": "^2.15.0",
|
||||||
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#5d237615463a84a23dd6f3f77c6ab577d68593ec",
|
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#5d237615463a84a23dd6f3f77c6ab577d68593ec",
|
||||||
"webpack": "^3.12.0",
|
"webpack": "^3.12.0",
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ div
|
|||||||
span.mr-1(v-if="msg.username") @{{ msg.username }}
|
span.mr-1(v-if="msg.username") @{{ msg.username }}
|
||||||
span.mr-1(v-if="msg.username") •
|
span.mr-1(v-if="msg.username") •
|
||||||
span(v-b-tooltip="", :title="msg.timestamp | date") {{ msg.timestamp | timeAgo }}
|
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
|
hr
|
||||||
.d-flex(v-if='msg.id')
|
.d-flex(v-if='msg.id')
|
||||||
.action.d-flex.align-items-center(v-if='!inbox', @click='copyAsTodo(msg)')
|
.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) || '';
|
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: {
|
methods: {
|
||||||
async like () {
|
async like () {
|
||||||
let message = cloneDeep(this.msg);
|
let message = cloneDeep(this.msg);
|
||||||
@@ -299,7 +309,8 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
parseMarkdown (text) {
|
parseMarkdown (text) {
|
||||||
return habiticaMarkdown.render(text);
|
const mdText = habiticaMarkdown.render(text);
|
||||||
|
return mdText;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,23 +4,17 @@
|
|||||||
h3(v-once) {{ label }}
|
h3(v-once) {{ label }}
|
||||||
|
|
||||||
.row
|
.row
|
||||||
textarea(:placeholder='placeholder',
|
vue-tribute(:options="autocompleteOptions")
|
||||||
v-model='newMessage',
|
textarea(:placeholder='placeholder',
|
||||||
ref='user-entry',
|
v-model='newMessage',
|
||||||
:class='{"user-entry": newMessage}',
|
ref='user-entry',
|
||||||
@keydown='updateCarretPosition',
|
:class='{"user-entry": newMessage}',
|
||||||
@keyup.ctrl.enter='sendMessageShortcut()',
|
@keydown='updateCarretPosition',
|
||||||
@paste='disableMessageSendShortcut()',
|
@keyup.ctrl.enter='sendMessageShortcut()',
|
||||||
maxlength='3000'
|
@paste='disableMessageSendShortcut()',
|
||||||
)
|
maxlength='3000'
|
||||||
|
)
|
||||||
span {{ currentLength }} / 3000
|
span {{ currentLength }} / 3000
|
||||||
autocomplete(
|
|
||||||
:text='newMessage',
|
|
||||||
v-on:select="selectedAutocomplete",
|
|
||||||
:textbox='textbox',
|
|
||||||
:coords='coords',
|
|
||||||
:caretPosition = 'caretPosition',
|
|
||||||
:chat='group.chat')
|
|
||||||
|
|
||||||
.row.chat-actions
|
.row.chat-actions
|
||||||
.col-6.chat-receive-actions
|
.col-6.chat-receive-actions
|
||||||
@@ -42,17 +36,29 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
|
import VueTribute from 'vue-tribute';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
import autocomplete from '../chat/autoComplete';
|
|
||||||
import communityGuidelines from './communityGuidelines';
|
import communityGuidelines from './communityGuidelines';
|
||||||
import chatMessage from '../chat/chatMessages';
|
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 {
|
export default {
|
||||||
props: ['label', 'group', 'placeholder'],
|
props: ['label', 'group', 'placeholder'],
|
||||||
components: {
|
components: {
|
||||||
autocomplete,
|
|
||||||
communityGuidelines,
|
communityGuidelines,
|
||||||
chatMessage,
|
chatMessage,
|
||||||
|
VueTribute,
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
@@ -67,6 +73,34 @@
|
|||||||
LEFT: 0,
|
LEFT: 0,
|
||||||
},
|
},
|
||||||
textbox: this.$refs,
|
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: {
|
computed: {
|
||||||
@@ -140,16 +174,19 @@
|
|||||||
}, 500);
|
}, 500);
|
||||||
},
|
},
|
||||||
|
|
||||||
selectedAutocomplete (newText) {
|
|
||||||
this.newMessage = newText;
|
|
||||||
},
|
|
||||||
|
|
||||||
fetchRecentMessages () {
|
fetchRecentMessages () {
|
||||||
this.$emit('fetchRecentMessages');
|
this.$emit('fetchRecentMessages');
|
||||||
},
|
},
|
||||||
reverseChat () {
|
reverseChat () {
|
||||||
this.group.chat.reverse();
|
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) {
|
beforeRouteUpdate (to, from, next) {
|
||||||
// Reset chat
|
// 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>
|
</style>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export async function highlightMentions (text) {
|
|||||||
.exec();
|
.exec();
|
||||||
members.forEach((member) => {
|
members.forEach((member) => {
|
||||||
const username = member.auth.local.username;
|
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;
|
return text;
|
||||||
|
|||||||
Reference in New Issue
Block a user