diff options
| author | かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com> | 2024-07-11 18:41:04 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-07-11 18:41:04 +0900 |
| commit | 6b876da44af0b7e5dd553676d230f68995df148d (patch) | |
| tree | ede6a6fc6d510fb80b0bf7781194f40f7855ca83 | |
| parent | リリースPRがないときにrelease-edit-with-push.ymlがfailして見栄... (diff) | |
| download | misskey-6b876da44af0b7e5dd553676d230f68995df148d.tar.gz misskey-6b876da44af0b7e5dd553676d230f68995df148d.tar.bz2 misskey-6b876da44af0b7e5dd553676d230f68995df148d.zip | |
enhance(frontend): ウェルカムタイムラインのデザインを調整 (#14156)
* enhance(frontend): 非ログイン時のハイライトTLのデザイン調整
* Update Changelog
* fix cw handling
* ホバーしてたらスクロールを止めるように
* fix
* lint
| -rw-r--r-- | CHANGELOG.md | 1 | ||||
| -rw-r--r-- | packages/frontend/src/pages/welcome.timeline.note.vue | 109 | ||||
| -rw-r--r-- | packages/frontend/src/pages/welcome.timeline.vue | 98 |
3 files changed, 162 insertions, 46 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index aec4e1868c..259420a6c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### Client - Enhance: 内蔵APIドキュメントのデザイン・パフォーマンスを改善 +- Enhance: 非ログイン時のハイライトTLのデザインを改善 - Fix: `/about#federation` ページなどで各インスタンスのチャートが表示されなくなっていた問題を修正 - Fix: ユーザーページの追加情報のラベルを投稿者のサーバーの絵文字で表示する (#13968) - Fix: リバーシの対局を正しく共有できないことがある問題を修正 diff --git a/packages/frontend/src/pages/welcome.timeline.note.vue b/packages/frontend/src/pages/welcome.timeline.note.vue new file mode 100644 index 0000000000..f385938343 --- /dev/null +++ b/packages/frontend/src/pages/welcome.timeline.note.vue @@ -0,0 +1,109 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<div :key="note.id" :class="$style.note"> + <div class="_panel _gaps_s" :class="$style.content"> + <div v-if="note.cw != null" :class="$style.richcontent"> + <div><Mfm :text="note.cw" :author="note.user"/></div> + <MkCwButton v-model="showContent" :text="note.text" :renote="note.renote" :files="note.files" :poll="note.poll" style="margin: 4px 0;"/> + <div v-if="showContent"> + <MkA v-if="note.replyId" class="reply" :to="`/notes/${note.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA> + <Mfm v-if="note.text" :text="note.text" :author="note.user"/> + <MkA v-if="note.renoteId" class="rp" :to="`/notes/${note.renoteId}`">RN: ...</MkA> + </div> + </div> + <div v-else ref="noteTextEl" :class="[$style.text, { [$style.collapsed]: shouldCollapse }]"> + <MkA v-if="note.replyId" class="reply" :to="`/notes/${note.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA> + <Mfm v-if="note.text" :text="note.text" :author="note.user"/> + <MkA v-if="note.renoteId" class="rp" :to="`/notes/${note.renoteId}`">RN: ...</MkA> + </div> + <div v-if="note.files && note.files.length > 0" :class="$style.richcontent"> + <MkMediaList :mediaList="note.files.slice(0, 4)"/> + </div> + <div v-if="note.poll"> + <MkPoll :noteId="note.id" :poll="note.poll" :readOnly="true"/> + </div> + <div v-if="note.reactionCount > 0" :class="$style.reactions"> + <MkReactionsViewer :note="note" :maxNumber="16"/> + </div> + </div> +</div> +</template> + +<script lang="ts" setup> +import { ref, shallowRef, onUpdated, onMounted } from 'vue'; +import * as Misskey from 'misskey-js'; +import MkReactionsViewer from '@/components/MkReactionsViewer.vue'; +import MkMediaList from '@/components/MkMediaList.vue'; +import MkPoll from '@/components/MkPoll.vue'; +import MkCwButton from '@/components/MkCwButton.vue'; + +defineProps<{ + note: Misskey.entities.Note; +}>(); + +const noteTextEl = shallowRef<HTMLDivElement>(); +const shouldCollapse = ref(false); +const showContent = ref(false); + +function calcCollapse() { + if (noteTextEl.value) { + const height = noteTextEl.value.scrollHeight; + if (height > 200) { + shouldCollapse.value = true; + } + } +} + +onMounted(() => { + calcCollapse(); +}); + +onUpdated(() => { + calcCollapse(); +}); +</script> + +<style lang="scss" module> +.note { + margin-left: auto; +} + +.text { + position: relative; + max-height: 200px; + overflow: hidden; + + &.collapsed::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 64px; + background: linear-gradient(0deg, var(--panel), var(--X15)); + } +} + +.content { + padding: 16px; + margin: 0 0 0 auto; + max-width: max-content; + border-radius: 16px; +} + +.reactions { + box-sizing: border-box; + margin: 8px -16px -8px; + padding: 8px 16px 0; + width: calc(100% + 32px); + border-top: 1px solid var(--divider); +} + +.richcontent { + min-width: 250px; +} +</style> diff --git a/packages/frontend/src/pages/welcome.timeline.vue b/packages/frontend/src/pages/welcome.timeline.vue index 139b2e0a07..db326f9e6c 100644 --- a/packages/frontend/src/pages/welcome.timeline.vue +++ b/packages/frontend/src/pages/welcome.timeline.vue @@ -4,24 +4,17 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<div :class="$style.root"> - <div ref="scrollEl" :class="[$style.scrollbox, { [$style.scroll]: isScrolling }]"> - <div v-for="note in notes" :key="note.id" :class="$style.note"> - <div class="_panel" :class="$style.content"> - <div> - <MkA v-if="note.replyId" class="reply" :to="`/notes/${note.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA> - <Mfm v-if="note.text" :text="note.text" :author="note.user"/> - <MkA v-if="note.renoteId" class="rp" :to="`/notes/${note.renoteId}`">RN: ...</MkA> - </div> - <div v-if="note.files.length > 0" :class="$style.richcontent"> - <MkMediaList :mediaList="note.files"/> - </div> - <div v-if="note.poll"> - <MkPoll :noteId="note.id" :poll="note.poll" :readOnly="true"/> - </div> - </div> - <MkReactionsViewer ref="reactionsViewer" :note="note"/> - </div> +<div :class="$style.root" class="_gaps"> + <div + ref="notesMainContainerEl" + class="_gaps" + :class="[$style.scrollBoxMain, { [$style.scrollIntro]: (scrollState === 'intro'), [$style.scrollLoop]: (scrollState === 'loop') }]" + @animationend="changeScrollState" + > + <XNote v-for="note in notes" :key="`${note.id}_1`" :class="$style.note" :note="note"/> + </div> + <div v-if="isScrolling" class="_gaps" :class="[$style.scrollBoxSub, { [$style.scrollIntro]: (scrollState === 'intro'), [$style.scrollLoop]: (scrollState === 'loop') }]"> + <XNote v-for="note in notes" :key="`${note.id}_2`" :class="$style.note" :note="note"/> </div> </div> </template> @@ -29,43 +22,54 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import * as Misskey from 'misskey-js'; import { onUpdated, ref, shallowRef } from 'vue'; -import MkReactionsViewer from '@/components/MkReactionsViewer.vue'; -import MkMediaList from '@/components/MkMediaList.vue'; -import MkPoll from '@/components/MkPoll.vue'; +import XNote from '@/pages/welcome.timeline.note.vue'; import { misskeyApiGet } from '@/scripts/misskey-api.js'; import { getScrollContainer } from '@/scripts/scroll.js'; const notes = ref<Misskey.entities.Note[]>([]); const isScrolling = ref(false); -const scrollEl = shallowRef<HTMLElement>(); +const scrollState = ref<null | 'intro' | 'loop'>(null); +const notesMainContainerEl = shallowRef<HTMLElement>(); misskeyApiGet('notes/featured').then(_notes => { notes.value = _notes; }); +function changeScrollState() { + if (scrollState.value !== 'loop') { + scrollState.value = 'loop'; + } +} + onUpdated(() => { - if (!scrollEl.value) return; - const container = getScrollContainer(scrollEl.value); + if (!notesMainContainerEl.value) return; + const container = getScrollContainer(notesMainContainerEl.value); const containerHeight = container ? container.clientHeight : window.innerHeight; - if (scrollEl.value.offsetHeight > containerHeight) { + if (notesMainContainerEl.value.offsetHeight > containerHeight) { + if (scrollState.value === null) { + scrollState.value = 'intro'; + } isScrolling.value = true; } }); </script> <style lang="scss" module> -@keyframes scroll { +@keyframes scrollIntro { 0% { transform: translate3d(0, 0, 0); } - 5% { - transform: translate3d(0, 0, 0); + 100% { + transform: translate3d(0, calc(calc(-100% - 128px) - var(--margin)), 0); } - 75% { - transform: translate3d(0, calc(-100% + 90vh), 0); +} + +@keyframes scrollConstant { + 0% { + transform: translate3d(0, -128px, 0); } - 90% { - transform: translate3d(0, calc(-100% + 90vh), 0); + 100% { + transform: translate3d(0, calc(calc(-100% - 128px) - var(--margin)), 0); } } @@ -73,24 +77,26 @@ onUpdated(() => { text-align: right; } -.scrollbox { - &.scroll { - animation: scroll 45s linear infinite; +.scrollBoxMain { + &.scrollIntro { + animation: scrollIntro 30s linear forwards; + } + &.scrollLoop { + animation: scrollConstant 30s linear infinite; } } -.note { - margin: 16px 0 16px auto; -} - -.content { - padding: 16px; - margin: 0 0 0 auto; - max-width: max-content; - border-radius: 16px; +.scrollBoxSub { + &.scrollIntro { + animation: scrollIntro 30s linear forwards; + } + &.scrollLoop { + animation: scrollConstant 30s linear infinite; + } } -.richcontent { - min-width: 250px; +.root:has(.note:hover) .scrollBoxMain, +.root:has(.note:hover) .scrollBoxSub { + animation-play-state: paused; } </style> |