Notes refactoring (?) + new CW design (#9888)
Moved a lot of the duplicated code in the different note components into the SubNoteContent component I've also replaced the detailed note stuff with just the MkNote component Co-authored-by: Freeplay <Freeplay@duck.com> Reviewed-on: https://codeberg.org/calckey/calckey/pulls/9888 Co-authored-by: Free <freeplay@duck.com> Co-committed-by: Free <freeplay@duck.com>
This commit is contained in:
parent
4974088061
commit
29818a067b
@ -1,8 +1,14 @@
|
||||
<template>
|
||||
<button class="nrvgflfu _button" @click.stop="toggle">
|
||||
<b>{{ modelValue ? i18n.ts._cw.hide : i18n.ts._cw.show }}</b>
|
||||
<button
|
||||
class="_button"
|
||||
:class="{showLess: modelValue, fade: !modelValue}"
|
||||
@click.stop="toggle"
|
||||
>
|
||||
<span>{{ modelValue ? i18n.ts._cw.hide : i18n.ts._cw.show }}
|
||||
<span v-if="!modelValue">{{ label }}</span>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@ -30,7 +36,8 @@ const label = computed(() => {
|
||||
? [i18n.t("_cw.files", { count: props.note.files.length })]
|
||||
: [],
|
||||
props.note.poll != null ? [i18n.ts.poll] : [],
|
||||
] as string[][]).join(" / ");
|
||||
props.note.renote != null ? [i18n.ts.quoteAttached] : [],
|
||||
] as string[][]).join(", ");
|
||||
});
|
||||
|
||||
const toggle = () => {
|
||||
@ -39,37 +46,25 @@ const toggle = () => {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.nrvgflfu {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: inline-block;
|
||||
padding: 4px 8px;
|
||||
font-size: 0.8em;
|
||||
color: var(--cwFg);
|
||||
background: var(--cwBg);
|
||||
padding: 6px 10px;
|
||||
width: 90%;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--divider);
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
transition: background-color 0.25s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
background: var(--cwFg);
|
||||
color: var(--cwBg);
|
||||
}
|
||||
|
||||
._button {
|
||||
font-weight: 700;
|
||||
> span {
|
||||
margin-left: 4px;
|
||||
|
||||
&:before {
|
||||
content: "(";
|
||||
background: var(--cwBg) !important;
|
||||
color: var(--cwFg);
|
||||
transition: background .2s, color .2s;
|
||||
> span {
|
||||
font-weight: 500;
|
||||
&::before {
|
||||
content: "("
|
||||
}
|
||||
|
||||
&:after {
|
||||
content: ")";
|
||||
&::after {
|
||||
content: ")"
|
||||
}
|
||||
}
|
||||
}
|
||||
&:hover > span {
|
||||
background: var(--cwFg) !important;
|
||||
color: var(--cwBg) !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -10,11 +10,11 @@
|
||||
:class="{ renote: isRenote }"
|
||||
>
|
||||
<MkNoteSub
|
||||
v-if="appearNote.reply"
|
||||
v-if="appearNote.reply && !detailedView"
|
||||
:note="appearNote.reply"
|
||||
class="reply-to"
|
||||
/>
|
||||
<div class="note-context" @click="noteClick">
|
||||
<div v-if="!detailedView" class="note-context" @click="noteClick">
|
||||
<div class="line"></div>
|
||||
<div v-if="appearNote._prId_" class="info">
|
||||
<i class="ph-megaphone-simple-bold ph-lg"></i>
|
||||
@ -77,32 +77,14 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="body">
|
||||
<p v-if="appearNote.cw != null" class="cw">
|
||||
<Mfm
|
||||
v-if="appearNote.cw != ''"
|
||||
<MkSubNoteContent
|
||||
class="text"
|
||||
:text="appearNote.cw"
|
||||
:author="appearNote.user"
|
||||
:custom-emojis="appearNote.emojis"
|
||||
:i="$i"
|
||||
/>
|
||||
<br />
|
||||
<XCwButton v-model="showContent" :note="appearNote" />
|
||||
</p>
|
||||
<div
|
||||
v-show="appearNote.cw == null || showContent"
|
||||
class="content"
|
||||
:class="{ collapsed, isLong }"
|
||||
>
|
||||
<div class="text">
|
||||
<Mfm
|
||||
v-if="appearNote.text"
|
||||
:text="appearNote.text"
|
||||
:author="appearNote.user"
|
||||
:i="$i"
|
||||
:custom-emojis="appearNote.emojis"
|
||||
/>
|
||||
<!-- <a v-if="appearNote.renote != null" class="rp">RN:</a> -->
|
||||
:note="appearNote"
|
||||
:detailed="true"
|
||||
:detailedView="detailedView"
|
||||
:parentId="appearNote.parentId"
|
||||
@push="(e) => router.push(notePage(e))"
|
||||
></MkSubNoteContent>
|
||||
<div
|
||||
v-if="translating || translation"
|
||||
class="translation"
|
||||
@ -124,47 +106,6 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="appearNote.files.length > 0" class="files">
|
||||
<XMediaList :media-list="appearNote.files" />
|
||||
</div>
|
||||
<XPoll
|
||||
v-if="appearNote.poll"
|
||||
ref="pollViewer"
|
||||
:note="appearNote"
|
||||
class="poll"
|
||||
/>
|
||||
<MkUrlPreview
|
||||
v-for="url in urls"
|
||||
:key="url"
|
||||
:url="url"
|
||||
:compact="true"
|
||||
:detail="false"
|
||||
class="url-preview"
|
||||
/>
|
||||
<div v-if="appearNote.renote" class="renote">
|
||||
<XNoteSimple
|
||||
:note="appearNote.renote"
|
||||
@click.stop="
|
||||
router.push(notePage(appearNote.renote))
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
v-if="isLong && collapsed"
|
||||
class="fade _button"
|
||||
@click.stop="collapsed = false"
|
||||
>
|
||||
<span>{{ i18n.ts.showMore }}</span>
|
||||
</button>
|
||||
<button
|
||||
v-else-if="isLong && !collapsed"
|
||||
class="showLess _button"
|
||||
@click.stop="collapsed = true"
|
||||
>
|
||||
<span>{{ i18n.ts.showLess }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<MkA
|
||||
v-if="appearNote.channel && !inChannel"
|
||||
class="channel"
|
||||
@ -174,6 +115,14 @@
|
||||
{{ appearNote.channel.name }}</MkA
|
||||
>
|
||||
</div>
|
||||
<div v-if="detailedView" class="info">
|
||||
<MkA class="created-at" :to="notePage(appearNote)">
|
||||
<MkTime
|
||||
:time="appearNote.createdAt"
|
||||
mode="absolute"
|
||||
/>
|
||||
</MkA>
|
||||
</div>
|
||||
<footer ref="el" class="footer" @click.stop>
|
||||
<XReactionsViewer
|
||||
v-if="enableEmojiReactions"
|
||||
@ -277,6 +226,7 @@ import * as mfm from "mfm-js";
|
||||
import type { Ref } from "vue";
|
||||
import type * as misskey from "calckey-js";
|
||||
import MkNoteSub from "@/components/MkNoteSub.vue";
|
||||
import MkSubNoteContent from "./MkSubNoteContent.vue";
|
||||
import XNoteHeader from "@/components/MkNoteHeader.vue";
|
||||
import XNoteSimple from "@/components/MkNoteSimple.vue";
|
||||
import XMediaList from "@/components/MkMediaList.vue";
|
||||
@ -297,7 +247,6 @@ import { userPage } from "@/filters/user";
|
||||
import * as os from "@/os";
|
||||
import { defaultStore, noteViewInterruptors } from "@/store";
|
||||
import { reactionPicker } from "@/scripts/reaction-picker";
|
||||
import { extractUrlFromMfm } from "@/scripts/extract-url-from-mfm";
|
||||
import { $i } from "@/account";
|
||||
import { i18n } from "@/i18n";
|
||||
import { getNoteMenu } from "@/scripts/get-note-menu";
|
||||
@ -310,6 +259,7 @@ const router = useRouter();
|
||||
const props = defineProps<{
|
||||
note: misskey.entities.Note;
|
||||
pinned?: boolean;
|
||||
detailedView?: boolean;
|
||||
}>();
|
||||
|
||||
const inChannel = inject("inChannel", null);
|
||||
@ -344,18 +294,10 @@ let appearNote = $computed(() =>
|
||||
);
|
||||
const isMyRenote = $i && $i.id === note.userId;
|
||||
const showContent = ref(false);
|
||||
const isLong =
|
||||
appearNote.cw == null &&
|
||||
appearNote.text != null &&
|
||||
(appearNote.text.split("\n").length > 9 || appearNote.text.length > 500);
|
||||
const collapsed = ref(appearNote.cw == null && isLong);
|
||||
const isDeleted = ref(false);
|
||||
const muted = ref(getWordMute(appearNote, $i, defaultStore.state.mutedWords));
|
||||
const translation = ref(null);
|
||||
const translating = ref(false);
|
||||
const urls = appearNote.text
|
||||
? extractUrlFromMfm(mfm.parse(appearNote.text)).slice(0, 5)
|
||||
: null;
|
||||
const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
|
||||
|
||||
const keymap = {
|
||||
@ -503,7 +445,7 @@ function focusAfter() {
|
||||
}
|
||||
|
||||
function noteClick(e) {
|
||||
if (document.getSelection().type === "Range") {
|
||||
if (document.getSelection().type === "Range" || props.detailedView) {
|
||||
e.stopPropagation();
|
||||
} else {
|
||||
router.push(notePage(appearNote));
|
||||
@ -690,91 +632,7 @@ function readPromo() {
|
||||
> .body {
|
||||
margin-top: 0.7em;
|
||||
|
||||
> .cw {
|
||||
cursor: default;
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow-wrap: break-word;
|
||||
|
||||
> .text {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
> .content {
|
||||
&.isLong {
|
||||
> .showLess {
|
||||
width: 100%;
|
||||
margin-top: 1em;
|
||||
position: sticky;
|
||||
bottom: var(--stickyBottom);
|
||||
|
||||
> span {
|
||||
display: inline-block;
|
||||
background: var(--popup);
|
||||
padding: 6px 10px;
|
||||
font-size: 0.8em;
|
||||
border-radius: 999px;
|
||||
box-shadow: 0 2px 6px rgb(0 0 0 / 20%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.collapsed {
|
||||
position: relative;
|
||||
max-height: 9em;
|
||||
overflow: hidden;
|
||||
> .text {
|
||||
max-height: 9em;
|
||||
mask: linear-gradient(
|
||||
black calc(100% - 64px),
|
||||
transparent
|
||||
);
|
||||
-webkit-mask: linear-gradient(
|
||||
black calc(100% - 64px),
|
||||
transparent
|
||||
);
|
||||
}
|
||||
> .fade {
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 64px;
|
||||
|
||||
> span {
|
||||
display: inline-block;
|
||||
background: var(--panel);
|
||||
padding: 6px 10px;
|
||||
font-size: 0.8em;
|
||||
border-radius: 999px;
|
||||
box-shadow: 0 2px 6px rgb(0 0 0 / 20%);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
> span {
|
||||
background: var(--panelHighlight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .text {
|
||||
overflow-wrap: break-word;
|
||||
|
||||
> .reply {
|
||||
color: var(--accent);
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
> .rp {
|
||||
margin-left: 4px;
|
||||
font-style: oblique;
|
||||
color: var(--renote);
|
||||
}
|
||||
|
||||
> .translation {
|
||||
border: solid 0.5px var(--divider);
|
||||
border-radius: var(--radius);
|
||||
@ -782,22 +640,8 @@ function readPromo() {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
> .files {
|
||||
margin-top: 0.4em;
|
||||
margin-bottom: 0.4em;
|
||||
}
|
||||
> .url-preview {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
> .poll {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
> .renote {
|
||||
padding: 8px 0;
|
||||
|
||||
padding-top: 8px;
|
||||
> * {
|
||||
padding: 16px;
|
||||
border: solid 1px var(--renote);
|
||||
@ -809,20 +653,24 @@ function readPromo() {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .channel {
|
||||
opacity: 0.7;
|
||||
font-size: 80%;
|
||||
}
|
||||
}
|
||||
> .info {
|
||||
margin-block: 16px;
|
||||
opacity: 0.7;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
> .footer {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
pointer-events: none; // Allow clicking anything w/out pointer-events: all; to open post
|
||||
|
||||
margin-top: .4em;
|
||||
> .button {
|
||||
margin: 0;
|
||||
padding: 8px;
|
||||
|
@ -20,246 +20,16 @@
|
||||
:note="appearNote.reply"
|
||||
class="reply-to"
|
||||
/>
|
||||
<div v-if="isRenote" class="renote">
|
||||
<MkAvatar class="avatar" :user="note.user" />
|
||||
<i class="ph-repeat ph-bold ph-lg"></i>
|
||||
<I18n :src="i18n.ts.renotedBy" tag="span">
|
||||
<template #user>
|
||||
<MkA
|
||||
v-user-preview="note.userId"
|
||||
class="name"
|
||||
:to="userPage(note.user)"
|
||||
>
|
||||
<MkUserName :user="note.user" />
|
||||
</MkA>
|
||||
</template>
|
||||
</I18n>
|
||||
<div class="info">
|
||||
<button
|
||||
ref="renoteTime"
|
||||
class="_button time"
|
||||
@click="showRenoteMenu()"
|
||||
>
|
||||
<i
|
||||
v-if="isMyRenote"
|
||||
class="ph-dots-three-outline ph-bold ph-lg dropdownIcon"
|
||||
></i>
|
||||
<MkTime :time="note.createdAt" />
|
||||
</button>
|
||||
<MkVisibility :note="note" />
|
||||
</div>
|
||||
</div>
|
||||
<article
|
||||
ref="noteEl"
|
||||
class="article"
|
||||
|
||||
<div ref="noteEl" class="article" tabindex="-1">
|
||||
<MkNote
|
||||
@contextmenu.stop="onContextmenu"
|
||||
tabindex="-1"
|
||||
>
|
||||
<header class="header">
|
||||
<MkAvatar
|
||||
class="avatar"
|
||||
:user="appearNote.user"
|
||||
:show-indicator="true"
|
||||
/>
|
||||
<div class="body">
|
||||
<div class="top">
|
||||
<MkA
|
||||
v-user-preview="appearNote.user.id"
|
||||
class="name"
|
||||
:to="userPage(appearNote.user)"
|
||||
>
|
||||
<MkUserName :user="appearNote.user" />
|
||||
</MkA>
|
||||
<span v-if="appearNote.user.isBot" class="is-bot"
|
||||
>bot</span
|
||||
>
|
||||
<div class="info">
|
||||
<MkVisibility :note="appearNote" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="username">
|
||||
<MkAcct :user="appearNote.user" />
|
||||
</div>
|
||||
<MkInstanceTicker
|
||||
v-if="showTicker"
|
||||
class="ticker"
|
||||
:instance="appearNote.user.instance"
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
<div class="main">
|
||||
<div class="body">
|
||||
<div v-if="appearNote.cw != null" class="cw">
|
||||
<Mfm
|
||||
v-if="appearNote.cw != ''"
|
||||
class="text"
|
||||
:text="appearNote.cw"
|
||||
:author="appearNote.user"
|
||||
:i="$i"
|
||||
:custom-emojis="appearNote.emojis"
|
||||
/>
|
||||
<br />
|
||||
<XCwButton v-model="showContent" :note="appearNote" />
|
||||
</div>
|
||||
<div
|
||||
v-show="appearNote.cw == null || showContent"
|
||||
class="content"
|
||||
>
|
||||
<div class="text">
|
||||
<Mfm
|
||||
v-if="appearNote.text"
|
||||
:text="appearNote.text"
|
||||
:author="appearNote.user"
|
||||
:i="$i"
|
||||
:custom-emojis="appearNote.emojis"
|
||||
/>
|
||||
<div
|
||||
v-if="translating || translation"
|
||||
class="translation"
|
||||
>
|
||||
<MkLoading v-if="translating" mini />
|
||||
<div v-else class="translated">
|
||||
<b
|
||||
>{{
|
||||
i18n.t("translatedFrom", {
|
||||
x: translation.sourceLang,
|
||||
})
|
||||
}}:
|
||||
</b>
|
||||
<Mfm
|
||||
:text="translation.text"
|
||||
:author="appearNote.user"
|
||||
:i="$i"
|
||||
:custom-emojis="appearNote.emojis"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="appearNote.files.length > 0" class="files">
|
||||
<XMediaList :media-list="appearNote.files" />
|
||||
</div>
|
||||
<XPoll
|
||||
v-if="appearNote.poll"
|
||||
ref="pollViewer"
|
||||
:note="appearNote"
|
||||
class="poll"
|
||||
/>
|
||||
<MkUrlPreview
|
||||
v-for="url in urls"
|
||||
:key="url"
|
||||
:url="url"
|
||||
:compact="true"
|
||||
:detail="true"
|
||||
class="url-preview"
|
||||
/>
|
||||
<div v-if="appearNote.renote" class="renote">
|
||||
<XNoteSimple
|
||||
:note="appearNote.renote"
|
||||
@click.stop="
|
||||
router.push(notePage(appearNote.renote))
|
||||
"
|
||||
/>
|
||||
:detailedView="true"
|
||||
></MkNote>
|
||||
</div>
|
||||
</div>
|
||||
<MkA
|
||||
v-if="appearNote.channel && !inChannel"
|
||||
class="channel"
|
||||
:to="`/channels/${appearNote.channel.id}`"
|
||||
><i class="ph-television ph-bold ph-lg"></i>
|
||||
{{ appearNote.channel.name }}</MkA
|
||||
>
|
||||
</div>
|
||||
<footer class="footer">
|
||||
<div class="info">
|
||||
<MkA class="created-at" :to="notePage(appearNote)">
|
||||
<MkTime
|
||||
:time="appearNote.createdAt"
|
||||
mode="detail"
|
||||
/>
|
||||
</MkA>
|
||||
</div>
|
||||
<XReactionsViewer
|
||||
v-if="enableEmojiReactions"
|
||||
ref="reactionsViewer"
|
||||
:note="appearNote"
|
||||
/>
|
||||
<button
|
||||
v-tooltip.noDelay.bottom="i18n.ts.reply"
|
||||
class="button _button"
|
||||
@click="reply()"
|
||||
>
|
||||
<template v-if="appearNote.reply"
|
||||
><i class="ph-arrow-u-up-left ph-bold ph-lg"></i
|
||||
></template>
|
||||
<template v-else
|
||||
><i class="ph-arrow-bend-up-left ph-bold ph-lg"></i
|
||||
></template>
|
||||
<p v-if="appearNote.repliesCount > 0" class="count">
|
||||
{{ appearNote.repliesCount }}
|
||||
</p>
|
||||
</button>
|
||||
<XRenoteButton
|
||||
ref="renoteButton"
|
||||
class="button"
|
||||
:note="appearNote"
|
||||
:count="appearNote.renoteCount"
|
||||
/>
|
||||
<XStarButtonNoEmoji
|
||||
v-if="!enableEmojiReactions"
|
||||
class="button"
|
||||
:note="appearNote"
|
||||
:count="
|
||||
Object.values(appearNote.reactions).reduce(
|
||||
(partialSum, val) => partialSum + val,
|
||||
0
|
||||
)
|
||||
"
|
||||
:reacted="appearNote.myReaction != null"
|
||||
/>
|
||||
<XStarButton
|
||||
v-if="
|
||||
enableEmojiReactions &&
|
||||
appearNote.myReaction == null
|
||||
"
|
||||
ref="starButton"
|
||||
class="button"
|
||||
:note="appearNote"
|
||||
/>
|
||||
<button
|
||||
v-if="
|
||||
enableEmojiReactions &&
|
||||
appearNote.myReaction == null
|
||||
"
|
||||
ref="reactButton"
|
||||
v-tooltip.noDelay.bottom="i18n.ts.reaction"
|
||||
class="button _button"
|
||||
@click="react()"
|
||||
>
|
||||
<i class="ph-smiley ph-bold ph-lg"></i>
|
||||
</button>
|
||||
<button
|
||||
v-if="
|
||||
enableEmojiReactions &&
|
||||
appearNote.myReaction != null
|
||||
"
|
||||
ref="reactButton"
|
||||
class="button _button reacted"
|
||||
@click="undoReact(appearNote)"
|
||||
>
|
||||
<i class="ph-minus ph-bold ph-lg"></i>
|
||||
</button>
|
||||
<XQuoteButton class="button" :note="appearNote" />
|
||||
<button
|
||||
ref="menuButton"
|
||||
v-tooltip.noDelay.bottom="i18n.ts.more"
|
||||
class="button _button"
|
||||
@click="menu()"
|
||||
>
|
||||
<i class="ph-dots-three-outline ph-bold ph-lg"></i>
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<MkNoteSub
|
||||
v-for="note in directReplies"
|
||||
:key="note.id"
|
||||
@ -298,6 +68,7 @@ import {
|
||||
} from "vue";
|
||||
import * as mfm from "mfm-js";
|
||||
import type * as misskey from "calckey-js";
|
||||
import MkNote from "@/components/MkNote.vue";
|
||||
import MkNoteSub from "@/components/MkNoteSub.vue";
|
||||
import XNoteSimple from "@/components/MkNoteSimple.vue";
|
||||
import XReactionsViewer from "@/components/MkReactionsViewer.vue";
|
||||
@ -672,8 +443,7 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
> .article {
|
||||
padding: 32px;
|
||||
padding-bottom: 6px;
|
||||
padding-block: 28px 6px;
|
||||
&:last-child {
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
@ -681,151 +451,8 @@ onUnmounted(() => {
|
||||
overflow: clip;
|
||||
outline: none;
|
||||
scroll-margin-top: calc(var(--stickyTop) + 20vh);
|
||||
> .header {
|
||||
display: flex;
|
||||
position: relative;
|
||||
margin-bottom: 16px;
|
||||
|
||||
> .avatar {
|
||||
display: block;
|
||||
flex-shrink: 0;
|
||||
width: var(--avatarSize);
|
||||
height: var(--avatarSize);
|
||||
}
|
||||
|
||||
> .body {
|
||||
width: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding-left: 14px;
|
||||
font-size: 0.95em;
|
||||
|
||||
> .top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
> .name {
|
||||
font-weight: bold;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
> .is-bot {
|
||||
flex-shrink: 0;
|
||||
align-self: center;
|
||||
margin: 0 0.5em;
|
||||
padding: 4px 6px;
|
||||
font-size: 80%;
|
||||
border: solid 0.5px var(--divider);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
> .info {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .main {
|
||||
> .body {
|
||||
> .cw {
|
||||
cursor: default;
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow-wrap: break-word;
|
||||
|
||||
> .text {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
> .content {
|
||||
> .text {
|
||||
overflow-wrap: break-word;
|
||||
|
||||
> .reply {
|
||||
color: var(--accent);
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
> .rp {
|
||||
margin-left: 4px;
|
||||
font-style: oblique;
|
||||
color: var(--renote);
|
||||
}
|
||||
|
||||
> .translation {
|
||||
border: solid 0.5px var(--divider);
|
||||
border-radius: var(--radius);
|
||||
padding: 12px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
> .url-preview {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
> .poll {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
> .renote {
|
||||
padding: 8px 0;
|
||||
|
||||
> * {
|
||||
padding: 16px;
|
||||
border: solid 1px var(--renote);
|
||||
border-radius: 8px;
|
||||
transition: background 0.2s;
|
||||
&:hover,
|
||||
&:focus-within {
|
||||
background-color: var(--panelHighlight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .channel {
|
||||
opacity: 0.7;
|
||||
font-size: 80%;
|
||||
}
|
||||
}
|
||||
|
||||
> .footer {
|
||||
> .info {
|
||||
margin: 16px 0;
|
||||
opacity: 0.7;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
> .button {
|
||||
margin: 0;
|
||||
padding: 8px;
|
||||
opacity: 0.7;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--fgHighlighted);
|
||||
}
|
||||
|
||||
> .count {
|
||||
display: inline;
|
||||
margin: 0 0 0 8px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
&.reacted {
|
||||
color: var(--accent);
|
||||
}
|
||||
}
|
||||
}
|
||||
:deep(.article) {
|
||||
cursor: unset;
|
||||
}
|
||||
}
|
||||
|
||||
@ -909,7 +536,7 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
> .article {
|
||||
padding: 16px;
|
||||
padding: 6px 0 0 0;
|
||||
> .header > .body {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
@ -4,24 +4,10 @@
|
||||
<div class="main">
|
||||
<XNoteHeader class="header" :note="note" :mini="true" />
|
||||
<div class="body">
|
||||
<p v-if="note.cw != null" class="cw">
|
||||
<Mfm
|
||||
v-if="note.cw != ''"
|
||||
class="text"
|
||||
:text="note.cw"
|
||||
:author="note.user"
|
||||
:i="$i"
|
||||
:custom-emojis="note.emojis"
|
||||
/>
|
||||
<br />
|
||||
<XCwButton v-model="showContent" :note="note" />
|
||||
</p>
|
||||
<div v-show="note.cw == null || showContent" class="content">
|
||||
<MkSubNoteContent class="text" :note="note" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@ -29,7 +15,6 @@ import {} from "vue";
|
||||
import * as misskey from "calckey-js";
|
||||
import XNoteHeader from "@/components/MkNoteHeader.vue";
|
||||
import MkSubNoteContent from "@/components/MkSubNoteContent.vue";
|
||||
import XCwButton from "@/components/MkCwButton.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
note: misskey.entities.Note;
|
||||
@ -79,28 +64,6 @@ const showContent = $ref(false);
|
||||
> .header {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
> .body {
|
||||
> .cw {
|
||||
cursor: default;
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow-wrap: break-word;
|
||||
|
||||
> .text {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
> .content {
|
||||
> .text {
|
||||
cursor: default;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -21,51 +21,12 @@
|
||||
<div class="body">
|
||||
<XNoteHeader class="header" :note="note" :mini="true" />
|
||||
<div class="body">
|
||||
<p v-if="appearNote.cw != null" class="cw">
|
||||
<MkA
|
||||
v-if="appearNote.replyId"
|
||||
:to="`/notes/${appearNote.replyId}`"
|
||||
class="reply-icon"
|
||||
@click.stop
|
||||
>
|
||||
<i class="ph-arrow-bend-left-up ph-bold ph-lg"></i>
|
||||
</MkA>
|
||||
<MkA
|
||||
v-if="
|
||||
conversation &&
|
||||
appearNote.renoteId &&
|
||||
appearNote.renoteId != parentId &&
|
||||
!appearNote.replyId
|
||||
"
|
||||
:to="`/notes/${appearNote.renoteId}`"
|
||||
class="reply-icon"
|
||||
@click.stop
|
||||
>
|
||||
<i class="ph-quotes ph-bold ph-lg"></i>
|
||||
</MkA>
|
||||
<Mfm
|
||||
v-if="appearNote.cw != ''"
|
||||
class="text"
|
||||
:text="appearNote.cw"
|
||||
:author="appearNote.user"
|
||||
:i="$i"
|
||||
:custom-emojis="appearNote.emojis"
|
||||
/>
|
||||
<br />
|
||||
<XCwButton v-model="showContent" :note="note" />
|
||||
</p>
|
||||
<div
|
||||
v-show="appearNote.cw == null || showContent"
|
||||
class="content"
|
||||
>
|
||||
<MkSubNoteContent
|
||||
class="text"
|
||||
:note="note"
|
||||
:detailed="true"
|
||||
:parentId="appearNote.parentId"
|
||||
:conversation="conversation"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="translating || translation" class="translation">
|
||||
<MkLoading v-if="translating" mini />
|
||||
<div v-else class="translated">
|
||||
@ -212,7 +173,6 @@ import XStarButton from "@/components/MkStarButton.vue";
|
||||
import XStarButtonNoEmoji from "@/components/MkStarButtonNoEmoji.vue";
|
||||
import XRenoteButton from "@/components/MkRenoteButton.vue";
|
||||
import XQuoteButton from "@/components/MkQuoteButton.vue";
|
||||
import XCwButton from "@/components/MkCwButton.vue";
|
||||
import { pleaseLogin } from "@/scripts/please-login";
|
||||
import { getNoteMenu } from "@/scripts/get-note-menu";
|
||||
import { notePage } from "@/filters/note";
|
||||
@ -262,7 +222,6 @@ let appearNote = $computed(() =>
|
||||
const isDeleted = ref(false);
|
||||
const translation = ref(null);
|
||||
const translating = ref(false);
|
||||
let showContent = $ref(false);
|
||||
const replies: misskey.entities.Note[] =
|
||||
props.conversation
|
||||
?.filter(
|
||||
@ -400,35 +359,6 @@ function noteClick(e) {
|
||||
}
|
||||
|
||||
> .body {
|
||||
.reply-icon {
|
||||
display: inline-block;
|
||||
border-radius: 6px;
|
||||
padding: 0.2em 0.2em;
|
||||
margin-right: 0.2em;
|
||||
color: var(--accent);
|
||||
transition: background 0.2s;
|
||||
&:hover,
|
||||
&:focus {
|
||||
background: var(--buttonHoverBg);
|
||||
}
|
||||
}
|
||||
> .cw {
|
||||
cursor: default;
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow-wrap: break-word;
|
||||
|
||||
> .text {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
> .content {
|
||||
> .text {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
> .translation {
|
||||
border: solid 0.5px var(--divider);
|
||||
border-radius: var(--radius);
|
||||
|
@ -1,12 +1,46 @@
|
||||
<template>
|
||||
<div class="wrmlmaau" :class="{ collapsed, isLong }">
|
||||
<p v-if="note.cw != null" class="cw">
|
||||
<MkA
|
||||
v-if="!detailed && note.replyId"
|
||||
:to="`/notes/${note.replyId}`"
|
||||
class="reply-icon"
|
||||
@click.stop
|
||||
>
|
||||
<i class="ph-arrow-bend-left-up ph-bold ph-lg"></i>
|
||||
</MkA>
|
||||
<MkA
|
||||
v-if="
|
||||
conversation &&
|
||||
note.renoteId &&
|
||||
note.renoteId != parentId &&
|
||||
!note.replyId
|
||||
"
|
||||
:to="`/notes/${note.renoteId}`"
|
||||
class="reply-icon"
|
||||
@click.stop
|
||||
>
|
||||
<i class="ph-quotes ph-bold ph-lg"></i>
|
||||
</MkA>
|
||||
<Mfm
|
||||
v-if="note.cw != ''"
|
||||
class="text"
|
||||
:text="note.cw"
|
||||
:author="note.user"
|
||||
:i="$i"
|
||||
:custom-emojis="note.emojis"
|
||||
/>
|
||||
</p>
|
||||
<div
|
||||
class="wrmlmaau"
|
||||
>
|
||||
<div class="content" :class="{ collapsed, isLong, showContent: note.cw && !showContent }">
|
||||
<div class="body">
|
||||
<span v-if="note.deletedAt" style="opacity: 0.5"
|
||||
>({{ i18n.ts.deleted }})</span
|
||||
>
|
||||
<template v-if="!note.cw">
|
||||
<MkA
|
||||
v-if="note.replyId"
|
||||
v-if="!detailed && note.replyId"
|
||||
:to="`/notes/${note.replyId}`"
|
||||
class="reply-icon"
|
||||
@click.stop
|
||||
@ -34,21 +68,14 @@
|
||||
:i="$i"
|
||||
:custom-emojis="note.emojis"
|
||||
/>
|
||||
<MkA v-if="note.renoteId" class="rp" :to="`/notes/${note.renoteId}`"
|
||||
<MkA v-if="!detailed && note.renoteId" class="rp" :to="`/notes/${note.renoteId}`"
|
||||
>{{ i18n.ts.quoteAttached }}: ...</MkA
|
||||
>
|
||||
</div>
|
||||
<div v-if="note.files.length > 0">
|
||||
<div v-if="note.files.length > 0" class="files">
|
||||
<XMediaList :media-list="note.files" />
|
||||
</div>
|
||||
<div v-if="note.poll">
|
||||
<summary>{{ i18n.ts.poll }}</summary>
|
||||
<XPoll :note="note" />
|
||||
</div>
|
||||
<XPoll v-if="note.poll" :note="note" class="poll"/>
|
||||
<template v-if="detailed">
|
||||
<!-- <div v-if="note.renoteId" class="renote">
|
||||
<XNoteSimple :note="note.renote"/>
|
||||
</div> -->
|
||||
<MkUrlPreview
|
||||
v-for="url in urls"
|
||||
:key="url"
|
||||
@ -57,7 +84,11 @@
|
||||
:detail="false"
|
||||
class="url-preview"
|
||||
/>
|
||||
<div v-if="note.renote" class="renote" @click.stop="emit('push', note.renote)">
|
||||
<XNoteSimple :note="note.renote"/>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<button
|
||||
v-if="isLong && collapsed"
|
||||
class="fade _button"
|
||||
@ -72,6 +103,8 @@
|
||||
>
|
||||
<span>{{ i18n.ts.showLess }}</span>
|
||||
</button>
|
||||
<XCwButton v-if="note.cw" v-model="showContent" :note="note" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -83,6 +116,7 @@ import XNoteSimple from "@/components/MkNoteSimple.vue";
|
||||
import XMediaList from "@/components/MkMediaList.vue";
|
||||
import XPoll from "@/components/MkPoll.vue";
|
||||
import MkUrlPreview from "@/components/MkUrlPreview.vue";
|
||||
import XCwButton from "@/components/MkCwButton.vue";
|
||||
import { extractUrlFromMfm } from "@/scripts/extract-url-from-mfm";
|
||||
import { i18n } from "@/i18n";
|
||||
|
||||
@ -91,23 +125,56 @@ const props = defineProps<{
|
||||
parentId?;
|
||||
conversation?;
|
||||
detailed?: boolean;
|
||||
detailedView?: boolean;
|
||||
}>();
|
||||
|
||||
const isLong =
|
||||
const emit = defineEmits<{
|
||||
(ev: "push", v): void;
|
||||
}>();
|
||||
|
||||
|
||||
const isLong = !props.detailedView && (
|
||||
props.note.cw == 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 urls = props.note.text
|
||||
? extractUrlFromMfm(mfm.parse(props.note.text))
|
||||
? extractUrlFromMfm(mfm.parse(props.note.text)).slice(0, 5)
|
||||
: null;
|
||||
|
||||
let showContent = $ref(false);
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.wrmlmaau {
|
||||
.reply-icon {
|
||||
display: inline-block;
|
||||
border-radius: 6px;
|
||||
padding: 0.2em 0.2em;
|
||||
margin-right: 0.2em;
|
||||
color: var(--accent);
|
||||
transition: background 0.2s;
|
||||
&:hover, &:focus {
|
||||
background: var(--buttonHoverBg);
|
||||
}
|
||||
}
|
||||
.cw {
|
||||
cursor: default;
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin-bottom: 10px;
|
||||
overflow-wrap: break-word;
|
||||
> .text {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
.wrmlmaau {
|
||||
.content {
|
||||
overflow-wrap: break-word;
|
||||
|
||||
> .body {
|
||||
transition: filter .1s;
|
||||
> .rp {
|
||||
margin-left: 4px;
|
||||
font-style: oblique;
|
||||
@ -125,38 +192,81 @@ const urls = props.note.text
|
||||
background: var(--buttonHoverBg);
|
||||
}
|
||||
}
|
||||
> .files {
|
||||
margin-top: 0.4em;
|
||||
margin-bottom: 0.4em;
|
||||
}
|
||||
|
||||
> .mk-url-preview {
|
||||
> .url-preview {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
&.collapsed {
|
||||
> .poll {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
> .renote {
|
||||
padding-top: 8px;
|
||||
> * {
|
||||
padding: 16px;
|
||||
border: solid 1px var(--renote);
|
||||
border-radius: 8px;
|
||||
transition: background 0.2s;
|
||||
&:hover,
|
||||
&:focus-within {
|
||||
background-color: var(--panelHighlight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
&.collapsed, &.showContent {
|
||||
position: relative;
|
||||
max-height: 9em;
|
||||
overflow: hidden;
|
||||
max-height: calc(9em + 50px);
|
||||
> .body {
|
||||
max-height: 9em;
|
||||
max-height: inherit;
|
||||
mask: linear-gradient(black calc(100% - 64px), transparent);
|
||||
-webkit-mask: linear-gradient(black calc(100% - 64px), transparent);
|
||||
padding-inline: 50px;
|
||||
margin-inline: -50px;
|
||||
margin-top: -50px;
|
||||
padding-top: 50px;
|
||||
overflow: hidden;
|
||||
}
|
||||
> .fade {
|
||||
&.collapsed > .body {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
&.showContent {
|
||||
> .body {
|
||||
min-height: 2em;
|
||||
max-height: 5em;
|
||||
filter: blur(4px);
|
||||
}
|
||||
:deep(.fade) {
|
||||
inset: 0;
|
||||
top: 40px;
|
||||
}
|
||||
:deep(span) {
|
||||
animation: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.fade) {
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 64px;
|
||||
|
||||
> span {
|
||||
display: inline-block;
|
||||
background: var(--panel);
|
||||
padding: 6px 10px;
|
||||
padding: .4em 1em;
|
||||
font-size: 0.8em;
|
||||
border-radius: 999px;
|
||||
box-shadow: 0 2px 6px rgb(0 0 0 / 20%);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
> span {
|
||||
background: var(--panelHighlight);
|
||||
@ -165,8 +275,7 @@ const urls = props.note.text
|
||||
}
|
||||
}
|
||||
|
||||
&.isLong {
|
||||
> .showLess {
|
||||
:deep(.showLess) {
|
||||
width: 100%;
|
||||
margin-top: 1em;
|
||||
position: sticky;
|
||||
|
Loading…
Reference in New Issue
Block a user