Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[JS] MessageDraftV2 #144

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ kotlin {

compilerOptions {
target.set("es2015")
// moduleKind.set(JsModuleKind.MODULE_UMD)
generateTypeScriptDefinitions()
}
binaries.library()
}
Expand Down
4 changes: 4 additions & 0 deletions js-chat/main.mjs
Original file line number Diff line number Diff line change
@@ -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
432 changes: 432 additions & 0 deletions js-chat/tests/message-draft-v2.test.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -845,7 +845,7 @@ class ChatImpl(

val customMap: Map<String, Any?> = buildMap {
membership.custom?.let { putAll(it) }
put(METADATA_LAST_READ_MESSAGE_TIMETOKEN, relevantLastMessageTimeToken)
put(METADATA_LAST_READ_MESSAGE_TIMETOKEN, relevantLastMessageTimeToken.toString())
}

PNChannelMembership.Partial(
Expand Down
8 changes: 7 additions & 1 deletion pubnub-chat-impl/src/jsMain/kotlin/ChannelJs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -209,7 +210,12 @@ open class ChannelJs internal constructor(internal val channel: Channel, interna
config?.userLimit ?: 10,
config?.channelLimit ?: 10
),
config
createJsObject<MessageDraftConfig> {
this.userSuggestionSource = config?.userSuggestionSource ?: "channel"
this.isTypingIndicatorTriggered = config?.isTypingIndicatorTriggered ?: (channel.type != ChannelType.PUBLIC)
this.userLimit = config?.userLimit ?: 10
this.channelLimit = config?.channelLimit ?: 10
}
)
}

Expand Down
168 changes: 115 additions & 53 deletions pubnub-chat-impl/src/jsMain/kotlin/MessageDraftV2Js.kt
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
@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

@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
Expand All @@ -41,40 +45,8 @@ class MessageDraftV2Js internal constructor(
messageDraft.removeMention(positionOnInput)
}

fun getMessagePreview(): Array<MessageElementJs> {
return messageDraft.getMessageElements().map { element ->
when (element) {
is MessageElement.Link -> when (val target = element.target) {
is MentionTarget.Channel -> createJsObject<MessageElementJs> {
this.type = "channelReference"
this.content = createJsObject<MessageElementPayloadJs.Channel> {
this.name = element.text.substring(1)
this.id = target.channelId
}
}
is MentionTarget.Url -> createJsObject<MessageElementJs> {
this.type = "textLink"
this.content = createJsObject<MessageElementPayloadJs.Link> {
this.text = element.text
this.link = target.url
}
}
is MentionTarget.User -> createJsObject<MessageElementJs> {
this.type = "mention"
this.content = createJsObject<MessageElementPayloadJs.User> {
this.name = element.text.substring(1)
this.id = target.userId
}
}
}
is MessageElement.PlainText -> createJsObject<MessageElementJs> {
this.type = "text"
this.content = createJsObject<MessageElementPayloadJs.Text> {
this.text = element.text
}
}
}
}.toTypedArray()
fun getMessagePreview(): Array<MixedTextTypedElement> {
return messageDraft.getMessageElements().toJs()
}

fun send(options: PubNub.PublishParameters?): Promise<PubNub.PublishResponse> {
Expand All @@ -96,30 +68,120 @@ class MessageDraftV2Js internal constructor(
options?.ttl?.toInt()
).then { it.toPublishResponse() }.asPromise()
}
}

external interface MessageElementJs {
var type: String
var content: MessageElementPayloadJs
}
fun addChangeListener(listener: (MessageDraftState) -> Unit) {
messageDraft.addChangeListener(MessageDraftListenerJs(listener))
}

external interface MessageElementPayloadJs {
interface Text : MessageElementPayloadJs {
var text: String
fun removeChangeListener(listener: (MessageDraftState) -> 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)
}

@JsExport
class MessageDraftState internal constructor(
val messageElements: Array<MixedTextTypedElement>,
suggestedMentionsFuture: PNFuture<List<SuggestedMention>>
) {
val suggestedMentions: Promise<Array<SuggestedMentionJs>> 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<MessageElement>,
suggestedMentions: PNFuture<List<SuggestedMention>>,
) {
listener(
MessageDraftState(
messageElements.toJs(),
suggestedMentions
)
)
}
}

@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<MessageElement>.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()
24 changes: 18 additions & 6 deletions pubnub-chat-impl/src/jsMain/kotlin/MessageJs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -67,13 +68,24 @@ open class MessageJs internal constructor(internal val message: Message, interna
return message.streamUpdates<Message> { it.asJs(chatJs) }::close
}

fun getLinkedText() = getMessageElements()

fun getMessageElements(): Array<MixedTextTypedElement> {
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<MessageJs> {
Expand Down
39 changes: 39 additions & 0 deletions src/jsMain/resources/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,44 @@ 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<PubNub.PublishResponse>;
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;
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 {
private constructor();
get messageElements(): Array<MixedTextTypedElement>;
get suggestedMentions(): Promise<Array<SuggestedMention>>;
}

declare class SuggestedMention {
offset: number;
replaceFrom: string;
replaceWith: string;
type: TextTypes;
target: string;
}

declare class MessageDraft {
private chat;
value: string;
Expand Down Expand Up @@ -527,6 +565,7 @@ declare class Channel {
limit: number;
}): Promise<Membership[]>;
createMessageDraft(config?: Partial<MessageDraftConfig>): MessageDraft;
createMessageDraftV2(config?: Partial<MessageDraftConfig>): MessageDraftV2;
registerForPush(): Promise<void>;
unregisterFromPush(): Promise<void>;
streamReadReceipts(callback: (receipts: {
Expand Down
Loading