mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-09-01 22:18:43 +00:00
Merge branch 'master' into chat-relays
This commit is contained in:
@@ -322,6 +322,7 @@ func messageText(
|
||||
var bold: UIFont?
|
||||
var italic: UIFont?
|
||||
var snippet: UIFont?
|
||||
var small: UIFont?
|
||||
var mention: UIFont?
|
||||
var secretIdx: Int = 0
|
||||
for ft in fts {
|
||||
@@ -353,6 +354,10 @@ func messageText(
|
||||
attrs[.backgroundColor] = secretColor
|
||||
}
|
||||
hasSecrets = true
|
||||
case .small:
|
||||
small = small ?? UIFont.preferredFont(forTextStyle: .footnote)
|
||||
attrs[.font] = small
|
||||
attrs[.foregroundColor] = UIColor.secondaryLabel
|
||||
case let .colored(color):
|
||||
if let c = color.uiColor {
|
||||
attrs[.foregroundColor] = UIColor(c)
|
||||
|
||||
@@ -182,8 +182,8 @@
|
||||
64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; };
|
||||
64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; };
|
||||
64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; };
|
||||
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-3esYZFBUokREq84LvcOgzJ-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-3esYZFBUokREq84LvcOgzJ-ghc9.6.3.a */; };
|
||||
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-3esYZFBUokREq84LvcOgzJ.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-3esYZFBUokREq84LvcOgzJ.a */; };
|
||||
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-IckAKQLBKZZ3c4EBa1qhzo-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-IckAKQLBKZZ3c4EBa1qhzo-ghc9.6.3.a */; };
|
||||
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-IckAKQLBKZZ3c4EBa1qhzo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-IckAKQLBKZZ3c4EBa1qhzo.a */; };
|
||||
64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; };
|
||||
64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; };
|
||||
64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; };
|
||||
@@ -553,8 +553,8 @@
|
||||
64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = "<group>"; };
|
||||
64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-3esYZFBUokREq84LvcOgzJ-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.9-3esYZFBUokREq84LvcOgzJ-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-3esYZFBUokREq84LvcOgzJ.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.9-3esYZFBUokREq84LvcOgzJ.a"; sourceTree = "<group>"; };
|
||||
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-IckAKQLBKZZ3c4EBa1qhzo-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.9-IckAKQLBKZZ3c4EBa1qhzo-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-IckAKQLBKZZ3c4EBa1qhzo.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.9-IckAKQLBKZZ3c4EBa1qhzo.a"; sourceTree = "<group>"; };
|
||||
64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = "<group>"; };
|
||||
64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = "<group>"; };
|
||||
@@ -716,8 +716,8 @@
|
||||
64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */,
|
||||
64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */,
|
||||
64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */,
|
||||
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-3esYZFBUokREq84LvcOgzJ-ghc9.6.3.a in Frameworks */,
|
||||
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-3esYZFBUokREq84LvcOgzJ.a in Frameworks */,
|
||||
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-IckAKQLBKZZ3c4EBa1qhzo-ghc9.6.3.a in Frameworks */,
|
||||
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-IckAKQLBKZZ3c4EBa1qhzo.a in Frameworks */,
|
||||
CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -803,8 +803,8 @@
|
||||
64C829992D54AEEE006B9E89 /* libffi.a */,
|
||||
64C829982D54AEED006B9E89 /* libgmp.a */,
|
||||
64C8299C2D54AEEE006B9E89 /* libgmpxx.a */,
|
||||
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-3esYZFBUokREq84LvcOgzJ-ghc9.6.3.a */,
|
||||
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-3esYZFBUokREq84LvcOgzJ.a */,
|
||||
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-IckAKQLBKZZ3c4EBa1qhzo-ghc9.6.3.a */,
|
||||
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-IckAKQLBKZZ3c4EBa1qhzo.a */,
|
||||
);
|
||||
path = Libraries;
|
||||
sourceTree = "<group>";
|
||||
|
||||
@@ -4756,6 +4756,7 @@ public enum Format: Decodable, Equatable, Hashable {
|
||||
case strikeThrough
|
||||
case snippet
|
||||
case secret
|
||||
case small
|
||||
case colored(color: FormatColor)
|
||||
case uri
|
||||
case hyperLink(showText: String?, linkUri: String)
|
||||
|
||||
@@ -4455,6 +4455,7 @@ sealed class Format {
|
||||
@Serializable @SerialName("strikeThrough") class StrikeThrough: Format()
|
||||
@Serializable @SerialName("snippet") class Snippet: Format()
|
||||
@Serializable @SerialName("secret") class Secret: Format()
|
||||
@Serializable @SerialName("small") class Small: Format()
|
||||
@Serializable @SerialName("colored") class Colored(val color: FormatColor): Format()
|
||||
@Serializable @SerialName("uri") class Uri: Format()
|
||||
@Serializable @SerialName("hyperLink") class HyperLink(val showText: String?, val linkUri: String): Format()
|
||||
@@ -4476,6 +4477,7 @@ sealed class Format {
|
||||
is StrikeThrough -> SpanStyle(textDecoration = TextDecoration.LineThrough)
|
||||
is Snippet -> SpanStyle(fontFamily = FontFamily.Monospace)
|
||||
is Secret -> SpanStyle(color = Color.Transparent, background = SecretColor)
|
||||
is Small -> SpanStyle(fontSize = MaterialTheme.typography.body2.fontSize, color = MaterialTheme.colors.secondary)
|
||||
is Colored -> SpanStyle(color = this.color.uiColor)
|
||||
is Uri -> linkStyle
|
||||
is HyperLink -> linkStyle
|
||||
|
||||
+1
@@ -153,6 +153,7 @@ fun MarkdownText (
|
||||
is Format.Italic -> withStyle(ft.format.style) { append(ft.text) }
|
||||
is Format.StrikeThrough -> withStyle(ft.format.style) { append(ft.text) }
|
||||
is Format.Snippet -> withStyle(ft.format.style) { append(ft.text) }
|
||||
is Format.Small -> withStyle(ft.format.style) { append(ft.text) }
|
||||
is Format.Colored -> withStyle(ft.format.style) { append(ft.text) }
|
||||
is Format.Secret -> {
|
||||
val ftStyle = ft.format.style
|
||||
|
||||
@@ -52,6 +52,9 @@ This file is generated automatically.
|
||||
- [APIListContacts](#apilistcontacts)
|
||||
- [APIListGroups](#apilistgroups)
|
||||
- [APIDeleteChat](#apideletechat)
|
||||
- [APISetGroupCustomData](#apisetgroupcustomdata)
|
||||
- [APISetContactCustomData](#apisetcontactcustomdata)
|
||||
- [APISetUserAutoAcceptMemberContacts](#apisetuserautoacceptmembercontacts)
|
||||
|
||||
[User profile commands](#user-profile-commands)
|
||||
- [ShowActiveUser](#showactiveuser)
|
||||
@@ -1613,6 +1616,117 @@ ChatCmdError: Command error (only used in WebSockets API).
|
||||
---
|
||||
|
||||
|
||||
### APISetGroupCustomData
|
||||
|
||||
Set group custom data.
|
||||
|
||||
*Network usage*: no.
|
||||
|
||||
**Parameters**:
|
||||
- groupId: int64
|
||||
- customData: JSONObject?
|
||||
|
||||
**Syntax**:
|
||||
|
||||
```
|
||||
/_set custom #<groupId>[ <json(customData)>]
|
||||
```
|
||||
|
||||
```javascript
|
||||
'/_set custom #' + groupId + (customData ? ' ' + JSON.stringify(customData) : '') // JavaScript
|
||||
```
|
||||
|
||||
```python
|
||||
'/_set custom #' + str(groupId) + ((' ' + json.dumps(customData)) if customData is not None else '') # Python
|
||||
```
|
||||
|
||||
**Responses**:
|
||||
|
||||
CmdOk: Ok.
|
||||
- type: "cmdOk"
|
||||
- user_: [User](./TYPES.md#user)?
|
||||
|
||||
ChatCmdError: Command error (only used in WebSockets API).
|
||||
- type: "chatCmdError"
|
||||
- chatError: [ChatError](./TYPES.md#chaterror)
|
||||
|
||||
---
|
||||
|
||||
|
||||
### APISetContactCustomData
|
||||
|
||||
Set contact custom data.
|
||||
|
||||
*Network usage*: no.
|
||||
|
||||
**Parameters**:
|
||||
- contactId: int64
|
||||
- customData: JSONObject?
|
||||
|
||||
**Syntax**:
|
||||
|
||||
```
|
||||
/_set custom @<contactId>[ <json(customData)>]
|
||||
```
|
||||
|
||||
```javascript
|
||||
'/_set custom @' + contactId + (customData ? ' ' + JSON.stringify(customData) : '') // JavaScript
|
||||
```
|
||||
|
||||
```python
|
||||
'/_set custom @' + str(contactId) + ((' ' + json.dumps(customData)) if customData is not None else '') # Python
|
||||
```
|
||||
|
||||
**Responses**:
|
||||
|
||||
CmdOk: Ok.
|
||||
- type: "cmdOk"
|
||||
- user_: [User](./TYPES.md#user)?
|
||||
|
||||
ChatCmdError: Command error (only used in WebSockets API).
|
||||
- type: "chatCmdError"
|
||||
- chatError: [ChatError](./TYPES.md#chaterror)
|
||||
|
||||
---
|
||||
|
||||
|
||||
### APISetUserAutoAcceptMemberContacts
|
||||
|
||||
Set auto-accept member contacts.
|
||||
|
||||
*Network usage*: no.
|
||||
|
||||
**Parameters**:
|
||||
- userId: int64
|
||||
- onOff: bool
|
||||
|
||||
**Syntax**:
|
||||
|
||||
```
|
||||
/_set accept member contacts <userId> on|off
|
||||
```
|
||||
|
||||
```javascript
|
||||
'/_set accept member contacts ' + userId + ' ' + (onOff ? 'on' : 'off') // JavaScript
|
||||
```
|
||||
|
||||
```python
|
||||
'/_set accept member contacts ' + str(userId) + ' ' + ('on' if onOff else 'off') # Python
|
||||
```
|
||||
|
||||
**Responses**:
|
||||
|
||||
CmdOk: Ok.
|
||||
- type: "cmdOk"
|
||||
- user_: [User](./TYPES.md#user)?
|
||||
|
||||
ChatCmdError: Command error (only used in WebSockets API).
|
||||
- type: "chatCmdError"
|
||||
- chatError: [ChatError](./TYPES.md#chaterror)
|
||||
|
||||
---
|
||||
|
||||
|
||||
## User profile commands
|
||||
|
||||
Most bots don't need to use these commands, as bot profile can be configured manually via CLI or desktop client. These commands can be used by bots that need to manage multiple user profiles (e.g., the profiles of support agents).
|
||||
|
||||
@@ -2000,6 +2000,9 @@ Snippet:
|
||||
Secret:
|
||||
- type: "secret"
|
||||
|
||||
Small:
|
||||
- type: "small"
|
||||
|
||||
Colored:
|
||||
- type: "colored"
|
||||
- color: [Color](#color)
|
||||
|
||||
@@ -144,7 +144,10 @@ chatCommandsDocsData =
|
||||
"Commands to list and delete conversations.",
|
||||
[ ("APIListContacts", [], "Get contacts.", ["CRContactsList", "CRChatCmdError"], [], Nothing, "/_contacts " <> Param "userId"),
|
||||
("APIListGroups", [], "Get groups.", ["CRGroupsList", "CRChatCmdError"], [], Nothing, "/_groups " <> Param "userId" <> Optional "" (" @" <> Param "$0") "contactId_" <> Optional "" (" " <> Param "$0") "search"),
|
||||
("APIDeleteChat", [], "Delete chat.", ["CRContactDeleted", "CRContactConnectionDeleted", "CRGroupDeletedUser", "CRChatCmdError"], [], Just UNBackground, "/_delete " <> Param "chatRef" <> " " <> Param "chatDeleteMode")
|
||||
("APIDeleteChat", [], "Delete chat.", ["CRContactDeleted", "CRContactConnectionDeleted", "CRGroupDeletedUser", "CRChatCmdError"], [], Just UNBackground, "/_delete " <> Param "chatRef" <> " " <> Param "chatDeleteMode"),
|
||||
("APISetGroupCustomData", [], "Set group custom data.", ["CRCmdOk", "CRChatCmdError"], [], Nothing, "/_set custom #" <> Param "groupId" <> Optional "" (" " <> Json "$0") "customData"),
|
||||
("APISetContactCustomData", [], "Set contact custom data.", ["CRCmdOk", "CRChatCmdError"], [], Nothing, "/_set custom @" <> Param "contactId" <> Optional "" (" " <> Json "$0") "customData"),
|
||||
("APISetUserAutoAcceptMemberContacts", [], "Set auto-accept member contacts.", ["CRCmdOk", "CRChatCmdError"], [], Nothing, "/_set accept member contacts " <> Param "userId" <> " " <> OnOff "onOff")
|
||||
-- ("APIChatItemsRead", [], "Mark items as read.", ["CRItemsReadForChat"], [], Nothing, ""),
|
||||
-- ("APIChatRead", [], "Mark chat as read.", ["CRCmdOk"], [], Nothing, ""),
|
||||
-- ("APIChatUnread", [], "Mark chat as unread.", ["CRCmdOk"], [], Nothing, ""),
|
||||
@@ -401,7 +404,6 @@ undocumentedCommands =
|
||||
"APISetServerOperators",
|
||||
"APISetUserContactReceipts",
|
||||
"APISetUserGroupReceipts",
|
||||
"APISetUserAutoAcceptMemberContacts",
|
||||
"APISetUserServers",
|
||||
"APISetUserUIThemes",
|
||||
"APIStandaloneFileInfo",
|
||||
|
||||
@@ -4,7 +4,6 @@ permalink: /downloads/index.html
|
||||
revision: 09.09.2024
|
||||
---
|
||||
|
||||
| Updated 09.09.2024 | Languages: EN |
|
||||
# Download SimpleX apps
|
||||
|
||||
You can get the latest beta releases from [GitHub](https://github.com/simplex-chat/simplex-chat/releases).
|
||||
|
||||
@@ -588,6 +588,51 @@ export namespace APIDeleteChat {
|
||||
}
|
||||
}
|
||||
|
||||
// Set group custom data.
|
||||
// Network usage: no.
|
||||
export interface APISetGroupCustomData {
|
||||
groupId: number // int64
|
||||
customData?: object
|
||||
}
|
||||
|
||||
export namespace APISetGroupCustomData {
|
||||
export type Response = CR.CmdOk | CR.ChatCmdError
|
||||
|
||||
export function cmdString(self: APISetGroupCustomData): string {
|
||||
return '/_set custom #' + self.groupId + (self.customData ? ' ' + JSON.stringify(self.customData) : '')
|
||||
}
|
||||
}
|
||||
|
||||
// Set contact custom data.
|
||||
// Network usage: no.
|
||||
export interface APISetContactCustomData {
|
||||
contactId: number // int64
|
||||
customData?: object
|
||||
}
|
||||
|
||||
export namespace APISetContactCustomData {
|
||||
export type Response = CR.CmdOk | CR.ChatCmdError
|
||||
|
||||
export function cmdString(self: APISetContactCustomData): string {
|
||||
return '/_set custom @' + self.contactId + (self.customData ? ' ' + JSON.stringify(self.customData) : '')
|
||||
}
|
||||
}
|
||||
|
||||
// Set auto-accept member contacts.
|
||||
// Network usage: no.
|
||||
export interface APISetUserAutoAcceptMemberContacts {
|
||||
userId: number // int64
|
||||
onOff: boolean
|
||||
}
|
||||
|
||||
export namespace APISetUserAutoAcceptMemberContacts {
|
||||
export type Response = CR.CmdOk | CR.ChatCmdError
|
||||
|
||||
export function cmdString(self: APISetUserAutoAcceptMemberContacts): string {
|
||||
return '/_set accept member contacts ' + self.userId + ' ' + (self.onOff ? 'on' : 'off')
|
||||
}
|
||||
}
|
||||
|
||||
// User profile commands
|
||||
// Most bots don't need to use these commands, as bot profile can be configured manually via CLI or desktop client. These commands can be used by bots that need to manage multiple user profiles (e.g., the profiles of support agents).
|
||||
|
||||
|
||||
@@ -2300,6 +2300,7 @@ export type Format =
|
||||
| Format.StrikeThrough
|
||||
| Format.Snippet
|
||||
| Format.Secret
|
||||
| Format.Small
|
||||
| Format.Colored
|
||||
| Format.Uri
|
||||
| Format.HyperLink
|
||||
@@ -2316,6 +2317,7 @@ export namespace Format {
|
||||
| "strikeThrough"
|
||||
| "snippet"
|
||||
| "secret"
|
||||
| "small"
|
||||
| "colored"
|
||||
| "uri"
|
||||
| "hyperLink"
|
||||
@@ -2349,6 +2351,10 @@ export namespace Format {
|
||||
type: "secret"
|
||||
}
|
||||
|
||||
export interface Small extends Interface {
|
||||
type: "small"
|
||||
}
|
||||
|
||||
export interface Colored extends Interface {
|
||||
type: "colored"
|
||||
color: Color
|
||||
|
||||
@@ -747,6 +747,47 @@ export class ChatApi {
|
||||
throw new ChatCommandError("error deleting chat", r)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set group custom data.
|
||||
* Network usage: no.
|
||||
*/
|
||||
async apiSetGroupCustomData(groupId: number, customData?: object): Promise<void> {
|
||||
const r = await this.sendChatCmd(CC.APISetGroupCustomData.cmdString({groupId, customData}))
|
||||
if (r.type === "cmdOk") return
|
||||
throw new ChatCommandError("error setting group custom data", r)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set contact custom data.
|
||||
* Network usage: no.
|
||||
*/
|
||||
async apiSetContactCustomData(contactId: number, customData?: object): Promise<void> {
|
||||
const r = await this.sendChatCmd(CC.APISetContactCustomData.cmdString({contactId, customData}))
|
||||
if (r.type === "cmdOk") return
|
||||
throw new ChatCommandError("error setting contact custom data", r)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set auto-accept member contacts.
|
||||
* Network usage: no.
|
||||
*/
|
||||
async apiSetAutoAcceptMemberContacts(userId: number, onOff: boolean): Promise<void> {
|
||||
const r = await this.sendChatCmd(CC.APISetUserAutoAcceptMemberContacts.cmdString({userId, onOff}))
|
||||
if (r.type === "cmdOk") return
|
||||
throw new ChatCommandError("error setting auto-accept member contacts", r)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get chat items.
|
||||
* Network usage: no.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
async apiGetChat(chatType: T.ChatType, chatId: number, count: number): Promise<any> {
|
||||
const r: any = await this.sendChatCmd(`/_get chat ${T.ChatType.cmdString(chatType)}${chatId} count=${count}`)
|
||||
if (r.type === "apiChat") return r.chat
|
||||
throw new ChatCommandError("error getting chat", r)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get active user profile
|
||||
* Network usage: no.
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
# Small Text Markdown
|
||||
|
||||
Add `!- text!` syntax for small gray text — legal disclaimers, secondary commentary, LLM reasoning, etc.
|
||||
|
||||
## Syntax
|
||||
|
||||
`!- text!` — renders as small gray text. Uses the `!` style prefix family, `-` for "reduced."
|
||||
|
||||
On old clients: `!- fine print!` shows as-is (old `coloredP` fails on `-`, falls to `wordP`). Readable.
|
||||
|
||||
## Changes
|
||||
|
||||
### Haskell — `src/Simplex/Chat/Markdown.hs`
|
||||
|
||||
1. **`Format`**: add `Small` constructor (no fields).
|
||||
|
||||
2. **`coloredP` parser**: before trying `colorP`, check for `-` followed by space. If matched, produce `Small`. Otherwise fall through to existing color parsing.
|
||||
|
||||
3. **`markdownText`**: add `Small` case, reconstruct as `!- text!`.
|
||||
|
||||
4. **JSON serialization**: TH-derived `ToJSON`/`FromJSON` via existing `sumTypeJSON fstToLower`. Produces `{"small": {}}`. Old Haskell `FromJSON Format` falls to `Unknown` via `<|> pure (Unknown v)`.
|
||||
|
||||
### Haskell — `src/Simplex/Chat/Styled.hs`
|
||||
|
||||
5. **`sgr`**: add `Small` case — map to `FaintIntensity` for terminal rendering.
|
||||
|
||||
### Haskell — `tests/MarkdownTests.hs`
|
||||
|
||||
6. Tests for:
|
||||
- `!- text!` parses as `Small`
|
||||
- `!- text!` with leading/trailing spaces in content → no format (same rule as other formats)
|
||||
- Existing color syntax unchanged
|
||||
- `markdownText` round-trip
|
||||
|
||||
### iOS — `apps/ios/SimpleXChat/ChatTypes.swift`
|
||||
|
||||
7. **`Format`** enum: add `case small`.
|
||||
|
||||
### iOS — `apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift`
|
||||
|
||||
8. **`messageText`**: render `Small` with smaller `UIFont` point size + gray color.
|
||||
|
||||
### Android — `apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt`
|
||||
|
||||
9. **`Format`**: add `@Serializable @SerialName("small") class Small: Format()`.
|
||||
10. **`Format.style`**: `SpanStyle` with smaller font size + gray color.
|
||||
|
||||
### Android — `apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TextItemView.kt`
|
||||
|
||||
11. **`MarkdownText`**: add `is Format.Small` case — same pattern as `Bold`/`Italic` (apply style, append text).
|
||||
|
||||
## Backward Compatibility
|
||||
|
||||
### Local (old app receiving message with new syntax)
|
||||
- Old app's bundled Haskell parses raw message text. Old `coloredP` doesn't know `-`, fails, falls to `wordP`. Text shows as `!- fine print!` — plain text with delimiters.
|
||||
|
||||
### Remote desktop (old desktop, new mobile)
|
||||
- New mobile Haskell parses `!- text!` as `Small`, serializes to JSON `{"small": {}}`.
|
||||
- Old desktop Haskell re-parses JSON via `J.parseJSON` (`Remote/Protocol.hs:184`). Old `FromJSON Format` doesn't know `"small"` → `<|> pure (Unknown v)`.
|
||||
- `Unknown` re-serializes to `{"type": "unknown", "json": ...}` → Kotlin `Format.Unknown` (`ignoreUnknownKeys` drops extra fields). Text renders without formatting.
|
||||
|
||||
## Order of Implementation
|
||||
|
||||
1. Haskell types + parser + tests
|
||||
2. iOS types + rendering
|
||||
3. Android types + rendering
|
||||
@@ -278,7 +278,7 @@ data ChatCommand
|
||||
| SetUserContactReceipts UserMsgReceiptSettings
|
||||
| APISetUserGroupReceipts UserId UserMsgReceiptSettings
|
||||
| SetUserGroupReceipts UserMsgReceiptSettings
|
||||
| APISetUserAutoAcceptMemberContacts UserId Bool
|
||||
| APISetUserAutoAcceptMemberContacts {userId :: UserId, onOff :: Bool}
|
||||
| SetUserAutoAcceptMemberContacts Bool
|
||||
| APIHideUser UserId UserPwd
|
||||
| APIUnhideUser UserId UserPwd
|
||||
@@ -365,6 +365,8 @@ data ChatCommand
|
||||
| APISetConnectionAlias {connectionId :: Int64, localAlias :: LocalAlias}
|
||||
| APISetUserUIThemes UserId (Maybe UIThemeEntityOverrides)
|
||||
| APISetChatUIThemes ChatRef (Maybe UIThemeEntityOverrides)
|
||||
| APISetGroupCustomData {groupId :: GroupId, customData :: Maybe CustomData}
|
||||
| APISetContactCustomData {contactId :: ContactId, customData :: Maybe CustomData}
|
||||
| APIGetNtfToken
|
||||
| APIRegisterToken DeviceToken NotificationsMode
|
||||
| APIVerifyToken DeviceToken C.CbNonce ByteString
|
||||
|
||||
@@ -1435,6 +1435,16 @@ processChatCommand vr nm = \case
|
||||
liftIO $ setGroupUIThemes db user g uiThemes
|
||||
ok user
|
||||
_ -> throwCmdError "not supported"
|
||||
APISetGroupCustomData groupId customData_ -> withUser $ \user -> do
|
||||
withFastStore $ \db -> do
|
||||
g <- getGroupInfo db vr user groupId
|
||||
liftIO $ setGroupCustomData db user g customData_
|
||||
ok user
|
||||
APISetContactCustomData contactId customData_ -> withUser $ \user -> do
|
||||
withFastStore $ \db -> do
|
||||
ct <- getContact db vr user contactId
|
||||
liftIO $ setContactCustomData db user ct customData_
|
||||
ok user
|
||||
APIGetNtfToken -> withUser' $ \_ -> crNtfToken <$> withAgent getNtfToken
|
||||
APIRegisterToken token mode -> withUser $ \_ ->
|
||||
CRNtfTokenStatus <$> withAgent (\a -> registerNtfToken a nm token mode)
|
||||
@@ -4693,6 +4703,8 @@ chatCommandP =
|
||||
"/_set prefs @" *> (APISetContactPrefs <$> A.decimal <* A.space <*> jsonP),
|
||||
"/_set theme user " *> (APISetUserUIThemes <$> A.decimal <*> optional (A.space *> jsonP)),
|
||||
"/_set theme " *> (APISetChatUIThemes <$> chatRefP <*> optional (A.space *> jsonP)),
|
||||
"/_set custom #" *> (APISetGroupCustomData <$> A.decimal <*> optional (A.space *> jsonP)),
|
||||
"/_set custom @" *> (APISetContactCustomData <$> A.decimal <*> optional (A.space *> jsonP)),
|
||||
"/_ntf get" $> APIGetNtfToken,
|
||||
"/_ntf register " *> (APIRegisterToken <$> strP_ <*> strP),
|
||||
"/_ntf verify " *> (APIVerifyToken <$> strP <* A.space <*> strP <* A.space <*> strP),
|
||||
@@ -4809,7 +4821,7 @@ chatCommandP =
|
||||
"/clear " *> char_ '@' *> (ClearContact <$> displayNameP),
|
||||
("/members " <|> "/ms ") *> char_ '#' *> (ListMembers <$> displayNameP),
|
||||
"/member support chats #" *> (ListMemberSupportChats <$> displayNameP),
|
||||
"/_groups" *> (APIListGroups <$> A.decimal <*> optional (" @" *> A.decimal) <*> optional (A.space *> textP)),
|
||||
"/_groups " *> (APIListGroups <$> A.decimal <*> optional (" @" *> A.decimal) <*> optional (A.space *> textP)),
|
||||
("/groups" <|> "/gs") *> (ListGroups <$> optional (" @" *> displayNameP) <*> optional (A.space *> textP)),
|
||||
"/_group_profile #" *> (APIUpdateGroupProfile <$> A.decimal <* A.space <*> jsonP),
|
||||
("/group_profile " <|> "/gp ") *> char_ '#' *> (UpdateGroupNames <$> displayNameP <* A.space <*> groupProfile),
|
||||
|
||||
@@ -53,6 +53,7 @@ data Format
|
||||
| StrikeThrough
|
||||
| Snippet
|
||||
| Secret
|
||||
| Small
|
||||
| Colored {color :: FormatColor}
|
||||
| Uri
|
||||
-- showText is Nothing for the usual Uri without text
|
||||
@@ -202,7 +203,7 @@ markdownP = mconcat <$> A.many' fragmentP
|
||||
'~' -> formattedP '~' StrikeThrough
|
||||
'`' -> formattedP '`' Snippet
|
||||
'#' -> A.char '#' *> secretP
|
||||
'!' -> coloredP <|> wordP
|
||||
'!' -> styledP <|> wordP
|
||||
'@' -> mentionP <|> wordP
|
||||
'/' -> commandP <|> wordP
|
||||
'[' -> sowLinkP <|> wordP
|
||||
@@ -228,13 +229,13 @@ markdownP = mconcat <$> A.many' fragmentP
|
||||
| otherwise = markdown Secret $ T.init ss
|
||||
where
|
||||
ss = b <> s <> a
|
||||
coloredP :: Parser Markdown
|
||||
coloredP = do
|
||||
clr <- A.char '!' *> colorP <* A.space
|
||||
styledP :: Parser Markdown
|
||||
styledP = do
|
||||
f <- A.char '!' *> ((A.char '-' $> Small) <|> (colored <$> colorP)) <* A.space
|
||||
s <- ((<>) <$> A.takeWhile1 (\c -> c /= ' ' && c /= '!') <*> A.takeTill (== '!')) <* A.char '!'
|
||||
if T.null s || T.last s == ' '
|
||||
then fail "not colored"
|
||||
else pure $ markdown (colored clr) s
|
||||
then fail "not styled"
|
||||
else pure $ markdown f s
|
||||
mentionP = prefixedStringP '@' displayNameTextP_ Mention
|
||||
commandP = prefixedStringP '/' commandTextP Command
|
||||
prefixedStringP pfx parser format = do
|
||||
@@ -441,6 +442,7 @@ markdownText (FormattedText f_ t) = case f_ of
|
||||
StrikeThrough -> around '~'
|
||||
Snippet -> around '`'
|
||||
Secret -> around '#'
|
||||
Small -> "!- " <> t <> "!"
|
||||
Colored (FormatColor c) -> color c
|
||||
Uri -> t
|
||||
HyperLink {} -> t
|
||||
|
||||
@@ -75,6 +75,7 @@ sgr = \case
|
||||
StrikeThrough -> [SetSwapForegroundBackground True]
|
||||
Colored (FormatColor c) -> [SetColor Foreground Vivid c]
|
||||
Secret -> [SetColor Foreground Dull Black, SetColor Background Dull Black]
|
||||
Small -> [SetConsoleIntensity FaintIntensity]
|
||||
_ -> []
|
||||
|
||||
unStyle :: StyledString -> String
|
||||
|
||||
@@ -20,6 +20,7 @@ markdownTests :: Spec
|
||||
markdownTests = do
|
||||
textFormat
|
||||
secretText
|
||||
textSmall
|
||||
textColor
|
||||
textWithUri
|
||||
textWithHyperlink
|
||||
@@ -131,6 +132,35 @@ secretText = describe "secret text" do
|
||||
"snippet: `this is #secret_text#`"
|
||||
<==> "snippet: " <> markdown Snippet "this is #secret_text#"
|
||||
|
||||
small :: Text -> Markdown
|
||||
small = markdown Small
|
||||
|
||||
textSmall :: Spec
|
||||
textSmall = describe "text small" do
|
||||
it "correct markdown" do
|
||||
"this is !- small! text"
|
||||
<==> "this is " <> small "small" <> " text"
|
||||
"!- small! text"
|
||||
<==> small "small" <> " text"
|
||||
"this is !- small!"
|
||||
<==> "this is " <> small "small"
|
||||
" !- small! text"
|
||||
<==> " " <> small "small" <> " text"
|
||||
"this is !- small! "
|
||||
<==> "this is " <> small "small" <> " "
|
||||
it "ignored as markdown" do
|
||||
"this is !- unformatted ! text"
|
||||
<==> "this is !- unformatted ! text"
|
||||
"this is !- unformatted! text"
|
||||
<==> "this is !- unformatted! text"
|
||||
"this is!- unformatted! text"
|
||||
<==> "this is!- unformatted! text"
|
||||
"this is !- unformatted text"
|
||||
<==> "this is !- unformatted text"
|
||||
it "ignored internal markdown" do
|
||||
"this is !- long *small* (not bold)! text"
|
||||
<==> "this is " <> small "long *small* (not bold)" <> " text"
|
||||
|
||||
red :: Text -> Markdown
|
||||
red = markdown (colored Red)
|
||||
|
||||
|
||||
@@ -313,5 +313,6 @@
|
||||
"index-roadmap-2027": "2027",
|
||||
"navbar-token": "رمز",
|
||||
"index-token-cta": "تعرف على المزيد و<strong>احصل على NFT مجاني</strong><br>للاختبار المبكر.",
|
||||
"navbar-old-site": "الموقع القديم"
|
||||
"navbar-old-site": "الموقع القديم",
|
||||
"send-file": "إرسال ملف"
|
||||
}
|
||||
|
||||
@@ -313,5 +313,6 @@
|
||||
"messengers-comparison-section-list-point-4": "Podpora více zařízení může oslabit post-kompromitační bezpečnost protokolu Double Ratchet",
|
||||
"messengers-comparison-section-list-point-5": "Dvoufaktorovou výměnu klíčů lze volitelně aktivovat pomocí ověření bezpečnostním kódem.",
|
||||
"messengers-comparison-section-list-point-6": "Postkvantová dohoda o klíči je omezená — chrání pouze některé kroky ratchet mechanismu.",
|
||||
"navbar-old-site": "Starý web"
|
||||
"navbar-old-site": "Starý web",
|
||||
"send-file": "Odeslat soubor"
|
||||
}
|
||||
|
||||
@@ -313,5 +313,6 @@
|
||||
"messengers-comparison-section-list-point-5": "Ein 2-Faktor-Schlüsselaustausch ist optional und kann über die Überprüfung eines Sicherheitscodes erfolgen.",
|
||||
"messengers-comparison-section-list-point-6": "Die Schlüsselvereinbarung per Post-Quanten-Security ist nicht durchgängig — Sie schützt lediglich ausgewählte Schritte innerhalb des Ratchet-Prozesses.",
|
||||
"navbar-token": "Token",
|
||||
"navbar-old-site": "Alte Webseite"
|
||||
"navbar-old-site": "Alte Webseite",
|
||||
"send-file": "Datei senden"
|
||||
}
|
||||
|
||||
@@ -325,7 +325,7 @@
|
||||
"why-p8": "Because we destroyed the power to know who you are. So that <em>your</em> power can never be taken.",
|
||||
"why-tagline": "Be free in your network.",
|
||||
"why-footer-link": "Why we are building it",
|
||||
"file": "File",
|
||||
"send-file": "Send file",
|
||||
"file-desc": "Send files securely with end-to-end encryption — no accounts, no tracking.",
|
||||
"file-noscript": "JavaScript is required for file transfer.",
|
||||
"file-e2e-note": "End-to-end encrypted — the server never sees your file.",
|
||||
@@ -336,7 +336,7 @@
|
||||
"file-drop-text": "Drag & drop a file here",
|
||||
"file-drop-hint": "or",
|
||||
"file-choose": "Choose file",
|
||||
"file-max-size": "Max 100 MB - <a href=\"#join-simplex\">SimpleX Chat app</a> supports files up to 1 GB",
|
||||
"file-max-size": "Max 100 MB - <a href=\"/downloads\">SimpleX Chat app</a> supports files up to 1 GB",
|
||||
"file-encrypting": "Encrypting\u2026",
|
||||
"file-uploading": "Uploading\u2026",
|
||||
"file-cancel": "Cancel",
|
||||
@@ -347,7 +347,7 @@
|
||||
"file-expiry": "Files are typically available for 48 hours.",
|
||||
"file-sec-1": "Your file was encrypted in the browser - data routers never see file contents, name or size.",
|
||||
"file-sec-2": "The encryption key is in the link\u2019s hash fragment - it is never sent to any server.",
|
||||
"file-sec-3": "For better security, use <a href=\"https://simplex.chat/download\">SimpleX Chat</a> app.",
|
||||
"file-sec-3": "For better security, use <a href=\"https://simplex.chat/downloads\">SimpleX Chat</a> app.",
|
||||
"file-retry": "Retry",
|
||||
"file-downloading": "Downloading\u2026",
|
||||
"file-decrypting": "Decrypting\u2026",
|
||||
|
||||
@@ -313,5 +313,6 @@
|
||||
"messengers-comparison-section-list-point-5": "El intercambio de clave de doble factor es opcional mediante la verificación del código de seguridad.",
|
||||
"messengers-comparison-section-list-point-6": "El acuerdo de claves postcuántico es \"parcial\", solo protege determinados pasos del ratchet.",
|
||||
"navbar-token": "Token",
|
||||
"navbar-old-site": "Web antigua"
|
||||
"navbar-old-site": "Web antigua",
|
||||
"send-file": "Enviar archivo"
|
||||
}
|
||||
|
||||
@@ -257,5 +257,6 @@
|
||||
"docs-dropdown-10": "Läpinäkyvyys",
|
||||
"docs-dropdown-11": "Usein kysytyt kysymykset",
|
||||
"docs-dropdown-12": "Turvallisuus",
|
||||
"docs-dropdown-14": "SimpleX yrityksille"
|
||||
"docs-dropdown-14": "SimpleX yrityksille",
|
||||
"send-file": "Lähetä tiedosto"
|
||||
}
|
||||
|
||||
@@ -280,5 +280,6 @@
|
||||
"index-token-h2": "Des communautés qui durent",
|
||||
"index-token-cta": "En savoir plus et <strong>obtenez votre NFT gratuit</strong><br>pour les premiers tests.",
|
||||
"index-roadmap-h2": "Feuille de route de SimpleX vers un Internet libre",
|
||||
"index-roadmap-2025": "2025"
|
||||
"index-roadmap-2025": "2025",
|
||||
"send-file": "Envoyer un fichier"
|
||||
}
|
||||
|
||||
@@ -255,5 +255,6 @@
|
||||
"docs-dropdown-11": "שאלות ותשובות",
|
||||
"docs-dropdown-12": "אבטחה",
|
||||
"hero-overlay-card-3-p-3": "Trail of bits סקר את הקוד הקריפטוגרפי של פרוטוקולי רשת SimpleX ביולי 2024. <a href=\"/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.html\">קרא עוד</a>.",
|
||||
"docs-dropdown-14": "SimpleX לעסקים"
|
||||
"docs-dropdown-14": "SimpleX לעסקים",
|
||||
"send-file": "שליחת קובץ"
|
||||
}
|
||||
|
||||
@@ -313,5 +313,6 @@
|
||||
"messengers-comparison-section-list-point-5": "A kétlépcsős kulcscsere nem követelmény a biztonsági kód ellenőrzéséhez.",
|
||||
"messengers-comparison-section-list-point-6": "A kvantumbiztos kulcscsere „ritka” — csak a racsnis lépések egy részét védi.",
|
||||
"navbar-token": "Token",
|
||||
"navbar-old-site": "Régi oldal"
|
||||
"navbar-old-site": "Régi oldal",
|
||||
"send-file": "Fájl küldése"
|
||||
}
|
||||
|
||||
@@ -313,5 +313,6 @@
|
||||
"messengers-comparison-section-list-point-5": "Pertukaran kunci 2 faktor bersifat opsional via verifikasi kode keamanan.",
|
||||
"messengers-comparison-section-list-point-6": "Kesepakatan kunci Post-quantum \"jarang\" — hanya melindungi beberapa langkah ratchet.",
|
||||
"navbar-token": "Token",
|
||||
"navbar-old-site": "Situs lama"
|
||||
"navbar-old-site": "Situs lama",
|
||||
"send-file": "Kirim file"
|
||||
}
|
||||
|
||||
@@ -313,5 +313,6 @@
|
||||
"messengers-comparison-section-list-point-5": "Lo scambio di chiavi a 2 fattori è facoltativo tramite la verifica del codice di sicurezza.",
|
||||
"messengers-comparison-section-list-point-6": "L'accordo sulle chiavi post-quantistico è “scarno” — protegge solo alcuni dei passaggi del ratchet.",
|
||||
"navbar-token": "Token",
|
||||
"navbar-old-site": "Sito vecchio"
|
||||
"navbar-old-site": "Sito vecchio",
|
||||
"send-file": "Invia file"
|
||||
}
|
||||
|
||||
@@ -295,5 +295,6 @@
|
||||
"index-publications-whonix-title": "Whonix推奨",
|
||||
"index-publications-heise-title": "Heise Online の記事",
|
||||
"index-publications-kuketz-title": "Mike Kuketzによるレビュー",
|
||||
"index-publications-optout-title": "OptOut ポッドキャストインタビュー"
|
||||
"index-publications-optout-title": "OptOut ポッドキャストインタビュー",
|
||||
"send-file": "ファイルを送信"
|
||||
}
|
||||
|
||||
@@ -257,5 +257,6 @@
|
||||
"hero-overlay-card-3-p-3": "Trail of Bits heeft in juli 2024 het cryptografische ontwerp van SimpleX-netwerkprotocollen beoordeeld. <a href=\"/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.html\">Lees meer</a>.",
|
||||
"docs-dropdown-14": "SimpleX voor bedrijven",
|
||||
"directory": "Directory",
|
||||
"about-and-contact-us": "Over ons & Contact"
|
||||
"about-and-contact-us": "Over ons & Contact",
|
||||
"send-file": "Bestand verzenden"
|
||||
}
|
||||
|
||||
@@ -313,5 +313,6 @@
|
||||
"messengers-comparison-section-list-point-4": "Wdrożenie wielu urządzeń zagraża zabezpieczeniu po naruszeniu bezpieczeństwa systemu Double Ratchet",
|
||||
"messengers-comparison-section-list-point-5": "Wymiana kluczy 2-składnikowych jest opcjonalna poprzez weryfikację kodu bezpieczeństwa.",
|
||||
"messengers-comparison-section-list-point-6": "Post-kwantowe, kluczowe porozumienie jest \"rzadkie\" — chroni tylko niektóre kroki systemu Ratchet.",
|
||||
"navbar-old-site": "Stara strona"
|
||||
"navbar-old-site": "Stara strona",
|
||||
"send-file": "Wyślij plik"
|
||||
}
|
||||
|
||||
@@ -313,5 +313,6 @@
|
||||
"messengers-comparison-section-list-point-5": "A troca de chaves de dois fatores é opcional por meio da verificação do código de segurança.",
|
||||
"messengers-comparison-section-list-point-6": "O acordo de chaves pós-quântico é 'esparso' — ele protege apenas algumas das etapas da catraca (ratchet).",
|
||||
"navbar-old-site": "Site antigo",
|
||||
"index-directory-users-group-title": "Grupos de usuários do SimpleX"
|
||||
"index-directory-users-group-title": "Grupos de usuários do SimpleX",
|
||||
"send-file": "Enviar arquivo"
|
||||
}
|
||||
|
||||
@@ -323,5 +323,6 @@
|
||||
"why-p7": "Древнейшая человеческая свобода — говорить с другим человеком без слежки — построенная на инфраструктуре, которая не может её предать.",
|
||||
"why-p8": "Потому что мы разрушили саму возможность узнать, кто вы. Чтобы вашу свободу невозможно было отнять.",
|
||||
"why-tagline": "Будь свободен в своей сети.",
|
||||
"why-footer-link": "Почему мы это строим"
|
||||
"why-footer-link": "Почему мы это строим",
|
||||
"send-file": "Отправить файл"
|
||||
}
|
||||
|
||||
@@ -258,5 +258,6 @@
|
||||
"docs-dropdown-14": "SimpleX для бізнесу",
|
||||
"directory": "Каталог",
|
||||
"navbar-token": "Токен",
|
||||
"about-and-contact-us": "Про нас & Контакти"
|
||||
"about-and-contact-us": "Про нас & Контакти",
|
||||
"send-file": "Надіслати файл"
|
||||
}
|
||||
|
||||
@@ -257,5 +257,6 @@
|
||||
"hero-overlay-card-3-p-3": "Trail of Bits 于 2024 年 7 月审核了 SimpleX 网络协议的加密设计。<a href=\"/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.html\">了解更多信息</a>。",
|
||||
"docs-dropdown-14": "企业版 SimpleX",
|
||||
"directory": "目录",
|
||||
"about-and-contact-us": "关于 & 联系我们"
|
||||
"about-and-contact-us": "关于 & 联系我们",
|
||||
"send-file": "发送文件"
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"tailwindcss": "3.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@simplex-chat/xftp-web": "^0.2.0",
|
||||
"@simplex-chat/xftp-web": "^0.3.0",
|
||||
"eleventy-plugin-i18n": "^0.1.3",
|
||||
"fs": "^0.0.1-security",
|
||||
"gray-matter": "^4.0.3",
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<script>if (localStorage.getItem('theme') === 'dark' || (!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches)) document.documentElement.classList.add('dark')</script>
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
{% include "dark-mode.html" %}
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>SimpleX blog: {{ title }}</title>
|
||||
@@ -23,6 +24,7 @@
|
||||
{% endif %}
|
||||
<meta name="twitter:card" content="summary" />
|
||||
<link rel="icon" type="image/png" sizes="96x96" href="/img/favicon.ico" />
|
||||
<meta name="theme-color" content="#F3FAFF">
|
||||
<link href="/css/tailwind.css" rel="stylesheet" />
|
||||
<link rel="stylesheet" href="/css/blog.css" />
|
||||
<link id="prism-theme" rel="stylesheet" href="/css/prism-light.min.css"/>
|
||||
|
||||
@@ -9,12 +9,14 @@
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
{% include "dark-mode.html" %}
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ title }}</title>
|
||||
<meta name="Content-Type" content="text/html;charset=utf-8"/>
|
||||
|
||||
<link rel="icon" type="image/png" sizes="96x96" href="/img/favicon.ico"/>
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<link href="/css/tailwind.css" rel="stylesheet"/>
|
||||
<link id="prism-theme" rel="stylesheet" href="/css/prism-light.min.css"/>
|
||||
<link href="/css/style.css" rel="stylesheet"/>
|
||||
|
||||
@@ -8,12 +8,14 @@
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
{% include "dark-mode.html" %}
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ title }}</title>
|
||||
<meta name="Content-Type" content="text/html;charset=utf-8" />
|
||||
|
||||
<link rel="icon" type="image/png" sizes="96x96" href="/img/favicon.ico" />
|
||||
<meta name="theme-color" content="#F3FAFF">
|
||||
<link href="/css/tailwind.css" rel="stylesheet" />
|
||||
<link id="prism-theme" rel="stylesheet" href="/css/prism-light.min.css" />
|
||||
<link href="/css/style.css" rel="stylesheet" />
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
{% endfor %}>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
{% include "dark-mode.html" %}
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ title }}</title>
|
||||
@@ -25,6 +26,7 @@
|
||||
<meta property="og:image" content="{% cfg 'siteLocation' %}/img/share_simplex.jpg"/>
|
||||
<meta name="twitter:card" content="summary"/>
|
||||
<link rel="icon" type="image/png" sizes="96x96" href="/img/favicon.ico"/>
|
||||
<meta name="theme-color" content="#F3FAFF">
|
||||
|
||||
<link rel="stylesheet" href="/css/swiper-bundle.min.css">
|
||||
<link rel="stylesheet" href="/css/blog.css">
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
{% include "dark-mode.html" %}
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>SimpleX Privacy Policy</title>
|
||||
@@ -23,6 +24,7 @@
|
||||
{% endif %}
|
||||
<meta name="twitter:card" content="summary" />
|
||||
<link rel="icon" type="image/png" sizes="96x96" href="/img/favicon.ico" />
|
||||
<meta name="theme-color" content="#F3FAFF">
|
||||
<link href="/css/tailwind.css" rel="stylesheet" />
|
||||
<link rel="stylesheet" href="/css/blog.css" />
|
||||
<link href="/css/style.css" rel="stylesheet" />
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
{% include "dark-mode.html" %}
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>{{ title }}</title>
|
||||
@@ -19,6 +20,7 @@
|
||||
<meta property="og:image" content="{% cfg 'siteLocation' %}/img/share_simplex.jpg" />
|
||||
<meta name="twitter:card" content="summary" />
|
||||
<link rel="icon" type="image/png" sizes="96x96" href="/img/favicon.ico" />
|
||||
<meta name="theme-color" content="#F3FAFF">
|
||||
<link href="/css/tailwind.css" rel="stylesheet" />
|
||||
<link rel="stylesheet" href="/css/blog.css" />
|
||||
<link id="prism-theme" rel="stylesheet" href="/css/prism-light.min.css"/>
|
||||
|
||||
@@ -26,13 +26,13 @@
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{# <hr>
|
||||
<hr>
|
||||
|
||||
<li class="nav-link {% if active_file %}active{% endif %}">
|
||||
<li class="nav-link send-file {% if active_file %}active{% endif %}">
|
||||
<a href="/file/">
|
||||
<span class="nav-link-text">{{ "file" | i18n({}, lang) | safe }}</span>
|
||||
<span class="nav-link-text">{{ "send-file" | i18n({}, lang) | safe }}</span>
|
||||
</a>
|
||||
</li> #}
|
||||
</li>
|
||||
|
||||
<hr>
|
||||
|
||||
@@ -218,6 +218,20 @@ function iconToggle () {
|
||||
moonIcon.classList.toggle('hidden');
|
||||
}
|
||||
|
||||
function updateThemeColor() {
|
||||
const meta = document.querySelector('meta[name="theme-color"]');
|
||||
if (meta) meta.setAttribute('content', getComputedStyle(document.body).backgroundColor);
|
||||
const navbar = document.querySelector('header#navbar');
|
||||
if (navbar && navbar.classList.contains('open')) {
|
||||
navbar.style.transition = 'none';
|
||||
navbar.classList.remove('open');
|
||||
setTimeout(() => {
|
||||
navbar.classList.add('open');
|
||||
setTimeout(() => navbar.style.transition = '', 0);
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
function themeCheck() {
|
||||
if (userTheme === 'dark' || (!userTheme && systemTheme)) {
|
||||
document.documentElement.classList.add('dark');
|
||||
@@ -232,6 +246,7 @@ function themeCheck() {
|
||||
prismThemeLink.setAttribute('href','/css/prism-light.min.css')
|
||||
}
|
||||
}
|
||||
updateThemeColor();
|
||||
}
|
||||
themeCheck();
|
||||
|
||||
@@ -251,6 +266,7 @@ function themeSwitch () {
|
||||
}
|
||||
iconToggle();
|
||||
}
|
||||
updateThemeColor();
|
||||
}
|
||||
|
||||
const nav = document.querySelector('header#navbar');
|
||||
|
||||
@@ -497,6 +497,14 @@ button#cross-btn {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
@media (max-width: 1050px) {
|
||||
header#navbar nav#menu .nav-link.send-file {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 959px) {
|
||||
#mobile-header {
|
||||
position: fixed;
|
||||
@@ -585,7 +593,7 @@ button#cross-btn {
|
||||
}
|
||||
|
||||
header#navbar nav#menu .nav-link a {
|
||||
font-family: "Manrope", "GT-Walsheim", sans-serif;
|
||||
font-family: "Manrope", sans-serif;
|
||||
font-weight: 300;
|
||||
font-size: 18px;
|
||||
width: 100%;
|
||||
@@ -638,7 +646,7 @@ button#cross-btn {
|
||||
}
|
||||
|
||||
header#navbar nav#menu .sub-menu li a {
|
||||
font-family: "Manrope", "GT-Walsheim", sans-serif;
|
||||
font-family: "Manrope", sans-serif;
|
||||
font-weight: 300;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
+21
-13
@@ -216,10 +216,6 @@ html {
|
||||
font-family: GT-Walsheim, Gilroy, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.dark html,
|
||||
.dark body {
|
||||
@@ -397,8 +393,8 @@ section.cover div.content {
|
||||
}
|
||||
|
||||
section.cover div.content h1 {
|
||||
font-family: "GT-Walsheim", "Manrope", sans-serif;
|
||||
font-weight: 600;
|
||||
font-family: "GT-Walsheim", sans-serif;
|
||||
font-weight: 700;
|
||||
font-size: calc(var(--sec-vwu) * 11.7);
|
||||
line-height: 0.9;
|
||||
letter-spacing: -0.025em;
|
||||
@@ -419,7 +415,7 @@ section.cover div.content h1 .medium {
|
||||
}
|
||||
|
||||
section.cover div.content h2 {
|
||||
font-family: "GT-Walsheim", "Manrope", sans-serif;
|
||||
font-family: "GT-Walsheim", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: calc(var(--sec-vwu) * 5);
|
||||
letter-spacing: -0.025em;
|
||||
@@ -427,7 +423,7 @@ section.cover div.content h2 {
|
||||
}
|
||||
|
||||
section.cover div.content p {
|
||||
font-family: "Manrope", "GT-Walsheim", sans-serif;
|
||||
font-family: "Manrope", sans-serif;
|
||||
font-weight: 200;
|
||||
font-size: calc(var(--sec-vwu) * 2.14);
|
||||
align-items: center;
|
||||
@@ -491,7 +487,7 @@ section.cover div.content p {
|
||||
|
||||
.security-audits {
|
||||
font-size: 14px !important;
|
||||
font-family: 'Manrope', 'GT-Walsheim', sans-serif !important;
|
||||
font-family: 'Manrope', sans-serif !important;
|
||||
font-weight: 300 !important;
|
||||
color: white;
|
||||
line-height: 1.2;
|
||||
@@ -532,6 +528,14 @@ section.cover div.content p {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.socials .desktop-app-btn,
|
||||
.socials .apple-store-btn,
|
||||
.socials .google-play-btn {
|
||||
display: flex !important;
|
||||
}
|
||||
}
|
||||
|
||||
[dir="ltr"] .socials {
|
||||
right: 30px;
|
||||
}
|
||||
@@ -567,7 +571,7 @@ section.cover div.content p {
|
||||
.desktop-app-btn .btn-content p {
|
||||
margin: 0;
|
||||
font-size: 11px !important;
|
||||
font-family: 'Manrope', 'GT-Walsheim', sans-serif !important;
|
||||
font-family: 'Manrope', sans-serif !important;
|
||||
font-weight: 300 !important;
|
||||
line-height: 1.2 !important;
|
||||
text-align: left;
|
||||
@@ -593,6 +597,10 @@ section.cover div.content p {
|
||||
.socials {
|
||||
position: static;
|
||||
}
|
||||
|
||||
.socials {
|
||||
min-height: 42px;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- MAIN SECTIONS --- */
|
||||
@@ -761,7 +769,7 @@ main .section-bg {
|
||||
}
|
||||
|
||||
.page .text-container h2 {
|
||||
font-family: "GT-Walsheim", "Manrope", sans-serif;
|
||||
font-family: "GT-Walsheim", sans-serif;
|
||||
font-weight: 300;
|
||||
font-size: calc(var(--sec-vwu)*4.94);
|
||||
letter-spacing: -0.025em;
|
||||
@@ -775,7 +783,7 @@ main .section-bg {
|
||||
}
|
||||
|
||||
.page .text-container p {
|
||||
font-family: "Manrope", "GT-Walsheim", sans-serif;
|
||||
font-family: "Manrope", sans-serif;
|
||||
font-weight: 200;
|
||||
font-size: calc(var(--sec-vwu)*1.62);
|
||||
max-width: calc(var(--sec-vwu)*23);
|
||||
@@ -791,7 +799,7 @@ main .section-bg {
|
||||
}
|
||||
|
||||
.page .text-container a {
|
||||
font-family: "Manrope", "GT-Walsheim", sans-serif;
|
||||
font-family: "Manrope", sans-serif;
|
||||
font-weight: 200;
|
||||
font-size: calc(var(--sec-vwu)*1.62);
|
||||
text-decoration: underline;
|
||||
|
||||
@@ -94,6 +94,15 @@ active_directory: true
|
||||
color: magenta;
|
||||
}
|
||||
|
||||
#directory .entry .small-text {
|
||||
font-size: 0.8em;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.dark #directory .entry .small-text {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.dark #directory .entry .green {
|
||||
color: #4DDA67;
|
||||
}
|
||||
|
||||
+30
-2
@@ -6,7 +6,7 @@ active_home: true
|
||||
---
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ page.url | getlang }}"
|
||||
<html class="bg-[#C1E3FE] dark:bg-[#050920]" lang="{{ page.url | getlang }}"
|
||||
{% for language in languages.languages %}
|
||||
{% if language.label == page.url | getlang %}
|
||||
dir="{{ "rtl" if language.rtl else "ltr" }}"
|
||||
@@ -15,9 +15,9 @@ active_home: true
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
{% include "dark-mode.html" %}
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
||||
<meta name="theme-color" content="transparent">
|
||||
<title>{{ title }}</title>
|
||||
<meta name="description" content="{{ description }}"/>
|
||||
<meta name="Content-Type" content="text/html;charset=utf-8"/>
|
||||
@@ -34,6 +34,34 @@ active_home: true
|
||||
<meta property="og:image" content="{% cfg 'siteLocation' %}/img/share_simplex.jpg"/>
|
||||
<meta name="twitter:card" content="summary"/>
|
||||
<link rel="icon" type="image/png" sizes="96x96" href="/img/favicon.ico"/>
|
||||
<meta name="theme-color" content="#C1E3FE">
|
||||
|
||||
<link rel="preload" href="/img/design_3/cover.webp" as="image" type="image/webp">
|
||||
<link rel="preload" href="/img/design_3/cover-light.webp" as="image" type="image/webp" media="(prefers-color-scheme: light)">
|
||||
<link rel="preload" href="/img/new/logo-dark.png" as="image">
|
||||
<link rel="preload" href="/img/new/logo-light.png" as="image" media="(prefers-color-scheme: light)">
|
||||
<link rel="preload" href="/img/design_3/publications/trail-of-bits.png" as="image">
|
||||
<link rel="preload" href="/img/design_3/publications/privacy-guides.png" as="image">
|
||||
<link rel="preload" href="/img/design_3/publications/whonix.png" as="image">
|
||||
<link rel="preload" href="/img/design_3/publications/heise.png" as="image">
|
||||
<link rel="preload" href="/img/design_3/publications/kuketz-blog.png" as="image">
|
||||
<link rel="preload" href="/img/design_3/publications/optout.png" as="image">
|
||||
|
||||
<link rel="preload" href="/fonts/GT-Walsheim/GT-Walsheim-LC-Bold.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/GT-Walsheim/GT-Walsheim-LC-Regular.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/Manrope/Manrope-ExtraLight.ttf" as="font" type="font/ttf" crossorigin>
|
||||
<link rel="preload" href="/fonts/Manrope/Manrope-Light.ttf" as="font" type="font/ttf" crossorigin>
|
||||
<link rel="preload" href="/fonts/Manrope/Manrope-Medium.ttf" as="font" type="font/ttf" crossorigin>
|
||||
<link rel="preload" href="/fonts/Manrope/Manrope-Bold.ttf" as="font" type="font/ttf" crossorigin>
|
||||
|
||||
<link rel="preload" href="/img/new/apple_store.svg" as="image">
|
||||
<link rel="preload" href="/img/new/google_play.svg" as="image">
|
||||
<link rel="preload" href="/img/new/f_droid.svg" as="image" media="(max-width: 959px)">
|
||||
<link rel="preload" href="/img/design_3/testflight-dark.png" as="image" media="(max-width: 959px)">
|
||||
<link rel="preload" href="/img/design_3/android-dark.png" as="image" media="(max-width: 959px)">
|
||||
<link rel="preload" href="/img/design_3/socials/linux.png" as="image" media="(min-width: 960px)">
|
||||
<link rel="preload" href="/img/design_3/socials/apple.png" as="image" media="(min-width: 960px)">
|
||||
<link rel="preload" href="/img/design_3/socials/windows.png" as="image" media="(min-width: 960px)">
|
||||
|
||||
<link href="/css/tailwind.css" rel="stylesheet"/>
|
||||
<link rel="stylesheet" href="/css/design3.css">
|
||||
|
||||
@@ -433,6 +433,9 @@ function renderMarkdown(fts) {
|
||||
case 'secret':
|
||||
html += `<span class="secret">${escapeHtml(text)}</span>`;
|
||||
break;
|
||||
case 'small':
|
||||
html += `<span class="small-text">${escapeHtml(text)}</span>`;
|
||||
break;
|
||||
case 'colored':
|
||||
html += `<span class="${format.color}">${escapeHtml(text)}</span>`;
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user