This commit is contained in:
syuilo
2018-07-18 07:19:24 +09:00
parent d2a5f4c5c1
commit df20f5063d
7 changed files with 187 additions and 18 deletions

View File

@ -7,6 +7,11 @@
<span class="username">@{{ user | acct }}</span>
</li>
</ol>
<ol class="hashtags" ref="suggests" v-if="hashtags.length > 0">
<li v-for="hashtag in hashtags" @click="complete(type, hashtag)" @keydown="onKeydown" tabindex="-1">
<span class="name">{{ hashtag }}</span>
</li>
</ol>
<ol class="emojis" ref="suggests" v-if="emojis.length > 0">
<li v-for="emoji in emojis" @click="complete(type, emoji.emoji)" @keydown="onKeydown" tabindex="-1">
<span class="emoji">{{ emoji.emoji }}</span>
@ -48,33 +53,33 @@ emjdb.sort((a, b) => a.name.length - b.name.length);
export default Vue.extend({
props: ['type', 'q', 'textarea', 'complete', 'close', 'x', 'y'],
data() {
return {
fetching: true,
users: [],
hashtags: [],
emojis: [],
select: -1,
emojilib
}
},
computed: {
items(): HTMLCollection {
return (this.$refs.suggests as Element).children;
}
},
updated() {
//#region 位置調整
const margin = 32;
if (this.x + this.$el.offsetWidth > window.innerWidth - margin) {
this.$el.style.left = (this.x - this.$el.offsetWidth) + 'px';
this.$el.style.marginLeft = '-16px';
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';
this.$el.style.marginLeft = '0';
}
if (this.y + this.$el.offsetHeight > window.innerHeight - margin) {
if (this.y + this.$el.offsetHeight > window.innerHeight) {
this.$el.style.top = (this.y - this.$el.offsetHeight) + 'px';
this.$el.style.marginTop = '0';
} else {
@ -83,6 +88,7 @@ export default Vue.extend({
}
//#endregion
},
mounted() {
this.textarea.addEventListener('keydown', this.onKeydown);
@ -100,6 +106,7 @@ export default Vue.extend({
});
});
},
beforeDestroy() {
this.textarea.removeEventListener('keydown', this.onKeydown);
@ -107,6 +114,7 @@ export default Vue.extend({
el.removeEventListener('mousedown', this.onMousedown);
});
},
methods: {
exec() {
this.select = -1;
@ -117,7 +125,8 @@ export default Vue.extend({
}
if (this.type == 'user') {
const cache = sessionStorage.getItem(this.q);
const cacheKey = 'autocomplete:user:' + this.q;
const cache = sessionStorage.getItem(cacheKey);
if (cache) {
const users = JSON.parse(cache);
this.users = users;
@ -131,7 +140,26 @@ export default Vue.extend({
this.fetching = false;
// キャッシュ
sessionStorage.setItem(this.q, JSON.stringify(users));
sessionStorage.setItem(cacheKey, JSON.stringify(users));
});
}
} else if (this.type == 'hashtag') {
const cacheKey = 'autocomplete:hashtag:' + this.q;
const cache = sessionStorage.getItem(cacheKey);
if (cache) {
const hashtags = JSON.parse(cache);
this.hashtags = hashtags;
this.fetching = false;
} else {
(this as any).api('hashtags/search', {
query: this.q,
limit: 30
}).then(hashtags => {
this.hashtags = hashtags;
this.fetching = false;
// キャッシュ
sessionStorage.setItem(cacheKey, JSON.stringify(hashtags));
});
}
} else if (this.type == 'emoji') {
@ -260,6 +288,8 @@ root(isDark)
user-select none
&:hover
background isDark ? rgba(#fff, 0.1) : rgba(#000, 0.1)
&[data-selected='true']
background $theme-color
@ -292,6 +322,14 @@ root(isDark)
vertical-align middle
color isDark ? rgba(#fff, 0.3) : rgba(#000, 0.3)
> .hashtags > li
.name
vertical-align middle
margin 0 8px 0 0
color isDark ? rgba(#fff, 0.8) : rgba(#000, 0.8)
> .emojis > li
.emoji
@ -300,11 +338,11 @@ root(isDark)
width 24px
.name
color rgba(#000, 0.8)
color isDark ? rgba(#fff, 0.8) : rgba(#000, 0.8)
.alias
margin 0 0 0 8px
color rgba(#000, 0.3)
color isDark ? rgba(#fff, 0.3) : rgba(#000, 0.3)
.mk-autocomplete[data-darkmode]
root(true)

View File

@ -67,15 +67,27 @@ class Autocomplete {
* テキスト入力時
*/
private onInput() {
const caret = this.textarea.selectionStart;
const text = this.text.substr(0, caret);
const caretPos = this.textarea.selectionStart;
const text = this.text.substr(0, caretPos);
const mentionIndex = text.lastIndexOf('@');
const hashtagIndex = text.lastIndexOf('#');
const emojiIndex = text.lastIndexOf(':');
const start = Math.min(
mentionIndex == -1 ? Infinity : mentionIndex,
hashtagIndex == -1 ? Infinity : hashtagIndex,
emojiIndex == -1 ? Infinity : emojiIndex);
if (start == Infinity) return;
const isMention = mentionIndex == start;
const isHashtag = hashtagIndex == start;
const isEmoji = emojiIndex == start;
let opened = false;
if (mentionIndex != -1 && mentionIndex > emojiIndex) {
if (isMention) {
const username = text.substr(mentionIndex + 1);
if (username != '' && username.match(/^[a-zA-Z0-9_]+$/)) {
this.open('user', username);
@ -83,7 +95,15 @@ class Autocomplete {
}
}
if (emojiIndex != -1 && emojiIndex > mentionIndex) {
if (isHashtag || opened == false) {
const hashtag = text.substr(hashtagIndex + 1);
if (hashtag != '' && !hashtag.includes(' ') && !hashtag.includes('\n')) {
this.open('hashtag', hashtag);
opened = true;
}
}
if (isEmoji || opened == false) {
const emoji = text.substr(emojiIndex + 1);
if (emoji != '' && emoji.match(/^[\+\-a-z0-9_]+$/)) {
this.open('emoji', emoji);
@ -173,6 +193,22 @@ class Autocomplete {
const pos = trimmedBefore.length + (value.username.length + 2);
this.textarea.setSelectionRange(pos, pos);
});
} else if (type == 'hashtag') {
const source = this.text;
const before = source.substr(0, caret);
const trimmedBefore = before.substring(0, before.lastIndexOf('#'));
const after = source.substr(caret);
// 挿入
this.text = trimmedBefore + '#' + value + ' ' + after;
// キャレットを戻す
this.vm.$nextTick(() => {
this.textarea.focus();
const pos = trimmedBefore.length + (value.length + 2);
this.textarea.setSelectionRange(pos, pos);
});
} else if (type == 'emoji') {
const source = this.text;

View File

@ -16,7 +16,7 @@
<a @click="addVisibleUser">+%i18n:@add-visible-user%</a>
</div>
<input v-show="useCw" v-model="cw" placeholder="%i18n:@cw-placeholder%">
<textarea v-model="text" ref="text" :disabled="posting" :placeholder="placeholder"></textarea>
<textarea v-model="text" ref="text" :disabled="posting" :placeholder="placeholder" v-autocomplete="'text'"></textarea>
<div class="attaches" v-show="files.length != 0">
<x-draggable class="files" :list="files" :options="{ animation: 150 }">
<div class="file" v-for="file in files" :key="file.id">