diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 29f3597d5..3549828dc 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -26,6 +26,7 @@ import { useStatusesStore } from 'src/stores/statuses.js' import { useStreamingStore } from 'src/stores/streaming.js' import { useScrollPosition } from 'src/composables/useScrollPosition.js' +import { useTreeConversationTopology } from 'src/composables/useTreeConversationTopology.js' import { useWindowSize } from 'src/composables/useWindowSize.js' import { @@ -155,7 +156,7 @@ export default { } const resetDisplayState = () => { setFocused(statusId.value) - threadDisplay.value = new Map() + resetThreadDisplay() } watch(statusId, (newVal, oldVal) => { const newConversationId = getConversationId(newVal) @@ -415,98 +416,25 @@ export default { const isTreeView = computed(() => displayStyle.value === 'tree') // ## Tree state - // ### Topology - const ancestors = computed(() => { - // First we fill map with empty sets and add given id's parent - // as set's only element (if any) - const parentMap = conversation.value.reduce( - (result, { id, in_reply_to_status_id: irid }) => { - if (!result.has(id)) { - result.set(id, new Set()) - } - if (irid) { - // Setting parent for current item - result.get(id).add(irid) - } - return result - }, - new Map(), - ) - - // Next we iterate over each entry and fill the whole ancestry chain - parentMap.entries().forEach(([originId, originSet]) => { - let current = originSet.values().next().value - while (current) { - originSet.add(current) - - const parent = parentMap.get(current) ?? new Set() - - current = parent.values().next().value - } - }) - return parentMap - }) - const topLevel = computed(() => - [...ancestors.value.entries()] - .filter(([id, ancestors]) => ancestors.size === 0) - .map(([id]) => getStatusObject(id)), - ) - const getAncestorIds = (id) => ancestors.value.get(id) ?? new Set() - const getAncestors = (id) => - [...getAncestorIds(id)].map(getStatusObject).filter(Boolean) - const currentAncestors = computed(() => - getAncestors(focusedId.value).reverse(), - ) - const currentDepth = computed(() => currentAncestors.value.length) - - // ### Thread Display - const threadDisplay = ref(new Map()) // id => 'showing' | 'hidden' - const threadDisplayDefault = computed(() => { - return conversation.value.reduce((map, status) => { - const { id } = status - const depth = ancestors.value.get(id).size - - const state = (() => { - if (depth - currentDepth.value <= maxDepthToShowByDefault.value) { - return 'showing' - } else { - return 'hidden' - } - })() - - map.set(id, state) - return map - }, new Map()) - }) - provide('threadDisplay', threadDisplay) - provide('threadDisplayDefault', threadDisplayDefault) - - const setThreadDisplayRecursively = (id, value) => { - threadDisplay.value.set(id, value) - ;[...getReplies(id)] - .map((k) => k.id) - .map((id) => setThreadDisplayRecursively(id, value)) - } - const showThreadRecursively = (id) => { - setThreadDisplayRecursively(id, 'showing') - } - - // ## Derived values and config const treeViewIsSimple = computed( () => !mergedConfig.value.conversationTreeAdvanced, ) - const maxDepthToShowByDefault = computed(() => { - // maxDepthInThread = max number of depths that is *visible* - // since our depth starts with 0 and "showing" means "showing children" - // there is a -2 here - const maxDepth = mergedConfig.value.maxDepthInThread - 2 - return Math.min(1, maxDepth) - }) + + // ### Topology + const { + topLevel, + currentAncestors, + threadDisplay, + showThreadRecursively, + resetThreadDisplay, + } = useTreeConversationTopology(conversation, replies, focusedId) + provide('threadDisplay', threadDisplay) + const shouldShowAllConversationButton = computed( () => currentAncestors.value.length > 0 && topLevel.value.length > 1, ) const shouldShowAncestors = computed( - () => isExpanded.value && ancestors.value.get(focusedId.value) != null, + () => isExpanded.value && currentAncestors.value.size > 0, ) const shouldFadeAncestors = computed( () => mergedConfig.value.conversationTreeFadeAncestors, diff --git a/src/components/thread_tree/thread_tree.js b/src/components/thread_tree/thread_tree.js index 714b2b3f5..9dbb4ff77 100644 --- a/src/components/thread_tree/thread_tree.js +++ b/src/components/thread_tree/thread_tree.js @@ -27,7 +27,6 @@ const ThreadTree = { 'focused', 'replies', 'threadDisplay', - 'threadDisplayDefault', 'isExpanded', 'isPage', ], @@ -39,10 +38,7 @@ const ThreadTree = { return !useMergedConfigStore().mergedConfig.conversationTreeAdvanced }, threadShowing() { - const result = - this.threadDisplay.get(this.statusId) ?? - this.threadDisplayDefault.get(this.statusId) - return result === 'showing' + return this.threadDisplay.get(this.statusId) === 'showing' }, canDive() { return this.isExpanded diff --git a/src/composables/useTreeConversationTopology.js b/src/composables/useTreeConversationTopology.js new file mode 100644 index 000000000..a698635de --- /dev/null +++ b/src/composables/useTreeConversationTopology.js @@ -0,0 +1,111 @@ +import { storeToRefs } from 'pinia' +import { computed, ref } from 'vue' + +import { useMergedConfigStore } from 'src/stores/merged_config.js' +import { useStatusesStore } from 'src/stores/statuses.js' + +export function useTreeConversationTopology(conversation, replies, current) { + const getStatusObject = (id) => useStatusesStore().allStatuses.get(id) + const getReplies = (id) => replies.value.get(id) ?? new Set() + + const { mergedConfig } = storeToRefs(useMergedConfigStore()) + + const maxDepthToShowByDefault = computed(() => { + // maxDepthInThread = max number of depths that is *visible* + // since our depth starts with 0 and "showing" means "showing children" + // there is a -2 here + const maxDepth = mergedConfig.value.maxDepthInThread - 2 + return Math.min(1, maxDepth) + }) + + const ancestors = computed(() => { + // First we fill map with empty sets and add given id's parent + // as set's only element (if any) + const parentMap = conversation.value.reduce( + (result, { id, in_reply_to_status_id: irid }) => { + if (!result.has(id)) { + result.set(id, new Set()) + } + if (irid) { + // Setting parent for current item + result.get(id).add(irid) + } + return result + }, + new Map(), + ) + + // Next we iterate over each entry and fill the whole ancestry chain + parentMap.entries().forEach(([originId, originSet]) => { + let current = originSet.values().next().value + while (current) { + originSet.add(current) + + const parent = parentMap.get(current) ?? new Set() + + current = parent.values().next().value + } + }) + return parentMap + }) + const topLevel = computed(() => + [...ancestors.value.entries()] + .filter(([id, ancestors]) => ancestors.size === 0) + .map(([id]) => getStatusObject(id)), + ) + const getAncestorIds = (id) => ancestors.value.get(id) ?? new Set() + const getAncestors = (id) => + [...getAncestorIds(id)].map(getStatusObject).filter(Boolean) + const currentAncestors = computed(() => getAncestors(current.value).reverse()) + const currentDepth = computed(() => currentAncestors.value.length) + + // Thread Display, for collapsing/expanding tree branches + // Map of id => 'showing' | 'hidden' + const threadDisplayOverride = ref(new Map()) + const threadDisplayDefault = computed(() => { + return conversation.value.reduce((map, status) => { + const { id } = status + const depth = ancestors.value.get(id).size + + const state = (() => { + if (depth - currentDepth.value <= maxDepthToShowByDefault.value) { + return 'showing' + } else { + return 'hidden' + } + })() + + map.set(id, state) + return map + }, new Map()) + }) + const threadDisplay = computed(() => { + return new Map( + [...threadDisplayOverride.value.entries()].map(([k, v]) => [ + k, + threadDisplayOverride.value.get(k) ?? threadDisplayDefault.value.get(k), + ]), + ) + }) + + const setThreadDisplayRecursively = (id, value) => { + threadDisplayOverride.value.set(id, value) + ;[...getReplies(id)] + .map((k) => k.id) + .map((id) => setThreadDisplayRecursively(id, value)) + } + const showThreadRecursively = (id) => { + setThreadDisplayRecursively(id, 'showing') + } + const resetThreadDisplay = () => { + threadDisplayOverride.value = new Map() + } + + return { + topLevel, + currentAncestors, + threadDisplay, + showThreadRecursively, + resetThreadDisplay, + } +}