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.
1753 lines
52 KiB
HTML
1753 lines
52 KiB
HTML
<html>
|
|
<head>
|
|
<include src="common.htm" />
|
|
<style src="main.css" />
|
|
<style id="usercss" />
|
|
<style id="dyncss" />
|
|
<script src="hint.js" />
|
|
<script src="menus/contactmenu.js" />
|
|
<script>
|
|
const Native = View.share.MainNative;
|
|
view.uniqueid = "main";
|
|
view.minSize = [150, 75];
|
|
CommonNative.HideTaskbarButton(document);
|
|
setupWindow(true, false, false);
|
|
|
|
var body = document.body,
|
|
mainmenu = $("#mainmenu"),
|
|
statusmenu = $("#statusmenu"),
|
|
divmenu = $("#divmenu"),
|
|
groupmenu = $("#groupmenu"),
|
|
mstatus = $("#mstatus"),
|
|
mstatuses = $("#mstatuses"),
|
|
mthemes = $("#mthemes"),
|
|
mtoggle = $("#mtoggle"),
|
|
mshowemptygroups = $("#mshowemptygroups"),
|
|
cl = null,
|
|
bar = $("#bar"),
|
|
filter = $("#filter"),
|
|
clfilter = $("#clfilter"),
|
|
progress = $("#progress"),
|
|
|
|
windowMoving = false,
|
|
windowResizing = false,
|
|
keyword = "",
|
|
rebuilding = false,
|
|
dragging = false,
|
|
draggingOnline = false,
|
|
startX = 0, startY = 0,
|
|
startWinX = 0, startWinY = 0,
|
|
showIcons,
|
|
settings = {
|
|
onlineOnly: false,
|
|
combinedGroups: false,
|
|
showGroups: true,
|
|
showEmptyGroups: false,
|
|
showHints: true,
|
|
showStatusMsgMenu: true,
|
|
indentRoster: true,
|
|
autosize: false,
|
|
autosizeFull: false,
|
|
autosizeUp: false,
|
|
//animatedGroups: false,
|
|
collapseGroups: true,
|
|
blinkEnabled: true,
|
|
blinkWithStatus: true,
|
|
blinkTime: 600,
|
|
sortBy: 2,
|
|
barPos: 0,
|
|
filterPos: 2,
|
|
mainCSS: ""
|
|
},
|
|
hintFunc = function(e) {
|
|
if (settings.showHints && !dragging)
|
|
if (e.type == "mouseenter" || e.type == "mousedown") {
|
|
var opt = this.$p("option");
|
|
if (e.type == "mousedown") {
|
|
hideHint();
|
|
return false;
|
|
}
|
|
if (opt.isContact() || opt.isGroup()) {
|
|
showHint(this, opt.data);
|
|
return true;
|
|
}
|
|
return false;
|
|
} else if (e.type == "mouseleave") {
|
|
document.timer(200, hideHint);
|
|
}
|
|
}
|
|
|
|
class TreeNode extends Element {
|
|
ignoreBlur = false;
|
|
oldTitle;
|
|
|
|
this(props, kids) {
|
|
this.data = props.data;
|
|
}
|
|
|
|
// componentUpdate(newData = null) {
|
|
// if (newData) this.data = newData;
|
|
// this.patch(this.render());
|
|
// }
|
|
|
|
isDiv() {
|
|
return this.data.kind == NODE_DIV;
|
|
}
|
|
|
|
isGroup() {
|
|
return this.data.kind == NODE_GROUP;
|
|
}
|
|
|
|
isContact() {
|
|
return this.data.kind == NODE_CONTACT;
|
|
}
|
|
|
|
isLocal() {
|
|
return this.data.local;
|
|
}
|
|
|
|
needAuth() {
|
|
return this.data.needAuth;
|
|
}
|
|
|
|
getDiv() {
|
|
return this.data.divisor;
|
|
}
|
|
|
|
getGroup() {
|
|
return this.data.group;
|
|
}
|
|
|
|
getUID() {
|
|
return this.data.uid;
|
|
}
|
|
|
|
getOrder() {
|
|
return this.data.order;
|
|
}
|
|
|
|
getStatus() {
|
|
return this.data.status.code;
|
|
}
|
|
|
|
getLastAct() {
|
|
return this.data.lastActivity;
|
|
}
|
|
|
|
getCaption() {
|
|
if (this.isDiv())
|
|
return this.$("> caption .divtitle");
|
|
else if (this.isGroup())
|
|
return this.$("> caption .title");
|
|
else if (this.isContact())
|
|
return this.$("> caption .displayed");
|
|
}
|
|
|
|
startEditing(reason = GA_NONE) {
|
|
if (!this.isGroup() && !this.isContact()) return;
|
|
this.scrollIntoView({
|
|
block : "nearest",
|
|
behavior: "instant"
|
|
});
|
|
cl.refreshVisible();
|
|
this.post(() => {
|
|
this.classList.add("editing");
|
|
var title = this.getCaption();
|
|
var length = title.text.length;
|
|
this.oldTitle = title.text;
|
|
title.state.focus = true;
|
|
title.edit.selectRange(0, length);
|
|
title.on("blur", (e) => {
|
|
if (!this.ignoreBlur) this.stopEditing(true, reason);
|
|
}).on("keydown", (e) => {
|
|
if (e.keyCode == Event.VK_RETURN) {
|
|
this.stopEditing(true, reason);
|
|
return true;
|
|
} else if (e.keyCode == Event.VK_ESCAPE) {
|
|
this.stopEditing(false, reason);
|
|
return true;
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
cancelEditing(opt, title, restore) {
|
|
if (restore) title.text = opt.oldTitle;
|
|
opt.oldTitle = "";
|
|
opt.classList.remove("editing");
|
|
title.off("blur");
|
|
title.off("keydown");
|
|
cl.state.focus = true;
|
|
}
|
|
|
|
stopEditing(save, reason = GA_NONE) {
|
|
var title = this.getCaption();
|
|
var newTitle = title.text.trim();
|
|
if (save && newTitle != "") {
|
|
var res = true;
|
|
if (title.text != this.oldTitle)
|
|
if (this.isContact())
|
|
res = Native.RenameContact(this.getUID(), newTitle, reason);
|
|
else if (this.isGroup())
|
|
res = Native.RenameGroup(this.getGroup(), newTitle, reason);
|
|
if (res === false) {
|
|
this.cancelEditing(this, title, true);
|
|
} else if (res !== true) {
|
|
this.ignoreBlur = true;
|
|
createDialog(
|
|
"exclamation",
|
|
_("Warning"),
|
|
res
|
|
);
|
|
this.post(() => {
|
|
this.getCaption().state.focus = true;
|
|
this.ignoreBlur = false;
|
|
});
|
|
} else {
|
|
this.cancelEditing(this, title, false);
|
|
}
|
|
} else {
|
|
this.cancelEditing(this, title, true);
|
|
if (reason == GA_ADD) Native.RemoveGroup(this.getGroup());
|
|
}
|
|
}
|
|
|
|
componentDidMount() {
|
|
if (this.isGroup())
|
|
this.on("mousedown", "caption .icon", function() { cl.toggleGroup(this) });
|
|
this.first.on("mouseenter mouseleave ^mousedown", hintFunc);
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
if (this.isGroup()) this.off("mousedown");
|
|
this.first.off(hintFunc);
|
|
}
|
|
}
|
|
|
|
class ThemedIcon extends Element {
|
|
img = null;
|
|
cls = null;
|
|
|
|
this(props) {
|
|
this.img = props.img;
|
|
this.cls = props.cls;
|
|
this.setupIcon();
|
|
}
|
|
|
|
setupIcon() {
|
|
if (this.img) {
|
|
this.applySprite(this.img);
|
|
this.requestPaint();
|
|
}
|
|
}
|
|
|
|
render() {
|
|
return <div class={this.cls}></div>;
|
|
}
|
|
}
|
|
|
|
class Status extends Element {
|
|
this(props) {
|
|
this.status = props.status;
|
|
this.setupIcon();
|
|
}
|
|
|
|
timerFunc() {
|
|
if (settings.blinkWithStatus) {
|
|
if (!this.status || !this.status.eventimg) return false;
|
|
//this.post(function() {
|
|
this.applySprite(this.state.expanded ? this.status.statusimg : this.status.eventimg);
|
|
this.state.expanded = !this.state.expanded;
|
|
// this.flushPaint();
|
|
//});
|
|
} else {
|
|
this.state.expanded = !this.state.expanded;
|
|
//this.flushPaint();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
setupIcon() {
|
|
if (this.status.eventimg) {
|
|
this.applySprite(settings.blinkWithStatus ? this.status.statusimg : this.status.eventimg);
|
|
this.classList.add("blinking");
|
|
this.timer(settings.blinkTime, this.timerFunc);
|
|
} else {
|
|
this.classList.remove("blinking");
|
|
this.timer(0, this.timerFunc);
|
|
this.applySprite(this.status.statusimg);
|
|
}
|
|
this.requestPaint();
|
|
}
|
|
|
|
render() {
|
|
var mod = "";
|
|
if (this.status.deleted) mod = <div class="deleted"></div>
|
|
else if (this.status.official) mod = <div class="official"></div>
|
|
else if (this.status.bot) mod = <div class="bot"></div>
|
|
return <div key="1" class="status">{mod}</div>;
|
|
}
|
|
}
|
|
|
|
function isContactFiltered(data) {
|
|
if (keyword !== "")
|
|
if (data.displayed.toLowerCase().indexOf(keyword) === -1 &&
|
|
data.uid.toLowerCase().indexOf(keyword) === -1 &&
|
|
data.nick.toLowerCase().indexOf(keyword) === -1 &&
|
|
data.first.toLowerCase().indexOf(keyword) === -1 &&
|
|
data.last.toLowerCase().indexOf(keyword) === -1) return true;
|
|
return false;
|
|
}
|
|
|
|
class Contact extends TreeNode {
|
|
render() {
|
|
var cls = "contact";
|
|
if (isContactFiltered(this.data)) cls += " filtered";
|
|
|
|
var children = [];
|
|
for (let showIcon of showIcons)
|
|
if (showIcon[1])
|
|
switch (showIcon[0].idx) {
|
|
case 1:
|
|
children.push(<Status status={this.data.status} />);
|
|
break;
|
|
case 2:
|
|
if (this.data.xstatus.status != "") children.push(<div key="2" class="xstatus">{this.data.xstatus.status}</div>); break;
|
|
case 3:
|
|
if (this.data.needAuth) children.push(<div key="3" class="auth"></div>); break;
|
|
case 4:
|
|
if (this.data.local) children.push(<div key="4" class="local"></div>); break;
|
|
case 5: {
|
|
var dcls = "displayed";
|
|
if (this.data.noClient) dcls += " noclient";
|
|
children.push(<div key="5" class={dcls}>{this.data.displayed}</div>);
|
|
break;
|
|
}
|
|
case 6:
|
|
if (this.data.bdimg) children.push(<ThemedIcon key="6" img={this.data.bdimg} cls="birthday" />); break;
|
|
case 7:
|
|
if (this.data.hasAvatar) children.push(<img key="7" class="avatar" src={"miniavatar:" + this.data.uid} />); break;
|
|
case 8:
|
|
if (this.data.clientimg) children.push(<ThemedIcon key="8" img={this.data.clientimg} cls="client" />); break;
|
|
}
|
|
return <option key={this.data.hash} div={this.data.divisor} group={this.data.group} uid={this.data.uid} class={cls}><caption>{children}</caption></option>;
|
|
}
|
|
|
|
["on mousedragrequest"](e) {
|
|
if (this.getUID() == "0spamers") return;
|
|
hideHint();
|
|
dragging = true;
|
|
draggingOnline = CommonNative.IsOnline();
|
|
view.performDrag({ json: this.data }, "move", this);
|
|
dragging = false;
|
|
}
|
|
}
|
|
|
|
class Group extends TreeNode {
|
|
render() {
|
|
var contacts = [];
|
|
var empty = true;
|
|
var expanded = rebuilding ? this.data.expanded && (settings.combinedGroups || !settings.collapseGroups || (settings.collapseGroups && this.data.divisor != 1 && this.data.divisor != 2)) : this.data.expanded;
|
|
var contactsData = cldata.getContacts(this.data.divisor, this.data.group);
|
|
if (contactsData)
|
|
for (let contact in contactsData) {
|
|
if (expanded) contacts.push(<Contact data={contactsData[contact]} />);
|
|
if (empty) if (!isContactFiltered(contactsData[contact])) empty = false;
|
|
}
|
|
var count = cldata.getGroupCount(this.data.group, this.data.divisor).toString() + "/" + cldata.getTotalGroupCount(this.data.group);
|
|
var local = this.data.local ? <div class="local"></div> : "";
|
|
var cls = "group";
|
|
if (empty && keyword !== "") cls += " filtered";
|
|
return <option state-expanded={expanded} state-collapsed={!expanded} key={this.data.hash} div={this.data.divisor} group={this.data.group} class={cls}><caption><div class="icon"></div><div class="title">{this.data.displayed}</div>{local}<div class="count">{count}</div></caption>{contacts}</option>;
|
|
}
|
|
|
|
["on mousedragrequest"](e) {
|
|
hideHint();
|
|
dragging = true;
|
|
view.performDrag({ json: this.data }, "move", this.$("> caption"));
|
|
dragging = false;
|
|
}
|
|
|
|
["on dragaccept"](e) {
|
|
if (e.detail?.dataType !== "json") return false;
|
|
var data = e.detail.data;
|
|
if (data.group === this.getGroup()) return false;
|
|
if (data.kind == NODE_CONTACT)
|
|
return data.local || draggingOnline;
|
|
else if (data.kind == NODE_GROUP)
|
|
return true;
|
|
}
|
|
|
|
["on dragenter"](e) {
|
|
if (!dragging) return;
|
|
var data = e.detail.data;
|
|
if (data.kind == NODE_CONTACT)
|
|
this.classList.add("acceptcontact");
|
|
else if (data.kind == NODE_GROUP) {
|
|
var current = cl.getCurrent();
|
|
if (current)
|
|
this.classList.add(current.index > this.index ? "acceptgrouptop" : "acceptgroupbottom");
|
|
}
|
|
}
|
|
|
|
["on dragleave"](e) {
|
|
this.classList.remove("acceptcontact");
|
|
this.classList.remove("acceptgrouptop");
|
|
this.classList.remove("acceptgroupbottom");
|
|
}
|
|
|
|
["on drag"](e) {
|
|
dragScroll(e);
|
|
return true;
|
|
}
|
|
|
|
["on drop"](e) {
|
|
this.classList.remove("acceptcontact");
|
|
this.classList.remove("acceptgrouptop");
|
|
this.classList.remove("acceptgroupbottom");
|
|
var data = e.detail.data;
|
|
if (data.kind == NODE_CONTACT)
|
|
Native.MoveCurrentToGroup(this.getGroup());
|
|
else if (data.kind == NODE_GROUP) {
|
|
var current = cl.getCurrent();
|
|
if (!current) return;
|
|
var moveHigher = current.index > this.index;
|
|
this.post(() => {
|
|
Native.SetGroupOrder(data.group, this.getOrder() + (moveHigher ? -1 : +1))
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
class Divisor extends TreeNode {
|
|
render() {
|
|
var contacts = [];
|
|
var contactsData = cldata.getContacts(this.data.divisor);
|
|
if (contactsData)
|
|
for (let contact in contactsData)
|
|
contacts.push(<Contact data={contactsData[contact]} />);
|
|
|
|
var groups = [];
|
|
var groupsData = cldata.getGroups(this.data.divisor);
|
|
if (groupsData)
|
|
for (let group in groupsData) {
|
|
groups.push(<Group data={groupsData[group]} />);
|
|
// if (this.data.divisor == 0 || this.data.divisor == 4)
|
|
// console.log("div:", this.data.divisor, "group:", group, "hash:", groupsData[group].hash)
|
|
}
|
|
|
|
var count = 0;
|
|
var hasGroups = cldata.groups[this.data.divisor] ? cldata.groups[this.data.divisor].size > 0 : false;
|
|
for (let group in cldata.counts)
|
|
if (cldata.counts[group] && cldata.counts[group][this.data.divisor])
|
|
count += cldata.counts[group][this.data.divisor];
|
|
|
|
var cls = "divisor" + ((settings.showEmptyGroups ? (count > 0 || hasGroups) : count > 0) ? "": " filtered");
|
|
return <option state-expanded={true} state-collapsed={false} key={this.data.hash} div={this.data.divisor} class={cls}><caption><div class="fineline" /><div class="divtitle">{Native.DivisorToTitle(this.data.divisor)}</div><div class="divcount">{count}</div><div class="fineline" /></caption>{contacts}{groups}</option>;
|
|
}
|
|
}
|
|
|
|
class ArrayMap extends Map {
|
|
constructor(props) {
|
|
super(props);
|
|
return new Proxy(this, {
|
|
get(map, key) {
|
|
let value = map.has(key) ? map.get(key) : map[key];
|
|
if (typeof value === "function") value = value.bind(map);
|
|
return value;
|
|
},
|
|
set(map, key, value) {
|
|
map.set(key, value);
|
|
return true;
|
|
},
|
|
deleteProperty(map, key) {
|
|
if (map.has(key)) {
|
|
map.delete(key);
|
|
return true;
|
|
} else return false;
|
|
},
|
|
has(map, key) {
|
|
return map.has(key);
|
|
},
|
|
ownKeys(map, key) {
|
|
return Array.from(map.keys());
|
|
},
|
|
enumerate(map) {
|
|
return map[Symbol.iterator || '@@iterator'];
|
|
},
|
|
getOwnPropertyDescriptor(map, key) {
|
|
return { enumerable: true, configurable: true, value: map[key] };
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
class VirtualTreeData {
|
|
divisors = new ArrayMap();
|
|
groups = new ArrayMap();
|
|
contacts = new ArrayMap();
|
|
counts = new ArrayMap();
|
|
|
|
putData(data, rebuild = false) {
|
|
if (data.kind == NODE_DIV) {
|
|
this.divisors[data.divisor] = data;
|
|
if (!rebuild) this.sortDivisors();
|
|
} else if (data.kind == NODE_GROUP) {
|
|
if (!this.groups[data.divisor]) this.groups[data.divisor] = new ArrayMap();
|
|
this.groups[data.divisor][data.group] = data;
|
|
if (!rebuild && settings.showGroups) this.sortGroups(data.divisor);
|
|
} else if (data.kind == NODE_CONTACT) {
|
|
if (!this.contacts[data.divisor]) this.contacts[data.divisor] = new ArrayMap();
|
|
if (!this.contacts[data.divisor][data.group]) this.contacts[data.divisor][data.group] = new ArrayMap();
|
|
if (!this.contacts[data.divisor][data.group][data.uid]) this.changeCount(data, "inc");
|
|
this.contacts[data.divisor][data.group][data.uid] = data;
|
|
if (!rebuild) this.sortContacts(data.divisor, data.group);
|
|
}
|
|
}
|
|
|
|
getData(kind, div, group = 0, uid = "") {
|
|
if (kind == NODE_DIV)
|
|
return this.divisors[div];
|
|
else if (kind == NODE_GROUP)
|
|
return this.groups[div] ? this.groups[div][group] : null;
|
|
else if (kind == NODE_CONTACT)
|
|
return this.contacts[div] && this.contacts[div][group] ? this.contacts[div][group][uid] : null;
|
|
}
|
|
|
|
getContacts(div, group = 0) {
|
|
return this.contacts[div] ? this.contacts[div][group] : null;
|
|
}
|
|
|
|
getGroups(div) {
|
|
return this.groups[div];
|
|
}
|
|
|
|
removeData(data, dec = true) {
|
|
try {
|
|
if (data.kind == NODE_DIV) {
|
|
delete this.divisors[data.divisor];
|
|
delete this.groups[data.divisor];
|
|
delete this.contacts[data.divisor];
|
|
} else if (data.kind == NODE_GROUP) {
|
|
delete this.groups[data.divisor][data.group];
|
|
delete this.contacts[data.divisor][data.group];
|
|
} else if (data.kind == NODE_CONTACT) {
|
|
delete this.contacts[data.divisor][data.group][data.uid];
|
|
if (dec) this.changeCount(data, "dec");
|
|
}
|
|
} catch(e) {
|
|
console.error(e, e.stack);
|
|
}
|
|
}
|
|
|
|
sortDivisors() {
|
|
if (this.divisors)
|
|
this.divisors = new ArrayMap([...this.divisors.entries()].sort((pair1, pair2) => {
|
|
var data1 = pair1[1];
|
|
var data2 = pair2[1];
|
|
return data1.order < data2.order ? -1 : (data1.order > data2.order ? +1 : 0);
|
|
}));
|
|
}
|
|
|
|
sortGroups(divisor) {
|
|
if (this.groups[divisor])
|
|
this.groups[divisor] = new ArrayMap([...this.groups[divisor].entries()].sort((pair1, pair2) => {
|
|
var data1 = pair1[1];
|
|
var data2 = pair2[1];
|
|
if (data1.order == data2.order)
|
|
return data1.displayed.toLowerCase().localeCompare(data2.displayed.toLowerCase());
|
|
else
|
|
return data1.order < data2.order ? -1 : +1;
|
|
}));
|
|
}
|
|
|
|
sortContacts(divisor, group) {
|
|
var contactlist = this.contacts[divisor] ? this.contacts[divisor][group] : null;
|
|
if (contactlist)
|
|
this.contacts[divisor][group] = new ArrayMap([...contactlist.entries()].sort((pair1, pair2) => {
|
|
var weight = 0;
|
|
var data1 = pair1[1];
|
|
var data2 = pair2[1];
|
|
|
|
if (settings.combinedGroups) {
|
|
if (data1.status.code == 0) weight += -1;
|
|
if (data2.status.code == 0) weight += +1;
|
|
if ((data1.status.code == 0 || data2.status.code == 0) && data1.status.code != data2.status.code) return weight;
|
|
}
|
|
|
|
if (data1.order != data2.order) return data1.order < data2.order ? -1 : +1;
|
|
|
|
if (settings.sortBy == 0) return weight;
|
|
if (settings.sortBy == 1 || (settings.sortBy == 2 && data1.lastActivity == 0 && data2.lastActivity == 0))
|
|
weight += data1.displayed.toLowerCase().localeCompare(data2.displayed.toLowerCase());
|
|
else if (settings.sortBy == 2)
|
|
weight += data1.lastActivity < data2.lastActivity ? +1 : -1;
|
|
|
|
return weight;
|
|
}));
|
|
}
|
|
|
|
sort() {
|
|
//var start = new Date().valueOf();
|
|
this.sortDivisors();
|
|
if (settings.showGroups) {
|
|
for (let divisor in this.groups) {
|
|
this.sortGroups(divisor);
|
|
for (let group in this.groups[divisor]) this.sortContacts(divisor, group);
|
|
}
|
|
} else {
|
|
for (let divisor in this.divisors) this.sortContacts(divisor, 0);
|
|
}
|
|
// var end = new Date().valueOf();
|
|
// console.log("cldata sort", end - start);
|
|
}
|
|
|
|
changeCount(data, mod) {
|
|
if (mod == "inc") {
|
|
if (!this.counts[data.group]) this.counts[data.group] = new ArrayMap();
|
|
if (!this.counts[data.group][data.divisor]) this.counts[data.group][data.divisor] = 0;
|
|
if (!this.counts[data.group]["total"]) this.counts[data.group]["total"] = 0;
|
|
this.counts[data.group][data.divisor]++;
|
|
this.counts[data.group]["total"]++;
|
|
} else if (mod == "dec" && this.counts[data.group]) {
|
|
this.counts[data.group][data.divisor]--;
|
|
this.counts[data.group]["total"]--;
|
|
}
|
|
}
|
|
|
|
getGroupCount(group, divisor) {
|
|
return this.counts[group] && cldata.counts[group][divisor] ? cldata.counts[group][divisor] : 0;
|
|
}
|
|
|
|
getTotalGroupCount(group) {
|
|
return this.counts[group] && cldata.counts[group]["total"] ? cldata.counts[group]["total"] : 0;
|
|
}
|
|
|
|
clear() {
|
|
this.divisors = new ArrayMap();
|
|
this.groups = new ArrayMap();
|
|
this.contacts = new ArrayMap();
|
|
this.counts = new ArrayMap();
|
|
}
|
|
}
|
|
var cldata = new VirtualTreeData();
|
|
|
|
class VirtualTree extends Element {
|
|
allContacts = [];
|
|
|
|
editNode(data, reason) {
|
|
var opt = this.getOption(data);
|
|
if (opt) {
|
|
opt.execCommand("set-current");
|
|
opt.startEditing(reason);
|
|
}
|
|
}
|
|
|
|
toggleGroup(opt, force = "auto") {
|
|
if (opt.state.expanded && (force == "auto" || force == "collapse")) {
|
|
opt.state.collapsed = true;
|
|
Native.GroupStateChanged(false, opt.getGroup(), opt.getDiv());
|
|
} else if (opt.state.collapsed && (force == "auto" || force == "expand")) {
|
|
//var start = new Date().valueOf();
|
|
opt.state.expanded = true;
|
|
Native.GroupStateChanged(true, opt.getGroup(), opt.getDiv());
|
|
//var end = new Date().valueOf();
|
|
//debug expand: opt.@#group, end - start;
|
|
}
|
|
}
|
|
|
|
toggleAll(action) {
|
|
var selected = this.getCurrent();
|
|
for (let opt of this.$$("option.group"))
|
|
if (!selected.isDiv() || (selected.isDiv() && opt.getDiv() == selected.getDiv()))
|
|
this.toggleGroup(opt, action);
|
|
}
|
|
|
|
getDivisor(data) {
|
|
return this.$(`> option.divisor[div="${data.divisor}"]`);
|
|
}
|
|
|
|
getGroup(data) {
|
|
if (data.group == 0) return null;
|
|
var div = this.getDivisor(data);
|
|
return div ? div.$(`> option.group[group="${data.group}"]`) : null;
|
|
}
|
|
|
|
getContact(data) {
|
|
var contact;
|
|
var div = this.getDivisor(data);
|
|
if (div) {
|
|
contact = div.$(`> option.contact[group="${data.group}"][uid="${data.uid}"]`);
|
|
if (contact) return contact;
|
|
}
|
|
var group = this.getGroup(data);
|
|
return group ? group.$(`> option.contact[group="${data.group}"][uid="${data.uid}"]`) : contact;
|
|
}
|
|
|
|
getContactByUIN(uin) {
|
|
return this.$(`option.contact[uid="${uin}"]`);
|
|
}
|
|
|
|
getOption(data) {
|
|
var opt = null;
|
|
if (data.kind == NODE_DIV)
|
|
opt = this.getDivisor(data);
|
|
else if (data.kind == NODE_GROUP)
|
|
opt = this.getGroup(data);
|
|
else if (data.kind == NODE_CONTACT)
|
|
opt = this.getContact(data);
|
|
return opt;
|
|
}
|
|
|
|
getCurrent() {
|
|
return this.$("option:current");
|
|
}
|
|
|
|
render() {
|
|
var divs = [];
|
|
for (let divisor in cldata.divisors)
|
|
divs.push(<Divisor data={cldata.divisors[divisor]} />);
|
|
|
|
var cls = settings.blinkWithStatus ? "statusblink" : "eventblink";
|
|
if (settings.onlineOnly) cls += " onlineOnly";
|
|
if (settings.indentRoster) cls += " groupPadding";
|
|
// if (settings.animatedGroups) cls += " animated";
|
|
|
|
return <widget id="cl" class={cls} type="tree">{divs}</widget>;
|
|
}
|
|
|
|
refreshAllContacts() {
|
|
this.allContacts = this.$$("option.contact");
|
|
}
|
|
|
|
queueUpdate() {
|
|
this.timer(250ms, this.componentUpdate);
|
|
}
|
|
|
|
refreshVisible() {
|
|
var overshoot = 180;
|
|
var treeHeight = cl.state.box("height", "client");
|
|
for (let contact of this.allContacts) {
|
|
var [cntLeft, cntTop, cntRight, cntBottom] = contact.state.box("rect", "border", cl);
|
|
if (cntBottom < -overshoot || cntTop > treeHeight + overshoot)
|
|
contact.classList.add("hidden");
|
|
else
|
|
contact.classList.remove("hidden");
|
|
}
|
|
}
|
|
|
|
executeAction(opt) {
|
|
if (opt.isDiv()) {
|
|
Native.ToggleOnlineOnly();
|
|
return true;
|
|
} else if (opt.isGroup()) {
|
|
cl.toggleGroup(opt);
|
|
return true;
|
|
} else if (opt.isContact()) {
|
|
Native.OpenContact(opt.getUID());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
setClicked(opt) {
|
|
if (opt)
|
|
Native.SetClicked(
|
|
opt.isContact() ? NODE_CONTACT : opt.isGroup() ? NODE_GROUP : opt.isDiv() ? NODE_DIV : 0,
|
|
opt.getDiv(),
|
|
opt.isGroup() ? opt.getGroup() : 0,
|
|
opt.isContact() ? opt.getUID() : ""
|
|
); else Native.SetClicked(NODE_DIV, 0, 0, "");
|
|
}
|
|
|
|
componentDidMount() {
|
|
cl = this;
|
|
}
|
|
|
|
componentDidUpdate() {
|
|
this.refreshAllContacts();
|
|
if (keyword !== "")
|
|
for (let div of this.children) if (div) {
|
|
var empty = true;
|
|
for (let opt of div.children)
|
|
if (opt && opt.tag === "option" && !opt.classList.contains("filtered")) {
|
|
empty = false;
|
|
break;
|
|
}
|
|
if (empty) div.classList.add("filtered");
|
|
}
|
|
this.onsizechange();
|
|
}
|
|
|
|
["on contextmenu"](e) {
|
|
openContextMenu(e.reason == Event.BY_MOUSE_CLICK);
|
|
return true;
|
|
}
|
|
|
|
["on ^dblclick at option > caption"](e, el) {
|
|
return e.mainButton ? this.executeAction(el.parent) : false;
|
|
}
|
|
|
|
["on ^mousedown"](e) {
|
|
if (!e.propButton) return;
|
|
var opt = e.target.$p("option");
|
|
if (opt) {
|
|
opt.execCommand("set-current");
|
|
this.setClicked(opt);
|
|
} else {
|
|
cl.value = null;
|
|
this.setClicked(null);
|
|
}
|
|
}
|
|
|
|
onmouseleave(e) {
|
|
if (settings.showHints) hideHint();
|
|
}
|
|
|
|
onwheel(e) {
|
|
if (settings.showHints) hideHint();
|
|
}
|
|
|
|
onscroll(e) {
|
|
this.post(this.refreshVisible);
|
|
}
|
|
|
|
onsizechange(e) {
|
|
if (settings.autosize && !windowResizing)
|
|
this.timer(32ms, doAutosize);
|
|
else
|
|
this.post(this.refreshVisible);
|
|
}
|
|
|
|
onkeydown(e) {
|
|
var selected = this.getCurrent();
|
|
if (!selected) return false;
|
|
if (selected.classList.contains("editing")) return true;
|
|
if (e.keyCode == Event.VK_RETURN) {
|
|
return this.executeAction(selected);
|
|
} else if (e.keyCode == Event.VK_LEFT || e.keyCode == Event.VK_RIGHT) {
|
|
if ((e.keyCode == Event.VK_LEFT && selected.state.expanded) ||
|
|
(e.keyCode == Event.VK_RIGHT && selected.state.collapsed))
|
|
if (selected.isGroup()) this.toggleGroup(selected);
|
|
return true;
|
|
} else if (e.keyCode == Event.VK_DELETE) {
|
|
if (selected.isGroup()) Native.DeleteCurrentGroup();
|
|
else if (selected.isContact()) Native.DeleteCurrentContact();
|
|
} else if (e.keyCode == Event.VK_F2) {
|
|
var opt = this.getCurrent();
|
|
if (!opt) return false;
|
|
if (opt.isGroup()) {
|
|
if (!opt.isLocal() && !CommonNative.IsOnline())
|
|
createDialog(
|
|
"asterisk",
|
|
_("Information"),
|
|
_("Group is not local and must be modified while online"),
|
|
true
|
|
);
|
|
else
|
|
opt.startEditing(GA_RENAME);
|
|
} else opt.startEditing();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
oncontentchange() {
|
|
this.timer(32ms, doAutosize);
|
|
}
|
|
|
|
onchange(e) {
|
|
var selected = this.getCurrent();
|
|
this.setClicked(selected);
|
|
this.post(this.refreshVisible);
|
|
}
|
|
|
|
["on dragaccept"](e) {
|
|
return e.detail?.dataType === "json";
|
|
}
|
|
|
|
["on drag"](e) {
|
|
dragScroll(e);
|
|
return false;
|
|
}
|
|
|
|
["on drop"](e) {
|
|
return false;
|
|
}
|
|
}
|
|
$("#cl").patch(<VirtualTree />);
|
|
//$("#cl").replaceWith(Element.create( |
|
cl = $("#cl");
|
|
|
|
function dragScroll(e) {
|
|
var onedip = 1dip.valueOf();
|
|
var [x, y, w, h] = cl.state.box("rectw", "border", "window");
|
|
var inc = e.yView < y + 20 * onedip ? -15 : (e.yView > y + h - 20 * onedip ? +15 : 0);
|
|
if ((inc < 0 && cl.scroll("top") == 0) || (inc > 0 && cl.scroll("bottom") == 0)) return;
|
|
if (inc != 0) cl.scrollTo(0, cl.scroll("top") + inc * onedip, true);
|
|
}
|
|
|
|
function clear() {
|
|
cldata.clear();
|
|
if (cl) cl.componentUpdate();
|
|
}
|
|
|
|
function insertNode(data, rebuild) {
|
|
cldata.putData(data, rebuild);
|
|
if (rebuild) return;
|
|
//console.log("insertNode:", data.hash, data.kind, data.divisor, data.group, data.uid);
|
|
cl.componentUpdate();
|
|
}
|
|
|
|
function removeNode(data) {
|
|
//console.log("removeNode:", data.hash, data.kind, data.divisor, data.group, data.uid);
|
|
cldata.removeData(data);
|
|
cl.componentUpdate();
|
|
}
|
|
|
|
function editNode(data, reason = GA_NONE) {
|
|
cl.editNode(data, reason);
|
|
}
|
|
|
|
function finishBuild() {
|
|
rebuilding = true;
|
|
cldata.sort();
|
|
cl.componentUpdate();
|
|
rebuilding = false;
|
|
}
|
|
|
|
function focusFilter() {
|
|
if (clfilter.state.visible)
|
|
clfilter.state.focus = true;
|
|
else
|
|
cl.state.focus = true;
|
|
}
|
|
|
|
function focusNode(data) {
|
|
var opt = cl.getOption(data);
|
|
if (opt) opt.post(function() { this.execCommand("set-current") });
|
|
}
|
|
|
|
function setGroupState(data, expand) {
|
|
var opt = cl.getGroup(data);
|
|
if (opt) cl.toggleGroup(opt, expand ? "expand" : "collapse");
|
|
}
|
|
|
|
function getCurrentContact() {
|
|
var selected = cl.getCurrent();
|
|
return selected && selected.isContact() ? selected.getUID() : "";
|
|
}
|
|
|
|
function getContacts(div) {
|
|
var uids = [];
|
|
try {
|
|
if (cldata.contacts[div])
|
|
for (let group in cldata.contacts[div])
|
|
for (let uid in cldata.contacts[div][group])
|
|
uids.push(uid);
|
|
} catch(e) {
|
|
console.error(e, e.stack);
|
|
}
|
|
return uids;
|
|
}
|
|
|
|
function initSettings(icons, sets) {
|
|
showIcons = icons;
|
|
settings = sets;
|
|
|
|
var newFrame = settings.noBorderShadow ? "solid-with-shadow" : "solid";
|
|
if (view.frameType != "standard" && view.frameType != newFrame)
|
|
view.frameType = newFrame;
|
|
|
|
view.isTopmost = settings.alwaysOnTop;
|
|
|
|
$("#mlocalhelp").setVisible(settings.helpExists);
|
|
$("#mnewgroup").state.disabled = !settings.showGroups;
|
|
|
|
var pic1 = $("#mshowgroups").$("div[pic]");
|
|
var pic2 = $("#mshowgroups2").$("div[pic]");
|
|
if (settings.showGroups) {
|
|
pic1.setIcon("right");
|
|
pic2.setIcon("right");
|
|
mshowemptygroups.state.disabled = false;
|
|
} else {
|
|
pic1.clearSprite();
|
|
pic2.clearSprite();
|
|
mshowemptygroups.state.disabled = true;
|
|
}
|
|
var pic = mshowemptygroups.$("div[pic]");
|
|
if (settings.showEmptyGroups) pic.setIcon("right"); else pic.clearSprite()
|
|
pic = $("#monlineonly").$("div[pic]");
|
|
if (settings.onlineOnly) pic.setIcon("right"); else pic.clearSprite()
|
|
pic = $("#mseparation").$("div[pic]");
|
|
if (settings.combinedGroups) pic.setIcon("right"); else pic.clearSprite()
|
|
|
|
bar.setVisible(settings.barPos != 2);
|
|
if (settings.barPos < 2) {
|
|
bar.detach();
|
|
if (settings.barPos == 0)
|
|
body.prepend(bar);
|
|
else
|
|
body.insert(bar, body.$("> menu").index);
|
|
}
|
|
|
|
filter.setVisible(settings.filterPos != 2);
|
|
if (settings.filterPos < 2) {
|
|
filter.detach();
|
|
if (settings.filterPos == 0) {
|
|
if (settings.barPos == 0)
|
|
body.insert(filter, 1);
|
|
else
|
|
body.prepend(filter);
|
|
} else {
|
|
if (settings.barPos == 1)
|
|
body.insert(filter, 1);
|
|
else
|
|
body.insert(filter, body.$("> menu").index);
|
|
}
|
|
}
|
|
if (settings.filterPos == 2 && keyword !== "") {
|
|
keyword = "";
|
|
clfilter.value = "";
|
|
}
|
|
|
|
var usercss = $("style#usercss");
|
|
usercss.text = settings.mainCSS;
|
|
|
|
if (cl) cl.componentUpdate();
|
|
}
|
|
|
|
function initMenus(statuses) {
|
|
var options = [];
|
|
for (let status of statuses)
|
|
options.push(<li code={status.code}><div auto pic={status.img}></div><span>{status.caption}</span></li>);
|
|
|
|
options.push(<hr/>);
|
|
options.push(<li class="visibility"><div pic></div><span></span></li>);
|
|
|
|
if (settings.showStatusMsgMenu) {
|
|
options.push(<hr/>);
|
|
options.push(<li><div class="xstatus"></div><span>{_("Message")}</span></li>);
|
|
}
|
|
|
|
statusmenu.patch(<menu>{options}</menu>, true);
|
|
mstatuses.patch(<menu>{options}</menu>, true);
|
|
}
|
|
|
|
function updateMenus(visible) {
|
|
mtoggle.$("> span").text = _(visible ? "Hide" : "Show");
|
|
mtoggle.$("> div").setIcon(visible ? "minimize" : "restore");
|
|
}
|
|
|
|
function initThemeList(themes, smiles, emojis, sounds) {
|
|
var oldthemes = mthemes.$$(".theme");
|
|
for (let oldtheme of oldthemes) oldtheme.remove();
|
|
|
|
if (themes)
|
|
for (let theme of themes) {
|
|
var check = theme.current ? "checked" : "unchecked";
|
|
mthemes.append(<li class="theme" index={theme.index}><div pic={check}></div><span>{theme.title}</span></li>);
|
|
}
|
|
|
|
var msmileslist = $("#msmileslist");
|
|
msmileslist.clear();
|
|
if (smiles)
|
|
for (let smile of smiles) {
|
|
var check = smile.current ? "checked" : "unchecked";
|
|
msmileslist.append(<li index={smile.index}><div pic={check}></div><span>{smile.title}</span></li>);
|
|
}
|
|
|
|
var memojislist = $("#memojislist");
|
|
memojislist.clear();
|
|
if (emojis)
|
|
for (let emoji of emojis) {
|
|
var check = emoji.current ? "checked" : "unchecked";
|
|
memojislist.append(<li index={emoji.index}><div pic={check}></div><span>{emoji.title}</span></li>);
|
|
}
|
|
|
|
var msoundslist = $("#msoundslist");
|
|
msoundslist.clear();
|
|
if (sounds)
|
|
for (let sound of sounds) {
|
|
var check = sound.current ? "checked" : "unchecked";
|
|
msoundslist.append(<li index={sound.index}><div pic={check}></div><span>{sound.title}</span></li>);
|
|
}
|
|
|
|
var pics = mthemes.$$("div[pic]");
|
|
for (let pic of pics) pic.setupIcon();
|
|
}
|
|
|
|
function applyTheme(offPic, botPic, delPic, authPic, localPic) {
|
|
// for (let icon of $$("menu.custom li")) {
|
|
// if (icon) icon.applySprite(getSpriteData(icon.attr["pic"]));
|
|
// }
|
|
|
|
var dyncss = $("style#dyncss");
|
|
dyncss.text = makeSpriteStyle("#cl option.contact > caption div.official", offPic);
|
|
dyncss.text += makeSpriteStyle("#cl option.contact > caption div.bot", botPic);
|
|
dyncss.text += makeSpriteStyle("#cl option.contact > caption div.deleted", delPic);
|
|
dyncss.text += makeSpriteStyle("#cl option.contact > caption div.auth", authPic);
|
|
dyncss.text += makeSpriteStyle("#cl option > caption div.local", localPic);
|
|
|
|
var groupClosed = getSpriteData("close.group");
|
|
var groupOpened = getSpriteData("open.group");
|
|
|
|
dyncss.text += makeSpriteStyle("#cl option.group:collapsed > caption .icon", groupClosed);
|
|
dyncss.text += makeSpriteStyle("#cl option.group:expanded > caption .icon", groupOpened);
|
|
|
|
var pics = $$("div[pic]");
|
|
for (let pic of pics) pic.setupIcon();
|
|
|
|
var bimages = $$("body, [style], div[pic]");
|
|
var themepics = [];
|
|
for (let bimage of bimages)
|
|
if (bimage) {
|
|
var link = bimage.style.backgroundImage;
|
|
if (!link) continue;
|
|
if (link.indexOf("url(") === 0)
|
|
link = link.match(/^url\((.*)\)$/)[1];
|
|
if (link.indexOf("themepic:") === -1 || themepics.indexOf(link) !== -1) continue;
|
|
themepics.push(link);
|
|
}
|
|
|
|
for (let themepic of themepics) reloadImage(themepic);
|
|
themepics = [];
|
|
}
|
|
|
|
function setProgress(progress) {
|
|
$("#pvalue").text = progress == 0 ? "" : Math.round(progress * 100) + "%";
|
|
$("#pslider").value = progress;
|
|
$("#pslider").flushPaint();
|
|
}
|
|
|
|
function updateContactCount(str) {
|
|
$("#ccount").text = str;
|
|
}
|
|
|
|
function updateStatusImage(pic) {
|
|
$("#statusBtn").$("> div[pic]").setIcon(pic);
|
|
mstatus.setIcon(pic);
|
|
}
|
|
|
|
function updateVisibilityImage(isVis) {
|
|
var visibilities = $$(".visibility");
|
|
for (let visibility of visibilities) {
|
|
visibility.$("> div[pic]").setIcon(isVis ? "visibility.all" : "visibility.none");
|
|
visibility.$("> span").text = _(isVis ? "Visible" : "Invisible");
|
|
}
|
|
}
|
|
|
|
function updateAdditionalImage(xst, pic) {
|
|
var xstatuses = $$("menu .xstatus");
|
|
for (let xstatus of xstatuses)
|
|
if (xstatus.$p("#statusmenu") || xstatus.$p("#mstatuses"))
|
|
xstatus.text = xst;
|
|
|
|
var addBtn = $("#addBtn");
|
|
if (pic === "") {
|
|
addBtn.classList.add("collapsed");
|
|
} else {
|
|
addBtn.classList.remove("collapsed");
|
|
var sprite = addBtn.$("> div[pic]");
|
|
var emoji = addBtn.$("> div.emoji");
|
|
if (pic === "outbox") {
|
|
emoji.setVisible(false);
|
|
sprite.setVisible(true);
|
|
sprite.setIcon(pic);
|
|
} else {
|
|
sprite.setVisible(false);
|
|
emoji.setVisible(true);
|
|
emoji.text = pic;
|
|
}
|
|
addBtn.requestPaint();
|
|
}
|
|
}
|
|
|
|
function openMainMenu(el = null) {
|
|
var pos, x, y;
|
|
if (el) {
|
|
var onedip = 1dip.valueOf();
|
|
[x, y] = el.state.box("position", "padding", "window");
|
|
var [w, h] = el.state.box("dimension", "padding");
|
|
var [ex, ey] = el.state.box("position", "padding", "screen");
|
|
var [sx, sy, sw, sh] = view.screenBox("frame", "rectw");
|
|
if (ey > sy + sh / 2) {
|
|
y += h;
|
|
if (ex < sx + sw / 2) {
|
|
y += onedip;
|
|
x += w - onedip;
|
|
pos = 1;
|
|
} else {
|
|
y += onedip;
|
|
x += onedip;
|
|
pos = 3;
|
|
}
|
|
} else {
|
|
y -= onedip;
|
|
if (ex < sx + sw / 2) {
|
|
x += w - onedip;
|
|
pos = 7;
|
|
} else {
|
|
x += onedip;
|
|
pos = 9;
|
|
}
|
|
}
|
|
} else {
|
|
[x, y] = view.box("position", "cursor", "desktop");
|
|
var scr = getScreenFromCoord(x, y);
|
|
var [sx, sy, sw, sh] = View.screenBox(scr, "frame", "rectw");
|
|
pos = x > sx + sw / 2 ? (y > sy + sh / 2 ? 3 : 9) : (y > sy + sh / 2 ? 1 : 7);
|
|
var [x, y] = coordScreenToView(x, y);
|
|
}
|
|
document.popup(mainmenu, {
|
|
anchorAt: 7,
|
|
popupAt: pos,
|
|
x: x,
|
|
y: y
|
|
});
|
|
}
|
|
|
|
function openStatusMenu(el = null) {
|
|
var pos, x, y, h;
|
|
var onedip = 1dip.valueOf();
|
|
if (el) {
|
|
[x, y] = el.state.box("position", "border", "window");
|
|
h = el.state.box("height", "border");
|
|
pos = 7;
|
|
x -= onedip;
|
|
y += h - onedip;
|
|
} else {
|
|
[x, y] = view.box("position", "cursor");
|
|
pos = 7;
|
|
}
|
|
statusmenu.flushPaint();
|
|
var oldpos = pos;
|
|
[pos, x, y] = getPopupPosition(x, y, statusmenu);
|
|
if (el && oldpos == 7 && pos == 1) y -= h - onedip * 2;
|
|
document.popup(statusmenu, {
|
|
anchorAt: 7,
|
|
popupAt: pos,
|
|
x: x,
|
|
y: y
|
|
});
|
|
}
|
|
|
|
function openContextMenu(followMouse = false) {
|
|
var selected = cl ? cl.getCurrent() : null, x, y;
|
|
if (followMouse || !selected) {
|
|
[x, y] = view.box("position", "cursor");
|
|
} else {
|
|
[x, y] = selected.state.box("position", "border", "window");
|
|
x -= 1;
|
|
y += selected.$("> caption").state.box("height", "border");
|
|
}
|
|
|
|
if (!selected || selected.isDiv()) {
|
|
var pos = 7;
|
|
[pos, x, y] = getPopupPosition(x, y, divmenu);
|
|
document.popup(divmenu, {
|
|
anchorAt: 7,
|
|
popupAt: pos,
|
|
x: x,
|
|
y: y
|
|
});
|
|
} else if (selected.isGroup()) {
|
|
var group = selected.getGroup();
|
|
if (group == 0) return;
|
|
var addToServer = $("#maddgrouptoserver");
|
|
var removeFromServer = $("#mremovegroupfromserver");
|
|
var renameGroup = $("#mrenamegroup");
|
|
var deleteGroup = $("#mdeletegroup");
|
|
var isOnline = CommonNative.IsOnline();
|
|
$("#mmoveallto").state.disabled = !isOnline;
|
|
renderGroupLists($("#mgrouplist"), false);
|
|
if (selected.isLocal()) {
|
|
addToServer.setVisible(true);
|
|
addToServer.state.disabled = !isOnline;
|
|
removeFromServer.setVisible(false);
|
|
renameGroup.state.disabled = false;
|
|
deleteGroup.state.disabled = false;
|
|
} else {
|
|
addToServer.setVisible(false);
|
|
removeFromServer.setVisible(true);
|
|
removeFromServer.state.disabled = !isOnline;
|
|
renameGroup.state.disabled = !isOnline;
|
|
deleteGroup.state.disabled = !isOnline;
|
|
}
|
|
var pos = 7;
|
|
[pos, x, y] = getPopupPosition(x, y, groupmenu);
|
|
document.popup(groupmenu, {
|
|
anchorAt: 7,
|
|
popupAt: pos,
|
|
x: x,
|
|
y: y
|
|
});
|
|
} else if (selected.isContact()) {
|
|
var uid = selected.getUID();
|
|
if (uid) openContactMenu(uid, x, y);
|
|
}
|
|
}
|
|
|
|
function openStatusDialog() {
|
|
openSingleWindow({
|
|
url: "status.htm",
|
|
caption: "XStatus and status message",
|
|
uniqueid: "statusmessage",
|
|
dialog: true,
|
|
alignment: 5,
|
|
x: view.box("left", "border", "desktop"),
|
|
y: view.box("top", "border", "desktop"),
|
|
width: 270,
|
|
height: 350,
|
|
params: {
|
|
mainThis: this,
|
|
parentWidth: view.box("width", "border", "desktop"),
|
|
parentHeight: view.box("height", "border", "desktop")
|
|
}
|
|
});
|
|
}
|
|
|
|
function openAddContactDialog(nick = null) {
|
|
openSingleWindow({
|
|
url: "addcontact.htm",
|
|
caption: "Add contact",
|
|
uniqueid: "addcontact",
|
|
alignment: 5,
|
|
params: {
|
|
nick: nick
|
|
}
|
|
});
|
|
}
|
|
|
|
function btnClickedEvent(e) {
|
|
btnClicked(this, e.propButton);
|
|
}
|
|
|
|
function btnClicked(el, prop) {
|
|
if (el.id == "menuBtn") {
|
|
openMainMenu(el);
|
|
return;
|
|
} else if (el.id == "statusBtn") {
|
|
if (prop)
|
|
openStatusDialog();
|
|
else
|
|
openStatusMenu(el);
|
|
return;
|
|
} else if (el.id == "addBtn" && prop) {
|
|
openStatusDialog();
|
|
return;
|
|
}
|
|
var [x, y] = el.state.box("position", "border", "screen");
|
|
Native.BarButtonClick(el.id, prop, x, y);
|
|
}
|
|
|
|
// function startCoverage() {
|
|
// VM.startStats("coverage");
|
|
// }
|
|
|
|
// function getCoverage() {
|
|
// var coverage = VM.getStats("coverage");
|
|
// debug coverage: coverage;
|
|
// }
|
|
|
|
function getWindowHeight() {
|
|
var h = 0;
|
|
if (!settings.autosizeFull) {
|
|
var d_online = cl.$("> option.divisor[div=\"0\"]");
|
|
if (!d_online) return -1;
|
|
if (d_online.state.visible) h += d_online.state.box("height", "margin");
|
|
var d_nil = cl.$("> option.divisor[div=\"3\"]");
|
|
if (d_nil && d_nil.state.visible) h += d_nil.state.box("height", "margin");
|
|
var d_recents = cl.$("> option.divisor[div=\"4\"]");
|
|
if (d_recents && d_recents.state.visible) h += d_recents.state.box("height", "margin");
|
|
} else {
|
|
h = cl.state.box("height", "content");
|
|
}
|
|
var p1 = body.style["padding-top"].valueOf();
|
|
var p2 = body.style["padding-bottom"].valueOf();
|
|
var b = bar.state.visible ? bar.state.box("height", "margin") : 0;
|
|
var f = filter.state.visible ? filter.state.box("height", "margin") : 0;
|
|
return h == 0 ? -1 : b + h + f + p1 + p2;
|
|
}
|
|
|
|
function doAutosize() {
|
|
if (!settings.autosize) return;
|
|
var y = getWindowHeight();
|
|
if (y <= 20) return;
|
|
y += 3;
|
|
var [left, top, width, height] = view.box("rectw", "client", "desktop");
|
|
if (y == height) return;
|
|
var [wx1, wy1, wx2, wy2] = view.screenBox("workarea", "rect");
|
|
var gap = 20dip.valueOf();
|
|
if (settings.autosizeUp) {
|
|
var limit = top + height - wy1 - gap;
|
|
if (y > limit) y = limit;
|
|
var delta = y - height;
|
|
view.move(left, top - delta, width, y, true);
|
|
} else {
|
|
var limit = wy2 - top - gap;
|
|
if (y > limit) y = limit;
|
|
view.move(left, top, width, y, true);
|
|
}
|
|
cl.post(cl.refreshVisible);
|
|
}
|
|
|
|
function updateCaption(caption) {
|
|
view.caption = caption;
|
|
}
|
|
|
|
function showHintForCurrent() {
|
|
var current = cl.getCurrent();
|
|
if (current) showHint(cl, current.data);
|
|
}
|
|
|
|
function show() {
|
|
view.state = View.WINDOW_SHOWN;
|
|
updateMenus(true);
|
|
Native.OnCLShow();
|
|
}
|
|
|
|
function hide() {
|
|
view.state = View.WINDOW_HIDDEN;
|
|
updateMenus(false);
|
|
Native.OnCLHide();
|
|
}
|
|
|
|
function setBorder(hasBorder) {
|
|
// document.post(() => {
|
|
// view.frameType = "solid-with-shadow";
|
|
// CommonNative.SetLargeWindowIcon(document, ""); // SendMessage(Handle, WM_SETICON, ICON_BIG, IconHandle)
|
|
// view.frameType = "standard";
|
|
// });
|
|
//return;
|
|
if (hasBorder) {
|
|
CommonNative.SetLargeWindowIcon(document, "-");
|
|
view.frameType = "standard";
|
|
} else
|
|
document.post(() => {
|
|
CommonNative.SetLargeWindowIcon(document, "-");
|
|
var newFrame = settings.noBorderShadow ? "solid-with-shadow" : "solid";
|
|
if (view.frameType != newFrame) view.frameType = newFrame;
|
|
}); // Sciter's bug
|
|
}
|
|
|
|
function tryToQuit() {
|
|
if (!View.share.commonSettings.quitConfirmation || createDialog(
|
|
"question",
|
|
_("Confirm"),
|
|
_("Really quit?"),
|
|
false, ["mbYes", "mbNo"], "mbYes", "mbNo"
|
|
) === "mbYes") Native.Quit();
|
|
}
|
|
|
|
var isModKey = false;
|
|
document.on("keydown", (e) => {
|
|
isModKey = e.ctrlKey || e.shiftKey;
|
|
if (e.keyCode == Event.VK_ESCAPE) {
|
|
if (windowMoving) {
|
|
progress.classList.remove("moving");
|
|
progress.state.capture(false);
|
|
windowMoving = false;
|
|
return true;
|
|
}
|
|
if (clfilter.state.focus) return false;
|
|
if (view.state == View.WINDOW_HIDDEN)
|
|
show();
|
|
else
|
|
hide();
|
|
return true;
|
|
} else if (e.ctrlKey && e.keyCode == Event.VK_F)
|
|
Native.ToggleFilter();
|
|
else if (e.altKey && e.keyCode == Event.VK_F4)
|
|
tryToQuit();
|
|
else
|
|
CommonNative.ProcessMacro(e.platformKeyCode, e.shiftKey, e.ctrlKey, e.altKey, e.metaKey);
|
|
})
|
|
.on("keyup", (e) => { if (e.keyCode != Event.VK_CONTEXT_MENU) isModKey = false })
|
|
.on("mouseenter", (e) => { Native.OnCLMouseEnter(); })
|
|
.on("mouseleave", (e) => { Native.OnCLMouseLeave(); })
|
|
.on("closerequest", function(e){
|
|
e.preventDefault();
|
|
if (e.reason == Event.REASON_BY_CHROME)
|
|
hide();
|
|
else
|
|
tryToQuit();
|
|
});
|
|
|
|
function updateTranslation() {
|
|
translateWindow("menu.custom li > span, menu.context li > label, menu.custom hr > span, menu.custom caption");
|
|
}
|
|
|
|
updateTranslation();
|
|
|
|
$("#addBtn").classList.add("collapsed");
|
|
$("#addBtn").on("mouseup", btnClickedEvent);
|
|
$("#menuBtn").on("mouseup", btnClickedEvent);
|
|
$("#statusBtn").on("mouseup", btnClickedEvent);
|
|
$("#clearBtn").on("click", (e) => {
|
|
keyword = "";
|
|
clfilter.value = "";
|
|
clfilter.state.focus = true;
|
|
cl.componentUpdate();
|
|
});
|
|
|
|
bar.on("mouseup", (e) => {
|
|
var isBtn = e.target.classList.contains("imgbtn") || (e.target.parent && e.target.parent.classList.contains("imgbtn"));
|
|
if (!isBtn && e.propButton) btnClicked($("#menuBtn"), false);
|
|
});
|
|
|
|
clfilter.on("change", function(e) {
|
|
keyword = this.value.toLowerCase();
|
|
cl.queueUpdate();
|
|
}).on("keydown", function(e) {
|
|
if (e.keyCode == Event.VK_ESCAPE) {
|
|
this.post(() => { Native.ToggleFilter() });
|
|
return true;
|
|
}
|
|
});
|
|
|
|
progress.on("^mousedown", function(e) {
|
|
if (!e.mainButton) return;
|
|
windowMoving = true;
|
|
this.classList.add("moving");
|
|
[startX, startY] = view.box("position", "cursor");
|
|
[startWinX, startWinY] = view.box("position", "client", "desktop");
|
|
startX += startWinX;
|
|
startY += startWinY;
|
|
this.state.capture("strict");
|
|
}).on("^mouseup", function(e) {
|
|
if (!windowMoving) return;
|
|
this.classList.remove("moving");
|
|
this.state.capture(false);
|
|
windowMoving = false;
|
|
}).on("mousemove", function(e) {
|
|
if (!windowMoving) return;
|
|
var [x, y] = view.box("position", "cursor");
|
|
var [winX, winY] = view.box("position", "client", "desktop");
|
|
view.move(startWinX + winX + x - startX, startWinY + winY + y - startY, true);
|
|
return true;
|
|
}).on("dblclick", function(e) {
|
|
if (e.mainButton) Native.GoOnline();
|
|
});
|
|
|
|
setMenuListener(mainmenu, function() {
|
|
var index = this.attr["index"];
|
|
if (this.parent)
|
|
switch (this.parent.id) {
|
|
case "mstatuses":
|
|
if (this.$(".xstatus"))
|
|
openStatusDialog();
|
|
else
|
|
Native.SetStatus(parseInt(this.attr["code"]));
|
|
return;
|
|
case "mthemes":
|
|
if (index) { Native.SelectTheme(parseInt(index)); return; }
|
|
break;
|
|
case "msmileslist":
|
|
if (index) { Native.SelectSmiles(parseInt(index)); return; }
|
|
break;
|
|
case "memojislist":
|
|
if (index) { Native.SelectEmojis(parseInt(index)); return; }
|
|
break;
|
|
case "msoundslist":
|
|
if (index) { Native.SelectSounds(parseInt(index)); return; }
|
|
break;
|
|
}
|
|
|
|
switch (this.id) {
|
|
case "madduin": openAddContactDialog(); break;
|
|
case "mprotopassword": openLoginDialog(); break;
|
|
case "maccpassword": openAccDialog(); break;
|
|
case "mlock": Native.Lock(); break;
|
|
case "mviewinfoabout": viewInfoAbout(); break;
|
|
case "mopenchat": openChatWith(); break;
|
|
case "msearch": openHistorySearch(); break;
|
|
case "moutbox": openOutbox(); break;
|
|
case "mdb": openContactsDB(); break;
|
|
case "mlog": openLog(); break;
|
|
case "msessionsmgr": openSessionsManager(); break;
|
|
case "mreloadlang": Native.ReloadLang(); break;
|
|
case "mgetcl": Native.GetCL(); break;
|
|
case "mreloadtheme": Native.ReloadTheme(); break;
|
|
case "mthemelist": Native.ReloadThemeList(); break;
|
|
case "mcontactthemes": Native.OpenContactThemes(); break;
|
|
case "musers": CommonNative.ChangeOrAddUser(); break;
|
|
case "mprefs": openPrefs(); break;
|
|
case "mmyinfo": Native.OpenMyInfo(); break;
|
|
case "mmyprofile": Native.OpenMyWebProfile(); break;
|
|
case "mupdate": openUpdater(); break;
|
|
case "mportal": CommonNative.OpenPortal(); break;
|
|
case "mhelp": Native.OpenHelpSite(); break;
|
|
case "mlocalhelp": Native.OpenHelpLocal(); break;
|
|
case "mabout": openAbout(); break;
|
|
case "mtoggle": Native.Toggle(); break;
|
|
case "mexit": tryToQuit(); break;
|
|
}
|
|
});
|
|
|
|
setMenuListener(statusmenu, function() {
|
|
if (this.$(".xstatus"))
|
|
openStatusDialog();
|
|
else if (this.classList.contains("visibility"))
|
|
Native.ToggleVisibility();
|
|
else
|
|
Native.SetStatus(parseInt(this.attr["code"]));
|
|
});
|
|
|
|
setMenuListener(divmenu, function() {
|
|
switch (this.id) {
|
|
case "mnewgroup": Native.AddGroup(); break;
|
|
case "maddcontact": openAddContactDialog(); break;
|
|
case "mexpandall": cl.toggleAll("expand"); break;
|
|
case "mcollapseall": cl.toggleAll("collapse"); break;
|
|
case "mdeleteempty": Native.DeleteEmptyGroups(); break;
|
|
case "mshowgroups": Native.ToggleShowGroups(); break;
|
|
case "mshowemptygroups": Native.ToggleShowEmptyGroups(); break;
|
|
case "monlineonly": Native.ToggleOnlineOnly(); break;
|
|
case "mseparation": Native.ToggleStatusSeparation(); break;
|
|
}
|
|
});
|
|
|
|
setMenuListener(groupmenu, function() {
|
|
var groupid = this.attr["groupid"];
|
|
if (this.parent)
|
|
switch (this.parent.id) {
|
|
case "mgrouplist":
|
|
if (groupid) Native.MoveAllFromCurrentToGroup(parseInt(groupid));
|
|
return;
|
|
}
|
|
|
|
switch (this.id) {
|
|
case "mnewgroup2": Native.AddGroup(); break;
|
|
case "maddcontact2": openAddContactDialog(); break;
|
|
case "maddgrouptoserver": Native.AddGroupToServer(); break;
|
|
case "mremovegroupfromserver": Native.RemoveCurrentGroupFromServer(); break;
|
|
case "mrenamegroup": cl.getCurrent().startEditing(); break;
|
|
case "mdeletegroup": Native.DeleteCurrentGroup(); break;
|
|
case "mshowgroups2": Native.ToggleShowGroups(); break;
|
|
}
|
|
});
|
|
|
|
var suppressUp = false;
|
|
document.on("^mousedown", () => { suppressUp = false; })
|
|
.on("^mouseup", () => { return suppressUp; })
|
|
.on("^dblclick", () => { suppressUp = true; })
|
|
.on("openmenu", (e) => {
|
|
if (e.data == 'main') openMainMenu();
|
|
else if (e.data == 'status') openStatusMenu();
|
|
});
|
|
|
|
view.on("replacementstart", (e) => {
|
|
windowResizing = true;
|
|
}).on("replacementend", (e) => {
|
|
windowResizing = false;
|
|
if (cl) cl.onsizechange();
|
|
}).on("activate", function(e) {
|
|
Native.OnCLActivate();
|
|
var active = e.reason !== 0;
|
|
if (!active) isModKey = false;
|
|
if (document.state.ownspopup || (active && view.focus != null)) return;
|
|
cl.post(() => { this.state.focus = active });
|
|
});
|
|
|
|
// openSingleWindow({
|
|
// url: "debug.htm",
|
|
// caption: "Debug",
|
|
// translate: false,
|
|
// uniqueid: "debug",
|
|
// alignment: 5,
|
|
// width: 450,
|
|
// height: 550
|
|
// });
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<div id="bar">
|
|
<div class="imgbtn" id="menuBtn" title="Menu"><div pic="rnq"></div></div>
|
|
<div class="imgbtn" id="statusBtn" title="Status"><div pic="status.offline"></div></div>
|
|
<div class="imgbtn" id="addBtn"><div pic></div><div class="emoji"></div></div>
|
|
<div id="progress">
|
|
<progress id="pslider" value="0" />
|
|
<div id="pvalue"></div>
|
|
<div id="ccount"></div>
|
|
</div>
|
|
</div>
|
|
<widget id="cl" type="tree"></widget>
|
|
<div id="filter">
|
|
<input id="clfilter" />
|
|
<div class="imgbtn filterbtn" id="clearBtn" title="Clear filter text"><div pic="cancel"></div></div>
|
|
</div>
|
|
<menu id="mainmenu" class="context custom">
|
|
<li><div id="mstatus" pic="status.offline"></div><span>Status</span><menu id="mstatuses"></menu></li>
|
|
<li><div pic="key"></div><span>Security</span><menu>
|
|
<li id="mprotopassword"><div pic="key"></div><span>Login password</span></li>
|
|
<li id="maccpassword"><div pic="key"></div><span>Account password</span></li>
|
|
<li id="mlock"><div pic="key"></div><span>Lock</span></li>
|
|
</menu></li>
|
|
<li><div pic="special"></div><span>Special</span><menu>
|
|
<li id="mviewinfoabout"><div pic="info"></div><span>View info about...</span></li>
|
|
<li id="mopenchat"><div pic="msg"></div><span>Open chat with...</span></li>
|
|
<li id="msearch"><div pic="search"></div><span>History search</span></li>
|
|
<li id="moutbox"><div pic="outbox"></div><span>Outbox</span></li>
|
|
<li id="mdb"><div pic="db"></div><span>Contacts database</span></li>
|
|
<li id="mlog"><div pic="log"></div><span>Show log window</span></li>
|
|
<li id="mreloadlang"><div pic="refresh"></div><span>Reload current language</span></li>
|
|
<hr/>
|
|
<li id="mgetcl"><div pic="request.contact.list"></div><span>Load contact list from server</span></li>
|
|
<li id="msessionsmgr"><div pic="sessions"></div><span>Sessions manager</span></li>
|
|
</menu></li>
|
|
<li><div pic="theme"></div><span>Themes</span><menu id="mthemes">
|
|
<li id="mreloadtheme"><div pic="refresh"></div><span>Reload current theme</span></li>
|
|
<li id="mthemelist"><div pic="reload.theme.list"></div><span>Refresh theme-list</span></li>
|
|
<li id="mcontactthemes" title="Open contacts-theme in notepad"><div pic="theme"></div><span>Open contacts-theme</span></li>
|
|
<li id="msmiles"><div pic="smiles"></div><span>Smiles</span><menu id="msmileslist"></menu></li>
|
|
<li id="memojis"><div pic="emoji"></div><span>Emojis</span><menu id="memojislist"></menu></li>
|
|
<li id="msounds"><div pic="sounds"></div><span>Sounds</span><menu id="msoundslist"></menu></li>
|
|
<hr/>
|
|
</menu></li>
|
|
<li><div pic="support"></div><span>Support</span><menu>
|
|
<li id="mupdate"><div pic="download"></div><span>Check for updates</span></li>
|
|
<hr/>
|
|
<li id="mportal" title="https://rnq.ru"><div></div><span>R&Q Portal</span></li>
|
|
<li id="mhelp" title="https://help.rnq.ru"><div></div><span>R&Q Help</span></li>
|
|
</menu></li>
|
|
<li id="madduin"><div pic="uin"></div><span>Add contact</span></li>
|
|
<li id="musers"><div pic="users"></div><span>Switch user</span></li>
|
|
<li id="mprefs"><div pic="preferences"></div><span>Preferences</span></li>
|
|
<li id="mmyinfo"><div pic="info"></div><span>View my info</span></li>
|
|
<li id="mmyprofile"><div pic="webinfo"></div><span>View my web profile</span></li>
|
|
<hr/>
|
|
<li id="mabout"><div pic="rnq"></div><span>About R&Q</span></li>
|
|
<li id="mlocalhelp"><div pic="help"></div><span>Help</span></li>
|
|
<li id="mtoggle"><div pic="minimize"></div><span>Hide</span></li>
|
|
<li id="mexit"><div pic="quit"></div><span>Quit</span></li>
|
|
</menu>
|
|
<menu id="statusmenu" class="context custom"></menu>
|
|
<menu id="divmenu" class="context custom">
|
|
<li id="mnewgroup"><div pic="new.group"></div><span>New group</span></li>
|
|
<li id="maddcontact"><div pic="add.contact"></div><span>Add contact</span></li>
|
|
<hr/>
|
|
<li id="mexpandall"><div pic="open.group"></div><span>Open all groups</span></li>
|
|
<li id="mcollapseall"><div pic="close.group"></div><span>Close all groups</span></li>
|
|
<li id="mdeleteempty"><div pic="delete"></div><span>Delete all empty groups</span></li>
|
|
<hr/>
|
|
<li id="mshowgroups"><div pic></div><span>Show groups</span></li>
|
|
<li id="mshowemptygroups"><div pic></div><span>Show empty groups</span></li>
|
|
<li id="monlineonly"><div pic></div><span>Show only online contacts</span></li>
|
|
<li id="mseparation"><div pic></div><span>Not separate by online\offline</span></li>
|
|
</menu>
|
|
<menu id="groupmenu" class="context custom">
|
|
<li id="mnewgroup2"><div pic="new.group"></div><span>New group</span></li>
|
|
<li id="maddcontact2"><div pic="add.contact"></div><span>Add contact</span></li>
|
|
<li id="maddgrouptoserver"><div pic="add"></div><span>Add to server</span></li>
|
|
<li id="mremovegroupfromserver"><div pic="add"></div><span>Remove from server (keep local)</span></li>
|
|
<hr/>
|
|
<li id="mrenamegroup"><div pic="rename"></div><span>Rename group</span></li>
|
|
<li id="mdeletegroup"><div pic="delete"></div><span>Delete group</span></li>
|
|
<li id="mmoveallto"><div pic="close.group"></div><span>Move all contacts to</span><menu id="mgrouplist"></menu></li>
|
|
<hr/>
|
|
<li id="mshowgroups2"><div pic></div><span>Show groups</span></li>
|
|
</menu>
|
|
<include src="menus/contactmenu.htm" />
|
|
</body>
|
|
</html> |