From 111eb43fd93d0a496da054c36ec84c6066c1c434 Mon Sep 17 00:00:00 2001 From: tamaina Date: Wed, 3 Jun 2020 13:30:17 +0900 Subject: [PATCH] =?UTF-8?q?feat(client):=20=E6=8A=95=E7=A8=BF=E3=83=95?= =?UTF-8?q?=E3=82=A9=E3=83=BC=E3=83=A0=E3=81=AE=E3=83=9C=E3=82=BF=E3=83=B3?= =?UTF-8?q?=E3=81=AE=E8=AA=AC=E6=98=8E=E3=82=92=E8=A1=A8=E7=A4=BA=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=20(#6408)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- locales/ja-JP.yml | 7 +- src/client/components/cw-button.vue | 2 +- src/client/components/link.vue | 4 +- src/client/components/post-form.vue | 18 +-- .../components/reactions-viewer.details.vue | 104 ++++-------------- .../components/reactions-viewer.reaction.vue | 5 +- src/client/components/ui/tooltip.vue | 96 ++++++++++++++++ src/client/components/url.vue | 4 +- src/client/directives/index.ts | 2 + src/client/directives/tooltip.ts | 62 +++++++++++ src/client/scripts/compose-notification.ts | 2 +- src/client/scripts/is-device-touch.ts | 4 +- src/misc/get-note-summary.ts | 2 +- 13 files changed, 207 insertions(+), 105 deletions(-) create mode 100644 src/client/components/ui/tooltip.vue create mode 100644 src/client/directives/tooltip.ts diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 38827ea35..c97aad482 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -45,6 +45,7 @@ loadMore: "もっと見る" youGotNewFollower: "フォローされました" receiveFollowRequest: "フォローリクエストされました" followRequestAccepted: "フォローが承認されました" +mention: "メンション" mentions: "あなた宛て" directNotes: "ダイレクト投稿" importAndExport: "インポートとエクスポート" @@ -104,6 +105,7 @@ suspendConfirm: "凍結しますか?" unsuspendConfirm: "解凍しますか?" selectList: "リストを選択" customEmojis: "カスタム絵文字" +emoji: "絵文字" emojiName: "絵文字名" emojiUrl: "絵文字画像URL" addEmoji: "絵文字を追加" @@ -510,6 +512,9 @@ serviceworkerInfo: "プッシュ通知を行うには有効する必要があり deletedNote: "削除された投稿" invisibleNote: "非公開の投稿" enableInfiniteScroll: "自動でもっと見る" +visibility: "公開範囲" +poll: "アンケート" +useCw: "内容を隠す" _theme: explore: "テーマを探す" @@ -648,7 +653,6 @@ _cw: show: "もっと見る" chars: "{count}文字" files: "{count}ファイル" - poll: "アンケート" _poll: noOnlyOneChoice: "選択肢は最低2つ必要です" @@ -1119,3 +1123,4 @@ _notification: youReceivedFollowRequest: "フォローリクエストが来ました" yourFollowRequestAccepted: "フォローリクエストが承認されました" youWereInvitedToGroup: "グループに招待されました" + diff --git a/src/client/components/cw-button.vue b/src/client/components/cw-button.vue index 07a44d970..16a9b84f6 100644 --- a/src/client/components/cw-button.vue +++ b/src/client/components/cw-button.vue @@ -27,7 +27,7 @@ export default Vue.extend({ return concat([ 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.poll != null ? [this.$t('_cw.poll')] : [] + this.note.poll != null ? [this.$t('poll')] : [] ] as string[][]).join(' / '); } }, diff --git a/src/client/components/link.vue b/src/client/components/link.vue index 4c709375d..7a364d098 100644 --- a/src/client/components/link.vue +++ b/src/client/components/link.vue @@ -62,13 +62,13 @@ export default Vue.extend({ } }, onMouseover() { - if (isDeviceTouch()) return; + if (isDeviceTouch) return; clearTimeout(this.showTimer); clearTimeout(this.hideTimer); this.showTimer = setTimeout(this.showPreview, 500); }, onMouseleave() { - if (isDeviceTouch()) return; + if (isDeviceTouch) return; clearTimeout(this.showTimer); clearTimeout(this.hideTimer); this.hideTimer = setTimeout(this.closePreview, 500); diff --git a/src/client/components/post-form.vue b/src/client/components/post-form.vue index cdb61f51d..ee6148a35 100644 --- a/src/client/components/post-form.vue +++ b/src/client/components/post-form.vue @@ -9,13 +9,13 @@
{{ max - trimmedLength(text) }} - - +
@@ -26,7 +26,7 @@
{{ $t('recipient') }}
- + @@ -39,11 +39,11 @@
- - - - - + + + + +
@@ -576,7 +576,7 @@ export default Vue.extend({ insertTextAtCursor(this.$refs.text, emoji); vm.close(); }); - } + }, } }); diff --git a/src/client/components/reactions-viewer.details.vue b/src/client/components/reactions-viewer.details.vue index 67c8b261b..96d1408fc 100644 --- a/src/client/components/reactions-viewer.details.vue +++ b/src/client/components/reactions-viewer.details.vue @@ -1,27 +1,29 @@ - - diff --git a/src/client/components/reactions-viewer.reaction.vue b/src/client/components/reactions-viewer.reaction.vue index 33911dedb..6b72f2e10 100644 --- a/src/client/components/reactions-viewer.reaction.vue +++ b/src/client/components/reactions-viewer.reaction.vue @@ -4,8 +4,10 @@ :class="{ reacted: note.myReaction == reaction, canToggle }" @click="toggleReaction(reaction)" v-if="count > 0" + @touchstart="onMouseover" @mouseover="onMouseover" @mouseleave="onMouseleave" + @touchend="onMouseleave" ref="reaction" v-particle > @@ -90,16 +92,17 @@ export default Vue.extend({ } }, onMouseover() { + if (this.isHovering) return; this.isHovering = true; this.detailsTimeoutId = setTimeout(this.openDetails, 300); }, onMouseleave() { + if (!this.isHovering) return; this.isHovering = false; clearTimeout(this.detailsTimeoutId); this.closeDetails(); }, openDetails() { - if (this.$root.isMobile) return; this.$root.api('notes/reactions', { noteId: this.note.id, type: this.reaction, diff --git a/src/client/components/ui/tooltip.vue b/src/client/components/ui/tooltip.vue new file mode 100644 index 000000000..b7a56708b --- /dev/null +++ b/src/client/components/ui/tooltip.vue @@ -0,0 +1,96 @@ + + + + + diff --git a/src/client/components/url.vue b/src/client/components/url.vue index 4dd23a50e..0a5a5bc50 100644 --- a/src/client/components/url.vue +++ b/src/client/components/url.vue @@ -93,13 +93,13 @@ export default Vue.extend({ } }, onMouseover() { - if (isDeviceTouch()) return; + if (isDeviceTouch) return; clearTimeout(this.showTimer); clearTimeout(this.hideTimer); this.showTimer = setTimeout(this.showPreview, 500); }, onMouseleave() { - if (isDeviceTouch()) return; + if (isDeviceTouch) return; clearTimeout(this.showTimer); clearTimeout(this.hideTimer); this.hideTimer = setTimeout(this.closePreview, 500); diff --git a/src/client/directives/index.ts b/src/client/directives/index.ts index 64d33c0ff..8cd5ed464 100644 --- a/src/client/directives/index.ts +++ b/src/client/directives/index.ts @@ -4,9 +4,11 @@ import userPreview from './user-preview'; import autocomplete from './autocomplete'; import size from './size'; import particle from './particle'; +import tooltip from './tooltip'; Vue.directive('autocomplete', autocomplete); Vue.directive('userPreview', userPreview); Vue.directive('user-preview', userPreview); Vue.directive('size', size); Vue.directive('particle', particle); +Vue.directive('tooltip', tooltip); diff --git a/src/client/directives/tooltip.ts b/src/client/directives/tooltip.ts new file mode 100644 index 000000000..28d22fc02 --- /dev/null +++ b/src/client/directives/tooltip.ts @@ -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); + }, +}; diff --git a/src/client/scripts/compose-notification.ts b/src/client/scripts/compose-notification.ts index c3281955e..1552d45e4 100644 --- a/src/client/scripts/compose-notification.ts +++ b/src/client/scripts/compose-notification.ts @@ -5,7 +5,7 @@ import { clientDb, get, bulkGet } from '../db'; const getTranslation = (text: string): Promise => get(text, clientDb.i18n); 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][]); switch (type) { diff --git a/src/client/scripts/is-device-touch.ts b/src/client/scripts/is-device-touch.ts index 9f439ae4f..3f0bfefed 100644 --- a/src/client/scripts/is-device-touch.ts +++ b/src/client/scripts/is-device-touch.ts @@ -1,3 +1 @@ -export function isDeviceTouch(): boolean { - return 'maxTouchPoints' in navigator && navigator.maxTouchPoints > 0; -} +export const isDeviceTouch = 'maxTouchPoints' in navigator && navigator.maxTouchPoints > 0; diff --git a/src/misc/get-note-summary.ts b/src/misc/get-note-summary.ts index c23306ab1..7db8bca3e 100644 --- a/src/misc/get-note-summary.ts +++ b/src/misc/get-note-summary.ts @@ -27,7 +27,7 @@ const summarize = (note: any, locale: any): string => { // 投票が添付されているとき if (note.poll) { - summary += ` (${locale._cw?.poll || locale['_cw.poll']})`; + summary += ` (${locale['poll']})`; } // 返信のとき