Some small additions

This commit is contained in:
WolverinDEV 2019-10-19 17:13:40 +02:00
parent 57caf21327
commit fb430af880
29 changed files with 466 additions and 88 deletions

View file

@ -31,7 +31,7 @@ function submitLogin(user: string, pass: string) {
return; return;
} }
if (data["allowed"] == false) { if (data["allowed"] == false) {
loginFailed("You're not allowed for the closed alpha!"); loginFailed("You're not allowed for the closed beta!");
return; return;
} }
$("#login").hide(500); $("#login").hide(500);

View file

@ -90,6 +90,7 @@ $border_color_activated: rgba(255, 255, 255, .75);
width: 1.5em; width: 1.5em;
} }
overflow: hidden;
padding: .25em; padding: .25em;
} }
} }

View file

@ -922,7 +922,7 @@ $client_info_avatar_size: 10em;
} }
} }
.no-permissions, .private-conversation { .no-permissions, .private-conversation, .not-supported {
flex-grow: 0; flex-grow: 0;
flex-shrink: 0; flex-shrink: 0;
@ -1308,13 +1308,13 @@ $client_info_avatar_size: 10em;
.container-private-conversations, .container-channel-chat { .container-private-conversations, .container-channel-chat {
.container-message .emoji { .container-message .emoji {
height: 1em; height: 1.1em;
width: 1em; width: 1.1em;
margin-left: .1em; margin-left: .1em;
margin-right: .1em; margin-right: .1em;
vertical-align: text-top; vertical-align: text-bottom;
} }
} }
} }

View file

@ -8,7 +8,7 @@
min-width: 30em!important; min-width: 30em!important;
max-height: calc(100vh - 10em); max-height: calc(100vh - 10em);
padding: 0em!important; padding: 0 !important;
.row { .row {
flex-grow: 0; flex-grow: 0;

View file

@ -477,6 +477,13 @@
.container-filter { .container-filter {
justify-content: stretch; justify-content: stretch;
height: 4em;
.form-group {
height: 3.5em;
padding-top: 1.25em;
margin-bottom: 0!important;
}
.button-toggle-clients { .button-toggle-clients {
flex-grow: 0; flex-grow: 0;
@ -498,6 +505,8 @@
.container-granted-switch { .container-granted-switch {
margin-left: 1em; margin-left: 1em;
margin-bottom: 1em;
position: relative; position: relative;
display: flex; display: flex;
@ -511,6 +520,8 @@
min-width: 8em; min-width: 8em;
> label { > label {
font-size: .75em;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: flex-start; justify-content: flex-start;
@ -528,10 +539,122 @@
a { a {
padding-left: .25em; 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 { .container-mode {

View file

@ -126,6 +126,10 @@
} }
&.general-application { &.general-application {
> div {
margin-top: .25em;
}
.container-font-size { .container-font-size {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -313,6 +317,14 @@
} }
} }
&.general-chat {
.container-icon-size {
.value {
margin-left: .5em;
}
}
}
&.audio-microphone, &.audio-speaker, &.audio-sounds, &.identity-forum { &.audio-microphone, &.audio-speaker, &.audio-sounds, &.identity-forum {
flex-direction: row; flex-direction: row;
justify-content: stretch; justify-content: stretch;

View file

@ -9,7 +9,7 @@
$localhost = true; $localhost = true;
?> ?>
<?php <?php
if(!$localhost) { if(!$localhost && !$WEB_CLIENT) {
/* Web Testing stuff */ /* Web Testing stuff */
define("_AUTH_API_ONLY", true); define("_AUTH_API_ONLY", true);
if(file_exists("./auth.php")) if(file_exists("./auth.php"))

View file

@ -371,6 +371,12 @@
<div class="private-conversation"> <div class="private-conversation">
<div>{{tr "Conversation is private. Join the channel to participate!" /}}</div> <div>{{tr "Conversation is private. Join the channel to participate!" /}}</div>
</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> </div>
</script> </script>
@ -804,17 +810,14 @@
<fieldset class="container-channel-type"> <fieldset class="container-channel-type">
<label class="type type-temp"> <label class="type type-temp">
<div class="ratio-button"> <div class="ratio-button">
<input type="radio" name="channel_type" value="temp" {{if <input type="radio" name="channel_type" value="temp"/>
!channel_flag_semi_permanent &&
!channel_flag_permanent}}checked{{/if}}/>
<div class="mark"></div> <div class="mark"></div>
</div> </div>
<a>{{tr "Temporary" /}}</a> <a>{{tr "Temporary" /}}</a>
</label> </label>
<label class="type type-semi"> <label class="type type-semi">
<div class="ratio-button"> <div class="ratio-button">
<input type="radio" name="channel_type" value="semi" {{if <input type="radio" name="channel_type" value="semi"/>
channel_flag_semi_permanent}}checked{{/if}}/>
<div class="mark"></div> <div class="mark"></div>
</div> </div>
<a>{{tr "Semi-Permanent" /}}</a> <a>{{tr "Semi-Permanent" /}}</a>
@ -822,8 +825,7 @@
<div class="container-perm-default"> <div class="container-perm-default">
<label class="type type-perm"> <label class="type type-perm">
<div class="ratio-button"> <div class="ratio-button">
<input type="radio" name="channel_type" value="perm" {{if <input type="radio" name="channel_type" value="perm"/>
channel_flag_permanent}}checked{{/if}}/>
<div class="mark"></div> <div class="mark"></div>
</div> </div>
<a>{{tr "Permanent" /}}</a> <a>{{tr "Permanent" /}}</a>
@ -1078,10 +1080,23 @@
</div> </div>
</div> </div>
<div class="container-permission"> <div class="container-permission">
<a class="name">{{tr "Subscribe" /}}</a> <a class="name">{{tr "Description view" /}}</a>
<div class="input-boxed"> <div class="input-boxed">
<input type="number" min="0" value="0" class="value" <input type="number" min="0" value="0" class="value"
permission="i_channel_needed_description_view_power"> 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"> <div class="container-tooltip tooltip-permission-subscribe">
<img src="img/icon_tooltip.svg"/> <img src="img/icon_tooltip.svg"/>
<div class="tooltip"> <div class="tooltip">
@ -1982,6 +1997,17 @@
</div> </div>
<a>{{tr "Enables markdown input support for chat. " /}}</a> <a>{{tr "Enables markdown input support for chat. " /}}</a>
</label> </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>
<div class="container audio-microphone"> <div class="container audio-microphone">
@ -2820,7 +2846,7 @@
<input type="text" class="form-control filter-input"/> <input type="text" class="form-control filter-input"/>
<!-- <small class="form-text text-muted">{{tr "Filter permissions by permission name" /}}</small> --> <!-- <small class="form-text text-muted">{{tr "Filter permissions by permission name" /}}</small> -->
</div> </div>
<div class="form-group container-granted-switch"> <div class="container-granted-switch">
<label> <label>
<div class="switch"> <div class="switch">
<input type="checkbox" class="filter-granted"> <input type="checkbox" class="filter-granted">
@ -2832,6 +2858,20 @@
<a>{{tr "Assigned only" /}}</a> <a>{{tr "Assigned only" /}}</a>
</label> </label>
</div> </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>
<div class="container-mode container-mode-permissions"> <div class="container-mode container-mode-permissions">
<div class="container-permission-list"> <div class="container-permission-list">

View file

@ -76,6 +76,7 @@ interface ConnectParameters {
}; };
token?: string; token?: string;
password?: {password: string, hashed: boolean}; password?: {password: string, hashed: boolean};
auto_reconnect_attempt?: boolean;
} }
class ConnectionHandler { class ConnectionHandler {
@ -99,8 +100,10 @@ class ConnectionHandler {
private _clientId: number = 0; private _clientId: number = 0;
private _local_client: LocalClientEntry; private _local_client: LocalClientEntry;
private _reconnect_timer: NodeJS.Timer; private _reconnect_timer: NodeJS.Timer;
private _reconnect_attempt: boolean = false; private _reconnect_attempt: boolean = false;
private _connect_initialize_id: number = 1; private _connect_initialize_id: number = 1;
client_status: VoiceStatus = { client_status: VoiceStatus = {
@ -169,7 +172,7 @@ class ConnectionHandler {
async startConnection(addr: string, profile: profiles.ConnectionProfile, user_action: boolean, parameters: ConnectParameters) { async startConnection(addr: string, profile: profiles.ConnectionProfile, user_action: boolean, parameters: ConnectParameters) {
this.tab_set_name(tr("Connecting")); this.tab_set_name(tr("Connecting"));
this.cancel_reconnect(false); this.cancel_reconnect(false);
this._reconnect_attempt = false; this._reconnect_attempt = parameters.auto_reconnect_attempt || false;
if(this.serverConnection) if(this.serverConnection)
this.handleDisconnect(DisconnectReason.REQUESTED); this.handleDisconnect(DisconnectReason.REQUESTED);
@ -475,10 +478,12 @@ class ConnectionHandler {
break; break;
case DisconnectReason.CONNECTION_CLOSED: case DisconnectReason.CONNECTION_CLOSED:
log.error(LogCategory.CLIENT, tr("Lost connection to remote server!")); log.error(LogCategory.CLIENT, tr("Lost connection to remote server!"));
createErrorModal( if(!this._reconnect_attempt) {
tr("Connection closed"), createErrorModal(
tr("The connection was closed by remote host") tr("Connection closed"),
).open(); tr("The connection was closed by remote host")
).open();
}
this.sound.play(Sound.CONNECTION_DISCONNECTED); this.sound.play(Sound.CONNECTION_DISCONNECTED);
auto_reconnect = true; auto_reconnect = true;
@ -494,7 +499,7 @@ class ConnectionHandler {
break; break;
case DisconnectReason.SERVER_CLOSED: case DisconnectReason.SERVER_CLOSED:
this.log.log(log.server.Type.SERVER_CLOSED, {message: data.reasonmsg}); this.log.log(log.server.Type.SERVER_CLOSED, {message: data.reasonmsg});
//this.chat.serverChat().appendError(tr("Server closed ({0})"), data.reasonmsg);
createErrorModal( createErrorModal(
tr("Server closed"), tr("Server closed"),
"The server is closed.<br>" + //TODO tr "The server is closed.<br>" + //TODO tr
@ -506,7 +511,6 @@ class ConnectionHandler {
break; break;
case DisconnectReason.SERVER_REQUIRES_PASSWORD: case DisconnectReason.SERVER_REQUIRES_PASSWORD:
this.log.log(log.server.Type.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 => { createInputModal(tr("Server password"), tr("Enter server password:"), password => password.length != 0, password => {
if(!(typeof password === "string")) return; if(!(typeof password === "string")) return;
@ -526,8 +530,8 @@ class ConnectionHandler {
break; break;
case DisconnectReason.CLIENT_KICKED: case DisconnectReason.CLIENT_KICKED:
createErrorModal( createErrorModal(
tr("You've been banned"), tr("You've been kicked"),
MessageHelper.formatMessage(tr("You've been banned from this server.{:br:}{0}"), data["extra_message"]) MessageHelper.formatMessage(tr("You've been kicked from this server.{:br:}{0}"), data["extra_message"])
).open(); ).open();
this.sound.play(Sound.SERVER_KICKED); this.sound.play(Sound.SERVER_KICKED);
auto_reconnect = false; auto_reconnect = false;
@ -590,8 +594,7 @@ class ConnectionHandler {
this.log.log(log.server.Type.RECONNECT_CANCELED, {}); this.log.log(log.server.Type.RECONNECT_CANCELED, {});
log.info(LogCategory.NETWORKING, tr("Reconnecting...")); log.info(LogCategory.NETWORKING, tr("Reconnecting..."));
this.startConnection(server_address.host + ":" + server_address.port, profile, false, this.reconnect_properties(profile)); this.startConnection(server_address.host + ":" + server_address.port, profile, false, Object.assign(this.reconnect_properties(profile), {auto_reconnect_attempt: true}));
this._reconnect_attempt = true;
}, 5000); }, 5000);
} }
} }

View file

@ -729,7 +729,7 @@ class IconManager {
throw "icon not found"; throw "icon not found";
} }
static generate_tag(icon: Promise<Icon> | Icon, options?: { static generate_tag(icon: Promise<Icon> | Icon | undefined, options?: {
animate?: boolean animate?: boolean
}) : JQuery<HTMLDivElement> { }) : JQuery<HTMLDivElement> {
options = options || {}; options = options || {};

View file

@ -557,8 +557,8 @@ namespace connection {
let client = tree.findClient(json["clid"]); let client = tree.findClient(json["clid"]);
let self = client instanceof LocalClientEntry; let self = client instanceof LocalClientEntry;
let channel_to = tree.findChannel(json["ctid"]); let channel_to = tree.findChannel(parseInt(json["ctid"]));
let channel_from = tree.findChannel(json["cfid"]); let channel_from = tree.findChannel(parseInt(json["cfid"]));
if(!client) { if(!client) {
log.error(LogCategory.NETWORKING, tr("Unknown client move (Client)!")); log.error(LogCategory.NETWORKING, tr("Unknown client move (Client)!"));
@ -573,6 +573,7 @@ namespace connection {
if(!self) { if(!self) {
if(!channel_from) { if(!channel_from) {
log.error(LogCategory.NETWORKING, tr("Unknown client move (Channel from)!")); log.error(LogCategory.NETWORKING, tr("Unknown client move (Channel from)!"));
channel_from = client.currentChannel();
} else if(channel_to !== client.currentChannel()) { } else if(channel_to !== client.currentChannel()) {
log.error(LogCategory.NETWORKING, log.error(LogCategory.NETWORKING,
tr("Client move from invalid source channel! Local client registered in channel %d but server send %d."), tr("Client move from invalid source channel! Local client registered in channel %d but server send %d."),

View file

@ -1,4 +1,7 @@
enum ErrorID { enum ErrorID {
NOT_IMPLEMENTED = 0x2,
COMMAND_NOT_FOUND = 0x100,
PERMISSION_ERROR = 2568, PERMISSION_ERROR = 2568,
EMPTY_RESULT = 0x0501, EMPTY_RESULT = 0x0501,
PLAYLIST_IS_IN_USE = 0x2103, PLAYLIST_IS_IN_USE = 0x2103,

View file

@ -383,7 +383,14 @@ class PermissionValue {
granted(requiredValue: number, required: boolean = true) : boolean { granted(requiredValue: number, required: boolean = true) : boolean {
let result; let result;
result = this.value == -1 || this.value >= requiredValue || (this.value == -2 && requiredValue == -2 && !required); 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; return result;
} }

View file

@ -14,6 +14,15 @@ namespace profiles {
this.id = id; 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 { selected_identity(current_type?: identities.IdentitifyType) : identities.Identity {
if(!current_type) if(!current_type)
current_type = this.selected_type(); current_type = this.selected_type();
@ -139,7 +148,7 @@ namespace profiles {
{ {
const profile = create_new_profile("default","default"); const profile = create_new_profile("default","default");
profile.default_password = ""; profile.default_password = "";
profile.default_username = "Another TeaSpeak user"; profile.default_username = "";
profile.profile_name = "Default Profile"; profile.profile_name = "Default Profile";
/* generate default identity */ /* generate default identity */
@ -160,7 +169,7 @@ namespace profiles {
{ /* forum identity (works only when connected to the forum) */ { /* forum identity (works only when connected to the forum) */
const profile = create_new_profile("TeaSpeak Forum","teaforo"); const profile = create_new_profile("TeaSpeak Forum","teaforo");
profile.default_password = ""; profile.default_password = "";
profile.default_username = "Another TeaSpeak user"; profile.default_username = "";
profile.profile_name = "TeaSpeak Forum profile"; profile.profile_name = "TeaSpeak Forum profile";
profile.set_identity(identities.IdentitifyType.TEAFORO, identities.static_forum_identity()); 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 { export function create_new_profile(name: string, id?: string) : ConnectionProfile {
const profile = new ConnectionProfile(id || guid()); const profile = new ConnectionProfile(id || guid());
profile.profile_name = name; profile.profile_name = name;
profile.default_username = "Another TeaSpeak user"; profile.default_username = "";
available_profiles.push(profile); available_profiles.push(profile);
return profile; return profile;
} }

View file

@ -6,7 +6,7 @@ namespace profiles.identities {
} }
export interface Identity { export interface Identity {
name() : string; fallback_name(): string | undefined ;
uid() : string; uid() : string;
type() : IdentitifyType; type() : IdentitifyType;

View file

@ -47,7 +47,9 @@ namespace profiles.identities {
set_name(name: string) { this._name = name; } set_name(name: string) { this._name = name; }
name(): string { name() : string { return this._name; }
fallback_name(): string | undefined {
return this._name; return this._name;
} }

View file

@ -84,8 +84,8 @@ namespace profiles.identities {
return new TeaForumHandshakeHandler(connection, this); return new TeaForumHandshakeHandler(connection, this);
} }
name(): string { fallback_name(): string | undefined {
return (this.identity_data ? this.identity_data.name() : "Another TeaSpeak user"); return this.identity_data ? this.identity_data.name() : undefined;
} }
type(): profiles.identities.IdentitifyType { type(): profiles.identities.IdentitifyType {

View file

@ -503,7 +503,7 @@ namespace profiles.identities {
} }
} }
name(): string { fallback_name(): string | undefined {
return this._name; return this._name;
} }
@ -808,7 +808,7 @@ namespace profiles.identities {
return "[Identity]\n" + return "[Identity]\n" +
"id=TeaWeb-Exported\n" + "id=TeaWeb-Exported\n" +
"identity=\"" + identity + "\"\n" + "identity=\"" + identity + "\"\n" +
"nickname=\"" + this.name() + "\"\n" + "nickname=\"" + this.fallback_name() + "\"\n" +
"phonetic_nickname="; "phonetic_nickname=";
} }

View file

@ -295,6 +295,11 @@ class Settings extends StaticSettings {
key: "font_size" 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> = { static readonly KEY_LAST_INVITE_LINK_TYPE: SettingsKey<string> = {
key: "last_invite_link_type", key: "last_invite_link_type",
default_value: "tea-web" default_value: "tea-web"

View file

@ -694,7 +694,7 @@ class ChannelEntry {
if(this._channel_name_formatted !== undefined) { if(this._channel_name_formatted !== undefined) {
tag_container_name.addClass(this._channel_name_alignment); 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) while(text.length < 1024 * 8)
text += text; text += text;
} }

View file

@ -147,7 +147,11 @@ class ClientEntry {
} }
if(this._audio_handle) { if(this._audio_handle) {
log.warn(LogCategory.AUDIO, tr("Destroying client with an active audio handle. This could cause memory leaks!")); 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_playback = undefined;
this._audio_handle.callback_stopped = undefined; this._audio_handle.callback_stopped = undefined;
this._audio_handle = undefined; this._audio_handle = undefined;
@ -159,7 +163,11 @@ class ClientEntry {
tree_unregistered() { tree_unregistered() {
this.channelTree = undefined; this.channelTree = undefined;
if(this._audio_handle) { 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_playback = undefined;
this._audio_handle.callback_stopped = undefined; this._audio_handle.callback_stopped = undefined;
this._audio_handle = undefined; this._audio_handle = undefined;

View file

@ -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 [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 ? "&nbsp;" : entry)); //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 ? "&nbsp;" : entry));
@ -264,11 +292,24 @@ namespace MessageHelper {
return result.length > 0 ? result.substring(1) : default_value; 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, { loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
name: "XBBCode code tag init", name: "XBBCode code tag init",
function: async () => { function: async () => {
/* override default parser */ /* override default parser */
xbbcode.register.register_parser( { xbbcode.register.register_parser({
tag: ["code", "icode", "i-code"], tag: ["code", "icode", "i-code"],
content_tags_whitelist: [], content_tags_whitelist: [],
@ -297,5 +338,13 @@ namespace MessageHelper {
}); });
}, },
priority: 10 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
});
} }

View file

@ -1791,11 +1791,14 @@ test
private _container_new_message: JQuery; private _container_new_message: JQuery;
private _container_no_permissions: 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: JQuery;
private _container_is_private_shown: boolean = false; 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_max_messages = 40; /* reset to 40 again as soon we tab out :) */
private _view_older_messages: ViewEntry; private _view_older_messages: ViewEntry;
private _has_older_messages: boolean; /* undefined := not known | else flag */ 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_new_message = this._html_tag.find(".new-message");
this._container_no_permissions = this._html_tag.find(".no-permissions").hide(); this._container_no_permissions = this._html_tag.find(".no-permissions").hide();
this._container_is_private = this._html_tag.find(".private-conversation").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 = this._html_tag.find(".container-messages");
this._container_messages.on('scroll', event => { this._container_messages.on('scroll', event => {
@ -1969,6 +1973,12 @@ test
} else if(error.id == ErrorID.CONVERSATION_IS_PRIVATE) { } else if(error.id == ErrorID.CONVERSATION_IS_PRIVATE) {
this.set_flag_private(true); 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! //TODO log and handle!
log.error(LogCategory.CHAT, tr("Failed to fetch conversation history. %o"), error); log.error(LogCategory.CHAT, tr("Failed to fetch conversation history. %o"), error);
@ -2124,7 +2134,7 @@ test
} }
chat_available() : boolean { 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) { text_send_failed(error: CommandResult | any) {

View file

@ -154,18 +154,16 @@ namespace Modals {
} }
console.log("Updating"); 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(); let address = input_address.val().toString();
settings.changeGlobal(Settings.KEY_CONNECT_ADDRESS, address); 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 flag_address = !!address.match(Regex.IP_V4) || !!address.match(Regex.IP_V6) || !!address.match(Regex.DOMAIN);
let nickname = input_nickname.val().toString(); let nickname = input_nickname.val().toString();
settings.changeGlobal(Settings.KEY_CONNECT_USERNAME, nickname); if(nickname)
let flag_nickname = (nickname.length == 0 && selected_profile && selected_profile.default_username.length > 0) || nickname.length >= 3 && nickname.length <= 32; 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_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); 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 => { input_profile.on('change', event => {
selected_profile = profiles.find_profile(input_profile.val() as string); selected_profile = profiles.find_profile(input_profile.val() as string);
{ {
settings.changeGlobal(Settings.KEY_CONNECT_USERNAME, selected_profile.default_username); settings.changeGlobal(Settings.KEY_CONNECT_USERNAME, undefined);
input_nickname.val(selected_profile.default_username); input_nickname
.attr('placeholder', selected_profile.connect_username() || "Another TeaSpeak user")
.val("");
} }
input_profile.toggleClass("is-invalid", !selected_profile || !selected_profile.valid()); input_profile.toggleClass("is-invalid", !selected_profile || !selected_profile.valid());
updateFields(true); updateFields(true);
@ -233,7 +233,7 @@ namespace Modals {
selected_profile, selected_profile,
true, 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} 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, selected_profile,
true, 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} password: (current_connect_data && current_connect_data.password_hash) ? {password: current_connect_data.password_hash, hashed: true} : {password: input_password.val().toString(), hashed: false}
} }
); );

View file

@ -227,13 +227,12 @@ namespace Modals {
else else
type = "temp"; type = "temp";
console.log(type);
console.log(Object.assign({}, properties));
simple.find("option[name='channel-type'][value='" + type + "']").prop("selected", true); simple.find("option[name='channel-type'][value='" + type + "']").prop("selected", true);
}; };
input_advanced_type.on('change', event => { 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": case "semi":
properties.channel_flag_permanent = false; properties.channel_flag_permanent = false;
properties.channel_flag_semi_permanent = true; properties.channel_flag_semi_permanent = true;
@ -264,20 +263,8 @@ namespace Modals {
const select_default = tag.find(".input-flag-default"); 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 => { select_default.on('change', event => {
const node = select_default[0] as HTMLInputElement; const node = select_default[0] as HTMLInputElement;
console.log(node.checked);
properties.channel_flag_default = node.checked; properties.channel_flag_default = node.checked;
if(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 */ /* Talk power */
@ -481,7 +487,8 @@ namespace Modals {
function applyPermissionListener(connection: ConnectionHandler, properties: ChannelProperties, tag: JQuery, button: JQuery, permissions: PermissionManager, channel?: ChannelEntry) { function applyPermissionListener(connection: ConnectionHandler, properties: ChannelProperties, tag: JQuery, button: JQuery, permissions: PermissionManager, channel?: ChannelEntry) {
let apply_permissions = (channel_permissions: PermissionValue[]) => { 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; let required_power = -2;
for(let cperm of channel_permissions) for(let cperm of channel_permissions)
if(cperm.type.name == PermissionType.I_CHANNEL_NEEDED_MODIFY_POWER) { 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 tag.find("input[permission]").prop("disabled", !permission).parent(".input-boxed").toggleClass("disabled", !permission); //No permissions
}; };

View file

@ -380,9 +380,6 @@ namespace Modals {
let upload_key: transfer.UploadKey; let upload_key: transfer.UploadKey;
try { try {
await new Promise(resolve => setTimeout(resolve, 1000));
throw "test error";;
upload_key = await client.fileManager.upload_file({ upload_key = await client.fileManager.upload_file({
channel: undefined, channel: undefined,
channel_password: undefined, channel_password: undefined,

View file

@ -16,7 +16,6 @@ namespace Modals {
left.find(".selected").removeClass("selected"); left.find(".selected").removeClass("selected");
const target = entry.attr("container"); const target = entry.attr("container");
console.log(target);
if(!target) return; if(!target) return;
right.find("> .container." + target).removeClass("hidden"); right.find("> .container." + target).removeClass("hidden");
@ -74,7 +73,7 @@ namespace Modals {
select.on('change', event => { select.on('change', event => {
const value = parseInt(select.val() as string); const value = parseInt(select.val() as string);
settings.changeGlobal(Settings.KEY_FONT_SIZE, value); 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"); $(document.body).css("font-size", value + "px");
}); });
@ -349,6 +348,28 @@ namespace Modals {
settings.changeGlobal(Settings.KEY_CHAT_TAG_URLS, option[0].checked); settings.changeGlobal(Settings.KEY_CHAT_TAG_URLS, option[0].checked);
}).prop("checked", settings.static_global(Settings.KEY_CHAT_TAG_URLS)); }).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) { function settings_audio_microphone(container: JQuery, modal: Modal) {
@ -942,7 +963,10 @@ namespace Modals {
select_type.parent().toggleClass("is-invalid", true); select_type.parent().toggleClass("is-invalid", true);
} else { } else {
input_name.val(selected_profile.identity.profile_name).prop("disabled", false); 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); select_type.val(selected_profile.identity.selected_identity_type || "unset").prop("disabled", false);
} }

View file

@ -205,7 +205,7 @@ namespace pe {
this._tag_granted_input.on('change', event => { this._tag_granted_input.on('change', event => {
const str_value = this._tag_granted_input.val() as string; const str_value = this._tag_granted_input.val() as string;
const value = parseInt(str_value); 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()); console.warn(tr("Failed to parse given permission granted value string: %s"), this._tag_granted_input.val());
this._reset_value(); this._reset_value();
return; return;
@ -369,6 +369,8 @@ namespace pe {
return (this._mask & 0x03) > 0; return (this._mask & 0x03) > 0;
} }
get_value() { return this._value; }
value(value: number | undefined, skip?: boolean, negate?: boolean) { value(value: number | undefined, skip?: boolean, negate?: boolean) {
if(typeof value === "undefined") { if(typeof value === "undefined") {
this._tag_value.detach(); 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() { private build_tag() {
this.container = $("#tmpl_permission_editor_html").renderTag(); this.container = $("#tmpl_permission_editor_html").renderTag();
this.container.find("input").on('change', event => { this.container.find("input").on('change', event => {
@ -765,6 +786,51 @@ namespace pe {
build_group(undefined, group, 0); 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 => { this.mode_container_permissions.on('contextmenu', event => {
if(event.isDefaultPrevented()) if(event.isDefaultPrevented())
return; return;
@ -812,6 +878,7 @@ namespace pe {
permission_handle.granted(new_permission.granted_value); permission_handle.granted(new_permission.granted_value);
} }
this.update_icon();
this.update_filter(); this.update_filter();
} }
@ -821,9 +888,17 @@ namespace pe {
this.mode_container_unset.css('display', mode == Modals.PermissionEditorMode.UNSET ? 'block' : 'none'); this.mode_container_unset.css('display', mode == Modals.PermissionEditorMode.UNSET ? 'block' : 'none');
} }
trigger_change(permission: PermissionInfo, value?: Modals.PermissionEditor.PermissionValue) : Promise<void> { trigger_change(permission: PermissionInfo, value?: Modals.PermissionEditor.PermissionValue, update_icon?: boolean) : Promise<void> {
if(this._listener_change) if(this._listener_change) {
return this._listener_change(permission, value); 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(); return Promise.reject();
} }

View file

@ -119,3 +119,5 @@ Fix these icons: https://img.did.science/Screenshot_20-11-06.png
- Focus crash window on crash - Focus crash window on crash
- Add a notification (Like the browser notifications) - 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