Added the ability to add custom context menus

canary
WolverinDEV 2019-06-30 16:03:28 +02:00
parent c42d890c7f
commit 40ca24f075
12 changed files with 442 additions and 328 deletions

View File

@ -397,6 +397,57 @@ function main() {
}
}
$(document).on('contextmenu', event => {
if(event.isDefaultPrevented())
return;
event.preventDefault();
contextmenu.spawn_context_menu(event.pageX, event.pageY, {
name: 'Test item 1',
type: contextmenu.MenuEntryType.ENTRY,
callback: () => console.log("Test 1 item clicked!"),
disabled: true
}, {
name: 'Test item 2',
type: contextmenu.MenuEntryType.ENTRY,
callback: () => console.log("Test 2 item clicked!")
}, {
name: 'Test item 3',
type: contextmenu.MenuEntryType.SUB_MENU,
callback: () => console.log("Test 3 item clicked!"),
sub_menu: [
{
name: 'Test sub item 1',
type: contextmenu.MenuEntryType.ENTRY,
callback: () => console.log("Test sub 1 item clicked!")
}, {
name: 'Test sub item 2',
type: contextmenu.MenuEntryType.ENTRY,
callback: () => console.log("Test sub 2 item clicked!")
}, {
name: 'Test sub item 4',
type: contextmenu.MenuEntryType.ENTRY,
callback: () => console.log("Test sub 3 item clicked!")
}
]
},
contextmenu.Entry.HR(),
{
name: 'Test item 4',
type: contextmenu.MenuEntryType.ENTRY,
callback: () => console.log("Test 4 item clicked!")
}, {
name: 'Test item 5',
type: contextmenu.MenuEntryType.CHECKBOX,
callback: () => console.log("Test 5 item clicked!")
}, {
name: 'Test item 5',
type: contextmenu.MenuEntryType.CHECKBOX,
callback: () => console.log("Test 5 item clicked!"),
checkbox_checked: true
}, contextmenu.Entry.CLOSE(() => console.log("Menu closed!")))
})
}
const task_teaweb_starter: loader.Task = {

View File

@ -452,58 +452,60 @@ class ChannelEntry {
}
let trigger_close = true;
spawn_context_menu(x, y, {
type: MenuEntryType.ENTRY,
const bold = text => contextmenu.get_provider().html_format_enabled() ? "<b>" + text + "</b>" : text;
contextmenu.spawn_context_menu(x, y, {
type: contextmenu.MenuEntryType.ENTRY,
name: tr("Show channel info"),
callback: () => {
trigger_close = false;
this.channelTree.client.select_info.open_popover()
},
icon: "client-about",
icon_class: "client-about",
visible: this.channelTree.client.select_info.is_popover()
}, {
type: MenuEntryType.HR,
type: contextmenu.MenuEntryType.HR,
visible: this.channelTree.client.select_info.is_popover(),
name: ''
}, {
type: MenuEntryType.ENTRY,
icon: "client-channel_switch",
name: tr("<b>Switch to channel</b>"),
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-channel_switch",
name: bold(tr("Switch to channel")),
callback: () => this.joinChannel()
},
...(() => {
const local_client = this.channelTree.client.getClient();
if (!local_client || local_client.currentChannel() !== this)
return [
MenuEntry.HR(),
contextmenu.Entry.HR(),
{
type: MenuEntryType.ENTRY,
type: contextmenu.MenuEntryType.ENTRY,
icon: "client-subscribe_to_channel",
name: tr("<b>Subscribe to channel</b>"),
name: bold(tr("Subscribe to channel")),
callback: () => this.subscribe(),
visible: !this.flag_subscribed
},
{
type: MenuEntryType.ENTRY,
type: contextmenu.MenuEntryType.ENTRY,
icon: "client-channel_unsubscribed",
name: tr("<b>Unsubscribe from channel</b>"),
name: bold(tr("Unsubscribe from channel")),
callback: () => this.unsubscribe(),
visible: this.flag_subscribed
},
{
type: MenuEntryType.ENTRY,
type: contextmenu.MenuEntryType.ENTRY,
icon: "client-subscribe_mode",
name: tr("<b>Use inherited subscribe mode</b>"),
name: bold(tr("Use inherited subscribe mode")),
callback: () => this.unsubscribe(true),
visible: this.subscribe_mode != ChannelSubscribeMode.INHERITED
}
];
return [];
})(),
MenuEntry.HR(),
contextmenu.Entry.HR(),
{
type: MenuEntryType.ENTRY,
icon: "client-channel_edit",
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-channel_edit",
name: tr("Edit channel"),
invalidPermission: !channelModify,
callback: () => {
@ -536,8 +538,8 @@ class ChannelEntry {
}
},
{
type: MenuEntryType.ENTRY,
icon: "client-channel_delete",
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-channel_delete",
name: tr("Delete channel"),
invalidPermission: !flagDelete,
callback: () => {
@ -546,10 +548,10 @@ class ChannelEntry {
})
}
},
MenuEntry.HR(),
contextmenu.Entry.HR(),
{
type: MenuEntryType.ENTRY,
icon: "client-addon-collection",
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-addon-collection",
name: tr("Create music bot"),
callback: () => {
this.channelTree.client.serverConnection.send_command("musicbotcreate", {cid: this.channelId}).then(() => {
@ -563,21 +565,21 @@ class ChannelEntry {
});
}
},
MenuEntry.HR(),
contextmenu.Entry.HR(),
{
type: MenuEntryType.ENTRY,
icon: "client-channel_create_sub",
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-channel_create_sub",
name: tr("Create sub channel"),
invalidPermission: !(channelCreate && this.channelTree.client.permissions.neededPermission(PermissionType.B_CHANNEL_CREATE_CHILD).granted(1)),
callback: () => this.channelTree.spawnCreateChannel(this)
}, {
type: MenuEntryType.ENTRY,
icon: "client-channel_create",
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-channel_create",
name: tr("Create channel"),
invalidPermission: !channelCreate,
callback: () => this.channelTree.spawnCreateChannel()
},
MenuEntry.CLOSE(() => (trigger_close ? on_close : () => {})())
contextmenu.Entry.CLOSE(() => (trigger_close ? on_close : () => {})())
);
}

View File

@ -169,20 +169,15 @@ class ClientEntry {
});
}
protected assignment_context() : ContextMenuEntry[] {
let server_groups: ContextMenuEntry[] = [];
protected assignment_context() : contextmenu.MenuEntry[] {
let server_groups: contextmenu.MenuEntry[] = [];
for(let group of this.channelTree.client.groups.serverGroups.sort(GroupManager.sorter())) {
if(group.type != GroupType.NORMAL) continue;
let entry: ContextMenuEntry = {} as any;
{
let tag = $.spawn("label").addClass("checkbox");
$.spawn("input").attr("type", "checkbox").prop("checked", this.groupAssigned(group)).appendTo(tag);
$.spawn("span").addClass("checkmark").appendTo(tag);
entry.icon = tag;
}
let entry: contextmenu.MenuEntry = {} as any;
//TODO: May add the server group icon?
entry.checkbox_checked = this.groupAssigned(group);
entry.name = group.name + " [" + (group.properties.savedb ? "perm" : "tmp") + "]";
if(this.groupAssigned(group)) {
entry.callback = () => {
@ -201,21 +196,19 @@ class ClientEntry {
};
entry.disabled = !this.channelTree.client.permissions.neededPermission(PermissionType.I_GROUP_MEMBER_REMOVE_POWER).granted(group.requiredMemberAddPower);
}
entry.type = MenuEntryType.ENTRY;
entry.type = contextmenu.MenuEntryType.CHECKBOX;
server_groups.push(entry);
}
let channel_groups: ContextMenuEntry[] = [];
let channel_groups: contextmenu.MenuEntry[] = [];
for(let group of this.channelTree.client.groups.channelGroups.sort(GroupManager.sorter())) {
if(group.type != GroupType.NORMAL) continue;
let entry: ContextMenuEntry = {} as any;
{
let tag = $.spawn("label").addClass("checkbox");
$.spawn("input").attr("type", "checkbox").prop("checked", this.assignedChannelGroup() == group.id).appendTo(tag);
$.spawn("span").addClass("checkmark").appendTo(tag);
entry.icon = tag;
}
let entry: contextmenu.MenuEntry = {} as any;
//TODO: May add the channel group icon?
entry.checkbox_checked = this.assignedChannelGroup() == group.id;
entry.name = group.name + " [" + (group.properties.savedb ? "perm" : "tmp") + "]";
entry.callback = () => {
this.channelTree.client.serverConnection.send_command("setclientchannelgroup", {
@ -225,17 +218,17 @@ class ClientEntry {
});
};
entry.disabled = !this.channelTree.client.permissions.neededPermission(PermissionType.I_GROUP_MEMBER_ADD_POWER).granted(group.requiredMemberRemovePower);
entry.type = MenuEntryType.ENTRY;
entry.type = contextmenu.MenuEntryType.CHECKBOX;
channel_groups.push(entry);
}
return [{
type: MenuEntryType.SUB_MENU,
icon: "client-permission_server_groups",
type: contextmenu.MenuEntryType.SUB_MENU,
icon_class: "client-permission_server_groups",
name: tr("Set server group"),
sub_menu: [
{
type: MenuEntryType.ENTRY,
type: contextmenu.MenuEntryType.ENTRY,
icon: "client-permission_server_groups",
name: "Server groups dialog",
callback: () => {
@ -253,19 +246,19 @@ class ClientEntry {
});
}
},
MenuEntry.HR(),
contextmenu.Entry.HR(),
...server_groups
]
},{
type: MenuEntryType.SUB_MENU,
icon: "client-permission_channel",
type: contextmenu.MenuEntryType.SUB_MENU,
icon_class: "client-permission_channel",
name: tr("Set channel group"),
sub_menu: [
...channel_groups
]
},{
type: MenuEntryType.SUB_MENU,
icon: "client-permission_client",
type: contextmenu.MenuEntryType.SUB_MENU,
icon_class: "client-permission_client",
name: tr("Permissions"),
disabled: true,
sub_menu: [ ]
@ -274,31 +267,33 @@ class ClientEntry {
showContextMenu(x: number, y: number, on_close: () => void = undefined) {
let trigger_close = true;
spawn_context_menu(x, y,
contextmenu.spawn_context_menu(x, y,
{
type: MenuEntryType.ENTRY,
type: contextmenu.MenuEntryType.ENTRY,
name: tr("Show client info"),
callback: () => {
trigger_close = false;
this.channelTree.client.select_info.open_popover()
},
icon: "client-about",
icon_class: "client-about",
visible: this.channelTree.client.select_info.is_popover()
}, {
type: MenuEntryType.HR,
type: contextmenu.MenuEntryType.HR,
visible: this.channelTree.client.select_info.is_popover(),
name: ''
}, {
type: MenuEntryType.ENTRY,
icon: "client-change_nickname",
name: tr("<b>Open text chat</b>"),
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-change_nickname",
name: (contextmenu.get_provider().html_format_enabled() ? "<b>" : "") +
tr("Open text chat") +
(contextmenu.get_provider().html_format_enabled() ? "</b>" : ""),
callback: () => {
this.channelTree.client.chat.activeChat = this.chat(true);
this.channelTree.client.chat.focus();
}
}, {
type: MenuEntryType.ENTRY,
icon: "client-poke",
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-poke",
name: tr("Poke client"),
callback: () => {
createInputModal(tr("Poke client"), tr("Poke message:<br>"), text => true, result => {
@ -314,8 +309,8 @@ class ClientEntry {
}, { width: 400, maxLength: 512 }).open();
}
}, {
type: MenuEntryType.ENTRY,
icon: "client-edit",
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-edit",
name: tr("Change description"),
callback: () => {
createInputModal(tr("Change client description"), tr("New description:<br>"), text => true, result => {
@ -331,11 +326,11 @@ class ClientEntry {
}, { width: 400, maxLength: 1024 }).open();
}
},
MenuEntry.HR(),
contextmenu.Entry.HR(),
...this.assignment_context(),
MenuEntry.HR(), {
type: MenuEntryType.ENTRY,
icon: "client-move_client_to_own_channel",
contextmenu.Entry.HR(), {
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-move_client_to_own_channel",
name: tr("Move client to your channel"),
callback: () => {
this.channelTree.client.serverConnection.send_command("clientmove", {
@ -344,8 +339,8 @@ class ClientEntry {
});
}
}, {
type: MenuEntryType.ENTRY,
icon: "client-kick_channel",
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-kick_channel",
name: tr("Kick client from channel"),
callback: () => {
createInputModal(tr("Kick client from channel"), tr("Kick reason:<br>"), text => true, result => {
@ -362,8 +357,8 @@ class ClientEntry {
}, { width: 400, maxLength: 255 }).open();
}
}, {
type: MenuEntryType.ENTRY,
icon: "client-kick_server",
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-kick_server",
name: tr("Kick client fom server"),
callback: () => {
createInputModal(tr("Kick client from server"), tr("Kick reason:<br>"), text => true, result => {
@ -380,8 +375,8 @@ class ClientEntry {
}, { width: 400, maxLength: 255 }).open();
}
}, {
type: MenuEntryType.ENTRY,
icon: "client-ban_client",
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-ban_client",
name: tr("Ban client"),
invalidPermission: !this.channelTree.client.permissions.neededPermission(PermissionType.I_CLIENT_BAN_MAX_BANTIME).granted(1),
callback: () => {
@ -398,7 +393,7 @@ class ClientEntry {
});
}
},
MenuEntry.HR(),
contextmenu.Entry.HR(),
/*
{
type: MenuEntryType.ENTRY,
@ -418,8 +413,8 @@ class ClientEntry {
MenuEntry.HR(),
*/
{
type: MenuEntryType.ENTRY,
icon: "client-volume",
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-volume",
name: tr("Change Volume"),
callback: () => {
Modals.spawnChangeVolume(this._audio_handle.get_volume(), volume => {
@ -430,7 +425,7 @@ class ClientEntry {
});
}
},
MenuEntry.CLOSE(() => (trigger_close ? on_close : () => {})())
contextmenu.Entry.CLOSE(() => (trigger_close ? on_close : () => {})())
);
}
@ -839,15 +834,18 @@ class LocalClientEntry extends ClientEntry {
showContextMenu(x: number, y: number, on_close: () => void = undefined): void {
const _self = this;
spawn_context_menu(x, y,
contextmenu.spawn_context_menu(x, y,
{
name: tr("<b>Change name</b>"),
icon: "client-change_nickname",
name: (contextmenu.get_provider().html_format_enabled() ? "<b>" : "") +
tr("Change name") +
(contextmenu.get_provider().html_format_enabled() ? "</b>" : ""),
icon_class: "client-change_nickname",
callback: () =>_self.openRename(),
type: MenuEntryType.ENTRY
type: contextmenu.MenuEntryType.ENTRY
}, {
name: tr("Change description"),
icon: "client-edit",
icon_class: "client-edit",
callback: () => {
createInputModal(tr("Change own description"), tr("New description:<br>"), text => true, result => {
if(result) {
@ -860,11 +858,11 @@ class LocalClientEntry extends ClientEntry {
}
}, { width: 400, maxLength: 1024 }).open();
},
type: MenuEntryType.ENTRY
type: contextmenu.MenuEntryType.ENTRY
},
MenuEntry.HR(),
contextmenu.Entry.HR(),
...this.assignment_context(),
MenuEntry.CLOSE(on_close)
contextmenu.Entry.CLOSE(on_close)
);
}
@ -968,23 +966,23 @@ class MusicClientEntry extends ClientEntry {
showContextMenu(x: number, y: number, on_close: () => void = undefined): void {
let trigger_close = true;
spawn_context_menu(x, y,
contextmenu.spawn_context_menu(x, y,
{
type: MenuEntryType.ENTRY,
type: contextmenu.MenuEntryType.ENTRY,
name: tr("Show bot info"),
callback: () => {
trigger_close = false;
this.channelTree.client.select_info.open_popover()
},
icon: "client-about",
icon_class: "client-about",
visible: this.channelTree.client.select_info.is_popover()
}, {
type: MenuEntryType.HR,
type: contextmenu.MenuEntryType.HR,
visible: this.channelTree.client.select_info.is_popover(),
name: ''
}, {
name: tr("<b>Change bot name</b>"),
icon: "client-change_nickname",
icon_class: "client-change_nickname",
disabled: false,
callback: () => {
createInputModal(tr("Change music bots nickname"), tr("New nickname:<br>"), text => text.length >= 3 && text.length <= 31, result => {
@ -997,10 +995,10 @@ class MusicClientEntry extends ClientEntry {
}
}, { width: 400, maxLength: 255 }).open();
},
type: MenuEntryType.ENTRY
type: contextmenu.MenuEntryType.ENTRY
}, {
name: tr("Change bot description"),
icon: "client-edit",
icon_class: "client-edit",
disabled: false,
callback: () => {
createInputModal(tr("Change music bots description"), tr("New description:<br>"), text => true, result => {
@ -1013,7 +1011,7 @@ class MusicClientEntry extends ClientEntry {
}
}, { width: 400, maxLength: 255 }).open();
},
type: MenuEntryType.ENTRY
type: contextmenu.MenuEntryType.ENTRY
},
/*
{
@ -1026,7 +1024,7 @@ class MusicClientEntry extends ClientEntry {
*/
{
name: tr("Open bot's playlist"),
icon: "client-edit",
icon_class: "client-edit",
disabled: false,
callback: () => {
this.channelTree.client.serverConnection.command_helper.request_playlist_list().then(lists => {
@ -1041,11 +1039,11 @@ class MusicClientEntry extends ClientEntry {
createErrorModal(tr("Failed to query playlist."), tr("Failed to query playlist info.")).open();
});
},
type: MenuEntryType.ENTRY
type: contextmenu.MenuEntryType.ENTRY
},
{
name: tr("Quick url replay"),
icon: "client-edit",
icon_class: "client-edit",
disabled: false,
callback: () => {
createInputModal(tr("Please enter the URL"), tr("URL:"), text => true, result => {
@ -1064,13 +1062,13 @@ class MusicClientEntry extends ClientEntry {
}
}, { width: 400, maxLength: 255 }).open();
},
type: MenuEntryType.ENTRY
type: contextmenu.MenuEntryType.ENTRY
},
MenuEntry.HR(),
contextmenu.Entry.HR(),
...super.assignment_context(),
MenuEntry.HR(),{
type: MenuEntryType.ENTRY,
icon: "client-move_client_to_own_channel",
contextmenu.Entry.HR(),{
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-move_client_to_own_channel",
name: tr("Move client to your channel"),
callback: () => {
this.channelTree.client.serverConnection.send_command("clientmove", {
@ -1079,8 +1077,8 @@ class MusicClientEntry extends ClientEntry {
});
}
}, {
type: MenuEntryType.ENTRY,
icon: "client-kick_channel",
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-kick_channel",
name: tr("Kick client from channel"),
callback: () => {
createInputModal(tr("Kick client from channel"), tr("Kick reason:<br>"), text => true, result => {
@ -1095,10 +1093,10 @@ class MusicClientEntry extends ClientEntry {
}, { width: 400, maxLength: 255 }).open();
}
},
MenuEntry.HR(),
contextmenu.Entry.HR(),
{
type: MenuEntryType.ENTRY,
icon: "client-volume",
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-volume",
name: tr("Change local volume"),
callback: () => {
Modals.spawnChangeVolume(this._audio_handle.get_volume(), volume => {
@ -1110,8 +1108,8 @@ class MusicClientEntry extends ClientEntry {
}
},
{
type: MenuEntryType.ENTRY,
icon: "client-volume",
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-volume",
name: tr("Change remote volume"),
callback: () => {
let max_volume = this.channelTree.client.permissions.neededPermission(PermissionType.I_CLIENT_MUSIC_CREATE_MODIFY_MAX_VOLUME).value;
@ -1132,10 +1130,10 @@ class MusicClientEntry extends ClientEntry {
});
}
},
MenuEntry.HR(),
contextmenu.Entry.HR(),
{
name: tr("Delete bot"),
icon: "client-delete",
icon_class: "client-delete",
disabled: false,
callback: () => {
const tag = $.spawn("div").append(MessageHelper.formatMessage(tr("Do you really want to delete {0}"), this.createChatTag(false)));
@ -1147,9 +1145,9 @@ class MusicClientEntry extends ClientEntry {
}
});
},
type: MenuEntryType.ENTRY
type: contextmenu.MenuEntryType.ENTRY
},
MenuEntry.CLOSE(() => (trigger_close ? on_close : () => {})())
contextmenu.Entry.CLOSE(() => (trigger_close ? on_close : () => {})())
);
}

View File

@ -1,142 +1,220 @@
let context_menu: JQuery;
namespace contextmenu {
export interface MenuEntry {
callback?: () => void;
type: MenuEntryType;
name: (() => string) | string;
icon_class?: string;
icon_path?: string;
disabled?: boolean;
visible?: boolean;
$(document).bind("click", function (e) {
let menu = context_menu || (context_menu = $(".context-menu"));
checkbox_checked?: boolean;
if(!menu.is(":visible")) return;
invalidPermission?: boolean;
sub_menu?: MenuEntry[];
}
if ($(e.target).parents(".context-menu").length == 0) {
export enum MenuEntryType {
CLOSE,
ENTRY,
CHECKBOX,
HR,
SUB_MENU
}
export class Entry {
static HR() {
return {
callback: () => {},
type: MenuEntryType.HR,
name: "",
icon: ""
};
};
static CLOSE(callback: () => void) {
return {
callback: callback,
type: MenuEntryType.CLOSE,
name: "",
icon: ""
};
}
}
export interface ContextMenuProvider {
despawn_context_menu();
e.preventDefault();
spawn_context_menu(x: number, y: number, ...entries: MenuEntry[]);
initialize();
finalize();
html_format_enabled() : boolean;
}
});
let contextMenuCloseFn = undefined;
function despawn_context_menu() {
let menu = context_menu || (context_menu = $(".context-menu"));
let provider: ContextMenuProvider;
export function spawn_context_menu(x: number, y: number, ...entries: MenuEntry[]) {
if(!provider) {
console.error(tr("Failed to spawn context menu! Missing provider!"));
return;
}
if(!menu.is(":visible")) return;
menu.animate({opacity: 0}, 100, () => menu.css("display", "none"));
if(contextMenuCloseFn) contextMenuCloseFn();
}
provider.spawn_context_menu(x, y, ...entries);
}
enum MenuEntryType {
CLOSE,
ENTRY,
HR,
SUB_MENU
}
export function despawn_context_menu() {
if(!provider)
return;
class MenuEntry {
static HR() {
return {
callback: () => {},
type: MenuEntryType.HR,
name: "",
icon: ""
};
};
provider.despawn_context_menu();
}
static CLOSE(callback: () => void) {
return {
callback: callback,
type: MenuEntryType.CLOSE,
name: "",
icon: ""
};
export function get_provider() : ContextMenuProvider { return provider; }
export function set_provider(_provider: ContextMenuProvider) {
provider = _provider;
provider.initialize();
}
}
interface ContextMenuEntry {
callback?: () => void;
type: MenuEntryType;
name: (() => string) | string;
icon?: (() => string) | string | JQuery;
disabled?: boolean;
visible?: boolean;
class HTMLContextMenuProvider implements contextmenu.ContextMenuProvider {
private _global_click_listener: (event) => any;
private _context_menu: JQuery;
private _close_callbacks: (() => any)[] = [];
invalidPermission?: boolean;
sub_menu?: ContextMenuEntry[];
}
despawn_context_menu() {
let menu = this._context_menu || (this._context_menu = $(".context-menu"));
function generate_tag(entry: ContextMenuEntry) : JQuery {
if(entry.type == MenuEntryType.HR) {
return $.spawn("hr");
} else if(entry.type == MenuEntryType.ENTRY) {
console.log(entry.icon);
let icon = $.isFunction(entry.icon) ? entry.icon() : entry.icon;
if(typeof(icon) === "string") {
if(!menu.is(":visible"))
return;
menu.animate({opacity: 0}, 100, () => menu.css("display", "none"));
for(const callback of this._close_callbacks)
callback();
this._close_callbacks = [];
}
finalize() {
$(document).unbind('click', this._global_click_listener);
}
initialize() {
this._global_click_listener = this.on_global_click.bind(this);
$(document).bind('click', this._global_click_listener);
}
private on_global_click(event) {
let menu = this._context_menu || (this._context_menu = $(".context-menu"));
if(!menu.is(":visible")) return;
if ($(event.target).parents(".context-menu").length == 0) {
this.despawn_context_menu();
event.preventDefault();
}
}
private generate_tag(entry: contextmenu.MenuEntry) : JQuery {
if(entry.type == contextmenu.MenuEntryType.HR) {
return $.spawn("hr");
} else if(entry.type == contextmenu.MenuEntryType.ENTRY) {
let icon = entry.icon_class;
if(!icon || icon.length == 0) icon = "icon_empty";
else icon = "icon " + icon;
}
let tag = $.spawn("div").addClass("entry");
tag.append(typeof(icon) === "string" ? $.spawn("div").addClass(icon) : icon);
tag.append($.spawn("div").html($.isFunction(entry.name) ? entry.name() : entry.name));
let tag = $.spawn("div").addClass("entry");
tag.append($.spawn("div").addClass(icon));
tag.append($.spawn("div").html($.isFunction(entry.name) ? entry.name() : entry.name));
if(entry.disabled || entry.invalidPermission) tag.addClass("disabled");
else {
tag.click(function () {
if($.isFunction(entry.callback)) entry.callback();
despawn_context_menu();
});
}
return tag;
} else if(entry.type == MenuEntryType.SUB_MENU) {
let icon = $.isFunction(entry.icon) ? entry.icon() : entry.icon;
if(typeof(icon) === "string") {
if(!icon || icon.length == 0) icon = "icon_empty";
else icon = "icon " + icon;
}
let tag = $.spawn("div").addClass("entry").addClass("sub-container");
tag.append(typeof(icon) === "string" ? $.spawn("div").addClass(icon) : icon);
tag.append($.spawn("div").html($.isFunction(entry.name) ? entry.name() : entry.name));
tag.append($.spawn("div").addClass("arrow right"));
if(entry.disabled || entry.invalidPermission) tag.addClass("disabled");
else {
let menu = $.spawn("div").addClass("sub-menu").addClass("context-menu-container");
for(const e of entry.sub_menu) {
if(typeof(entry.visible) === 'boolean' && !entry.visible)
continue;
menu.append(generate_tag(e));
if(entry.disabled || entry.invalidPermission) tag.addClass("disabled");
else {
tag.click( () => {
if($.isFunction(entry.callback))
entry.callback();
this.despawn_context_menu();
});
}
menu.appendTo(tag);
return tag;
} else if(entry.type == contextmenu.MenuEntryType.CHECKBOX) {
let checkbox = $.spawn("label").addClass("checkbox");
$.spawn("input").attr("type", "checkbox").prop("checked", !!entry.checkbox_checked).appendTo(checkbox);
$.spawn("span").addClass("checkmark").appendTo(checkbox);
let tag = $.spawn("div").addClass("entry");
tag.append(checkbox);
tag.append($.spawn("div").html($.isFunction(entry.name) ? entry.name() : entry.name));
if(entry.disabled || entry.invalidPermission)
tag.addClass("disabled");
else {
tag.click( () => {
if($.isFunction(entry.callback))
entry.callback();
this.despawn_context_menu();
});
}
return tag;
} else if(entry.type == contextmenu.MenuEntryType.SUB_MENU) {
let icon = entry.icon_class;
if(!icon || icon.length == 0) icon = "icon_empty";
else icon = "icon " + icon;
let tag = $.spawn("div").addClass("entry").addClass("sub-container");
tag.append($.spawn("div").addClass(icon));
tag.append($.spawn("div").html($.isFunction(entry.name) ? entry.name() : entry.name));
tag.append($.spawn("div").addClass("arrow right"));
if(entry.disabled || entry.invalidPermission) tag.addClass("disabled");
else {
let menu = $.spawn("div").addClass("sub-menu").addClass("context-menu-container");
for(const e of entry.sub_menu) {
if(typeof(entry.visible) === 'boolean' && !entry.visible)
continue;
menu.append(this.generate_tag(e));
}
menu.appendTo(tag);
}
return tag;
}
return tag;
}
return $.spawn("div").text("undefined");
}
function spawn_context_menu(x, y, ...entries: ContextMenuEntry[]) {
let menu_tag = context_menu || (context_menu = $(".context-menu"));
menu_tag.finish().empty().css("opacity", "0");
const menu_container = $.spawn("div").addClass("context-menu-container");
contextMenuCloseFn = undefined;
for(const entry of entries){
if(typeof(entry.visible) === 'boolean' && !entry.visible)
continue;
if(entry.type == MenuEntryType.CLOSE) {
contextMenuCloseFn = entry.callback;
} else
menu_container.append(generate_tag(entry));
return $.spawn("div").text("undefined");
}
menu_tag.append(menu_container);
menu_tag.animate({opacity: 1}, 100).css("display", "block");
spawn_context_menu(x: number, y: number, ...entries: contextmenu.MenuEntry[]) {
let menu_tag = this._context_menu || (this._context_menu = $(".context-menu"));
menu_tag.finish().empty().css("opacity", "0");
const width = menu_container.visible_width();
if(x + width + 5 > window.innerWidth)
menu_container.addClass("left");
const menu_container = $.spawn("div").addClass("context-menu-container");
this._close_callbacks = [];
// In the right position (the mouse)
menu_tag.css({
"top": y + "px",
"left": x + "px"
});
for(const entry of entries){
if(typeof(entry.visible) === 'boolean' && !entry.visible)
continue;
if(entry.type == contextmenu.MenuEntryType.CLOSE) {
this._close_callbacks.push(entry.callback);
} else
menu_container.append(this.generate_tag(entry));
}
menu_tag.append(menu_container);
menu_tag.animate({opacity: 1}, 100).css("display", "block");
const width = menu_container.visible_width();
if(x + width + 5 > window.innerWidth)
menu_container.addClass("left");
// In the right position (the mouse)
menu_tag.css({
"top": y + "px",
"left": x + "px"
});
}
html_format_enabled(): boolean {
return true;
}
}
//TODO: Improve
if(!window.require)
contextmenu.set_provider(new HTMLContextMenuProvider());

View File

@ -541,17 +541,17 @@ class ControlBar {
return;
event.preventDefault();
spawn_context_menu(event.pageX, event.pageY, {
type: MenuEntryType.ENTRY,
contextmenu.spawn_context_menu(event.pageX, event.pageY, {
type: contextmenu.MenuEntryType.ENTRY,
name: tr("Connect"),
icon: 'client-connect',
icon_class: 'client-connect',
callback: () => bookmark_connect(false)
}, {
type: MenuEntryType.ENTRY,
type: contextmenu.MenuEntryType.ENTRY,
name: tr("Connect in a new tab"),
icon: 'client-connect',
icon_class: 'client-connect',
callback: () => bookmark_connect(true)
}, MenuEntry.CLOSE(() => {
}, contextmenu.Entry.CLOSE(() => {
setTimeout(() => {
this.htmlTag.find(".btn_bookmark.button-dropdown").removeClass("force-show")
}, 250);

View File

@ -455,7 +455,7 @@ class ChannelInfoManager extends InfoManager<ChannelEntry> {
properties["channel_name"] = channel.generate_tag(false);
properties["channel_type"] = ChannelType.normalize(channel.channelType());
properties["channel_clients"] = channel.channelTree.clientsByChannel(channel).length;
properties["channel_subscribed"] = true; //TODO
properties["channel_subscribed"] = channel.flag_subscribed;
properties["server_encryption"] = channel.channelTree.server.properties.virtualserver_codec_encryption_mode;
for(let key in channel.properties)
@ -465,16 +465,15 @@ class ChannelInfoManager extends InfoManager<ChannelEntry> {
properties["bbcode_channel_description"] = tag_channel_description;
channel.getChannelDescription().then(description => {
let result = XBBCODE.process({
text: description,
escapeHtml: true,
addInLineBreaks: true
});
const result = xbbcode.parse(description, {});
/*
if(result.error) {
console.log("BBCode parse error: %o", result.errorQueue);
}
*/
tag_channel_description.html(result.html)
tag_channel_description.empty()
.append($.spawn("div").html(result.build_html()).contents())
.css("overflow-y", "auto")
.css("flex-grow", "1");
});

View File

@ -245,10 +245,10 @@ class ChatEntry {
tag.on("contextmenu", (e) => {
e.preventDefault();
let actions: ContextMenuEntry[] = [];
let actions: contextmenu.MenuEntry[] = [];
actions.push({
type: MenuEntryType.ENTRY,
icon: "",
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "",
name: tr("Clear"),
callback: () => {
this.history = [];
@ -257,23 +257,23 @@ class ChatEntry {
});
if(this.flag_closeable) {
actions.push({
type: MenuEntryType.ENTRY,
icon: "client-tab_close_button",
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-tab_close_button",
name: tr("Close"),
callback: () => this.handle.deleteChat(this)
});
}
actions.push({
type: MenuEntryType.ENTRY,
icon: "client-tab_close_button",
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-tab_close_button",
name: tr("Close all private tabs"),
callback: () => {
//TODO Implement this?
},
visible: false
});
spawn_context_menu(e.pageX, e.pageY, ...actions);
contextmenu.spawn_context_menu(e.pageX, e.pageY, ...actions);
});
tag_close.click(() => {

View File

@ -181,24 +181,20 @@ namespace unused {
if(event.isDefaultPrevented()) return;
event.preventDefault();
spawn_context_menu(event.pageX, event.pageY, {
type: MenuEntryType.ENTRY,
icon: "",
contextmenu.spawn_context_menu(event.pageX, event.pageY, {
type: contextmenu.MenuEntryType.ENTRY,
name: tr("Expend group"),
callback: () => update_collapse_status(true, false)
}, {
type: MenuEntryType.ENTRY,
icon: "",
type: contextmenu.MenuEntryType.ENTRY,
name: tr("Expend all"),
callback: () => update_collapse_status(true, true)
}, {
type: MenuEntryType.ENTRY,
icon: "",
type: contextmenu.MenuEntryType.ENTRY,
name: tr("Collapse group"),
callback: () => update_collapse_status(false, false)
}, {
type: MenuEntryType.ENTRY,
icon: "",
type: contextmenu.MenuEntryType.ENTRY,
name: tr("Collapse all"),
callback: () => update_collapse_status(false, true)
});

View File

@ -130,14 +130,12 @@ namespace Modals {
if(event.isDefaultPrevented()) return;
event.preventDefault();
spawn_context_menu(event.pageX, event.pageY, {
type: MenuEntryType.ENTRY,
icon: "",
contextmenu.spawn_context_menu(event.pageX, event.pageY, {
type: contextmenu.MenuEntryType.ENTRY,
name: tr("Expend all"),
callback: () => this.entry_editor.expend_all()
}, {
type: MenuEntryType.ENTRY,
icon: "",
type: contextmenu.MenuEntryType.ENTRY,
name: tr("Collapse all"),
callback: () => this.entry_editor.collapse_all()
});
@ -221,18 +219,16 @@ namespace Modals {
};
entry.on_context_menu = (x, y) => {
let entries: ContextMenuEntry[] = [];
let entries: contextmenu.MenuEntry[] = [];
if(typeof(entry.value) === "undefined") {
entries.push({
type: MenuEntryType.ENTRY,
icon: "",
type: contextmenu.MenuEntryType.ENTRY,
name: tr("Add permission"),
callback: () => entry.trigger_value_assign()
});
} else {
entries.push({
type: MenuEntryType.ENTRY,
icon: "",
type: contextmenu.MenuEntryType.ENTRY,
name: tr("Remove permission"),
callback: () => {
entry.value = undefined;
@ -243,15 +239,13 @@ namespace Modals {
if(typeof(entry.granted) === "undefined") {
entries.push({
type: MenuEntryType.ENTRY,
icon: "",
type: contextmenu.MenuEntryType.ENTRY,
name: tr("Add grant permission"),
callback: () => entry.trigger_grant_assign()
});
} else {
entries.push({
type: MenuEntryType.ENTRY,
icon: "",
type: contextmenu.MenuEntryType.ENTRY,
name: tr("Remove grant permission"),
callback: () => {
entry.granted = undefined;
@ -259,23 +253,20 @@ namespace Modals {
}
});
}
entries.push(MenuEntry.HR());
entries.push(contextmenu.Entry.HR());
entries.push({
type: MenuEntryType.ENTRY,
icon: "",
type: contextmenu.MenuEntryType.ENTRY,
name: tr("Expend all"),
callback: () => this.entry_editor.expend_all()
});
entries.push({
type: MenuEntryType.ENTRY,
icon: "",
type: contextmenu.MenuEntryType.ENTRY,
name: tr("Collapse all"),
callback: () => this.entry_editor.collapse_all()
});
entries.push(MenuEntry.HR());
entries.push(contextmenu.Entry.HR());
entries.push({
type: MenuEntryType.ENTRY,
icon: "",
type: contextmenu.MenuEntryType.ENTRY,
name: tr("Show permission description"),
callback: () => {
createInfoModal(
@ -285,15 +276,14 @@ namespace Modals {
}
});
entries.push({
type: MenuEntryType.ENTRY,
icon: "",
type: contextmenu.MenuEntryType.ENTRY,
name: tr("Copy permission name"),
callback: () => {
copy_to_clipboard(permission.name);
}
});
spawn_context_menu(x, y, ...entries);
contextmenu.spawn_context_menu(x, y, ...entries);
}
}
}
@ -1220,10 +1210,10 @@ namespace Modals {
return;
event.preventDefault();
spawn_context_menu(event.pageX, event.pageY, {
type: MenuEntryType.ENTRY,
contextmenu.spawn_context_menu(event.pageX, event.pageY, {
type: contextmenu.MenuEntryType.ENTRY,
name: tr("Remove client"),
icon: 'client-delete',
icon_class: 'client-delete',
callback: () => {
connection.serverConnection.send_command("servergroupdelclient", {
sgid: current_group.id,
@ -1233,9 +1223,9 @@ namespace Modals {
});
}
}, {
type: MenuEntryType.ENTRY,
type: contextmenu.MenuEntryType.ENTRY,
name: tr("Copy unique id"),
icon: 'client-copy',
icon_class: 'client-copy',
callback: () => copy_to_clipboard(client.client_unique_identifier)
})
});

View File

@ -136,22 +136,22 @@ class ServerEntry {
spawnContextMenu(x: number, y: number, on_close: () => void = () => {}) {
let trigger_close = true;
spawn_context_menu(x, y, {
type: MenuEntryType.ENTRY,
contextmenu.spawn_context_menu(x, y, {
type: contextmenu.MenuEntryType.ENTRY,
name: tr("Show server info"),
callback: () => {
trigger_close = false;
this.channelTree.client.select_info.open_popover()
},
icon: "client-about",
icon_class: "client-about",
visible: this.channelTree.client.select_info.is_popover()
}, {
type: MenuEntryType.HR,
type: contextmenu.MenuEntryType.HR,
visible: this.channelTree.client.select_info.is_popover(),
name: ''
}, {
type: MenuEntryType.ENTRY,
icon: "client-virtualserver_edit",
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-virtualserver_edit",
name: tr("Edit"),
callback: () => {
Modals.createServerModal(this, properties => {
@ -164,22 +164,22 @@ class ServerEntry {
});
}
}, {
type: MenuEntryType.ENTRY,
icon: "client-iconviewer",
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-iconviewer",
name: tr("View icons"),
callback: () => Modals.spawnIconSelect(this.channelTree.client)
}, {
type: MenuEntryType.ENTRY,
icon: 'client-iconsview',
type: contextmenu.MenuEntryType.ENTRY,
icon_class: 'client-iconsview',
name: tr("View avatars"),
callback: () => Modals.spawnAvatarList(this.channelTree.client)
}, {
type: MenuEntryType.ENTRY,
icon: "client-invite_buddy",
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-invite_buddy",
name: tr("Invite buddy"),
callback: () => Modals.spawnInviteEditor(this.channelTree.client)
},
MenuEntry.CLOSE(() => (trigger_close ? on_close : () => {})())
contextmenu.Entry.CLOSE(() => (trigger_close ? on_close : () => {})())
);
}

View File

@ -90,15 +90,15 @@ class ChannelTree {
this.client.permissions.neededPermission(PermissionType.B_CHANNEL_CREATE_SEMI_PERMANENT).granted(1) ||
this.client.permissions.neededPermission(PermissionType.B_CHANNEL_CREATE_PERMANENT).granted(1);
spawn_context_menu(x, y,
contextmenu.spawn_context_menu(x, y,
{
type: MenuEntryType.ENTRY,
icon: "client-channel_create",
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-channel_create",
name: tr("Create channel"),
invalidPermission: !channelCreate,
callback: () => this.spawnCreateChannel()
},
MenuEntry.CLOSE(on_close)
contextmenu.Entry.CLOSE(on_close)
);
}
@ -461,11 +461,11 @@ class ChannelTree {
const music_entry = clients.map(e => e instanceof MusicClientEntry ? 1 : 0).reduce((a, b) => a + b, 0) > 0;
const local_client = clients.map(e => e instanceof LocalClientEntry ? 1 : 0).reduce((a, b) => a + b, 0) > 0;
console.log(tr("Music only: %o | Container music: %o | Container local: %o"), music_entry, music_entry, local_client);
let entries: ContextMenuEntry[] = [];
let entries: contextmenu.MenuEntry[] = [];
if (!music_entry && !local_client) { //Music bots or local client cant be poked
entries.push({
type: MenuEntryType.ENTRY,
icon: "client-poke",
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-poke",
name: tr("Poke clients"),
callback: () => {
createInputModal(tr("Poke clients"), tr("Poke message:<br>"), text => true, result => {
@ -482,8 +482,8 @@ class ChannelTree {
});
}
entries.push({
type: MenuEntryType.ENTRY,
icon: "client-move_client_to_own_channel",
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-move_client_to_own_channel",
name: tr("Move clients to your channel"),
callback: () => {
const target = this.client.getClient().currentChannel().getChannelId();
@ -495,10 +495,10 @@ class ChannelTree {
}
});
if (!local_client) {//local client cant be kicked and/or banned or kicked
entries.push(MenuEntry.HR());
entries.push(contextmenu.Entry.HR());
entries.push({
type: MenuEntryType.ENTRY,
icon: "client-kick_channel",
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-kick_channel",
name: tr("Kick clients from channel"),
callback: () => {
createInputModal(tr("Kick clients from channel"), tr("Kick reason:<br>"), text => true, result => {
@ -517,8 +517,8 @@ class ChannelTree {
if (!music_entry) { //Music bots cant be banned or kicked
entries.push({
type: MenuEntryType.ENTRY,
icon: "client-kick_server",
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-kick_server",
name: tr("Kick clients fom server"),
callback: () => {
createInputModal(tr("Kick clients from server"), tr("Kick reason:<br>"), text => true, result => {
@ -534,8 +534,8 @@ class ChannelTree {
}, {width: 400, maxLength: 255}).open();
}
}, {
type: MenuEntryType.ENTRY,
icon: "client-ban_client",
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-ban_client",
name: tr("Ban clients"),
invalidPermission: !this.client.permissions.neededPermission(PermissionType.I_CLIENT_BAN_MAX_BANTIME).granted(1),
callback: () => {
@ -555,10 +555,10 @@ class ChannelTree {
});
}
if(music_only) {
entries.push(MenuEntry.HR());
entries.push(contextmenu.Entry.HR());
entries.push({
name: tr("Delete bots"),
icon: "client-delete",
icon_class: "client-delete",
disabled: false,
callback: () => {
const param_string = clients.map((_, index) => "{" + index + "}").join(', ');
@ -574,11 +574,11 @@ class ChannelTree {
}
});
},
type: MenuEntryType.ENTRY
type: contextmenu.MenuEntryType.ENTRY
});
}
}
spawn_context_menu(event.pageX, event.pageY, ...entries);
contextmenu.spawn_context_menu(event.pageX, event.pageY, ...entries);
}
clientsByGroup(group: Group) : ClientEntry[] {

2
vendor/xbbcode vendored

@ -1 +1 @@
Subproject commit d9a47d059ae9cce559d7a75553a25ba342d36229
Subproject commit 8092afb59615aa19cff372689cd6985c96e3f2ba