" + path + "
" : path;
+ else
+ return html ? "" + path.url + "
" : path.url;
+}
+
+class SyntaxError {
+ source: any;
+
+ constructor(source: any) {
+ this.source = source;
+ }
+}
+
+let _script_promises: {[key: string]: Promise';
- html += '';
- html += result.value;
- return html + "
";
- }
- });
-
- /* override the yt parser */
- const original_parser = xbbcode.register.find_parser("yt");
- if(original_parser)
- xbbcode.register.register_parser({
- tag: ["yt", "youtube"],
- build_html(layer): string {
- const result = original_parser.build_html(layer);
- if(!result.startsWith("";
- return sanitizer_escaped(uid);
- }
- });
-
- /* the image parse & displayer */
- xbbcode.register.register_parser({
- tag: ["img", "image"],
- build_html(layer): string {
- const uid = guid();
- const fallback_value = "[img]" + layer.build_text() + "[/img]";
-
- let target;
- let content = layer.content.map(e => e.build_text()).join("");
- if (!layer.options) {
- target = content;
- } else
- target = layer.options;
-
- let url: URL;
- try {
- url = new URL(target);
- if(!url.hostname) throw "";
- } catch(error) {
- return fallback_value;
- }
-
- sanitizer_escaped_map[uid] = "";
- return sanitizer_escaped(uid);
- }
- })
- },
- priority: 10
- });
+ export interface FormatSettings {
+ is_chat_message?: boolean
}
- export function sanitize_text(text: string) : string {
- return $(DOMPurify.sanitize("" + text + "", {
+ export function format(message: string, fsettings?: FormatSettings) : JQuery[] {
+ fsettings = fsettings || {};
+
+ single_url_parse:
+ if(fsettings.is_chat_message) {
+ /* try if its only one url */
+ const raw_url = message.replace(/\[url(=\S+)?](\S+)\[\/url]/, "$2");
+ let url: URL;
+ try {
+ url = new URL(raw_url);
+ } catch(error) {
+ break single_url_parse;
+ }
+
+ single_url_yt:
+ {
+ const result = raw_url.match(yt_url_regex);
+ if(!result) break single_url_yt;
+
+ return format("[yt]https://www.youtube.com/watch?v=" + result[5] + "[/yt]");
+ }
+
+ single_url_image:
+ {
+ const ext_index = url.pathname.lastIndexOf(".");
+ if(ext_index == -1) break single_url_image;
+
+ const ext_name = url.pathname.substr(ext_index + 1).toLowerCase();
+ if([
+ "jpeg", "jpg",
+ "png", "bmp", "gif",
+ "tiff", "pdf", "svg"
+ ].findIndex(e => e === ext_name) == -1) break single_url_image;
+
+ return format("[img]" + message + "[/img]");
+ }
+ }
+
+ const result = xbbcode.parse(message, {
+ tag_whitelist: [
+ "b", "big",
+ "i", "italic",
+ "u", "underlined",
+ "s", "strikethrough",
+ "color",
+ "url",
+ "code",
+ "i-code", "icode",
+ "sub", "sup",
+ "size",
+ "hr", "br",
+
+ "ul", "ol", "list",
+ "li",
+
+ "table",
+ "tr", "td", "th",
+
+ "yt", "youtube",
+ "img"
+ ]
+ });
+ let html = result.build_html();
+ if(typeof(window.twemoji) !== "undefined" && settings.static_global(Settings.KEY_CHAT_COLORED_EMOJIES))
+ html = twemoji.parse(html);
+
+ const container = $.spawn("div");
+ let sanitized = DOMPurify.sanitize(html, {
ADD_ATTR: [
"x-highlight-type",
"x-code-type",
"x-image-url"
]
- })).text();
+ });
+
+ sanitized = sanitized.replace(sanitizer_escaped_regex, data => {
+ const uid = data.match(sanitizer_escaped_regex)[1];
+ const value = sanitizer_escaped_map[uid];
+ if(!value) return data;
+ delete sanitizer_escaped_map[uid];
+
+ return value;
+ });
+
+ container[0].innerHTML = sanitized;
+
+
+ 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: loader.version().type === "native" && 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));
}
+
+ export function load_image(entry: HTMLImageElement) {
+ const url = decodeURIComponent(entry.getAttribute("x-image-url") || "");
+ const proxy_url = "https://images.weserv.nl/?url=" + encodeURIComponent(url);
+
+ entry.onload = undefined;
+ entry.src = proxy_url;
+
+ const parent = $(entry.parentElement);
+ parent.on('contextmenu', event => {
+ contextmenu.spawn_context_menu(event.pageX, event.pageY, {
+ callback: () => {
+ const win = window.open(url, '_blank');
+ win.focus();
+ },
+ name: tr("Open image in browser"),
+ type: contextmenu.MenuEntryType.ENTRY,
+ icon_class: "client-browse-addon-online"
+ }, contextmenu.Entry.HR(), {
+ callback: () => copy_to_clipboard(url),
+ name: tr("Copy image URL to clipboard"),
+ type: contextmenu.MenuEntryType.ENTRY,
+ icon_class: "client-copy"
+ })
+ });
+ parent.css("cursor", "pointer").on('click', event => image_preview.preview_image(proxy_url, url));
+ }
+
+ loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
+ name: "XBBCode code tag init",
+ function: async () => {
+ /* override default parser */
+ xbbcode.register.register_parser({
+ tag: ["code", "icode", "i-code"],
+ content_tags_whitelist: [],
+
+ build_html(layer) : string {
+ const klass = layer.tag_normalized != 'code' ? "tag-hljs-inline-code" : "tag-hljs-code";
+ const language = (layer.options || "").replace("\"", "'").toLowerCase();
+
+ /* remove heading empty lines */
+ let text = layer.content.map(e => e.build_text())
+ .reduce((a, b) => a.length == 0 && b.replace(/[ \n\r\t]+/g, "").length == 0 ? "" : a + b, "")
+ .replace(/^([ \n\r\t]*)(?=\n)+/g, "");
+ if(text.startsWith("\r") || text.startsWith("\n"))
+ text = text.substr(1);
+
+ let result: HighlightJSResult;
+ if(window.hljs.getLanguage(language))
+ result = window.hljs.highlight(language, text, true);
+ else
+ result = window.hljs.highlightAuto(text);
+
+ let html = '';
+ html += '';
+ html += result.value;
+ return html + "
";
+ }
+ });
+
+ /* override the yt parser */
+ const original_parser = xbbcode.register.find_parser("yt");
+ if(original_parser)
+ xbbcode.register.register_parser({
+ tag: ["yt", "youtube"],
+ build_html(layer): string {
+ const result = original_parser.build_html(layer);
+ if(!result.startsWith("";
+ return sanitizer_escaped(uid);
+ }
+ });
+
+ /* the image parse & displayer */
+ xbbcode.register.register_parser({
+ tag: ["img", "image"],
+ build_html(layer): string {
+ const uid = guid();
+ const fallback_value = "[img]" + layer.build_text() + "[/img]";
+
+ let target;
+ let content = layer.content.map(e => e.build_text()).join("");
+ if (!layer.options) {
+ target = content;
+ } else
+ target = layer.options;
+
+ let url: URL;
+ try {
+ url = new URL(target);
+ if(!url.hostname) throw "";
+ } catch(error) {
+ return fallback_value;
+ }
+
+ sanitizer_escaped_map[uid] = "";
+ return sanitizer_escaped(uid);
+ }
+ })
+ },
+ priority: 10
+ });
+}
+
+export function sanitize_text(text: string) : string {
+ return $(DOMPurify.sanitize("" + text + "", {
+ ADD_ATTR: [
+ "x-highlight-type",
+ "x-code-type",
+ "x-image-url"
+ ]
+ })).text();
+}
+
+export function formatDate(secs: number) : string {
+ let years = Math.floor(secs / (60 * 60 * 24 * 365));
+ let days = Math.floor(secs / (60 * 60 * 24)) % 365;
+ let hours = Math.floor(secs / (60 * 60)) % 24;
+ let minutes = Math.floor(secs / 60) % 60;
+ let seconds = Math.floor(secs % 60);
+
+ let result = "";
+ if(years > 0)
+ result += years + " " + tr("years") + " ";
+ if(years > 0 || days > 0)
+ result += days + " " + tr("days") + " ";
+ if(years > 0 || days > 0 || hours > 0)
+ result += hours + " " + tr("hours") + " ";
+ if(years > 0 || days > 0 || hours > 0 || minutes > 0)
+ result += minutes + " " + tr("minutes") + " ";
+ if(years > 0 || days > 0 || hours > 0 || minutes > 0 || seconds > 0)
+ result += seconds + " " + tr("seconds") + " ";
+ else
+ result = tr("now") + " ";
+
+ return result.substr(0, result.length - 1);
}
\ No newline at end of file
diff --git a/shared/js/PPTListener.ts b/shared/js/PPTListener.ts
index b0d32f5d..d0439914 100644
--- a/shared/js/PPTListener.ts
+++ b/shared/js/PPTListener.ts
@@ -1,4 +1,4 @@
-enum KeyCode {
+export enum KeyCode {
KEY_CANCEL = 3,
KEY_HELP = 6,
KEY_BACK_SPACE = 8,
@@ -118,59 +118,57 @@ enum KeyCode {
KEY_META = 224
}
-namespace ppt {
- export enum EventType {
- KEY_PRESS,
- KEY_RELEASE,
- KEY_TYPED
- }
+export enum EventType {
+ KEY_PRESS,
+ KEY_RELEASE,
+ KEY_TYPED
+}
- export enum SpecialKey {
- CTRL,
- WINDOWS,
- SHIFT,
- ALT
- }
+export enum SpecialKey {
+ CTRL,
+ WINDOWS,
+ SHIFT,
+ ALT
+}
- export interface KeyDescriptor {
- key_code: string;
+export interface KeyDescriptor {
+ key_code: string;
- key_ctrl: boolean;
- key_windows: boolean;
- key_shift: boolean;
- key_alt: boolean;
- }
+ key_ctrl: boolean;
+ key_windows: boolean;
+ key_shift: boolean;
+ key_alt: boolean;
+}
- export interface KeyEvent extends KeyDescriptor {
- readonly type: EventType;
+export interface KeyEvent extends KeyDescriptor {
+ readonly type: EventType;
- readonly key: string;
- }
+ readonly key: string;
+}
- export interface KeyHook extends KeyDescriptor {
- cancel: boolean;
+export interface KeyHook extends KeyDescriptor {
+ cancel: boolean;
- callback_press: () => any;
- callback_release: () => any;
- }
+ callback_press: () => any;
+ callback_release: () => any;
+}
- export function key_description(key: KeyDescriptor) {
- let result = "";
- if(key.key_shift)
- result += " + " + tr("Shift");
- if(key.key_alt)
- result += " + " + tr("Alt");
- if(key.key_ctrl)
- result += " + " + tr("CTRL");
- if(key.key_windows)
- result += " + " + tr("Win");
+export function key_description(key: KeyDescriptor) {
+ let result = "";
+ if(key.key_shift)
+ result += " + " + tr("Shift");
+ if(key.key_alt)
+ result += " + " + tr("Alt");
+ if(key.key_ctrl)
+ result += " + " + tr("CTRL");
+ if(key.key_windows)
+ result += " + " + tr("Win");
- if(!result && !key.key_code)
- return tr("unset");
+ if(!result && !key.key_code)
+ return tr("unset");
- if(key.key_code)
- result += " + " + key.key_code;
- return result.substr(3);
- }
+ if(key.key_code)
+ result += " + " + key.key_code;
+ return result.substr(3);
}
\ No newline at end of file
diff --git a/shared/js/audio/audio.ts b/shared/js/audio/audio.ts
deleted file mode 100644
index 4314f775..00000000
--- a/shared/js/audio/audio.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace audio {
- export namespace player {
- export interface Device {
- device_id: string;
-
- driver: string;
- name: string;
- }
- }
-}
\ No newline at end of file
diff --git a/shared/js/audio/player.ts b/shared/js/audio/player.ts
new file mode 100644
index 00000000..0afb5f7c
--- /dev/null
+++ b/shared/js/audio/player.ts
@@ -0,0 +1,6 @@
+export interface Device {
+ device_id: string;
+
+ driver: string;
+ name: string;
+}
\ No newline at end of file
diff --git a/shared/js/bookmarks.ts b/shared/js/bookmarks.ts
index cb12ca5e..c16a79bf 100644
--- a/shared/js/bookmarks.ts
+++ b/shared/js/bookmarks.ts
@@ -1,262 +1,260 @@
-namespace bookmarks {
- function guid() {
- function s4() {
- return Math
- .floor((1 + Math.random()) * 0x10000)
- .toString(16)
- .substring(1);
- }
- return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
+import * as log from "tc-shared/log";
+import {LogCategory} from "tc-shared/log";
+import {guid} from "tc-shared/crypto/uid";
+import {createErrorModal, createInfoModal, createInputModal} from "tc-shared/ui/elements/Modal";
+import {default_profile, find_profile} from "tc-shared/profiles/ConnectionProfile";
+import {server_connections} from "tc-shared/ui/frames/connection_handlers";
+import {spawnConnectModal} from "tc-shared/ui/modal/ModalConnect";
+import {control_bar} from "tc-shared/ui/frames/ControlBar";
+import * as top_menu from "./ui/frames/MenuBar";
+
+export const boorkmak_connect = (mark: Bookmark, new_tab?: boolean) => {
+ const profile = find_profile(mark.connect_profile) || default_profile();
+ if(profile.valid()) {
+ const connection = (typeof(new_tab) !== "boolean" || !new_tab) ? server_connections.active_connection_handler() : server_connections.spawn_server_connection_handler();
+ server_connections.set_active_connection_handler(connection);
+ connection.startConnection(
+ mark.server_properties.server_address + ":" + mark.server_properties.server_port,
+ profile,
+ true,
+ {
+ nickname: mark.nickname === "Another TeaSpeak user" || !mark.nickname ? profile.connect_username() : mark.nickname,
+ password: mark.server_properties.server_password_hash ? {
+ password: mark.server_properties.server_password_hash,
+ hashed: true
+ } : mark.server_properties.server_password ? {
+ hashed: false,
+ password: mark.server_properties.server_password
+ } : undefined
+ }
+ );
+ } else {
+ spawnConnectModal({}, {
+ url: mark.server_properties.server_address + ":" + mark.server_properties.server_port,
+ enforce: true
+ }, {
+ profile: profile,
+ enforce: true
+ })
}
+};
- export const boorkmak_connect = (mark: Bookmark, new_tab?: boolean) => {
- const profile = profiles.find_profile(mark.connect_profile) || profiles.default_profile();
- if(profile.valid()) {
- const connection = (typeof(new_tab) !== "boolean" || !new_tab) ? server_connections.active_connection_handler() : server_connections.spawn_server_connection_handler();
- server_connections.set_active_connection_handler(connection);
- connection.startConnection(
- mark.server_properties.server_address + ":" + mark.server_properties.server_port,
- profile,
- true,
- {
- nickname: mark.nickname === "Another TeaSpeak user" || !mark.nickname ? profile.connect_username() : mark.nickname,
- password: mark.server_properties.server_password_hash ? {
- password: mark.server_properties.server_password_hash,
- hashed: true
- } : mark.server_properties.server_password ? {
- hashed: false,
- password: mark.server_properties.server_password
- } : undefined
- }
- );
- } else {
- Modals.spawnConnectModal({}, {
- url: mark.server_properties.server_address + ":" + mark.server_properties.server_port,
- enforce: true
- }, {
- profile: profile,
- enforce: true
- })
- }
- };
+export interface ServerProperties {
+ server_address: string;
+ server_port: number;
+ server_password_hash?: string;
+ server_password?: string;
+}
- export interface ServerProperties {
- server_address: string;
- server_port: number;
- server_password_hash?: string;
- server_password?: string;
- }
+export enum BookmarkType {
+ ENTRY,
+ DIRECTORY
+}
- export enum BookmarkType {
- ENTRY,
- DIRECTORY
- }
+export interface Bookmark {
+ type: /* BookmarkType.ENTRY */ BookmarkType;
+ /* readonly */ parent: DirectoryBookmark;
- export interface Bookmark {
- type: /* BookmarkType.ENTRY */ BookmarkType;
- /* readonly */ parent: DirectoryBookmark;
+ server_properties: ServerProperties;
+ display_name: string;
+ unique_id: string;
- server_properties: ServerProperties;
- display_name: string;
- unique_id: string;
+ nickname: string;
+ default_channel?: number | string;
+ default_channel_password_hash?: string;
+ default_channel_password?: string;
- nickname: string;
- default_channel?: number | string;
- default_channel_password_hash?: string;
- default_channel_password?: string;
+ connect_profile: string;
- connect_profile: string;
+ last_icon_id?: number;
+}
- last_icon_id?: number;
- }
+export interface DirectoryBookmark {
+ type: /* BookmarkType.DIRECTORY */ BookmarkType;
+ /* readonly */ parent: DirectoryBookmark;
- export interface DirectoryBookmark {
- type: /* BookmarkType.DIRECTORY */ BookmarkType;
- /* readonly */ parent: DirectoryBookmark;
+ readonly content: (Bookmark | DirectoryBookmark)[];
+ unique_id: string;
+ display_name: string;
+}
- readonly content: (Bookmark | DirectoryBookmark)[];
- unique_id: string;
- display_name: string;
- }
+interface BookmarkConfig {
+ root_bookmark?: DirectoryBookmark;
+ default_added?: boolean;
+}
- interface BookmarkConfig {
- root_bookmark?: DirectoryBookmark;
- default_added?: boolean;
- }
-
- let _bookmark_config: BookmarkConfig;
-
- function bookmark_config() : BookmarkConfig {
- if(_bookmark_config)
- return _bookmark_config;
-
- let bookmark_json = localStorage.getItem("bookmarks");
- let bookmarks;
- try {
- bookmarks = JSON.parse(bookmark_json) || {} as BookmarkConfig;
- } catch(error) {
- log.error(LogCategory.BOOKMARKS, tr("Failed to load bookmarks: %o"), error);
- bookmarks = {} as any;
- }
-
- _bookmark_config = bookmarks;
- _bookmark_config.root_bookmark = _bookmark_config.root_bookmark || { content: [], display_name: "root", type: BookmarkType.DIRECTORY} as DirectoryBookmark;
-
- if(!_bookmark_config.default_added) {
- _bookmark_config.default_added = true;
- create_bookmark("TeaSpeak official Test-Server", _bookmark_config.root_bookmark, {
- server_address: "ts.teaspeak.de",
- server_port: 9987
- }, undefined);
-
- save_config();
- }
-
- const fix_parent = (parent: DirectoryBookmark, entry: Bookmark | DirectoryBookmark) => {
- entry.parent = parent;
- if(entry.type === BookmarkType.DIRECTORY)
- for(const child of (entry as DirectoryBookmark).content)
- fix_parent(entry as DirectoryBookmark, child);
- };
- for(const entry of _bookmark_config.root_bookmark.content)
- fix_parent(_bookmark_config.root_bookmark, entry);
+let _bookmark_config: BookmarkConfig;
+function bookmark_config() : BookmarkConfig {
+ if(_bookmark_config)
return _bookmark_config;
+
+ let bookmark_json = localStorage.getItem("bookmarks");
+ let bookmarks;
+ try {
+ bookmarks = JSON.parse(bookmark_json) || {} as BookmarkConfig;
+ } catch(error) {
+ log.error(LogCategory.BOOKMARKS, tr("Failed to load bookmarks: %o"), error);
+ bookmarks = {} as any;
}
- function save_config() {
- localStorage.setItem("bookmarks", JSON.stringify(bookmark_config(), (key, value) => {
- if(key === "parent")
- return undefined;
- return value;
- }));
+ _bookmark_config = bookmarks;
+ _bookmark_config.root_bookmark = _bookmark_config.root_bookmark || { content: [], display_name: "root", type: BookmarkType.DIRECTORY} as DirectoryBookmark;
+
+ if(!_bookmark_config.default_added) {
+ _bookmark_config.default_added = true;
+ create_bookmark("TeaSpeak official Test-Server", _bookmark_config.root_bookmark, {
+ server_address: "ts.teaspeak.de",
+ server_port: 9987
+ }, undefined);
+
+ save_config();
}
- export function bookmarks() : DirectoryBookmark {
- return bookmark_config().root_bookmark;
- }
+ const fix_parent = (parent: DirectoryBookmark, entry: Bookmark | DirectoryBookmark) => {
+ entry.parent = parent;
+ if(entry.type === BookmarkType.DIRECTORY)
+ for(const child of (entry as DirectoryBookmark).content)
+ fix_parent(entry as DirectoryBookmark, child);
+ };
+ for(const entry of _bookmark_config.root_bookmark.content)
+ fix_parent(_bookmark_config.root_bookmark, entry);
- export function bookmarks_flat() : Bookmark[] {
- const result: Bookmark[] = [];
- const _flat = (bookmark: Bookmark | DirectoryBookmark) => {
- if(bookmark.type == BookmarkType.DIRECTORY)
- for(const book of (bookmark as DirectoryBookmark).content)
- _flat(book);
- else
- result.push(bookmark as Bookmark);
- };
- _flat(bookmark_config().root_bookmark);
- return result;
- }
+ return _bookmark_config;
+}
- function find_bookmark_recursive(parent: DirectoryBookmark, uuid: string) : Bookmark | DirectoryBookmark {
- for(const entry of parent.content) {
- if(entry.unique_id == uuid)
- return entry;
- if(entry.type == BookmarkType.DIRECTORY) {
- const result = find_bookmark_recursive(entry as DirectoryBookmark, uuid);
- if(result) return result;
- }
- }
- return undefined;
- }
+function save_config() {
+ localStorage.setItem("bookmarks", JSON.stringify(bookmark_config(), (key, value) => {
+ if(key === "parent")
+ return undefined;
+ return value;
+ }));
+}
- export function find_bookmark(uuid: string) : Bookmark | DirectoryBookmark | undefined {
- return find_bookmark_recursive(bookmarks(), uuid);
- }
+export function bookmarks() : DirectoryBookmark {
+ return bookmark_config().root_bookmark;
+}
- export function parent_bookmark(bookmark: Bookmark) : DirectoryBookmark {
- const books: (DirectoryBookmark | Bookmark)[] = [bookmarks()];
- while(!books.length) {
- const directory = books.pop_front();
- if(directory.type == BookmarkType.DIRECTORY) {
- const cast =