diff --git a/changelog.d/preview_posting.fix b/changelog.d/preview_posting.fix deleted file mode 100644 index 1a35dbeba..000000000 --- a/changelog.d/preview_posting.fix +++ /dev/null @@ -1 +0,0 @@ -Fix posting not working when using preview diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index 4fb6f4746..1cea48670 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -720,10 +720,6 @@ const PostStatusForm = { }) .finally(() => { this.previewLoading = false - // If we post right after doing a preview request - // backend treats non-preview request as preview one - // and "eats" the post - this.updateIdempotencyKey() }) }, debouncePreviewStatus: debounce(function () { diff --git a/src/components/status/status.js b/src/components/status/status.js index 43b0f4806..a53812da1 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -152,7 +152,8 @@ const Status = { }, showReasonMutedThread() { return ( - (this.mainStatus.thread_muted || this.mainSatus.reblog?.thread_muted) && + (this.mainStatus.thread_muted || + this.smainSatus.reblog?.thread_muted) && !this.inConversation ) }, diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js index ed9519e06..3c7e33060 100644 --- a/src/components/timeline/timeline.js +++ b/src/components/timeline/timeline.js @@ -31,7 +31,6 @@ const Timeline = { embedded: Boolean, inProfile: Boolean, skipPinned: Boolean, - hideEmpty: Boolean, }, data() { return { @@ -107,9 +106,7 @@ const Timeline = { const statusesPerSide = Math.ceil(Math.max(3, window.innerHeight / 80)) const min = Math.max(0, this.virtualScrollIndex - statusesPerSide) const max = Math.min(amount, this.virtualScrollIndex + statusesPerSide) - return new Set( - this.filteredVisibleStatuses.slice(min, max).map(({ id }) => id), - ) + return new Set(this.timeline.order.slice(min, max)) }, virtualScrollingEnabled() { return useMergedConfigStore().mergedConfig.virtualScrolling diff --git a/src/components/timeline/timeline.scss b/src/components/timeline/timeline.scss index 984129bf4..2dd66328d 100644 --- a/src/components/timeline/timeline.scss +++ b/src/components/timeline/timeline.scss @@ -4,14 +4,6 @@ backdrop-filter: none; } - .timeline-placeholder { - background: var(--background); - display: flex; - place-items: center; - place-content: center; - padding: 5em; - } - .alert-badge { font-size: 0.75em; line-height: 1; diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue index d3c3eced2..1435bba48 100644 --- a/src/components/timeline/timeline.vue +++ b/src/components/timeline/timeline.vue @@ -40,7 +40,7 @@
-
+ {{ $t('timeline.no_statuses') }} +
+
{{ $t('timeline.no_more_statuses') }}
+
-
diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue index 43d91b8e5..9a987cd09 100644 --- a/src/components/user_profile/user_profile.vue +++ b/src/components/user_profile/user_profile.vue @@ -30,7 +30,6 @@ :timeline-ref="{ name: 'userPinned', argument: userId }" embedded in-profile - hide-empty /> { const numStatusesBeforeFetch = timeline.statusIds.size - if (older && bottomedOut.value) return + if (bottomedOut.value) return return fetchTimeline(args) - .then(({ data, pagination, timestamp }) => { - // No statuses for timeline, ever. - if (timeline.order.length === 0 && data.length === 0) { - bottomedOut.value = true - } - if (!older && data.length >= 20 && numStatusesBeforeFetch > 0) { + .then(({ data: statuses, pagination, timestamp }) => { + if (!older && statuses.length >= 20 && numStatusesBeforeFetch > 0) { useTimelinesStore().requireReload(timeline.name) } - if (older && data.length === 0) { + if (older && statuses.length === 0) { bottomedOut.value = true } - const processed = useStatusesStore().addNewStatuses({ - statuses: data, - timestamp, - }) - - const statuses = processed.map(({ id }) => id) - - const repeats = processed - .filter(({ retweeted_status }) => Boolean(retweeted_status)) - .map(({ id, retweeted_status: { id: repeatedId } }) => [ - id, - repeatedId, - ]) + const processed = useStatusesStore() + .addNewStatuses({ statuses, timestamp }) + .map(({ id }) => id) useTimelinesStore().addStatusesToTimeline(timeline.name, argument, { - statuses, - repeats, + statuses: processed, showImmediately, older, pagination, diff --git a/src/stores/timelines.js b/src/stores/timelines.js index ed4ecfaec..26223e69d 100644 --- a/src/stores/timelines.js +++ b/src/stores/timelines.js @@ -21,11 +21,6 @@ const emptyTl = (name, argument = null) => { // Statuses shown to user visibleStatusIds: new Set(), - // Tracked reprööts, used to avoid displaying same reprööt more than once - repeatedToRepeat: new Map(), - repeatToRepeated: new Map(), - ignoredIds: new Set(), - // Number of statuses not shown yet newStatusCount: 0, @@ -193,7 +188,6 @@ export const useTimelinesStore = defineStore('timelines', { timeline.order = [] timeline.statusIds = new Set() timeline.visibleStatusIds = new Set() - timeline.ignoredIds = new Set() timeline.newStatusCount = 0 timeline.maxId = '' timeline.minId = '' @@ -258,7 +252,6 @@ export const useTimelinesStore = defineStore('timelines', { argument, { statuses, - repeats = [], showImmediately = false, noIdUpdate = false, pagination = {}, @@ -268,8 +261,6 @@ export const useTimelinesStore = defineStore('timelines', { if (statuses.length === 0) return const timeline = this[timelineName] - this.populateRepeats(timeline, repeats) - // This makes sure that user timeline won't get data meant for other // user. I.e. opening different user profiles makes request which could // return data late after user already viewing different user profile @@ -296,18 +287,13 @@ export const useTimelinesStore = defineStore('timelines', { timeline.statusIds.add(statusId) if (isNew) { - const seenBefore = this.checkSeenBefore(timeline, statusId) - if (!seenBefore) { - if (showImmediately) { - // Add it directly to the visibleStatuses, don't change - // newStatusCount - timeline.visibleStatusIds.add(statusId) - } else { - // Just change newStatuscount - timeline.newStatusCount += 1 - } + if (showImmediately) { + // Add it directly to the visibleStatuses, don't change + // newStatusCount + timeline.visibleStatusIds.add(statusId) } else { - timeline.ignoredIds.add(statusId) + // Just change newStatuscount + timeline.newStatusCount += 1 } } }) @@ -315,43 +301,9 @@ export const useTimelinesStore = defineStore('timelines', { onStreamMessage(timeline, argument, event) { this.addStatusesToTimeline(timeline, argument, { statuses: event.data.map(({ id }) => id), - repeats: event.data - .filter(({ retweeted_status }) => Boolean(retweeted_status)) - .map(({ id, retweeted_status: { id: repeatedId } }) => [ - id, - repeatedId, - ]), }) }, - // Reprööt handling - populateRepeats(timeline, repeats) { - // Starting from oldest - ;[...repeats].reverse().forEach(([repeatId, repeatedId]) => { - timeline.repeatToRepeated.set(repeatId, repeatedId) - const knownRepeats = - timeline.repeatedToRepeat.get(repeatedId) ?? new Set() - knownRepeats.add(repeatId) - timeline.repeatedToRepeat.set(repeatedId, knownRepeats) - }) - }, - checkSeenBefore(timeline, statusId) { - // Check if this is a reprööt - const repeatedStatusId = timeline.repeatToRepeated.get(statusId) - // Non-reprööts are never seen before - if (!repeatedStatusId) return false - // We've seen this status already directly - if (timeline.statusIds.has(repeatedStatusId)) return true - - // Check for reprööts - const knownRepeats = timeline.repeatedToRepeat.get(repeatedStatusId) - - // If it's the only reprööt then we've never seen post before - if (knownRepeats.size === 1) return false - // If we're working on oldest known reprööt then we've never seen it before - return first(knownRepeats) !== statusId - }, - // Poll & Push onStreamConnect(timeline) { console.debug('[Timelines] Stream connected', timeline) @@ -439,11 +391,7 @@ export const useTimelinesStore = defineStore('timelines', { timeline.newStatusCount = 0 timeline.order = timeline.order.slice(0, 50) timeline.statusIds = new Set([...timeline.order]) - - // Reprööts CAN be present in order but some must be ignored - timeline.visibleStatusIds = new Set([ - ...timeline.order.filter((id) => !timeline.ignoredIds.has(id)), - ]) + timeline.visibleStatusIds = new Set([...timeline.order]) this.updateTimelineExtremes(timeline) }, syncOrder(timeline) { diff --git a/test/unit/specs/stores/timelines.spec.js b/test/unit/specs/stores/timelines.spec.js index 2b4704052..cfc84ea3d 100644 --- a/test/unit/specs/stores/timelines.spec.js +++ b/test/unit/specs/stores/timelines.spec.js @@ -122,7 +122,6 @@ describe('Timelines store', () => { store.activate('list', '1') store.addStatusesToTimeline('list', '1', { statuses, - repeats: [], pagination: { minId: '1', maxId: '3' }, }) @@ -141,12 +140,10 @@ describe('Timelines store', () => { store.activate('list', '1') store.addStatusesToTimeline('list', '1', { statuses, - repeats: [], pagination: { minId: '1', maxId: '3' }, }) store.addStatusesToTimeline('list', '1', { statuses, - repeats: [], pagination: { minId: '1', maxId: '3' }, }) @@ -165,7 +162,6 @@ describe('Timelines store', () => { store.activate('list', '1') store.addStatusesToTimeline('list', '1', { statuses, - repeats: [], showImmediately: true, pagination: { minId: '1', maxId: '3' }, }) @@ -185,7 +181,6 @@ describe('Timelines store', () => { store.activate('list', '1') store.addStatusesToTimeline('list', '1', { statuses, - repeats: [], noIdUpdate: true, pagination: { minId: '1', maxId: '3' }, }) @@ -205,7 +200,6 @@ describe('Timelines store', () => { store.activate('list', '1') store.addStatusesToTimeline('list', '2', { statuses, - repeats: [], noIdUpdate: true, pagination: { minId: '1', maxId: '3' }, }) @@ -226,12 +220,10 @@ describe('Timelines store', () => { store.activate('list', '1') store.addStatusesToTimeline('list', '1', { statuses: statuses1, - repeats: [], pagination: { minId: '3', maxId: '1' }, }) store.addStatusesToTimeline('list', '1', { statuses: statuses2, - repeats: [], pagination: { minId: '6', maxId: '4' }, }) @@ -252,12 +244,10 @@ describe('Timelines store', () => { store.activate('list', '1') store.addStatusesToTimeline('list', '1', { statuses: statuses1, - repeats: [], pagination: { minId: '6', maxId: '4' }, }) store.addStatusesToTimeline('list', '1', { statuses: statuses2, - repeats: [], pagination: { minId: '3', maxId: '1' }, older: true, }) @@ -270,157 +260,6 @@ describe('Timelines store', () => { expect(store.list).to.have.property('maxId', '6') expect(store.list).to.have.property('minId', '1') }) - - describe('repeat de-duplication', () => { - it('handles repeat de-duplication (older)', () => { - const store = useTimelinesStore() - const statuses1 = ['s7s3', 's6s3', 's5', 's4s0', 's3', 's2'] - const statuses2 = ['s1s0', 's0'] - - store.activate('list', '1') - store.addStatusesToTimeline('list', '1', { - statuses: statuses1, - repeats: [ - ['s7s3', 's2'], - ['s6s3', 's2'], - ['s4s0', 's0'], - ], - pagination: { minId: 's7s3', maxId: 's2' }, - showImmediately: true, - }) - expect(store.list.order).to.eql(statuses1) - expect(store.list.statusIds).to.eql(new Set(statuses1)) - expect(store.list.visibleStatusIds).to.eql( - new Set(['s5', 's4s0', 's3', 's2']), - ) - expect(store.list).to.have.property('maxId', 's7s3') - expect(store.list).to.have.property('minId', 's2') - - store.addStatusesToTimeline('list', '1', { - statuses: statuses2, - repeats: [['s1s0', 's0']], - pagination: { minId: 's1', maxId: 's0' }, - showImmediately: true, - older: true, - }) - - const newOrder = [...statuses1, ...statuses2] - expect(store.list.order).to.eql(newOrder) - expect(store.list.statusIds).to.eql(new Set(newOrder)) - expect(store.list.visibleStatusIds).to.eql( - new Set(['s5', 's4s0', 's3', 's2', 's0']), - ) - expect(store.list).to.have.property('maxId', 's7s3') - expect(store.list).to.have.property('minId', 's0') - }) - - it('handles repeat de-duplication(reverse)', () => { - const store = useTimelinesStore() - const statuses1 = ['s6s2', 's5s2', 's4', 's3s0', 's2', 's1'] - const statuses2 = ['sAs0', 's9s1', 's8', 's7s2'] - - store.activate('list', '1') - store.addStatusesToTimeline('list', '1', { - statuses: statuses1, - repeats: [ - ['s6s2', 's2'], - ['s5s2', 's2'], - ['s3s0', 's0'], - ], - pagination: { minId: 's6s2', maxId: 's1' }, - showImmediately: true, - }) - expect(store.list.order).to.eql(statuses1) - expect(store.list.statusIds).to.eql(new Set(statuses1)) - expect(store.list.visibleStatusIds).to.eql( - new Set(['s4', 's3s0', 's2', 's1']), - ) - expect(store.list).to.have.property('maxId', 's6s2') - expect(store.list).to.have.property('minId', 's1') - - store.addStatusesToTimeline('list', '1', { - statuses: statuses2, - repeats: [ - ['sAs0', 's0'], - ['s9s1', 's1'], - ['s7s2', 's2'], - ], - pagination: { minId: 'sAs0', maxId: 's7s2' }, - showImmediately: true, - }) - - const newOrder = [...statuses2, ...statuses1] - expect(store.list.order).to.eql(newOrder) - expect(store.list.statusIds).to.eql(new Set(newOrder)) - expect(store.list.visibleStatusIds).to.eql( - new Set([ - 's4', - 's3s0', - 's2', - 's1', - // Newer - 's8', - ]), - ) - expect(store.list).to.have.property('maxId', 'sAs0') - expect(store.list).to.have.property('minId', 's1') - }) - - it('showNewStatuses follows de-duplication rules', () => { - const store = useTimelinesStore() - const statuses1 = ['s6s2', 's5s2', 's4', 's3s0', 's2', 's1'] - const statuses2 = ['sAs0', 's9s1', 's8', 's7s2'] - - store.activate('list', '1') - store.addStatusesToTimeline('list', '1', { - statuses: statuses1, - repeats: [ - ['s6s2', 's2'], - ['s5s2', 's2'], - ['s3s0', 's0'], - ], - pagination: { minId: 's6s2', maxId: 's1' }, - showImmediately: true, - }) - expect(store.list.order).to.eql(statuses1) - expect(store.list.statusIds).to.eql(new Set(statuses1)) - expect(store.list.visibleStatusIds).to.eql( - new Set(['s4', 's3s0', 's2', 's1']), - ) - expect(store.list).to.have.property('maxId', 's6s2') - expect(store.list).to.have.property('minId', 's1') - - store.addStatusesToTimeline('list', '1', { - statuses: statuses2, - repeats: [ - ['sAs0', 's0'], - ['s9s1', 's1'], - ['s7s2', 's2'], - ], - pagination: { minId: 'sAs0', maxId: 's7s2' }, - }) - - const newOrder = [...statuses2, ...statuses1] - expect(store.list.order).to.eql(newOrder) - expect(store.list.statusIds).to.eql(new Set(newOrder)) - expect(store.list.newStatusCount).to.eql(1) - expect(store.list.visibleStatusIds).to.eql( - new Set(['s4', 's3s0', 's2', 's1']), - ) - - store.showNewStatuses('list') - expect(store.list.visibleStatusIds).to.eql( - new Set([ - 's4', - 's3s0', - 's2', - 's1', - // Newer - 's8', - ]), - ) - }) - }) }) describe('showNewStatuses', () => { @@ -431,7 +270,6 @@ describe('Timelines store', () => { store.activate('public') store.addStatusesToTimeline('public', undefined, { statuses, - repeats: [], pagination: { minId: '1', maxId: '3' }, }) @@ -442,27 +280,6 @@ describe('Timelines store', () => { expect(store.public.visibleStatusIds).to.eql(new Set(statuses)) expect(store.public.newStatusCount).to.equal(0) }) - - it('limits shown items to 50 for better performance', () => { - const store = useTimelinesStore() - const statuses = new Array(100).fill().map((_, index) => 's' + index) - - store.activate('public') - store.addStatusesToTimeline('public', undefined, { - statuses, - repeats: [], - pagination: { minId: '1', maxId: '3' }, - }) - - expect(store.public.statusIds).to.eql(new Set(statuses)) - expect(store.public.visibleStatusIds).to.eql(new Set()) - expect(store.public.newStatusCount).to.equal(100) - store.showNewStatuses('public') - expect(store.public.visibleStatusIds).to.eql( - new Set(statuses.slice(0, 50)), - ) - expect(store.public.newStatusCount).to.equal(0) - }) }) describe('wipeStatuses', () => { @@ -473,11 +290,9 @@ describe('Timelines store', () => { store.activate('public') store.addStatusesToTimeline('public', undefined, { statuses: ['1', '2', '3', '0'], - repeats: [], }) store.addStatusesToTimeline('friends', undefined, { statuses: ['5', '0', '9', '1'], - repeats: [], }) store.wipeStatuses(['0']) expect(store.friends.statusIds).to.not.have.members('0')