blob: 2cd1771ba276a40d9b190c2fe99f57926a9c5454 [file] [log] [blame]
Adrià Vilanova Martínez7c1a3c12024-12-05 15:34:40 +01001import { ProtobufNumber, ProtobufObject } from '../../common/protojs.types';
2import { kAdditionalInfoClass } from '../../features/flattenThreads/core/flattenThreads.js';
3import GapModel from '../../models/Gap';
4import MessageModel from '../../models/Message';
5import StartupDataModel from '../../models/StartupData';
6import ThreadModel from '../../models/Thread';
7import { Modifier } from './types';
8
9const currentUser = StartupDataModel.buildFromCCDOM().getCurrentUserModel();
10
11const flattenThread: Modifier = {
12 urlRegex: /api\/ViewThread/i,
13 featureGated: true,
14 features: ['flattenthreads', 'flattenthreads_switch_enabled'],
15 isEnabled(options) {
16 return (
17 options['flattenthreads'] && options['flattenthreads_switch_enabled']
18 );
19 },
20 async interceptor(response) {
21 if (!response[1]?.[40]) return response;
22
23 const thread = new ThreadModel(response[1]);
24
25 // Do the actual flattening
26 const originalMogs = thread.getMessageOrGapModels();
27 let extraMogs: Array<MessageModel | GapModel> = [];
28 originalMogs.forEach((mog) => {
29 if (mog instanceof GapModel) return;
30 const cogs = mog.getCommentsAndGaps();
31 extraMogs = extraMogs.concat(cogs);
32 mog.clearCommentsAndGaps();
33 });
34 const mogs = originalMogs.concat(extraMogs);
35
36 // Add some message data to the payload so the extension can show the parent
37 // comment/reply in the case of comments.
38 const newPayloads: Record<string, string> = {};
39 let prevReplyId: ProtobufNumber | undefined = undefined;
40 let prevReplyParentId: ProtobufNumber | undefined = undefined;
41 mogs.forEach((m) => {
42 if (m instanceof GapModel) return;
43
44 const info = getAdditionalInformation(
45 m,
46 mogs,
47 prevReplyId,
48 prevReplyParentId,
49 );
50 prevReplyId = m.getId();
51 prevReplyParentId = info.isComment ? info.parentId : undefined;
52
53 const extraInfoEl = document.createElement('code');
54 extraInfoEl.textContent = JSON.stringify(info);
55 extraInfoEl.setAttribute('style', 'display: none');
56 extraInfoEl.classList.add(kAdditionalInfoClass);
57 newPayloads[m.getId()] = m.getPayload() + extraInfoEl.outerHTML;
58 });
59 mogs.forEach(
60 (m) => m instanceof MessageModel && m.setPayload(newPayloads[m.getId()]),
61 );
62
63 // Clear parent_message_id fields
64 mogs.forEach((m) => m instanceof MessageModel && m.clearParentMessageId());
65
66 // Sort the messages by date
67 mogs.sort((a, b) => {
68 const c =
69 a instanceof MessageModel
70 ? a.getCreatedMicroseconds()
71 : a.getStartTimestamp();
72 const d =
73 b instanceof MessageModel
74 ? b.getCreatedMicroseconds()
75 : b.getStartTimestamp();
76 const diff = c - d;
77 return diff > 0 ? 1 : diff < 0 ? -1 : 0;
78 });
79
80 thread.setRawCommentsAndGaps(mogs.map((mog) => mog.toRawMessageOrGap()));
81
82 // Set last_message to the last message after sorting
83 thread.setLastMessage(thread.getRawCommentsAndGaps().slice(-1)?.[1]);
84
85 // Set num_messages to the updated value, since we've flattened the replies.
86 thread.setNumMessages(thread.getRawCommentsAndGaps().length);
87
88 response[1] = thread.toRawThread();
89 return response;
90 },
91};
92
93interface BaseAdditionalInformation {
94 id: ProtobufNumber;
95 authorName: string;
96 canComment: boolean;
97}
98
99type CommentAdditionalInformation =
100 | {
101 isComment: false;
102 }
103 | {
104 isComment: true;
105 parentId: ProtobufNumber;
106 prevMessage: {
107 id: ProtobufNumber;
108 payload: string;
109 author: ProtobufObject | null;
110 };
111 };
112
113export type AdditionalInformation = BaseAdditionalInformation &
114 CommentAdditionalInformation;
115
116function getAdditionalInformation(
117 message: MessageModel,
118 mogs: Array<MessageModel | GapModel>,
119 prevReplyId: ProtobufNumber | undefined,
120 prevReplyParentId: ProtobufNumber | undefined,
121): AdditionalInformation {
122 const id = message.getId();
123 const parentId = message.getParentMessageId();
124 const authorName = message.getAuthor()?.[1]?.[1];
125 const canComment = message.canComment(currentUser);
126 if (!parentId) {
127 return {
128 isComment: false,
129 id,
130 authorName,
131 canComment,
132 };
133 }
134
135 let prevId;
136 if (parentId == prevReplyParentId && prevReplyParentId) {
137 prevId = prevReplyId;
138 } else {
139 prevId = parentId;
140 }
141
142 const prevMessage = prevId
143 ? mogs.find(
144 (m): m is MessageModel =>
145 m instanceof MessageModel && m.getId() == prevId,
146 )
147 : null;
148
149 return {
150 isComment: true,
151 id,
152 authorName,
153 parentId,
154 prevMessage: {
155 id: prevId,
156 payload: prevMessage?.getPayload(),
157 author: prevMessage?.getAuthor(),
158 },
159 canComment,
160 };
161}
162
163export default flattenThread;