mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-11-09 11:50:11 +00:00
implement invites as a class, use tooltip for email send status
the DOMInvite class represents an invite on the dom, and modifying its attributes applies the changes on the web page. Email send status message is now on the right of the invite and represented by an icon. Hovering reveals the "Sent to"/"Failed to send to" message.
This commit is contained in:
parent
95db48d8f8
commit
1b41621569
@ -135,6 +135,10 @@ sup.\~critical, .text-critical {
|
|||||||
color: var(--color-critical-normal-content);
|
color: var(--color-critical-normal-content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.grey {
|
||||||
|
color: var(--color-neutral-500);
|
||||||
|
}
|
||||||
|
|
||||||
.aside.sm {
|
.aside.sm {
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
padding: 0.8rem;
|
padding: 0.8rem;
|
||||||
|
@ -16,9 +16,17 @@
|
|||||||
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
top: -1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip.right .content {
|
||||||
left: 120%;
|
left: 120%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tooltip.left .content {
|
||||||
|
right: 120%;
|
||||||
|
}
|
||||||
|
|
||||||
.tooltip .content.sm {
|
.tooltip .content.sm {
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
}
|
}
|
||||||
|
@ -133,11 +133,13 @@
|
|||||||
<div id="invitesTab">
|
<div id="invitesTab">
|
||||||
<div class="card ~neutral !low invites mb-1">
|
<div class="card ~neutral !low invites mb-1">
|
||||||
<span class="heading">Invites</span>
|
<span class="heading">Invites</span>
|
||||||
|
<div id="invites">
|
||||||
<div class="inv">
|
<div class="inv">
|
||||||
<div class="card ~neutral !normal inv-header flex-expand mt-half">
|
<div class="card ~neutral !normal inv-header flex-expand mt-half">
|
||||||
<div class="inv-codearea">
|
<div class="inv-codearea">
|
||||||
<a href="#" class="code monospace mr-1">ZD8ZeC55Jcpmbtv54FuVM3</a>
|
<a href="#" class="code monospace mr-1">ZD8ZeC55Jcpmbtv54FuVM3</a>
|
||||||
<span class="button ~info !normal">Copy</span>
|
<span class="button ~info !normal" title="Copy invite link"><i class="ri-file-copy-line"></i></span>
|
||||||
|
<span class="support ml-1">Failed to send to this.email@addre.ss</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="inv-infoarea">
|
<div class="inv-infoarea">
|
||||||
<span class="inv-expiry mr-1">Expires in 30m</span>
|
<span class="inv-expiry mr-1">Expires in 30m</span>
|
||||||
@ -241,6 +243,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="card ~neutral !low create-inv">
|
<div class="card ~neutral !low create-inv">
|
||||||
<span class="heading">Create</span>
|
<span class="heading">Create</span>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@ -356,7 +359,7 @@
|
|||||||
<div class="setting">
|
<div class="setting">
|
||||||
<label class="label" for="settings-input">
|
<label class="label" for="settings-input">
|
||||||
Input <span class="badge ~critical">*</span>
|
Input <span class="badge ~critical">*</span>
|
||||||
<div class="tooltip">
|
<div class="tooltip right">
|
||||||
<i class="icon ri-information-line"></i>
|
<i class="icon ri-information-line"></i>
|
||||||
<span class="content sm">An example tooltip.</span>
|
<span class="content sm">An example tooltip.</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { toggleTheme, loadTheme } from "./modules/theme.js";
|
import { toggleTheme, loadTheme } from "./modules/theme.js";
|
||||||
import { Modal } from "./modules/modal.js";
|
import { Modal } from "./modules/modal.js";
|
||||||
import { Tabs } from "./modules/tabs.js";
|
import { Tabs } from "./modules/tabs.js";
|
||||||
|
import { inviteList } from "./modules/invites.js";
|
||||||
|
|
||||||
loadTheme();
|
loadTheme();
|
||||||
(document.getElementById('button-theme') as HTMLSpanElement).onclick = toggleTheme;
|
(document.getElementById('button-theme') as HTMLSpanElement).onclick = toggleTheme;
|
||||||
@ -13,7 +14,7 @@ const whichAnimationEvent = () => {
|
|||||||
return "webkitAnimationEnd";
|
return "webkitAnimationEnd";
|
||||||
}
|
}
|
||||||
window.animationEvent = whichAnimationEvent();
|
window.animationEvent = whichAnimationEvent();
|
||||||
const toggles: HTMLInputElement[] = Array.from(document.getElementsByClassName('toggle-details'));
|
/*const toggles: HTMLInputElement[] = Array.from(document.getElementsByClassName('toggle-details'));
|
||||||
for (let toggle of toggles) {
|
for (let toggle of toggles) {
|
||||||
toggle.onclick = () => {
|
toggle.onclick = () => {
|
||||||
const el = toggle.parentElement.parentElement.parentElement.nextElementSibling as HTMLDivElement;
|
const el = toggle.parentElement.parentElement.parentElement.nextElementSibling as HTMLDivElement;
|
||||||
@ -27,7 +28,7 @@ for (let toggle of toggles) {
|
|||||||
toggle.previousElementSibling.classList.toggle("rotated");
|
toggle.previousElementSibling.classList.toggle("rotated");
|
||||||
toggle.previousElementSibling.classList.toggle("not-rotated");
|
toggle.previousElementSibling.classList.toggle("not-rotated");
|
||||||
};
|
};
|
||||||
}
|
}*/
|
||||||
|
|
||||||
const checkInfUses = function (check: HTMLInputElement, mode = 2) {
|
const checkInfUses = function (check: HTMLInputElement, mode = 2) {
|
||||||
const uses = document.getElementById('inv-uses') as HTMLInputElement;
|
const uses = document.getElementById('inv-uses') as HTMLInputElement;
|
||||||
@ -254,6 +255,8 @@ function login(username: string, password: string) {
|
|||||||
const data = this.response;
|
const data = this.response;
|
||||||
window.token = data["token"];
|
window.token = data["token"];
|
||||||
window.modals.login.close();
|
window.modals.login.close();
|
||||||
|
window.invites.reload();
|
||||||
|
setInterval(window.invites.reload, 30*1000);
|
||||||
/*generateInvites();
|
/*generateInvites();
|
||||||
setInterval((): void => generateInvites(), 60 * 1000);
|
setInterval((): void => generateInvites(), 60 * 1000);
|
||||||
addOptions(30, document.getElementById('days') as HTMLSelectElement);
|
addOptions(30, document.getElementById('days') as HTMLSelectElement);
|
||||||
@ -288,3 +291,5 @@ document.getElementById('form-login').addEventListener('submit', (event: Event)
|
|||||||
});
|
});
|
||||||
|
|
||||||
login("", "");
|
login("", "");
|
||||||
|
|
||||||
|
window.invites = new inviteList();
|
||||||
|
@ -50,13 +50,13 @@ export const rmAttr = (el: HTMLElement, attr: string): void => {
|
|||||||
|
|
||||||
export const addAttr = (el: HTMLElement, attr: string): void => el.classList.add(attr);
|
export const addAttr = (el: HTMLElement, attr: string): void => el.classList.add(attr);
|
||||||
|
|
||||||
export const _get = (url: string, data: Object, onreadystatechange: () => void): void => {
|
export const _get = (url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void): void => {
|
||||||
let req = new XMLHttpRequest();
|
let req = new XMLHttpRequest();
|
||||||
req.open("GET", window.URLBase + url, true);
|
req.open("GET", window.URLBase + url, true);
|
||||||
req.responseType = 'json';
|
req.responseType = 'json';
|
||||||
req.setRequestHeader("Authorization", "Bearer " + window.token);
|
req.setRequestHeader("Authorization", "Bearer " + window.token);
|
||||||
req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
|
req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
|
||||||
req.onreadystatechange = onreadystatechange;
|
req.onreadystatechange = () => { onreadystatechange(req); };
|
||||||
req.send(JSON.stringify(data));
|
req.send(JSON.stringify(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -81,3 +81,19 @@ export function _delete(url: string, data: Object, onreadystatechange: () => voi
|
|||||||
req.send(JSON.stringify(data));
|
req.send(JSON.stringify(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function toClipboard (str: string) {
|
||||||
|
const el = document.createElement('textarea') as HTMLTextAreaElement;
|
||||||
|
el.value = str;
|
||||||
|
el.readOnly = true;
|
||||||
|
el.style.position = "absolute";
|
||||||
|
el.style.left = "-9999px";
|
||||||
|
document.body.appendChild(el);
|
||||||
|
const selected = document.getSelection().rangeCount > 0 ? document.getSelection().getRangeAt(0) : false;
|
||||||
|
el.select();
|
||||||
|
document.execCommand("copy");
|
||||||
|
document.body.removeChild(el);
|
||||||
|
if (selected) {
|
||||||
|
document.getSelection().removeAllRanges();
|
||||||
|
document.getSelection().addRange(selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
436
ts/modules/invites.ts
Normal file
436
ts/modules/invites.ts
Normal file
@ -0,0 +1,436 @@
|
|||||||
|
import { _get, _post, _delete, toClipboard } from "../modules/common.js";
|
||||||
|
|
||||||
|
export class DOMInvite implements Invite {
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
updateNotify = () => {}; // SetNotify
|
||||||
|
delete = () => {}; // deleteInvite
|
||||||
|
|
||||||
|
private _code: string = "None";
|
||||||
|
get code(): string { return this._code; }
|
||||||
|
set code(code: string) {
|
||||||
|
this._code = code;
|
||||||
|
this._codeLink = window.location.href.split("#")[0] + "invite/" + code;
|
||||||
|
const linkEl = this._codeArea.querySelector("a") as HTMLAnchorElement;
|
||||||
|
linkEl.textContent = code.replace(/-/g, '-');
|
||||||
|
linkEl.href = this._codeLink;
|
||||||
|
}
|
||||||
|
private _codeLink: string;
|
||||||
|
|
||||||
|
private _expiresIn: string;
|
||||||
|
get expiresIn(): string { return this._expiresIn }
|
||||||
|
set expiresIn(expiry: string) {
|
||||||
|
this._expiresIn = expiry;
|
||||||
|
this._infoArea.querySelector("span.inv-expiry").textContent = expiry;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _remainingUses: string = "1";
|
||||||
|
get remainingUses(): string { return this._remainingUses; }
|
||||||
|
set remainingUses(remaining: string) {
|
||||||
|
this._remainingUses = remaining;
|
||||||
|
this._middle.querySelector("strong.inv-remaining").textContent = remaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _email: string = "";
|
||||||
|
get email(): string { return this._email };
|
||||||
|
set email(address: string) {
|
||||||
|
this._email = address;
|
||||||
|
const icon = this._infoArea.querySelector(".tooltip i");
|
||||||
|
const chip = this._infoArea.querySelector(".tooltip span.inv-email-chip");
|
||||||
|
const tooltip = this._infoArea.querySelector(".tooltip span.content") as HTMLSpanElement;
|
||||||
|
if (address == "") {
|
||||||
|
icon.classList.remove("ri-mail-line");
|
||||||
|
icon.classList.remove("ri-mail-close-line");
|
||||||
|
chip.classList.remove("~neutral");
|
||||||
|
chip.classList.remove("~critical");
|
||||||
|
chip.classList.remove("chip");
|
||||||
|
} else {
|
||||||
|
chip.classList.add("chip");
|
||||||
|
if (address.includes("Failed to send to")) {
|
||||||
|
icon.classList.remove("ri-mail-line");
|
||||||
|
icon.classList.add("ri-mail-close-line");
|
||||||
|
chip.classList.remove("~neutral");
|
||||||
|
chip.classList.add("~critical");
|
||||||
|
} else {
|
||||||
|
address = "Sent to " + address;
|
||||||
|
icon.classList.remove("ri-mail-close-line");
|
||||||
|
icon.classList.add("ri-mail-line");
|
||||||
|
chip.classList.remove("~critical");
|
||||||
|
chip.classList.add("~neutral");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tooltip.textContent = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _usedBy: string[][];
|
||||||
|
get usedBy(): string[][] { return this._usedBy; }
|
||||||
|
set usedBy(uB: string[][]) {
|
||||||
|
// ub[i][0]: username, ub[i][1]: date
|
||||||
|
this._usedBy = uB;
|
||||||
|
if (uB.length == 0) {
|
||||||
|
this._right.classList.add("empty");
|
||||||
|
this._userTable.innerHTML = `<p class="content">None yet!</p>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._right.classList.remove("empty");
|
||||||
|
let innerHTML = `
|
||||||
|
<table class="table inv-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Date</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
`;
|
||||||
|
for (let user of uB) {
|
||||||
|
innerHTML += `
|
||||||
|
<tr>
|
||||||
|
<td>${user[0]}</td>
|
||||||
|
<td>${user[1]}</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
innerHTML += `
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
`;
|
||||||
|
this._userTable.innerHTML = innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _created: string;
|
||||||
|
get created(): string { return this._created; }
|
||||||
|
set created(created: string) {
|
||||||
|
this._created = created;
|
||||||
|
this._middle.querySelector("strong.inv-created").textContent = created;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _notifyExpiry: boolean = false;
|
||||||
|
get notifyExpiry(): boolean { return this._notifyExpiry }
|
||||||
|
set notifyExpiry(state: boolean) {
|
||||||
|
this._notifyExpiry = state;
|
||||||
|
(this._left.querySelector("input.inv-notify-expiry") as HTMLInputElement).checked = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _notifyCreation: boolean = false;
|
||||||
|
get notifyCreation(): boolean { return this._notifyCreation }
|
||||||
|
set notifyCreation(state: boolean) {
|
||||||
|
this._notifyCreation = state;
|
||||||
|
(this._left.querySelector("input.inv-notify-creation") as HTMLInputElement).checked = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _profile: string;
|
||||||
|
get profile(): string { return this._profile; }
|
||||||
|
set profile(profile: string) { this.loadProfiles(profile); }
|
||||||
|
loadProfiles = (selected?: string) => {
|
||||||
|
const select = this._left.querySelector("select") as HTMLSelectElement;
|
||||||
|
let noProfile = false;
|
||||||
|
if (selected === "") {
|
||||||
|
noProfile = true;
|
||||||
|
} else {
|
||||||
|
selected = selected || select.value;
|
||||||
|
}
|
||||||
|
let innerHTML = `<option value="noProfile" ${noProfile ? "selected" : ""}>No Profile</option>`;
|
||||||
|
for (let profile of window.availableProfiles) {
|
||||||
|
innerHTML += `<option value="${profile}" ${((profile == selected) && !noProfile) ? "selected" : ""}>${profile}</option>`;
|
||||||
|
}
|
||||||
|
select.innerHTML = innerHTML;
|
||||||
|
};
|
||||||
|
|
||||||
|
private _container: HTMLDivElement;
|
||||||
|
|
||||||
|
private _header: HTMLDivElement;
|
||||||
|
private _codeArea: HTMLDivElement;
|
||||||
|
private _infoArea: HTMLDivElement;
|
||||||
|
|
||||||
|
private _details: HTMLDivElement;
|
||||||
|
private _left: HTMLDivElement;
|
||||||
|
private _middle: HTMLDivElement;
|
||||||
|
private _right: HTMLDivElement;
|
||||||
|
private _userTable: HTMLDivElement;
|
||||||
|
|
||||||
|
// whether the details card is expanded.
|
||||||
|
get expanded(): boolean {
|
||||||
|
return this._details.classList.contains("focused");
|
||||||
|
}
|
||||||
|
set expanded(state: boolean) {
|
||||||
|
const toggle = (this._infoArea.querySelector("input.inv-toggle-details") as HTMLInputElement);
|
||||||
|
if (state) {
|
||||||
|
this._details.classList.remove("unfocused");
|
||||||
|
this._details.classList.add("focused");
|
||||||
|
toggle.previousElementSibling.classList.add("rotated");
|
||||||
|
toggle.previousElementSibling.classList.remove("not-rotated");
|
||||||
|
} else {
|
||||||
|
this._details.classList.add("unfocused");
|
||||||
|
this._details.classList.remove("focused");
|
||||||
|
toggle.previousElementSibling.classList.remove("rotated");
|
||||||
|
toggle.previousElementSibling.classList.add("not-rotated");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(invite: Invite) {
|
||||||
|
// first create the invite structure, then use our setter methods to fill in the data.
|
||||||
|
this._container = document.createElement('div') as HTMLDivElement;
|
||||||
|
this._container.classList.add("inv");
|
||||||
|
|
||||||
|
this._header = document.createElement('div') as HTMLDivElement;
|
||||||
|
this._container.appendChild(this._header);
|
||||||
|
this._header.classList.add("card", "~neutral", "!normal", "inv-header", "flex-expand", "mt-half");
|
||||||
|
|
||||||
|
this._codeArea = document.createElement('div') as HTMLDivElement;
|
||||||
|
this._header.appendChild(this._codeArea);
|
||||||
|
this._codeArea.classList.add("inv-codearea");
|
||||||
|
this._codeArea.innerHTML = `
|
||||||
|
<a class="code monospace mr-1" href=""></a>
|
||||||
|
<span class="button ~info !normal" title="Copy invite link"><i class="ri-file-copy-line"></i></span>
|
||||||
|
`;
|
||||||
|
(this._codeArea.querySelector("span.button") as HTMLSpanElement).onclick = () => { toClipboard(this._codeLink); };
|
||||||
|
|
||||||
|
this._infoArea = document.createElement('div') as HTMLDivElement;
|
||||||
|
this._header.appendChild(this._infoArea);
|
||||||
|
this._infoArea.classList.add("inv-infoarea");
|
||||||
|
this._infoArea.innerHTML = `
|
||||||
|
<div class="tooltip left mr-1">
|
||||||
|
<span class="inv-email-chip"><i></i></span>
|
||||||
|
<span class="content sm"></span>
|
||||||
|
</div>
|
||||||
|
<span class="inv-expiry mr-1"></span>
|
||||||
|
<span class="button ~critical !normal inv-delete">Delete</span>
|
||||||
|
<label>
|
||||||
|
<i class="icon ri-arrow-down-s-line not-rotated"></i>
|
||||||
|
<input class="inv-toggle-details unfocused" type="checkbox">
|
||||||
|
</label>
|
||||||
|
`;
|
||||||
|
|
||||||
|
(this._infoArea.querySelector(".inv-delete") as HTMLSpanElement).onclick = this.delete;
|
||||||
|
|
||||||
|
const toggle = (this._infoArea.querySelector("input.inv-toggle-details") as HTMLInputElement);
|
||||||
|
toggle.onchange = () => { this.expanded = !this.expanded; };
|
||||||
|
|
||||||
|
this._details = document.createElement('div') as HTMLDivElement;
|
||||||
|
this._container.appendChild(this._details);
|
||||||
|
this._details.classList.add("card", "~neutral", "!normal", "mt-half", "inv-details");
|
||||||
|
const detailsInner = document.createElement('div') as HTMLDivElement;
|
||||||
|
this._details.appendChild(detailsInner);
|
||||||
|
detailsInner.classList.add("inv-row", "flex-expand", "align-top");
|
||||||
|
|
||||||
|
this._left = document.createElement('div') as HTMLDivElement;
|
||||||
|
detailsInner.appendChild(this._left);
|
||||||
|
this._left.classList.add("inv-profilearea");
|
||||||
|
this._left.innerHTML = `
|
||||||
|
<p class="supra mb-1 top">Profile</p>
|
||||||
|
<div class="select ~neutral !normal inv-profileselect inline-block">
|
||||||
|
<select>
|
||||||
|
<option value="noProfile" selected>No Profile</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<p class="label supra">Notify on:</p>
|
||||||
|
<label class="switch block">
|
||||||
|
<input class="inv-notify-expiry" type="checkbox">
|
||||||
|
<span>On expiry</span>
|
||||||
|
</label>
|
||||||
|
<label class="switch block">
|
||||||
|
<input class="inv-notify-creation" type="checkbox">
|
||||||
|
<span>On user creation</span>
|
||||||
|
</label>
|
||||||
|
`;
|
||||||
|
|
||||||
|
this._middle = document.createElement('div') as HTMLDivElement;
|
||||||
|
detailsInner.appendChild(this._middle);
|
||||||
|
this._middle.classList.add("block");
|
||||||
|
this._middle.innerHTML = `
|
||||||
|
<p class="supra mb-1 top">Created <strong class="inv-created"></strong></p>
|
||||||
|
<p class="supra mb-1">Remaining uses <strong class="inv-remaining"></strong></p>
|
||||||
|
`;
|
||||||
|
|
||||||
|
this._right = document.createElement('div') as HTMLDivElement;
|
||||||
|
detailsInner.appendChild(this._right);
|
||||||
|
this._right.classList.add("card", "~neutral", "!low", "inv-created-users");
|
||||||
|
this._right.innerHTML = `<strong class="supra table-header">Created users</strong>`;
|
||||||
|
this._userTable = document.createElement('div') as HTMLDivElement;
|
||||||
|
this._right.appendChild(this._userTable);
|
||||||
|
|
||||||
|
|
||||||
|
this.expanded = false;
|
||||||
|
this.update(invite);
|
||||||
|
}
|
||||||
|
|
||||||
|
update = (invite: Invite) => {
|
||||||
|
this.code = invite.code;
|
||||||
|
this.created = invite.created;
|
||||||
|
this.email = invite.email;
|
||||||
|
this.expiresIn = invite.expiresIn;
|
||||||
|
this.notifyCreation = invite.notifyCreation;
|
||||||
|
this.notifyExpiry = invite.notifyExpiry;
|
||||||
|
this.profile = invite.profile;
|
||||||
|
this.remainingUses = invite.remainingUses;
|
||||||
|
this.usedBy = invite.usedBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
asElement = (): HTMLDivElement => { return this._container; }
|
||||||
|
|
||||||
|
remove = () => { this._container.remove(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// implement inviteList as a class
|
||||||
|
// inviteList has empty boolean value, set true adds an emptyInvite
|
||||||
|
|
||||||
|
export class inviteList implements inviteList {
|
||||||
|
private _list: HTMLDivElement;
|
||||||
|
private _empty: boolean;
|
||||||
|
invites: { [code: string]: DOMInvite };
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this._list = document.getElementById('invites') as HTMLDivElement;
|
||||||
|
this.empty = true;
|
||||||
|
this.invites = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
get empty(): boolean { return this._empty; }
|
||||||
|
set empty(state: boolean) {
|
||||||
|
this._empty = state;
|
||||||
|
if (state) {
|
||||||
|
this.invites = {};
|
||||||
|
this._list.classList.add("empty");
|
||||||
|
this._list.innerHTML = `
|
||||||
|
<div class="inv inv-empty">
|
||||||
|
<div class="card ~neutral !normal inv-header flex-expand mt-half">
|
||||||
|
<div class="inv-codearea">
|
||||||
|
<span class="code monospace">None</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
this._list.classList.remove("empty");
|
||||||
|
if (this._list.querySelector(".inv-empty")) {
|
||||||
|
this._list.textContent = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
add = (invite: Invite) => {
|
||||||
|
let domInv = new DOMInvite(invite);
|
||||||
|
this.invites[invite.code] = domInv;
|
||||||
|
if (this.empty) { this.empty = false; }
|
||||||
|
this._list.appendChild(domInv.asElement());
|
||||||
|
}
|
||||||
|
|
||||||
|
reload = () => { _get("/invites", null, (req: XMLHttpRequest) => {
|
||||||
|
if (req.readyState == 4) {
|
||||||
|
let data = req.response;
|
||||||
|
window.availableProfiles = data["profiles"];
|
||||||
|
for (let code in this.invites) {
|
||||||
|
this.invites[code].loadProfiles();
|
||||||
|
}
|
||||||
|
if (data["invites"] === undefined || data["invites"].length == 0) {
|
||||||
|
this.empty = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// get a list of all current inv codes on dom
|
||||||
|
// every time we find a match in resp, delete from list
|
||||||
|
// at end delete all remaining in list from dom
|
||||||
|
let invitesOnDOM: { [code: string]: boolean } = {};
|
||||||
|
for (let code in this.invites) { invitesOnDOM[code] = true; }
|
||||||
|
for (let inv of (data["invites"] as Array<any>)) {
|
||||||
|
const invite = parseInvite(inv);
|
||||||
|
if (invite.code in this.invites) {
|
||||||
|
this.invites[invite.code].update(invite);
|
||||||
|
delete invitesOnDOM[invite.code];
|
||||||
|
} else {
|
||||||
|
this.add(invite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let code in invitesOnDOM) {
|
||||||
|
this.invites[code].remove();
|
||||||
|
delete this.invites[code];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function parseInvite(invite: { [f: string]: string | number | string[][] | boolean }): Invite {
|
||||||
|
let parsed: Invite = {};
|
||||||
|
parsed.code = invite["code"] as string;
|
||||||
|
parsed.email = invite["email"] as string || "";
|
||||||
|
let time = "";
|
||||||
|
const fields = ["days", "hours", "minutes"];
|
||||||
|
for (let i = 0; i < fields.length; i++) {
|
||||||
|
if (invite[fields[i]] != 0) {
|
||||||
|
time += `${invite[fields[i]]}${fields[i][0]} `;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parsed.expiresIn = `Expires in ${time.slice(0, -1)}`;
|
||||||
|
parsed.remainingUses = invite["no-limit"] ? "∞" : String(invite["remaining-uses"])
|
||||||
|
parsed.usedBy = invite["used-by"] as string[][] || [];
|
||||||
|
parsed.created = invite["created"] as string || "Unknown";
|
||||||
|
parsed.profile = invite["profile"] as string || "";
|
||||||
|
parsed.notifyExpiry = invite["notify-expiry"] as boolean || false;
|
||||||
|
parsed.notifyCreation = invite["notify-creation"] as boolean || false;
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
function addInvite(invite: Invite): void {
|
||||||
|
const list = document.getElementById('invites') as HTMLDivElement;
|
||||||
|
const container = document.createElement('div') as HTMLDivElement;
|
||||||
|
container.classList.add("inv");
|
||||||
|
container.id = "invite-" + invite.code;
|
||||||
|
// invite header
|
||||||
|
const header = document.createElement("div") as HTMLDivElement;
|
||||||
|
(() => {
|
||||||
|
header.classList.add("card", "~neutral", "!normal", "inv-header", "flex-expand", "mt-half");
|
||||||
|
// code area (code, copy button, "sent to" message)
|
||||||
|
const codeArea = document.createElement('div') as HTMLDivElement;
|
||||||
|
(() => {
|
||||||
|
codeArea.classList.add('inv-codearea');
|
||||||
|
if (invite.empty) {
|
||||||
|
codeArea.innerHTML = `
|
||||||
|
<a class="code monospace">None</a>
|
||||||
|
`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const link = window.location.href.split("#")[0] + "invite/" + invite.code;
|
||||||
|
let innerHTML = `
|
||||||
|
<a class="code monospace mr-1" href="${link}">${invite.code.replace(/-/g, '-')}</a>
|
||||||
|
<span class="button ~info !normal" title="Copy invite link"><i class="ri-file-copy-line"></i></span>
|
||||||
|
`;
|
||||||
|
if (invite.email) {
|
||||||
|
let email = invite.email;
|
||||||
|
if (!invite.email.includes("Failed to send to")) {
|
||||||
|
email = "Sent to " + email;
|
||||||
|
}
|
||||||
|
innerHTML += `
|
||||||
|
<span class="support ml-1">${email}</span>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
codeArea.innerHTML = innerHTML;
|
||||||
|
})();
|
||||||
|
header.appendChild(codeArea);
|
||||||
|
|
||||||
|
// info area (expiry, delete, dropdown button)
|
||||||
|
const infoArea = document.createElement('div') as HTMLDivElement;
|
||||||
|
(() => {
|
||||||
|
infoArea.classList.add("inv-infoarea");
|
||||||
|
infoArea.innerHTML = `
|
||||||
|
<span class="inv-expiry mr-1">${invite.expiresIn}</span>
|
||||||
|
<span class="button ~critical !normal inv-delete">Delete</span>
|
||||||
|
<label>
|
||||||
|
<i class="icon ri-arrow-down-s-line not-rotated"></i>
|
||||||
|
<input class="toggle-details
|
||||||
|
`; /// LINE 70
|
||||||
|
})();
|
||||||
|
|
||||||
|
})();
|
||||||
|
if (invite.empty) {
|
||||||
|
container.appendChild(header);
|
||||||
|
list.appendChild(container);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
@ -21,7 +21,8 @@ declare interface Window {
|
|||||||
buttonWidth: number;
|
buttonWidth: number;
|
||||||
transitionEvent: string;
|
transitionEvent: string;
|
||||||
animationEvent: string;
|
animationEvent: string;
|
||||||
tabs: Tabs
|
tabs: Tabs;
|
||||||
|
invites: inviteList;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare interface Tabs {
|
declare interface Tabs {
|
||||||
@ -54,15 +55,21 @@ declare interface Modals {
|
|||||||
interface Invite {
|
interface Invite {
|
||||||
code?: string;
|
code?: string;
|
||||||
expiresIn?: string;
|
expiresIn?: string;
|
||||||
empty: boolean;
|
|
||||||
remainingUses?: string;
|
remainingUses?: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
usedBy?: Array<Array<string>>;
|
usedBy?: string[][];
|
||||||
created?: string;
|
created?: string;
|
||||||
notifyExpiry?: boolean;
|
notifyExpiry?: boolean;
|
||||||
notifyCreation?: boolean;
|
notifyCreation?: boolean;
|
||||||
profile?: string;
|
profile?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface inviteList {
|
||||||
|
empty: boolean;
|
||||||
|
invites: { [code: string]: Invite }
|
||||||
|
add: (invite: Invite) => void;
|
||||||
|
reload: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
declare var config: Object;
|
declare var config: Object;
|
||||||
declare var modifiedConfig: Object;
|
declare var modifiedConfig: Object;
|
||||||
|
Loading…
Reference in New Issue
Block a user