Merge branch 'main' into MM-53902-cont
This commit is contained in:
commit
79d7c628e4
381 changed files with 32433 additions and 49498 deletions
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
|
|
@ -1,6 +1,10 @@
|
|||
---
|
||||
name: ci
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- 'release*'
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
{
|
||||
"rule": "cli",
|
||||
"binary": "pod",
|
||||
"semver": "1.12.1",
|
||||
"semver": "1.14.3",
|
||||
"platform": "darwin"
|
||||
}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ apply plugin: 'kotlin-android'
|
|||
// root = file("../")
|
||||
// The folder where the react-native NPM package is. Default is ../node_modules/react-native
|
||||
// reactNativeDir = file("../node_modules/react-native")
|
||||
// The folder where the react-native Codegen package is. Default is ../node_modules/react-native-codegen
|
||||
// codegenDir = file("../node_modules/react-native-codegen")
|
||||
// The folder where the react-native Codegen package is. Default is ../node_modules/@react-native/codegen
|
||||
// codegenDir = file("../node_modules/@react-native/codegen")
|
||||
// The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js
|
||||
// cliFile = file("../node_modules/react-native/cli.js")
|
||||
/* Variants */
|
||||
|
|
@ -98,6 +98,7 @@ def reactNativeArchitectures() {
|
|||
|
||||
android {
|
||||
ndkVersion rootProject.ext.ndkVersion
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
namespace "com.mattermost.rnbeta"
|
||||
|
||||
|
|
@ -110,8 +111,8 @@ android {
|
|||
applicationId "com.mattermost.rnbeta"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 493
|
||||
versionName "2.10.0"
|
||||
versionCode 494
|
||||
versionName "2.11.0"
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
}
|
||||
|
|
@ -190,8 +191,6 @@ dependencies {
|
|||
// The version of react-native is set by the React Native Gradle Plugin
|
||||
implementation("com.facebook.react:react-android")
|
||||
|
||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
||||
|
||||
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}")
|
||||
debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
|
||||
exclude group:'com.squareup.okhttp3', module:'okhttp'
|
||||
|
|
@ -216,7 +215,6 @@ dependencies {
|
|||
|
||||
androidTestImplementation('com.wix:detox:+')
|
||||
implementation project(':reactnativenotifications')
|
||||
implementation project(':watermelondb')
|
||||
implementation project(':watermelondb-jsi')
|
||||
}
|
||||
|
||||
|
|
@ -224,25 +222,22 @@ configurations.all {
|
|||
resolutionStrategy {
|
||||
eachDependency { DependencyResolveDetails details ->
|
||||
if (details.requested.name == 'play-services-base') {
|
||||
details.useTarget group: details.requested.group, name: details.requested.name, version: '18.1.0'
|
||||
details.useTarget group: details.requested.group, name: details.requested.name, version: '18.2.0'
|
||||
}
|
||||
if (details.requested.name == 'play-services-tasks') {
|
||||
details.useTarget group: details.requested.group, name: details.requested.name, version: '18.0.2'
|
||||
}
|
||||
if (details.requested.name == 'play-services-stats') {
|
||||
details.useTarget group: details.requested.group, name: details.requested.name, version: '17.0.3'
|
||||
}
|
||||
if (details.requested.name == 'play-services-basement') {
|
||||
details.useTarget group: details.requested.group, name: details.requested.name, version: '18.1.0'
|
||||
details.useTarget group: details.requested.group, name: details.requested.name, version: '18.2.0'
|
||||
}
|
||||
if (details.requested.name == 'okhttp') {
|
||||
details.useTarget group: details.requested.group, name: details.requested.name, version: '4.10.0'
|
||||
details.useTarget group: details.requested.group, name: details.requested.name, version: '4.12.0'
|
||||
}
|
||||
if (details.requested.name == 'okhttp-tls') {
|
||||
details.useTarget group: details.requested.group, name: details.requested.name, version: '4.10.0'
|
||||
details.useTarget group: details.requested.group, name: details.requested.name, version: '4.12.0'
|
||||
}
|
||||
if (details.requested.name == 'okhttp-urlconnection') {
|
||||
details.useTarget group: details.requested.group, name: details.requested.name, version: '4.10.0'
|
||||
details.useTarget group: details.requested.group, name: details.requested.name, version: '4.12.0'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,10 +7,5 @@
|
|||
<application
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:targetApi="28"
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
<activity
|
||||
android:name="com.facebook.react.devsupport.DevSettingsActivity"
|
||||
android:exported="false"
|
||||
/>
|
||||
</application>
|
||||
tools:ignore="GoogleAppIndexingWarning"/>
|
||||
</manifest>
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ import androidx.core.app.RemoteInput;
|
|||
import androidx.core.graphics.drawable.IconCompat;
|
||||
|
||||
import com.mattermost.rnbeta.*;
|
||||
import com.nozbe.watermelondb.Database;
|
||||
import com.nozbe.watermelondb.WMDatabase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
|
@ -81,7 +81,7 @@ public class CustomPushNotificationHelper {
|
|||
.setKey(senderId)
|
||||
.setName(senderName);
|
||||
|
||||
if (serverUrl != null && !type.equals(CustomPushNotificationHelper.PUSH_TYPE_SESSION)) {
|
||||
if (serverUrl != null && type != null && !type.equals(CustomPushNotificationHelper.PUSH_TYPE_SESSION)) {
|
||||
try {
|
||||
Bitmap avatar = userAvatar(context, serverUrl, senderId, urlOverride);
|
||||
if (avatar != null) {
|
||||
|
|
@ -133,7 +133,7 @@ public class CustomPushNotificationHelper {
|
|||
private static void addNotificationReplyAction(Context context, NotificationCompat.Builder notification, Bundle bundle, int notificationId) {
|
||||
String postId = bundle.getString("post_id");
|
||||
String serverUrl = bundle.getString("server_url");
|
||||
boolean canReply = bundle.containsKey("category") && bundle.getString("category").equals(CATEGORY_CAN_REPLY);
|
||||
boolean canReply = bundle.containsKey("category") && Objects.equals(bundle.getString("category"), CATEGORY_CAN_REPLY);
|
||||
|
||||
if (android.text.TextUtils.isEmpty(postId) || serverUrl == null || !canReply) {
|
||||
return;
|
||||
|
|
@ -183,7 +183,7 @@ public class CustomPushNotificationHelper {
|
|||
String rootId = bundle.getString("root_id");
|
||||
int notificationId = postId != null ? postId.hashCode() : MESSAGE_NOTIFICATION_ID;
|
||||
|
||||
boolean is_crt_enabled = bundle.containsKey("is_crt_enabled") && bundle.getString("is_crt_enabled").equals("true");
|
||||
boolean is_crt_enabled = bundle.containsKey("is_crt_enabled") && Objects.equals(bundle.getString("is_crt_enabled"), "true");
|
||||
String groupId = is_crt_enabled && !android.text.TextUtils.isEmpty(rootId) ? rootId : channelId;
|
||||
|
||||
addNotificationExtras(notification, bundle);
|
||||
|
|
@ -275,7 +275,7 @@ public class CustomPushNotificationHelper {
|
|||
.setKey(senderId)
|
||||
.setName("Me");
|
||||
|
||||
if (serverUrl != null && !type.equals(CustomPushNotificationHelper.PUSH_TYPE_SESSION)) {
|
||||
if (serverUrl != null && type != null && !type.equals(CustomPushNotificationHelper.PUSH_TYPE_SESSION)) {
|
||||
try {
|
||||
Bitmap avatar = userAvatar(context, serverUrl, "me", urlOverride);
|
||||
if (avatar != null) {
|
||||
|
|
@ -418,7 +418,7 @@ public class CustomPushNotificationHelper {
|
|||
} else {
|
||||
DatabaseHelper dbHelper = DatabaseHelper.Companion.getInstance();
|
||||
if (dbHelper != null) {
|
||||
Database db = getDatabaseForServer(dbHelper, context, serverUrl);
|
||||
WMDatabase db = getDatabaseForServer(dbHelper, context, serverUrl);
|
||||
if (db != null) {
|
||||
lastUpdateAt = getLastPictureUpdate(db, userId);
|
||||
if (lastUpdateAt == null) {
|
||||
|
|
|
|||
|
|
@ -1,17 +1,21 @@
|
|||
package com.mattermost.helpers
|
||||
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import com.facebook.react.bridge.WritableMap
|
||||
|
||||
import com.nozbe.watermelondb.Database
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
|
||||
import java.lang.Exception
|
||||
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
|
||||
typealias QueryArgs = Array<Any?>
|
||||
|
||||
class DatabaseHelper {
|
||||
var defaultDatabase: Database? = null
|
||||
var defaultDatabase: WMDatabase? = null
|
||||
|
||||
val onlyServerUrl: String?
|
||||
get() {
|
||||
|
|
@ -39,7 +43,7 @@ class DatabaseHelper {
|
|||
private fun setDefaultDatabase(context: Context) {
|
||||
val databaseName = "app.db"
|
||||
val databasePath = Uri.fromFile(context.filesDir).toString() + "/" + databaseName
|
||||
defaultDatabase = Database.getInstance(databasePath, context)
|
||||
defaultDatabase = WMDatabase.getInstance(databasePath, context)
|
||||
}
|
||||
|
||||
internal fun JSONObject.toMap(): Map<String, Any?> = keys().asSequence().associateWith { it ->
|
||||
|
|
@ -73,3 +77,15 @@ class DatabaseHelper {
|
|||
private set
|
||||
}
|
||||
}
|
||||
|
||||
fun WritableMap.mapCursor(cursor: Cursor) {
|
||||
for (i in 0 until cursor.columnCount) {
|
||||
when (cursor.getType(i)) {
|
||||
Cursor.FIELD_TYPE_NULL -> putNull(cursor.getColumnName(i))
|
||||
Cursor.FIELD_TYPE_INTEGER -> putDouble(cursor.getColumnName(i), cursor.getDouble(i))
|
||||
Cursor.FIELD_TYPE_FLOAT -> putDouble(cursor.getColumnName(i), cursor.getDouble(i))
|
||||
Cursor.FIELD_TYPE_STRING -> putString(cursor.getColumnName(i), cursor.getString(i))
|
||||
else -> putString(cursor.getColumnName(i), "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ package com.mattermost.helpers.database_extension
|
|||
|
||||
import com.facebook.react.bridge.ReadableArray
|
||||
import com.facebook.react.bridge.ReadableMap
|
||||
import com.nozbe.watermelondb.Database
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
|
||||
fun insertCategory(db: Database, category: ReadableMap) {
|
||||
fun insertCategory(db: WMDatabase, category: ReadableMap) {
|
||||
try {
|
||||
val id = category.getString("id") ?: return
|
||||
val collapsed = false
|
||||
|
|
@ -31,7 +31,7 @@ fun insertCategory(db: Database, category: ReadableMap) {
|
|||
}
|
||||
}
|
||||
|
||||
fun insertCategoryChannels(db: Database, categoryId: String, teamId: String, channelIds: ReadableArray) {
|
||||
fun insertCategoryChannels(db: WMDatabase, categoryId: String, teamId: String, channelIds: ReadableArray) {
|
||||
try {
|
||||
for (i in 0 until channelIds.size()) {
|
||||
val channelId = channelIds.getString(i)
|
||||
|
|
@ -50,7 +50,7 @@ fun insertCategoryChannels(db: Database, categoryId: String, teamId: String, cha
|
|||
}
|
||||
}
|
||||
|
||||
fun insertCategoriesWithChannels(db: Database, orderCategories: ReadableMap) {
|
||||
fun insertCategoriesWithChannels(db: WMDatabase, orderCategories: ReadableMap) {
|
||||
val categories = orderCategories.getArray("categories") ?: return
|
||||
for (i in 0 until categories.size()) {
|
||||
val category = categories.getMap(i)
|
||||
|
|
@ -64,7 +64,7 @@ fun insertCategoriesWithChannels(db: Database, orderCategories: ReadableMap) {
|
|||
}
|
||||
}
|
||||
|
||||
fun insertChannelToDefaultCategory(db: Database, categoryChannels: ReadableArray) {
|
||||
fun insertChannelToDefaultCategory(db: WMDatabase, categoryChannels: ReadableArray) {
|
||||
try {
|
||||
for (i in 0 until categoryChannels.size()) {
|
||||
val cc = categoryChannels.getMap(i)
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@ package com.mattermost.helpers.database_extension
|
|||
import com.facebook.react.bridge.ReadableMap
|
||||
import com.mattermost.helpers.DatabaseHelper
|
||||
import com.mattermost.helpers.ReadableMapUtils
|
||||
import com.nozbe.watermelondb.Database
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
|
||||
fun findChannel(db: Database?, channelId: String): Boolean {
|
||||
fun findChannel(db: WMDatabase?, channelId: String): Boolean {
|
||||
if (db != null) {
|
||||
val team = find(db, "Channel", channelId)
|
||||
return team != null
|
||||
|
|
@ -15,7 +15,7 @@ fun findChannel(db: Database?, channelId: String): Boolean {
|
|||
return false
|
||||
}
|
||||
|
||||
fun findMyChannel(db: Database?, channelId: String): Boolean {
|
||||
fun findMyChannel(db: WMDatabase?, channelId: String): Boolean {
|
||||
if (db != null) {
|
||||
val team = find(db, "MyChannel", channelId)
|
||||
return team != null
|
||||
|
|
@ -23,7 +23,7 @@ fun findMyChannel(db: Database?, channelId: String): Boolean {
|
|||
return false
|
||||
}
|
||||
|
||||
internal fun handleChannel(db: Database, channel: ReadableMap) {
|
||||
internal fun handleChannel(db: WMDatabase, channel: ReadableMap) {
|
||||
try {
|
||||
val exists = channel.getString("id")?.let { findChannel(db, it) } ?: false
|
||||
if (!exists) {
|
||||
|
|
@ -37,7 +37,7 @@ internal fun handleChannel(db: Database, channel: ReadableMap) {
|
|||
}
|
||||
}
|
||||
|
||||
internal fun DatabaseHelper.handleMyChannel(db: Database, myChannel: ReadableMap, postsData: ReadableMap?, receivingThreads: Boolean) {
|
||||
internal fun DatabaseHelper.handleMyChannel(db: WMDatabase, myChannel: ReadableMap, postsData: ReadableMap?, receivingThreads: Boolean) {
|
||||
try {
|
||||
val json = ReadableMapUtils.toJSONObject(myChannel)
|
||||
val exists = myChannel.getString("id")?.let { findMyChannel(db, it) } ?: false
|
||||
|
|
@ -71,7 +71,7 @@ internal fun DatabaseHelper.handleMyChannel(db: Database, myChannel: ReadableMap
|
|||
}
|
||||
}
|
||||
|
||||
fun insertChannel(db: Database, channel: JSONObject): Boolean {
|
||||
fun insertChannel(db: WMDatabase, channel: JSONObject): Boolean {
|
||||
val id = try { channel.getString("id") } catch (e: JSONException) { return false }
|
||||
val createAt = try { channel.getDouble("create_at") } catch (e: JSONException) { 0 }
|
||||
val deleteAt = try { channel.getDouble("delete_at") } catch (e: JSONException) { 0 }
|
||||
|
|
@ -104,7 +104,7 @@ fun insertChannel(db: Database, channel: JSONObject): Boolean {
|
|||
}
|
||||
}
|
||||
|
||||
fun insertChannelInfo(db: Database, channel: JSONObject) {
|
||||
fun insertChannelInfo(db: WMDatabase, channel: JSONObject) {
|
||||
val id = try { channel.getString("id") } catch (e: JSONException) { return }
|
||||
val header = try { channel.getString("header") } catch (e: JSONException) { "" }
|
||||
val purpose = try { channel.getString("purpose") } catch (e: JSONException) { "" }
|
||||
|
|
@ -123,7 +123,7 @@ fun insertChannelInfo(db: Database, channel: JSONObject) {
|
|||
}
|
||||
}
|
||||
|
||||
fun insertMyChannel(db: Database, myChanel: JSONObject): Boolean {
|
||||
fun insertMyChannel(db: WMDatabase, myChanel: JSONObject): Boolean {
|
||||
return try {
|
||||
val id = try { myChanel.getString("id") } catch (e: JSONException) { return false }
|
||||
val roles = try { myChanel.getString("roles") } catch (e: JSONException) { "" }
|
||||
|
|
@ -156,7 +156,7 @@ fun insertMyChannel(db: Database, myChanel: JSONObject): Boolean {
|
|||
}
|
||||
}
|
||||
|
||||
fun insertMyChannelSettings(db: Database, myChanel: JSONObject) {
|
||||
fun insertMyChannelSettings(db: WMDatabase, myChanel: JSONObject) {
|
||||
try {
|
||||
val id = try { myChanel.getString("id") } catch (e: JSONException) { return }
|
||||
val notifyProps = try { myChanel.getString("notify_props") } catch (e: JSONException) { return }
|
||||
|
|
@ -173,7 +173,7 @@ fun insertMyChannelSettings(db: Database, myChanel: JSONObject) {
|
|||
}
|
||||
}
|
||||
|
||||
fun insertChannelMember(db: Database, myChanel: JSONObject) {
|
||||
fun insertChannelMember(db: WMDatabase, myChanel: JSONObject) {
|
||||
try {
|
||||
val userId = queryCurrentUserId(db) ?: return
|
||||
val channelId = try { myChanel.getString("id") } catch (e: JSONException) { return }
|
||||
|
|
@ -193,7 +193,7 @@ fun insertChannelMember(db: Database, myChanel: JSONObject) {
|
|||
}
|
||||
}
|
||||
|
||||
fun updateMyChannel(db: Database, myChanel: JSONObject) {
|
||||
fun updateMyChannel(db: WMDatabase, myChanel: JSONObject) {
|
||||
try {
|
||||
val id = try { myChanel.getString("id") } catch (e: JSONException) { return }
|
||||
val msgCount = try { myChanel.getInt("message_count") } catch (e: JSONException) { 0 }
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
package com.mattermost.helpers.database_extension
|
||||
|
||||
import com.nozbe.watermelondb.Database
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
import org.json.JSONArray
|
||||
|
||||
internal fun insertCustomEmojis(db: Database, customEmojis: JSONArray) {
|
||||
internal fun insertCustomEmojis(db: WMDatabase, customEmojis: JSONArray) {
|
||||
for (i in 0 until customEmojis.length()) {
|
||||
try {
|
||||
val emoji = customEmojis.getJSONObject(i)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
package com.mattermost.helpers.database_extension
|
||||
|
||||
import com.nozbe.watermelondb.Database
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONException
|
||||
|
||||
internal fun insertFiles(db: Database, files: JSONArray) {
|
||||
internal fun insertFiles(db: WMDatabase, files: JSONArray) {
|
||||
try {
|
||||
for (i in 0 until files.length()) {
|
||||
val file = files.getJSONObject(i)
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@ import android.text.TextUtils
|
|||
import com.facebook.react.bridge.Arguments
|
||||
import com.facebook.react.bridge.ReadableMap
|
||||
import com.mattermost.helpers.DatabaseHelper
|
||||
import com.nozbe.watermelondb.Database
|
||||
import com.nozbe.watermelondb.QueryArgs
|
||||
import com.nozbe.watermelondb.mapCursor
|
||||
import com.mattermost.helpers.QueryArgs
|
||||
import com.mattermost.helpers.mapCursor
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
import java.util.*
|
||||
import kotlin.Exception
|
||||
|
||||
internal fun DatabaseHelper.saveToDatabase(db: Database, data: ReadableMap, teamId: String?, channelId: String?, receivingThreads: Boolean) {
|
||||
internal fun DatabaseHelper.saveToDatabase(db: WMDatabase, data: ReadableMap, teamId: String?, channelId: String?, receivingThreads: Boolean) {
|
||||
db.transaction {
|
||||
val posts = data.getMap("posts")
|
||||
data.getMap("team")?.let { insertTeam(db, it) }
|
||||
|
|
@ -50,14 +50,14 @@ fun DatabaseHelper.getServerUrlForIdentifier(identifier: String): String? {
|
|||
return null
|
||||
}
|
||||
|
||||
fun DatabaseHelper.getDatabaseForServer(context: Context?, serverUrl: String): Database? {
|
||||
fun DatabaseHelper.getDatabaseForServer(context: Context?, serverUrl: String): WMDatabase? {
|
||||
try {
|
||||
val query = "SELECT db_path FROM Servers WHERE url=?"
|
||||
defaultDatabase!!.rawQuery(query, arrayOf(serverUrl)).use { cursor ->
|
||||
if (cursor.count == 1) {
|
||||
cursor.moveToFirst()
|
||||
val databasePath = cursor.getString(0)
|
||||
return Database.getInstance(databasePath, context!!)
|
||||
return WMDatabase.getInstance(databasePath, context!!)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
|
|
@ -67,7 +67,7 @@ fun DatabaseHelper.getDatabaseForServer(context: Context?, serverUrl: String): D
|
|||
return null
|
||||
}
|
||||
|
||||
fun find(db: Database, tableName: String, id: String?): ReadableMap? {
|
||||
fun find(db: WMDatabase, tableName: String, id: String?): ReadableMap? {
|
||||
try {
|
||||
db.rawQuery(
|
||||
"SELECT * FROM $tableName WHERE id == ? LIMIT 1",
|
||||
|
|
@ -87,7 +87,7 @@ fun find(db: Database, tableName: String, id: String?): ReadableMap? {
|
|||
}
|
||||
}
|
||||
|
||||
fun findByColumns(db: Database, tableName: String, columnNames: Array<String>, values: QueryArgs): ReadableMap? {
|
||||
fun findByColumns(db: WMDatabase, tableName: String, columnNames: Array<String>, values: QueryArgs): ReadableMap? {
|
||||
try {
|
||||
val whereString = columnNames.joinToString(" AND ") { "$it = ?" }
|
||||
db.rawQuery(
|
||||
|
|
@ -108,7 +108,7 @@ fun findByColumns(db: Database, tableName: String, columnNames: Array<String>, v
|
|||
}
|
||||
}
|
||||
|
||||
fun queryIds(db: Database, tableName: String, ids: Array<String>): List<String> {
|
||||
fun queryIds(db: WMDatabase, tableName: String, ids: Array<String>): List<String> {
|
||||
val list: MutableList<String> = ArrayList()
|
||||
val args = TextUtils.join(",", Arrays.stream(ids).map { "?" }.toArray())
|
||||
try {
|
||||
|
|
@ -129,7 +129,7 @@ fun queryIds(db: Database, tableName: String, ids: Array<String>): List<String>
|
|||
return list
|
||||
}
|
||||
|
||||
fun queryByColumn(db: Database, tableName: String, columnName: String, values: Array<Any?>): List<String> {
|
||||
fun queryByColumn(db: WMDatabase, tableName: String, columnName: String, values: Array<Any?>): List<String> {
|
||||
val list: MutableList<String> = ArrayList()
|
||||
val args = TextUtils.join(",", Arrays.stream(values).map { "?" }.toArray())
|
||||
try {
|
||||
|
|
@ -149,7 +149,7 @@ fun queryByColumn(db: Database, tableName: String, columnName: String, values: A
|
|||
return list
|
||||
}
|
||||
|
||||
fun countByColumn(db: Database, tableName: String, columnName: String, value: Any?): Int {
|
||||
fun countByColumn(db: WMDatabase, tableName: String, columnName: String, value: Any?): Int {
|
||||
try {
|
||||
db.rawQuery(
|
||||
"SELECT COUNT(*) FROM $tableName WHERE $columnName == ? LIMIT 1",
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@ package com.mattermost.helpers.database_extension
|
|||
import com.facebook.react.bridge.ReadableMap
|
||||
import com.mattermost.helpers.DatabaseHelper
|
||||
import com.mattermost.helpers.ReadableMapUtils
|
||||
import com.nozbe.watermelondb.Database
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import kotlin.Exception
|
||||
|
||||
internal fun queryLastPostCreateAt(db: Database?, channelId: String): Double? {
|
||||
internal fun queryLastPostCreateAt(db: WMDatabase?, channelId: String): Double? {
|
||||
try {
|
||||
if (db != null) {
|
||||
val postsInChannelQuery = "SELECT earliest, latest FROM PostsInChannel WHERE channel_id=? ORDER BY latest DESC LIMIT 1"
|
||||
|
|
@ -35,7 +35,7 @@ internal fun queryLastPostCreateAt(db: Database?, channelId: String): Double? {
|
|||
return null
|
||||
}
|
||||
|
||||
fun queryPostSinceForChannel(db: Database?, channelId: String): Double? {
|
||||
fun queryPostSinceForChannel(db: WMDatabase?, channelId: String): Double? {
|
||||
try {
|
||||
if (db != null) {
|
||||
val postsInChannelQuery = "SELECT last_fetched_at FROM MyChannel WHERE id=? LIMIT 1"
|
||||
|
|
@ -57,7 +57,7 @@ fun queryPostSinceForChannel(db: Database?, channelId: String): Double? {
|
|||
return null
|
||||
}
|
||||
|
||||
fun queryLastPostInThread(db: Database?, rootId: String): Double? {
|
||||
fun queryLastPostInThread(db: WMDatabase?, rootId: String): Double? {
|
||||
try {
|
||||
if (db != null) {
|
||||
val query = "SELECT create_at FROM Post WHERE root_id=? AND delete_at=0 ORDER BY create_at DESC LIMIT 1"
|
||||
|
|
@ -75,7 +75,7 @@ fun queryLastPostInThread(db: Database?, rootId: String): Double? {
|
|||
return null
|
||||
}
|
||||
|
||||
internal fun insertPost(db: Database, post: JSONObject) {
|
||||
internal fun insertPost(db: WMDatabase, post: JSONObject) {
|
||||
try {
|
||||
val id = try { post.getString("id") } catch (e: JSONException) { return }
|
||||
val channelId = try { post.getString("channel_id") } catch (e: JSONException) { return }
|
||||
|
|
@ -129,7 +129,7 @@ internal fun insertPost(db: Database, post: JSONObject) {
|
|||
}
|
||||
}
|
||||
|
||||
internal fun updatePost(db: Database, post: JSONObject) {
|
||||
internal fun updatePost(db: WMDatabase, post: JSONObject) {
|
||||
try {
|
||||
val id = try { post.getString("id") } catch (e: JSONException) { return }
|
||||
val channelId = try { post.getString("channel_id") } catch (e: JSONException) { return }
|
||||
|
|
@ -182,7 +182,7 @@ internal fun updatePost(db: Database, post: JSONObject) {
|
|||
}
|
||||
}
|
||||
|
||||
fun DatabaseHelper.handlePosts(db: Database, postsData: ReadableMap?, channelId: String, receivingThreads: Boolean) {
|
||||
fun DatabaseHelper.handlePosts(db: WMDatabase, postsData: ReadableMap?, channelId: String, receivingThreads: Boolean) {
|
||||
// Posts, PostInChannel, PostInThread, Reactions, Files, CustomEmojis, Users
|
||||
try {
|
||||
if (postsData != null) {
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import com.facebook.react.bridge.Arguments
|
|||
import com.facebook.react.bridge.ReadableArray
|
||||
import com.facebook.react.bridge.ReadableMap
|
||||
import com.mattermost.helpers.RandomId
|
||||
import com.nozbe.watermelondb.Database
|
||||
import com.nozbe.watermelondb.mapCursor
|
||||
import com.mattermost.helpers.mapCursor
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
|
||||
internal fun findPostInChannel(chunks: ReadableArray, earliest: Double, latest: Double): ReadableMap? {
|
||||
for (i in 0 until chunks.size()) {
|
||||
|
|
@ -18,7 +18,7 @@ internal fun findPostInChannel(chunks: ReadableArray, earliest: Double, latest:
|
|||
return null
|
||||
}
|
||||
|
||||
internal fun insertPostInChannel(db: Database, channelId: String, earliest: Double, latest: Double): ReadableMap? {
|
||||
internal fun insertPostInChannel(db: WMDatabase, channelId: String, earliest: Double, latest: Double): ReadableMap? {
|
||||
return try {
|
||||
val id = RandomId.generate()
|
||||
db.execute(
|
||||
|
|
@ -41,7 +41,7 @@ internal fun insertPostInChannel(db: Database, channelId: String, earliest: Doub
|
|||
}
|
||||
}
|
||||
|
||||
internal fun mergePostsInChannel(db: Database, existingChunks: ReadableArray, newChunk: ReadableMap) {
|
||||
internal fun mergePostsInChannel(db: WMDatabase, existingChunks: ReadableArray, newChunk: ReadableMap) {
|
||||
for (i in 0 until existingChunks.size()) {
|
||||
try {
|
||||
val chunk = existingChunks.getMap(i)
|
||||
|
|
@ -56,7 +56,7 @@ internal fun mergePostsInChannel(db: Database, existingChunks: ReadableArray, ne
|
|||
}
|
||||
}
|
||||
|
||||
internal fun handlePostsInChannel(db: Database, channelId: String, earliest: Double, latest: Double) {
|
||||
internal fun handlePostsInChannel(db: WMDatabase, channelId: String, earliest: Double, latest: Double) {
|
||||
try {
|
||||
db.rawQuery(
|
||||
"SELECT id, channel_id, earliest, latest FROM PostsInChannel WHERE channel_id = ?",
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
package com.mattermost.helpers.database_extension
|
||||
|
||||
import com.facebook.react.bridge.Arguments
|
||||
import com.nozbe.watermelondb.Database
|
||||
import com.nozbe.watermelondb.mapCursor
|
||||
import com.mattermost.helpers.mapCursor
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
|
||||
fun getTeammateDisplayNameSetting(db: Database): String {
|
||||
fun getTeammateDisplayNameSetting(db: WMDatabase): String {
|
||||
val configSetting = queryConfigDisplayNameSetting(db)
|
||||
if (configSetting != null) {
|
||||
return configSetting
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
package com.mattermost.helpers.database_extension
|
||||
|
||||
import com.mattermost.helpers.RandomId
|
||||
import com.nozbe.watermelondb.Database
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
import org.json.JSONArray
|
||||
|
||||
internal fun insertReactions(db: Database, reactions: JSONArray) {
|
||||
internal fun insertReactions(db: WMDatabase, reactions: JSONArray) {
|
||||
for (i in 0 until reactions.length()) {
|
||||
try {
|
||||
val reaction = reactions.getJSONObject(i)
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
package com.mattermost.helpers.database_extension
|
||||
|
||||
import com.nozbe.watermelondb.Database
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
import org.json.JSONObject
|
||||
|
||||
fun queryCurrentUserId(db: Database): String? {
|
||||
fun queryCurrentUserId(db: WMDatabase): String? {
|
||||
val result = find(db, "System", "currentUserId")
|
||||
return result?.getString("value")?.removeSurrounding("\"")
|
||||
}
|
||||
|
||||
fun queryCurrentTeamId(db: Database): String? {
|
||||
fun queryCurrentTeamId(db: WMDatabase): String? {
|
||||
val result = find(db, "System", "currentTeamId")
|
||||
return result?.getString("value")?.removeSurrounding("\"")
|
||||
}
|
||||
|
||||
fun queryConfigDisplayNameSetting(db: Database): String? {
|
||||
fun queryConfigDisplayNameSetting(db: WMDatabase): String? {
|
||||
val license = find(db, "System", "license")
|
||||
val lockDisplayName = find(db, "Config", "LockTeammateNameDisplay")
|
||||
val displayName = find(db, "Config", "TeammateNameDisplay")
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ package com.mattermost.helpers.database_extension
|
|||
import com.facebook.react.bridge.Arguments
|
||||
import com.facebook.react.bridge.NoSuchKeyException
|
||||
import com.facebook.react.bridge.ReadableMap
|
||||
import com.nozbe.watermelondb.Database
|
||||
import com.nozbe.watermelondb.mapCursor
|
||||
import com.mattermost.helpers.mapCursor
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
|
||||
fun findTeam(db: Database?, teamId: String): Boolean {
|
||||
fun findTeam(db: WMDatabase?, teamId: String): Boolean {
|
||||
if (db != null) {
|
||||
val team = find(db, "Team", teamId)
|
||||
return team != null
|
||||
|
|
@ -14,7 +14,7 @@ fun findTeam(db: Database?, teamId: String): Boolean {
|
|||
return false
|
||||
}
|
||||
|
||||
fun findMyTeam(db: Database?, teamId: String): Boolean {
|
||||
fun findMyTeam(db: WMDatabase?, teamId: String): Boolean {
|
||||
if (db != null) {
|
||||
val team = find(db, "MyTeam", teamId)
|
||||
return team != null
|
||||
|
|
@ -22,7 +22,7 @@ fun findMyTeam(db: Database?, teamId: String): Boolean {
|
|||
return false
|
||||
}
|
||||
|
||||
fun queryMyTeams(db: Database?): ArrayList<ReadableMap>? {
|
||||
fun queryMyTeams(db: WMDatabase?): ArrayList<ReadableMap>? {
|
||||
db?.rawQuery("SELECT * FROM MyTeam")?.use { cursor ->
|
||||
val results = ArrayList<ReadableMap>()
|
||||
if (cursor.count > 0) {
|
||||
|
|
@ -38,7 +38,7 @@ fun queryMyTeams(db: Database?): ArrayList<ReadableMap>? {
|
|||
return null
|
||||
}
|
||||
|
||||
fun insertTeam(db: Database, team: ReadableMap): Boolean {
|
||||
fun insertTeam(db: WMDatabase, team: ReadableMap): Boolean {
|
||||
val id = try { team.getString("id") } catch (e: Exception) { return false }
|
||||
val deleteAt = try {team.getDouble("delete_at") } catch (e: Exception) { 0 }
|
||||
if (deleteAt.toInt() > 0) {
|
||||
|
|
@ -78,7 +78,7 @@ fun insertTeam(db: Database, team: ReadableMap): Boolean {
|
|||
}
|
||||
}
|
||||
|
||||
fun insertMyTeam(db: Database, myTeam: ReadableMap): Boolean {
|
||||
fun insertMyTeam(db: WMDatabase, myTeam: ReadableMap): Boolean {
|
||||
val currentUserId = queryCurrentUserId(db) ?: return false
|
||||
val id = try { myTeam.getString("id") } catch (e: NoSuchKeyException) { return false }
|
||||
val roles = try { myTeam.getString("roles") } catch (e: NoSuchKeyException) { "" }
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@ import com.facebook.react.bridge.NoSuchKeyException
|
|||
import com.facebook.react.bridge.ReadableArray
|
||||
import com.facebook.react.bridge.ReadableMap
|
||||
import com.mattermost.helpers.RandomId
|
||||
import com.nozbe.watermelondb.Database
|
||||
import com.nozbe.watermelondb.mapCursor
|
||||
import com.mattermost.helpers.mapCursor
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
import org.json.JSONObject
|
||||
|
||||
internal fun insertThread(db: Database, thread: ReadableMap) {
|
||||
internal fun insertThread(db: WMDatabase, thread: ReadableMap) {
|
||||
// These fields are not present when we extract threads from posts
|
||||
try {
|
||||
val id = try { thread.getString("id") } catch (e: NoSuchKeyException) { return }
|
||||
|
|
@ -36,7 +36,7 @@ internal fun insertThread(db: Database, thread: ReadableMap) {
|
|||
}
|
||||
}
|
||||
|
||||
internal fun updateThread(db: Database, thread: ReadableMap, existingRecord: ReadableMap) {
|
||||
internal fun updateThread(db: WMDatabase, thread: ReadableMap, existingRecord: ReadableMap) {
|
||||
try {
|
||||
// These fields are not present when we extract threads from posts
|
||||
val id = try { thread.getString("id") } catch (e: NoSuchKeyException) { return }
|
||||
|
|
@ -63,7 +63,7 @@ internal fun updateThread(db: Database, thread: ReadableMap, existingRecord: Rea
|
|||
}
|
||||
}
|
||||
|
||||
internal fun insertThreadParticipants(db: Database, threadId: String, participants: ReadableArray) {
|
||||
internal fun insertThreadParticipants(db: WMDatabase, threadId: String, participants: ReadableArray) {
|
||||
for (i in 0 until participants.size()) {
|
||||
try {
|
||||
val participant = participants.getMap(i)
|
||||
|
|
@ -82,7 +82,7 @@ internal fun insertThreadParticipants(db: Database, threadId: String, participan
|
|||
}
|
||||
}
|
||||
|
||||
fun insertTeamThreadsSync(db: Database, teamId: String, earliest: Double, latest: Double) {
|
||||
fun insertTeamThreadsSync(db: WMDatabase, teamId: String, earliest: Double, latest: Double) {
|
||||
try {
|
||||
val query = """
|
||||
INSERT INTO TeamThreadsSync (id, _changed, _status, earliest, latest)
|
||||
|
|
@ -94,7 +94,7 @@ fun insertTeamThreadsSync(db: Database, teamId: String, earliest: Double, latest
|
|||
}
|
||||
}
|
||||
|
||||
fun updateTeamThreadsSync(db: Database, teamId: String, earliest: Double, latest: Double, existingRecord: ReadableMap) {
|
||||
fun updateTeamThreadsSync(db: WMDatabase, teamId: String, earliest: Double, latest: Double, existingRecord: ReadableMap) {
|
||||
try {
|
||||
val storeEarliest = minOf(earliest, existingRecord.getDouble("earliest"))
|
||||
val storeLatest = maxOf(latest, existingRecord.getDouble("latest"))
|
||||
|
|
@ -105,7 +105,7 @@ fun updateTeamThreadsSync(db: Database, teamId: String, earliest: Double, latest
|
|||
}
|
||||
}
|
||||
|
||||
fun syncParticipants(db: Database, thread: ReadableMap) {
|
||||
fun syncParticipants(db: WMDatabase, thread: ReadableMap) {
|
||||
try {
|
||||
val threadId = thread.getString("id")
|
||||
val participants = thread.getArray("participants")
|
||||
|
|
@ -121,7 +121,7 @@ fun syncParticipants(db: Database, thread: ReadableMap) {
|
|||
}
|
||||
}
|
||||
|
||||
internal fun handlePostsInThread(db: Database, postsInThread: Map<String, List<JSONObject>>) {
|
||||
internal fun handlePostsInThread(db: WMDatabase, postsInThread: Map<String, List<JSONObject>>) {
|
||||
postsInThread.forEach { (key, list) ->
|
||||
try {
|
||||
val sorted = list.sortedBy { it.getDouble("create_at") }
|
||||
|
|
@ -161,7 +161,7 @@ internal fun handlePostsInThread(db: Database, postsInThread: Map<String, List<J
|
|||
}
|
||||
}
|
||||
|
||||
fun handleThreads(db: Database, threads: ArrayList<ReadableMap>, teamId: String?) {
|
||||
fun handleThreads(db: WMDatabase, threads: ArrayList<ReadableMap>, teamId: String?) {
|
||||
val teamIds = ArrayList<String>()
|
||||
if (teamId.isNullOrEmpty()) {
|
||||
val myTeams = queryMyTeams(db)
|
||||
|
|
@ -186,7 +186,7 @@ fun handleThreads(db: Database, threads: ArrayList<ReadableMap>, teamId: String?
|
|||
handleTeamThreadsSync(db, threads, teamIds)
|
||||
}
|
||||
|
||||
fun handleThread(db: Database, thread: ReadableMap, teamIds: ArrayList<String>) {
|
||||
fun handleThread(db: WMDatabase, thread: ReadableMap, teamIds: ArrayList<String>) {
|
||||
// Insert/Update the thread
|
||||
val threadId = thread.getString("id")
|
||||
val isFollowing = thread.getBoolean("is_following")
|
||||
|
|
@ -207,7 +207,7 @@ fun handleThread(db: Database, thread: ReadableMap, teamIds: ArrayList<String>)
|
|||
}
|
||||
}
|
||||
|
||||
fun handleThreadInTeam(db: Database, thread: ReadableMap, teamId: String) {
|
||||
fun handleThreadInTeam(db: WMDatabase, thread: ReadableMap, teamId: String) {
|
||||
val threadId = thread.getString("id") ?: return
|
||||
val existingRecord = findByColumns(
|
||||
db,
|
||||
|
|
@ -229,7 +229,7 @@ fun handleThreadInTeam(db: Database, thread: ReadableMap, teamId: String) {
|
|||
}
|
||||
}
|
||||
|
||||
fun handleTeamThreadsSync(db: Database, threadList: ArrayList<ReadableMap>, teamIds: ArrayList<String>) {
|
||||
fun handleTeamThreadsSync(db: WMDatabase, threadList: ArrayList<ReadableMap>, teamIds: ArrayList<String>) {
|
||||
val sortedList = threadList.filter{ it.getBoolean("is_following") }
|
||||
.sortedBy { it.getDouble("last_reply_at") }
|
||||
.map { it.getDouble("last_reply_at") }
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ import com.facebook.react.bridge.Arguments
|
|||
import com.facebook.react.bridge.NoSuchKeyException
|
||||
import com.facebook.react.bridge.ReadableArray
|
||||
import com.mattermost.helpers.ReadableMapUtils
|
||||
import com.nozbe.watermelondb.Database
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
|
||||
fun getLastPictureUpdate(db: Database?, userId: String): Double? {
|
||||
fun getLastPictureUpdate(db: WMDatabase?, userId: String): Double? {
|
||||
try {
|
||||
if (db != null) {
|
||||
var id = userId
|
||||
|
|
@ -28,7 +28,7 @@ fun getLastPictureUpdate(db: Database?, userId: String): Double? {
|
|||
return null
|
||||
}
|
||||
|
||||
fun getCurrentUserLocale(db: Database): String {
|
||||
fun getCurrentUserLocale(db: WMDatabase): String {
|
||||
try {
|
||||
val currentUserId = queryCurrentUserId(db) ?: return "en"
|
||||
val userQuery = "SELECT locale FROM User WHERE id=?"
|
||||
|
|
@ -45,7 +45,7 @@ fun getCurrentUserLocale(db: Database): String {
|
|||
return "en"
|
||||
}
|
||||
|
||||
fun handleUsers(db: Database, users: ReadableArray) {
|
||||
fun handleUsers(db: WMDatabase, users: ReadableArray) {
|
||||
for (i in 0 until users.size()) {
|
||||
val user = users.getMap(i)
|
||||
val roles = user.getString("roles") ?: ""
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ import com.mattermost.helpers.PushNotificationDataRunnable
|
|||
import com.mattermost.helpers.database_extension.findByColumns
|
||||
import com.mattermost.helpers.database_extension.queryCurrentUserId
|
||||
import com.mattermost.helpers.database_extension.queryMyTeams
|
||||
import com.nozbe.watermelondb.Database
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
|
||||
suspend fun PushNotificationDataRunnable.Companion.fetchMyTeamCategories(db: Database, serverUrl: String, teamId: String): ReadableMap? {
|
||||
suspend fun PushNotificationDataRunnable.Companion.fetchMyTeamCategories(db: WMDatabase, serverUrl: String, teamId: String): ReadableMap? {
|
||||
return try {
|
||||
val userId = queryCurrentUserId(db)
|
||||
val categories = fetch(serverUrl, "/api/v4/users/$userId/teams/$teamId/channels/categories")
|
||||
|
|
@ -20,7 +20,7 @@ suspend fun PushNotificationDataRunnable.Companion.fetchMyTeamCategories(db: Dat
|
|||
}
|
||||
}
|
||||
|
||||
fun PushNotificationDataRunnable.Companion.addToDefaultCategoryIfNeeded(db: Database, channel: ReadableMap): ReadableArray? {
|
||||
fun PushNotificationDataRunnable.Companion.addToDefaultCategoryIfNeeded(db: WMDatabase, channel: ReadableMap): ReadableArray? {
|
||||
val channelId = channel.getString("id") ?: return null
|
||||
val channelType = channel.getString("type")
|
||||
val categoryChannels = Arguments.createArray()
|
||||
|
|
@ -44,7 +44,7 @@ fun PushNotificationDataRunnable.Companion.addToDefaultCategoryIfNeeded(db: Data
|
|||
return categoryChannels
|
||||
}
|
||||
|
||||
private fun categoryChannelForTeam(db: Database, channelId: String, teamId: String?, type: String): ReadableMap? {
|
||||
private fun categoryChannelForTeam(db: WMDatabase, channelId: String, teamId: String?, type: String): ReadableMap? {
|
||||
teamId?.let { id ->
|
||||
val category = findByColumns(db, "Category", arrayOf("type", "team_id"), arrayOf(type, id))
|
||||
val categoryId = category?.getString("id")
|
||||
|
|
|
|||
|
|
@ -8,12 +8,11 @@ import com.mattermost.helpers.database_extension.findChannel
|
|||
import com.mattermost.helpers.database_extension.getCurrentUserLocale
|
||||
import com.mattermost.helpers.database_extension.getTeammateDisplayNameSetting
|
||||
import com.mattermost.helpers.database_extension.queryCurrentUserId
|
||||
import com.nozbe.watermelondb.Database
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
import java.text.Collator
|
||||
import java.util.Locale
|
||||
import kotlin.math.max
|
||||
|
||||
suspend fun PushNotificationDataRunnable.Companion.fetchMyChannel(db: Database, serverUrl: String, channelId: String, isCRTEnabled: Boolean): Triple<ReadableMap?, ReadableMap?, ReadableArray?> {
|
||||
suspend fun PushNotificationDataRunnable.Companion.fetchMyChannel(db: WMDatabase, serverUrl: String, channelId: String, isCRTEnabled: Boolean): Triple<ReadableMap?, ReadableMap?, ReadableArray?> {
|
||||
val channel = fetch(serverUrl, "/api/v4/channels/$channelId")
|
||||
var channelData = channel?.getMap("data")
|
||||
val myChannelData = channelData?.let { fetchMyChannelData(serverUrl, channelId, isCRTEnabled, it) }
|
||||
|
|
@ -109,7 +108,7 @@ private suspend fun PushNotificationDataRunnable.Companion.fetchMyChannelData(se
|
|||
return null
|
||||
}
|
||||
|
||||
private suspend fun PushNotificationDataRunnable.Companion.fetchProfileInChannel(db: Database, serverUrl: String, channelId: String): ReadableArray? {
|
||||
private suspend fun PushNotificationDataRunnable.Companion.fetchProfileInChannel(db: WMDatabase, serverUrl: String, channelId: String): ReadableArray? {
|
||||
return try {
|
||||
val currentUserId = queryCurrentUserId(db)
|
||||
val profilesInChannel = fetch(serverUrl, "/api/v4/users?in_channel=${channelId}&page=0&per_page=8&sort=")
|
||||
|
|
|
|||
|
|
@ -9,10 +9,10 @@ import com.mattermost.helpers.PushNotificationDataRunnable
|
|||
import com.mattermost.helpers.ReadableArrayUtils
|
||||
import com.mattermost.helpers.ReadableMapUtils
|
||||
import com.mattermost.helpers.database_extension.*
|
||||
import com.nozbe.watermelondb.Database
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
|
||||
internal suspend fun PushNotificationDataRunnable.Companion.fetchPosts(
|
||||
db: Database, serverUrl: String, channelId: String, isCRTEnabled: Boolean,
|
||||
db: WMDatabase, serverUrl: String, channelId: String, isCRTEnabled: Boolean,
|
||||
rootId: String?, loadedProfiles: ReadableArray?
|
||||
): ReadableMap? {
|
||||
return try {
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ import com.facebook.react.bridge.ReadableMap
|
|||
import com.mattermost.helpers.PushNotificationDataRunnable
|
||||
import com.mattermost.helpers.database_extension.findMyTeam
|
||||
import com.mattermost.helpers.database_extension.findTeam
|
||||
import com.nozbe.watermelondb.Database
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
|
||||
suspend fun PushNotificationDataRunnable.Companion.fetchTeamIfNeeded(db: Database, serverUrl: String, teamId: String): Pair<ReadableMap?, ReadableMap?> {
|
||||
suspend fun PushNotificationDataRunnable.Companion.fetchTeamIfNeeded(db: WMDatabase, serverUrl: String, teamId: String): Pair<ReadableMap?, ReadableMap?> {
|
||||
return try {
|
||||
var team: ReadableMap? = null
|
||||
var myTeam: ReadableMap? = null
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ package com.mattermost.helpers.push_notification
|
|||
import com.facebook.react.bridge.ReadableMap
|
||||
import com.mattermost.helpers.PushNotificationDataRunnable
|
||||
import com.mattermost.helpers.database_extension.*
|
||||
import com.nozbe.watermelondb.Database
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
|
||||
internal suspend fun PushNotificationDataRunnable.Companion.fetchThread(db: Database, serverUrl: String, threadId: String, teamId: String?): ReadableMap? {
|
||||
internal suspend fun PushNotificationDataRunnable.Companion.fetchThread(db: WMDatabase, serverUrl: String, threadId: String, teamId: String?): ReadableMap? {
|
||||
val currentUserId = queryCurrentUserId(db) ?: return null
|
||||
val threadTeamId = (if (teamId.isNullOrEmpty()) queryCurrentTeamId(db) else teamId) ?: return null
|
||||
|
||||
|
|
|
|||
|
|
@ -36,10 +36,7 @@ public class MainActivity extends NavigationActivity {
|
|||
this,
|
||||
Objects.requireNonNull(getMainComponentName()),
|
||||
// If you opted-in for the New Architecture, we enable the Fabric Renderer.
|
||||
DefaultNewArchitectureEntryPoint.getFabricEnabled(), // fabricEnabled
|
||||
// If you opted-in for the New Architecture, we enable Concurrent React (i.e. React 18).
|
||||
DefaultNewArchitectureEntryPoint.getConcurrentReactEnabled() // concurrentRootEnabled
|
||||
);
|
||||
DefaultNewArchitectureEntryPoint.getFabricEnabled());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ buildscript {
|
|||
compileSdkVersion = 33
|
||||
targetSdkVersion = 33
|
||||
supportLibVersion = "33.0.0"
|
||||
kotlinVersion = "1.7.21"
|
||||
kotlin_version = "1.7.21"
|
||||
firebaseVersion = "23.1.1"
|
||||
kotlinVersion = "1.8.21"
|
||||
kotlin_version = kotlinVersion
|
||||
RNNKotlinVersion = kotlinVersion
|
||||
firebaseVersion = "23.3.1"
|
||||
|
||||
// We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP.
|
||||
ndkVersion = "23.1.7779620"
|
||||
|
|
@ -21,9 +21,9 @@ buildscript {
|
|||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath("com.android.tools.build:gradle:7.3.1")
|
||||
classpath("com.android.tools.build:gradle")
|
||||
classpath("com.facebook.react:react-native-gradle-plugin")
|
||||
classpath('com.google.gms:google-services:4.3.15')
|
||||
classpath('com.google.gms:google-services:4.4.0')
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
|
|
|
|||
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
|
|
@ -1,5 +1,6 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-all.zip
|
||||
networkTimeout=10000
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
|
||||
|
|
|
|||
20
android/gradlew
vendored
20
android/gradlew
vendored
|
|
@ -55,7 +55,7 @@
|
|||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
|
|
@ -80,10 +80,10 @@ do
|
|||
esac
|
||||
done
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
|
@ -143,12 +143,16 @@ fi
|
|||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
|
|
@ -205,6 +209,12 @@ set -- \
|
|||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
|
|
@ -231,4 +241,4 @@ eval "set -- $(
|
|||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
exec "$JAVACMD" "$@"
|
||||
|
|
|
|||
17
android/gradlew.bat
vendored
17
android/gradlew.bat
vendored
|
|
@ -14,7 +14,7 @@
|
|||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
|
|
@ -25,7 +25,8 @@
|
|||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
|
|
@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
|||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
|
@ -75,15 +76,17 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
|||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
:omega
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ project(':reactnativenotifications').projectDir = new File(rootProject.projectDi
|
|||
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
|
||||
include ':react-native-video'
|
||||
project(':react-native-video').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-video/android-exoplayer')
|
||||
include ':watermelondb'
|
||||
project(':watermelondb').projectDir = new File(rootProject.projectDir, '../node_modules/@nozbe/watermelondb/native/android')
|
||||
include ':watermelondb-jsi'
|
||||
project(':watermelondb-jsi').projectDir = new File(rootProject.projectDir, '../node_modules/@nozbe/watermelondb/native/android-jsi')
|
||||
includeBuild('../node_modules/react-native-gradle-plugin')
|
||||
includeBuild('../node_modules/@react-native/gradle-plugin')
|
||||
|
|
|
|||
69
app/actions/local/category.test.ts
Normal file
69
app/actions/local/category.test.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import DatabaseManager from '@database/manager';
|
||||
|
||||
import {handleConvertedGMCategories} from './category';
|
||||
|
||||
import type ServerDataOperator from '@database/operator/server_data_operator';
|
||||
|
||||
describe('handleConvertedGMCategories', () => {
|
||||
const serverUrl = 'baseHandler.test.com';
|
||||
const channelId = 'channel_id_1';
|
||||
const teamId1 = 'team_id_1';
|
||||
const teamId2 = 'team_id_2';
|
||||
const team: Team = {
|
||||
id: teamId1,
|
||||
} as Team;
|
||||
|
||||
let operator: ServerDataOperator;
|
||||
|
||||
beforeEach(async () => {
|
||||
await DatabaseManager.init([serverUrl]);
|
||||
operator = DatabaseManager.serverDatabases[serverUrl]!.operator;
|
||||
});
|
||||
|
||||
it('base case', async () => {
|
||||
await operator.handleTeam({teams: [team], prepareRecordsOnly: false});
|
||||
|
||||
const defaultCategory: Category = {
|
||||
id: 'default_category_id',
|
||||
team_id: teamId1,
|
||||
type: 'channels',
|
||||
} as Category;
|
||||
|
||||
const customCategory: Category = {
|
||||
id: 'custom_category_id',
|
||||
team_id: teamId2,
|
||||
type: 'custom',
|
||||
} as Category;
|
||||
|
||||
const dmCategory: Category = {
|
||||
id: 'dm_category_id',
|
||||
team_id: teamId1,
|
||||
type: 'direct_messages',
|
||||
} as Category;
|
||||
|
||||
await operator.handleCategories({categories: [defaultCategory, customCategory, dmCategory], prepareRecordsOnly: false});
|
||||
|
||||
const dmCategoryChannel: CategoryChannel = {
|
||||
id: 'dm_category_channel_id',
|
||||
category_id: 'dm_category_id',
|
||||
channel_id: channelId,
|
||||
sort_order: 1,
|
||||
};
|
||||
|
||||
const customCategoryChannel: CategoryChannel = {
|
||||
id: 'custom_category_channel_id',
|
||||
category_id: 'dm_category_id',
|
||||
channel_id: channelId,
|
||||
sort_order: 1,
|
||||
};
|
||||
await operator.handleCategoryChannels({categoryChannels: [dmCategoryChannel, customCategoryChannel], prepareRecordsOnly: false});
|
||||
|
||||
const {models, error} = await handleConvertedGMCategories(serverUrl, channelId, teamId1, true);
|
||||
expect(error).toBeUndefined();
|
||||
expect(models).toBeDefined();
|
||||
expect(models!.length).toBe(3); // two for removing channel for a custom and a DM category, and one for adding it to default channels category
|
||||
});
|
||||
});
|
||||
|
|
@ -3,12 +3,13 @@
|
|||
|
||||
import {CHANNELS_CATEGORY, DMS_CATEGORY} from '@constants/categories';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {prepareCategoryChannels, queryCategoriesByTeamIds, getCategoryById, prepareCategoriesAndCategoriesChannels} from '@queries/servers/categories';
|
||||
import {prepareCategoryChannels, queryCategoriesByTeamIds, getCategoryById, prepareCategoriesAndCategoriesChannels, queryCategoryChannelsByChannelId} from '@queries/servers/categories';
|
||||
import {getCurrentUserId} from '@queries/servers/system';
|
||||
import {queryMyTeams} from '@queries/servers/team';
|
||||
import {isDMorGM} from '@utils/channel';
|
||||
import {logError} from '@utils/log';
|
||||
import {logDebug, logError} from '@utils/log';
|
||||
|
||||
import type {Database, Model} from '@nozbe/watermelondb';
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
|
||||
export const deleteCategory = async (serverUrl: string, categoryId: string) => {
|
||||
|
|
@ -91,11 +92,8 @@ export async function addChannelToDefaultCategory(serverUrl: string, channel: Ch
|
|||
categoriesWithChannels.push(cwc);
|
||||
}
|
||||
} else {
|
||||
const categories = await queryCategoriesByTeamIds(database, [teamId]).fetch();
|
||||
const channelCategory = categories.find((c) => c.type === CHANNELS_CATEGORY);
|
||||
if (channelCategory) {
|
||||
const cwc = await channelCategory.toCategoryWithChannels();
|
||||
cwc.channel_ids.unshift(channel.id);
|
||||
const cwc = await prepareAddNonGMDMChannelToDefaultCategory(database, teamId, channel.id);
|
||||
if (cwc) {
|
||||
categoriesWithChannels.push(cwc);
|
||||
}
|
||||
}
|
||||
|
|
@ -108,6 +106,62 @@ export async function addChannelToDefaultCategory(serverUrl: string, channel: Ch
|
|||
|
||||
return {models};
|
||||
} catch (error) {
|
||||
logError('Failed to add channel to default category', error);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
async function prepareAddNonGMDMChannelToDefaultCategory(database: Database, teamId: string, channelId: string): Promise<CategoryWithChannels | undefined> {
|
||||
const categories = await queryCategoriesByTeamIds(database, [teamId]).fetch();
|
||||
const channelCategory = categories.find((category) => category.type === CHANNELS_CATEGORY);
|
||||
if (channelCategory) {
|
||||
const cwc = await channelCategory.toCategoryWithChannels();
|
||||
if (cwc.channel_ids.indexOf(channelId) < 0) {
|
||||
cwc.channel_ids.unshift(channelId);
|
||||
return cwc;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export async function handleConvertedGMCategories(serverUrl: string, channelId: string, targetTeamID: string, prepareRecordsOnly = false) {
|
||||
try {
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const categoryChannels = await queryCategoryChannelsByChannelId(database, channelId).fetch();
|
||||
|
||||
const categories = await queryCategoriesByTeamIds(database, [targetTeamID]).fetch();
|
||||
const channelCategory = categories.find((category) => category.type === CHANNELS_CATEGORY);
|
||||
|
||||
if (!channelCategory) {
|
||||
const error = 'Failed to find default category when handling category of converted GM';
|
||||
logError(error);
|
||||
return {error};
|
||||
}
|
||||
|
||||
const models: Model[] = [];
|
||||
|
||||
categoryChannels.forEach((categoryChannel) => {
|
||||
if (categoryChannel.categoryId !== channelCategory.id) {
|
||||
models.push(categoryChannel.prepareDestroyPermanently());
|
||||
}
|
||||
});
|
||||
|
||||
const cwc = await prepareAddNonGMDMChannelToDefaultCategory(database, targetTeamID, channelId);
|
||||
if (cwc) {
|
||||
const model = await prepareCategoryChannels(operator, [cwc]);
|
||||
models.push(...model);
|
||||
} else {
|
||||
logDebug('handleConvertedGMCategories: could not find channel category of target team');
|
||||
}
|
||||
|
||||
if (models.length > 0 && !prepareRecordsOnly) {
|
||||
await operator.batchRecords(models, 'putGMInCorrectCategory');
|
||||
}
|
||||
|
||||
return {models};
|
||||
} catch (error) {
|
||||
logError('Failed to handle category update for GM converted to channel', error);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
/* eslint-disable max-lines */
|
||||
import {DeviceEventEmitter} from 'react-native';
|
||||
|
||||
import {addChannelToDefaultCategory, storeCategories} from '@actions/local/category';
|
||||
import {addChannelToDefaultCategory, handleConvertedGMCategories, storeCategories} from '@actions/local/category';
|
||||
import {markChannelAsViewed, removeCurrentUserFromChannel, setChannelDeleteAt, storeMyChannelsForTeam, switchToChannel} from '@actions/local/channel';
|
||||
import {switchToGlobalThreads} from '@actions/local/thread';
|
||||
import {loadCallForChannel} from '@calls/actions/calls';
|
||||
|
|
@ -231,6 +231,7 @@ export async function createChannel(serverUrl: string, displayName: string, purp
|
|||
const resolvedModels = await Promise.all(channelModels);
|
||||
models.push(...resolvedModels.flat());
|
||||
}
|
||||
|
||||
const categoriesModels = await addChannelToDefaultCategory(serverUrl, channelData, true);
|
||||
if (categoriesModels.models?.length) {
|
||||
models.push(...categoriesModels.models);
|
||||
|
|
@ -1262,3 +1263,56 @@ export const handleKickFromChannel = async (serverUrl: string, channelId: string
|
|||
logDebug('cannot kick user from channel', error);
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchGroupMessageMembersCommonTeams = async (serverUrl: string, channelId: string) => {
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const teams = await client.getGroupMessageMembersCommonTeams(channelId);
|
||||
return {teams};
|
||||
} catch (error) {
|
||||
logDebug('error on getGroupMessageMembersCommonTeams', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const convertGroupMessageToPrivateChannel = async (serverUrl: string, channelId: string, targetTeamId: string, displayName: string) => {
|
||||
try {
|
||||
const name = generateChannelNameFromDisplayName(displayName);
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
|
||||
const existingChannel = await getChannelById(database, channelId);
|
||||
if (existingChannel) {
|
||||
EphemeralStore.addConvertingChannel(channelId);
|
||||
}
|
||||
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const updatedChannel = await client.convertGroupMessageToPrivateChannel(channelId, targetTeamId, displayName, name);
|
||||
|
||||
if (existingChannel) {
|
||||
existingChannel.prepareUpdate((channel) => {
|
||||
channel.type = General.PRIVATE_CHANNEL;
|
||||
channel.displayName = displayName;
|
||||
channel.name = name;
|
||||
channel.teamId = targetTeamId;
|
||||
});
|
||||
|
||||
const models: Model[] = [existingChannel];
|
||||
|
||||
const {models: categoryUpdateModels} = await handleConvertedGMCategories(serverUrl, channelId, targetTeamId, true);
|
||||
if (categoryUpdateModels) {
|
||||
models.push(...categoryUpdateModels);
|
||||
}
|
||||
|
||||
await operator.batchRecords(models, 'convertGroupMessageToPrivateChannel');
|
||||
}
|
||||
|
||||
return {updatedChannel};
|
||||
} catch (error) {
|
||||
logError('error on convertGroupMessageToPrivateChannel', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
return {error};
|
||||
} finally {
|
||||
EphemeralStore.removeConvertingChannel(channelId);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -16,6 +16,35 @@ import {forceLogoutIfNecessary} from './session';
|
|||
import type {Model} from '@nozbe/watermelondb';
|
||||
import type PostModel from '@typings/database/models/servers/post';
|
||||
|
||||
export async function getIsReactionAlreadyAddedToPost(serverUrl: string, postId: string, emojiName: string) {
|
||||
try {
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
|
||||
const currentUserId = await getCurrentUserId(database);
|
||||
const emojiAlias = getEmojiFirstAlias(emojiName);
|
||||
return await queryReaction(database, emojiAlias, postId, currentUserId).fetchCount() > 0;
|
||||
} catch (error) {
|
||||
logDebug('error on getIsReactionAlreadyAddedToPost', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
export async function toggleReaction(serverUrl: string, postId: string, emojiName: string) {
|
||||
try {
|
||||
const isReactionAlreadyAddedToPost = await getIsReactionAlreadyAddedToPost(serverUrl, postId, emojiName);
|
||||
|
||||
if (isReactionAlreadyAddedToPost) {
|
||||
return removeReaction(serverUrl, postId, emojiName);
|
||||
}
|
||||
return addReaction(serverUrl, postId, emojiName);
|
||||
} catch (error) {
|
||||
logDebug('error on toggleReaction', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
export async function addReaction(serverUrl: string, postId: string, emojiName: string) {
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {addChannelToDefaultCategory} from '@actions/local/category';
|
||||
import {addChannelToDefaultCategory, handleConvertedGMCategories} from '@actions/local/category';
|
||||
import {
|
||||
markChannelAsViewed, removeCurrentUserFromChannel, setChannelDeleteAt,
|
||||
storeMyChannelsForTeam, updateChannelInfoFromChannel, updateMyChannelFromWebsocket,
|
||||
|
|
@ -12,10 +12,10 @@ import {fetchPostsForChannel} from '@actions/remote/post';
|
|||
import {fetchRolesIfNeeded} from '@actions/remote/role';
|
||||
import {fetchUsersByIds, updateUsersNoLongerVisible} from '@actions/remote/user';
|
||||
import {loadCallForChannel} from '@calls/actions/calls';
|
||||
import {Events} from '@constants';
|
||||
import {Events, General} from '@constants';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {deleteChannelMembership, getChannelById, prepareMyChannelsForTeam, getCurrentChannel} from '@queries/servers/channel';
|
||||
import {getConfig, getCurrentChannelId} from '@queries/servers/system';
|
||||
import {getConfig, getCurrentChannelId, getCurrentTeamId, setCurrentTeamId} from '@queries/servers/system';
|
||||
import {getCurrentUser, getTeammateNameDisplay, getUserById} from '@queries/servers/user';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
import MyChannelModel from '@typings/database/models/servers/my_channel';
|
||||
|
|
@ -93,14 +93,35 @@ export async function handleChannelConvertedEvent(serverUrl: string, msg: any) {
|
|||
export async function handleChannelUpdatedEvent(serverUrl: string, msg: any) {
|
||||
try {
|
||||
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const updatedChannel = JSON.parse(msg.data.channel) as Channel;
|
||||
|
||||
if (EphemeralStore.isConvertingChannel(updatedChannel.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const existingChannel = await getChannelById(database, updatedChannel.id);
|
||||
const existingChannelType = existingChannel?.type;
|
||||
|
||||
const updatedChannel = JSON.parse(msg.data.channel);
|
||||
const models: Model[] = await operator.handleChannel({channels: [updatedChannel], prepareRecordsOnly: true});
|
||||
const infoModel = await updateChannelInfoFromChannel(serverUrl, updatedChannel, true);
|
||||
if (infoModel.model) {
|
||||
models.push(...infoModel.model);
|
||||
}
|
||||
operator.batchRecords(models, 'handleChannelUpdatedEvent');
|
||||
|
||||
// This indicates a GM was converted to a private channel
|
||||
if (existingChannelType === General.GM_CHANNEL && updatedChannel.type === General.PRIVATE_CHANNEL) {
|
||||
await handleConvertedGMCategories(serverUrl, updatedChannel.id, updatedChannel.team_id);
|
||||
|
||||
const currentChannelId = await getCurrentChannelId(database);
|
||||
const currentTeamId = await getCurrentTeamId(database);
|
||||
|
||||
// Making sure user is in the correct team
|
||||
if (currentChannelId === updatedChannel.id && currentTeamId !== updatedChannel.team_id) {
|
||||
await setCurrentTeamId(operator, updatedChannel.team_id);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,12 @@
|
|||
import {markChannelAsViewed} from '@actions/local/channel';
|
||||
import {dataRetentionCleanup} from '@actions/local/systems';
|
||||
import {markChannelAsRead} from '@actions/remote/channel';
|
||||
import {deferredAppEntryActions, entry, handleEntryAfterLoadNavigation, registerDeviceToken} from '@actions/remote/entry/common';
|
||||
import {
|
||||
deferredAppEntryActions,
|
||||
entry,
|
||||
handleEntryAfterLoadNavigation,
|
||||
registerDeviceToken,
|
||||
} from '@actions/remote/entry/common';
|
||||
import {fetchPostsForChannel, fetchPostThread} from '@actions/remote/post';
|
||||
import {openAllUnreadChannels} from '@actions/remote/preference';
|
||||
import {autoUpdateTimezone} from '@actions/remote/user';
|
||||
|
|
@ -17,9 +22,9 @@ import {
|
|||
handleCallRecordingState,
|
||||
handleCallScreenOff,
|
||||
handleCallScreenOn,
|
||||
handleCallStarted,
|
||||
handleCallUserConnected,
|
||||
handleCallUserDisconnected,
|
||||
handleCallStarted, handleCallUserConnected, handleCallUserDisconnected,
|
||||
handleCallUserJoined,
|
||||
handleCallUserLeft,
|
||||
handleCallUserMuted,
|
||||
handleCallUserRaiseHand,
|
||||
handleCallUserReacted,
|
||||
|
|
@ -51,8 +56,14 @@ import {setTeamLoading} from '@store/team_load_store';
|
|||
import {isTablet} from '@utils/helpers';
|
||||
import {logDebug, logInfo} from '@utils/log';
|
||||
|
||||
import {handleCategoryCreatedEvent, handleCategoryDeletedEvent, handleCategoryOrderUpdatedEvent, handleCategoryUpdatedEvent} from './category';
|
||||
import {handleChannelConvertedEvent, handleChannelCreatedEvent,
|
||||
import {
|
||||
handleCategoryCreatedEvent,
|
||||
handleCategoryDeletedEvent,
|
||||
handleCategoryOrderUpdatedEvent,
|
||||
handleCategoryUpdatedEvent,
|
||||
} from './category';
|
||||
import {
|
||||
handleChannelConvertedEvent, handleChannelCreatedEvent,
|
||||
handleChannelDeletedEvent,
|
||||
handleChannelMemberUpdatedEvent,
|
||||
handleChannelUnarchiveEvent,
|
||||
|
|
@ -61,15 +72,39 @@ import {handleChannelConvertedEvent, handleChannelCreatedEvent,
|
|||
handleMultipleChannelsViewedEvent,
|
||||
handleDirectAddedEvent,
|
||||
handleUserAddedToChannelEvent,
|
||||
handleUserRemovedFromChannelEvent} from './channel';
|
||||
import {handleGroupMemberAddEvent, handleGroupMemberDeleteEvent, handleGroupReceivedEvent, handleGroupTeamAssociatedEvent, handleGroupTeamDissociateEvent} from './group';
|
||||
handleUserRemovedFromChannelEvent,
|
||||
} from './channel';
|
||||
import {
|
||||
handleGroupMemberAddEvent,
|
||||
handleGroupMemberDeleteEvent,
|
||||
handleGroupReceivedEvent,
|
||||
handleGroupTeamAssociatedEvent,
|
||||
handleGroupTeamDissociateEvent,
|
||||
} from './group';
|
||||
import {handleOpenDialogEvent} from './integrations';
|
||||
import {handleNewPostEvent, handlePostAcknowledgementAdded, handlePostAcknowledgementRemoved, handlePostDeleted, handlePostEdited, handlePostUnread} from './posts';
|
||||
import {handlePreferenceChangedEvent, handlePreferencesChangedEvent, handlePreferencesDeletedEvent} from './preferences';
|
||||
import {
|
||||
handleNewPostEvent,
|
||||
handlePostAcknowledgementAdded,
|
||||
handlePostAcknowledgementRemoved,
|
||||
handlePostDeleted,
|
||||
handlePostEdited,
|
||||
handlePostUnread,
|
||||
} from './posts';
|
||||
import {
|
||||
handlePreferenceChangedEvent,
|
||||
handlePreferencesChangedEvent,
|
||||
handlePreferencesDeletedEvent,
|
||||
} from './preferences';
|
||||
import {handleAddCustomEmoji, handleReactionRemovedFromPostEvent, handleReactionAddedToPostEvent} from './reactions';
|
||||
import {handleUserRoleUpdatedEvent, handleTeamMemberRoleUpdatedEvent, handleRoleUpdatedEvent} from './roles';
|
||||
import {handleLicenseChangedEvent, handleConfigChangedEvent} from './system';
|
||||
import {handleLeaveTeamEvent, handleUserAddedToTeamEvent, handleUpdateTeamEvent, handleTeamArchived, handleTeamRestored} from './teams';
|
||||
import {
|
||||
handleLeaveTeamEvent,
|
||||
handleUserAddedToTeamEvent,
|
||||
handleUpdateTeamEvent,
|
||||
handleTeamArchived,
|
||||
handleTeamRestored,
|
||||
} from './teams';
|
||||
import {handleThreadUpdatedEvent, handleThreadReadChangedEvent, handleThreadFollowChangedEvent} from './threads';
|
||||
import {handleUserUpdatedEvent, handleUserTypingEvent, handleStatusChangedEvent} from './users';
|
||||
|
||||
|
|
@ -347,12 +382,23 @@ export async function handleEvent(serverUrl: string, msg: WebSocketMessage) {
|
|||
case WebsocketEvents.CALLS_CHANNEL_DISABLED:
|
||||
handleCallChannelDisabled(serverUrl, msg);
|
||||
break;
|
||||
|
||||
// DEPRECATED in favour of user_joined (since v0.21.0)
|
||||
case WebsocketEvents.CALLS_USER_CONNECTED:
|
||||
handleCallUserConnected(serverUrl, msg);
|
||||
break;
|
||||
|
||||
// DEPRECATED in favour of user_left (since v0.21.0)
|
||||
case WebsocketEvents.CALLS_USER_DISCONNECTED:
|
||||
handleCallUserDisconnected(serverUrl, msg);
|
||||
break;
|
||||
|
||||
case WebsocketEvents.CALLS_USER_JOINED:
|
||||
handleCallUserJoined(serverUrl, msg);
|
||||
break;
|
||||
case WebsocketEvents.CALLS_USER_LEFT:
|
||||
handleCallUserLeft(serverUrl, msg);
|
||||
break;
|
||||
case WebsocketEvents.CALLS_USER_MUTED:
|
||||
handleCallUserMuted(serverUrl, msg);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ export interface ClientChannelsMix {
|
|||
searchAllChannels: (term: string, teamIds: string[], archivedOnly?: boolean) => Promise<Channel[]>;
|
||||
updateChannelMemberSchemeRoles: (channelId: string, userId: string, isSchemeUser: boolean, isSchemeAdmin: boolean) => Promise<any>;
|
||||
getMemberInChannel: (channelId: string, userId: string) => Promise<ChannelMembership>;
|
||||
getGroupMessageMembersCommonTeams: (channelId: string) => Promise<Team[]>;
|
||||
convertGroupMessageToPrivateChannel: (channelId: string, teamId: string, displayName: string, name: string) => Promise<Channel>;
|
||||
}
|
||||
|
||||
const ClientChannels = <TBase extends Constructor<ClientBase>>(superclass: TBase) => class extends superclass {
|
||||
|
|
@ -350,6 +352,27 @@ const ClientChannels = <TBase extends Constructor<ClientBase>>(superclass: TBase
|
|||
{method: 'get'},
|
||||
);
|
||||
};
|
||||
|
||||
getGroupMessageMembersCommonTeams = (channelId: string) => {
|
||||
return this.doFetch(
|
||||
`${this.getChannelRoute(channelId)}/common_teams`,
|
||||
{method: 'get'},
|
||||
);
|
||||
};
|
||||
|
||||
convertGroupMessageToPrivateChannel = (channelId: string, teamId: string, displayName: string, name: string) => {
|
||||
const body = {
|
||||
channel_id: channelId,
|
||||
team_id: teamId,
|
||||
display_name: displayName,
|
||||
name,
|
||||
};
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getChannelRoute(channelId)}/convert_to_channel?team-id=${teamId}`,
|
||||
{method: 'post', body},
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
export default ClientChannels;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import {of as of$, combineLatest} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import {of as of$, combineLatest, Observable} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {map, switchMap} from 'rxjs/operators';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withObservables} from '@nozbe/watermelondb/react';
|
||||
import {of as of$} from 'rxjs';
|
||||
|
||||
import AppsManager from '@managers/apps_manager';
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
|
||||
import {observeConfigBooleanValue, observeCurrentTeamId} from '@queries/servers/system';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
|
||||
import {observeCurrentTeamId} from '@queries/servers/system';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import React, {useCallback, useEffect, useState} from 'react';
|
||||
import {type IntlShape, useIntl} from 'react-intl';
|
||||
import {Text, View} from 'react-native';
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useMemo} from 'react';
|
||||
import React, {useMemo, type ReactNode} from 'react';
|
||||
import {type StyleProp, StyleSheet, Text, type TextStyle, View, type ViewStyle} from 'react-native';
|
||||
import RNButton from 'react-native-button';
|
||||
|
||||
|
|
@ -21,6 +21,7 @@ type Props = ConditionalProps & {
|
|||
testID?: string;
|
||||
onPress: () => void;
|
||||
text: string;
|
||||
iconComponent?: ReactNode;
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
|
@ -41,6 +42,7 @@ const Button = ({
|
|||
testID,
|
||||
iconName,
|
||||
iconSize,
|
||||
iconComponent,
|
||||
}: Props) => {
|
||||
const bgStyle = useMemo(() => [
|
||||
buttonBackgroundStyle(theme, size, emphasis, buttonType, buttonState),
|
||||
|
|
@ -61,6 +63,21 @@ const Button = ({
|
|||
[iconSize],
|
||||
);
|
||||
|
||||
let icon: ReactNode;
|
||||
|
||||
if (iconComponent) {
|
||||
icon = iconComponent;
|
||||
} else if (iconName) {
|
||||
icon = (
|
||||
<CompassIcon
|
||||
name={iconName!}
|
||||
size={iconSize}
|
||||
color={StyleSheet.flatten(txtStyle).color}
|
||||
style={styles.icon}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<RNButton
|
||||
containerStyle={bgStyle}
|
||||
|
|
@ -68,14 +85,7 @@ const Button = ({
|
|||
testID={testID}
|
||||
>
|
||||
<View style={containerStyle}>
|
||||
{Boolean(iconName) &&
|
||||
<CompassIcon
|
||||
name={iconName!}
|
||||
size={iconSize}
|
||||
color={StyleSheet.flatten(txtStyle).color}
|
||||
style={styles.icon}
|
||||
/>
|
||||
}
|
||||
{icon}
|
||||
<Text
|
||||
style={txtStyle}
|
||||
numberOfLines={1}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
|
||||
import OptionItem from '@components/option_item';
|
||||
import {Screens} from '@constants';
|
||||
import {dismissBottomSheet, goToScreen} from '@screens/navigation';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
|
||||
type Props = {
|
||||
channelId: string;
|
||||
}
|
||||
|
||||
const ConvertToChannelLabel = ({channelId}: Props) => {
|
||||
const {formatMessage} = useIntl();
|
||||
|
||||
const goToConvertToPrivateChannel = useCallback(preventDoubleTap(async () => {
|
||||
await dismissBottomSheet();
|
||||
const title = formatMessage({id: 'channel_info.convert_gm_to_channel.screen_title', defaultMessage: 'Convert to Private Channel'});
|
||||
goToScreen(Screens.CONVERT_GM_TO_CHANNEL, title, {channelId});
|
||||
}), [channelId]);
|
||||
|
||||
return (
|
||||
<OptionItem
|
||||
action={goToConvertToPrivateChannel}
|
||||
icon='lock-outline'
|
||||
label={formatMessage({id: 'channel_info.convert_gm_to_channel', defaultMessage: 'Convert to a Private Channel'})}
|
||||
type='default'
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConvertToChannelLabel;
|
||||
|
|
@ -4,8 +4,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import {combineLatestWith, switchMap} from 'rxjs/operators';
|
||||
|
||||
import {observeIsChannelFavorited} from '@queries/servers/categories';
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {combineLatestWith, switchMap} from 'rxjs/operators';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {switchMap, distinctUntilChanged} from 'rxjs/operators';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import React from 'react';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {switchMap, distinctUntilChanged} from 'rxjs/operators';
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import {combineLatest, of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
|
||||
import {observeTeamIdByThread} from '@queries/servers/thread';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withObservables} from '@nozbe/watermelondb/react';
|
||||
|
||||
import {withServerUrl} from '@context/server';
|
||||
import websocket_manager from '@managers/websocket_manager';
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import moment, {type Moment} from 'moment-timezone';
|
||||
import React from 'react';
|
||||
import {Text, type TextStyle} from 'react-native';
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import React from 'react';
|
||||
import {
|
||||
Image,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import {of as of$, from as from$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import compose from 'lodash/fp/compose';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
|
||||
import {observeConfigBooleanValue, observeCanDownloadFiles} from '@queries/servers/system';
|
||||
|
||||
|
|
@ -18,7 +16,4 @@ const enhance = withObservables([], ({database}: WithDatabaseArgs) => {
|
|||
};
|
||||
});
|
||||
|
||||
export default compose(
|
||||
withDatabase,
|
||||
enhance,
|
||||
)(OptionMenus);
|
||||
export default withDatabase(enhance(OptionMenus));
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {ActivityIndicator, type StyleProp, View, type ViewStyle} from 'react-native';
|
||||
import {ActivityIndicator, type StyleProp, View, type ViewStyle, Text, type TextStyle} from 'react-native';
|
||||
|
||||
import {useTheme} from '@context/theme';
|
||||
|
||||
|
|
@ -11,9 +11,18 @@ type LoadingProps = {
|
|||
size?: number | 'small' | 'large';
|
||||
color?: string;
|
||||
themeColor?: keyof Theme;
|
||||
footerText?: string;
|
||||
footerTextStyles?: TextStyle;
|
||||
}
|
||||
|
||||
const Loading = ({containerStyle, size, color, themeColor}: LoadingProps) => {
|
||||
const Loading = ({
|
||||
containerStyle,
|
||||
size,
|
||||
color,
|
||||
themeColor,
|
||||
footerText,
|
||||
footerTextStyles,
|
||||
}: LoadingProps) => {
|
||||
const theme = useTheme();
|
||||
const indicatorColor = themeColor ? theme[themeColor] : color;
|
||||
|
||||
|
|
@ -23,6 +32,10 @@ const Loading = ({containerStyle, size, color, themeColor}: LoadingProps) => {
|
|||
color={indicatorColor}
|
||||
size={size}
|
||||
/>
|
||||
{
|
||||
footerText &&
|
||||
<Text style={footerTextStyles}>{footerText}</Text>
|
||||
}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,24 +11,20 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
|||
import {fetchUserOrGroupsByMentionsInBatch} from '@actions/remote/user';
|
||||
import SlideUpPanelItem, {ITEM_HEIGHT} from '@components/slide_up_panel_item';
|
||||
import {Screens} from '@constants';
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import GroupModel from '@database/models/server/group';
|
||||
import UserModel from '@database/models/server/user';
|
||||
import {useMemoMentionedGroup, useMemoMentionedUser} from '@hooks/markdown';
|
||||
import {bottomSheet, dismissBottomSheet, openAsBottomSheet} from '@screens/navigation';
|
||||
import {bottomSheetSnapPoint} from '@utils/helpers';
|
||||
import {displayUsername, getUsersByUsername} from '@utils/user';
|
||||
import {displayUsername} from '@utils/user';
|
||||
|
||||
import type {Database} from '@nozbe/watermelondb';
|
||||
import type GroupModelType from '@typings/database/models/servers/group';
|
||||
import type GroupMembershipModel from '@typings/database/models/servers/group_membership';
|
||||
import type UserModelType from '@typings/database/models/servers/user';
|
||||
|
||||
type AtMentionProps = {
|
||||
channelId?: string;
|
||||
currentUserId: string;
|
||||
database: Database;
|
||||
disableAtChannelMentionHighlight?: boolean;
|
||||
isSearchResult?: boolean;
|
||||
location: string;
|
||||
|
|
@ -43,8 +39,6 @@ type AtMentionProps = {
|
|||
groupMemberships: GroupMembershipModel[];
|
||||
}
|
||||
|
||||
const {SERVER: {GROUP, USER}} = MM_TABLES;
|
||||
|
||||
const style = StyleSheet.create({
|
||||
bottomSheet: {flex: 1},
|
||||
});
|
||||
|
|
@ -52,7 +46,6 @@ const style = StyleSheet.create({
|
|||
const AtMention = ({
|
||||
channelId,
|
||||
currentUserId,
|
||||
database,
|
||||
disableAtChannelMentionHighlight,
|
||||
isSearchResult,
|
||||
location,
|
||||
|
|
@ -72,31 +65,15 @@ const AtMention = ({
|
|||
const {bottom} = useSafeAreaInsets();
|
||||
const serverUrl = useServerUrl();
|
||||
|
||||
const user = useMemo(() => {
|
||||
const usersByUsername = getUsersByUsername(users);
|
||||
let mn = mentionName.toLowerCase();
|
||||
|
||||
while (mn.length > 0) {
|
||||
if (usersByUsername[mn]) {
|
||||
return usersByUsername[mn];
|
||||
}
|
||||
|
||||
// Repeatedly trim off trailing punctuation in case this is at the end of a sentence
|
||||
if ((/[._-]$/).test(mn)) {
|
||||
mn = mn.substring(0, mn.length - 1);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-expect-error: The model constructor is hidden within WDB type definition
|
||||
return new UserModel(database.get(USER), {username: ''});
|
||||
}, [users, mentionName]);
|
||||
const user = useMemoMentionedUser(users, mentionName);
|
||||
|
||||
const userMentionKeys = useMemo(() => {
|
||||
if (mentionKeys) {
|
||||
return mentionKeys;
|
||||
}
|
||||
if (!user) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (user.id !== currentUserId) {
|
||||
return [];
|
||||
|
|
@ -105,50 +82,21 @@ const AtMention = ({
|
|||
return user.mentionKeys;
|
||||
}, [currentUserId, mentionKeys, user]);
|
||||
|
||||
// Checks if the mention is a group
|
||||
const group = useMemo(() => {
|
||||
if (user?.username) {
|
||||
return undefined;
|
||||
}
|
||||
const getGroupsByName = (gs: GroupModelType[]) => {
|
||||
const groupsByName: Dictionary<GroupModelType> = {};
|
||||
|
||||
for (const g of gs) {
|
||||
groupsByName[g.name] = g;
|
||||
}
|
||||
|
||||
return groupsByName;
|
||||
};
|
||||
|
||||
const groupsByName = getGroupsByName(groups);
|
||||
let mn = mentionName.toLowerCase();
|
||||
|
||||
while (mn.length > 0) {
|
||||
if (groupsByName[mn]) {
|
||||
return groupsByName[mn];
|
||||
}
|
||||
|
||||
// Repeatedly trim off trailing punctuation in case this is at the end of a sentence
|
||||
if ((/[._-]$/).test(mn)) {
|
||||
mn = mn.substring(0, mn.length - 1);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-expect-error: The model constructor is hidden within WDB type definition
|
||||
return new GroupModel(database.get(GROUP), {name: ''});
|
||||
}, [groups, user, mentionName]);
|
||||
const group = useMemoMentionedGroup(groups, user, mentionName);
|
||||
|
||||
// Effects
|
||||
useEffect(() => {
|
||||
// Fetches and updates the local db store with the mention
|
||||
if (!user.username && !group?.name) {
|
||||
if (!user?.username && !group?.name) {
|
||||
fetchUserOrGroupsByMentionsInBatch(serverUrl, mentionName);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const openUserProfile = () => {
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
|
||||
const screen = Screens.USER_PROFILE;
|
||||
const title = intl.formatMessage({id: 'mobile.routes.user_profile', defaultMessage: 'Profile'});
|
||||
const closeButtonId = 'close-user-profile';
|
||||
|
|
@ -171,7 +119,7 @@ const AtMention = ({
|
|||
onPress={() => {
|
||||
dismissBottomSheet();
|
||||
let username = mentionName;
|
||||
if (user.username) {
|
||||
if (user?.username) {
|
||||
username = user.username;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
import {queryGroupsByName, queryGroupMembershipForMember} from '@queries/servers/group';
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
import {queryAllChannelsForTeam} from '@queries/servers/channel';
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import React from 'react';
|
||||
|
||||
import {observeConfigBooleanValue, observeConfigIntValue} from '@queries/servers/system';
|
||||
|
|
|
|||
|
|
@ -29,11 +29,11 @@ import MarkdownTable from './markdown_table';
|
|||
import MarkdownTableCell, {type MarkdownTableCellProps} from './markdown_table_cell';
|
||||
import MarkdownTableImage from './markdown_table_image';
|
||||
import MarkdownTableRow, {type MarkdownTableRowProps} from './markdown_table_row';
|
||||
import {addListItemIndices, combineTextNodes, highlightMentions, highlightSearchPatterns, parseTaskLists, pullOutImages} from './transform';
|
||||
import {addListItemIndices, combineTextNodes, highlightMentions, highlightWithoutNotification, highlightSearchPatterns, parseTaskLists, pullOutImages} from './transform';
|
||||
|
||||
import type {
|
||||
MarkdownAtMentionRenderer, MarkdownBaseRenderer, MarkdownBlockStyles, MarkdownChannelMentionRenderer,
|
||||
MarkdownEmojiRenderer, MarkdownImageRenderer, MarkdownLatexRenderer, MarkdownTextStyles, SearchPattern, UserMentionKey,
|
||||
MarkdownEmojiRenderer, MarkdownImageRenderer, MarkdownLatexRenderer, MarkdownTextStyles, SearchPattern, UserMentionKey, HighlightWithoutNotificationKey,
|
||||
} from '@typings/global/markdown';
|
||||
|
||||
type MarkdownProps = {
|
||||
|
|
@ -55,6 +55,7 @@ type MarkdownProps = {
|
|||
disableTables?: boolean;
|
||||
enableLatex: boolean;
|
||||
enableInlineLatex: boolean;
|
||||
highlightKeys?: HighlightWithoutNotificationKey[];
|
||||
imagesMetadata?: Record<string, PostImage | undefined>;
|
||||
isEdited?: boolean;
|
||||
isReplyPost?: boolean;
|
||||
|
|
@ -133,7 +134,7 @@ const Markdown = ({
|
|||
disableCodeBlock, disableGallery, disableHashtags, disableHeading, disableTables,
|
||||
enableInlineLatex, enableLatex, maxNodes,
|
||||
imagesMetadata, isEdited, isReplyPost, isSearchResult, layoutHeight, layoutWidth,
|
||||
location, mentionKeys, minimumHashtagLength = 3, onPostPress, postId, searchPatterns,
|
||||
location, mentionKeys, highlightKeys, minimumHashtagLength = 3, onPostPress, postId, searchPatterns,
|
||||
textStyles = {}, theme, value = '', baseParagraphStyle, onLinkLongPress,
|
||||
}: MarkdownProps) => {
|
||||
const style = getStyleSheet(theme);
|
||||
|
|
@ -578,6 +579,7 @@ const Markdown = ({
|
|||
|
||||
mention_highlight: Renderer.forwardChildren,
|
||||
search_highlight: Renderer.forwardChildren,
|
||||
highlight_without_notification: Renderer.forwardChildren,
|
||||
checkbox: renderCheckbox,
|
||||
|
||||
editedIndicator: renderEditedIndicator,
|
||||
|
|
@ -604,10 +606,12 @@ const Markdown = ({
|
|||
if (mentionKeys) {
|
||||
ast = highlightMentions(ast, mentionKeys);
|
||||
}
|
||||
if (highlightKeys) {
|
||||
ast = highlightWithoutNotification(ast, highlightKeys);
|
||||
}
|
||||
if (searchPatterns) {
|
||||
ast = highlightSearchPatterns(ast, searchPatterns);
|
||||
}
|
||||
|
||||
if (isEdited) {
|
||||
const editIndicatorNode = new Node('edited_indicator');
|
||||
if (ast.lastChild && ['heading', 'paragraph'].includes(ast.lastChild.type)) {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
|
||||
import {observeConfigValue} from '@queries/servers/system';
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ import {
|
|||
highlightTextNode,
|
||||
mentionKeysToPatterns,
|
||||
pullOutImages,
|
||||
highlightWithoutNotification,
|
||||
highlightKeysToPatterns,
|
||||
} from '@components/markdown/transform';
|
||||
import {logError} from '@utils/log';
|
||||
|
||||
|
|
@ -2602,7 +2604,7 @@ describe('Components.Markdown.transform', () => {
|
|||
}
|
||||
});
|
||||
|
||||
describe('getFirstMention', () => {
|
||||
describe('getFirstMention with mentionKeysToPatterns', () => {
|
||||
const tests = [{
|
||||
name: 'no mention keys',
|
||||
input: 'apple banana orange',
|
||||
|
|
@ -2801,6 +2803,461 @@ describe('Components.Markdown.transform', () => {
|
|||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('highlightWithoutNotification', () => {
|
||||
const tests = [{
|
||||
name: 'no highlights',
|
||||
input: 'Cant put down an anti gravity book',
|
||||
highlightKeys: [],
|
||||
expected: {
|
||||
type: 'document',
|
||||
children: [{
|
||||
type: 'paragraph',
|
||||
children: [{
|
||||
type: 'text',
|
||||
literal: 'Cant put down an anti gravity book',
|
||||
}],
|
||||
}],
|
||||
},
|
||||
}, {
|
||||
name: 'key bigger than input',
|
||||
input: 'incredible',
|
||||
highlightKeys: [{key: 'incredible and industructable'}],
|
||||
expected: {
|
||||
type: 'document',
|
||||
children: [{
|
||||
type: 'paragraph',
|
||||
children: [{
|
||||
type: 'text',
|
||||
literal: 'incredible',
|
||||
}],
|
||||
}],
|
||||
},
|
||||
}, {
|
||||
name: 'key part of the word',
|
||||
input: 'Sesquipedalian',
|
||||
highlightKeys: [{key: 'quipedalian'}],
|
||||
expected: {
|
||||
type: 'document',
|
||||
children: [{
|
||||
type: 'paragraph',
|
||||
children: [{
|
||||
type: 'text',
|
||||
literal: 'Sesquipedalian',
|
||||
}],
|
||||
}],
|
||||
},
|
||||
}, {
|
||||
name: 'word part of key',
|
||||
input: 'floccinauc',
|
||||
highlightKeys: [{key: 'floccinaucinihilipilification'}],
|
||||
expected: {
|
||||
type: 'document',
|
||||
children: [{
|
||||
type: 'paragraph',
|
||||
children: [{
|
||||
type: 'text',
|
||||
literal: 'floccinauc',
|
||||
}],
|
||||
}],
|
||||
},
|
||||
}, {
|
||||
name: 'a word highlight',
|
||||
input: 'Cant put down an anti gravity book',
|
||||
highlightKeys: [{key: 'anti'}],
|
||||
expected: {
|
||||
type: 'document',
|
||||
children: [{
|
||||
type: 'paragraph',
|
||||
children: [
|
||||
{
|
||||
type: 'text',
|
||||
literal: 'Cant put down an ',
|
||||
},
|
||||
{
|
||||
type: 'highlight_without_notification',
|
||||
children: [{
|
||||
type: 'text',
|
||||
literal: 'anti',
|
||||
}],
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
literal: ' gravity book',
|
||||
},
|
||||
],
|
||||
}],
|
||||
},
|
||||
}, {
|
||||
name: 'a sentence highlight',
|
||||
input: 'Cant put down an anti gravity book',
|
||||
highlightKeys: [{key: 'anti gravity'}],
|
||||
expected: {
|
||||
type: 'document',
|
||||
children: [{
|
||||
type: 'paragraph',
|
||||
children: [
|
||||
{
|
||||
type: 'text',
|
||||
literal: 'Cant put down an ',
|
||||
},
|
||||
{
|
||||
type: 'highlight_without_notification',
|
||||
children: [{
|
||||
type: 'text',
|
||||
literal: 'anti gravity',
|
||||
}],
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
literal: ' book',
|
||||
},
|
||||
],
|
||||
}],
|
||||
},
|
||||
}, {
|
||||
name: 'insensitive keywords',
|
||||
input: 'Cant put down an anti gravity book',
|
||||
highlightKeys: [{key: 'dOwN'}, {key: 'Anti'}, {key: 'BOOK'}],
|
||||
expected: {
|
||||
type: 'document',
|
||||
children: [{
|
||||
type: 'paragraph',
|
||||
children: [
|
||||
{
|
||||
type: 'text',
|
||||
literal: 'Cant put ',
|
||||
},
|
||||
{
|
||||
type: 'highlight_without_notification',
|
||||
children: [{
|
||||
type: 'text',
|
||||
literal: 'down',
|
||||
}],
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
literal: ' an ',
|
||||
},
|
||||
{
|
||||
type: 'highlight_without_notification',
|
||||
children: [{
|
||||
type: 'text',
|
||||
literal: 'anti',
|
||||
}],
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
literal: ' gravity ',
|
||||
},
|
||||
{
|
||||
type: 'highlight_without_notification',
|
||||
children: [{
|
||||
type: 'text',
|
||||
literal: 'book',
|
||||
}],
|
||||
},
|
||||
],
|
||||
}],
|
||||
},
|
||||
}, {
|
||||
name: 'insensitive keywords',
|
||||
input: 'Cant put down an anti gravity book',
|
||||
highlightKeys: [{key: 'dOwN'}, {key: 'Anti'}, {key: 'BOOK'}],
|
||||
expected: {
|
||||
type: 'document',
|
||||
children: [{
|
||||
type: 'paragraph',
|
||||
children: [
|
||||
{
|
||||
type: 'text',
|
||||
literal: 'Cant put ',
|
||||
},
|
||||
{
|
||||
type: 'highlight_without_notification',
|
||||
children: [{
|
||||
type: 'text',
|
||||
literal: 'down',
|
||||
}],
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
literal: ' an ',
|
||||
},
|
||||
{
|
||||
type: 'highlight_without_notification',
|
||||
children: [{
|
||||
type: 'text',
|
||||
literal: 'anti',
|
||||
}],
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
literal: ' gravity ',
|
||||
},
|
||||
{
|
||||
type: 'highlight_without_notification',
|
||||
children: [{
|
||||
type: 'text',
|
||||
literal: 'book',
|
||||
}],
|
||||
},
|
||||
],
|
||||
}],
|
||||
},
|
||||
}, {
|
||||
name: 'words with characters surrounding them',
|
||||
input: 'peace& ^peace -peace-',
|
||||
highlightKeys: [{key: 'PEACE'}],
|
||||
expected: {
|
||||
type: 'document',
|
||||
children: [{
|
||||
type: 'paragraph',
|
||||
children: [
|
||||
{
|
||||
type: 'highlight_without_notification',
|
||||
children: [{
|
||||
type: 'text',
|
||||
literal: 'peace',
|
||||
}],
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
literal: '& ^',
|
||||
},
|
||||
{
|
||||
type: 'highlight_without_notification',
|
||||
children: [{
|
||||
type: 'text',
|
||||
literal: 'peace',
|
||||
}],
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
literal: ' -',
|
||||
},
|
||||
{
|
||||
type: 'highlight_without_notification',
|
||||
children: [{
|
||||
type: 'text',
|
||||
literal: 'peace',
|
||||
}],
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
literal: '-',
|
||||
},
|
||||
],
|
||||
}],
|
||||
},
|
||||
}, {
|
||||
name: 'input in code block',
|
||||
input: '```\nTurning it off and\non\n```',
|
||||
highlightKeys: [{key: 'words'}],
|
||||
expected: {
|
||||
type: 'document',
|
||||
children: [{
|
||||
type: 'code_block',
|
||||
literal: 'Turning it off and\non\n',
|
||||
}],
|
||||
},
|
||||
}, {
|
||||
name: 'key in bold',
|
||||
input: 'Actions speak **louder** than words',
|
||||
highlightKeys: [{key: 'louder'}],
|
||||
expected: {
|
||||
type: 'document',
|
||||
children: [{
|
||||
type: 'paragraph',
|
||||
children: [{
|
||||
type: 'text',
|
||||
literal: 'Actions speak ',
|
||||
}, {
|
||||
type: 'strong',
|
||||
children: [{
|
||||
type: 'highlight_without_notification',
|
||||
children: [{
|
||||
type: 'text',
|
||||
literal: 'louder',
|
||||
}],
|
||||
}],
|
||||
}, {
|
||||
type: 'text',
|
||||
literal: ' than words',
|
||||
}],
|
||||
}],
|
||||
},
|
||||
}, {
|
||||
name: 'key in italic',
|
||||
input: 'Actions speak *louder* than words',
|
||||
highlightKeys: [{key: 'louder'}],
|
||||
expected: {
|
||||
type: 'document',
|
||||
children: [{
|
||||
type: 'paragraph',
|
||||
children: [{
|
||||
type: 'text',
|
||||
literal: 'Actions speak ',
|
||||
}, {
|
||||
type: 'emph',
|
||||
children: [{
|
||||
type: 'highlight_without_notification',
|
||||
children: [{
|
||||
type: 'text',
|
||||
literal: 'louder',
|
||||
}],
|
||||
}],
|
||||
}, {
|
||||
type: 'text',
|
||||
literal: ' than words',
|
||||
}],
|
||||
}],
|
||||
},
|
||||
}, {
|
||||
name: 'key in heading',
|
||||
input: '### Actions speak louder than words',
|
||||
highlightKeys: [{key: 'Actions'}],
|
||||
expected: {
|
||||
type: 'document',
|
||||
children: [{
|
||||
type: 'heading',
|
||||
level: 3,
|
||||
children: [
|
||||
{
|
||||
type: 'highlight_without_notification',
|
||||
children: [{
|
||||
type: 'text',
|
||||
literal: 'Actions',
|
||||
}],
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
literal: ' speak louder than words',
|
||||
}],
|
||||
}],
|
||||
},
|
||||
}, {
|
||||
name: 'Do not mention partial keys',
|
||||
input: 'Adding more memory wont help @bob',
|
||||
highlightKeys: [{key: 'bob'}],
|
||||
expected: {
|
||||
type: 'document',
|
||||
children: [{
|
||||
type: 'paragraph',
|
||||
children: [{
|
||||
type: 'text',
|
||||
literal: 'Adding more memory wont help ',
|
||||
}, {
|
||||
type: 'at_mention',
|
||||
_mentionName: 'bob',
|
||||
}],
|
||||
}],
|
||||
},
|
||||
}, {
|
||||
name: 'CJK word highlight',
|
||||
input: '我确实喜欢我的同事。',
|
||||
highlightKeys: [{key: '喜欢'}],
|
||||
expected: {
|
||||
type: 'document',
|
||||
children: [{
|
||||
type: 'paragraph',
|
||||
children: [
|
||||
{
|
||||
type: 'text',
|
||||
literal: '我确实',
|
||||
},
|
||||
{
|
||||
type: 'highlight_without_notification',
|
||||
children: [{
|
||||
type: 'text',
|
||||
literal: '喜欢',
|
||||
}],
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
literal: '我的同事。',
|
||||
},
|
||||
],
|
||||
}],
|
||||
},
|
||||
}];
|
||||
|
||||
for (const test of tests) {
|
||||
it(test.name, () => {
|
||||
const input = combineTextNodes(parser.parse(test.input));
|
||||
const expected = makeAst(test.expected);
|
||||
const actual = highlightWithoutNotification(input, test.highlightKeys);
|
||||
|
||||
assert.ok(verifyAst(actual));
|
||||
assert.deepStrictEqual(stripUnusedFields(actual), stripUnusedFields(expected));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('getFirstMatch with highlightKeysToPatterns', () => {
|
||||
const tests = [{
|
||||
name: 'text with space before',
|
||||
input: ' lol',
|
||||
patterns: highlightKeysToPatterns([{key: 'lol'}]),
|
||||
expected: {index: 1, length: 3},
|
||||
},
|
||||
{
|
||||
name: 'text with space afterwards and before',
|
||||
input: ' Lol ',
|
||||
patterns: highlightKeysToPatterns([{key: 'lol'}]),
|
||||
expected: {index: 1, length: 3},
|
||||
},
|
||||
{
|
||||
name: 'text with a non word character before (?)',
|
||||
input: '?Lol',
|
||||
patterns: highlightKeysToPatterns([{key: 'lol'}]),
|
||||
expected: {index: 1, length: 3},
|
||||
},
|
||||
{
|
||||
name: 'text with a non word character before (.)',
|
||||
input: '?Lol',
|
||||
patterns: highlightKeysToPatterns([{key: 'lol'}]),
|
||||
expected: {index: 1, length: 3},
|
||||
},
|
||||
{
|
||||
name: 'text with a non word character before (_)',
|
||||
input: '?Lol',
|
||||
patterns: highlightKeysToPatterns([{key: 'lol'}]),
|
||||
expected: {index: 1, length: 3},
|
||||
},
|
||||
{
|
||||
name: 'text with non word character after (.)',
|
||||
input: 'Lol.',
|
||||
patterns: highlightKeysToPatterns([{key: 'lol'}]),
|
||||
expected: {index: 0, length: 3},
|
||||
},
|
||||
{
|
||||
name: 'text with non word character after (?)',
|
||||
input: 'Lol?',
|
||||
patterns: highlightKeysToPatterns([{key: 'lol'}]),
|
||||
expected: {index: 0, length: 3},
|
||||
},
|
||||
{
|
||||
name: 'text with non word character after (_)',
|
||||
input: 'Lol_',
|
||||
patterns: highlightKeysToPatterns([{key: 'lol'}]),
|
||||
expected: {index: 0, length: 3},
|
||||
},
|
||||
{
|
||||
name: 'text with non word character before and after',
|
||||
input: '?Lol?',
|
||||
patterns: highlightKeysToPatterns([{key: 'lol'}]),
|
||||
expected: {index: 1, length: 3},
|
||||
}];
|
||||
|
||||
for (const test of tests) {
|
||||
it(test.name, () => {
|
||||
const actual = getFirstMatch(test.input, test.patterns);
|
||||
|
||||
assert.deepStrictEqual(actual, test.expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Testing and debugging functions
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {Node, type NodeType} from 'commonmark';
|
|||
|
||||
import {escapeRegex} from '@utils/markdown';
|
||||
|
||||
import type {SearchPattern, UserMentionKey} from '@typings/global/markdown';
|
||||
import type {HighlightWithoutNotificationKey, SearchPattern, UserMentionKey} from '@typings/global/markdown';
|
||||
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
|
||||
|
|
@ -174,6 +174,55 @@ export function mentionKeysToPatterns(mentionKeys: UserMentionKey[]) {
|
|||
});
|
||||
}
|
||||
|
||||
export function highlightWithoutNotification(ast: Node, highlightKeys: HighlightWithoutNotificationKey[]) {
|
||||
const walker = ast.walker();
|
||||
|
||||
const patterns = highlightKeysToPatterns(highlightKeys);
|
||||
|
||||
let e;
|
||||
while ((e = walker.next())) {
|
||||
if (!e.entering) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const node = e.node;
|
||||
if (node.type === 'text' && node.literal) {
|
||||
const {index, length} = getFirstMatch(node.literal, patterns);
|
||||
|
||||
// If the text node doesn't match any of the patterns, skip the loop
|
||||
if (index === -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const matchNode = highlightTextNode(node, index, index + length, 'highlight_without_notification');
|
||||
|
||||
// Resume processing on the next node after the match node which may include any remaining text
|
||||
// that was part of this one
|
||||
walker.resumeAt(matchNode, false);
|
||||
}
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
export function highlightKeysToPatterns(highlightKeys: HighlightWithoutNotificationKey[]) {
|
||||
if (highlightKeys.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return highlightKeys.
|
||||
filter((highlight) => highlight.key.trim() !== '').
|
||||
sort((a, b) => b.key.length - a.key.length).
|
||||
map(({key}) => {
|
||||
if (cjkPattern.test(key)) {
|
||||
// If the key contains Chinese, Japanese, Korean or Russian characters, don't mark word boundaries
|
||||
return new RegExp(`${escapeRegex(key)}`, 'gi');
|
||||
}
|
||||
|
||||
// If the key contains only English characters, mark word boundaries
|
||||
return new RegExp(`(^|\\b)(${escapeRegex(key)})(?=_*\\b)`, 'gi');
|
||||
});
|
||||
}
|
||||
|
||||
export function highlightSearchPatterns(ast: Node, searchPatterns: SearchPattern[]) {
|
||||
const walker = ast.walker();
|
||||
|
||||
|
|
@ -212,14 +261,22 @@ export function getFirstMatch(str: string, patterns: RegExp[]) {
|
|||
let firstMatchLength = -1;
|
||||
|
||||
for (const pattern of patterns) {
|
||||
const match = pattern.exec(str);
|
||||
if (!match || match[0] === '') {
|
||||
let matchResult;
|
||||
if (pattern.global || pattern.sticky) {
|
||||
// Since regex objects are stateful in global or sticky flags, we need to reset
|
||||
const regex = new RegExp(pattern.source, pattern.flags);
|
||||
matchResult = regex.exec(str);
|
||||
} else {
|
||||
matchResult = pattern.exec(str);
|
||||
}
|
||||
|
||||
if (!matchResult || matchResult[0] === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (firstMatchIndex === -1 || match.index < firstMatchIndex) {
|
||||
firstMatchIndex = match.index;
|
||||
firstMatchLength = match[0].length;
|
||||
if (firstMatchIndex === -1 || matchResult.index < firstMatchIndex) {
|
||||
firstMatchIndex = matchResult.index;
|
||||
firstMatchLength = matchResult[0].length;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -43,10 +43,14 @@ const hitSlop = {top: 11, bottom: 11, left: 11, right: 11};
|
|||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
return {
|
||||
actionContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginLeft: 16,
|
||||
},
|
||||
actionSubContainer: {
|
||||
marginLeft: 'auto',
|
||||
},
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
|
|
@ -61,8 +65,9 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
|||
marginTop: DESCRIPTION_MARGIN_TOP,
|
||||
},
|
||||
iconContainer: {marginRight: 16},
|
||||
infoContainer: {marginRight: 2},
|
||||
info: {
|
||||
flex: 1,
|
||||
textAlign: 'right',
|
||||
color: changeOpacity(theme.centerChannelColor, 0.56),
|
||||
...typography('Body', 100),
|
||||
},
|
||||
|
|
@ -99,7 +104,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
|||
...typography('Body', 200),
|
||||
},
|
||||
row: {
|
||||
flex: 1,
|
||||
flex: 3,
|
||||
flexDirection: 'row',
|
||||
},
|
||||
};
|
||||
|
|
@ -265,6 +270,7 @@ const OptionItem = ({
|
|||
<Text
|
||||
style={[labelTextStyle, optionLabelTextStyle]}
|
||||
testID={`${testID}.label`}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
|
|
@ -284,16 +290,17 @@ const OptionItem = ({
|
|||
<View style={styles.actionContainer}>
|
||||
{
|
||||
Boolean(info) &&
|
||||
<View style={styles.infoContainer}>
|
||||
<Text
|
||||
style={[styles.info, !actionComponent && styles.iconContainer, destructive && {color: theme.dndIndicator}]}
|
||||
testID={`${testID}.info`}
|
||||
>
|
||||
{info}
|
||||
</Text>
|
||||
</View>
|
||||
<Text
|
||||
style={[styles.info, !actionComponent && styles.iconContainer, destructive && {color: theme.dndIndicator}]}
|
||||
testID={`${testID}.info`}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{info}
|
||||
</Text>
|
||||
}
|
||||
{actionComponent}
|
||||
<View style={styles.actionSubContainer}>
|
||||
{actionComponent}
|
||||
</View>
|
||||
</View>
|
||||
}
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import React from 'react';
|
||||
|
||||
import {DEFAULT_SERVER_MAX_FILE_SIZE} from '@constants/post_draft';
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import React from 'react';
|
||||
import {combineLatest, of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import React from 'react';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {switchMap, distinctUntilChanged} from 'rxjs/operators';
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import React from 'react';
|
||||
|
||||
import {observeIsPostPriorityEnabled} from '@queries/servers/post';
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import {combineLatest, of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import React from 'react';
|
||||
import {combineLatest, of as of$} from 'rxjs';
|
||||
import {map, switchMap} from 'rxjs/operators';
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import React from 'react';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import React from 'react';
|
||||
import {of as of$, first as first$} from 'rxjs';
|
||||
import {distinctUntilChanged, switchMap} from 'rxjs/operators';
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import enhance from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
|
||||
import {observePostAuthor} from '@queries/servers/post';
|
||||
import {observeConfigBooleanValue} from '@queries/servers/system';
|
||||
|
|
@ -12,7 +11,7 @@ import Avatar from './avatar';
|
|||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
import type PostModel from '@typings/database/models/servers/post';
|
||||
|
||||
const withPost = enhance(['post'], ({database, post}: {post: PostModel} & WithDatabaseArgs) => {
|
||||
const withPost = withObservables(['post'], ({database, post}: {post: PostModel} & WithDatabaseArgs) => {
|
||||
const enablePostIconOverride = observeConfigBooleanValue(database, 'EnablePostIconOverride');
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import {typography} from '@utils/typography';
|
|||
import UsersList from './users_list';
|
||||
import {USER_ROW_HEIGHT} from './users_list/user_list_item';
|
||||
|
||||
import type {BottomSheetProps} from '@gorhom/bottom-sheet';
|
||||
import type PostModel from '@typings/database/models/servers/post';
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
|
|
@ -146,7 +145,7 @@ const Acknowledgements = ({currentUserId, currentUserTimezone, hasReactions, loc
|
|||
|
||||
const snapPoint1 = bottomSheetSnapPoint(Math.min(userIds.length, 5), USER_ROW_HEIGHT, bottom) + TITLE_HEIGHT;
|
||||
const snapPoint2 = height * 0.8;
|
||||
const snapPoints: BottomSheetProps['snapPoints'] = [1, Math.min(snapPoint1, snapPoint2)];
|
||||
const snapPoints: number[] = [1, Math.min(snapPoint1, snapPoint2)];
|
||||
if (userIds.length > 5 && snapPoint1 < snapPoint2) {
|
||||
snapPoints.push(snapPoint2);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import compose from 'lodash/fp/compose';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
|
||||
import {queryUsersById} from '@queries/servers/user';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import React, {useCallback, useRef} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import Button from 'react-native-button';
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import React, {useCallback, useMemo, useState} from 'react';
|
||||
import {map} from 'rxjs/operators';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import {of as of$, combineLatest} from 'rxjs';
|
||||
|
||||
import {Preferences} from '@constants';
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
|
||||
import {observeIfHighlightWithoutNotificationHasLicense} from '@queries/servers/system';
|
||||
import {observeCurrentUser} from '@queries/servers/user';
|
||||
|
||||
import Message from './message';
|
||||
|
|
@ -12,8 +12,10 @@ import type {WithDatabaseArgs} from '@typings/database/database';
|
|||
|
||||
const withMessageInput = withObservables([], ({database}: WithDatabaseArgs) => {
|
||||
const currentUser = observeCurrentUser(database);
|
||||
const isHighlightWithoutNotificationLicensed = observeIfHighlightWithoutNotificationHasLicense(database);
|
||||
return {
|
||||
currentUser,
|
||||
isHighlightWithoutNotificationLicensed,
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useMemo, useState} from 'react';
|
||||
import React, {useCallback, useState} from 'react';
|
||||
import {type LayoutChangeEvent, ScrollView, useWindowDimensions, View} from 'react-native';
|
||||
import Animated from 'react-native-reanimated';
|
||||
|
||||
|
|
@ -16,10 +16,11 @@ import ShowMoreButton from './show_more_button';
|
|||
|
||||
import type PostModel from '@typings/database/models/servers/post';
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
import type {SearchPattern} from '@typings/global/markdown';
|
||||
import type {HighlightWithoutNotificationKey, SearchPattern, UserMentionKey} from '@typings/global/markdown';
|
||||
|
||||
type MessageProps = {
|
||||
currentUser?: UserModel;
|
||||
isHighlightWithoutNotificationLicensed?: boolean;
|
||||
highlight: boolean;
|
||||
isEdited: boolean;
|
||||
isPendingOrFailed: boolean;
|
||||
|
|
@ -33,6 +34,9 @@ type MessageProps = {
|
|||
|
||||
const SHOW_MORE_HEIGHT = 54;
|
||||
|
||||
const EMPTY_MENTION_KEYS: UserMentionKey[] = [];
|
||||
const EMPTY_HIGHLIGHT_KEYS: HighlightWithoutNotificationKey[] = [];
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
return {
|
||||
messageContainer: {
|
||||
|
|
@ -52,7 +56,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
|||
};
|
||||
});
|
||||
|
||||
const Message = ({currentUser, highlight, isEdited, isPendingOrFailed, isReplyPost, layoutWidth, location, post, searchPatterns, theme}: MessageProps) => {
|
||||
const Message = ({currentUser, isHighlightWithoutNotificationLicensed, highlight, isEdited, isPendingOrFailed, isReplyPost, layoutWidth, location, post, searchPatterns, theme}: MessageProps) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [height, setHeight] = useState<number|undefined>();
|
||||
const dimensions = useWindowDimensions();
|
||||
|
|
@ -62,10 +66,6 @@ const Message = ({currentUser, highlight, isEdited, isPendingOrFailed, isReplyPo
|
|||
const blockStyles = getMarkdownBlockStyles(theme);
|
||||
const textStyles = getMarkdownTextStyles(theme);
|
||||
|
||||
const mentionKeys = useMemo(() => {
|
||||
return currentUser?.mentionKeys;
|
||||
}, [currentUser]);
|
||||
|
||||
const onLayout = useCallback((event: LayoutChangeEvent) => setHeight(event.nativeEvent.layout.height), []);
|
||||
const onPress = () => setOpen(!open);
|
||||
|
||||
|
|
@ -96,7 +96,8 @@ const Message = ({currentUser, highlight, isEdited, isPendingOrFailed, isReplyPo
|
|||
postId={post.id}
|
||||
textStyles={textStyles}
|
||||
value={post.message}
|
||||
mentionKeys={mentionKeys}
|
||||
mentionKeys={currentUser?.mentionKeys ?? EMPTY_MENTION_KEYS}
|
||||
highlightKeys={isHighlightWithoutNotificationLicensed ? (currentUser?.highlightKeys ?? EMPTY_HIGHLIGHT_KEYS) : EMPTY_HIGHLIGHT_KEYS}
|
||||
searchPatterns={searchPatterns}
|
||||
theme={theme}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import {combineLatest, of as of$} from 'rxjs';
|
||||
import {map, switchMap} from 'rxjs/operators';
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import React, {useCallback, useRef, useState} from 'react';
|
|||
import {useIntl} from 'react-intl';
|
||||
import {Keyboard, TouchableOpacity} from 'react-native';
|
||||
|
||||
import {addReaction, removeReaction} from '@actions/remote/reactions';
|
||||
import {addReaction, removeReaction, toggleReaction} from '@actions/remote/reactions';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {Screens} from '@constants';
|
||||
import {MAX_ALLOWED_REACTIONS} from '@constants/emoji';
|
||||
|
|
@ -100,8 +100,8 @@ const Reactions = ({currentUserId, canAddReaction, canRemoveReaction, disabled,
|
|||
return {reactionsByName, highlightedReactions};
|
||||
}, [sortedReactions, reactions]);
|
||||
|
||||
const handleAddReactionToPost = (emoji: string) => {
|
||||
addReaction(serverUrl, postId, emoji);
|
||||
const handleToggleReactionToPost = (emoji: string) => {
|
||||
toggleReaction(serverUrl, postId, emoji);
|
||||
};
|
||||
|
||||
const handleAddReaction = useCallback(preventDoubleTap(() => {
|
||||
|
|
@ -110,7 +110,7 @@ const Reactions = ({currentUserId, canAddReaction, canRemoveReaction, disabled,
|
|||
screen: Screens.EMOJI_PICKER,
|
||||
theme,
|
||||
title: formatMessage({id: 'mobile.post_info.add_reaction', defaultMessage: 'Add Reaction'}),
|
||||
props: {onEmojiPress: handleAddReactionToPost},
|
||||
props: {onEmojiPress: handleToggleReactionToPost},
|
||||
});
|
||||
}), [formatMessage, theme]);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
|
||||
import {observeTeamIdByThread, queryThreadParticipants} from '@queries/servers/thread';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {map, switchMap} from 'rxjs/operators';
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue