You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3900 lines
120 KiB
HTML
3900 lines
120 KiB
HTML
<html>
|
|
<head>
|
|
<include src="common.htm" />
|
|
<style src="chat.css" />
|
|
<style src="colorizer.css" />
|
|
<style id="dyncss">
|
|
.input > text::mark(wrongword) {
|
|
text-decoration: underline dotted #AB4145;
|
|
}
|
|
</style>
|
|
<style id="usercss"></style>
|
|
<script src="hint.js" />
|
|
<script src="menus/contactmenu.js" />
|
|
<script>
|
|
const Native = View.share.ChatNative;
|
|
|
|
const icqfilelnk = "https://files.icq.net/get/";
|
|
|
|
// include "ddm.tis";
|
|
|
|
var isActive = false,
|
|
initialLoad = true,
|
|
msgPreview = false,
|
|
lastSmartReplies = {},
|
|
rightClickedTime = "0",
|
|
rightClickedData = null,
|
|
scrollbarActive = false,
|
|
draggingTab = false,
|
|
draggingTarget = null,
|
|
skipAltUp = false,
|
|
skipMBtnUp = false,
|
|
embeddedCnt = 0,
|
|
sMgr = null,
|
|
sKeywords = "",
|
|
settings = {
|
|
showSmiles: true,
|
|
showRelTimes: false,
|
|
showAvatar: true,
|
|
showSmileCaption: false,
|
|
showHintsInChat: true,
|
|
showSmartReplies: false,
|
|
showReactions: true,
|
|
autoCopy: true,
|
|
smoothFontRendering: true,
|
|
fontCodes: false,
|
|
viewTextWrap: true,
|
|
animatedScroll: true,
|
|
quoteSelected: true,
|
|
cursorBelow: true,
|
|
alwaysOnTop: false,
|
|
wheelVelocity: 3,
|
|
imageQuality: 0,
|
|
maxImgWidth: 0,
|
|
maxImgHeight: 0,
|
|
msgBuffer: 20,
|
|
sendOnEnter: 0,
|
|
screenshotFormat: 0,
|
|
screenshotFilename: "snapshot.png",
|
|
spellErrorStyle: 0,
|
|
spellErrorColor: "",
|
|
cachePath: "",
|
|
chatCSS: ""
|
|
},
|
|
|
|
pages = $("#pages"),
|
|
tabs = $("#tabs"),
|
|
backTabs = $("#backTabs"),
|
|
sendBtn = $("#sendBtn"),
|
|
sendBtnImg = $("#sendBtn > div"),
|
|
sendVariant = $("#sendVariant"),
|
|
closeBtn = $("#closeBtn"),
|
|
closeVariant = $("#closeVariant"),
|
|
sendmenu = $("#sendmenu"),
|
|
closemenu = $("#closemenu"),
|
|
filemenu = $("#filemenu"),
|
|
snapmenu = $("#snapmenu"),
|
|
subscreens = $("#subscreens"),
|
|
codemenu = $("#codemenu"),
|
|
monIndicator = $("#monIndicator"),
|
|
plugButtons = $("#plugButtons"),
|
|
histmenu = {},
|
|
chatButtons = {},
|
|
dragFile = $("#dragFile"),
|
|
dragHtml = $("#dragHtml"),
|
|
patches = $("#patches"),
|
|
myreactions = $("#myreactions"),
|
|
linkinfo = $("#linkinfo"),
|
|
overlay, snapLayer, videoview,
|
|
closePreview = (e = null) => {
|
|
overlay.off("wheel");
|
|
overlay.off("^mousedown");
|
|
overlay.on("transitionend", () => {
|
|
if (overlay == null) return;
|
|
overlay.remove();
|
|
overlay = null;
|
|
});
|
|
overlay.classList.remove("active");
|
|
return true;
|
|
},
|
|
updateTabsDepth = () => {
|
|
for (let tab of tabs.children) tab.updateDepth();
|
|
//tabs.update();
|
|
};
|
|
|
|
class ControlledList extends Element {
|
|
|
|
componentDidMount(byCSS) {
|
|
this.state.focusable = true;
|
|
this.initList();
|
|
}
|
|
|
|
initList(focus = false) {
|
|
this.list = this.children[0];
|
|
if (!this.list) return;
|
|
if (this.list.length > 0) {
|
|
var cur = this.list.$(":current");
|
|
if (cur) this.scrollToViewWithSpacing(cur); else {
|
|
this.list.children[0].state.current = true;
|
|
this.scrollTo({
|
|
position: [0, 0],
|
|
behavior: "instant"
|
|
});
|
|
}
|
|
}
|
|
if (focus) this.list.state.focus = true;
|
|
}
|
|
|
|
scrollToViewWithSpacing(el) {
|
|
if ((el.state.box("top", "margin", "parent") == 0 && this.scrollTop == 0) ||
|
|
(el.state.box("bottom", "margin", "parent") == this.list.state.box("height") && this.scrollBottom == 0)) return;
|
|
|
|
el.scrollIntoView({
|
|
behavior: "instant",
|
|
block: "nearest"
|
|
});
|
|
this.flushPaint();
|
|
|
|
var pBottom = this.list.style["padding-bottom"].valueOf();
|
|
var pTop = this.list.style["padding-top"].valueOf();
|
|
var sTop = this.scrollTop;
|
|
|
|
var elBottom = el.state.box("bottom", "border", "parent");
|
|
if (elBottom + pBottom + pTop > sTop + this.state.box("height", "padding")) {
|
|
this.scrollTo({
|
|
position: [0, sTop + pTop],
|
|
behavior: "instant"
|
|
});
|
|
}
|
|
|
|
var elTop = el.state.box("top", "border", "parent");
|
|
if (elTop < sTop)
|
|
this.scrollTo({
|
|
position: [0, elTop],
|
|
behavior: "instant"
|
|
});
|
|
}
|
|
|
|
["on keydown"](e) {
|
|
if (this.list.length == 0) return;
|
|
|
|
var c = this.list.$(":current"), n = null, imgCat = null;
|
|
if (!c) {
|
|
this.list.first.state.current = true;
|
|
return true;
|
|
}
|
|
|
|
var inarow = 7;
|
|
var pgstep = 5;
|
|
if (c.$p("#smilesList")) {
|
|
inarow = settings.showSmileCaption ? 6 : 14;
|
|
} else if (c.$p("#emojiList")) {
|
|
inarow = 8;
|
|
pgstep = 4;
|
|
} else if (c.$p("#stickersList")) {
|
|
inarow = 4;
|
|
pgstep = 2;
|
|
}
|
|
|
|
switch (e.keyCode) {
|
|
case Event.VK_RIGHT:
|
|
n = c.next ? (c.$p("#smilesList") ? c.next.next : c.next) : null;
|
|
if (!n) return true;
|
|
c.state.current = false;
|
|
n.state.current = true;
|
|
this.scrollToViewWithSpacing(n);
|
|
return true;
|
|
break;
|
|
case Event.VK_LEFT:
|
|
n = c.prior ? (c.$p("#smilesList") ? c.prior.prior : c.prior) : null;
|
|
if (!n) return true;
|
|
c.state.current = false;
|
|
n.state.current = true;
|
|
this.scrollToViewWithSpacing(n);
|
|
return true;
|
|
break;
|
|
case Event.VK_DOWN:
|
|
case Event.VK_NEXT:
|
|
n = c.parent.children[c.index + inarow * (e.keyCode == Event.VK_NEXT ? pgstep : 1)];
|
|
let lstrw = c.parent.length % inarow;
|
|
if (!n) n = c.parent.children[c.parent.length - (lstrw == 0 ? inarow : lstrw) + c.index % inarow];
|
|
//if (!n) n = c.parent.children[c.parent.length - c.parent.length % inarow - inarow + c.index % inarow];
|
|
if (!n) n = c.$p("#smilesList") ? this.list.last.prior : this.list.last;
|
|
c.state.current = false;
|
|
n.state.current = true;
|
|
this.scrollToViewWithSpacing(n);
|
|
return true;
|
|
break;
|
|
case Event.VK_UP:
|
|
case Event.VK_PRIOR:
|
|
n = c.parent.children[c.index - inarow * (e.keyCode == Event.VK_PRIOR ? pgstep : 1)];
|
|
if (!n) n = c.parent.children[c.index % inarow];
|
|
c.state.current = false;
|
|
n.state.current = true;
|
|
this.scrollToViewWithSpacing(n);
|
|
return true;
|
|
break;
|
|
case Event.VK_END:
|
|
n = c.$p("#smilesList") ? this.list.last.prior : this.list.last;
|
|
c.state.current = false;
|
|
n.state.current = true;
|
|
this.scrollToViewWithSpacing(n);
|
|
return true;
|
|
break;
|
|
case Event.VK_HOME:
|
|
n = this.list.first;
|
|
c.state.current = false;
|
|
n.state.current = true;
|
|
this.scrollToViewWithSpacing(n);
|
|
return true;
|
|
break;
|
|
case Event.VK_RETURN:
|
|
case Event.VK_SPACE:
|
|
if (this.$p("#stickersList")) {
|
|
var stsearch = $("#stsearch");
|
|
if (stsearch && stsearch.state.focus) break;
|
|
Native.SendStickerToCurrent(c.attr["sticker"]);
|
|
} else {
|
|
getCurrentPage().addToInput(c.attr["text"]);
|
|
}
|
|
this.parent.state.popup = false;
|
|
break;
|
|
case Event.VK_ESCAPE:
|
|
getCurrentPage().input.state.focus = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
class HistScroller extends Element {
|
|
scrollToEx(x, y, animate = true) {
|
|
this.scrollTo({
|
|
position: [x, y],
|
|
behavior: animate ? "smooth" : "instant"
|
|
});
|
|
this.$p(".page").checkScrollPosition();
|
|
}
|
|
}
|
|
|
|
class MakeCurrent extends Element {
|
|
componentDidMount() {
|
|
var el = this.attr["class"] == "caption" ? this.prior : this;
|
|
var e = el.parent.$(":current");
|
|
if (e) e.state.current = false;
|
|
el.state.current = true;
|
|
}
|
|
}
|
|
|
|
class TitleHover extends Element {
|
|
reactBtn;
|
|
|
|
componentDidMount() {
|
|
if (!settings.showReactions || msgPreview) return;
|
|
this.reactBtn = this.$(".msgReactBtn");
|
|
this.reactBtn.setVisible(true);
|
|
this.reactBtn.classList.remove("hidden");
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
if (!settings.showReactions || !this.reactBtn) return;
|
|
myreactions.state.popup = false;
|
|
this.reactBtn.classList.add("hidden");
|
|
}
|
|
}
|
|
|
|
class CatButton extends Element {
|
|
componentDidMount() {
|
|
this.on("click", function(e) {
|
|
for (let btn of this.parent.children)
|
|
btn.state.checked = false;
|
|
this.state.checked = true;
|
|
});
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
this.off("click");
|
|
}
|
|
}
|
|
|
|
class ImageGrid extends Element {
|
|
|
|
onpopupdismissing(e) {
|
|
this.post(() => {
|
|
if (this === smilesGrid) this.detach();
|
|
else if (this === stickersGrid) stickersList.clear();
|
|
});
|
|
}
|
|
|
|
["on keydown"](e) {
|
|
if (e.keyCode == Event.VK_ESCAPE) {
|
|
this.state.popup = false;
|
|
return true;
|
|
} else if (e.keyCode == Event.VK_TAB) return true;
|
|
}
|
|
|
|
["on ^keydown"](e) {
|
|
if (e.keyCode !== Event.VK_TAB) return false;
|
|
|
|
var imgCat = this.$(".imagecat");
|
|
if (!imgCat) return false;
|
|
var c = imgCat.$(":checked");
|
|
if (!c) return false;
|
|
var n = e.shiftKey ? (c.prior ? c.prior : c.parent.last) : (c.next ? c.next : c.parent.first);
|
|
if (!n) return false;
|
|
|
|
for (let btn of imgCat.children)
|
|
btn.state.checked = false;
|
|
this.state.focus = true;
|
|
|
|
if (this === stickersGrid) n.scrollIntoView({
|
|
behavior: "smooth",
|
|
block: "nearest"
|
|
});
|
|
n.dispatchEvent(new Event("click", { bubbles: true }));
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
class VideoPreview extends Element {
|
|
page = null;
|
|
videoinit = false;
|
|
videotimecode = "00s";
|
|
videotitle;
|
|
videocodecs;
|
|
videoformat;
|
|
videolink;
|
|
videokind;
|
|
vollevel = 0.85;
|
|
animCnt = 0;
|
|
animTotal = 30.0;
|
|
|
|
render() {
|
|
return <div id="videoview">
|
|
<div id="videoplayer">
|
|
<div id="playerdisplay">
|
|
<video id="video" />
|
|
<div id="videoload"></div>
|
|
<svg id="play" viewbox="1 1 28 28">
|
|
<path d="M 11,8 21,15 11,22 z"/>
|
|
</svg>
|
|
<svg id="pause" viewbox="1 1 28 28">
|
|
<path d="M 8,8 13,8 13,22 8,22 z M 17,8 22,8 22,22 17,22 z"/>
|
|
</svg>
|
|
</div>
|
|
<div id="playercontrols">
|
|
<div id="controls">
|
|
<input uwp compact type="hslider" id="videopos" />
|
|
<output value="00:00 / 00:00" id="videotime" />
|
|
<input uwp compact type="hslider" id="videovol" value="50" min="1" max="100" />
|
|
<div id="info" titleid="videoinfo">
|
|
<svg viewBox="0 0 12.581 25.837">
|
|
<path d="M5.109,25.456c-3.041,1.068-5.547-0.157-5.044-3.077c0.503-2.92,3.388-9.172,3.799-10.354 c0.412-1.183-0.377-1.506-1.223-1.025c-0.487,0.281-1.212,0.845-1.834,1.393c-0.172-0.348-0.415-0.744-0.598-1.125 c1.015-1.017,2.712-2.381,4.721-2.875c2.4-0.592,6.411,0.354,4.688,4.942c-1.232,3.269-2.103,5.526-2.65,7.206 C6.419,22.222,7.07,22.574,8.03,21.92c0.75-0.512,1.549-1.208,2.135-1.749c0.271,0.441,0.358,0.581,0.626,1.087 C9.774,22.299,7.115,24.738,5.109,25.456z M11.408,5.229c-1.38,1.174-3.424,1.148-4.566-0.057C5.698,3.966,5.889,2.038,7.267,0.864 c1.379-1.174,3.423-1.148,4.566,0.057C12.977,2.126,12.787,4.054,11.408,5.229z"/>
|
|
</svg>
|
|
</div>
|
|
<div id="upload">
|
|
<svg height="1000" width="928.571" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M714.24 821.44q0 -14.508 -10.602 -25.11t-25.11 -10.602 -25.11 10.602 -10.602 25.11 10.602 25.11 25.11 10.602 25.11 -10.602 10.602 -25.11zm142.848 0q0 -14.508 -10.602 -25.11t-25.11 -10.602 -25.11 10.602 -10.602 25.11 10.602 25.11 25.11 10.602 25.11 -10.602 10.602 -25.11zm71.424 -124.992v178.56q0 22.32 -15.624 37.944t-37.944 15.624h-821.376q-22.32 0 -37.944 -15.624t-15.624 -37.944v-178.56q0 -22.32 15.624 -37.944t37.944 -15.624h238.266q11.718 31.248 39.339 51.336t61.659 20.088h142.848q34.038 0 61.659 -20.088t39.339 -51.336h238.266q22.32 0 37.944 15.624t15.624 37.944zm-181.35 -361.584q-9.486 22.32 -32.922 22.32h-142.848v249.984q0 14.508 -10.602 25.11t-25.11 10.602h-142.848q-14.508 0 -25.11 -10.602t-10.602 -25.11v-249.984h-142.848q-23.436 0 -32.922 -22.32 -9.486 -21.762 7.812 -38.502l249.984 -249.984q10.044 -10.602 25.11 -10.602t25.11 10.602l249.984 249.984q17.298 16.74 7.812 38.502z"/>
|
|
</svg>
|
|
</div>
|
|
<div id="close">
|
|
<svg viewBox="0 0 480 480">
|
|
<path d="M310.182,235.995l103.285-103.259c5.006-5.018,5.006-13.237,0-18.251l-54.721-54.733c-5.014-5-13.229-5-18.24,0 l-103.281,103.28L133.944,59.752c-5.018-5-13.229-5-18.246,0l-54.717,54.733c-5.008,5.014-5.008,13.233,0,18.251l103.281,103.259 L60.999,339.263c-5.018,5.014-5.018,13.232,0,18.25l54.717,54.738c5.018,5.001,13.229,5.001,18.242,0l103.268-103.285 l103.264,103.285c5.018,5.001,13.229,5.001,18.24,0l54.721-54.738c5.014-5.018,5.014-13.236,0-18.25L310.182,235.995z"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
initChildren() {
|
|
this.video = $("#video"),
|
|
this.videopos = $("#videopos"),
|
|
this.videotime = $("#videotime"),
|
|
this.videovol = $("#videovol"),
|
|
this.videoload = $("#videoload"),
|
|
this.videoinfo = $("#videoinfo"),
|
|
this.videoplay = $("#play"),
|
|
this.videopause = $("#pause"),
|
|
this.videoupload = $("#upload"),
|
|
this.videoclose = $("#close"),
|
|
this.videocontrols = $("#controls");
|
|
|
|
this.videoplay.on("click", () => this.playpauseSwitch());
|
|
this.videopause.on("click", () => this.playpauseSwitch());
|
|
this.video.on("click", () => this.playpauseSwitch());
|
|
this.videopos.on("click", (e) => this.play(e.target.value));
|
|
this.videovol.on("change", (e) => {
|
|
this.vollevel = this.logslider(e.target.value);
|
|
this.video.video.audioVolume = this.vollevel;
|
|
e.target.timer(1s, () => { Native.SaveVolumeLevel(this.vollevel) });
|
|
});
|
|
this.videotime.on("mousedown", (e) => {
|
|
var lnk;
|
|
if (this.kind == "youtube")
|
|
lnk = this.videolink + (this.videolink.indexOf("?") >= 0 ? "&t=" : "?t=") + this.videotimecode;
|
|
else if (this.kind == "vimeo")
|
|
lnk = this.videolink + "#t=" + this.videotimecode;
|
|
if (e.mainButton) {
|
|
this.closeVideo();
|
|
CommonNative.OpenLink(lnk);
|
|
} else {
|
|
this.page.input.value = lnk;
|
|
this.page.changeInput();
|
|
}
|
|
});
|
|
this.video.on("videostart", (e) => this.alignPlayerControls());
|
|
this.video.on("videoready", (e) => {
|
|
this.videoinit = true;
|
|
var content = this.videotitle + " |
|
if (this.videoformat) content += " |
|
if (this.videocodecs) content += " |
|
this.videoinfo.html = content;
|
|
this.alignPlayerControls();
|
|
});
|
|
|
|
this.videoclose.on("click", () => this.closeVideo());
|
|
this.videoupload.on("click", () => {
|
|
if (!settings.cachePath) return;
|
|
var image = new Graphics.Image(this.video.video.width, this.video.video.height, this.video);
|
|
image.toBytes(settings.screenshotFormatSymbol, 95).save(settings.cachePath + settings.screenshotFilename);
|
|
image = null;
|
|
Native.UploadLastSnapshot();
|
|
});
|
|
|
|
this.videotime.attr["title"] = _("Get link with current time [LMB - open in browser, RMB - insert to chat input]");
|
|
this.videoupload.attr["title"] = _("Make snapshot and upload it to server");
|
|
this.videoclose.attr["title"] = _("Close video preview");
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
this.videoplay.off("click");
|
|
this.videopause.off("click");
|
|
this.videopos.off("click");
|
|
this.videovol.off("change");
|
|
this.videotime.off("mousedown");
|
|
this.videoclose.off("click");
|
|
this.videoupload.off("click");
|
|
this.videoload.off("click");
|
|
this.video.off("click");
|
|
this.video.off("ready");
|
|
this.video.off("start");
|
|
}
|
|
|
|
animFunc(el) {
|
|
var opacityVal = this.easeOutCubic(this.animCnt, 0.6, -0.6, this.animTotal);
|
|
var scaleVal = this.easeOutCubic(this.animCnt, 1.0, 0.8, this.animTotal);
|
|
el.style.set({
|
|
opacity: opacityVal,
|
|
transform: "scale(" + scaleVal + ") translate(-50dip, -50dip)"
|
|
});
|
|
this.animCnt++;
|
|
if (this.animCnt > this.animTotal) {
|
|
el.style.display = "none";
|
|
return false;
|
|
} else {
|
|
el.style.display = "block";
|
|
return true;
|
|
}
|
|
}
|
|
|
|
playpauseSwitch() {
|
|
if (!this.videoinit) return;
|
|
this.videoplay.style.display = "none";
|
|
this.videopause.style.display = "none";
|
|
if (this.video.video.isPlaying) {
|
|
this.videoplay.timer(0, () => this.animFunc(this.videoplay));
|
|
this.animCnt = 0;
|
|
this.videopause.timer(10ms, () => this.animFunc(this.videopause));
|
|
this.video.video.stop();
|
|
} else {
|
|
this.videopause.timer(0, () => this.animFunc(this.videopause));
|
|
this.animCnt = 0;
|
|
this.videoplay.timer(10ms, () => this.animFunc(this.videoplay));
|
|
if (this.video.video.isEnded)
|
|
this.video.video.position = 0;
|
|
this.video.video.play();
|
|
}
|
|
}
|
|
|
|
closeVideo() {
|
|
this.video.video.unload();
|
|
this.videoinit = false;
|
|
this.videocontrols.classList.remove("visible");
|
|
this.classList.remove("visible");
|
|
}
|
|
|
|
show() {
|
|
this.videocontrols.classList.remove("visible");
|
|
this.classList.add("visible");
|
|
}
|
|
|
|
isVisible() {
|
|
return this.classList.contains("visible");
|
|
}
|
|
|
|
load(link) {
|
|
return this.video.video.load(link);
|
|
}
|
|
|
|
play(pos) {
|
|
this.video.video.position = pos;
|
|
this.video.video.play();
|
|
}
|
|
|
|
startPulse() {
|
|
this.video.timer(500ms, () => this.pulse());
|
|
this.pulse();
|
|
}
|
|
|
|
startVideo(url, format, codecs) {
|
|
this.setLoadingText(_("Loading video stream..."));
|
|
this.videoformat = format;
|
|
this.videocodecs = codecs;
|
|
if (this.load(url)) {
|
|
this.volume = Native.GetVolumeLevel();
|
|
this.play(0);
|
|
this.startPulse();
|
|
} else {
|
|
this.error();
|
|
}
|
|
this.setLoadingText(null);
|
|
}
|
|
|
|
setLoadingText(content, showFormats = false) {
|
|
if (content == null)
|
|
this.videoload.clear();
|
|
else
|
|
this.videoload.content(content);
|
|
this.videoload.on("click", "a[close]", () => this.closeVideo());
|
|
if (showFormats) this.videoload.on("click", "div[format]", (e) => {
|
|
this.startVideo(e.target.attr["url"], e.target.attr["format"], e.target.attr["codecs"]);
|
|
});
|
|
}
|
|
|
|
set init(val) {
|
|
this.videoinit = val;
|
|
}
|
|
get init() {
|
|
return this.videoinit;
|
|
}
|
|
|
|
set title(val) {
|
|
this.videotitle = val;
|
|
}
|
|
get title() {
|
|
return this.videotitle;
|
|
}
|
|
|
|
set src(val) {
|
|
this.videolink = val;
|
|
}
|
|
get src() {
|
|
return this.videolink;
|
|
}
|
|
|
|
set kind(val) {
|
|
this.videokind = val;
|
|
}
|
|
get kind() {
|
|
return this.videokind;
|
|
}
|
|
|
|
set volume(val) {
|
|
this.vollevel = val;
|
|
this.videovol.value = this.expslider(val);
|
|
this.video.video.audioVolume = val;
|
|
}
|
|
get volume() {
|
|
return this.vollevel;
|
|
}
|
|
|
|
error() {
|
|
this.videoinit = false;
|
|
this.classList.remove("visible");
|
|
createDialog(
|
|
"hand",
|
|
_("Error"),
|
|
_("Failed to load video")
|
|
);
|
|
}
|
|
|
|
easeOutCubic(currentIteration, startValue, changeInValue, totalIterations) {
|
|
return changeInValue * (Math.pow(currentIteration / totalIterations - 1.0, 3.0) + 1.0) + startValue;
|
|
}
|
|
|
|
logslider(pos) {
|
|
return Math.log10(pos) / 2;
|
|
}
|
|
|
|
expslider(val) {
|
|
return Math.pow(10, val * 2);
|
|
}
|
|
|
|
hms(seconds) {
|
|
var h, m, s;
|
|
s = seconds % 60; seconds /= 60;
|
|
m = seconds % 60; seconds /= 60;
|
|
h = seconds % 60; return [h, m, s];
|
|
}
|
|
|
|
pulse() {
|
|
var m = this.video.video.duration;
|
|
var t = this.video.video.position;
|
|
if (!this.videopos.state.hover) {
|
|
this.videopos.attr["min"] = 0;
|
|
this.videopos.attr["max"] = m;
|
|
this.videopos.value = t;
|
|
}
|
|
var [th, tm, ts] = this.hms(t);
|
|
var [mh, mm, ms] = this.hms(m);
|
|
if (th == 0 && mh == 0) {
|
|
this.videotime.value = printf("%02d:%02d / %02d:%02d", tm, ts, mm, ms);
|
|
this.videotimecode = printf("%02dm%02ds", tm, ts);
|
|
} else {
|
|
this.videotime.value = printf("%d:%02d:%02d / %d:%02d:%02d", th, tm, ts, mh, mm, ms);
|
|
this.videotimecode = printf("%dh%02dm%02ds", th, tm, ts);
|
|
}
|
|
if (this.isVisible())
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
alignPlayerControls() {
|
|
this.timer(100ms, () => {
|
|
var videoWidth = this.video.video.renderingBox[2];
|
|
if (videoWidth == 0) return;
|
|
this.videocontrols.style.width = (videoWidth + 2) + "px";
|
|
this.videocontrols.classList.add("visible");
|
|
this.videocontrols.requestPaint();
|
|
});
|
|
}
|
|
}
|
|
|
|
class SwitchTab extends Element {
|
|
chatid = "";
|
|
plugid = 0;
|
|
pagetype = "im";
|
|
chat = null;
|
|
displayed = "";
|
|
|
|
this(props) {
|
|
this.pagetype = props.pagetype;
|
|
this.displayed = props.displayed;
|
|
this.chatid = props.chatid;
|
|
this.plugid = props.plugid;
|
|
this.chat = props.chat;
|
|
}
|
|
|
|
render() {
|
|
return <div class="tab">
|
|
<div class="tab-icon-group"><img class="tab-icon" src="" /><img class="tab-icon-add" src="" /></div>
|
|
<span class="tab-text">{this.displayed}</span>
|
|
<div class="tab-close"></div>
|
|
</div>;
|
|
}
|
|
|
|
componentDidMount() {
|
|
this.state.focusable = true;
|
|
this.paintForeground = function(gfx) {
|
|
var zindex = this.style["z-index"];
|
|
if (zindex && (parseInt(zindex) || 0) < 10000) return false;
|
|
var [p1, p2, p3, p4] = this.state.box("rect", "border");
|
|
gfx.strokeWidth = 1dip;
|
|
gfx.strokeStyle = Color.RGB(246, 246, 246);
|
|
gfx.translate(0.5, 0.5);
|
|
gfx.beginPath().moveTo(p1 - 0.5 + (this.index == 0 ? 0 : 1), p4).lineTo(p3 - 1.5, p4).stroke();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
this.paintForeground = null;
|
|
}
|
|
|
|
closePage(realize = false) {
|
|
if (this.pagetype == "im") {
|
|
if (realize) if (Native.RealizeEvents(this.chatid)) return true;
|
|
Native.CloseChatPage(this.chatid);
|
|
} else {
|
|
Native.DetachWindow(getPluginPage(this.plugid));
|
|
Native.ClosePluginPage(this.plugid);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
updateDepth() {
|
|
if (this.state.checked)
|
|
this.style["z-index"] = 10000;
|
|
else
|
|
this.style["z-index"] = 9000 + tabs.length - this.index;
|
|
}
|
|
|
|
["on mouseenter"](e) {
|
|
if (settings.showHintsInChat && this.pagetype == "im" && !draggingSnap && !draggingTab) showHint(this, this.chatid);
|
|
}
|
|
["on mouseleave"](e) {
|
|
if (settings.showHintsInChat && this.pagetype == "im") hideHint();
|
|
}
|
|
["on ^mousedown"](e) {
|
|
if (e.mainButton)
|
|
this.startx = e.x + this.style["padding-left"].valueOf();
|
|
hideHint();
|
|
}
|
|
["on mousedown"](e) {
|
|
if (!e.mainButton) return false;
|
|
if (this.state.checked || !this.state.pressed) return false;
|
|
var chked = this.parent.$(":checked");
|
|
if (chked) {
|
|
chked.state.checked = false;
|
|
chked.chat.unsetChat();
|
|
}
|
|
this.state.checked = true;
|
|
updateTabsDepth();
|
|
this.chat.setChat();
|
|
this.chat.scrollTabToView();
|
|
}
|
|
["on ^mouseup"](e) {
|
|
if ((e.buttons & 4) || (e.mainButton && !skipMBtnUp && e.target && e.target.attr["class"] == "tab-close")) {
|
|
skipMBtnUp = false;
|
|
return this.closePage();
|
|
}
|
|
skipMBtnUp = false;
|
|
}
|
|
["on mouseup"](e) {
|
|
if (e.propButton && this.pagetype == "im") {
|
|
openContactMenu(this.chatid);
|
|
return true;
|
|
}
|
|
}
|
|
["on mousedragrequest"](e) {
|
|
draggingTab = true;
|
|
draggingTarget = this.cloneNode();
|
|
draggingTarget.style.visibility = "hidden";
|
|
var pw = tabs.state.box("width", "client");
|
|
var w = this.state.box("width");
|
|
var wb = this.state.box("width", "border") + this.style["margin-left"];
|
|
var h = this.state.box("height");
|
|
var l = this.state.box("left", "margin", tabs);
|
|
var first = this.$is(".tab:first-child");
|
|
var last = this.$is(".tab:last-child");
|
|
this.parent.append(draggingTarget);
|
|
this.swapWith(draggingTarget);
|
|
this.classList.add("capture");
|
|
tabs.state.capture(true);
|
|
this.style.set({
|
|
"left": l,
|
|
"width": w,
|
|
"height": h
|
|
});
|
|
tabs.on("mousemove", (me) => {
|
|
this.post(() => {
|
|
let x = me.x - this.startx;
|
|
let left = Math.min(Math.max(0, x), pw - wb);
|
|
this.style.left = left;
|
|
for (let tab of tabs.children) {
|
|
if (tab.index == draggingTarget.index) continue;
|
|
let tabLeft = tab.state.box("left", "margin", tabs);
|
|
let tabCenter = tabLeft + tab.state.box("width") / 2;
|
|
if ((tabLeft < left && left < tabCenter) ||
|
|
(tabLeft > left && left + w > tabCenter)) {
|
|
draggingTarget.detach();
|
|
tabs.insert(draggingTarget, draggingTarget.index > tab.index ? tab.index : tab.index + 1);
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
});
|
|
view.doEvent("untilMouseUp");
|
|
tabs.state.capture(false);
|
|
tabs.off("mousemove");
|
|
let targetLeft = draggingTarget.state.box("left", "margin", tabs);
|
|
let targetDistance = targetLeft - this.style.left;
|
|
this.morphContent((progress) => {
|
|
this.style.left = targetLeft - targetDistance * (1 - progress);
|
|
if (progress >= 1) {
|
|
this.classList.remove("capture");
|
|
this.attr["style"] = "";
|
|
draggingTarget.swapWith(this);
|
|
draggingTarget.remove();
|
|
draggingTarget = null;
|
|
draggingTab = false;
|
|
let openedTabs = [];
|
|
for (let tab of tabs.children)
|
|
if (tab.pagetype === "im") openedTabs.push(tab.chatid);
|
|
Native.SaveTabsOrder(openedTabs);
|
|
return false;
|
|
}
|
|
return true;
|
|
}, { duration: 200ms, ease: "cubic-out" });
|
|
skipMBtnUp = true;
|
|
}
|
|
["on dblclick"](e) {
|
|
if (e.mainButton) return this.closePage();
|
|
}
|
|
}
|
|
|
|
function insertText(el, text) {
|
|
el.execCommand("edit:insert-text", text);
|
|
}
|
|
|
|
function fixHeart(text) {
|
|
return text.replace(/❤️/g, "❤");
|
|
}
|
|
|
|
class ChatEvent extends Element {
|
|
this(props, kids) {
|
|
var data = props.data;
|
|
this.encrypted = data.encrypted;
|
|
this.eccencrypted = data.eccencrypted;
|
|
this.prefix = data.prefix;
|
|
this.prefixCls = data.prefixCls;
|
|
this.patches = data.patches;
|
|
this.statusImg = data.statusImg;
|
|
this.statusImgExt = data.statusImgExt;
|
|
this.reactions = data.reactions;
|
|
this.myReaction = data.myReaction;
|
|
this.msg = data.msg;
|
|
this.embedded = data.embedded;
|
|
this.cls = data.cls;
|
|
this.time = data.time;
|
|
this.msgid = data.msgid;
|
|
this.eventImg = data.eventImg;
|
|
this.what = data.what;
|
|
this.when = data.when;
|
|
}
|
|
|
|
render() {
|
|
var encrypted = this.encrypted ? <div class="msgCryptImg" auto pic={this.eccencrypted ? "key.ecc" : "key"}></div> : "";
|
|
var prefix = this.prefix !== "" ? [<div class={this.prefixCls} key="prefix">{this.prefix}</div>] : [];
|
|
|
|
var statusImg = [];
|
|
if (this.statusImg != "") statusImg.push(<div class="msgStatusImg" auto pic={this.statusImg}></div>);
|
|
if (this.statusImgExt != "") statusImg.push(<div class="msgStatusExtImg" auto pic={this.statusImgExt}></div>);
|
|
|
|
var reactions = [];
|
|
if (this.reactions)
|
|
for (let reaction of this.reactions) if (reaction[1] > 0) {
|
|
reaction[0] = fixHeart(reaction[0]);
|
|
var reactCls = "msgReaction";
|
|
if (this.myReaction && this.myReaction == reaction[0]) reactCls += " my";
|
|
reactions.push(<div class={reactCls}>{reaction[0] + " "}<span>{reaction[1] > 1 ? reaction[1] : []}</span></div>);
|
|
}
|
|
if (!msgPreview)
|
|
prefix.push(<div class="msgReactBtn hidden" key="react" auto pic="reaction" hint={_("Select your reaction to this message")}></div>);
|
|
|
|
var body = this.msg + this.embedded;
|
|
|
|
return <div class={this.cls} time={this.time} msgid={this.msgid}>
|
|
<div class="msgTitle">
|
|
<div class="msgEventImg" auto pic={this.eventImg}></div>{encrypted}
|
|
<div class="msgWhat">{this.what}</div>
|
|
<div class="msgStatusImages">{statusImg}</div>
|
|
<div class="msgDate">{this.when}</div>{prefix}
|
|
</div>
|
|
<div class="msgBody" state-html={body}></div>
|
|
<div class="msgReactions">{reactions}</div>
|
|
</div>;
|
|
}
|
|
}
|
|
|
|
class Page extends Element {
|
|
chatid = "";
|
|
plugid = 0;
|
|
pagetype = "im";
|
|
displayed = "";
|
|
pendingScroll = ["none", false, null];
|
|
lastInputText = "";
|
|
enterCount = 0;
|
|
quoteIdx = 0;
|
|
selStart = 0;
|
|
topTime = 0;
|
|
msgOffset = 0;
|
|
bottomPos = -1;
|
|
topEvent = null;
|
|
topEventTime;
|
|
firstUnseenEvent = null;
|
|
firstUnreadEventTime = 0;
|
|
misspellings = null;
|
|
historyStartReached = false;
|
|
chatHasScroll = false;
|
|
selInfo = {
|
|
text: "",
|
|
startTime: "0",
|
|
endTime: "0",
|
|
wholeEvents: false
|
|
};
|
|
pageSettings = {
|
|
splitX: 0,
|
|
splitY: 0,
|
|
tiled: "",
|
|
positioned: ""
|
|
};
|
|
|
|
getChatId() {
|
|
return this.chatid;
|
|
}
|
|
|
|
getPlugId() {
|
|
return this.plugid;
|
|
}
|
|
|
|
this(props) {
|
|
this.pagetype = props.pagetype;
|
|
this.displayed = props.displayed;
|
|
if (this.pagetype == "plugin") {
|
|
this.plugid = props.plugid;
|
|
} else {
|
|
this.chatid = props.uid;
|
|
this.focused = props.focused;
|
|
}
|
|
}
|
|
|
|
render() {
|
|
var isPlug = this.pagetype == "plugin";
|
|
var contents = isPlug ? {} :
|
|
<frameset rows="*, 150dip" class="chatFrame" tabindex="-1">
|
|
<div class="chatBackground">
|
|
<div class="chatHist">
|
|
<div class="history hidden">
|
|
<div class="curtime"></div>
|
|
<div class="hr"></div>
|
|
</div>
|
|
<div class="serverHist inactive"></div>
|
|
<div class="chat"></div>
|
|
</div>
|
|
<div class="indicator hidden">{_("Back to latest messages")}</div>
|
|
<div class="loading"></div>
|
|
</div>
|
|
<splitter class="inputSplit" />
|
|
<div class="inputPanel">
|
|
<frameset cols="*, 100dip" class="inputFrame">
|
|
<plaintext class="input renderOption" spellcheck="false"></plaintext>
|
|
<splitter class="avatarSplit"/>
|
|
<div class="avatar hidden"><img /></div>
|
|
</frameset>
|
|
<div class="smartReplies"></div>
|
|
</div>
|
|
</frameset>;
|
|
return <div class="page" plugin={isPlug}>{contents}</div>
|
|
}
|
|
|
|
init() {
|
|
if (this.pagetype == "plugin") {
|
|
this.createTab();
|
|
Native.AttachWindow(this, this.plugid);
|
|
this.setCurrent();
|
|
return;
|
|
}
|
|
|
|
this.chat = this.$(".chat");
|
|
this.history = this.$(".history");
|
|
this.serverHist = this.$(".serverHist");
|
|
this.indicator = this.$(".indicator");
|
|
this.indicator.state.disabled = true;
|
|
this.indicator.setVisible(false);
|
|
this.curtime = this.$(".curtime");
|
|
this.loading = this.$(".loading");
|
|
this.chatHist = this.$(".chatHist");
|
|
this.chatBackground = this.$(".chatBackground");
|
|
this.input = this.$(".input");
|
|
this.inputSplit = this.$(".inputSplit");
|
|
this.inputPanel = this.$(".inputPanel");
|
|
this.avatarSplit = this.$(".avatarSplit");
|
|
this.chatFrame = this.$(".chatFrame");
|
|
this.inputFrame = this.$(".inputFrame");
|
|
this.smartReplies = this.$(".smartReplies");
|
|
this.avatar = this.$(".avatar");
|
|
this.avatarimg = this.avatar.$("> img");
|
|
|
|
if (!msgPreview) this.createTab();
|
|
if (pages.length == 1 || this.focused)
|
|
this.setCurrent();
|
|
else
|
|
updateTabsDepth();
|
|
|
|
this.serverHist.classList.remove("inactive");
|
|
this.history.classList.remove("hidden");
|
|
this.serverHist.attr["title"] = _("New messages are available in server history");
|
|
this.serverHist.on("click", (e) => {
|
|
e.target.classList.remove("visible");
|
|
this.startHistoryLoading();
|
|
});
|
|
|
|
this.indicator.on("click", (e) => {
|
|
this.scrollToFirstUnseen(true);
|
|
}).on("wheel", (e) => {
|
|
this.chatHist.dispatchEvent(e);
|
|
});
|
|
|
|
// this.on("^dblclick", ".codeCopyAll", function(e) { return true; });
|
|
this.on("^mousedown", ".codeCopyAll", (e, el) => this.mousedownCopyAll(e, el))
|
|
.on("^mousedown", ".msgTitle", (e, el) => {
|
|
if (e.mainButton && e.target && e.target.classList.contains("msgReactBtn")) {
|
|
var msg = e.target.$p(".msgFull");
|
|
var msgid = msg.attr["msgid"];
|
|
if (!msgid || (parseFloat(msgid) || 0) < 1000) {
|
|
showAlert("warning", "Cannot complete due to missing unique message ID");
|
|
} else {
|
|
myreactions.attr["msgid"] = msgid;
|
|
myreactions.attr["chatid"] = this.chatid;
|
|
var my = Native.GetMyReaction(msgid);
|
|
for (let option of myreactions.$$("> div")) {
|
|
var del = option.$("> div.delete");
|
|
if (parseInt(option.attr["option"]) == my) {
|
|
option.classList.add("my");
|
|
del.setVisible(true);
|
|
} else {
|
|
option.classList.remove("my");
|
|
del.setVisible(false);
|
|
}
|
|
}
|
|
e.target.popup(myreactions, {
|
|
anchorAt: 8,
|
|
popupAt: 2
|
|
});
|
|
}
|
|
return true;
|
|
} else return this.mousedownTitle(e, el);
|
|
}).on("^mouseup", ".msgBody", (e, el) => {
|
|
if (e.mainButton) {
|
|
this.saveSelection();
|
|
if (el.selection.toString() == "") document.body.state.focus = true;
|
|
}
|
|
});
|
|
|
|
this.chatHist.on("^mousedown", (e) => {
|
|
if (e.mainButton) {
|
|
this.clearSelection(e.ctrlKey || e.shiftKey);
|
|
this.saveSelection();
|
|
};
|
|
}).on("^mouseup", (e) => {
|
|
if (msgPreview) return false;
|
|
|
|
function isUINLink(link) {
|
|
return link && link.toLowerCase().indexOf("uin:") === 0;
|
|
}
|
|
|
|
if (e.propButton || (e.mainButton && e.target.tag == "a" && isUINLink(e.target.attr["href"]))) {
|
|
if (videoview && videoview.isVisible()) return true;
|
|
var isLink = false;
|
|
var isImage = false;
|
|
var isICQImage = false;
|
|
var msg = null;
|
|
if (e.target.attr["class"] == "msgFull") {
|
|
rightClickedTime = e.target.attr["time"];
|
|
rightClickedData = e.target.attr["msgid"];
|
|
msg = e.target;
|
|
} else {
|
|
msg = e.target.$p(".msgFull");
|
|
if (msg) {
|
|
rightClickedTime = msg.attr["time"];
|
|
rightClickedData = msg.attr["msgid"];
|
|
}
|
|
}
|
|
var isValidMsgID = rightClickedData !== null && (parseFloat(rightClickedData) || 0) > 10000;
|
|
if (e.target.tag == "a") {
|
|
isLink = true;
|
|
rightClickedData = e.target.attr["href"];
|
|
} else if (e.target.tag == "img" || e.target.tag == "lottie") {
|
|
isImage = true;
|
|
rightClickedData = e.target.attr["src"];
|
|
isICQImage = rightClickedData.toLowerCase().indexOf(icqfilelnk) >= 0;
|
|
}
|
|
|
|
var isUIN = isLink && isUINLink(rightClickedData);
|
|
var selText = this.selInfo.text;
|
|
var selWhole = this.selInfo.wholeEvents && this.selInfo.startTime != "0" && this.selInfo.endTime != "0";
|
|
var isJustMsg = !isLink && !isImage && !selWhole && rightClickedData !== null && msg !== null;
|
|
histmenu.hm_contactmenu.setVisible(isUIN);
|
|
histmenu.hm_copylink.setVisible(isLink);
|
|
histmenu.hm_copy.setVisible(selText != "");
|
|
histmenu.hm_savepic.setVisible(isImage);
|
|
histmenu.hm_stickerpack.setVisible(isICQImage);
|
|
histmenu.hm_selectall.state.disabled = this.chat.length == 0;
|
|
histmenu.hm_resethist.setVisible(this.chat.children.length > settings.msgBuffer);
|
|
histmenu.hm_viewinwin.state.disabled = selText == "" && rightClickedTime == "0";
|
|
histmenu.hm_saveas.setVisible(selText !== "");
|
|
histmenu.hm_saveashtml.setVisible(selWhole);
|
|
histmenu.hm_addtofav.setVisible(isLink && rightClickedData.indexOf("link:") == 0);
|
|
histmenu.hm_edit.setVisible(isJustMsg && msg.classList.contains("my") && !msg.classList.contains("binary"));
|
|
histmenu.hm_edit.state.disabled = !isValidMsgID;
|
|
histmenu.hm_delete.setVisible(selWhole);
|
|
histmenu.hm_reactions.setVisible(settings.showReactions && isJustMsg);
|
|
histmenu.hm_reactions.state.disabled = !isValidMsgID;
|
|
histmenu.hm_hr_textsel.setVisible(selText != "");
|
|
histmenu.hm_antispam.setVisible(selText != "");
|
|
histmenu.hm_viewinfo.setVisible(rightClickedTime != "0");
|
|
histmenu.hm_imgsmiles.$("> div").style.opacity = settings.showSmiles ? 1.0 : 0.5;
|
|
histmenu.hm_reltimes.$("> div").style.opacity = settings.showRelTimes ? 1.0 : 0.5;
|
|
|
|
var [x, y] = view.box("position", "cursor");
|
|
this.popup(histmenu.self, {
|
|
x: x,
|
|
y: y
|
|
});
|
|
}
|
|
});
|
|
|
|
if (msgPreview) return;
|
|
|
|
this.chatHist.on("scroll", (e) => this.checkScrollPositionThrottled())
|
|
.on("sizechange", () => {
|
|
this.checkScrollPositionThrottled(false);
|
|
this.checkScrollBackground();
|
|
}).on("scrollsliderpress", (e) => {
|
|
scrollbarActive = true;
|
|
}).on("scrollsliderrelease", (e) => {
|
|
scrollbarActive = false;
|
|
if (!this.historyStartReached && this.chatHist.scrollTop <= 0) this.loadHistory();
|
|
});
|
|
|
|
this.avatar.on("mouseup", (e) => {
|
|
if (e.propButton && this.pagetype == "im") {
|
|
openContactMenu(this.chatid);
|
|
return true;
|
|
}
|
|
});
|
|
|
|
this.input.on("mousedragrequest", e => true)
|
|
.on("dragaccept", (e) => { return e.detail?.dataType !== "html" })
|
|
.on("drag", () => false)
|
|
.on("dragenter", function() {
|
|
this.state.disabled = true;
|
|
document.body.isDragActive = true;
|
|
return false;
|
|
}).on("dragleave", function() {
|
|
this.state.disabled = false;
|
|
document.body.hideDrags();
|
|
return false;
|
|
}).on("drop", function() {
|
|
this.state.disabled = false;
|
|
document.body.isDragActive = false;
|
|
return false;
|
|
}).on("change", () => this.changeInput())
|
|
.on("focus", (e) => this.enterCount = 0)
|
|
.on("blur", (e) => this.enterCount = 0)
|
|
.on("keydown", (e) => {
|
|
if (e.keyCode == Event.VK_RETURN) {
|
|
this.enterCount++;
|
|
if (this.enterCount == settings.sendOnEnter) {
|
|
this.input.value = this.input.value.slice(0, -settings.sendOnEnter * 2 + 1);
|
|
this.sendMessage(0);
|
|
this.enterCount = 0;
|
|
return true;
|
|
}
|
|
if (e.ctrlKey) {
|
|
this.sendMessage(0);
|
|
this.enterCount = 0;
|
|
return true;
|
|
}
|
|
return false;
|
|
} else {
|
|
this.enterCount = 0;
|
|
}
|
|
|
|
if (e.altKey) {
|
|
if (e.keyCode == Event.VK_S) this.sendMessage(0);
|
|
return true;
|
|
}
|
|
}).on("^keypress", (e) => {
|
|
if (e.ctrlKey && e.key == " ") {
|
|
var [x, y] = this.input.state.box("position", "caret", "window");
|
|
this.input.popup(codemenu, {
|
|
x: x,
|
|
y: y + 12pt.valueOf()
|
|
});
|
|
return true;
|
|
}
|
|
if (e.altKey) return true;
|
|
}).on("contextmenusetup", (e) => {
|
|
var suggest = e.source.$("#suggest");
|
|
suggest.clear();
|
|
if (!this.misspellings) return;
|
|
var pos = this.input.getCaretPos();
|
|
var word = null, st, len;
|
|
|
|
for (let misspell of this.misspellings) if (misspell)
|
|
if (pos >= misspell[0] && pos <= misspell[0] + misspell[1]) {
|
|
st = misspell[0];
|
|
len = misspell[1];
|
|
word = this.input.value.substr(st, len);
|
|
break;
|
|
}
|
|
|
|
if (word) {
|
|
var suggestions = Native.GetSpellingSuggestions(word);
|
|
if (suggestions && suggestions.length > 0) {
|
|
for (let suggestion of suggestions)
|
|
suggest.append(<li start={st} len={len}><label>{suggestion}</label></li>);
|
|
suggest.append(<hr/>);
|
|
}
|
|
}
|
|
}).on("click", "#suggest", (e) => {
|
|
var st = parseInt(e.target.attr["start"]);
|
|
var len = parseInt(e.target.attr["len"]);
|
|
this.input.saveCaretPos();
|
|
this.input.value = this.input.value.splice(st, len, e.target.$("label").text);
|
|
this.input.restoreCaretPos();
|
|
this.input.state.focus = true;
|
|
this.changeInput();
|
|
});
|
|
this.inputSplit.on("mouseup", () => this.saveAndUpdateSplitter());
|
|
this.avatarSplit.on("mouseup", () => this.saveAndUpdateSplitter());
|
|
|
|
this.smartReplies.on("click", "div", (e, el) => {
|
|
this.input.value = "";
|
|
this.addToInput(el.text);
|
|
});
|
|
|
|
this.updateSmartReplies();
|
|
this.updateAvatar();
|
|
|
|
Native.RequestChatPageSettings(this.chatid);
|
|
|
|
if (this.chat.children.length == 0 && !this.historyStartReached) this.loadHistory();
|
|
}
|
|
|
|
["on ^wheel"](e) {
|
|
if (this.pagetype == "plugin") return false;
|
|
|
|
if (this.isHistoryLoading()) return true;
|
|
if (this.input.state.hover) return false;
|
|
if (videoview && videoview.isVisible()) return true;
|
|
if (e.altKey) {
|
|
this.scrollEvent(e.deltaY < 0 ? -1 : +1);
|
|
skipAltUp = true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
["on ^keyup"](e) {
|
|
if (this.pagetype == "plugin") return false;
|
|
|
|
if (skipAltUp && e.altKey && (e.keyCode == Event.VK_LEFT_ALT || e.keyCode == Event.VK_MENU)) {
|
|
skipAltUp = false;
|
|
return true;
|
|
}
|
|
|
|
if (e.shiftKey && e.target.attr["class"] == "msgBody") this.saveSelection();
|
|
}
|
|
|
|
["on ^keydown"](e) {
|
|
if (this.pagetype == "plugin" || e.shiftKey) return false;
|
|
|
|
if ((videoview && videoview.isVisible()) || (overlay && overlay.state.visible)) return false;
|
|
|
|
// Switch chats on Alt + 1..9
|
|
if (e.altKey && e.keyCode >= 49 && e.keyCode <= 57)
|
|
if (tabs.children[e.keyCode - 49])
|
|
tabs.children[e.keyCode - 49].chat.setCurrent();
|
|
if (this.isHistoryLoading()) return true;
|
|
if ([Event.VK_UP, Event.VK_DOWN, Event.VK_PRIOR, Event.VK_NEXT].includes(e.keyCode))
|
|
if (e.ctrlKey)
|
|
switch (e.keyCode) {
|
|
case Event.VK_UP: this.scrollLine(-1); return true;
|
|
case Event.VK_DOWN: this.scrollLine(+1); return true;
|
|
case Event.VK_PRIOR: this.scrollLine(-10); return true;
|
|
case Event.VK_NEXT: this.scrollLine(+10); return true;
|
|
} else if (e.altKey)
|
|
switch (e.keyCode) {
|
|
case Event.VK_UP: this.scrollEvent(-1); return true;
|
|
case Event.VK_DOWN: this.scrollEvent(+1); return true;
|
|
case Event.VK_PRIOR: this.scrollEvent(-5); return true;
|
|
case Event.VK_NEXT: this.scrollEvent(+5); return true;
|
|
}
|
|
|
|
if (e.altKey && [Event.VK_HOME, Event.VK_END].includes(e.keyCode))
|
|
switch (e.keyCode) {
|
|
case Event.VK_HOME: this.scrollToTop(true); return true;
|
|
case Event.VK_END: this.scrollToBottom(true); return true;
|
|
}
|
|
|
|
// Override default copy when selecting in chat
|
|
if (!this.input.state.focus && e.ctrlKey && e.keyCode == Event.VK_C) {
|
|
this.copySelected()
|
|
return true;
|
|
}
|
|
}
|
|
|
|
updateSettings(sets) {
|
|
if (this.pagetype == "plugin") return false;
|
|
|
|
this.pageSettings = sets;
|
|
|
|
reloadImage(this.pageSettings.tiled);
|
|
if (this.pageSettings.tiled) this.chatBackground.style.set({
|
|
"background-image": "url(" + this.pageSettings.tiled + ")",
|
|
"background-repeat": "repeat",
|
|
"background-position": "center center"
|
|
}); else this.chatBackground.style.set({
|
|
"background-image": "none",
|
|
"background-repeat": "no-repeat"
|
|
});
|
|
|
|
reloadImage(this.pageSettings.positioned);
|
|
this.checkScrollBackground(true);
|
|
|
|
if (this.pageSettings.splitX > 0) {
|
|
//this.inputFrame.frameset.state = [1fx, Length.px(this.pageSettings.splitX)];
|
|
this.inputFrame.attr["cols"] = "*, " + this.pageSettings.splitX + "px";
|
|
this.avatar.style.width = this.pageSettings.splitX + "px";
|
|
}
|
|
if (this.pageSettings.splitY > 0) {
|
|
this.chatFrame.attr["rows"] = "*, " + this.pageSettings.splitY + "px";
|
|
this.inputPanel.style.height = this.pageSettings.splitY + "px";
|
|
}
|
|
|
|
this.updateSplitterWidth();
|
|
}
|
|
|
|
saveAndUpdateSplitter(e) {
|
|
if (settings.showAvatar) Native.StoreSplit(0, this.avatar.state.box("width"));
|
|
Native.StoreSplit(1, this.inputPanel.state.box("height"));
|
|
this.updateSplitterWidth();
|
|
}
|
|
|
|
checkScrollPosition(loadHist = true) {
|
|
if (msgPreview || !this.parent) return;
|
|
var atBottom = this.isAtBottom();
|
|
if (atBottom) this.firstUnseenEvent = null;
|
|
if (this.indicator.state.disabled !== atBottom) {
|
|
if (atBottom) {
|
|
this.indicator.on("transitionend", (e) => {
|
|
if (this.indicator.style.opacity > 0) return;
|
|
this.indicator.off("transitionend");
|
|
this.indicator.setVisible(false);
|
|
});
|
|
this.indicator.classList.add("hidden");
|
|
} else {
|
|
this.indicator.off("transitionend");
|
|
this.indicator.setVisible(true);
|
|
this.indicator.classList.remove("hidden");
|
|
}
|
|
this.indicator.state.disabled = atBottom;
|
|
}
|
|
|
|
if (loadHist && !this.historyStartReached && !scrollbarActive && this.chatHist.scrollTop <= 0) this.loadHistory();
|
|
}
|
|
checkScrollPositionThrottled = this.checkScrollPosition.throttle(200ms, true);
|
|
|
|
// Update background tile
|
|
checkScrollBackground(force = false) {
|
|
if (msgPreview || this.pagetype == "plugin") return;
|
|
|
|
if (!force) {
|
|
var hasScroll = this.chatHist.state.box("height", "content", "window") > this.chatHist.state.box("height");
|
|
if (hasScroll == this.chatHasScroll) return;
|
|
this.chatHasScroll = hasScroll;
|
|
}
|
|
|
|
if (!this.pageSettings.positioned || this.pageSettings.positioned == "") {
|
|
this.chatHist.style["background-image"] = "none";
|
|
return;
|
|
};
|
|
|
|
var bpos = "right bottom";
|
|
switch (this.pageSettings.positioned.charAt(this.pageSettings.positioned.length - 1)) {
|
|
case "1": bpos = "left top"; break;
|
|
case "2": bpos = (this.chatHasScroll ? "right system-scrollbar-width" : "right") + " top"; break;
|
|
case "3": bpos = "left bottom"; break;
|
|
case "4": bpos = (this.chatHasScroll ? "right system-scrollbar-width" : "right") + " bottom"; break;
|
|
}
|
|
this.chatHist.style.set({
|
|
"background-image": "url(" + this.pageSettings.positioned + ")",
|
|
"background-position": bpos
|
|
});
|
|
}
|
|
|
|
isAtBottom() {
|
|
return this.chatHist.scrollBottom <= (this.chat.last ? (this.chat.last.$(".msgBody").state?.box("height", "margin") || 0) : 0);
|
|
}
|
|
|
|
isInView(el) {
|
|
var [elLeft, elTop, elRight, elBottom] = el.state.box("rect", "border", this.chatHist);
|
|
var chatHeight = this.chatHist.state.box("height", "client");
|
|
if (elTop >= 0 && elBottom <= chatHeight)
|
|
return 1;
|
|
else if (!(elBottom <= 0 || elTop >= chatHeight))
|
|
return 2;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
getImages(kind, prepend = false, empty = false) {
|
|
this.bottomPos = prepend && !empty || kind === "embedded" ? -1 : (empty ? 0 : this.chatHist.scrollBottom);
|
|
//console.log("getImages scrollBottom:", this.chatHist.scrollBottom, this.bottomPos);
|
|
var imgs = this.chat.$$("img[data-action], lottie[data-action]");
|
|
for (let img of imgs) if (img)
|
|
if (kind == "both" ||
|
|
(kind == "embedded" && img.classList.contains("embeddedImg")) ||
|
|
(kind == "linked" && img.classList.contains("linkedImg"))) getImg(img);
|
|
}
|
|
|
|
mousedownCopyAll(e, el) {
|
|
if (!e.mainButton) return false;
|
|
this.clearSelection(e.ctrlKey || e.shiftKey);
|
|
var code = el.parent.$("source-code");
|
|
var first = code.first?.firstChild;
|
|
var last = code.last?.lastChild;
|
|
if (first && last)
|
|
code.selection.setBaseAndExtent(first, 0, last, last.length);
|
|
else
|
|
code.execCommand("edit:selectall");
|
|
return true;
|
|
}
|
|
|
|
mousedownTitle(e, el) {
|
|
if (!e.mainButton) return false;
|
|
var chatEvent = e.target.$p(".msgFull");
|
|
if (e.target.classList.contains("msgMulti") && e.target.classList.contains("updated") && chatEvent.patches) {
|
|
patches.clear();
|
|
var delSelfStr = _("Message was removed from server (for self)");
|
|
var delAllStr = _("Message was removed from server (for all)");
|
|
var noDataStr = _("No data");
|
|
for (let patch of chatEvent.patches) {
|
|
var removedSelf = patch[0] == "delete"
|
|
var removedAll = patch[0] == "modify";
|
|
var removed = removedSelf || removedAll;
|
|
var empty = patch[1] === "";
|
|
var text = "";
|
|
if (removedSelf)
|
|
text = delSelfStr;
|
|
else if (removedAll)
|
|
text = delAllStr;
|
|
else if (empty)
|
|
text = noDataStr;
|
|
else {
|
|
text = fixHeart(patch[1]);
|
|
if (text.length > 300) text = text.substr(0, 300) + "…";
|
|
}
|
|
patches.append(<div action={patch[0]} class={removed || empty ? "grayedout" : ""}><div pic={removed ? "delete" : "refresh"}></div><div class="patchText">{text}</div></div>);
|
|
}
|
|
var pics = patches.$$("div[pic]");
|
|
for (let pic of pics) pic.setupIcon();
|
|
e.target.popup(patches, {
|
|
anchorAt: 7,
|
|
popupAt: 1
|
|
});
|
|
if (patches.last) patches.last.scrollIntoView({
|
|
block : "nearest",
|
|
behavior: "instant"
|
|
});
|
|
return true;
|
|
}
|
|
|
|
if ((!e.shiftKey && !e.ctrlKey) || this.selStart == 0) {
|
|
this.clearSelection();
|
|
this.selStart = parseFloat(el.parent.attr["time"]) || 0;
|
|
selectEvent(el.parent);
|
|
} else {
|
|
var selEnd = parseFloat(el.parent.attr["time"]) || 0;
|
|
if (e.ctrlKey) {
|
|
if (isEventSelected(el.parent))
|
|
deselectEvent(el.parent);
|
|
else
|
|
selectEvent(el.parent);
|
|
} else if (e.shiftKey)
|
|
for (let msg of this.chat.children) {
|
|
var msgTime = parseFloat(msg.attr["time"]) || 0;
|
|
if ((this.selStart <= selEnd && msgTime >= this.selStart && msgTime <= selEnd) ||
|
|
(this.selStart > selEnd && msgTime <= this.selStart && msgTime >= selEnd))
|
|
selectEvent(msg);
|
|
else
|
|
deselectEvent(msg);
|
|
}
|
|
}
|
|
this.saveSelection();
|
|
return true;
|
|
}
|
|
|
|
updateSpelling(errors) {
|
|
if (this.pagetype == "plugin") return;
|
|
this.misspellings = errors ? deepCopy(errors) : null;
|
|
var node = this.input.first;
|
|
if (node) {
|
|
const range = new Range();
|
|
range.setStart(node, 0);
|
|
range.setEnd(this.input.last, this.input.last?.textContent.length ?? 0);
|
|
range.clearMark("wrongword");
|
|
}
|
|
if (!node || !errors || errors.length == 0) return;
|
|
var cnt = 0;
|
|
do {
|
|
if (errors[cnt]) {
|
|
do {
|
|
if (errors[cnt][0] > node.textContent.length) {
|
|
for (var i = cnt; i < errors.length; i++)
|
|
if (errors[i]) errors[i][0] -= node.textContent.length + 2;
|
|
node = node.next;
|
|
} else break;
|
|
} while (node);
|
|
if (node) {
|
|
var tNode = node.firstChild;
|
|
const range = new Range();
|
|
range.setStart(tNode, errors[cnt][0]);
|
|
range.setEnd(tNode, errors[cnt][0] + errors[cnt][1]);
|
|
range.applyMark("wrongword");
|
|
}
|
|
}
|
|
cnt++;
|
|
} while (cnt < errors.length);
|
|
}
|
|
|
|
sendMessage(opt = 0) {
|
|
let msg = this.input.value;
|
|
if (opt > 0 && msg.trim() === "") {
|
|
this.input.state.focus = true;
|
|
showAlert("warning", "Can't send an empty message");
|
|
return;
|
|
}
|
|
var UINs = [];
|
|
if (opt == 1) {
|
|
UINs = openUINList("Send multiple", "Send message", ["sco_multi", "sco_groups", "sco_predefined"], this.chatid);
|
|
if (!UINs || UINs.length == 0) {
|
|
if (!findWindow("uinlist"))
|
|
this.input.state.focus = true;
|
|
return;
|
|
}
|
|
}
|
|
this.input.state.focus = true;
|
|
var sent = Native.SendChatMessage(opt, this.chatid, msg, UINs);
|
|
if (!chatButtons.singleBtn.state.checked && this.input.parent) {
|
|
if (sent === true) {
|
|
this.input.value = "";
|
|
this.input.state.focus = true;
|
|
this.input.execCommand("navigate:start"); // focus bug fix
|
|
}
|
|
this.changeInput();
|
|
}
|
|
}
|
|
|
|
updateCharCount() {
|
|
$("#sbChars").text = this.input.value.length > 0 ? _("Chars") + ": " + this.input.value.length : "";
|
|
}
|
|
|
|
changeInput() {
|
|
this.quoteIdx = 0;
|
|
this.updateCharCount();
|
|
Native.InputChangedFor(this.chatid, this.input.value);
|
|
}
|
|
|
|
addToInput(text) {
|
|
if (text === "") return;
|
|
insertText(this.input, text);
|
|
this.input.state.focus = true;
|
|
this.changeInput();
|
|
}
|
|
|
|
createTab() {
|
|
this.tab = Element.create(<SwitchTab pagetype={this.pagetype} chatid={this.chatid} plugid={this.plugid} chat={this} displayed={this.displayed} />);
|
|
tabs.append(this.tab);
|
|
}
|
|
|
|
setChat() {
|
|
for (let page of pages.children)
|
|
page.state.current = this.pagetype == "plugin" ? page.plugid == this.plugid : page.chatid == this.chatid;
|
|
|
|
if (msgPreview) return;
|
|
|
|
setupChatButtons(this.pagetype == "plugin");
|
|
|
|
if (this.pagetype == "plugin") {
|
|
Native.PluginPageSelected(this.plugid);
|
|
this.state.focus = true;
|
|
} else {
|
|
this.updateCharCount();
|
|
this.updateSplitterWidth();
|
|
Native.ChatPageSelected(this.chatid, this.input.value);
|
|
this.checkScrollBackground(true);
|
|
if (this.pendingScroll[0] == "tobottom")
|
|
this.scrollToBottom(this.pendingScroll[1]);
|
|
else if (this.pendingScroll[0] == "tounseen")
|
|
this.scrollToFirstUnseen(this.pendingScroll[1]);
|
|
else if (this.pendingScroll[0] == "movetotime")
|
|
this.moveToTime(this.pendingScroll[2], this.pendingScroll[1]);
|
|
this.pendingScroll = ["none", false];
|
|
this.post(() => this.input.state.focus = true);
|
|
//this.flushPaint();
|
|
}
|
|
}
|
|
|
|
unsetChat() {
|
|
if (msgPreview) return;
|
|
if (this.pagetype == "plugin")
|
|
Native.PluginPageDeselected(this.plugid);
|
|
else
|
|
Native.ChatPageDeselected(this.chatid);
|
|
}
|
|
|
|
setCurrent() {
|
|
if (msgPreview) {
|
|
this.setChat();
|
|
return;
|
|
}
|
|
|
|
var isPlug = this.pagetype == "plugin";
|
|
var chked = tabs.$(":checked");
|
|
if (chked) {
|
|
if (isPlug ? chked.plugid == this.plugid : chked.chatid == this.chatid) return;
|
|
chked.state.checked = false;
|
|
chked.chat.unsetChat();
|
|
}
|
|
for (let tab of tabs.children)
|
|
if (isPlug ? tab.plugid == this.plugid : tab.chatid == this.chatid) {
|
|
tab.state.checked = true;
|
|
updateTabsDepth();
|
|
this.scrollTabToView();
|
|
this.setChat();
|
|
break;
|
|
}
|
|
}
|
|
|
|
scrollTabToView() {
|
|
this.tab.scrollIntoView({ behavior: "smooth" });
|
|
}
|
|
|
|
redrawTab(caption, hash, hashadd = 0) {
|
|
if (!this.tab) return;
|
|
|
|
var icon = this.tab.$(".tab-icon");
|
|
var iconGroup = this.tab.$(".tab-icon-group");
|
|
if (hash && hash !== 0) {
|
|
icon.attr["src"] = "tabicon:" + printf("%u", hash);
|
|
iconGroup.classList.remove("hidden");
|
|
} else {
|
|
iconGroup.classList.add("hidden");
|
|
}
|
|
|
|
var iconadd = this.tab.$(".tab-icon-add");
|
|
if (hashadd && hashadd !== 0) {
|
|
iconadd.attr["src"] = "tabicon:" + printf("%u", hashadd);
|
|
iconadd.classList.remove("hidden");
|
|
} else {
|
|
iconadd.classList.add("hidden");
|
|
}
|
|
|
|
this.tab.$(".tab-text").text = caption;
|
|
}
|
|
|
|
closeTab() {
|
|
hideHint();
|
|
if (tabs.length == 1 || this.tab.index !== tabs.length - 1) {
|
|
// remove single and any middle tabs without animation
|
|
this.tab.remove();
|
|
return;
|
|
}
|
|
this.tab.state.focusable = false;
|
|
this.tab.state.disabled = true;
|
|
this.tab.state.checked = false;
|
|
this.tab.flushPaint();
|
|
var [x, y] = this.tab.state.box("position", "border", "parent");
|
|
var pl = backTabs.style["padding-left"].valueOf();
|
|
var pt = backTabs.style["padding-top"].valueOf();
|
|
var [w, h] = this.tab.state.box("dimension", "inner");
|
|
x += pl; y += pt;
|
|
this.tab.detach();
|
|
backTabs.append(this.tab);
|
|
this.tab.style.set({
|
|
"position": "absolute",
|
|
"margin": 0,
|
|
"left": x + "px",
|
|
"top": y + "px",
|
|
"width": w + "px",
|
|
"height": h + "px"
|
|
});
|
|
this.tab.morphContent((progress) => {
|
|
this.tab.style.set({
|
|
"opacity": 1.0 - progress,
|
|
//"width": w - 20 * progress + "px"
|
|
"top": (y + 10 * progress) + "px"
|
|
});
|
|
if (progress >= 1) {
|
|
this.tab.remove();
|
|
return false;
|
|
}
|
|
return true;
|
|
}, { duration: 300ms, ease: "cubic-out" });
|
|
}
|
|
|
|
close() {
|
|
if (msgPreview) {
|
|
this.remove();
|
|
return;
|
|
}
|
|
if (this.pagetype == "im" && videoview) {
|
|
if (videoview.init) videoview.closeVideo();
|
|
videoview.detach();
|
|
}
|
|
var i = this.tab.index;
|
|
this.closeTab();
|
|
this.remove();
|
|
if (i >= tabs.length) i = tabs.length - 1;
|
|
if (i < 0) {
|
|
closeForm();
|
|
} else {
|
|
var chked = tabs.$(":checked");
|
|
if (chked) {
|
|
chked.state.checked = false;
|
|
chked.chat.unsetChat();
|
|
}
|
|
for (let tab of tabs.children)
|
|
if (tabs.length == 1 || tab.index == i) {
|
|
tab.state.checked = true;
|
|
updateTabsDepth();
|
|
tab.chat.setChat();
|
|
tab.chat.scrollTabToView();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
initTopTime() {
|
|
if (this.chat.first)
|
|
this.topTime = parseFloat(this.chat.first.attr["time"]) || 0;
|
|
if (this.topTime == 0) {
|
|
var now = new Date();
|
|
this.topTime = unix2delphi(now.valueOf() + now.getTimezoneOffset());
|
|
}
|
|
}
|
|
|
|
updateCurTime() {
|
|
var date = new Date(delphi2unix(this.topTime));
|
|
var weekDay = date.getDay();
|
|
weekDay = weekDay === 0 ? 6 : weekDay - 1;
|
|
this.curtime.html = CommonNative.GetWeekDayByNumber(weekDay) + ", " + date.getDate() + " " + CommonNative.GetMonthNameByNumber(date.getMonth()) + " " + date.getFullYear();
|
|
this.curtime.style.display = "block";
|
|
}
|
|
|
|
updateSmiles() {
|
|
if (settings.showSmiles) {
|
|
pages.classList.remove("smileAsText");
|
|
pages.classList.add("smileAsImg");
|
|
} else {
|
|
pages.classList.remove("smileAsImg");
|
|
pages.classList.add("smileAsText");
|
|
}
|
|
}
|
|
|
|
showServerHistoryNotif() {
|
|
this.serverHist.classList.add("visible");
|
|
}
|
|
|
|
startHistoryLoading() {
|
|
this.loading.classList.add("visible");
|
|
this.curtime.html = _("Loading history...");
|
|
this.curtime.style.display = "block";
|
|
}
|
|
|
|
endHistoryLoading() {
|
|
this.loading.classList.remove("visible");
|
|
}
|
|
|
|
isHistoryLoading() {
|
|
return this.loading.classList.contains("visible");
|
|
}
|
|
|
|
hideHistory() {
|
|
this.historyStartReached = true;
|
|
this.endHistoryLoading();
|
|
this.history.classList.add("removed");
|
|
}
|
|
|
|
loadHistory() {
|
|
this.startHistoryLoading();
|
|
var localMsgOffset = this.msgOffset;
|
|
this.msgOffset += settings.msgBuffer;
|
|
this.post(() => Native.LoadHistory(this.chatid, localMsgOffset, settings.msgBuffer));
|
|
}
|
|
|
|
resetHistory() {
|
|
this.clearEvents();
|
|
this.loadHistory();
|
|
}
|
|
|
|
rememberTopEvent() {
|
|
if (!this.chat) return;
|
|
this.topEvent = this.chat.first;
|
|
if (this.topEvent) this.topEventTime = this.topEvent.attr["time"];
|
|
}
|
|
/*
|
|
restoreTopEvent() {
|
|
var msg;
|
|
if (this.topEventTime)
|
|
msg = this.chat.$(".msgFull[time=\"{this.topEventTime}\"]");
|
|
else
|
|
msg = this.chat.last;
|
|
if (msg)
|
|
this.scrollEventToView(msg, true, false);
|
|
}
|
|
*/
|
|
addEvents(events, prepend) {
|
|
var preScrollBottom = this.isAtBottom();
|
|
var preEmpty = this.chat.children.length == 0;
|
|
if (prepend) this.rememberTopEvent();
|
|
if (events && events.length > 0) {
|
|
var data = [];
|
|
for (let event of events) {
|
|
//if (event.msgid !== "" && event.msgid !== "0" && this.chat.$("> .msgFull[msgid=\"" + event.msgid + "\"]")) continue;
|
|
data.push(<ChatEvent data={event} />);
|
|
if (!prepend && event.writeHist) this.msgOffset++;
|
|
}
|
|
|
|
if (prepend)
|
|
this.chat.prepend(data);
|
|
else
|
|
this.chat.append(data);
|
|
}
|
|
|
|
//var hiddenMsgs = this.chat.$$(> .msgFull.hidden);
|
|
//for (let hiddenMsg of hiddenMsgs)
|
|
//hiddenMsg.classList.remove("hidden");
|
|
|
|
if (msgPreview) hideSearchHere();
|
|
|
|
this.endHistoryLoading();
|
|
this.initTopTime();
|
|
this.updateCurTime();
|
|
this.updateSmiles();
|
|
this.getImages("embedded");
|
|
this.checkScrollBackground();
|
|
|
|
if (prepend) {
|
|
if (this.topEvent) {
|
|
var prevMsg = this.topEvent.prior;
|
|
var ht = this.chatHist.scrollTop - 2dip.valueOf();
|
|
while (prevMsg) {
|
|
ht += prevMsg.state.box("height", "margin");
|
|
prevMsg = prevMsg.prior;
|
|
}
|
|
this.chatHist.scrollToEx(0, ht, false);
|
|
} else this.scrollToBottom(false);
|
|
} else {
|
|
var noFirstUnseen = this.firstUnseenEvent == null;
|
|
var evcnt = events ? events.length : 0;
|
|
var evlast = this.chat.last;
|
|
if (evcnt > 0) {
|
|
delete lastSmartReplies[this.chatid];
|
|
this.smartReplies.clear();
|
|
}
|
|
while (evlast && evcnt > 0) {
|
|
if (noFirstUnseen && this.isInView(evlast) > 0) this.firstUnseenEvent = evlast;
|
|
evlast = evlast.prior;
|
|
evcnt--;
|
|
}
|
|
if (evlast && evlast.next)
|
|
if (evlast.next.classList.contains("my"))
|
|
this.firstUnreadEventTime = 0;
|
|
else if ((!isActive || getCurrentPage() !== this) && this.firstUnreadEventTime === 0)
|
|
this.firstUnreadEventTime = evlast.next.attr["time"];
|
|
|
|
if (!msgPreview && preScrollBottom && this.chat.children.length > 0)
|
|
this.scrollToBottom(false);
|
|
else
|
|
this.checkScrollPosition(false);
|
|
}
|
|
this.getImages("linked", prepend, preEmpty);
|
|
this.redrawFirstUnreadEvent();
|
|
}
|
|
|
|
updateEvents(events) {
|
|
if (!events || events.length == 0) return;
|
|
for (let event of events)
|
|
if (event.msgid !== "" && event.msgid !== "0") {
|
|
var msg = this.chat.$("> .msgFull[msgid=\"" + event.msgid + "\"]");
|
|
if (msg) {
|
|
msg.patch(<ChatEvent data={event} />);
|
|
if (msg == this.chat.last && this.isAtBottom()) this.scrollToBottom(true);
|
|
}
|
|
}
|
|
this.getImages("both");
|
|
}
|
|
|
|
redrawFirstUnreadEvent() {
|
|
var currentLast = this.chat.$("> .msgFull.lastevent");
|
|
if (currentLast) currentLast.classList.remove("lastevent");
|
|
if (this.firstUnreadEventTime !== 0 && this.firstUnreadEventTime !== "") {
|
|
var msg = this.chat.$("> .msgFull[time=\"" + this.firstUnreadEventTime + "\"]");
|
|
if (msg) msg.classList.add("lastevent");
|
|
}
|
|
}
|
|
|
|
setFirstUnreadEvent(time) {
|
|
if (this.firstUnreadEventTime !== 0) return;
|
|
this.firstUnreadEventTime = time;
|
|
this.redrawFirstUnreadEvent();
|
|
}
|
|
|
|
clearEvents() {
|
|
if (videoview && videoview.isVisible()) videoview.closeVideo();
|
|
this.chat.clear();
|
|
this.msgOffset = 0;
|
|
}
|
|
|
|
scrollEvent(cnt) {
|
|
for (let msg of this.chat.children) {
|
|
var vis = this.isInView(msg);
|
|
if (vis > 0) {
|
|
if (vis == 2) {
|
|
if (this.chatHist.state.animating) {
|
|
if (cnt < 0) cnt--; else if (cnt > 0) cnt++;
|
|
} else if (cnt < 0) cnt++;
|
|
}
|
|
if (cnt > 0) {
|
|
for (var i = 1; i <= cnt; i++)
|
|
if (msg.next) msg = msg.next;
|
|
} else if (cnt < 0) {
|
|
for (var i = cnt; i <= -1; i++)
|
|
if (msg.prior) msg = msg.prior;
|
|
}
|
|
if (msg) this.scrollEventToView(msg, true, settings.animatedScroll);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
scrollLine(cnt) {
|
|
// 1 step = 10% of chat height
|
|
this.chatHist.scrollToEx(0, parseInt(this.chatHist.scrollTop + cnt * this.chatHist.state.box("height") / 10), settings.animatedScroll);
|
|
}
|
|
|
|
// scrollWheel(direction) {
|
|
// // 1 step = 10% of chat height
|
|
// if (direction < 0) direction = -1;
|
|
// if (direction > 0) direction = 1;
|
|
// //if ((this.chatHist.scroll(#top) <= 0 && direction < 0) || (this.chatHist.scroll(#bottom) <= 0 && direction > 0)) return;
|
|
// var y = this.chatHist.scrollTop + direction * settings.wheelVelocity * this.chatHist.state.box("height") / (msgPreview ? 5 : 10);
|
|
// this.chatHist.scrollToEx(0, y, settings.animatedScroll);
|
|
// }
|
|
|
|
scrollEventToView(el, toTop, animate) {
|
|
var topShift = this.history.state.visible ? this.history.state.box("height", "margin") : 0;
|
|
var scrollPosY = topShift + (toTop ? el.state.box("top", "margin", "parent") : el.state.box("bottom", "margin", "parent") - this.chatHist.clientHeight);
|
|
scrollPosY = Math.min(Math.max(0, scrollPosY), this.chatHist.scrollHeight - this.chatHist.clientHeight);
|
|
this.chatHist.scrollToEx(0, scrollPosY, animate);
|
|
}
|
|
|
|
scrollToTop(animate) {
|
|
this.chatHist.scrollToEx(0, 0, animate && settings.animatedScroll);
|
|
}
|
|
|
|
scrollToBottom(animate) {
|
|
if (getCurrentPage() !== this) {
|
|
if (this.pendingScroll[0] == "movetotime") return;
|
|
this.pendingScroll = ["tobottom", animate];
|
|
return;
|
|
}
|
|
|
|
if (this.chat.length > 0 && this.chat.last)
|
|
this.scrollEventToView(this.chat.last, false, animate && settings.animatedScroll);
|
|
}
|
|
|
|
scrollToFirstUnseen(animate) {
|
|
if (getCurrentPage() !== this) {
|
|
this.pendingScroll = ["tounseen", animate];
|
|
return;
|
|
}
|
|
|
|
if (this.firstUnseenEvent == null)
|
|
this.scrollToBottom(animate);
|
|
else
|
|
this.scrollEventToView(this.firstUnseenEvent, true, animate && settings.animatedScroll)
|
|
this.firstUnseenEvent = null;
|
|
}
|
|
|
|
getLastEventTime() {
|
|
return this.chat.last ? this.chat.last.attr["time"] : 0;
|
|
}
|
|
|
|
moveToTime(time, fast) {
|
|
if (getCurrentPage() !== this) {
|
|
this.pendingScroll = ["movetotime", fast, time];
|
|
return;
|
|
}
|
|
|
|
if (this.chat.children.length > 0)
|
|
for (let msg of this.chat.children)
|
|
if (msg.attr["time"] == time) {
|
|
this.scrollEventToView(msg, true, !fast && settings.animatedScroll);
|
|
break;
|
|
}
|
|
}
|
|
|
|
selectAll() {
|
|
if (this.chat.children.length > 0)
|
|
this.setSelection(this.chat.first.attr["time"], this.chat.last.attr["time"]);
|
|
}
|
|
|
|
clearSelection(wholeOnly = false) {
|
|
if (!wholeOnly) {
|
|
var bodies = this.chat.$$(".msgBody");
|
|
for (let body of bodies)
|
|
body.selection.collapseToStart();
|
|
|
|
var codeBlocks = this.chat.$$("source-code");
|
|
for (let codeBlock of codeBlocks)
|
|
codeBlock.selection.collapseToStart();
|
|
}
|
|
|
|
this.selStart = 0;
|
|
for (let el of this.chat.children)
|
|
deselectEvent(el);
|
|
}
|
|
|
|
setSelection(start, end) {
|
|
this.clearSelection();
|
|
|
|
for (let el of this.chat.children) {
|
|
var msgTime = parseFloat(el.attr["time"]) || 0;
|
|
if (msgTime >= (parseFloat(start) || 0) && msgTime <= (parseFloat(end) || 0)) selectEvent(el);
|
|
}
|
|
this.selStart = parseFloat(start) || 0;
|
|
|
|
this.saveSelection();
|
|
}
|
|
|
|
getTextNodes(node) {
|
|
var result = "";
|
|
for (var child = node.firstChild; child != null; child = child.nextSibling)
|
|
if (child.nodeType == 3 || (child.state.visible && child.length == 0))
|
|
result += child.nodeType == 3 ? child.textContent : (child.tag == "text" ? child.text + "\r\n" : child.text);
|
|
else if (child.state.visible)
|
|
result += this.getTextNodes(child);
|
|
return result;
|
|
}
|
|
|
|
getSelectedTextInfo() {
|
|
var str = "";
|
|
var startHistNode = "0";
|
|
var endHistNode = "0";
|
|
var wholeEvents = false;
|
|
if (this.selStart > 0) {
|
|
wholeEvents = true;
|
|
var nodes = this.chat.$$("> .msgFull.selected");
|
|
if (nodes.length > 0) {
|
|
startHistNode = nodes.first.attr["time"];
|
|
endHistNode = nodes.last.attr["time"];
|
|
}
|
|
for (let node of nodes) {
|
|
if (str.length > 0) str += "\r\n";
|
|
var dateText = node.$(".msgDate").text;
|
|
var whatText = node.$(".msgWhat").text;
|
|
var multiText = "";
|
|
var multi = node.$(".msgMulti");
|
|
if (multi != null) {
|
|
multiText = multi.text;
|
|
if (multiText.length > 0) multiText = " " + multiText;
|
|
}
|
|
|
|
var bodyText = this.getTextNodes(node.$(".msgBody"));
|
|
str += dateText + ", " + whatText + multiText + "\r\n" + bodyText;
|
|
}
|
|
} else {
|
|
var bodies = this.chat.$$(".msgBody");
|
|
for (let body of bodies) {
|
|
if (body.selection.toString() != null && body.selection.toString() !== "") {
|
|
if (str !== "") str += "\r\n";
|
|
str += body.selection.toString();
|
|
}
|
|
|
|
var codeBlocks = body.$$("source-code");
|
|
for (let codeBlock of codeBlocks)
|
|
if (codeBlock.plaintext.selectionText != null && codeBlock.plaintext.selectionText !== "") {
|
|
if (str !== "") str += "\r\n";
|
|
str += codeBlock.plaintext.selectionText;
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
text: str.replace(/\r?\n/g, "\r\n"),
|
|
startTime: startHistNode === undefined ? "0" : startHistNode,
|
|
endTime: endHistNode === undefined ? "0" : endHistNode,
|
|
wholeEvents: wholeEvents
|
|
};
|
|
}
|
|
|
|
saveSelection() {
|
|
this.selInfo = this.getSelectedTextInfo();
|
|
Native.UpdateSelection(this.selInfo.text, this.selInfo.startTime, this.selInfo.endTime, this.selInfo.wholeEvents);
|
|
if (settings.autoCopy) this.copySelected();
|
|
}
|
|
|
|
copySelected() {
|
|
var str = this.selInfo.text;
|
|
if (str) Clipboard.writeText(str);
|
|
}
|
|
|
|
deleteEvents(start, end) {
|
|
var startTime = parseFloat(start) || 0;
|
|
var endTime = parseFloat(end) || 0;
|
|
var msgs = [...this.chat.children];
|
|
for (let msg of msgs) {
|
|
var when = parseFloat(msg.attr["time"]) || 0;
|
|
if (when >= startTime && when <= endTime) {
|
|
msg.remove();
|
|
this.msgOffset--;
|
|
}
|
|
}
|
|
}
|
|
|
|
deleteEvent(msgid) {
|
|
var msgs = [...this.chat.children];
|
|
for (let msg of msgs)
|
|
if (msgid === msg.attr["msgid"]) {
|
|
msg.remove();
|
|
this.msgOffset--;
|
|
}
|
|
}
|
|
|
|
updateMsgStatus(status) {
|
|
var msg = this.chat.$("> .msgFull[time=\"" + status.when + "\"]");
|
|
if (msg) {
|
|
msg.attr["msgid"] = status.msgid;
|
|
var img = msg.$(".msgEventImg");
|
|
if (img) img.applySprite(status.eventimg);
|
|
}
|
|
}
|
|
|
|
updateSplitterWidth() {
|
|
this.post(() => {
|
|
var m = this.inputPanel.style["padding-left"].valueOf();
|
|
var chatWidth = this.state.box("width", "border");
|
|
var inputWidth = this.input.state.box("width", "border");
|
|
if (chatWidth == 0 || inputWidth == 0) return;
|
|
this.inputSplit.style["margin-right"] = (chatWidth - inputWidth - m) + "px";
|
|
});
|
|
}
|
|
|
|
quote(qs, limitWidth = false) {
|
|
if (qs && qs.length > 0) {
|
|
this.processQuote(qs, true, limitWidth);
|
|
} else {
|
|
if (settings.quoteSelected && this.selInfo.text != "") {
|
|
this.processQuote(this.selInfo.text);
|
|
} else {
|
|
// Save original reply at the beginning of a quoting cycle
|
|
if (this.quoteIdx == 0) this.lastInputText = this.input.value;
|
|
var res = Native.GetMessageByIdx(this.chatid, this.quoteIdx);
|
|
if (res === false) return; // Nothing to quote
|
|
this.quoteIdx = res[0];
|
|
this.processQuote(res[1], false, limitWidth);
|
|
}
|
|
}
|
|
}
|
|
|
|
processQuote(qs, insertQuote = true, limitWidth = false) {
|
|
this.input.state.focus = true;
|
|
|
|
var res = "";
|
|
var qslist = qs.split("\n");
|
|
for (let line of qslist) {
|
|
line = line.trimRight();
|
|
if (line == "") continue;
|
|
var leading = "";
|
|
for (var c = 0; c < line.length; ++c) {
|
|
var cc = line.charAt(c);
|
|
if (cc == ">" || cc == " ")
|
|
leading += cc;
|
|
else
|
|
break;
|
|
}
|
|
|
|
if (limitWidth) line = Native.WrapText(line, 50);
|
|
|
|
var list = line.split("\n");
|
|
for (let line2 of list) {
|
|
var t;
|
|
if (leading.length > 0 && leading[0] == ">")
|
|
t = ">" + line2;
|
|
else
|
|
t = "> " + line2;
|
|
res += t + "\n";
|
|
}
|
|
}
|
|
res += "\n";
|
|
|
|
var i = this.quoteIdx;
|
|
if (insertQuote) {
|
|
this.addToInput(res);
|
|
} else {
|
|
this.input.saveCaretPos();
|
|
if (this.lastInputText) res = this.lastInputText + "\n" + res;
|
|
this.input.value = res;
|
|
this.input.state.focus = true;
|
|
if (settings.cursorBelow)
|
|
this.input.execCommand("navigate:end");
|
|
else
|
|
this.input.restoreCaretPos();
|
|
this.changeInput();
|
|
}
|
|
this.post(() => this.quoteIdx = i);
|
|
}
|
|
|
|
updateAvatar() {
|
|
var avt = "avatar:" + this.chatid;
|
|
this.loadImage(avt, (image) => {
|
|
if (image !== null) {
|
|
document.bindImage(avt, image);
|
|
this.avatarimg.attr["src"] = "avatar:" + this.chatid;
|
|
this.avatar.classList.remove("hidden");
|
|
this.avatarSplit.state.disabled = false;
|
|
} else this.clearAvatar();
|
|
}, false);
|
|
}
|
|
|
|
clearAvatar() {
|
|
this.avatar.classList.add("hidden");
|
|
this.avatarimg.attr["src"] = undefined;
|
|
this.avatarSplit.state.disabled = true;
|
|
}
|
|
|
|
updateSmartReplies() {
|
|
this.smartReplies.clear();
|
|
var sreplies = lastSmartReplies[this.chatid];
|
|
if (!sreplies) return;
|
|
var replies = [];
|
|
for (let sreply of sreplies) replies.push(<div>{sreply}</div>)
|
|
this.smartReplies.append(replies);
|
|
}
|
|
}
|
|
|
|
class FileDropZone extends Element {
|
|
isDragActive = false;
|
|
textData = "";
|
|
|
|
shiftX = 26dip.valueOf();
|
|
shiftY = 18dip.valueOf();
|
|
|
|
moveToCursor(el) {
|
|
var [x, y] = view.box("position", "cursor");
|
|
el.takeOff({
|
|
x: x + this.shiftX, y: y + this.shiftY,
|
|
window: "popup"
|
|
});
|
|
}
|
|
|
|
hideDrags() {
|
|
dragHtml.state.expanded = false;
|
|
dragHtml.takeOff();
|
|
dragFile.state.expanded = false;
|
|
dragFile.takeOff();
|
|
chatButtons.fileBtn.state.expanded = false;
|
|
}
|
|
|
|
["on ^dragaccept"](e) {
|
|
if (e.detail?.dataType == "html") return false;
|
|
return true;
|
|
}
|
|
|
|
["on dragenter"](e) {
|
|
this.isDragActive = true;
|
|
if (e.detail?.dataType == "file") {
|
|
var hint = "";
|
|
if (Array.isArray(e.detail.data)) {
|
|
var cnt = 0;
|
|
for (let file of e.detail.data) cnt++;
|
|
hint = printf(_("Upload ZIP archive with %d files to %s"), cnt, CommonNative.GetUploadServerName());
|
|
} else if (typeof e.detail.data == "string") {
|
|
hint = printf(_("Upload %s to %s"), new URL(e.detail.data).filename, CommonNative.GetUploadServerName());
|
|
} else hint = _("Unknown");
|
|
dragFile.$("span").html = hint;
|
|
dragFile.$("> div[pic]").setupIcon();
|
|
this.moveToCursor(dragFile);
|
|
dragFile.state.expanded = true;
|
|
chatButtons.fileBtn.state.expanded = true;
|
|
} else {
|
|
this.textData = e.detail.data.toString();
|
|
dragHtml.$("span").text = this.textData;
|
|
this.moveToCursor(dragHtml);
|
|
dragHtml.state.expanded = true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
["on dragleave"](e) {
|
|
this.hideDrags();
|
|
return true;
|
|
}
|
|
|
|
["on drag"](e) {
|
|
this.moveToCursor(e.detail?.dataType == "file" ? dragFile : dragHtml);
|
|
return true;
|
|
}
|
|
|
|
["on drop"](e) {
|
|
this.isDragActive = false;
|
|
var page = getCurrentPage();
|
|
if (page) page.input.state.disabled = false;
|
|
if (e.detail?.dataType == "file") {
|
|
dragFile.state.expanded = false;
|
|
dragFile.takeOff();
|
|
chatButtons.fileBtn.state.expanded = false;
|
|
if (Array.isArray(e.detail.data)) {
|
|
var files = e.detail.data;
|
|
for (let [index, file] of files.entries())
|
|
files[index] = files[index].toLocalPath();
|
|
Native.UploadFiles(true, files.join(";"));
|
|
} else if (typeof e.detail.data == "string") {
|
|
Native.UploadFiles(false, e.detail.data.toLocalPath());
|
|
}
|
|
} else {
|
|
dragHtml.state.expanded = false;
|
|
dragHtml.takeOff();
|
|
var page = getCurrentPage();
|
|
if (page && page.pagetype !== "plugin")
|
|
page.addToInput(this.textData);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// DragDrop({
|
|
// what: "div#tabs > .tab",
|
|
// where: "div#tabs",
|
|
// container: "div#tabs",
|
|
// ignore: ".tab-close",
|
|
// easeDrop: Animation.Ease.OutQuad,
|
|
// acceptDrag: function(draggable, target) {
|
|
// draggable.chat.setCurrent();
|
|
// draggingTab = true;
|
|
// Native.SetTabDragging(true);
|
|
// return #moving;
|
|
// },
|
|
// onFinalize: function() {
|
|
// updateTabsDepth();
|
|
// draggingTab = false;
|
|
// Native.SetTabDragging(false);
|
|
// },
|
|
// animationDuration: 200ms,
|
|
// autoScroll: true
|
|
// });
|
|
|
|
function isSyntaxHighlightEnabled() {
|
|
return document.body.style.variable("syntax-highlight") === "on";
|
|
}
|
|
|
|
function setupHint(el) {
|
|
if (!el.attr["hint"]) return;
|
|
if (!el.attr["hint_en"]) el.attr["hint_en"] = el.attr["hint"];
|
|
el.attr["hint"] = _(el.attr["hint_en"] ? el.attr["hint_en"] : el.attr["hint"]);
|
|
el.on("mouseenter", (e) => setStatusBarHint(el.attr["hint"], el.attr["sticker"] !== undefined))
|
|
.on("mouseleave", (e) => setStatusBarHint(""));
|
|
}
|
|
|
|
function updateHintsTraslation() {
|
|
var hints = $$("[hint]");
|
|
for (let hint of hints) setupHint(hint);
|
|
}
|
|
|
|
function ShowHint() {
|
|
setupHint(this);
|
|
}
|
|
|
|
function isEventSelected(el) {
|
|
return el.classList.contains("selected");
|
|
}
|
|
|
|
function selectEvent(el) {
|
|
if (!el.classList.contains("selected"))
|
|
el.classList.add("selected");
|
|
}
|
|
|
|
function deselectEvent(el) {
|
|
if (el.classList.contains("selected"))
|
|
el.classList.remove("selected");
|
|
}
|
|
|
|
function showImage(imgTag, link) {
|
|
if (link.indexOf("embedded:") == 0) {
|
|
imgTag.attr["src"] = link;
|
|
finishImage(imgTag);
|
|
} else {
|
|
var cached = false;
|
|
try {
|
|
cached = document.bindImage("download:" + link) !== null;
|
|
} catch(e) {
|
|
cached = true;
|
|
}
|
|
imgTag.attr["src"] = "download:" + link;
|
|
if (imgTag.tag == "lottie") imgTag.lottie.load("download:" + link);
|
|
if (cached) {
|
|
imgTag.attr["cached"] = true;
|
|
finishImage(imgTag);
|
|
}
|
|
}
|
|
}
|
|
|
|
function finishImagesByLink(link) {
|
|
var imgTags = pages.$$("img[src=\"" + link + "\"], lottie[src=\"" + link + "\"]");
|
|
for (let imgTag of imgTags) finishImage(imgTag);
|
|
}
|
|
|
|
function finishImage(imgTag) {
|
|
if (!imgTag || imgTag.classList.contains("loaded")) return;
|
|
if (imgTag.attr["lottie"] == "true") {
|
|
var lottie = Element.create("lottie");
|
|
lottie.attr["class"] = imgTag.attr["class"];
|
|
lottie.attr["src"] = imgTag.attr["src"];
|
|
lottie.attr["autoplay"] = "";
|
|
lottie.attr["loop"] = "";
|
|
imgTag.parent.append(lottie);
|
|
imgTag.remove();
|
|
imgTag = lottie;
|
|
}
|
|
var pg = imgTag.$p(".page");
|
|
var bottomScroll = msgPreview ? false : scrollImageToBottom(pg, imgTag);
|
|
let parent = imgTag.parent;
|
|
if (parent) {
|
|
var aTag = parent.prior?.prior;
|
|
if (aTag) aTag.classList.remove("checking");
|
|
parent.classList.remove("hidden");
|
|
}
|
|
if (!imgTag.classList.contains("embeddedImg") && !imgTag.classList.contains("cached")) imgTag.flushPaint();
|
|
if (!msgPreview)
|
|
if (bottomScroll)
|
|
pg.scrollToBottom(false);
|
|
else if (pg.bottomPos >= 0)
|
|
pg.chatHist.scrollToEx(0, pg.chatHist.state.box("height", "content") - pg.chatHist.state.box("height", "client") - pg.bottomPos, false);
|
|
imgTag.classList.add("loaded");
|
|
}
|
|
|
|
class LoadedImage extends Element {
|
|
componentDidMount() {
|
|
//console.log("LoadedImage:", this.tag, this.attr["src"], this.attr["style"]);
|
|
this.post(() => this.attr["style"] = "");
|
|
this.on("mousedown", (e) => openOverlay(e, this));
|
|
}
|
|
}
|
|
|
|
function scrollImageToBottom(pg, imgTag) {
|
|
if (!pg) return false;
|
|
return pg.isAtBottom() && pg.chat.children.length > 0 && imgTag.$p(".msgFull") == pg.chat.last;
|
|
}
|
|
|
|
function getImg(imgTag) {
|
|
try {
|
|
var action = imgTag.attr["data-action"];
|
|
var link = imgTag.attr["data-link"];
|
|
imgTag.removeAttribute("data-action");
|
|
imgTag.removeAttribute("data-link");
|
|
if (action == "check") // not in cache, check first
|
|
fetch(new Request("check:" + link, { method: "GET", cache: "reload" })).then((resp) => resp.json()).then((data) => {
|
|
if (imgTag)
|
|
if (data && data.isImg === 1) { // image link confirmed, show preloader and start download
|
|
var pg = imgTag.$p(".page");
|
|
var bottomScroll = !msgPreview && scrollImageToBottom(pg, imgTag);
|
|
var aTag = imgTag.parent?.prior.prior;
|
|
if (aTag) aTag.classList.add("checking");
|
|
if (data.isLottie === 1) imgTag.attr["lottie"] = "true";
|
|
showImage(imgTag, data.link);
|
|
} else { // not an image link, remove loader
|
|
if (imgTag.parent) imgTag.parent.remove();
|
|
}
|
|
}, (e) => {
|
|
console.error(e, e.stack);
|
|
if (imgTag.parent) imgTag.parent.remove();
|
|
});
|
|
else { // image was already cached or embedded
|
|
showImage(imgTag, link);
|
|
}
|
|
} catch(e) {
|
|
console.error(e, e.stack);
|
|
}
|
|
}
|
|
|
|
function viewInWindow(uid, title, body, when, formicon) {
|
|
var msg = getChatPage(uid).$(".msgFull[time=\"" + when + "\"]");
|
|
var images = [];
|
|
var winid = "";
|
|
if (msg) {
|
|
var embeddedImgs = msg.$$("img[src^=\"embedded:\"]");
|
|
for (let embeddedImg of embeddedImgs)
|
|
images.push(embeddedImg.attr["src"]);
|
|
winid = msg.attr["msgid"];
|
|
} else {
|
|
winid = "selection";
|
|
}
|
|
|
|
openTextWindow(uid + ":" + winid, title, body, settings.viewTextWrap, images, formicon);
|
|
}
|
|
|
|
function getChatPage(uid) {
|
|
for (let page of pages.children)
|
|
if (page.chatid == uid) return page;
|
|
}
|
|
|
|
function getPluginPage(id) {
|
|
for (let page of pages.children)
|
|
if (page.plugid == id) return page;
|
|
}
|
|
|
|
function openChatPage(uid, displayed, focused) {
|
|
var page = Element.create(<Page pagetype="im" uid={uid} displayed={displayed} focused={focused} />);
|
|
pages.append(page);
|
|
page.init();
|
|
}
|
|
|
|
function openPluginPage(id, displayed) {
|
|
var page = Element.create(<Page pagetype="plugin" plugid={id} displayed={displayed} />);
|
|
pages.append(page);
|
|
page.init();
|
|
}
|
|
|
|
function getPluginBounds() {
|
|
var [x1, y1, x2, y2] = pages.state.box("rect", "border", "window");
|
|
var padding = 5dip.valueOf();
|
|
return [x1 + padding, y1 + padding, x2 - padding, y2 - padding];
|
|
}
|
|
|
|
function addPluginButton(id) {
|
|
if (id == 0) return;
|
|
var plugBtn = Element.create("div");
|
|
var plugBtnImg = Element.create("div");
|
|
plugBtn.attr["proc"] = id;
|
|
plugBtn.append(plugBtnImg);
|
|
plugButtons.append(plugBtn);
|
|
}
|
|
|
|
function modifyPluginButton(id, hint, pic) {
|
|
var plugBtn = plugButtons.$("div[proc=\"" + id + "\"]");
|
|
if (!plugBtn) return;
|
|
if (pic) plugBtn.$("> div").applySprite(getSpriteData(pic), "plugin");
|
|
plugBtn.attr["hint"] = hint;
|
|
}
|
|
|
|
function delPluginButton(id) {
|
|
var plugBtn = plugButtons.$("div[proc=\"" + id + "\"]");
|
|
if (plugBtn) plugBtn.remove();
|
|
}
|
|
|
|
function setCurrentPage(dir) {
|
|
var index = tabs.$(":checked").index;
|
|
index += dir;
|
|
if (index < 0)
|
|
index = tabs.length - 1;
|
|
else if (index >= tabs.length)
|
|
index = 0;
|
|
tabs.children[index].chat.setCurrent();
|
|
}
|
|
|
|
function initSettings(sets) {
|
|
settings = sets;
|
|
|
|
view.isTopmost = settings.alwaysOnTop;
|
|
settings.screenshotFormatSymbol = settings.screenshotFormat == 2 ? "webp" : (settings.screenshotFormat == 1 ? "jpeg" : "png");
|
|
|
|
switch (settings.imageQuality) {
|
|
case 0: pages.style["image-rendering"] = "optimize-quality"; break;
|
|
case 1: pages.style["image-rendering"] = "optimize-speed"; break;
|
|
case 2: pages.style["image-rendering"] = "default"; break;
|
|
}
|
|
|
|
var step = settings.wheelVelocity == 3 ? 10 : settings.wheelVelocity * 3.3;
|
|
pages.style["scroll-manner"] = "scroll-manner(animation: " + (settings.animatedScroll ? "true": "false") + ", wheel-step: " + step + "%)";
|
|
|
|
if (settings.showSmileCaption)
|
|
smilesList.children[0].classList.add("captions");
|
|
else
|
|
smilesList.children[0].classList.remove("captions");
|
|
|
|
setupRelTimesUpdates();
|
|
|
|
var spellErrorStyles = {
|
|
0: "solid",
|
|
1: "dotted",
|
|
2: "dashed",
|
|
3: "wavy"
|
|
};
|
|
|
|
var dyncss = $("style#dyncss");
|
|
dyncss.text = ".input > text::mark(wrongword) { text-decoration: underline " + spellErrorStyles[settings.spellErrorStyle] + " " + settings.spellErrorColor + " }";
|
|
dyncss.text += ".renderOption { font-rendering-mode: " + (settings.smoothFontRendering ? "sub-pixel" : "snap-pixel") + " }";
|
|
dyncss.text += ".avatar { display: " + (settings.showAvatar ? "block" : "none") + " }";
|
|
dyncss.text += ".smartReplies { display: " + (settings.showSmartReplies ? "block" : "none") + " }";
|
|
dyncss.text += ".msgReaction { display: " + (settings.showReactions ? "inline-block" : "none") + " }";
|
|
dyncss.text += ".msgTitle .msgReactBtn { display: " + (settings.showReactions ? "block" : "none") + " }";
|
|
dyncss.text += ".input > text::mark(wrongword) { text-decoration: underline " + spellErrorStyles[settings.spellErrorStyle] + " " + settings.spellErrorColor + "; }";
|
|
|
|
function cssStyleMax() {
|
|
return "max-width: " + (settings.maxImgWidth > 0 ? settings.maxImgWidth + "px" : "calc(100vw - 41dip)") + "; " +
|
|
"max-height: " + (settings.maxImgHeight > 0 ? settings.maxImgHeight + "px" : "none") + ";";
|
|
}
|
|
|
|
// function cssStyleLottie() {
|
|
// return "width: " + (settings.maxImgWidth > 0 ? settings.maxImgWidth : 256) + "px; " +
|
|
// "height: " + (settings.maxImgHeight > 0 ? settings.maxImgHeight : 256) + "px;";
|
|
// }
|
|
|
|
dyncss.text += ".msgBody .linkedImg { " + cssStyleMax() + " }";
|
|
dyncss.text += ".msgBody lottie.linkedImg { " + cssStyleMax() + " }";
|
|
dyncss.text += ".msgBody .embeddedImg { " + cssStyleMax() + " }";
|
|
dyncss.text += "span.bold { font-weight: " + (settings.fontCodes ? 500 : "normal") + " }";
|
|
dyncss.text += "span.underlined { text-decoration: " + (settings.fontCodes ? "underline" : "none") + " }";
|
|
|
|
var usercss = $("style#usercss");
|
|
usercss.text = settings.chatCSS;
|
|
}
|
|
|
|
function initMsgPreview(searchText) {
|
|
msgPreview = true;
|
|
document.body.classList.add("msgPreview");
|
|
if (!$(".searchHere"))
|
|
document.body.append(<div class="searchHere">{searchText}</div>);
|
|
}
|
|
|
|
function applyTheme() {
|
|
reloadImage("themepicsingle:msg");
|
|
|
|
for (let el of $$("menu.custom li, button.ddbutton, #chatButtons > *, #sbOutbox, #myreactions > div")) {
|
|
var icon = el.$("> div[pic]");
|
|
if (icon) icon.setupIcon();
|
|
}
|
|
|
|
if (initialLoad) {
|
|
initialLoad = false;
|
|
return;
|
|
}
|
|
|
|
var bimages = $$("[style], div[pic], .chatBackground, .chatHist");
|
|
var themepics = [];
|
|
for (let bimage of bimages)
|
|
if (bimage) {
|
|
var link = bimage.style["background-image"];
|
|
if (!link || link.indexOf("themepic:") === -1 || themepics.indexOf(link) !== -1) continue;
|
|
themepics.push(link);
|
|
}
|
|
|
|
for (let themepic of themepics) reloadImage(themepic);
|
|
for (let page of pages.children) page.resetHistory();
|
|
themepics = [];
|
|
}
|
|
|
|
function showSearchHere() {
|
|
if (getCurrentPage().chat.children.length == 0)
|
|
$(".searchHere").classList.remove("hidden")
|
|
}
|
|
|
|
function hideSearchHere() {
|
|
$(".searchHere").classList.add("hidden")
|
|
}
|
|
|
|
function reloadSmiles(smiles) {
|
|
if (smilesGrid.parent == null) document.body.append(smilesGrid);
|
|
smilesList.list.clear();
|
|
if (smiles)
|
|
for (let sm of smiles) if (sm) {
|
|
reloadImage("smile:n" + sm[0]);
|
|
smilesList.list.append([<div class="sesbtn" text={sm[1]} hint={sm[1]}><div style={"background-image: url(smile:n" + sm[0] + "); width: " + sm[2] + "px; height: " + sm[3] + "px;"}></div></div>, <div class="caption">{sm[1]}</div>]);
|
|
}
|
|
if (smilesList.list.length > 0 && smilesList.list.first?.state) smilesList.list.first.state.current = true;
|
|
smilesGrid.detach();
|
|
}
|
|
|
|
function makeEmojiBtn(em, emojiSize) {
|
|
return <div style={"background-image: url(themepic:emojis.sprite); background-position: " + em[1] + " " + em[2] + "; width: " + emojiSize + "px; height: " + emojiSize + "px"}></div>;
|
|
}
|
|
|
|
function makeEmojiCat(cat, emojiNums, emojiSize) {
|
|
return <div class="catbtn" hint={emojiNums[cat][0]} category={cat}>{makeEmojiBtn(emojiNums[cat], emojiSize)}</div>;
|
|
}
|
|
|
|
function reloadEmojis(hasEmojis, emojiSize, emoji, emojiNums) {
|
|
emojiList = $("#emojiList");
|
|
reloadImage("themepic:emojis.sprite");
|
|
|
|
emojiPages.length = 0;
|
|
if (!hasEmojis) {
|
|
emojiCat.clear();
|
|
emojiList.clear();
|
|
return;
|
|
}
|
|
|
|
var cats = [];
|
|
if (emoji)
|
|
for (let [cat, emojiCat] of emoji.entries()) {
|
|
if (!emojiCat) continue;
|
|
cats.push(makeEmojiCat(cat, emojiNums, emojiSize));
|
|
|
|
var emlist = [];
|
|
for (let em of emojiCat)
|
|
emlist.push(<div class="sesbtn" text={em[0]}>{makeEmojiBtn(em, emojiSize)}</div>);
|
|
emojiList.content(<div class="emlist">{emlist}</div>);
|
|
emojiPages[cat] = emojiList.children[0];
|
|
emojiPages[cat].detach();
|
|
}
|
|
|
|
emojiCat.content(cats);
|
|
if (emojiCat.length > 0) emojiCat.children[0].state.checked = true;
|
|
|
|
emojiList.clear();
|
|
if (emojiPages.length > 0) {
|
|
emojiList.append(emojiPages[0]);
|
|
emojiList.initList();
|
|
}
|
|
}
|
|
|
|
function preloadImages(urlList) {
|
|
if (urlList === "") return;
|
|
var urls = urlList.split("\n");
|
|
for (let url of urls) reloadImage(url.trim(), true);
|
|
}
|
|
|
|
function delphi2unix(time) {
|
|
return (time - 25569.0) * 86400000.0;
|
|
}
|
|
|
|
function unix2delphi(time) {
|
|
return time / 86400000.0 + 25569.0;
|
|
}
|
|
|
|
function getCurrentPage() {
|
|
return pages.$("> .page:current");
|
|
}
|
|
|
|
function setSendBtnImage(pic) {
|
|
sendBtnImg.applySprite(pic);
|
|
}
|
|
|
|
function sendMessageToCurrent(opt = 0) {
|
|
var page = getCurrentPage();
|
|
if (page) page.sendMessage(opt);
|
|
}
|
|
|
|
function closePages(opt = 0) {
|
|
closeVariant.value = undefined;
|
|
document.post(() => Native.ClosePages(opt));
|
|
}
|
|
|
|
function closeForm() {
|
|
var popups = $$(":popup");
|
|
for (let popup of popups) popup.state.popup = false;
|
|
backTabs.clear();
|
|
hide();
|
|
}
|
|
|
|
function uploadFile(opt = 0) {
|
|
var url = Native.UploadFile(opt);
|
|
if (url) {
|
|
var page = getCurrentPage();
|
|
if (!page || page.pagetype == "plugin") return;
|
|
page.addToInput(url);
|
|
}
|
|
}
|
|
|
|
function selectContactsToSend() {
|
|
var UINs = [];
|
|
UINs = openUINList("Send contacts", "Select", ["sco_multi", "sco_groups", "sco_predefined"]);
|
|
if (!UINs || UINs.length == 0) return;
|
|
var page = getCurrentPage();
|
|
for (let UIN of UINs) page.addToInput("https://icq.im/" + UIN + "\n");
|
|
}
|
|
|
|
function openSearch(searchAll = true) {
|
|
var page = getCurrentPage();
|
|
if (searchAll || page.pagetype != "im")
|
|
openHistorySearch();
|
|
else
|
|
openHistorySearch(page.chatid, page.displayed);
|
|
}
|
|
|
|
function openInfo() {
|
|
Native.ChatButtonClick("infoBtn");
|
|
}
|
|
|
|
function openSmiles() {
|
|
if (smilesList.list.length == 0) {
|
|
showAlert("warning", "No smiles found in current theme");
|
|
return;
|
|
}
|
|
document.body.append(smilesGrid);
|
|
chatButtons.smilesBtn.parent.popup(smilesGrid, {
|
|
anchorAt: 7,
|
|
popupAt: 1
|
|
});
|
|
smilesList.list.state.focus = true;
|
|
}
|
|
|
|
function openEmoji() {
|
|
if (emojiList.length == 0) {
|
|
showAlert("warning", "Emoji image cannot be found in current theme");
|
|
return;
|
|
}
|
|
chatButtons.emojiBtn.parent.popup(emojiGrid, {
|
|
anchorAt: 7,
|
|
popupAt: 1
|
|
});
|
|
emojiList.list.state.focus = true;
|
|
}
|
|
|
|
function searchStickers() {
|
|
var emlist = $(".imagegrid#stickersGrid .imagelist div.emlist[category=\"-1\"]");
|
|
if (!emlist) return;
|
|
emlist.clear();
|
|
if (sKeywords == "") return;
|
|
var found = Native.SearchStickersByKeywords(sKeywords);
|
|
if (!found || found.length == 0)
|
|
emlist.append(<div class="nothingfound">{_("Nothing can be found")}</div>);
|
|
else for (let st of found)
|
|
emlist.append(<div class="sesbtn" cat="-1" sticker={st[1]} hint={Native.SpanEmojis(st[2])}>{makeStickerBtn(st[1], false, st[0] == "animated")}</div>);
|
|
}
|
|
|
|
function switchToStickerCat(cat) {
|
|
stickersList.content(stickersPages[cat]);
|
|
stickersList.initList();
|
|
stickersList.scrollTo({
|
|
position: [0, 0],
|
|
behavior: "instant"
|
|
});
|
|
var stsearch = stickersList.$("#stsearch");
|
|
if (stsearch) {
|
|
stsearch.value = sKeywords;
|
|
stsearch.edit.selectRange(sKeywords.length, sKeywords.length);
|
|
stsearch.state.focus = true;
|
|
searchStickers();
|
|
} else {
|
|
stickersList.list.state.focus = true;
|
|
}
|
|
}
|
|
|
|
function openStickers() {
|
|
var keys = Object.keys(stickersPages);
|
|
if (keys.length == 1) {
|
|
openStickersManager();
|
|
return;
|
|
}
|
|
var page = getCurrentPage();
|
|
if (page && page.pagetype == "im")
|
|
if (page.input.selection.toString() !== "") sKeywords = page.input.selection.toString();
|
|
chatButtons.stickersBtn.parent.popup(stickersGrid, {
|
|
anchorAt: 7,
|
|
popupAt: 1,
|
|
});
|
|
if (stickersList.length == 0) {
|
|
var cat = stickersCat.$(".catbtn:checked");
|
|
switchToStickerCat(cat ? cat.attr["category"] : keys[0]);
|
|
}
|
|
}
|
|
|
|
function openStickersManager() {
|
|
var [px, py, dw, dh] = view.screenBox("frame", "rectw");
|
|
sMgr = openSingleWindow({
|
|
url: "stickersmgr.htm",
|
|
caption: "Stickers manager",
|
|
uniqueid: "stickersmgr",
|
|
alignment: 5,
|
|
width: dw / 3,
|
|
height: dh / 2.5
|
|
});
|
|
sMgr.on("close", function(e) {
|
|
sMgr.off("close");
|
|
sMgr = null;
|
|
});
|
|
}
|
|
|
|
function updateStickersManager() {
|
|
if (sMgr !== null) sMgr.updateMyStickerPacks();
|
|
}
|
|
|
|
function updateSearchResults(res) {
|
|
if (sMgr !== null) sMgr.updateSearchResults(res);
|
|
}
|
|
|
|
function startStickersSearch(qry) {
|
|
if (sMgr !== null) sMgr.startStickersSearch(qry);
|
|
}
|
|
|
|
function makeQuote(fromClipboard = false, limitWidth = false) {
|
|
getCurrentPage().quote(fromClipboard ? Clipboard.readText() : "", limitWidth);
|
|
}
|
|
|
|
function setupStickersBtn(st) {
|
|
chatButtons.stickersBtn.state.disabled = !st;
|
|
}
|
|
|
|
function setupBuzzBtn(st) {
|
|
chatButtons.buzzBtn.setVisible(st);
|
|
}
|
|
|
|
function setupSingleBtn(st) {
|
|
chatButtons.singleBtn.state.checked = st;
|
|
}
|
|
|
|
function setupFileBtn(st) {
|
|
chatButtons.fileBtn.state.disabled = !st;
|
|
}
|
|
|
|
function setupChatButtons(st) {
|
|
for (let chatBtn of chatButtons.self.children) chatBtn.state.disabled = st;
|
|
// for (let plugBtn of plugButtons) plugBtn.state.disabled = st;
|
|
}
|
|
|
|
function updateStatusBar(outbox, trlt, encpic, enchint) {
|
|
var pic = $("#sbOutbox > div");
|
|
pic.applySprite(getSpriteData(outbox ? "outbox" : "outbox.empty"));
|
|
pic.style.opacity = outbox ? 1 : 0.5;
|
|
$("#sbTrlt > div").style.opacity = trlt ? 0.8 : 0.4;
|
|
var pic = $("#sbCrypt > div#pic");
|
|
if (encpic) {
|
|
pic.setVisible(true);
|
|
pic.applySprite(getSpriteData(encpic));
|
|
} else pic.setVisible(false);
|
|
$("#sbCrypt").attr["hint"] = enchint;
|
|
}
|
|
|
|
function setStatusBarHint(hint, html = false) {
|
|
if (html)
|
|
$("#sbHint").html = hint;
|
|
else
|
|
$("#sbHint").text = hint;
|
|
}
|
|
|
|
function setSmartReplies(chat, suggests) {
|
|
lastSmartReplies[chat] = suggests;
|
|
var page = getChatPage(chat);
|
|
if (page) page.updateSmartReplies();
|
|
}
|
|
|
|
function makeStickerBtn(id, isPicker, isLottie) {
|
|
if (isLottie && !isPicker)
|
|
return <lottie src={"sticker:a" + id} autoplay loop />;
|
|
else
|
|
return <img src={(isPicker ? (isLottie ? "picker:a" : "picker:n") : "sticker:n") + id} />;
|
|
}
|
|
|
|
function loadStickers(data) {
|
|
updateStickersManager();
|
|
|
|
stickersPages = {};
|
|
var cats = [];
|
|
cats.push(<div class="catbtn" hint={_("Search")} category="-1"><div pic="search_button2" auto></div></div>);
|
|
stickersPages["-1"] = [<div class="stinputdiv"><input id="stsearch" class="stinput" /></div>, <div class="emlist" category="-1"></div>];
|
|
|
|
if (data)
|
|
for (let pack of data) {
|
|
if (!pack.Content || pack.Content.length == 0) continue;
|
|
|
|
var isLottie = pack.ContentType == "animated";
|
|
cats.push(<div class="catbtn" hint={pack.Name} category={pack.Id}>{makeStickerBtn(pack.Content[0], true, isLottie)}</div>);
|
|
|
|
var emlist = [];
|
|
var pos = 0;
|
|
for (let st of pack.Content) {
|
|
var hint = "";
|
|
if (pack.Keywords && pos < pack.Keywords.length) hint = Native.SpanEmojis(pack.Keywords[pos]);
|
|
emlist.push(<div class="sesbtn" cat={pack.Id} sticker={st} hint={hint}>{makeStickerBtn(st, false, isLottie)}</div>);
|
|
pos++;
|
|
}
|
|
stickersPages[pack.Id.toString()] = <div class="emlist" category={pack.Id}>{emlist}</div>;
|
|
}
|
|
stickersCat.content(cats);
|
|
if (stickersCat.length > 1) stickersCat.children[1].state.checked = true;
|
|
else if (stickersCat.length > 0) stickersCat.children[0].state.checked = true;
|
|
|
|
stickersList.clear();
|
|
}
|
|
|
|
function show() {
|
|
if (msgPreview) return;
|
|
if (view.state == View.WINDOW_SHOWN) return;
|
|
view.loadIcon("themepicsingle:msg");
|
|
view.state = View.WINDOW_SHOWN;
|
|
Native.OnChatShow();
|
|
}
|
|
|
|
function hide() {
|
|
if (msgPreview) return;
|
|
if (view.state == View.WINDOW_HIDDEN) return;
|
|
if (view.state != View.WINDOW_MAXIMIZED && view.state != View.WINDOW_FULL_SCREEN)
|
|
Native.UpdateChatXY(getBounds());
|
|
Native.OnChatHide();
|
|
view.state = View.WINDOW_HIDDEN;
|
|
}
|
|
|
|
function updateCaption(caption) {
|
|
if (msgPreview) return;
|
|
view.caption = caption;
|
|
}
|
|
|
|
function updateTranslation() {
|
|
translateWindow("menu.custom li > span, menu.context li > label, menu.custom hr > span, menu.custom caption, button > span, .indicator");
|
|
}
|
|
|
|
document.on("ready", () => {
|
|
updateTranslation();
|
|
}).on("closerequest", (e) => {
|
|
e.preventDefault();
|
|
hide();
|
|
});
|
|
|
|
function relTimesTimerFunc() {
|
|
if (settings.showRelTimes) updateRelTimes();
|
|
return settings.showRelTimes;
|
|
}
|
|
|
|
function setupRelTimesUpdates() {
|
|
if (settings.showRelTimes)
|
|
document.timer(20s, relTimesTimerFunc);
|
|
else
|
|
document.timer(0, relTimesTimerFunc);
|
|
}
|
|
|
|
function toggleRelTimes() {
|
|
settings.showRelTimes = !settings.showRelTimes;
|
|
Native.ToggleRelTimes();
|
|
updateRelTimes();
|
|
setupRelTimesUpdates();
|
|
}
|
|
|
|
function updateRelTimes() {
|
|
for (let page of pages.children)
|
|
for (let msg of page.chat.children) if (msg)
|
|
msg.$(".msgDate").text = Native.GetEventHeaderTime(parseFloat(msg.attr["time"]) || 0);
|
|
}
|
|
|
|
histmenu["self"] = $("#histmenu");
|
|
for (let menuItem of histmenu.self.$$("li, hr"))
|
|
histmenu[menuItem.id] = menuItem;
|
|
|
|
histmenu.self.on("click", function(e) {
|
|
this.ondismiss = () => {
|
|
rightClickedTime = "0";
|
|
rightClickedData = null;
|
|
};
|
|
|
|
var tgt = e.target;
|
|
|
|
//if (tgt.attr["parentmenu"] == "hm_contactmenu")
|
|
//Native.AddUIN2CL(rightClickedData.substr(4), tgt.attr["tag"]);
|
|
|
|
var owner = this.$o(":owns-popup");
|
|
switch (tgt.id) {
|
|
case "hm_contactmenu": {
|
|
var [x, y] = this.state.box("position", "margin", "window");
|
|
var uid = rightClickedData.substr(4);
|
|
document.post(() => openContactMenu(uid, x, y));
|
|
break;
|
|
}
|
|
case "hm_copylink": Native.CopyLink(rightClickedData); break;
|
|
case "hm_copy": owner.copySelected(); break;
|
|
case "hm_savepic": Native.SavePicture(rightClickedData); break;
|
|
case "hm_stickerpack": {
|
|
if (!CommonNative.CheckOnline()) return;
|
|
var fileid = rightClickedData.substr(rightClickedData.toLowerCase().indexOf(icqfilelnk) + icqfilelnk.length);
|
|
document.timer(100ms, () => {
|
|
openStickersManager();
|
|
startStickersSearch("fileid:" + fileid);
|
|
});
|
|
break;
|
|
}
|
|
case "hm_selectall": owner.selectAll(); break;
|
|
case "hm_resethist": owner.resetHistory(); break;
|
|
case "hm_viewinwin":
|
|
if (owner.selInfo.text != "") {
|
|
viewInWindow(owner.chatid, _("Selected"), owner.selInfo.text, "", "");
|
|
} else if (rightClickedTime != "0") {
|
|
var ev = Native.GetEvent(owner.chatid, rightClickedTime);
|
|
if (ev)
|
|
viewInWindow(owner.chatid, ev.caption, ev.text, ev.when, ev.img);
|
|
else
|
|
showAlert("warning", "This event was not saved in history");
|
|
}
|
|
break;
|
|
case "hm_saveastxt": Native.SaveAs(0); break;
|
|
case "hm_saveashtml": Native.SaveAs(1); break;
|
|
case "hm_addtofav": Native.AddLinkToFav(rightClickedData); break;
|
|
case "hm_edit": {
|
|
var chat = owner.chatid;
|
|
var data = rightClickedData;
|
|
var text = Native.GetChatMessageText(chat, data);
|
|
if (text === undefined) return;
|
|
var edited = openTextEdit(text, true);
|
|
if (edited !== null && edited !== "" && edited !== text)
|
|
Native.EditChatMessage(chat, data, edited);
|
|
break;
|
|
}
|
|
case "hm_delete": {
|
|
var option = createDialog(
|
|
"question",
|
|
_("Delete selected messages"),
|
|
_("Choose where messages should be deleted from"),
|
|
false, [
|
|
["forme", _("Server (for self only)")],
|
|
["forall", _("Server (for everyone)"), true],
|
|
["local", _("Local history")],
|
|
"mbCancel"
|
|
], "mbCancel", "mbCancel"
|
|
);
|
|
if (option === "mbCancel") return;
|
|
var msgids = [];
|
|
if (option === "forme" || option === "forall") {
|
|
var missing = false;
|
|
var nodes = owner.chat.$$("> .msgFull.selected");
|
|
if (nodes.length > 0)
|
|
for (let node of nodes)
|
|
if (node.attr["msgid"] !== "" && (parseFloat(node.attr["msgid"]) || 0) > 1000) // don't send ack IDs
|
|
msgids.push(node.attr["msgid"]);
|
|
else
|
|
missing = true;
|
|
if (missing && createDialog(
|
|
"asterisk",
|
|
_("Delete selected messages"),
|
|
_("Some messages are missing unique ID and cannot be removed from server. Proceed?"),
|
|
false, ["mbYes", "mbNo"], "mbYes", "mbNo"
|
|
) === "mbNo") return;
|
|
}
|
|
Native.DeleteMessages(owner.chatid, option, msgids);
|
|
break;
|
|
}
|
|
case "hm_reactions": Native.GetReactions(owner.chatid, rightClickedData); break;
|
|
case "hm_antispam": Native.AddToAntispam(); break;
|
|
case "hm_openwith": openChatWith(); break;
|
|
case "hm_viewinfo": openViewInfo(owner.chatid, owner.displayed); break;
|
|
case "hm_imgsmiles": Native.ToggleSmiles(); break;
|
|
case "hm_reltimes": toggleRelTimes(); break;
|
|
}
|
|
});
|
|
|
|
codemenu.on("click", function(e) {
|
|
function insertDecor(input, decor) {
|
|
var sel = input.selection.toString();
|
|
insertText(input, decor + sel + decor);
|
|
if (sel == "") input.execCommand("navigate:backward");
|
|
input.state.focus = true;
|
|
}
|
|
|
|
function insertCode(input, lang = "") {
|
|
if (lang == "")
|
|
insertText(input, "[code]\n" + input.selection.toString() + "\n[/code]");
|
|
else
|
|
insertText(input, "[code=" + lang + "]\n" + input.selection.toString() + "\n[/code]");
|
|
input.execCommand("navigate:line-start");
|
|
input.execCommand("navigate:backward");
|
|
input.state.focus = true;
|
|
}
|
|
|
|
var owner = this.$o(":owns-popup");
|
|
switch (e.target.id) {
|
|
case "cm_bold": insertDecor(owner, "*"); break;
|
|
case "cm_underline": insertDecor(owner, "_"); break;
|
|
case "cm_syntax_generic": insertCode(owner); break;
|
|
case "cm_syntax_html": insertCode(owner, "html"); break;
|
|
case "cm_syntax_js": insertCode(owner, "js"); break;
|
|
case "cm_syntax_css": insertCode(owner, "css"); break;
|
|
case "cm_syntax_json": insertCode(owner, "json"); break;
|
|
case "cm_syntax_php": insertCode(owner, "php"); break;
|
|
case "cm_syntax_delphi": insertCode(owner, "delphi"); break;
|
|
case "cm_syntax_c": insertCode(owner, "c"); break;
|
|
}
|
|
});
|
|
|
|
sendBtn.on("click", () => sendMessageToCurrent());
|
|
sendVariant.on("click", function(e) {
|
|
var [x, y, w, h] = this.parent.state.box("rectw", "border", "window");
|
|
var onedip = 1dip.valueOf();
|
|
this.parent.popup(sendmenu, {
|
|
anchorAt: 7,
|
|
x: x - onedip,
|
|
y: y + h - onedip
|
|
});
|
|
});
|
|
sendmenu.on("click", (e) => {
|
|
sendMessageToCurrent(parseInt(e.target.value));
|
|
});
|
|
|
|
closeBtn.on("click", () => closePages());
|
|
closeVariant.on("click", function(e) {
|
|
var [x, y, w, h] = this.parent.state.box("rectw", "border", "window");
|
|
var onedip = 1dip.valueOf();
|
|
this.parent.popup(closemenu, {
|
|
anchorAt: 7,
|
|
x: x - onedip,
|
|
y: y + h - onedip
|
|
});
|
|
});
|
|
closemenu.on("click", (e) => {
|
|
closePages(parseInt(e.target.value));
|
|
});
|
|
|
|
chatButtons["self"] = $("#chatButtons");
|
|
for (let btn of chatButtons.self.$$("> div > div"))
|
|
chatButtons[btn.id] = btn.parent;
|
|
|
|
chatButtons.self.on("^mouseup", function(e) {
|
|
var btn = e.target.id ? e.target.parent : e.target;
|
|
var pic = e.target.id ? e.target : e.target.$("> div");
|
|
if (!btn || !pic) return;
|
|
var [x, y] = btn.state.box("position", "border", "desktop");
|
|
x -= 3; y -= 6;
|
|
var st = e.propButton;
|
|
if (pic.id == "smilesBtn") { openSmiles(); return; }
|
|
else if (pic.id == "emojiBtn") { openEmoji(); return; }
|
|
else if (pic.id == "stickersBtn") {
|
|
if (e.propButton)
|
|
openStickersManager();
|
|
else
|
|
openStickers();
|
|
return;
|
|
} else if (pic.id == "searchBtn") {
|
|
openSearch(e.propButton);
|
|
return;
|
|
} else if (pic.id == "prefBtn") {
|
|
openPrefs(e.propButton ? "Plugins" : "Chat", false);
|
|
return;
|
|
} else if (pic.id == "contactsBtn") {
|
|
selectContactsToSend();
|
|
return;
|
|
} else if (pic.id == "quoteBtn") {
|
|
makeQuote(st, e.ctrlKey);
|
|
return;
|
|
} else if (pic.id == "fileBtn" && st) {
|
|
openPrefs("Other", false);
|
|
return;
|
|
}
|
|
if (pic.id == "singleBtn") st = !chatButtons.singleBtn.state.checked;
|
|
Native.ChatButtonClick(pic.id, st, x, y);
|
|
});
|
|
|
|
chatButtons.fileBtn.on("click", function(e) {
|
|
this.popup(filemenu, {
|
|
anchorAt: 1,
|
|
popupAt: 7
|
|
});
|
|
});
|
|
filemenu.on("click", (e) => {
|
|
uploadFile(parseInt(e.target.value));
|
|
});
|
|
|
|
var ax, ay = 0;
|
|
var cx, cy = 0;
|
|
var draggingSnap = false;
|
|
const DRAG_THRESHOLD = 3;
|
|
var monitors = [];
|
|
var snapshots = [];
|
|
|
|
function requestSnapshotAction() {
|
|
var result = createDialog(
|
|
"question",
|
|
_("Action required"),
|
|
_("Choose what to do with the screenshot"),
|
|
false, [
|
|
["upload", _("Upload to server")],
|
|
["save", _("Save image to file")],
|
|
"mbCancel"
|
|
], "upload", "mbCancel"
|
|
);
|
|
if (result == "upload")
|
|
Native.UploadLastSnapshot();
|
|
else if (result == "save")
|
|
Native.SaveAs(2);
|
|
else
|
|
Native.DeleteSnapshot();
|
|
}
|
|
|
|
function captureRegion() {
|
|
var [sx, sy, ex, ey] = [Math.min(ax, cx), Math.min(ay, cy), Math.max(ax, cx), Math.max(ay, cy)];
|
|
var cropWidth = ex - sx + 1;
|
|
var cropHeight = ey - sy + 1;
|
|
if (cropWidth <= 1 && cropHeight <= 1) return;
|
|
|
|
document.timer(500ms, () => {
|
|
for (var m = 0; m < View.screens; ++m)
|
|
snapshots.push(View.screenBox(m, "snapshot"));
|
|
|
|
var img = new Graphics.Image(cropWidth, cropHeight, function(gfx) {
|
|
for (var m = 0; m < View.screens; ++m)
|
|
gfx.draw(snapshots[m], {
|
|
x: monitors[m].x - sx,
|
|
y: monitors[m].y - sy,
|
|
srcX: 0,
|
|
srcY: 0,
|
|
srcWidth: monitors[m].w,
|
|
srcHeight: monitors[m].h
|
|
});
|
|
});
|
|
img.toBytes(settings.screenshotFormatSymbol, 95).save(settings.cachePath + settings.screenshotFilename);
|
|
|
|
snapshots = [];
|
|
requestSnapshotAction();
|
|
});
|
|
}
|
|
|
|
function closeSnapLayer() {
|
|
if (snapLayer == null) return false;
|
|
draggingSnap = false;
|
|
snapLayer.state.capture(false);
|
|
snapLayer.paintForeground = null;
|
|
snapLayer.off(".tracker");
|
|
snapLayer.remove();
|
|
snapLayer = null;
|
|
return true;
|
|
}
|
|
|
|
function snapLayerMouseMove(e) {
|
|
if (!draggingSnap) {
|
|
if (Math.abs(ax - e.x) > DRAG_THRESHOLD || Math.abs(ay - e.y) > DRAG_THRESHOLD) {
|
|
draggingSnap = true;
|
|
cx = e.x;
|
|
cy = e.y;
|
|
snapLayer.paintForeground = (gfx) => {
|
|
gfx.strokeWidth = 1dip;
|
|
gfx.strokeStyle = Color.RGB(127, 127, 127);
|
|
gfx.fillStyle = Color.RGB(127, 127, 127, 127);
|
|
gfx.translate(0.5, 0.5)
|
|
.fillRect(ax, ay, cx - ax + 1, cy - ay + 1)
|
|
.strokeRect(ax, ay, cx - ax + 1, cy - ay + 1);
|
|
};
|
|
snapLayer.state.capture(true);
|
|
snapLayer.requestPaint();
|
|
return true;
|
|
}
|
|
return false;
|
|
} else {
|
|
cx = e.x;
|
|
cy = e.y;
|
|
snapLayer.requestPaint();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
chatButtons.snapshotBtn.on("click", function(e) {
|
|
this.popup(snapmenu, {
|
|
anchorAt: 1,
|
|
popupAt: 7
|
|
});
|
|
});
|
|
snapmenu.on("click", function(e) {
|
|
var val = parseInt(e.target.value);
|
|
var screen = val % 10 - 1;
|
|
var action = Math.floor(val / 10);
|
|
|
|
// Entire screen
|
|
if (action == 1) {
|
|
document.timer(500ms, () => {
|
|
var img = View.screenBox(screen, "snapshot");
|
|
img.toBytes(settings.screenshotFormatSymbol, 95).save(settings.cachePath + settings.screenshotFilename);
|
|
requestSnapshotAction();
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Selected region
|
|
if (!snapLayer) {
|
|
snapLayer = Element.create("div");
|
|
snapLayer.classList.add("snaplayer");
|
|
snapLayer.on("^mousedown.tracker", function(e) {
|
|
if (e.mainButton) {
|
|
ax = e.x;
|
|
ay = e.y;
|
|
cx = ax;
|
|
cy = ay;
|
|
view.focus = this;
|
|
this.on("mousemove.tracker", snapLayerMouseMove);
|
|
this.on("mouseup.tracker", () => {
|
|
captureRegion();
|
|
closeSnapLayer();
|
|
});
|
|
return true;
|
|
}
|
|
});
|
|
document.body.append(snapLayer);
|
|
}
|
|
|
|
var minx = 0,
|
|
miny = 0,
|
|
maxx = 0,
|
|
maxy = 0;
|
|
monitors = [];
|
|
for (var m = 0; m < View.screens; ++m ) {
|
|
var [x, y, w, h] = View.screenBox(m, "frame", "rectw");
|
|
minx = Math.min(minx, x);
|
|
miny = Math.min(miny, y);
|
|
maxx = Math.max(maxx, x + w);
|
|
maxy = Math.max(maxy, y + h);
|
|
monitors.push({ x: x, y: y, w: w, h: h });
|
|
}
|
|
snapLayer.takeOff({
|
|
x: minx, y: miny,
|
|
width: maxx - minx, height: maxy - miny,
|
|
relativeTo : "screen",
|
|
window: "popup"
|
|
});
|
|
snapLayer.state.focusable = true;
|
|
snapLayer.state.focus = true;
|
|
}).on("currentstatechange", function(e) {
|
|
if (e.target.parent && e.target.parent === subscreens) {
|
|
var modId = parseInt(e.target.value) - 11;
|
|
var [x, y, w, h] = View.screenBox(modId, "frame", "rectw");
|
|
monIndicator.style.display = "none";
|
|
monIndicator.takeOff();
|
|
monIndicator.content(<div>{modId + 1}</div>);
|
|
monIndicator.style.display = "block";
|
|
monIndicator.takeOff({
|
|
x: x + w - 50 - monIndicator.state.box("width", "border"), y: y + 40,
|
|
relativeTo : "screen",
|
|
window: "popup"
|
|
});
|
|
} else if (monIndicator.style.display != "none") {
|
|
monIndicator.style.display = "none";
|
|
monIndicator.takeOff();
|
|
}
|
|
});
|
|
|
|
subscreens.on("popupdismissing", function(e) {
|
|
monIndicator.style.display = "none";
|
|
monIndicator.takeOff();
|
|
});
|
|
|
|
if (View.screens > 1) {
|
|
for (var i = 1; i <= View.screens; i++)
|
|
subscreens.append(<li value={parseInt(subscreens.parent.value) + i}><span>{i}</span></li>);
|
|
} else {
|
|
subscreens.remove();
|
|
}
|
|
|
|
plugButtons.on("mouseup", "div[proc]", (e, el) => {
|
|
Native.PluginButtonClick(parseInt(el.attr["proc"]), el.attr["title"], e.buttons);
|
|
});
|
|
|
|
$("#sbOutbox").on("mouseup", () => openOutbox());
|
|
$("#sbTrlt").on("dblclick", (e) => { if (e.mainButton) Native.ToggleTranslit() });
|
|
|
|
var smilesGrid = $("#smilesGrid"),
|
|
smilesList = $("#smilesList");
|
|
emojiGrid = $("#emojiGrid"),
|
|
emojiCat = $("#emojiCat"),
|
|
emojiList = $("#emojiList"),
|
|
emojiPages = [],
|
|
stickersGrid = $("#stickersGrid"),
|
|
stickersCat = $("#stickersCat"),
|
|
stickersList = $("#stickersList"),
|
|
stickersPages = {};
|
|
|
|
emojiCat.on("click", ".catbtn", (e, el) => {
|
|
emojiList.list.detach();
|
|
emojiList.append(emojiPages[parseInt(el.attr["category"])]);
|
|
emojiList.initList(true);
|
|
});
|
|
|
|
stickersCat.on("click", ".catbtn", (e, el) => {
|
|
switchToStickerCat(el.attr["category"]);
|
|
});
|
|
|
|
stickersCat.on("keydown", ".catbtn", function(e) {
|
|
return true;
|
|
});
|
|
|
|
stickersCat.on("wheel", function(e) {
|
|
var dir = e.deltaY > 0 ? 1 : e.deltaY < 0 ? -1 : 0;
|
|
var posX = parseInt(stickersCat.scrollLeft + dir * settings.wheelVelocity * 48);
|
|
posX = Math.min(Math.max(0, posX), stickersCat.scrollWidth - stickersCat.state.box("width", "padding"));
|
|
stickersCat.scrollTo({
|
|
position: [posX, 0],
|
|
behavior: "smooth"
|
|
});
|
|
return true;
|
|
});
|
|
|
|
stickersList.on("change", "#stsearch", (e, el) => {
|
|
sKeywords = el.value;
|
|
if (sKeywords == "")
|
|
searchStickers();
|
|
else
|
|
document.timer(1s, searchStickers);
|
|
});
|
|
|
|
document.body.on("click", ".imagegrid .sesbtn, .imagegrid .caption", (e, el) => {
|
|
var el2 = el.attr["class"] == "caption" ? el.prior : el;
|
|
if (el.$p("#stickersList"))
|
|
Native.SendStickerToCurrent(el2.attr["sticker"]);
|
|
else
|
|
getCurrentPage().addToInput(el2.attr["text"]);
|
|
el.$p(".imagegrid").state.popup = false;
|
|
});
|
|
|
|
function overlayAction(keyCode) {
|
|
if (overlay && overlay.state.visible)
|
|
switch (keyCode) {
|
|
case Event.VK_RIGHT:
|
|
case Event.VK_UP:
|
|
case Event.VK_NEXT: {
|
|
if (overlay.length == 1) return closePreview();
|
|
var curr = overlay.$("img.visible");
|
|
curr.classList.remove("visible");
|
|
if (curr.next)
|
|
curr.next.classList.add("visible");
|
|
else
|
|
curr.parent.first.classList.add("visible");
|
|
return true;
|
|
}
|
|
case Event.VK_LEFT:
|
|
case Event.VK_DOWN:
|
|
case Event.VK_PRIOR: {
|
|
if (overlay.length == 1) return closePreview();
|
|
var curr = overlay.$("img.visible");
|
|
curr.classList.remove("visible");
|
|
if (curr.prior)
|
|
curr.prior.classList.add("visible");
|
|
else
|
|
curr.parent.last.classList.add("visible");
|
|
return true;
|
|
}
|
|
default:
|
|
return closePreview();
|
|
};
|
|
}
|
|
|
|
document.body.on("^keyup", (e) => overlayAction(e.keyCode));
|
|
|
|
tabs.on("wheel", function(e) {
|
|
var dir = e.deltaY < 0 ? 1 : e.deltaY > 0 ? -1 : 0;
|
|
var scrollPosX = parseInt(tabs.scrollLeft - dir * settings.wheelVelocity * tabs.state.box("width") / 20)
|
|
scrollPosX = Math.min(Math.max(0, scrollPosX), tabs.scrollWidth - tabs.clientWidth);
|
|
tabs.scrollTo({
|
|
position: [scrollPosX, 0],
|
|
behavior: "smooth"
|
|
});
|
|
return true;
|
|
});
|
|
|
|
pages.on("^mousedown", ".linkedImgWrapper, .msgEmbeddedImages", (e, el) => {
|
|
var page = el.$p(".page");
|
|
if (page != null) {
|
|
page.clearSelection();
|
|
page.saveSelection();
|
|
}
|
|
return !(e.target && e.target.tag == "img" && e.target.classList.contains("loaded"));
|
|
});
|
|
|
|
function openOverlay(e, el) {
|
|
if (!e.mainButton || !e.isOnIcon) return false;
|
|
|
|
var imgLinks = [];
|
|
var msg = el.$p(".msgFull");
|
|
if (msg) {
|
|
var linkedImgs = msg.$$("img[src^=\"download:\"]");
|
|
for (let linkedImg of linkedImgs)
|
|
imgLinks.push(linkedImg.attr["src"]);
|
|
|
|
var embeddedImgs = msg.$$("img[src^=\"embedded:\"]");
|
|
for (let embeddedImg of embeddedImgs)
|
|
imgLinks.push(embeddedImg.attr["src"]);
|
|
} else {
|
|
imgLinks.push(el.attr["src"]);
|
|
}
|
|
|
|
if (!overlay) {
|
|
overlay = Element.create(<div class="imgoverlay"></div>);
|
|
overlay.on("^mousedown", closePreview);
|
|
overlay.on("wheel", function(e) {
|
|
if (!this.classList.contains("active")) return false;
|
|
return overlayAction(e.deltaY > 0 ? Event.VK_UP : Event.VK_DOWN);
|
|
});
|
|
|
|
document.body.append(overlay);
|
|
|
|
for (let imgLink of imgLinks)
|
|
overlay.append(<img src={imgLink} class={imgLink == el.attr["src"] ? "visible" : ""} />);
|
|
}
|
|
var [sx, sy, sw, sh] = view.screenBox("frame", "rectw");
|
|
overlay.takeOff({
|
|
x: sx, y: sy,
|
|
width: sw, height: sh,
|
|
relativeTo : "screen",
|
|
window: "popup"
|
|
});
|
|
overlay.classList.add("active");
|
|
overlay.state.focusable = true;
|
|
overlay.state.focus = true;
|
|
return true;
|
|
}
|
|
|
|
pages.on("mousedown", ".avatar > img", openOverlay);
|
|
|
|
pages.on("click", "a[kind]", (e, el) => {
|
|
if (el.attr["kind"] == "other" && el.prior) {
|
|
linkinfo.first.clear();
|
|
linkinfo.last.clear();
|
|
linkinfo.first.append(<div class="row"><span class="spinner"><img src="resource:spinner" /></span></div>);
|
|
var [x, y, w, h] = el.state.box("rectw", "margin", "window");
|
|
el.popup(linkinfo, {
|
|
anchorAt: 7,
|
|
x: x + 6dip.valueOf(),
|
|
y: y + h + 20dip.valueOf()
|
|
});
|
|
var infos = Native.GetLinkInfo(el.attr["src"]);
|
|
linkinfo.first.clear();
|
|
for (let info of infos)
|
|
if (info.param == "AVATAR") {
|
|
linkinfo.last.append(<img src={info.value} />);
|
|
} else if (info.param == "TRANSCRIPTION") {
|
|
linkinfo.first.append(<div class="row"><span colspan="2" class="param separated">{_("Transcription")}</span></div>);
|
|
linkinfo.first.append(<div class="row"><span colspan="2" state-html={info.value}></span></div>);
|
|
} else {
|
|
linkinfo.first.append(<div class="row"><span class="param" colspan={info.value == "" ? 2 : 1}>{info.param}</span><span state-html={info.value}></span></div>);
|
|
}
|
|
linkinfo.state.disabled = true;
|
|
return;
|
|
}
|
|
|
|
var page = getCurrentPage();
|
|
if (videoview) {
|
|
videoview.detach();
|
|
page.chatBackground.insert(videoview);
|
|
} else {
|
|
videoview = Element.create(<VideoPreview />);
|
|
page.chatBackground.insert(videoview);
|
|
videoview.initChildren();
|
|
}
|
|
videoview.setLoadingText(_("Retrieving list of available video formats..."));
|
|
videoview.show();
|
|
videoview.page = page;
|
|
videoview.src = el.attr["src"];
|
|
videoview.kind = el.attr["kind"];
|
|
videoview.init = false;
|
|
videoview.timer(300ms, () => {
|
|
var links;
|
|
if (videoview.kind == "youtube")
|
|
links = Native.GetYoutubeLinks(videoview.src);
|
|
else if (videoview.kind == "vimeo")
|
|
links = Native.GetVimeoLinks(videoview.src);
|
|
|
|
if (!links || links.length == 0) {
|
|
videoview.error();
|
|
return;
|
|
}
|
|
|
|
if (links.length == 1) {
|
|
var linkdata = JSON.parse(links[0]);
|
|
videoview.title = linkdata.title;
|
|
videoview.startVideo(linkdata.url, linkdata.format, linkdata.codecs);
|
|
return;
|
|
}
|
|
|
|
var htmldata = [];
|
|
for (let link of links) {
|
|
var linkdata = JSON.parse(link);
|
|
videoview.title = linkdata.title;
|
|
htmldata.push(<div format={linkdata.format} codecs={linkdata.codecs} url={linkdata.url}>{linkdata.format}</div>);
|
|
}
|
|
var data = [];
|
|
data.push(_("Choose video format"));
|
|
data.push(<div id="videoformats">{htmldata}</div>);
|
|
data.push(<a href="#" close="">{_("Close")}</a>);
|
|
videoview.setLoadingText(data, true);
|
|
});
|
|
});
|
|
|
|
myreactions.on("click", function(e) {
|
|
if (!e.target) return;
|
|
this.state.popup = false;
|
|
var el = e.target.attr["option"] ? e.target : e.target.$p("div[option]");
|
|
if (el)
|
|
if (el.classList.contains("my"))
|
|
Native.RemoveReaction(this.attr["chatid"], this.attr["msgid"]);
|
|
else
|
|
Native.AddReaction(this.attr["chatid"], this.attr["msgid"], el.attr["option"]);
|
|
});
|
|
|
|
var isModKey = false;
|
|
document.on("^mouseup", (e) => {
|
|
if (e.buttons == 8) // browser backward
|
|
setCurrentPage(-1);
|
|
else if (e.buttons == 16) // browser forward
|
|
setCurrentPage(+1);
|
|
}).on("keydown", (e) => {
|
|
if (snapLayer) { closeSnapLayer(); return; }
|
|
if (e.keyCode == Event.VK_ESCAPE && !document.body.isDragActive) {
|
|
if (overlay && overlay.state.visible)
|
|
closePreview();
|
|
else
|
|
closeForm();
|
|
} else if (e.keyCode == Event.VK_C && e.ctrlKey) {
|
|
var page = getCurrentPage();
|
|
if (page && page.pagetype == "im") page.copySelected();
|
|
return true;
|
|
}
|
|
}).on("^keydown", function(e) {
|
|
isModKey = e.ctrlKey || e.shiftKey;
|
|
if (e.keyCode == Event.VK_CONTEXT_MENU) {
|
|
var page = getCurrentPage();
|
|
if (page && page.pagetype == "im")
|
|
if (!page.input.state.focus && $$(":popup").length == 0)
|
|
openContactMenu(page.chatid);
|
|
}
|
|
else if ((e.keyCode == Event.VK_W || e.keyCode == Event.VK_F4) && e.ctrlKey) closePages();
|
|
else if (e.keyCode == 170 /* VK_BROWSER_SEARCH */ ||
|
|
(e.keyCode == Event.VK_F && e.ctrlKey)) {
|
|
openSearch(false);
|
|
return true;
|
|
} else if (e.keyCode == Event.VK_S && e.ctrlKey && e.shiftKey) openStickers();
|
|
else if (e.keyCode == Event.VK_S && e.ctrlKey) openSmiles();
|
|
else if (e.keyCode == Event.VK_E && e.ctrlKey) openEmoji();
|
|
else if (e.keyCode == Event.VK_P && e.altKey) openPrefs("Chat", false);
|
|
else if (e.keyCode == Event.VK_I && e.altKey) openInfo();
|
|
else if (e.keyCode == Event.VK_Q && e.altKey) makeQuote(false, e.ctrlKey);
|
|
else if (e.keyCode == Event.VK_M && e.altKey) {
|
|
var page = getCurrentPage();
|
|
if (page && page.pagetype == "im") Native.ToggleSmiles();
|
|
} else if (e.keyCode == Event.VK_TAB && e.ctrlKey) {
|
|
setCurrentPage(e.shiftKey ? -1 : +1);
|
|
return true;
|
|
} else if (e.keyCode == 166 /* VK_BROWSER_BACK */ ||
|
|
(e.keyCode == Event.VK_LEFT && e.altKey) ||
|
|
(e.keyCode == Event.VK_F5 && e.ctrlKey)) {
|
|
setCurrentPage(-1);
|
|
return true;
|
|
} else if (e.keyCode == 167 /* VK_BROWSER_FORWARD */ ||
|
|
(e.keyCode == Event.VK_RIGHT && e.altKey) ||
|
|
(e.keyCode == Event.VK_F6 && e.ctrlKey)) {
|
|
setCurrentPage(+1);
|
|
return true;
|
|
}
|
|
}).on("keyup", () => { isModKey = false })
|
|
.on("appendevent prependevent updateevent updatesmiles showsearchhere showhistnotif updatemsgstatus switchtopage updatespell", function(e) {
|
|
if (e.type == "appendevent") e.target.addEvents(e.data, false);
|
|
else if (e.type == "prependevent") e.target.addEvents(e.data, true);
|
|
else if (e.type == "updateevent") e.target.updateEvents(e.data);
|
|
else if (e.type == "updatesmiles") { for (let page of pages.children) if (page.pagetype == "im") page.updateSmiles(); }
|
|
else if (e.type == "showhistnotif") e.target.showServerHistoryNotif();
|
|
else if (e.type == "showsearchhere") showSearchHere();
|
|
else if (e.type == "updatemsgstatus" && e.data !== null) e.target.updateMsgStatus(e.data);
|
|
else if (e.type == "switchtopage") e.target.setCurrent();
|
|
else if (e.type == "updatespell") e.target.updateSpelling(e.data);
|
|
})
|
|
.on("mouseenter", (e) => {
|
|
Native.OnChatMouseEnter();
|
|
}).on("mouseleave", (e) => {
|
|
Native.OnChatMouseLeave();
|
|
});
|
|
|
|
view.on("activate", (e) => {
|
|
isActive = e.reason !== 0;
|
|
Native.OnChatActivate(isActive);
|
|
}).on("size", () => {
|
|
if (videoview && videoview.init) videoview.alignPlayerControls();
|
|
Native.OnChatResize();
|
|
});
|
|
</script>
|
|
<popup id="videoinfo"></popup>
|
|
</head>
|
|
<body>
|
|
<div id="tabs"></div>
|
|
<div id="backTabs"></div>
|
|
<div id="pages" class="renderOption"></div>
|
|
<div id="buttonBar">
|
|
<div class="ddgroup first">
|
|
<button id="sendBtn" class="ddbutton"><div pic="status.unk"></div><span>Send</span></button>
|
|
<div id="sendVariant" class="ddpopup"></div>
|
|
</div>
|
|
<div class="ddgroup">
|
|
<button id="closeBtn" class="ddbutton"><div pic="close"></div><span>Close</span></button>
|
|
<div id="closeVariant" class="ddpopup"></div>
|
|
</div>
|
|
<div id="chatButtons">
|
|
<div hint="Search in history Ctrl+F"><div id="searchBtn" pic="search"></div></div>
|
|
<div hint="Shows smiles as pictures Alt+M, Smiles menu Ctrl+S"><div id="smilesBtn" pic="smiles"></div></div>
|
|
<div hint="Shows emoji as pictures Alt+M, Emoji menu Ctrl+E"><div id="emojiBtn" pic="emoji"></div></div>
|
|
<div hint="Shows stickers menu Ctrl+Shift+S, Stickers manager - RMB"><div id="stickersBtn" pic="stickers"></div></div>
|
|
<div hint="Preferences for the chat window Alt+P"><div id="prefBtn" pic="preferences"></div></div>
|
|
<div hint="Info about this user Alt+I"><div id="infoBtn" pic="info"></div></div>
|
|
<div hint="Quote previous received messages Alt+Q"><div id="quoteBtn" pic="quote"></div></div>
|
|
<div hint="Auto-close chat with this contact after sending a message. You can choose a default setting from the <Chat settings> - <Single messages>." class="checkable"><div id="singleBtn" pic="1.msg"></div></div>
|
|
<div hint="Send contacts"><div id="contactsBtn" pic="contacts"></div></div>
|
|
<div hint="Send pics"><div id="picsBtn" pic="pics"></div></div>
|
|
<div hint="Send file"><div id="fileBtn" pic="file"></div></div>
|
|
<div hint="Make screenshot"><div id="snapshotBtn" pic="snapshot"></div></div>
|
|
<div hint="Buzz contact"><div id="buzzBtn" pic="buzz"></div></div>
|
|
</div>
|
|
<div id="plugButtons"></div>
|
|
</div>
|
|
<status-bar>
|
|
<div id="sbChars"></div>
|
|
<div id="sbOutbox" hint="Outgoing messages queue"><div pic="outbox.empty"></div></div>
|
|
<div id="sbTrlt" hint="Transliteration (double click to toggle)"><div>TRLT</div></div>
|
|
<div id="sbCrypt" hint="Encryption status for current contact"><div id="pic"></div></div>
|
|
<div id="sbHint"></div>
|
|
</status-bar>
|
|
<menu id="histmenu" class="context custom">
|
|
<li id="hm_contactmenu"><div pic="ssi"></div><span>Contact menu...</span></li>
|
|
<li id="hm_copylink"><div pic="url"></div><span>Copy link</span></li>
|
|
<li id="hm_copy"><div pic="copy"></div><span>Copy</span></li>
|
|
<li id="hm_savepic"><div pic="pics"></div><span>Save pic</span></li>
|
|
<li id="hm_stickerpack"><div pic="stickers"></div><span>Find sticker pack</span></li>
|
|
<li id="hm_selectall"><div pic="select.all"></div><span>Select all</span></li>
|
|
<li id="hm_viewinwin"><div pic="msg"></div><span>View message in window</span></li>
|
|
<li id="hm_saveas"><div pic="save"></div><span>Save as</span><menu><li id="hm_saveastxt"><span>Text</span></li><li id="hm_saveashtml"><span>HTML</span></li></menu></li>
|
|
<li id="hm_addtofav"><div pic="theme"></div><span>Add link to favorites</span></li>
|
|
<li id="hm_edit"><div pic="typing"></div><span>Edit message</span></li>
|
|
<li id="hm_delete"><div pic="delete"></div><span>Delete selected</span></li>
|
|
<li id="hm_reactions"><div pic="reaction"></div><span>Refresh reactions</span></li>
|
|
<hr id="hm_hr_textsel">
|
|
<li id="hm_antispam"><div pic="msgbad"></div><span>Add to antispam</span></li>
|
|
<hr>
|
|
<li id="hm_imgsmiles"><div pic="smiles"></div><span>Show smiles and emoji as images</span></li>
|
|
<li id="hm_reltimes"><div pic="reltime"></div><span>Show relative timestamps</span></li>
|
|
<li id="hm_resethist"><div pic="reload.theme.list"></div><span>Reset loaded history</span></li>
|
|
<hr>
|
|
<li id="hm_openwith"><div pic="history"></div><span>Open chat with...</span></li>
|
|
<li id="hm_viewinfo"><div pic="info"></div><span>View info</span></li>
|
|
</menu>
|
|
<menu id="codemenu" class="context custom">
|
|
<li id="cm_bold"><span>Bold</span></li>
|
|
<li id="cm_underline"><span>Underlined</span></li>
|
|
<li id="cm_syntax"><span>Syntax highligh</span>
|
|
<menu>
|
|
<li id="cm_syntax_generic"><span>Generic</span></li>
|
|
<li id="cm_syntax_html"><span>HTML</span></li>
|
|
<li id="cm_syntax_js"><span>JavaScript</span></li>
|
|
<li id="cm_syntax_css"><span>CSS</span></li>
|
|
<li id="cm_syntax_json"><span>JSON</span></li>
|
|
<li id="cm_syntax_php"><span>PHP</span></li>
|
|
<li id="cm_syntax_delphi"><span>Delphi</span></li>
|
|
<li id="cm_syntax_c"><span>C</span></li>
|
|
</menu></li>
|
|
</menu>
|
|
<menu id="sendmenu" class="context custom">
|
|
<li value="1"><span>Multiple send</span></li>
|
|
<li value="2"><span>Send to all opened chats</span></li>
|
|
</menu>
|
|
<menu id="closemenu" class="context custom">
|
|
<li value="1"><span>Close all</span></li>
|
|
<li value="2"><span>Close all but this one</span></li>
|
|
<li value="3"><span>Close all offline</span></li>
|
|
<li value="4"><span>Close and add to ignore list</span></li>
|
|
<li value="5"><span>Close and add to ignore list those not in CL</span></li>
|
|
</menu>
|
|
<menu id="filemenu" class="context custom">
|
|
<li value="1"><span>Upload file</span></li>
|
|
<li value="2"><span>Archive and upload file(s)</span></li>
|
|
</menu>
|
|
<menu id="snapmenu" class="context custom">
|
|
<li value="10"><span>Entire screen</span>
|
|
<menu id="subscreens"></menu></li>
|
|
<li value="20"><span>Selected area</span></li>
|
|
</menu>
|
|
<menu id="inputmenu" class="context">
|
|
<div id="suggest"></div>
|
|
<li name="edit:undo" command="edit:undo"><label>Undo</label><span class="accesskey">&platform-cmd-mod;+Z</span></li>
|
|
<hr/>
|
|
<li name="edit:cut" command="edit:cut"><label>Cut</label><span class="accesskey">&platform-cmd-mod;+X</span></li>
|
|
<li name="edit:copy" command="edit:copy"><label>Copy</label><span class="accesskey">&platform-cmd-mod;+C</span></li>
|
|
<li name="edit:paste" command="edit:paste"><label>Paste</label><span class="accesskey">&platform-cmd-mod;+V</span></li>
|
|
<hr/>
|
|
<li name="edit:selectall" command="edit:selectall"><label>Select All</label><span class="accesskey">&platform-cmd-mod;+A</span></li>
|
|
</menu>
|
|
<include src="menus/contactmenu.htm" />
|
|
<popup class="imagegrid" id="smilesGrid">
|
|
<div class="imagelist" id="smilesList"><div></div></div>
|
|
</popup>
|
|
<popup class="imagegrid" id="emojiGrid">
|
|
<div class="imagecat" id="emojiCat"></div>
|
|
<div class="imagelist" id="emojiList"></div>
|
|
</popup>
|
|
<popup class="imagegrid" id="stickersGrid">
|
|
<div class="imagecat" id="stickersCat"></div>
|
|
<div class="imagelist" id="stickersList"></div>
|
|
</popup>
|
|
<div id="monIndicator"><div>2</div></div>
|
|
<div id="dragHtml" class="customHint dragHint"><span></span></div>
|
|
<div id="dragFile" class="customHint dragHint">
|
|
<div pic="file"></div>
|
|
<span></span>
|
|
</div>
|
|
<popup id="patches" class="customHint patchesHint renderOption"></popup>
|
|
<popup id="myreactions" class="customHint reactionsHint">
|
|
<div option="0"><div>👍</div><div class="delete" pic="deleted"></div></div>
|
|
<div option="1"><div>❤</div><div class="delete" pic="deleted"></div></div>
|
|
<div option="2"><div>🤣</div><div class="delete" pic="deleted"></div></div>
|
|
<div option="3"><div>😳</div><div class="delete" pic="deleted"></div></div>
|
|
<div option="4"><div>😢</div><div class="delete" pic="deleted"></div></div>
|
|
<div option="5"><div>😡</div><div class="delete" pic="deleted"></div></div>
|
|
</popup>
|
|
<popup id="linkinfo" class="customHint infoHint">
|
|
<div class="table"></div>
|
|
<div class="miniavatar"></div>
|
|
</popup>
|
|
</body>
|
|
</html> |