diff --git a/src/components/attachment/attachment.js b/src/components/attachment/attachment.js
index f7d6ebf10..d5bce12a7 100644
--- a/src/components/attachment/attachment.js
+++ b/src/components/attachment/attachment.js
@@ -52,7 +52,6 @@ const Attachment = {
'shiftDn',
'edit',
],
- emits: ['play', 'pause', 'naturalSizeLoad'],
data() {
return {
localDescription: this.description || this.attachment.description,
diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js
index 696167831..54bcbd373 100644
--- a/src/components/conversation/conversation.js
+++ b/src/components/conversation/conversation.js
@@ -55,6 +55,16 @@ const sortAndFilterConversation = (conversation, statusoid) => {
}
const conversation = {
+ data() {
+ return {
+ highlight: null,
+ expanded: false,
+ threadDisplayStatusObject: {}, // id => 'showing' | 'hidden'
+ statusContentPropertiesObject: {},
+ inlineDivePosition: null,
+ loadStatusError: null,
+ }
+ },
props: [
'statusId',
'collapsable',
@@ -64,16 +74,6 @@ const conversation = {
'profileUserId',
'virtualHidden',
],
- data() {
- return {
- highlight: null,
- expanded: false,
- threadDisplayStatusObject: {}, // id => 'showing' | 'hidden'
- inlineDivePosition: null,
- loadStatusError: null,
- unsuspendibleIds: new Set(),
- }
- },
created() {
if (this.isPage) {
this.fetchConversation()
@@ -118,7 +118,16 @@ const conversation = {
return this.otherRepliesButtonPosition === 'inside'
},
suspendable() {
- return this.unsuspendibleIds.size > 0
+ if (this.isTreeView) {
+ return Object.entries(this.statusContentProperties).every(
+ ([, prop]) => !prop.replying && prop.mediaPlaying.length === 0,
+ )
+ }
+ if (this.$refs.statusComponent && this.$refs.statusComponent[0]) {
+ return this.$refs.statusComponent.every((s) => s.suspendable)
+ } else {
+ return true
+ }
},
hideStatus() {
return this.virtualHidden && this.suspendable
@@ -355,6 +364,31 @@ const conversation = {
return a
}, {})
},
+ statusContentProperties() {
+ return this.conversation.reduce((a, k) => {
+ const id = k.id
+ const props = (() => {
+ const def = {
+ showingTall: false,
+ expandingSubject: false,
+ showingLongSubject: false,
+ isReplying: false,
+ mediaPlaying: [],
+ }
+
+ if (this.statusContentPropertiesObject[id]) {
+ return {
+ ...def,
+ ...this.statusContentPropertiesObject[id],
+ }
+ }
+ return def
+ })()
+
+ a[id] = props
+ return a
+ }, {})
+ },
canDive() {
return this.isTreeView && this.isExpanded
},
@@ -480,6 +514,22 @@ const conversation = {
showThreadRecursively(id) {
this.setThreadDisplayRecursively(id, 'showing')
},
+ setStatusContentProperty(id, name, value) {
+ this.statusContentPropertiesObject = {
+ ...this.statusContentPropertiesObject,
+ [id]: {
+ ...this.statusContentPropertiesObject[id],
+ [name]: value,
+ },
+ }
+ },
+ toggleStatusContentProperty(id, name) {
+ this.setStatusContentProperty(
+ id,
+ name,
+ !this.statusContentProperties[id][name],
+ )
+ },
leastVisibleAncestor(id) {
let cur = id
let parent = this.parentOf(cur)
@@ -579,13 +629,6 @@ const conversation = {
this.undive()
this.threadDisplayStatusObject = {}
},
- onStatusSuspendStateChange({ id, suspend }) {
- if (!suspend) {
- this.unsuspendibleIds.add(id)
- } else {
- this.unsuspendibleIds.delete(id)
- }
- },
},
}
diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue
index 8a46722ad..8d4734083 100644
--- a/src/components/conversation/conversation.vue
+++ b/src/components/conversation/conversation.vue
@@ -97,7 +97,7 @@
:expandable="!isExpanded"
:focused="isFocused(status.id)"
- :highlight="maybeHighlight"
+ :highlight="getHighlight()"
:inline-expanded="collapsable && isExpanded"
:show-pinned="pinnedStatusIdsObject && pinnedStatusIdsObject[status.id]"
:in-profile="inProfile"
@@ -105,11 +105,21 @@
:profile-user-id="profileUserId"
:simple-tree="treeViewIsSimple"
:show-other-replies-as-button="showOtherRepliesButtonInsideStatus"
- can-dive
+ :dive="() => diveIntoStatus(status.id)"
+
+ :controlled-showing-tall="statusContentProperties[status.id].showingTall"
+ :controlled-toggle-showing-tall="() => toggleStatusContentProperty(status.id, 'showingTall')"
+ :controlled-expanding-subject="statusContentProperties[status.id].expandingSubject"
+ :controlled-toggle-expanding-subject="() => toggleStatusContentProperty(status.id, 'expandingSubject')"
+ :controlled-showing-long-subject="statusContentProperties[status.id].showingLongSubject"
+ :controlled-toggle-showing-long-subject="() => toggleStatusContentProperty(status.id, 'showingLongSubject')"
+ :controlled-replying="statusContentProperties[status.id].replying"
+ :controlled-toggle-replying="() => toggleStatusContentProperty(status.id, 'replying')"
+ :controlled-media-playing="statusContentProperties[status.id].mediaPlaying"
+ :controlled-set-media-playing="(newVal) => toggleStatusContentProperty(status.id, 'mediaPlaying', newVal)"
@goto="setHighlight"
- @dive="() => diveIntoStatus(status.id)"
- @suspendable-state-change="onStatusSuspendStateChange"
+ @toggle-expanded="toggleExpanded"
/>
-
diff --git a/src/components/error_modal/error_modal.js b/src/components/error_modal/error_modal.js
index 560de3a53..aaa9f99ec 100644
--- a/src/components/error_modal/error_modal.js
+++ b/src/components/error_modal/error_modal.js
@@ -30,7 +30,7 @@ const ErrorModal = {
type: Error,
},
},
- emits: ['clear', 'recover'],
+ emits: ['clear', 'recover']
}
export default ErrorModal
diff --git a/src/components/gallery/gallery.js b/src/components/gallery/gallery.js
index c244a8cb3..252a32bf6 100644
--- a/src/components/gallery/gallery.js
+++ b/src/components/gallery/gallery.js
@@ -27,10 +27,8 @@ const Gallery = {
return {
sizes: {},
hidingLong: true,
- playingMedia: new Set(),
}
},
- emits: ['play', 'pause'],
components: { Attachment },
computed: {
rows() {
@@ -117,21 +115,11 @@ const Gallery = {
return this.attachmentsDimensionalScore > 1
}
},
- hasPlayingMedia() {
- return this.playingMedia.size > 0
- },
},
methods: {
onNaturalSizeLoad({ id, width, height }) {
set(this.sizes, id, { width, height })
},
- onMediaStateChange(playing, id) {
- if (playing) {
- this.playingMedia.add(id)
- } else {
- this.playingMedia.delete(id)
- }
- },
rowStyle(row) {
if (row.audio) {
return { 'padding-bottom': '25%' } // fixed reduced height for audio
@@ -158,15 +146,6 @@ const Gallery = {
useMediaViewerStore().setMedia(this.attachments)
},
},
- watch: {
- hasPlayingMedia(newValue) {
- if (newValue) {
- this.$emit('play')
- } else {
- this.$emit('pause')
- }
- },
- },
}
export default Gallery
diff --git a/src/components/gallery/gallery.vue b/src/components/gallery/gallery.vue
index a32d4feb7..11637f3c2 100644
--- a/src/components/gallery/gallery.vue
+++ b/src/components/gallery/gallery.vue
@@ -34,8 +34,6 @@
:style="itemStyle(attachment.id, row.items)"
@set-media="onMedia"
@natural-size-load="onNaturalSizeLoad"
- @play="() => onMediaStateChange(true, attachment.id)"
- @pause="() => onMediaStateChange(false, attachment.id)"
/>
diff --git a/src/components/global_error/global_error.js b/src/components/global_error/global_error.js
index cd4593521..0f826f4c4 100644
--- a/src/components/global_error/global_error.js
+++ b/src/components/global_error/global_error.js
@@ -1,9 +1,9 @@
-import { mapActions, mapState } from 'pinia'
-
import ErrorModal from 'src/components/error_modal/error_modal.vue'
import { useInterfaceStore } from 'src/stores/interface.js'
+import { mapState, mapActions } from 'pinia'
+
const GlobalError = {
components: {
ErrorModal,
@@ -24,11 +24,7 @@ const GlobalError = {
details() {
if (this.globalError == null) return null
if (this.globalError.error != null) {
- return (
- this.globalError.error.toString() +
- '\n\n' +
- this.globalError.error.stack
- )
+ return this.globalError.error.toString() + '\n\n' + this.globalError.error.stack
} else {
return this.globalError.details
}
diff --git a/src/components/lists_edit/lists_edit.vue b/src/components/lists_edit/lists_edit.vue
index 25b818deb..713a40673 100644
--- a/src/components/lists_edit/lists_edit.vue
+++ b/src/components/lists_edit/lists_edit.vue
@@ -50,7 +50,7 @@
{
- this.$emit('close-accepted')
+ this.$emit('can-close')
})
},
discardAndCloseDraft() {
this.abandonDraft().then(() => {
- this.$emit('close-accepted')
+ this.$emit('can-close')
})
},
addBeforeUnloadListener() {
diff --git a/src/components/search/search.vue b/src/components/search/search.vue
index 16418d389..4302f93f9 100644
--- a/src/components/search/search.vue
+++ b/src/components/search/search.vue
@@ -63,6 +63,7 @@
:compact="false"
class="search-result"
:statusoid="status"
+ :no-heading="false"
/>
@@ -549,9 +555,9 @@
:replied-user="status.user"
:copy-message-scope="status.visibility"
:subject="replySubject"
- @posted="closeReplyForm"
- @draft-done="closeReplyForm"
- @close-accepted="closeReplyForm"
+ @posted="doToggleReplying"
+ @draft-done="doToggleReplying"
+ @can-close="doToggleReplying"
/>
diff --git a/src/components/status_body/status_body.js b/src/components/status_body/status_body.js
index 2da6da555..cdd43a024 100644
--- a/src/components/status_body/status_body.js
+++ b/src/components/status_body/status_body.js
@@ -15,47 +15,32 @@ library.add(faFile, faMusic, faImage, faLink, faPollH)
const StatusBody = {
name: 'StatusBody',
- props: {
- status: {
- // Main thing
- type: Object,
- required: true,
- },
- compact: {
- // Resizes emoji and minimizes vertical space used
- // Primarily used for showing status in react notifications
- type: Boolean,
- default: false,
- },
- collapse: {
- // replaces newlines with spaces
- type: Boolean,
- default: false,
- },
- singleLine: {
- // Show entire thing (subject and content) in a single line
- // Primarily used in chats
- type: Boolean,
- default: false,
- },
- inConversation: {
- // Is status rendered within open conversation?
- // Used to automatically expand subjects (if collapsed)
- type: Boolean,
- default: false,
- }
- },
+ props: [
+ 'compact',
+ 'collapse', // replaces newlines with spaces
+ 'status',
+ 'focused',
+ 'noHeading',
+ 'fullContent',
+ 'singleLine',
+ 'showingTall',
+ 'expandingSubject',
+ 'showingLongSubject',
+ 'toggleShowingTall',
+ 'toggleExpandingSubject',
+ 'toggleShowingLongSubject',
+ ],
data() {
return {
postLength: this.status.text.length,
parseReadyDone: false,
- showingTall: false,
- showingLongSubject: false,
- expandingSubject: null,
}
},
emits: ['parseReady'],
computed: {
+ localCollapseSubjectDefault() {
+ return this.mergedConfig.collapseMessageWithSubject
+ },
allowNonSquareEmoji() {
return this.mergedConfig.nonSquareEmoji
},
@@ -66,31 +51,32 @@ const StatusBody = {
//
// Using max-height + overflow: auto for status components resulted in false positives
// very often with japanese characters, and it was very annoying.
- hasLongSubject() {
- return this.status.summary.length > 240
- },
- hasSubject() {
- return !!this.status.summary
- },
- // When a status has a subject and is also tall, we should only have one show more/less
- // button. If the default is to collapse statuses with subjects, we just treat it like
- // a status with a subject; otherwise, we just treat it like a tall status.
- mightHideBecauseSubject() {
- return !this.inConversation && this.hasSubject && this.mergedConfig.collapseMessageWithSubject
- },
- mightHideBecauseTall() {
+ tallStatus() {
if (this.singleLine || this.compact) return false
const lengthScore =
this.status.raw_html.split(/ 20
},
+ longSubject() {
+ return this.status.summary.length > 240
+ },
+ // When a status has a subject and is also tall, we should only have one show more/less button. If the default is to collapse statuses with subjects, we just treat it like a status with a subject; otherwise, we just treat it like a tall status.
+ mightHideBecauseSubject() {
+ return !!this.status.summary && this.localCollapseSubjectDefault
+ },
+ mightHideBecauseTall() {
+ return (
+ this.tallStatus &&
+ !(this.status.summary && this.localCollapseSubjectDefault)
+ )
+ },
hideSubjectStatus() {
return this.mightHideBecauseSubject && !this.expandingSubject
},
hideTallStatus() {
return this.mightHideBecauseTall && !this.showingTall
},
- shouldShowExpandToggle() {
+ shouldShowToggle() {
return this.mightHideBecauseSubject || this.mightHideBecauseTall
},
toggleButtonClasses() {
@@ -111,11 +97,6 @@ const StatusBody = {
: this.$t('general.show_more')
}
},
- shouldHide() {
- return (
- !this.showingMore && this.mightHideBecauseSubject && this.hasSubject
- )
- },
showingMore() {
return (
(this.mightHideBecauseTall && this.showingTall) ||
@@ -166,9 +147,9 @@ const StatusBody = {
},
toggleShowMore() {
if (this.mightHideBecauseTall) {
- this.showingTall = !this.showingTall
+ this.toggleShowingTall()
} else if (this.mightHideBecauseSubject) {
- this.expandingSubject = !this.expandingSubject
+ this.toggleExpandingSubject()
}
},
generateTagLink(tag) {
diff --git a/src/components/status_body/status_body.scss b/src/components/status_body/status_body.scss
index 9954663e7..ff35bde58 100644
--- a/src/components/status_body/status_body.scss
+++ b/src/components/status_body/status_body.scss
@@ -53,7 +53,6 @@
}
.text-wrapper {
- position: relative;
text-overflow: ellipsis;
overflow-wrap: break-word;
overflow: hidden;
@@ -61,12 +60,10 @@
flex-flow: column nowrap;
&.-tall-status {
+ position: relative;
+ height: 16em;
z-index: 1;
- &:not(.-hidden) {
- height: 16em;
- }
-
.media-body {
min-height: 0;
mask:
@@ -101,7 +98,6 @@
text-wrap: pretty;
width: 100%;
text-align: center;
- margin: 0.1em 0;
}
.status-unhider {
@@ -111,17 +107,17 @@
padding-bottom: 1em;
}
+ .tall-status-hider {
+ position: absolute;
+ height: 5em;
+ margin-top: 10em;
+ line-height: 8em;
+ z-index: 2;
+ }
+
.tall-subject-hider {
// position: absolute;
padding-bottom: 0.5em;
-
- &:not(.cw-status-hider) {
- position: absolute;
- margin-top: 10em;
- height: 5em;
- line-height: 8em;
- z-index: 2;
- }
}
& .status-unhider,
diff --git a/src/components/status_body/status_body.vue b/src/components/status_body/status_body.vue
index b611d69b8..f724f2413 100644
--- a/src/components/status_body/status_body.vue
+++ b/src/components/status_body/status_body.vue
@@ -5,9 +5,9 @@
>
{{ $t("status.hide_full_subject") }}
@@ -34,10 +34,10 @@
diff --git a/src/components/status_content/status_content.js b/src/components/status_content/status_content.js
index 1cd3fc7b4..2186498ef 100644
--- a/src/components/status_content/status_content.js
+++ b/src/components/status_content/status_content.js
@@ -22,40 +22,69 @@ import {
library.add(faCircleNotch, faFile, faMusic, faImage, faLink, faPollH)
+const camelCase = (name) => name.charAt(0).toUpperCase() + name.slice(1)
+
+const controlledOrUncontrolledGetters = (list) =>
+ list.reduce((res, name) => {
+ const camelized = camelCase(name)
+ const toggle = `controlledToggle${camelized}`
+ const controlledName = `controlled${camelized}`
+ const uncontrolledName = `uncontrolled${camelized}`
+ res[name] = function () {
+ return (this.$data[toggle] !== undefined ||
+ this.$props[toggle] !== undefined) &&
+ this[toggle]
+ ? this[controlledName]
+ : this[uncontrolledName]
+ }
+ return res
+ }, {})
+
+const controlledOrUncontrolledToggle = (obj, name) => {
+ const camelized = camelCase(name)
+ const toggle = `controlledToggle${camelized}`
+ const uncontrolledName = `uncontrolled${camelized}`
+ if (obj[toggle]) {
+ obj[toggle]()
+ } else {
+ obj[uncontrolledName] = !obj[uncontrolledName]
+ }
+}
+
const StatusContent = {
name: 'StatusContent',
- props: {
- status: {
- // Main thing
- type: Object,
- required: true,
- },
- compact: {
- // Resizes emoji and minimizes vertical space used
- // Primarily used for showing status in react notifications
- type: Boolean,
- default: false,
- },
- collapse: {
- // replaces newlines with spaces
- type: Boolean,
- default: false,
- },
- singleLine: {
- // Show entire thing (subject and content) in a single line
- // Primarily used in chats
- type: Boolean,
- default: false,
- },
- inConversation: {
- // Whether status content is being shown in an (open) conversation
- // Used to control whether to display attachments or not
- type: Boolean,
- default: false,
- },
- },
+ props: [
+ 'status',
+ 'compact',
+ 'collapse',
+ 'focused',
+ 'noHeading',
+ 'fullContent',
+ 'singleLine',
+ 'controlledShowingTall',
+ 'controlledExpandingSubject',
+ 'controlledToggleShowingTall',
+ 'controlledToggleExpandingSubject',
+ 'controlledShowingLongSubject',
+ 'controlledToggleShowingLongSubject',
+ ],
emits: ['parseReady', 'mediaplay', 'mediapause'],
+ data() {
+ return {
+ uncontrolledShowingTall:
+ this.fullContent || (this.inConversation && this.focused),
+ uncontrolledShowingLongSubject: false,
+ // not as computed because it sets the initial state which will be changed later
+ uncontrolledExpandingSubject:
+ !useMergedConfigStore().mergedConfig.collapseMessageWithSubject,
+ }
+ },
computed: {
+ ...controlledOrUncontrolledGetters([
+ 'showingTall',
+ 'expandingSubject',
+ 'showingLongSubject',
+ ]),
statusCard() {
if (!this.status.card) return null
return this.status.card.url === this.status.quote_url
@@ -64,20 +93,22 @@ const StatusContent = {
},
hideAttachments() {
return (
- !this.fullContent &&
- ((this.mergedConfig.hideAttachments && !this.inConversation) ||
- (this.mergedConfig.hideAttachmentsInConv && this.inConversation))
+ (this.mergedConfig.hideAttachments && !this.inConversation) ||
+ (this.mergedConfig.hideAttachmentsInConv && this.inConversation)
)
},
nsfwClickthrough() {
if (!this.status.nsfw) {
return false
}
- if (this.status.summary && this.mergedConfig.collapseMessageWithSubject) {
+ if (this.status.summary && this.localCollapseSubjectDefault) {
return false
}
return true
},
+ localCollapseSubjectDefault() {
+ return this.mergedConfig.collapseMessageWithSubject
+ },
attachmentSize() {
if (this.compact) {
return 'small'
@@ -106,6 +137,15 @@ const StatusContent = {
StatusBody,
},
methods: {
+ toggleShowingTall() {
+ controlledOrUncontrolledToggle(this, 'showingTall')
+ },
+ toggleExpandingSubject() {
+ controlledOrUncontrolledToggle(this, 'expandingSubject')
+ },
+ toggleShowingLongSubject() {
+ controlledOrUncontrolledToggle(this, 'showingLongSubject')
+ },
setMedia() {
const attachments =
this.attachmentSize === 'hide'
diff --git a/src/components/status_content/status_content.vue b/src/components/status_content/status_content.vue
index 6b46bd768..6dc80b8a5 100644
--- a/src/components/status_content/status_content.vue
+++ b/src/components/status_content/status_content.vue
@@ -8,8 +8,13 @@
:status="status"
:compact="compact"
:single-line="singleLine"
+ :showing-tall="showingTall"
+ :expanding-subject="expandingSubject"
+ :showing-long-subject="showingLongSubject"
+ :toggle-showing-tall="toggleShowingTall"
+ :toggle-expanding-subject="toggleExpandingSubject"
+ :toggle-showing-long-subject="toggleShowingLongSubject"
:collapse="collapse"
- :in-conversation="inConversation"
@parse-ready="$emit('parseReady', $event)"
>
@@ -37,12 +42,12 @@
:attachments="status.attachments"
:limit="compact ? 1 : 0"
:size="attachmentSize"
- @play="$emit('mediaplay')"
- @pause="$emit('mediapause')"
+ @play="$emit('mediaplay', attachment.id)"
+ @pause="$emit('mediapause', attachment.id)"
/>
s.suspendable)
+ )
+ }
+ return selfSuspendable
+ },
reverseLookupTable() {
return this.conversation.reduce(
(table, status, index) => {
@@ -53,11 +69,29 @@ const ThreadTree = {
threadShowing() {
return this.threadDisplayStatus[this.status.id] === 'showing'
},
+ currentProp() {
+ return this.statusContentProperties[this.status.id]
+ },
},
methods: {
statusById(id) {
return this.conversation[this.reverseLookupTable[id]]
},
+ collapseThread() {
+ /* no-op */
+ },
+ showThread() {
+ /* no-op */
+ },
+ showAllSubthreads() {
+ /* no-op */
+ },
+ toggleCurrentProp(name) {
+ this.toggleStatusContentProperty(this.status.id, name)
+ },
+ setCurrentProp(name) {
+ this.setStatusContentProperty(this.status.id, name)
+ },
},
}
diff --git a/src/components/thread_tree/thread_tree.vue b/src/components/thread_tree/thread_tree.vue
index 72f8512e5..4ffe463fe 100644
--- a/src/components/thread_tree/thread_tree.vue
+++ b/src/components/thread_tree/thread_tree.vue
@@ -1,34 +1,44 @@
- $emit('suspendableStateChange', e)"
/>
- $emit('goto', e)"
- @dive="(e) => $emit('dive', e)"
- @suspendable-state-change="e => $emit('suspendableStateChange', e)"
+ :status-content-properties="statusContentProperties"
+ :set-status-content-property="setStatusContentProperty"
+ :toggle-status-content-property="toggleStatusContentProperty"
+ :dive="dive"
/>
-
diff --git a/src/stores/interface.js b/src/stores/interface.js
index 6e82dc5f9..e76a23cc2 100644
--- a/src/stores/interface.js
+++ b/src/stores/interface.js
@@ -199,7 +199,7 @@ export const useInterfaceStore = defineStore('interface', {
console.log(this.globalError)
},
clearGlobalError() {
- this.globalError = null
+ this.globalError = null;
},
pushGlobalNotice({
messageKey,
diff --git a/src/sw.js b/src/sw.js
index b7629b5f2..f1c1b75d2 100644
--- a/src/sw.js
+++ b/src/sw.js
@@ -1,8 +1,5 @@
/* eslint-env serviceworker */
-// biome-ignore: side effect import of assets list
-import 'virtual:pleroma-fe/service_worker_env'
-
import { createI18n } from 'vue-i18n'
import { storage } from 'src/lib/storage.js'