mirror of
https://github.com/nix-community/home-manager.git
synced 2026-01-12 01:59:37 +08:00
@@ -1,8 +1,8 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
inherit (lib)
|
||||
attrValues concatStringsSep filter length literalExpression mapAttrsToList
|
||||
mkIf mkOption mkOptionDefault optionalAttrs optionalString types;
|
||||
inherit (pkgs.stdenv.hostPlatform) isDarwin;
|
||||
|
||||
cfg = config.programs.thunderbird;
|
||||
@@ -17,8 +17,8 @@ let
|
||||
|
||||
moduleName = "programs.thunderbird";
|
||||
|
||||
enabledAccounts = attrValues
|
||||
(filterAttrs (_: a: a.thunderbird.enable) config.accounts.email.accounts);
|
||||
enabledAccounts = attrValues (lib.filterAttrs (_: a: a.thunderbird.enable)
|
||||
config.accounts.email.accounts);
|
||||
|
||||
enabledAccountsWithId =
|
||||
map (a: a // { id = builtins.hashString "sha256" a.name; }) enabledAccounts;
|
||||
@@ -32,15 +32,15 @@ let
|
||||
thunderbirdConfigPath;
|
||||
|
||||
profilesWithId =
|
||||
imap0 (i: v: v // { id = toString i; }) (attrValues cfg.profiles);
|
||||
lib.imap0 (i: v: v // { id = toString i; }) (attrValues cfg.profiles);
|
||||
|
||||
profilesIni = foldl recursiveUpdate {
|
||||
profilesIni = lib.foldl lib.recursiveUpdate {
|
||||
General = {
|
||||
StartWithLastProfile = 1;
|
||||
} // lib.optionalAttrs (cfg.profileVersion != null) {
|
||||
Version = cfg.profileVersion;
|
||||
};
|
||||
} (flip map profilesWithId (profile: {
|
||||
} (lib.flip map profilesWithId (profile: {
|
||||
"Profile${profile.id}" = {
|
||||
Name = profile.name;
|
||||
Path = if isDarwin then "Profiles/${profile.name}" else profile.name;
|
||||
@@ -150,7 +150,7 @@ let
|
||||
mkUserJs = prefs: extraPrefs: ''
|
||||
// Generated by Home Manager.
|
||||
|
||||
${concatStrings (mapAttrsToList (name: value: ''
|
||||
${lib.concatStrings (mapAttrsToList (name: value: ''
|
||||
user_pref("${name}", ${builtins.toJSON value});
|
||||
'') prefs)}
|
||||
${extraPrefs}
|
||||
@@ -175,18 +175,18 @@ let
|
||||
''
|
||||
version="9"
|
||||
logging="no"
|
||||
'' + concatStrings (map (f: mkFilterToIniString f) filters);
|
||||
'' + lib.concatStrings (map (f: mkFilterToIniString f) filters);
|
||||
|
||||
getEmailAccountsForProfile = profileName: accounts:
|
||||
(filter (a:
|
||||
a.thunderbird.profiles == [ ]
|
||||
|| any (p: p == profileName) a.thunderbird.profiles) accounts);
|
||||
|| lib.any (p: p == profileName) a.thunderbird.profiles) accounts);
|
||||
in {
|
||||
meta.maintainers = with hm.maintainers; [ d-dervishi jkarlson ];
|
||||
meta.maintainers = with lib.hm.maintainers; [ d-dervishi jkarlson ];
|
||||
|
||||
options = {
|
||||
programs.thunderbird = {
|
||||
enable = mkEnableOption "Thunderbird";
|
||||
enable = lib.mkEnableOption "Thunderbird";
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
@@ -214,143 +214,142 @@ in {
|
||||
};
|
||||
|
||||
profiles = mkOption {
|
||||
type = with types;
|
||||
attrsOf (submodule ({ config, name, ... }: {
|
||||
options = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
default = name;
|
||||
readOnly = true;
|
||||
description = "This profile's name.";
|
||||
};
|
||||
type = types.attrsOf (types.submodule ({ config, name, ... }: {
|
||||
options = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
default = name;
|
||||
readOnly = true;
|
||||
description = "This profile's name.";
|
||||
};
|
||||
|
||||
isDefault = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
example = true;
|
||||
description = ''
|
||||
Whether this is a default profile. There must be exactly one
|
||||
default profile.
|
||||
'';
|
||||
};
|
||||
isDefault = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
example = true;
|
||||
description = ''
|
||||
Whether this is a default profile. There must be exactly one
|
||||
default profile.
|
||||
'';
|
||||
};
|
||||
|
||||
feedAccounts = mkOption {
|
||||
type = types.attrsOf (submodule ({ config, name, ... }: {
|
||||
options = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
default = name;
|
||||
readOnly = true;
|
||||
description = "This feed account's name.";
|
||||
feedAccounts = mkOption {
|
||||
type = types.attrsOf (types.submodule ({ config, name, ... }: {
|
||||
options = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
default = name;
|
||||
readOnly = true;
|
||||
description = "This feed account's name.";
|
||||
};
|
||||
};
|
||||
}));
|
||||
default = { };
|
||||
description = ''
|
||||
Attribute set of feed accounts. Feeds themselves have to be
|
||||
managed through Thunderbird's settings. This option allows
|
||||
feeds to coexist with declaratively managed email accounts.
|
||||
'';
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = thunderbirdJson;
|
||||
default = { };
|
||||
example = literalExpression ''
|
||||
{
|
||||
"mail.spellcheck.inline" = false;
|
||||
"mailnews.database.global.views.global.columns" = {
|
||||
selectCol = {
|
||||
visible = false;
|
||||
ordinal = 1;
|
||||
};
|
||||
threadCol = {
|
||||
visible = true;
|
||||
ordinal = 2;
|
||||
};
|
||||
};
|
||||
}));
|
||||
default = { };
|
||||
description = ''
|
||||
Attribute set of feed accounts. Feeds themselves have to be
|
||||
managed through Thunderbird's settings. This option allows
|
||||
feeds to coexist with declaratively managed email accounts.
|
||||
'';
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = thunderbirdJson;
|
||||
default = { };
|
||||
example = literalExpression ''
|
||||
{
|
||||
"mail.spellcheck.inline" = false;
|
||||
"mailnews.database.global.views.global.columns" = {
|
||||
selectCol = {
|
||||
visible = false;
|
||||
ordinal = 1;
|
||||
};
|
||||
threadCol = {
|
||||
visible = true;
|
||||
ordinal = 2;
|
||||
};
|
||||
};
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
Preferences to add to this profile's
|
||||
{file}`user.js`.
|
||||
'';
|
||||
};
|
||||
|
||||
withExternalGnupg = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
example = true;
|
||||
description = "Allow using external GPG keys with GPGME.";
|
||||
};
|
||||
|
||||
userChrome = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = "Custom Thunderbird user chrome CSS.";
|
||||
example = ''
|
||||
/* Hide tab bar in Thunderbird */
|
||||
#tabs-toolbar {
|
||||
visibility: collapse !important;
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
userContent = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = "Custom Thunderbird user content CSS.";
|
||||
example = ''
|
||||
/* Hide scrollbar on Thunderbird pages */
|
||||
*{scrollbar-width:none !important}
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = ''
|
||||
Extra preferences to add to {file}`user.js`.
|
||||
'';
|
||||
};
|
||||
|
||||
search = mkOption {
|
||||
type = types.submodule (args:
|
||||
import ./firefox/profiles/search.nix {
|
||||
inherit (args) config;
|
||||
inherit lib pkgs;
|
||||
appName = "Thunderbird";
|
||||
package = cfg.package;
|
||||
modulePath =
|
||||
[ "programs" "thunderbird" "profiles" name "search" ];
|
||||
profilePath = name;
|
||||
});
|
||||
default = { };
|
||||
description = "Declarative search engine configuration.";
|
||||
};
|
||||
|
||||
extensions = mkOption {
|
||||
type = types.listOf types.package;
|
||||
default = [ ];
|
||||
example = literalExpression ''
|
||||
[
|
||||
pkgs.some-thunderbird-extension
|
||||
]
|
||||
'';
|
||||
description = ''
|
||||
List of ${name} add-on packages to install for this profile.
|
||||
|
||||
Note that it is necessary to manually enable extensions
|
||||
inside ${name} after the first installation.
|
||||
|
||||
To automatically enable extensions add
|
||||
`"extensions.autoDisableScopes" = 0;`
|
||||
to
|
||||
[{option}`${moduleName}.profiles.<profile>.settings`](#opt-${moduleName}.profiles._name_.settings)
|
||||
'';
|
||||
};
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
Preferences to add to this profile's
|
||||
{file}`user.js`.
|
||||
'';
|
||||
};
|
||||
}));
|
||||
|
||||
withExternalGnupg = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
example = true;
|
||||
description = "Allow using external GPG keys with GPGME.";
|
||||
};
|
||||
|
||||
userChrome = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = "Custom Thunderbird user chrome CSS.";
|
||||
example = ''
|
||||
/* Hide tab bar in Thunderbird */
|
||||
#tabs-toolbar {
|
||||
visibility: collapse !important;
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
userContent = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = "Custom Thunderbird user content CSS.";
|
||||
example = ''
|
||||
/* Hide scrollbar on Thunderbird pages */
|
||||
*{scrollbar-width:none !important}
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = ''
|
||||
Extra preferences to add to {file}`user.js`.
|
||||
'';
|
||||
};
|
||||
|
||||
search = mkOption {
|
||||
type = types.submodule (args:
|
||||
import ./firefox/profiles/search.nix {
|
||||
inherit (args) config;
|
||||
inherit lib pkgs;
|
||||
appName = "Thunderbird";
|
||||
package = cfg.package;
|
||||
modulePath =
|
||||
[ "programs" "thunderbird" "profiles" name "search" ];
|
||||
profilePath = name;
|
||||
});
|
||||
default = { };
|
||||
description = "Declarative search engine configuration.";
|
||||
};
|
||||
|
||||
extensions = mkOption {
|
||||
type = types.listOf types.package;
|
||||
default = [ ];
|
||||
example = literalExpression ''
|
||||
[
|
||||
pkgs.some-thunderbird-extension
|
||||
]
|
||||
'';
|
||||
description = ''
|
||||
List of ${name} add-on packages to install for this profile.
|
||||
|
||||
Note that it is necessary to manually enable extensions
|
||||
inside ${name} after the first installation.
|
||||
|
||||
To automatically enable extensions add
|
||||
`"extensions.autoDisableScopes" = 0;`
|
||||
to
|
||||
[{option}`${moduleName}.profiles.<profile>.settings`](#opt-${moduleName}.profiles._name_.settings)
|
||||
'';
|
||||
};
|
||||
};
|
||||
}));
|
||||
description = "Attribute set of Thunderbird profiles.";
|
||||
};
|
||||
|
||||
@@ -383,142 +382,141 @@ in {
|
||||
};
|
||||
|
||||
accounts.email.accounts = mkOption {
|
||||
type = with types;
|
||||
attrsOf (submodule ({ config, ... }: {
|
||||
config.thunderbird = {
|
||||
settings = lib.mkIf (config.flavor == "gmail.com") (id: {
|
||||
"mail.smtpserver.smtp_${id}.authMethod" =
|
||||
mkOptionDefault 10; # 10 = OAuth2
|
||||
"mail.server.server_${id}.authMethod" =
|
||||
mkOptionDefault 10; # 10 = OAuth2
|
||||
"mail.server.server_${id}.socketType" =
|
||||
mkOptionDefault 3; # SSL/TLS
|
||||
"mail.server.server_${id}.is_gmail" =
|
||||
mkOptionDefault true; # handle labels, trash, etc
|
||||
});
|
||||
type = types.attrsOf (types.submodule ({ config, ... }: {
|
||||
config.thunderbird = {
|
||||
settings = lib.mkIf (config.flavor == "gmail.com") (id: {
|
||||
"mail.smtpserver.smtp_${id}.authMethod" =
|
||||
mkOptionDefault 10; # 10 = OAuth2
|
||||
"mail.server.server_${id}.authMethod" =
|
||||
mkOptionDefault 10; # 10 = OAuth2
|
||||
"mail.server.server_${id}.socketType" = mkOptionDefault 3; # SSL/TLS
|
||||
"mail.server.server_${id}.is_gmail" =
|
||||
mkOptionDefault true; # handle labels, trash, etc
|
||||
});
|
||||
};
|
||||
options.thunderbird = {
|
||||
enable =
|
||||
lib.mkEnableOption "the Thunderbird mail client for this account";
|
||||
|
||||
profiles = mkOption {
|
||||
type = with types; listOf str;
|
||||
default = [ ];
|
||||
example = literalExpression ''
|
||||
[ "profile1" "profile2" ]
|
||||
'';
|
||||
description = ''
|
||||
List of Thunderbird profiles for which this account should be
|
||||
enabled. If this list is empty (the default), this account will
|
||||
be enabled for all declared profiles.
|
||||
'';
|
||||
};
|
||||
options.thunderbird = {
|
||||
enable =
|
||||
mkEnableOption "the Thunderbird mail client for this account";
|
||||
|
||||
profiles = mkOption {
|
||||
type = with types; listOf str;
|
||||
default = [ ];
|
||||
example = literalExpression ''
|
||||
[ "profile1" "profile2" ]
|
||||
'';
|
||||
description = ''
|
||||
List of Thunderbird profiles for which this account should be
|
||||
enabled. If this list is empty (the default), this account will
|
||||
be enabled for all declared profiles.
|
||||
'';
|
||||
};
|
||||
settings = mkOption {
|
||||
type = with types; functionTo (attrsOf (oneOf [ bool int str ]));
|
||||
default = _: { };
|
||||
defaultText = literalExpression "_: { }";
|
||||
example = literalExpression ''
|
||||
id: {
|
||||
"mail.server.server_''${id}.check_new_mail" = false;
|
||||
};
|
||||
'';
|
||||
description = ''
|
||||
Extra settings to add to this Thunderbird account configuration.
|
||||
The {var}`id` given as argument is an automatically
|
||||
generated account identifier.
|
||||
'';
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = with types; functionTo (attrsOf (oneOf [ bool int str ]));
|
||||
default = _: { };
|
||||
defaultText = literalExpression "_: { }";
|
||||
example = literalExpression ''
|
||||
id: {
|
||||
"mail.server.server_''${id}.check_new_mail" = false;
|
||||
};
|
||||
'';
|
||||
description = ''
|
||||
Extra settings to add to this Thunderbird account configuration.
|
||||
The {var}`id` given as argument is an automatically
|
||||
generated account identifier.
|
||||
'';
|
||||
};
|
||||
perIdentitySettings = mkOption {
|
||||
type = with types; functionTo (attrsOf (oneOf [ bool int str ]));
|
||||
default = _: { };
|
||||
defaultText = literalExpression "_: { }";
|
||||
example = literalExpression ''
|
||||
id: {
|
||||
"mail.identity.id_''${id}.protectSubject" = false;
|
||||
"mail.identity.id_''${id}.autoEncryptDrafts" = false;
|
||||
};
|
||||
'';
|
||||
description = ''
|
||||
Extra settings to add to each identity of this Thunderbird
|
||||
account configuration. The {var}`id` given as
|
||||
argument is an automatically generated identifier.
|
||||
'';
|
||||
};
|
||||
|
||||
perIdentitySettings = mkOption {
|
||||
type = with types; functionTo (attrsOf (oneOf [ bool int str ]));
|
||||
default = _: { };
|
||||
defaultText = literalExpression "_: { }";
|
||||
example = literalExpression ''
|
||||
id: {
|
||||
"mail.identity.id_''${id}.protectSubject" = false;
|
||||
"mail.identity.id_''${id}.autoEncryptDrafts" = false;
|
||||
};
|
||||
'';
|
||||
description = ''
|
||||
Extra settings to add to each identity of this Thunderbird
|
||||
account configuration. The {var}`id` given as
|
||||
argument is an automatically generated identifier.
|
||||
'';
|
||||
};
|
||||
|
||||
messageFilters = mkOption {
|
||||
type = with types;
|
||||
listOf (submodule {
|
||||
options = {
|
||||
name = mkOption {
|
||||
type = str;
|
||||
description = "Name for the filter.";
|
||||
};
|
||||
enabled = mkOption {
|
||||
type = bool;
|
||||
default = true;
|
||||
description = "Whether this filter is currently active.";
|
||||
};
|
||||
type = mkOption {
|
||||
type = str;
|
||||
description = "Type for this filter.";
|
||||
};
|
||||
action = mkOption {
|
||||
type = str;
|
||||
description = "Action to perform on matched messages.";
|
||||
};
|
||||
actionValue = mkOption {
|
||||
type = nullOr str;
|
||||
default = null;
|
||||
description =
|
||||
"Argument passed to the filter action, e.g. a folder path.";
|
||||
};
|
||||
condition = mkOption {
|
||||
type = str;
|
||||
description = "Condition to match messages against.";
|
||||
};
|
||||
extraConfig = mkOption {
|
||||
type = nullOr str;
|
||||
default = null;
|
||||
description = "Extra settings to apply to the filter";
|
||||
};
|
||||
text = mkOption {
|
||||
type = nullOr str;
|
||||
default = null;
|
||||
description = ''
|
||||
The raw text of the filter.
|
||||
Note that this will override all other options.
|
||||
'';
|
||||
};
|
||||
messageFilters = mkOption {
|
||||
type = with types;
|
||||
listOf (submodule {
|
||||
options = {
|
||||
name = mkOption {
|
||||
type = str;
|
||||
description = "Name for the filter.";
|
||||
};
|
||||
});
|
||||
default = [ ];
|
||||
defaultText = literalExpression "[ ]";
|
||||
example = literalExpression ''
|
||||
[
|
||||
{
|
||||
name = "Mark as Read on Archive";
|
||||
enabled = true;
|
||||
type = "128";
|
||||
action = "Mark read";
|
||||
condition = "ALL";
|
||||
}
|
||||
]
|
||||
'';
|
||||
description = ''
|
||||
List of message filters to add to this Thunderbird account
|
||||
configuration.
|
||||
'';
|
||||
};
|
||||
enabled = mkOption {
|
||||
type = bool;
|
||||
default = true;
|
||||
description = "Whether this filter is currently active.";
|
||||
};
|
||||
type = mkOption {
|
||||
type = str;
|
||||
description = "Type for this filter.";
|
||||
};
|
||||
action = mkOption {
|
||||
type = str;
|
||||
description = "Action to perform on matched messages.";
|
||||
};
|
||||
actionValue = mkOption {
|
||||
type = nullOr str;
|
||||
default = null;
|
||||
description =
|
||||
"Argument passed to the filter action, e.g. a folder path.";
|
||||
};
|
||||
condition = mkOption {
|
||||
type = str;
|
||||
description = "Condition to match messages against.";
|
||||
};
|
||||
extraConfig = mkOption {
|
||||
type = nullOr str;
|
||||
default = null;
|
||||
description = "Extra settings to apply to the filter";
|
||||
};
|
||||
text = mkOption {
|
||||
type = nullOr str;
|
||||
default = null;
|
||||
description = ''
|
||||
The raw text of the filter.
|
||||
Note that this will override all other options.
|
||||
'';
|
||||
};
|
||||
};
|
||||
});
|
||||
default = [ ];
|
||||
defaultText = literalExpression "[ ]";
|
||||
example = literalExpression ''
|
||||
[
|
||||
{
|
||||
name = "Mark as Read on Archive";
|
||||
enabled = true;
|
||||
type = "128";
|
||||
action = "Mark read";
|
||||
condition = "ALL";
|
||||
}
|
||||
]
|
||||
'';
|
||||
description = ''
|
||||
List of message filters to add to this Thunderbird account
|
||||
configuration.
|
||||
'';
|
||||
};
|
||||
}));
|
||||
};
|
||||
}));
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
assertions = [
|
||||
(let defaults = catAttrs "name" (filter (a: a.isDefault) profilesWithId);
|
||||
(let
|
||||
defaults = lib.catAttrs "name" (filter (a: a.isDefault) profilesWithId);
|
||||
in {
|
||||
assertion = cfg.profiles == { } || length defaults == 1;
|
||||
message = "Must have exactly one default Thunderbird profile but found "
|
||||
@@ -527,11 +525,11 @@ in {
|
||||
})
|
||||
|
||||
(let
|
||||
profiles = catAttrs "name" profilesWithId;
|
||||
profiles = lib.catAttrs "name" profilesWithId;
|
||||
selectedProfiles =
|
||||
concatMap (a: a.thunderbird.profiles) enabledAccounts;
|
||||
lib.concatMap (a: a.thunderbird.profiles) enabledAccounts;
|
||||
in {
|
||||
assertion = (intersectLists profiles selectedProfiles)
|
||||
assertion = (lib.intersectLists profiles selectedProfiles)
|
||||
== selectedProfiles;
|
||||
message = "Cannot enable an account for a non-declared profile. "
|
||||
+ "The declared profiles are " + (concatStringsSep "," profiles)
|
||||
@@ -540,25 +538,25 @@ in {
|
||||
})
|
||||
];
|
||||
|
||||
warnings = optional (isDarwin && cfg.darwinSetupWarning) ''
|
||||
warnings = lib.optional (isDarwin && cfg.darwinSetupWarning) ''
|
||||
Thunderbird packages are not yet supported on Darwin. You can still use
|
||||
this module to manage your accounts and profiles by setting
|
||||
'programs.thunderbird.package' to a dummy value, for example using
|
||||
'pkgs.runCommand'.
|
||||
'';
|
||||
|
||||
home.packages = [ cfg.package ]
|
||||
++ optional (any (p: p.withExternalGnupg) (attrValues cfg.profiles))
|
||||
pkgs.gpgme;
|
||||
home.packages = [ cfg.package ] ++ lib.optional
|
||||
(lib.any (p: p.withExternalGnupg) (attrValues cfg.profiles)) pkgs.gpgme;
|
||||
|
||||
mozilla.thunderbirdNativeMessagingHosts = [
|
||||
cfg.package # package configured native messaging hosts (entire mail app actually)
|
||||
] ++ cfg.nativeMessagingHosts; # user configured native messaging hosts
|
||||
|
||||
home.file = mkMerge ([{
|
||||
"${thunderbirdConfigPath}/profiles.ini" =
|
||||
mkIf (cfg.profiles != { }) { text = generators.toINI { } profilesIni; };
|
||||
}] ++ flip mapAttrsToList cfg.profiles (name: profile: {
|
||||
home.file = lib.mkMerge ([{
|
||||
"${thunderbirdConfigPath}/profiles.ini" = mkIf (cfg.profiles != { }) {
|
||||
text = lib.generators.toINI { } profilesIni;
|
||||
};
|
||||
}] ++ lib.flip mapAttrsToList cfg.profiles (name: profile: {
|
||||
"${thunderbirdProfilesPath}/${name}/chrome/userChrome.css" =
|
||||
mkIf (profile.userChrome != "") { text = profile.userChrome; };
|
||||
|
||||
|
||||
Reference in New Issue
Block a user