Add show more button to notifications + make show more button into its own component

This commit is contained in:
Freeplay 2023-04-28 18:18:09 -04:00
parent 6713c5b2d0
commit 0150a41a0b
5 changed files with 143 additions and 78 deletions

View File

@ -66,5 +66,41 @@ const toggle = () => {
background: var(--cwFg) !important; background: var(--cwFg) !important;
color: var(--cwBg) !important; color: var(--cwBg) !important;
} }
&.fade {
display: block;
position: absolute;
bottom: 0;
left: 0;
width: 100%;
> span {
display: inline-block;
background: var(--panel);
padding: 0.4em 1em;
font-size: 0.8em;
border-radius: 999px;
box-shadow: 0 2px 6px rgb(0 0 0 / 20%);
}
&:hover {
> span {
background: var(--panelHighlight);
}
}
}
&.showLess {
width: 100%;
margin-top: 1em;
position: sticky;
bottom: var(--stickyBottom);
> span {
display: inline-block;
background: var(--panel);
padding: 6px 10px;
font-size: 0.8em;
border-radius: 999px;
box-shadow: 0 0 7px 7px var(--bg);
}
}
} }
</style> </style>

View File

@ -89,7 +89,7 @@
/> />
</div> </div>
</div> </div>
<div class="tail"> <div class="tail" :class="{ collapsed }">
<header> <header>
<span v-if="notification.type === 'pollEnded'">{{ <span v-if="notification.type === 'pollEnded'">{{
i18n.ts._notification.pollEnded i18n.ts._notification.pollEnded
@ -112,11 +112,11 @@
v-if="notification.type === 'reaction'" v-if="notification.type === 'reaction'"
class="text" class="text"
:to="notePage(notification.note)" :to="notePage(notification.note)"
:title="getNoteSummary(notification.note)" :title="summary"
> >
<i class="ph-quotes ph-fill ph-lg"></i> <i class="ph-quotes ph-fill ph-lg"></i>
<Mfm <Mfm
:text="getNoteSummary(notification.note)" :text="summary"
:plain="true" :plain="true"
:nowrap="!full" :nowrap="!full"
:custom-emojis="notification.note.emojis" :custom-emojis="notification.note.emojis"
@ -142,10 +142,10 @@
v-if="notification.type === 'reply'" v-if="notification.type === 'reply'"
class="text" class="text"
:to="notePage(notification.note)" :to="notePage(notification.note)"
:title="getNoteSummary(notification.note)" :title="summary"
> >
<Mfm <Mfm
:text="getNoteSummary(notification.note)" :text="summary"
:plain="true" :plain="true"
:nowrap="!full" :nowrap="!full"
:custom-emojis="notification.note.emojis" :custom-emojis="notification.note.emojis"
@ -155,10 +155,10 @@
v-if="notification.type === 'mention'" v-if="notification.type === 'mention'"
class="text" class="text"
:to="notePage(notification.note)" :to="notePage(notification.note)"
:title="getNoteSummary(notification.note)" :title="summary"
> >
<Mfm <Mfm
:text="getNoteSummary(notification.note)" :text="summary"
:plain="true" :plain="true"
:nowrap="!full" :nowrap="!full"
:custom-emojis="notification.note.emojis" :custom-emojis="notification.note.emojis"
@ -168,10 +168,10 @@
v-if="notification.type === 'quote'" v-if="notification.type === 'quote'"
class="text" class="text"
:to="notePage(notification.note)" :to="notePage(notification.note)"
:title="getNoteSummary(notification.note)" :title="summary"
> >
<Mfm <Mfm
:text="getNoteSummary(notification.note)" :text="summary"
:plain="true" :plain="true"
:nowrap="!full" :nowrap="!full"
:custom-emojis="notification.note.emojis" :custom-emojis="notification.note.emojis"
@ -181,11 +181,11 @@
v-if="notification.type === 'pollVote'" v-if="notification.type === 'pollVote'"
class="text" class="text"
:to="notePage(notification.note)" :to="notePage(notification.note)"
:title="getNoteSummary(notification.note)" :title="summary"
> >
<i class="ph-quotes ph-fill ph-lg"></i> <i class="ph-quotes ph-fill ph-lg"></i>
<Mfm <Mfm
:text="getNoteSummary(notification.note)" :text="summary"
:plain="true" :plain="true"
:nowrap="!full" :nowrap="!full"
:custom-emojis="notification.note.emojis" :custom-emojis="notification.note.emojis"
@ -196,11 +196,11 @@
v-if="notification.type === 'pollEnded'" v-if="notification.type === 'pollEnded'"
class="text" class="text"
:to="notePage(notification.note)" :to="notePage(notification.note)"
:title="getNoteSummary(notification.note)" :title="summary"
> >
<i class="ph-quotes ph-fill ph-lg"></i> <i class="ph-quotes ph-fill ph-lg"></i>
<Mfm <Mfm
:text="getNoteSummary(notification.note)" :text="summary"
:plain="true" :plain="true"
:nowrap="!full" :nowrap="!full"
:custom-emojis="notification.note.emojis" :custom-emojis="notification.note.emojis"
@ -264,6 +264,7 @@
<span v-if="notification.type === 'app'" class="text"> <span v-if="notification.type === 'app'" class="text">
<Mfm :text="notification.body" :nowrap="!full" /> <Mfm :text="notification.body" :nowrap="!full" />
</span> </span>
<xShowMoreButton v-if="isLong" v-model="collapsed"></xShowMoreButton>
</div> </div>
</div> </div>
</template> </template>
@ -274,6 +275,7 @@ import * as misskey from "calckey-js";
import XReactionIcon from "@/components/MkReactionIcon.vue"; import XReactionIcon from "@/components/MkReactionIcon.vue";
import MkFollowButton from "@/components/MkFollowButton.vue"; import MkFollowButton from "@/components/MkFollowButton.vue";
import XReactionTooltip from "@/components/MkReactionTooltip.vue"; import XReactionTooltip from "@/components/MkReactionTooltip.vue";
import XShowMoreButton from "./MkShowMoreButton.vue";
import { getNoteSummary } from "@/scripts/get-note-summary"; import { getNoteSummary } from "@/scripts/get-note-summary";
import { notePage } from "@/filters/note"; import { notePage } from "@/filters/note";
import { userPage } from "@/filters/user"; import { userPage } from "@/filters/user";
@ -299,12 +301,19 @@ const props = withDefaults(
const elRef = ref<HTMLElement>(null); const elRef = ref<HTMLElement>(null);
const reactionRef = ref(null); const reactionRef = ref(null);
const summary = getNoteSummary(props.notification.note);
const showEmojiReactions = const showEmojiReactions =
defaultStore.state.enableEmojiReactions || defaultStore.state.enableEmojiReactions ||
defaultStore.state.showEmojisInReactionNotifications; defaultStore.state.showEmojisInReactionNotifications;
const defaultReaction = ["⭐", "👍", "❤️"].includes(instance.defaultReaction) const defaultReaction = ["⭐", "👍", "❤️"].includes(instance.defaultReaction)
? instance.defaultReaction ? instance.defaultReaction
: "⭐"; : "⭐";
const isLong = (summary.split("\n").length > 3 || summary.length > 200);
const collapsed = $ref(isLong);
let readObserver: IntersectionObserver | undefined; let readObserver: IntersectionObserver | undefined;
let connection; let connection;
@ -486,6 +495,7 @@ useTooltip(reactionRef, (showing) => {
} }
> .tail { > .tail {
position: relative;
flex: 1; flex: 1;
min-width: 0; min-width: 0;
@ -526,6 +536,17 @@ useTooltip(reactionRef, (showing) => {
margin-left: 4px; margin-left: 4px;
} }
} }
&.collapsed > .text {
display: block;
position: relative;
max-height: calc(4em + 50px);
overflow: hidden;
mask: linear-gradient(black calc(100% - 64px), transparent);
-webkit-mask: linear-gradient(
black calc(100% - 64px),
transparent
);
}
} }
} }
</style> </style>

View File

@ -0,0 +1,68 @@
<template>
<button
v-if="modelValue"
class="fade _button"
@click.stop="toggle"
>
<span>{{ i18n.ts.showMore }}</span>
</button>
<button
v-if="!modelValue"
class="showLess _button"
@click.stop="toggle"
>
<span>{{ i18n.ts.showLess }}</span>
</button>
</template>
<script lang="ts" setup>
import { i18n } from "@/i18n";
const props = defineProps<{
modelValue: boolean;
}>();
const emit = defineEmits<{
(ev: "update:modelValue", v: boolean): void;
}>();
const toggle = () => {
emit("update:modelValue", !props.modelValue);
};
</script>
<style lang="scss" scoped>
.fade {
display: block;
position: absolute;
bottom: 0;
left: 0;
width: 100%;
> span {
display: inline-block;
background: var(--panel);
padding: 0.4em 1em;
font-size: 0.8em;
border-radius: 999px;
box-shadow: 0 2px 6px rgb(0 0 0 / 20%);
}
&:hover {
> span {
background: var(--panelHighlight);
}
}
}
.showLess {
width: 100%;
margin-top: 1em;
position: sticky;
bottom: var(--stickyBottom);
> span {
display: inline-block;
background: var(--panel);
padding: 6px 10px;
font-size: 0.8em;
border-radius: 999px;
box-shadow: 0 0 7px 7px var(--bg);
}
}
</style>

View File

@ -97,20 +97,7 @@
</div> </div>
</template> </template>
</div> </div>
<button <XShowMoreButton v-if="isLong" v-model="collapsed"></XShowMoreButton>
v-if="isLong && collapsed"
class="fade _button"
@click.stop="collapsed = false"
>
<span>{{ i18n.ts.showMore }}</span>
</button>
<button
v-if="isLong && !collapsed"
class="showLess _button"
@click.stop="collapsed = true"
>
<span>{{ i18n.ts.showLess }}</span>
</button>
<XCwButton v-if="note.cw" v-model="showContent" :note="note" /> <XCwButton v-if="note.cw" v-model="showContent" :note="note" />
</div> </div>
</div> </div>
@ -124,6 +111,7 @@ import XNoteSimple from "@/components/MkNoteSimple.vue";
import XMediaList from "@/components/MkMediaList.vue"; import XMediaList from "@/components/MkMediaList.vue";
import XPoll from "@/components/MkPoll.vue"; import XPoll from "@/components/MkPoll.vue";
import MkUrlPreview from "@/components/MkUrlPreview.vue"; import MkUrlPreview from "@/components/MkUrlPreview.vue";
import XShowMoreButton from "./MkShowMoreButton.vue";
import XCwButton from "@/components/MkCwButton.vue"; import XCwButton from "@/components/MkCwButton.vue";
import { extractUrlFromMfm } from "@/scripts/extract-url-from-mfm"; import { extractUrlFromMfm } from "@/scripts/extract-url-from-mfm";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
@ -146,6 +134,7 @@ const isLong =
props.note.text != null && props.note.text != null &&
(props.note.text.split("\n").length > 9 || props.note.text.length > 500); (props.note.text.split("\n").length > 9 || props.note.text.length > 500);
const collapsed = $ref(props.note.cw == null && isLong); const collapsed = $ref(props.note.cw == null && isLong);
const urls = props.note.text const urls = props.note.text
? extractUrlFromMfm(mfm.parse(props.note.text)).slice(0, 5) ? extractUrlFromMfm(mfm.parse(props.note.text)).slice(0, 5)
: null; : null;
@ -264,43 +253,6 @@ let showContent = $ref(false);
top: 40px; top: 40px;
} }
} }
:deep(.fade) {
display: block;
position: absolute;
bottom: 0;
left: 0;
width: 100%;
> span {
display: inline-block;
background: var(--panel);
padding: 0.4em 1em;
font-size: 0.8em;
border-radius: 999px;
box-shadow: 0 2px 6px rgb(0 0 0 / 20%);
}
&:hover {
> span {
background: var(--panelHighlight);
}
}
}
}
:deep(.showLess) {
width: 100%;
margin-top: 1em;
position: sticky;
bottom: var(--stickyBottom);
> span {
display: inline-block;
background: var(--panel);
padding: 6px 10px;
font-size: 0.8em;
border-radius: 999px;
box-shadow: 0 0 7px 7px var(--bg);
}
} }
} }
} }

View File

@ -55,20 +55,7 @@
:custom-emojis="user.emojis" :custom-emojis="user.emojis"
/> />
</div> </div>
<button <XShowMoreButton v-if="isLong" v-model="collapsed"></XShowMoreButton>
v-if="isLong && collapsed"
class="fade _button"
@click.stop="collapsed = false"
>
<span>{{ i18n.ts.showMore }}</span>
</button>
<button
v-if="isLong && !collapsed"
class="showLess _button"
@click.stop="collapsed = true"
>
<span>{{ i18n.ts.showLess }}</span>
</button>
<div v-if="user.fields.length > 0" class="fields"> <div v-if="user.fields.length > 0" class="fields">
<dl <dl
v-for="(field, i) in user.fields" v-for="(field, i) in user.fields"
@ -128,6 +115,7 @@ import * as Acct from "calckey-js/built/acct";
import type * as misskey from "calckey-js"; import type * as misskey from "calckey-js";
import MkFollowButton from "@/components/MkFollowButton.vue"; import MkFollowButton from "@/components/MkFollowButton.vue";
import { userPage } from "@/filters/user"; import { userPage } from "@/filters/user";
import XShowMoreButton from "./MkShowMoreButton.vue";
import * as os from "@/os"; import * as os from "@/os";
import { $i } from "@/account"; import { $i } from "@/account";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";