refactor: refactor thread, message, and gap models to Typescript
Bug: twpowertools:230
Change-Id: I6729546ec4e84f4ef2e801a3c479b63c351008f9
diff --git a/src/common/protojs.types.ts b/src/common/protojs.types.ts
new file mode 100644
index 0000000..0ac6164
--- /dev/null
+++ b/src/common/protojs.types.ts
@@ -0,0 +1,23 @@
+export type ProtobufNumber = string | number;
+
+/**
+ * Protobuf message encoded in the regular format returned by default by the
+ * Tailwind API.
+ *
+ * @example
+ * ```ts
+ * // Numbers are coerced to strings.
+ * {
+ * 1: {
+ * 2: 'foo',
+ * 3: {
+ * 42: 'bar',
+ * 43: false,
+ * 44: null,
+ * },
+ * },
+ * 2: [true, false, true],
+ * },
+ * ```
+ */
+export type ProtobufObject = any;
diff --git a/src/features/extraInfo/core/infoHandlers/thread.js b/src/features/extraInfo/core/infoHandlers/thread.js
index 6c8fb73..d783fc0 100644
--- a/src/features/extraInfo/core/infoHandlers/thread.js
+++ b/src/features/extraInfo/core/infoHandlers/thread.js
@@ -1,7 +1,7 @@
import {waitFor} from 'poll-until-promise';
import {parseUrl} from '../../../../common/commonUtils.js';
-import ThreadModel from '../../../../models/Thread.js';
+import ThreadModel from '../../../../models/Thread';
import {kViewThreadResponse} from '../consts.js';
import MessageExtraInfoService from '../services/message.js';
diff --git a/src/features/extraInfo/core/injections/baseThreadMessage.js b/src/features/extraInfo/core/injections/baseThreadMessage.js
index d0cd162..568f2db 100644
--- a/src/features/extraInfo/core/injections/baseThreadMessage.js
+++ b/src/features/extraInfo/core/injections/baseThreadMessage.js
@@ -1,7 +1,6 @@
import {MDCTooltip} from '@material/tooltip';
import {shouldImplement} from '../../../../common/commonUtils.js';
-import ThreadModel from '../../../../models/Thread.js';
import MessageExtraInfoService from '../services/message.js';
import BaseExtraInfoInjection from './base.js';
diff --git a/src/features/extraInfo/core/injections/threadQuestion.js b/src/features/extraInfo/core/injections/threadQuestion.js
index d797ee1..0aa397f 100644
--- a/src/features/extraInfo/core/injections/threadQuestion.js
+++ b/src/features/extraInfo/core/injections/threadQuestion.js
@@ -1,6 +1,5 @@
import {MDCTooltip} from '@material/tooltip';
-import ThreadModel from '../../../../models/Thread.js';
import ThreadExtraInfoService from '../services/thread.js';
import BaseExtraInfoInjection from './base.js';
diff --git a/src/models/Gap.js b/src/models/Gap.js
index 8c380b2..f5be276 100644
--- a/src/models/Gap.js
+++ b/src/models/Gap.js
@@ -1,4 +1,4 @@
-import ThreadModel from './Thread.js';
+import ThreadModel from './Thread.ts';
export default class GapModel {
constructor(data, thread) {
diff --git a/src/models/Message.js b/src/models/Message.js
deleted file mode 100644
index 0e9d6fb..0000000
--- a/src/models/Message.js
+++ /dev/null
@@ -1,128 +0,0 @@
-import ItemMetadataState from './enums/ItemMetadataState.js';
-import GapModel from './Gap.js';
-import ThreadModel from './Thread.js';
-
-export default class MessageModel {
- constructor(data, thread) {
- this.data = data ?? {};
- this.thread = thread ?? new ThreadModel();
- this.commentsAndGaps = null;
- }
-
- getCreatedTimestamp() {
- return this.data[1]?.[1]?.[2] ?? null;
- }
-
- getCreatedMicroseconds() {
- let a = this.getCreatedTimestamp();
- if (a === null) a = '0';
- return BigInt(a);
- }
-
- getRawCommentsAndGaps() {
- return this.data[12] ?? [];
- }
-
- #getMessageOrGapModels() {
- const rawMogs = this.getRawCommentsAndGaps();
- return rawMogs.filter(mog => mog !== undefined).map(mog => {
- if (mog[1]) return new MessageModel(mog[1], this.thread);
- if (mog[2]) return new GapModel(mog[2], this.thread);
- });
- }
-
- getCommentsAndGaps() {
- if (this.commentsAndGaps === null)
- this.commentsAndGaps = this.#getMessageOrGapModels();
- return this.commentsAndGaps;
- }
-
- clearCommentsAndGaps() {
- this.commentsAndGaps = [];
- this.data[12] = [];
- }
-
- getPayload() {
- return this.data[1]?.[4] ?? null;
- }
-
- setPayload(value) {
- if (!this.data[1]) this.data[1] = [];
- this.data[1][4] = value;
- }
-
- getId() {
- return this.data[1]?.[1]?.[1] ?? null;
- }
-
- getAuthor() {
- return this.data[3] ?? null;
- }
-
- getParentMessageId() {
- return this.data[1]?.[37] ?? null;
- }
-
- clearParentMessageId() {
- if (!this.data[1]) return;
- delete this.data[1][37];
- }
-
- isDeleted() {
- return this.data[5]?.[3] ?? null;
- }
-
- getState() {
- return this.data[5]?.[1] ?? null;
- }
-
- getEndPendingStateTimestampMicros() {
- return this.data[1]?.[17] ?? null;
- }
-
- isTakenDown() {
- return [
- ItemMetadataState.AUTOMATED_ABUSE_TAKE_DOWN_DELETE,
- ItemMetadataState.MANUAL_PROFILE_TAKE_DOWN_SUSPEND,
- ItemMetadataState.AUTOMATED_ABUSE_TAKE_DOWN_HIDE,
- ItemMetadataState.MANUAL_TAKE_DOWN_DELETE,
- ItemMetadataState.MANUAL_TAKE_DOWN_HIDE,
- ].includes(this.getState());
- }
-
- isComment() {
- return !!this.getParentMessageId;
- }
-
- toRawMessageOrGap() {
- return {1: this.data};
- }
-
- mergeCommentOrGapViews(a) {
- this.commentsAndGaps = ThreadModel.mergeMessageOrGaps(
- a.getCommentsAndGaps(), this.getCommentsAndGaps());
- this.data[12] = this.commentsAndGaps.map(cog => cog.toRawMessageOrGap());
- }
-
- /**
- * The following method is based on logic written by Googlers in the TW
- * frontend and thus is not included as part of the MIT license.
- *
- * Source:
- * module$exports$google3$customer_support$content$ui$client$tailwind$models$message_model$message_model.MessageModel.prototype.canComment
- */
- canComment(currentUser) {
- if (this.isDeleted()) return false;
- if (this.isTakenDown()) return false;
- if (currentUser.isAccountDisabled()) return false;
- if (this.thread.isLocked() &&
- !currentUser.isAtLeastCommunityManager(this.thread.getForumId())) {
- return false;
- }
- if (this.thread.isSoftLocked() && !currentUser.isAtLeastSilverRole() &&
- !this.thread.isAuthoredByUser()) {
- return false;
- }
- return true;
- }
-}
diff --git a/src/models/Message.ts b/src/models/Message.ts
new file mode 100644
index 0000000..d9edfe6
--- /dev/null
+++ b/src/models/Message.ts
@@ -0,0 +1,151 @@
+import { ProtobufNumber, ProtobufObject } from '../common/protojs.types.js';
+import ItemMetadataState from './enums/ItemMetadataState.js';
+import GapModel from './Gap.js';
+import ThreadModel from './Thread';
+import UserModel from './User.js';
+
+// TODO(https://iavm.xyz/b/twpowertools/231): This class is being used for 2
+// messages in different places. Fix this.
+/**
+ * Model for the `ForumMessage` protobuf message.
+ *
+ * WARNING: it has methods which correspond to the `MessageView` message.
+ */
+export default class MessageModel {
+ private data: ProtobufObject;
+ private thread: ThreadModel;
+ private commentsAndGaps: Array<MessageModel | GapModel> | null;
+
+ constructor(data?: ProtobufObject, thread?: ThreadModel) {
+ this.data = data ?? {};
+ this.thread = thread ?? new ThreadModel();
+ this.commentsAndGaps = null;
+ }
+
+ getCreatedTimestamp() {
+ return (this.data[1]?.[1]?.[2] as ProtobufNumber) ?? null;
+ }
+
+ getCreatedMicroseconds() {
+ let a = this.getCreatedTimestamp();
+ if (a === null) a = '0';
+ return BigInt(a);
+ }
+
+ getRawCommentsAndGaps(): ProtobufObject[] {
+ return this.data[12] ?? [];
+ }
+
+ private getMessageOrGapModels(): Array<MessageModel | GapModel> {
+ const rawMogs = this.getRawCommentsAndGaps();
+ return rawMogs
+ .filter((mog) => mog !== undefined)
+ .map((mog) => {
+ if (mog[1]) return new MessageModel(mog[1], this.thread);
+ if (mog[2]) return new GapModel(mog[2], this.thread);
+ throw new Error('Expected message or gap.');
+ });
+ }
+
+ getCommentsAndGaps(): Array<MessageModel | GapModel> {
+ if (this.commentsAndGaps === null)
+ this.commentsAndGaps = this.getMessageOrGapModels();
+ return this.commentsAndGaps;
+ }
+
+ clearCommentsAndGaps() {
+ this.commentsAndGaps = [];
+ this.data[12] = [];
+ }
+
+ getPayload() {
+ return this.data[1]?.[4] as string ?? null;
+ }
+
+ setPayload(value: string | null) {
+ if (!this.data[1]) this.data[1] = [];
+ this.data[1][4] = value;
+ }
+
+ getId() {
+ return this.data[1]?.[1]?.[1] as ProtobufNumber ?? null;
+ }
+
+ getAuthor(): ProtobufObject | null {
+ return this.data[3] ?? null;
+ }
+
+ getParentMessageId() {
+ return this.data[1]?.[37] as ProtobufNumber ?? null;
+ }
+
+ clearParentMessageId() {
+ if (!this.data[1]) return;
+ delete this.data[1][37];
+ }
+
+ isDeleted() {
+ return this.data[5]?.[3] as boolean ?? null;
+ }
+
+ getState() {
+ return this.data[5]?.[1] as number ?? null;
+ }
+
+ getEndPendingStateTimestampMicros() {
+ return this.data[1]?.[17] as ProtobufNumber ?? null;
+ }
+
+ isTakenDown() {
+ return [
+ ItemMetadataState.AUTOMATED_ABUSE_TAKE_DOWN_DELETE,
+ ItemMetadataState.MANUAL_PROFILE_TAKE_DOWN_SUSPEND,
+ ItemMetadataState.AUTOMATED_ABUSE_TAKE_DOWN_HIDE,
+ ItemMetadataState.MANUAL_TAKE_DOWN_DELETE,
+ ItemMetadataState.MANUAL_TAKE_DOWN_HIDE,
+ ].includes(this.getState());
+ }
+
+ isComment() {
+ return !!this.getParentMessageId;
+ }
+
+ toRawMessageOrGap(): ProtobufObject {
+ return { 1: this.data };
+ }
+
+ mergeCommentOrGapViews(a: MessageModel) {
+ this.commentsAndGaps = ThreadModel.mergeMessageOrGaps(
+ a.getCommentsAndGaps(),
+ this.getCommentsAndGaps(),
+ );
+ this.data[12] = this.commentsAndGaps.map((cog) => cog.toRawMessageOrGap());
+ }
+
+ /**
+ * The following method is based on logic written by Googlers in the TW
+ * frontend and thus is not included as part of the MIT license.
+ *
+ * Source:
+ * module$exports$google3$customer_support$content$ui$client$tailwind$models$message_model$message_model.MessageModel.prototype.canComment
+ */
+ canComment(currentUser: UserModel) {
+ if (this.isDeleted()) return false;
+ if (this.isTakenDown()) return false;
+ if (currentUser.isAccountDisabled()) return false;
+ if (
+ this.thread.isLocked() &&
+ !currentUser.isAtLeastCommunityManager(this.thread.getForumId())
+ ) {
+ return false;
+ }
+ if (
+ this.thread.isSoftLocked() &&
+ !currentUser.isAtLeastSilverRole() &&
+ !this.thread.isAuthoredByUser()
+ ) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/models/Thread.js b/src/models/Thread.js
deleted file mode 100644
index a613fb7..0000000
--- a/src/models/Thread.js
+++ /dev/null
@@ -1,202 +0,0 @@
-import GapModel from './Gap.js';
-import MessageModel from './Message.js';
-
-// Keys of the PromotedMessages protobuf message which contain lists of promoted
-// messages.
-const kPromotedMessagesKeys = [1, 2, 3, 4, 5, 6];
-
-export default class ThreadModel {
- constructor(data) {
- this.data = data ?? {};
- }
-
- getId() {
- return this.data[2]?.[1]?.[1] ?? null;
- }
-
- getForumId() {
- return this.data[2]?.[1]?.[3] ?? null;
- }
-
- getRawCommentsAndGaps() {
- return this.data[40] ?? [];
- }
-
- setRawCommentsAndGaps(cogs) {
- this.data[40] = cogs;
- }
-
- getMessageOrGapModels() {
- const rawMogs = this.getRawCommentsAndGaps();
- return rawMogs.filter(mog => mog !== undefined).map(mog => {
- if (mog[1]) return new MessageModel(mog[1], this);
- if (mog[2]) return new GapModel(mog[2], this);
- });
- }
-
- setLastMessage(message) {
- if (!this.data[17]) this.data[17] = [];
- this.data[17][3] = message;
- }
-
- setNumMessages(num) {
- this.data[8] = num;
- }
-
- isLocked() {
- // TODO: When a forum is read-only, this should also return true.
- return this.data[2]?.[5] == true;
- }
-
- isSoftLocked() {
- return this.data[2]?.[51] == true;
- }
-
- isAuthoredByUser() {
- return this.data[9] == true;
- }
-
- toRawThread() {
- return this.data;
- }
-
- getPromotedMessagesList() {
- const promotedMessages = [];
- for (const key of kPromotedMessagesKeys) {
- const messagesList = this.data[17][key] ?? [];
- for (const rawMessage of messagesList) {
- const message = new MessageModel(rawMessage);
- if (message.getId() === null) continue;
-
- const isMessageAlreadyIncluded = promotedMessages.some(
- existingMessage => existingMessage.getId() == message.getId());
- if (isMessageAlreadyIncluded) continue;
-
- promotedMessages.push(message);
- }
- }
- return promotedMessages;
- }
-
- /**
- * Get a list with all the messages contained in the model.
- */
- getAllMessagesList() {
- const messages = [];
-
- for (const messageOrGap of this.getMessageOrGapModels()) {
- if (!(messageOrGap instanceof MessageModel)) continue;
- messages.push(messageOrGap);
- for (const subMessageOrGap of messageOrGap.getCommentsAndGaps()) {
- if (!(subMessageOrGap instanceof MessageModel)) continue;
- messages.push(subMessageOrGap);
- }
- }
-
- const promotedMessages = this.getPromotedMessagesList();
- for (const message of promotedMessages) {
- const isMessageAlreadyIncluded = messages.some(
- existingMessage => existingMessage.getId() == message.getId());
- if (isMessageAlreadyIncluded) continue;
-
- messages.push(message);
- }
-
- return messages;
- }
-
- /**
- * The following code is based on logic written by Googlers in the TW frontend
- * and thus is not included as part of the MIT license.
- */
- static mergeMessageOrGaps(a, b) {
- if (a.length == 0 || b.length == 0)
- return a.length > 0 ? a : b.length > 0 ? b : [];
-
- let e = [];
- for (let g = 0, k = 0, m = 0, q = a[g], u = b[k];
- g < a.length && k < b.length;) {
- if (q instanceof MessageModel && u instanceof MessageModel) {
- if (q.getCreatedMicroseconds() === u.getCreatedMicroseconds()) {
- u.mergeCommentOrGapViews(q);
- }
-
- e.push(u);
-
- if (g === a.length - 1 || k === b.length - 1) {
- for (; ++g < a.length;) e.push(a[g]);
- for (; ++k < b.length;) e.push(b[k]);
- break;
- }
-
- q = a[++g];
- u = b[++k];
- } else {
- if (u instanceof GapModel) {
- let z;
- for (z = q instanceof MessageModel ? q.getCreatedMicroseconds() :
- q.getEndTimestamp();
- z < u.getEndTimestamp();) {
- e.push(q);
- m += q instanceof GapModel ? q.getCount() : 1;
- if (g === a.length - 1) break;
- q = a[++g];
- z = q instanceof MessageModel ? q.getCreatedMicroseconds() :
- q.getEndTimestamp();
- }
- if (q instanceof GapModel && u.getCount() - m > 0 &&
- z >= u.getEndTimestamp()) {
- const gm = new GapModel();
- gm.setCount(u.getCount() - m);
- gm.setStartMicroseconds('' + q.getStartTimestamp());
- gm.setEndMicroseconds('' + u.getEndTimestamp());
- gm.setParentId(u.getParentId());
- e.push(gm);
- m = u.getCount() - m;
- } else {
- m = 0;
- }
- if (k === b.length - 1) break;
- u = b[++k];
- }
- if (q instanceof GapModel) {
- let z;
- for (z = u instanceof MessageModel ? u.getCreatedMicroseconds() :
- u.getEndTimestamp();
- z < q.getEndTimestamp();) {
- e.push(u);
- m += u instanceof GapModel ? u.getCount() : 1;
- if (k === b.length - 1) break;
- u = b[++k];
- z = u instanceof MessageModel ? u.getCreatedMicroseconds() :
- u.getEndTimestamp();
- }
- if (u instanceof GapModel && q.getCount() - m > 0 &&
- z >= q.getEndTimestamp()) {
- const gm = new GapModel();
- gm.setCount(q.getCount() - m);
- gm.setStartMicroseconds('' + u.getStartTimestamp());
- gm.setEndMicroseconds('' + q.getEndTimestamp());
- gm.setParentId(q.getParentId());
- e.push(gm);
- m = q.getCount() - m;
- } else {
- m = 0;
- }
- if (g === a.length - 1) break;
- q = a[++g];
- }
- }
- }
- return e;
- }
-
- static mergeMessageOrGapsMultiarray(mogsModels) {
- if (mogsModels.length < 1) return [];
- let mergeResult = mogsModels[0];
- for (let i = 1; i < mogsModels.length; ++i) {
- mergeResult = ThreadModel.mergeMessageOrGaps(mergeResult, mogsModels[i]);
- }
- return mergeResult;
- }
-}
diff --git a/src/models/Thread.ts b/src/models/Thread.ts
new file mode 100644
index 0000000..bc8f8e7
--- /dev/null
+++ b/src/models/Thread.ts
@@ -0,0 +1,241 @@
+import { ProtobufNumber, ProtobufObject } from '../common/protojs.types.js';
+import GapModel from './Gap.js';
+import MessageModel from './Message';
+
+// Keys of the PromotedMessages protobuf message which contain lists of promoted
+// messages.
+const kPromotedMessagesKeys = [1, 2, 3, 4, 5, 6];
+
+/**
+ * Model for the `ThreadView` protobuf message.
+ */
+export default class ThreadModel {
+ private data: ProtobufObject;
+
+ constructor(data?: ProtobufObject) {
+ this.data = data ?? {};
+ }
+
+ getId() {
+ return (this.data[2]?.[1]?.[1] as ProtobufNumber) ?? null;
+ }
+
+ getForumId() {
+ return (this.data[2]?.[1]?.[3] as ProtobufNumber) ?? null;
+ }
+
+ getRawCommentsAndGaps(): ProtobufObject[] {
+ return (this.data[40] as ProtobufObject[]) ?? [];
+ }
+
+ setRawCommentsAndGaps(cogs: ProtobufObject[] | null) {
+ this.data[40] = cogs;
+ }
+
+ getMessageOrGapModels() {
+ const rawMogs = this.getRawCommentsAndGaps();
+ return rawMogs
+ .filter((mog) => mog !== undefined)
+ .map((mog) => {
+ if (mog[1]) return new MessageModel(mog[1], this);
+ if (mog[2]) return new GapModel(mog[2], this);
+ throw new Error('Expected message or gap.');
+ });
+ }
+
+ setLastMessage(message: ProtobufObject | null) {
+ if (!this.data[17]) this.data[17] = [];
+ this.data[17][3] = message;
+ }
+
+ setNumMessages(num: ProtobufNumber | null) {
+ this.data[8] = num;
+ }
+
+ isLocked() {
+ // TODO: When a forum is read-only, this should also return true.
+ return this.data[2]?.[5] == true;
+ }
+
+ isSoftLocked() {
+ return this.data[2]?.[51] == true;
+ }
+
+ isAuthoredByUser() {
+ return this.data[9] == true;
+ }
+
+ toRawThread(): ProtobufObject {
+ return this.data;
+ }
+
+ getPromotedMessagesList(): MessageModel[] {
+ const promotedMessages: MessageModel[] = [];
+ for (const key of kPromotedMessagesKeys) {
+ const messagesList = this.data[17][key] ?? [];
+ for (const rawMessage of messagesList) {
+ const message = new MessageModel(rawMessage);
+ if (message.getId() === null) continue;
+
+ const isMessageAlreadyIncluded = promotedMessages.some(
+ (existingMessage) => existingMessage.getId() == message.getId(),
+ );
+ if (isMessageAlreadyIncluded) continue;
+
+ promotedMessages.push(message);
+ }
+ }
+ return promotedMessages;
+ }
+
+ /**
+ * Get a list with all the messages contained in the model.
+ */
+ getAllMessagesList(): MessageModel[] {
+ const messages: MessageModel[] = [];
+
+ for (const messageOrGap of this.getMessageOrGapModels()) {
+ if (!(messageOrGap instanceof MessageModel)) continue;
+ messages.push(messageOrGap);
+ for (const subMessageOrGap of messageOrGap.getCommentsAndGaps()) {
+ if (!(subMessageOrGap instanceof MessageModel)) continue;
+ messages.push(subMessageOrGap);
+ }
+ }
+
+ const promotedMessages = this.getPromotedMessagesList();
+ for (const message of promotedMessages) {
+ const isMessageAlreadyIncluded = messages.some(
+ (existingMessage) => existingMessage.getId() == message.getId(),
+ );
+ if (isMessageAlreadyIncluded) continue;
+
+ messages.push(message);
+ }
+
+ return messages;
+ }
+
+ /**
+ * The following code is based on logic written by Googlers in the TW frontend
+ * and thus is not included as part of the MIT license.
+ */
+ static mergeMessageOrGaps(
+ a: Array<MessageModel | GapModel>,
+ b: Array<MessageModel | GapModel>,
+ ): Array<MessageModel | GapModel> {
+ if (a.length == 0 || b.length == 0)
+ return a.length > 0 ? a : b.length > 0 ? b : [];
+
+ let e: Array<MessageModel | GapModel> = [];
+ for (
+ let g = 0, k = 0, m = 0, q = a[g], u = b[k];
+ g < a.length && k < b.length;
+
+ ) {
+ if (q instanceof MessageModel && u instanceof MessageModel) {
+ if (q.getCreatedMicroseconds() === u.getCreatedMicroseconds()) {
+ u.mergeCommentOrGapViews(q);
+ }
+
+ e.push(u);
+
+ if (g === a.length - 1 || k === b.length - 1) {
+ for (; ++g < a.length; ) e.push(a[g]);
+ for (; ++k < b.length; ) e.push(b[k]);
+ break;
+ }
+
+ q = a[++g];
+ u = b[++k];
+ } else {
+ if (u instanceof GapModel) {
+ let z: bigint;
+ for (
+ z =
+ q instanceof MessageModel
+ ? q.getCreatedMicroseconds()
+ : q.getEndTimestamp();
+ z < u.getEndTimestamp();
+
+ ) {
+ e.push(q);
+ m += q instanceof GapModel ? q.getCount() : 1;
+ if (g === a.length - 1) break;
+ q = a[++g];
+ z =
+ q instanceof MessageModel
+ ? q.getCreatedMicroseconds()
+ : q.getEndTimestamp();
+ }
+ if (
+ q instanceof GapModel &&
+ u.getCount() - m > 0 &&
+ z >= u.getEndTimestamp()
+ ) {
+ const gm = new GapModel();
+ gm.setCount(u.getCount() - m);
+ gm.setStartMicroseconds('' + q.getStartTimestamp());
+ gm.setEndMicroseconds('' + u.getEndTimestamp());
+ gm.setParentId(u.getParentId());
+ e.push(gm);
+ m = u.getCount() - m;
+ } else {
+ m = 0;
+ }
+ if (k === b.length - 1) break;
+ u = b[++k];
+ }
+ if (q instanceof GapModel) {
+ let z: bigint;
+ for (
+ z =
+ u instanceof MessageModel
+ ? u.getCreatedMicroseconds()
+ : u.getEndTimestamp();
+ z < q.getEndTimestamp();
+
+ ) {
+ e.push(u);
+ m += u instanceof GapModel ? u.getCount() : 1;
+ if (k === b.length - 1) break;
+ u = b[++k];
+ z =
+ u instanceof MessageModel
+ ? u.getCreatedMicroseconds()
+ : u.getEndTimestamp();
+ }
+ if (
+ u instanceof GapModel &&
+ q.getCount() - m > 0 &&
+ z >= q.getEndTimestamp()
+ ) {
+ const gm = new GapModel();
+ gm.setCount(q.getCount() - m);
+ gm.setStartMicroseconds('' + u.getStartTimestamp());
+ gm.setEndMicroseconds('' + q.getEndTimestamp());
+ gm.setParentId(q.getParentId());
+ e.push(gm);
+ m = q.getCount() - m;
+ } else {
+ m = 0;
+ }
+ if (g === a.length - 1) break;
+ q = a[++g];
+ }
+ }
+ }
+ return e;
+ }
+
+ static mergeMessageOrGapsMultiarray(
+ mogsModels: Array<Array<MessageModel | GapModel>>,
+ ) {
+ if (mogsModels.length < 1) return [];
+ let mergeResult = mogsModels[0];
+ for (let i = 1; i < mogsModels.length; ++i) {
+ mergeResult = ThreadModel.mergeMessageOrGaps(mergeResult, mogsModels[i]);
+ }
+ return mergeResult;
+ }
+}
diff --git a/src/redirect/index.js b/src/redirect/index.js
index 551d4d6..09c4b87 100644
--- a/src/redirect/index.js
+++ b/src/redirect/index.js
@@ -1,5 +1,5 @@
import {parseView} from '../common/TWBasicUtils.js';
-import ThreadModel from '../models/Thread.js';
+import ThreadModel from '../models/Thread';
var CCThreadWithoutMessage = /forum\/[0-9]*\/thread\/[0-9]*$/;
diff --git a/src/xhrInterceptor/responseModifiers/flattenThread.js b/src/xhrInterceptor/responseModifiers/flattenThread.js
index 452b1fb..6923178 100644
--- a/src/xhrInterceptor/responseModifiers/flattenThread.js
+++ b/src/xhrInterceptor/responseModifiers/flattenThread.js
@@ -1,8 +1,8 @@
import {kAdditionalInfoClass} from '../../features/flattenThreads/core/flattenThreads.js';
import GapModel from '../../models/Gap.js';
-import MessageModel from '../../models/Message.js';
+import MessageModel from '../../models/Message';
import StartupDataModel from '../../models/StartupData.js';
-import ThreadModel from '../../models/Thread.js';
+import ThreadModel from '../../models/Thread';
const currentUser = StartupDataModel.buildFromCCDOM().getCurrentUserModel();
diff --git a/src/xhrInterceptor/responseModifiers/loadMoreThread.js b/src/xhrInterceptor/responseModifiers/loadMoreThread.js
index 2ddf37d..2b2fe63 100644
--- a/src/xhrInterceptor/responseModifiers/loadMoreThread.js
+++ b/src/xhrInterceptor/responseModifiers/loadMoreThread.js
@@ -1,8 +1,8 @@
import {CCApi} from '../../common/api.js';
import {getAuthUser} from '../../common/communityConsoleUtils.js';
import GapModel from '../../models/Gap.js';
-import MessageModel from '../../models/Message.js';
-import ThreadModel from '../../models/Thread.js';
+import MessageModel from '../../models/Message';
+import ThreadModel from '../../models/Thread';
const authuser = getAuthUser();