Improve autocomplete

This commit is contained in:
syuilo 2020-02-01 07:38:40 +09:00
parent 46b0c5f354
commit 958c59b065
2 changed files with 82 additions and 40 deletions

View file

@ -1,13 +1,14 @@
<template> <template>
<div class="mk-autocomplete" @contextmenu.prevent="() => {}"> <div class="swhvrteh" @contextmenu.prevent="() => {}">
<ol class="users" ref="suggests" v-if="users.length > 0"> <ol class="users" ref="suggests" v-if="type === 'user'">
<li v-for="user in users" @click="complete(type, user)" @keydown="onKeydown" tabindex="-1"> <li v-for="user in users" @click="complete(type, user)" @keydown="onKeydown" tabindex="-1" class="user">
<img class="avatar" :src="user.avatarUrl" alt=""/> <img class="avatar" :src="user.avatarUrl" alt=""/>
<span class="name"> <span class="name">
<mk-user-name :user="user" :key="user.id"/> <mk-user-name :user="user" :key="user.id"/>
</span> </span>
<span class="username">@{{ user | acct }}</span> <span class="username">@{{ user | acct }}</span>
</li> </li>
<li @click="chooseUser()" @keydown="onKeydown" tabindex="-1" class="choose">{{ $t('selectUser') }}</li>
</ol> </ol>
<ol class="hashtags" ref="suggests" v-if="hashtags.length > 0"> <ol class="hashtags" ref="suggests" v-if="hashtags.length > 0">
<li v-for="hashtag in hashtags" @click="complete(type, hashtag)" @keydown="onKeydown" tabindex="-1"> <li v-for="hashtag in hashtags" @click="complete(type, hashtag)" @keydown="onKeydown" tabindex="-1">
@ -32,6 +33,7 @@ import { emojilist } from '../../misc/emojilist';
import contains from '../scripts/contains'; import contains from '../scripts/contains';
import { twemojiSvgBase } from '../../misc/twemoji-base'; import { twemojiSvgBase } from '../../misc/twemoji-base';
import { getStaticImageUrl } from '../scripts/get-static-image-url'; import { getStaticImageUrl } from '../scripts/get-static-image-url';
import MkUserSelect from './user-select.vue';
type EmojiDef = { type EmojiDef = {
emoji: string; emoji: string;
@ -73,7 +75,42 @@ for (const x of lib) {
emjdb.sort((a, b) => a.name.length - b.name.length); emjdb.sort((a, b) => a.name.length - b.name.length);
export default Vue.extend({ export default Vue.extend({
props: ['type', 'q', 'textarea', 'complete', 'close', 'x', 'y'], props: {
type: {
type: String,
required: true,
},
q: {
type: String,
required: false,
},
textarea: {
type: HTMLTextAreaElement,
required: true,
},
complete: {
type: Function,
required: true,
},
close: {
type: Function,
required: true,
},
x: {
type: Number,
required: true,
},
y: {
type: Number,
required: true,
},
},
data() { data() {
return { return {
@ -99,24 +136,12 @@ export default Vue.extend({
}, },
updated() { updated() {
//#region 調 this.setPosition();
if (this.x + this.$el.offsetWidth > window.innerWidth) {
this.$el.style.left = (window.innerWidth - this.$el.offsetWidth) + 'px';
} else {
this.$el.style.left = this.x + 'px';
}
if (this.y + this.$el.offsetHeight > window.innerHeight) {
this.$el.style.top = (this.y - this.$el.offsetHeight) + 'px';
this.$el.style.marginTop = '0';
} else {
this.$el.style.top = this.y + 'px';
this.$el.style.marginTop = 'calc(1em + 8px)';
}
//#endregion
}, },
mounted() { mounted() {
this.setPosition();
//#region Construct Emoji DB //#region Construct Emoji DB
const customEmojis = (this.$root.getMetaSync() || { emojis: [] }).emojis || []; const customEmojis = (this.$root.getMetaSync() || { emojis: [] }).emojis || [];
const emojiDefinitions: EmojiDef[] = []; const emojiDefinitions: EmojiDef[] = [];
@ -149,7 +174,7 @@ export default Vue.extend({
this.textarea.addEventListener('keydown', this.onKeydown); this.textarea.addEventListener('keydown', this.onKeydown);
for (const el of Array.from(document.querySelectorAll('*'))) { for (const el of Array.from(document.querySelectorAll('body *'))) {
el.addEventListener('mousedown', this.onMousedown); el.addEventListener('mousedown', this.onMousedown);
} }
@ -167,12 +192,28 @@ export default Vue.extend({
beforeDestroy() { beforeDestroy() {
this.textarea.removeEventListener('keydown', this.onKeydown); this.textarea.removeEventListener('keydown', this.onKeydown);
for (const el of Array.from(document.querySelectorAll('*'))) { for (const el of Array.from(document.querySelectorAll('body *'))) {
el.removeEventListener('mousedown', this.onMousedown); el.removeEventListener('mousedown', this.onMousedown);
} }
}, },
methods: { methods: {
setPosition() {
if (this.x + this.$el.offsetWidth > window.innerWidth) {
this.$el.style.left = (window.innerWidth - this.$el.offsetWidth) + 'px';
} else {
this.$el.style.left = this.x + 'px';
}
if (this.y + this.$el.offsetHeight > window.innerHeight) {
this.$el.style.top = (this.y - this.$el.offsetHeight) + 'px';
this.$el.style.marginTop = '0';
} else {
this.$el.style.top = this.y + 'px';
this.$el.style.marginTop = 'calc(1em + 8px)';
}
},
exec() { exec() {
this.select = -1; this.select = -1;
if (this.$refs.suggests) { if (this.$refs.suggests) {
@ -182,6 +223,12 @@ export default Vue.extend({
} }
if (this.type == 'user') { if (this.type == 'user') {
if (this.q == null) {
this.users = [];
this.fetching = false;
return;
}
const cacheKey = `autocomplete:user:${this.q}`; const cacheKey = `autocomplete:user:${this.q}`;
const cache = sessionStorage.getItem(cacheKey); const cache = sessionStorage.getItem(cacheKey);
if (cache) { if (cache) {
@ -323,13 +370,21 @@ export default Vue.extend({
this.items[this.select].setAttribute('data-selected', 'true'); this.items[this.select].setAttribute('data-selected', 'true');
(this.items[this.select] as any).focus(); (this.items[this.select] as any).focus();
},
chooseUser() {
this.close();
const vm = this.$root.new(MkUserSelect, {});
vm.$once('selected', user => {
this.complete('user', user);
});
} }
} }
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.mk-autocomplete { .swhvrteh {
position: fixed; position: fixed;
z-index: 65535; z-index: 65535;
max-width: 100%; max-width: 100%;
@ -402,18 +457,6 @@ export default Vue.extend({
.name { .name {
margin: 0 8px 0 0; margin: 0 8px 0 0;
color: var(--autocompleteItemText);
}
.username {
color: var(--autocompleteItemTextSub);
}
}
> .hashtags > li {
.name {
color: var(--autocompleteItemText);
} }
} }
@ -430,13 +473,8 @@ export default Vue.extend({
} }
} }
.name {
color: var(--autocompleteItemText);
}
.alias { .alias {
margin: 0 0 0 8px; margin: 0 0 0 8px;
color: var(--autocompleteItemTextSub);
} }
} }
} }

View file

@ -99,6 +99,9 @@ class Autocomplete {
if (username != '' && username.match(/^[a-zA-Z0-9_]+$/)) { if (username != '' && username.match(/^[a-zA-Z0-9_]+$/)) {
this.open('user', username); this.open('user', username);
opened = true; opened = true;
} else if (username === '') {
this.open('user', null);
opened = true;
} }
} }
@ -126,7 +129,7 @@ class Autocomplete {
/** /**
* *
*/ */
private async open(type, q) { private async open(type: string, q: string) {
if (type != this.currentType) { if (type != this.currentType) {
this.close(); this.close();
} }
@ -144,6 +147,7 @@ class Autocomplete {
//#endregion //#endregion
if (this.suggestion) { if (this.suggestion) {
// TODO: Vueの警告が出るのでなんとかする
this.suggestion.x = x; this.suggestion.x = x;
this.suggestion.y = y; this.suggestion.y = y;
this.suggestion.q = q; this.suggestion.q = q;