upd: rework threading/post ui 1/2

Co-authored-by: Gaspard Wierzbinski <contact@cpluspatch.com>
Co-authored-by: Marie <marie@kaifa.ch>
This commit is contained in:
Insert5StarName 2023-12-02 01:39:29 +01:00
parent ad5a6c1e41
commit 1a4bff7698
8 changed files with 327 additions and 81 deletions

View file

@ -35,51 +35,48 @@ const faviconUrl = $computed(() => props.instance ? getProxiedImageUrlNullable(p
const themeColor = instance.themeColor ?? '#777777'; const themeColor = instance.themeColor ?? '#777777';
const bg = { const bg = {
background: `linear-gradient(90deg, ${themeColor}, ${themeColor}00)`, //background: `linear-gradient(90deg, ${themeColor}, ${themeColor}00)`,
background: `${themeColor}`,
}; };
</script> </script>
<style lang="scss" module> <style lang="scss" module>
$height: 2ex;
.root { .root {
display: flex; display: flex;
align-items: center; align-items: center;
height: $height; height: 1.5ex;
border-radius: var(--radius-xs) 0 0 var(--radius-xs); border-radius: var(--radius-xl);
margin-top: 5px;
padding: 4px;
overflow: clip; overflow: clip;
color: #fff; color: #fff;
text-shadow: /* .866 ≈ sin(60deg) */ text-shadow: -1px -1px 0 var(--bg),1px -1px 0 var(--bg),-1px 1px 0 var(--bg),1px 1px 0 var(--bg)
1px 0 1px #000,
.866px .5px 1px #000,
.5px .866px 1px #000,
0 1px 1px #000,
-.5px .866px 1px #000,
-.866px .5px 1px #000,
-1px 0 1px #000,
-.866px -.5px 1px #000,
-.5px -.866px 1px #000,
0 -1px 1px #000,
.5px -.866px 1px #000,
.866px -.5px 1px #000;
mask-image: linear-gradient(90deg,
rgb(0,0,0),
rgb(0,0,0) calc(100% - 16px),
rgba(0,0,0,0) 100%
);
} }
.icon { .icon {
height: $height; height: 2ex;
flex-shrink: 0; flex-shrink: 0;
} }
.name { .name {
margin-left: 4px; margin-left: 4px;
line-height: 1; line-height: 1;
font-size: 0.9em; font-size: 0.8em;
font-weight: bold; font-weight: bold;
white-space: nowrap; white-space: nowrap;
overflow: visible; overflow: hidden;
overflow-wrap: anywhere;
max-width: 300px;
text-overflow: ellipsis;
&::-webkit-scrollbar {
display: none;
}
}
@container (max-width: 400px) {
.name {
max-width: 50px;
}
} }
</style> </style>

View file

@ -47,11 +47,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<Mfm :text="getNoteSummary(appearNote)" :plain="true" :nowrap="true" :author="appearNote.user" :nyaize="'respect'" :class="$style.collapsedRenoteTargetText" @click="renoteCollapsed = false"/> <Mfm :text="getNoteSummary(appearNote)" :plain="true" :nowrap="true" :author="appearNote.user" :nyaize="'respect'" :class="$style.collapsedRenoteTargetText" @click="renoteCollapsed = false"/>
</div> </div>
<article v-else :class="$style.article" @contextmenu.stop="onContextmenu"> <article v-else :class="$style.article" @contextmenu.stop="onContextmenu">
<div v-if="appearNote.channel" :class="$style.colorBar" :style="{ background: appearNote.channel.color }"></div> <div style="display: flex; padding-bottom: 10px;">
<MkAvatar :class="$style.avatar" :user="appearNote.user" :link="!mock" :preview="!mock"/> <div v-if="appearNote.channel" :class="$style.colorBar" :style="{ background: appearNote.channel.color }"></div>
<div :class="[$style.main, { [$style.clickToOpen]: defaultStore.state.clickToOpen }]" @click="defaultStore.state.clickToOpen ? noteclick(appearNote.id) : undefined"> <MkAvatar :class="[$style.avatar, { [$style.avatarReplyTo]: appearNote.reply }]" :user="appearNote.user" :link="!mock" :preview="!mock"/>
<MkNoteHeader :note="appearNote" :mini="true" v-on:click.stop/> <div :class="$style.main">
<MkInstanceTicker v-if="showTicker" :instance="appearNote.user.instance"/> <MkNoteHeader :note="appearNote" :mini="true"/>
</div>
</div>
<div :class="[{ [$style.clickToOpen]: defaultStore.state.clickToOpen }]" @click="defaultStore.state.clickToOpen ? noteclick(appearNote.id) : undefined">
<div style="container-type: inline-size;"> <div style="container-type: inline-size;">
<p v-if="appearNote.cw != null" :class="$style.cw"> <p v-if="appearNote.cw != null" :class="$style.cw">
<Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :nyaize="'respect'"/> <Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :nyaize="'respect'"/>
@ -60,7 +63,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-show="appearNote.cw == null || showContent" :class="[{ [$style.contentCollapsed]: collapsed }]" > <div v-show="appearNote.cw == null || showContent" :class="[{ [$style.contentCollapsed]: collapsed }]" >
<div :class="$style.text"> <div :class="$style.text">
<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span> <span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
<MkA v-if="appearNote.replyId" :class="$style.replyIcon" :to="`/notes/${appearNote.replyId}`"><i class="ph-arrow-bend-left-up ph-bold ph-lg"></i></MkA>
<Mfm <Mfm
v-if="appearNote.text" v-if="appearNote.text"
:parsedNodes="parsed" :parsedNodes="parsed"
@ -176,7 +178,6 @@ import MkCwButton from '@/components/MkCwButton.vue';
import MkPoll from '@/components/MkPoll.vue'; import MkPoll from '@/components/MkPoll.vue';
import MkUsersTooltip from '@/components/MkUsersTooltip.vue'; import MkUsersTooltip from '@/components/MkUsersTooltip.vue';
import MkUrlPreview from '@/components/MkUrlPreview.vue'; import MkUrlPreview from '@/components/MkUrlPreview.vue';
import MkInstanceTicker from '@/components/MkInstanceTicker.vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import { pleaseLogin } from '@/scripts/please-login.js'; import { pleaseLogin } from '@/scripts/please-login.js';
import { focusPrev, focusNext } from '@/scripts/focus.js'; import { focusPrev, focusNext } from '@/scripts/focus.js';
@ -830,7 +831,7 @@ function emitUpdReaction(emoji: string, delta: number) {
position: relative; position: relative;
display: flex; display: flex;
align-items: center; align-items: center;
padding: 16px 32px 8px 32px; padding: 24px 38px 16px;
line-height: 28px; line-height: 28px;
white-space: pre; white-space: pre;
color: var(--renote); color: var(--renote);
@ -882,7 +883,7 @@ function emitUpdReaction(emoji: string, delta: number) {
align-items: center; align-items: center;
line-height: 28px; line-height: 28px;
white-space: pre; white-space: pre;
padding: 0 32px 18px; padding: 8px 38px 24px;
} }
.collapsedRenoteTargetAvatar { .collapsedRenoteTargetAvatar {
@ -909,7 +910,6 @@ function emitUpdReaction(emoji: string, delta: number) {
.article { .article {
position: relative; position: relative;
display: flex;
padding: 28px 32px; padding: 28px 32px;
} }
@ -926,12 +926,19 @@ function emitUpdReaction(emoji: string, delta: number) {
.avatar { .avatar {
flex-shrink: 0; flex-shrink: 0;
display: block !important; display: block !important;
position: sticky !important;
margin: 0 14px 0 0; margin: 0 14px 0 0;
width: 58px; width: 58px;
height: 58px; height: 58px;
position: sticky !important; position: sticky !important;
top: calc(22px + var(--stickyTop, 0px)); top: calc(22px + var(--stickyTop, 0px));
left: 0; left: 0;
transition: top 0.5s;
&.avatarReplyTo {
position: relative !important;
top: 0 !important;
}
} }
.main { .main {
@ -994,7 +1001,6 @@ function emitUpdReaction(emoji: string, delta: number) {
.text { .text {
overflow-wrap: break-word; overflow-wrap: break-word;
overflow: hidden;
} }
.replyIcon { .replyIcon {
@ -1027,7 +1033,8 @@ function emitUpdReaction(emoji: string, delta: number) {
.quoteNote { .quoteNote {
padding: 16px; padding: 16px;
border: dashed 1px var(--renote); // Made border solid, stylistic choice
border: solid 1px var(--renote);
border-radius: var(--radius-sm); border-radius: var(--radius-sm);
overflow: clip; overflow: clip;
} }
@ -1067,7 +1074,11 @@ function emitUpdReaction(emoji: string, delta: number) {
} }
.renote { .renote {
padding: 12px 26px 0 26px; padding: 24px 28px 16px;
}
.collapsedRenoteTarget {
padding: 8px 28px 24px;
} }
.article { .article {
@ -1085,12 +1096,8 @@ function emitUpdReaction(emoji: string, delta: number) {
font-size: 0.9em; font-size: 0.9em;
} }
.renote {
padding: 10px 22px 0 22px;
}
.article { .article {
padding: 20px 22px; padding: 23px 25px;
} }
.footer { .footer {
@ -1100,7 +1107,7 @@ function emitUpdReaction(emoji: string, delta: number) {
@container (max-width: 480px) { @container (max-width: 480px) {
.renote { .renote {
padding: 8px 16px 0 16px; padding: 20px 24px 8px;
} }
.tip { .tip {
@ -1108,12 +1115,12 @@ function emitUpdReaction(emoji: string, delta: number) {
} }
.collapsedRenoteTarget { .collapsedRenoteTarget {
padding: 0 16px 9px; padding: 8px 24px 20px;
margin-top: 4px; margin-top: 4px;
} }
.article { .article {
padding: 14px 16px; padding: 22px 24px;
} }
} }

View file

@ -11,13 +11,9 @@ SPDX-License-Identifier: AGPL-3.0-only
v-hotkey="keymap" v-hotkey="keymap"
:class="$style.root" :class="$style.root"
> >
<div v-if="appearNote.reply && appearNote.reply.replyId"> <div v-if="appearNote.reply && appearNote.reply.replyId && !conversationLoaded" style="padding: 16px">
<div v-if="!conversationLoaded" style="padding: 16px"> <MkButton style="margin: 0 auto;" primary rounded @click="loadConversation">{{ i18n.ts.loadConversation }}</MkButton>
<MkButton style="margin: 0 auto;" primary rounded @click="loadConversation">{{ i18n.ts.loadConversation }}</MkButton>
</div>
<MkNoteSub v-for="note in conversation" :key="note.id" :class="$style.replyToMore" :note="note" :expandAllCws="props.expandAllCws"/>
</div> </div>
<MkNoteSub v-if="appearNote.reply" :note="appearNote.reply" :class="$style.replyTo" :expandAllCws="props.expandAllCws"/>
<div v-if="isRenote" :class="$style.renote"> <div v-if="isRenote" :class="$style.renote">
<MkAvatar :class="$style.renoteAvatar" :user="note.user" link preview/> <MkAvatar :class="$style.renoteAvatar" :user="note.user" link preview/>
<i class="ph-rocket-launch ph-bold ph-lg" style="margin-right: 4px;"></i> <i class="ph-rocket-launch ph-bold ph-lg" style="margin-right: 4px;"></i>
@ -43,15 +39,29 @@ SPDX-License-Identifier: AGPL-3.0-only
<span v-if="note.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['disableFederation']"><i class="ph-rocket ph-bold ph-lg"></i></span> <span v-if="note.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['disableFederation']"><i class="ph-rocket ph-bold ph-lg"></i></span>
</div> </div>
</div> </div>
<template v-if="appearNote.reply && appearNote.reply.replyId">
<MkNoteSub v-for="note in conversation" :key="note.id" :class="$style.replyToMore" :note="note" :expandAllCws="props.expandAllCws"/>
</template>
<MkNoteSub v-if="appearNote.reply" :note="appearNote.reply" :class="$style.replyTo" :expandAllCws="props.expandAllCws"/>
<article :class="$style.note" @contextmenu.stop="onContextmenu"> <article :class="$style.note" @contextmenu.stop="onContextmenu">
<header :class="$style.noteHeader"> <header :class="$style.noteHeader">
<MkAvatar :class="$style.noteHeaderAvatar" :user="appearNote.user" indicator link preview/> <MkAvatar :class="$style.noteHeaderAvatar" :user="appearNote.user" indicator link preview/>
<div :class="$style.noteHeaderBody"> <div style="display: flex; align-items: center; white-space: nowrap; overflow: hidden;">
<div> <div :class="$style.noteHeaderBody">
<MkA v-user-preview="appearNote.user.id" :class="$style.noteHeaderName" :to="userPage(appearNote.user)"> <div>
<MkUserName :nowrap="false" :user="appearNote.user"/> <MkA v-user-preview="appearNote.user.id" :class="$style.noteHeaderName" :to="userPage(appearNote.user)">
</MkA> <MkUserName :nowrap="false" :user="appearNote.user"/>
<span v-if="appearNote.user.isBot" :class="$style.isBot">bot</span> </MkA>
<span v-if="appearNote.user.isBot" :class="$style.isBot">bot</span>
<span v-if="appearNote.user.badgeRoles" :class="$style.badgeRoles">
<img v-for="role in appearNote.user.badgeRoles" :key="role.id" v-tooltip="role.name" :class="$style.badgeRole" :src="role.iconUrl"/>
</span>
</div>
<div :class="$style.noteHeaderUsername"><MkAcct :user="appearNote.user"/></div>
</div>
</div>
<div style="display: flex; align-items: flex-end; margin-left: auto;">
<div :class="$style.noteHeaderBody">
<div :class="$style.noteHeaderInfo"> <div :class="$style.noteHeaderInfo">
<span v-if="appearNote.visibility !== 'public'" style="margin-left: 0.5em;" :title="i18n.ts._visibility[appearNote.visibility]"> <span v-if="appearNote.visibility !== 'public'" style="margin-left: 0.5em;" :title="i18n.ts._visibility[appearNote.visibility]">
<i v-if="appearNote.visibility === 'home'" class="ph-house ph-bold ph-lg"></i> <i v-if="appearNote.visibility === 'home'" class="ph-house ph-bold ph-lg"></i>
@ -61,9 +71,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<span v-if="appearNote.updatedAt" ref="menuVersionsButton" style="margin-left: 0.5em;" title="Edited" @mousedown="menuVersions()"><i class="ph-pencil ph-bold ph-lg"></i></span> <span v-if="appearNote.updatedAt" ref="menuVersionsButton" style="margin-left: 0.5em;" title="Edited" @mousedown="menuVersions()"><i class="ph-pencil ph-bold ph-lg"></i></span>
<span v-if="appearNote.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['disableFederation']"><i class="ph-rocket ph-bold ph-lg"></i></span> <span v-if="appearNote.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['disableFederation']"><i class="ph-rocket ph-bold ph-lg"></i></span>
</div> </div>
<MkInstanceTicker v-if="showTicker" :instance="appearNote.user.instance"/>
</div> </div>
<div :class="$style.noteHeaderUsername"><MkAcct :user="appearNote.user"/></div>
<MkInstanceTicker v-if="showTicker" :instance="appearNote.user.instance"/>
</div> </div>
</header> </header>
<div :class="$style.noteContent"> <div :class="$style.noteContent">
@ -73,7 +82,6 @@ SPDX-License-Identifier: AGPL-3.0-only
</p> </p>
<div v-show="appearNote.cw == null || showContent"> <div v-show="appearNote.cw == null || showContent">
<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span> <span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
<MkA v-if="appearNote.replyId" :class="$style.noteReplyTarget" :to="`/notes/${appearNote.replyId}`"><i class="ph-arrow-bend-left-up ph-bold ph-lg"></i></MkA>
<Mfm <Mfm
v-if="appearNote.text" v-if="appearNote.text"
:parsedNodes="parsed" :parsedNodes="parsed"
@ -851,12 +859,19 @@ function animatedMFM() {
.noteHeaderInfo { .noteHeaderInfo {
float: right; float: right;
text-align: right;
} }
.noteHeaderUsername { .noteHeaderUsername {
margin-bottom: 2px; margin-bottom: 2px;
line-height: 1.3; line-height: 1.3;
word-wrap: anywhere; word-wrap: anywhere;
text-overflow: ellipsis;
white-space: nowrap;
&::-webkit-scrollbar {
display: none;
}
} }
.playMFMButton { .playMFMButton {
@ -1037,9 +1052,31 @@ function animatedMFM() {
} }
} }
.avatar {
flex-shrink: 0 !important;
display: block !important;
margin: 0 10px 0 0 !important;
width: 40px !important;
height: 40px !important;
border-radius: var(--radius-sm) !important;
}
.muted { .muted {
padding: 8px; padding: 8px;
text-align: center; text-align: center;
opacity: 0.7; opacity: 0.7;
} }
.badgeRoles {
margin: 0 .5em 0 0;
}
.badgeRole {
height: 1.3em;
vertical-align: -20%;
& + .badgeRole {
margin-left: 0.2em;
}
}
</style> </style>

View file

@ -4,19 +4,56 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<header :class="$style.root"> <header v-if="!classic" :class="$style.root">
<div :class="$style.section">
<div style="display: flex;">
<div v-if="mock" :class="$style.name">
<MkUserName :user="note.user"/>
</div>
<MkA v-else v-user-preview="note.user.id" :class="$style.name" :to="userPage(note.user)">
<MkUserName :user="note.user"/>
</MkA>
<div v-if="note.user.isBot" :class="$style.isBot">bot</div>
<div v-if="note.user.badgeRoles" :class="$style.badgeRoles">
<img v-for="role in note.user.badgeRoles" :key="role.id" v-tooltip="role.name" :class="$style.badgeRole" :src="role.iconUrl"/>
</div>
</div>
<div :class="$style.username"><MkAcct :user="note.user"/></div>
</div>
<div :class="$style.section">
<div :class="$style.info">
<div v-if="mock">
<MkTime :time="note.createdAt" colored/>
</div>
<MkA v-else :class="$style.time" :to="notePage(note)">
<MkTime :time="note.createdAt" colored/>
</MkA>
<span v-if="note.visibility !== 'public'" style="margin-left: 0.5em;" :title="i18n.ts._visibility[note.visibility]">
<i v-if="note.visibility === 'home'" class="ph-house ph-bold ph-lg"></i>
<i v-else-if="note.visibility === 'followers'" class="ph-lock ph-bold ph-lg"></i>
<i v-else-if="note.visibility === 'specified'" ref="specified" class="ph-envelope ph-bold ph-lg"></i>
</span>
<span v-if="note.updatedAt" ref="menuVersionsButton" style="margin-left: 0.5em; cursor: pointer;" title="Edited" @mousedown="menuVersions()"><i class="ph-pencil ph-bold ph-lg"></i></span>
<span v-if="note.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['disableFederation']"><i class="ph-rocket ph-bold ph-lg"></i></span>
<span v-if="note.channel" style="margin-left: 0.5em;" :title="note.channel.name"><i class="ph-television ph-bold ph-lg"></i></span>
</div>
<div :class="$style.info"><MkInstanceTicker v-if="showTicker" style="cursor: pointer;" :instance="note.user.instance" @click.stop="showOnRemote()"/></div>
</div>
</header>
<header v-else :class="$style.classicRoot">
<div v-if="mock" :class="$style.name"> <div v-if="mock" :class="$style.name">
<MkUserName :user="note.user"/> <MkUserName :user="note.user"/>
</div> </div>
<MkA v-else v-user-preview="note.user.id" :class="$style.name" :to="userPage(note.user)"> <MkA v-else v-user-preview="note.user.id" :class="$style.classicName" :to="userPage(note.user)">
<MkUserName :user="note.user"/> <MkUserName :user="note.user"/>
</MkA> </MkA>
<div v-if="note.user.isBot" :class="$style.isBot">bot</div> <div v-if="note.user.isBot" :class="$style.isBot">bot</div>
<div :class="$style.username"><MkAcct :user="note.user"/></div> <div :class="$style.classicUsername"><MkAcct :user="note.user"/></div>
<div v-if="note.user.badgeRoles" :class="$style.badgeRoles"> <div v-if="note.user.badgeRoles" :class="$style.badgeRoles">
<img v-for="role in note.user.badgeRoles" :key="role.id" v-tooltip="role.name" :class="$style.badgeRole" :src="role.iconUrl"/> <img v-for="role in note.user.badgeRoles" :key="role.id" v-tooltip="role.name" :class="$style.badgeRole" :src="role.iconUrl"/>
</div> </div>
<div :class="$style.info"> <MkInstanceTicker v-if="showTicker && !isMobile && defaultStore.state.showTickerOnReplies" style="cursor: pointer; max-height: 5px; top: 3px; position: relative; margin-top: 0px !important;" :instance="note.user.instance" @click.stop="showOnRemote()"/>
<div :class="$style.classicInfo">
<div v-if="mock"> <div v-if="mock">
<MkTime :time="note.createdAt" colored/> <MkTime :time="note.createdAt" colored/>
</div> </div>
@ -36,19 +73,29 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { inject, shallowRef } from 'vue'; import { inject, shallowRef, ref } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { notePage } from '@/filters/note.js'; import { notePage } from '@/filters/note.js';
import { userPage } from '@/filters/user.js'; import { userPage } from '@/filters/user.js';
import { getNoteVersionsMenu } from '@/scripts/get-note-versions-menu.js'; import { getNoteVersionsMenu } from '@/scripts/get-note-versions-menu.js';
import MkInstanceTicker from '@/components/MkInstanceTicker.vue';
import { popupMenu } from '@/os.js'; import { popupMenu } from '@/os.js';
import { defaultStore } from '@/store.js';
import { useRouter } from '@/router.js';
import { deviceKind } from '@/scripts/device-kind.js';
const props = defineProps<{ const props = defineProps<{
note: Misskey.entities.Note; note: Misskey.entities.Note;
classic?: boolean;
}>(); }>();
const menuVersionsButton = shallowRef<HTMLElement>(); const menuVersionsButton = shallowRef<HTMLElement>();
const router = useRouter();
const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && props.note.user.instance);
const MOBILE_THRESHOLD = 500;
const isMobile = ref(deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD);
async function menuVersions(viaKeyboard = false): Promise<void> { async function menuVersions(viaKeyboard = false): Promise<void> {
const { menu, cleanup } = await getNoteVersionsMenu({ note: props.note, menuVersionsButton }); const { menu, cleanup } = await getNoteVersionsMenu({ note: props.note, menuVersionsButton });
@ -57,18 +104,67 @@ async function menuVersions(viaKeyboard = false): Promise<void> {
}).then(focus).finally(cleanup); }).then(focus).finally(cleanup);
} }
function showOnRemote() {
if (props.note.url ?? props.note.uri === undefined) router.push(notePage(props.note));
else window.open(props.note.url ?? props.note.uri);
}
const mock = inject<boolean>('mock', false); const mock = inject<boolean>('mock', false);
</script> </script>
<style lang="scss" module> <style lang="scss" module>
.root { .root {
display: flex;
cursor: auto; /* not clickToOpen-able */
}
.classicRoot {
display: flex; display: flex;
align-items: baseline; align-items: baseline;
white-space: nowrap; white-space: nowrap;
cursor: auto; /* not clickToOpen-able */ cursor: auto; /* not clickToOpen-able */
} }
.section {
align-items: flex-start;
white-space: nowrap;
flex-direction: column;
overflow: hidden;
&:last-child {
display: flex;
align-items: flex-end;
margin-left: auto;
padding-left: 10px;
overflow: clip;
}
}
.name { .name {
flex-shrink: 1;
display: block;
// note, these margin top values were done by hand may need futher checking if it actualy aligns pixel perfect
margin: 3px .5em 0 0;
padding: 0;
overflow: scroll;
overflow-wrap: anywhere;
font-size: 1em;
font-weight: bold;
text-decoration: none;
text-overflow: ellipsis;
max-width: 300px;
&::-webkit-scrollbar {
display: none;
}
&:hover {
color: var(--nameHover);
text-decoration: none;
}
}
.classicName {
flex-shrink: 1; flex-shrink: 1;
display: block; display: block;
margin: 0 .5em 0 0; margin: 0 .5em 0 0;
@ -95,6 +191,20 @@ const mock = inject<boolean>('mock', false);
} }
.username { .username {
flex-shrink: 9999999;
// note these top margins were made to align with the instance ticker
margin: 4px .5em 0 0;
overflow: hidden;
text-overflow: ellipsis;
font-size: .95em;
max-width: 300px;
&::-webkit-scrollbar {
display: none;
}
}
.classicUsername {
flex-shrink: 9999999; flex-shrink: 9999999;
margin: 0 .5em 0 0; margin: 0 .5em 0 0;
overflow: hidden; overflow: hidden;
@ -102,11 +212,34 @@ const mock = inject<boolean>('mock', false);
} }
.info { .info {
&:first-child {
margin-top: 4px;
flex-shrink: 0;
margin-left: auto;
font-size: 0.9em;
}
&:not(:first-child) {
flex-shrink: 0;
margin-left: auto;
font-size: 0.9em;
}
}
.classicInfo {
flex-shrink: 0; flex-shrink: 0;
margin-left: auto; margin-left: auto;
font-size: 0.9em; font-size: 0.9em;
} }
.time {
text-decoration: none;
&:hover {
text-decoration: none;
}
}
.badgeRoles { .badgeRoles {
margin: 0 .5em 0 0; margin: 0 .5em 0 0;
} }
@ -119,4 +252,14 @@ const mock = inject<boolean>('mock', false);
margin-left: 0.2em; margin-left: 0.2em;
} }
} }
.danger {
color: var(--accent);
}
@container (max-width: 500px) {
.name, .username {
max-width: 200px;
}
}
</style> </style>

View file

@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.root"> <div :class="$style.root">
<MkAvatar :class="$style.avatar" :user="note.user" link preview/> <MkAvatar :class="$style.avatar" :user="note.user" link preview/>
<div :class="$style.main"> <div :class="$style.main">
<MkNoteHeader :class="$style.header" :note="note" :mini="true"/> <MkNoteHeader :class="$style.header" :classic="true" :note="note" :mini="true"/>
<div> <div>
<p v-if="note.cw != null" :class="$style.cw"> <p v-if="note.cw != null" :class="$style.cw">
<Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :nyaize="'respect'" :emojiUrls="note.emojis"/> <Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :nyaize="'respect'" :emojiUrls="note.emojis"/>

View file

@ -5,11 +5,12 @@ SPDX-License-Identifier: AGPL-3.0-only
<template> <template>
<div v-if="!muted" ref="el" :class="[$style.root, { [$style.children]: depth > 1 }]"> <div v-if="!muted" ref="el" :class="[$style.root, { [$style.children]: depth > 1 }]">
<div v-if="!hideLine" :class="$style.line"></div>
<div :class="$style.main"> <div :class="$style.main">
<div v-if="note.channel" :class="$style.colorBar" :style="{ background: note.channel.color }"></div> <div v-if="note.channel" :class="$style.colorBar" :style="{ background: note.channel.color }"></div>
<MkAvatar :class="$style.avatar" :user="note.user" link preview/> <MkAvatar :class="$style.avatar" :user="note.user" link preview/>
<div :class="$style.body"> <div :class="$style.body">
<MkNoteHeader :class="$style.header" :note="note" :mini="true"/> <MkNoteHeader :class="$style.header" :note="note" :classic="true" :mini="true"/>
<div :class="$style.content"> <div :class="$style.content">
<p v-if="note.cw != null" :class="$style.cw"> <p v-if="note.cw != null" :class="$style.cw">
<Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :nyaize="'respect'"/> <Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :nyaize="'respect'"/>
@ -106,6 +107,7 @@ import { getNoteMenu } from '@/scripts/get-note-menu.js';
import { useNoteCapture } from '@/scripts/use-note-capture.js'; import { useNoteCapture } from '@/scripts/use-note-capture.js';
const canRenote = computed(() => ['public', 'home'].includes(props.note.visibility) || props.note.userId === $i.id); const canRenote = computed(() => ['public', 'home'].includes(props.note.visibility) || props.note.userId === $i.id);
const hideLine = computed(() => { return props.detail ? true : false; });
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
note: Misskey.entities.Note; note: Misskey.entities.Note;
@ -361,7 +363,7 @@ if (props.detail) {
<style lang="scss" module> <style lang="scss" module>
.root { .root {
padding: 16px 32px; padding: 28px 32px;
font-size: 0.9em; font-size: 0.9em;
position: relative; position: relative;
@ -371,12 +373,20 @@ if (props.detail) {
} }
} }
.line {
position: absolute;
height: 100%;
left: 60px;
// using solid instead of dotted, stylelistic choice
border-left: 2.5px solid rgb(174, 174, 174);
}
.footer { .footer {
position: relative; position: relative;
z-index: 1; z-index: 1;
margin-top: 0.4em; margin-top: 0.4em;
width: max-content; width: max-content;
min-width: max-content; min-width: max-content;
} }
.main { .main {
@ -396,9 +406,9 @@ if (props.detail) {
.avatar { .avatar {
flex-shrink: 0; flex-shrink: 0;
display: block; display: block;
margin: 0 8px 0 0; margin: 0 14px 0 0;
width: 38px; width: 58px;
height: 38px; height: 58px;
border-radius: var(--radius-sm); border-radius: var(--radius-sm);
} }
@ -411,6 +421,11 @@ if (props.detail) {
overflow: hidden; overflow: hidden;
} }
.text {
margin: 0;
padding: 0;
}
.header { .header {
margin-bottom: 2px; margin-bottom: 2px;
} }
@ -430,6 +445,36 @@ if (props.detail) {
} }
} }
.reply, .more {
border-left: solid 0.5px var(--divider);
margin-top: 10px;
}
.more {
padding: 10px 0 0 16px;
}
@container (max-width: 580px) {
.root {
padding: 28px 26px 0;
}
.line {
left: 50.5px;
}
.avatar {
width: 50px;
height: 50px;
}
}
@container (max-width: 500px) {
.root {
padding: 23px 25px;
}
}
@container (max-width: 400px) { @container (max-width: 400px) {
.noteFooterButton { .noteFooterButton {
&:not(:last-child) { &:not(:last-child) {
@ -469,9 +514,9 @@ if (props.detail) {
padding: 10px 0 0 16px; padding: 10px 0 0 16px;
} }
@container (max-width: 450px) { @container (max-width: 480px) {
.root { .root {
padding: 14px 16px; padding: 22px 24px;
&.children { &.children {
padding: 10px 0 0 8px; padding: 10px 0 0 8px;
@ -479,6 +524,17 @@ if (props.detail) {
} }
} }
@container (max-width: 450px) {
.line {
left: 46px;
}
.avatar {
width: 46px;
height: 46px;
}
}
.muted { .muted {
text-align: center; text-align: center;
padding: 8px !important; padding: 8px !important;

View file

@ -52,6 +52,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-if="advancedMfm" v-model="animatedMfm">{{ i18n.ts.enableAnimatedMfm }}</MkSwitch> <MkSwitch v-if="advancedMfm" v-model="animatedMfm">{{ i18n.ts.enableAnimatedMfm }}</MkSwitch>
<MkSwitch v-model="showGapBetweenNotesInTimeline">{{ i18n.ts.showGapBetweenNotesInTimeline }}</MkSwitch> <MkSwitch v-model="showGapBetweenNotesInTimeline">{{ i18n.ts.showGapBetweenNotesInTimeline }}</MkSwitch>
<MkSwitch v-model="loadRawImages">{{ i18n.ts.loadRawImages }}</MkSwitch> <MkSwitch v-model="loadRawImages">{{ i18n.ts.loadRawImages }}</MkSwitch>
<MkSwitch v-model="showTickerOnReplies">Show instance ticker on replies</MkSwitch>
<MkRadios v-model="reactionsDisplaySize"> <MkRadios v-model="reactionsDisplaySize">
<template #label>{{ i18n.ts.reactionsDisplaySize }}</template> <template #label>{{ i18n.ts.reactionsDisplaySize }}</template>
<option value="small">{{ i18n.ts.small }}</option> <option value="small">{{ i18n.ts.small }}</option>
@ -271,6 +272,7 @@ const notificationStackAxis = computed(defaultStore.makeGetterSetter('notificati
const keepScreenOn = computed(defaultStore.makeGetterSetter('keepScreenOn')); const keepScreenOn = computed(defaultStore.makeGetterSetter('keepScreenOn'));
const disableStreamingTimeline = computed(defaultStore.makeGetterSetter('disableStreamingTimeline')); const disableStreamingTimeline = computed(defaultStore.makeGetterSetter('disableStreamingTimeline'));
const useGroupedNotifications = computed(defaultStore.makeGetterSetter('useGroupedNotifications')); const useGroupedNotifications = computed(defaultStore.makeGetterSetter('useGroupedNotifications'));
const showTickerOnReplies = computed(defaultStore.makeGetterSetter('showTickerOnReplies'));
watch(lang, () => { watch(lang, () => {
miLocalStorage.setItem('lang', lang.value as string); miLocalStorage.setItem('lang', lang.value as string);

View file

@ -250,6 +250,10 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'device', where: 'device',
default: false, default: false,
}, },
showTickerOnReplies: {
where: 'device',
default: false,
},
enableInfiniteScroll: { enableInfiniteScroll: {
where: 'device', where: 'device',
default: true, default: true,