From 781ee172fea30c34690c63859d8325a05dd0ae31 Mon Sep 17 00:00:00 2001 From: Freeplay <Freeplay@duck.com> Date: Wed, 17 May 2023 18:56:03 -0400 Subject: [PATCH 01/11] Display account info of post rather than 'Post' in header --- packages/client/src/pages/note.vue | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/client/src/pages/note.vue b/packages/client/src/pages/note.vue index dbc2f2aa9..cee0bf854 100644 --- a/packages/client/src/pages/note.vue +++ b/packages/client/src/pages/note.vue @@ -26,6 +26,7 @@ v-if="!showNext && hasNext" class="load next" @click="showNext = true" + v-tooltip="i18n.ts.loadMore" ><i class="ph-caret-up ph-bold ph-lg"></i ></MkButton> <div class="note _gap"> @@ -74,6 +75,7 @@ v-if="!showPrev && hasPrev" class="load prev" @click="showPrev = true" + v-tooltip="i18n.ts.loadMore" ><i class="ph-caret-down ph-bold ph-lg"></i ></MkButton> </div> @@ -193,8 +195,8 @@ definePageMetadata( computed(() => note ? { - title: i18n.ts.note, - subtitle: new Date(note.createdAt).toLocaleString(), + title: note.user.name, + subtitle: `@${note.user.username}@${note.user.host}`, avatar: note.user, path: `/notes/${note.id}`, share: { From dc377c926328c437c909e2a18c526ceccbfb0bb7 Mon Sep 17 00:00:00 2001 From: Freeplay <Freeplay@duck.com> Date: Wed, 17 May 2023 21:54:37 -0400 Subject: [PATCH 02/11] Jump to Reply + other note page improvmeents --- locales/en-US.yml | 3 +++ .../client/src/components/MkNoteDetailed.vue | 18 ++++++++++++++++-- packages/client/src/components/MkNoteSub.vue | 6 ++++++ .../client/src/components/MkSubNoteContent.vue | 8 ++++++-- packages/client/src/components/global/MkA.vue | 11 +++-------- .../src/components/global/MkPageHeader.vue | 7 ++++++- packages/client/src/pages/note.vue | 9 +++++++-- 7 files changed, 47 insertions(+), 15 deletions(-) diff --git a/locales/en-US.yml b/locales/en-US.yml index 254ab1346..ec24104b0 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -57,8 +57,11 @@ sendMessage: "Send a message" copyUsername: "Copy username" searchUser: "Search for a user" reply: "Reply" +jumpToReply: "Jump to Reply" loadMore: "Load more" showMore: "Show more" +newer: "newer" +older: "older" showLess: "Close" youGotNewFollower: "followed you" receiveFollowRequest: "Follow request received" diff --git a/packages/client/src/components/MkNoteDetailed.vue b/packages/client/src/components/MkNoteDetailed.vue index b1caca160..00c5ba9d2 100644 --- a/packages/client/src/components/MkNoteDetailed.vue +++ b/packages/client/src/components/MkNoteDetailed.vue @@ -15,20 +15,22 @@ :key="note.id" class="reply-to" :note="note" + :detailedView="true" /> <MkLoading v-else-if="appearNote.reply" mini /> <MkNoteSub v-if="appearNote.reply" :note="appearNote.reply" class="reply-to" + :detailedView="true" /> - <div ref="noteEl" class="article" tabindex="-1"> + <div ref="noteEl" class="article" tabindex="-1" :id="appearNote.id"> <MkNote @contextmenu.stop="onContextmenu" tabindex="-1" :note="appearNote" - :detailedView="true" + detailedView ></MkNote> </div> @@ -39,6 +41,7 @@ :note="note" class="reply" :conversation="replies" + :detailedView="true" /> <MkLoading v-else-if="appearNote.repliesCount > 0" /> </div> @@ -329,6 +332,9 @@ onMounted(() => { onUpdated(() => { if (!isScrolling) { noteEl?.scrollIntoView(); + if (location.hash) { + location.replace(location.hash); // Jump to highlighted reply + } } }); @@ -510,6 +516,14 @@ onUnmounted(() => { // } // } } + :deep(.reply:target > .main), + :deep(.reply-to:target) { + z-index: 2; + &::before { + outline: auto; + opacity: 1; + } + } &.max-width_500px { font-size: 0.9em; diff --git a/packages/client/src/components/MkNoteSub.vue b/packages/client/src/components/MkNoteSub.vue index f27b3e27a..c0d2405e4 100644 --- a/packages/client/src/components/MkNoteSub.vue +++ b/packages/client/src/components/MkNoteSub.vue @@ -4,6 +4,8 @@ ref="el" v-size="{ max: [450, 500] }" class="wrpstxzv" + :id="detailedView ? appearNote.id : null" + tabindex="-1" :class="{ children: depth > 1, singleStart: replies.length == 1, @@ -138,6 +140,7 @@ :depth="depth" :replyLevel="replyLevel + 1" :parentId="appearNote.replyId" + :detailedView="detailedView" /> </template> <template v-else> @@ -150,6 +153,7 @@ :depth="depth + 1" :replyLevel="replyLevel + 1" :parentId="appearNote.replyId" + :detailedView="detailedView" /> </template> </template> @@ -212,6 +216,7 @@ const props = withDefaults( note: misskey.entities.Note; conversation?: misskey.entities.Note[]; parentId?; + detailedView?; // how many notes are in between this one and the note being viewed in detail depth?: number; @@ -348,6 +353,7 @@ function noteClick(e) { <style lang="scss" scoped> .wrpstxzv { padding: 16px 32px; + outline: none; &.children { padding: 10px 0 0 var(--indent); padding-left: var(--indent) !important; diff --git a/packages/client/src/components/MkSubNoteContent.vue b/packages/client/src/components/MkSubNoteContent.vue index 4742611ac..d683e9a82 100644 --- a/packages/client/src/components/MkSubNoteContent.vue +++ b/packages/client/src/components/MkSubNoteContent.vue @@ -2,7 +2,8 @@ <p v-if="note.cw != null" class="cw"> <MkA v-if="!detailed && note.replyId" - :to="`/notes/${note.replyId}`" + :to="`#${note.replyId}`" + behavior="browser" class="reply-icon" @click.stop > @@ -16,6 +17,7 @@ !note.replyId " :to="`/notes/${note.renoteId}`" + v-tooltip="i18n.ts.jumpToReply" class="reply-icon" @click.stop > @@ -60,7 +62,9 @@ <template v-if="!note.cw"> <MkA v-if="!detailed && note.replyId" - :to="`/notes/${note.replyId}`" + :to="`#${note.replyId}`" + behavior="browser" + v-tooltip="i18n.ts.jumpToReply" class="reply-icon" @click.stop > diff --git a/packages/client/src/components/global/MkA.vue b/packages/client/src/components/global/MkA.vue index e1a50ad35..bb1321d9f 100644 --- a/packages/client/src/components/global/MkA.vue +++ b/packages/client/src/components/global/MkA.vue @@ -2,9 +2,8 @@ <a :href="to" :class="active ? activeClass : null" - @click="nav" @contextmenu.prevent.stop="onContextmenu" - @click.stop + @click.stop="nav" > <slot></slot> </a> @@ -99,13 +98,9 @@ function popout() { } function nav(ev: MouseEvent) { - if (!ev.ctrlKey) { - ev.preventDefault(); + if (!ev.ctrlKey && props.behavior !== "browser") { - if (props.behavior === "browser") { - location.href = props.to; - return; - } + ev.preventDefault(); if (props.behavior) { if (props.behavior === "window") { diff --git a/packages/client/src/components/global/MkPageHeader.vue b/packages/client/src/components/global/MkPageHeader.vue index b288fcaa0..eb4abf316 100644 --- a/packages/client/src/components/global/MkPageHeader.vue +++ b/packages/client/src/components/global/MkPageHeader.vue @@ -139,6 +139,7 @@ const props = defineProps<{ thin?: boolean; displayMyAvatar?: boolean; displayBackButton?: boolean; + to?: string; }>(); const emit = defineEmits<{ @@ -193,7 +194,11 @@ const preventDrag = (ev: TouchEvent) => { }; const onClick = () => { - scrollToTop(el, { behavior: "smooth" }); + if (props.to) { + location.href = props.to; + } else { + scrollToTop(el, { behavior: "smooth" }); + } }; function onTabMousedown(tab: Tab, ev: MouseEvent): void { diff --git a/packages/client/src/pages/note.vue b/packages/client/src/pages/note.vue index cee0bf854..d0f6ed639 100644 --- a/packages/client/src/pages/note.vue +++ b/packages/client/src/pages/note.vue @@ -5,6 +5,7 @@ :actions="headerActions" :tabs="headerTabs" :display-back-button="true" + :to="`#${noteId}`" /></template> <MkSpacer :content-max="800" :marginMin="6"> <div class="fcuexfpr"> @@ -26,7 +27,7 @@ v-if="!showNext && hasNext" class="load next" @click="showNext = true" - v-tooltip="i18n.ts.loadMore" + v-tooltip="`${i18n.ts.loadMore} (${i18n.ts.newer})`" ><i class="ph-caret-up ph-bold ph-lg"></i ></MkButton> <div class="note _gap"> @@ -75,7 +76,7 @@ v-if="!showPrev && hasPrev" class="load prev" @click="showPrev = true" - v-tooltip="i18n.ts.loadMore" + v-tooltip="`${i18n.ts.loadMore} (${i18n.ts.older})`" ><i class="ph-caret-down ph-bold ph-lg"></i ></MkButton> </div> @@ -219,6 +220,10 @@ definePageMetadata( opacity: 0; } +:global(html, body) { + scroll-behavior: smooth; +} + .fcuexfpr { #calckey_app > :not(.wallpaper) & { background: var(--bg); From a5d81aafead7946aa9520c05cc219df299b30716 Mon Sep 17 00:00:00 2001 From: Freeplay <Freeplay@duck.com> Date: Thu, 18 May 2023 15:48:36 -0400 Subject: [PATCH 03/11] simplify conversation code --- packages/client/src/components/MkNoteSub.vue | 41 +++++++------------- 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/packages/client/src/components/MkNoteSub.vue b/packages/client/src/components/MkNoteSub.vue index c0d2405e4..9a3e577b6 100644 --- a/packages/client/src/components/MkNoteSub.vue +++ b/packages/client/src/components/MkNoteSub.vue @@ -129,34 +129,19 @@ </div> </div> <template v-if="conversation"> - <template v-if="replyLevel < 11 && depth < 5"> - <template v-if="replies.length == 1"> - <MkNoteSub - v-for="reply in replies" - :key="reply.id" - :note="reply" - class="reply single" - :conversation="conversation" - :depth="depth" - :replyLevel="replyLevel + 1" - :parentId="appearNote.replyId" - :detailedView="detailedView" - /> - </template> - <template v-else> - <MkNoteSub - v-for="reply in replies" - :key="reply.id" - :note="reply" - class="reply" - :conversation="conversation" - :depth="depth + 1" - :replyLevel="replyLevel + 1" - :parentId="appearNote.replyId" - :detailedView="detailedView" - /> - </template> - </template> + <MkNoteSub + v-if="replyLevel < 11 && depth < 5" + v-for="reply in replies" + :key="reply.id" + :note="reply" + class="reply" + :class="{single: replies.length == 1}" + :conversation="conversation" + :depth="replies.lenght == 1 ? depth : depth + 1" + :replyLevel="replyLevel + 1" + :parentId="appearNote.replyId" + :detailedView="detailedView" + /> <div v-else-if="replies.length > 0" class="more"> <div class="line"></div> <MkA class="text _link" :to="notePage(note)" From a6094039c5f57803f0c58d1a408263edc160e2ee Mon Sep 17 00:00:00 2001 From: Freeplay <Freeplay@duck.com> Date: Fri, 19 May 2023 13:02:41 -0400 Subject: [PATCH 04/11] Tabbed content in detailed notes page --- packages/client/src/components/MkNote.vue | 36 ++- .../client/src/components/MkNoteDetailed.vue | 298 ++++++++++++------ .../client/src/components/MkRenoteButton.vue | 3 +- packages/client/src/components/MkTab.vue | 30 +- .../client/src/components/MkUserCardMini.vue | 1 + packages/client/src/pages/note.vue | 70 +--- 6 files changed, 251 insertions(+), 187 deletions(-) diff --git a/packages/client/src/components/MkNote.vue b/packages/client/src/components/MkNote.vue index 5028d711e..9045c890b 100644 --- a/packages/client/src/components/MkNote.vue +++ b/packages/client/src/components/MkNote.vue @@ -5,7 +5,7 @@ ref="el" v-hotkey="keymap" v-size="{ max: [500, 450, 350, 300] }" - class="tkcbzcuz" + class="tkcbzcuz note-container" :tabindex="!isDeleted ? '-1' : null" :class="{ renote: isRenote }" > @@ -104,6 +104,11 @@ /> </div> </div> + </div> + <div v-if="detailedView" class="info"> + <MkA class="created-at" :to="notePage(appearNote)"> + <MkTime :time="appearNote.createdAt" mode="absolute" /> + </MkA> <MkA v-if="appearNote.channel && !inChannel" class="channel" @@ -113,11 +118,6 @@ {{ 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="footerEl" class="footer" @click.stop tabindex="-1"> <XReactionsViewer v-if="enableEmojiReactions" @@ -130,7 +130,7 @@ @click="reply()" > <i class="ph-arrow-u-up-left ph-bold ph-lg"></i> - <template v-if="appearNote.repliesCount > 0"> + <template v-if="appearNote.repliesCount > 0 && !detailedView"> <p class="count">{{ appearNote.repliesCount }}</p> </template> </button> @@ -139,6 +139,7 @@ class="button" :note="appearNote" :count="appearNote.renoteCount" + :detailedView="detailedView" /> <XStarButtonNoEmoji v-if="!enableEmojiReactions" @@ -450,6 +451,10 @@ function focusAfter() { focusNext(el.value); } +function scrollIntoView() { + el.value.scrollIntoView(); +} + function noteClick(e) { if (document.getSelection().type === "Range" || props.detailedView) { e.stopPropagation(); @@ -464,6 +469,12 @@ function readPromo() { }); isDeleted.value = true; } + +defineExpose({ + focus, + blur, + scrollIntoView, +}); </script> <style lang="scss" scoped> @@ -656,14 +667,13 @@ function readPromo() { } } } - - > .channel { - opacity: 0.7; - font-size: 80%; - } } > .info { - margin-block: 16px; + display: flex; + justify-content: space-between; + flex-wrap: wrap; + gap: .7em; + margin-top: 16px; opacity: 0.7; font-size: 0.9em; } diff --git a/packages/client/src/components/MkNoteDetailed.vue b/packages/client/src/components/MkNoteDetailed.vue index 00c5ba9d2..1cffb267e 100644 --- a/packages/client/src/components/MkNoteDetailed.vue +++ b/packages/client/src/components/MkNoteDetailed.vue @@ -25,17 +25,51 @@ :detailedView="true" /> - <div ref="noteEl" class="article" tabindex="-1" :id="appearNote.id"> - <MkNote - @contextmenu.stop="onContextmenu" - tabindex="-1" - :note="appearNote" - detailedView - ></MkNote> - </div> + <MkNote + ref="noteEl" + @contextmenu.stop="onContextmenu" + tabindex="-1" + :note="appearNote" + detailedView + ></MkNote> + + <MkTab + v-model="tab" + :style="'chips'" + @update:modelValue="loadTab" + > + <option value="replies"> + <i class="ph-arrow-u-up-left ph-bold ph-lg"></i> + <template v-if="appearNote.repliesCount > 0"> + <span class="count">{{ appearNote.repliesCount }}</span> + </template> + {{ i18n.ts._notification._types.reply }} + </option> + <option value="renotes"> + <i class="ph-repeat ph-bold ph-lg"></i> + <template v-if="appearNote.renoteCount > 0"> + <span class="count">{{ appearNote.renoteCount }}</span> + </template> + {{ i18n.ts._notification._types.renote }} + </option> + <option value="quotes"> + <i class="ph-quotes ph-bold ph-lg"></i> + <template v-if="directQuotes?.length > 0"> + <span class="count">{{ directQuotes.length }}</span> + </template> + {{ i18n.ts._notification._types.quote }} + </option> + <option value="clips"> + <i class="ph-paperclip ph-bold ph-lg"></i> + <template v-if="clips.length > 0"> + <span class="count">{{ clips.length }}</span> + </template> + {{ i18n.ts.clips }} + </option> + </MkTab> <MkNoteSub - v-if="directReplies" + v-if="directReplies && tab === 'replies'" v-for="note in directReplies" :key="note.id" :note="note" @@ -43,7 +77,66 @@ :conversation="replies" :detailedView="true" /> - <MkLoading v-else-if="appearNote.repliesCount > 0" /> + <MkLoading v-else-if="tab === 'replies' && appearNote.repliesCount > 0" /> + + <MkNoteSub + v-if="directQuotes && tab === 'quotes'" + v-for="note in directQuotes" + :key="note.id" + :note="note" + class="reply" + :conversation="directQuotes" + :detailedView="true" + /> + <MkLoading v-else-if="tab === 'quotes' && directQuotes.length > 0" /> + + <!-- <MkPagination + v-if="tab === 'renotes'" + v-slot="{ items }" + ref="pagingComponent" + :pagination="pagination" + > --> + <MkUserCardMini + v-if="tab === 'renotes' && appearNote.renoteCount > 0" + v-for="item in renotes" + :key="item.user.id" + :user="item.user" + :with-chart="false" + /> + <!-- </MkPagination> --> + <MkLoading v-else-if="tab === 'renotes' && appearNote.renoteCount > 0" /> + + <div + v-if="tab === 'clips' && clips.length > 0" + class="_content clips" + > + <MkA + v-for="item in clips" + :key="item.id" + :to="`/clips/${item.id}`" + class="item _panel" + > + <b>{{ item.name }}</b> + <div + v-if="item.description" + class="description" + > + {{ item.description }} + </div> + <div class="user"> + <MkAvatar + :user="item.user" + class="avatar" + :show-indicator="true" + /> + <MkUserName + :user="item.user" + :nowrap="false" + /> + </div> + </MkA> + </div> + <MkLoading v-else-if="tab === 'clips' && clips.length > 0" /> </div> <div v-else class="_panel muted" @click="muted.muted = false"> <I18n :src="softMuteReasonI18nSrc(muted.what)" tag="small"> @@ -73,15 +166,17 @@ import { reactive, ref, } from "vue"; -import type * as misskey from "calckey-js"; +import * as misskey from "calckey-js"; +import MkTab from "@/components/MkTab.vue"; import MkNote from "@/components/MkNote.vue"; import MkNoteSub from "@/components/MkNoteSub.vue"; import XStarButton from "@/components/MkStarButton.vue"; import XRenoteButton from "@/components/MkRenoteButton.vue"; +import MkPagination from "@/components/MkPagination.vue"; +import MkUserCardMini from "@/components/MkUserCardMini.vue"; import { pleaseLogin } from "@/scripts/please-login"; import { getWordSoftMute } from "@/scripts/check-word-mute"; import { userPage } from "@/filters/user"; -import { useRouter } from "@/router"; import * as os from "@/os"; import { defaultStore, noteViewInterruptors } from "@/store"; import { reactionPicker } from "@/scripts/reaction-picker"; @@ -92,12 +187,15 @@ import { useNoteCapture } from "@/scripts/use-note-capture"; import { deepClone } from "@/scripts/clone"; import { stream } from "@/stream"; import { NoteUpdatedEvent } from "calckey-js/built/streaming.types"; +import appear from "@/directives/appear"; const props = defineProps<{ note: misskey.entities.Note; pinned?: boolean; }>(); +let tab = $ref("replies"); + let note = $ref(deepClone(props.note)); const softMuteReasonI18nSrc = (what?: string) => { @@ -146,6 +244,9 @@ const translating = ref(false); let conversation = $ref<null | misskey.entities.Note[]>([]); const replies = ref<misskey.entities.Note[]>([]); let directReplies = $ref<null | misskey.entities.Note[]>([]); +let directQuotes = $ref<null | misskey.entities.Note[]>([]); +let clips = $ref(); +let renotes = $ref(); let isScrolling; const keymap = { @@ -259,10 +360,14 @@ os.api("notes/children", { directReplies = res .filter( (note) => - note.replyId === appearNote.id || - note.renoteId === appearNote.id + note.replyId === appearNote.id ) .reverse(); + directQuotes = res + .filter( + (note) => + note.renoteId === appearNote.id + ); }); conversation = null; @@ -276,6 +381,33 @@ if (appearNote.replyId) { }); } +clips = null; +os.api("notes/clips", { + noteId: appearNote.id, +}).then((res) => { + clips = res; +}); + +// const pagination = { +// endpoint: "notes/renotes", +// noteId: appearNote.id, +// limit: 10, +// }; + +// const pagingComponent = $ref<InstanceType<typeof MkPagination>>(); + +renotes = null; +function loadTab() { + if (tab === "renotes" && !renotes) { + os.api("notes/renotes", { + noteId: appearNote.id, + limit: 100, + }).then((res) => { + renotes = res; + }) + } +} + async function onNoteUpdated(noteData: NoteUpdatedEvent): Promise<void> { const { type, id, body } = noteData; @@ -326,12 +458,12 @@ document.addEventListener("wheel", () => { onMounted(() => { stream.on("noteUpdated", onNoteUpdated); isScrolling = false; - noteEl?.scrollIntoView(); + noteEl.scrollIntoView(); }); onUpdated(() => { if (!isScrolling) { - noteEl?.scrollIntoView(); + noteEl.scrollIntoView(); if (location.hash) { location.replace(location.hash); // Jump to highlighted reply } @@ -372,80 +504,36 @@ onUnmounted(() => { } } - &:hover > .article > .main > .footer > .button { - opacity: 1; - } > .reply-to { margin-bottom: -16px; padding-bottom: 16px; } - > .renote { - display: flex; - align-items: center; - padding: 16px 32px 8px 32px; - line-height: 28px; - white-space: pre; - color: var(--renote); - - > .avatar { - flex-shrink: 0; - display: inline-block; - width: 28px; - height: 28px; - margin: 0 8px 0 0; - border-radius: 6px; - } - - > i { - margin-right: 4px; - } - - > span { - overflow: hidden; - flex-shrink: 1; - text-overflow: ellipsis; - white-space: nowrap; - - > .name { - font-weight: bold; - } - } - - > .info { - margin-left: auto; - font-size: 0.9em; - - > .time { - flex-shrink: 0; - color: inherit; - - > .dropdownIcon { - margin-right: 4px; - } - } - } - } - - > .renote + .article { - padding-top: 8px; - } - - > .article { - padding-block: 28px 6px; + > :deep(.note-container) { + padding-block: 28px 0; padding-top: 12px; font-size: 1.1rem; overflow: clip; outline: none; scroll-margin-top: calc(var(--stickyTop) + 20vh); - :deep(.article) { + .article { cursor: unset; + padding-bottom: 0; } &:first-of-type { padding-top: 28px; } } + > :deep(.chips) { + padding: 6px 32px 12px; + } + > :deep(.user-card-mini) { + padding-inline: 32px; + border-top: 1px solid var(--divider); + border-radius: 0; + } + > .reply { border-top: solid 0.5px var(--divider); cursor: pointer; @@ -532,46 +620,20 @@ onUnmounted(() => { > .reply-to:first-child { padding-top: 14px; } - > .renote { - padding: 8px 16px 0 16px; - } - > .article { + > :deep(.note-container) { padding: 6px 0 0 0; > .header > .body { padding-left: 10px; } } - } - - &.max-width_350px { - > .article { - > .main { - > .footer { - > .button { - &:not(:last-child) { - margin-right: 18px; - } - } - } - } + > .clips, > .chips, > :deep(.user-card-mini) { + padding-inline: 16px !important; } } &.max-width_300px { font-size: 0.825em; - - > .article { - > .main { - > .footer { - > .button { - &:not(:last-child) { - margin-right: 12px; - } - } - } - } - } } } @@ -580,4 +642,36 @@ onUnmounted(() => { text-align: center; opacity: 0.7; } + +.clips { // want to redesign at some point + padding: 24px 32px; + padding-top: 0; + > .item { + display: block; + padding: 16px; + // background: var(--buttonBg); + border: 1px solid var(--divider); + margin-bottom: var(--margin); + transition: background .2s; + &:hover, &:focus-within { + background: var(--panelHighlight); + } + + > .description { + padding: 8px 0; + } + + > .user { + $height: 32px; + padding-top: 16px; + border-top: solid 0.5px var(--divider); + line-height: $height; + + > .avatar { + width: $height; + height: $height; + } + } + } +} </style> diff --git a/packages/client/src/components/MkRenoteButton.vue b/packages/client/src/components/MkRenoteButton.vue index 631c7415a..881566693 100644 --- a/packages/client/src/components/MkRenoteButton.vue +++ b/packages/client/src/components/MkRenoteButton.vue @@ -7,7 +7,7 @@ @click="renote(false, $event)" > <i class="ph-repeat ph-bold ph-lg"></i> - <p v-if="count > 0" class="count">{{ count }}</p> + <p v-if="count > 0 && !detailedView" class="count">{{ count }}</p> </button> <button v-else class="eddddedb _button"> <i class="ph-prohibit ph-bold ph-lg"></i> @@ -30,6 +30,7 @@ import { MenuItem } from "@/types/menu"; const props = defineProps<{ note: misskey.entities.Note; count: number; + detailedView?; }>(); const buttonRef = ref<HTMLElement>(); diff --git a/packages/client/src/components/MkTab.vue b/packages/client/src/components/MkTab.vue index de3ef290b..f28b37213 100644 --- a/packages/client/src/components/MkTab.vue +++ b/packages/client/src/components/MkTab.vue @@ -6,6 +6,9 @@ export default defineComponent({ modelValue: { required: true, }, + style: { + required: false, + }, }, render() { const options = this.$slots.default(); @@ -13,7 +16,10 @@ export default defineComponent({ return h( "div", { - class: "pxhvhrfw", + class: [ + "pxhvhrfw", + { chips: this.style === "chips" }, + ], }, options.map((option) => withDirectives( @@ -66,7 +72,7 @@ export default defineComponent({ &.active { color: var(--accent); - background: var(--accentedBg); + background: var(--accentedBg) !important; } &:not(.active):hover { @@ -83,6 +89,26 @@ export default defineComponent({ } } + &.chips { + padding: 12px 32px; + font-size: .85em; + overflow-x: auto; + > button { + display: flex; + gap: 6px; + align-items: center; + flex: unset; + margin: 0; + margin-right: 8px; + padding: .5em 1em; + border-radius: 100px; + background: var(--buttonBg); + > i { + margin-top: -.1em; + } + } + } + &.max-width_500px { font-size: 80%; diff --git a/packages/client/src/components/MkUserCardMini.vue b/packages/client/src/components/MkUserCardMini.vue index 9d97e2484..02c22b523 100644 --- a/packages/client/src/components/MkUserCardMini.vue +++ b/packages/client/src/components/MkUserCardMini.vue @@ -1,5 +1,6 @@ <template> <div + class="user-card-mini" :class="[ $style.root, { yellow: user.isSilenced, red: user.isSuspended, gray: false }, diff --git a/packages/client/src/pages/note.vue b/packages/client/src/pages/note.vue index d0f6ed639..65f7abd48 100644 --- a/packages/client/src/pages/note.vue +++ b/packages/client/src/pages/note.vue @@ -41,37 +41,6 @@ class="note" /> </div> - <div - v-if="clips && clips.length > 0" - class="_content clips _gap" - > - <div class="title">{{ i18n.ts.clip }}</div> - <MkA - v-for="item in clips" - :key="item.id" - :to="`/clips/${item.id}`" - class="item _panel _gap" - > - <b>{{ item.name }}</b> - <div - v-if="item.description" - class="description" - > - {{ item.description }} - </div> - <div class="user"> - <MkAvatar - :user="item.user" - class="avatar" - :show-indicator="true" - /> - <MkUserName - :user="item.user" - :nowrap="false" - /> - </div> - </MkA> - </div> <MkButton v-if="!showPrev && hasPrev" class="load prev" @@ -114,7 +83,6 @@ const props = defineProps<{ }>(); let note = $ref<null | misskey.entities.Note>(); -let clips = $ref(); let hasPrev = $ref(false); let hasNext = $ref(false); let showPrev = $ref(false); @@ -160,9 +128,6 @@ function fetchNote() { .then((res) => { note = res; Promise.all([ - os.api("notes/clips", { - noteId: note.id, - }), os.api("users/notes", { userId: note.userId, untilId: note.id, @@ -173,8 +138,7 @@ function fetchNote() { sinceId: note.id, limit: 1, }), - ]).then(([_clips, prev, next]) => { - clips = _clips; + ]).then(([prev, next]) => { hasPrev = prev.length !== 0; hasNext = next.length !== 0; }); @@ -220,10 +184,6 @@ definePageMetadata( opacity: 0; } -:global(html, body) { - scroll-behavior: smooth; -} - .fcuexfpr { #calckey_app > :not(.wallpaper) & { background: var(--bg); @@ -251,34 +211,6 @@ definePageMetadata( background: var(--panel); } } - - > .clips { - > .title { - font-weight: bold; - padding: 12px; - } - - > .item { - display: block; - padding: 16px; - - > .description { - padding: 8px 0; - } - - > .user { - $height: 32px; - padding-top: 16px; - border-top: solid 0.5px var(--divider); - line-height: $height; - - > .avatar { - width: $height; - height: $height; - } - } - } - } } } } From b7c83e0725924f0072f5bf76b638df3f8428abb1 Mon Sep 17 00:00:00 2001 From: Freeplay <Freeplay@duck.com> Date: Fri, 19 May 2023 13:20:48 -0400 Subject: [PATCH 05/11] little bit more accessible tabs --- packages/client/src/components/MkTab.vue | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/packages/client/src/components/MkTab.vue b/packages/client/src/components/MkTab.vue index f28b37213..1f89c8ad0 100644 --- a/packages/client/src/components/MkTab.vue +++ b/packages/client/src/components/MkTab.vue @@ -20,21 +20,17 @@ export default defineComponent({ "pxhvhrfw", { chips: this.style === "chips" }, ], + role: "tablist", }, options.map((option) => withDirectives( h( "button", { - class: [ - "_button", - { - active: - this.modelValue === option.props?.value, - }, - ], + class: "_button", + role: "tab", key: option.key, - disabled: this.modelValue === option.props?.value, + 'aria-selected': this.modelValue === option.props?.value ? "true" : "false", onClick: () => { this.$emit( "update:modelValue", @@ -70,12 +66,12 @@ export default defineComponent({ cursor: default; } - &.active { + &[aria-selected="true"] { color: var(--accent); background: var(--accentedBg) !important; } - &:not(.active):hover { + &:not([aria-selected="true"]):hover { color: var(--fgHighlighted); background: var(--panelHighlight); } From 848739ed844dd6c5cd935202b91dee500e3a1fd6 Mon Sep 17 00:00:00 2001 From: Freeplay <Freeplay@duck.com> Date: Fri, 19 May 2023 14:28:36 -0400 Subject: [PATCH 06/11] add withChart prop to UserCardMini --- .../client/src/components/MkUserCardMini.vue | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/packages/client/src/components/MkUserCardMini.vue b/packages/client/src/components/MkUserCardMini.vue index 02c22b523..27b3e939f 100644 --- a/packages/client/src/components/MkUserCardMini.vue +++ b/packages/client/src/components/MkUserCardMini.vue @@ -28,21 +28,28 @@ import MkMiniChart from "@/components/MkMiniChart.vue"; import * as os from "@/os"; import { acct } from "@/filters/user"; -const props = defineProps<{ - user: misskey.entities.User; -}>(); +const props = withDefaults(defineProps<{ + user: misskey.entities.User; + withChart?: boolean; + }>(), + { + withChart: true, + } +); let chartValues = $ref<number[] | null>(null); -os.apiGet("charts/user/notes", { - userId: props.user.id, - limit: 16 + 1, - span: "day", -}).then((res) => { - // 今日のぶんの値はまだ途中の値であり、それも含めると大抵の場合前日よりも下降しているようなグラフになってしまうため今日は弾く - res.inc.splice(0, 1); - chartValues = res.inc; -}); +if (props.withChart) { + os.apiGet("charts/user/notes", { + userId: props.user.id, + limit: 16 + 1, + span: "day", + }).then((res) => { + // 今日のぶんの値はまだ途中の値であり、それも含めると大抵の場合前日よりも下降しているようなグラフになってしまうため今日は弾く + res.inc.splice(0, 1); + chartValues = res.inc; + }); +} </script> <style lang="scss" module> From 595e77493eec87368a86fd4bd267c7ccd0ba5fc7 Mon Sep 17 00:00:00 2001 From: Freeplay <Freeplay@duck.com> Date: Fri, 19 May 2023 14:34:16 -0400 Subject: [PATCH 07/11] mini mfm button --- packages/client/src/components/MkButton.vue | 11 +++++++++-- packages/client/src/components/MkSubNoteContent.vue | 1 + 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/client/src/components/MkButton.vue b/packages/client/src/components/MkButton.vue index feac281d9..2290daa6b 100644 --- a/packages/client/src/components/MkButton.vue +++ b/packages/client/src/components/MkButton.vue @@ -2,7 +2,7 @@ <button v-if="!link" class="bghgjjyj _button" - :class="{ inline, primary, gradate, danger, rounded, full }" + :class="{ inline, primary, gradate, danger, rounded, full, mini }" :type="type" @click="emit('click', $event)" @mousedown="onMousedown" @@ -15,7 +15,7 @@ <MkA v-else class="bghgjjyj _button" - :class="{ inline, primary, gradate, danger, rounded, full }" + :class="{ inline, primary, gradate, danger, rounded, full, mini }" :to="to" @mousedown="onMousedown" > @@ -41,6 +41,7 @@ const props = defineProps<{ wait?: boolean; danger?: boolean; full?: boolean; + mini: boolean; }>(); const emit = defineEmits<{ @@ -190,6 +191,12 @@ function onMousedown(evt: MouseEvent): void { } } + &.mini { + padding: 4px 8px; + font-size: .9em; + border-radius: 100px; + } + &:disabled { opacity: 0.7; } diff --git a/packages/client/src/components/MkSubNoteContent.vue b/packages/client/src/components/MkSubNoteContent.vue index d683e9a82..c1d2e10ff 100644 --- a/packages/client/src/components/MkSubNoteContent.vue +++ b/packages/client/src/components/MkSubNoteContent.vue @@ -133,6 +133,7 @@ <MkButton v-if="hasMfm && defaultStore.state.animatedMfm" @click.stop="toggleMfm" + :mini="true" > <template v-if="disableMfm"> <i class="ph-play ph-bold"></i> {{ i18n.ts._mfm.play }} From e10f7431f3d2170ecfc0ee32d002a929dd6a11a8 Mon Sep 17 00:00:00 2001 From: Freeplay <Freeplay@duck.com> Date: Fri, 19 May 2023 14:40:47 -0400 Subject: [PATCH 08/11] fix loading icon for boosts tab --- packages/client/src/components/MkNoteDetailed.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/components/MkNoteDetailed.vue b/packages/client/src/components/MkNoteDetailed.vue index 1cffb267e..6dc6ddb62 100644 --- a/packages/client/src/components/MkNoteDetailed.vue +++ b/packages/client/src/components/MkNoteDetailed.vue @@ -97,7 +97,7 @@ :pagination="pagination" > --> <MkUserCardMini - v-if="tab === 'renotes' && appearNote.renoteCount > 0" + v-if="tab === 'renotes' && renotes" v-for="item in renotes" :key="item.user.id" :user="item.user" From 46f25c34b53cc2fa784617d269fc081d95d14093 Mon Sep 17 00:00:00 2001 From: Freeplay <Freeplay@duck.com> Date: Fri, 19 May 2023 14:45:29 -0400 Subject: [PATCH 09/11] Make UserCardMini clickable --- packages/client/src/components/MkUserCardMini.vue | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/client/src/components/MkUserCardMini.vue b/packages/client/src/components/MkUserCardMini.vue index 27b3e939f..908882ec3 100644 --- a/packages/client/src/components/MkUserCardMini.vue +++ b/packages/client/src/components/MkUserCardMini.vue @@ -1,10 +1,11 @@ <template> - <div + <MkA class="user-card-mini" :class="[ $style.root, { yellow: user.isSilenced, red: user.isSuspended, gray: false }, ]" + :to="userPage(user)" > <MkAvatar class="avatar" @@ -19,14 +20,14 @@ > </div> <MkMiniChart v-if="chartValues" class="chart" :src="chartValues" /> - </div> + </MkA> </template> <script lang="ts" setup> import * as misskey from "calckey-js"; import MkMiniChart from "@/components/MkMiniChart.vue"; import * as os from "@/os"; -import { acct } from "@/filters/user"; +import { acct, userPage } from "@/filters/user"; const props = withDefaults(defineProps<{ user: misskey.entities.User; @@ -62,6 +63,7 @@ if (props.withChart) { padding: 16px; background: var(--panel); border-radius: 8px; + transition: background .2s; > :global(.avatar) { display: block; @@ -102,6 +104,10 @@ if (props.withChart) { height: 30px; } + &:hover, &:focus { + background: var(--panelHighlight); + } + &:global(.yellow) { --c: rgb(255 196 0 / 15%); background-image: linear-gradient( From 501b39e4922522842effde8753b0b0d7e7acc3c6 Mon Sep 17 00:00:00 2001 From: Freeplay <Freeplay@duck.com> Date: Fri, 19 May 2023 16:29:16 -0400 Subject: [PATCH 10/11] note metadata --- packages/client/src/pages/note.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/client/src/pages/note.vue b/packages/client/src/pages/note.vue index 65f7abd48..72b71996e 100644 --- a/packages/client/src/pages/note.vue +++ b/packages/client/src/pages/note.vue @@ -160,8 +160,8 @@ definePageMetadata( computed(() => note ? { - title: note.user.name, - subtitle: `@${note.user.username}@${note.user.host}`, + title: i18n.t("noteOf", { user: note.user.name }), + subtitle: new Date(note.createdAt).toLocaleString(), avatar: note.user, path: `/notes/${note.id}`, share: { From 5a9a4e74354c1aa58816ac708cf1ad333db3d9bb Mon Sep 17 00:00:00 2001 From: Freeplay <Freeplay@duck.com> Date: Fri, 19 May 2023 17:07:22 -0400 Subject: [PATCH 11/11] fix null error --- packages/client/src/components/MkNoteDetailed.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/components/MkNoteDetailed.vue b/packages/client/src/components/MkNoteDetailed.vue index 6dc6ddb62..a6b2bdf7d 100644 --- a/packages/client/src/components/MkNoteDetailed.vue +++ b/packages/client/src/components/MkNoteDetailed.vue @@ -61,7 +61,7 @@ </option> <option value="clips"> <i class="ph-paperclip ph-bold ph-lg"></i> - <template v-if="clips.length > 0"> + <template v-if="clips?.length > 0"> <span class="count">{{ clips.length }}</span> </template> {{ i18n.ts.clips }}