Added image preview and preview overlay
parent
7250c146d8
commit
b4fc739a13
|
@ -1,6 +1,10 @@
|
|||
# Changelog:
|
||||
* **19.03.20**
|
||||
- Using proper icons for the client info
|
||||
- Added an image preview overlay
|
||||
- Added image preview within the chat
|
||||
- Added an image preview to the music bot thumbnails
|
||||
- Added an image preview to client avatars
|
||||
|
||||
* **18.03.20**
|
||||
- Updated the sound playback mechanism and allowing the native backend to playback sounds via the native interface.
|
||||
|
|
|
@ -14,6 +14,7 @@ function remove_if_exists() {
|
|||
function cleanup_declarations() {
|
||||
remove_if_exists shared/declarations/
|
||||
remove_if_exists web/declarations/
|
||||
remove_if_exists client/declarations/
|
||||
}
|
||||
|
||||
function cleanup_generated_files() {
|
||||
|
@ -58,4 +59,4 @@ cleanup_declarations
|
|||
echo "Deleting generated output files"
|
||||
cleanup_generated_files
|
||||
|
||||
echo "Project cleaned up"
|
||||
echo "Project cleaned up"
|
||||
|
|
|
@ -47,6 +47,7 @@ files=(
|
|||
"css/static/modal-serverinfo.css"
|
||||
"css/static/modal-settings.css"
|
||||
"css/static/modal-volume.css"
|
||||
"css/static/overlay-image-preview.css"
|
||||
|
||||
"css/static/ts/tab.css"
|
||||
"css/static/ts/chat.css"
|
||||
|
|
|
@ -1431,6 +1431,20 @@ $bot_thumbnail_height: 9em;
|
|||
background-color: #25252a;
|
||||
}
|
||||
}
|
||||
|
||||
.xbbcode-tag-img {
|
||||
padding: .25em;
|
||||
border-radius: .25em;
|
||||
|
||||
overflow: hidden;
|
||||
max-width: 20em;
|
||||
max-height: 20em;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.container-music-info {
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
@import "mixin";
|
||||
|
||||
.overlay-image-preview {
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
|
||||
pointer-events: all;
|
||||
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
opacity: 1;
|
||||
background-color: #000000EF;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
.container-menu-bar {
|
||||
position: absolute;
|
||||
|
||||
top: .25em;
|
||||
left: 0;
|
||||
right: .25em;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
|
||||
.entry {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
font-size: 2em;
|
||||
|
||||
margin: .25em;
|
||||
padding: .15em;
|
||||
|
||||
border-radius: .125em;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
.container-icon {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
display: flex;
|
||||
|
||||
img {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #FFFFFF1F;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.container-image {
|
||||
max-width: 90%;
|
||||
max-height: 90%;
|
||||
align-self: center;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: stretch;
|
||||
|
||||
img {
|
||||
flex-shrink: 1;
|
||||
|
||||
min-height: 1em;
|
||||
min-width: 1em;
|
||||
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@include transition(ease-in-out .25s);
|
||||
}
|
|
@ -224,6 +224,26 @@
|
|||
</div>
|
||||
</div>
|
||||
<div id="contextMenu" class="context-menu"></div>
|
||||
<div class="overlay-image-preview hidden" id="overlay-image-preview">
|
||||
<div class="container-menu-bar">
|
||||
<div class="entry button-open-in-window">
|
||||
<div class="container-icon">
|
||||
<img src="img/icon_image_preview_browse.svg">
|
||||
</div>
|
||||
</div>
|
||||
<!-- Why would you like to download images?
|
||||
<div class="entry button-download">
|
||||
<div class="icon_em client-download"></div>
|
||||
</div>
|
||||
-->
|
||||
<div class="entry button-close">
|
||||
<div class="icon_em client-close_button"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-image">
|
||||
<img src="https://forum.teaspeak.de/index.php?attachments/1581248374635-png.2098/">
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script class="jsrender-template" id="tmpl_frame_chat" type="text/html">
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" fill="#7289da" enable-background="new 0 0 512.418 512.418" height="512" viewBox="0 0 512.418 512.418" width="512" xmlns="http://www.w3.org/2000/svg"><path d="m437.335 75.082c-100.1-100.102-262.136-100.118-362.252 0-100.103 100.102-100.118 262.136 0 362.253 100.1 100.102 262.136 100.117 362.252 0 100.103-100.102 100.117-262.136 0-362.253zm-10.706 325.739c-11.968-10.702-24.77-20.173-38.264-28.335 8.919-30.809 14.203-64.712 15.452-99.954h75.309c-3.405 47.503-21.657 92.064-52.497 128.289zm-393.338-128.289h75.309c1.249 35.242 6.533 69.145 15.452 99.954-13.494 8.162-26.296 17.633-38.264 28.335-30.84-36.225-49.091-80.786-52.497-128.289zm52.498-160.936c11.968 10.702 24.77 20.173 38.264 28.335-8.919 30.809-14.203 64.712-15.452 99.954h-75.31c3.406-47.502 21.657-92.063 52.498-128.289zm154.097 31.709c-26.622-1.904-52.291-8.461-76.088-19.278 13.84-35.639 39.354-78.384 76.088-88.977zm0 32.708v63.873h-98.625c1.13-29.812 5.354-58.439 12.379-84.632 27.043 11.822 56.127 18.882 86.246 20.759zm0 96.519v63.873c-30.119 1.877-59.203 8.937-86.246 20.759-7.025-26.193-11.249-54.82-12.379-84.632zm0 96.581v108.254c-36.732-10.593-62.246-53.333-76.088-88.976 23.797-10.817 49.466-17.374 76.088-19.278zm32.646 0c26.622 1.904 52.291 8.461 76.088 19.278-13.841 35.64-39.354 78.383-76.088 88.976zm0-32.708v-63.873h98.625c-1.13 29.812-5.354 58.439-12.379 84.632-27.043-11.822-56.127-18.882-86.246-20.759zm0-96.519v-63.873c30.119-1.877 59.203-8.937 86.246-20.759 7.025 26.193 11.249 54.82 12.379 84.632zm0-96.581v-108.254c36.734 10.593 62.248 53.338 76.088 88.977-23.797 10.816-49.466 17.373-76.088 19.277zm73.32-91.957c20.895 9.15 40.389 21.557 57.864 36.951-8.318 7.334-17.095 13.984-26.26 19.931-8.139-20.152-18.536-39.736-31.604-56.882zm-210.891 56.882c-9.165-5.947-17.941-12.597-26.26-19.931 17.475-15.394 36.969-27.801 57.864-36.951-13.068 17.148-23.465 36.732-31.604 56.882zm.001 295.958c8.138 20.151 18.537 39.736 31.604 56.882-20.895-9.15-40.389-21.557-57.864-36.951 8.318-7.334 17.095-13.984 26.26-19.931zm242.494 0c9.165 5.947 17.942 12.597 26.26 19.93-17.475 15.394-36.969 27.801-57.864 36.951 13.067-17.144 23.465-36.729 31.604-56.881zm26.362-164.302c-1.249-35.242-6.533-69.146-15.452-99.954 13.494-8.162 26.295-17.633 38.264-28.335 30.84 36.225 49.091 80.786 52.497 128.289z"/></svg>
|
After Width: | Height: | Size: 2.2 KiB |
|
@ -0,0 +1,250 @@
|
|||
namespace messages.formatter {
|
||||
export namespace bbcode {
|
||||
const sanitizer_escaped = (key: string) => "[-- sescaped: " + key + " --]";
|
||||
const sanitizer_escaped_regex = /\[-- sescaped: ([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}) --]/;
|
||||
const sanitizer_escaped_map: {[key: string]: string} = {};
|
||||
|
||||
const yt_url_regex = /^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?$/;
|
||||
|
||||
export interface FormatSettings {
|
||||
is_chat_message?: boolean
|
||||
}
|
||||
|
||||
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"
|
||||
]
|
||||
});
|
||||
|
||||
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: !app.is_web() && false // Currently not possible
|
||||
}, contextmenu.Entry.HR(), {
|
||||
callback: () => copy_to_clipboard(url),
|
||||
name: tr("Copy URL to clipboard"),
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
icon_class: "client-copy"
|
||||
});
|
||||
});
|
||||
|
||||
return [container.contents() as JQuery];
|
||||
//return result.root_tag.content.map(e => e.build_html()).map((entry, idx, array) => $.spawn("a").css("display", (idx == 0 ? "inline" : "") + "block").html(entry == "" && idx != 0 ? " " : entry));
|
||||
}
|
||||
|
||||
export function load_image(entry: HTMLImageElement) {
|
||||
const url = decodeURIComponent(entry.getAttribute("x-image-url") || "");
|
||||
const proxy_url = "//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 = '<pre class="' + klass + '">';
|
||||
html += '<code class="hljs" x-code-type="' + language + '" x-highlight-type="' + result.language + '">';
|
||||
html += result.value;
|
||||
return html + "</code></pre>";
|
||||
}
|
||||
});
|
||||
|
||||
/* 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("<iframe")) return result;
|
||||
|
||||
const url = result.match(/src="(\S+)" /)[1];
|
||||
const uid = guid();
|
||||
|
||||
sanitizer_escaped_map[uid] = "<iframe class=\"xbbcode-tag xbbcode-tag-video\" src=\"" + url + "\" frameborder=\"0\" allow=\"autoplay; encrypted-media\" allowfullscreen></iframe>";
|
||||
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] = "<div class='xbbcode-tag-img'><img src='img/loading_image.svg' onload='messages.formatter.bbcode.load_image(this)' x-image-url='" + encodeURIComponent(target) + "' title='" + sanitize_text(target) + "' /></div>";
|
||||
return sanitizer_escaped(uid);
|
||||
}
|
||||
})
|
||||
},
|
||||
priority: 10
|
||||
});
|
||||
}
|
||||
|
||||
export function sanitize_text(text: string) : string {
|
||||
return $(DOMPurify.sanitize("<a>" + text + "</a>", {
|
||||
ADD_ATTR: [
|
||||
"x-highlight-type",
|
||||
"x-code-type",
|
||||
"x-image-url"
|
||||
]
|
||||
})).text();
|
||||
}
|
||||
}
|
|
@ -269,6 +269,11 @@ class Settings extends StaticSettings {
|
|||
description: 'Enabled bbcode support in chat.'
|
||||
};
|
||||
|
||||
static readonly KEY_CHAT_IMAGE_WHITELIST_REGEX: SettingsKey<string> = {
|
||||
key: 'chat_image_whitelist_regex',
|
||||
default_value: JSON.stringify([])
|
||||
};
|
||||
|
||||
static readonly KEY_SWITCH_INSTANT_CHAT: SettingsKey<boolean> = {
|
||||
key: 'switch_instant_chat',
|
||||
default_value: true,
|
||||
|
|
|
@ -90,89 +90,11 @@ namespace MessageHelper {
|
|||
return result;
|
||||
}
|
||||
|
||||
const yt_embed_regex = /\[-- yt: ([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}) --]/;
|
||||
//TODO: Remove this (only legacy)
|
||||
export function bbcode_chat(message: string) : JQuery[] {
|
||||
const result = xbbcode.parse(message, {
|
||||
/* TODO make this configurable and allow IMG */
|
||||
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" */
|
||||
] //[img]https://i.ytimg.com/vi/kgeSTkZssPg/maxresdefault.jpg[/img]
|
||||
return messages.formatter.bbcode.format(message, {
|
||||
is_chat_message: true
|
||||
});
|
||||
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"
|
||||
]
|
||||
});
|
||||
|
||||
sanitized = sanitized.replace(yt_embed_regex, data => {
|
||||
const uid = data.match(yt_embed_regex)[1];
|
||||
const url = yt_url_map[uid];
|
||||
if(!url) return data;
|
||||
delete yt_url_map[uid];
|
||||
|
||||
return "<iframe class=\"xbbcode-tag xbbcode-tag-video\" src=\"" + url + "\" frameborder=\"0\" allow=\"autoplay; encrypted-media\" allowfullscreen></iframe>";
|
||||
});
|
||||
|
||||
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: !app.is_web() && false // Currently not possible
|
||||
}, contextmenu.Entry.HR(), {
|
||||
callback: () => copy_to_clipboard(url),
|
||||
name: tr("Copy URL to clipboard"),
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
icon_class: "client-copy"
|
||||
});
|
||||
});
|
||||
|
||||
return [container.contents() as JQuery];
|
||||
//return result.root_tag.content.map(e => e.build_html()).map((entry, idx, array) => $.spawn("a").css("display", (idx == 0 ? "inline" : "") + "block").html(entry == "" && idx != 0 ? " " : entry));
|
||||
}
|
||||
|
||||
export namespace network {
|
||||
|
@ -318,59 +240,6 @@ namespace MessageHelper {
|
|||
);
|
||||
}
|
||||
|
||||
const yt_url_map: {[key: string]: string} = {};
|
||||
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 = '<pre class="' + klass + '">';
|
||||
html += '<code class="hljs" x-code-type="' + language + '" x-highlight-type="' + result.language + '">';
|
||||
html += result.value;
|
||||
return html + "</code></pre>";
|
||||
}
|
||||
});
|
||||
|
||||
/* 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("<iframe")) return result;
|
||||
|
||||
const url = result.match(/src="(\S+)" /)[1];
|
||||
const uid = guid();
|
||||
|
||||
yt_url_map[uid] = url;
|
||||
return "[-- yt: " + uid + " --]";
|
||||
}
|
||||
});
|
||||
},
|
||||
priority: 10
|
||||
});
|
||||
|
||||
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
||||
name: "icon size init",
|
||||
function: async () => {
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
namespace image_preview {
|
||||
let preview_overlay: JQuery<HTMLDivElement>;
|
||||
let container_image: JQuery<HTMLDivElement>;
|
||||
let button_open_in_browser: JQuery;
|
||||
|
||||
export function preview_image(url: string, original_url: string) {
|
||||
if(!preview_overlay) return;
|
||||
|
||||
container_image.empty();
|
||||
$.spawn("img").attr({
|
||||
"src": url,
|
||||
"title": original_url,
|
||||
"x-original-src": original_url
|
||||
}).appendTo(container_image);
|
||||
|
||||
preview_overlay.removeClass("hidden");
|
||||
button_open_in_browser.show();
|
||||
}
|
||||
|
||||
export function preview_image_tag(tag: JQuery) {
|
||||
if(!preview_overlay) return;
|
||||
|
||||
container_image.empty();
|
||||
container_image.append(tag);
|
||||
|
||||
preview_overlay.removeClass("hidden");
|
||||
button_open_in_browser.hide();
|
||||
}
|
||||
|
||||
export function current_url() {
|
||||
const image_tag = container_image.find("img");
|
||||
return image_tag.attr("x-original-src") || image_tag.attr("src") || "";
|
||||
}
|
||||
|
||||
export function close_preview() {
|
||||
preview_overlay.addClass("hidden");
|
||||
}
|
||||
|
||||
loader.register_task(loader.Stage.LOADED, {
|
||||
priority: 0,
|
||||
name: "image preview init",
|
||||
function: async () => {
|
||||
preview_overlay = $("#overlay-image-preview");
|
||||
container_image = preview_overlay.find(".container-image") as any;
|
||||
|
||||
preview_overlay.find("img").on('click', event => event.preventDefault());
|
||||
preview_overlay.on('click', event => {
|
||||
if(event.isDefaultPrevented()) return;
|
||||
close_preview();
|
||||
});
|
||||
|
||||
preview_overlay.find(".button-close").on('click', event => {
|
||||
event.preventDefault();
|
||||
close_preview();
|
||||
});
|
||||
|
||||
preview_overlay.find(".button-download").on('click', event => {
|
||||
event.preventDefault();
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.href = current_url();
|
||||
link.target = "_blank";
|
||||
|
||||
const findex = link.href.lastIndexOf("/") + 1;
|
||||
link.download = link.href.substr(findex);
|
||||
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
});
|
||||
|
||||
button_open_in_browser = preview_overlay.find(".button-open-in-window");
|
||||
button_open_in_browser.on('click', event => {
|
||||
event.preventDefault();
|
||||
|
||||
const win = window.open(current_url(), '_blank');
|
||||
win.focus();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
|
@ -70,14 +70,21 @@ namespace chat {
|
|||
const client_description = this._html_tag.find(".client-description");
|
||||
client_description.text(client ? client.properties.client_description : "").toggle(!!client.properties.client_description);
|
||||
|
||||
const is_local_entry = client instanceof LocalClientEntry;
|
||||
const container_avatar = this._html_tag.find(".container-avatar");
|
||||
container_avatar.find(".avatar").remove();
|
||||
if(client)
|
||||
this.handle.handle.fileManager.avatars.generate_chat_tag({id: client.clientId()}, client.clientUid()).appendTo(container_avatar);
|
||||
else
|
||||
if(client) {
|
||||
const avatar = this.handle.handle.fileManager.avatars.generate_chat_tag({id: client.clientId()}, client.clientUid());
|
||||
if(!is_local_entry) {
|
||||
avatar.css("cursor", "pointer").on('click', event => {
|
||||
image_preview.preview_image_tag(this.handle.handle.fileManager.avatars.generate_chat_tag({id: client.clientId()}, client.clientUid()));
|
||||
});
|
||||
}
|
||||
avatar.appendTo(container_avatar);
|
||||
} else
|
||||
this.handle.handle.fileManager.avatars.generate_chat_tag(undefined, undefined).appendTo(container_avatar);
|
||||
|
||||
container_avatar.toggleClass("editable", client instanceof LocalClientEntry);
|
||||
container_avatar.toggleClass("editable", is_local_entry);
|
||||
}
|
||||
/* updating the info fields */
|
||||
{
|
||||
|
|
|
@ -99,6 +99,13 @@ namespace chat {
|
|||
|
||||
this._html_tag.find(".button-reload-playlist").on('click', () => this.events.fire("action_playlist_reload"));
|
||||
this._html_tag.find(".button-song-add").on('click', () => this.events.fire("action_song_add"));
|
||||
this._html_tag.find(".thumbnail").on('click', event => {
|
||||
const image = this._html_tag.find(".thumbnail img");
|
||||
const url = image.attr("x-thumbnail-url");
|
||||
if(!url) return;
|
||||
|
||||
image_preview.preview_image(decodeURIComponent(url), decodeURIComponent(url));
|
||||
});
|
||||
|
||||
{
|
||||
const button_play = this._html_tag.find(".control-buttons .button-play");
|
||||
|
@ -288,7 +295,11 @@ namespace chat {
|
|||
const container_thumbnail = this._html_tag.find(".player .container-thumbnail");
|
||||
const container_info = this._html_tag.find(".player .container-song-info");
|
||||
|
||||
container_thumbnail.find("img").attr("src", song.song_thumbnail || "img/music/no-thumbnail.png");
|
||||
container_thumbnail.find("img")
|
||||
.attr("src", song.song_thumbnail || "img/music/no-thumbnail.png")
|
||||
.attr("x-thumbnail-url", encodeURIComponent(song.song_thumbnail))
|
||||
.css("cursor", song.song_thumbnail ? "pointer" : null);
|
||||
|
||||
if(song.song_id)
|
||||
container_info.find(".song-name").text(song.song_title || song.song_url).attr("title", song.song_title || song.song_url);
|
||||
else
|
||||
|
@ -522,8 +533,10 @@ namespace chat {
|
|||
name.text(meta.title);
|
||||
description.text(meta.description);
|
||||
length.text(this.format_time(meta.length || 0));
|
||||
if(meta.thumbnail)
|
||||
thumbnail.attr("src", meta.thumbnail);
|
||||
if(meta.thumbnail) {
|
||||
thumbnail.attr("src", meta.thumbnail)
|
||||
.attr("x-thumbnail-url", encodeURIComponent(meta.thumbnail));
|
||||
}
|
||||
} else {
|
||||
name.text(tr("failed to load ") + entry.attr("song-url")).attr("title", tr("failed to load ") + entry.attr("song-url"));
|
||||
description.text(event.error_msg || tr("unknown error")).attr("title", event.error_msg || tr("unknown error"));
|
||||
|
@ -770,6 +783,13 @@ namespace chat {
|
|||
const length = tag.find(".length");
|
||||
|
||||
tag.find(".button-delete").on('click', () => this.events.fire("action_song_delete", { song_id: data.song_id }));
|
||||
tag.find(".container-thumbnail").on('click', event => {
|
||||
const target = tag.find(".container-thumbnail img");
|
||||
const url = target.attr("x-thumbnail-url");
|
||||
if(!url) return;
|
||||
|
||||
image_preview.preview_image(decodeURIComponent(url), decodeURIComponent(url));
|
||||
});
|
||||
tag.on('dblclick', event => this.events.fire("action_song_set", { song_id: data.song_id }));
|
||||
name.text(tr("loading..."));
|
||||
description.text(data.song_url);
|
||||
|
|
|
@ -228,6 +228,7 @@ const loader_javascript = {
|
|||
"js/ui/frames/server_log.js",
|
||||
"js/ui/frames/hostbanner.js",
|
||||
"js/ui/frames/MenuBar.js",
|
||||
"js/ui/frames/image_preview.js",
|
||||
|
||||
//Load audio
|
||||
"js/voice/RecorderBase.js",
|
||||
|
@ -239,6 +240,7 @@ const loader_javascript = {
|
|||
"js/FileManager.js",
|
||||
"js/ConnectionHandler.js",
|
||||
"js/BrowserIPC.js",
|
||||
"js/MessageFormatter.js",
|
||||
|
||||
//Connection
|
||||
"js/connection/CommandHandler.js",
|
||||
|
@ -373,6 +375,7 @@ const loader_style = {
|
|||
"css/static/modal-keyselect.css",
|
||||
"css/static/modal-permissions.css",
|
||||
"css/static/modal-group-assignment.css",
|
||||
"css/static/overlay-image-preview.css",
|
||||
"css/static/music/info_plate.css",
|
||||
"css/static/frame/SelectInfo.css",
|
||||
"css/static/control_bar.css",
|
||||
|
|
Loading…
Reference in New Issue