Compare commits

..

9 Commits

Author SHA1 Message Date
Evgeny Poberezkin
d6b62d0c18 5.3: ios 173, android 152, desktop 10 2023-09-22 21:09:36 +01:00
Evgeny Poberezkin
ba4c427bec ios: update core libraries 2023-09-22 17:43:03 +01:00
Evgeny Poberezkin
a179154e87 core: 5.3.0.10 2023-09-22 17:20:30 +01:00
Stanislav Dmitrenko
5eea3da7f4 desktop: check chat id before changing chat items (#3103) 2023-09-22 14:59:14 +01:00
Evgeny Poberezkin
b3e880ee54 core: optimize C apis (#3100)
* core: optimize C apis

* more

* fix tests

* use pokeByteOff

* write lazy bytestring to buffer without conversion to strict

* avoid conversion of JSON to strict bytestrings
2023-09-22 13:45:16 +01:00
Stanislav Dmitrenko
08ea5dc2e7 desktop: ability to send a video (#3102) 2023-09-22 12:43:45 +01:00
M. Sarmad Qadeer
20f90ee865 website: add feeds filter (#3098) 2023-09-22 09:39:18 +01:00
spaced4ndy
5ca2ab6138 ios: fix delete contact crashing from chat view (#3099) 2023-09-22 12:23:53 +04:00
Evgeny Poberezkin
ba61b15225 docs: update downloads 2023-09-22 08:21:00 +01:00
22 changed files with 160 additions and 126 deletions

View File

@@ -440,9 +440,9 @@ struct ChatInfoView: View {
do {
try await apiDeleteChat(type: chat.chatInfo.chatType, id: chat.chatInfo.apiId)
await MainActor.run {
chatModel.removeChat(chat.chatInfo.id)
chatModel.chatId = nil
dismiss()
chatModel.chatId = nil
chatModel.removeChat(chat.chatInfo.id)
}
} catch let error {
logger.error("deleteContactAlert apiDeleteChat error: \(responseError(error))")

View File

@@ -299,9 +299,9 @@ struct GroupChatInfoView: View {
do {
try await apiDeleteChat(type: chat.chatInfo.chatType, id: chat.chatInfo.apiId)
await MainActor.run {
chatModel.removeChat(chat.chatInfo.id)
chatModel.chatId = nil
dismiss()
chatModel.chatId = nil
chatModel.removeChat(chat.chatInfo.id)
}
} catch let error {
logger.error("deleteGroupAlert apiDeleteChat error: \(error.localizedDescription)")

View File

@@ -48,11 +48,11 @@
5C55A921283CCCB700C4E99E /* IncomingCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C55A920283CCCB700C4E99E /* IncomingCallView.swift */; };
5C55A923283CEDE600C4E99E /* SoundPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C55A922283CEDE600C4E99E /* SoundPlayer.swift */; };
5C55A92E283D0FDE00C4E99E /* sounds in Resources */ = {isa = PBXBuildFile; fileRef = 5C55A92D283D0FDE00C4E99E /* sounds */; };
5C5625062ABCBD3200A21210 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C5625012ABCBD3200A21210 /* libffi.a */; };
5C5625072ABCBD3200A21210 /* libHSsimplex-chat-5.3.0.9-JpoF1vnleecHyL9iiCdgEo-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C5625022ABCBD3200A21210 /* libHSsimplex-chat-5.3.0.9-JpoF1vnleecHyL9iiCdgEo-ghc8.10.7.a */; };
5C5625082ABCBD3200A21210 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C5625032ABCBD3200A21210 /* libgmp.a */; };
5C5625092ABCBD3200A21210 /* libHSsimplex-chat-5.3.0.9-JpoF1vnleecHyL9iiCdgEo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C5625042ABCBD3200A21210 /* libHSsimplex-chat-5.3.0.9-JpoF1vnleecHyL9iiCdgEo.a */; };
5C56250A2ABCBD3200A21210 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C5625052ABCBD3200A21210 /* libgmpxx.a */; };
5C5625102ABDFA8900A21210 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C56250B2ABDFA8900A21210 /* libffi.a */; };
5C5625112ABDFA8900A21210 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C56250C2ABDFA8900A21210 /* libgmp.a */; };
5C5625122ABDFA8900A21210 /* libHSsimplex-chat-5.3.0.10-9vCXcrdx54qEnAYPtVuU9m.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C56250D2ABDFA8900A21210 /* libHSsimplex-chat-5.3.0.10-9vCXcrdx54qEnAYPtVuU9m.a */; };
5C5625132ABDFA8900A21210 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C56250E2ABDFA8900A21210 /* libgmpxx.a */; };
5C5625142ABDFA8900A21210 /* libHSsimplex-chat-5.3.0.10-9vCXcrdx54qEnAYPtVuU9m-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C56250F2ABDFA8900A21210 /* libHSsimplex-chat-5.3.0.10-9vCXcrdx54qEnAYPtVuU9m-ghc8.10.7.a */; };
5C577F7D27C83AA10006112D /* MarkdownHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C577F7C27C83AA10006112D /* MarkdownHelp.swift */; };
5C58BCD6292BEBE600AF9E4F /* CIChatFeatureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C58BCD5292BEBE600AF9E4F /* CIChatFeatureView.swift */; };
5C5DB70E289ABDD200730FFF /* AppearanceSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C5DB70D289ABDD200730FFF /* AppearanceSettings.swift */; };
@@ -293,11 +293,11 @@
5C55A920283CCCB700C4E99E /* IncomingCallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncomingCallView.swift; sourceTree = "<group>"; };
5C55A922283CEDE600C4E99E /* SoundPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoundPlayer.swift; sourceTree = "<group>"; };
5C55A92D283D0FDE00C4E99E /* sounds */ = {isa = PBXFileReference; lastKnownFileType = folder; path = sounds; sourceTree = "<group>"; };
5C5625012ABCBD3200A21210 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
5C5625022ABCBD3200A21210 /* libHSsimplex-chat-5.3.0.9-JpoF1vnleecHyL9iiCdgEo-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.3.0.9-JpoF1vnleecHyL9iiCdgEo-ghc8.10.7.a"; sourceTree = "<group>"; };
5C5625032ABCBD3200A21210 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
5C5625042ABCBD3200A21210 /* libHSsimplex-chat-5.3.0.9-JpoF1vnleecHyL9iiCdgEo.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.3.0.9-JpoF1vnleecHyL9iiCdgEo.a"; sourceTree = "<group>"; };
5C5625052ABCBD3200A21210 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
5C56250B2ABDFA8900A21210 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
5C56250C2ABDFA8900A21210 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
5C56250D2ABDFA8900A21210 /* libHSsimplex-chat-5.3.0.10-9vCXcrdx54qEnAYPtVuU9m.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.3.0.10-9vCXcrdx54qEnAYPtVuU9m.a"; sourceTree = "<group>"; };
5C56250E2ABDFA8900A21210 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
5C56250F2ABDFA8900A21210 /* libHSsimplex-chat-5.3.0.10-9vCXcrdx54qEnAYPtVuU9m-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.3.0.10-9vCXcrdx54qEnAYPtVuU9m-ghc8.10.7.a"; sourceTree = "<group>"; };
5C577F7C27C83AA10006112D /* MarkdownHelp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownHelp.swift; sourceTree = "<group>"; };
5C58BCD5292BEBE600AF9E4F /* CIChatFeatureView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIChatFeatureView.swift; sourceTree = "<group>"; };
5C5B67912ABAF4B500DA9412 /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = "<group>"; };
@@ -507,13 +507,13 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
5C5625132ABDFA8900A21210 /* libgmpxx.a in Frameworks */,
5C5625122ABDFA8900A21210 /* libHSsimplex-chat-5.3.0.10-9vCXcrdx54qEnAYPtVuU9m.a in Frameworks */,
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
5C5625082ABCBD3200A21210 /* libgmp.a in Frameworks */,
5C5625062ABCBD3200A21210 /* libffi.a in Frameworks */,
5C5625142ABDFA8900A21210 /* libHSsimplex-chat-5.3.0.10-9vCXcrdx54qEnAYPtVuU9m-ghc8.10.7.a in Frameworks */,
5C5625102ABDFA8900A21210 /* libffi.a in Frameworks */,
5C5625112ABDFA8900A21210 /* libgmp.a in Frameworks */,
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
5C5625092ABCBD3200A21210 /* libHSsimplex-chat-5.3.0.9-JpoF1vnleecHyL9iiCdgEo.a in Frameworks */,
5C5625072ABCBD3200A21210 /* libHSsimplex-chat-5.3.0.9-JpoF1vnleecHyL9iiCdgEo-ghc8.10.7.a in Frameworks */,
5C56250A2ABCBD3200A21210 /* libgmpxx.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -574,11 +574,11 @@
5C764E5C279C70B7000C6508 /* Libraries */ = {
isa = PBXGroup;
children = (
5C5625012ABCBD3200A21210 /* libffi.a */,
5C5625032ABCBD3200A21210 /* libgmp.a */,
5C5625052ABCBD3200A21210 /* libgmpxx.a */,
5C5625022ABCBD3200A21210 /* libHSsimplex-chat-5.3.0.9-JpoF1vnleecHyL9iiCdgEo-ghc8.10.7.a */,
5C5625042ABCBD3200A21210 /* libHSsimplex-chat-5.3.0.9-JpoF1vnleecHyL9iiCdgEo.a */,
5C56250B2ABDFA8900A21210 /* libffi.a */,
5C56250C2ABDFA8900A21210 /* libgmp.a */,
5C56250E2ABDFA8900A21210 /* libgmpxx.a */,
5C56250F2ABDFA8900A21210 /* libHSsimplex-chat-5.3.0.10-9vCXcrdx54qEnAYPtVuU9m-ghc8.10.7.a */,
5C56250D2ABDFA8900A21210 /* libHSsimplex-chat-5.3.0.10-9vCXcrdx54qEnAYPtVuU9m.a */,
);
path = Libraries;
sourceTree = "<group>";
@@ -1486,7 +1486,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 172;
CURRENT_PROJECT_VERSION = 173;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
ENABLE_PREVIEWS = YES;
@@ -1528,7 +1528,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 172;
CURRENT_PROJECT_VERSION = 173;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
ENABLE_PREVIEWS = YES;
@@ -1608,7 +1608,7 @@
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 172;
CURRENT_PROJECT_VERSION = 173;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
GENERATE_INFOPLIST_FILE = YES;
@@ -1640,7 +1640,7 @@
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 172;
CURRENT_PROJECT_VERSION = 173;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
GENERATE_INFOPLIST_FILE = YES;
@@ -1672,7 +1672,7 @@
APPLICATION_EXTENSION_API_ONLY = YES;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 172;
CURRENT_PROJECT_VERSION = 173;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
DYLIB_COMPATIBILITY_VERSION = 1;
@@ -1718,7 +1718,7 @@
APPLICATION_EXTENSION_API_ONLY = YES;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 172;
CURRENT_PROJECT_VERSION = 173;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
DYLIB_COMPATIBILITY_VERSION = 1;

View File

@@ -309,7 +309,7 @@ actual suspend fun saveTempImageUncompressed(image: ImageBitmap, asPng: Boolean)
}
}
actual fun getBitmapFromVideo(uri: URI, timestamp: Long?, random: Boolean): VideoPlayerInterface.PreviewAndDuration {
actual suspend fun getBitmapFromVideo(uri: URI, timestamp: Long?, random: Boolean): VideoPlayerInterface.PreviewAndDuration {
val mmr = MediaMetadataRetriever()
mmr.setDataSource(androidAppContext, uri.toUri())
val durationMs = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLong()

View File

@@ -449,7 +449,7 @@ fun ChatLayout(
val images = groups[true] ?: emptyList()
val files = groups[false] ?: emptyList()
if (images.isNotEmpty()) {
composeState.processPickedMedia(images, null)
CoroutineScope(Dispatchers.IO).launch { composeState.processPickedMedia(images, null) }
} else if (files.isNotEmpty()) {
composeState.processPickedFile(uris.first(), null)
}
@@ -459,7 +459,7 @@ fun ChatLayout(
tmpFile.deleteOnExit()
chatModel.filesToDelete.add(tmpFile)
val uri = tmpFile.toURI()
composeState.processPickedMedia(listOf(uri), null)
CoroutineScope(Dispatchers.IO).launch { composeState.processPickedMedia(listOf(uri), null) }
},
onText = {
// Need to parse HTML in order to correctly display the content

View File

@@ -176,7 +176,7 @@ fun MutableState<ComposeState>.processPickedFile(uri: URI?, text: String?) {
}
}
fun MutableState<ComposeState>.processPickedMedia(uris: List<URI>, text: String?) {
suspend fun MutableState<ComposeState>.processPickedMedia(uris: List<URI>, text: String?) {
val content = ArrayList<UploadContent>()
val imagesPreview = ArrayList<String>()
uris.forEach { uri ->
@@ -237,7 +237,7 @@ fun ComposeView(
val textStyle = remember(MaterialTheme.colors.isLight) { mutableStateOf(smallFont) }
val recState: MutableState<RecordingState> = remember { mutableStateOf(RecordingState.NotStarted) }
AttachmentSelection(composeState, attachmentOption, composeState::processPickedFile, composeState::processPickedMedia)
AttachmentSelection(composeState, attachmentOption, composeState::processPickedFile) { uris, text -> CoroutineScope(Dispatchers.IO).launch { composeState.processPickedMedia(uris, text) } }
fun isSimplexLink(link: String): Boolean =
link.startsWith("https://simplex.chat", true) || link.startsWith("http://simplex.chat", true)

View File

@@ -117,9 +117,7 @@ fun groupChatAction(groupInfo: GroupInfo, chatModel: ChatModel) {
suspend fun openDirectChat(contactId: Long, chatModel: ChatModel) {
val chat = chatModel.controller.apiGetChat(ChatType.Direct, contactId)
if (chat != null) {
chatModel.chatItems.clear()
chatModel.chatItems.addAll(chat.chatItems)
chatModel.chatId.value = "@$contactId"
openChat(chat, chatModel)
}
}
@@ -139,11 +137,13 @@ suspend fun openChat(chat: Chat, chatModel: ChatModel) {
suspend fun apiLoadPrevMessages(chatInfo: ChatInfo, chatModel: ChatModel, beforeChatItemId: Long, search: String) {
val pagination = ChatPagination.Before(beforeChatItemId, ChatPagination.PRELOAD_COUNT)
val chat = chatModel.controller.apiGetChat(chatInfo.chatType, chatInfo.apiId, pagination, search) ?: return
if (chatModel.chatId.value != chat.id) return
chatModel.chatItems.addAll(0, chat.chatItems)
}
suspend fun apiFindMessages(chatInfo: ChatInfo, chatModel: ChatModel, search: String) {
val chat = chatModel.controller.apiGetChat(chatInfo.chatType, chatInfo.apiId, search = search) ?: return
if (chatModel.chatId.value != chat.id) return
chatModel.chatItems.clear()
chatModel.chatItems.addAll(0, chat.chatItems)
}

View File

@@ -267,7 +267,7 @@ fun getMaxFileSize(fileProtocol: FileProtocol): Long {
}
}
expect fun getBitmapFromVideo(uri: URI, timestamp: Long? = null, random: Boolean = true): VideoPlayerInterface.PreviewAndDuration
expect suspend fun getBitmapFromVideo(uri: URI, timestamp: Long? = null, random: Boolean = true): VideoPlayerInterface.PreviewAndDuration
fun Color.darker(factor: Float = 0.1f): Color =
Color(max(red * (1 - factor), 0f), max(green * (1 - factor), 0f), max(blue * (1 - factor), 0f), alpha)

View File

@@ -174,7 +174,7 @@ actual class VideoPlayer actual constructor(
private suspend fun setPreviewAndDuration() {
// It freezes main thread, doing it in IO thread
CoroutineScope(Dispatchers.IO).launch {
val previewAndDuration = VideoPlayerHolder.previewsAndDurations.getOrPut(uri) { getBitmapFromVideo() }
val previewAndDuration = VideoPlayerHolder.previewsAndDurations.getOrPut(uri) { getBitmapFromVideo(defaultPreview, uri) }
withContext(Dispatchers.Main) {
preview.value = previewAndDuration.preview ?: defaultPreview
duration.value = (previewAndDuration.duration ?: 0)
@@ -182,23 +182,6 @@ actual class VideoPlayer actual constructor(
}
}
private suspend fun getBitmapFromVideo(): VideoPlayerInterface.PreviewAndDuration {
val player = CallbackMediaPlayerComponent().mediaPlayer()
val filepath = getAppFilePath(uri)
if (filepath == null || !File(filepath).exists()) {
return VideoPlayerInterface.PreviewAndDuration(preview = defaultPreview, timestamp = 0L, duration = 0L)
}
player.media().startPaused(filepath)
val start = System.currentTimeMillis()
while (player.snapshots()?.get() == null && start + 5000 > System.currentTimeMillis()) {
delay(10)
}
val preview = player.snapshots()?.get()?.toComposeImageBitmap()
val duration = player.duration.toLong()
CoroutineScope(Dispatchers.IO).launch { player.release() }
return VideoPlayerInterface.PreviewAndDuration(preview = preview, timestamp = 0L, duration = duration)
}
private fun initializeMediaPlayerComponent(): Component {
return if (desktopPlatform.isMac()) {
CallbackMediaPlayerComponent()
@@ -212,4 +195,22 @@ actual class VideoPlayer actual constructor(
is EmbeddedMediaPlayerComponent -> mediaPlayer()
else -> error("mediaPlayer() can only be called on vlcj player components")
}
companion object {
suspend fun getBitmapFromVideo(defaultPreview: ImageBitmap?, uri: URI?): VideoPlayerInterface.PreviewAndDuration {
val player = CallbackMediaPlayerComponent().mediaPlayer()
if (uri == null || !File(uri.rawPath).exists()) {
return VideoPlayerInterface.PreviewAndDuration(preview = defaultPreview, timestamp = 0L, duration = 0L)
}
player.media().startPaused(uri.toString().replaceFirst("file:", "file://"))
val start = System.currentTimeMillis()
while (player.snapshots()?.get() == null && start + 5000 > System.currentTimeMillis()) {
delay(10)
}
val preview = player.snapshots()?.get()?.toComposeImageBitmap()
val duration = player.duration.toLong()
CoroutineScope(Dispatchers.IO).launch { player.release() }
return VideoPlayerInterface.PreviewAndDuration(preview = preview, timestamp = 0L, duration = duration)
}
}
}

View File

@@ -11,10 +11,14 @@ import dev.icerock.moko.resources.compose.stringResource
@Composable
actual fun ChooseAttachmentButtons(attachmentOption: MutableState<AttachmentOption?>, hide: () -> Unit) {
ActionButton(Modifier.fillMaxWidth(0.5f), null, stringResource(MR.strings.gallery_image_button), icon = painterResource(MR.images.ic_add_photo)) {
ActionButton(Modifier.fillMaxWidth(0.33f), null, stringResource(MR.strings.gallery_image_button), icon = painterResource(MR.images.ic_add_photo)) {
attachmentOption.value = AttachmentOption.GalleryImage
hide()
}
ActionButton(Modifier.fillMaxWidth(0.5f), null, stringResource(MR.strings.gallery_video_button), icon = painterResource(MR.images.ic_smart_display)) {
attachmentOption.value = AttachmentOption.GalleryVideo
hide()
}
ActionButton(Modifier.fillMaxWidth(1f), null, stringResource(MR.strings.choose_file), icon = painterResource(MR.images.ic_note_add)) {
attachmentOption.value = AttachmentOption.File
hide()

View File

@@ -132,8 +132,8 @@ actual suspend fun saveTempImageUncompressed(image: ImageBitmap, asPng: Boolean)
} else null
}
actual fun getBitmapFromVideo(uri: URI, timestamp: Long?, random: Boolean): VideoPlayerInterface.PreviewAndDuration {
return VideoPlayerInterface.PreviewAndDuration(preview = null, timestamp = 0L, duration = 0L)
actual suspend fun getBitmapFromVideo(uri: URI, timestamp: Long?, random: Boolean): VideoPlayerInterface.PreviewAndDuration {
return VideoPlayer.getBitmapFromVideo(null, uri)
}
@OptIn(ExperimentalEncodingApi::class)

View File

@@ -25,11 +25,11 @@ android.nonTransitiveRClass=true
android.enableJetifier=true
kotlin.mpp.androidSourceSetLayoutVersion=2
android.version_name=5.3-beta.9
android.version_code=151
android.version_name=5.3
android.version_code=152
desktop.version_name=1.7.0
desktop.version_code=9
desktop.version_name=5.3
desktop.version_code=10
kotlin.version=1.8.20
gradle.plugin.version=7.4.2

View File

@@ -15,28 +15,28 @@ revision: 20.09.2023
<img src="/docs/images/simplex-desktop-light.png" alt="desktop app" width=500>
The latest version of desktop app is v5.3-beta.8 (1.6.0 in the app).
The latest version of desktop app is v5.3-beta.9 (1.6.0 in the app).
Using the same profile as on mobile device is not yet supported you need to create a separate profile to use desktop apps.
**Linux**: [AppImage](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.0-beta.8/simplex-desktop-x86_64.AppImage) (most Linux distros), [Ubuntu 20.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.0-beta.8/simplex-desktop-ubuntu-20_04-x86_64.deb) (and Debian-based distros), [Ubuntu 22.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.0-beta.8/simplex-desktop-ubuntu-22_04-x86_64.deb).
**Linux**: [AppImage](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.0-beta.9/simplex-desktop-x86_64.AppImage) (most Linux distros), [Ubuntu 20.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.0-beta.9/simplex-desktop-ubuntu-20_04-x86_64.deb) (and Debian-based distros), [Ubuntu 22.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.0-beta.9/simplex-desktop-ubuntu-22_04-x86_64.deb).
**Mac**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.0-beta.8/simplex-desktop-macos-x86_64.dmg) (Intel), [aarch64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.0-beta.8/simplex-desktop-macos-aarch64.dmg) (Apple Silicon).
**Mac**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.0-beta.9/simplex-desktop-macos-x86_64.dmg) (Intel), [aarch64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.0-beta.9/simplex-desktop-macos-aarch64.dmg) (Apple Silicon).
**Windows**: coming soon.
## Mobile apps
**iOS**: [App store](https://apps.apple.com/us/app/simplex-chat/id1605771084) (v5.2.3), [TestFlight](https://testflight.apple.com/join/DWuT2LQu) (v5.3-beta.8).
**iOS**: [App store](https://apps.apple.com/us/app/simplex-chat/id1605771084) (v5.2.3), [TestFlight](https://testflight.apple.com/join/DWuT2LQu) (v5.3-beta.9).
**Android**: [Play store](https://play.google.com/store/apps/details?id=chat.simplex.app), [F-Droid](https://simplex.chat/fdroid/), [APK aarch64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.0-beta.8/simplex.apk), [APK armv7](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.0-beta.8/simplex-armv7a.apk).
**Android**: [Play store](https://play.google.com/store/apps/details?id=chat.simplex.app), [F-Droid](https://simplex.chat/fdroid/), [APK aarch64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.0-beta.9/simplex.apk), [APK armv7](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.0-beta.9/simplex-armv7a.apk).
## Terminal (console) app
See [Using terminal app](/docs/CLI.md).
**Linux**: [Ubuntu 20.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.0-beta.8/simplex-chat-ubuntu-20_04-x86-64), [Ubuntu 22.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.0-beta.8/simplex-chat-ubuntu-22_04-x86-64).
**Linux**: [Ubuntu 20.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.0-beta.9/simplex-chat-ubuntu-20_04-x86-64), [Ubuntu 22.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.0-beta.9/simplex-chat-ubuntu-22_04-x86-64).
**Mac** [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.0-beta.8/simplex-chat-macos-x86-64), aarch64 - [compile from source](./CLI.md#).
**Mac** [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.0-beta.9/simplex-chat-macos-x86-64), aarch64 - [compile from source](./CLI.md#).
**Windows**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.0-beta.8/simplex-chat-windows-x86-64).
**Windows**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.0-beta.9/simplex-chat-windows-x86-64).

View File

@@ -1,5 +1,5 @@
name: simplex-chat
version: 5.3.0.9
version: 5.3.0.10
#synopsis:
#description:
homepage: https://github.com/simplex-chat/simplex-chat#readme

View File

@@ -5,7 +5,7 @@ cabal-version: 1.12
-- see: https://github.com/sol/hpack
name: simplex-chat
version: 5.3.0.9
version: 5.3.0.10
category: Web, System, Services, Cryptography
homepage: https://github.com/simplex-chat/simplex-chat#readme
author: simplex.chat

View File

@@ -14,14 +14,12 @@ import Data.Aeson (ToJSON (..))
import qualified Data.Aeson as J
import Data.Bifunctor (first)
import qualified Data.ByteString.Base64.URL as U
import Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as B
import qualified Data.ByteString.Lazy.Char8 as LB
import Data.Functor (($>))
import Data.List (find)
import qualified Data.List.NonEmpty as L
import Data.Maybe (fromMaybe)
import qualified Data.Text as T
import Data.Text.Encoding (encodeUtf8)
import Data.Word (Word8)
import Database.SQLite.Simple (SQLError (..))
import qualified Database.SQLite.Simple as DB
@@ -95,36 +93,36 @@ cChatMigrateInit fp key conf ctrl = do
chatMigrateInit dbPath dbKey confirm >>= \case
Right cc -> (newStablePtr cc >>= poke ctrl) $> DBMOk
Left e -> pure e
newCAString . LB.unpack $ J.encode r
newCStringFromLazyBS $ J.encode r
-- | send command to chat (same syntax as in terminal for now)
cChatSendCmd :: StablePtr ChatController -> CString -> IO CJSONString
cChatSendCmd cPtr cCmd = do
c <- deRefStablePtr cPtr
cmd <- peekCAString cCmd
newCAString =<< chatSendCmd c cmd
cmd <- B.packCString cCmd
newCStringFromLazyBS =<< chatSendCmd c cmd
-- | receive message from chat (blocking)
cChatRecvMsg :: StablePtr ChatController -> IO CJSONString
cChatRecvMsg cc = deRefStablePtr cc >>= chatRecvMsg >>= newCAString
cChatRecvMsg cc = deRefStablePtr cc >>= chatRecvMsg >>= newCStringFromLazyBS
-- | receive message from chat (blocking up to `t` microseconds (1/10^6 sec), returns empty string if times out)
cChatRecvMsgWait :: StablePtr ChatController -> CInt -> IO CJSONString
cChatRecvMsgWait cc t = deRefStablePtr cc >>= (`chatRecvMsgWait` fromIntegral t) >>= newCAString
cChatRecvMsgWait cc t = deRefStablePtr cc >>= (`chatRecvMsgWait` fromIntegral t) >>= newCStringFromLazyBS
-- | parse markdown - returns ParsedMarkdown type JSON
cChatParseMarkdown :: CString -> IO CJSONString
cChatParseMarkdown s = newCAString . chatParseMarkdown =<< peekCAString s
cChatParseMarkdown s = newCStringFromLazyBS . chatParseMarkdown =<< B.packCString s
-- | parse server address - returns ParsedServerAddress JSON
cChatParseServer :: CString -> IO CJSONString
cChatParseServer s = newCAString . chatParseServer =<< peekCAString s
cChatParseServer s = newCStringFromLazyBS . chatParseServer =<< B.packCString s
cChatPasswordHash :: CString -> CString -> IO CString
cChatPasswordHash cPwd cSalt = do
pwd <- peekCAString cPwd
salt <- peekCAString cSalt
newCAString $ chatPasswordHash pwd salt
pwd <- B.packCString cPwd
salt <- B.packCString cSalt
newCStringFromBS $ chatPasswordHash pwd salt
mobileChatOpts :: String -> String -> ChatOpts
mobileChatOpts dbFilePrefix dbKey =
@@ -197,22 +195,22 @@ chatMigrateInit dbFilePrefix dbKey confirm = runExceptT $ do
_ -> dbError e
dbError e = Left . DBMErrorSQL dbFile $ show e
chatSendCmd :: ChatController -> String -> IO JSONString
chatSendCmd cc s = LB.unpack . J.encode . APIResponse Nothing <$> runReaderT (execChatCommand $ B.pack s) cc
chatSendCmd :: ChatController -> ByteString -> IO JSONByteString
chatSendCmd cc s = J.encode . APIResponse Nothing <$> runReaderT (execChatCommand s) cc
chatRecvMsg :: ChatController -> IO JSONString
chatRecvMsg :: ChatController -> IO JSONByteString
chatRecvMsg ChatController {outputQ} = json <$> atomically (readTBQueue outputQ)
where
json (corr, resp) = LB.unpack $ J.encode APIResponse {corr, resp}
json (corr, resp) = J.encode APIResponse {corr, resp}
chatRecvMsgWait :: ChatController -> Int -> IO JSONString
chatRecvMsgWait :: ChatController -> Int -> IO JSONByteString
chatRecvMsgWait cc time = fromMaybe "" <$> timeout time (chatRecvMsg cc)
chatParseMarkdown :: String -> JSONString
chatParseMarkdown = LB.unpack . J.encode . ParsedMarkdown . parseMaybeMarkdownList . safeDecodeUtf8 . B.pack
chatParseMarkdown :: ByteString -> JSONByteString
chatParseMarkdown = J.encode . ParsedMarkdown . parseMaybeMarkdownList . safeDecodeUtf8
chatParseServer :: String -> JSONString
chatParseServer = LB.unpack . J.encode . toServerAddress . strDecode . B.pack
chatParseServer :: ByteString -> JSONByteString
chatParseServer = J.encode . toServerAddress . strDecode
where
toServerAddress :: Either String AProtoServerWithAuth -> ParsedServerAddress
toServerAddress = \case
@@ -223,11 +221,11 @@ chatParseServer = LB.unpack . J.encode . toServerAddress . strDecode . B.pack
enc :: StrEncoding a => a -> String
enc = B.unpack . strEncode
chatPasswordHash :: String -> String -> String
chatPasswordHash :: ByteString -> ByteString -> ByteString
chatPasswordHash pwd salt = either (const "") passwordHash salt'
where
salt' = U.decode $ B.pack salt
passwordHash = B.unpack . U.encode . C.sha512Hash . (encodeUtf8 (T.pack pwd) <>)
salt' = U.decode salt
passwordHash = U.encode . C.sha512Hash . (pwd <>)
data APIResponse = APIResponse {corr :: Maybe CorrId, resp :: ChatResponse}
deriving (Generic)

View File

@@ -27,11 +27,11 @@ import qualified Data.ByteString.Lazy as LB
import qualified Data.ByteString.Lazy.Char8 as LB'
import Data.Char (chr)
import Data.Either (fromLeft)
import Data.Word (Word8, Word32)
import Data.Word (Word32, Word8)
import Foreign.C
import Foreign.Marshal.Alloc (mallocBytes)
import Foreign.Ptr
import Foreign.Storable (poke)
import Foreign.Storable (poke, pokeByteOff)
import GHC.Generics (Generic)
import Simplex.Chat.Mobile.Shared
import Simplex.Chat.Util (chunkSize, encryptFile)
@@ -54,7 +54,7 @@ cChatWriteFile cPath ptr len = do
path <- peekCString cPath
s <- getByteString ptr len
r <- chatWriteFile path s
newCAString $ LB'.unpack $ J.encode r
newCStringFromLazyBS $ J.encode r
chatWriteFile :: FilePath -> ByteString -> IO WriteFileResult
chatWriteFile path s = do
@@ -78,12 +78,11 @@ cChatReadFile cPath cKey cNonce = do
chatReadFile path key nonce >>= \case
Left e -> castPtr <$> newCString (chr 1 : e)
Right s -> do
let s' = LB.toStrict s
len = B.length s'
let len = fromIntegral $ LB.length s
ptr <- mallocBytes $ len + 5
poke ptr 0
poke (ptr `plusPtr` 1) (fromIntegral len :: Word32)
putByteString (ptr `plusPtr` 5) s'
poke ptr (0 :: Word8)
pokeByteOff ptr 1 (fromIntegral len :: Word32)
putLazyByteString (ptr `plusPtr` 5) s
pure ptr
chatReadFile :: FilePath -> ByteString -> ByteString -> IO (Either String LB.ByteString)

View File

@@ -1,19 +1,48 @@
{-# LANGUAGE LambdaCase #-}
module Simplex.Chat.Mobile.Shared where
import qualified Data.ByteString as B
import Data.ByteString.Internal (ByteString (PS), memcpy)
import Data.ByteString.Internal (ByteString (..), memcpy)
import qualified Data.ByteString.Lazy as LB
import qualified Data.ByteString.Lazy.Internal as LB
import Foreign.C (CInt, CString)
import Foreign (Ptr, Word8, newForeignPtr_, plusPtr)
import Foreign.ForeignPtr.Unsafe
import Foreign
type CJSONString = CString
type JSONByteString = LB.ByteString
getByteString :: Ptr Word8 -> CInt -> IO ByteString
getByteString ptr len = do
fp <- newForeignPtr_ ptr
pure $ PS fp 0 $ fromIntegral len
pure $ BS fp $ fromIntegral len
{-# INLINE getByteString #-}
putByteString :: Ptr Word8 -> ByteString -> IO ()
putByteString ptr bs@(PS fp offset _) = do
let p = unsafeForeignPtrToPtr fp `plusPtr` offset
memcpy ptr p $ B.length bs
putByteString ptr (BS fp len) =
withForeignPtr fp $ \p -> memcpy ptr p len
{-# INLINE putByteString #-}
putLazyByteString :: Ptr Word8 -> LB.ByteString -> IO ()
putLazyByteString ptr = \case
LB.Empty -> pure ()
LB.Chunk ch s -> do
putByteString ptr ch
putLazyByteString (ptr `plusPtr` B.length ch) s
newCStringFromBS :: ByteString -> IO CString
newCStringFromBS s = do
let len = B.length s
buf <- mallocBytes (len + 1)
putByteString buf s
pokeByteOff buf len (0 :: Word8)
pure $ castPtr buf
newCStringFromLazyBS :: LB.ByteString -> IO CString
newCStringFromLazyBS s = do
let len = fromIntegral $ LB.length s
buf <- mallocBytes (len + 1)
putLazyByteString buf s
pokeByteOff buf len (0 :: Word8)
pure $ castPtr buf

View File

@@ -1393,8 +1393,6 @@ serializeIntroStatus = \case
data Notification = Notification {title :: Text, text :: Text}
type JSONString = String
textParseJSON :: TextEncoding a => String -> J.Value -> JT.Parser a
textParseJSON name = J.withText name $ maybe (fail $ "bad " <> name) pure . textDecode

View File

@@ -1,4 +1,5 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
@@ -61,66 +62,66 @@ mobileTests = do
it "utf8 name 2" $ testFileEncryptionCApi "👍"
it "no exception on missing file" testMissingFileEncryptionCApi
noActiveUser :: String
noActiveUser :: LB.ByteString
#if defined(darwin_HOST_OS) && defined(swiftJSON)
noActiveUser = "{\"resp\":{\"chatCmdError\":{\"chatError\":{\"error\":{\"errorType\":{\"noActiveUser\":{}}}}}}}"
#else
noActiveUser = "{\"resp\":{\"type\":\"chatCmdError\",\"chatError\":{\"type\":\"error\",\"errorType\":{\"type\":\"noActiveUser\"}}}}"
#endif
activeUserExists :: String
activeUserExists :: LB.ByteString
#if defined(darwin_HOST_OS) && defined(swiftJSON)
activeUserExists = "{\"resp\":{\"chatCmdError\":{\"user_\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true},\"chatError\":{\"error\":{\"errorType\":{\"userExists\":{\"contactName\":\"alice\"}}}}}}}"
#else
activeUserExists = "{\"resp\":{\"type\":\"chatCmdError\",\"user_\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true},\"chatError\":{\"type\":\"error\",\"errorType\":{\"type\":\"userExists\",\"contactName\":\"alice\"}}}}"
#endif
activeUser :: String
activeUser :: LB.ByteString
#if defined(darwin_HOST_OS) && defined(swiftJSON)
activeUser = "{\"resp\":{\"activeUser\":{\"user\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}}}}"
#else
activeUser = "{\"resp\":{\"type\":\"activeUser\",\"user\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}}}"
#endif
chatStarted :: String
chatStarted :: LB.ByteString
#if defined(darwin_HOST_OS) && defined(swiftJSON)
chatStarted = "{\"resp\":{\"chatStarted\":{}}}"
#else
chatStarted = "{\"resp\":{\"type\":\"chatStarted\"}}"
#endif
contactSubSummary :: String
contactSubSummary :: LB.ByteString
#if defined(darwin_HOST_OS) && defined(swiftJSON)
contactSubSummary = "{\"resp\":{\"contactSubSummary\":{" <> userJSON <> ",\"contactSubscriptions\":[]}}}"
#else
contactSubSummary = "{\"resp\":{\"type\":\"contactSubSummary\"," <> userJSON <> ",\"contactSubscriptions\":[]}}"
#endif
memberSubSummary :: String
memberSubSummary :: LB.ByteString
#if defined(darwin_HOST_OS) && defined(swiftJSON)
memberSubSummary = "{\"resp\":{\"memberSubSummary\":{" <> userJSON <> ",\"memberSubscriptions\":[]}}}"
#else
memberSubSummary = "{\"resp\":{\"type\":\"memberSubSummary\"," <> userJSON <> ",\"memberSubscriptions\":[]}}"
#endif
userContactSubSummary :: String
userContactSubSummary :: LB.ByteString
#if defined(darwin_HOST_OS) && defined(swiftJSON)
userContactSubSummary = "{\"resp\":{\"userContactSubSummary\":{" <> userJSON <> ",\"userContactSubscriptions\":[]}}}"
#else
userContactSubSummary = "{\"resp\":{\"type\":\"userContactSubSummary\"," <> userJSON <> ",\"userContactSubscriptions\":[]}}"
#endif
pendingSubSummary :: String
pendingSubSummary :: LB.ByteString
#if defined(darwin_HOST_OS) && defined(swiftJSON)
pendingSubSummary = "{\"resp\":{\"pendingSubSummary\":{" <> userJSON <> ",\"pendingSubscriptions\":[]}}}"
#else
pendingSubSummary = "{\"resp\":{\"type\":\"pendingSubSummary\"," <> userJSON <> ",\"pendingSubscriptions\":[]}}"
#endif
userJSON :: String
userJSON :: LB.ByteString
userJSON = "\"user\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}"
parsedMarkdown :: String
parsedMarkdown :: LB.ByteString
#if defined(darwin_HOST_OS) && defined(swiftJSON)
parsedMarkdown = "{\"formattedText\":[{\"format\":{\"bold\":{}},\"text\":\"hello\"}]}"
#else

View File

@@ -23,6 +23,7 @@ metadata:
<email>{{ metadata.author.email }}</email>
</author>
{%- for blog in collections.blogs | reverse %}
{%- if not blog.data.draft %}
{%- set absolutePostUrl = blog.url | absoluteUrl(metadata.url) %}
<entry>
<title>{{ blog.data.title }}</title>
@@ -33,5 +34,6 @@ metadata:
<content xml:lang="{{ metadata.language }}" type="html">{{ blog.templateContent | htmlToAbsoluteUrls(absolutePostUrl) }}</content>
{# <content xml:lang="{{ metadata.language }}" type="html">{{ blog.templateContent | striptags | truncate(200) }}</content> #}
</entry>
{%- endif %}
{%- endfor %}
</feed>

View File

@@ -19,6 +19,7 @@ metadata:
<description>{{ metadata.subtitle }}</description>
<language>{{ metadata.language }}</language>
{%- for blog in collections.blogs %}
{%- if not blog.data.draft %}
{%- set absolutePostUrl = blog.url | absoluteUrl(metadata.url) %}
<item>
<title>{{ blog.data.title }}</title>
@@ -30,6 +31,7 @@ metadata:
<dc:creator>{{ metadata.author.name }}</dc:creator>
<guid>{{ absolutePostUrl }}</guid>
</item>
{%- endif %}
{%- endfor %}
</channel>
</rss>