From f4863147c52ca9de2716149fa83b92e061072afc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Kalici=C5=84ski?= Date: Wed, 11 Dec 2024 10:38:08 +0100 Subject: [PATCH 1/9] [JS] MessageDraftV2 --- build.gradle.kts | 1 - js-chat/main.mjs | 4 + js-chat/tests/message-draft-v2.test.ts | 432 ++++++++++++++++++ .../src/jsMain/kotlin/ChannelJs.kt | 8 +- .../src/jsMain/kotlin/MessageDraftV2Js.kt | 156 ++++--- .../src/jsMain/kotlin/MessageJs.kt | 24 +- src/jsMain/resources/index.d.ts | 33 ++ 7 files changed, 597 insertions(+), 61 deletions(-) create mode 100644 js-chat/tests/message-draft-v2.test.ts diff --git a/build.gradle.kts b/build.gradle.kts index 10633c3e..33ae92f8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -78,7 +78,6 @@ kotlin { compilerOptions { target.set("es2015") -// moduleKind.set(JsModuleKind.MODULE_UMD) } binaries.library() } diff --git a/js-chat/main.mjs b/js-chat/main.mjs index b8325cb0..c9d62c85 100644 --- a/js-chat/main.mjs +++ b/js-chat/main.mjs @@ -1,4 +1,8 @@ export * from "../build/dist/js/productionLibrary/pubnub-chat.mjs" export const INTERNAL_MODERATION_PREFIX = "PUBNUB_INTERNAL_MODERATION_" +export const MESSAGE_THREAD_ID_PREFIX = "PUBNUB_INTERNAL_THREAD"; +export const INTERNAL_ADMIN_CHANNEL = "PUBNUB_INTERNAL_ADMIN_CHANNEL"; +export const ERROR_LOGGER_KEY_PREFIX = "PUBNUB_INTERNAL_ERROR_LOGGER"; + import PubNub from "pubnub" export let CryptoModule = PubNub.CryptoModule \ No newline at end of file diff --git a/js-chat/tests/message-draft-v2.test.ts b/js-chat/tests/message-draft-v2.test.ts new file mode 100644 index 00000000..c91b60b9 --- /dev/null +++ b/js-chat/tests/message-draft-v2.test.ts @@ -0,0 +1,432 @@ +import { Channel, Chat } from "../dist" +import { + createChatInstance, + createRandomChannel, + createRandomUser, + renderMessagePart, +} from "./utils" +import { jest } from "@jest/globals" + +describe("MessageDraft", function () { + jest.retryTimes(2) + let chat: Chat + let channel: Channel + let messageDraft + + beforeAll(async () => { + chat = await createChatInstance() + }) + + beforeEach(async () => { + channel = await createRandomChannel() + messageDraft = channel.createMessageDraftV2({ userSuggestionSource: "global" }) + }) + + test.only("should mention 2 users", async () => { + const [user1, user2] = await Promise.all([createRandomUser(), createRandomUser()]) + + messageDraft.update("Hello @user1 and @user2") + messageDraft.addMention(6, 6, "mention", user1.id) + messageDraft.addMention(17, 6, "mention", user2.id) + const messagePreview = messageDraft.getMessagePreview() + expect(messagePreview.length).toBe(4) + expect(messagePreview[0].type).toBe("text") + expect(messagePreview[1].type).toBe("mention") + expect(messagePreview[2].type).toBe("text") + expect(messagePreview[3].type).toBe("mention") + expect(messageDraft.value).toBe(`Hello @user1 and @user2`) + expect(messagePreview.map(renderMessagePart).join("")).toBe( + `Hello @user1 and @user2` + ) + await Promise.all([user1.delete({ soft: false }), user2.delete({ soft: false })]) + }) + + test("should mention 2 - 3 users next to each other", async () => { + const [user1, user2, user3] = await Promise.all([ + createRandomUser(), + createRandomUser(), + createRandomUser(), + ]) + + messageDraft.onChange("Hello @user1 @user2 @user3") + messageDraft.addMentionedUser(user1, 0) + messageDraft.addMentionedUser(user2, 1) + messageDraft.addMentionedUser(user3, 2) + const messagePreview = messageDraft.getMessagePreview() + + expect(messagePreview.length).toBe(6) + expect(messagePreview[0].type).toBe("text") + expect(messagePreview[1].type).toBe("mention") + expect(messagePreview[2].type).toBe("text") + expect(messagePreview[3].type).toBe("mention") + expect(messagePreview[4].type).toBe("text") + expect(messagePreview[5].type).toBe("mention") + expect(messagePreview.map(renderMessagePart).join("")).toBe( + `Hello @${user1.name} @${user2.name} @${user3.name}` + ) + expect(messageDraft.value).toBe(`Hello @${user1.name} @${user2.name} @${user3.name}`) + await Promise.all([ + user1.delete({ soft: false }), + user2.delete({ soft: false }), + user3.delete({ soft: false }), + ]) + }) + + test("should mention 2 - 3 users with words between second and third", async () => { + const [user1, user2, user3] = await Promise.all([ + createRandomUser(), + createRandomUser(), + createRandomUser(), + ]) + + messageDraft.onChange("Hello @user1 @user2 and @user3") + messageDraft.addMentionedUser(user1, 0) + messageDraft.addMentionedUser(user2, 1) + messageDraft.addMentionedUser(user3, 2) + const messagePreview = messageDraft.getMessagePreview() + expect(messageDraft.value).toBe(`Hello @${user1.name} @${user2.name} and @${user3.name}`) + expect(messagePreview.length).toBe(6) + expect(messagePreview[0].type).toBe("text") + expect(messagePreview[1].type).toBe("mention") + expect(messagePreview[2].type).toBe("text") + expect(messagePreview[3].type).toBe("mention") + expect(messagePreview[4].type).toBe("text") + expect(messagePreview[4].content.text).toBe(" and ") + expect(messagePreview[5].type).toBe("mention") + expect(messagePreview.map(renderMessagePart).join("")).toBe( + `Hello @${user1.name} @${user2.name} and @${user3.name}` + ) + expect(messageDraft.value).toBe(`Hello @${user1.name} @${user2.name} and @${user3.name}`) + await Promise.all([ + user1.delete({ soft: false }), + user2.delete({ soft: false }), + user3.delete({ soft: false }), + ]) + }) + + test("should reference 2 channels", async () => { + const [channel1, channel2] = await Promise.all([createRandomChannel(), createRandomChannel()]) + + messageDraft.onChange("Hello #channel1 and #channl2") + messageDraft.addReferencedChannel(channel1, 0) + messageDraft.addReferencedChannel(channel2, 1) + const messagePreview = messageDraft.getMessagePreview() + + expect(messagePreview.length).toBe(4) + expect(messagePreview[0].type).toBe("text") + expect(messagePreview[1].type).toBe("channelReference") + expect(messagePreview[2].type).toBe("text") + expect(messagePreview[3].type).toBe("channelReference") + expect(messagePreview.map(renderMessagePart).join("")).toBe( + `Hello #${channel1.name} and #${channel2.name}` + ) + expect(messageDraft.value).toBe(`Hello #${channel1.name} and #${channel2.name}`) + await Promise.all([channel1.delete({ soft: false }), channel2.delete({ soft: false })]) + }) + + test("should reference 2 channels and 2 mentions", async () => { + const [channel1, channel2] = await Promise.all([createRandomChannel(), createRandomChannel()]) + const [user1, user2] = await Promise.all([createRandomUser(), createRandomUser()]) + + messageDraft.onChange("Hello #channel1 and @brad and #channel2 or @jasmine.") + messageDraft.addReferencedChannel(channel1, 0) + messageDraft.addReferencedChannel(channel2, 1) + messageDraft.addMentionedUser(user1, 0) + messageDraft.addMentionedUser(user2, 1) + const messagePreview = messageDraft.getMessagePreview() + + expect(messagePreview.length).toBe(9) + expect(messagePreview[0].type).toBe("text") + expect(messagePreview[1].type).toBe("channelReference") + expect(messagePreview[2].type).toBe("text") + expect(messagePreview[3].type).toBe("mention") + expect(messagePreview[4].type).toBe("text") + expect(messagePreview[5].type).toBe("channelReference") + expect(messagePreview[6].type).toBe("text") + expect(messagePreview[7].type).toBe("mention") + expect(messagePreview.map(renderMessagePart).join("")).toBe( + `Hello #${channel1.name} and @${user1.name} and #${channel2.name} or @${user2.name}.` + ) + expect(messageDraft.value).toBe( + `Hello #${channel1.name} and @${user1.name} and #${channel2.name} or @${user2.name}.` + ) + await Promise.all([channel1.delete({ soft: false }), channel2.delete({ soft: false })]) + await Promise.all([user1.delete({ soft: false }), user2.delete({ soft: false })]) + }) + + test("should reference 2 channels and 2 mentions with commas", async () => { + const [channel1, channel2] = await Promise.all([createRandomChannel(), createRandomChannel()]) + const [user1, user2] = await Promise.all([createRandomUser(), createRandomUser()]) + + messageDraft.onChange("Hello #channel1, @brad, #channel2 or @jasmine") + messageDraft.addReferencedChannel(channel1, 0) + messageDraft.addReferencedChannel(channel2, 1) + messageDraft.addMentionedUser(user1, 0) + messageDraft.addMentionedUser(user2, 1) + const messagePreview = messageDraft.getMessagePreview() + + expect(messagePreview.length).toBe(8) + expect(messagePreview[0].type).toBe("text") + expect(messagePreview[1].type).toBe("channelReference") + expect(messagePreview[2].type).toBe("text") + expect(messagePreview[3].type).toBe("mention") + expect(messagePreview[4].type).toBe("text") + expect(messagePreview[5].type).toBe("channelReference") + expect(messagePreview[6].type).toBe("text") + expect(messagePreview[7].type).toBe("mention") + expect(messagePreview.map(renderMessagePart).join("")).toBe( + `Hello #${channel1.name}, @${user1.name}, #${channel2.name} or @${user2.name}` + ) + expect(messageDraft.value).toBe( + `Hello #${channel1.name}, @${user1.name}, #${channel2.name} or @${user2.name}` + ) + await Promise.all([channel1.delete({ soft: false }), channel2.delete({ soft: false })]) + await Promise.all([user1.delete({ soft: false }), user2.delete({ soft: false })]) + }) + + test("should reference 2 channels and 2 mentions with commas - another variation", async () => { + const [channel1, channel2, channel3] = await Promise.all([ + createRandomChannel(), + createRandomChannel(), + createRandomChannel(), + ]) + const [user1, user2] = await Promise.all([createRandomUser(), createRandomUser()]) + + messageDraft.onChange("Hello #channel1, @brad, #channel2, #some-random-channel, @jasmine") + messageDraft.addReferencedChannel(channel1, 0) + messageDraft.addReferencedChannel(channel2, 1) + messageDraft.addReferencedChannel(channel2, 2) + messageDraft.addMentionedUser(user1, 0) + messageDraft.addMentionedUser(user2, 1) + const messagePreview = messageDraft.getMessagePreview() + + expect(messagePreview.length).toBe(10) + expect(messagePreview[0].type).toBe("text") + expect(messagePreview[1].type).toBe("channelReference") + expect(messagePreview[2].type).toBe("text") + expect(messagePreview[3].type).toBe("mention") + expect(messagePreview[4].type).toBe("text") + expect(messagePreview[5].type).toBe("channelReference") + expect(messagePreview[6].type).toBe("text") + expect(messagePreview[7].type).toBe("channelReference") + expect(messagePreview[8].type).toBe("text") + expect(messagePreview[9].type).toBe("mention") + expect(messagePreview.map(renderMessagePart).join("")).toBe( + `Hello #${channel1.name}, @${user1.name}, #${channel2.name}, #${channel3.name}, @${user2.name}` + ) + expect(messageDraft.value).toBe( + `Hello #${channel1.name}, @${user1.name}, #${channel2.name}, #${channel3.name}, @${user2.name}` + ) + await Promise.all([ + channel1.delete({ soft: false }), + channel2.delete({ soft: false }), + channel3.delete({ soft: false }), + ]) + await Promise.all([user1.delete({ soft: false }), user2.delete({ soft: false })]) + }) + + test("should add 2 text links and 2 plain links", async () => { + messageDraft.onChange("Hello https://pubnub.com, https://google.com and ") + messageDraft.addLinkedText({ + text: "pubnub", + link: "https://pubnub.com", + positionInInput: messageDraft.value.length, + }) + messageDraft.onChange("Hello https://pubnub.com, https://google.com and pubnub, ") + messageDraft.addLinkedText({ + text: "google", + link: "https://google.com", + positionInInput: messageDraft.value.length, + }) + messageDraft.onChange("Hello https://pubnub.com, https://google.com and pubnub, google.") + const messagePreview = messageDraft.getMessagePreview() + expect(messagePreview.length).toBe(9) + expect(messagePreview[0].type).toBe("text") + expect(messagePreview[1].type).toBe("plainLink") + expect(messagePreview[2].type).toBe("text") + expect(messagePreview[3].type).toBe("plainLink") + expect(messagePreview[4].type).toBe("text") + expect(messagePreview[5].type).toBe("textLink") + expect(messagePreview[6].type).toBe("text") + expect(messagePreview[7].type).toBe("textLink") + expect(messagePreview[8].type).toBe("text") + expect(messagePreview.map(renderMessagePart).join("")).toBe( + "Hello https://pubnub.com, https://google.com and pubnub, google." + ) + expect(messageDraft.value).toBe( + "Hello https://pubnub.com, https://google.com and pubnub, google." + ) + }) + + test("should mix every type of message part", async () => { + const [channel1, channel2] = await Promise.all([createRandomChannel(), createRandomChannel()]) + const [user1, user2, user4, user5] = await Promise.all([ + createRandomUser(), + createRandomUser(), + createRandomUser(), + createRandomUser(), + ]) + messageDraft.onChange("Hello ") + messageDraft.addLinkedText({ + text: "pubnub", + link: "https://pubnub.com", + positionInInput: messageDraft.value.length, + }) + messageDraft.onChange("Hello pubnub at https://pubnub.com! Hello to ") + messageDraft.addLinkedText({ + text: "google", + link: "https://google.com", + positionInInput: messageDraft.value.length, + }) + messageDraft.onChange( + "Hello pubnub at https://pubnub.com! Hello to google at https://google.com. Referencing #channel1, #channel2, #blankchannel, @user1, @user2, and mentioning @blankuser3 @user4 @user5" + ) + messageDraft.addReferencedChannel(channel1, 0) + messageDraft.addReferencedChannel(channel2, 1) + messageDraft.addMentionedUser(user1, 0) + messageDraft.addMentionedUser(user2, 1) + messageDraft.addMentionedUser(user4, 3) + messageDraft.addMentionedUser(user5, 4) + const messagePreview = messageDraft.getMessagePreview() + + expect(messagePreview.length).toBe(20) + expect(messagePreview[0].type).toBe("text") + expect(messagePreview[1].type).toBe("textLink") + expect(messagePreview[2].type).toBe("text") + expect(messagePreview[3].type).toBe("plainLink") + expect(messagePreview[4].type).toBe("text") + expect(messagePreview[5].type).toBe("textLink") + expect(messagePreview[6].type).toBe("text") + expect(messagePreview[7].type).toBe("plainLink") + expect(messagePreview[8].type).toBe("text") + expect(messagePreview[9].type).toBe("channelReference") + expect(messagePreview[10].type).toBe("text") + expect(messagePreview[11].type).toBe("channelReference") + expect(messagePreview[12].type).toBe("text") + expect(messagePreview[13].type).toBe("mention") + expect(messagePreview[14].type).toBe("text") + expect(messagePreview[15].type).toBe("mention") + expect(messagePreview[16].type).toBe("text") + expect(messagePreview[17].type).toBe("mention") + expect(messagePreview[18].type).toBe("text") + expect(messagePreview[19].type).toBe("mention") + expect(messagePreview.map(renderMessagePart).join("")).toBe( + `Hello pubnub at https://pubnub.com! Hello to google at https://google.com. Referencing #${channel1.name}, #${channel2.name}, #blankchannel, @${user1.name}, @${user2.name}, and mentioning @blankuser3 @${user4.name} @${user5.name}` + ) + expect(messageDraft.value).toBe( + `Hello pubnub at https://pubnub.com! Hello to google at https://google.com. Referencing #${channel1.name}, #${channel2.name}, #blankchannel, @${user1.name}, @${user2.name}, and mentioning @blankuser3 @${user4.name} @${user5.name}` + ) + await Promise.all([channel1.delete({ soft: false }), channel2.delete({ soft: false })]) + await Promise.all([ + user1.delete({ soft: false }), + user2.delete({ soft: false }), + user4.delete({ soft: false }), + ]) + }) + + test("should mix every type of message part - variant 2", async () => { + const [channel1, channel2] = await Promise.all([createRandomChannel(), createRandomChannel()]) + const [user1, user2, user4, user5] = await Promise.all([ + createRandomUser(), + createRandomUser(), + createRandomUser(), + createRandomUser(), + ]) + messageDraft.onChange("Hello @user1 #channel1 ") + messageDraft.addMentionedUser(user1, 0) + messageDraft.addReferencedChannel(channel1, 0) + messageDraft.onChange(`${messageDraft.value} `) + messageDraft.addLinkedText({ + text: "pubnub", + link: "https://pubnub.com", + positionInInput: messageDraft.value.length, + }) + messageDraft.onChange(`${messageDraft.value} at https://pubnub.com. `) + messageDraft.addLinkedText({ + text: "google", + link: "https://google.com", + positionInInput: messageDraft.value.length, + }) + messageDraft.onChange( + `${messageDraft.value} at https://google.com, @user2 @blankuser3 #channel2, random text @user4, @user5.` + ) + messageDraft.addReferencedChannel(channel2, 1) + messageDraft.addMentionedUser(user2, 1) + messageDraft.addMentionedUser(user4, 3) + messageDraft.addMentionedUser(user5, 4) + const messagePreview = messageDraft.getMessagePreview() + + expect(messagePreview.length).toBe(21) + expect(messagePreview[0].type).toBe("text") + expect(messagePreview[1].type).toBe("mention") + expect(messagePreview[2].type).toBe("text") + expect(messagePreview[3].type).toBe("channelReference") + expect(messagePreview[4].type).toBe("text") + expect(messagePreview[5].type).toBe("textLink") + expect(messagePreview[6].type).toBe("text") + expect(messagePreview[7].type).toBe("plainLink") + expect(messagePreview[8].type).toBe("text") + expect(messagePreview[9].type).toBe("textLink") + expect(messagePreview[10].type).toBe("text") + expect(messagePreview[11].type).toBe("plainLink") + expect(messagePreview[12].type).toBe("text") + expect(messagePreview[13].type).toBe("mention") + expect(messagePreview[14].type).toBe("text") + expect(messagePreview[15].type).toBe("channelReference") + expect(messagePreview[16].type).toBe("text") + expect(messagePreview[17].type).toBe("mention") + expect(messagePreview[18].type).toBe("text") + expect(messagePreview[19].type).toBe("mention") + expect(messagePreview.map(renderMessagePart).join("")).toBe( + `Hello @Test User #Test Channel pubnub at https://pubnub.com. google at https://google.com, @Test User @blankuser3 #Test Channel, random text @Test User, @Test User.` + ) + expect(messageDraft.value).toBe( + `Hello @Test User #Test Channel pubnub at https://pubnub.com. google at https://google.com, @Test User @blankuser3 #Test Channel, random text @Test User, @Test User.` + ) + await Promise.all([channel1.delete({ soft: false }), channel2.delete({ soft: false })]) + await Promise.all([ + user1.delete({ soft: false }), + user2.delete({ soft: false }), + user4.delete({ soft: false }), + ]) + }) + + test("should reference 3 channels and 3 mentions with no order", async () => { + const [channel1, channel2, channel3] = await Promise.all([ + createRandomChannel(), + createRandomChannel(), + createRandomChannel(), + ]) + const [user1, user2, user3] = await Promise.all([ + createRandomUser(), + createRandomUser(), + createRandomUser(), + ]) + + messageDraft.onChange( + `Hello @real #real #fake @fake @real #fake #fake #real @real #fake #real @@@ @@@@ @ #fake #fake` + ) + messageDraft.addReferencedChannel(channel1, 0) + messageDraft.addReferencedChannel(channel2, 4) + messageDraft.addReferencedChannel(channel3, 6) + messageDraft.addMentionedUser(user1, 0) + messageDraft.addMentionedUser(user2, 2) + messageDraft.addMentionedUser(user3, 3) + const messagePreview = messageDraft.getMessagePreview() + + expect(messagePreview.map(renderMessagePart).join("")).toBe( + "Hello @Test User #Test Channel #fake @fake @Test User #fake #fake #Test Channel @Test User #fake #Test Channel @@@ @@@@ @ #fake #fake" + ) + + await Promise.all([ + channel1.delete({ soft: false }), + channel2.delete({ soft: false }), + channel3.delete({ soft: false }), + ]) + await Promise.all([ + user1.delete({ soft: false }), + user2.delete({ soft: false }), + user3.delete({ soft: false }), + ]) + }) +}) diff --git a/pubnub-chat-impl/src/jsMain/kotlin/ChannelJs.kt b/pubnub-chat-impl/src/jsMain/kotlin/ChannelJs.kt index abe175af..7c09519c 100644 --- a/pubnub-chat-impl/src/jsMain/kotlin/ChannelJs.kt +++ b/pubnub-chat-impl/src/jsMain/kotlin/ChannelJs.kt @@ -200,6 +200,7 @@ open class ChannelJs internal constructor(internal val channel: Channel, interna fun createMessageDraftV2(config: MessageDraftConfig?): MessageDraftV2Js { return MessageDraftV2Js( + this.chatJs, MessageDraftImpl( this.channel, config?.userSuggestionSource?.let { @@ -209,7 +210,12 @@ open class ChannelJs internal constructor(internal val channel: Channel, interna config?.userLimit ?: 10, config?.channelLimit ?: 10 ), - config + createJsObject { + this.userSuggestionSource = config?.userSuggestionSource ?: "channel" + this.isTypingIndicatorTriggered = config?.isTypingIndicatorTriggered ?: (channel.type != ChannelType.PUBLIC) + this.userLimit = config?.userLimit ?: 10 + this.channelLimit = config?.channelLimit ?: 10 + } ) } diff --git a/pubnub-chat-impl/src/jsMain/kotlin/MessageDraftV2Js.kt b/pubnub-chat-impl/src/jsMain/kotlin/MessageDraftV2Js.kt index bc15f1ea..0380ceb6 100644 --- a/pubnub-chat-impl/src/jsMain/kotlin/MessageDraftV2Js.kt +++ b/pubnub-chat-impl/src/jsMain/kotlin/MessageDraftV2Js.kt @@ -1,12 +1,14 @@ @file:OptIn(ExperimentalJsExport::class) import com.pubnub.chat.MentionTarget +import com.pubnub.chat.MessageDraftChangeListener import com.pubnub.chat.MessageElement +import com.pubnub.chat.SuggestedMention import com.pubnub.chat.internal.MessageDraftImpl import com.pubnub.chat.types.InputFile import com.pubnub.kmp.JsMap +import com.pubnub.kmp.PNFuture import com.pubnub.kmp.UploadableImpl -import com.pubnub.kmp.createJsObject import com.pubnub.kmp.then import com.pubnub.kmp.toMap import kotlin.js.Promise @@ -14,9 +16,11 @@ import kotlin.js.Promise @JsExport @JsName("MessageDraftV2") class MessageDraftV2Js internal constructor( + private val chat: ChatJs, private val messageDraft: MessageDraftImpl, - val config: MessageDraftConfig?, + val config: MessageDraftConfig, ) { + val channel: ChannelJs get() = messageDraft.channel.asJs(chat) val value: String get() = messageDraft.value.toString() var quotedMessage: MessageJs? = null var files: Any? = null @@ -41,40 +45,8 @@ class MessageDraftV2Js internal constructor( messageDraft.removeMention(positionOnInput) } - fun getMessagePreview(): Array { - return messageDraft.getMessageElements().map { element -> - when (element) { - is MessageElement.Link -> when (val target = element.target) { - is MentionTarget.Channel -> createJsObject { - this.type = "channelReference" - this.content = createJsObject { - this.name = element.text.substring(1) - this.id = target.channelId - } - } - is MentionTarget.Url -> createJsObject { - this.type = "textLink" - this.content = createJsObject { - this.text = element.text - this.link = target.url - } - } - is MentionTarget.User -> createJsObject { - this.type = "mention" - this.content = createJsObject { - this.name = element.text.substring(1) - this.id = target.userId - } - } - } - is MessageElement.PlainText -> createJsObject { - this.type = "text" - this.content = createJsObject { - this.text = element.text - } - } - } - }.toTypedArray() + fun getMessagePreview(): Array { + return messageDraft.getMessageElements().toJs() } fun send(options: PubNub.PublishParameters?): Promise { @@ -96,30 +68,108 @@ class MessageDraftV2Js internal constructor( options?.ttl?.toInt() ).then { it.toPublishResponse() }.asPromise() } -} -external interface MessageElementJs { - var type: String - var content: MessageElementPayloadJs -} + fun addChangeListener(listener: (Array, Promise>) -> Unit) { + messageDraft.addChangeListener(MessageDraftListenerJs(listener)) + } -external interface MessageElementPayloadJs { - interface Text : MessageElementPayloadJs { - var text: String + fun removeChangeListener(listener: (Array, Promise>) -> Unit) { + messageDraft.removeChangeListener(MessageDraftListenerJs(listener)) } - interface User : MessageElementPayloadJs { - var name: String - var id: String + fun insertText(offset: Int, text: String) = messageDraft.insertText(offset, text) + + fun removeText(offset: Int, length: Int) = messageDraft.removeText(offset, length) + + fun insertSuggestedMention(mention: SuggestedMentionJs, text: String) { + return messageDraft.insertSuggestedMention( + SuggestedMention( + mention.offset, + mention.replaceFrom, + mention.replaceWith, + when (mention.type) { + TYPE_MENTION -> MentionTarget.User(mention.target) + TYPE_CHANNEL_REFERENCE -> MentionTarget.Channel(mention.target) + TYPE_TEXT_LINK -> MentionTarget.Url(mention.target) + else -> throw IllegalStateException("Unknown target type") + } + ), + text + ) } - interface Link : MessageElementPayloadJs { - var text: String - var link: String + fun addMention(offset: Int, length: Int, mentionType: String, mentionTarget: String) { + return messageDraft.addMention( + offset, + length, + when (mentionType) { + TYPE_MENTION -> MentionTarget.User(mentionTarget) + TYPE_CHANNEL_REFERENCE -> MentionTarget.Channel(mentionTarget) + TYPE_TEXT_LINK -> MentionTarget.Url(mentionTarget) + else -> throw IllegalStateException("Unknown target type") + } + ) } - interface Channel : MessageElementPayloadJs { - var name: String - var id: String + fun removeMention(offset: Int) = messageDraft.removeMention(offset) + + fun update(text: String) = messageDraft.update(text) +} + +data class MessageDraftListenerJs(val listener: (Array, Promise>) -> Unit) : MessageDraftChangeListener { + override fun onChange( + messageElements: List, + suggestedMentions: PNFuture>, + ) { + listener( + messageElements.toJs(), + suggestedMentions.then { + it.map { + SuggestedMentionJs( + it.offset, + it.replaceFrom, + it.replaceWith, + when (it.target) { + is MentionTarget.Channel -> TYPE_CHANNEL_REFERENCE + is MentionTarget.Url -> TYPE_TEXT_LINK + is MentionTarget.User -> TYPE_MENTION + }, + when (val link = it.target) { + is MentionTarget.Channel -> link.channelId + is MentionTarget.Url -> link.url + is MentionTarget.User -> link.userId + } + ) + }.toTypedArray() + }.asPromise() + ) } } + +@JsExport +@JsName("SuggestedMention") +class SuggestedMentionJs( + val offset: Int, + val replaceFrom: String, + val replaceWith: String, + val type: String, + val target: String, +) + +private const val TYPE_CHANNEL_REFERENCE = "channelReference" +private const val TYPE_TEXT_LINK = "textLink" +private const val TYPE_MENTION = "mention" +private const val TYPE_TEXT = "text" + +fun List.toJs() = map { element -> + when (element) { + is MessageElement.Link -> when (val target = element.target) { + is MentionTarget.Channel -> MixedTextTypedElement.ChannelReference( + ChannelReferenceContent(target.channelId, element.text.substring(1)) + ) + is MentionTarget.Url -> MixedTextTypedElement.TextLink(TextLinkContent(target.url, element.text)) + is MentionTarget.User -> MixedTextTypedElement.Mention(MentionContent(target.userId, element.text.substring(1))) + } + is MessageElement.PlainText -> MixedTextTypedElement.Text(TextContent(element.text)) + } +}.toTypedArray() diff --git a/pubnub-chat-impl/src/jsMain/kotlin/MessageJs.kt b/pubnub-chat-impl/src/jsMain/kotlin/MessageJs.kt index f1a8dd20..a808a23b 100644 --- a/pubnub-chat-impl/src/jsMain/kotlin/MessageJs.kt +++ b/pubnub-chat-impl/src/jsMain/kotlin/MessageJs.kt @@ -3,6 +3,7 @@ import com.pubnub.api.PubNubError import com.pubnub.api.adjustCollectionTypes import com.pubnub.chat.Message +import com.pubnub.chat.internal.MessageDraftImpl import com.pubnub.chat.internal.message.BaseMessage import com.pubnub.chat.types.EventContent import com.pubnub.chat.types.MessageMentionedUser @@ -67,13 +68,24 @@ open class MessageJs internal constructor(internal val message: Message, interna return message.streamUpdates { it.asJs(chatJs) }::close } + fun getLinkedText() = getMessageElements() + fun getMessageElements(): Array { - return MessageElementsUtils.getMessageElements( - text, - mentionedUsers?.toMap()?.mapKeys { it.key.toInt() } ?: emptyMap(), - textLinks?.toList() ?: emptyList(), - referencedChannels?.toMap()?.mapKeys { it.key.toInt() } ?: emptyMap(), - ) + // data from v1 message draft + if (mentionedUsers?.toMap()?.isNotEmpty() == true || + textLinks?.isNotEmpty() == true || + referencedChannels?.toMap()?.isNotEmpty() == true + ) { + return MessageElementsUtils.getMessageElements( + text, + mentionedUsers?.toMap()?.mapKeys { it.key.toInt() } ?: emptyMap(), + textLinks?.toList() ?: emptyList(), + referencedChannels?.toMap()?.mapKeys { it.key.toInt() } ?: emptyMap(), + ) + } else { + // use v2 message draft + return MessageDraftImpl.getMessageElements(text).toJs() + } } fun editText(newText: String): Promise { diff --git a/src/jsMain/resources/index.d.ts b/src/jsMain/resources/index.d.ts index be0c3e00..a9661de5 100644 --- a/src/jsMain/resources/index.d.ts +++ b/src/jsMain/resources/index.d.ts @@ -421,6 +421,38 @@ type AddLinkedTextParams = { link: string; positionInInput: number; }; + +declare class MessageDraftV2 { + get channel: Channel; + get value: string; + quotedMessage: Message | undefined; + readonly config: MessageDraftConfig; + files?: FileList | File[] | SendFileParameters["file"][]; + addQuote(message: Message): void; + removeQuote(): void; + addLinkedText(params: AddLinkedTextParams): void; + removeLinkedText(positionInInput: number): void; + getMessagePreview(): MixedTextTypedElement[]; + send(params?: MessageDraftOptions): Promise; + addChangeListener(listener:(messageElements: MixedTextTypedElement[], suggestedMentions:Promise) => unknown): void; + removeChangeListener(listener:(messageElements: MixedTextTypedElement[], suggestedMentions:Promise) => unknown): void; + insertText(offset: number, text: string): void; + removeText(offset: number, length: number): void; + removeText(offset: number, length: number): void; + insertSuggestedMention(mention: SuggestedMention, text: string): void; + addMention(offset: number, length: number, mentionType: TextTypes, mentionTarget: string): void; + removeMention(offset: number): void; + update(text: string): void; +} + +declare class SuggestedMention { + offset: number; + replaceFrom: string; + replaceWith: string; + type: TextTypes; + target: string; +} + declare class MessageDraft { private chat; value: string; @@ -527,6 +559,7 @@ declare class Channel { limit: number; }): Promise; createMessageDraft(config?: Partial): MessageDraft; + createMessageDraftV2(config?: Partial): MessageDraftV2; registerForPush(): Promise; unregisterFromPush(): Promise; streamReadReceipts(callback: (receipts: { From 7c4f98245cabea7a3869df4faa5cbe87a5e6f135 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Kalici=C5=84ski?= Date: Wed, 11 Dec 2024 14:52:07 +0100 Subject: [PATCH 2/9] Change listener interface --- build.gradle.kts | 1 + .../src/jsMain/kotlin/MessageDraftV2Js.kt | 58 +++++++++++-------- src/jsMain/resources/index.d.ts | 10 +++- 3 files changed, 44 insertions(+), 25 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 33ae92f8..24b12488 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -78,6 +78,7 @@ kotlin { compilerOptions { target.set("es2015") + generateTypeScriptDefinitions() } binaries.library() } diff --git a/pubnub-chat-impl/src/jsMain/kotlin/MessageDraftV2Js.kt b/pubnub-chat-impl/src/jsMain/kotlin/MessageDraftV2Js.kt index 0380ceb6..fcbc5fb0 100644 --- a/pubnub-chat-impl/src/jsMain/kotlin/MessageDraftV2Js.kt +++ b/pubnub-chat-impl/src/jsMain/kotlin/MessageDraftV2Js.kt @@ -69,11 +69,11 @@ class MessageDraftV2Js internal constructor( ).then { it.toPublishResponse() }.asPromise() } - fun addChangeListener(listener: (Array, Promise>) -> Unit) { + fun addChangeListener(listener: (MessageDraftState) -> Unit) { messageDraft.addChangeListener(MessageDraftListenerJs(listener)) } - fun removeChangeListener(listener: (Array, Promise>) -> Unit) { + fun removeChangeListener(listener: (MessageDraftState) -> Unit) { messageDraft.removeChangeListener(MessageDraftListenerJs(listener)) } @@ -116,32 +116,44 @@ class MessageDraftV2Js internal constructor( fun update(text: String) = messageDraft.update(text) } -data class MessageDraftListenerJs(val listener: (Array, Promise>) -> Unit) : MessageDraftChangeListener { +@JsExport +class MessageDraftState internal constructor( + val messageElements: Array, + suggestedMentionsFuture: PNFuture> +) { + val suggestedMentions: Promise> by lazy { + suggestedMentionsFuture.then { + it.map { + SuggestedMentionJs( + it.offset, + it.replaceFrom, + it.replaceWith, + when (it.target) { + is MentionTarget.Channel -> TYPE_CHANNEL_REFERENCE + is MentionTarget.Url -> TYPE_TEXT_LINK + is MentionTarget.User -> TYPE_MENTION + }, + when (val link = it.target) { + is MentionTarget.Channel -> link.channelId + is MentionTarget.Url -> link.url + is MentionTarget.User -> link.userId + } + ) + }.toTypedArray() + }.asPromise() + } +} + +data class MessageDraftListenerJs(val listener: (MessageDraftState) -> Unit) : MessageDraftChangeListener { override fun onChange( messageElements: List, suggestedMentions: PNFuture>, ) { listener( - messageElements.toJs(), - suggestedMentions.then { - it.map { - SuggestedMentionJs( - it.offset, - it.replaceFrom, - it.replaceWith, - when (it.target) { - is MentionTarget.Channel -> TYPE_CHANNEL_REFERENCE - is MentionTarget.Url -> TYPE_TEXT_LINK - is MentionTarget.User -> TYPE_MENTION - }, - when (val link = it.target) { - is MentionTarget.Channel -> link.channelId - is MentionTarget.Url -> link.url - is MentionTarget.User -> link.userId - } - ) - }.toTypedArray() - }.asPromise() + MessageDraftState( + messageElements.toJs(), + suggestedMentions + ) ) } } diff --git a/src/jsMain/resources/index.d.ts b/src/jsMain/resources/index.d.ts index a9661de5..75f8ba7d 100644 --- a/src/jsMain/resources/index.d.ts +++ b/src/jsMain/resources/index.d.ts @@ -434,8 +434,8 @@ declare class MessageDraftV2 { removeLinkedText(positionInInput: number): void; getMessagePreview(): MixedTextTypedElement[]; send(params?: MessageDraftOptions): Promise; - addChangeListener(listener:(messageElements: MixedTextTypedElement[], suggestedMentions:Promise) => unknown): void; - removeChangeListener(listener:(messageElements: MixedTextTypedElement[], suggestedMentions:Promise) => unknown): void; + addChangeListener(listener: (p0: MessageDraftState) => void): void; + removeChangeListener(listener: (p0: MessageDraftState) => void): void; insertText(offset: number, text: string): void; removeText(offset: number, length: number): void; removeText(offset: number, length: number): void; @@ -445,6 +445,12 @@ declare class MessageDraftV2 { update(text: string): void; } +declare class MessageDraftState { + private constructor(); + get messageElements(): Array; + get suggestedMentions(): Promise>; +} + declare class SuggestedMention { offset: number; replaceFrom: string; From bca74a93c0619179209805ba5d7489fadb1c364f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Kalici=C5=84ski?= Date: Thu, 12 Dec 2024 11:14:42 +0100 Subject: [PATCH 3/9] More MessageDraftV2 --- js-chat/tests/message-draft-v2.test.ts | 43 +++++++++++++++++--------- src/jsMain/resources/index.d.ts | 11 +++---- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/js-chat/tests/message-draft-v2.test.ts b/js-chat/tests/message-draft-v2.test.ts index c91b60b9..6d79fbff 100644 --- a/js-chat/tests/message-draft-v2.test.ts +++ b/js-chat/tests/message-draft-v2.test.ts @@ -1,9 +1,10 @@ -import { Channel, Chat } from "../dist" +import { Channel, Chat, MessageDraftV2, MixedTextTypedElement } from "../dist" import { createChatInstance, createRandomChannel, createRandomUser, renderMessagePart, + sleep, } from "./utils" import { jest } from "@jest/globals" @@ -11,7 +12,7 @@ describe("MessageDraft", function () { jest.retryTimes(2) let chat: Chat let channel: Channel - let messageDraft + let messageDraft: MessageDraftV2 beforeAll(async () => { chat = await createChatInstance() @@ -22,7 +23,7 @@ describe("MessageDraft", function () { messageDraft = channel.createMessageDraftV2({ userSuggestionSource: "global" }) }) - test.only("should mention 2 users", async () => { + test("should mention 2 users", async () => { const [user1, user2] = await Promise.all([createRandomUser(), createRandomUser()]) messageDraft.update("Hello @user1 and @user2") @@ -41,30 +42,42 @@ describe("MessageDraft", function () { await Promise.all([user1.delete({ soft: false }), user2.delete({ soft: false })]) }) - test("should mention 2 - 3 users next to each other", async () => { + test.only("should mention 2 - 3 users next to each other", async () => { const [user1, user2, user3] = await Promise.all([ createRandomUser(), createRandomUser(), createRandomUser(), ]) - messageDraft.onChange("Hello @user1 @user2 @user3") - messageDraft.addMentionedUser(user1, 0) - messageDraft.addMentionedUser(user2, 1) - messageDraft.addMentionedUser(user3, 2) - const messagePreview = messageDraft.getMessagePreview() + let elements: MixedTextTypedElement[][] = [] + let resolve, reject; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); - expect(messagePreview.length).toBe(6) + messageDraft.addChangeListener(async function(state) { + elements.push(state.messageElements) + if (elements.length == 3) { + resolve() + return + } + let mentions = await state.suggestedMentions + messageDraft.insertSuggestedMention(mentions[0], mentions[0].replaceWith) + }) + + messageDraft.update("Hello @Te @Tes @Test") + await promise + + const messagePreview = messageDraft.getMessagePreview() + expect(messagePreview.length).toBe(4) expect(messagePreview[0].type).toBe("text") expect(messagePreview[1].type).toBe("mention") expect(messagePreview[2].type).toBe("text") expect(messagePreview[3].type).toBe("mention") - expect(messagePreview[4].type).toBe("text") - expect(messagePreview[5].type).toBe("mention") expect(messagePreview.map(renderMessagePart).join("")).toBe( - `Hello @${user1.name} @${user2.name} @${user3.name}` + elements[2].map(renderMessagePart).join("") ) - expect(messageDraft.value).toBe(`Hello @${user1.name} @${user2.name} @${user3.name}`) await Promise.all([ user1.delete({ soft: false }), user2.delete({ soft: false }), @@ -128,7 +141,7 @@ describe("MessageDraft", function () { const [channel1, channel2] = await Promise.all([createRandomChannel(), createRandomChannel()]) const [user1, user2] = await Promise.all([createRandomUser(), createRandomUser()]) - messageDraft.onChange("Hello #channel1 and @brad and #channel2 or @jasmine.") + messageDraft.update("Hello #channel1 and @brad and #channel2 or @jasmine.") messageDraft.addReferencedChannel(channel1, 0) messageDraft.addReferencedChannel(channel2, 1) messageDraft.addMentionedUser(user1, 0) diff --git a/src/jsMain/resources/index.d.ts b/src/jsMain/resources/index.d.ts index 75f8ba7d..27ea9eb1 100644 --- a/src/jsMain/resources/index.d.ts +++ b/src/jsMain/resources/index.d.ts @@ -422,9 +422,9 @@ type AddLinkedTextParams = { positionInInput: number; }; -declare class MessageDraftV2 { - get channel: Channel; - get value: string; +export declare class MessageDraftV2 { + get channel(): Channel; + get value(): string; quotedMessage: Message | undefined; readonly config: MessageDraftConfig; files?: FileList | File[] | SendFileParameters["file"][]; @@ -438,20 +438,19 @@ declare class MessageDraftV2 { removeChangeListener(listener: (p0: MessageDraftState) => void): void; insertText(offset: number, text: string): void; removeText(offset: number, length: number): void; - removeText(offset: number, length: number): void; insertSuggestedMention(mention: SuggestedMention, text: string): void; addMention(offset: number, length: number, mentionType: TextTypes, mentionTarget: string): void; removeMention(offset: number): void; update(text: string): void; } -declare class MessageDraftState { +export declare class MessageDraftState { private constructor(); get messageElements(): Array; get suggestedMentions(): Promise>; } -declare class SuggestedMention { +export declare class SuggestedMention { offset: number; replaceFrom: string; replaceWith: string; From 821b2e643bb35692c72089d3ba86204796175aca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Kalici=C5=84ski?= Date: Thu, 12 Dec 2024 11:15:05 +0100 Subject: [PATCH 4/9] Remove crypto dependency by replacing JS UUIDv4 implementation --- build.gradle.kts | 1 - .../kotlin/com/pubnub/kmp/UuidTest.kt | 13 +++++++++++ .../com/pubnub/chat/internal/ChatImpl.js.kt | 22 ++++++++++++++----- 3 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/UuidTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index 24b12488..33ae92f8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -78,7 +78,6 @@ kotlin { compilerOptions { target.set("es2015") - generateTypeScriptDefinitions() } binaries.library() } diff --git a/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/UuidTest.kt b/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/UuidTest.kt new file mode 100644 index 00000000..196969fd --- /dev/null +++ b/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/UuidTest.kt @@ -0,0 +1,13 @@ +package com.pubnub.kmp + +import com.pubnub.chat.internal.generateRandomUuid +import kotlin.test.Test +import kotlin.test.assertTrue + +class UuidTest { + @Test + fun generateUuid() { + val uuid = generateRandomUuid() + assertTrue { uuid.matches(Regex("[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}")) } + } +} diff --git a/pubnub-chat-impl/src/jsMain/kotlin/com/pubnub/chat/internal/ChatImpl.js.kt b/pubnub-chat-impl/src/jsMain/kotlin/com/pubnub/chat/internal/ChatImpl.js.kt index e734ab91..8300bea1 100644 --- a/pubnub-chat-impl/src/jsMain/kotlin/com/pubnub/chat/internal/ChatImpl.js.kt +++ b/pubnub-chat-impl/src/jsMain/kotlin/com/pubnub/chat/internal/ChatImpl.js.kt @@ -1,16 +1,26 @@ package com.pubnub.chat.internal +import kotlin.experimental.and +import kotlin.experimental.or +import kotlin.math.floor +import kotlin.random.Random import kotlin.uuid.ExperimentalUuidApi -import kotlin.uuid.Uuid external val globalThis: dynamic @OptIn(ExperimentalUuidApi::class) actual fun generateRandomUuid(): String { - val process = js("process") - if (process !== undefined && process.versions && process.versions.node && globalThis.crypto === undefined) { - // Node.js environment detected - globalThis.crypto = js("require('crypto')") + val uuid = ByteArray(32) + for (i in 0 until 32) { + uuid[i] = floor(Random.nextDouble() * 16).toInt().toByte() } - return Uuid.random().toString() + uuid[12] = 4; // set bits 12-15 of time-high-and-version to 0100 + uuid[16] = uuid[19] and (1 shl 2).inv().toByte() // set bit 6 of clock-seq-and-reserved to zero + uuid[16] = uuid[19] or (1 shl 3).toByte(); // set bit 7 of clock-seq-and-reserved to one + val uuidString = uuid.joinToString("") { it.toString(16) } + return uuidString.substring(0, 8) + + "-" + uuidString.substring(8, 12) + + "-" + uuidString.substring(12, 16) + + "-" + uuidString.substring(16, 20) + + "-" + uuidString.substring(20) } From 355b16737a0d285b2a9016f6045d6fce3c75270e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Kalici=C5=84ski?= Date: Thu, 12 Dec 2024 16:04:55 +0100 Subject: [PATCH 5/9] MD2 tests --- js-chat/tests/message-draft-v2.test.ts | 359 ++---------------- js-chat/tests/message.test.ts | 4 +- js-chat/tests/utils.ts | 14 +- .../src/jsMain/kotlin/MessageDraftV2Js.kt | 4 +- 4 files changed, 52 insertions(+), 329 deletions(-) diff --git a/js-chat/tests/message-draft-v2.test.ts b/js-chat/tests/message-draft-v2.test.ts index 6d79fbff..bf255c7e 100644 --- a/js-chat/tests/message-draft-v2.test.ts +++ b/js-chat/tests/message-draft-v2.test.ts @@ -5,6 +5,7 @@ import { createRandomUser, renderMessagePart, sleep, + makeid } from "./utils" import { jest } from "@jest/globals" @@ -37,12 +38,12 @@ describe("MessageDraft", function () { expect(messagePreview[3].type).toBe("mention") expect(messageDraft.value).toBe(`Hello @user1 and @user2`) expect(messagePreview.map(renderMessagePart).join("")).toBe( - `Hello @user1 and @user2` + `Hello @@user1 and @@user2` ) await Promise.all([user1.delete({ soft: false }), user2.delete({ soft: false })]) }) - test.only("should mention 2 - 3 users next to each other", async () => { + test("should mention 2 - 3 users next to each other", async () => { const [user1, user2, user3] = await Promise.all([ createRandomUser(), createRandomUser(), @@ -85,316 +86,74 @@ describe("MessageDraft", function () { ]) }) - test("should mention 2 - 3 users with words between second and third", async () => { - const [user1, user2, user3] = await Promise.all([ - createRandomUser(), - createRandomUser(), - createRandomUser(), - ]) - - messageDraft.onChange("Hello @user1 @user2 and @user3") - messageDraft.addMentionedUser(user1, 0) - messageDraft.addMentionedUser(user2, 1) - messageDraft.addMentionedUser(user3, 2) - const messagePreview = messageDraft.getMessagePreview() - expect(messageDraft.value).toBe(`Hello @${user1.name} @${user2.name} and @${user3.name}`) - expect(messagePreview.length).toBe(6) - expect(messagePreview[0].type).toBe("text") - expect(messagePreview[1].type).toBe("mention") - expect(messagePreview[2].type).toBe("text") - expect(messagePreview[3].type).toBe("mention") - expect(messagePreview[4].type).toBe("text") - expect(messagePreview[4].content.text).toBe(" and ") - expect(messagePreview[5].type).toBe("mention") - expect(messagePreview.map(renderMessagePart).join("")).toBe( - `Hello @${user1.name} @${user2.name} and @${user3.name}` - ) - expect(messageDraft.value).toBe(`Hello @${user1.name} @${user2.name} and @${user3.name}`) - await Promise.all([ - user1.delete({ soft: false }), - user2.delete({ soft: false }), - user3.delete({ soft: false }), - ]) - }) - - test("should reference 2 channels", async () => { - const [channel1, channel2] = await Promise.all([createRandomChannel(), createRandomChannel()]) - - messageDraft.onChange("Hello #channel1 and #channl2") - messageDraft.addReferencedChannel(channel1, 0) - messageDraft.addReferencedChannel(channel2, 1) - const messagePreview = messageDraft.getMessagePreview() - - expect(messagePreview.length).toBe(4) - expect(messagePreview[0].type).toBe("text") - expect(messagePreview[1].type).toBe("channelReference") - expect(messagePreview[2].type).toBe("text") - expect(messagePreview[3].type).toBe("channelReference") - expect(messagePreview.map(renderMessagePart).join("")).toBe( - `Hello #${channel1.name} and #${channel2.name}` - ) - expect(messageDraft.value).toBe(`Hello #${channel1.name} and #${channel2.name}`) - await Promise.all([channel1.delete({ soft: false }), channel2.delete({ soft: false })]) - }) - - test("should reference 2 channels and 2 mentions", async () => { - const [channel1, channel2] = await Promise.all([createRandomChannel(), createRandomChannel()]) - const [user1, user2] = await Promise.all([createRandomUser(), createRandomUser()]) - - messageDraft.update("Hello #channel1 and @brad and #channel2 or @jasmine.") - messageDraft.addReferencedChannel(channel1, 0) - messageDraft.addReferencedChannel(channel2, 1) - messageDraft.addMentionedUser(user1, 0) - messageDraft.addMentionedUser(user2, 1) - const messagePreview = messageDraft.getMessagePreview() - - expect(messagePreview.length).toBe(9) - expect(messagePreview[0].type).toBe("text") - expect(messagePreview[1].type).toBe("channelReference") - expect(messagePreview[2].type).toBe("text") - expect(messagePreview[3].type).toBe("mention") - expect(messagePreview[4].type).toBe("text") - expect(messagePreview[5].type).toBe("channelReference") - expect(messagePreview[6].type).toBe("text") - expect(messagePreview[7].type).toBe("mention") - expect(messagePreview.map(renderMessagePart).join("")).toBe( - `Hello #${channel1.name} and @${user1.name} and #${channel2.name} or @${user2.name}.` - ) - expect(messageDraft.value).toBe( - `Hello #${channel1.name} and @${user1.name} and #${channel2.name} or @${user2.name}.` - ) - await Promise.all([channel1.delete({ soft: false }), channel2.delete({ soft: false })]) - await Promise.all([user1.delete({ soft: false }), user2.delete({ soft: false })]) - }) - - test("should reference 2 channels and 2 mentions with commas", async () => { - const [channel1, channel2] = await Promise.all([createRandomChannel(), createRandomChannel()]) - const [user1, user2] = await Promise.all([createRandomUser(), createRandomUser()]) - - messageDraft.onChange("Hello #channel1, @brad, #channel2 or @jasmine") - messageDraft.addReferencedChannel(channel1, 0) - messageDraft.addReferencedChannel(channel2, 1) - messageDraft.addMentionedUser(user1, 0) - messageDraft.addMentionedUser(user2, 1) - const messagePreview = messageDraft.getMessagePreview() - - expect(messagePreview.length).toBe(8) - expect(messagePreview[0].type).toBe("text") - expect(messagePreview[1].type).toBe("channelReference") - expect(messagePreview[2].type).toBe("text") - expect(messagePreview[3].type).toBe("mention") - expect(messagePreview[4].type).toBe("text") - expect(messagePreview[5].type).toBe("channelReference") - expect(messagePreview[6].type).toBe("text") - expect(messagePreview[7].type).toBe("mention") - expect(messagePreview.map(renderMessagePart).join("")).toBe( - `Hello #${channel1.name}, @${user1.name}, #${channel2.name} or @${user2.name}` - ) - expect(messageDraft.value).toBe( - `Hello #${channel1.name}, @${user1.name}, #${channel2.name} or @${user2.name}` - ) - await Promise.all([channel1.delete({ soft: false }), channel2.delete({ soft: false })]) - await Promise.all([user1.delete({ soft: false }), user2.delete({ soft: false })]) - }) - - test("should reference 2 channels and 2 mentions with commas - another variation", async () => { - const [channel1, channel2, channel3] = await Promise.all([ - createRandomChannel(), - createRandomChannel(), - createRandomChannel(), - ]) - const [user1, user2] = await Promise.all([createRandomUser(), createRandomUser()]) - - messageDraft.onChange("Hello #channel1, @brad, #channel2, #some-random-channel, @jasmine") - messageDraft.addReferencedChannel(channel1, 0) - messageDraft.addReferencedChannel(channel2, 1) - messageDraft.addReferencedChannel(channel2, 2) - messageDraft.addMentionedUser(user1, 0) - messageDraft.addMentionedUser(user2, 1) - const messagePreview = messageDraft.getMessagePreview() - - expect(messagePreview.length).toBe(10) - expect(messagePreview[0].type).toBe("text") - expect(messagePreview[1].type).toBe("channelReference") - expect(messagePreview[2].type).toBe("text") - expect(messagePreview[3].type).toBe("mention") - expect(messagePreview[4].type).toBe("text") - expect(messagePreview[5].type).toBe("channelReference") - expect(messagePreview[6].type).toBe("text") - expect(messagePreview[7].type).toBe("channelReference") - expect(messagePreview[8].type).toBe("text") - expect(messagePreview[9].type).toBe("mention") - expect(messagePreview.map(renderMessagePart).join("")).toBe( - `Hello #${channel1.name}, @${user1.name}, #${channel2.name}, #${channel3.name}, @${user2.name}` - ) - expect(messageDraft.value).toBe( - `Hello #${channel1.name}, @${user1.name}, #${channel2.name}, #${channel3.name}, @${user2.name}` - ) - await Promise.all([ - channel1.delete({ soft: false }), - channel2.delete({ soft: false }), - channel3.delete({ soft: false }), - ]) - await Promise.all([user1.delete({ soft: false }), user2.delete({ soft: false })]) - }) - - test("should add 2 text links and 2 plain links", async () => { - messageDraft.onChange("Hello https://pubnub.com, https://google.com and ") - messageDraft.addLinkedText({ - text: "pubnub", - link: "https://pubnub.com", - positionInInput: messageDraft.value.length, - }) - messageDraft.onChange("Hello https://pubnub.com, https://google.com and pubnub, ") - messageDraft.addLinkedText({ - text: "google", - link: "https://google.com", - positionInInput: messageDraft.value.length, - }) - messageDraft.onChange("Hello https://pubnub.com, https://google.com and pubnub, google.") - const messagePreview = messageDraft.getMessagePreview() - expect(messagePreview.length).toBe(9) - expect(messagePreview[0].type).toBe("text") - expect(messagePreview[1].type).toBe("plainLink") - expect(messagePreview[2].type).toBe("text") - expect(messagePreview[3].type).toBe("plainLink") - expect(messagePreview[4].type).toBe("text") - expect(messagePreview[5].type).toBe("textLink") - expect(messagePreview[6].type).toBe("text") - expect(messagePreview[7].type).toBe("textLink") - expect(messagePreview[8].type).toBe("text") - expect(messagePreview.map(renderMessagePart).join("")).toBe( - "Hello https://pubnub.com, https://google.com and pubnub, google." - ) - expect(messageDraft.value).toBe( - "Hello https://pubnub.com, https://google.com and pubnub, google." - ) - }) - test("should mix every type of message part", async () => { - const [channel1, channel2] = await Promise.all([createRandomChannel(), createRandomChannel()]) + const [channel1, channel2] = await Promise.all([createRandomChannel(makeid()), createRandomChannel(makeid())]) const [user1, user2, user4, user5] = await Promise.all([ - createRandomUser(), - createRandomUser(), - createRandomUser(), - createRandomUser(), + createRandomUser(makeid()), + createRandomUser(makeid()), + createRandomUser(makeid()), + createRandomUser(makeid()), ]) - messageDraft.onChange("Hello ") + messageDraft.update("Hello ") messageDraft.addLinkedText({ text: "pubnub", link: "https://pubnub.com", positionInInput: messageDraft.value.length, }) - messageDraft.onChange("Hello pubnub at https://pubnub.com! Hello to ") + messageDraft.update("Hello pubnub at https://pubnub.com! Hello to ") messageDraft.addLinkedText({ text: "google", link: "https://google.com", positionInInput: messageDraft.value.length, }) - messageDraft.onChange( - "Hello pubnub at https://pubnub.com! Hello to google at https://google.com. Referencing #channel1, #channel2, #blankchannel, @user1, @user2, and mentioning @blankuser3 @user4 @user5" - ) - messageDraft.addReferencedChannel(channel1, 0) - messageDraft.addReferencedChannel(channel2, 1) - messageDraft.addMentionedUser(user1, 0) - messageDraft.addMentionedUser(user2, 1) - messageDraft.addMentionedUser(user4, 3) - messageDraft.addMentionedUser(user5, 4) - const messagePreview = messageDraft.getMessagePreview() - expect(messagePreview.length).toBe(20) - expect(messagePreview[0].type).toBe("text") - expect(messagePreview[1].type).toBe("textLink") - expect(messagePreview[2].type).toBe("text") - expect(messagePreview[3].type).toBe("plainLink") - expect(messagePreview[4].type).toBe("text") - expect(messagePreview[5].type).toBe("textLink") - expect(messagePreview[6].type).toBe("text") - expect(messagePreview[7].type).toBe("plainLink") - expect(messagePreview[8].type).toBe("text") - expect(messagePreview[9].type).toBe("channelReference") - expect(messagePreview[10].type).toBe("text") - expect(messagePreview[11].type).toBe("channelReference") - expect(messagePreview[12].type).toBe("text") - expect(messagePreview[13].type).toBe("mention") - expect(messagePreview[14].type).toBe("text") - expect(messagePreview[15].type).toBe("mention") - expect(messagePreview[16].type).toBe("text") - expect(messagePreview[17].type).toBe("mention") - expect(messagePreview[18].type).toBe("text") - expect(messagePreview[19].type).toBe("mention") - expect(messagePreview.map(renderMessagePart).join("")).toBe( - `Hello pubnub at https://pubnub.com! Hello to google at https://google.com. Referencing #${channel1.name}, #${channel2.name}, #blankchannel, @${user1.name}, @${user2.name}, and mentioning @blankuser3 @${user4.name} @${user5.name}` - ) - expect(messageDraft.value).toBe( - `Hello pubnub at https://pubnub.com! Hello to google at https://google.com. Referencing #${channel1.name}, #${channel2.name}, #blankchannel, @${user1.name}, @${user2.name}, and mentioning @blankuser3 @${user4.name} @${user5.name}` - ) - await Promise.all([channel1.delete({ soft: false }), channel2.delete({ soft: false })]) - await Promise.all([ - user1.delete({ soft: false }), - user2.delete({ soft: false }), - user4.delete({ soft: false }), - ]) - }) + let elements: MixedTextTypedElement[][] = [] + let resolve, reject; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); - test("should mix every type of message part - variant 2", async () => { - const [channel1, channel2] = await Promise.all([createRandomChannel(), createRandomChannel()]) - const [user1, user2, user4, user5] = await Promise.all([ - createRandomUser(), - createRandomUser(), - createRandomUser(), - createRandomUser(), - ]) - messageDraft.onChange("Hello @user1 #channel1 ") - messageDraft.addMentionedUser(user1, 0) - messageDraft.addReferencedChannel(channel1, 0) - messageDraft.onChange(`${messageDraft.value} `) - messageDraft.addLinkedText({ - text: "pubnub", - link: "https://pubnub.com", - positionInInput: messageDraft.value.length, - }) - messageDraft.onChange(`${messageDraft.value} at https://pubnub.com. `) - messageDraft.addLinkedText({ - text: "google", - link: "https://google.com", - positionInInput: messageDraft.value.length, + messageDraft.addChangeListener(async function(state) { + elements.push(state.messageElements) + let mentions = await state.suggestedMentions + if (mentions.length == 0) { + resolve() + return + } + messageDraft.insertSuggestedMention(mentions[0], mentions[0].replaceWith) }) - messageDraft.onChange( - `${messageDraft.value} at https://google.com, @user2 @blankuser3 #channel2, random text @user4, @user5.` + + + messageDraft.update( + `Hello pubnub at https://pubnub.com! Hello to google at https://google.com. Referencing #${channel1.name.substring(0,8)}, #${channel2.name.substring(0,8)}, #blankchannel, @${user1.name.substring(0,8)}, @${user2.name.substring(0,8)}, and mentioning @blankuser3 @${user4.name.substring(0,8)} @${user5.name.substring(0,8)}` ) - messageDraft.addReferencedChannel(channel2, 1) - messageDraft.addMentionedUser(user2, 1) - messageDraft.addMentionedUser(user4, 3) - messageDraft.addMentionedUser(user5, 4) + await promise + const messagePreview = messageDraft.getMessagePreview() - expect(messagePreview.length).toBe(21) + expect(messagePreview.length).toBe(16) expect(messagePreview[0].type).toBe("text") - expect(messagePreview[1].type).toBe("mention") + expect(messagePreview[1].type).toBe("textLink") expect(messagePreview[2].type).toBe("text") - expect(messagePreview[3].type).toBe("channelReference") + expect(messagePreview[3].type).toBe("textLink") expect(messagePreview[4].type).toBe("text") - expect(messagePreview[5].type).toBe("textLink") + expect(messagePreview[5].type).toBe("channelReference") expect(messagePreview[6].type).toBe("text") - expect(messagePreview[7].type).toBe("plainLink") + expect(messagePreview[7].type).toBe("channelReference") expect(messagePreview[8].type).toBe("text") - expect(messagePreview[9].type).toBe("textLink") + expect(messagePreview[9].type).toBe("mention") expect(messagePreview[10].type).toBe("text") - expect(messagePreview[11].type).toBe("plainLink") + expect(messagePreview[11].type).toBe("mention") expect(messagePreview[12].type).toBe("text") expect(messagePreview[13].type).toBe("mention") expect(messagePreview[14].type).toBe("text") - expect(messagePreview[15].type).toBe("channelReference") - expect(messagePreview[16].type).toBe("text") - expect(messagePreview[17].type).toBe("mention") - expect(messagePreview[18].type).toBe("text") - expect(messagePreview[19].type).toBe("mention") + expect(messagePreview[15].type).toBe("mention") expect(messagePreview.map(renderMessagePart).join("")).toBe( - `Hello @Test User #Test Channel pubnub at https://pubnub.com. google at https://google.com, @Test User @blankuser3 #Test Channel, random text @Test User, @Test User.` + `Hello pubnub at https://pubnub.com! Hello to google at https://google.com. Referencing #${channel1.name}, #${channel2.name}, #blankchannel, @${user1.name}, @${user2.name}, and mentioning @blankuser3 @${user4.name} @${user5.name}` ) expect(messageDraft.value).toBe( - `Hello @Test User #Test Channel pubnub at https://pubnub.com. google at https://google.com, @Test User @blankuser3 #Test Channel, random text @Test User, @Test User.` + `Hello pubnub at https://pubnub.com! Hello to google at https://google.com. Referencing ${channel1.name}, ${channel2.name}, #blankchannel, ${user1.name}, ${user2.name}, and mentioning @blankuser3 ${user4.name} ${user5.name}` ) await Promise.all([channel1.delete({ soft: false }), channel2.delete({ soft: false })]) await Promise.all([ @@ -404,42 +163,4 @@ describe("MessageDraft", function () { ]) }) - test("should reference 3 channels and 3 mentions with no order", async () => { - const [channel1, channel2, channel3] = await Promise.all([ - createRandomChannel(), - createRandomChannel(), - createRandomChannel(), - ]) - const [user1, user2, user3] = await Promise.all([ - createRandomUser(), - createRandomUser(), - createRandomUser(), - ]) - - messageDraft.onChange( - `Hello @real #real #fake @fake @real #fake #fake #real @real #fake #real @@@ @@@@ @ #fake #fake` - ) - messageDraft.addReferencedChannel(channel1, 0) - messageDraft.addReferencedChannel(channel2, 4) - messageDraft.addReferencedChannel(channel3, 6) - messageDraft.addMentionedUser(user1, 0) - messageDraft.addMentionedUser(user2, 2) - messageDraft.addMentionedUser(user3, 3) - const messagePreview = messageDraft.getMessagePreview() - - expect(messagePreview.map(renderMessagePart).join("")).toBe( - "Hello @Test User #Test Channel #fake @fake @Test User #fake #fake #Test Channel @Test User #fake #Test Channel @@@ @@@@ @ #fake #fake" - ) - - await Promise.all([ - channel1.delete({ soft: false }), - channel2.delete({ soft: false }), - channel3.delete({ soft: false }), - ]) - await Promise.all([ - user1.delete({ soft: false }), - user2.delete({ soft: false }), - user3.delete({ soft: false }), - ]) - }) }) diff --git a/js-chat/tests/message.test.ts b/js-chat/tests/message.test.ts index 7181a64e..e8141322 100644 --- a/js-chat/tests/message.test.ts +++ b/js-chat/tests/message.test.ts @@ -1037,11 +1037,11 @@ describe("Send message test", () => { const firstThreadMessage = (await thread.getHistory()).messages[0] - const messageDraft = thread.createMessageDraft() + const messageDraft = thread.createMessageDraftV2() messageDraft.addQuote(firstThreadMessage) - await messageDraft.onChange("This is a forwarded message.") + await messageDraft.update("This is a forwarded message.") await messageDraft.send() await sleep(500) diff --git a/js-chat/tests/utils.ts b/js-chat/tests/utils.ts index 561bcbfc..f6b2fcc2 100644 --- a/js-chat/tests/utils.ts +++ b/js-chat/tests/utils.ts @@ -64,16 +64,18 @@ export async function createChatInstance( return chat } -export function createRandomChannel() { - return chat.createChannel(`channel_${makeid()}`, { - name: "Test Channel", +export function createRandomChannel(prefix?: string) { + if (!prefix) prefix = "" + return chat.createChannel(`${prefix}channel_${makeid()}`, { + name: `${prefix}Test Channel`, description: "This is a test channel", }) } -export function createRandomUser() { - return chat.createUser(`user_${makeid()}`, { - name: "Test User", +export function createRandomUser(prefix?: string) { + if (!prefix) prefix = "" + return chat.createUser(`${prefix}user_${makeid()}`, { + name: `${prefix}Test User`, }) } diff --git a/pubnub-chat-impl/src/jsMain/kotlin/MessageDraftV2Js.kt b/pubnub-chat-impl/src/jsMain/kotlin/MessageDraftV2Js.kt index fcbc5fb0..bdca582b 100644 --- a/pubnub-chat-impl/src/jsMain/kotlin/MessageDraftV2Js.kt +++ b/pubnub-chat-impl/src/jsMain/kotlin/MessageDraftV2Js.kt @@ -177,10 +177,10 @@ fun List.toJs() = map { element -> when (element) { is MessageElement.Link -> when (val target = element.target) { is MentionTarget.Channel -> MixedTextTypedElement.ChannelReference( - ChannelReferenceContent(target.channelId, element.text.substring(1)) + ChannelReferenceContent(target.channelId, element.text) ) is MentionTarget.Url -> MixedTextTypedElement.TextLink(TextLinkContent(target.url, element.text)) - is MentionTarget.User -> MixedTextTypedElement.Mention(MentionContent(target.userId, element.text.substring(1))) + is MentionTarget.User -> MixedTextTypedElement.Mention(MentionContent(target.userId, element.text)) } is MessageElement.PlainText -> MixedTextTypedElement.Text(TextContent(element.text)) } From bc3d376d39be1b6fb63d1fd76902ad939c03f0bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Kalici=C5=84ski?= Date: Thu, 12 Dec 2024 18:10:53 +0100 Subject: [PATCH 6/9] Bump pubnub SDK version --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1be46100..35050e02 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ ktlint = "12.1.0" dokka = "1.9.20" kotlinx_serialization = "1.7.3" kotlinx_coroutines = "1.9.0" -pubnub = "10.3.0" +pubnub = "10.3.1" [libraries] pubnub-kotlin-api = { module = "com.pubnub:pubnub-kotlin-api", version.ref = "pubnub" } From c6095bf389a5611c1b254e27448a4737e4b3e00f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Kalici=C5=84ski?= Date: Thu, 12 Dec 2024 18:30:09 +0100 Subject: [PATCH 7/9] Bump Pubnub Swift version --- .../gradle/PubNubKotlinMultiplatformPlugin.kt | 14 +++++++++++--- gradle.properties | 2 +- gradle/libs.versions.toml | 1 + 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/build-logic/gradle-plugins/src/main/kotlin/com/pubnub/gradle/PubNubKotlinMultiplatformPlugin.kt b/build-logic/gradle-plugins/src/main/kotlin/com/pubnub/gradle/PubNubKotlinMultiplatformPlugin.kt index e8e37e3f..8b95d4ed 100644 --- a/build-logic/gradle-plugins/src/main/kotlin/com/pubnub/gradle/PubNubKotlinMultiplatformPlugin.kt +++ b/build-logic/gradle-plugins/src/main/kotlin/com/pubnub/gradle/PubNubKotlinMultiplatformPlugin.kt @@ -2,6 +2,7 @@ package com.pubnub.gradle import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.api.artifacts.VersionCatalogsExtension import org.gradle.api.plugins.ExtensionAware import org.gradle.api.tasks.testing.AbstractTestTask import org.gradle.api.tasks.testing.logging.TestExceptionFormat @@ -38,9 +39,16 @@ class PubNubKotlinMultiplatformPlugin : Plugin { } pod("PubNubSwift") { -// val swiftPath = project.findProperty("SWIFT_PATH") as? String ?: "swift" -// source = path(rootProject.file(swiftPath)) - version = "8.1.0" + val swiftPath = project.findProperty("SWIFT_PATH") as? String + if (swiftPath != null) { + source = path(rootProject.file(swiftPath)) + } else { + version = project.rootProject + .extensions + .getByType(VersionCatalogsExtension::class.java) + .named("libs") + .findVersion("pubnub.swift").get().requiredVersion + } moduleName = "PubNubSDK" extraOpts += listOf("-compiler-option", "-fmodules") } diff --git a/gradle.properties b/gradle.properties index 4af72bc1..6747b9a7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -30,7 +30,7 @@ POM_DEVELOPER_NAME=PubNub POM_DEVELOPER_URL=support@pubnub.com IOS_SIMULATOR_ID=iPhone 15 Pro -SWIFT_PATH=pubnub-kotlin/swift +#SWIFT_PATH=../swift ENABLE_TARGET_JS=true ENABLE_TARGET_IOS_OTHER=false diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 35050e02..8b3b706e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,6 +7,7 @@ dokka = "1.9.20" kotlinx_serialization = "1.7.3" kotlinx_coroutines = "1.9.0" pubnub = "10.3.1" +pubnub_swift = "8.2.2" [libraries] pubnub-kotlin-api = { module = "com.pubnub:pubnub-kotlin-api", version.ref = "pubnub" } From 72fe6565b2aacef930f28222a7cafe88f92f195e Mon Sep 17 00:00:00 2001 From: PubNub Release Bot <120067856+pubnub-release-bot@users.noreply.github.com> Date: Thu, 12 Dec 2024 19:40:56 +0100 Subject: [PATCH 8/9] PubNub kotlin v0.9.2 release. --- .pubnub.yml | 17 ++++++++++++++--- Package.swift | 4 ++-- README.md | 2 +- gradle.properties | 2 +- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/.pubnub.yml b/.pubnub.yml index 3c4eca9f..d7368f5e 100644 --- a/.pubnub.yml +++ b/.pubnub.yml @@ -1,5 +1,5 @@ name: kmp-chat -version: 0.9.0 +version: 0.9.2 schema: 1 scm: github.com/pubnub/kmp-chat sdks: @@ -21,8 +21,8 @@ sdks: - distribution-type: library distribution-repository: maven - package-name: pubnub-chat-0.9.0 - location: https://repo.maven.apache.org/maven2/com/pubnub/pubnub-chat/0.9.0/ + package-name: pubnub-chat-0.9.2 + location: https://repo.maven.apache.org/maven2/com/pubnub/pubnub-chat/0.9.2/ supported-platforms: supported-operating-systems: Android: @@ -77,6 +77,17 @@ sdks: license-url: https://github.com/pubnub/kotlin/blob/master/LICENSE is-required: Required changelog: + - date: 2024-12-12 + version: v0.9.2 + changes: + - type: feature + text: "Lock moderated messages from editing ." + - type: bug + text: "Wrong user suggestion source for message draft created on ThreadChannel." + - type: bug + text: "Wrong type of last user activity time stored on server (precision)." + - type: improvement + text: "Moderation events are now sent to a channel prefixed with `PUBNUB_INTERNAL_MODERATION.`." - date: 2024-11-06 version: v0.9.0 changes: diff --git a/Package.swift b/Package.swift index 32e6b4c4..1de66b19 100644 --- a/Package.swift +++ b/Package.swift @@ -18,8 +18,8 @@ let package = Package( targets: [ .binaryTarget( name: "PubNubChatRemoteBinaryPackage", - url: "https://github.com/pubnub/kmp-chat/releases/download/kotlin-v0.9.0/PubNubChat.xcframework.zip", - checksum: "e043957bd849c7243085368c0e64607cec6dc2e8db6c863f1a80c025d11f6497" + url: "https://github.com/pubnub/kmp-chat/releases/download/kotlin-0.9.2/PubNubChat.xcframework.zip", + checksum: "ba90004881639c1ae7e05cf93d68554a77717f8f7c89515a1894cce03a557122" ) ] ) diff --git a/README.md b/README.md index 58b05b31..281447f1 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ You will need the publish and subscribe keys to authenticate your app. Get your com.pubnub pubnub-chat - 0.9.0 + 0.9.2 ``` diff --git a/gradle.properties b/gradle.properties index 6747b9a7..bedce09e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,7 +10,7 @@ SONATYPE_HOST=DEFAULT SONATYPE_AUTOMATIC_RELEASE=false GROUP=com.pubnub POM_PACKAGING=jar -VERSION_NAME=0.9.1 +VERSION_NAME=0.9.2 POM_NAME=PubNub Chat SDK POM_DESCRIPTION=This SDK offers a set of handy methods to create your own feature-rich chat or add a chat to your existing application. From 184d3819cfee63d46be41b05fd3d2ad5ff17c184 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Kalici=C5=84ski?= Date: Thu, 12 Dec 2024 19:59:35 +0100 Subject: [PATCH 9/9] Update package.swift --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 1de66b19..489dd560 100644 --- a/Package.swift +++ b/Package.swift @@ -18,7 +18,7 @@ let package = Package( targets: [ .binaryTarget( name: "PubNubChatRemoteBinaryPackage", - url: "https://github.com/pubnub/kmp-chat/releases/download/kotlin-0.9.2/PubNubChat.xcframework.zip", + url: "https://github.com/pubnub/kmp-chat/releases/download/kotlin-v0.9.2/PubNubChat.xcframework.zip", checksum: "ba90004881639c1ae7e05cf93d68554a77717f8f7c89515a1894cce03a557122" ) ]