mirror of
https://github.com/paricafe/misskey.git
synced 2024-11-24 15:16:44 -06:00
feat(client): 投稿フォームのボタンの説明を表示するように (#6408)
* Add title attr with buttons on the post form * fix * tooltip * missing ; * remove title attr * fix bug * Update reactions-viewer.details.vue * help wip * ok! * i18n Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
This commit is contained in:
parent
f2964101d1
commit
111eb43fd9
13 changed files with 207 additions and 105 deletions
|
@ -45,6 +45,7 @@ loadMore: "もっと見る"
|
||||||
youGotNewFollower: "フォローされました"
|
youGotNewFollower: "フォローされました"
|
||||||
receiveFollowRequest: "フォローリクエストされました"
|
receiveFollowRequest: "フォローリクエストされました"
|
||||||
followRequestAccepted: "フォローが承認されました"
|
followRequestAccepted: "フォローが承認されました"
|
||||||
|
mention: "メンション"
|
||||||
mentions: "あなた宛て"
|
mentions: "あなた宛て"
|
||||||
directNotes: "ダイレクト投稿"
|
directNotes: "ダイレクト投稿"
|
||||||
importAndExport: "インポートとエクスポート"
|
importAndExport: "インポートとエクスポート"
|
||||||
|
@ -104,6 +105,7 @@ suspendConfirm: "凍結しますか?"
|
||||||
unsuspendConfirm: "解凍しますか?"
|
unsuspendConfirm: "解凍しますか?"
|
||||||
selectList: "リストを選択"
|
selectList: "リストを選択"
|
||||||
customEmojis: "カスタム絵文字"
|
customEmojis: "カスタム絵文字"
|
||||||
|
emoji: "絵文字"
|
||||||
emojiName: "絵文字名"
|
emojiName: "絵文字名"
|
||||||
emojiUrl: "絵文字画像URL"
|
emojiUrl: "絵文字画像URL"
|
||||||
addEmoji: "絵文字を追加"
|
addEmoji: "絵文字を追加"
|
||||||
|
@ -510,6 +512,9 @@ serviceworkerInfo: "プッシュ通知を行うには有効する必要があり
|
||||||
deletedNote: "削除された投稿"
|
deletedNote: "削除された投稿"
|
||||||
invisibleNote: "非公開の投稿"
|
invisibleNote: "非公開の投稿"
|
||||||
enableInfiniteScroll: "自動でもっと見る"
|
enableInfiniteScroll: "自動でもっと見る"
|
||||||
|
visibility: "公開範囲"
|
||||||
|
poll: "アンケート"
|
||||||
|
useCw: "内容を隠す"
|
||||||
|
|
||||||
_theme:
|
_theme:
|
||||||
explore: "テーマを探す"
|
explore: "テーマを探す"
|
||||||
|
@ -648,7 +653,6 @@ _cw:
|
||||||
show: "もっと見る"
|
show: "もっと見る"
|
||||||
chars: "{count}文字"
|
chars: "{count}文字"
|
||||||
files: "{count}ファイル"
|
files: "{count}ファイル"
|
||||||
poll: "アンケート"
|
|
||||||
|
|
||||||
_poll:
|
_poll:
|
||||||
noOnlyOneChoice: "選択肢は最低2つ必要です"
|
noOnlyOneChoice: "選択肢は最低2つ必要です"
|
||||||
|
@ -1119,3 +1123,4 @@ _notification:
|
||||||
youReceivedFollowRequest: "フォローリクエストが来ました"
|
youReceivedFollowRequest: "フォローリクエストが来ました"
|
||||||
yourFollowRequestAccepted: "フォローリクエストが承認されました"
|
yourFollowRequestAccepted: "フォローリクエストが承認されました"
|
||||||
youWereInvitedToGroup: "グループに招待されました"
|
youWereInvitedToGroup: "グループに招待されました"
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ export default Vue.extend({
|
||||||
return concat([
|
return concat([
|
||||||
this.note.text ? [this.$t('_cw.chars', { count: length(this.note.text) })] : [],
|
this.note.text ? [this.$t('_cw.chars', { count: length(this.note.text) })] : [],
|
||||||
this.note.files && this.note.files.length !== 0 ? [this.$t('_cw.files', { count: this.note.files.length }) ] : [],
|
this.note.files && this.note.files.length !== 0 ? [this.$t('_cw.files', { count: this.note.files.length }) ] : [],
|
||||||
this.note.poll != null ? [this.$t('_cw.poll')] : []
|
this.note.poll != null ? [this.$t('poll')] : []
|
||||||
] as string[][]).join(' / ');
|
] as string[][]).join(' / ');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -62,13 +62,13 @@ export default Vue.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onMouseover() {
|
onMouseover() {
|
||||||
if (isDeviceTouch()) return;
|
if (isDeviceTouch) return;
|
||||||
clearTimeout(this.showTimer);
|
clearTimeout(this.showTimer);
|
||||||
clearTimeout(this.hideTimer);
|
clearTimeout(this.hideTimer);
|
||||||
this.showTimer = setTimeout(this.showPreview, 500);
|
this.showTimer = setTimeout(this.showPreview, 500);
|
||||||
},
|
},
|
||||||
onMouseleave() {
|
onMouseleave() {
|
||||||
if (isDeviceTouch()) return;
|
if (isDeviceTouch) return;
|
||||||
clearTimeout(this.showTimer);
|
clearTimeout(this.showTimer);
|
||||||
clearTimeout(this.hideTimer);
|
clearTimeout(this.hideTimer);
|
||||||
this.hideTimer = setTimeout(this.closePreview, 500);
|
this.hideTimer = setTimeout(this.closePreview, 500);
|
||||||
|
|
|
@ -9,13 +9,13 @@
|
||||||
<button v-if="!fixed" class="cancel _button" @click="cancel"><fa :icon="faTimes"/></button>
|
<button v-if="!fixed" class="cancel _button" @click="cancel"><fa :icon="faTimes"/></button>
|
||||||
<div>
|
<div>
|
||||||
<span class="text-count" :class="{ over: trimmedLength(text) > max }">{{ max - trimmedLength(text) }}</span>
|
<span class="text-count" :class="{ over: trimmedLength(text) > max }">{{ max - trimmedLength(text) }}</span>
|
||||||
<button class="_button visibility" @click="setVisibility" ref="visibilityButton">
|
<button class="_button visibility" @click="setVisibility" ref="visibilityButton" v-tooltip="$t('visibility')">
|
||||||
<span v-if="visibility === 'public'"><fa :icon="faGlobe"/></span>
|
<span v-if="visibility === 'public'"><fa :icon="faGlobe"/></span>
|
||||||
<span v-if="visibility === 'home'"><fa :icon="faHome"/></span>
|
<span v-if="visibility === 'home'"><fa :icon="faHome"/></span>
|
||||||
<span v-if="visibility === 'followers'"><fa :icon="faUnlock"/></span>
|
<span v-if="visibility === 'followers'"><fa :icon="faUnlock"/></span>
|
||||||
<span v-if="visibility === 'specified'"><fa :icon="faEnvelope"/></span>
|
<span v-if="visibility === 'specified'"><fa :icon="faEnvelope"/></span>
|
||||||
</button>
|
</button>
|
||||||
<button class="_button localOnly" v-if="visibility !== 'specified'" @click="localOnly = !localOnly" :class="{ active: localOnly }"><fa :icon="faBiohazard"/></button>
|
<button class="_button localOnly" v-if="visibility !== 'specified'" @click="localOnly = !localOnly" :class="{ active: localOnly }" v-tooltip="$t('_visibility.localOnly')"><fa :icon="faBiohazard"/></button>
|
||||||
<button class="submit _buttonPrimary" :disabled="!canPost" @click="post">{{ submitText }}<fa :icon="reply ? faReply : renote ? faQuoteRight : faPaperPlane"/></button>
|
<button class="submit _buttonPrimary" :disabled="!canPost" @click="post">{{ submitText }}<fa :icon="reply ? faReply : renote ? faQuoteRight : faPaperPlane"/></button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
<div v-if="visibility === 'specified'" class="to-specified">
|
<div v-if="visibility === 'specified'" class="to-specified">
|
||||||
<span style="margin-right: 8px;">{{ $t('recipient') }}</span>
|
<span style="margin-right: 8px;">{{ $t('recipient') }}</span>
|
||||||
<div class="visibleUsers">
|
<div class="visibleUsers">
|
||||||
<span v-for="u in visibleUsers">
|
<span v-for="u in visibleUsers" :key="u.id">
|
||||||
<mk-acct :user="u"/>
|
<mk-acct :user="u"/>
|
||||||
<button class="_button" @click="removeVisibleUser(u)"><fa :icon="faTimes"/></button>
|
<button class="_button" @click="removeVisibleUser(u)"><fa :icon="faTimes"/></button>
|
||||||
</span>
|
</span>
|
||||||
|
@ -39,11 +39,11 @@
|
||||||
<x-poll-editor v-if="poll" ref="poll" @destroyed="poll = false" @updated="onPollUpdate()"/>
|
<x-poll-editor v-if="poll" ref="poll" @destroyed="poll = false" @updated="onPollUpdate()"/>
|
||||||
<x-uploader ref="uploader" @uploaded="attachMedia" @change="onChangeUploadings"/>
|
<x-uploader ref="uploader" @uploaded="attachMedia" @change="onChangeUploadings"/>
|
||||||
<footer>
|
<footer>
|
||||||
<button class="_button" @click="chooseFileFrom"><fa :icon="faPhotoVideo"/></button>
|
<button class="_button" @click="chooseFileFrom" v-tooltip="$t('attachFile')"><fa :icon="faPhotoVideo"/></button>
|
||||||
<button class="_button" @click="poll = !poll" :class="{ active: poll }"><fa :icon="faPollH"/></button>
|
<button class="_button" @click="poll = !poll" :class="{ active: poll }" v-tooltip="$t('poll')"><fa :icon="faPollH"/></button>
|
||||||
<button class="_button" @click="useCw = !useCw" :class="{ active: useCw }"><fa :icon="faEyeSlash"/></button>
|
<button class="_button" @click="useCw = !useCw" :class="{ active: useCw }" v-tooltip="$t('useCw')"><fa :icon="faEyeSlash"/></button>
|
||||||
<button class="_button" @click="insertMention"><fa :icon="faAt"/></button>
|
<button class="_button" @click="insertMention" v-tooltip="$t('mention')"><fa :icon="faAt"/></button>
|
||||||
<button class="_button" @click="insertEmoji"><fa :icon="faLaughSquint"/></button>
|
<button class="_button" @click="insertEmoji" v-tooltip="$t('emoji')"><fa :icon="faLaughSquint"/></button>
|
||||||
</footer>
|
</footer>
|
||||||
<input ref="file" class="file _button" type="file" multiple="multiple" @change="onChangeFile"/>
|
<input ref="file" class="file _button" type="file" multiple="multiple" @change="onChangeFile"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -576,7 +576,7 @@ export default Vue.extend({
|
||||||
insertTextAtCursor(this.$refs.text, emoji);
|
insertTextAtCursor(this.$refs.text, emoji);
|
||||||
vm.close();
|
vm.close();
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,27 +1,29 @@
|
||||||
<template>
|
<template>
|
||||||
<transition name="zoom-in-top">
|
<mk-tooltip :source="source" ref="tooltip">
|
||||||
<div class="buebdbiu" ref="popover" v-if="show">
|
<template v-if="users.length <= 10">
|
||||||
<template v-if="users.length <= 10">
|
<b v-for="u in users" :key="u.id" style="margin-right: 12px;">
|
||||||
<b v-for="u in users" :key="u.id" style="margin-right: 12px;">
|
<mk-avatar :user="u" style="width: 24px; height: 24px; margin-right: 2px;"/>
|
||||||
<mk-avatar :user="u" style="width: 24px; height: 24px; margin-right: 2px;"/>
|
<mk-user-name :user="u" :nowrap="false" style="line-height: 24px;"/>
|
||||||
<mk-user-name :user="u" :nowrap="false" style="line-height: 24px;"/>
|
</b>
|
||||||
</b>
|
</template>
|
||||||
</template>
|
<template v-if="10 < users.length">
|
||||||
<template v-if="10 < users.length">
|
<b v-for="u in users" :key="u.id" style="margin-right: 12px;">
|
||||||
<b v-for="u in users" :key="u.id" style="margin-right: 12px;">
|
<mk-avatar :user="u" style="width: 24px; height: 24px; margin-right: 2px;"/>
|
||||||
<mk-avatar :user="u" style="width: 24px; height: 24px; margin-right: 2px;"/>
|
<mk-user-name :user="u" :nowrap="false" style="line-height: 24px;"/>
|
||||||
<mk-user-name :user="u" :nowrap="false" style="line-height: 24px;"/>
|
</b>
|
||||||
</b>
|
<span slot="omitted">+{{ count - 10 }}</span>
|
||||||
<span slot="omitted">+{{ count - 10 }}</span>
|
</template>
|
||||||
</template>
|
</mk-tooltip>
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
import MkTooltip from './ui/tooltip.vue';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
|
components: {
|
||||||
|
MkTooltip
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
reaction: {
|
reaction: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -39,77 +41,11 @@ export default Vue.extend({
|
||||||
required: true,
|
required: true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
show: false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.show = true;
|
|
||||||
|
|
||||||
this.$nextTick(() => {
|
|
||||||
const popover = this.$refs.popover as any;
|
|
||||||
|
|
||||||
if (this.source == null) {
|
|
||||||
this.destroyDom();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const rect = this.source.getBoundingClientRect();
|
|
||||||
|
|
||||||
const x = rect.left + window.pageXOffset + (this.source.offsetWidth / 2);
|
|
||||||
const y = rect.top + window.pageYOffset + this.source.offsetHeight;
|
|
||||||
popover.style.left = (x - 28) + 'px';
|
|
||||||
popover.style.top = (y + 16) + 'px';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
methods: {
|
methods: {
|
||||||
close() {
|
close() {
|
||||||
this.show = false;
|
this.$refs.tooltip.close();
|
||||||
setTimeout(this.destroyDom, 300);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.buebdbiu {
|
|
||||||
z-index: 10000;
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
max-width: 240px;
|
|
||||||
font-size: 0.8em;
|
|
||||||
padding: 6px 8px;
|
|
||||||
background: var(--panel);
|
|
||||||
text-align: center;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,0.25);
|
|
||||||
pointer-events: none;
|
|
||||||
transform-origin: center -16px;
|
|
||||||
|
|
||||||
&:before {
|
|
||||||
content: "";
|
|
||||||
pointer-events: none;
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
top: -28px;
|
|
||||||
left: 12px;
|
|
||||||
border-top: solid 14px transparent;
|
|
||||||
border-right: solid 14px transparent;
|
|
||||||
border-bottom: solid 14px rgba(0,0,0,0.1);
|
|
||||||
border-left: solid 14px transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:after {
|
|
||||||
content: "";
|
|
||||||
pointer-events: none;
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
top: -27px;
|
|
||||||
left: 12px;
|
|
||||||
border-top: solid 14px transparent;
|
|
||||||
border-right: solid 14px transparent;
|
|
||||||
border-bottom: solid 14px var(--panel);
|
|
||||||
border-left: solid 14px transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -4,8 +4,10 @@
|
||||||
:class="{ reacted: note.myReaction == reaction, canToggle }"
|
:class="{ reacted: note.myReaction == reaction, canToggle }"
|
||||||
@click="toggleReaction(reaction)"
|
@click="toggleReaction(reaction)"
|
||||||
v-if="count > 0"
|
v-if="count > 0"
|
||||||
|
@touchstart="onMouseover"
|
||||||
@mouseover="onMouseover"
|
@mouseover="onMouseover"
|
||||||
@mouseleave="onMouseleave"
|
@mouseleave="onMouseleave"
|
||||||
|
@touchend="onMouseleave"
|
||||||
ref="reaction"
|
ref="reaction"
|
||||||
v-particle
|
v-particle
|
||||||
>
|
>
|
||||||
|
@ -90,16 +92,17 @@ export default Vue.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onMouseover() {
|
onMouseover() {
|
||||||
|
if (this.isHovering) return;
|
||||||
this.isHovering = true;
|
this.isHovering = true;
|
||||||
this.detailsTimeoutId = setTimeout(this.openDetails, 300);
|
this.detailsTimeoutId = setTimeout(this.openDetails, 300);
|
||||||
},
|
},
|
||||||
onMouseleave() {
|
onMouseleave() {
|
||||||
|
if (!this.isHovering) return;
|
||||||
this.isHovering = false;
|
this.isHovering = false;
|
||||||
clearTimeout(this.detailsTimeoutId);
|
clearTimeout(this.detailsTimeoutId);
|
||||||
this.closeDetails();
|
this.closeDetails();
|
||||||
},
|
},
|
||||||
openDetails() {
|
openDetails() {
|
||||||
if (this.$root.isMobile) return;
|
|
||||||
this.$root.api('notes/reactions', {
|
this.$root.api('notes/reactions', {
|
||||||
noteId: this.note.id,
|
noteId: this.note.id,
|
||||||
type: this.reaction,
|
type: this.reaction,
|
||||||
|
|
96
src/client/components/ui/tooltip.vue
Normal file
96
src/client/components/ui/tooltip.vue
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
<template>
|
||||||
|
<transition name="zoom-in-top" appear>
|
||||||
|
<div class="buebdbiu" v-if="show">
|
||||||
|
<slot>{{ text }}</slot>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
props: {
|
||||||
|
source: {
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
type: String,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
show: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.show = true;
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (this.source == null) {
|
||||||
|
this.destroyDom();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const rect = this.source.getBoundingClientRect();
|
||||||
|
|
||||||
|
const x = rect.left + window.pageXOffset + (this.source.offsetWidth / 2);
|
||||||
|
const y = rect.top + window.pageYOffset + this.source.offsetHeight;
|
||||||
|
this.$el.style.left = (x - 28) + 'px';
|
||||||
|
this.$el.style.top = (y + 16) + 'px';
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
close() {
|
||||||
|
this.show = false;
|
||||||
|
setTimeout(this.destroyDom, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.buebdbiu {
|
||||||
|
z-index: 11000;
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
max-width: 240px;
|
||||||
|
font-size: 0.8em;
|
||||||
|
padding: 6px 8px;
|
||||||
|
background: var(--panel);
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.25);
|
||||||
|
pointer-events: none;
|
||||||
|
transform-origin: center -16px;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: "";
|
||||||
|
pointer-events: none;
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
top: -28px;
|
||||||
|
left: 12px;
|
||||||
|
border-top: solid 14px transparent;
|
||||||
|
border-right: solid 14px transparent;
|
||||||
|
border-bottom: solid 14px rgba(0,0,0,0.1);
|
||||||
|
border-left: solid 14px transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: "";
|
||||||
|
pointer-events: none;
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
top: -27px;
|
||||||
|
left: 12px;
|
||||||
|
border-top: solid 14px transparent;
|
||||||
|
border-right: solid 14px transparent;
|
||||||
|
border-bottom: solid 14px var(--panel);
|
||||||
|
border-left: solid 14px transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -93,13 +93,13 @@ export default Vue.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onMouseover() {
|
onMouseover() {
|
||||||
if (isDeviceTouch()) return;
|
if (isDeviceTouch) return;
|
||||||
clearTimeout(this.showTimer);
|
clearTimeout(this.showTimer);
|
||||||
clearTimeout(this.hideTimer);
|
clearTimeout(this.hideTimer);
|
||||||
this.showTimer = setTimeout(this.showPreview, 500);
|
this.showTimer = setTimeout(this.showPreview, 500);
|
||||||
},
|
},
|
||||||
onMouseleave() {
|
onMouseleave() {
|
||||||
if (isDeviceTouch()) return;
|
if (isDeviceTouch) return;
|
||||||
clearTimeout(this.showTimer);
|
clearTimeout(this.showTimer);
|
||||||
clearTimeout(this.hideTimer);
|
clearTimeout(this.hideTimer);
|
||||||
this.hideTimer = setTimeout(this.closePreview, 500);
|
this.hideTimer = setTimeout(this.closePreview, 500);
|
||||||
|
|
|
@ -4,9 +4,11 @@ import userPreview from './user-preview';
|
||||||
import autocomplete from './autocomplete';
|
import autocomplete from './autocomplete';
|
||||||
import size from './size';
|
import size from './size';
|
||||||
import particle from './particle';
|
import particle from './particle';
|
||||||
|
import tooltip from './tooltip';
|
||||||
|
|
||||||
Vue.directive('autocomplete', autocomplete);
|
Vue.directive('autocomplete', autocomplete);
|
||||||
Vue.directive('userPreview', userPreview);
|
Vue.directive('userPreview', userPreview);
|
||||||
Vue.directive('user-preview', userPreview);
|
Vue.directive('user-preview', userPreview);
|
||||||
Vue.directive('size', size);
|
Vue.directive('size', size);
|
||||||
Vue.directive('particle', particle);
|
Vue.directive('particle', particle);
|
||||||
|
Vue.directive('tooltip', tooltip);
|
||||||
|
|
62
src/client/directives/tooltip.ts
Normal file
62
src/client/directives/tooltip.ts
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import MkTooltip from '../components/ui/tooltip.vue';
|
||||||
|
import { isDeviceTouch } from '../scripts/is-device-touch';
|
||||||
|
|
||||||
|
const start = isDeviceTouch ? 'touchstart' : 'mouseover';
|
||||||
|
const end = isDeviceTouch ? 'touchend' : 'mouseleave';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
bind(el: HTMLElement, binding, vn) {
|
||||||
|
const self = (el as any)._tooltipDirective_ = {} as any;
|
||||||
|
|
||||||
|
self.text = binding.value as string;
|
||||||
|
self.tag = null;
|
||||||
|
self.showTimer = null;
|
||||||
|
self.hideTimer = null;
|
||||||
|
self.checkTimer = null;
|
||||||
|
|
||||||
|
self.close = () => {
|
||||||
|
if (self.tag) {
|
||||||
|
clearInterval(self.checkTimer);
|
||||||
|
self.tag.close();
|
||||||
|
self.tag = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const show = e => {
|
||||||
|
if (!document.body.contains(el)) return;
|
||||||
|
if (self.tag) return;
|
||||||
|
|
||||||
|
self.tag = new MkTooltip({
|
||||||
|
parent: vn.context,
|
||||||
|
propsData: {
|
||||||
|
text: self.text,
|
||||||
|
source: el
|
||||||
|
}
|
||||||
|
}).$mount();
|
||||||
|
|
||||||
|
document.body.appendChild(self.tag.$el);
|
||||||
|
};
|
||||||
|
|
||||||
|
el.addEventListener(start, () => {
|
||||||
|
clearTimeout(self.showTimer);
|
||||||
|
clearTimeout(self.hideTimer);
|
||||||
|
self.showTimer = setTimeout(show, 300);
|
||||||
|
});
|
||||||
|
|
||||||
|
el.addEventListener(end, () => {
|
||||||
|
clearTimeout(self.showTimer);
|
||||||
|
clearTimeout(self.hideTimer);
|
||||||
|
self.hideTimer = setTimeout(self.close, 300);
|
||||||
|
});
|
||||||
|
|
||||||
|
el.addEventListener('click', () => {
|
||||||
|
clearTimeout(self.showTimer);
|
||||||
|
self.close();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
unbind(el, binding, vn) {
|
||||||
|
const self = el._tooltipDirective_;
|
||||||
|
clearInterval(self.checkTimer);
|
||||||
|
},
|
||||||
|
};
|
|
@ -5,7 +5,7 @@ import { clientDb, get, bulkGet } from '../db';
|
||||||
const getTranslation = (text: string): Promise<string> => get(text, clientDb.i18n);
|
const getTranslation = (text: string): Promise<string> => get(text, clientDb.i18n);
|
||||||
|
|
||||||
export default async function(type, data): Promise<[string, NotificationOptions]> {
|
export default async function(type, data): Promise<[string, NotificationOptions]> {
|
||||||
const contexts = ['deletedNote', 'invisibleNote', 'withNFiles', '_cw.poll'];
|
const contexts = ['deletedNote', 'invisibleNote', 'withNFiles', 'poll'];
|
||||||
const locale = Object.fromEntries(await bulkGet(contexts, clientDb.i18n) as [string, string][]);
|
const locale = Object.fromEntries(await bulkGet(contexts, clientDb.i18n) as [string, string][]);
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
|
|
@ -1,3 +1 @@
|
||||||
export function isDeviceTouch(): boolean {
|
export const isDeviceTouch = 'maxTouchPoints' in navigator && navigator.maxTouchPoints > 0;
|
||||||
return 'maxTouchPoints' in navigator && navigator.maxTouchPoints > 0;
|
|
||||||
}
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ const summarize = (note: any, locale: any): string => {
|
||||||
|
|
||||||
// 投票が添付されているとき
|
// 投票が添付されているとき
|
||||||
if (note.poll) {
|
if (note.poll) {
|
||||||
summary += ` (${locale._cw?.poll || locale['_cw.poll']})`;
|
summary += ` (${locale['poll']})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 返信のとき
|
// 返信のとき
|
||||||
|
|
Loading…
Reference in a new issue