diff --git a/locales/en-US.yml b/locales/en-US.yml
index 5df1a3f8fe..67980552de 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -1058,6 +1058,7 @@ thisPostIsMissingAltTextCancel: "Cancel"
thisPostIsMissingAltTextIgnore: "Post anyway"
thisPostIsMissingAltText: "One of the files attached to this post is missing alt text. Please ensure all the attachments have alt text."
collapseRenotes: "Collapse boosts you've already seen"
+collapseReplies: "Collapse replies"
collapseFiles: "Collapse files"
autoloadConversation: "Load conversation on replies"
internalServerError: "Internal Server Error"
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index a14854fba6..068a99a59e 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -12,7 +12,11 @@ SPDX-License-Identifier: AGPL-3.0-only
:class="[$style.root, { [$style.showActionsOnlyHover]: defaultStore.state.showNoteActionsOnlyHover }]"
:tabindex="!isDeleted ? '-1' : undefined"
>
-
+
+
+
+
+
{{ i18n.ts.pinnedNote }}
@@ -310,6 +314,7 @@ const renoteCollapsed = ref(
(appearNote.value.myReaction != null)
)
);
+const replyCollapsed = ref(defaultStore.state.collapseReplies && !renoteCollapsed.value);
const defaultLike = computed(() => defaultStore.state.like ? defaultStore.state.like : null);
const animated = computed(() => parsed.value ? checkAnimationFromMfm(parsed.value) : null);
const allowAnim = ref(defaultStore.state.advancedMfm && defaultStore.state.animatedMfm ? true : false);
@@ -919,7 +924,7 @@ function emitUpdReaction(emoji: string, delta: number) {
margin-right: 4px;
}
-.collapsedRenoteTarget {
+.collapsedRenoteTarget, .collapsedReply {
display: flex;
align-items: center;
line-height: 28px;
@@ -927,7 +932,12 @@ function emitUpdReaction(emoji: string, delta: number) {
padding: 0 32px 18px;
}
-.collapsedRenoteTargetAvatar {
+.collapsedReply {
+ padding: 28px 32px 0;
+ opacity: 0.7;
+}
+
+.collapsedRenoteTargetAvatar, .collapsedReplyAvatar {
flex-shrink: 0;
display: inline-block;
width: 28px;
@@ -936,12 +946,15 @@ function emitUpdReaction(emoji: string, delta: number) {
}
.collapsedRenoteTargetText {
+ opacity: 0.7;
+}
+
+.collapsedRenoteTargetText, .collapsedReplyText {
overflow: hidden;
flex-shrink: 1;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 90%;
- opacity: 0.7;
cursor: pointer;
&:hover {
@@ -1154,6 +1167,10 @@ function emitUpdReaction(emoji: string, delta: number) {
margin-top: 4px;
}
+ .collapsedReply {
+ padding: 28px 16px 0;
+ }
+
.article {
padding: 14px 16px;
}
diff --git a/packages/frontend/src/components/SkNote.vue b/packages/frontend/src/components/SkNote.vue
index 3c5a1baffc..553c38eed8 100644
--- a/packages/frontend/src/components/SkNote.vue
+++ b/packages/frontend/src/components/SkNote.vue
@@ -12,7 +12,12 @@ SPDX-License-Identifier: AGPL-3.0-only
:class="[$style.root, { [$style.showActionsOnlyHover]: defaultStore.state.showNoteActionsOnlyHover }]"
:tabindex="!isDeleted ? '-1' : undefined"
>
-
+
+
{{ i18n.ts.pinnedNote }}
@@ -309,6 +314,7 @@ const renoteCollapsed = ref(
(appearNote.value.myReaction != null)
)
);
+const replyCollapsed = ref(defaultStore.state.collapseReplies && !renoteCollapsed.value);
const defaultLike = computed(() => defaultStore.state.like ? defaultStore.state.like : null);
const animated = computed(() => parsed.value ? checkAnimationFromMfm(parsed.value) : null);
const allowAnim = ref(defaultStore.state.advancedMfm && defaultStore.state.animatedMfm ? true : false);
@@ -934,7 +940,7 @@ function emitUpdReaction(emoji: string, delta: number) {
margin-right: 4px;
}
-.collapsedRenoteTarget {
+.collapsedRenoteTarget, .collapsedReply {
display: flex;
align-items: center;
line-height: 28px;
@@ -942,7 +948,11 @@ function emitUpdReaction(emoji: string, delta: number) {
padding: 8px 38px 24px;
}
-.collapsedRenoteTargetAvatar {
+.collapsedReply {
+ padding: 28px 44px 0;
+}
+
+.collapsedRenoteTargetAvatar, .collapsedReplyAvatar {
flex-shrink: 0;
display: inline-block;
width: 28px;
@@ -950,7 +960,7 @@ function emitUpdReaction(emoji: string, delta: number) {
margin: 0 8px 0 0;
}
-.collapsedRenoteTargetText {
+.collapsedRenoteTargetText, .collapsedReplyText {
overflow: hidden;
flex-shrink: 1;
text-overflow: ellipsis;
@@ -964,6 +974,15 @@ function emitUpdReaction(emoji: string, delta: number) {
}
}
+.collapsedReplyLine {
+ position: absolute;
+ left: 56px;
+ // using solid instead of dotted, stylelistic choice
+ border-left: var(--thread-width) solid var(--thread);
+ top: calc(28px + 28px); // 28px of .root padding, plus 28px of avatar height (see SkNote)
+ height: 28px;
+}
+
.article {
position: relative;
padding: 28px 32px;
@@ -1142,6 +1161,14 @@ function emitUpdReaction(emoji: string, delta: number) {
padding: 8px 26px 24px;
}
+ .collapsedReply {
+ padding: 28px 35px 0;
+ }
+
+ .collapsedReplyLine {
+ left: 47px;
+ }
+
.article {
padding: 24px 26px;
}
@@ -1189,6 +1216,14 @@ function emitUpdReaction(emoji: string, delta: number) {
margin-top: 4px;
}
+ .collapsedReply {
+ padding: 28px 33px 0;
+ }
+
+ .collapsedReplyLine {
+ left: 45px;
+ }
+
.article {
padding: 22px 24px;
}
diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue
index 76081666d8..0f428b6d30 100644
--- a/packages/frontend/src/pages/settings/general.vue
+++ b/packages/frontend/src/pages/settings/general.vue
@@ -53,6 +53,7 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts.showNoteActionsOnlyHover }}
{{ i18n.ts.showClipButtonInNoteFooter }}
{{ i18n.ts.collapseRenotes }}
+ {{ i18n.ts.collapseReplies }}
{{ i18n.ts.collapseFiles }}
Uncollapse CWs on notes
{{ i18n.ts.autoloadConversation }}
@@ -321,6 +322,7 @@ const showClipButtonInNoteFooter = computed(defaultStore.makeGetterSetter('showC
const reactionsDisplaySize = computed(defaultStore.makeGetterSetter('reactionsDisplaySize'));
const limitWidthOfReaction = computed(defaultStore.makeGetterSetter('limitWidthOfReaction'));
const collapseRenotes = computed(defaultStore.makeGetterSetter('collapseRenotes'));
+const collapseReplies = computed(defaultStore.makeGetterSetter('collapseReplies'));
const clickToOpen = computed(defaultStore.makeGetterSetter('clickToOpen'));
// copied from src/pages/timeline.vue
const showBots = computed({
diff --git a/packages/frontend/src/pages/settings/preferences-backups.vue b/packages/frontend/src/pages/settings/preferences-backups.vue
index d4349b466d..514a37a3ff 100644
--- a/packages/frontend/src/pages/settings/preferences-backups.vue
+++ b/packages/frontend/src/pages/settings/preferences-backups.vue
@@ -56,6 +56,7 @@ const { t, ts } = i18n;
const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [
'collapseRenotes',
+ 'collapseReplies',
'menu',
'visibility',
'localOnly',
diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts
index 7f6377613e..ba77f96dd5 100644
--- a/packages/frontend/src/store.ts
+++ b/packages/frontend/src/store.ts
@@ -89,6 +89,10 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'account',
default: false,
},
+ collapseReplies: {
+ where: 'account',
+ default: false,
+ },
collapseFiles: {
where: 'account',
default: false,