Some small additions
This commit is contained in:
parent
57caf21327
commit
fb430af880
29 changed files with 466 additions and 88 deletions
|
@ -31,7 +31,7 @@ function submitLogin(user: string, pass: string) {
|
|||
return;
|
||||
}
|
||||
if (data["allowed"] == false) {
|
||||
loginFailed("You're not allowed for the closed alpha!");
|
||||
loginFailed("You're not allowed for the closed beta!");
|
||||
return;
|
||||
}
|
||||
$("#login").hide(500);
|
||||
|
|
|
@ -90,6 +90,7 @@ $border_color_activated: rgba(255, 255, 255, .75);
|
|||
width: 1.5em;
|
||||
}
|
||||
|
||||
overflow: hidden;
|
||||
padding: .25em;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -922,7 +922,7 @@ $client_info_avatar_size: 10em;
|
|||
}
|
||||
}
|
||||
|
||||
.no-permissions, .private-conversation {
|
||||
.no-permissions, .private-conversation, .not-supported {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
|
||||
|
@ -1308,13 +1308,13 @@ $client_info_avatar_size: 10em;
|
|||
|
||||
.container-private-conversations, .container-channel-chat {
|
||||
.container-message .emoji {
|
||||
height: 1em;
|
||||
width: 1em;
|
||||
height: 1.1em;
|
||||
width: 1.1em;
|
||||
|
||||
margin-left: .1em;
|
||||
margin-right: .1em;
|
||||
|
||||
vertical-align: text-top;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
min-width: 30em!important;
|
||||
max-height: calc(100vh - 10em);
|
||||
padding: 0em!important;
|
||||
padding: 0 !important;
|
||||
|
||||
.row {
|
||||
flex-grow: 0;
|
||||
|
|
|
@ -477,6 +477,13 @@
|
|||
|
||||
.container-filter {
|
||||
justify-content: stretch;
|
||||
height: 4em;
|
||||
|
||||
.form-group {
|
||||
height: 3.5em;
|
||||
padding-top: 1.25em;
|
||||
margin-bottom: 0!important;
|
||||
}
|
||||
|
||||
.button-toggle-clients {
|
||||
flex-grow: 0;
|
||||
|
@ -498,6 +505,8 @@
|
|||
|
||||
.container-granted-switch {
|
||||
margin-left: 1em;
|
||||
margin-bottom: 1em;
|
||||
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
|
@ -511,6 +520,8 @@
|
|||
min-width: 8em;
|
||||
|
||||
> label {
|
||||
font-size: .75em;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
|
@ -528,10 +539,122 @@
|
|||
|
||||
a {
|
||||
padding-left: .25em;
|
||||
font-size: 1.45em;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.container-icon-select {
|
||||
position: relative;
|
||||
|
||||
height: 2.5em;
|
||||
|
||||
border-radius: .2em;
|
||||
margin-left: 1em;
|
||||
margin-bottom: 1em;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-self: flex-end;
|
||||
|
||||
cursor: pointer;
|
||||
background-color: #121213;
|
||||
border: 1px solid #0d0d0d;
|
||||
|
||||
.icon-preview {
|
||||
height: 100%;
|
||||
width: 3em;
|
||||
|
||||
border: none;
|
||||
border-right: 1px solid #0d0d0d;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
|
||||
> div {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
> img {
|
||||
align-self: center;
|
||||
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
@include transition(border-color $button_hover_animation_time ease-in-out);
|
||||
}
|
||||
|
||||
.container-dropdown {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
|
||||
height: 100%;
|
||||
width: 1.5em;
|
||||
|
||||
.button {
|
||||
text-align: center;
|
||||
|
||||
.arrow {
|
||||
border-color: #999999;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
display: none;
|
||||
position: absolute;
|
||||
width: max-content;
|
||||
|
||||
top: calc(2.5em - 1px);
|
||||
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
|
||||
background-color: #121213;
|
||||
border: 1px solid #0d0d0d;
|
||||
border-radius: .2em 0 .2em .2em;
|
||||
|
||||
right: -1px;
|
||||
|
||||
.entry {
|
||||
padding: .5em;
|
||||
|
||||
&:not(:last-of-type) {
|
||||
border: none;
|
||||
border-bottom: 1px solid #0d0d0d;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #17171a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-bottom-right-radius: 0;
|
||||
.dropdown {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #17171a;
|
||||
border-color: hsla(0, 0%, 20%, 1);
|
||||
|
||||
.icon-preview {
|
||||
border-color: hsla(0, 0%, 20%, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@include transition(border-color $button_hover_animation_time ease-in-out);
|
||||
}
|
||||
}
|
||||
|
||||
.container-mode {
|
||||
|
|
|
@ -126,6 +126,10 @@
|
|||
}
|
||||
|
||||
&.general-application {
|
||||
> div {
|
||||
margin-top: .25em;
|
||||
}
|
||||
|
||||
.container-font-size {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
@ -313,6 +317,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
&.general-chat {
|
||||
.container-icon-size {
|
||||
.value {
|
||||
margin-left: .5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.audio-microphone, &.audio-speaker, &.audio-sounds, &.identity-forum {
|
||||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
$localhost = true;
|
||||
?>
|
||||
<?php
|
||||
if(!$localhost) {
|
||||
if(!$localhost && !$WEB_CLIENT) {
|
||||
/* Web Testing stuff */
|
||||
define("_AUTH_API_ONLY", true);
|
||||
if(file_exists("./auth.php"))
|
||||
|
|
|
@ -371,6 +371,12 @@
|
|||
<div class="private-conversation">
|
||||
<div>{{tr "Conversation is private. Join the channel to participate!" /}}</div>
|
||||
</div>
|
||||
<div class="not-supported">
|
||||
<div>
|
||||
{{tr "The target server does not support channel chats." /}}<br>
|
||||
{{tr "You're only able to write in your own channel" /}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
|
@ -804,17 +810,14 @@
|
|||
<fieldset class="container-channel-type">
|
||||
<label class="type type-temp">
|
||||
<div class="ratio-button">
|
||||
<input type="radio" name="channel_type" value="temp" {{if
|
||||
!channel_flag_semi_permanent &&
|
||||
!channel_flag_permanent}}checked{{/if}}/>
|
||||
<input type="radio" name="channel_type" value="temp"/>
|
||||
<div class="mark"></div>
|
||||
</div>
|
||||
<a>{{tr "Temporary" /}}</a>
|
||||
</label>
|
||||
<label class="type type-semi">
|
||||
<div class="ratio-button">
|
||||
<input type="radio" name="channel_type" value="semi" {{if
|
||||
channel_flag_semi_permanent}}checked{{/if}}/>
|
||||
<input type="radio" name="channel_type" value="semi"/>
|
||||
<div class="mark"></div>
|
||||
</div>
|
||||
<a>{{tr "Semi-Permanent" /}}</a>
|
||||
|
@ -822,8 +825,7 @@
|
|||
<div class="container-perm-default">
|
||||
<label class="type type-perm">
|
||||
<div class="ratio-button">
|
||||
<input type="radio" name="channel_type" value="perm" {{if
|
||||
channel_flag_permanent}}checked{{/if}}/>
|
||||
<input type="radio" name="channel_type" value="perm"/>
|
||||
<div class="mark"></div>
|
||||
</div>
|
||||
<a>{{tr "Permanent" /}}</a>
|
||||
|
@ -1078,10 +1080,23 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="container-permission">
|
||||
<a class="name">{{tr "Subscribe" /}}</a>
|
||||
<a class="name">{{tr "Description view" /}}</a>
|
||||
<div class="input-boxed">
|
||||
<input type="number" min="0" value="0" class="value"
|
||||
permission="i_channel_needed_description_view_power">
|
||||
<div class="container-tooltip tooltip-permission-view">
|
||||
<img src="img/icon_tooltip.svg"/>
|
||||
<div class="tooltip">
|
||||
<a>{{tr "Required power to see the channel description" /}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-permission">
|
||||
<a class="name">{{tr "Subscribe" /}}</a>
|
||||
<div class="input-boxed">
|
||||
<input type="number" min="0" value="0" class="value"
|
||||
permission="i_channel_needed_subscribe_power">
|
||||
<div class="container-tooltip tooltip-permission-subscribe">
|
||||
<img src="img/icon_tooltip.svg"/>
|
||||
<div class="tooltip">
|
||||
|
@ -1982,6 +1997,17 @@
|
|||
</div>
|
||||
<a>{{tr "Enables markdown input support for chat. " /}}</a>
|
||||
</label>
|
||||
<div class="container-icon-size">
|
||||
<div>{{tr "Chat-icon size:" /}}<a class="value">100%</a></div>
|
||||
<div class="container-slider">
|
||||
<div class="filler" style="width: 30%"></div>
|
||||
<div class="thumb container-tooltip" style="left: 30%">
|
||||
<div class="tooltip">
|
||||
<a>86%</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container audio-microphone">
|
||||
|
@ -2820,7 +2846,7 @@
|
|||
<input type="text" class="form-control filter-input"/>
|
||||
<!-- <small class="form-text text-muted">{{tr "Filter permissions by permission name" /}}</small> -->
|
||||
</div>
|
||||
<div class="form-group container-granted-switch">
|
||||
<div class="container-granted-switch">
|
||||
<label>
|
||||
<div class="switch">
|
||||
<input type="checkbox" class="filter-granted">
|
||||
|
@ -2832,6 +2858,20 @@
|
|||
<a>{{tr "Assigned only" /}}</a>
|
||||
</label>
|
||||
</div>
|
||||
<div class="container-icon-select">
|
||||
<div class="button-select-icon icon-preview">
|
||||
<div class="icon-container icon_empty"></div>
|
||||
</div>
|
||||
<div class="container-dropdown">
|
||||
<div class="button">
|
||||
<div class="arrow down"></div>
|
||||
</div>
|
||||
<div class="dropdown">
|
||||
<div class="entry button-select-icon">Edit icon</div>
|
||||
<div class="entry button-icon-remove">Remove icon</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-mode container-mode-permissions">
|
||||
<div class="container-permission-list">
|
||||
|
|
|
@ -76,6 +76,7 @@ interface ConnectParameters {
|
|||
};
|
||||
token?: string;
|
||||
password?: {password: string, hashed: boolean};
|
||||
auto_reconnect_attempt?: boolean;
|
||||
}
|
||||
|
||||
class ConnectionHandler {
|
||||
|
@ -99,8 +100,10 @@ class ConnectionHandler {
|
|||
|
||||
private _clientId: number = 0;
|
||||
private _local_client: LocalClientEntry;
|
||||
|
||||
private _reconnect_timer: NodeJS.Timer;
|
||||
private _reconnect_attempt: boolean = false;
|
||||
|
||||
private _connect_initialize_id: number = 1;
|
||||
|
||||
client_status: VoiceStatus = {
|
||||
|
@ -169,7 +172,7 @@ class ConnectionHandler {
|
|||
async startConnection(addr: string, profile: profiles.ConnectionProfile, user_action: boolean, parameters: ConnectParameters) {
|
||||
this.tab_set_name(tr("Connecting"));
|
||||
this.cancel_reconnect(false);
|
||||
this._reconnect_attempt = false;
|
||||
this._reconnect_attempt = parameters.auto_reconnect_attempt || false;
|
||||
if(this.serverConnection)
|
||||
this.handleDisconnect(DisconnectReason.REQUESTED);
|
||||
|
||||
|
@ -475,10 +478,12 @@ class ConnectionHandler {
|
|||
break;
|
||||
case DisconnectReason.CONNECTION_CLOSED:
|
||||
log.error(LogCategory.CLIENT, tr("Lost connection to remote server!"));
|
||||
createErrorModal(
|
||||
tr("Connection closed"),
|
||||
tr("The connection was closed by remote host")
|
||||
).open();
|
||||
if(!this._reconnect_attempt) {
|
||||
createErrorModal(
|
||||
tr("Connection closed"),
|
||||
tr("The connection was closed by remote host")
|
||||
).open();
|
||||
}
|
||||
this.sound.play(Sound.CONNECTION_DISCONNECTED);
|
||||
|
||||
auto_reconnect = true;
|
||||
|
@ -494,7 +499,7 @@ class ConnectionHandler {
|
|||
break;
|
||||
case DisconnectReason.SERVER_CLOSED:
|
||||
this.log.log(log.server.Type.SERVER_CLOSED, {message: data.reasonmsg});
|
||||
//this.chat.serverChat().appendError(tr("Server closed ({0})"), data.reasonmsg);
|
||||
|
||||
createErrorModal(
|
||||
tr("Server closed"),
|
||||
"The server is closed.<br>" + //TODO tr
|
||||
|
@ -506,7 +511,6 @@ class ConnectionHandler {
|
|||
break;
|
||||
case DisconnectReason.SERVER_REQUIRES_PASSWORD:
|
||||
this.log.log(log.server.Type.SERVER_REQUIRES_PASSWORD, {});
|
||||
//this.chat.serverChat().appendError(tr("Server requires password"));
|
||||
|
||||
createInputModal(tr("Server password"), tr("Enter server password:"), password => password.length != 0, password => {
|
||||
if(!(typeof password === "string")) return;
|
||||
|
@ -526,8 +530,8 @@ class ConnectionHandler {
|
|||
break;
|
||||
case DisconnectReason.CLIENT_KICKED:
|
||||
createErrorModal(
|
||||
tr("You've been banned"),
|
||||
MessageHelper.formatMessage(tr("You've been banned from this server.{:br:}{0}"), data["extra_message"])
|
||||
tr("You've been kicked"),
|
||||
MessageHelper.formatMessage(tr("You've been kicked from this server.{:br:}{0}"), data["extra_message"])
|
||||
).open();
|
||||
this.sound.play(Sound.SERVER_KICKED);
|
||||
auto_reconnect = false;
|
||||
|
@ -590,8 +594,7 @@ class ConnectionHandler {
|
|||
this.log.log(log.server.Type.RECONNECT_CANCELED, {});
|
||||
log.info(LogCategory.NETWORKING, tr("Reconnecting..."));
|
||||
|
||||
this.startConnection(server_address.host + ":" + server_address.port, profile, false, this.reconnect_properties(profile));
|
||||
this._reconnect_attempt = true;
|
||||
this.startConnection(server_address.host + ":" + server_address.port, profile, false, Object.assign(this.reconnect_properties(profile), {auto_reconnect_attempt: true}));
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -729,7 +729,7 @@ class IconManager {
|
|||
throw "icon not found";
|
||||
}
|
||||
|
||||
static generate_tag(icon: Promise<Icon> | Icon, options?: {
|
||||
static generate_tag(icon: Promise<Icon> | Icon | undefined, options?: {
|
||||
animate?: boolean
|
||||
}) : JQuery<HTMLDivElement> {
|
||||
options = options || {};
|
||||
|
|
|
@ -557,8 +557,8 @@ namespace connection {
|
|||
let client = tree.findClient(json["clid"]);
|
||||
let self = client instanceof LocalClientEntry;
|
||||
|
||||
let channel_to = tree.findChannel(json["ctid"]);
|
||||
let channel_from = tree.findChannel(json["cfid"]);
|
||||
let channel_to = tree.findChannel(parseInt(json["ctid"]));
|
||||
let channel_from = tree.findChannel(parseInt(json["cfid"]));
|
||||
|
||||
if(!client) {
|
||||
log.error(LogCategory.NETWORKING, tr("Unknown client move (Client)!"));
|
||||
|
@ -573,6 +573,7 @@ namespace connection {
|
|||
if(!self) {
|
||||
if(!channel_from) {
|
||||
log.error(LogCategory.NETWORKING, tr("Unknown client move (Channel from)!"));
|
||||
channel_from = client.currentChannel();
|
||||
} else if(channel_to !== client.currentChannel()) {
|
||||
log.error(LogCategory.NETWORKING,
|
||||
tr("Client move from invalid source channel! Local client registered in channel %d but server send %d."),
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
enum ErrorID {
|
||||
NOT_IMPLEMENTED = 0x2,
|
||||
COMMAND_NOT_FOUND = 0x100,
|
||||
|
||||
PERMISSION_ERROR = 2568,
|
||||
EMPTY_RESULT = 0x0501,
|
||||
PLAYLIST_IS_IN_USE = 0x2103,
|
||||
|
|
|
@ -383,7 +383,14 @@ class PermissionValue {
|
|||
granted(requiredValue: number, required: boolean = true) : boolean {
|
||||
let result;
|
||||
result = this.value == -1 || this.value >= requiredValue || (this.value == -2 && requiredValue == -2 && !required);
|
||||
log.trace(LogCategory.PERMISSIONS, tr("Test needed required: %o | %i | %o => %o"), this, requiredValue, required, result);
|
||||
|
||||
log.trace(LogCategory.PERMISSIONS,
|
||||
tr("Required permission test resulted for permission %s: %s. Required value: %s, Granted value: %s"),
|
||||
this.type.name,
|
||||
result ? tr("granted") : tr("denied"),
|
||||
requiredValue + (required ? " (" + tr("required") + ")" : ""),
|
||||
this.hasValue() ? this.value : tr("none")
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,15 @@ namespace profiles {
|
|||
this.id = id;
|
||||
}
|
||||
|
||||
connect_username() : string {
|
||||
if(this.default_username && this.default_username !== "Another TeaSpeak user")
|
||||
return this.default_username;
|
||||
|
||||
let selected = this.selected_identity();
|
||||
let name = selected ? selected.fallback_name() : undefined;
|
||||
return name || "Another TeaSpeak user";
|
||||
}
|
||||
|
||||
selected_identity(current_type?: identities.IdentitifyType) : identities.Identity {
|
||||
if(!current_type)
|
||||
current_type = this.selected_type();
|
||||
|
@ -139,7 +148,7 @@ namespace profiles {
|
|||
{
|
||||
const profile = create_new_profile("default","default");
|
||||
profile.default_password = "";
|
||||
profile.default_username = "Another TeaSpeak user";
|
||||
profile.default_username = "";
|
||||
profile.profile_name = "Default Profile";
|
||||
|
||||
/* generate default identity */
|
||||
|
@ -160,7 +169,7 @@ namespace profiles {
|
|||
{ /* forum identity (works only when connected to the forum) */
|
||||
const profile = create_new_profile("TeaSpeak Forum","teaforo");
|
||||
profile.default_password = "";
|
||||
profile.default_username = "Another TeaSpeak user";
|
||||
profile.default_username = "";
|
||||
profile.profile_name = "TeaSpeak Forum profile";
|
||||
|
||||
profile.set_identity(identities.IdentitifyType.TEAFORO, identities.static_forum_identity());
|
||||
|
@ -174,7 +183,7 @@ namespace profiles {
|
|||
export function create_new_profile(name: string, id?: string) : ConnectionProfile {
|
||||
const profile = new ConnectionProfile(id || guid());
|
||||
profile.profile_name = name;
|
||||
profile.default_username = "Another TeaSpeak user";
|
||||
profile.default_username = "";
|
||||
available_profiles.push(profile);
|
||||
return profile;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ namespace profiles.identities {
|
|||
}
|
||||
|
||||
export interface Identity {
|
||||
name() : string;
|
||||
fallback_name(): string | undefined ;
|
||||
uid() : string;
|
||||
type() : IdentitifyType;
|
||||
|
||||
|
|
|
@ -47,7 +47,9 @@ namespace profiles.identities {
|
|||
|
||||
set_name(name: string) { this._name = name; }
|
||||
|
||||
name(): string {
|
||||
name() : string { return this._name; }
|
||||
|
||||
fallback_name(): string | undefined {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
|
|
|
@ -84,8 +84,8 @@ namespace profiles.identities {
|
|||
return new TeaForumHandshakeHandler(connection, this);
|
||||
}
|
||||
|
||||
name(): string {
|
||||
return (this.identity_data ? this.identity_data.name() : "Another TeaSpeak user");
|
||||
fallback_name(): string | undefined {
|
||||
return this.identity_data ? this.identity_data.name() : undefined;
|
||||
}
|
||||
|
||||
type(): profiles.identities.IdentitifyType {
|
||||
|
|
|
@ -503,7 +503,7 @@ namespace profiles.identities {
|
|||
}
|
||||
}
|
||||
|
||||
name(): string {
|
||||
fallback_name(): string | undefined {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
|
@ -808,7 +808,7 @@ namespace profiles.identities {
|
|||
return "[Identity]\n" +
|
||||
"id=TeaWeb-Exported\n" +
|
||||
"identity=\"" + identity + "\"\n" +
|
||||
"nickname=\"" + this.name() + "\"\n" +
|
||||
"nickname=\"" + this.fallback_name() + "\"\n" +
|
||||
"phonetic_nickname=";
|
||||
}
|
||||
|
||||
|
|
|
@ -295,6 +295,11 @@ class Settings extends StaticSettings {
|
|||
key: "font_size"
|
||||
};
|
||||
|
||||
static readonly KEY_ICON_SIZE: SettingsKey<number> = {
|
||||
key: "icon_size",
|
||||
default_value: 100
|
||||
};
|
||||
|
||||
static readonly KEY_LAST_INVITE_LINK_TYPE: SettingsKey<string> = {
|
||||
key: "last_invite_link_type",
|
||||
default_value: "tea-web"
|
||||
|
|
|
@ -694,7 +694,7 @@ class ChannelEntry {
|
|||
if(this._channel_name_formatted !== undefined) {
|
||||
tag_container_name.addClass(this._channel_name_alignment);
|
||||
|
||||
if(this._channel_name_alignment == "align-repetitive") {
|
||||
if(this._channel_name_alignment == "align-repetitive" && text.length > 0) {
|
||||
while(text.length < 1024 * 8)
|
||||
text += text;
|
||||
}
|
||||
|
|
|
@ -147,7 +147,11 @@ class ClientEntry {
|
|||
}
|
||||
if(this._audio_handle) {
|
||||
log.warn(LogCategory.AUDIO, tr("Destroying client with an active audio handle. This could cause memory leaks!"));
|
||||
this._audio_handle.abort_replay();
|
||||
try {
|
||||
this._audio_handle.abort_replay();
|
||||
} catch(error) {
|
||||
log.warn(LogCategory.AUDIO, tr("Failed to abort replay: %o"), error);
|
||||
}
|
||||
this._audio_handle.callback_playback = undefined;
|
||||
this._audio_handle.callback_stopped = undefined;
|
||||
this._audio_handle = undefined;
|
||||
|
@ -159,7 +163,11 @@ class ClientEntry {
|
|||
tree_unregistered() {
|
||||
this.channelTree = undefined;
|
||||
if(this._audio_handle) {
|
||||
this._audio_handle.abort_replay();
|
||||
try {
|
||||
this._audio_handle.abort_replay();
|
||||
} catch(error) {
|
||||
log.warn(LogCategory.AUDIO, tr("Failed to abort replay: %o"), error);
|
||||
}
|
||||
this._audio_handle.callback_playback = undefined;
|
||||
this._audio_handle.callback_stopped = undefined;
|
||||
this._audio_handle = undefined;
|
||||
|
|
|
@ -128,7 +128,35 @@ namespace MessageHelper {
|
|||
]
|
||||
});
|
||||
|
||||
container.find("a").attr('target', "_blank");
|
||||
container.find("a")
|
||||
.attr('target', "_blank")
|
||||
.on('contextmenu', event => {
|
||||
if(event.isDefaultPrevented()) return;
|
||||
event.preventDefault();
|
||||
|
||||
const url = $(event.target).attr("href");
|
||||
contextmenu.spawn_context_menu(event.pageX, event.pageY, {
|
||||
callback: () => {
|
||||
const win = window.open(url, '_blank');
|
||||
win.focus();
|
||||
},
|
||||
name: tr("Open URL"),
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
icon_class: "client-browse-addon-online"
|
||||
}, {
|
||||
callback: () => {
|
||||
//TODO
|
||||
},
|
||||
name: tr("Open URL in Browser"),
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
visible: !app.is_web() && false // Currently not possible
|
||||
}, contextmenu.Entry.HR(), {
|
||||
callback: () => copy_to_clipboard(url),
|
||||
name: tr("Copy URL to clipboard"),
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
icon_class: "client-copy"
|
||||
});
|
||||
});
|
||||
|
||||
return [container.contents() as JQuery];
|
||||
//return result.root_tag.content.map(e => e.build_html()).map((entry, idx, array) => $.spawn("a").css("display", (idx == 0 ? "inline" : "") + "block").html(entry == "" && idx != 0 ? " " : entry));
|
||||
|
@ -264,11 +292,24 @@ namespace MessageHelper {
|
|||
return result.length > 0 ? result.substring(1) : default_value;
|
||||
}
|
||||
|
||||
let _icon_size_style: JQuery<HTMLStyleElement>;
|
||||
export function set_icon_size(size: string) {
|
||||
if(!_icon_size_style)
|
||||
_icon_size_style = $.spawn("style").appendTo($("#style"));
|
||||
|
||||
_icon_size_style.text("\n" +
|
||||
".message > .emoji {\n" +
|
||||
" height: " + size + "!important;\n" +
|
||||
" width: " + size + "!important;\n" +
|
||||
"}\n"
|
||||
);
|
||||
}
|
||||
|
||||
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
||||
name: "XBBCode code tag init",
|
||||
function: async () => {
|
||||
/* override default parser */
|
||||
xbbcode.register.register_parser( {
|
||||
xbbcode.register.register_parser({
|
||||
tag: ["code", "icode", "i-code"],
|
||||
content_tags_whitelist: [],
|
||||
|
||||
|
@ -297,5 +338,13 @@ namespace MessageHelper {
|
|||
});
|
||||
},
|
||||
priority: 10
|
||||
})
|
||||
});
|
||||
|
||||
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
||||
name: "icon size init",
|
||||
function: async () => {
|
||||
MessageHelper.set_icon_size((settings.static_global(Settings.KEY_ICON_SIZE) / 100).toFixed(2) + "em");
|
||||
},
|
||||
priority: 10
|
||||
});
|
||||
}
|
|
@ -1791,11 +1791,14 @@ test
|
|||
private _container_new_message: JQuery;
|
||||
|
||||
private _container_no_permissions: JQuery;
|
||||
private _container_no_permissions_shown: boolean = false
|
||||
private _container_no_permissions_shown: boolean = false;
|
||||
|
||||
private _container_is_private: JQuery;
|
||||
private _container_is_private_shown: boolean = false;
|
||||
|
||||
private _container_no_support: JQuery;
|
||||
private _container_no_support_shown: boolean = false;
|
||||
|
||||
private _view_max_messages = 40; /* reset to 40 again as soon we tab out :) */
|
||||
private _view_older_messages: ViewEntry;
|
||||
private _has_older_messages: boolean; /* undefined := not known | else flag */
|
||||
|
@ -1837,6 +1840,7 @@ test
|
|||
this._container_new_message = this._html_tag.find(".new-message");
|
||||
this._container_no_permissions = this._html_tag.find(".no-permissions").hide();
|
||||
this._container_is_private = this._html_tag.find(".private-conversation").hide();
|
||||
this._container_no_support = this._html_tag.find(".not-supported").hide();
|
||||
|
||||
this._container_messages = this._html_tag.find(".container-messages");
|
||||
this._container_messages.on('scroll', event => {
|
||||
|
@ -1969,6 +1973,12 @@ test
|
|||
} else if(error.id == ErrorID.CONVERSATION_IS_PRIVATE) {
|
||||
this.set_flag_private(true);
|
||||
}
|
||||
/*
|
||||
else if(error.id == ErrorID.NOT_IMPLEMENTED || error.id == ErrorID.COMMAND_NOT_FOUND) {
|
||||
this._container_no_support.show();
|
||||
this._container_no_support_shown = true;
|
||||
}
|
||||
*/
|
||||
}
|
||||
//TODO log and handle!
|
||||
log.error(LogCategory.CHAT, tr("Failed to fetch conversation history. %o"), error);
|
||||
|
@ -2124,7 +2134,7 @@ test
|
|||
}
|
||||
|
||||
chat_available() : boolean {
|
||||
return !this._container_no_permissions_shown && !this._container_is_private_shown;
|
||||
return !this._container_no_permissions_shown && !this._container_is_private_shown && !this._container_no_support_shown;
|
||||
}
|
||||
|
||||
text_send_failed(error: CommandResult | any) {
|
||||
|
|
|
@ -154,18 +154,16 @@ namespace Modals {
|
|||
}
|
||||
|
||||
console.log("Updating");
|
||||
if(selected_profile)
|
||||
input_nickname.attr("placeholder", selected_profile.default_username);
|
||||
else
|
||||
input_nickname.attr("placeholder", "");
|
||||
|
||||
let address = input_address.val().toString();
|
||||
settings.changeGlobal(Settings.KEY_CONNECT_ADDRESS, address);
|
||||
let flag_address = !!address.match(Regex.IP_V4) || !!address.match(Regex.IP_V6) || !!address.match(Regex.DOMAIN);
|
||||
|
||||
let nickname = input_nickname.val().toString();
|
||||
settings.changeGlobal(Settings.KEY_CONNECT_USERNAME, nickname);
|
||||
let flag_nickname = (nickname.length == 0 && selected_profile && selected_profile.default_username.length > 0) || nickname.length >= 3 && nickname.length <= 32;
|
||||
if(nickname)
|
||||
settings.changeGlobal(Settings.KEY_CONNECT_USERNAME, nickname);
|
||||
else
|
||||
nickname = input_nickname.attr("placeholder") || "";
|
||||
let flag_nickname = nickname.length >= 3 && nickname.length <= 32;
|
||||
|
||||
input_address.attr('pattern', flag_address ? null : '^[a]{1000}$').toggleClass('is-invalid', !flag_address);
|
||||
input_nickname.attr('pattern', flag_nickname ? null : '^[a]{1000}$').toggleClass('is-invalid', !flag_nickname);
|
||||
|
@ -202,8 +200,10 @@ namespace Modals {
|
|||
input_profile.on('change', event => {
|
||||
selected_profile = profiles.find_profile(input_profile.val() as string);
|
||||
{
|
||||
settings.changeGlobal(Settings.KEY_CONNECT_USERNAME, selected_profile.default_username);
|
||||
input_nickname.val(selected_profile.default_username);
|
||||
settings.changeGlobal(Settings.KEY_CONNECT_USERNAME, undefined);
|
||||
input_nickname
|
||||
.attr('placeholder', selected_profile.connect_username() || "Another TeaSpeak user")
|
||||
.val("");
|
||||
}
|
||||
input_profile.toggleClass("is-invalid", !selected_profile || !selected_profile.valid());
|
||||
updateFields(true);
|
||||
|
@ -233,7 +233,7 @@ namespace Modals {
|
|||
selected_profile,
|
||||
true,
|
||||
{
|
||||
nickname: input_nickname.val().toString() || selected_profile.default_username,
|
||||
nickname: input_nickname.val().toString() || input_nickname.attr("placeholder"),
|
||||
password: (current_connect_data && current_connect_data.password_hash) ? {password: current_connect_data.password_hash, hashed: true} : {password: input_password.val().toString(), hashed: false}
|
||||
}
|
||||
);
|
||||
|
@ -251,7 +251,7 @@ namespace Modals {
|
|||
selected_profile,
|
||||
true,
|
||||
{
|
||||
nickname: input_nickname.val().toString() || selected_profile.default_username,
|
||||
nickname: input_nickname.val().toString() || input_nickname.attr("placeholder"),
|
||||
password: (current_connect_data && current_connect_data.password_hash) ? {password: current_connect_data.password_hash, hashed: true} : {password: input_password.val().toString(), hashed: false}
|
||||
}
|
||||
);
|
||||
|
|
|
@ -227,13 +227,12 @@ namespace Modals {
|
|||
else
|
||||
type = "temp";
|
||||
|
||||
console.log(type);
|
||||
console.log(Object.assign({}, properties));
|
||||
simple.find("option[name='channel-type'][value='" + type + "']").prop("selected", true);
|
||||
};
|
||||
|
||||
input_advanced_type.on('change', event => {
|
||||
switch(input_advanced_type.val()) {
|
||||
const value = [...input_advanced_type as JQuery<HTMLInputElement>].find(e => e.checked).value;
|
||||
switch(value) {
|
||||
case "semi":
|
||||
properties.channel_flag_permanent = false;
|
||||
properties.channel_flag_semi_permanent = true;
|
||||
|
@ -264,20 +263,8 @@ namespace Modals {
|
|||
const select_default = tag.find(".input-flag-default");
|
||||
|
||||
{
|
||||
|
||||
if(!channel) {
|
||||
if(permission_perm)
|
||||
tag_type_perm.find("input").trigger('click');
|
||||
else if(permission_semi)
|
||||
tag_type_semi.find("input").trigger('click');
|
||||
else
|
||||
tag_type_temp.find("input").trigger('click');
|
||||
}
|
||||
|
||||
select_default.on('change', event => {
|
||||
const node = select_default[0] as HTMLInputElement;
|
||||
console.log(node.checked);
|
||||
|
||||
properties.channel_flag_default = node.checked;
|
||||
|
||||
if(node.checked)
|
||||
|
@ -345,6 +332,25 @@ namespace Modals {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* init */
|
||||
setTimeout(() => {
|
||||
if(!channel) {
|
||||
if(permission_perm)
|
||||
tag_type_perm.find("input").trigger('click');
|
||||
else if(permission_semi)
|
||||
tag_type_semi.find("input").trigger('click');
|
||||
else
|
||||
tag_type_temp.find("input").trigger('click');
|
||||
} else {
|
||||
if(channel.properties.channel_flag_permanent)
|
||||
tag_type_perm.find("input").trigger('click');
|
||||
else if(channel.properties.channel_flag_semi_permanent)
|
||||
tag_type_semi.find("input").trigger('click');
|
||||
else
|
||||
tag_type_temp.find("input").trigger('click');
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/* Talk power */
|
||||
|
@ -481,7 +487,8 @@ namespace Modals {
|
|||
|
||||
function applyPermissionListener(connection: ConnectionHandler, properties: ChannelProperties, tag: JQuery, button: JQuery, permissions: PermissionManager, channel?: ChannelEntry) {
|
||||
let apply_permissions = (channel_permissions: PermissionValue[]) => {
|
||||
console.log(tr("Got permissions: %o"), channel_permissions);
|
||||
log.trace(LogCategory.CHANNEL, tr("Received channel permissions: %o"), channel_permissions);
|
||||
|
||||
let required_power = -2;
|
||||
for(let cperm of channel_permissions)
|
||||
if(cperm.type.name == PermissionType.I_CHANNEL_NEEDED_MODIFY_POWER) {
|
||||
|
@ -509,7 +516,7 @@ namespace Modals {
|
|||
}
|
||||
});
|
||||
|
||||
const permission = permissions.neededPermission(PermissionType.I_CHANNEL_MODIFY_POWER).granted(required_power, false);
|
||||
const permission = permissions.neededPermission(PermissionType.I_CHANNEL_PERMISSION_MODIFY_POWER).granted(required_power, false);
|
||||
tag.find("input[permission]").prop("disabled", !permission).parent(".input-boxed").toggleClass("disabled", !permission); //No permissions
|
||||
};
|
||||
|
||||
|
|
|
@ -380,9 +380,6 @@ namespace Modals {
|
|||
|
||||
let upload_key: transfer.UploadKey;
|
||||
try {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
throw "test error";;
|
||||
|
||||
upload_key = await client.fileManager.upload_file({
|
||||
channel: undefined,
|
||||
channel_password: undefined,
|
||||
|
|
|
@ -16,7 +16,6 @@ namespace Modals {
|
|||
left.find(".selected").removeClass("selected");
|
||||
|
||||
const target = entry.attr("container");
|
||||
console.log(target);
|
||||
if(!target) return;
|
||||
|
||||
right.find("> .container." + target).removeClass("hidden");
|
||||
|
@ -74,7 +73,7 @@ namespace Modals {
|
|||
select.on('change', event => {
|
||||
const value = parseInt(select.val() as string);
|
||||
settings.changeGlobal(Settings.KEY_FONT_SIZE, value);
|
||||
console.log("Changed font size of %dpx", value);
|
||||
console.log("Changed font size to %dpx", value);
|
||||
|
||||
$(document.body).css("font-size", value + "px");
|
||||
});
|
||||
|
@ -349,6 +348,28 @@ namespace Modals {
|
|||
settings.changeGlobal(Settings.KEY_CHAT_TAG_URLS, option[0].checked);
|
||||
}).prop("checked", settings.static_global(Settings.KEY_CHAT_TAG_URLS));
|
||||
}
|
||||
/* Icon size */
|
||||
{
|
||||
const container_slider = container.find(".container-icon-size .container-slider");
|
||||
const container_value = container.find(".container-icon-size .value");
|
||||
|
||||
sliderfy(container_slider, {
|
||||
unit: '%',
|
||||
min_value: 25,
|
||||
max_value: 300,
|
||||
step: 5,
|
||||
initial_value: settings.static_global(Settings.KEY_ICON_SIZE),
|
||||
value_field: container_value
|
||||
});
|
||||
|
||||
container_slider.on('change', event => {
|
||||
const value = parseInt(container_slider.attr("value") as string);
|
||||
settings.changeGlobal(Settings.KEY_ICON_SIZE, value);
|
||||
console.log("Changed icon size to %sem", (value / 100).toFixed(2));
|
||||
|
||||
MessageHelper.set_icon_size((value / 100).toFixed(2) + "em");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function settings_audio_microphone(container: JQuery, modal: Modal) {
|
||||
|
@ -942,7 +963,10 @@ namespace Modals {
|
|||
select_type.parent().toggleClass("is-invalid", true);
|
||||
} else {
|
||||
input_name.val(selected_profile.identity.profile_name).prop("disabled", false);
|
||||
input_default_name.val(selected_profile.identity.default_username).prop("disabled", false);
|
||||
input_default_name
|
||||
.val(selected_profile.identity.default_username)
|
||||
.attr("placeholder", selected_profile.identity.connect_username() || "Another TeaSpeak user")
|
||||
.prop("disabled", false);
|
||||
select_type.val(selected_profile.identity.selected_identity_type || "unset").prop("disabled", false);
|
||||
}
|
||||
|
||||
|
|
|
@ -205,7 +205,7 @@ namespace pe {
|
|||
this._tag_granted_input.on('change', event => {
|
||||
const str_value = this._tag_granted_input.val() as string;
|
||||
const value = parseInt(str_value);
|
||||
if(!HTMLPermission.number_filter_re.test(str_value) || value == NaN) {
|
||||
if(!HTMLPermission.number_filter_re.test(str_value) || Number.isNaN(value)) {
|
||||
console.warn(tr("Failed to parse given permission granted value string: %s"), this._tag_granted_input.val());
|
||||
this._reset_value();
|
||||
return;
|
||||
|
@ -369,6 +369,8 @@ namespace pe {
|
|||
return (this._mask & 0x03) > 0;
|
||||
}
|
||||
|
||||
get_value() { return this._value; }
|
||||
|
||||
value(value: number | undefined, skip?: boolean, negate?: boolean) {
|
||||
if(typeof value === "undefined") {
|
||||
this._tag_value.detach();
|
||||
|
@ -672,6 +674,25 @@ namespace pe {
|
|||
}
|
||||
}
|
||||
|
||||
private update_icon() {
|
||||
const permission = this.permission_map.find(e => e && e.permission.name === "i_icon_id");
|
||||
const icon_id = permission ? permission.get_value() : 0;
|
||||
|
||||
const icon_node = this.container.find(".container-icon-select .icon-preview");
|
||||
icon_node.children().remove();
|
||||
|
||||
let resolve: Promise<JQuery<HTMLDivElement>>;
|
||||
if(icon_id >= 0 && icon_id <= 1000)
|
||||
resolve = Promise.resolve(IconManager.generate_tag({id: icon_id, url: ""}));
|
||||
else
|
||||
resolve = this.icon_resolver(permission ? permission.get_value() : 0).then(e => $(e));
|
||||
|
||||
resolve.then(tag => tag.appendTo(icon_node))
|
||||
.catch(error => {
|
||||
log.error(LogCategory.PERMISSIONS, tr("Failed to generate empty icon preview: %o"), error);
|
||||
});
|
||||
}
|
||||
|
||||
private build_tag() {
|
||||
this.container = $("#tmpl_permission_editor_html").renderTag();
|
||||
this.container.find("input").on('change', event => {
|
||||
|
@ -765,6 +786,51 @@ namespace pe {
|
|||
build_group(undefined, group, 0);
|
||||
}
|
||||
|
||||
{
|
||||
const container = this.container.find(".container-icon-select");
|
||||
container.find(".button-select-icon").on('click', event => {
|
||||
const permission = this.permission_map.find(e => e && e.permission.name === "i_icon_id");
|
||||
this.icon_selector(permission ? permission.get_value() : 0).then(id => {
|
||||
const permission = this.permission_map.find(e => e && e.permission.name === "i_icon_id");
|
||||
if(permission) {
|
||||
this.trigger_change(permission.permission, {
|
||||
remove: false,
|
||||
value: id,
|
||||
flag_skip: false,
|
||||
flag_negate: false
|
||||
}, false).then(() => {
|
||||
log.debug(LogCategory.PERMISSIONS, tr("Selected new icon %s"), id);
|
||||
|
||||
permission.value(id, false, false);
|
||||
this.update_icon();
|
||||
}).catch(error => {
|
||||
log.warn(LogCategory.PERMISSIONS, tr("Failed to set icon permission within permission editor: %o"), error);
|
||||
});
|
||||
} else {
|
||||
log.warn(LogCategory.PERMISSIONS, tr("Failed to find icon permissions within permission editor"));
|
||||
}
|
||||
}).catch(error => {
|
||||
log.error(LogCategory.PERMISSIONS, tr("Failed to select an icon for the icon permission: %o"), error);
|
||||
});
|
||||
});
|
||||
|
||||
container.find(".button-icon-remove").on('click', event => {
|
||||
const permission = this.permission_map.find(e => e && e.permission.name === "i_icon_id");
|
||||
if(permission) {
|
||||
this.trigger_change(permission.permission, {
|
||||
remove: true,
|
||||
}, false).then(() => {
|
||||
permission.value(undefined);
|
||||
this.update_icon();
|
||||
}).catch(error => {
|
||||
log.warn(LogCategory.PERMISSIONS, tr("Failed to remove icon permission within permission editor: %o"), error);
|
||||
});
|
||||
} else {
|
||||
log.warn(LogCategory.PERMISSIONS, tr("Failed to find icon permission within permission editor"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.mode_container_permissions.on('contextmenu', event => {
|
||||
if(event.isDefaultPrevented())
|
||||
return;
|
||||
|
@ -812,6 +878,7 @@ namespace pe {
|
|||
permission_handle.granted(new_permission.granted_value);
|
||||
}
|
||||
|
||||
this.update_icon();
|
||||
this.update_filter();
|
||||
}
|
||||
|
||||
|
@ -821,9 +888,17 @@ namespace pe {
|
|||
this.mode_container_unset.css('display', mode == Modals.PermissionEditorMode.UNSET ? 'block' : 'none');
|
||||
}
|
||||
|
||||
trigger_change(permission: PermissionInfo, value?: Modals.PermissionEditor.PermissionValue) : Promise<void> {
|
||||
if(this._listener_change)
|
||||
return this._listener_change(permission, value);
|
||||
trigger_change(permission: PermissionInfo, value?: Modals.PermissionEditor.PermissionValue, update_icon?: boolean) : Promise<void> {
|
||||
if(this._listener_change) {
|
||||
if((typeof(update_icon) !== "boolean" || update_icon) && permission && permission.name === "i_icon_id")
|
||||
return this._listener_change(permission, value).then(e => {
|
||||
setTimeout(() => this.update_icon(), 0); /* we need to fully handle the response and then only we're able to update the icon */
|
||||
return e;
|
||||
});
|
||||
else
|
||||
return this._listener_change(permission, value);
|
||||
}
|
||||
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
|
|
4
todo.txt
4
todo.txt
|
@ -118,4 +118,6 @@ Fix these icons: https://img.did.science/Screenshot_20-11-06.png
|
|||
- Crash
|
||||
- Focus crash window on crash
|
||||
- Add a notification (Like the browser notifications)
|
||||
Connection state sometimes does not update
|
||||
Connection state sometimes does not update
|
||||
The teaforum account does not show the premium status
|
||||
Allow channel chatting in the current channel
|
Loading…
Add table
Reference in a new issue